diff --git a/.gitignore b/.gitignore index eb5a316c..4b4a6bc9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target +perf.data* diff --git a/src/core.rs b/src/core.rs index bb659486..ad0aff06 100644 --- a/src/core.rs +++ b/src/core.rs @@ -111,7 +111,7 @@ pub fn main_thread ( let exited = exited.clone(); let device = device.clone(); let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?; - let sleep = Duration::from_millis(16); + let sleep = Duration::from_millis(20); Ok(spawn(move || loop { if let Ok(device) = device.try_read() { diff --git a/src/jack.rs b/src/jack.rs index 1fada643..459cbae1 100644 --- a/src/jack.rs +++ b/src/jack.rs @@ -1,18 +1,6 @@ use crate::core::*; -submod!( device event factory ports process ); - -pub type DynamicAsyncClient = - AsyncClient; - -pub type DynamicNotifications = - Notifications>; - -pub type DynamicProcessHandler = - ClosureProcessHandler; - -pub type BoxedProcessHandler = - Box Control + Send>; +submod!( device event factory ports ); pub use ::_jack::{ AsyncClient, @@ -42,6 +30,43 @@ pub use ::_jack::{ Unowned }; +pub type DynamicAsyncClient = + AsyncClient; + +type DynamicProcessHandler = + ClosureProcessHandler; + +pub type BoxedProcessHandler = + Box Control + Send>; + +/// Trait for things that have a JACK process callback. +pub trait Process { + fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue + } +} + +/// Define the JACK process callback associated with a struct. +#[macro_export] macro_rules! process { + ($T:ty) => { + impl Process for $T {} + }; + ($T:ty |$self:ident, $c:ident, $s:ident|$block:tt) => { + impl Process for $T { + fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { + $block + } + } + }; + ($T:ty = $process:path) => { + impl Process for $T { + fn process (&mut self, c: &Client, s: &ProcessScope) -> Control { + $process(self, c, s) + } + } + } +} + /// Just run thing with JACK. Returns the activated client. pub fn jack_run (name: &str, app: &Arc>) -> Usually where T: Handle + Process + Send + 'static @@ -65,3 +90,41 @@ pub fn jack_run (name: &str, app: &Arc>) -> Usually Usually<()>; + fn process_out (&self, _: &Client, _: &ProcessScope) -> Usually<()>; +} + +impl Process for T { + fn process (&mut self, c: &Client, s: &ProcessScope) -> Control { + self.process_in(c, s).unwrap(); + self.process_out(c, s).unwrap(); + Control::Continue + } +} + +/// Just run thing with JACK. Returns the activated client. +pub fn jack_run_split (name: &str, app: &Arc>) -> Usually + where T: Handle + ProcessSplit + Send + 'static +{ + let options = ClientOptions::NO_START_SERVER; + let (client, _status) = Client::new(name, options)?; + Ok(client.activate_async( + Notifications(Box::new({ + let _app = app.clone(); + move|_event|{ + // FIXME: this deadlocks + //app.lock().unwrap().handle(&event).unwrap(); + } + }) as Box), + ClosureProcessHandler::new(Box::new({ + let app = app.clone(); + move|c: &Client, s: &ProcessScope|{ + app.write().unwrap().process_in(c, s).unwrap(); + app.read().unwrap().process_out(c, s).unwrap(); + Control::Continue + } + }) as BoxedProcessHandler) + )?) +} diff --git a/src/jack/event.rs b/src/jack/event.rs index fc0585dc..2602aa79 100644 --- a/src/jack/event.rs +++ b/src/jack/event.rs @@ -1,5 +1,8 @@ use super::*; +pub type DynamicNotifications = + Notifications>; + #[derive(Debug)] pub enum JackEvent { ThreadInit, diff --git a/src/jack/process.rs b/src/jack/process.rs deleted file mode 100644 index e4d54e2a..00000000 --- a/src/jack/process.rs +++ /dev/null @@ -1,30 +0,0 @@ -use super::*; - -/// Trait for things that have a JACK process callback. -pub trait Process { - fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { - Control::Continue - } -} - -/// Define the JACK process callback associated with a struct. -#[macro_export] macro_rules! process { - ($T:ty) => { - impl Process for $T {} - }; - ($T:ty |$self:ident, $c:ident, $s:ident|$block:tt) => { - impl Process for $T { - fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { - $block - } - } - }; - ($T:ty = $process:path) => { - impl Process for $T { - fn process (&mut self, c: &Client, s: &ProcessScope) -> Control { - $process(self, c, s) - } - } - } -} - diff --git a/src/model.rs b/src/model.rs index 164fa5bf..0b78a876 100644 --- a/src/model.rs +++ b/src/model.rs @@ -73,32 +73,9 @@ pub struct App { pub quant: usize, } process!(App |self, _client, scope| { - self.chunk_size = scope.n_frames() as usize; - let CycleTimes { - current_frames, - current_usecs, - next_usecs, - period_usecs - } = scope.cycle_times().unwrap(); - let transport = self.transport.as_ref().unwrap().query().unwrap(); - self.playhead = transport.pos.frame() as usize; - let mut reset = false; - if self.playing != Some(transport.state) { - match transport.state { - TransportState::Rolling => { - self.play_started = Some(( - current_frames as usize, - current_usecs as usize, - )); - }, - TransportState::Stopped => { - self.play_started = None; - reset = true; - }, - _ => {} - } - } - self.playing = Some(transport.state); + let ( + reset, current_frames, current_usecs, next_usecs, period_usecs + ) = self.process_update_time(&scope); for track in self.tracks.iter_mut() { track.process( self.midi_in.as_ref().unwrap().iter(scope), @@ -116,6 +93,37 @@ process!(App |self, _client, scope| { Control::Continue }); impl App { + pub fn process_update_time (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, f64) { + self.chunk_size = scope.n_frames() as usize; + let CycleTimes { + current_frames, + current_usecs, + next_usecs, + period_usecs + } = scope.cycle_times().unwrap(); + let transport = self.transport.as_ref().unwrap().query().unwrap(); + self.playhead = transport.pos.frame() as usize; + let mut reset = false; + if self.playing != Some(transport.state) { + match transport.state { + TransportState::Rolling => { + self.play_started = Some(( + current_frames as usize, + current_usecs as usize, + )); + }, + TransportState::Stopped => { + self.play_started = None; + reset = true; + }, + _ => {} + } + } + self.playing = Some(transport.state); + ( + reset, current_frames as usize, current_usecs as usize, next_usecs as usize, period_usecs as f64 + ) + } pub fn client (&self) -> &Client { self.jack.as_ref().unwrap().as_client() } diff --git a/src/model/phrase.rs b/src/model/phrase.rs index 4da8c78a..f8b11e1b 100644 --- a/src/model/phrase.rs +++ b/src/model/phrase.rs @@ -55,22 +55,23 @@ impl Phrase { timebase: &Arc, (frame0, frames, _): (usize, usize, f64), ) { + let mut buf = Vec::with_capacity(8); for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames( frame0, frame0 + frames ) { let tick = tick % self.length; if let Some(events) = self.notes.get(&(tick as usize)) { for message in events.iter() { - let mut buf = vec![]; + buf.clear(); let channel = 0.into(); let message = *message; + LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); + output[time as usize].push(buf.clone()); match message { MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true, MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false, _ => {} } - LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); - output[time as usize].push(buf); } } } diff --git a/src/model/sampler.rs b/src/model/sampler.rs index 47ee028c..7f5be3bc 100644 --- a/src/model/sampler.rs +++ b/src/model/sampler.rs @@ -6,6 +6,8 @@ pub struct Sampler { pub samples: BTreeMap>, pub voices: Vec, pub ports: JackPorts, + pub buffer: Vec>, + pub output_gain: f32 } render!(Sampler = crate::view::sampler::render); @@ -48,15 +50,21 @@ impl Sampler { cursor: (0, 0), samples: samples.unwrap_or(BTreeMap::new()), voices: vec![], - ports + ports, + buffer: vec![vec![0.0;16384];2], + output_gain: 0.5, })) } pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - // Output buffer: this will be copied to the audio outs. - let channel_count = self.ports.audio_outs.len(); - let mut mixed = vec![vec![0.0;scope.n_frames() as usize];channel_count]; - // Process MIDI input to add new voices. + self.process_midi_in(scope); + self.clear_output_buffer(); + self.process_audio_out(scope); + self.write_output_buffer(scope); + Control::Continue + } + + fn process_midi_in (&mut self, scope: &ProcessScope) { for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) { if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { if let MidiMessage::NoteOn { ref key, .. } = message { @@ -66,33 +74,39 @@ impl Sampler { } } } - // Emit next chunk of each currently playing voice, - // dropping voices that have reached their ends. - let gain = 0.5; + } + + fn clear_output_buffer (&mut self) { + for buffer in self.buffer.iter_mut() { + buffer.fill(0.0); + } + } + + fn process_audio_out (&mut self, scope: &ProcessScope) { + let channel_count = self.buffer.len(); self.voices.retain_mut(|voice|{ - if let Some(chunk) = voice.chunk(scope.n_frames() as usize) { - for (i, channel) in chunk.iter().enumerate() { - let buffer = &mut mixed[i % channel_count]; - for (i, sample) in channel.iter().enumerate() { - buffer[i] += sample * gain; + for index in 0..scope.n_frames() as usize { + if let Some(frame) = voice.next() { + for (channel, sample) in frame.iter().enumerate() { + self.buffer[channel % channel_count][index] += sample * self.output_gain; } + } else { + return false } - true - } else { - false } + return true }); - // Write output buffer to output ports. + } + + /// Write output buffer to output ports. + fn write_output_buffer (&mut self, scope: &ProcessScope) { for (i, port) in self.ports.audio_outs.values_mut().enumerate() { - let buffer = &mixed[i]; + let buffer = &self.buffer[i]; for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { *value = *buffer.get(i).unwrap_or(&0.0); } } - Control::Continue } - - fn load_sample (&mut self, _path: &str) {} } #[macro_export] macro_rules! sample { @@ -126,7 +140,11 @@ impl Sample { 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 } + Voice { + sample: self.clone(), + after, + position: self.start + } } } @@ -136,32 +154,23 @@ pub struct Voice { 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; +const BUFFER: [f32;64] = [0.0f32;64]; + +impl Iterator for Voice { + type Item = [f32;2]; + fn next (&mut self) -> Option { + if self.after > 0 { + self.after = self.after - 1; + return Some([0.0, 0.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 + let position = self.position; + self.position = self.position + 1; + return Some([ + self.sample.channels[0][position], + self.sample.channels[0][position], + ]) } + None } } diff --git a/src/model/track.rs b/src/model/track.rs index 7d41dfee..6667c301 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -45,7 +45,7 @@ impl Track { Ok(Self { name: name.to_string(), midi_out: jack.register_port(name, MidiOut)?, - midi_out_buf: vec![vec![];1024], + midi_out_buf: vec![vec![];16384], notes_on: vec![false;128], monitoring: false, recording: false,