diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index 09a1912e..24b8e545 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -8,11 +8,11 @@ pub struct Sequencer { /// JACK transport handle. transport: ::jack::Transport, /// Holds info about tempo - timebase: Arc>, + timebase: Arc, /// 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, @@ -56,7 +56,7 @@ enum SequencerView { } impl Sequencer { - pub fn new (name: &str, timebase: &Arc>) -> Usually> { + pub fn new (name: &str, timebase: &Arc) -> Usually> { 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 { 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 { })) } -fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { +fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually { 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 { +fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually { + 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 { 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 0 { buf.set_string(x + 5 + step as u16, y, "█", bw); } @@ -446,12 +448,12 @@ pub const COMMANDS: &'static [KeyBinding] = 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 ); diff --git a/src/device/transport.rs b/src/device/transport.rs index 62e67750..de453eac 100644 --- a/src/device/transport.rs +++ b/src/device/transport.rs @@ -3,7 +3,9 @@ use crate::prelude::*; pub struct Transport { name: String, /// Holds info about tempo - timebase: Arc>, + timebase: Arc, + + 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> { Ok(self.transport.stop()?) } + + pub fn timebase (&self) -> Arc { + 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); diff --git a/src/main.rs b/src/main.rs index 24776888..8be516ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,8 +21,8 @@ fn main () -> Result<(), Box> { 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![ diff --git a/src/prelude.rs b/src/prelude.rs index 4dac77b8..a5a6da84 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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} }; diff --git a/src/time.rs b/src/time.rs index 46be5617..b33aadcc 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,10 +1,12 @@ +use crate::prelude::*; + pub struct Timebase { /// Frames per second - pub rate: Option, + pub rate: AtomicUsize, /// Beats per minute - pub tempo: Option, + pub tempo: AtomicUsize, /// Ticks per beat - pub ppq: usize, + pub ppq: AtomicUsize, } enum QuantizeMode { @@ -24,22 +26,32 @@ struct Loop { /// 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), - Tuplet(u16, Box), + Tuplet(usize, Box), } 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) - } -}