1use serde::Serialize;
5
6use crate::audio::{
7 BAND_EDGE_CONVENTION, Band, HARD_LIMITED_CONVENTION, STEREO_CONVENTION, Stem, StereoScreen,
8 TEMPO_CONVENTION, Tempo,
9};
10use crate::confidence::{Confidence, Estimate, Level, NoSpread, Spread};
11use crate::dsp::{
12 BAND_CONVENTION, Descriptors, FLATNESS_CONVENTION, LEVEL_CONVENTION, SHAPE_CONVENTION, Stats,
13};
14use crate::estimate::envelope::{
15 ENVELOPE_CONVENTION, EnvelopeFlags, EnvelopeLayer, KNEE_CONVENTION, LEVEL_SPREAD_CONVENTION,
16 NoteEnvelope, PUMP_CONVENTION, Pump, TAIL_CONVENTION, TailVotes, lock_name,
17};
18use crate::estimate::pitch::{
19 DROP_CONVENTION, DropFit, GLIDE_CONVENTION, GlideFit, LINE_CONVENTION, PitchFlags, PitchLayer,
20 Rated, VIBRATO_CONVENTION, VibratoFit,
21};
22use crate::estimate::voices::{JOIN_CONVENTION, Join, VOICES_CONVENTION, VoicesLayer};
23use crate::notes::{FinalNotes, TICKS_PER_BEAT, TranscriptionRecord};
24use crate::preset::edits::Edit;
25use crate::preset::mapper::pitch_envelope::group_of;
26use crate::preset::mapper::{Mapped, Unrepresentable};
27use crate::report::NOTES_FILE;
28use crate::synth::Synth;
29use crate::units::{
30 Bpm, Cents, Correlation, Decibels, Degrees, Hz, Normalized, Ratio, Seconds, Semitones,
31};
32
33pub const SCHEMA: &str = "layers/1";
35
36const STATISTICS_CONVENTION: &str = "median and interquartile range over active frames, \
38quantiles by linear interpolation at q (n - 1) between the sorted values; a frame is active \
39when its RMS lies within 40 dB of the loudest frame";
40
41const NOTES_CONVENTION: &str = "the notes of notes.mid, one track on channel 1 at the used \
43tempo, 480 ticks per quarter note; a time becomes round(seconds * bpm / 60 * 480) ticks, and \
44onset_s and end_s are the written ticks in seconds; list_onset_s and list_end_s are the note in \
45the cleaned note list, or the earliest onset and latest end of the notes a pitch drop merged; key \
46is the nearest key of the pitch, of f_end for a pitch drop and of the mean pitch for a note a \
47vibrato covers over half its length, when within a semitone; only glides, drops and vibratos above \
48low confidence in a pitch group above low confidence count; a drop merges the notes that start \
49from 30 ms before its onset until five time constants after it, each within 30 ms of the end of \
50the one before and on a key from f_end to the drop's start pitch; end_rule names what set the end: \
51knee is the note-off knee; no_knee is 50 ms before the list end for a note the envelope layer read \
52without a knee; unread is the same cut for a note the envelope layer skipped, with no pad end; a \
53line is mono when the voices layer reads at most one voice at a time or counts none, unless the \
54notes step ran with --profile pad; pad is, for a note of a polyphonic line, where its amp proxy falls 20 dB below its \
55peak plus its sustain level, first after its knee or its peak, or, for a note the envelope layer \
56skipped, where its full-band level falls 20 dB below the median of the last 30 % of its held part, \
57from the first frame within 1 dB of its peak to 25 ms before its list end, first after its peak, \
58both searched no later than the list end; minimum is the \
59decoder's minimum length at the tempo by the BPM rule, when a knee, no-knee or pad cut would go \
60below it, and a note the list gives shorter keeps its length; next_note is the next note-on of a \
61mono line, which the minimum does not hold back; glide is 5 ms after the next note-on, rounded up \
62to whole ticks, at a glide join of a mono line, whose later note starts at the glide's start or at \
63its own onset, whichever comes first; no end lies after the list end, except at a glide join; of \
64the notes of a mono line that start on one tick only one stays, and dropped counts the others: a \
65note without the octave flag before one with it, then the longer, then the lower key; level_db is \
66the note's full-band level, as level_spread_db defines it, the highest of the notes a drop merged, \
67or, when that span holds no frame, the same level from its start to its end before the joins and \
68cuts of a mono line, on a mono line no later than the next later onset; \
69velocity is level when level_spread_db exceeds 3 dB: \
70the loudest note gets 127 and a note d dB under it round(127 * 10^(-d / 40)), at least 1, and a note \
71without a level 100; else constant, 100 for every note; octave_uncertain is the transcriber's \
72octave flag, null when none ran; bend_events counts the pitch bends: on a mono line, a note whose \
73f0 contour leaves its key by 50 cents or more, of one sign, over adjacent counted frames that span \
74the tracker's reach of the note, a quarter frame plus one period, outside every counted glide, drop \
75and vibrato span, gets a bend at each counted contour frame, clamped to 2 semitones, at the frame's audio time, its \
76track time less the track's lag of a quarter frame less half a period; a frame counts only when \
77what it reads, from a quarter frame before it to one period after it, lies inside the note's list \
78onset and end, each one model frame of 11.6 ms inward, and outside every other note of the list; \
79the bend is centred again at the note's end or the next note-on, whichever comes \
80first; a host must set a bend range of 2 semitones";
81
82pub fn layers_json(
98 stem: &Stem,
99 descriptors: &Descriptors,
100 synth: Synth,
101 (source, source_sha256): (&str, &str),
102 tempo: &Tempo,
103 (transcription, notes): (Option<&TranscriptionRecord>, Option<&FinalNotes>),
104 ((pitch, envelope, voices), mapped): ((&PitchLayer, &EnvelopeLayer, &VoicesLayer), &Mapped),
105) -> Result<String, serde_json::Error> {
106 let report = Layers {
107 schema: SCHEMA,
108 source,
109 source_sha256,
110 synth,
111 stem: StemGroup::new(stem, descriptors, tempo),
112 transcription,
113 notes: notes.map(NotesGroup::new),
114 pitch: PitchGroup::new(pitch, Mapping::of(mapped, "pitch")),
115 voices: VoicesGroup::new(voices, Mapping::of(mapped, "voices")),
116 envelope: EnvelopeGroup::new(envelope, Mapping::of(mapped, "envelope")),
117 conventions: Conventions::new(descriptors.frame_len),
118 };
119 let mut text = serde_json::to_string_pretty(&report)?;
120 text.push('\n');
121 Ok(text)
122}
123
124#[derive(Serialize)]
125struct Layers<'a> {
126 schema: &'static str,
127 source: &'a str,
129 source_sha256: &'a str,
132 synth: Synth,
133 stem: StemGroup<'a>,
134 transcription: Option<&'a TranscriptionRecord>,
136 notes: Option<NotesGroup>,
138 pitch: PitchGroup<'a>,
139 voices: VoicesGroup<'a>,
140 envelope: EnvelopeGroup<'a>,
141 conventions: Conventions,
142}
143
144#[derive(Serialize)]
146struct NotesGroup {
147 file: &'static str,
148 ticks_per_beat: u16,
149 velocity: &'static str,
150 bend_events: usize,
151 dropped: usize,
152 list: Vec<FinalRecord>,
153}
154
155#[derive(Serialize)]
156struct FinalRecord {
157 onset_s: Seconds,
158 end_s: Seconds,
159 list_onset_s: Seconds,
160 list_end_s: Seconds,
161 key: u8,
162 velocity: u8,
163 end_rule: &'static str,
164 level_db: Option<Decibels>,
165 octave_uncertain: Option<bool>,
166}
167
168impl NotesGroup {
169 fn new(notes: &FinalNotes) -> Self {
170 Self {
171 file: NOTES_FILE,
172 ticks_per_beat: TICKS_PER_BEAT,
173 velocity: if notes.level_velocity {
174 "level"
175 } else {
176 "constant"
177 },
178 bend_events: notes.bends.len(),
179 dropped: notes.dropped,
180 list: notes
181 .notes
182 .iter()
183 .map(|n| FinalRecord {
184 onset_s: notes.seconds(n.start),
185 end_s: notes.seconds(n.end),
186 list_onset_s: n.list_onset,
187 list_end_s: n.list_end,
188 key: n.key,
189 velocity: n.velocity.get(),
190 end_rule: n.end_rule.name(),
191 level_db: n.level,
192 octave_uncertain: n.octave_uncertain,
193 })
194 .collect(),
195 }
196 }
197}
198
199#[derive(Serialize)]
201struct EnvelopeGroup<'a> {
202 values: Option<EnvelopeValues<'a>>,
203 confidence: &'a Confidence<NoSpread>,
204 flags: EnvelopeFlags,
205 needs_render: bool,
206 #[serde(flatten)]
207 mapping: Mapping<'a>,
208}
209
210#[derive(Serialize)]
213struct Mapping<'a> {
214 suggested_edits: Vec<EditRecord<'a>>,
215 unrepresentable: Vec<Miss<'a>>,
216}
217
218#[derive(Serialize)]
221struct EditRecord<'a> {
222 #[serde(flatten)]
223 edit: &'a Edit,
224 uncalibrated: bool,
225 confidence: Level,
226}
227
228#[derive(Serialize)]
230struct Miss<'a> {
231 #[serde(flatten)]
232 entry: &'a Unrepresentable,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 residual: Option<f64>,
235}
236
237impl<'a> Mapping<'a> {
238 fn of(mapped: &'a Mapped, group: &str) -> Self {
239 Self {
240 suggested_edits: mapped
241 .edits
242 .iter()
243 .filter(|edit| edit.edit.reason.as_deref() == Some(group))
244 .map(|edit| EditRecord {
245 edit: &edit.edit,
246 uncalibrated: edit.uncalibrated,
247 confidence: edit.confidence,
248 })
249 .collect(),
250 unrepresentable: mapped
251 .unrepresentable
252 .iter()
253 .filter(|entry| group_of(&entry.field) == Some(group))
254 .map(|entry| Miss {
255 entry,
256 residual: entry.residual(),
257 })
258 .collect(),
259 }
260 }
261}
262
263#[derive(Serialize)]
264struct EnvelopeValues<'a> {
265 attack_s: Statistic<'a, Seconds>,
266 hold_s: Statistic<'a, Seconds>,
267 decay_s: Statistic<'a, Seconds>,
268 sustain_db: Statistic<'a, Decibels>,
269 note_off_offset_s: Statistic<'a, Seconds>,
270 release_s: Statistic<'a, Seconds>,
271 tail_rt60_s: Statistic<'a, Seconds>,
272 tail_weight: Statistic<'a, Ratio>,
273 release_confidence: Option<Level>,
274 tail_votes: TailVotes,
275 tail_vote_count: usize,
276 tail_is_reverb: bool,
277 level_spread_db: Value<'a, Decibels, NoSpread>,
278 ripple_db: Option<Decibels>,
279 fit_residual_db: Option<Decibels>,
280 n_notes_used: usize,
281 overlap_fraction: f64,
282 pump_depth_db: Option<Decibels>,
283 pump_recovery_s: Option<Seconds>,
284 pump_tau_ms: Option<f64>,
285 pump_locked_to: Option<&'static str>,
286 pump_beats: usize,
287 pump_bpm: Option<Bpm>,
288 notes: Vec<NoteRecord>,
289}
290
291#[derive(Serialize)]
292struct NoteRecord {
293 onset_s: Seconds,
294 peak_db: Decibels,
295 attack_s: Option<Seconds>,
296 attack_upper_bound: bool,
297 knee_s: Option<Seconds>,
298 pooled: bool,
299}
300
301impl<'a> EnvelopeGroup<'a> {
302 fn new(layer: &'a EnvelopeLayer, mapping: Mapping<'a>) -> Self {
303 let pump = layer.pump.as_ref();
304 Self {
305 values: layer.confidence.is_estimate().then(|| EnvelopeValues {
306 attack_s: Statistic::of(&layer.attack),
307 hold_s: Statistic::of(&layer.hold),
308 decay_s: Statistic::of(&layer.decay),
309 sustain_db: Statistic::of(&layer.sustain),
310 note_off_offset_s: Statistic::of(&layer.note_off_offset),
311 release_s: Statistic::of(&layer.release),
312 tail_rt60_s: Statistic::of(&layer.tail_rt60),
313 tail_weight: Statistic::of(&layer.tail_weight),
314 release_confidence: layer.release_confidence,
315 tail_votes: layer.tail_votes,
316 tail_vote_count: layer.tail_votes.count(),
317 tail_is_reverb: false,
318 level_spread_db: Value::of(&layer.level_spread),
319 ripple_db: layer.ripple,
320 fit_residual_db: layer.residual,
321 n_notes_used: layer.pooled.iter().filter(|&&p| p).count(),
322 overlap_fraction: layer.overlap_fraction,
323 pump_depth_db: pump.map(|p| p.depth),
324 pump_recovery_s: pump.and_then(|p| p.back),
325 pump_tau_ms: pump.and_then(|p| p.tau).map(|t| 1000.0 * t.get()),
326 pump_locked_to: pump.map(|p| lock_name(p.lock)),
327 pump_beats: pump.map_or(0, |p| p.beats),
328 pump_bpm: pump.map(Pump::bpm),
329 notes: layer
330 .notes
331 .iter()
332 .zip(&layer.pooled)
333 .map(|(n, &pooled)| NoteRecord::of(n, pooled))
334 .collect(),
335 }),
336 confidence: &layer.confidence,
337 flags: layer.flags,
338 needs_render: false,
339 mapping,
340 }
341 }
342}
343
344impl NoteRecord {
345 fn of(note: &NoteEnvelope, pooled: bool) -> Self {
346 Self {
347 onset_s: note.onset,
348 peak_db: note.peak_db,
349 attack_s: note.attack,
350 attack_upper_bound: note.attack_upper_bound,
351 knee_s: note.knee,
352 pooled,
353 }
354 }
355}
356
357#[derive(Serialize)]
359struct VoicesGroup<'a> {
360 values: Option<VoicesValues<'a>>,
361 confidence: &'a Confidence<NoSpread>,
362 flags: VoicesFlags,
363 needs_render: bool,
364 #[serde(flatten)]
365 mapping: Mapping<'a>,
366}
367
368#[derive(Serialize)]
370struct VoicesFlags {}
371
372impl<'a> VoicesGroup<'a> {
373 fn new(layer: &'a VoicesLayer, mapping: Mapping<'a>) -> Self {
374 Self {
375 values: layer.confidence.is_estimate().then(|| VoicesValues {
376 mono: layer.mono,
377 max_polyphony: layer.max_polyphony,
378 mean_polyphony: layer.mean_polyphony,
379 joins: layer.joins.iter().map(JoinRecord::of).collect(),
380 }),
381 confidence: &layer.confidence,
382 flags: VoicesFlags {},
383 needs_render: false,
384 mapping,
385 }
386 }
387}
388
389#[derive(Serialize)]
390struct VoicesValues<'a> {
391 mono: bool,
392 max_polyphony: usize,
393 mean_polyphony: f64,
394 joins: Vec<JoinRecord<'a>>,
395}
396
397#[derive(Serialize)]
398struct JoinRecord<'a> {
399 time_s: Seconds,
400 gap_ms: f64,
401 dip_db: Option<Decibels>,
402 centroid_change: Option<f64>,
403 legato: Option<bool>,
404 confidence: &'a Confidence<NoSpread>,
405}
406
407impl<'a> JoinRecord<'a> {
408 fn of(join: &'a Join) -> Self {
409 Self {
410 time_s: join.time,
411 gap_ms: 1000.0 * join.gap,
412 dip_db: join.dip,
413 centroid_change: join.centroid_change,
414 legato: join.legato,
415 confidence: &join.confidence,
416 }
417 }
418}
419
420#[derive(Serialize)]
422struct PitchGroup<'a> {
423 values: Option<PitchValues<'a>>,
424 confidence: &'a Confidence<NoSpread>,
425 flags: PitchFlags,
426 needs_render: bool,
427 #[serde(flatten)]
428 mapping: Mapping<'a>,
429}
430
431impl<'a> PitchGroup<'a> {
432 fn new(layer: &'a PitchLayer, mapping: Mapping<'a>) -> Self {
433 Self {
434 values: layer.confidence.is_estimate().then(|| PitchValues {
435 glides: layer.glides.iter().map(GlideRecord::of).collect(),
436 drops: layer.drops.iter().map(DropRecord::of).collect(),
437 vibrato: layer.vibrato.iter().map(VibratoRecord::of).collect(),
438 }),
439 confidence: &layer.confidence,
440 flags: layer.flags,
441 needs_render: false,
442 mapping,
443 }
444 }
445}
446
447#[derive(Serialize)]
448struct PitchValues<'a> {
449 glides: Vec<GlideRecord<'a>>,
450 drops: Vec<DropRecord<'a>>,
451 vibrato: Vec<VibratoRecord<'a>>,
452}
453
454#[derive(Serialize)]
455struct GlideRecord<'a> {
456 start_s: Seconds,
457 interval_st: Semitones,
458 time_s: Seconds,
459 curve: f64,
460 from_silence: bool,
461 confidence: &'a Confidence<NoSpread>,
462}
463
464impl<'a> GlideRecord<'a> {
465 fn of(glide: &'a Rated<GlideFit>) -> Self {
466 let g = &glide.value;
467 Self {
468 start_s: g.start,
469 interval_st: g.interval,
470 time_s: g.time,
471 curve: g.curve,
472 from_silence: g.from_silence,
473 confidence: &glide.confidence,
474 }
475 }
476}
477
478#[derive(Serialize)]
479struct DropRecord<'a> {
480 onset_s: Seconds,
481 f_end_hz: Hz,
482 df_st: Option<Semitones>,
483 df_hz: f64,
484 tau_s: Seconds,
485 residual: f64,
486 confidence: &'a Confidence<NoSpread>,
487}
488
489impl<'a> DropRecord<'a> {
490 fn of(drop: &'a Rated<DropFit>) -> Self {
491 let d = &drop.value;
492 Self {
493 onset_s: d.onset,
494 f_end_hz: d.f_end,
495 df_st: d.df(),
496 df_hz: d.df_hz,
497 tau_s: d.tau,
498 residual: d.residual,
499 confidence: &drop.confidence,
500 }
501 }
502}
503
504#[derive(Serialize)]
505struct VibratoRecord<'a> {
506 start_s: Seconds,
507 end_s: Seconds,
508 rate_hz: Hz,
509 extent_ct: Cents,
510 extent_raw_ct: Cents,
511 extent_factor: f64,
512 share: f64,
513 confidence: &'a Confidence<NoSpread>,
514}
515
516impl<'a> VibratoRecord<'a> {
517 fn of(vibrato: &'a Rated<VibratoFit>) -> Self {
518 let v = &vibrato.value;
519 Self {
520 start_s: v.start,
521 end_s: v.end,
522 rate_hz: v.rate,
523 extent_ct: v.extent(),
524 extent_raw_ct: v.extent_raw,
525 extent_factor: v.factor,
526 share: v.share,
527 confidence: &vibrato.confidence,
528 }
529 }
530}
531
532#[derive(Serialize)]
533struct StemGroup<'a> {
534 bpm: Bpm,
535 bpm_given: Bpm,
536 bpm_refined: Value<'a, Bpm, NoSpread>,
537 duration_s: Seconds,
538 sample_rate_hz: u32,
539 band_edge_hz: Value<'a, Hz, Hz>,
540 hard_limited: bool,
541 peak_dbfs: Value<'a, Decibels, Decibels>,
542 rms_dbfs: Value<'a, Decibels, Decibels>,
543 crest_db: Value<'a, Decibels, Decibels>,
544 centroid_hz: Statistic<'a, Hz>,
545 centroid_over_f0: Option<Statistic<'a, Ratio>>,
547 spread_hz: Statistic<'a, Hz>,
548 rolloff85_hz: Statistic<'a, Hz>,
549 rolloff95_hz: Statistic<'a, Hz>,
550 flatness: Statistic<'a, Normalized>,
551 band_share: BandShares<'a>,
552 stereo: Stereo<'a>,
553}
554
555impl<'a> StemGroup<'a> {
556 fn new(stem: &'a Stem, descriptors: &'a Descriptors, tempo: &'a Tempo) -> Self {
557 Self {
558 bpm: stem.grid().bpm(),
559 bpm_given: tempo.given(),
560 bpm_refined: Value::of(tempo.refined()),
561 duration_s: stem.duration(),
562 sample_rate_hz: stem.sample_rate().get(),
563 band_edge_hz: Value::of(stem.band_edge()),
564 hard_limited: stem.hard_limited(),
565 peak_dbfs: Value::of(&descriptors.peak_dbfs),
566 rms_dbfs: Value::of(&descriptors.rms_dbfs),
567 crest_db: Value::of(&descriptors.crest_db),
568 centroid_hz: Statistic::of(&descriptors.centroid),
569 centroid_over_f0: stem
570 .median_f0()
571 .map(|_| Statistic::of(&descriptors.centroid_over_f0)),
572 spread_hz: Statistic::of(&descriptors.spread),
573 rolloff85_hz: Statistic::of(&descriptors.rolloff85),
574 rolloff95_hz: Statistic::of(&descriptors.rolloff95),
575 flatness: Statistic::of(&descriptors.flatness),
576 band_share: BandShares {
577 value: descriptors.band_share.value().map(|shares| {
578 shares
579 .iter()
580 .map(|share| BandPart {
581 lo_hz: share.lo,
582 hi_hz: share.hi,
583 share: share.share,
584 })
585 .collect()
586 }),
587 confidence: descriptors.band_share.confidence(),
588 },
589 stereo: Stereo::of(stem.stereo()),
590 }
591 }
592}
593
594#[derive(Serialize)]
596#[serde(bound = "V: Serialize")]
597struct Value<'a, V, S: Spread> {
598 value: Option<&'a V>,
599 confidence: &'a Confidence<S>,
600}
601
602impl<'a, V, S: Spread> Value<'a, V, S> {
603 fn of(estimate: &'a Estimate<V, S>) -> Self {
604 Self {
605 value: estimate.value(),
606 confidence: estimate.confidence(),
607 }
608 }
609}
610
611#[derive(Serialize)]
614struct Statistic<'a, T: Spread> {
615 median: Option<T>,
616 iqr: Option<T>,
617 confidence: &'a Confidence<T>,
618}
619
620impl<'a, T: Spread> Statistic<'a, T> {
621 fn of(estimate: &'a Estimate<Stats<T>, T>) -> Self {
622 Self {
623 median: estimate.value().map(Stats::median),
624 iqr: estimate.value().map(Stats::iqr),
625 confidence: estimate.confidence(),
626 }
627 }
628}
629
630#[derive(Serialize)]
631struct BandShares<'a> {
632 value: Option<Vec<BandPart>>,
633 confidence: &'a Confidence<Normalized>,
634}
635
636#[derive(Serialize)]
637struct BandPart {
638 lo_hz: Hz,
639 hi_hz: Hz,
640 share: Normalized,
641}
642
643#[derive(Serialize)]
644struct Stereo<'a> {
645 full: StereoBand,
646 sub: StereoBand,
647 low_mid: StereoBand,
648 high: StereoBand,
649 confidence: &'a Confidence<NoSpread>,
650}
651
652impl<'a> Stereo<'a> {
653 fn of(screen: &'a StereoScreen) -> Self {
654 let band = |band| {
655 let stats = screen.band(band);
656 StereoBand {
657 rho: stats.rho,
658 sm_db: stats.sm_db,
659 ipd_deg: stats.ipd_deg,
660 }
661 };
662 Self {
663 full: band(Band::Full),
664 sub: band(Band::Sub),
665 low_mid: band(Band::LowMid),
666 high: band(Band::High),
667 confidence: screen.confidence(),
668 }
669 }
670}
671
672#[derive(Serialize)]
673struct StereoBand {
674 rho: Option<Correlation>,
675 sm_db: Option<Decibels>,
676 ipd_deg: Option<Degrees>,
677}
678
679#[derive(Serialize)]
681struct Conventions {
682 frame_size: usize,
683 statistics: &'static str,
684 spectral: &'static str,
685 bpm: &'static str,
686 bpm_given: &'static str,
687 bpm_refined: &'static str,
688 duration_s: &'static str,
689 sample_rate_hz: &'static str,
690 band_edge_hz: &'static str,
691 hard_limited: &'static str,
692 peak_dbfs: &'static str,
693 rms_dbfs: &'static str,
694 crest_db: &'static str,
695 centroid_hz: &'static str,
696 centroid_over_f0: &'static str,
697 spread_hz: &'static str,
698 rolloff85_hz: &'static str,
699 rolloff95_hz: &'static str,
700 flatness: &'static str,
701 band_share: &'static str,
702 stereo: &'static str,
703 confidence: &'static str,
704 pitch: &'static str,
705 voices: &'static str,
706 joins: &'static str,
707 glides: &'static str,
708 drops: &'static str,
709 sub_band_line: &'static str,
710 vibrato: &'static str,
711 envelope: &'static str,
712 note_off: &'static str,
713 tail: &'static str,
714 level_spread_db: &'static str,
715 pump: &'static str,
716 notes: &'static str,
717}
718
719impl Conventions {
720 fn new(frame_size: usize) -> Self {
721 Self {
722 frame_size,
723 statistics: STATISTICS_CONVENTION,
724 spectral: "magnitude weights for shape, power for band shares; periodic Hann frames \
725of frame_size samples, hop frame_size / 4, centred and zero-padded; at 44.1 kHz frame_size is \
7268192 when the note list has a median f0 below 100 Hz, else 4096, and 4096 without a note list; at \
727another rate it is the power of two nearest, in log, to that length times the rate over 44100 Hz, \
728the same at 32 and 48 kHz and twice it at 88.2 and 96 kHz",
729 bpm: "the tempo of the beat grid and of every later step: bpm_refined when its \
730confidence is high, else bpm_given",
731 bpm_given: "given by --bpm",
732 bpm_refined: TEMPO_CONVENTION,
733 duration_s: "decoded frames over the sample rate",
734 sample_rate_hz: "the native rate of the file; no resampling",
735 band_edge_hz: BAND_EDGE_CONVENTION,
736 hard_limited: HARD_LIMITED_CONVENTION,
737 peak_dbfs: LEVEL_CONVENTION,
738 rms_dbfs: LEVEL_CONVENTION,
739 crest_db: LEVEL_CONVENTION,
740 centroid_hz: SHAPE_CONVENTION,
741 centroid_over_f0: "the centroid of each active frame over the median f0 of the note \
742list, MIDI pitch to hertz with A4 = 440 Hz; null without a note list",
743 spread_hz: SHAPE_CONVENTION,
744 rolloff85_hz: SHAPE_CONVENTION,
745 rolloff95_hz: SHAPE_CONVENTION,
746 flatness: FLATNESS_CONVENTION,
747 band_share: BAND_CONVENTION,
748 stereo: STEREO_CONVENTION,
749 confidence: "level, reasons and spread of each estimate; the spread of a statistic \
750is its interquartile range and that of the band edge one bin width, in the unit of the value; \
751estimate false means no value, with the reasons why",
752 voices: VOICES_CONVENTION,
753 joins: JOIN_CONVENTION,
754 pitch: "the pitch layer of a monophonic line, read from the f0 track, the note list \
755and the sub-band line; values is null when the line plays chords or has no notes; every time is \
756audio time from the start of the stem",
757 glides: GLIDE_CONVENTION,
758 drops: DROP_CONVENTION,
759 sub_band_line: LINE_CONVENTION,
760 vibrato: VIBRATO_CONVENTION,
761 envelope: ENVELOPE_CONVENTION,
762 note_off: KNEE_CONVENTION,
763 tail: TAIL_CONVENTION,
764 level_spread_db: LEVEL_SPREAD_CONVENTION,
765 pump: PUMP_CONVENTION,
766 notes: NOTES_CONVENTION,
767 }
768 }
769}