Skip to main content

sva_samples/physics/
mod.rs

1// Concern: names the six finite-difference models, opens a solver or its tail bound, 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;
9mod chaigne_tail;
10pub mod darabundit_scavone;
11pub mod hammer;
12pub mod rhaouti_chaigne_joly;
13pub(crate) mod stiff_string;
14pub(crate) mod string_tail;
15pub mod tonehole;
16#[cfg(test)]
17mod twofold;
18mod unison_tail;
19pub mod willemsen_bilbao_serafin;
20
21use std::any::Any;
22
23use crate::error::SampleError;
24
25use botteldooren::{BotteldoorenParams, BotteldoorenSite};
26use chaigne_askenfelt::{ChaigneAskenfeltParams, ChaigneAskenfeltSite};
27use chaigne_doutaut::{ChaigneDoutautParams, ChaigneDoutautSite};
28use darabundit_scavone::{BoreParams, BoreSite};
29use rhaouti_chaigne_joly::{RhaoutiChaigneJolyParams, RhaoutiChaigneJolySite};
30use willemsen_bilbao_serafin::{WillemsenBilbaoSerafinParams, WillemsenBilbaoSerafinSite};
31
32/// One sample per call. The derivative half of the old pair died with the lanes.
33pub trait Solver: Held {
34    fn step(&mut self) -> Result<f64, SampleError>;
35
36    /// `held`'s motion under this site's own parameters, where the two share one.
37    fn take_motion(&mut self, _held: &dyn Solver) -> bool {
38        false
39    }
40}
41
42pub trait Held {
43    fn boxed(&self) -> Box<dyn Solver>;
44    fn as_any(&self) -> &dyn Any;
45}
46
47impl<T: Solver + Clone + 'static> Held for T {
48    fn boxed(&self) -> Box<dyn Solver> {
49        Box::new(self.clone())
50    }
51
52    fn as_any(&self) -> &dyn Any {
53        self
54    }
55}
56
57#[derive(Clone, Debug, PartialEq)]
58pub enum Params {
59    ChaigneAskenfelt(ChaigneAskenfeltParams),
60    WillemsenBilbaoSerafin(WillemsenBilbaoSerafinParams),
61    DarabunditScavone(BoreParams),
62    RhaoutiChaigneJoly(RhaoutiChaigneJolyParams),
63    ChaigneDoutaut(ChaigneDoutautParams),
64    Botteldooren(BotteldoorenParams),
65}
66
67impl Params {
68    pub fn name(&self) -> &'static str {
69        match self {
70            Params::ChaigneAskenfelt(_) => "chaigne_askenfelt",
71            Params::WillemsenBilbaoSerafin(_) => "willemsen_bilbao_serafin",
72            Params::DarabunditScavone(_) => "darabundit_scavone",
73            Params::RhaoutiChaigneJoly(_) => "rhaouti_chaigne_joly",
74            Params::ChaigneDoutaut(_) => "chaigne_doutaut",
75            Params::Botteldooren(_) => "botteldooren",
76        }
77    }
78
79    pub fn differs_in_release_alone(&self, other: &Params) -> bool {
80        match (self, other) {
81            (Params::ChaigneAskenfelt(a), Params::ChaigneAskenfelt(b)) => {
82                a.release != b.release
83                    && *a
84                        == ChaigneAskenfeltParams {
85                            release: a.release,
86                            ..b.clone()
87                        }
88            }
89            _ => false,
90        }
91    }
92
93    pub fn valid(&self) -> bool {
94        match self {
95            Params::ChaigneAskenfelt(p) => p.valid(),
96            Params::WillemsenBilbaoSerafin(p) => p.valid(),
97            Params::DarabunditScavone(p) => p.valid(),
98            Params::RhaoutiChaigneJoly(p) => p.valid(),
99            Params::ChaigneDoutaut(p) => p.valid(),
100            Params::Botteldooren(p) => p.valid(),
101        }
102    }
103}
104
105/// One grid, sized at the observation's rate; nothing here reads a sample back. A size only
106/// the rate decides is checked here, where the rate is known — `valid()` never sees one.
107pub fn site(p: &Params, rate: u32) -> Result<Box<dyn Solver>, SampleError> {
108    let sr = f64::from(rate);
109    Ok(match p {
110        Params::ChaigneAskenfelt(p) => Box::new(ChaigneAskenfeltSite::new(p, sr)?),
111        Params::WillemsenBilbaoSerafin(p) => Box::new(WillemsenBilbaoSerafinSite::new(p, sr)?),
112        Params::DarabunditScavone(p) => Box::new(BoreSite::new(p, sr)),
113        Params::RhaoutiChaigneJoly(p) => {
114            under_ceiling(
115                "rhaouti_chaigne_joly",
116                rhaouti_chaigne_joly::node_count(p, sr),
117                rhaouti_chaigne_joly::NODE_COUNT_CEILING,
118            )?;
119            Box::new(RhaoutiChaigneJolySite::new(p, sr))
120        }
121        Params::ChaigneDoutaut(p) => Box::new(ChaigneDoutautSite::new(p, sr)),
122        Params::Botteldooren(p) => {
123            under_ceiling(
124                "botteldooren",
125                botteldooren::node_count(p.f0, p.aspect_y, p.aspect_z, sr),
126                botteldooren::NODE_COUNT_CEILING,
127            )?;
128            Box::new(BotteldoorenSite::new(p, sr))
129        }
130    })
131}
132
133/// `at[j]` bounds every sample from `j * step` on; `held` if held under `level` early.
134#[derive(Clone, Debug, PartialEq)]
135pub struct Tail {
136    pub at: Vec<f64>,
137    pub held: bool,
138}
139
140pub fn tail(p: &Params, rate: u32, step: usize, points: usize, level: f64) -> Result<Tail, String> {
141    match p {
142        Params::ChaigneAskenfelt(p) => chaigne_tail::tail(p, f64::from(rate), step, points, level),
143        other => Err(format!("the {} solver", other.name())),
144    }
145}
146
147/// [`tail`] from the state `solver` holds now; `None` for a model with no bound.
148pub fn tail_from(
149    solver: &dyn Solver,
150    step: usize,
151    points: usize,
152    level: f64,
153) -> Option<Result<Tail, String>> {
154    let site = solver.as_any().downcast_ref::<ChaigneAskenfeltSite>()?;
155    Some(chaigne_tail::tail_from(site, step, points, level))
156}
157
158fn under_ceiling(model: &'static str, nodes: f64, ceiling: usize) -> Result<(), SampleError> {
159    match nodes > ceiling as f64 {
160        true => Err(SampleError::GridTooLarge {
161            model,
162            nodes: nodes as usize,
163            ceiling,
164        }),
165        false => Ok(()),
166    }
167}
168
169/// The raised-cosine pressure pulse the room and the bore are driven by: one half-turn over
170/// `width` seconds and silence after, so the drive starts and ends at rest.
171pub(crate) fn raised_cosine_pulse(t: f64, amp: f64, width: f64) -> f64 {
172    match t < width {
173        true => amp * 0.5 * (1.0 - (std::f64::consts::TAU * t / width).cos()),
174        false => 0.0,
175    }
176}