mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 12:46:42 +01:00
wip: launcher grid
This commit is contained in:
parent
55e6c19c92
commit
1f194dafd8
5 changed files with 371 additions and 129 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue