Skip to main content

sva_engine/
vocabulary.rs

1// Concern: each builtin's named arguments, and the maths each sva-ast filter name stands for | Non-concern: which names are builtins or reserved (sva-ast) | IO: (name) -> named keys, a Shape
2
3use sva_ast::{CHANNEL, FILTERS, JOIN, SERIES};
4use sva_formula::filter::{ALL_SHAPES, Shape};
5
6/// Enough for surround; ambisonics is deferred rather than pretended at.
7pub const MAX_WIDTH: usize = 8;
8
9/// The maths of the filter sva-ast names `name`.
10pub fn shape(name: &str) -> Option<Shape> {
11    FILTERS
12        .iter()
13        .position(|held| *held == name)
14        .map(|at| ALL_SHAPES[at])
15}
16
17const _: () = assert!(FILTERS.len() == ALL_SHAPES.len());
18
19pub fn shape_name(shape: Shape) -> &'static str {
20    let at = ALL_SHAPES.iter().position(|held| *held == shape);
21    FILTERS[at.expect("ALL_SHAPES holds every shape")]
22}
23
24pub(crate) fn note_hz(name: &str) -> Option<f64> {
25    sva_ast::note_midi(name).map(sva_formula::note::frequency)
26}
27
28/// Consts, so `recognized_named` hands back the same data a call site checks against.
29const NO_NAMED: &[&str] = &[];
30const WAVE_NAMED: &[&str] = &["tol"];
31const SAT_NAMED: &[&str] = &["drive"];
32const CROP_NAMED: &[&str] = &["start", "end", "rise", "fall"];
33const RAND_NAMED: &[&str] = &["seed"];
34const DELTA_NAMED: &[&str] = &["k"];
35const STFT_NAMED: &[&str] = &["window", "hop"];
36const NOISE_NAMED: &[&str] = &["period", "color"];
37/// Every modal bank reads the same damping, mode budget and excitation beside its own
38/// geometry's names. A cavity is one mode, so `one_mode` leaves the budget out.
39macro_rules! modal_named {
40    (one_mode $($own:literal),+ $(,)?) => {
41        &[$($own,)+ "damp_dc", "damp_freq", "vel", "at", "contact", "p", "force"]
42    };
43    ($($own:literal),+ $(,)?) => {
44        &[$($own,)+ "damp_dc", "damp_freq", "modes", "vel", "at", "contact", "p", "force"]
45    };
46}
47
48const STRING_NAMED: &[&str] = modal_named!("inharmonicity", "strike");
49const MEMBRANE_NAMED: &[&str] = modal_named!("tension", "density", "strike_x", "strike_y");
50const BAR_NAMED: &[&str] = modal_named!("thickness", "young", "density");
51const BORE_NAMED: &[&str] = modal_named!("radius", "speed", "closed");
52const ROOM_NAMED: &[&str] = modal_named!("speed");
53const HELMHOLTZ_NAMED: &[&str] =
54    modal_named!(one_mode "neck_area", "neck_length", "radius", "speed");
55const HAMMER_NAMED: &[&str] = &["at", "contact", "p", "force"];
56const FILTER_NAMED: &[&str] = &["cutoff", "q", "gain"];
57const CHAIGNE_ASKENFELT_NAMED: &[&str] = &[
58    "b",
59    "strike_pos",
60    "vel",
61    "hammer_mass",
62    "hammer_k",
63    "hammer_p",
64    "damp_dc",
65    "damp_freq",
66    "unison_count",
67    "detune",
68    "bridge_coupling",
69    "bridge_mass",
70    "string1_cents",
71    "string2_cents",
72    "string3_cents",
73    "string1_hammer_k_ratio",
74    "string2_hammer_k_ratio",
75    "string3_hammer_k_ratio",
76    "damper_pos",
77    "damper_r",
78    "damper_k",
79];
80const WILLEMSEN_BILBAO_SERAFIN_NAMED: &[&str] = &[
81    "b",
82    "bow_pos",
83    "bow_vel",
84    "bow_force",
85    "mu_s",
86    "mu_c",
87    "stribeck_vel",
88    "bristle_stiffness",
89    "bristle_damping",
90    "viscous_friction",
91    "damp_dc",
92    "damp_freq",
93];
94/// `holeN_*` is flat and capped at 6: no array-valued argument exists in this grammar.
95const DARABUNDIT_SCAVONE_NAMED: &[&str] = &[
96    "radius_in",
97    "radius_out",
98    "excite_pos",
99    "pulse_amp",
100    "pulse_width",
101    "damp_dc",
102    "damp_freq",
103    "hole1_pos",
104    "hole1_open",
105    "hole1_radius",
106    "hole1_height",
107    "hole2_pos",
108    "hole2_open",
109    "hole2_radius",
110    "hole2_height",
111    "hole3_pos",
112    "hole3_open",
113    "hole3_radius",
114    "hole3_height",
115    "hole4_pos",
116    "hole4_open",
117    "hole4_radius",
118    "hole4_height",
119    "hole5_pos",
120    "hole5_open",
121    "hole5_radius",
122    "hole5_height",
123    "hole6_pos",
124    "hole6_open",
125    "hole6_radius",
126    "hole6_height",
127];
128const RHAOUTI_CHAIGNE_JOLY_NAMED: &[&str] = &[
129    "aspect_ratio",
130    "strike_x",
131    "strike_y",
132    "vel",
133    "hammer_mass",
134    "hammer_k",
135    "hammer_p",
136    "damp_dc",
137    "damp_freq",
138];
139const CHAIGNE_DOUTAUT_NAMED: &[&str] = &[
140    "strike_pos",
141    "vel",
142    "hammer_mass",
143    "hammer_k",
144    "hammer_p",
145    "damp_dc",
146    "damp_freq",
147];
148const BOTTELDOOREN_NAMED: &[&str] = &[
149    "aspect_y",
150    "aspect_z",
151    "listener_x",
152    "listener_y",
153    "listener_z",
154    "pulse_amp",
155    "pulse_width",
156    "damp_dc",
157    "damp_freq",
158];
159
160/// A filter's cutoff, q and gain, and a solver's varying parameters, may move with `t` or
161/// read a signal: the builtin routes each itself. Every other named argument is one number.
162pub fn named_may_move(name: &str, key: &str) -> bool {
163    (shape(name).is_some() && FILTER_NAMED.contains(&key))
164        || sva_samples::physics::varying(name)
165            .iter()
166            .any(|(k, _)| *k == key)
167}
168
169/// `None` for a name `is_builtin` does not recognize.
170pub fn recognized_named(name: &str) -> Option<&'static [&'static str]> {
171    if shape(name).is_some() {
172        return Some(FILTER_NAMED);
173    }
174    Some(match name {
175        "sin" | "cos" | "exp" | "sqrt" | "abs" | "tanh" | "step" | "max" | "min" | "pow"
176        | "log" => NO_NAMED,
177        "saw" | "square" | "triangle" => WAVE_NAMED,
178        "sat" => SAT_NAMED,
179        SERIES | "pv" => NO_NAMED,
180        "crop" => CROP_NAMED,
181        "rand" => RAND_NAMED,
182        "delta" => DELTA_NAMED,
183        "chaigne_askenfelt" => CHAIGNE_ASKENFELT_NAMED,
184        "willemsen_bilbao_serafin" => WILLEMSEN_BILBAO_SERAFIN_NAMED,
185        "darabundit_scavone" => DARABUNDIT_SCAVONE_NAMED,
186        "rhaouti_chaigne_joly" => RHAOUTI_CHAIGNE_JOLY_NAMED,
187        "chaigne_doutaut" => CHAIGNE_DOUTAUT_NAMED,
188        "botteldooren" => BOTTELDOOREN_NAMED,
189        JOIN | CHANNEL => NO_NAMED,
190        "stft" => STFT_NAMED,
191        "sample" | "fourier" | "ifourier" | "istft" => NO_NAMED,
192        "noise" => NOISE_NAMED,
193        "string" => STRING_NAMED,
194        "membrane" => MEMBRANE_NAMED,
195        "bar" => BAR_NAMED,
196        "bore" => BORE_NAMED,
197        "room" => ROOM_NAMED,
198        "helmholtz" => HELMHOLTZ_NAMED,
199        "hammer_pulse" => HAMMER_NAMED,
200        _ => return None,
201    })
202}