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);