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.
transport: ::jack::Transport,
/// Holds info about tempo
timebase: Arc<Mutex<Timebase>>,
timebase: Arc<Timebase>,
/// Sequencer resolution, e.g. 16 steps per beat.
resolution: u64,
resolution: usize,
/// Steps in sequence, e.g. 64 16ths = 4 beat loop.
steps: u64,
steps: usize,
/// JACK MIDI input port that will be created.
input_port: Port<MidiIn>,
@ -56,7 +56,7 @@ enum SequencerView {
}
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)?;
DynamicDevice::new(render, handle, process, Self {
name: name.into(),
@ -96,10 +96,10 @@ fn process_in (s: &mut Sequencer, scope: &ProcessScope) {
return
}
let pos = s.transport.query().unwrap().pos;
let usecs = Frame(pos.frame()).to_usec(&s.rate).0;
let steps = usecs / s.tempo.usec_per_step(s.resolution as u64).0;
let usecs = s.timebase.frame_to_usec(pos.frame() as usize);
let steps = usecs / s.timebase.usec_per_step(s.resolution as usize);
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) {
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 {
return
}
let frame = transport.pos.frame() as u64;
let ticks = s.frames_to_ticks(frame, frame + scope.n_frames() as u64);
let frame = transport.pos.frame() as usize;
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);
for (time, tick) in ticks.iter() {
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 frame = pos.frame();
let rate = pos.frame_rate().unwrap();
let usecs = Frame(frame).to_usec(&Hz(rate)).0;
let usec_per_step = s.tempo.usec_per_step(s.resolution as u64).0;
let usecs = s.timebase.frame_to_usec(frame as usize);
let usec_per_step = s.timebase.usec_per_step(s.resolution as usize);
let steps = usecs / usec_per_step;
let header = draw_header(s, buf, area, steps)?;
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 step = beat % s.steps;
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 (time0, time1) = s.time_axis;
let (note0, note1) = s.note_axis;
let bw = Style::default().dim();
let _bw = Style::default().dim();
let bg = Style::default().on_black();
for key in note0..note1 {
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 step = beat % s.steps;
let (a, b, c) = (
(step + 0) as u32 * s.ticks_per_beat as u32 / s.resolution as u32,
(step + 1) as u32 * s.ticks_per_beat as u32 / s.resolution as u32,
(step + 2) 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 * ppq / s.resolution as u32,
(step + 2) as u32 * ppq / s.resolution as u32,
);
let key = ::midly::num::u7::from(key as u8);
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 {
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);
if step % s.resolution == 0 {
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);
if step % 2 == 0 {
let (a, b, c) = (
(step + 0) as u32 * s.ticks_per_beat as u32 / s.resolution as u32,
(step + 1) as u32 * s.ticks_per_beat as u32 / s.resolution as u32,
(step + 2) 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 * ppq / s.resolution as u32,
(step + 2) as u32 * ppq / s.resolution as u32,
);
let (character, style) = match (
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);
}
}
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());
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()
} else {
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 })
}
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 {
fg: Some(Color::Gray),
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,
];
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> {
let Rect { x, y, .. } = area;
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 {
let time_start = step as u32 * s.ticks_per_beat as u32;
let time_end = (step + 1) as u32 * s.ticks_per_beat as u32;
for (i, (t, events)) in s.sequence.range(time_start..time_end).enumerate() {
let time_start = step as u32 * s.timebase.ppq() as u32;
let time_end = (step + 1) as u32 * s.timebase.ppq() as u32;
for (_, (_, events)) in s.sequence.range(time_start..time_end).enumerate() {
if events.len() > 0 {
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 note_add (s: &mut Sequencer) {
let pos = s.transport.query().unwrap().pos;
let usecs = Frame(pos.frame()).to_usec(&s.rate).0;
let steps = usecs / s.tempo.usec_per_step(s.resolution as u64).0;
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 end = ((step + 1) as u64 * s.ticks_per_beat / s.resolution) as u32;
let pos = s.transport.query().unwrap().pos;
let usecs = s.timebase.frame_to_usec(pos.frame() as usize);
let steps = usecs / s.timebase.usec_per_step(s.resolution as usize);
let step = (s.time_axis.0 + s.time_cursor) as u32;
let start = (step as usize * s.timebase.ppq() / 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 note_on = ::midly::MidiMessage::NoteOn { 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());
let fpt = s.fpt();
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 + 1) * frames_per_chunk
);

View file

@ -3,7 +3,9 @@ use crate::prelude::*;
pub struct Transport {
name: String,
/// Holds info about tempo
timebase: Arc<Mutex<Timebase>>,
timebase: Arc<Timebase>,
transport: ::jack::Transport,
}
impl Transport {
@ -12,11 +14,17 @@ impl Transport {
let transport = client.transport();
DynamicDevice::new(render, handle, process, Self {
name: name.into(),
timebase: Timebase {
rate: transport.query()?.pos.frame_rate(),
tempo: 113000,
ppq: 96,
},
timebase: Arc::new(Timebase {
rate: AtomicUsize::new(
transport.query()?.pos.frame_rate().map(|x|x as usize).unwrap_or(0)
),
tempo: AtomicUsize::new(
113000
),
ppq: AtomicUsize::new(
96
),
}),
transport
}).activate(client)
}
@ -39,6 +47,10 @@ impl Transport {
pub fn stop (&mut self) -> Result<(), Box<dyn Error>> {
Ok(self.transport.stop()?)
}
pub fn timebase (&self) -> Arc<Timebase> {
self.timebase.clone()
}
}
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:00.000",
&format!("BPM {:03}.{:03}",
state.bpm as u64,
((state.bpm % 1.0) * 1000.0) as u64
state.timebase.tempo() / 1000,
state.timebase.tempo() % 1000,
)
].iter() {
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")?;
crate::config::create_dirs(&xdg)?;
//crate::device::run(Sequencer::new("Rhythm#000")?)
const transport = Transport::new("Transport")?;
const timebase = transport.timebase.clone();
let transport = Transport::new("Transport")?;
let timebase = transport.state.lock().unwrap().timebase();
crate::device::run(Rows::new(true, vec![
Box::new(transport),
Box::new(Columns::new(true, vec![

View file

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

View file

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