mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
loop recording!
This commit is contained in:
parent
7fb9369012
commit
d39cce271f
3 changed files with 301 additions and 214 deletions
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue