//! Phrase editor. use crate::{core::*, model::*, view::*}; /// Key bindings for phrase editor. pub const KEYMAP_SEQUENCER: &'static [KeyBinding] = 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>; #[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) -> 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, (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>>, 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, pub time_axis: ScaledAxis, } 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>>) -> 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