diff --git a/src/device.rs b/src/device.rs index 3d8d7139..b95e1bdd 100644 --- a/src/device.rs +++ b/src/device.rs @@ -21,7 +21,7 @@ use crossterm::event; pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box> { let device = Arc::new(Mutex::new(device)); let exited = Arc::new(AtomicBool::new(false)); - let input_thread = { + let _input_thread = { let poll = std::time::Duration::from_millis(100); let exited = exited.clone(); let device = device.clone(); diff --git a/src/device/mixer.rs b/src/device/mixer.rs index b3285b38..087232b0 100644 --- a/src/device/mixer.rs +++ b/src/device/mixer.rs @@ -48,7 +48,7 @@ pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect) draw_box(buf, area); let x = area.x + 1; let y = area.y + 1; - let h = area.height - 2; + let _h = area.height - 2; for (i, track) in state.tracks.iter().enumerate() { //buf.set_string( //x, y + index as u16, diff --git a/src/device/plugin.rs b/src/device/plugin.rs index 21e427cb..50932815 100644 --- a/src/device/plugin.rs +++ b/src/device/plugin.rs @@ -16,7 +16,7 @@ pub fn process (_: &mut Plugin, _: &Client, _: &ProcessScope) -> Control { Control::Continue } -pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, width, height }: Rect) +pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, .. }: Rect) -> Usually { let style = Style::default().gray(); diff --git a/src/device/sampler.rs b/src/device/sampler.rs index 124495fd..ed038b2e 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -15,8 +15,7 @@ pub struct Sampler { impl Sampler { pub fn new (name: &str) -> Result, Box> { - let exited = Arc::new(AtomicBool::new(false)); - let (client, status) = Client::new(name, ClientOptions::NO_START_SERVER)?; + let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; let samples = vec![ Sample::new("Kick", &client, 1, 35)?, Sample::new("Snare", &client, 1, 38)?, @@ -35,7 +34,7 @@ impl Sampler { pub fn process ( state: &mut Sampler, - client: &Client, + _: &Client, scope: &ProcessScope, ) -> Control { let mut samples = state.samples.lock().unwrap(); @@ -48,7 +47,7 @@ pub fn process ( let note = data[1]; let velocity = data[2]; for sample in samples.iter_mut() { - if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note { + if sample.trigger.0 == channel && sample.trigger.1 == note { sample.play(velocity); } } @@ -95,13 +94,13 @@ impl Sample { }) } - fn play (&mut self, velocity: u8) { + fn play (&mut self, _velocity: u8) { self.playing = Some(0) } } -pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, width, height }: Rect) +pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, .. }: Rect) -> Usually { let style = Style::default().gray(); @@ -170,7 +169,7 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, width, height }: //Ok(()) //} -pub fn handle (state: &mut Sampler, event: &AppEvent) -> Result<(), Box> { +pub fn handle (_: &mut Sampler, _: &AppEvent) -> Result<(), Box> { //if let Event::Input(crossterm::event::Event::Key(event)) = event { //match event.code { //KeyCode::Char('c') => { diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index 6c4bc4cb..09a1912e 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -5,18 +5,18 @@ type Sequence = std::collections::BTreeMap>; pub struct Sequencer { name: String, + /// JACK transport handle. transport: ::jack::Transport, - /// Samples per second - rate: Hz, - /// Beats per minute - tempo: Tempo, - /// MIDI resolution (a.k.a. PPQ) - ticks_per_beat: u64, + /// Holds info about tempo + timebase: Arc>, /// Sequencer resolution, e.g. 16 steps per beat. - steps_per_beat: u64, + resolution: u64, /// Steps in sequence, e.g. 64 16ths = 4 beat loop. steps: u64, + + /// JACK MIDI input port that will be created. input_port: Port, + /// Port name patterns to connect to. input_connect: Vec, /// Play input through output. monitoring: bool, @@ -30,7 +30,9 @@ pub struct Sequencer { overdub: bool, /// Play sequence to output. playing: bool, + /// JACK MIDI output port that will be created. output_port: Port, + /// Port name patterns to connect to. output_connect: Vec, /// Display mode @@ -54,103 +56,33 @@ enum SequencerView { } impl Sequencer { - pub fn new (name: &str) -> 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(), + transport: client.transport(), + timebase: timebase.clone(), + steps: 64, + resolution: 8, + + input_port: client.register_port("in", MidiIn::default())?, + input_connect: vec!["nanoKEY Studio * (capture): *".into()], + monitoring: true, + notes_on: vec![false;128], + recording: true, + overdub: true, + sequence: std::collections::BTreeMap::new(), + playing: true, + output_port: client.register_port("out", MidiOut::default())?, + output_connect: vec![], + mode: SequencerView::Vertical, note_axis: (36, 68), note_cursor: 0, time_axis: (0, 64), time_cursor: 0, - rate: Hz(client.sample_rate() as u32), - tempo: Tempo(120000), - ticks_per_beat: 96, - steps_per_beat: 8, - steps: 64, - transport: client.transport(), - input_port: client.register_port("in", MidiIn::default())?, - input_connect: vec!["nanoKEY Studio * (capture): *".into()], - monitoring: true, - notes_on: vec![false;128], - playing: true, - recording: true, - sequence: std::collections::BTreeMap::new(), - overdub: true, - output_port: client.register_port("out", MidiOut::default())?, - output_connect: vec![], }).activate(client) } - - /// Beats per second - #[inline] fn bps (&self) -> f64 { - self.tempo.0 as f64 / 60000.0 - } - - /// Frames per second - #[inline] fn fps (&self) -> u64 { - self.rate.0 as u64 - } - /// Frames per beat - #[inline] fn fpb (&self) -> f64 { - self.fps() as f64 / self.bps() - } - /// Frames per tick FIXME double times - #[inline] fn fpt (&self) -> f64 { - 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 - } - - /// Ticks per beat - #[inline] fn tpb (&self) -> f64 { - self.ticks_per_beat 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; - let start_frame = start % fpl; - let end_frame = end % fpl; - let fpt = self.fpt(); - let mut ticks = vec![]; - let mut add_frame = |frame: f64|{ - let jitter = frame.rem_euclid(fpt); - 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)); - }; - }; - if start_frame < end_frame { - for frame in start_frame..end_frame { - add_frame(frame as f64); - } - } else { - let mut frame = start_frame; - loop { - add_frame(frame as f64); - frame = frame + 1; - if frame >= fpl { - frame = 0; - loop { - add_frame(frame as f64); - frame = frame + 1; - if frame >= end_frame.saturating_sub(1) { - break - } - } - break - } - } - } - ticks - } } pub fn process (s: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Control { @@ -165,13 +97,15 @@ fn process_in (s: &mut Sequencer, scope: &ProcessScope) { } 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.steps_per_beat as u64).0; + let steps = usecs / s.tempo.usec_per_step(s.resolution as u64).0; let step = steps % s.steps; - let tick = step * s.ticks_per_beat / s.steps_per_beat; + let tick = step * s.ticks_per_beat / s.resolution; for event in s.input_port.iter(scope) { match midly::live::LiveEvent::parse(event.bytes).unwrap() { + midly::live::LiveEvent::Midi { channel: _, message } => match message { + midly::MidiMessage::NoteOn { key, vel: _ } => { s.notes_on[key.as_int() as usize] = true; if s.sequence.contains_key(&(tick as u32)) { @@ -180,6 +114,7 @@ fn process_in (s: &mut Sequencer, scope: &ProcessScope) { s.sequence.insert(tick as u32, vec![message.clone()]); } }, + midly::MidiMessage::NoteOff { key, vel: _ } => { s.notes_on[key.as_int() as usize] = false; if s.sequence.contains_key(&(tick as u32)) { @@ -188,9 +123,13 @@ fn process_in (s: &mut Sequencer, scope: &ProcessScope) { s.sequence.insert(tick as u32, vec![message.clone()]); } }, + _ => {} + }, + _ => {} + } } } @@ -232,7 +171,7 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { 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.steps_per_beat as u64).0; + let usec_per_step = s.tempo.usec_per_step(s.resolution as u64).0; let steps = usecs / usec_per_step; let header = draw_header(s, buf, area, steps)?; let piano = match s.mode { @@ -264,50 +203,42 @@ fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { let rep = beat / s.steps; let step = beat % s.steps; - let reps = s.steps / s.steps_per_beat; - let steps = s.steps % s.steps_per_beat; + let reps = s.steps / s.resolution; + let steps = s.steps % s.resolution; let Rect { x, y, .. } = area; let style = Style::default().gray(); - buf.set_string(x + 1, y + 1, &format!(" │ {rep}.{step:2} / {reps}.{steps}"), style); - buf.set_string(x + 2, y + 1, &format!("{}", &s.name), style.white().bold()); - buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ │"), style); - buf.set_string(x + 2, y + 2, &format!("▶ PLAY"), if s.playing { - Style::default().green() - } else { - Style::default().dim() - }); - buf.set_string(x + 24, y + 2, &format!("⏺ REC"), if s.recording { - Style::default().red() - } else { - Style::default().dim() - }); - buf.set_string(x + 32, y + 2, &format!("⏺ DUB"), if s.overdub { - Style::default().yellow() - } else { - Style::default().dim() - }); + buf.set_string(x + 1, y + 1, + &format!(" │ {rep}.{step:2} / {reps}.{steps}"), + style); + buf.set_string(x + 2, y + 1, + &format!("{}", &s.name), + style.white().bold()); + buf.set_string(x + 1, y + 2, + &format!(" ▶ PLAY │ ⏹ STOP │ │"), + style); + buf.set_string(x + 2, y + 2, + &format!("▶ PLAY"), if s.playing { + Style::default().green() + } else { + Style::default().dim() + }); + buf.set_string(x + 24, y + 2, + &format!("⏺ REC"), if s.recording { + Style::default().red() + } else { + Style::default().dim() + }); + buf.set_string(x + 32, y + 2, + &format!("⏺ DUB"), if s.overdub { + Style::default().yellow() + } else { + Style::default().dim() + }); Ok(Rect { x, y, width: 39, height: 4 }) } -const KEY_WHITE: Style = Style { - fg: Some(Color::Gray), - bg: None, - underline_color: None, - add_modifier: ::ratatui::style::Modifier::empty(), - sub_modifier: ::ratatui::style::Modifier::empty(), -}; - -const KEY_BLACK: Style = Style { - fg: Some(Color::Black), - bg: None, - underline_color: None, - add_modifier: ::ratatui::style::Modifier::empty(), - sub_modifier: ::ratatui::style::Modifier::empty(), -}; - -const KEY_HORIZONTAL_STYLE: [Style;12] = [ - KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, - KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, +const KEYS_VERTICAL: [&'static str; 6] = [ + "▀", "▀", "▀", "█", "▄", "▄", ]; fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { @@ -326,9 +257,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.steps_per_beat as u32, - (step + 1) as u32 * s.ticks_per_beat as u32 / s.steps_per_beat as u32, - (step + 2) as u32 * s.ticks_per_beat as u32 / s.steps_per_beat as u32, + (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, ); let key = ::midly::num::u7::from(key as u8); is_on = is_on || contains_note_on(&s.sequence, key, a, b); @@ -342,16 +273,16 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu let y = y - time0 + step / 2; let step = step as u64; //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); - if step % s.steps_per_beat == 0 { + if step % s.resolution == 0 { buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); } for k in note0..note1 { 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.steps_per_beat as u32, - (step + 1) as u32 * s.ticks_per_beat as u32 / s.steps_per_beat as u32, - (step + 2) as u32 * s.ticks_per_beat as u32 / s.steps_per_beat as u32, + (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, ); let (character, style) = match ( contains_note_on(&s.sequence, key, a, b), @@ -379,7 +310,7 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu let height = (time1-time0)/2; buf.set_string(x + 2, y + height + 1, format!( "Q 1/{} | N {} ({}-{}) | T {} ({}-{})", - 4 * s.steps_per_beat, + 4 * s.resolution, s.note_axis.0 + s.note_cursor, s.note_axis.0, s.note_axis.1 - 1, @@ -396,8 +327,29 @@ fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usu Ok(Rect { x, y, width: area.width, height }) } +const KEY_WHITE: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: ::ratatui::style::Modifier::empty(), + sub_modifier: ::ratatui::style::Modifier::empty(), +}; + +const KEY_BLACK: Style = Style { + fg: Some(Color::Black), + bg: None, + underline_color: None, + add_modifier: ::ratatui::style::Modifier::empty(), + sub_modifier: ::ratatui::style::Modifier::empty(), +}; + +const KEY_HORIZONTAL_STYLE: [Style;12] = [ + 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 (i, (t, events)) in sequence.range(start..end).enumerate() { + for (_, (_, events)) in sequence.range(start..end).enumerate() { for event in events.iter() { match event { ::midly::MidiMessage::NoteOn {key,..} => { @@ -444,7 +396,7 @@ fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually note_cursor_dec(s), SequencerView::Horizontal => time_cursor_dec(s), @@ -556,18 +506,16 @@ fn cursor_left (s: &mut Sequencer) { } } fn cursor_right (s: &mut Sequencer) { - let time = s.time_axis.1 - s.time_axis.0; - let note = s.note_axis.1 - s.note_axis.0; match s.mode { SequencerView::Vertical => note_cursor_inc(s), SequencerView::Horizontal => time_cursor_inc(s), _ => unimplemented!() } } -fn cursor_duration_inc (s: &mut Sequencer) { +fn cursor_duration_inc (_: &mut Sequencer) { //s.cursor.2 = s.cursor.2 + 1 } -fn cursor_duration_dec (s: &mut Sequencer) { +fn cursor_duration_dec (_: &mut Sequencer) { //if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 } } fn mode_next (s: &mut Sequencer) { @@ -595,13 +543,13 @@ fn toggle_overdub (s: &mut Sequencer) { s.overdub = !s.overdub } fn quantize_next (s: &mut Sequencer) { - if s.steps_per_beat < 64 { - s.steps_per_beat = s.steps_per_beat * 2 + if s.resolution < 64 { + s.resolution = s.resolution * 2 } } fn quantize_prev (s: &mut Sequencer) { - if s.steps_per_beat > 1 { - s.steps_per_beat = s.steps_per_beat / 2 + if s.resolution > 1 { + s.resolution = s.resolution / 2 } } diff --git a/src/device/transport.rs b/src/device/transport.rs index 5d940caf..62e67750 100644 --- a/src/device/transport.rs +++ b/src/device/transport.rs @@ -1,20 +1,23 @@ use crate::prelude::*; pub struct Transport { - name: String, - transport: ::jack::Transport, - timesig: (f32, f32), - bpm: f64, + name: String, + /// Holds info about tempo + timebase: Arc>, } impl Transport { pub fn new (name: &str) -> Result, Box> { let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; + let transport = client.transport(); DynamicDevice::new(render, handle, process, Self { - name: name.into(), - transport: client.transport(), - timesig: (4.0, 4.0), - bpm: 113.0, + name: name.into(), + timebase: Timebase { + rate: transport.query()?.pos.frame_rate(), + tempo: 113000, + ppq: 96, + }, + transport }).activate(client) } @@ -45,8 +48,8 @@ pub fn process (_: &mut Transport, _: &Client, _: &ProcessScope) -> Control { pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect) -> Usually { - area.x = area.width.saturating_sub(80) / 2; - area.width = area.width.min(80); + //area.x = area.width.saturating_sub(80) / 2; + //area.width = area.width.min(80); area.height = 5; //draw_box(buf, area); let label = Style::default().white().not_dim(); diff --git a/src/main.rs b/src/main.rs index e695746a..24776888 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,15 @@ 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(); crate::device::run(Rows::new(true, vec![ + Box::new(transport), + Box::new(Columns::new(true, vec![ + Box::new(Sequencer::new("Melody#000", &timebase)?), + Box::new(Sequencer::new("Melody#001", &timebase)?), + Box::new(Sequencer::new("Rhythm#000", &timebase)?), + ])), //Box::new(Columns::new(false, vec![ //Box::new(Chain::new("Chain#00", vec![ //Box::new(Sequencer::new("Rhythm#000")?), @@ -33,12 +41,6 @@ fn main () -> Result<(), Box> { //])?), //])), //Box::new(Mixer::new("Mixer#000")?), - Box::new(Transport::new("Transport")?), - Box::new(Columns::new(true, vec![ - Box::new(Sequencer::new("Melody#000")?), - Box::new(Sequencer::new("Melody#001")?), - Box::new(Sequencer::new("Rhythm#000")?), - ])), //Box::new(Sequencer::new("Rhythm#000")?), ])) } diff --git a/src/time.rs b/src/time.rs index 2a1912f0..46be5617 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,89 +1,16 @@ -/// Number of data frames in a second. -#[derive(Debug)] -pub struct Hz(pub u32); - -/// One data frame. -#[derive(Debug)] -pub struct Frame(pub u32); - -/// One microsecond. -#[derive(Debug)] -pub struct Usec(pub u64); - -/// Beats per minute as `120000` = 120.000BPM -#[derive(Debug)] -pub struct Tempo(pub u64); - -/// Time signature: N/Mths per bar. -#[derive(Debug)] -pub struct Signature(pub u32, pub u32); - -/// NoteDuration in musical terms. Has definite usec value -/// for given bpm and sample rate. -pub enum NoteDuration { - Nth(u16, u16), - Dotted(Box), - Tuplet(u16, Box), +pub struct Timebase { + /// Frames per second + pub rate: Option, + /// Beats per minute + pub tempo: Option, + /// Ticks per beat + pub ppq: usize, } -impl Frame { - #[inline] - pub fn to_usec (&self, rate: &Hz) -> Usec { - Usec((self.0 as u64 * 1000000) / rate.0 as u64) - } -} - -impl Usec { - #[inline] - pub fn to_frame (&self, rate: &Hz) -> Frame { - Frame(((self.0 * rate.0 as u64) / 1000) as u32) - } -} - -impl Tempo { - #[inline] - pub fn usec_per_bar (&self, beats: u64) -> Usec { - Usec(beats * self.usec_per_beat().0) - } - #[inline] - pub fn usec_per_beat (&self) -> Usec { - Usec(60_000_000_000 / self.0 as u64) - } - #[inline] - pub fn usec_per_step (&self, divisor: u64) -> Usec { - Usec(self.usec_per_beat().0 / divisor as u64) - } - #[inline] - pub fn quantize (&self, step: &NoteDuration, time: Usec) -> Usec { - let step = step.to_usec(self); - let t = time.0 / step.0; - Usec(step.0 * t) - } - #[inline] - pub fn quantize_into (&self, step: &NoteDuration, events: U) - -> Vec<(Usec, T)> - where U: std::iter::Iterator + Sized - { - events - .map(|(time, event)|(self.quantize(step, time), event)) - .collect() - } -} - -impl NoteDuration { - fn to_usec (&self, bpm: &Tempo) -> Usec { - Usec(match self { - Self::Nth(time, flies) => - bpm.usec_per_beat().0 * *time as u64 / *flies as u64, - Self::Dotted(duration) => - duration.to_usec(bpm).0 * 3 / 2, - Self::Tuplet(n, duration) => - duration.to_usec(bpm).0 * 2 / *n as u64, - }) - } - fn to_frame (&self, bpm: &Tempo, rate: &Hz) -> Frame { - self.to_usec(bpm).to_frame(rate) - } +enum QuantizeMode { + Forward, + Backward, + Nearest, } struct Loop { @@ -93,3 +20,145 @@ struct Loop { end: T, post_end: T, } + +/// NoteDuration in musical terms. Has definite usec value +/// for given bpm and sample rate. +pub enum NoteDuration { + Nth(u16, u16), + Dotted(Box), + Tuplet(u16, Box), +} + +impl Timebase { + /// Beats per second + #[inline] fn bps (&self) -> f64 { + self.tempo.0 as f64 / 60000.0 + } + /// Frames per second + #[inline] fn fps (&self) -> u64 { + self.rate.0 as u64 + } + /// Frames per beat + #[inline] fn fpb (&self) -> f64 { + self.fps() as f64 / self.bps() + } + /// Frames per tick FIXME double times + #[inline] fn fpt (&self) -> f64 { + 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 + } + /// Ticks per beat + #[inline] fn tpb (&self) -> f64 { + self.ticks_per_beat 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; + let start_frame = start % fpl; + let end_frame = end % fpl; + let fpt = self.fpt(); + let mut ticks = vec![]; + let mut add_frame = |frame: f64|{ + let jitter = frame.rem_euclid(fpt); + 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)); + }; + }; + if start_frame < end_frame { + for frame in start_frame..end_frame { + add_frame(frame as f64); + } + } else { + let mut frame = start_frame; + loop { + add_frame(frame as f64); + frame = frame + 1; + if frame >= fpl { + frame = 0; + loop { + add_frame(frame as f64); + frame = frame + 1; + if frame >= end_frame.saturating_sub(1) { + break + } + } + break + } + } + } + ticks + } + + #[inline] + pub fn frame_to_usec (&self, frame: usize) -> usize { + frame * 1000000 / self.rate + } + #[inline] + pub fn usec_to_frame (&self, usec: usize) -> usize { + frame * self.rate / 1000 + } + #[inline] + pub fn usec_per_bar (&self, beats_per_bar: usize) -> usize { + self.usec_per_beat() * beats_per_bar + } + #[inline] + pub fn usec_per_beat (&self) -> usize { + 60_000_000_000 / self.tempo + } + #[inline] + pub fn usec_per_step (&self, divisor: usize) -> usize { + self.usec_per_beat() / divisor + } + #[inline] + pub fn usec_per_tick (&self) -> usize { + self.usec_per_beat() / self.ppq + } + #[inline] + pub fn usec_per_note (&self, note: &NoteDuration) -> usize { + match note { + NoteDuration::Nth(time, flies) => + self.usec_per_beat() * *time / *flies, + NoteDuration::Dotted(note) => + self.usec_per_note(note) * 3 / 2, + NoteDuration::Tuplet(n, note) => + self.usec_per_note(note) * 2 / *n, + } + } + pub fn quantize (&self, step: &NoteDuration, time: usize) -> (usize, usize) { + let step = self.usec_per_note(step); + let time = time / step; + let offset = time % step; + (time, offset) + } + pub fn quantize_into (&self, step: &NoteDuration, events: U) -> Vec<(usize, T)> + where U: std::iter::Iterator + Sized + { + events + .map(|(time, event)|(self.quantize(step, time).0, event)) + .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) + } +}