1use 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
19pub 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;
24pub const DEFAULT_BUFFER_SECS: f64 = 0.05;
26pub const DEFAULT_FLOOR_DB: f64 = -40.0;
28pub const DEFAULT_MAX_PAIRS: usize = 1 << 17;
30pub const DEFAULT_LOUDNESS_WINDOW_SECS: f64 = sva_samples::measure::loudness::MOMENTARY_SECS;
32pub const DEFAULT_PERIODICITY_WINDOW_SECS: f64 = 6.0;
34pub const DEFAULT_MODULATION_WINDOW_SECS: f64 = 4.0;
36pub const DEFAULT_CELL_SECS: f64 = 0.5;
38pub const DEFAULT_SMOOTH_SECS: f64 = 4.0;
40pub const DEFAULT_NOVELTY_WINDOW_SECS: f64 = 8.0;
42
43#[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#[derive(Clone, Copy, Debug, PartialEq)]
89pub struct Given {
90 pub peaks: usize,
91 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
128pub enum Reads {
129 ClosedForm,
131 Either,
133 Samples,
135 Graph,
137 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
464pub 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 Loudness {
523 window_secs: f64,
524 },
525 Onsets,
526 Alias {
527 oversample: u32,
528 },
529 Harmony {
531 reading: Harmonic,
532 frame_secs: f64,
533 floor_db: f64,
534 },
535 Periodicity {
537 frame_secs: f64,
538 window_secs: f64,
539 curve: bool,
540 },
541 Modulation {
543 frame_secs: f64,
544 window_secs: f64,
545 bpm: Option<f64>,
546 },
547 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#[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#[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#[derive(Clone, Debug, PartialEq)]
608pub struct Atom {
609 pub var: Var,
610 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#[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 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 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 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}