From 665885f6ff40a3c44fd2a0c7692abfe470ef4bc8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 5 Jul 2024 14:36:09 +0300 Subject: [PATCH] wip: correct cycle timings (jitter eats notes) --- src/core/time.rs | 51 +++++----------------------- src/jack.rs | 1 + src/main.rs | 14 +++++--- src/model/phrase.rs | 82 ++++++++++++++++++++++++++++++++++----------- src/model/track.rs | 18 +++++----- 5 files changed, 90 insertions(+), 76 deletions(-) diff --git a/src/core/time.rs b/src/core/time.rs index 2218ac5b..df0de2ca 100644 --- a/src/core/time.rs +++ b/src/core/time.rs @@ -35,6 +35,10 @@ impl Timebase { #[inline] pub fn frames_usecs (&self, frame: f64) -> f64 { frame * self.frame_usec() } + /// Frames to usecs + #[inline] pub fn usecs_frames (&self, usec: f64) -> f64 { + usec / self.frame_usec() + } /// Beats per minute #[inline] pub fn bpm (&self) -> f64 { @@ -53,6 +57,10 @@ impl Timebase { #[inline] fn pulse_usec (&self) -> f64 { self.beat_usec() / self.ppq() as f64 } + /// Usecs to pulses + #[inline] pub fn pulses_usecs (&self, pulses: f64) -> f64 { + self.pulse_usec() * pulses + } /// Pulses per frame #[inline] pub fn pulse_frame (&self) -> f64 { self.pulse_usec() / self.frame_usec() as f64 @@ -110,49 +118,6 @@ impl Timebase { #[inline] pub fn frames_per_tick (&self) -> f64 { self.rate() as f64 / self.ticks_per_second() } - pub fn frames_to_ticks ( - &self, - start: f64, - end: f64, - repeat: f64, - ) -> Vec<(usize, usize)> { - let start_frame = start % repeat; - let end_frame = end % repeat; - let fpt = self.pulse_frame(); - //panic!("{start_frame} {end_frame} {fpt}"); - let mut ticks = vec![]; - let mut add_frame = |frame: f64|{ - let jitter = frame.rem_euclid(fpt); - let last_jitter = (frame - 1.0).max(0.0) % fpt; - let next_jitter = frame + 1.0 % fpt; - if jitter <= last_jitter && jitter <= next_jitter { - ticks.push((frame as usize % (end as usize-start as usize), (frame / fpt) as usize)); - }; - }; - if start_frame < end_frame { - for frame in start_frame as usize..end_frame as usize { - add_frame(frame as f64); - } - } else { - let mut frame = start_frame as usize; - loop { - add_frame(frame as f64); - frame = frame + 1; - if frame >= repeat as usize { - frame = 0; - loop { - add_frame(frame as f64); - frame = frame + 1; - if frame >= (end_frame as usize).saturating_sub(1) { - break - } - } - break - } - } - } - ticks - } } #[cfg(test)] mod test { use super::*; diff --git a/src/jack.rs b/src/jack.rs index a33c946b..ef0a2ebd 100644 --- a/src/jack.rs +++ b/src/jack.rs @@ -23,6 +23,7 @@ pub use ::_jack::{ ClientStatus, ClosureProcessHandler, Control, + CycleTimes, Frames, MidiIn, MidiIter, diff --git a/src/main.rs b/src/main.rs index 8bf408a4..377cfd65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -237,17 +237,23 @@ process!(App |self, _client, scope| { } self.playing = Some(transport.state); self.playhead = transport.pos.frame() as usize; - let frame = scope.last_frame_time() as usize; let frames = scope.n_frames() as usize; + let CycleTimes { + current_frames, + current_usecs, + next_usecs, + period_usecs + } = scope.cycle_times().unwrap(); for track in self.tracks.iter_mut() { track.process( self.midi_in.as_ref().unwrap().iter(scope), &self.timebase, self.playing, - &scope, - self.playhead, - frames, panic, + &scope, + (current_frames as usize, frames), + (current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize), + period_usecs as f64 ); } Control::Continue diff --git a/src/model/phrase.rs b/src/model/phrase.rs index e6eddfd9..7a224b4e 100644 --- a/src/model/phrase.rs +++ b/src/model/phrase.rs @@ -44,36 +44,78 @@ impl Phrase { output: &mut MIDIChunk, notes_on: &mut Vec, timebase: &Arc, - frame0: usize, - frames: usize, + (usec0, usecs, _): (usize, usize, f64), ) { - let start = frame0 as f64; - let end = start + frames as f64; + let start = timebase.usecs_frames(usec0 as f64); + let end = timebase.usecs_frames((usec0 + usecs) as f64); let repeat = timebase.pulses_frames(self.length as f64); let ticks = timebase.frames_to_ticks(start, end, repeat); - //panic!("{start} {end} {repeat} {ticks:?}"); for (time, tick) in ticks.iter() { - let events = self.notes.get(&(*tick as usize)); - if events.is_none() { - continue - } - for message in events.unwrap().iter() { - let mut buf = vec![]; - let channel = 0.into(); - let message = *message; - match message { - MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true, - MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false, - _ => {} + if let Some(events) = self.notes.get(&(*tick as usize)) { + for message in events.iter() { + let mut buf = vec![]; + let channel = 0.into(); + let message = *message; + 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(); + let t = *time as usize; + output[t].push(buf); } - LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); - let t = *time as usize; - output[t].push(buf); } } } } +impl Timebase { + pub fn frames_to_ticks ( + &self, + start: f64, + end: f64, + repeat: f64, + ) -> Vec<(usize, usize)> { + let start_frame = start % repeat; + let end_frame = end % repeat; + let fpt = self.pulse_frame(); + //panic!("{start_frame} {end_frame} {fpt}"); + let mut ticks = vec![]; + let mut add_frame = |frame: f64|{ + let jitter = frame.rem_euclid(fpt); + let last_jitter = (frame - 1.0).max(0.0) % fpt; + let next_jitter = frame + 1.0 % fpt; + if jitter <= last_jitter && jitter <= next_jitter { + ticks.push((frame as usize % (end as usize-start as usize), (frame / fpt) as usize)); + }; + }; + if start_frame < end_frame { + for frame in start_frame as usize..end_frame as usize { + add_frame(frame as f64); + } + } else { + let mut frame = start_frame as usize; + loop { + add_frame(frame as f64); + frame = frame + 1; + if frame >= repeat as usize { + frame = 0; + loop { + add_frame(frame as f64); + frame = frame + 1; + if frame >= (end_frame as usize).saturating_sub(1) { + break + } + } + break + } + } + } + ticks + } +} + #[macro_export] macro_rules! phrase { ($($t:expr => $msg:expr),* $(,)?) => {{ let mut phrase = BTreeMap::new(); diff --git a/src/model/track.rs b/src/model/track.rs index b3d1a04c..e6447eeb 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -115,10 +115,11 @@ impl Track { input: MidiIter, timebase: &Arc, playing: Option, - scope: &ProcessScope, - frame0: usize, - frames: usize, panic: bool, + scope: &ProcessScope, + (frame0, frames): (usize, usize), + (usec0, usecs): (usize, usize), + period: f64, ) { // Need to be borrowed outside the conditionals? let recording = self.recording; @@ -132,14 +133,14 @@ impl Track { all_notes_off(&mut self.midi_out_buf); } // Play from phrase into output buffer - if let Some(Some(phrase)) = self.sequence.map(|id|self.phrases.get(id)) { - if playing == Some(TransportState::Rolling) { + let phrase = &mut self.sequence.map(|id|self.phrases.get_mut(id)).flatten(); + if playing == Some(TransportState::Rolling) { + if let Some(phrase) = phrase { phrase.process_out( &mut self.midi_out_buf, &mut self.notes_on, timebase, - frame0, - frames + (usec0, usecs, period) ); } } @@ -147,7 +148,6 @@ impl Track { if self.recording || self.monitoring { // For highlighting keys let notes_on = &mut self.notes_on; - let phrase = &mut self.sequence.map(|id|self.phrases.get_mut(id)); for (time, event, bytes) in parse_midi_input(input) { match event { LiveEvent::Midi { message, .. } => { @@ -155,7 +155,7 @@ impl Track { self.midi_out_buf[time].push(bytes.to_vec()) } if recording { - if let Some(Some(phrase)) = phrase { + if let Some(phrase) = phrase { let pulse = timebase.frames_pulses((frame0 + time) as f64) as usize; let pulse = pulse % phrase.length; let contains = phrase.notes.contains_key(&pulse);