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