load scenes from project

This commit is contained in:
🪞👃🪞 2024-07-08 20:18:44 +03:00
parent f1f812d0fb
commit dff42ca5a7
5 changed files with 202 additions and 301 deletions

View file

@ -2,6 +2,17 @@ use crate::{core::*, model::*, App};
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
macro_rules! edn {
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
match $edn { $($pat => $expr),* }
};
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
for $edn in $args {
edn!($edn { $($pat => $expr),* })
}
};
}
impl App {
pub fn load_edn (&mut self, mut src: &str) -> Usually<()> {
loop {
@ -27,11 +38,7 @@ impl App {
fn load_edn_one <'e> (&mut self, edn: Edn<'e>) -> Usually<()> {
match edn {
Edn::List(items) => {
let head = items.get(0);
match head {
Some(Edn::Symbol("track")) => {
Track::load_edn(self, &items[1..])?;
},
match items.get(0) {
Some(Edn::Symbol("bpm")) => {
match items.get(1) {
Some(Edn::Int(b)) => self.timebase.set_bpm(*b as f64),
@ -39,7 +46,13 @@ impl App {
_ => panic!("unspecified bpm")
}
},
_ => panic!("unexpected edn: {head:?}")
Some(Edn::Symbol("scene")) => {
Scene::load_edn(self, &items[1..])?;
},
Some(Edn::Symbol("track")) => {
Track::load_edn(self, &items[1..])?;
},
_ => panic!("unexpected edn: {:?}", items.get(0))
}
},
_ => {
@ -50,6 +63,35 @@ impl App {
}
}
impl Scene {
fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> {
let mut name = None;
let mut clips = vec![];
edn!(edn in args {
Edn::Map(map) => {
let key = map.get(&Edn::Key(":name"));
if let Some(Edn::Str(n)) = key {
name = Some(String::from(*n));
} else {
panic!("unexpected key in scene '{name:?}': {key:?}")
}
},
Edn::Symbol("_") => {
clips.push(None);
},
Edn::Int(i) => {
clips.push(Some(*i as usize));
},
_ => panic!("unexpected in scene '{name:?}': {edn:?}")
});
app.add_scene_with_clips(name.as_deref(), &clips)
//for edn in args {
//match end {
//}
//}
}
}
impl Track {
fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> {
let ppq = app.timebase.ppq() as usize;
@ -57,32 +99,30 @@ impl Track {
let mut gain = 0.0f64;
let mut devices: Vec<JackDevice> = vec![];
let mut phrases: Vec<Phrase> = vec![];
for edn in args {
match edn {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Double(g)) = map.get(&Edn::Key(":gain")) {
gain = f64::from(*g)
}
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Double(g)) = map.get(&Edn::Key(":gain")) {
gain = f64::from(*g)
}
},
Edn::List(args) => match args.get(0) {
Some(Edn::Symbol("phrase")) => {
phrases.push(Phrase::load_edn(ppq, &args[1..])?)
},
Edn::List(args) => match args.get(0) {
Some(Edn::Symbol("phrase")) => {
phrases.push(Phrase::load_edn(ppq, &args[1..])?)
},
Some(Edn::Symbol("sampler")) => {
devices.push(Sampler::load_edn(&args[1..])?)
},
Some(Edn::Symbol("lv2")) => {
devices.push(LV2Plugin::load_edn(&args[1..])?)
},
None => panic!("empty list track {name}"),
_ => panic!("unexpected in track {name}: {:?}", args.get(0).unwrap())
Some(Edn::Symbol("sampler")) => {
devices.push(Sampler::load_edn(&args[1..])?)
},
_ => {}
}
}
Some(Edn::Symbol("lv2")) => {
devices.push(LV2Plugin::load_edn(&args[1..])?)
},
None => panic!("empty list track {name}"),
_ => panic!("unexpected in track {name}: {:?}", args.get(0).unwrap())
},
_ => {}
});
let (left, right) = (app.audio_out(0), app.audio_out(1));
app.add_track_with_cb(Some(name.as_str()), move|_, track|{
for phrase in phrases {
@ -113,54 +153,52 @@ impl Phrase {
let mut beats = 0usize;
let mut steps = 0usize;
let mut data = BTreeMap::new();
for edn in args {
match edn {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Int(b)) = map.get(&Edn::Key(":beats")) {
beats = *b as usize;
}
if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) {
steps = *s as usize;
}
},
Edn::List(args) => {
let time = (match args.get(0) {
Some(Edn::Key(text)) => text[1..].parse::<f64>()?,
Some(Edn::Int(i)) => *i as f64,
Some(Edn::Double(f)) => f64::from(*f),
_ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)),
} * beats as f64 * ppq as f64 / steps as f64) as usize;
for edn in args[1..].iter() {
match edn {
Edn::List(args) => if let (
Some(Edn::Int(key)),
Some(Edn::Int(vel)),
) = (
args.get(0),
args.get(1),
) {
if !data.contains_key(&time) {
data.insert(time, vec![]);
}
let (key, vel) = (
u7::from((*key as u8).min(127)),
u7::from((*vel as u8).min(127))
);
data.get_mut(&time).unwrap()
.push(MidiMessage::NoteOn { key, vel })
} else {
panic!("unexpected list in phrase '{name}'")
},
_ => panic!("unexpected in phrase '{name}': {edn:?}")
}
if let Some(Edn::Int(b)) = map.get(&Edn::Key(":beats")) {
beats = *b as usize;
}
if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) {
steps = *s as usize;
}
},
Edn::List(args) => {
let time = (match args.get(0) {
Some(Edn::Key(text)) => text[1..].parse::<f64>()?,
Some(Edn::Int(i)) => *i as f64,
Some(Edn::Double(f)) => f64::from(*f),
_ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)),
} * beats as f64 * ppq as f64 / steps as f64) as usize;
for edn in args[1..].iter() {
match edn {
Edn::List(args) => if let (
Some(Edn::Int(key)),
Some(Edn::Int(vel)),
) = (
args.get(0),
args.get(1),
) {
if !data.contains_key(&time) {
data.insert(time, vec![]);
}
let (key, vel) = (
u7::from((*key as u8).min(127)),
u7::from((*vel as u8).min(127))
);
data.get_mut(&time).unwrap()
.push(MidiMessage::NoteOn { key, vel })
} else {
panic!("unexpected list in phrase '{name}'")
},
_ => panic!("unexpected in phrase '{name}': {edn:?}")
}
}
},
_ => panic!("unexpected in phrase '{name}': {edn:?}"),
}
}
}
},
_ => panic!("unexpected in phrase '{name}': {edn:?}"),
});
Ok(Self::new(&name, beats * ppq, Some(data)))
}
}
@ -170,30 +208,28 @@ impl Sampler {
let mut name = String::new();
let mut dir = String::new();
let mut samples = BTreeMap::new();
for edn in args {
match edn {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
dir = String::from(*n);
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
dir = String::from(*n);
}
},
Edn::List(args) => match args.get(0) {
Some(Edn::Symbol("sample")) => {
let (midi, sample) = Sample::load_edn(&dir, &args[1..])?;
if let Some(midi) = midi {
samples.insert(midi, sample);
} else {
panic!("sample without midi binding: {}", sample.name);
}
},
Edn::List(args) => match args.get(0) {
Some(Edn::Symbol("sample")) => {
let (midi, sample) = Sample::load_edn(&dir, &args[1..])?;
if let Some(midi) = midi {
samples.insert(midi, sample);
} else {
panic!("sample without midi binding: {}", sample.name);
}
},
_ => panic!("unexpected in sampler {name}: {args:?}")
},
_ => panic!("unexpected in sampler {name}: {edn:?}")
}
}
_ => panic!("unexpected in sampler {name}: {args:?}")
},
_ => panic!("unexpected in sampler {name}: {edn:?}")
});
Self::new(&name, Some(samples))
}
}
@ -204,25 +240,23 @@ impl Sample {
let mut file = String::new();
let mut midi = None;
let mut start = 0usize;
for edn in args.iter() {
match edn {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
file = String::from(*f);
}
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
start = *i as usize;
}
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
midi = Some(u7::from(*m as u8));
}
},
_ => panic!("unexpected in sample {name}"),
}
}
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
file = String::from(*f);
}
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
start = *i as usize;
}
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
midi = Some(u7::from(*m as u8));
}
},
_ => panic!("unexpected in sample {name}"),
});
let (end, data) = read_sample_data(&format!("{dir}/{file}"))?;
Ok((midi, Self::new(&name, start, end, data)))
}
@ -232,19 +266,17 @@ impl LV2Plugin {
fn load_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
let mut name = String::new();
let mut path = String::new();
for edn in args.iter() {
match edn {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
path = String::from(*p);
}
},
_ => panic!("unexpected in lv2 '{name}'"),
}
}
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
path = String::from(*p);
}
},
_ => panic!("unexpected in lv2 '{name}'"),
});
Plugin::lv2(&name, &path)
}
}

