From 329da026d760551759de81ed83c09c21c77ce89e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 11 May 2025 01:46:37 +0300 Subject: [PATCH] sequencer: extract seq_audio, remove Api suffix from traits --- crates/device/src/sequencer.rs | 1 + crates/device/src/sequencer/seq_audio.rs | 286 ++++++++++++++++++++++ crates/device/src/sequencer/seq_model.rs | 297 +---------------------- 3 files changed, 293 insertions(+), 291 deletions(-) create mode 100644 crates/device/src/sequencer/seq_audio.rs diff --git a/crates/device/src/sequencer.rs b/crates/device/src/sequencer.rs index df991b7f..3b6d9fee 100644 --- a/crates/device/src/sequencer.rs +++ b/crates/device/src/sequencer.rs @@ -1,3 +1,4 @@ +mod seq_audio; pub use self::seq_audio::*; mod seq_clip; pub use self::seq_clip::*; mod seq_launch; pub use self::seq_launch::*; mod seq_model; pub use self::seq_model::*; diff --git a/crates/device/src/sequencer/seq_audio.rs b/crates/device/src/sequencer/seq_audio.rs new file mode 100644 index 00000000..c4f05a6d --- /dev/null +++ b/crates/device/src/sequencer/seq_audio.rs @@ -0,0 +1,286 @@ +use crate::*; + +/// Hosts the JACK callback for a single MIDI sequencer +pub struct PlayerAudio<'a, T: MidiSequencer>( + /// Player + pub &'a mut T, + /// Note buffer + pub &'a mut Vec, + /// Note chunk buffer + pub &'a mut Vec>>, +); + +/// JACK process callback for a sequencer's clip sequencer/recorder. +impl Audio for PlayerAudio<'_, T> { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + let model = &mut self.0; + let note_buf = &mut self.1; + let midi_buf = &mut self.2; + // Clear output buffer(s) + model.clear(scope, midi_buf, false); + // Write chunk of clip to output, handle switchover + if model.play(scope, note_buf, midi_buf) { + model.switchover(scope, note_buf, midi_buf); + } + if model.has_midi_ins() { + if model.recording() || model.monitoring() { + // Record and/or monitor input + model.record(scope, midi_buf) + } else if model.has_midi_outs() && model.monitoring() { + // Monitor input to output + model.monitor(scope, midi_buf) + } + } + // Write to output port(s) + model.write(scope, midi_buf); + Control::Continue + } +} + +pub trait MidiSequencer: MidiRecorder + MidiPlayer + Send + Sync {} + +impl MidiSequencer for Sequencer {} + +pub trait MidiRecorder: HasClock + HasPlayClip + HasMidiIns { + fn notes_in (&self) -> &Arc>; + + fn recording (&self) -> bool; + + fn recording_mut (&mut self) -> &mut bool; + + fn toggle_record (&mut self) { + *self.recording_mut() = !self.recording(); + } + + fn monitoring (&self) -> bool; + + fn monitoring_mut (&mut self) -> &mut bool; + + fn toggle_monitor (&mut self) { + *self.monitoring_mut() = !self.monitoring(); + } + + fn overdub (&self) -> bool; + + fn overdub_mut (&mut self) -> &mut bool; + + fn toggle_overdub (&mut self) { + *self.overdub_mut() = !self.overdub(); + } + + fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { + // For highlighting keys and note repeat + let notes_in = self.notes_in().clone(); + let monitoring = self.monitoring(); + for input in self.midi_ins_mut().iter() { + for (sample, event, bytes) in parse_midi_input(input.port().iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + if monitoring { + midi_buf[sample].push(bytes.to_vec()); + } + // FIXME: don't lock on every event! + update_keys(&mut notes_in.write().unwrap(), &message); + } + } + } + } + + fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { + if self.monitoring() { + self.monitor(scope, midi_buf); + } + if !self.clock().is_rolling() { + return + } + if let Some((started, ref clip)) = self.play_clip().clone() { + self.record_clip(scope, started, clip, midi_buf); + } + if let Some((_start_at, _clip)) = &self.next_clip() { + self.record_next(); + } + } + + fn record_clip ( + &mut self, + scope: &ProcessScope, + started: Moment, + clip: &Option>>, + _midi_buf: &mut Vec>> + ) { + if let Some(clip) = clip { + let sample0 = scope.last_frame_time() as usize; + let start = started.sample.get() as usize; + let _recording = self.recording(); + let timebase = self.clock().timebase().clone(); + let quant = self.clock().quant.get(); + let mut clip = clip.write().unwrap(); + let length = clip.length; + for input in self.midi_ins_mut().iter() { + for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + clip.record_event({ + let sample = (sample0 + sample - start) as f64; + let pulse = timebase.samples_to_pulse(sample); + let quantized = (pulse / quant).round() * quant; + quantized as usize % length + }, message); + } + } + } + } + } + + fn record_next (&mut self) { + // TODO switch to next clip and record into it + } + +} + +pub trait MidiPlayer: HasPlayClip + HasClock + HasMidiOuts { + + fn notes_out (&self) -> &Arc>; + + /// Clear the section of the output buffer that we will be using, + /// emitting "all notes off" at start of buffer if requested. + fn clear ( + &mut self, scope: &ProcessScope, out: &mut [Vec>], reset: bool + ) { + let n_frames = (scope.n_frames() as usize).min(out.len()); + for frame in &mut out[0..n_frames] { + frame.clear(); + } + if reset { + all_notes_off(out); + } + } + + /// Output notes from clip to MIDI output ports. + fn play ( + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] + ) -> bool { + if !self.clock().is_rolling() { + return false + } + // If a clip is playing, write a chunk of MIDI events from it to the output buffer. + // If no clip is playing, prepare for switchover immediately. + self.play_clip().as_ref().map_or(true, |(started, clip)|{ + self.play_chunk(scope, note_buf, out, started, clip) + }) + } + + /// Handle switchover from current to next playing clip. + fn switchover ( + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] + ) { + if !self.clock().is_rolling() { + return + } + let sample0 = scope.last_frame_time() as usize; + //let samples = scope.n_frames() as usize; + if let Some((start_at, clip)) = &self.next_clip() { + let start = start_at.sample.get() as usize; + let sample = self.clock().started.read().unwrap() + .as_ref().unwrap().sample.get() as usize; + // If it's time to switch to the next clip: + if start <= sample0.saturating_sub(sample) { + // Samples elapsed since clip was supposed to start + let _skipped = sample0 - start; + // Switch over to enqueued clip + let started = Moment::from_sample(self.clock().timebase(), start as f64); + // Launch enqueued clip + *self.play_clip_mut() = Some((started, clip.clone())); + // Unset enqueuement (TODO: where to implement looping?) + *self.next_clip_mut() = None; + // Fill in remaining ticks of chunk from next clip. + self.play(scope, note_buf, out); + } + } + } + + fn play_chunk ( + &self, + scope: &ProcessScope, + note_buf: &mut Vec, + out: &mut [Vec>], + started: &Moment, + clip: &Option>> + ) -> bool { + // First sample to populate. Greater than 0 means that the first + // pulse of the clip falls somewhere in the middle of the chunk. + let sample = (scope.last_frame_time() as usize).saturating_sub( + started.sample.get() as usize + + self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize + ); + // Iterator that emits sample (index into output buffer at which to write MIDI event) + // paired with pulse (index into clip from which to take the MIDI event) for each + // sample of the output buffer that corresponds to a MIDI pulse. + let pulses = self.clock().timebase().pulses_between_samples(sample, sample + scope.n_frames() as usize); + // Notes active during current chunk. + let notes = &mut self.notes_out().write().unwrap(); + let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); + for (sample, pulse) in pulses { + // If a next clip is enqueued, and we're past the end of the current one, + // break the loop here (FIXME count pulse correctly) + let past_end = if clip.is_some() { pulse >= length } else { true }; + if self.next_clip().is_some() && past_end { + return true + } + // If there's a currently playing clip, output notes from it to buffer: + if let Some(ref clip) = clip { + Self::play_pulse(clip, pulse, sample, note_buf, out, notes) + } + } + false + } + + fn play_pulse ( + clip: &RwLock, + pulse: usize, + sample: usize, + note_buf: &mut Vec, + out: &mut [Vec>], + notes: &mut [bool;128] + ) { + // Source clip from which the MIDI events will be taken. + let clip = clip.read().unwrap(); + // Clip with zero length is not processed + if clip.length > 0 { + // Current pulse index in source clip + let pulse = pulse % clip.length; + // Output each MIDI event from clip at appropriate frames of output buffer: + for message in clip.notes[pulse].iter() { + // Clear output buffer for this MIDI event. + note_buf.clear(); + // TODO: support MIDI channels other than CH1. + let channel = 0.into(); + // Serialize MIDI event into message buffer. + LiveEvent::Midi { channel, message: *message } + .write(note_buf) + .unwrap(); + // Append serialized message to output buffer. + out[sample].push(note_buf.clone()); + // Update the list of currently held notes. + update_keys(&mut*notes, message); + } + } + } + + /// Write a chunk of MIDI data from the output buffer to all assigned output ports. + fn write (&mut self, scope: &ProcessScope, out: &[Vec>]) { + let samples = scope.n_frames() as usize; + for port in self.midi_outs_mut().iter_mut() { + Self::write_port(&mut port.port_mut().writer(scope), samples, out) + } + } + + /// Write a chunk of MIDI data from the output buffer to an output port. + fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec>]) { + for (time, events) in out.iter().enumerate().take(samples) { + for bytes in events.iter() { + writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{ + panic!("Failed to write MIDI data: {bytes:?}"); + }); + } + } + } +} diff --git a/crates/device/src/sequencer/seq_model.rs b/crates/device/src/sequencer/seq_model.rs index c55a839a..60a57b26 100644 --- a/crates/device/src/sequencer/seq_model.rs +++ b/crates/device/src/sequencer/seq_model.rs @@ -2,23 +2,19 @@ use crate::*; pub trait HasSequencer { - fn sequencer (&self) -> &impl MidiPlayerApi; - fn sequencer_mut (&mut self) -> &mut impl MidiPlayerApi; + fn sequencer (&self) -> &impl MidiSequencer; + fn sequencer_mut (&mut self) -> &mut impl MidiSequencer; } #[macro_export] macro_rules! has_sequencer { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasSequencer for $Struct $(<$($L),*$($T),*>)? { - fn sequencer (&$self) -> &impl MidiPlayerApi { &$cb } - fn sequencer_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb } + fn sequencer (&$self) -> &impl MidiSequencer { &$cb } + fn sequencer_mut (&mut $self) -> &mut impl MidiSequencer { &mut$cb } } } } -pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} - -impl MidiPlayerApi for Sequencer {} - /// Contains state for playing a clip pub struct Sequencer { /// State of clock and playhead @@ -119,44 +115,7 @@ impl HasMidiOuts for Sequencer { fn midi_note (&mut self) -> &mut Vec { &mut self.note_buf } } -/// Hosts the JACK callback for a single MIDI sequencer -pub struct PlayerAudio<'a, T: MidiPlayerApi>( - /// Player - pub &'a mut T, - /// Note buffer - pub &'a mut Vec, - /// Note chunk buffer - pub &'a mut Vec>>, -); - -/// JACK process callback for a sequencer's clip sequencer/recorder. -impl Audio for PlayerAudio<'_, T> { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let model = &mut self.0; - let note_buf = &mut self.1; - let midi_buf = &mut self.2; - // Clear output buffer(s) - model.clear(scope, midi_buf, false); - // Write chunk of clip to output, handle switchover - if model.play(scope, note_buf, midi_buf) { - model.switchover(scope, note_buf, midi_buf); - } - if model.has_midi_ins() { - if model.recording() || model.monitoring() { - // Record and/or monitor input - model.record(scope, midi_buf) - } else if model.has_midi_outs() && model.monitoring() { - // Monitor input to output - model.monitor(scope, midi_buf) - } - } - // Write to output port(s) - model.write(scope, midi_buf); - Control::Continue - } -} - -impl MidiRecordApi for Sequencer { +impl MidiRecorder for Sequencer { fn recording (&self) -> bool { self.recording } @@ -180,7 +139,7 @@ impl MidiRecordApi for Sequencer { } } -impl MidiPlaybackApi for Sequencer { +impl MidiPlayer for Sequencer { fn notes_out (&self) -> &Arc> { &self.notes_out } @@ -206,247 +165,3 @@ impl HasPlayClip for Sequencer { &mut self.next_clip } } - -pub trait MidiRecordApi: HasClock + HasPlayClip + HasMidiIns { - fn notes_in (&self) -> &Arc>; - - fn recording (&self) -> bool; - - fn recording_mut (&mut self) -> &mut bool; - - fn toggle_record (&mut self) { - *self.recording_mut() = !self.recording(); - } - - fn monitoring (&self) -> bool; - - fn monitoring_mut (&mut self) -> &mut bool; - - fn toggle_monitor (&mut self) { - *self.monitoring_mut() = !self.monitoring(); - } - - fn overdub (&self) -> bool; - - fn overdub_mut (&mut self) -> &mut bool; - - fn toggle_overdub (&mut self) { - *self.overdub_mut() = !self.overdub(); - } - - fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { - // For highlighting keys and note repeat - let notes_in = self.notes_in().clone(); - let monitoring = self.monitoring(); - for input in self.midi_ins_mut().iter() { - for (sample, event, bytes) in parse_midi_input(input.port().iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - if monitoring { - midi_buf[sample].push(bytes.to_vec()); - } - // FIXME: don't lock on every event! - update_keys(&mut notes_in.write().unwrap(), &message); - } - } - } - } - - fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { - if self.monitoring() { - self.monitor(scope, midi_buf); - } - if !self.clock().is_rolling() { - return - } - if let Some((started, ref clip)) = self.play_clip().clone() { - self.record_clip(scope, started, clip, midi_buf); - } - if let Some((_start_at, _clip)) = &self.next_clip() { - self.record_next(); - } - } - - fn record_clip ( - &mut self, - scope: &ProcessScope, - started: Moment, - clip: &Option>>, - _midi_buf: &mut Vec>> - ) { - if let Some(clip) = clip { - let sample0 = scope.last_frame_time() as usize; - let start = started.sample.get() as usize; - let _recording = self.recording(); - let timebase = self.clock().timebase().clone(); - let quant = self.clock().quant.get(); - let mut clip = clip.write().unwrap(); - let length = clip.length; - for input in self.midi_ins_mut().iter() { - for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - clip.record_event({ - let sample = (sample0 + sample - start) as f64; - let pulse = timebase.samples_to_pulse(sample); - let quantized = (pulse / quant).round() * quant; - quantized as usize % length - }, message); - } - } - } - } - } - - fn record_next (&mut self) { - // TODO switch to next clip and record into it - } - -} - -pub trait MidiPlaybackApi: HasPlayClip + HasClock + HasMidiOuts { - - fn notes_out (&self) -> &Arc>; - - /// Clear the section of the output buffer that we will be using, - /// emitting "all notes off" at start of buffer if requested. - fn clear ( - &mut self, scope: &ProcessScope, out: &mut [Vec>], reset: bool - ) { - let n_frames = (scope.n_frames() as usize).min(out.len()); - for frame in &mut out[0..n_frames] { - frame.clear(); - } - if reset { - all_notes_off(out); - } - } - - /// Output notes from clip to MIDI output ports. - fn play ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] - ) -> bool { - if !self.clock().is_rolling() { - return false - } - // If a clip is playing, write a chunk of MIDI events from it to the output buffer. - // If no clip is playing, prepare for switchover immediately. - self.play_clip().as_ref().map_or(true, |(started, clip)|{ - self.play_chunk(scope, note_buf, out, started, clip) - }) - } - - /// Handle switchover from current to next playing clip. - fn switchover ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] - ) { - if !self.clock().is_rolling() { - return - } - let sample0 = scope.last_frame_time() as usize; - //let samples = scope.n_frames() as usize; - if let Some((start_at, clip)) = &self.next_clip() { - let start = start_at.sample.get() as usize; - let sample = self.clock().started.read().unwrap() - .as_ref().unwrap().sample.get() as usize; - // If it's time to switch to the next clip: - if start <= sample0.saturating_sub(sample) { - // Samples elapsed since clip was supposed to start - let _skipped = sample0 - start; - // Switch over to enqueued clip - let started = Moment::from_sample(self.clock().timebase(), start as f64); - // Launch enqueued clip - *self.play_clip_mut() = Some((started, clip.clone())); - // Unset enqueuement (TODO: where to implement looping?) - *self.next_clip_mut() = None; - // Fill in remaining ticks of chunk from next clip. - self.play(scope, note_buf, out); - } - } - } - - fn play_chunk ( - &self, - scope: &ProcessScope, - note_buf: &mut Vec, - out: &mut [Vec>], - started: &Moment, - clip: &Option>> - ) -> bool { - // First sample to populate. Greater than 0 means that the first - // pulse of the clip falls somewhere in the middle of the chunk. - let sample = (scope.last_frame_time() as usize).saturating_sub( - started.sample.get() as usize + - self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize - ); - // Iterator that emits sample (index into output buffer at which to write MIDI event) - // paired with pulse (index into clip from which to take the MIDI event) for each - // sample of the output buffer that corresponds to a MIDI pulse. - let pulses = self.clock().timebase().pulses_between_samples(sample, sample + scope.n_frames() as usize); - // Notes active during current chunk. - let notes = &mut self.notes_out().write().unwrap(); - let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); - for (sample, pulse) in pulses { - // If a next clip is enqueued, and we're past the end of the current one, - // break the loop here (FIXME count pulse correctly) - let past_end = if clip.is_some() { pulse >= length } else { true }; - if self.next_clip().is_some() && past_end { - return true - } - // If there's a currently playing clip, output notes from it to buffer: - if let Some(ref clip) = clip { - Self::play_pulse(clip, pulse, sample, note_buf, out, notes) - } - } - false - } - - fn play_pulse ( - clip: &RwLock, - pulse: usize, - sample: usize, - note_buf: &mut Vec, - out: &mut [Vec>], - notes: &mut [bool;128] - ) { - // Source clip from which the MIDI events will be taken. - let clip = clip.read().unwrap(); - // Clip with zero length is not processed - if clip.length > 0 { - // Current pulse index in source clip - let pulse = pulse % clip.length; - // Output each MIDI event from clip at appropriate frames of output buffer: - for message in clip.notes[pulse].iter() { - // Clear output buffer for this MIDI event. - note_buf.clear(); - // TODO: support MIDI channels other than CH1. - let channel = 0.into(); - // Serialize MIDI event into message buffer. - LiveEvent::Midi { channel, message: *message } - .write(note_buf) - .unwrap(); - // Append serialized message to output buffer. - out[sample].push(note_buf.clone()); - // Update the list of currently held notes. - update_keys(&mut*notes, message); - } - } - } - - /// Write a chunk of MIDI data from the output buffer to all assigned output ports. - fn write (&mut self, scope: &ProcessScope, out: &[Vec>]) { - let samples = scope.n_frames() as usize; - for port in self.midi_outs_mut().iter_mut() { - Self::write_port(&mut port.port_mut().writer(scope), samples, out) - } - } - - /// Write a chunk of MIDI data from the output buffer to an output port. - fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec>]) { - for (time, events) in out.iter().enumerate().take(samples) { - for bytes in events.iter() { - writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{ - panic!("Failed to write MIDI data: {bytes:?}"); - }); - } - } - } -}