sync midi by frames rather than useconds

This commit is contained in:
🪞👃🪞 2024-07-05 20:44:31 +03:00
parent 63b5eb3740
commit b1e4ec3a88
4 changed files with 29 additions and 17 deletions

View file

@ -107,6 +107,13 @@ pub fn main () -> Usually<()> {
08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
})); }));
track.add_phrase("5 kicks", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
14 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
}));
track.add_phrase("D-Beat", ppq * 4, Some(phrase! { track.add_phrase("D-Beat", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }, 00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }, 00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
@ -175,9 +182,9 @@ pub fn main () -> Usually<()> {
state.scenes = vec![ state.scenes = vec![
Scene::new("Intro", vec![None, Some(0), None, None]), Scene::new("Intro", vec![None, Some(0), None, None]),
Scene::new("Hook", vec![Some(0), Some(1), None, None]), Scene::new("Hook", vec![Some(0), Some(1), None, None]),
Scene::new("Verse", vec![Some(1), Some(0), Some(0), None]), Scene::new("Verse", vec![Some(2), Some(0), Some(0), None]),
Scene::new("Chorus", vec![Some(0), Some(1), None, None]), Scene::new("Chorus", vec![Some(1), Some(1), None, None]),
Scene::new("Bridge", vec![Some(2), Some(0), Some(0), None]), Scene::new("Bridge", vec![Some(3), Some(0), Some(0), None]),
Scene::new("Outro", vec![None, Some(1), None, None]), Scene::new("Outro", vec![None, Some(1), None, None]),
]; ];

View file

@ -32,7 +32,7 @@ pub struct App {
/// Current position according to transport /// Current position according to transport
pub playhead: usize, pub playhead: usize,
/// Position of T0 for this playback within global timeline /// Position of T0 for this playback within global timeline
pub play_started: Option<usize>, pub play_started: Option<(usize, usize)>,
/// Current sample rate and tempo. /// Current sample rate and tempo.
pub timebase: Arc<Timebase>, pub timebase: Arc<Timebase>,
/// Display mode of grid section /// Display mode of grid section
@ -86,7 +86,10 @@ process!(App |self, _client, scope| {
if self.playing != Some(transport.state) { if self.playing != Some(transport.state) {
match transport.state { match transport.state {
TransportState::Rolling => { TransportState::Rolling => {
self.play_started = Some(current_usecs as usize); self.play_started = Some((
current_frames as usize,
current_usecs as usize,
));
}, },
TransportState::Stopped => { TransportState::Stopped => {
self.play_started = None; self.play_started = None;

View file

@ -44,12 +44,12 @@ impl Phrase {
output: &mut MIDIChunk, output: &mut MIDIChunk,
notes_on: &mut Vec<bool>, notes_on: &mut Vec<bool>,
timebase: &Arc<Timebase>, timebase: &Arc<Timebase>,
(usec0, usecs, _): (usize, usize, f64), (frame0, frames, _): (usize, usize, f64),
) { ) {
let start = timebase.usecs_frames(usec0 as f64); let start = frame0;
let end = timebase.usecs_frames((usec0 + usecs) as f64); let end = frame0 + frames;
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 as f64, end as f64, repeat);
//panic!("{start} {end} {repeat} {ticks:?}"); //panic!("{start} {end} {repeat} {ticks:?}");
for (time, tick) in ticks.iter() { for (time, tick) in ticks.iter() {
if let Some(events) = self.notes.get(&(*tick as usize)) { if let Some(events) = self.notes.get(&(*tick as usize)) {
@ -84,11 +84,13 @@ impl Timebase {
//panic!("{start_frame} {end_frame} {fpt}"); //panic!("{start_frame} {end_frame} {fpt}");
let mut ticks = vec![]; let mut ticks = vec![];
let mut add_frame = |frame: f64|{ let mut add_frame = |frame: f64|{
let jitter = frame.rem_euclid(fpt); let jitter = frame.rem_euclid(fpt); // ramps
let last_jitter = (frame - 1.0).max(0.0) % fpt; let next_jitter = (frame + 1.0).rem_euclid(fpt);
let next_jitter = frame + 1.0 % fpt; if jitter > next_jitter { // at head of ramp crossing
if jitter <= last_jitter && jitter <= next_jitter { ticks.push((
ticks.push((frame as usize % (end as usize-start as usize), (frame / fpt) as usize)); frame as usize % (end as usize-start as usize),
(frame / fpt) as usize)
);
}; };
}; };
if start_frame < end_frame { if start_frame < end_frame {

View file

@ -119,7 +119,7 @@ impl Track {
input: MidiIter, input: MidiIter,
timebase: &Arc<Timebase>, timebase: &Arc<Timebase>,
playing: Option<TransportState>, playing: Option<TransportState>,
started: Option<usize>, started: Option<(usize, usize)>,
quant: usize, quant: usize,
reset: bool, reset: bool,
scope: &ProcessScope, scope: &ProcessScope,
@ -145,13 +145,13 @@ impl Track {
// Play from phrase into output buffer // Play from phrase into output buffer
let phrase = &mut self.sequence.map(|id|self.phrases.get_mut(id)).flatten(); 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(started) = started { if let Some((start_frame, start_usec)) = started {
if let Some(phrase) = phrase { 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,
(usec0 - started, usecs, period) (frame0.saturating_sub(start_frame), frames, period)
); );
} }
} }