experimental SVG-based video rendering engine made for music videos. React to MIDI or arbitrary signals from your DAW through "probe" VSTs
1use super::sync::SyncData;
2use serde::{Deserialize, Serialize};
3use std::{
4 collections::HashMap,
5 fmt::Display,
6 fs::File,
7 io::{BufReader, Write},
8 path::{Path, PathBuf},
9};
10
11#[derive(Debug, Deserialize, Serialize)]
12pub struct Stem {
13 pub amplitude_db: Vec<f32>,
14 /// max amplitude of this stem
15 pub amplitude_max: f32,
16 /// in milliseconds
17 pub duration_ms: usize,
18
19 #[serde(default)]
20 pub notes: HashMap<usize, Vec<Note>>,
21
22 #[serde(default)]
23 pub name: String,
24}
25
26impl Stem {
27 pub fn load_from_cbor(path: &str) -> Stem {
28 let file = File::open(path).unwrap();
29 let reader = BufReader::new(file);
30 let stem: Stem = serde_cbor::from_reader(reader).unwrap();
31 stem
32 }
33
34 pub fn save_to_cbor(&self, path: &str) {
35 let mut file = File::create(path).unwrap();
36 let bytes = serde_cbor::to_vec(&self).unwrap();
37 file.write_all(&bytes).unwrap();
38 }
39
40 pub fn cbor_path(path: PathBuf, name: String) -> String {
41 format!(
42 "{}/{}.cbor",
43 path.parent().unwrap_or(Path::new("./")).to_string_lossy(),
44 name,
45 )
46 }
47}
48
49#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
50pub struct Note {
51 pub pitch: u8,
52 pub velocity: u8,
53 pub tick: u32,
54}
55
56impl Note {
57 pub fn symbol(&self) -> String {
58 let scale = vec![
59 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "B",
60 ];
61 let (octave, scale_index) = (
62 self.pitch as usize / scale.len(),
63 self.pitch as usize % scale.len(),
64 );
65 format!("{}{}", scale[scale_index], octave)
66 }
67
68 pub fn is_off(&self) -> bool {
69 self.velocity == 0
70 }
71
72 pub fn is_on(&self) -> bool {
73 !self.is_off()
74 }
75}
76
77impl Display for SyncData {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(
80 f,
81 "SyncData @ {} bpm, {} stems",
82 self.bpm.map_or("?".to_string(), |bpm| bpm.to_string()),
83 self.stems.len()
84 )
85 }
86}
87
88#[derive(Debug, Clone, Default)]
89pub struct AudioSyncPaths {
90 pub stems: String,
91 pub landmarks: String,
92 pub complete: String,
93 pub bpm: String,
94 pub midi: String,
95}
96
97pub type AudioStemToMIDITrack<'a> = HashMap<&'a str, &'a str>;
98
99pub enum MusicalDurationUnit {
100 Beats,
101 Halves,
102 Thirds,
103 Quarters,
104 Eighths,
105 Sixteenths,
106}
107
108#[derive(Debug)]
109pub struct StemAtInstant {
110 pub amplitude: f32,
111 pub amplitude_max: f32,
112 pub duration: usize,
113 pub velocity_max: u8,
114 pub notes: Vec<Note>,
115 /// How many instants (individual ms values)
116 /// have occurred where this stem played notes
117 /// before this instant (not counting the current instant)
118 pub playcount: usize,
119}
120impl StemAtInstant {
121 pub fn amplitude_relative(&self) -> f32 {
122 self.amplitude / self.amplitude_max
123 }
124
125 pub fn velocity_relative(&self) -> f32 {
126 self.notes.iter().map(|n| n.velocity).sum::<u8>() as f32
127 / self.notes.len() as f32
128 / self.velocity_max as f32
129 }
130}