loop recording!

This commit is contained in:
🪞👃🪞 2024-06-18 00:12:16 +03:00
parent 7fb9369012
commit d39cce271f
3 changed files with 301 additions and 214 deletions

View file

@ -4,29 +4,38 @@ use ratatui::style::Stylize;
type Sequence = std::collections::BTreeMap<u32, Vec<::midly::MidiMessage>>;
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<MidiIn>,
input_connect: Vec<String>,
output_port: Port<MidiOut>,
output_connect: Vec<String>,
playing: bool,
recording: bool,
overdub: bool,
notes_on: Vec<bool>,
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<MidiIn>,
input_connect: Vec<String>,
output_port: Port<MidiOut>,
output_connect: Vec<String>,
/// 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<bool>,
/// 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<DynamicDevice<Self>> {
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<Rect>
{
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<Rect> {
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<Rect> {
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<Rect> {
fn draw_vertical (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually<Rect> {
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<Rect> {
fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
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<dyn Error>> {
pub fn handle (s: &mut Sequencer, event: &AppEvent) -> Result<(), Box<dyn Error>> {
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(())
}
}

View file

@ -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<Option<usize>> = 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<Option<usize>> = 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));
}
}

View file

@ -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