View file

@ -22,9 +22,8 @@ use crate::{core::*, model::*};
/// Application entrypoint.
pub fn main () -> Usually<()> {
// Construct app
let mut app = App::default();
// Load config
let xdg = Arc::new(microxdg::XdgApp::new("tek")?);
app.xdg = Some(xdg.clone());
@ -33,9 +32,7 @@ pub fn main () -> Usually<()> {
}
let midi_from = ["nanoKEY Studio.*capture.*"];
let audio_into = ["Komplete.+:playback_FL", "Komplete.+:playback_FR"];
// Init
let ppq = app.timebase.ppq() as usize;
// Init view
app.track_cursor = 1;
app.scene_cursor = 1;
app.note_start = 12;
@ -45,13 +42,11 @@ pub fn main () -> Usually<()> {
// Start main loop
app.run(Some(|app: Arc<RwLock<App>>|{
let mut state = app.write().unwrap();
// Start JACK and setup device graph
let jack = jack_run("tek", &app)?;
let client = jack.as_client();
state.transport = Some(client.transport());
state.midi_in = Some(client.register_port("midi-in", MidiIn)?);
let _ = midi_from
.iter()
.map(|name|client
@ -65,7 +60,6 @@ pub fn main () -> Usually<()> {
})
.collect::<Usually<()>>())
.collect::<Usually<()>>()?;
state.audio_outs = audio_into
.iter()
.map(|name|client
@ -76,169 +70,9 @@ pub fn main () -> Usually<()> {
.filter_map(|x|x)
.map(Arc::new)
.collect();
state.jack = Some(jack);
// Load project
state.load_edn(include_str!("../demos/project.edn"))?;
//state.add_track_with_cb(Some("Drums"), |_, track|{
//track.add_device_with_cb(Sampler::new("Sampler", Some(BTreeMap::from([
//sample!(34, "808", "/home/user/Lab/Music/pak/808.wav"),
//sample!(35, "Kick1", "/home/user/Lab/Music/pak/kik.wav"),
//sample!(36, "Kick2", "/home/user/Lab/Music/pak/kik2.wav"),
//sample!(38, "Snare1", "/home/user/Lab/Music/pak/sna.wav"),
//sample!(40, "Snare2", "/home/user/Lab/Music/pak/sna2.wav"),
//sample!(42, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
//sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh2.wav"),
//])))?, |track, device|{
//device.connect_midi_in(0, &track.midi_out.clone_unowned())?;
//if let Some(Some(left)) = audio_outs.get(0) {
//device.connect_audio_out(0, left)?;
//}
//if let Some(Some(right)) = audio_outs.get(0) {
//device.connect_audio_out(1, right)?;
//}
//Ok(())
//})?;
////track.add_device_with_cb(Plugin::lv2(
////"Panagement",
////"file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2"
////)?, |track, device|{
////device.connect_audio_in(0, &track.devices[0].audio_outs()?[0])?;
////device.connect_audio_in(0, &track.devices[0].audio_outs()?[1])?;
////if let Some(Some(left)) = audio_outs.get(0) {
////device.connect_audio_out(0, left)?;
////}
////if let Some(Some(right)) = audio_outs.get(0) {
////device.connect_audio_out(1, right)?;
////}
////Ok(())
////})?;
//track.sequence = Some(1); // FIXME
//track.add_phrase("4 kicks", ppq * 4, Some(phrase! {
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//}));
//track.add_phrase("5 kicks", ppq * 4, Some(phrase! {
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//14 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//}));
//track.add_phrase("D-Beat", ppq * 4, Some(phrase! {
//00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//02 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
//04 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
//06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//08 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
//10 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
//12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//13 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
//14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//15 * ppq/4 => MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
//00 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
//04 * ppq/4 => MidiMessage::NoteOn { key: 38.into(), vel: 100.into() },
//08 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
//10 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() },
//12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
//}));
//track.add_phrase("Garage", ppq * 4, Some(phrase! {
//00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//01 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//02 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//03 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//04 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//07 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//09 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//10 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//00 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() },
//02 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
//07 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
//04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
//11 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
//11 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() },
//12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
//}));
//Ok(())
//})?;
//state.add_track_with_cb(Some("Bass"), |_, track|{
//track.add_device_with_cb(Plugin::lv2(
//"Odin2",
//"file:///home/user/.lv2/Odin2.lv2"
//)?, |track, device|{
//device.connect_midi_in(0, &track.midi_out.clone_unowned())?;
//if let Some(Some(left)) = audio_outs.get(0) {
//device.connect_audio_out(0, left)?;
//}
//if let Some(Some(right)) = audio_outs.get(0) {
//device.connect_audio_out(1, right)?;
//}
//Ok(())
//})?;
//track.sequence = Some(0); // FIXME
//track.add_phrase("Offbeat", ppq * 4, Some(phrase! {
////00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
////02 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
////04 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
////06 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
////08 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
////10 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
////12 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
////14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
//}));
//track.add_phrase("Custom1", ppq * 4, None);
//track.add_phrase("Custom2", ppq * 4, None);
//track.add_phrase("Custom3", ppq * 4, None);
//track.add_phrase("Custom4", ppq * 4, None);
//Ok(())
//})?;
//state.add_track_with_cb(Some("Lead"), |_, track|{
//track.add_device_with_cb(Plugin::lv2(
//"Odin2",
//"file:///home/user/.lv2/Odin2.lv2"
//)?, |track, device|{
//device.connect_midi_in(0, &track.midi_out.clone_unowned())?;
//if let Some(Some(left)) = audio_outs.get(0) {
//device.connect_audio_out(0, left)?;
//}
//if let Some(Some(right)) = audio_outs.get(0) {
//device.connect_audio_out(1, right)?;
//}
//Ok(())
//})?;
//track.sequence = Some(0); // FIXME
//track.add_phrase("Custom0", ppq * 4, None);
//track.add_phrase("Custom1", ppq * 4, None);
//track.add_phrase("Custom2", ppq * 4, None);
//track.add_phrase("Custom3", ppq * 4, None);
//track.add_phrase("Custom4", ppq * 4, None);
//Ok(())
//})?;
state.scenes = vec![
Scene::new("Intro", vec![None, Some(0), None, None]),
Scene::new("Hook", vec![Some(0), Some(1), Some(0), None]),
Scene::new("Verse", vec![Some(2), Some(2), Some(1), None]),
Scene::new("Chorus", vec![Some(1), Some(3), Some(2), None]),
Scene::new("Bridge", vec![Some(3), Some(4), Some(3), None]),
Scene::new("Outro", vec![None, Some(1), Some(4), None]),
];
Ok(())
}))
}

View file

@ -185,12 +185,25 @@ impl App {
self.tracks.get_mut(id).map(|t|(id, t))
} }
}
pub fn new_scene_name (&self) -> String {
format!("Scene {}", self.scenes.len() + 1)
}
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
let name = name.ok_or_else(||format!("Scene {}", self.scenes.len() + 1))?;
let name = name.ok_or_else(||self.new_scene_name())?;
self.scenes.push(Scene::new(&name, vec![]));
self.scene_cursor = self.scenes.len();
Ok(&mut self.scenes[self.scene_cursor - 1])
}
pub fn add_scene_with_clips (
&mut self,
name: Option<&str>,
clips: &[Option<usize>]
) -> Usually<&mut Scene> {
let name = name.ok_or_else(||self.new_scene_name())?;
self.scenes.push(Scene::new(&name, Vec::from(clips)));
self.scene_cursor = self.scenes.len();
Ok(&mut self.scenes[self.scene_cursor - 1])
}
pub fn scene (&self) -> Option<(usize, &Scene)> {
match self.scene_cursor { 0 => None, _ => {
let id = self.scene_cursor as usize - 1;