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