refactor tick timer to make sense

This commit is contained in:
🪞👃🪞 2024-07-06 14:13:31 +03:00
parent b3e6206b08
commit 597c3fa903
4 changed files with 82 additions and 80 deletions

View file

@ -13,7 +13,7 @@ impl Default for Timebase {
fn default () -> Self { fn default () -> Self {
Self { Self {
rate: 48000f64.into(), rate: 48000f64.into(),
bpm: 125f64.into(), bpm: 100f64.into(),
ppq: 96f64.into(), ppq: 96f64.into(),
} }
} }
@ -49,12 +49,12 @@ impl Timebase {
#[inline] pub fn ppq (&self) -> f64 { #[inline] pub fn ppq (&self) -> f64 {
self.ppq.load(Ordering::Relaxed) self.ppq.load(Ordering::Relaxed)
} }
#[inline] fn usec_per_pulse (&self) -> f64 {
self.usec_per_beat() / self.ppq() as f64
}
#[inline] pub fn pulse_per_frame (&self) -> f64 { #[inline] pub fn pulse_per_frame (&self) -> f64 {
self.usec_per_pulse() / self.usec_per_frame() as f64 self.usec_per_pulse() / self.usec_per_frame() as f64
} }
#[inline] fn usec_per_pulse (&self) -> f64 {
self.usec_per_beat() / self.ppq() as f64
}
#[inline] pub fn frame_to_pulse (&self, pulses: f64) -> f64 { #[inline] pub fn frame_to_pulse (&self, pulses: f64) -> f64 {
self.pulse_per_frame() * pulses self.pulse_per_frame() * pulses
} }
@ -64,19 +64,19 @@ impl Timebase {
#[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { #[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 {
4.0 * self.usec_per_beat() * num / den 4.0 * self.usec_per_beat() * num / den
} }
#[inline] fn pulses_per_second (&self) -> f64 {
self.beat_per_second() * self.ppq() as f64
}
#[inline] pub fn frames_per_pulse (&self) -> f64 { #[inline] pub fn frames_per_pulse (&self) -> f64 {
self.rate() as f64 / self.pulses_per_second() self.rate() as f64 / self.pulses_per_second()
} }
#[inline] fn pulses_per_second (&self) -> f64 {
#[inline] fn usec_to_frame (&self, usec: f64) -> f64 { self.beat_per_second() * self.ppq() as f64
usec * self.rate() / 1000.0
} }
#[inline] pub fn note_to_frame (&self, note: (f64, f64)) -> f64 { #[inline] pub fn note_to_frame (&self, note: (f64, f64)) -> f64 {
self.usec_to_frame(self.note_to_usec(note)) self.usec_to_frame(self.note_to_usec(note))
} }
#[inline] fn usec_to_frame (&self, usec: f64) -> f64 {
usec * self.rate() / 1000.0
}
#[inline] pub fn quantize ( #[inline] pub fn quantize (
&self, step: (f64, f64), time: f64 &self, step: (f64, f64), time: f64

View file

@ -126,8 +126,24 @@ pub fn main () -> Usually<()> {
12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
})); }));
track.add_phrase("Garage", ppq * 4, Some(phrase! { track.add_phrase("Garage", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
01 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
02 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
03 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
07 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
09 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
10 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
14 * 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: 35.into(), vel: 100.into() }, 00 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() },
02 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
07 * ppq/4 => MidiMessage::NoteOn { key: 34.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
11 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
11 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() }, 11 * ppq/4 => MidiMessage::NoteOn { key: 35.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() }, 12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
})); }));
@ -164,19 +180,19 @@ pub fn main () -> Usually<()> {
})?; })?;
state.add_track_with_cb(Some("Lead"), |_, track|{ state.add_track_with_cb(Some("Lead"), |_, track|{
track.add_device_with_cb(Plugin::lv2( //track.add_device_with_cb(Plugin::lv2(
"Helm", //"Helm",
"file:///home/user/.lv2/Helm.lv2" //"file:///home/user/.lv2/Helm.lv2"
)?, |track, device|{ //)?, |track, device|{
device.connect_midi_in(0, &track.midi_out.clone_unowned())?; //device.connect_midi_in(0, &track.midi_out.clone_unowned())?;
if let Some(Some(left)) = audio_outs.get(0) { //if let Some(Some(left)) = audio_outs.get(0) {
device.connect_audio_out(0, left)?; //device.connect_audio_out(0, left)?;
} //}
if let Some(Some(right)) = audio_outs.get(0) { //if let Some(Some(right)) = audio_outs.get(0) {
device.connect_audio_out(1, right)?; //device.connect_audio_out(1, right)?;
} //}
Ok(()) //Ok(())
})?; //})?;
track.sequence = Some(0); // FIXME track.sequence = Some(0); // FIXME
track.add_phrase("Custom", ppq * 4, None); track.add_phrase("Custom", ppq * 4, None);
Ok(()) Ok(())

View file

@ -1,5 +1,14 @@
use crate::core::*; use crate::core::*;
#[macro_export] macro_rules! phrase {
($($t:expr => $msg:expr),* $(,)?) => {{
let mut phrase = BTreeMap::new();
$(phrase.insert($t, vec![]);)*
$(phrase.get_mut(&$t).unwrap().push($msg);)*
phrase
}}
}
pub struct Phrase { pub struct Phrase {
pub name: String, pub name: String,
pub length: usize, pub length: usize,
@ -46,12 +55,10 @@ impl Phrase {
timebase: &Arc<Timebase>, timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64), (frame0, frames, _): (usize, usize, f64),
) { ) {
for (time, tick) in frames_to_ticks( for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames(
timebase.pulse_per_frame(), frame0, frame0 + frames
timebase.frame_to_pulse(self.length as f64),
frame0 as f64,
(frame0 + frames) as f64,
) { ) {
let tick = tick % self.length;
if let Some(events) = self.notes.get(&(tick as usize)) { if let Some(events) = self.notes.get(&(tick as usize)) {
for message in events.iter() { for message in events.iter() {
let mut buf = vec![]; let mut buf = vec![];
@ -70,72 +77,48 @@ impl Phrase {
} }
} }
fn frames_to_ticks (fpt: f64, repeat: f64, start: f64, end: f64) -> Box<dyn Iterator<Item=(usize, usize)>> { struct Ticks(f64);
let mut ticks = vec![];
let mut add_frame = |frame: f64|{
let jitter = frame.rem_euclid(fpt); // ramps
let next_jitter = (frame + 1.0).rem_euclid(fpt);
if jitter > next_jitter { // at head of ramp crossing
let time = frame as usize % (end as usize-start as usize);
let tick = (frame / fpt) as usize;
ticks.push((time, tick));
};
};
let start_frame = start % repeat;
let end_frame = end % repeat;
if start_frame < end_frame {
frames_to_ticks_1(&mut add_frame, start % repeat, end % repeat);
} else {
frames_to_ticks_2(&mut add_frame, start % repeat, end % repeat, repeat);
}
Box::new(ticks.into_iter())
}
fn frames_to_ticks_1 (add_frame: &mut impl FnMut(f64), start_frame: f64, end_frame: f64) { impl Ticks {
for frame in start_frame as usize..end_frame as usize { fn between_frames (&self, start: usize, end: usize) -> TicksIterator {
add_frame(frame as f64); TicksIterator(self.0, start, start, end)
} }
} }
fn frames_to_ticks_2 (add_frame: &mut impl FnMut(f64), start_frame: f64, end_frame: f64, repeat: f64) { struct TicksIterator(f64, usize, usize, usize);
let mut frame = start_frame as usize;
loop { impl Iterator for TicksIterator {
add_frame(frame as f64); type Item = (usize, usize);
frame = frame + 1; fn next (&mut self) -> Option<Self::Item> {
if frame >= repeat as usize { loop {
frame = 0; if self.1 > self.3 {
loop { return None
add_frame(frame as f64); }
frame = frame + 1; let fpt = self.0;
if frame >= (end_frame as usize).saturating_sub(1) { let frame = self.1 as f64;
break let start = self.2;
} let end = self.3;
self.1 = self.1 + 1;
//println!("{fpt} {frame} {start} {end}");
let jitter = frame.rem_euclid(fpt); // ramps
let next_jitter = (frame + 1.0).rem_euclid(fpt);
if jitter > next_jitter { // at crossing:
let time = (frame as usize) % (end as usize-start as usize);
let tick = (frame / fpt) as usize;
return Some((time, tick))
} }
break
} }
} }
} }
#[macro_export] macro_rules! phrase {
($($t:expr => $msg:expr),* $(,)?) => {{
let mut phrase = BTreeMap::new();
$(phrase.insert($t, vec![]);)*
$(phrase.get_mut(&$t).unwrap().push($msg);)*
phrase
}}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[test] #[test]
fn test_frames_to_ticks () { fn test_frames_to_ticks () {
let fpt = Timebase::default().pulse_per_frame(); let ticks = Ticks(12.3).between_frames(0, 100).collect::<Vec<_>>();
let repeat = 0.0; println!("{ticks:?}");
let start = 0.0;
let end = 0.0;
println!("{:?}", frames_to_ticks(fpt, repeat, start, end).collect::<Vec<_>>())
} }
} }

View file

@ -20,12 +20,15 @@ impl LV2Plugin {
break break
} }
let plugin = plugin.unwrap(); let plugin = plugin.unwrap();
let err = &format!("init {uri}");
// Instantiate // Instantiate
Ok(Self { Ok(Self {
world, world,
instance: unsafe { instance: unsafe {
plugin.instantiate(features.clone(), 48000.0).expect("boop") plugin
.instantiate(features.clone(), 48000.0)
.expect(&err)
}, },
port_list: { port_list: {
let mut port_list = vec![]; let mut port_list = vec![];