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