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    pub fn valid(&self) -> bool {
110        match self {
111            Params::ChaigneAskenfelt(p) => p.valid(),
112            Params::WillemsenBilbaoSerafin(p) => p.valid(),
113            Params::DarabunditScavone(p) => p.valid(),
114            Params::RhaoutiChaigneJoly(p) => p.valid(),
115            Params::ChaigneDoutaut(p) => p.valid(),
116            Params::Botteldooren(p) => p.valid(),
117        }
118    }
119}
120
121pub fn varying(model: &str) -> &'static [(&'static str, Varies)] {
122    match model {
123        "chaigne_askenfelt" => chaigne_askenfelt::VARYING,
124        "willemsen_bilbao_serafin" => willemsen_bilbao_serafin::VARYING,
125        _ => &[],
126    }
127}
128
129/// One grid, sized at the observation's rate; nothing here reads a sample back. A size only
130/// the rate decides is checked here, where the rate is known — `valid()` never sees one.
131pub fn site(p: &Params, sr: f64) -> Result<Box<dyn Solver>, SampleError> {
132    Ok(match p {
133        Params::ChaigneAskenfelt(p) => Box::new(ChaigneAskenfeltSite::new(p, sr)?),
134        Params::WillemsenBilbaoSerafin(p) => Box::new(WillemsenBilbaoSerafinSite::new(p, sr)?),
135        Params::DarabunditScavone(p) => Box::new(BoreSite::new(p, sr)),
136        Params::RhaoutiChaigneJoly(p) => {
137            under_ceiling(
138                "rhaouti_chaigne_joly",
139                rhaouti_chaigne_joly::node_count(p, sr),
140                rhaouti_chaigne_joly::NODE_COUNT_CEILING,
141            )?;
142            Box::new(RhaoutiChaigneJolySite::new(p, sr))
143        }
144        Params::ChaigneDoutaut(p) => Box::new(ChaigneDoutautSite::new(p, sr)),
145        Params::Botteldooren(p) => {
146            under_ceiling(
147                "botteldooren",
148                botteldooren::node_count(p.f0, p.aspect_y, p.aspect_z, sr),
149                botteldooren::NODE_COUNT_CEILING,
150            )?;
151            Box::new(BotteldoorenSite::new(p, sr))
152        }
153    })
154}
155
156/// A slice's own heap share.
157pub(crate) fn floats(held: &[f64]) -> usize {
158    std::mem::size_of_val(held)
159}
160
161fn under_ceiling(model: &'static str, nodes: f64, ceiling: usize) -> Result<(), SampleError> {
162    match nodes > ceiling as f64 {
163        true => Err(SampleError::GridTooLarge {
164            model,
165            nodes: nodes as usize,
166            ceiling,
167        }),
168        false => Ok(()),
169    }
170}
171
172/// The raised-cosine pressure pulse the room and the bore are driven by: one half-turn over
173/// `width` seconds and silence after, so the drive starts and ends at rest.
174pub(crate) fn raised_cosine_pulse(t: f64, amp: f64, width: f64) -> f64 {
175    match t < width {
176        true => amp * 0.5 * (1.0 - (std::f64::consts::TAU * t / width).cos()),
177        false => 0.0,
178    }
179}