diff --git a/src/core.rs b/src/core.rs index 1a9f30e8..3a26abb8 100644 --- a/src/core.rs +++ b/src/core.rs @@ -114,7 +114,9 @@ pub fn main_thread ( terminal.draw(|frame|{ let area = frame.size(); let buffer = frame.buffer_mut(); - device.lock().unwrap().render(buffer, area).expect("Failed to render content"); + device.lock().unwrap() + .render(buffer, area) + .expect("Failed to render content"); }).expect("Failed to render frame"); if exited.fetch_and(true, Ordering::Relaxed) { diff --git a/src/jack/device.rs b/src/jack/device.rs index 8fe97661..ca89672c 100644 --- a/src/jack/device.rs +++ b/src/jack/device.rs @@ -25,4 +25,16 @@ impl JackDevice { pub fn state (&self) -> MutexGuard> { self.state.lock().unwrap() } + pub fn connect_midi_in (&self, index: usize, port: &Port) -> Usually<()> { + Ok(self.client.as_client().connect_ports(port, self.midi_ins()?[index])?) + } + pub fn connect_midi_out (&self, index: usize, port: &Port) -> Usually<()> { + Ok(self.client.as_client().connect_ports(self.midi_outs()?[index], port)?) + } + pub fn connect_audio_in (&self, index: usize, port: &Port) -> Usually<()> { + Ok(self.client.as_client().connect_ports(port, self.audio_ins()?[index])?) + } + pub fn connect_audio_out (&self, index: usize, port: &Port) -> Usually<()> { + Ok(self.client.as_client().connect_ports(self.audio_outs()?[index], port)?) + } } diff --git a/src/jack/factory.rs b/src/jack/factory.rs index ed2cfd99..8f556972 100644 --- a/src/jack/factory.rs +++ b/src/jack/factory.rs @@ -3,22 +3,44 @@ use super::*; /// `JackDevice` factory. Creates JACK `Client`s, performs port registration /// and activation, and encapsulates a `Device` into a `JackDevice`. pub struct Jack { - pub client: Client, - pub ports: JackPorts, + pub client: Client, + pub midi_ins: Vec, + pub audio_ins: Vec, + pub midi_outs: Vec, + pub audio_outs: Vec, } impl Jack { pub fn new (name: &str) -> Usually { - let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; - Ok(Self { client, ports: JackPorts::default() }) + Ok(Self { + midi_ins: vec![], + audio_ins: vec![], + midi_outs: vec![], + audio_outs: vec![], + client: Client::new( + name, + ClientOptions::NO_START_SERVER + )?.0, + }) } pub fn run ( - mut self, state: impl FnOnce(JackPorts)->Box + self, state: impl FnOnce(JackPorts)->Box ) -> Usually { - let mut owned_ports = JackPorts::default(); - std::mem::swap(&mut self.ports, &mut owned_ports); - let unowned_ports = owned_ports.clone_unowned(&self.client); + let owned_ports = JackPorts { + audio_ins: register_ports(&self.client, self.audio_ins, AudioIn)?, + audio_outs: register_ports(&self.client, self.audio_outs, AudioOut)?, + midi_ins: register_ports(&self.client, self.midi_ins, MidiIn)?, + midi_outs: register_ports(&self.client, self.midi_outs, MidiOut)?, + }; + let midi_outs = owned_ports.midi_outs.values() + .map(|p|Ok(p.name()?)).collect::>>()?; + let midi_ins = owned_ports.midi_ins.values() + .map(|p|Ok(p.name()?)).collect::>>()?; + let audio_outs = owned_ports.audio_outs.values() + .map(|p|Ok(p.name()?)).collect::>>()?; + let audio_ins = owned_ports.audio_ins.values() + .map(|p|Ok(p.name()?)).collect::>>()?; let state = Arc::new(Mutex::new(state(owned_ports) as Box)); let client = self.client.activate_async( Notifications(Box::new({ @@ -35,44 +57,68 @@ impl Jack { } }) as BoxedProcessHandler) )?; - Ok(JackDevice { client, state, ports: unowned_ports }) + Ok(JackDevice { + ports: UnownedJackPorts { + audio_ins: query_ports(&client.as_client(), audio_ins), + audio_outs: query_ports(&client.as_client(), audio_outs), + midi_ins: query_ports(&client.as_client(), midi_ins), + midi_outs: query_ports(&client.as_client(), midi_outs), + }, + client, + state, + }) } - pub fn ports_from_lv2 (self, plugin: &::livi::Plugin) -> Usually { + pub fn ports_from_lv2 (self, plugin: &::livi::Plugin) -> Self { let counts = plugin.port_counts(); let mut jack = self; for i in 0..counts.atom_sequence_inputs { - jack = jack.register_midi_in(&format!("midi-in-{i}"))? + jack = jack.midi_in(&format!("midi-in-{i}")) } for i in 0..counts.atom_sequence_outputs { - jack = jack.register_midi_out(&format!("midi-out-{i}"))?; + jack = jack.midi_out(&format!("midi-out-{i}")); } for i in 0..counts.audio_inputs { - jack = jack.register_audio_in(&format!("audio-in-{i}"))? + jack = jack.audio_in(&format!("audio-in-{i}")); } for i in 0..counts.audio_outputs { - jack = jack.register_audio_out(&format!("audio-out-{i}"))?; + jack = jack.audio_out(&format!("audio-out-{i}")); } - Ok(jack) + jack } - pub fn register_midi_out (mut self, name: &str) -> Usually { - let port = self.client.register_port(name, MidiOut::default())?; - self.ports.midi_outs.insert(name.to_string(), port); - Ok(self) + pub fn audio_in (mut self, name: &str) -> Self { + self.audio_ins.push(name.to_string()); + self } - pub fn register_midi_in (mut self, name: &str) -> Usually { - let port = self.client.register_port(name, MidiIn::default())?; - self.ports.midi_ins.insert(name.to_string(), port); - Ok(self) + pub fn audio_out (mut self, name: &str) -> Self { + self.audio_outs.push(name.to_string()); + self } - pub fn register_audio_out (mut self, name: &str) -> Usually { - let port = self.client.register_port(name, AudioOut::default())?; - self.ports.audio_outs.insert(name.to_string(), port); - Ok(self) + pub fn midi_in (mut self, name: &str) -> Self { + self.midi_ins.push(name.to_string()); + self } - pub fn register_audio_in (mut self, name: &str) -> Usually { - let port = self.client.register_port(name, AudioIn::default())?; - self.ports.audio_ins.insert(name.to_string(), port); - Ok(self) + pub fn midi_out (mut self, name: &str) -> Self { + self.midi_outs.push(name.to_string()); + self } } +fn register_ports ( + client: &Client, names: Vec, spec: T +) -> Usually>> { + names.into_iter().try_fold(BTreeMap::new(), |mut ports, name|{ + let port = client.register_port(&name, spec)?; + ports.insert(name, port); + Ok(ports) + }) +} + +fn query_ports ( + client: &Client, names: Vec +) -> BTreeMap> { + names.into_iter().fold(BTreeMap::new(), |mut ports, name|{ + let port = client.port_by_name(&name).unwrap(); + ports.insert(name, port); + ports + }) +} diff --git a/src/jack/ports.rs b/src/jack/ports.rs index 6fb63b50..4fa575a6 100644 --- a/src/jack/ports.rs +++ b/src/jack/ports.rs @@ -2,7 +2,6 @@ use super::*; #[derive(Default)] pub struct JackPorts { - lifesaver: Arc<()>, pub audio_ins: BTreeMap>, pub midi_ins: BTreeMap>, pub audio_outs: BTreeMap>, @@ -18,27 +17,19 @@ pub struct UnownedJackPorts { } impl JackPorts { - pub fn clone_unowned (&self, client: &Client) -> UnownedJackPorts { + pub fn clone_unowned (&self) -> UnownedJackPorts { let mut unowned = UnownedJackPorts::default(); for (name, port) in self.midi_ins.iter() { - unowned.midi_ins.insert(name.clone(), unsafe { - Port::from_raw(::jack::Unowned, client.raw(), port.raw(), Arc::downgrade(&self.lifesaver)) - }); + unowned.midi_ins.insert(name.clone(), port.clone_unowned()); } for (name, port) in self.midi_outs.iter() { - unowned.midi_outs.insert(name.clone(), unsafe { - Port::from_raw(::jack::Unowned, client.raw(), port.raw(), Arc::downgrade(&self.lifesaver)) - }); + unowned.midi_outs.insert(name.clone(), port.clone_unowned()); } for (name, port) in self.audio_ins.iter() { - unowned.audio_ins.insert(name.clone(), unsafe { - Port::from_raw(::jack::Unowned, client.raw(), port.raw(), Arc::downgrade(&self.lifesaver)) - }); + unowned.audio_ins.insert(name.clone(), port.clone_unowned()); } for (name, port) in self.audio_outs.iter() { - unowned.audio_outs.insert(name.clone(), unsafe { - Port::from_raw(::jack::Unowned, client.raw(), port.raw(), Arc::downgrade(&self.lifesaver)) - }); + unowned.audio_outs.insert(name.clone(), port.clone_unowned()); } unowned } diff --git a/src/main.rs b/src/main.rs index 0ced8af7..1d683a98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,67 +19,60 @@ use crate::{core::*, model::*}; pub fn main () -> Usually<()> { App::default().run(Some(|app: Arc>|{ let mut state = app.lock().unwrap(); + let xdg = Arc::new(microxdg::XdgApp::new("tek")?); state.xdg = Some(xdg.clone()); + if crate::config::AppPaths::new(&xdg)?.should_create() { state.modal = Some(Box::new(crate::config::SetupModal(Some(xdg.clone())))); } - state.scenes = vec![ - Scene::new("Intro", vec![None, Some(0), None, None]), - Scene::new("Hook", vec![Some(0), Some(0), None, None]), - Scene::new("Verse", vec![Some(1), Some(0), None, None]), - Scene::new("Chorus", vec![Some(0), Some(0), None, None]), - Scene::new("Bridge", vec![Some(2), Some(0), None, None]), - Scene::new("Outro", vec![None, Some(0), None, None]), - ]; - let jack = jack_run("tek", &app)?; - let client = jack.as_client(); - let timebase = &state.timebase; - let ppq = timebase.ppq() as usize; + + let jack = jack_run("tek", &app)?; + let client = jack.as_client(); + state.transport = Some(client.transport()); + state.playing = Some(TransportState::Stopped); + state.midi_in = Some(client.register_port("midi-in", MidiIn)?); + + let timebase = &state.timebase; + let ppq = timebase.ppq() as usize; state.track_cursor = 1; state.scene_cursor = 1; state.note_start = 12; state.time_zoom = 12; - state.transport = Some(client.transport()); - state.playing = Some(TransportState::Stopped); - state.midi_in = Some(client.register_port("midi-in", MidiIn)?); - state.jack = Some(jack); - state.add_track_with_cb(Some("Drums"), |client: &Client, track: &mut Track|{ + let outputs: Vec<_> = ["Komplete.+:playback_FL", "Komplete.+:playback_FR"] + .iter() + .map(|name|client + .ports(Some(name), None, PortFlags::empty()) + .get(0) + .map(|name|client.port_by_name(name))) + .flatten() + .collect(); + + state.jack = Some(jack); + + state.add_track_with_cb(Some("Drums"), |_, track|{ track.add_device_with_cb(Sampler::new("Sampler", Some(BTreeMap::from([ sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"), sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"), sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"), - ])))?, |track: &Track, device: &mut JackDevice|{ - client.connect_ports( - &track.midi_out, &device.midi_ins()?[0] - )?; + ])))?, |track, device|{ + device.connect_midi_in(0, &track.midi_out.clone_unowned())?; Ok(()) })?; track.add_device_with_cb(Plugin::lv2( "Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2" - )?, |track: &Track, device: &mut JackDevice|{ - client.connect_ports( - &track.devices[0].audio_outs()?[0], &device.audio_ins()?[0] - )?; - let output_left = client - .ports(Some("Komplete.+:playback_FL"), None, PortFlags::empty()) - .get(0) - .map(|name|client.port_by_name(&name)) - .flatten(); - if let Some(output_left) = output_left { - client.connect_ports(&device.audio_outs()?[0], &output_left)?; + )?, |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)) = outputs.get(0) { + device.connect_audio_out(0, left)?; } - let output_right = client - .ports(Some("Komplete.+:playback_FR"), None, PortFlags::empty()) - .get(0) - .map(|name|client.port_by_name(&name)) - .flatten(); - if let Some(output_right) = output_right { - client.connect_ports(&device.audio_outs()?[0], &output_right)?; + if let Some(Some(right)) = outputs.get(0) { + device.connect_audio_out(1, right)?; } Ok(()) })?; @@ -108,26 +101,20 @@ pub fn main () -> Usually<()> { Ok(()) })?; - state.add_track_with_cb(Some("Bass"), |_client: &Client, track: &mut Track|{ - track.add_device(Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?)?; + state.add_track_with_cb(Some("Bass"), |_, track|{ + track.add_device_with_cb(Plugin::lv2( + "Odin2", + "file:///home/user/.lv2/Odin2.lv2" + )?, |_, device|{ + if let Some(Some(left)) = outputs.get(0) { + device.connect_audio_out(0, left)?; + } + if let Some(Some(right)) = outputs.get(0) { + device.connect_audio_out(1, right)?; + } + Ok(()) + })?; track.sequence = Some(0); // FIXME - //client.connect_ports(&track.midi_out, &track.devices[0].midi_ins()?[0])?; - //let output_left = client.ports(Some(".+:playback_FL"), None, PortFlags::empty()); - //if let Some( - //output_left - //) = output_left.get(0).map(|name|client.port_by_name(&name)).flatten() { - //client.connect_ports( - //&track.devices[0].audio_outs()?[0], &output_left - //)?; - //} - //let output_right = client.ports(Some(".+:playback_FR"), None, PortFlags::empty()); - //if let Some( - //output_right - //) = output_right.get(0).map(|name|client.port_by_name(&name)).flatten() { - //client.connect_ports( - //&track.devices[0].audio_outs()?[1], &output_right - //)?; - //} 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() }, @@ -141,6 +128,15 @@ pub fn main () -> Usually<()> { Ok(()) })?; + state.scenes = vec![ + Scene::new("Intro", vec![None, Some(0), None, None]), + Scene::new("Hook", vec![Some(0), Some(0), None, None]), + Scene::new("Verse", vec![Some(1), Some(0), None, None]), + Scene::new("Chorus", vec![Some(0), Some(0), None, None]), + Scene::new("Bridge", vec![Some(2), Some(0), None, None]), + Scene::new("Outro", vec![None, Some(0), None, None]), + ]; + Ok(()) })) } @@ -298,32 +294,4 @@ impl App { let (_, scene) = self.scene()?; *scene.clips.get(track_id)? } - pub fn connect_tracks (&self) -> Usually<()> { - //let (client, _status) = Client::new( - //&format!("{}-init", &self.name), ClientOptions::NO_START_SERVER - //)?; - //let midi_ins = client.ports(Some(midi_in), None, PortFlags::IS_OUTPUT); - //let audio_outs: Vec> = audio_outs.iter() - //.map(|pattern|client.ports(Some(pattern), None, PortFlags::IS_INPUT)) - //.collect(); - //for (i, sequencer) in self.tracks.iter().enumerate() { - //for sequencer_midi_in in sequencer.midi_ins()?.iter() { - //for midi_in in midi_ins.iter() { - //client.connect_ports_by_name(&midi_in, &sequencer_midi_in)?; - //} - //} - //let chain: &Chain = &self.tracks[i].chain; - //for port in sequencer.midi_outs()?.iter() { - //for midi_in in chain.midi_ins()?.iter() { - //client.connect_ports_by_name(&port, &midi_in)?; - //} - //} - //for (j, port) in chain.audio_outs()?.iter().enumerate() { - //for audio_out in audio_outs[j % audio_outs.len()].iter() { - //client.connect_ports_by_name(&port, &audio_out)?; - //} - //} - //} - Ok(()) - } } diff --git a/src/model/plugin/lv2.rs b/src/model/plugin/lv2.rs index 4cd3638f..83f35cee 100644 --- a/src/model/plugin/lv2.rs +++ b/src/model/plugin/lv2.rs @@ -44,7 +44,7 @@ impl super::Plugin { pub fn lv2 (name: &str, path: &str) -> Usually { let plugin = LV2Plugin::new(path)?; Jack::new(name)? - .ports_from_lv2(&plugin.plugin)? + .ports_from_lv2(&plugin.plugin) .run(|ports|Box::new(Self { name: name.into(), path: Some(String::from(path)), diff --git a/src/model/sampler.rs b/src/model/sampler.rs index 67361243..3305a423 100644 --- a/src/model/sampler.rs +++ b/src/model/sampler.rs @@ -1,57 +1,5 @@ use crate::core::*; -pub struct Voice { - pub sample: Arc, - pub after: usize, - pub position: usize, -} - -pub struct Sample { - pub name: String, - pub start: usize, - pub end: usize, - pub channels: Vec>, -} - -impl Voice { - pub fn chunk (&mut self, mut frames: usize) -> Option>> { - // Create output buffer for each channel - let mut chunk = vec![vec![];self.sample.channels.len()]; - // If it's not time to play yet, count down - if self.after >= frames { - self.after = self.after - frames; - return Some(chunk) - } - // If the voice will start playing within the current buffer, - // subtract the remaining number of wait frames. - if self.after > 0 && self.after < frames { - chunk = vec![vec![0.0;self.after];self.sample.channels.len()]; - frames = frames - self.after; - self.after = 0; - } - if self.position < self.sample.end { - let start = self.position.min(self.sample.end); - let end = (self.position + frames).min(self.sample.end); - for (i, channel) in self.sample.channels.iter().enumerate() { - chunk[i].extend_from_slice(&channel[start..end]); - }; - self.position = self.position + frames; - Some(chunk) - } else { - None - } - } -} - -impl Sample { - pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Arc { - Arc::new(Self { name: name.to_string(), start, end, channels }) - } - pub fn play (self: &Arc, after: usize) -> Voice { - Voice { sample: self.clone(), after, position: self.start } - } -} - pub struct Sampler { pub name: String, pub cursor: (usize, usize), @@ -90,11 +38,11 @@ impl Sampler { name: &str, samples: Option>>, ) -> Usually { Jack::new(name)? - .register_midi_in("midi")? - .register_audio_in("recL")? - .register_audio_in("recR")? - .register_audio_out("outL")? - .register_audio_out("outR")? + .midi_in("midi") + .audio_in("recL") + .audio_in("recR") + .audio_out("outL") + .audio_out("outR") .run(|ports|Box::new(Self { name: name.into(), cursor: (0, 0), @@ -168,3 +116,55 @@ impl Sampler { } }; } + +pub struct Sample { + pub name: String, + pub start: usize, + pub end: usize, + pub channels: Vec>, +} + +impl Sample { + pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Arc { + Arc::new(Self { name: name.to_string(), start, end, channels }) + } + pub fn play (self: &Arc, after: usize) -> Voice { + Voice { sample: self.clone(), after, position: self.start } + } +} + +pub struct Voice { + pub sample: Arc, + pub after: usize, + pub position: usize, +} + +impl Voice { + pub fn chunk (&mut self, mut frames: usize) -> Option>> { + // Create output buffer for each channel + let mut chunk = vec![vec![];self.sample.channels.len()]; + // If it's not time to play yet, count down + if self.after >= frames { + self.after = self.after - frames; + return Some(chunk) + } + // If the voice will start playing within the current buffer, + // subtract the remaining number of wait frames. + if self.after > 0 && self.after < frames { + chunk = vec![vec![0.0;self.after];self.sample.channels.len()]; + frames = frames - self.after; + self.after = 0; + } + if self.position < self.sample.end { + let start = self.position.min(self.sample.end); + let end = (self.position + frames).min(self.sample.end); + for (i, channel) in self.sample.channels.iter().enumerate() { + chunk[i].extend_from_slice(&channel[start..end]); + }; + self.position = self.position + frames; + Some(chunk) + } else { + None + } + } +}