wip: launcher grid

This commit is contained in:
🪞👃🪞 2024-06-24 21:35:45 +03:00
parent 55e6c19c92
commit 1f194dafd8
5 changed files with 371 additions and 129 deletions

View file

@ -1,19 +1,26 @@
use crate::prelude::*;
use ratatui::style::Stylize;
type Sequence = std::collections::BTreeMap<u32, Vec<::midly::MidiMessage>>;
mod keys;
use keys::*;
mod horizontal;
use horizontal::*;
pub mod horizontal;
mod vertical;
use vertical::*;
pub mod vertical;
pub struct Sequence {
pub name: String,
pub notes: std::collections::BTreeMap<u32, Vec<::midly::MidiMessage>>
}
impl Sequence {
pub fn new (name: &str) -> Self {
Self { name: name.to_string(), notes: std::collections::BTreeMap::new() }
}
}
pub struct Sequencer {
name: String,
pub name: String,
/// JACK transport handle.
transport: ::jack::Transport,
/// JACK MIDI input port that will be created.
@ -32,7 +39,7 @@ pub struct Sequencer {
/// Sequence selector
sequence: usize,
/// Map: tick -> MIDI events at tick
sequences: Vec<Sequence>,
pub sequences: Vec<Sequence>,
/// Red keys on piano roll.
notes_on: Vec<bool>,
@ -71,35 +78,44 @@ impl Sequencer {
let transport = client.transport();
let state = transport.query_state()?;
DynamicDevice::new(render, handle, Self::process, Self {
name: name.into(),
input_port: client.register_port("in", MidiIn::default())?,
output_port: client.register_port("out", MidiOut::default())?,
name: name.into(),
input_port: client.register_port("in", MidiIn::default())?,
output_port: client.register_port("out", MidiOut::default())?,
timebase: timebase.clone(),
steps: 64,
resolution: 8,
sequence: 0,
sequences: vec![std::collections::BTreeMap::new();8],
notes_on: vec![false;128],
timebase: timebase.clone(),
steps: 64,
resolution: 4,
sequence: 0,
sequences: vec![
Sequence::new(&"Phrase#01"),
Sequence::new(&"Phrase#02"),
Sequence::new(&"Phrase#03"),
Sequence::new(&"Phrase#04"),
Sequence::new(&"Phrase#05"),
Sequence::new(&"Phrase#06"),
Sequence::new(&"Phrase#07"),
Sequence::new(&"Phrase#08"),
],
notes_on: vec![false;128],
playing: TransportState::Starting,
monitoring: true,
recording: true,
overdub: true,
playing: TransportState::Starting,
monitoring: true,
recording: true,
overdub: true,
transport,
mode: SequencerView::Horizontal,
note_axis: (36, 68),
note_cursor: 0,
time_axis: (0, 64),
time_cursor: 0,
mode: SequencerView::Horizontal,
note_axis: (36, 68),
note_cursor: 0,
time_axis: (0, 64),
time_cursor: 0,
}).activate(client)
}
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
// Update time
let mut sequence = &mut self.sequences[self.sequence];
let mut sequence = &mut self.sequences[self.sequence].notes;
let transport = self.transport.query().unwrap();
self.playing = transport.state;
let pos = &transport.pos;
@ -112,7 +128,33 @@ impl Sequencer {
let frames = scope.n_frames() as usize;
let mut output: Vec<Option<Vec<Vec<u8>>>> = vec![None;frames];
// Read from input, write inputs to sequence and/or output
// Read from sequence into output buffer
if self.playing == TransportState::Rolling {
let frame = transport.pos.frame() as usize;
let quant = self.timebase.fpb() as usize * self.steps / self.resolution;
let ticks = self.timebase.frames_to_ticks(frame, frame + frames, quant);
for (time, tick) in ticks.iter() {
if let Some(events) = sequence.get(&(*tick as u32)) {
for message in events.iter() {
let mut buf = vec![];
let channel = 0.into();
let message = *message;
::midly::live::LiveEvent::Midi { channel, message }
.write(&mut buf)
.unwrap();
let t = *time as usize;
if output[t].is_none() {
output[t] = Some(vec![]);
}
if let Some(Some(frame)) = output.get_mut(t) {
frame.push(buf);
}
}
}
}
}
// Read from input, write inputs to sequence and/or output buffer
for event in self.input_port.iter(scope) {
let tick = tick as u32;
let msg = midly::live::LiveEvent::parse(event.bytes).unwrap();
@ -164,40 +206,14 @@ impl Sequencer {
}
}
// Read from sequence
if self.playing == TransportState::Rolling {
let frame = transport.pos.frame() as usize;
let quant = self.timebase.fpb() as usize * self.steps / self.resolution;
let ticks = self.timebase.frames_to_ticks(frame, frame + frames, quant);
for (time, tick) in ticks.iter() {
if let Some(events) = sequence.get(&(*tick as u32)) {
for message in events.iter() {
let mut buf = vec![];
::midly::live::LiveEvent::Midi {
channel: 0.into(),
message: *message,
}.write(&mut buf).unwrap();
let t = *time as usize;
if output[t].is_none() {
output[t] = Some(vec![]);
}
if let Some(Some(frame)) = output.get_mut(t) {
frame.push(buf);
}
}
}
}
}
// Write from monitor and/or sequence
// Write to port from output buffer
// (containing notes from sequence and/or monitor)
let mut writer = self.output_port.writer(scope);
for t in 0..scope.n_frames() {
if let Some(Some(frame)) = output.get_mut(t as usize) {
for time in 0..scope.n_frames() {
if let Some(Some(frame)) = output.get_mut(time as usize) {
for event in frame.iter() {
writer.write(&::jack::RawMidi {
time: t,
bytes: &event
}).expect(&format!("{event:?}"));
writer.write(&::jack::RawMidi { time, bytes: &event })
.expect(&format!("{event:?}"));
}
}
}
@ -221,13 +237,13 @@ fn render (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
Rect { x, y, width, height: 0 },
SequencerView::Compact =>
Rect { x, y, width, height: 0 },
SequencerView::Vertical => draw_vertical(s, buf, Rect {
SequencerView::Vertical => self::vertical::draw(s, buf, Rect {
x,
y: y + header.height,
width: 3 + note1 - note0,
height: 3 + time1 - time0,
}, steps)?,
SequencerView::Horizontal => draw_horizontal(s, buf, Rect {
SequencerView::Horizontal => self::horizontal::draw(s, buf, Rect {
x,
y: y + header.height,
width: area.width.max(3 + time1 - time0),
@ -256,9 +272,9 @@ fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usu
TransportState::Starting => format!("READY ..."),
TransportState::Stopped => format!("⏹ STOPPED")
}, match s.playing {
TransportState::Rolling => style.dim().bold(),
TransportState::Stopped => style.dim().bold(),
TransportState::Starting => style.not_dim().bold(),
TransportState::Stopped => style.not_dim().white().bold()
TransportState::Rolling => style.not_dim().white().bold()
});
buf.set_string(x, y + 2, format!("{}", "-".repeat((area.width - 2).into())), style.dim());
//buf.set_string(x + 2, y + 2,
@ -267,24 +283,21 @@ fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usu
//} else {
//Style::default().dim()
//});
buf.set_string(x + 13, y + 1,
&format!("⏺ REC"), if s.recording {
Style::default().bold().red()
} else {
Style::default().bold().dim()
});
buf.set_string(x + 20, y + 1,
&format!("⏺ DUB"), if s.overdub {
Style::default().bold().yellow()
} else {
Style::default().bold().dim()
});
buf.set_string(x + 27, y + 1,
&format!("⏺ MON"), if s.monitoring {
Style::default().bold().green()
} else {
Style::default().bold().dim()
});
buf.set_string(x + 13, y + 1, &format!("⏺ REC"), if s.recording {
Style::default().bold().red()
} else {
Style::default().bold().dim()
});
buf.set_string(x + 20, y + 1, &format!("⏺ DUB"), if s.overdub {
Style::default().bold().yellow()
} else {
Style::default().bold().dim()
});
buf.set_string(x + 27, y + 1, &format!("⏺ MON"), if s.monitoring {
Style::default().bold().green()
} else {
Style::default().bold().dim()
});
let clips = draw_clips(s, buf, area)?;
Ok(Rect { x, y, width: area.width, height: 3 })
}
@ -292,8 +305,9 @@ fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usu
fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, .. } = area;
let style = Style::default().gray();
for i in 0..8 {
buf.set_string(x + 2, y + 3 + i*2, &format!("{}", &s.name), if i as usize == s.sequence {
for (i, sequence) in s.sequences.iter().enumerate() {
let label = format!("{}", &sequence.name);
buf.set_string(x + 2, y + 3 + (i as u16)*2, &label, if i == s.sequence {
match s.playing {
TransportState::Rolling => style.white().bold(),
_ => style.not_dim().bold()
@ -306,7 +320,7 @@ fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
}
pub fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool {
for (_, (_, events)) in sequence.range(start..end).enumerate() {
for (_, (_, events)) in sequence.notes.range(start..end).enumerate() {
for event in events.iter() {
match event {
::midly::MidiMessage::NoteOn {key,..} => {
@ -339,9 +353,10 @@ pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
[Char('z'), NONE, "note_del", "Delete note", note_del],
[CapsLock, NONE, "advance", "Toggle auto advance", nop],
[Char('w'), NONE, "rest", "Advance by note duration", nop],
[Char(' '), NONE, "toggle_play", "Toggle play/pause", toggle_play],
[Char('r'), NONE, "toggle_record", "Toggle recording", toggle_record],
[Char('d'), NONE, "toggle_overdub", "Toggle overdub", toggle_overdub],
[Char(' '), NONE, "toggle_play", "Toggle play/pause", toggle_play],
[Char('m'), NONE, "toggle_monitor", "Toggle input monitoring", toggle_monitor],
[Char('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind],
[Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next],
[Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev],
@ -378,7 +393,7 @@ fn note_add (s: &mut Sequencer) -> Usually<bool> {
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() };
let mut sequence = &mut s.sequences[s.sequence];
let mut sequence = &mut s.sequences[s.sequence].notes;
if sequence.contains_key(&start) {
sequence.get_mut(&start).unwrap().push(note_on.clone());
} else {
@ -496,6 +511,10 @@ fn toggle_overdub (s: &mut Sequencer) -> Usually<bool> {
s.overdub = !s.overdub;
Ok(true)
}
fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
s.monitoring = !s.monitoring;
Ok(true)
}
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
if s.resolution < 64 {
s.resolution = s.resolution * 2;