Skip to main content

sva_engine/
vocabulary.rs

1// Concern: the names this language answers: callables, their named arguments, and each reserved word with a line on it | Non-concern: evaluating any of them | IO: (name) -> is-a-builtin, named keys
2
3use sva_formula::filter::Shape;
4
5use crate::lower::physics::MODAL;
6use crate::overload::FINITE_DIFFERENCE as PHYSICS;
7
8/// Every name the language reserves; `self` is call-only, the rest are values.
9pub const RESERVED: [(&str, &str); 6] = [
10    ("t", "time in seconds"),
11    ("f", "frequency in hertz"),
12    ("i", "the imaginary unit"),
13    ("pi", "the constant pi"),
14    (
15        "inf",
16        "infinity, a value: a crop edge never reached, a sum's open bound; arithmetic that \
17         leaves no number refuses",
18    ),
19    (
20        SELF,
21        "a loop's own past: self(t - 17ms) in a continuous loop, self[idx(t) - 1] in a discrete one",
22    ),
23];
24
25pub const SELF: &str = "self";
26
27/// Enough for surround; ambisonics is deferred rather than pretended at.
28pub const MAX_WIDTH: usize = 8;
29
30pub use sva_ast::{JOIN, SERIES};
31pub const CHANNEL: &str = "ch";
32
33const ARITHMETIC: [&str; 22] = [
34    "sin", "cos", "exp", "log", "pow", "sqrt", "abs", "tanh", "step", "max", "min", "saw",
35    "square", "triangle", "sat", "crop", "rand", "delta", "pv", SERIES, JOIN, CHANNEL,
36];
37
38/// The five written crossings of FORMAT 7, callable like any other name.
39pub const CASTS: [&str; 5] = ["sample", "fourier", "ifourier", "stft", "istft"];
40
41/// The noise realization, beside the modal instruments `lower/physics.rs` dispatches:
42/// every one of them a closed form.
43const WRITTEN_LAWS: [&str; 1] = ["noise"];
44
45const LAWS: [&str; WRITTEN_LAWS.len() + MODAL.len()] = {
46    let mut all = [""; WRITTEN_LAWS.len() + MODAL.len()];
47    let mut i = 0;
48    while i < WRITTEN_LAWS.len() {
49        all[i] = WRITTEN_LAWS[i];
50        i += 1;
51    }
52    let mut j = 0;
53    while j < MODAL.len() {
54        all[WRITTEN_LAWS.len() + j] = MODAL[j];
55        j += 1;
56    }
57    all
58};
59
60/// Every callable name besides the filter shapes.
61pub const BUILTINS: [&str; ARITHMETIC.len() + PHYSICS.len() + CASTS.len() + LAWS.len()] = {
62    let mut all = [""; ARITHMETIC.len() + PHYSICS.len() + CASTS.len() + LAWS.len()];
63    let mut i = 0;
64    while i < ARITHMETIC.len() {
65        all[i] = ARITHMETIC[i];
66        i += 1;
67    }
68    let mut j = 0;
69    while j < PHYSICS.len() {
70        all[ARITHMETIC.len() + j] = PHYSICS[j];
71        j += 1;
72    }
73    let mut k = 0;
74    while k < CASTS.len() {
75        all[ARITHMETIC.len() + PHYSICS.len() + k] = CASTS[k];
76        k += 1;
77    }
78    let mut n = 0;
79    while n < LAWS.len() {
80        all[ARITHMETIC.len() + PHYSICS.len() + CASTS.len() + n] = LAWS[n];
81        n += 1;
82    }
83    all
84};
85
86pub fn is_builtin(name: &str) -> bool {
87    Shape::from_name(name).is_some() || BUILTINS.contains(&name) || name == sva_ast::INDEX
88}
89
90/// Consts, so `recognized_named` hands back the same data a call site checks against.
91const NO_NAMED: &[&str] = &[];
92const WAVE_NAMED: &[&str] = &["tol"];
93const SAT_NAMED: &[&str] = &["drive"];
94const CROP_NAMED: &[&str] = &["start", "end", "rise", "fall"];
95const RAND_NAMED: &[&str] = &["seed"];
96const DELTA_NAMED: &[&str] = &["k"];
97const STFT_NAMED: &[&str] = &["window", "hop"];
98const NOISE_NAMED: &[&str] = &["period", "color"];
99/// Every modal bank reads the same damping, mode budget and excitation beside its own
100/// geometry's names. A cavity is one mode, so `one_mode` leaves the budget out.
101macro_rules! modal_named {
102    (one_mode $($own:literal),+ $(,)?) => {
103        &[$($own,)+ "damp_dc", "damp_freq", "vel", "at", "contact", "p", "force"]
104    };
105    ($($own:literal),+ $(,)?) => {
106        &[$($own,)+ "damp_dc", "damp_freq", "modes", "vel", "at", "contact", "p", "force"]
107    };
108}
109
110const STRING_NAMED: &[&str] = modal_named!("inharmonicity", "strike");
111const MEMBRANE_NAMED: &[&str] = modal_named!("tension", "density", "strike_x", "strike_y");
112const BAR_NAMED: &[&str] = modal_named!("thickness", "young", "density");
113const BORE_NAMED: &[&str] = modal_named!("radius", "speed", "closed");
114const ROOM_NAMED: &[&str] = modal_named!("speed");
115const HELMHOLTZ_NAMED: &[&str] =
116    modal_named!(one_mode "neck_area", "neck_length", "radius", "speed");
117const HAMMER_NAMED: &[&str] = &["at", "contact", "p", "force"];
118const FILTER_NAMED: &[&str] = &["cutoff", "q", "gain"];
119const CHAIGNE_ASKENFELT_NAMED: &[&str] = &[
120    "b",
121    "strike_pos",
122    "vel",
123    "hammer_mass",
124    "hammer_k",
125    "hammer_p",
126    "damp_dc",
127    "damp_freq",
128    "unison_count",
129    "detune",
130    "bridge_coupling",
131    "bridge_mass",
132    "string1_cents",
133    "string2_cents",
134    "string3_cents",
135    "string1_hammer_k_ratio",
136    "string2_hammer_k_ratio",
137    "string3_hammer_k_ratio",
138    "damper_pos",
139    "damper_r",
140    "damper_k",
141];
142const WILLEMSEN_BILBAO_SERAFIN_NAMED: &[&str] = &[
143    "b",
144    "bow_pos",
145    "bow_vel",
146    "bow_force",
147    "mu_s",
148    "mu_c",
149    "stribeck_vel",
150    "bristle_stiffness",
151    "bristle_damping",
152    "viscous_friction",
153    "damp_dc",
154    "damp_freq",
155];
156/// `holeN_*` is flat and capped at 6: no array-valued argument exists in this grammar.
157const DARABUNDIT_SCAVONE_NAMED: &[&str] = &[
158    "radius_in",
159    "radius_out",
160    "excite_pos",
161    "pulse_amp",
162    "pulse_width",
163    "damp_dc",
164    "damp_freq",
165    "hole1_pos",
166    "hole1_open",
167    "hole1_radius",
168    "hole1_height",
169    "hole2_pos",
170    "hole2_open",
171    "hole2_radius",
172    "hole2_height",
173    "hole3_pos",
174    "hole3_open",
175    "hole3_radius",
176    "hole3_height",
177    "hole4_pos",
178    "hole4_open",
179    "hole4_radius",
180    "hole4_height",
181    "hole5_pos",
182    "hole5_open",
183    "hole5_radius",
184    "hole5_height",
185    "hole6_pos",
186    "hole6_open",
187    "hole6_radius",
188    "hole6_height",
189];
190const RHAOUTI_CHAIGNE_JOLY_NAMED: &[&str] = &[
191    "aspect_ratio",
192    "strike_x",
193    "strike_y",
194    "vel",
195    "hammer_mass",
196    "hammer_k",
197    "hammer_p",
198    "damp_dc",
199    "damp_freq",
200];
201const CHAIGNE_DOUTAUT_NAMED: &[&str] = &[
202    "strike_pos",
203    "vel",
204    "hammer_mass",
205    "hammer_k",
206    "hammer_p",
207    "damp_dc",
208    "damp_freq",
209];
210const BOTTELDOOREN_NAMED: &[&str] = &[
211    "aspect_y",
212    "aspect_z",
213    "listener_x",
214    "listener_y",
215    "listener_z",
216    "pulse_amp",
217    "pulse_width",
218    "damp_dc",
219    "damp_freq",
220];
221
222/// A filter's cutoff, q and gain, and a solver's varying parameters, may move with `t` or
223/// read a signal: the builtin routes each itself. Every other named argument is one number.
224pub fn named_may_move(name: &str, key: &str) -> bool {
225    (Shape::from_name(name).is_some() && FILTER_NAMED.contains(&key))
226        || sva_samples::physics::varying(name)
227            .iter()
228            .any(|(k, _)| *k == key)
229}
230
231/// `None` for a name `is_builtin` does not recognize.
232pub fn recognized_named(name: &str) -> Option<&'static [&'static str]> {
233    if Shape::from_name(name).is_some() {
234        return Some(FILTER_NAMED);
235    }
236    Some(match name {
237        "sin" | "cos" | "exp" | "sqrt" | "abs" | "tanh" | "step" | "max" | "min" | "pow"
238        | "log" => NO_NAMED,
239        "saw" | "square" | "triangle" => WAVE_NAMED,
240        "sat" => SAT_NAMED,
241        SERIES | "pv" => NO_NAMED,
242        "crop" => CROP_NAMED,
243        "rand" => RAND_NAMED,
244        "delta" => DELTA_NAMED,
245        "chaigne_askenfelt" => CHAIGNE_ASKENFELT_NAMED,
246        "willemsen_bilbao_serafin" => WILLEMSEN_BILBAO_SERAFIN_NAMED,
247        "darabundit_scavone" => DARABUNDIT_SCAVONE_NAMED,
248        "rhaouti_chaigne_joly" => RHAOUTI_CHAIGNE_JOLY_NAMED,
249        "chaigne_doutaut" => CHAIGNE_DOUTAUT_NAMED,
250        "botteldooren" => BOTTELDOOREN_NAMED,
251        JOIN | CHANNEL => NO_NAMED,
252        "stft" => STFT_NAMED,
253        "sample" | "fourier" | "ifourier" | "istft" => NO_NAMED,
254        "noise" => NOISE_NAMED,
255        "string" => STRING_NAMED,
256        "membrane" => MEMBRANE_NAMED,
257        "bar" => BAR_NAMED,
258        "bore" => BORE_NAMED,
259        "room" => ROOM_NAMED,
260        "helmholtz" => HELMHOLTZ_NAMED,
261        "hammer_pulse" => HAMMER_NAMED,
262        _ => return None,
263    })
264}