From 997d67a4879330af8d2ccb0a30f7c0efeb7bb3ee Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 11 May 2025 01:56:02 +0300 Subject: [PATCH] sequencer: extract get_sample_offset, get_pulses --- crates/device/src/sequencer/seq_audio.rs | 95 ++++++++++++++---------- 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/crates/device/src/sequencer/seq_audio.rs b/crates/device/src/sequencer/seq_audio.rs index c4f05a6d..0b78b210 100644 --- a/crates/device/src/sequencer/seq_audio.rs +++ b/crates/device/src/sequencer/seq_audio.rs @@ -163,9 +163,64 @@ pub trait MidiPlayer: HasPlayClip + HasClock + HasMidiOuts { } // 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)|{ + if let Some((started, clip)) = self.play_clip() { self.play_chunk(scope, note_buf, out, started, clip) - }) + } else { + true + } + } + + fn play_chunk ( + &self, + scope: &ProcessScope, + note_buf: &mut Vec, + out: &mut [Vec>], + started: &Moment, + clip: &Option>> + ) -> bool { + // Index of first sample to populate. + let offset = self.get_sample_offset(scope, started); + // Notes active during current chunk. + let notes = &mut self.notes_out().write().unwrap(); + // Length of clip. + let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); + // Write MIDI events from clip at sample offsets corresponding to pulses. + for (sample, pulse) in self.get_pulses(scope, offset) { + // 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 }; + // Is it time for switchover? + 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 + } + + /// Get index of first sample to populate. + /// + /// Greater than 0 means that the first pulse of the clip + /// falls somewhere in the middle of the chunk. + fn get_sample_offset (&self, scope: &ProcessScope, started: &Moment) -> usize{ + (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 + ) + } + + // Get iterator that emits sample paired with pulse. + // + // * Sample: index into output buffer at which to write MIDI event + // * Pulse: index into clip from which to take the MIDI event + // + // Emitted for each sample of the output buffer that corresponds to a MIDI pulse. + fn get_pulses (&self, scope: &ProcessScope, offset: usize) -> TicksIterator { + self.clock().timebase().pulses_between_samples( + offset, offset + scope.n_frames() as usize) } /// Handle switchover from current to next playing clip. @@ -197,42 +252,6 @@ pub trait MidiPlayer: HasPlayClip + HasClock + HasMidiOuts { } } - 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,