mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
key lights up in response to recording
This commit is contained in:
parent
2118615aea
commit
674f95fcd2
2 changed files with 254 additions and 396 deletions
|
|
@ -12,9 +12,11 @@ pub struct Sequencer {
|
||||||
time_cursor: u16,
|
time_cursor: u16,
|
||||||
rate: Hz,
|
rate: Hz,
|
||||||
tempo: Tempo,
|
tempo: Tempo,
|
||||||
|
steps: u64,
|
||||||
|
divisions: u64,
|
||||||
transport: ::jack::Transport,
|
transport: ::jack::Transport,
|
||||||
sequence: Sequence,
|
sequence: Sequence,
|
||||||
ppq: u32,
|
ppq: u64,
|
||||||
input_port: Port<MidiIn>,
|
input_port: Port<MidiIn>,
|
||||||
input_connect: Vec<String>,
|
input_connect: Vec<String>,
|
||||||
output_port: Port<MidiOut>,
|
output_port: Port<MidiOut>,
|
||||||
|
|
@ -55,6 +57,8 @@ impl Sequencer {
|
||||||
recording: true,
|
recording: true,
|
||||||
overdub: true,
|
overdub: true,
|
||||||
notes_on: vec![false;128],
|
notes_on: vec![false;128],
|
||||||
|
steps: 64,
|
||||||
|
divisions: 16,
|
||||||
}).activate(client)
|
}).activate(client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,32 +70,29 @@ pub fn process (state: &mut Sequencer, client: &Client, scope: &ProcessScope) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_in (state: &mut Sequencer, scope: &ProcessScope) {
|
fn process_in (state: &mut Sequencer, scope: &ProcessScope) {
|
||||||
let pos = state.transport.query().unwrap().pos;
|
let pos = state.transport.query().unwrap().pos;
|
||||||
let frame = pos.frame();
|
let usecs = Frame(pos.frame()).to_usec(&state.rate).0;
|
||||||
let rate = pos.frame_rate().unwrap();
|
let beat = usecs / state.tempo.usec_per_beat().0;
|
||||||
let usecs = Frame(frame).to_usec(&Hz(rate)).0 as u64;
|
let step = usecs / state.tempo.usec_per_step(state.divisions as u64).0;
|
||||||
let usec_per_beat = state.tempo.usec_per_beat().0 as u64;
|
let tick_usec = state.tempo.usec_per_step(state.ppq).0;
|
||||||
let usec_per_tick = state.tempo.usec_per_tick(state.ppq).0 as u64;
|
let tick = usecs / tick_usec;
|
||||||
let beats = usecs / usec_per_beat;
|
|
||||||
let time = beats as u32 * state.ppq;
|
|
||||||
|
|
||||||
for event in state.input_port.iter(scope) {
|
for event in state.input_port.iter(scope) {
|
||||||
match midly::live::LiveEvent::parse(event.bytes).unwrap() {
|
match midly::live::LiveEvent::parse(event.bytes).unwrap() {
|
||||||
midly::live::LiveEvent::Midi { channel, message } => match message {
|
midly::live::LiveEvent::Midi { channel, message } => match message {
|
||||||
midly::MidiMessage::NoteOn { key, vel } => {
|
midly::MidiMessage::NoteOn { key, vel } => {
|
||||||
state.notes_on[key.as_int() as usize] = true;
|
state.notes_on[key.as_int() as usize] = true;
|
||||||
if state.sequence.contains_key(&time) {
|
if state.sequence.contains_key(&(tick as u32)) {
|
||||||
state.sequence.get_mut(&time).unwrap().push(message.clone());
|
state.sequence.get_mut(&(tick as u32)).unwrap().push(message.clone());
|
||||||
} else {
|
} else {
|
||||||
state.sequence.insert(time, vec![message.clone()]);
|
state.sequence.insert(tick as u32, vec![message.clone()]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
midly::MidiMessage::NoteOff { key, vel } => {
|
midly::MidiMessage::NoteOff { key, vel } => {
|
||||||
state.notes_on[key.as_int() as usize] = false;
|
state.notes_on[key.as_int() as usize] = false;
|
||||||
if state.sequence.contains_key(&time) {
|
if state.sequence.contains_key(&(tick as u32)) {
|
||||||
state.sequence.get_mut(&time).unwrap().push(message.clone());
|
state.sequence.get_mut(&(tick as u32)).unwrap().push(message.clone());
|
||||||
} else {
|
} else {
|
||||||
state.sequence.insert(time, vec![message.clone()]);
|
state.sequence.insert(tick as u32, vec![message.clone()]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -107,8 +108,15 @@ fn process_out (state: &mut Sequencer, scope: &ProcessScope) {
|
||||||
let start = scope.last_frame_time();
|
let start = scope.last_frame_time();
|
||||||
let end = start + size;
|
let end = start + size;
|
||||||
let mut writer = state.output_port.writer(scope);
|
let mut writer = state.output_port.writer(scope);
|
||||||
|
let pulse_usec = state.tempo.usec_per_step(state.ppq as u64).0;
|
||||||
|
let tick_start = Frame(start).to_usec(&state.rate).0;
|
||||||
|
let tick_start = tick_start / pulse_usec;
|
||||||
|
let tick_end = Frame(start).to_usec(&state.rate).0;
|
||||||
|
let tick_end = tick_start / pulse_usec;
|
||||||
for time in 0..size {
|
for time in 0..size {
|
||||||
// TODO
|
let usecs = Frame(start + time).to_usec(&state.rate).0;
|
||||||
|
let ticks = usecs / state.tempo.usec_per_step(state.ppq as u64).0;
|
||||||
|
//println!("{usecs} = {ticks}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,6 +142,226 @@ impl NotificationHandler for Sequencer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render (state: &Sequencer, buf: &mut Buffer, mut area: Rect)
|
||||||
|
-> Usually<Rect>
|
||||||
|
{
|
||||||
|
let Rect { x, y, width, height } = area;
|
||||||
|
let (time0, time1) = state.time_axis;
|
||||||
|
let (note0, note1) = state.note_axis;
|
||||||
|
let pos = state.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_beat = state.tempo.usec_per_beat().0;
|
||||||
|
let usec_per_tick = state.tempo.usec_per_step(state.ppq).0;
|
||||||
|
let usec_per_step = state.tempo.usec_per_step(state.divisions as u64).0;
|
||||||
|
let steps = usecs / usec_per_step;
|
||||||
|
let header = draw_state_header(state, buf, area, steps)?;
|
||||||
|
let piano = match state.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 {
|
||||||
|
x,
|
||||||
|
y: y + header.height,
|
||||||
|
width: 3 + note1 - note0,
|
||||||
|
height: 3 + time1 - time0,
|
||||||
|
}, steps)?,
|
||||||
|
SequencerView::Horizontal => draw_state_horizontal(state, buf, Rect {
|
||||||
|
x,
|
||||||
|
y: y + header.height,
|
||||||
|
width: 3 + time1 - time0,
|
||||||
|
height: 3 + note1 - note0,
|
||||||
|
})?,
|
||||||
|
};
|
||||||
|
Ok(draw_box(buf, Rect {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width: header.width.max(piano.width),
|
||||||
|
height: header.height + piano.height + 1
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
let Rect { x, y, width, height } = 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 + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ │"), style);
|
||||||
|
buf.set_string(x + 2, y + 2, &format!("▶ PLAY"), if state.playing {
|
||||||
|
Style::default().green()
|
||||||
|
} else {
|
||||||
|
Style::default().dim()
|
||||||
|
});
|
||||||
|
buf.set_string(x + 24, y + 2, &format!("⏺ REC"), if state.recording {
|
||||||
|
Style::default().red()
|
||||||
|
} else {
|
||||||
|
Style::default().dim()
|
||||||
|
});
|
||||||
|
buf.set_string(x + 32, y + 2, &format!("⏺ DUB"), if state.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,
|
||||||
|
];
|
||||||
|
|
||||||
|
fn draw_state_vertical (state: &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 bw = Style::default().dim();
|
||||||
|
let bg = Style::default().on_black();
|
||||||
|
for key in note0..note1.max(note0+32) {
|
||||||
|
let x = x + 5 + key - note0;
|
||||||
|
if key % 12 == 0 {
|
||||||
|
let octave = format!("C{}", (key / 12) as i8 - 4);
|
||||||
|
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 (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,
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
if is_on {
|
||||||
|
color = Style::default().red();
|
||||||
|
}
|
||||||
|
buf.set_string(x, y - 1, &format!("▄"), color);
|
||||||
|
}
|
||||||
|
for step in time0..time1 {
|
||||||
|
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 {
|
||||||
|
buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
|
||||||
|
}
|
||||||
|
for k in note0..note1.max(note0+32) {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
let (character, style) = match (
|
||||||
|
contains_note_on(&state.sequence, key, a, b),
|
||||||
|
contains_note_on(&state.sequence, key, b, c),
|
||||||
|
) {
|
||||||
|
(true, true) => ("█", bg),
|
||||||
|
(true, false) => ("▀", bg),
|
||||||
|
(false, true) => ("▄", bg),
|
||||||
|
(false, false) => (" ", bg),
|
||||||
|
};
|
||||||
|
buf.set_string(x + 5 + k - note0, y, character, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if beat % state.steps == step as u64 {
|
||||||
|
buf.set_string(x + 1, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow());
|
||||||
|
for key in note0..note1.max(note0+32) {
|
||||||
|
let color = if state.notes_on[key as usize] {
|
||||||
|
Style::default().red()
|
||||||
|
} else {
|
||||||
|
KEY_HORIZONTAL_STYLE[key as usize % 12]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.set_string(
|
||||||
|
x + 5 + state.note_cursor,
|
||||||
|
y + state.time_cursor / 2,
|
||||||
|
if state.time_cursor % 2 == 0 { "▀" } else { "▄" },
|
||||||
|
Style::default()
|
||||||
|
);
|
||||||
|
Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
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 event in events.iter() {
|
||||||
|
match event {
|
||||||
|
::midly::MidiMessage::NoteOn {key,..} => {
|
||||||
|
if *key == k {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const KEYS_VERTICAL: [&'static str; 6] = [
|
||||||
|
"▀", "▀", "▀", "█", "▄", "▄",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn draw_state_horizontal (state: &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 bw = Style::default().dim();
|
||||||
|
let bg = Style::default().on_black();
|
||||||
|
for i in 0..32.max(note1-note0)/2 {
|
||||||
|
let y = y + i;
|
||||||
|
buf.set_string(x + 1, y, KEYS_VERTICAL[(i % 6) as usize], bw);
|
||||||
|
buf.set_string(x + 2, y, "█", bw);
|
||||||
|
buf.set_string(x + 5, y, &" ".repeat((time1 - time0) as usize), bg);
|
||||||
|
if i % 6 == 0 {
|
||||||
|
let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4);
|
||||||
|
buf.set_string(x+5, y, &octave, Style::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
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 { "▄" },
|
||||||
|
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 (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||||
match event {
|
match event {
|
||||||
AppEvent::Input(Event::Key(event)) => {
|
AppEvent::Input(Event::Key(event)) => {
|
||||||
|
|
@ -174,8 +402,8 @@ fn nop (_: &mut Sequencer) {
|
||||||
}
|
}
|
||||||
fn note_add (s: &mut Sequencer) {
|
fn note_add (s: &mut Sequencer) {
|
||||||
let time = (s.time_axis.0 + s.time_cursor) as u32;
|
let time = (s.time_axis.0 + s.time_cursor) as u32;
|
||||||
let time_start = time * s.ppq;
|
let time_start = time * s.ppq as u32;
|
||||||
let time_end = (time + 1) * s.ppq;
|
let time_end = (time + 1) * s.ppq as u32;
|
||||||
let key = ::midly::num::u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8);
|
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_on = ::midly::MidiMessage::NoteOn { key, vel: 100.into() };
|
||||||
let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() };
|
let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() };
|
||||||
|
|
@ -249,373 +477,3 @@ fn toggle_record (s: &mut Sequencer) {
|
||||||
fn toggle_overdub (s: &mut Sequencer) {
|
fn toggle_overdub (s: &mut Sequencer) {
|
||||||
s.overdub = !s.overdub
|
s.overdub = !s.overdub
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect)
|
|
||||||
-> Usually<Rect>
|
|
||||||
{
|
|
||||||
let Rect { x, y, width, height } = area;
|
|
||||||
let (time0, time1) = sequencer.time_axis;
|
|
||||||
let (note0, note1) = sequencer.note_axis;
|
|
||||||
let pos = sequencer.transport.query().unwrap().pos;
|
|
||||||
let frame = pos.frame();
|
|
||||||
let rate = pos.frame_rate().unwrap();
|
|
||||||
let usecs = Frame(frame).to_usec(&Hz(rate)).0 as u64;
|
|
||||||
let usec_per_beat = sequencer.tempo.usec_per_beat().0 as u64;
|
|
||||||
let usec_per_tick = sequencer.tempo.usec_per_tick(sequencer.ppq).0 as u64;
|
|
||||||
let beats = usecs / usec_per_beat;
|
|
||||||
let bar = beats / 4;
|
|
||||||
let beat = beats % 4;
|
|
||||||
let tick = (usecs % usec_per_beat) / usec_per_tick;
|
|
||||||
let header = draw_sequencer_header(sequencer, buf, area, bar, beat, tick)?;
|
|
||||||
let piano = match sequencer.mode {
|
|
||||||
SequencerView::Tiny =>
|
|
||||||
Rect { x, y, width, height: 0 },
|
|
||||||
SequencerView::Compact =>
|
|
||||||
Rect { x, y, width, height: 0 },
|
|
||||||
SequencerView::Vertical => draw_sequencer_vertical(sequencer, buf, Rect {
|
|
||||||
x,
|
|
||||||
y: y + header.height,
|
|
||||||
width: 3 + note1 - note0,
|
|
||||||
height: 3 + time1 - time0,
|
|
||||||
}, beats)?,
|
|
||||||
SequencerView::Horizontal => draw_sequencer_horizontal(sequencer, buf, Rect {
|
|
||||||
x,
|
|
||||||
y: y + header.height,
|
|
||||||
width: 3 + time1 - time0,
|
|
||||||
height: 3 + note1 - note0,
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
Ok(draw_box(buf, Rect {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width: header.width.max(piano.width),
|
|
||||||
height: header.height + piano.height + 1
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect, bar: u64, beat: u64, tick: u64) -> Usually<Rect> {
|
|
||||||
let Rect { x, y, width, height } = area;
|
|
||||||
let style = Style::default().gray();
|
|
||||||
buf.set_string(x + 1, y + 1, &format!(" │ {bar}.{beat}.{tick:03}"), style);
|
|
||||||
buf.set_string(x + 2, y + 1, &format!("{}", &sequencer.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 sequencer.playing {
|
|
||||||
Style::default().green()
|
|
||||||
} else {
|
|
||||||
Style::default().dim()
|
|
||||||
});
|
|
||||||
buf.set_string(x + 24, y + 2, &format!("⏺ REC"), if sequencer.recording {
|
|
||||||
Style::default().red()
|
|
||||||
} else {
|
|
||||||
Style::default().dim()
|
|
||||||
});
|
|
||||||
buf.set_string(x + 32, y + 2, &format!("⏺ DUB"), if sequencer.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,
|
|
||||||
];
|
|
||||||
|
|
||||||
fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually<Rect> {
|
|
||||||
let Rect { x, y, .. } = area;
|
|
||||||
let transport = sequencer.transport.query().unwrap();
|
|
||||||
let (time0, time1) = sequencer.time_axis;
|
|
||||||
let (note0, note1) = sequencer.note_axis;
|
|
||||||
let bw = Style::default().dim();
|
|
||||||
let bg = Style::default().on_black();
|
|
||||||
for key in note0..note1.max(note0+32) {
|
|
||||||
let x = x + 5 + key - note0;
|
|
||||||
let color = if sequencer.notes_on[key as usize] {
|
|
||||||
Style::default().red()
|
|
||||||
} else {
|
|
||||||
KEY_HORIZONTAL_STYLE[key as usize % 12]
|
|
||||||
};
|
|
||||||
buf.set_string(x, y - 1, &format!("▄"), color);
|
|
||||||
if key % 12 == 0 {
|
|
||||||
let octave = format!("C{}", (key / 12) as i8 - 4);
|
|
||||||
buf.set_string(x, y, &octave, Style::default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for step in time0..time1 {
|
|
||||||
let y = y - time0 + step / 2;
|
|
||||||
//buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg);
|
|
||||||
if step % 8 == 0 {
|
|
||||||
buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
|
|
||||||
}
|
|
||||||
for k in note0..note1.max(note0+32) {
|
|
||||||
let key = ::midly::num::u7::from_int_lossy(k as u8);
|
|
||||||
if step % 2 == 0 {
|
|
||||||
let (a, b, c) = (
|
|
||||||
(step + 0) as u32 * sequencer.ppq,
|
|
||||||
(step + 1) as u32 * sequencer.ppq,
|
|
||||||
(step + 2) as u32 * sequencer.ppq,
|
|
||||||
);
|
|
||||||
let (character, style) = match (
|
|
||||||
contains_note_on(&sequencer.sequence, key, a, b),
|
|
||||||
contains_note_on(&sequencer.sequence, key, b, c),
|
|
||||||
) {
|
|
||||||
(true, true) => ("█", bg),
|
|
||||||
(true, false) => ("▀", bg),
|
|
||||||
(false, true) => ("▄", bg),
|
|
||||||
(false, false) => (" ", bg),
|
|
||||||
};
|
|
||||||
buf.set_string(x + 5 + k - note0, y, character, style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if beat == step as u64 {
|
|
||||||
buf.set_string(x + 1, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.set_string(
|
|
||||||
x + 5 + sequencer.note_cursor,
|
|
||||||
y + sequencer.time_cursor / 2,
|
|
||||||
if sequencer.time_cursor % 2 == 0 { "▀" } else { "▄" },
|
|
||||||
Style::default()
|
|
||||||
);
|
|
||||||
Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 })
|
|
||||||
}
|
|
||||||
|
|
||||||
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 event in events.iter() {
|
|
||||||
match event {
|
|
||||||
::midly::MidiMessage::NoteOn {key,..} => {
|
|
||||||
if *key == k {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const KEYS_VERTICAL: [&'static str; 6] = [
|
|
||||||
"▀", "▀", "▀", "█", "▄", "▄",
|
|
||||||
];
|
|
||||||
|
|
||||||
fn draw_sequencer_horizontal (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
let Rect { x, y, .. } = area;
|
|
||||||
let (time0, time1) = sequencer.time_axis;
|
|
||||||
let (note0, note1) = sequencer.note_axis;
|
|
||||||
let bw = Style::default().dim();
|
|
||||||
let bg = Style::default().on_black();
|
|
||||||
for i in 0..32.max(note1-note0)/2 {
|
|
||||||
let y = y + i;
|
|
||||||
buf.set_string(x + 1, y, KEYS_VERTICAL[(i % 6) as usize], bw);
|
|
||||||
buf.set_string(x + 2, y, "█", bw);
|
|
||||||
buf.set_string(x + 5, y, &" ".repeat((time1 - time0) as usize), bg);
|
|
||||||
if i % 6 == 0 {
|
|
||||||
let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4);
|
|
||||||
buf.set_string(x+5, y, &octave, Style::default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for step in time0..time1 {
|
|
||||||
let time_start = step as u32 * sequencer.ppq;
|
|
||||||
let time_end = (step + 1) as u32 * sequencer.ppq;
|
|
||||||
for (i, (t, events)) in sequencer.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 + sequencer.time_cursor,
|
|
||||||
y + sequencer.note_cursor / 2,
|
|
||||||
if sequencer.note_cursor % 2 == 0 { "▀" } else { "▄" },
|
|
||||||
Style::default()
|
|
||||||
);
|
|
||||||
Ok(Rect { x, y, width: time1 - time0 + 6, height: 32.max(note1 - note0) / 2 })
|
|
||||||
}
|
|
||||||
|
|
||||||
//buf.set_string(x + 3, y, "╭1.1.", Style::default().dim());
|
|
||||||
//buf.set_string(x + 3 + 16, y, "╭1.2.", Style::default().dim());
|
|
||||||
//buf.set_string(x + 3 + 32, y, "╭1.3.", Style::default().dim());
|
|
||||||
//buf.set_string(x + 3 + 48, y, "╭1.4.", Style::default().dim());
|
|
||||||
////buf.set_string(x + 2, y, "╭", Style::default().dim());
|
|
||||||
////buf.set_string(x + 2, y + 13, "╰", Style::default().dim());
|
|
||||||
//buf.set_string(x + 2 + 65, y, "╮", Style::default().dim());
|
|
||||||
//buf.set_string(x + 2 + 65, y + 13, "╯", Style::default().dim());
|
|
||||||
//let transport = sequencer.transport.query().unwrap();
|
|
||||||
//let frame = transport.pos.frame();
|
|
||||||
//let rate = transport.pos.frame_rate().unwrap();
|
|
||||||
//let second = (frame as f64) / (rate as f64);
|
|
||||||
//let minute = second / 60f64;
|
|
||||||
//let bpm = 120f64;
|
|
||||||
//let div = 4;
|
|
||||||
//let beats = minute * bpm;
|
|
||||||
//let bars = beats as u32 / div as u32;
|
|
||||||
//let beat = beats as u32 % div as u32 + 1;
|
|
||||||
//let beat_sub = beats % 1.0;
|
|
||||||
//buf.set_string(
|
|
||||||
//x - 18, y + height,
|
|
||||||
//format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32),
|
|
||||||
//Style::default()
|
|
||||||
//);
|
|
||||||
//let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 {
|
|
||||||
//ratatui::style::Color::Gray
|
|
||||||
//} else if step == sequencer.cursor.1 as usize || key == sequencer.cursor.0/2 {
|
|
||||||
//ratatui::style::Color::Black
|
|
||||||
//} else {
|
|
||||||
//ratatui::style::Color::Reset
|
|
||||||
//};
|
|
||||||
//let top = sequence[(key * 2) as usize][step].is_some();
|
|
||||||
//let bottom = sequence[(key * 2 + 1) as usize][step].is_some();
|
|
||||||
//match (top, bottom) {
|
|
||||||
//(true, true) => {
|
|
||||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "█",
|
|
||||||
//Style::default().yellow().not_dim().bold().bg(bg));
|
|
||||||
//},
|
|
||||||
//(true, false) => {
|
|
||||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "▀",
|
|
||||||
//Style::default().yellow().not_dim().bold().bg(bg));
|
|
||||||
//},
|
|
||||||
//(false, true) => {
|
|
||||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "▄",
|
|
||||||
//Style::default().yellow().not_dim().bold().bg(bg));
|
|
||||||
//},
|
|
||||||
//(false, false) => if step % 16 == 0 {
|
|
||||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "┊",
|
|
||||||
//Style::default().dim().bg(bg))
|
|
||||||
//} else {
|
|
||||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "·",
|
|
||||||
//Style::default().dim().bg(bg))
|
|
||||||
//},
|
|
||||||
//}
|
|
||||||
//for step in 0..64 {
|
|
||||||
//if step % 8 == 0 {
|
|
||||||
//buf.set_string(x + 3 + step as u16, y + 1 + 12, [
|
|
||||||
//"|a", "|b", "|c", "|d", "|e", "|f", "|g", "|h"
|
|
||||||
//][step / 8 as usize], Style::default().dim())
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//{
|
|
||||||
//let mut area = area.clone();
|
|
||||||
//area.y = area.y + 3;
|
|
||||||
//area.x = area.x + 1;
|
|
||||||
//area.height = area.height - 2;
|
|
||||||
//draw_sequence_keys(area, buf,
|
|
||||||
//&sequencer.jack_client.as_client().transport().query().unwrap(),
|
|
||||||
//&sequencer.sequence,
|
|
||||||
//&sequencer.cursor);
|
|
||||||
//draw_sequence_cursor(area, buf,
|
|
||||||
//&sequencer.cursor);
|
|
||||||
//}
|
|
||||||
//draw_box(buf, Rect { x, y, width: 18, height });
|
|
||||||
|
|
||||||
//draw_box(buf, Rect { x, y, width: 40, height: 8 });
|
|
||||||
//buf.set_string(x + 1, y + 3,
|
|
||||||
//&format!(" │ INPUT │ OUTPUT │"),
|
|
||||||
//Style::default().gray());
|
|
||||||
//buf.set_string(x + 1, y + 5,
|
|
||||||
//&format!(" {} │ IN │ ⏺ REC │ ⏺ DUB │ ▶ PLAY │ ⏹ STOP │ OUT │ 00:00.00 / 00:00.00", &sequencer.name),
|
|
||||||
//Style::default().gray());
|
|
||||||
//if sequencer.inputs_open.fetch_and(true, Ordering::Relaxed) {
|
|
||||||
//buf.set_string(x + 15, y + 1, "IN", Style::default().green().bold());
|
|
||||||
//}
|
|
||||||
//if sequencer.outputs_open.fetch_and(true, Ordering::Relaxed) {
|
|
||||||
//buf.set_string(x + 54, y + 1, "OUT", Style::default().green().bold());
|
|
||||||
//}
|
|
||||||
|
|
||||||
//let mut command = |y2: u16, c: &str, ommand: &str, value: &str| {
|
|
||||||
//buf.set_string(x + 1, y + y2, c, Style::default().bold());
|
|
||||||
//buf.set_string(x + 2, y + y2, ommand, Style::default().dim());
|
|
||||||
//buf.set_string(x + 4 + ommand.len() as u16, y + y2, value, Style::default().bold());
|
|
||||||
//};
|
|
||||||
//for (y, c, ommand, value) in [
|
|
||||||
////(5, "I", "nputs", "[+]"),
|
|
||||||
////(6, "O", "utputs", "[+]"),
|
|
||||||
////(7, "C", "hannel", "01"),
|
|
||||||
//(8, "G", "rid", "1/16"),
|
|
||||||
//(9, "Z", "oom", "1/64"),
|
|
||||||
//(10, "R", "ate", "1/1"),
|
|
||||||
//(11, "S", "ync", "1 bar"),
|
|
||||||
//(12, "A", "dd note", ""),
|
|
||||||
//(13, "D", "elete note", ""),
|
|
||||||
//] {
|
|
||||||
//command(y, c, ommand, value)
|
|
||||||
//}
|
|
||||||
//draw_box(buf, Rect { x, y: y + height - 1, width, height: 3 });
|
|
||||||
//buf.set_string(x + 1, y + height,
|
|
||||||
//&format!(" Grid 1/16 │ Zoom 1/64 │ Rate 1/1 │ Sync 1 bar │ Add note │ Delete note "),
|
|
||||||
//Style::default().gray());
|
|
||||||
//buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", &sequencer.name),
|
|
||||||
//Style::default().white().bold().not_dim());
|
|
||||||
//buf.set_string(x + 4, y + 0, "┬", Style::default().gray());
|
|
||||||
//buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim());
|
|
||||||
//buf.set_string(x + 4, y + 2, "┴", Style::default().gray());
|
|
||||||
//buf.set_string(x + 21, y + 1, " ⏺ REC DUB ",
|
|
||||||
//Style::default().white().bold().not_dim());
|
|
||||||
//buf.set_string(x + 18, y + 0, "┬", Style::default().gray());
|
|
||||||
//buf.set_string(x + 18, y + 1, "│", Style::default().gray().dim());
|
|
||||||
//buf.set_string(x + 18, y + 2, "┴", Style::default().gray());
|
|
||||||
|
|
||||||
//draw_rec_dub_button(buf, x + 17, y);
|
|
||||||
//draw_sequence_button(buf, x, y, &sequencer.name);
|
|
||||||
|
|
||||||
//draw_leaf(buf, Rect { x, y: y + height, width, height: 2 }, 0, 0, "Inputs ");
|
|
||||||
//draw_leaf(buf, Rect { x, y: y + height, width, height: 2 }, 0, 8, "Outputs");
|
|
||||||
//for x2 in 0..4 {
|
|
||||||
//for y2 in 0..4 {
|
|
||||||
//buf.set_string(
|
|
||||||
//x + 2 + x2 * 3,
|
|
||||||
//y + 5 + y2,
|
|
||||||
//format!("{:>02} ", 1 + x2 + y2 * 4),
|
|
||||||
//Style::default().green().bold().not_dim()
|
|
||||||
//)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//fn draw_sequence_button (
|
|
||||||
//buf: &mut Buffer,
|
|
||||||
//x: u16,
|
|
||||||
//y: u16,
|
|
||||||
//name: &str
|
|
||||||
//) {
|
|
||||||
//draw_box(buf, Rect { x, y, width: 18, height: 3 });
|
|
||||||
//buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", name),
|
|
||||||
//Style::default().white().bold().not_dim());
|
|
||||||
//buf.set_string(x + 4, y + 0, "┬", Style::default().gray());
|
|
||||||
//buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim());
|
|
||||||
//buf.set_string(x + 4, y + 2, "┴", Style::default().gray());
|
|
||||||
//}
|
|
||||||
|
|
||||||
//fn draw_rec_dub_button (
|
|
||||||
//buf: &mut Buffer,
|
|
||||||
//x: u16,
|
|
||||||
//y: u16,
|
|
||||||
//) {
|
|
||||||
//draw_box(buf, Rect { x, y, width: 18, height: 3 });
|
|
||||||
//buf.set_string(x + 1, y + 1, " ⏺ REC DUB ",
|
|
||||||
//Style::default().white().bold().not_dim());
|
|
||||||
//buf.set_string(x + 5, y + 0, "┬", Style::default().gray());
|
|
||||||
//buf.set_string(x + 5, y + 1, "│", Style::default().gray().dim());
|
|
||||||
//buf.set_string(x + 5, y + 2, "┴", Style::default().gray());
|
|
||||||
//buf.set_string(x + 11, y + 0, "┬", Style::default().gray());
|
|
||||||
//buf.set_string(x + 11, y + 1, "│", Style::default().gray().dim());
|
|
||||||
//buf.set_string(x + 11, y + 2, "┴", Style::default().gray());
|
|
||||||
//}
|
|
||||||
|
|
|
||||||
14
src/time.rs
14
src/time.rs
|
|
@ -8,7 +8,7 @@ pub struct Frame(pub u32);
|
||||||
pub struct Usec(pub u64);
|
pub struct Usec(pub u64);
|
||||||
|
|
||||||
/// Beats per minute as `120000` = 120.000BPM
|
/// Beats per minute as `120000` = 120.000BPM
|
||||||
pub struct Tempo(pub u32);
|
pub struct Tempo(pub u64);
|
||||||
|
|
||||||
/// Time signature: N/Mths per bar.
|
/// Time signature: N/Mths per bar.
|
||||||
pub struct Signature(pub u32, pub u32);
|
pub struct Signature(pub u32, pub u32);
|
||||||
|
|
@ -36,17 +36,17 @@ impl Usec {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tempo {
|
impl Tempo {
|
||||||
|
#[inline]
|
||||||
|
pub fn usec_per_bar (&self, beats: u64) -> Usec {
|
||||||
|
Usec(beats * self.usec_per_beat().0)
|
||||||
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn usec_per_beat (&self) -> Usec {
|
pub fn usec_per_beat (&self) -> Usec {
|
||||||
Usec(60_000_000_000 / self.0 as u64)
|
Usec(60_000_000_000 / self.0 as u64)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn usec_per_quarter (&self) -> Usec {
|
pub fn usec_per_step (&self, divisor: u64) -> Usec {
|
||||||
Usec(self.usec_per_beat().0 / 4)
|
Usec(self.usec_per_beat().0 / divisor as u64)
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn usec_per_tick (&self, ppq: u32) -> Usec {
|
|
||||||
Usec(self.usec_per_quarter().0 / ppq as u64)
|
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn quantize (&self, step: &NoteDuration, time: Usec) -> Usec {
|
pub fn quantize (&self, step: &NoteDuration, time: Usec) -> Usec {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue