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