"""The strike and the listener.""" from __future__ import annotations import math from dataclasses import dataclass @dataclass(frozen=True) class Strike: """A drumstick strike: a free mass arriving at a point of the plate with a Hertz contact. The default contact stiffness is softer than the Hertz value for a hard wooden tip on metal (of order 1e8 to 1e9); it stands for the compliance of a real tip and gives a rounder attack. """ x: float = 0.9 """Strike point as a fraction of the bounding-box width from the left edge.""" y: float = 0.5 """Strike point as a fraction of the bounding-box height from the bottom edge.""" velocity: float = 3.0 """Stick tip speed at impact [m/s].""" mass: float = 0.03 """Effective mass of the stick [kg].""" stiffness: float = 1e7 """Hertz contact stiffness K [N/m^alpha].""" exponent: float = 1.5 """Hertz exponent alpha (1.5 for a rounded tip).""" radius: float = 5e-3 """Radius of the contact patch [m].""" def __post_init__(self) -> None: if not (0 <= self.x <= 1 and 0 <= self.y <= 1): raise ValueError("the strike point (x, y) is given as fractions of the bounding box in [0, 1]") if not (self.velocity > 0 and self.mass > 0 and self.stiffness > 0 and self.exponent >= 1 and self.radius >= 0): raise ValueError("velocity, mass and stiffness must be positive, exponent at least 1, radius non-negative") @dataclass(frozen=True) class Listener: """The listener (a microphone) in spherical coordinates about the center of the plate.""" distance: float = 0.5 """Distance from the center of the plate [m].""" angle: float = 30.0 """Angle from the plate's normal [deg]; 0 is straight above the plate.""" azimuth: float = 0.0 """Rotation about the normal [deg]; 0 is toward +x.""" def __post_init__(self) -> None: if not self.distance > 0: raise ValueError("distance must be positive") @property def position(self) -> tuple[float, float, float]: """Cartesian position relative to the center of the plate, z along the normal [m].""" th = math.radians(self.angle) ph = math.radians(self.azimuth) return ( self.distance * math.sin(th) * math.cos(ph), self.distance * math.sin(th) * math.sin(ph), self.distance * math.cos(th), )