tek/src/devices/sequencer.rs

448 lines
14 KiB
Rust

//! Phrase editor.
use crate::{core::*, model::*, view::*};
/// Key bindings for phrase editor.
pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
[Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| {
match app.sequencer.entered {
true => { app.sequencer.note_axis.point_dec(); },
false => { app.sequencer.note_axis.start_dec(); },
}
Ok(true)
}],
[Down, NONE, "seq_cursor_down", "move cursor down", |app: &mut App| {
match app.sequencer.entered {
true => { app.sequencer.note_axis.point_inc(); },
false => { app.sequencer.note_axis.start_inc(); },
}
Ok(true)
}],
[Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| {
match app.sequencer.entered {
true => { app.sequencer.time_axis.point_dec(); },
false => { app.sequencer.time_axis.start_dec(); },
}
Ok(true)
}],
[Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| {
match app.sequencer.entered {
true => { app.sequencer.time_axis.point_inc(); },
false => { app.sequencer.time_axis.start_inc(); },
}
Ok(true)
}],
[Char('`'), NONE, "seq_mode_switch", "switch the display mode", |app: &mut App| {
app.sequencer.mode = !app.sequencer.mode;
Ok(true)
}],
/*
[Char('a'), NONE, "note_add", "Add note", note_add],
[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],
*/
});
pub type PhraseData = Vec<Vec<MidiMessage>>;
#[derive(Debug)]
/// A MIDI sequence.
pub struct Phrase {
pub name: String,
pub length: usize,
pub notes: PhraseData,
pub looped: Option<(usize, usize)>,
/// Immediate note-offs in view
pub percussive: bool
}
impl Default for Phrase {
fn default () -> Self {
Self::new("", 0, None)
}
}
impl Phrase {
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
Self {
name: name.to_string(),
length,
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
looped: Some((0, length)),
percussive: true,
}
}
pub fn record_event (&mut self, pulse: usize, message: MidiMessage) {
if pulse >= self.length {
panic!("extend phrase first")
}
self.notes[pulse].push(message);
}
/// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
//panic!("{:?} {start} {end}", &self);
for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() {
for event in events.iter() {
match event {
MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
/// Write a chunk of MIDI events to an output port.
pub fn process_out (
&self,
output: &mut MIDIChunk,
notes_on: &mut [bool;128],
timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64),
) {
let mut buf = Vec::with_capacity(8);
for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames(
frame0, frame0 + frames
) {
let tick = tick % self.length;
for message in self.notes[tick].iter() {
buf.clear();
let channel = 0.into();
let message = *message;
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[time as usize].push(buf.clone());
match message {
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
}
}
/// Phrase editor.
pub struct Sequencer {
pub mode: bool,
pub focused: bool,
pub entered: bool,
pub phrase: Option<Arc<RwLock<Phrase>>>,
pub buffer: Buffer,
pub keys: Buffer,
/// Highlight input keys
pub keys_in: [bool; 128],
/// Highlight output keys
pub keys_out: [bool; 128],
pub now: usize,
pub ppq: usize,
pub note_axis: FixedAxis<u16>,
pub time_axis: ScaledAxis<u16>,
}
render!(Sequencer |self, buf, area| {
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
self.horizontal_draw(buf, area)?;
if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
Ok(area)
});
impl Sequencer {
pub fn new () -> Self {
Self {
buffer: Buffer::empty(Rect::default()),
keys: keys_vert(),
entered: false,
focused: false,
mode: false,
keys_in: [false;128],
keys_out: [false;128],
phrase: None,
now: 0,
ppq: 96,
note_axis: FixedAxis {
start: 12,
point: Some(36)
},
time_axis: ScaledAxis {
start: 0,
scale: 24,
point: Some(0)
},
}
}
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
/// FIXME: Support phrases longer that 65536 ticks
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) -> Usually<()> {
self.phrase = phrase.map(Clone::clone);
if let Some(ref phrase) = self.phrase {
let width = u16::MAX.min(phrase.read().unwrap().length as u16);
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height: 64 });
let phrase = phrase.read().unwrap();
fill_seq_bg(&mut buffer, phrase.length, self.ppq)?;
fill_seq_fg(&mut buffer, &phrase)?;
self.buffer = buffer;
} else {
self.buffer = Buffer::empty(Rect::default())
}
Ok(())
}
fn style_focus (&self) -> Option<Style> {
Some(if self.focused {
Style::default().green().not_dim()
} else {
Style::default().green().dim()
})
}
fn style_timer_step (now: usize, step: usize, next_step: usize) -> Style {
if step <= now && now < next_step {
Style::default().yellow().bold().not_dim()
} else {
Style::default()
}
}
fn index_to_color (&self, index: u16, default: Color) -> Color {
let index = index as usize;
if self.keys_in[index] && self.keys_out[index] {
Color::Yellow
} else if self.keys_in[index] {
Color::Red
} else if self.keys_out[index] {
Color::Green
} else {
default
}
}
const H_KEYS_OFFSET: u16 = 5;
fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
self.horizontal_keys(buf, area)?;
if let Some(ref phrase) = self.phrase {
self.horizontal_timer(buf, area, phrase)?;
}
self.horizontal_notes(buf, area)?;
self.horizontal_cursor(buf, area)?;
self.horizontal_quant(buf, area)?;
Ok(())
}
fn horizontal_notes (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if area.height < 2 {
return Ok(area)
}
let area = Rect {
x: area.x + Self::H_KEYS_OFFSET,
y: area.y + 1,
width: area.width - Self::H_KEYS_OFFSET,
height: area.height - 2
};
buffer_update(buf, area, &|cell, x, y|{
let src_x = (x + self.time_axis.start) * self.time_axis.scale;
let src_y = y + self.note_axis.start;
if src_x < self.buffer.area.width && src_y < self.buffer.area.height - 1 {
let src = self.buffer.get(src_x, self.buffer.area.height - src_y);
cell.set_symbol(src.symbol());
cell.set_fg(src.fg);
}
});
Ok(area)
}
fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if area.height < 2 {
return Ok(area)
}
let area = Rect {
x: area.x,
y: area.y + 1,
width: 5,
height: area.height - 2
};
buffer_update(buf, area, &|cell, x, y|{
let y = y + self.note_axis.start;
if x < self.keys.area.width && y < self.keys.area.height {
*cell = self.keys.get(x, y).clone()
}
});
Ok(area)
}
fn horizontal_quant (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let quant = ppq_to_name(self.time_axis.scale);
let quant_x = area.x + area.width - 1 - quant.len() as u16;
let quant_y = area.y + area.height - 2;
quant.blit(buf, quant_x, quant_y, self.style_focus())
}
fn horizontal_cursor (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
let x = area.x + Self::H_KEYS_OFFSET + time as u16;
let y = area.y + 1 + note as u16 / 2;
let c = if note % 2 == 0 { "" } else { "" };
c.blit(buf, x, y, self.style_focus())
} else {
Ok(Rect::default())
}
}
fn horizontal_timer (
&self, buf: &mut Buffer, area: Rect, phrase: &RwLock<Phrase>
) -> Usually<Rect> {
let phrase = phrase.read().unwrap();
let (time0, time_z, now) = (self.time_axis.start, self.time_axis.scale, self.now % phrase.length);
let Rect { x, width, .. } = area;
for x in x+Self::H_KEYS_OFFSET..x+width {
let step = (time0 + (x-Self::H_KEYS_OFFSET)) * time_z;
let next_step = (time0 + (x-Self::H_KEYS_OFFSET) + 1) * time_z;
let style = Self::style_timer_step(now, step as usize, next_step as usize);
"-".blit(buf, x, area.y, Some(style))?;
}
return Ok(Rect { x: area.x, y: area.y, width: area.width, height: 1 })
}
}
fn keys_vert () -> Buffer {
let area = Rect { x: 0, y: 0, width: 5, height: 64 };
let mut buffer = Buffer::empty(area);
buffer_update(&mut buffer, area, &|cell, x, y| {
let y = 63 - y;
match x {
0 => {
cell.set_char('▀');
let (fg, bg) = key_colors(6 - y % 6);
cell.set_fg(fg);
cell.set_bg(bg);
},
1 => {
cell.set_char('▀');
cell.set_fg(Color::White);
cell.set_bg(Color::White);
},
2 => if y % 6 == 0 {
cell.set_char('C');
},
3 => if y % 6 == 0 {
cell.set_symbol(nth_octave(y / 6));
},
_ => {}
}
});
buffer
}
fn nth_octave (index: u16) -> &'static str {
match index {
0 => "-1",
1 => "0",
2 => "1",
3 => "2",
4 => "3",
5 => "4",
6 => "5",
7 => "6",
8 => "7",
9 => "8",
10 => "9",
_ => unreachable!()
}
}
fn key_colors (index: u16) -> (Color, Color) {
match index % 6 {
0 => (Color::White, Color::Black),
1 => (Color::White, Color::Black),
2 => (Color::White, Color::White),
3 => (Color::Black, Color::White),
4 => (Color::Black, Color::White),
5 => (Color::Black, Color::White),
_ => unreachable!()
}
}
fn fill_seq_bg (buf: &mut Buffer, length: usize, ppq: usize) -> Usually<()> {
for x in 0 .. buf.area.width - buf.area.x {
if x as usize >= length {
break
}
let style = Style::default();
let cell = buf.get_mut(x, buf.area.y);
cell.set_char('-');
cell.set_style(style);
for y in 0 .. buf.area.height - buf.area.y {
let cell = buf.get_mut(x, y);
cell.set_char(char_seq_bg(ppq, x));
cell.set_fg(Color::Gray);
cell.modifier = Modifier::DIM;
}
}
Ok(())
}
fn char_seq_bg (ppq: usize, x: u16) -> char {
if ppq == 0 {
'·'
} else if x % (4 * ppq as u16) == 0 {
'│'
} else if x % ppq as u16 == 0 {
'╎'
} else {
'·'
}
}
fn fill_seq_fg (buf: &mut Buffer, phrase: &Phrase) -> Usually<()> {
let mut notes_on = [false;128];
for x in 0 .. buf.area.width - buf.area.x {
if x as usize >= phrase.length {
break
}
if let Some(notes) = phrase.notes.get(x as usize) {
for note in notes {
if phrase.percussive {
match note {
MidiMessage::NoteOn { key, .. } =>
notes_on[key.as_int() as usize] = true,
_ => {}
}
} else {
match note {
MidiMessage::NoteOn { key, .. } =>
notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } =>
notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
for y in 0 .. (buf.area.height - buf.area.y) / 2 {
if y >= 64 {
break
}
if let Some(block) = half_block(
notes_on[y as usize * 2],
notes_on[y as usize * 2 + 1],
) {
let cell = buf.get_mut(x, y);
cell.set_char(block);
cell.set_fg(Color::White);
}
}
if phrase.percussive {
notes_on.fill(false);
}
}
}
Ok(())
}