From d39cce271fca6030eb5aabc5a1070cf507306d06 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 18 Jun 2024 00:12:16 +0300 Subject: [PATCH] loop recording! --- src/device/sequencer.rs | 433 ++++++++++++++++++++++++----------- src/device/sequencer_test.rs | 77 ------- src/time.rs | 5 + 3 files changed, 301 insertions(+), 214 deletions(-) delete mode 100644 src/device/sequencer_test.rs diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index f970bf34..76aeb426 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -4,29 +4,38 @@ use ratatui::style::Stylize; type Sequence = std::collections::BTreeMap>; pub struct Sequencer { - name: String, - mode: SequencerView, - note_axis: (u16, u16), - note_cursor: u16, - time_axis: (u16, u16), - time_cursor: u16, - rate: Hz, - tempo: Tempo, - steps: u64, - divisions: u64, - transport: ::jack::Transport, - sequence: Sequence, - ppq: u64, - input_port: Port, - input_connect: Vec, - output_port: Port, - output_connect: Vec, - playing: bool, - recording: bool, - overdub: bool, - notes_on: Vec, + name: String, + mode: SequencerView, + note_axis: (u16, u16), + note_cursor: u16, + time_axis: (u16, u16), + time_cursor: u16, + rate: Hz, + tempo: Tempo, + transport: ::jack::Transport, + input_port: Port, + input_connect: Vec, + output_port: Port, + output_connect: Vec, + /// Play sequence to output. + playing: bool, + /// Write input to sequence. + recording: bool, + /// Don't delete when recording. + overdub: bool, + /// Red keys on piano roll. + notes_on: Vec, + /// MIDI resolution (a.k.a. PPQ) + ticks_per_beat: u64, + /// Sequencer resolution, e.g. 16 steps per beat. + steps_per_beat: u64, + /// Steps in sequence, e.g. 64 16ths = 4 beat loop. + steps: u64, + /// Map: tick -> MIDI events at tick + sequence: Sequence, } +#[derive(Debug)] enum SequencerView { Tiny, Compact, @@ -38,61 +47,175 @@ impl Sequencer { pub fn new (name: &str) -> Usually> { let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; DynamicDevice::new(render, handle, process, Self { - name: name.into(), - 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), - transport: client.transport(), - input_port: client.register_port("in", MidiIn::default())?, - input_connect: vec!["nanoKEY Studio * (capture): *".into()], - output_port: client.register_port("out", MidiOut::default())?, - output_connect: vec![], - ppq: 96, - sequence: std::collections::BTreeMap::new(), - playing: true, - recording: true, - overdub: true, - notes_on: vec![false;128], - steps: 64, - divisions: 16, + name: name.into(), + 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), + transport: client.transport(), + input_port: client.register_port("in", MidiIn::default())?, + input_connect: vec!["nanoKEY Studio * (capture): *".into()], + output_port: client.register_port("out", MidiOut::default())?, + output_connect: vec![], + sequence: std::collections::BTreeMap::new(), + playing: true, + recording: true, + overdub: true, + notes_on: vec![false;128], + ticks_per_beat: 96, + steps_per_beat: 8, + steps: 64, }).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() + } + /// Ticks per loop яснота + #[inline] fn tpl (&self) -> f64 { + self.tpb() * self.steps as f64 / self.steps_per_beat as f64 + } + + /// Length of sequence in beat notes and remainder. + #[inline] fn beats (&self) -> (u64, u64) { + (self.steps / self.steps_per_beat, self.steps % self.steps_per_beat) + } + /// Length of sequence in ticks, rounded down. + #[inline] fn ticks (&self) -> u64 { + self.steps * self.ticks_per_beat / self.steps_per_beat + } + /// Length of sequence in frames. + #[inline] fn frames (&self) -> u64 { + self.steps * self.usec_per_step().0 + } + /// Ticks per step, rounded down. + #[inline] fn ticks_per_step (&self) -> u64 { + self.ticks_per_beat / self.steps_per_beat + } + /// Microseconds per step for current tempo. + #[inline] fn usec_per_step (&self) -> Usec { + self.tempo.usec_per_step(self.steps_per_beat as u64) + } + /// Microseconds per tick for current tempo. + #[inline] fn usec_per_tick (&self) -> Usec { + Usec(self.tempo.usec_per_beat().0 / self.ticks_per_beat) + } + /// Convert frame to microsecond for current sample rate. + #[inline] fn frame_to_usec (&self, frame: u32) -> Usec { + Frame(frame).to_usec(&self.rate) + } + /// Convert frame to tick for current sample rate, tempo, and PPQ. + #[inline] fn frame_to_tick (&self, frame: Frames) -> u32 { + (self.frame_to_usec(frame).0 / self.usec_per_tick().0) as u32 + } + /// Convert tick to usec for current sample rate, tempo, and PPQ. + #[inline] fn tick_to_usec (&self, tick: u32) -> u32 { + (tick as u64 * self.usec_per_tick().0) as u32 + } + + 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 (state: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Control { - process_out(state, scope); - process_in(state, scope); +pub fn process (s: &mut Sequencer, _: &Client, scope: &ProcessScope) -> Control { + process_out(s, scope); + process_in(s, scope); Control::Continue } -fn process_in (state: &mut Sequencer, scope: &ProcessScope) { - let pos = state.transport.query().unwrap().pos; - let usecs = Frame(pos.frame()).to_usec(&state.rate).0; - let steps = usecs / state.tempo.usec_per_step(state.divisions as u64).0; - let step = steps % state.steps; - let tick = step * state.ppq / state.divisions; +fn process_in (s: &mut Sequencer, scope: &ProcessScope) { + if !s.recording { + 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.steps_per_beat as u64).0; + let step = steps % s.steps; + let tick = step * s.ticks_per_beat / s.steps_per_beat; - for event in state.input_port.iter(scope) { + 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: _ } => { - state.notes_on[key.as_int() as usize] = true; - if state.sequence.contains_key(&(tick as u32)) { - state.sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); + s.notes_on[key.as_int() as usize] = true; + if s.sequence.contains_key(&(tick as u32)) { + s.sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); } else { - state.sequence.insert(tick as u32, vec![message.clone()]); + s.sequence.insert(tick as u32, vec![message.clone()]); } }, midly::MidiMessage::NoteOff { key, vel: _ } => { - state.notes_on[key.as_int() as usize] = false; - if state.sequence.contains_key(&(tick as u32)) { - state.sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); + s.notes_on[key.as_int() as usize] = false; + if s.sequence.contains_key(&(tick as u32)) { + s.sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); } else { - state.sequence.insert(tick as u32, vec![message.clone()]); + s.sequence.insert(tick as u32, vec![message.clone()]); } }, _ => {} @@ -102,57 +225,60 @@ fn process_in (state: &mut Sequencer, scope: &ProcessScope) { } } -fn process_out (state: &mut Sequencer, scope: &ProcessScope) { - if state.playing { - let pps = state.ppq / state.divisions; - let ups = state.tempo.usec_per_step(state.divisions as u64).0; - let start_usecs = Frame(scope.last_frame_time()).to_usec(&state.rate).0; - let start_steps = start_usecs / ups; - let start_step = start_steps % state.steps; - let end_usecs = Frame(scope.last_frame_time() + scope.n_frames()).to_usec(&state.rate).0; - let end_steps = end_usecs / ups; - let end_step = end_steps % state.steps; - let mut writer = state.output_port.writer(scope); - //for (i, (t, events)) in state.sequence.range( - //(step * pps) as u32..((step + 1) * pps) as u32 - //).enumerate() { - //if events.len() > 0 { - //panic!("{events:?}"); - //} - //} - //for time in start..end { - //let usecs = Frame(time).to_usec(&state.rate).0; - //let ticks = usecs / state.tempo.usec_per_step(state.ppq as u64).0; - ////println!("{usecs} = {ticks}"); - //} +fn process_out (s: &mut Sequencer, scope: &ProcessScope) { + if !s.playing { + return + } + let transport = s.transport.query().unwrap(); + 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 mut writer = s.output_port.writer(scope); + for (time, tick) in ticks.iter() { + if let Some(events) = s.sequence.get(&(*tick as u32)) { + for message in events.iter() { + let mut buf = vec![]; + ::midly::live::LiveEvent::Midi { + channel: 1.into(), + message: *message, + }.write(&mut buf).unwrap(); + let midi = ::jack::RawMidi { + time: *time as u32, + bytes: &buf + }; + writer.write(&midi).expect(&format!("{midi:?}")) + } + } } } -fn render (state: &Sequencer, buf: &mut Buffer, area: Rect) +fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, .. } = area; - let (time0, time1) = state.time_axis; - let (note0, note1) = state.note_axis; - let pos = state.transport.query().unwrap().pos; + let (time0, time1) = s.time_axis; + let (note0, note1) = s.note_axis; + 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 = state.tempo.usec_per_step(state.divisions as u64).0; + let usec_per_step = s.tempo.usec_per_step(s.steps_per_beat as u64).0; let steps = usecs / usec_per_step; - let header = draw_state_header(state, buf, area, steps)?; - let piano = match state.mode { + let header = draw_header(s, buf, area, steps)?; + let piano = match s.mode { SequencerView::Tiny => Rect { x, y, width, height: 0 }, SequencerView::Compact => Rect { x, y, width, height: 0 }, - SequencerView::Vertical => draw_state_vertical(state, buf, Rect { + SequencerView::Vertical => draw_vertical(s, buf, Rect { x, y: y + header.height, width: 3 + note1 - note0, height: 3 + time1 - time0, }, steps)?, - SequencerView::Horizontal => draw_state_horizontal(state, buf, Rect { + SequencerView::Horizontal => draw_horizontal(s, buf, Rect { x, y: y + header.height, width: 3 + time1 - time0, @@ -167,27 +293,27 @@ fn render (state: &Sequencer, buf: &mut Buffer, area: Rect) })) } -fn draw_state_header (state: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { - let rep = beat / state.steps; - let step = beat % state.steps; - let reps = state.steps / state.divisions; - let steps = state.steps % state.divisions; +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 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!("{}", &state.name), style.white().bold()); + 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 state.playing { + 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 state.recording { + 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 state.overdub { + buf.set_string(x + 32, y + 2, &format!("⏺ DUB"), if s.overdub { Style::default().yellow() } else { Style::default().dim() @@ -216,11 +342,10 @@ const KEY_HORIZONTAL_STYLE: [Style;12] = [ KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, ]; -fn draw_state_vertical (state: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { +fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { let Rect { x, y, .. } = area; - let transport = state.transport.query().unwrap(); - let (time0, time1) = state.time_axis; - let (note0, note1) = state.note_axis; + let (time0, time1) = s.time_axis; + let (note0, note1) = s.note_axis; let bw = Style::default().dim(); let bg = Style::default().on_black(); for key in note0..note1 { @@ -230,16 +355,16 @@ fn draw_state_vertical (state: &Sequencer, buf: &mut Buffer, area: Rect, beat: u buf.set_string(x, y, &octave, Style::default()); } let mut color = KEY_HORIZONTAL_STYLE[key as usize % 12]; - let mut is_on = state.notes_on[key as usize]; - let step = beat % state.steps; + let mut is_on = s.notes_on[key as usize]; + let step = beat % s.steps; let (a, b, c) = ( - (step + 0) as u32 * state.ppq as u32 / state.divisions as u32, - (step + 1) as u32 * state.ppq as u32 / state.divisions as u32, - (step + 2) as u32 * state.ppq as u32 / state.divisions as u32, + (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, ); let key = ::midly::num::u7::from(key as u8); - is_on = is_on || contains_note_on(&state.sequence, key, a, b); - is_on = is_on || contains_note_on(&state.sequence, key, b, c); + is_on = is_on || contains_note_on(&s.sequence, key, a, b); + is_on = is_on || contains_note_on(&s.sequence, key, b, c); if is_on { color = Style::default().red(); } @@ -249,20 +374,20 @@ fn draw_state_vertical (state: &Sequencer, buf: &mut Buffer, area: Rect, beat: u 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 % state.divisions == 0 { + if step % s.steps_per_beat == 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 * state.ppq as u32 / state.divisions as u32, - (step + 1) as u32 * state.ppq as u32 / state.divisions as u32, - (step + 2) as u32 * state.ppq as u32 / state.divisions as u32, + (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, ); let (character, style) = match ( - contains_note_on(&state.sequence, key, a, b), - contains_note_on(&state.sequence, key, b, c), + contains_note_on(&s.sequence, key, a, b), + contains_note_on(&s.sequence, key, b, c), ) { (true, true) => ("█", bg), (true, false) => ("▀", bg), @@ -272,10 +397,10 @@ fn draw_state_vertical (state: &Sequencer, buf: &mut Buffer, area: Rect, beat: u buf.set_string(x + 5 + k - note0, y, character, style); } } - if beat % state.steps == step as u64 { + if beat % s.steps == step as u64 { buf.set_string(x + 39 - 2, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); for key in note0..note1 { - let color = if state.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] @@ -284,9 +409,9 @@ fn draw_state_vertical (state: &Sequencer, buf: &mut Buffer, area: Rect, beat: u } } buf.set_string( - x + 5 + state.note_cursor, - y + state.time_cursor / 2, - if state.time_cursor % 2 == 0 { "▀" } else { "▄" }, + x + 5 + s.note_cursor, + y + s.time_cursor / 2, + if s.time_cursor % 2 == 0 { "▀" } else { "▄" }, Style::default() ); Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 }) @@ -312,10 +437,10 @@ const KEYS_VERTICAL: [&'static str; 6] = [ "▀", "▀", "▀", "█", "▄", "▄", ]; -fn draw_state_horizontal (state: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { +fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, .. } = area; - let (time0, time1) = state.time_axis; - let (note0, note1) = state.note_axis; + let (time0, time1) = s.time_axis; + let (note0, note1) = s.note_axis; let bw = Style::default().dim(); let bg = Style::default().on_black(); for i in 0..32.max(note1-note0)/2 { @@ -329,29 +454,29 @@ fn draw_state_horizontal (state: &Sequencer, buf: &mut Buffer, area: Rect) -> Us } } for step in time0..time1 { - let time_start = step as u32 * state.ppq as u32; - let time_end = (step + 1) as u32 * state.ppq as u32; - for (i, (t, events)) in state.sequence.range(time_start..time_end).enumerate() { + 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() { if events.len() > 0 { buf.set_string(x + 5 + step as u16, y, "█", bw); } } } buf.set_string( - x + 5 + state.time_cursor, - y + state.note_cursor / 2, - if state.note_cursor % 2 == 0 { "▀" } else { "▄" }, + x + 5 + s.time_cursor, + y + s.note_cursor / 2, + if s.note_cursor % 2 == 0 { "▀" } else { "▄" }, Style::default() ); Ok(Rect { x, y, width: time1 - time0 + 6, height: 32.max(note1 - note0) / 2 }) } -pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box> { +pub fn handle (s: &mut Sequencer, event: &AppEvent) -> Result<(), Box> { match event { AppEvent::Input(Event::Key(event)) => { for (code, _, _, command) in COMMANDS.iter() { if *code == event.code { - command(state); + command(s); break } } @@ -387,10 +512,10 @@ 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.divisions as u64).0; + let steps = usecs / s.tempo.usec_per_step(s.steps_per_beat as u64).0; let step = (s.time_axis.0 + s.time_cursor) as u32; - let start = (step as u64 * s.ppq / s.divisions) as u32; - let end = ((step + 1) as u64 * s.ppq / s.divisions) as u32; + let start = (step as u64 * s.ticks_per_beat / s.steps_per_beat) as u32; + let end = ((step + 1) as u64 * s.ticks_per_beat / s.steps_per_beat) 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() }; @@ -464,3 +589,37 @@ fn toggle_record (s: &mut Sequencer) { fn toggle_overdub (s: &mut Sequencer) { s.overdub = !s.overdub } + +#[cfg(test)] mod test { + use super::*; + + #[test] + fn test_sequencer_output () -> Usually<()> { + let sequencer = Sequencer::new("test")?; + let mut s = sequencer.state.lock().unwrap(); + s.rate = Hz(48000); + s.tempo = Tempo(240_000); + println!("F/S = {:.03}", s.fps()); + println!("B/S = {:.03}", s.bps()); + println!("F/B = {:.03}", s.fpb()); + println!("T/B = {:.03}", s.tpb()); + println!("F/T = {:.03}", s.fpt()); + println!("F/L = {:.03}", s.fpl()); + println!("T/L = {:.03}", s.tpl()); + let fpt = s.fpt(); + let frames_per_chunk = 240; + let chunk = |chunk: u64| s.frames_to_ticks( + chunk * frames_per_chunk, + (chunk + 1) * frames_per_chunk + ); + //for i in 0..2000 { + //println!("{i} {:?}", chunk(i)); + //} + assert_eq!(chunk(0), vec![(0, 0), (125, 1)]); + assert_eq!(chunk(1), vec![(10, 2), (135, 3)]); + assert_eq!(chunk(12), vec![(120, 24)]); + assert_eq!(chunk(412), vec![(120, 24)]); + assert_eq!(chunk(413), vec![(5, 25), (130, 26)]); + Ok(()) + } +} diff --git a/src/device/sequencer_test.rs b/src/device/sequencer_test.rs deleted file mode 100644 index 4a0882af..00000000 --- a/src/device/sequencer_test.rs +++ /dev/null @@ -1,77 +0,0 @@ -#![cfg(test)] - -#[test] -fn test_midi_frames () { - let beats = 4; - let steps = 16; - let bpm = 120; - let rate = 44100; // Hz - let frame = 1f64 / rate as f64; // msec - let buf = 512; // frames - let t_beat = 60.0 / bpm as f64; // msec - let t_loop = t_beat * beats as f64; // msec - let t_step = t_beat / steps as f64; // msec - - let assign = |chunk: usize| { - let start = chunk * buf; // frames - let end = (chunk + 1) * buf; // frames - println!("{chunk}: {start} .. {end}"); - let mut steps: Vec<(usize, usize, f64)> = vec![]; - for frame_index in start..end { - let frame_msec = frame_index as f64 * frame; - let offset = (frame_msec * 1000.0) % (t_step * 1000.0); - if offset < 0.1 { - let time = frame_index - start; - let step_index = (frame_msec % t_loop / t_step) as usize; - println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})"); - steps.push((time, step_index, offset)); - } - } - steps - }; - - for chunk in 0..10 { - let chunk = assign(chunk); - //println!("{chunk} {:#?}", assign(chunk)); - } -} - -#[test] -fn test_midi_frames_2 () { - let beats = 4; - let steps = 16; - let bpm = 120; - let rate = 44100; // Hz - let frame = 1f64 / rate as f64; // msec - let buf = 512; // frames - let t_beat = 60.0 / bpm as f64; // msec - let t_loop = t_beat * beats as f64; // msec - let t_step = t_beat / steps as f64; // msec - let mut step_frames = vec![]; - for step in 0..beats*steps { - let step_index = (step as f64 * t_step / frame) as usize; - step_frames.push(step_index); - } - let loop_frames = (t_loop*rate as f64) as usize; - let mut frame_steps: Vec> = vec![None;loop_frames]; - for (index, frame) in step_frames.iter().enumerate() { - println!("{index} {frame}"); - frame_steps[*frame] = Some(index); - } - let assign = |chunk: usize| { - let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames - let (start_looped, end_looped) = (start % loop_frames, end % loop_frames); - println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})"); - let mut steps: Vec> = vec![None;buf]; - for frame in 0..buf { - let value = frame_steps[(start_looped + frame) as usize % loop_frames]; - if value.is_some() { println!("{frame:03} = {value:?}, ") }; - steps[frame as usize] = value; - } - steps - }; - for chunk in 0..1000 { - let chunk = assign(chunk); - //println!("{chunk} {:#?}", assign(chunk)); - } -} diff --git a/src/time.rs b/src/time.rs index 2f7ce94d..2a1912f0 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,16 +1,21 @@ /// 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