Skip to main content

sva_engine/
query.rs

1// Concern: the one table of observations: names, arguments, defaults, what each reads, help; the answer envelope | Non-concern: taking one reading (answer.rs) | IO: (name, Given) -> Representation
2
3use sva_formula::{C64, Line, SpectralSum, Var};
4use sva_samples::measure::beating::Beating;
5use sva_samples::measure::harmony::dissonance::Dissonance;
6use sva_samples::measure::harmony::inharmonicity::Inharmonicity;
7use sva_samples::measure::harmony::tonnetz::TonalDistance;
8use sva_samples::measure::harmony::voice_leading::{Norm, VoiceLeading};
9use sva_samples::measure::loudness_pattern::LoudnessPattern;
10use sva_samples::measure::modulation::Modulation;
11use sva_samples::measure::partials::Partials;
12use sva_samples::measure::periodicity::Periodicity;
13use sva_samples::measure::self_similarity::SelfSimilarity;
14use sva_samples::measure::sharpness::Sharpness;
15use sva_samples::{
16    Alias, Bands, Buffer, Consumes, Crest, EnvelopeFrame, FormantFrame, LedgerEntry, Loudness,
17    PitchFrame, Score, Source, Spectrum, StereoImage,
18};
19
20use crate::arguments::Arguments;
21use crate::bindings::Binding;
22
23/// The default window a framed reading uses when the observation names none.
24pub const DEFAULT_FRAME_SECS: f64 = 0.05;
25pub const DEFAULT_MAX_PEAKS: usize = 16;
26pub const DEFAULT_LEDGER_DEPTH: usize = 3;
27pub const DEFAULT_OVERSAMPLE: u32 = 4;
28/// How much audio a live device holds ahead of what it plays, where `samples` names one.
29pub const DEFAULT_BUFFER_SECS: f64 = 0.05;
30/// The amplitude, in dB against full scale, under which a harmonic reading drops a partial.
31pub const DEFAULT_FLOOR_DB: f64 = -40.0;
32/// The most partial pairs one `dissonance` frame sums before it refuses.
33pub const DEFAULT_MAX_PAIRS: usize = 1 << 17;
34/// BS.1770's momentary block, which `loudness` integrates over unless asked another.
35pub const DEFAULT_LOUDNESS_WINDOW_SECS: f64 = sva_samples::measure::loudness::MOMENTARY_SECS;
36/// The span of onset-strength envelope one `periodicity` window autocorrelates.
37pub const DEFAULT_PERIODICITY_WINDOW_SECS: f64 = 6.0;
38/// The span of band envelope one `modulation` window transforms.
39pub const DEFAULT_MODULATION_WINDOW_SECS: f64 = 4.0;
40/// The side of one `self_similarity` matrix cell.
41pub const DEFAULT_CELL_SECS: f64 = 0.5;
42/// The length `self_similarity` averages each diagonal over before it picks stripes.
43pub const DEFAULT_SMOOTH_SECS: f64 = 4.0;
44/// The whole width of `self_similarity`'s novelty kernel.
45pub const DEFAULT_NOVELTY_WINDOW_SECS: f64 = 8.0;
46/// A Hann this long parts partials some 20 Hz apart.
47pub const DEFAULT_PARTIALS_FRAME_SECS: f64 = 0.1;
48/// A short note's length, over which a noise's chance peaks average away.
49pub const DEFAULT_PARTIALS_WINDOW_SECS: f64 = 0.5;
50pub const DEFAULT_PARTIALS_HOP_SECS: f64 = 0.005;
51pub const DEFAULT_PARTIALS_FLOOR_DB: f64 = -70.0;
52pub const DEFAULT_PARTIALS_TRACKS: usize = 120;
53/// A loudness pattern's frames step 10 ms: a 20 Hz level swing reads five frames a cycle.
54pub const DEFAULT_PATTERN_FRAME_SECS: f64 = 0.01;
55pub use sva_samples::measure::loudness_pattern::DEFAULT_SPL_DB;
56/// Half a second of frames: a swing at 4 Hz turns twice in it, a note's fall stays its trend.
57pub const DEFAULT_BEATING_WINDOW_SECS: f64 = 0.5;
58
59/// Every named argument a reading may take; the engine reads only those `Given` holds.
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum Arg {
62    Peaks,
63    Frame,
64    Depth,
65    Oversample,
66    Brief,
67    Skim,
68    Node,
69    Buffer,
70    Floor,
71    Norm,
72    Pairs,
73    Window,
74    Curve,
75    Bpm,
76    Cell,
77    Smooth,
78    Hop,
79    Spl,
80}
81
82impl Arg {
83    pub fn key(self) -> &'static str {
84        match self {
85            Arg::Peaks => "peaks",
86            Arg::Frame => "frame",
87            Arg::Depth => "depth",
88            Arg::Oversample => "oversample",
89            Arg::Brief => "brief",
90            Arg::Skim => "skim",
91            Arg::Node => "node",
92            Arg::Buffer => "buffer",
93            Arg::Floor => "floor",
94            Arg::Norm => "norm",
95            Arg::Pairs => "pairs",
96            Arg::Window => "window",
97            Arg::Curve => "curve",
98            Arg::Bpm => "bpm",
99            Arg::Cell => "cell",
100            Arg::Smooth => "smooth",
101            Arg::Hop => "hop",
102            Arg::Spl => "spl",
103        }
104    }
105}
106
107/// The arguments a `Representation` is built from, each at its default until written.
108#[derive(Clone, Copy, Debug, PartialEq)]
109pub struct Given {
110    /// `None` until written: each reading then takes its own default.
111    pub peaks: Option<usize>,
112    /// `None` until written: `spectrum` then sizes its transform to the range, and every
113    /// other framed reading steps by `DEFAULT_FRAME_SECS`.
114    pub frame: Option<f64>,
115    pub depth: usize,
116    pub oversample: u32,
117    /// `None` until written: each reading then takes its own default.
118    pub floor_db: Option<f64>,
119    pub norm: Norm,
120    pub max_pairs: usize,
121    /// `None` until written: each windowed reading then takes its own default.
122    pub window: Option<f64>,
123    pub curve: bool,
124    pub bpm: Option<f64>,
125    pub cell_secs: f64,
126    pub smooth_secs: f64,
127    pub hop: Option<f64>,
128    /// `None` until written: the level a full-scale sine plays at, `DEFAULT_SPL_DB`.
129    pub spl_db: Option<f64>,
130}
131
132impl Default for Given {
133    fn default() -> Given {
134        Given {
135            peaks: None,
136            frame: None,
137            depth: DEFAULT_LEDGER_DEPTH,
138            oversample: DEFAULT_OVERSAMPLE,
139            floor_db: None,
140            norm: Norm::L1,
141            max_pairs: DEFAULT_MAX_PAIRS,
142            window: None,
143            curve: false,
144            bpm: None,
145            cell_secs: DEFAULT_CELL_SECS,
146            smooth_secs: DEFAULT_SMOOTH_SECS,
147            hop: None,
148            spl_db: None,
149        }
150    }
151}
152
153/// What a reading reads, which decides whether its answer is exact or measured.
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub enum Reads {
156    /// The written closed form alone: always exact.
157    ClosedForm,
158    /// Whichever the node holds: exact off a closed form, measured off samples.
159    Either,
160    /// Samples alone: always measured.
161    Samples,
162    /// Samples and the graph that rendered them, so no file answers it.
163    Graph,
164    /// The resolved instances, before anything renders; nothing composes for it.
165    Structure,
166}
167
168impl Reads {
169    pub fn consumes(self, closed: bool) -> Consumes {
170        match self {
171            Reads::ClosedForm | Reads::Structure => Consumes::ClosedForm,
172            Reads::Either if closed => Consumes::ClosedForm,
173            Reads::Either | Reads::Samples | Reads::Graph => Consumes::Buffer,
174        }
175    }
176
177    pub fn off_samples(self, sampled: bool) -> bool {
178        match self {
179            Reads::Samples => true,
180            Reads::Either => sampled,
181            Reads::ClosedForm | Reads::Graph | Reads::Structure => false,
182        }
183    }
184}
185
186pub struct Reading {
187    pub name: &'static str,
188    pub takes: &'static [Arg],
189    pub reads: Reads,
190    pub help: &'static str,
191    build: fn(&Given) -> Representation,
192}
193
194impl Reading {
195    pub fn representation(&self, given: &Given) -> Representation {
196        (self.build)(given)
197    }
198}
199
200static LINES: Reading = Reading {
201    name: "lines",
202    takes: &[],
203    reads: Reads::ClosedForm,
204    help: "the exact line list of a closed form, every channel's in channel order with no \
205           channel named; `atoms` names each term's",
206    build: |_| Representation::Lines,
207};
208
209static ATOMS: Reading = Reading {
210    name: "atoms",
211    takes: &[],
212    reads: Reads::ClosedForm,
213    help: "each term of a closed form: its channel, sketch, line, decay, polynomial and window",
214    build: |_| Representation::Atoms,
215};
216
217static SPECTRUM: Reading = Reading {
218    name: "spectrum",
219    takes: &[Arg::Peaks, Arg::Frame],
220    reads: Reads::Either,
221    help: "its lines, or its spectrum averaged over every frame and, power per bin, every \
222           channel; off samples a Hann spans the samples a frame holds, zero past them, each \
223           third-octave band reads as the amplitude of the one sine carrying its power, and, \
224           loudest first, a local maximum is no peak where its bin holds no more than the \
225           peaks kept, and a DC bin 0 holds as its maximum, can leak there, each bounded by \
226           Harris's (1978) Hann sidelobe bound 1/(pi m (m^2 - 1)) at m bins, its sinusoid taken \
227           anywhere within half a bin of its own: a bound where the frame is stationary \
228           sinusoids at the kept peaks",
229    build: |g| Representation::Spectrum {
230        max_peaks: g.peaks.unwrap_or(DEFAULT_MAX_PEAKS),
231        frame_secs: g.frame,
232    },
233};
234
235static ENVELOPE: Reading = Reading {
236    name: "envelope",
237    takes: &[Arg::Frame],
238    reads: Reads::Either,
239    help: "off a closed form the squared modulus of its analytic signal, x^2 + H{x}^2, as \
240           atoms per channel; off samples rms and peak per frame: the rms of every channel's \
241           samples together, the peak of any channel",
242    build: |g| Representation::Envelope {
243        frame_secs: g.frame,
244    },
245};
246
247static DERIVATIVE: Reading = Reading {
248    name: "derivative",
249    takes: &[],
250    reads: Reads::Either,
251    help: "the derivative's atoms per channel, each series listed as `atoms` lists it; off \
252           samples the first difference, (x[n] - x[n-1]) * rate at every sample but the first",
253    build: |_| Representation::Derivative,
254};
255
256static SAMPLES: Reading = Reading {
257    name: "samples",
258    takes: &[Arg::Buffer],
259    reads: Reads::Samples,
260    help: "the samples themselves; a `.wav` destination writes them as audio, an \
261           `alsa:<device>` one plays them as they render",
262    build: |_| Representation::Samples,
263};
264
265static LEDGER: Reading = Reading {
266    name: "ledger",
267    takes: &[Arg::Depth, Arg::Brief, Arg::Skim],
268    reads: Reads::Graph,
269    help: "rms, peak, share and clipping per node under the target",
270    build: |g| Representation::Ledger { depth: g.depth },
271};
272
273static PITCH: Reading = Reading {
274    name: "pitch",
275    takes: &[Arg::Peaks, Arg::Frame],
276    reads: Reads::Either,
277    help: "the `peaks` loudest notes it holds, per frame off samples, every channel's power \
278           per bin averaged; off samples its peaks are picked as `spectrum` picks them, and a series' \
279           fundamental is read within half a bin of its root's peak, the rate over the samples \
280           its Hann spans",
281    build: |g| Representation::Pitch {
282        max_notes: g.peaks.unwrap_or(DEFAULT_MAX_PEAKS),
283        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
284    },
285};
286
287static FORMANTS: Reading = Reading {
288    name: "formants",
289    takes: &[Arg::Peaks, Arg::Frame],
290    reads: Reads::Samples,
291    help: "formant frequencies, bandwidths and levels per frame, the `peaks` lowest, off \
292           every channel's autocorrelation averaged, an all-pole fit of 2 poles and 1 per kHz \
293           of rate, at most 64: a frame or target holding no more samples than its order \
294           refuses, and a tail frame reads zero past its end",
295    build: |g| Representation::Formants {
296        max_formants: g.peaks.unwrap_or(DEFAULT_MAX_PEAKS),
297        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
298    },
299};
300
301static STEREO: Reading = Reading {
302    name: "stereo",
303    takes: &[Arg::Frame],
304    reads: Reads::Samples,
305    help: "correlation, width and balance between exactly two channels, per frame; any other \
306           count refuses",
307    build: |g| Representation::Stereo {
308        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
309    },
310};
311
312static BANDS: Reading = Reading {
313    name: "bands",
314    takes: &[],
315    reads: Reads::Samples,
316    help: "each auditory band's level over time, every channel's band energy averaged, at 2 kHz",
317    build: |_| Representation::Bands,
318};
319
320static CREST: Reading = Reading {
321    name: "crest",
322    takes: &[],
323    reads: Reads::Samples,
324    help: "peak against rms, broadband and per band: any channel's peak against the rms of \
325           every channel's samples together",
326    build: |_| Representation::Crest,
327};
328
329static LOUDNESS: Reading = Reading {
330    name: "loudness",
331    takes: &[Arg::Window],
332    reads: Reads::Samples,
333    help: "integrated, momentary and short-term loudness in LUFS, the momentary blocks `window` \
334           long and the integrated gated over them",
335    build: |g| Representation::Loudness {
336        window_secs: g.window.unwrap_or(DEFAULT_LOUDNESS_WINDOW_SECS),
337    },
338};
339
340static SCORE: Reading = Reading {
341    name: "score",
342    takes: &[],
343    reads: Reads::Samples,
344    help: "its events as a score: each one's onset, placed at the sample its strike rises, its \
345           end and the next, new, a re-strike or held from the start, tonal, noisy-pitched or \
346           noisy, an unpitched hit's kind, its attack, level and periodicity, and its voices, \
347           several where a chord sounds, each with its f0, tonal or noisy, whether it sounds \
348           newly there, its bend and its partials, the loudest new one its main; every \
349           channel's samples averaged",
350    build: |_| Representation::Score,
351};
352
353static LOUDNESS_PATTERN: Reading = Reading {
354    name: "loudness_pattern",
355    takes: &[Arg::Frame, Arg::Spl],
356    reads: Reads::Samples,
357    help: "specific loudness in sone per Cam, band by band every `frame`, and each frame's \
358           total, after the ear's transfer and the excitation's spread, so a sound another \
359           masks adds little, a full-scale sine at `spl` dB SPL; every channel's power per \
360           bin averaged",
361    build: |g| Representation::LoudnessPattern {
362        frame_secs: g.frame.unwrap_or(DEFAULT_PATTERN_FRAME_SECS),
363        spl_db: g.spl_db.unwrap_or(DEFAULT_SPL_DB),
364    },
365};
366
367static SHARPNESS: Reading = Reading {
368    name: "sharpness",
369    takes: &[Arg::Frame, Arg::Spl],
370    reads: Reads::Samples,
371    help: "sharpness in acum every `frame`, DIN 45692's weighting over the specific loudness \
372           `loudness_pattern` reads at the same `frame` and `spl`, null where the frame is \
373           silent; every channel's power per bin averaged",
374    build: |g| Representation::Sharpness {
375        frame_secs: g.frame.unwrap_or(DEFAULT_PATTERN_FRAME_SECS),
376        spl_db: g.spl_db.unwrap_or(DEFAULT_SPL_DB),
377    },
378};
379
380static BEATING: Reading = Reading {
381    name: "beating",
382    takes: &[Arg::Frame, Arg::Window, Arg::Spl],
383    reads: Reads::Samples,
384    help: "how far each band's level swings about its own quadratic trend over a `window` \
385           centred on every `frame`, in phon, the bands weighted by their loudness: beating \
386           and tremolo, a note's rise and fall left in the trend; off the specific loudness \
387           `loudness_pattern` reads at the same `frame` and `spl`, every channel's power per \
388           bin averaged",
389    build: |g| Representation::Beating {
390        frame_secs: g.frame.unwrap_or(DEFAULT_PATTERN_FRAME_SECS),
391        window_secs: g.window.unwrap_or(DEFAULT_BEATING_WINDOW_SECS),
392        spl_db: g.spl_db.unwrap_or(DEFAULT_SPL_DB),
393    },
394};
395
396static ALIAS: Reading = Reading {
397    name: "alias",
398    takes: &[Arg::Oversample],
399    reads: Reads::Graph,
400    help: "what folded, against a re-render at a multiple of the rate",
401    build: |g| Representation::Alias {
402        oversample: g.oversample,
403    },
404};
405
406static DISSONANCE: Reading = Reading {
407    name: "dissonance",
408    takes: &[Arg::Frame, Arg::Floor, Arg::Pairs],
409    reads: Reads::Either,
410    help: "Sethares' sensory roughness per frame of its partials over a floor, with its mean, \
411           max and min; measured partials off every channel's power per bin averaged",
412    build: |g| Representation::Harmony {
413        reading: Harmonic::Dissonance {
414            max_pairs: g.max_pairs,
415        },
416        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
417        floor_db: g.floor_db.unwrap_or(DEFAULT_FLOOR_DB),
418    },
419};
420
421static VOICE_LEADING: Reading = Reading {
422    name: "voice_leading",
423    takes: &[Arg::Frame, Arg::Floor, Arg::Norm],
424    reads: Reads::Either,
425    help: "its chords over time, and the smallest voice leading between each two, l1 or l2 \
426           in semitones; measured partials off every channel's power per bin averaged",
427    build: |g| Representation::Harmony {
428        reading: Harmonic::VoiceLeading { norm: g.norm },
429        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
430        floor_db: g.floor_db.unwrap_or(DEFAULT_FLOOR_DB),
431    },
432};
433
434static TONAL_DISTANCE: Reading = Reading {
435    name: "tonal_distance",
436    takes: &[Arg::Frame, Arg::Floor],
437    reads: Reads::Either,
438    help: "its chords over time, the Tonnetz steps between each two triads and each one's \
439           from the first triad; measured partials off every channel's power per bin averaged",
440    build: |g| Representation::Harmony {
441        reading: Harmonic::TonalDistance,
442        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
443        floor_db: g.floor_db.unwrap_or(DEFAULT_FLOOR_DB),
444    },
445};
446
447static INHARMONICITY: Reading = Reading {
448    name: "inharmonicity",
449    takes: &[Arg::Frame, Arg::Floor],
450    reads: Reads::Either,
451    help: "its harmonic series per frame: each member's cents off n f1, the stiff-string B each \
452           fits, and the energy in no series; measured partials off every channel's power per \
453           bin averaged",
454    build: |g| Representation::Harmony {
455        reading: Harmonic::Inharmonicity,
456        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
457        floor_db: g.floor_db.unwrap_or(DEFAULT_FLOOR_DB),
458    },
459};
460
461static PERIODICITY: Reading = Reading {
462    name: "periodicity",
463    takes: &[Arg::Frame, Arg::Window, Arg::Curve],
464    reads: Reads::Samples,
465    help: "how strongly its onset strength repeats per sliding window, broadband, under 150 Hz \
466           and over: the leading periods up to 2 s, or half the window, in seconds and bpm \
467           with their bounds, their \
468           autocorrelation heights, the drift between windows; every channel's band energy \
469           averaged",
470    build: |g| Representation::Periodicity {
471        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
472        window_secs: g.window.unwrap_or(DEFAULT_PERIODICITY_WINDOW_SECS),
473        curve: g.curve,
474    },
475};
476
477static MODULATION: Reading = Reading {
478    name: "modulation",
479    takes: &[Arg::Frame, Arg::Window, Arg::Bpm],
480    reads: Reads::Samples,
481    help: "how its band envelopes move per sliding window, under 150 Hz, to 2 kHz and over: \
482           the leading modulation rates from 0.5 Hz, or the slowest two of whose periods the \
483           window holds, to 20 Hz, in beats where `bpm` is given, \
484           their depth as a modulation index, each with its bounds, the level, and the \
485           spectral centroid's mean and its own swing in Hz; every channel's band energy \
486           averaged",
487    build: |g| Representation::Modulation {
488        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
489        window_secs: g.window.unwrap_or(DEFAULT_MODULATION_WINDOW_SECS),
490        bpm: g.bpm,
491    },
492};
493
494static SELF_SIMILARITY: Reading = Reading {
495    name: "self_similarity",
496    takes: &[
497        Arg::Frame,
498        Arg::Cell,
499        Arg::Smooth,
500        Arg::Window,
501        Arg::Floor,
502        Arg::Peaks,
503        Arg::Curve,
504    ],
505    reads: Reads::Samples,
506    help: "how its stretches resemble one another, by chroma and by band levels, over cells \
507           of `cell`: the repeated stretches as stripes, each pair of spans with its \
508           similarity and, by chroma, its transposition in semitones; Foote's novelty at every \
509           cell boundary and its peaks; `curve=1` adds the cells and the matrix; every \
510           channel's power per bin and band energy averaged",
511    build: |g| Representation::SelfSimilarity {
512        frame_secs: g.frame.unwrap_or(DEFAULT_FRAME_SECS),
513        cell_secs: g.cell_secs,
514        smooth_secs: g.smooth_secs,
515        window_secs: g.window.unwrap_or(DEFAULT_NOVELTY_WINDOW_SECS),
516        floor_db: g.floor_db.unwrap_or(DEFAULT_FLOOR_DB),
517        listed: g.peaks.unwrap_or(DEFAULT_MAX_PEAKS),
518        curve: g.curve,
519    },
520};
521
522static PARTIALS: Reading = Reading {
523    name: "partials",
524    takes: &[Arg::Frame, Arg::Window, Arg::Hop, Arg::Floor, Arg::Peaks],
525    reads: Reads::Samples,
526    help: "each partial as a track: its frequency and amplitude as knot tables for `pwl` and \
527           `pwl_integral`, tonal or breathy by its amplitude's flutter and its band's \
528           coherence, each with its resolution and the residual its tables leave; peaks \
529           of `frame` Hann frames' power averaged over `window`, found every `hop` over the \
530           floor, the `peaks` tracks of most energy kept; every channel's samples averaged",
531    build: |g| Representation::Partials {
532        frame_secs: g.frame.unwrap_or(DEFAULT_PARTIALS_FRAME_SECS),
533        window_secs: g.window.unwrap_or(DEFAULT_PARTIALS_WINDOW_SECS),
534        hop_secs: g.hop.unwrap_or(DEFAULT_PARTIALS_HOP_SECS),
535        floor_db: g.floor_db.unwrap_or(DEFAULT_PARTIALS_FLOOR_DB),
536        tracks: g.peaks.unwrap_or(DEFAULT_PARTIALS_TRACKS),
537    },
538};
539
540static BINDINGS: Reading = Reading {
541    name: "bindings",
542    takes: &[Arg::Node],
543    reads: Reads::Structure,
544    help: "the bindings one instance resolved, each with its source",
545    build: |_| Representation::Bindings,
546};
547
548static ARGUMENTS: Reading = Reading {
549    name: "arguments",
550    takes: &[],
551    reads: Reads::Structure,
552    help: "each builtin call's named arguments as the numbers it was lowered with",
553    build: |_| Representation::Arguments,
554};
555
556/// Every reading a caller may ask for, in the order a listing prints them.
557pub static READINGS: [&Reading; 28] = [
558    &LINES,
559    &ATOMS,
560    &SPECTRUM,
561    &ENVELOPE,
562    &DERIVATIVE,
563    &SAMPLES,
564    &LEDGER,
565    &PITCH,
566    &FORMANTS,
567    &STEREO,
568    &BANDS,
569    &CREST,
570    &LOUDNESS,
571    &SCORE,
572    &ALIAS,
573    &DISSONANCE,
574    &VOICE_LEADING,
575    &TONAL_DISTANCE,
576    &INHARMONICITY,
577    &PERIODICITY,
578    &MODULATION,
579    &SELF_SIMILARITY,
580    &PARTIALS,
581    &LOUDNESS_PATTERN,
582    &SHARPNESS,
583    &BEATING,
584    &BINDINGS,
585    &ARGUMENTS,
586];
587
588#[derive(Clone, Copy, Debug, PartialEq)]
589pub enum Representation {
590    Lines,
591    Atoms,
592    Spectrum {
593        max_peaks: usize,
594        frame_secs: Option<f64>,
595    },
596    Envelope {
597        frame_secs: Option<f64>,
598    },
599    Derivative,
600    Samples,
601    Ledger {
602        depth: usize,
603    },
604    Pitch {
605        max_notes: usize,
606        frame_secs: f64,
607    },
608    Formants {
609        max_formants: usize,
610        frame_secs: f64,
611    },
612    Stereo {
613        frame_secs: f64,
614    },
615    Bands,
616    Crest,
617    /// Momentary blocks of `window_secs`, the integrated loudness gated over them.
618    Loudness {
619        window_secs: f64,
620    },
621    Score,
622    Alias {
623        oversample: u32,
624    },
625    /// The partials it is read from step by `frame_secs` and stop at `floor_db`.
626    Harmony {
627        reading: Harmonic,
628        frame_secs: f64,
629        floor_db: f64,
630    },
631    /// Windows of `window_secs` centred every `frame_secs`; `curve` adds each one's heights.
632    Periodicity {
633        frame_secs: f64,
634        window_secs: f64,
635        curve: bool,
636    },
637    /// Windows of `window_secs` centred every `frame_secs`; `bpm` adds each rate in beats.
638    Modulation {
639        frame_secs: f64,
640        window_secs: f64,
641        bpm: Option<f64>,
642    },
643    /// Cells of `cell_secs` over frames every `frame_secs`; `listed` stripes and peaks are
644    /// listed, and `curve` adds the cells and matrices.
645    SelfSimilarity {
646        frame_secs: f64,
647        cell_secs: f64,
648        smooth_secs: f64,
649        window_secs: f64,
650        floor_db: f64,
651        listed: usize,
652        curve: bool,
653    },
654    Partials {
655        frame_secs: f64,
656        window_secs: f64,
657        hop_secs: f64,
658        floor_db: f64,
659        tracks: usize,
660    },
661    LoudnessPattern {
662        frame_secs: f64,
663        spl_db: f64,
664    },
665    Sharpness {
666        frame_secs: f64,
667        spl_db: f64,
668    },
669    /// Windows of `window_secs` centred on every frame of the pattern `frame_secs` steps.
670    Beating {
671        frame_secs: f64,
672        window_secs: f64,
673        spl_db: f64,
674    },
675    Bindings,
676    Arguments,
677}
678
679/// The readings taken off one curve of partials.
680#[derive(Clone, Copy, Debug, PartialEq, Eq)]
681pub enum Harmonic {
682    Dissonance { max_pairs: usize },
683    VoiceLeading { norm: Norm },
684    TonalDistance,
685    Inharmonicity,
686}
687
688/// What a caller asked of one node.
689#[derive(Clone, Debug, PartialEq)]
690pub struct Ask {
691    pub node: String,
692    pub representation: Representation,
693}
694
695#[derive(Clone, Debug, PartialEq)]
696pub enum Output {
697    Lines(Vec<Line>),
698    Atoms(Vec<Atom>),
699    Symbolic(Box<SpectralSum>),
700    Samples(Box<Buffer>),
701    Spectrum(Box<Spectrum>),
702    Envelope(Vec<EnvelopeFrame>),
703    Pitch(Vec<PitchFrame>),
704    Formants(Vec<FormantFrame>),
705    Stereo(Box<StereoImage>),
706    Bands(Box<Bands>),
707    Crest(Box<Crest>),
708    Loudness(Box<Loudness>),
709    Score(Box<Score>),
710    Alias(Box<Alias>),
711    Ledger(Vec<LedgerEntry>),
712    Dissonance(Box<Dissonance>),
713    VoiceLeading(Box<VoiceLeading>),
714    TonalDistance(Box<TonalDistance>),
715    Inharmonicity(Box<Inharmonicity>),
716    Periodicity(Box<Periodicity>),
717    Modulation(Box<Modulation>),
718    SelfSimilarity(Box<SelfSimilarity>),
719    Partials(Box<Partials>),
720    LoudnessPattern(Box<LoudnessPattern>),
721    Sharpness(Box<Sharpness>),
722    Beating(Box<Beating>),
723    Bindings(Vec<Binding>),
724    Arguments(Vec<Arguments>),
725}
726
727/// `amp (x-polynomial_at)^degree e^(-decay (x-decay_from)) e^(2pi i turns x)` on `[start, end)`
728#[derive(Clone, Debug, PartialEq)]
729pub struct Atom {
730    pub var: Var,
731    /// The lane, or channel, it stands in.
732    pub channel: usize,
733    pub sketch: String,
734    pub amp: C64,
735    pub turns: f64,
736    pub decay: f64,
737    pub decay_from: f64,
738    pub degree: u16,
739    pub polynomial_at: f64,
740    pub start: Option<f64>,
741    pub end: Option<f64>,
742}
743
744/// Every answer says which reading ran and under which profile, so a caller never has to
745/// infer either from the shape of the value.
746#[derive(Clone, Debug, PartialEq)]
747pub struct Answer {
748    pub value: Output,
749    pub source: Source,
750    pub profile: &'static str,
751    pub rate: Option<u32>,
752    /// A series answers the terms it keeps, the tail beside them.
753    pub dropped: Vec<sva_formula::Line>,
754    pub tail_db: Option<f64>,
755}
756
757impl Answer {
758    pub fn whole(
759        value: Output,
760        source: Source,
761        profile: &'static str,
762        rate: Option<u32>,
763    ) -> Answer {
764        Answer {
765            value,
766            source,
767            profile,
768            rate,
769            dropped: Vec::new(),
770            tail_db: None,
771        }
772    }
773}
774
775impl Representation {
776    /// Exhaustive, so a variant with no row is a compile error.
777    pub fn reading(self) -> &'static Reading {
778        match self {
779            Representation::Lines => &LINES,
780            Representation::Atoms => &ATOMS,
781            Representation::Spectrum { .. } => &SPECTRUM,
782            Representation::Envelope { .. } => &ENVELOPE,
783            Representation::Derivative => &DERIVATIVE,
784            Representation::Samples => &SAMPLES,
785            Representation::Ledger { .. } => &LEDGER,
786            Representation::Pitch { .. } => &PITCH,
787            Representation::Formants { .. } => &FORMANTS,
788            Representation::Stereo { .. } => &STEREO,
789            Representation::Bands => &BANDS,
790            Representation::Crest => &CREST,
791            Representation::Loudness { .. } => &LOUDNESS,
792            Representation::Score => &SCORE,
793            Representation::Alias { .. } => &ALIAS,
794            Representation::Harmony { reading, .. } => match reading {
795                Harmonic::Dissonance { .. } => &DISSONANCE,
796                Harmonic::VoiceLeading { .. } => &VOICE_LEADING,
797                Harmonic::TonalDistance => &TONAL_DISTANCE,
798                Harmonic::Inharmonicity => &INHARMONICITY,
799            },
800            Representation::Periodicity { .. } => &PERIODICITY,
801            Representation::Modulation { .. } => &MODULATION,
802            Representation::SelfSimilarity { .. } => &SELF_SIMILARITY,
803            Representation::Partials { .. } => &PARTIALS,
804            Representation::LoudnessPattern { .. } => &LOUDNESS_PATTERN,
805            Representation::Sharpness { .. } => &SHARPNESS,
806            Representation::Beating { .. } => &BEATING,
807            Representation::Bindings => &BINDINGS,
808            Representation::Arguments => &ARGUMENTS,
809        }
810    }
811
812    pub fn name(self) -> &'static str {
813        self.reading().name
814    }
815
816    pub fn consumes(self, closed: bool) -> Consumes {
817        self.reading().reads.consumes(closed)
818    }
819
820    /// Whether a node's samples alone answer this reading, `sampled` where it holds samples.
821    pub fn off_samples(self, sampled: bool) -> bool {
822        self.reading().reads.off_samples(sampled)
823    }
824
825    pub fn from_name(name: &str) -> Option<Representation> {
826        READINGS
827            .iter()
828            .find(|row| row.name == name)
829            .map(|row| row.representation(&Given::default()))
830    }
831}