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