wip: correct cycle timings (jitter eats notes)

This commit is contained in:
🪞👃🪞 2024-07-05 14:36:09 +03:00
parent e83802e1fd
commit 665885f6ff
5 changed files with 90 additions and 76 deletions

View file

@ -35,6 +35,10 @@ impl Timebase {
#[inline] pub fn frames_usecs (&self, frame: f64) -> f64 { #[inline] pub fn frames_usecs (&self, frame: f64) -> f64 {
frame * self.frame_usec() frame * self.frame_usec()
} }
/// Frames to usecs
#[inline] pub fn usecs_frames (&self, usec: f64) -> f64 {
usec / self.frame_usec()
}
/// Beats per minute /// Beats per minute
#[inline] pub fn bpm (&self) -> f64 { #[inline] pub fn bpm (&self) -> f64 {
@ -53,6 +57,10 @@ impl Timebase {
#[inline] fn pulse_usec (&self) -> f64 { #[inline] fn pulse_usec (&self) -> f64 {
self.beat_usec() / self.ppq() as 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 /// Pulses per frame
#[inline] pub fn pulse_frame (&self) -> f64 { #[inline] pub fn pulse_frame (&self) -> f64 {
self.pulse_usec() / self.frame_usec() as f64 self.pulse_usec() / self.frame_usec() as f64
@ -110,49 +118,6 @@ impl Timebase {
#[inline] pub fn frames_per_tick (&self) -> f64 { #[inline] pub fn frames_per_tick (&self) -> f64 {
self.rate() as f64 / self.ticks_per_second() 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 { #[cfg(test)] mod test {
use super::*; use super::*;

View file

@ -23,6 +23,7 @@ pub use ::_jack::{
ClientStatus, ClientStatus,
ClosureProcessHandler, ClosureProcessHandler,
Control, Control,
CycleTimes,
Frames, Frames,
MidiIn, MidiIn,
MidiIter, MidiIter,

View file

@ -237,17 +237,23 @@ process!(App |self, _client, scope| {
} }
self.playing = Some(transport.state); self.playing = Some(transport.state);
self.playhead = transport.pos.frame() as usize; self.playhead = transport.pos.frame() as usize;
let frame = scope.last_frame_time() as usize;
let frames = scope.n_frames() 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() { for track in self.tracks.iter_mut() {
track.process( track.process(
self.midi_in.as_ref().unwrap().iter(scope), self.midi_in.as_ref().unwrap().iter(scope),
&self.timebase, &self.timebase,
self.playing, self.playing,
&scope,
self.playhead,
frames,
panic, 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 Control::Continue

View file

@ -44,20 +44,15 @@ impl Phrase {
output: &mut MIDIChunk, output: &mut MIDIChunk,
notes_on: &mut Vec<bool>, notes_on: &mut Vec<bool>,
timebase: &Arc<Timebase>, timebase: &Arc<Timebase>,
frame0: usize, (usec0, usecs, _): (usize, usize, f64),
frames: usize,
) { ) {
let start = frame0 as f64; let start = timebase.usecs_frames(usec0 as f64);
let end = start + frames as f64; let end = timebase.usecs_frames((usec0 + usecs) as f64);
let repeat = timebase.pulses_frames(self.length as f64); let repeat = timebase.pulses_frames(self.length as f64);
let ticks = timebase.frames_to_ticks(start, end, repeat); let ticks = timebase.frames_to_ticks(start, end, repeat);
//panic!("{start} {end} {repeat} {ticks:?}");
for (time, tick) in ticks.iter() { for (time, tick) in ticks.iter() {
let events = self.notes.get(&(*tick as usize)); if let Some(events) = self.notes.get(&(*tick as usize)) {
if events.is_none() { for message in events.iter() {
continue
}
for message in events.unwrap().iter() {
let mut buf = vec![]; let mut buf = vec![];
let channel = 0.into(); let channel = 0.into();
let message = *message; let message = *message;
@ -73,6 +68,53 @@ impl Phrase {
} }
} }
} }
}
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 { #[macro_export] macro_rules! phrase {
($($t:expr => $msg:expr),* $(,)?) => {{ ($($t:expr => $msg:expr),* $(,)?) => {{

View file

@ -115,10 +115,11 @@ impl Track {
input: MidiIter, input: MidiIter,
timebase: &Arc<Timebase>, timebase: &Arc<Timebase>,
playing: Option<TransportState>, playing: Option<TransportState>,
scope: &ProcessScope,
frame0: usize,
frames: usize,
panic: bool, panic: bool,
scope: &ProcessScope,
(frame0, frames): (usize, usize),
(usec0, usecs): (usize, usize),
period: f64,
) { ) {
// Need to be borrowed outside the conditionals? // Need to be borrowed outside the conditionals?
let recording = self.recording; let recording = self.recording;
@ -132,14 +133,14 @@ impl Track {
all_notes_off(&mut self.midi_out_buf); all_notes_off(&mut self.midi_out_buf);
} }
// Play from phrase into output buffer // Play from phrase into output buffer
if let Some(Some(phrase)) = self.sequence.map(|id|self.phrases.get(id)) { let phrase = &mut self.sequence.map(|id|self.phrases.get_mut(id)).flatten();
if playing == Some(TransportState::Rolling) { if playing == Some(TransportState::Rolling) {
if let Some(phrase) = phrase {
phrase.process_out( phrase.process_out(
&mut self.midi_out_buf, &mut self.midi_out_buf,
&mut self.notes_on, &mut self.notes_on,
timebase, timebase,
frame0, (usec0, usecs, period)
frames
); );
} }
} }
@ -147,7 +148,6 @@ impl Track {
if self.recording || self.monitoring { if self.recording || self.monitoring {
// For highlighting keys // For highlighting keys
let notes_on = &mut self.notes_on; 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) { for (time, event, bytes) in parse_midi_input(input) {
match event { match event {
LiveEvent::Midi { message, .. } => { LiveEvent::Midi { message, .. } => {
@ -155,7 +155,7 @@ impl Track {
self.midi_out_buf[time].push(bytes.to_vec()) self.midi_out_buf[time].push(bytes.to_vec())
} }
if recording { 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 = timebase.frames_pulses((frame0 + time) as f64) as usize;
let pulse = pulse % phrase.length; let pulse = pulse % phrase.length;
let contains = phrase.notes.contains_key(&pulse); let contains = phrase.notes.contains_key(&pulse);