Skip to main content

sva_samples/physics/
mod.rs

1// Concern: names the six finite-difference models, opens a solver, holds the ceiling and the drive two share | Non-concern: a model's own grid (the siblings) | IO: (Params) -> a Solver
2
3pub(crate) mod arrow;
4mod ball;
5pub mod botteldooren;
6pub mod bound;
7pub mod chaigne_askenfelt;
8pub mod chaigne_doutaut;
9pub mod darabundit_scavone;
10pub mod hammer;
11pub mod rhaouti_chaigne_joly;
12pub(crate) mod stiff_string;
13pub(crate) mod string_energy;
14pub mod tonehole;
15#[cfg(test)]
16mod twofold;
17mod unison_energy;
18pub mod willemsen_bilbao_serafin;
19
20use std::any::Any;
21
22use crate::error::SampleError;
23
24use botteldooren::{BotteldoorenParams, BotteldoorenSite};
25use chaigne_askenfelt::{ChaigneAskenfeltParams, ChaigneAskenfeltSite};
26use chaigne_doutaut::{ChaigneDoutautParams, ChaigneDoutautSite};
27use darabundit_scavone::{BoreParams, BoreSite};
28use rhaouti_chaigne_joly::{RhaoutiChaigneJolyParams, RhaoutiChaigneJolySite};
29use willemsen_bilbao_serafin::{WillemsenBilbaoSerafinParams, WillemsenBilbaoSerafinSite};
30
31/// One sample per call, `args` in [`Params::varying`] order; none steps on the site's own.
32pub trait Solver: Held + Send + Sync {
33    fn step(&mut self, args: &[f64]) -> Result<f64, SampleError>;
34
35    /// What one copy of this site holds, its grids and parameters alike.
36    fn bytes(&self) -> usize;
37}
38
39pub trait Held {
40    fn boxed(&self) -> Box<dyn Solver>;
41    fn as_any(&self) -> &dyn Any;
42}
43
44impl<T: Solver + Clone + 'static> Held for T {
45    fn boxed(&self) -> Box<dyn Solver> {
46        Box::new(self.clone())
47    }
48
49    fn as_any(&self) -> &dyn Any {
50        self
51    }
52}
53
54/// How a parameter may move, by where it enters the energy.
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub enum Varies {
57    PerSample,
58    /// Stored energy: constant between jumps.
59    Piecewise,
60}
61
62pub const MAX_VARYING: usize = 2;
63
64#[derive(Clone, Debug, PartialEq)]
65pub enum Params {
66    ChaigneAskenfelt(ChaigneAskenfeltParams),
67    WillemsenBilbaoSerafin(WillemsenBilbaoSerafinParams),
68    DarabunditScavone(BoreParams),
69    RhaoutiChaigneJoly(RhaoutiChaigneJolyParams),
70    ChaigneDoutaut(ChaigneDoutautParams),
71    Botteldooren(BotteldoorenParams),
72}
73
74impl Params {
75    pub fn name(&self) -> &'static str {
76        match self {
77            Params::ChaigneAskenfelt(_) => "chaigne_askenfelt",
78            Params::WillemsenBilbaoSerafin(_) => "willemsen_bilbao_serafin",
79            Params::DarabunditScavone(_) => "darabundit_scavone",
80            Params::RhaoutiChaigneJoly(_) => "rhaouti_chaigne_joly",
81            Params::ChaigneDoutaut(_) => "chaigne_doutaut",
82            Params::Botteldooren(_) => "botteldooren",
83        }
84    }
85
86    /// Read every sample, in order.
87    pub fn varying(&self) -> &'static [(&'static str, Varies)] {
88        varying(self.name())
89    }
90
91    pub fn structural(&self) -> Params {
92        match self {
93            Params::ChaigneAskenfelt(p) => Params::ChaigneAskenfelt(ChaigneAskenfeltParams {
94                damper_r: 0.0,
95                damper_k: 0.0,
96                ..p.clone()
97            }),
98            Params::WillemsenBilbaoSerafin(p) => {
99                Params::WillemsenBilbaoSerafin(WillemsenBilbaoSerafinParams {
100                    bow_vel: 0.0,
101                    bow_force: 1.0,
102                    ..p.clone()
103                })
104            }
105            other => other.clone(),
106        }
107    }
108
109    /// The model and every field's bits in declaration order: what a solver is, for whoever
110    /// content-addresses one. Each struct is taken apart whole, so a new field cannot be left
111    /// out.
112    pub fn words(&self) -> Vec<u64> {
113        let model = match self {
114            Params::ChaigneAskenfelt(_) => 0,
115            Params::WillemsenBilbaoSerafin(_) => 1,
116            Params::DarabunditScavone(_) => 2,
117            Params::RhaoutiChaigneJoly(_) => 3,
118            Params::ChaigneDoutaut(_) => 4,
119            Params::Botteldooren(_) => 5,
120        };
121        let mut out = vec![model];
122        let mut put = |vs: &[f64]| out.extend(vs.iter().map(|v| v.to_bits()));
123        match self {
124            Params::ChaigneAskenfelt(ChaigneAskenfeltParams {
125                f0,
126                b,
127                strike_pos,
128                vel,
129                hammer_mass,
130                hammer_k,
131                hammer_p,
132                damp_dc,
133                damp_freq,
134                unison_count,
135                detune,
136                bridge_coupling,
137                bridge_mass,
138                string_cents,
139                string_hammer_k_ratio,
140                damper_pos,
141                damper_r,
142                damper_k,
143            }) => {
144                put(&[
145                    *f0,
146                    *b,
147                    *strike_pos,
148                    *vel,
149                    *hammer_mass,
150                    *hammer_k,
151                    *hammer_p,
152                ]);
153                put(&[
154                    *damp_dc,
155                    *damp_freq,
156                    *unison_count,
157                    *detune,
158                    *bridge_coupling,
159                ]);
160                put(&[*bridge_mass]);
161                put(string_cents);
162                put(string_hammer_k_ratio);
163                put(&[*damper_pos, *damper_r, *damper_k]);
164            }
165            Params::WillemsenBilbaoSerafin(WillemsenBilbaoSerafinParams {
166                f0,
167                b,
168                bow_pos,
169                bow_vel,
170                bow_force,
171                mu_s,
172                mu_c,
173                stribeck_vel,
174                bristle_stiffness,
175                bristle_damping,
176                viscous_friction,
177                damp_dc,
178                damp_freq,
179            }) => {
180                put(&[*f0, *b, *bow_pos, *bow_vel, *bow_force, *mu_s, *mu_c]);
181                put(&[*stribeck_vel, *bristle_stiffness, *bristle_damping]);
182                put(&[*viscous_friction, *damp_dc, *damp_freq]);
183            }
184            Params::DarabunditScavone(BoreParams {
185                length,
186                radius_in,
187                radius_out,
188                excite_pos,
189                pulse_amp,
190                pulse_width,
191                damp_dc,
192                damp_freq,
193                holes,
194            }) => {
195                put(&[*length, *radius_in, *radius_out, *excite_pos, *pulse_amp]);
196                put(&[*pulse_width, *damp_dc, *damp_freq]);
197                for hole in holes {
198                    match hole {
199                        None => put(&[0.0]),
200                        Some(darabundit_scavone::ToneholeSpec {
201                            pos,
202                            open,
203                            radius,
204                            height,
205                        }) => put(&[1.0, *pos, f64::from(u8::from(*open)), *radius, *height]),
206                    }
207                }
208            }
209            Params::RhaoutiChaigneJoly(RhaoutiChaigneJolyParams {
210                f0,
211                aspect_ratio,
212                strike_x,
213                strike_y,
214                vel,
215                hammer_mass,
216                hammer_k,
217                hammer_p,
218                damp_dc,
219                damp_freq,
220            }) => {
221                put(&[*f0, *aspect_ratio, *strike_x, *strike_y, *vel, *hammer_mass]);
222                put(&[*hammer_k, *hammer_p, *damp_dc, *damp_freq]);
223            }
224            Params::ChaigneDoutaut(ChaigneDoutautParams {
225                f0,
226                strike_pos,
227                vel,
228                hammer_mass,
229                hammer_k,
230                hammer_p,
231                damp_dc,
232                damp_freq,
233            }) => {
234                put(&[*f0, *strike_pos, *vel, *hammer_mass, *hammer_k, *hammer_p]);
235                put(&[*damp_dc, *damp_freq]);
236            }
237            Params::Botteldooren(BotteldoorenParams {
238                f0,
239                aspect_y,
240                aspect_z,
241                listener_x,
242                listener_y,
243                listener_z,
244                pulse_amp,
245                pulse_width,
246                damp_dc,
247                damp_freq,
248            }) => {
249                put(&[
250                    *f0,
251                    *aspect_y,
252                    *aspect_z,
253                    *listener_x,
254                    *listener_y,
255                    *listener_z,
256                ]);
257                put(&[*pulse_amp, *pulse_width, *damp_dc, *damp_freq]);
258            }
259        }
260        out
261    }
262
263    pub fn valid(&self) -> bool {
264        match self {
265            Params::ChaigneAskenfelt(p) => p.valid(),
266            Params::WillemsenBilbaoSerafin(p) => p.valid(),
267            Params::DarabunditScavone(p) => p.valid(),
268            Params::RhaoutiChaigneJoly(p) => p.valid(),
269            Params::ChaigneDoutaut(p) => p.valid(),
270            Params::Botteldooren(p) => p.valid(),
271        }
272    }
273}
274
275pub fn varying(model: &str) -> &'static [(&'static str, Varies)] {
276    match model {
277        "chaigne_askenfelt" => chaigne_askenfelt::VARYING,
278        "willemsen_bilbao_serafin" => willemsen_bilbao_serafin::VARYING,
279        _ => &[],
280    }
281}
282
283/// One grid, sized at the observation's rate; nothing here reads a sample back. A size only
284/// the rate decides is checked here, where the rate is known — `valid()` never sees one.
285pub fn site(p: &Params, sr: f64) -> Result<Box<dyn Solver>, SampleError> {
286    Ok(match p {
287        Params::ChaigneAskenfelt(p) => Box::new(ChaigneAskenfeltSite::new(p, sr)?),
288        Params::WillemsenBilbaoSerafin(p) => Box::new(WillemsenBilbaoSerafinSite::new(p, sr)?),
289        Params::DarabunditScavone(p) => Box::new(BoreSite::new(p, sr)),
290        Params::RhaoutiChaigneJoly(p) => {
291            under_ceiling(
292                "rhaouti_chaigne_joly",
293                rhaouti_chaigne_joly::node_count(p, sr),
294                rhaouti_chaigne_joly::NODE_COUNT_CEILING,
295            )?;
296            Box::new(RhaoutiChaigneJolySite::new(p, sr))
297        }
298        Params::ChaigneDoutaut(p) => Box::new(ChaigneDoutautSite::new(p, sr)),
299        Params::Botteldooren(p) => {
300            under_ceiling(
301                "botteldooren",
302                botteldooren::node_count(p.f0, p.aspect_y, p.aspect_z, sr),
303                botteldooren::NODE_COUNT_CEILING,
304            )?;
305            Box::new(BotteldoorenSite::new(p, sr))
306        }
307    })
308}
309
310/// A slice's own heap share.
311pub(crate) fn floats(held: &[f64]) -> usize {
312    std::mem::size_of_val(held)
313}
314
315fn under_ceiling(model: &'static str, nodes: f64, ceiling: usize) -> Result<(), SampleError> {
316    match nodes > ceiling as f64 {
317        true => Err(SampleError::GridTooLarge {
318            model,
319            nodes: nodes as usize,
320            ceiling,
321        }),
322        false => Ok(()),
323    }
324}
325
326/// The raised-cosine pressure pulse the room and the bore are driven by: one half-turn over
327/// `width` seconds and silence after, so the drive starts and ends at rest.
328pub(crate) fn raised_cosine_pulse(t: f64, amp: f64, width: f64) -> f64 {
329    match t < width {
330        true => amp * 0.5 * (1.0 - (std::f64::consts::TAU * t / width).cos()),
331        false => 0.0,
332    }
333}