diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index e175d59e..a90827f1 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -32,17 +32,18 @@ impl GrooveboxCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ let app = tek::GrooveboxTui::try_from(jack)?; - let jack = jack.read().unwrap(); + jack.read().unwrap().client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; - jack.client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; + jack.connect_midi_from(&app.player.midi_ins[0], &self.midi_from)?; + jack.connect_midi_from(&app.sampler.midi_in, &self.midi_from)?; - connect_from(&jack, &app.player.midi_ins[0], &self.midi_from)?; - connect_to(&jack, &app.player.midi_outs[0], &self.midi_to)?; + jack.connect_midi_to(&app.player.midi_outs[0], &self.midi_to)?; - connect_audio_from(&jack, &app.sampler.audio_ins[0], &self.l_from)?; - connect_audio_from(&jack, &app.sampler.audio_ins[1], &self.r_from)?; - connect_audio_to(&jack, &app.sampler.audio_outs[0], &self.l_to)?; - connect_audio_to(&jack, &app.sampler.audio_outs[1], &self.r_to)?; + jack.connect_audio_from(&app.sampler.audio_ins[0], &self.l_from)?; + jack.connect_audio_from(&app.sampler.audio_ins[1], &self.r_from)?; + + jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?; + jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; Ok(app) })?)?; diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 439c57d2..71eea6ef 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -52,6 +52,30 @@ audio!(|self: GrooveboxTui, client, scope|{ if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { return Control::Quit } + for RawMidi { time, bytes } in self.sampler.midi_in.iter(scope) { + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + match message { + MidiMessage::Controller { controller, value } => { + if controller == u7::from(20) { + if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { + let mut sample = sample.write().unwrap(); + let percentage = value.as_int() as f64 / 127.; + sample.start = (percentage * sample.end as f64) as usize; + } + } else if controller == u7::from(21) { + if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { + let mut sample = sample.write().unwrap(); + let percentage = value.as_int() as f64 / 127.; + let length = sample.channels[0].len(); + sample.end = sample.start + (percentage * (length as f64 - sample.start as f64)) as usize; + sample.end = sample.end.min(length); + } + } + } + _ => {} + } + } + } self.perf.update(t0, scope); Control::Continue }); diff --git a/crates/tek/src/jack.rs b/crates/tek/src/jack.rs index 0a21d92d..fae62340 100644 --- a/crates/tek/src/jack.rs +++ b/crates/tek/src/jack.rs @@ -22,6 +22,7 @@ pub(crate) use self::jack_event::*; pub mod ports; pub(crate) use self::ports::*; +pub use self::ports::RegisterPort; /// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. #[macro_export] macro_rules! from_jack { diff --git a/crates/tek/src/jack/ports.rs b/crates/tek/src/jack/ports.rs index c64e050a..d65ac790 100644 --- a/crates/tek/src/jack/ports.rs +++ b/crates/tek/src/jack/ports.rs @@ -5,9 +5,13 @@ pub trait RegisterPort { fn midi_out (&self, name: &str) -> Usually>; fn audio_in (&self, name: &str) -> Usually>; fn audio_out (&self, name: &str) -> Usually>; + fn connect_midi_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; + fn connect_midi_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; + fn connect_audio_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; + fn connect_audio_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; } -impl RegisterPort for &Arc> { +impl RegisterPort for Arc> { fn midi_in (&self, name: &str) -> Usually> { Ok(self.read().unwrap().client().register_port(name, MidiIn::default())?) } @@ -20,6 +24,50 @@ impl RegisterPort for &Arc> { fn audio_in (&self, name: &str) -> Usually> { Ok(self.read().unwrap().client().register_port(name, AudioIn::default())?) } + fn connect_midi_from (&self, input: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_midi_to (&self, output: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_audio_from (&self, input: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_audio_to (&self, output: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } } /// Trait for things that may expose JACK ports. diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index 0c106cab..54926e09 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -67,7 +67,7 @@ impl Sampler { unmapped: vec![], voices: Arc::new(RwLock::new(vec![])), buffer: vec![vec![0.0;16384];2], - output_gain: 0.5, + output_gain: 1., recording: None, }) } @@ -97,6 +97,7 @@ pub enum SamplerCommand { RecordCancel, RecordFinish, SetSample(u7, Option>>), + SetStart(u7, usize), SetGain(f32), NoteOn(u7, u7), NoteOff(u7), @@ -176,11 +177,14 @@ impl Sampler { pub fn process_midi_in (&mut self, scope: &ProcessScope) { let Sampler { midi_in, mapped, voices, .. } = self; for RawMidi { time, bytes } in midi_in.iter(scope) { - if let LiveEvent::Midi { - message: MidiMessage::NoteOn { ref key, ref vel }, .. - } = LiveEvent::parse(bytes).unwrap() { - if let Some(ref sample) = mapped[key.as_int() as usize] { - voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + match message { + MidiMessage::NoteOn { ref key, ref vel } => { + if let Some(ref sample) = mapped[key.as_int() as usize] { + voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + } + }, + _ => {} } } }