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