mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16: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,38 +1,256 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Launcher {
|
pub struct Launcher {
|
||||||
name: String
|
name: String,
|
||||||
|
timebase: Arc<Timebase>,
|
||||||
|
cursor: (usize, usize),
|
||||||
|
tracks: Vec<DynamicDevice<Sequencer>>,
|
||||||
|
chains: Vec<DynamicDevice<Chain>>,
|
||||||
|
rows: usize,
|
||||||
|
show_help: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Launcher {
|
impl Launcher {
|
||||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
pub fn new (name: &str, timebase: &Arc<Timebase>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
Ok(DynamicDevice::new(render, handle, process, Self {
|
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
timebase: timebase.clone(),
|
||||||
|
cursor: (0, 0),
|
||||||
|
rows: 8,
|
||||||
|
tracks: vec![
|
||||||
|
Sequencer::new("Drum", timebase)?,
|
||||||
|
Sequencer::new("Bass", timebase)?,
|
||||||
|
Sequencer::new("Pads", timebase)?,
|
||||||
|
Sequencer::new("Lead", timebase)?,
|
||||||
|
],
|
||||||
|
chains: vec![
|
||||||
|
// TODO
|
||||||
|
],
|
||||||
|
show_help: true
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
fn cols (&self) -> usize {
|
||||||
|
(self.tracks.len() + 1) as usize
|
||||||
|
}
|
||||||
|
fn col (&self) -> usize {
|
||||||
|
self.cursor.0 as usize
|
||||||
|
}
|
||||||
|
fn dec_col (&mut self) {
|
||||||
|
self.cursor.0 = if self.cursor.0 > 0 {
|
||||||
|
self.cursor.0 - 1
|
||||||
|
} else {
|
||||||
|
(self.cols() - 1) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn inc_col (&mut self) {
|
||||||
|
self.cursor.0 = if self.cursor.0 >= self.cols() - 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.cursor.0 + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn rows (&self) -> usize {
|
||||||
|
(self.rows + 2) as usize
|
||||||
|
}
|
||||||
|
fn row (&self) -> usize {
|
||||||
|
self.cursor.1 as usize
|
||||||
|
}
|
||||||
|
fn dec_row (&mut self) {
|
||||||
|
self.cursor.1 = if self.cursor.1 > 0 {
|
||||||
|
self.cursor.1 - 1
|
||||||
|
} else {
|
||||||
|
self.rows() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn inc_row (&mut self) {
|
||||||
|
self.cursor.1 = if self.cursor.1 >= self.rows() - 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.cursor.1 + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
impl DevicePorts for Launcher {}
|
||||||
pub fn process (_: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
|
pub fn process (_: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
macro_rules! set {
|
||||||
pub fn render (_: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
($buf:expr, $style:expr, $x: expr, $y: expr, $fmt: literal $(, $val:expr)*) => {
|
||||||
for i in 1..=8 {
|
$buf.set_string($x, $y, &format!($fmt $(, $val)*), $style)
|
||||||
buf.set_string(area.x + 2 + (i-1) * 10, area.y, format!("Track#{i} | "), Style::default())
|
|
||||||
}
|
}
|
||||||
for i in 0..=7 {
|
}
|
||||||
for j in 0..=7 {
|
trait Blit {
|
||||||
buf.set_string(area.x + 2 + i * 10, area.y + 2 + j, format!("······· | "), Style::default().dim())
|
fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option<Style>);
|
||||||
|
}
|
||||||
|
impl<T: AsRef<str>> Blit for T {
|
||||||
|
fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option<Style>) {
|
||||||
|
buf.set_string(x, y, self.as_ref(), style.unwrap_or(Style::default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
let Rect { x, y, width, height } = area;
|
||||||
|
let separator = format!("├{}┤", "-".repeat((width - 2).into()));
|
||||||
|
let scenes = draw_launcher_scenes(state, buf, x, y);
|
||||||
|
separator.blit(buf, x, y + 2, Some(Style::default().dim()));
|
||||||
|
separator.blit(buf, x, y + 4, Some(Style::default().dim()));
|
||||||
|
separator.blit(buf, x, y + 40, Some(Style::default().dim()));
|
||||||
|
let (w, mut highlight) = draw_launcher_tracks(state, buf, x, y);
|
||||||
|
if state.col() == 0 {
|
||||||
|
highlight = Some(scenes);
|
||||||
|
}
|
||||||
|
draw_crossings(state, buf, x + w - 2, y);
|
||||||
|
draw_sequencer(state, buf, x, y + 21, width, height - 22)?;
|
||||||
|
draw_box(buf, area);
|
||||||
|
draw_launcher_highlight(state, buf, &highlight);
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_launcher_scenes (
|
||||||
|
state: &Launcher, buf: &mut Buffer, x: u16, y: u16,
|
||||||
|
) -> Rect {
|
||||||
|
let style = Style::default().not_dim().bold();
|
||||||
|
let row = state.row() as u16;
|
||||||
|
let col = state.col() as u16;
|
||||||
|
let mut width = 8u16;
|
||||||
|
let mut height = 6u16;
|
||||||
|
format!("{} | ", state.name).blit(buf, x+2, y+1, Some(
|
||||||
|
if row == 0 && col == 0 { Style::default().green() } else { Style::default() }
|
||||||
|
));
|
||||||
|
format!("Sync 1/1").blit(buf, x+2, y+3, Some(
|
||||||
|
if row == 1 && col == 0 { Style::default().green() } else { Style::default() }
|
||||||
|
));
|
||||||
|
for j in 0..=7 {
|
||||||
|
let y = y + 5 + j as u16 * 2;
|
||||||
|
let label = format!("▶ Scene {j}");
|
||||||
|
width = width.max(label.len() as u16 + 3);
|
||||||
|
label.blit(buf, x + 2, y, Some(
|
||||||
|
if row == j + 2 && col == 0 { Style::default().green() } else { Style::default() }
|
||||||
|
));
|
||||||
|
height = height + 2;
|
||||||
|
}
|
||||||
|
Rect { x, y, width, height }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_launcher_tracks (
|
||||||
|
state: &Launcher, buf: &mut Buffer, x: u16, y: u16
|
||||||
|
) -> (u16, Option<Rect>) {
|
||||||
|
let mut w = 15;
|
||||||
|
let mut highlight = None;
|
||||||
|
for (i, track) in state.tracks.iter().enumerate() {
|
||||||
|
let track = track.state();
|
||||||
|
draw_crossings(state, buf, x + w - 2, y);
|
||||||
|
let width = draw_launcher_track(state, buf, x + w, y, i as u16, &track);
|
||||||
|
if i + 1 == state.col() {
|
||||||
|
highlight = Some(Rect { x: x + w - 2, y, width: width + 1, height: 22 });
|
||||||
|
}
|
||||||
|
w = w + width;
|
||||||
|
}
|
||||||
|
(w, highlight)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_launcher_track (
|
||||||
|
state: &Launcher, buf: &mut Buffer, x: u16, y: u16, i: u16, track: &Sequencer
|
||||||
|
) -> u16 {
|
||||||
|
let mut width = track.name.len() as u16 + 3;
|
||||||
|
let row = state.row() as u16;
|
||||||
|
let col = state.col() as u16;
|
||||||
|
|
||||||
|
track.name.blit(buf, x, y + 1, Some(
|
||||||
|
if row == 0 && col == i + 1 { Style::default().green() } else { Style::default() }
|
||||||
|
));
|
||||||
|
|
||||||
|
"(global)".blit(buf, x, y + 3, Some(
|
||||||
|
if row == 1 && col == i + 1 { Style::default().green() } else { Style::default().dim() }
|
||||||
|
));
|
||||||
|
|
||||||
|
"┊".blit(buf, x - 2, y + 3, Some(
|
||||||
|
Style::default().dim()
|
||||||
|
));
|
||||||
|
|
||||||
|
for j in 0..=7 {
|
||||||
|
let y = y + 5 + j as u16 * 2;
|
||||||
|
if let Some(sequence) = track.sequences.get(j) {
|
||||||
|
let label = format!("▶ {}", &sequence.name);
|
||||||
|
width = width.max(label.len() as u16 + 1);
|
||||||
|
let style = if j + 2 == row as usize && i + 1 == col {
|
||||||
|
Style::default().green().bold()
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
};
|
||||||
|
buf.set_string(x - 0, y + 0, &label, style);
|
||||||
|
buf.set_string(x - 2, y + 0, &"┊", style.dim());
|
||||||
|
buf.set_string(x - 2, y + 1, &"┊", style.dim());
|
||||||
|
buf.set_string(x + width - 2, y + 0, &"┊", style.dim());
|
||||||
|
buf.set_string(x + width - 2, y + 1, &"┊", style.dim());
|
||||||
|
} else {
|
||||||
|
buf.set_string(x - 2, y as u16,
|
||||||
|
format!("┊ {}", &"·".repeat(track.name.len())),
|
||||||
|
Style::default().dim()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(draw_box(buf, Rect {
|
|
||||||
x: area.x,
|
width
|
||||||
y: area.y - 1,
|
|
||||||
width: area.width,
|
|
||||||
height: 12
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle (_: &mut Launcher, _: &AppEvent) -> Usually<bool> {
|
fn draw_crossings (state: &Launcher, buf: &mut Buffer, x: u16, y: u16) {
|
||||||
Ok(false)
|
"╭".blit(buf, x, y + 0, None);
|
||||||
|
"┊".blit(buf, x, y + 1, Some(Style::default().dim()));
|
||||||
|
"┼".blit(buf, x, y + 2, Some(Style::default().dim()));
|
||||||
|
"┊".blit(buf, x, y + 3, Some(Style::default().dim()));
|
||||||
|
"┼".blit(buf, x, y + 4, Some(Style::default().dim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_sequencer (
|
||||||
|
state: &Launcher, buf: &mut Buffer, x: u16, y: u16, width: u16, height: u16
|
||||||
|
) -> Usually<()> {
|
||||||
|
if let Some(sequencer) = state.tracks.get(state.col().saturating_sub(1)) {
|
||||||
|
crate::device::sequencer::horizontal::footer(
|
||||||
|
&sequencer.state(), buf, 0, y, width, 0
|
||||||
|
);
|
||||||
|
crate::device::sequencer::horizontal::keys(
|
||||||
|
&sequencer.state(), buf, Rect { x, y: y + 3, width, height }
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_launcher_highlight (
|
||||||
|
state: &Launcher, buf: &mut Buffer, highlight: &Option<Rect>) {
|
||||||
|
if let Some(area) = highlight {
|
||||||
|
draw_box_styled(buf, *area, Some(Style::default().green().dim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually<bool> {
|
||||||
|
handle_keymap(state, event, KEYMAP)
|
||||||
|
}
|
||||||
|
pub const KEYMAP: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
|
||||||
|
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
||||||
|
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
||||||
|
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
|
||||||
|
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
|
||||||
|
[Char('r'), NONE, "rename", "rename current element", rename],
|
||||||
|
[F(1), NONE, "toggle_help", "toggle help", toggle_help],
|
||||||
|
});
|
||||||
|
fn rename (_: &mut Launcher) -> Usually<bool> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn cursor_up (state: &mut Launcher) -> Usually<bool> {
|
||||||
|
state.dec_row();
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn cursor_down (state: &mut Launcher) -> Usually<bool> {
|
||||||
|
state.inc_row();
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn cursor_left (state: &mut Launcher) -> Usually<bool> {
|
||||||
|
state.dec_col();
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn cursor_right (state: &mut Launcher) -> Usually<bool> {
|
||||||
|
state.inc_col();
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
fn toggle_help (state: &mut Launcher) -> Usually<bool> {
|
||||||
|
state.show_help = !state.show_help;
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,26 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use ratatui::style::Stylize;
|
use ratatui::style::Stylize;
|
||||||
|
|
||||||
type Sequence = std::collections::BTreeMap<u32, Vec<::midly::MidiMessage>>;
|
|
||||||
|
|
||||||
mod keys;
|
mod keys;
|
||||||
use keys::*;
|
use keys::*;
|
||||||
|
|
||||||
mod horizontal;
|
pub mod horizontal;
|
||||||
use horizontal::*;
|
|
||||||
|
|
||||||
mod vertical;
|
pub mod vertical;
|
||||||
use 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 {
|
pub struct Sequencer {
|
||||||
name: String,
|
pub name: String,
|
||||||
/// JACK transport handle.
|
/// JACK transport handle.
|
||||||
transport: ::jack::Transport,
|
transport: ::jack::Transport,
|
||||||
/// JACK MIDI input port that will be created.
|
/// JACK MIDI input port that will be created.
|
||||||
|
|
@ -32,7 +39,7 @@ pub struct Sequencer {
|
||||||
/// Sequence selector
|
/// Sequence selector
|
||||||
sequence: usize,
|
sequence: usize,
|
||||||
/// Map: tick -> MIDI events at tick
|
/// Map: tick -> MIDI events at tick
|
||||||
sequences: Vec<Sequence>,
|
pub sequences: Vec<Sequence>,
|
||||||
/// Red keys on piano roll.
|
/// Red keys on piano roll.
|
||||||
notes_on: Vec<bool>,
|
notes_on: Vec<bool>,
|
||||||
|
|
||||||
|
|
@ -71,35 +78,44 @@ impl Sequencer {
|
||||||
let transport = client.transport();
|
let transport = client.transport();
|
||||||
let state = transport.query_state()?;
|
let state = transport.query_state()?;
|
||||||
DynamicDevice::new(render, handle, Self::process, Self {
|
DynamicDevice::new(render, handle, Self::process, Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
input_port: client.register_port("in", MidiIn::default())?,
|
input_port: client.register_port("in", MidiIn::default())?,
|
||||||
output_port: client.register_port("out", MidiOut::default())?,
|
output_port: client.register_port("out", MidiOut::default())?,
|
||||||
|
|
||||||
timebase: timebase.clone(),
|
timebase: timebase.clone(),
|
||||||
steps: 64,
|
steps: 64,
|
||||||
resolution: 8,
|
resolution: 4,
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
sequences: vec![std::collections::BTreeMap::new();8],
|
sequences: vec![
|
||||||
notes_on: vec![false;128],
|
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,
|
playing: TransportState::Starting,
|
||||||
monitoring: true,
|
monitoring: true,
|
||||||
recording: true,
|
recording: true,
|
||||||
overdub: true,
|
overdub: true,
|
||||||
transport,
|
transport,
|
||||||
|
|
||||||
mode: SequencerView::Horizontal,
|
mode: SequencerView::Horizontal,
|
||||||
note_axis: (36, 68),
|
note_axis: (36, 68),
|
||||||
note_cursor: 0,
|
note_cursor: 0,
|
||||||
time_axis: (0, 64),
|
time_axis: (0, 64),
|
||||||
time_cursor: 0,
|
time_cursor: 0,
|
||||||
}).activate(client)
|
}).activate(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
|
||||||
// Update time
|
// 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();
|
let transport = self.transport.query().unwrap();
|
||||||
self.playing = transport.state;
|
self.playing = transport.state;
|
||||||
let pos = &transport.pos;
|
let pos = &transport.pos;
|
||||||
|
|
@ -112,7 +128,33 @@ impl Sequencer {
|
||||||
let frames = scope.n_frames() as usize;
|
let frames = scope.n_frames() as usize;
|
||||||
let mut output: Vec<Option<Vec<Vec<u8>>>> = vec![None;frames];
|
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) {
|
for event in self.input_port.iter(scope) {
|
||||||
let tick = tick as u32;
|
let tick = tick as u32;
|
||||||
let msg = midly::live::LiveEvent::parse(event.bytes).unwrap();
|
let msg = midly::live::LiveEvent::parse(event.bytes).unwrap();
|
||||||
|
|
@ -164,40 +206,14 @@ impl Sequencer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read from sequence
|
// Write to port from output buffer
|
||||||
if self.playing == TransportState::Rolling {
|
// (containing notes from sequence and/or monitor)
|
||||||
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
|
|
||||||
let mut writer = self.output_port.writer(scope);
|
let mut writer = self.output_port.writer(scope);
|
||||||
for t in 0..scope.n_frames() {
|
for time in 0..scope.n_frames() {
|
||||||
if let Some(Some(frame)) = output.get_mut(t as usize) {
|
if let Some(Some(frame)) = output.get_mut(time as usize) {
|
||||||
for event in frame.iter() {
|
for event in frame.iter() {
|
||||||
writer.write(&::jack::RawMidi {
|
writer.write(&::jack::RawMidi { time, bytes: &event })
|
||||||
time: t,
|
.expect(&format!("{event:?}"));
|
||||||
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 },
|
Rect { x, y, width, height: 0 },
|
||||||
SequencerView::Compact =>
|
SequencerView::Compact =>
|
||||||
Rect { x, y, width, height: 0 },
|
Rect { x, y, width, height: 0 },
|
||||||
SequencerView::Vertical => draw_vertical(s, buf, Rect {
|
SequencerView::Vertical => self::vertical::draw(s, buf, Rect {
|
||||||
x,
|
x,
|
||||||
y: y + header.height,
|
y: y + header.height,
|
||||||
width: 3 + note1 - note0,
|
width: 3 + note1 - note0,
|
||||||
height: 3 + time1 - time0,
|
height: 3 + time1 - time0,
|
||||||
}, steps)?,
|
}, steps)?,
|
||||||
SequencerView::Horizontal => draw_horizontal(s, buf, Rect {
|
SequencerView::Horizontal => self::horizontal::draw(s, buf, Rect {
|
||||||
x,
|
x,
|
||||||
y: y + header.height,
|
y: y + header.height,
|
||||||
width: area.width.max(3 + time1 - time0),
|
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::Starting => format!("READY ..."),
|
||||||
TransportState::Stopped => format!("⏹ STOPPED")
|
TransportState::Stopped => format!("⏹ STOPPED")
|
||||||
}, match s.playing {
|
}, match s.playing {
|
||||||
TransportState::Rolling => style.dim().bold(),
|
TransportState::Stopped => style.dim().bold(),
|
||||||
TransportState::Starting => style.not_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, y + 2, format!("├{}┤", "-".repeat((area.width - 2).into())), style.dim());
|
||||||
//buf.set_string(x + 2, y + 2,
|
//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 {
|
//} else {
|
||||||
//Style::default().dim()
|
//Style::default().dim()
|
||||||
//});
|
//});
|
||||||
buf.set_string(x + 13, y + 1,
|
buf.set_string(x + 13, y + 1, &format!("⏺ REC"), if s.recording {
|
||||||
&format!("⏺ REC"), if s.recording {
|
Style::default().bold().red()
|
||||||
Style::default().bold().red()
|
} else {
|
||||||
} else {
|
Style::default().bold().dim()
|
||||||
Style::default().bold().dim()
|
});
|
||||||
});
|
buf.set_string(x + 20, y + 1, &format!("⏺ DUB"), if s.overdub {
|
||||||
buf.set_string(x + 20, y + 1,
|
Style::default().bold().yellow()
|
||||||
&format!("⏺ DUB"), if s.overdub {
|
} else {
|
||||||
Style::default().bold().yellow()
|
Style::default().bold().dim()
|
||||||
} else {
|
});
|
||||||
Style::default().bold().dim()
|
buf.set_string(x + 27, y + 1, &format!("⏺ MON"), if s.monitoring {
|
||||||
});
|
Style::default().bold().green()
|
||||||
buf.set_string(x + 27, y + 1,
|
} else {
|
||||||
&format!("⏺ MON"), if s.monitoring {
|
Style::default().bold().dim()
|
||||||
Style::default().bold().green()
|
});
|
||||||
} else {
|
|
||||||
Style::default().bold().dim()
|
|
||||||
});
|
|
||||||
let clips = draw_clips(s, buf, area)?;
|
let clips = draw_clips(s, buf, area)?;
|
||||||
Ok(Rect { x, y, width: area.width, height: 3 })
|
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> {
|
fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
let Rect { x, y, .. } = area;
|
let Rect { x, y, .. } = area;
|
||||||
let style = Style::default().gray();
|
let style = Style::default().gray();
|
||||||
for i in 0..8 {
|
for (i, sequence) in s.sequences.iter().enumerate() {
|
||||||
buf.set_string(x + 2, y + 3 + i*2, &format!("▶ {}", &s.name), if i as usize == s.sequence {
|
let label = format!("▶ {}", &sequence.name);
|
||||||
|
buf.set_string(x + 2, y + 3 + (i as u16)*2, &label, if i == s.sequence {
|
||||||
match s.playing {
|
match s.playing {
|
||||||
TransportState::Rolling => style.white().bold(),
|
TransportState::Rolling => style.white().bold(),
|
||||||
_ => style.not_dim().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 {
|
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() {
|
for event in events.iter() {
|
||||||
match event {
|
match event {
|
||||||
::midly::MidiMessage::NoteOn {key,..} => {
|
::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],
|
[Char('z'), NONE, "note_del", "Delete note", note_del],
|
||||||
[CapsLock, NONE, "advance", "Toggle auto advance", nop],
|
[CapsLock, NONE, "advance", "Toggle auto advance", nop],
|
||||||
[Char('w'), NONE, "rest", "Advance by note duration", 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('r'), NONE, "toggle_record", "Toggle recording", toggle_record],
|
||||||
[Char('d'), NONE, "toggle_overdub", "Toggle overdub", toggle_overdub],
|
[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('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind],
|
||||||
[Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next],
|
[Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next],
|
||||||
[Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev],
|
[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 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() };
|
||||||
let mut sequence = &mut s.sequences[s.sequence];
|
let mut sequence = &mut s.sequences[s.sequence].notes;
|
||||||
if sequence.contains_key(&start) {
|
if sequence.contains_key(&start) {
|
||||||
sequence.get_mut(&start).unwrap().push(note_on.clone());
|
sequence.get_mut(&start).unwrap().push(note_on.clone());
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -496,6 +511,10 @@ fn toggle_overdub (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.overdub = !s.overdub;
|
s.overdub = !s.overdub;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
|
||||||
|
s.monitoring = !s.monitoring;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
|
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
|
||||||
if s.resolution < 64 {
|
if s.resolution < 64 {
|
||||||
s.resolution = s.resolution * 2;
|
s.resolution = s.resolution * 2;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn draw_horizontal (
|
pub fn draw (
|
||||||
s: &Sequencer,
|
s: &Sequencer,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
mut area: Rect,
|
mut area: Rect,
|
||||||
|
|
@ -14,7 +14,7 @@ pub fn draw_horizontal (
|
||||||
let height = 32.max(s.note_axis.1 - s.note_axis.0) / 2;
|
let height = 32.max(s.note_axis.1 - s.note_axis.0) / 2;
|
||||||
lanes(s, buf, x, y, width);
|
lanes(s, buf, x, y, width);
|
||||||
cursor(s, buf, x, y);
|
cursor(s, buf, x, y);
|
||||||
footer(s, buf, x, y, width, height);
|
footer(s, buf, x - 13, y, width, height);
|
||||||
Ok(Rect {
|
Ok(Rect {
|
||||||
x: x - 13,
|
x: x - 13,
|
||||||
y: y,
|
y: y,
|
||||||
|
|
@ -23,7 +23,7 @@ pub fn draw_horizontal (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn timer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, beat: usize) {
|
pub fn timer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, beat: usize) {
|
||||||
let bw = Style::default().dim();
|
let bw = Style::default().dim();
|
||||||
let bg = Style::default().on_black();
|
let bg = Style::default().on_black();
|
||||||
let (time0, time1) = s.time_axis;
|
let (time0, time1) = s.time_axis;
|
||||||
|
|
@ -36,18 +36,19 @@ fn timer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, beat: usize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lanes (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, width: u16) {
|
pub fn lanes (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, width: u16) {
|
||||||
let bw = Style::default().dim();
|
let bw = Style::default().dim();
|
||||||
let bg = Style::default().on_black();
|
let bg = Style::default().on_black();
|
||||||
let ppq = s.timebase.ppq() as u32;
|
let ppq = s.timebase.ppq() as u32;
|
||||||
let (time0, time1) = s.time_axis;
|
let (time0, time1) = s.time_axis;
|
||||||
|
let notes = &s.sequences[s.sequence].notes;
|
||||||
for step in time0..time1 {
|
for step in time0..time1 {
|
||||||
let time_start = step as u32 * ppq;
|
let time_start = step as u32 * ppq;
|
||||||
let time_end = (step + 1) as u32 * ppq;
|
let time_end = (step + 1) as u32 * ppq;
|
||||||
if step % s.resolution as u16 == 0 {
|
if step % s.resolution as u16 == 0 {
|
||||||
buf.set_string(x + 6 + step, y - 1, &format!("{}", step + 1), Style::default());
|
buf.set_string(x + 6 + step, y - 1, &format!("{}", step + 1), Style::default());
|
||||||
}
|
}
|
||||||
for (_, (_, events)) in s.sequences[s.sequence].range(time_start..time_end).enumerate() {
|
for (_, (_, events)) in notes.range(time_start..time_end).enumerate() {
|
||||||
if events.len() > 0 {
|
if events.len() > 0 {
|
||||||
buf.set_string(x + 6 + step as u16, y, "█", bw);
|
buf.set_string(x + 6 + step as u16, y, "█", bw);
|
||||||
}
|
}
|
||||||
|
|
@ -55,14 +56,16 @@ fn lanes (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, width: u16) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn footer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, width: u16, height: u16) {
|
pub fn footer (s: &Sequencer, buf: &mut Buffer, mut x: u16, y: u16, width: u16, height: u16) {
|
||||||
let bw = Style::default().dim();
|
let bw = Style::default().dim();
|
||||||
let bg = Style::default().on_black();
|
let bg = Style::default().on_black();
|
||||||
let (note0, note1) = s.note_axis;
|
let (note0, note1) = s.note_axis;
|
||||||
buf.set_string(x - 13, y + height, format!("├{}┤", "-".repeat((width - 2).into())),
|
buf.set_string(x, y + height, format!("├{}┤", "-".repeat((width - 2).into())),
|
||||||
Style::default().dim());
|
Style::default().dim());
|
||||||
|
buf.set_string(x, y + height + 2, format!("├{}┤", "-".repeat((width - 2).into())),
|
||||||
|
Style::default().dim());
|
||||||
|
x = x + 2;
|
||||||
{
|
{
|
||||||
let mut x = x - 11;
|
|
||||||
for (i, [letter, title, value]) in [
|
for (i, [letter, title, value]) in [
|
||||||
["S", &format!("ync"), &format!("<4/4>")],
|
["S", &format!("ync"), &format!("<4/4>")],
|
||||||
["Q", &format!("uant"), &format!("<1/{}>", 4 * s.resolution)],
|
["Q", &format!("uant"), &format!("<1/{}>", 4 * s.resolution)],
|
||||||
|
|
@ -87,7 +90,7 @@ fn footer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, width: u16, height:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
pub fn cursor (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
||||||
let bw = Style::default().dim();
|
let bw = Style::default().dim();
|
||||||
let bg = Style::default().on_black();
|
let bg = Style::default().on_black();
|
||||||
buf.set_string(
|
buf.set_string(
|
||||||
|
|
@ -98,7 +101,7 @@ fn cursor (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keys (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
pub fn keys (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||||
let bw = Style::default().dim();
|
let bw = Style::default().dim();
|
||||||
let bg = Style::default().on_black();
|
let bg = Style::default().on_black();
|
||||||
let Rect { x, y, .. } = area;
|
let Rect { x, y, .. } = area;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn draw_vertical (
|
pub fn draw (
|
||||||
s: &Sequencer,
|
s: &Sequencer,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
mut area: Rect,
|
mut area: Rect,
|
||||||
|
|
@ -17,7 +17,7 @@ pub fn draw_vertical (
|
||||||
Ok(Rect { x, y, width: area.width, height: height + 3 })
|
Ok(Rect { x, y, width: area.width, height: height + 3 })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
pub fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
||||||
let ppq = s.timebase.ppq() as u32;
|
let ppq = s.timebase.ppq() as u32;
|
||||||
let bw = Style::default().dim().on_black();
|
let bw = Style::default().dim().on_black();
|
||||||
let bg = Style::default().on_black();
|
let bg = Style::default().on_black();
|
||||||
|
|
@ -64,7 +64,7 @@ fn steps (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn footer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, height: u16) {
|
pub fn footer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, height: u16) {
|
||||||
buf.set_string(x + 2, y + height + 1, format!(
|
buf.set_string(x + 2, y + height + 1, format!(
|
||||||
"Q 1/{} | N {} ({}-{}) | T {} ({}-{})",
|
"Q 1/{} | N {} ({}-{}) | T {} ({}-{})",
|
||||||
4 * s.resolution,
|
4 * s.resolution,
|
||||||
|
|
@ -77,7 +77,7 @@ fn footer (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16, height: u16) {
|
||||||
), Style::default().dim());
|
), Style::default().dim());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn playhead (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
pub fn playhead (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
||||||
buf.set_string(
|
buf.set_string(
|
||||||
x + 5 + s.note_cursor,
|
x + 5 + s.note_cursor,
|
||||||
y + s.time_cursor / 2,
|
y + s.time_cursor / 2,
|
||||||
|
|
@ -86,7 +86,7 @@ fn playhead (s: &Sequencer, buf: &mut Buffer, x: u16, y: u16) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keys (s: &Sequencer, buf: &mut Buffer, mut area: Rect, beat: usize) {
|
pub fn keys (s: &Sequencer, buf: &mut Buffer, mut area: Rect, beat: usize) {
|
||||||
let ppq = s.timebase.ppq() as u32;
|
let ppq = s.timebase.ppq() as u32;
|
||||||
let Rect { x, y, .. } = area;
|
let Rect { x, y, .. } = area;
|
||||||
let (note0, note1) = s.note_axis;
|
let (note0, note1) = s.note_axis;
|
||||||
|
|
|
||||||
14
src/main.rs
14
src/main.rs
|
|
@ -21,10 +21,12 @@ fn main () -> Result<(), Box<dyn Error>> {
|
||||||
crate::config::create_dirs(&xdg)?;
|
crate::config::create_dirs(&xdg)?;
|
||||||
let transport = crate::device::Transport::new("Transport")?;
|
let transport = crate::device::Transport::new("Transport")?;
|
||||||
let timebase = transport.state.lock().unwrap().timebase();
|
let timebase = transport.state.lock().unwrap().timebase();
|
||||||
crate::device::run(Chain::new("Chain#0000", vec![
|
//crate::device::run(Sequencer::new("Sequencer#0", &timebase)?)
|
||||||
Box::new(Sequencer::new("Phrase#000", &timebase)?),
|
crate::device::run(Launcher::new("Launcher#0", &timebase)?)
|
||||||
Box::new(Sequencer::new("Phrase#001", &timebase)?),
|
//crate::device::run(Chain::new("Chain#0000", vec![
|
||||||
//Box::new(Sequencer::new("Phrase#002", &timebase)?),
|
//Box::new(Sequencer::new("Phrase#000", &timebase)?),
|
||||||
//Box::new(Plugin::new("Plugin#000")?),
|
//Box::new(Sequencer::new("Phrase#001", &timebase)?),
|
||||||
])?)
|
////Box::new(Sequencer::new("Phrase#002", &timebase)?),
|
||||||
|
////Box::new(Plugin::new("Plugin#000")?),
|
||||||
|
//])?)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue