refactor: atomic timebase

This commit is contained in:
🪞👃🪞 2024-06-20 19:15:26 +03:00
parent 87c5e47b43
commit f77c84a99c
5 changed files with 116 additions and 99 deletions

View file

@ -8,11 +8,11 @@ pub struct Sequencer {
/// JACK transport handle. /// JACK transport handle.
transport: ::jack::Transport, transport: ::jack::Transport,
/// Holds info about tempo /// Holds info about tempo
timebase: Arc<Mutex<Timebase>>, timebase: Arc<Timebase>,
/// Sequencer resolution, e.g. 16 steps per beat. /// Sequencer resolution, e.g. 16 steps per beat.
resolution: u64, resolution: usize,
/// Steps in sequence, e.g. 64 16ths = 4 beat loop. /// Steps in sequence, e.g. 64 16ths = 4 beat loop.
steps: u64, steps: usize,
/// JACK MIDI input port that will be created. /// JACK MIDI input port that will be created.
input_port: Port<MidiIn>, input_port: Port<MidiIn>,
@ -56,7 +56,7 @@ enum SequencerView {
} }
impl Sequencer { impl Sequencer {
pub fn new (name: &str, timebase: &Arc<Mutex<Timebase>>) -> Usually<DynamicDevice<Self>> { pub fn new (name: &str, timebase: &Arc<Timebase>) -> Usually<DynamicDevice<Self>> {
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
DynamicDevice::new(render, handle, process, Self { DynamicDevice::new(render, handle, process, Self {
name: name.into(), name: name.into(),
@ -96,10 +96,10 @@ fn process_in (s: &mut Sequencer, scope: &ProcessScope) {
return return
} }
let pos = s.transport.query().unwrap().pos; let pos = s.transport.query().unwrap().pos;
let usecs = Frame(pos.frame()).to_usec(&s.rate).0; let usecs = s.timebase.frame_to_usec(pos.frame() as usize);
let steps = usecs / s.tempo.usec_per_step(s.resolution as u64).0; let steps = usecs / s.timebase.usec_per_step(s.resolution as usize);
let step = steps % s.steps; let step = steps % s.steps;
let tick = step * s.ticks_per_beat / s.resolution; let tick = step * s.timebase.ppq() / s.resolution;
for event in s.input_port.iter(scope) { for event in s.input_port.iter(scope) {
match midly::live::LiveEvent::parse(event.bytes).unwrap() { match midly::live::LiveEvent::parse(event.bytes).unwrap() {
@ -142,8 +142,12 @@ fn process_out (s: &mut Sequencer, scope: &ProcessScope) {
if transport.state != ::jack::TransportState::Rolling { if transport.state != ::jack::TransportState::Rolling {
return return
} }
let frame = transport.pos.frame() as u64; let frame = transport.pos.frame() as usize;
let ticks = s.frames_to_ticks(frame, frame + scope.n_frames() as u64); let ticks = s.timebase.frames_to_ticks(
frame,
frame + scope.n_frames() as usize,
s.timebase.fpb() as usize * s.steps / s.resolution
);
let mut writer = s.output_port.writer(scope); let mut writer = s.output_port.writer(scope);
for (time, tick) in ticks.iter() { for (time, tick) in ticks.iter() {
if let Some(events) = s.sequence.get(&(*tick as u32)) { if let Some(events) = s.sequence.get(&(*tick as u32)) {
@ -170,8 +174,8 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let pos = s.transport.query().unwrap().pos; let pos = s.transport.query().unwrap().pos;
let frame = pos.frame(); let frame = pos.frame();
let rate = pos.frame_rate().unwrap(); let rate = pos.frame_rate().unwrap();
let usecs = Frame(frame).to_usec(&Hz(rate)).0; let usecs = s.timebase.frame_to_usec(frame as usize);
let usec_per_step = s.tempo.usec_per_step(s.resolution as u64).0; let usec_per_step = s.timebase.usec_per_step(s.resolution as usize);
let steps = usecs / usec_per_step; let steps = usecs / usec_per_step;
let header = draw_header(s, buf, area, steps)?; let header = draw_header(s, buf, area, steps)?;
let piano = match s.mode { let piano = match s.mode {
@ -200,7 +204,7 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
})) }))
} }
fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually<Rect> { fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually<Rect> {
let rep = beat / s.steps; let rep = beat / s.steps;
let step = beat % s.steps; let step = beat % s.steps;
let reps = s.steps / s.resolution; let reps = s.steps / s.resolution;
@ -241,11 +245,12 @@ const KEYS_VERTICAL: [&'static str; 6] = [
"", "", "", "", "", "", "", "", "", "", "", "",
]; ];
fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually<Rect> { fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually<Rect> {
let ppq = s.timebase.ppq() as u32;
let Rect { x, y, .. } = area; let Rect { x, y, .. } = area;
let (time0, time1) = s.time_axis; let (time0, time1) = s.time_axis;
let (note0, note1) = s.note_axis; let (note0, note1) = s.note_axis;
let bw = Style::default().dim(); let _bw = Style::default().dim();
let bg = Style::default().on_black(); let bg = Style::default().on_black();
for key in note0..note1 { for key in note0..note1 {
let x = x + 5 + key - note0; let x = x + 5 + key - note0;
@ -257,9 +262,9 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu
let mut is_on = s.notes_on[key as usize]; let mut is_on = s.notes_on[key as usize];
let step = beat % s.steps; let step = beat % s.steps;
let (a, b, c) = ( let (a, b, c) = (
(step + 0) as u32 * s.ticks_per_beat as u32 / s.resolution as u32, (step + 0) as u32 * ppq / s.resolution as u32,
(step + 1) as u32 * s.ticks_per_beat as u32 / s.resolution as u32, (step + 1) as u32 * ppq / s.resolution as u32,
(step + 2) as u32 * s.ticks_per_beat as u32 / s.resolution as u32, (step + 2) as u32 * ppq / s.resolution as u32,
); );
let key = ::midly::num::u7::from(key as u8); let key = ::midly::num::u7::from(key as u8);
is_on = is_on || contains_note_on(&s.sequence, key, a, b); is_on = is_on || contains_note_on(&s.sequence, key, a, b);
@ -271,7 +276,7 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu
} }
for step in time0..time1 { for step in time0..time1 {
let y = y - time0 + step / 2; let y = y - time0 + step / 2;
let step = step as u64; let step = step as usize;
//buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg);
if step % s.resolution == 0 { if step % s.resolution == 0 {
buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
@ -280,9 +285,9 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu
let key = ::midly::num::u7::from_int_lossy(k as u8); let key = ::midly::num::u7::from_int_lossy(k as u8);
if step % 2 == 0 { if step % 2 == 0 {
let (a, b, c) = ( let (a, b, c) = (
(step + 0) as u32 * s.ticks_per_beat as u32 / s.resolution as u32, (step + 0) as u32 * ppq / s.resolution as u32,
(step + 1) as u32 * s.ticks_per_beat as u32 / s.resolution as u32, (step + 1) as u32 * ppq / s.resolution as u32,
(step + 2) as u32 * s.ticks_per_beat as u32 / s.resolution as u32, (step + 2) as u32 * ppq / s.resolution as u32,
); );
let (character, style) = match ( let (character, style) = match (
contains_note_on(&s.sequence, key, a, b), contains_note_on(&s.sequence, key, a, b),
@ -296,10 +301,10 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu
buf.set_string(x + 5 + k - note0, y, character, style); buf.set_string(x + 5 + k - note0, y, character, style);
} }
} }
if beat % s.steps == step as u64 { if beat % s.steps == step as usize {
buf.set_string(x + 4, y, if beat % 2 == 0 { "" } else { "" }, Style::default().yellow()); buf.set_string(x + 4, y, if beat % 2 == 0 { "" } else { "" }, Style::default().yellow());
for key in note0..note1 { for key in note0..note1 {
let color = if s.notes_on[key as usize] { let _color = if s.notes_on[key as usize] {
Style::default().red() Style::default().red()
} else { } else {
KEY_HORIZONTAL_STYLE[key as usize % 12] KEY_HORIZONTAL_STYLE[key as usize % 12]
@ -327,6 +332,23 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu
Ok(Rect { x, y, width: area.width, height }) Ok(Rect { x, y, width: area.width, height })
} }
fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool {
for (_, (_, events)) in sequence.range(start..end).enumerate() {
for event in events.iter() {
match event {
::midly::MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
const KEY_WHITE: Style = Style { const KEY_WHITE: Style = Style {
fg: Some(Color::Gray), fg: Some(Color::Gray),
bg: None, bg: None,
@ -348,26 +370,6 @@ const KEY_HORIZONTAL_STYLE: [Style;12] = [
KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
]; ];
fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool {
for (_, (_, events)) in sequence.range(start..end).enumerate() {
for event in events.iter() {
match event {
::midly::MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
const KEYS_VERTICAL: [&'static str; 6] = [
"", "", "", "", "", "",
];
fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> { fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, .. } = area; let Rect { x, y, .. } = area;
let (time0, time1) = s.time_axis; let (time0, time1) = s.time_axis;
@ -385,9 +387,9 @@ fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect
} }
} }
for step in time0..time1 { for step in time0..time1 {
let time_start = step as u32 * s.ticks_per_beat as u32; let time_start = step as u32 * s.timebase.ppq() as u32;
let time_end = (step + 1) as u32 * s.ticks_per_beat as u32; let time_end = (step + 1) as u32 * s.timebase.ppq() as u32;
for (i, (t, events)) in s.sequence.range(time_start..time_end).enumerate() { for (_, (_, events)) in s.sequence.range(time_start..time_end).enumerate() {
if events.len() > 0 { if events.len() > 0 {
buf.set_string(x + 5 + step as u16, y, "", bw); buf.set_string(x + 5 + step as u16, y, "", bw);
} }
@ -446,12 +448,12 @@ pub const COMMANDS: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
fn nop (_: &mut Sequencer) { fn nop (_: &mut Sequencer) {
} }
fn note_add (s: &mut Sequencer) { fn note_add (s: &mut Sequencer) {
let pos = s.transport.query().unwrap().pos; let pos = s.transport.query().unwrap().pos;
let usecs = Frame(pos.frame()).to_usec(&s.rate).0; let usecs = s.timebase.frame_to_usec(pos.frame() as usize);
let steps = usecs / s.tempo.usec_per_step(s.resolution as u64).0; let steps = usecs / s.timebase.usec_per_step(s.resolution as usize);
let step = (s.time_axis.0 + s.time_cursor) as u32; let step = (s.time_axis.0 + s.time_cursor) as u32;
let start = (step as u64 * s.ticks_per_beat / s.resolution) as u32; let start = (step as usize * s.timebase.ppq() / s.resolution) as u32;
let end = ((step + 1) as u64 * s.ticks_per_beat / s.resolution) as u32; let end = ((step + 1) as usize * s.timebase.ppq() / s.resolution) as u32;
let key = ::midly::num::u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8); let key = ::midly::num::u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8);
let note_on = ::midly::MidiMessage::NoteOn { key, vel: 100.into() }; let note_on = ::midly::MidiMessage::NoteOn { key, vel: 100.into() };
let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() }; let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() };
@ -571,7 +573,7 @@ fn quantize_prev (s: &mut Sequencer) {
println!("T/L = {:.03}", s.tpl()); println!("T/L = {:.03}", s.tpl());
let fpt = s.fpt(); let fpt = s.fpt();
let frames_per_chunk = 240; let frames_per_chunk = 240;
let chunk = |chunk: u64| s.frames_to_ticks( let chunk = |chunk: usize| s.frames_to_ticks(
chunk * frames_per_chunk, chunk * frames_per_chunk,
(chunk + 1) * frames_per_chunk (chunk + 1) * frames_per_chunk
); );

View file

@ -3,7 +3,9 @@ use crate::prelude::*;
pub struct Transport { pub struct Transport {
name: String, name: String,
/// Holds info about tempo /// Holds info about tempo
timebase: Arc<Mutex<Timebase>>, timebase: Arc<Timebase>,
transport: ::jack::Transport,
} }
impl Transport { impl Transport {
@ -12,11 +14,17 @@ impl Transport {
let transport = client.transport(); let transport = client.transport();
DynamicDevice::new(render, handle, process, Self { DynamicDevice::new(render, handle, process, Self {
name: name.into(), name: name.into(),
timebase: Timebase { timebase: Arc::new(Timebase {
rate: transport.query()?.pos.frame_rate(), rate: AtomicUsize::new(
tempo: 113000, transport.query()?.pos.frame_rate().map(|x|x as usize).unwrap_or(0)
ppq: 96, ),
}, tempo: AtomicUsize::new(
113000
),
ppq: AtomicUsize::new(
96
),
}),
transport transport
}).activate(client) }).activate(client)
} }
@ -39,6 +47,10 @@ impl Transport {
pub fn stop (&mut self) -> Result<(), Box<dyn Error>> { pub fn stop (&mut self) -> Result<(), Box<dyn Error>> {
Ok(self.transport.stop()?) Ok(self.transport.stop()?)
} }
pub fn timebase (&self) -> Arc<Timebase> {
self.timebase.clone()
}
} }
pub fn process (_: &mut Transport, _: &Client, _: &ProcessScope) -> Control { pub fn process (_: &mut Transport, _: &Client, _: &ProcessScope) -> Control {
@ -63,8 +75,8 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
"0.0.00", "0.0.00",
"0:00.000", "0:00.000",
&format!("BPM {:03}.{:03}", &format!("BPM {:03}.{:03}",
state.bpm as u64, state.timebase.tempo() / 1000,
((state.bpm % 1.0) * 1000.0) as u64 state.timebase.tempo() % 1000,
) )
].iter() { ].iter() {
buf.set_string(area.x + x, area.y + 2, button, label); buf.set_string(area.x + x, area.y + 2, button, label);

View file

@ -21,8 +21,8 @@ fn main () -> Result<(), Box<dyn Error>> {
let xdg = microxdg::XdgApp::new("dawdle")?; let xdg = microxdg::XdgApp::new("dawdle")?;
crate::config::create_dirs(&xdg)?; crate::config::create_dirs(&xdg)?;
//crate::device::run(Sequencer::new("Rhythm#000")?) //crate::device::run(Sequencer::new("Rhythm#000")?)
const transport = Transport::new("Transport")?; let transport = Transport::new("Transport")?;
const timebase = transport.timebase.clone(); let timebase = transport.state.lock().unwrap().timebase();
crate::device::run(Rows::new(true, vec![ crate::device::run(Rows::new(true, vec![
Box::new(transport), Box::new(transport),
Box::new(Columns::new(true, vec![ Box::new(Columns::new(true, vec![

View file

@ -33,7 +33,11 @@ pub use std::time::Duration;
pub use std::sync::{ pub use std::sync::{
Arc, Arc,
Mutex, Mutex,
atomic::{AtomicBool, Ordering}, atomic::{
Ordering,
AtomicBool,
AtomicUsize
},
mpsc::{self, channel, Sender, Receiver} mpsc::{self, channel, Sender, Receiver}
}; };

View file

@ -1,10 +1,12 @@
use crate::prelude::*;
pub struct Timebase { pub struct Timebase {
/// Frames per second /// Frames per second
pub rate: Option<usize>, pub rate: AtomicUsize,
/// Beats per minute /// Beats per minute
pub tempo: Option<usize>, pub tempo: AtomicUsize,
/// Ticks per beat /// Ticks per beat
pub ppq: usize, pub ppq: AtomicUsize,
} }
enum QuantizeMode { enum QuantizeMode {
@ -24,22 +26,32 @@ struct Loop<T> {
/// NoteDuration in musical terms. Has definite usec value /// NoteDuration in musical terms. Has definite usec value
/// for given bpm and sample rate. /// for given bpm and sample rate.
pub enum NoteDuration { pub enum NoteDuration {
Nth(u16, u16), Nth(usize, usize),
Dotted(Box<Self>), Dotted(Box<Self>),
Tuplet(u16, Box<Self>), Tuplet(usize, Box<Self>),
} }
impl Timebase { impl Timebase {
pub fn rate (&self) -> usize {
self.rate.load(Ordering::Relaxed)
}
pub fn tempo (&self) -> usize {
self.tempo.load(Ordering::Relaxed)
}
pub fn ppq (&self) -> usize {
self.ppq.load(Ordering::Relaxed)
}
/// Beats per second /// Beats per second
#[inline] fn bps (&self) -> f64 { #[inline] fn bps (&self) -> f64 {
self.tempo.0 as f64 / 60000.0 self.tempo() as f64 / 60000.0
} }
/// Frames per second /// Frames per second
#[inline] fn fps (&self) -> u64 { #[inline] fn fps (&self) -> usize {
self.rate.0 as u64 self.rate()
} }
/// Frames per beat /// Frames per beat
#[inline] fn fpb (&self) -> f64 { #[inline] pub fn fpb (&self) -> f64 {
self.fps() as f64 / self.bps() self.fps() as f64 / self.bps()
} }
/// Frames per tick FIXME double times /// Frames per tick FIXME double times
@ -47,19 +59,18 @@ impl Timebase {
self.fps() as f64 / self.tps() self.fps() as f64 / self.tps()
} }
/// Frames per loop /// Frames per loop
#[inline] fn fpl (&self) -> f64 { #[inline] fn fpl (&self, steps: f64, steps_per_beat: f64) -> f64 {
self.fpb() * self.steps as f64 / self.steps_per_beat as f64 self.fpb() * steps / steps_per_beat
} }
/// Ticks per beat /// Ticks per beat
#[inline] fn tpb (&self) -> f64 { #[inline] fn tpb (&self) -> f64 {
self.ticks_per_beat as f64 self.ppq.load(Ordering::Relaxed) as f64
} }
/// Ticks per second /// Ticks per second
#[inline] fn tps (&self) -> f64 { #[inline] fn tps (&self) -> f64 {
self.bps() * self.tpb() self.bps() * self.tpb()
} }
fn frames_to_ticks (&self, start: u64, end: u64) -> Vec<(u64, u64)> { pub fn frames_to_ticks (&self, start: usize, end: usize, fpl: usize) -> Vec<(usize, usize)> {
let fpl = self.fpl() as u64;
let start_frame = start % fpl; let start_frame = start % fpl;
let end_frame = end % fpl; let end_frame = end % fpl;
let fpt = self.fpt(); let fpt = self.fpt();
@ -69,7 +80,7 @@ impl Timebase {
let last_jitter = (frame - 1.0).max(0.0) % fpt; let last_jitter = (frame - 1.0).max(0.0) % fpt;
let next_jitter = frame + 1.0 % fpt; let next_jitter = frame + 1.0 % fpt;
if jitter <= last_jitter && jitter <= next_jitter { if jitter <= last_jitter && jitter <= next_jitter {
ticks.push((frame as u64 % (end-start), (frame / fpt) as u64)); ticks.push((frame as usize % (end-start), (frame / fpt) as usize));
}; };
}; };
if start_frame < end_frame { if start_frame < end_frame {
@ -99,11 +110,11 @@ impl Timebase {
#[inline] #[inline]
pub fn frame_to_usec (&self, frame: usize) -> usize { pub fn frame_to_usec (&self, frame: usize) -> usize {
frame * 1000000 / self.rate frame * 1000000 / self.rate()
} }
#[inline] #[inline]
pub fn usec_to_frame (&self, usec: usize) -> usize { pub fn usec_to_frame (&self, usec: usize) -> usize {
frame * self.rate / 1000 usec * self.rate() / 1000
} }
#[inline] #[inline]
pub fn usec_per_bar (&self, beats_per_bar: usize) -> usize { pub fn usec_per_bar (&self, beats_per_bar: usize) -> usize {
@ -111,7 +122,7 @@ impl Timebase {
} }
#[inline] #[inline]
pub fn usec_per_beat (&self) -> usize { pub fn usec_per_beat (&self) -> usize {
60_000_000_000 / self.tempo 60_000_000_000 / self.tempo()
} }
#[inline] #[inline]
pub fn usec_per_step (&self, divisor: usize) -> usize { pub fn usec_per_step (&self, divisor: usize) -> usize {
@ -119,7 +130,7 @@ impl Timebase {
} }
#[inline] #[inline]
pub fn usec_per_tick (&self) -> usize { pub fn usec_per_tick (&self) -> usize {
self.usec_per_beat() / self.ppq self.usec_per_beat() / self.ppq()
} }
#[inline] #[inline]
pub fn usec_per_note (&self, note: &NoteDuration) -> usize { pub fn usec_per_note (&self, note: &NoteDuration) -> usize {
@ -132,6 +143,10 @@ impl Timebase {
self.usec_per_note(note) * 2 / *n, self.usec_per_note(note) * 2 / *n,
} }
} }
#[inline]
pub fn frame_per_note (&self, note: &NoteDuration) -> usize {
self.usec_to_frame(self.usec_per_note(note))
}
pub fn quantize (&self, step: &NoteDuration, time: usize) -> (usize, usize) { pub fn quantize (&self, step: &NoteDuration, time: usize) -> (usize, usize) {
let step = self.usec_per_note(step); let step = self.usec_per_note(step);
let time = time / step; let time = time / step;
@ -146,19 +161,3 @@ impl Timebase {
.collect() .collect()
} }
} }
impl NoteDuration {
fn to_usec (&self, bpm: &Tempo) -> Usec {
Usec(match self {
Self::Nth(time, flies) =>
bpm.usec_per_beat().0 * *time as usize / *flies as usize,
Self::Dotted(duration) =>
duration.to_usec(bpm).0 * 3 / 2,
Self::Tuplet(n, duration) =>
duration.to_usec(bpm).0 * 2 / *n as usize,
})
}
fn to_frame (&self, bpm: &Tempo, rate: &Hz) -> Frame {
self.to_usec(bpm).to_frame(rate)
}
}