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