use crate::*; use KeyCode::{Char, Up, Down, Left, Right, Enter}; use PhraseCommand::*; pub trait HasEditor { fn editor (&self) -> &MidiEditor; } #[macro_export] macro_rules! has_editor { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? { fn editor (&$self) -> &MidiEditor { &$cb } } } } #[derive(Clone, Debug)] pub enum PhraseCommand { // TODO: 1-9 seek markers that by default start every 8th of the phrase AppendNote, PutNote, SetNoteCursor(usize), SetNoteLength(usize), SetNoteScroll(usize), SetTimeCursor(usize), SetTimeScroll(usize), SetTimeZoom(usize), SetTimeLock(bool), Show(Option>>), } event_map_input_to_command!(Tui: MidiEditor: PhraseCommand: MidiEditor::KEYS); pub(crate) type KeyMapping = [(E, &'static dyn Fn(&T)->U);N]; impl MidiEditor { const KEYS: KeyMapping<31, Event, Self, PhraseCommand> = [ (kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)), (kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))), (kexp!(Ctrl-Alt-Left), &|s: &Self|SetTimeScroll(s.time_point().saturating_sub(s.time_zoom().get()))), (kexp!(Ctrl-Alt-Right), &|s: &Self|SetTimeScroll((s.time_point() + s.time_zoom().get()) % s.phrase_length())), (kexp!(Ctrl-Up), &|s: &Self|SetNoteScroll(s.note_lo().get() + 1)), (kexp!(Ctrl-Down), &|s: &Self|SetNoteScroll(s.note_lo().get().saturating_sub(1))), (kexp!(Ctrl-Left), &|s: &Self|SetTimeScroll(s.time_start().get().saturating_sub(s.note_len()))), (kexp!(Ctrl-Right), &|s: &Self|SetTimeScroll(s.time_start().get() + s.note_len())), (kexp!(Alt-Up), &|s: &Self|SetNoteCursor(s.note_point() + 3)), (kexp!(Alt-Down), &|s: &Self|SetNoteCursor(s.note_point().saturating_sub(3))), (kexp!(Alt-Left), &|s: &Self|SetTimeCursor(s.time_point().saturating_sub(s.time_zoom().get()))), (kexp!(Alt-Right), &|s: &Self|SetTimeCursor((s.time_point() + s.time_zoom().get()) % s.phrase_length())), (kexp!(Up), &|s: &Self|SetNoteCursor(s.note_point() + 1)), (kexp!(Char('w')), &|s: &Self|SetNoteCursor(s.note_point() + 1)), (kexp!(Down), &|s: &Self|SetNoteCursor(s.note_point().saturating_sub(1))), (kexp!(Char('s')), &|s: &Self|SetNoteCursor(s.note_point().saturating_sub(1))), (kexp!(Left), &|s: &Self|SetTimeCursor(s.time_point().saturating_sub(s.note_len()))), (kexp!(Char('a')), &|s: &Self|SetTimeCursor(s.time_point().saturating_sub(s.note_len()))), (kexp!(Right), &|s: &Self|SetTimeCursor((s.time_point() + s.note_len()) % s.phrase_length())), (kexp!(Char('d')), &|s: &Self|SetTimeCursor((s.time_point() + s.note_len()) % s.phrase_length())), (kexp!(Char('z')), &|s: &Self|SetTimeLock(!s.time_lock().get())), (kexp!(Char('-')), &|s: &Self|SetTimeZoom(Note::next(s.time_zoom().get()))), (kexp!(Char('_')), &|s: &Self|SetTimeZoom(Note::next(s.time_zoom().get()))), (kexp!(Char('=')), &|s: &Self|SetTimeZoom(Note::prev(s.time_zoom().get()))), (kexp!(Char('+')), &|s: &Self|SetTimeZoom(Note::prev(s.time_zoom().get()))), (kexp!(Enter), &|s: &Self|PutNote), (kexp!(Ctrl-Enter), &|s: &Self|AppendNote), (kexp!(Char(',')), &|s: &Self|SetNoteLength(Note::prev(s.note_len()))), // TODO: no 3plet (kexp!(Char('.')), &|s: &Self|SetNoteLength(Note::next(s.note_len()))), (kexp!(Char('<')), &|s: &Self|SetNoteLength(Note::prev(s.note_len()))), // TODO: 3plet (kexp!(Char('>')), &|s: &Self|SetNoteLength(Note::next(s.note_len()))), //// TODO: key_pat!(Char('/')) => // toggle 3plet //// TODO: key_pat!(Char('?')) => // toggle dotted ]; fn phrase_length (&self) -> usize { self.phrase().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) } } impl Command for PhraseCommand { fn execute (self, state: &mut MidiEditor) -> Perhaps { use PhraseCommand::*; match self { Show(phrase) => { state.set_phrase(phrase.as_ref()); }, PutNote => { state.put_note(false); }, AppendNote => { state.put_note(true); }, SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); }, SetTimeLock(x) => { state.time_lock().set(x); }, SetTimeScroll(x) => { state.time_start().set(x); }, SetNoteScroll(x) => { state.note_lo().set(x.min(127)); }, SetNoteLength(x) => { state.set_note_len(x); }, SetTimeCursor(x) => { state.set_time_point(x); }, SetNoteCursor(note) => { state.set_note_point(note.min(127)); }, _ => todo!("{:?}", self) } Ok(None) } } /// Contains state for viewing and editing a phrase pub struct MidiEditor { /// Contents the phrase pub mode: Box, pub size: Measure } impl Default for MidiEditor { fn default () -> Self { let mut mode = Box::new(PianoHorizontal::new(None)); mode.redraw(); Self { mode, size: Measure::new() } } } has_size!(|self: MidiEditor|&self.size); render!(Tui: (self: MidiEditor) => { self.autoscroll(); self.autozoom(); &self.mode }); //render!(|self: MidiEditor|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks pub trait PhraseViewMode: HasSize + MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize); fn redraw (&mut self); fn phrase (&self) -> &Option>>; fn phrase_mut (&mut self) -> &mut Option>>; fn set_phrase (&mut self, phrase: Option<&Arc>>) { *self.phrase_mut() = phrase.cloned(); self.redraw(); } } impl Content for Box { fn content (&self) -> impl Content { Some(&(*self)) } } impl MidiView for MidiEditor {} impl TimeRange for MidiEditor { fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() } fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() } fn time_start (&self) -> &AtomicUsize { self.mode.time_start() } fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } } impl NoteRange for MidiEditor { fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() } fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() } } impl NotePoint for MidiEditor { fn note_len (&self) -> usize { self.mode.note_len() } fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) } fn note_point (&self) -> usize { self.mode.note_point() } fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) } } impl TimePoint for MidiEditor { fn time_point (&self) -> usize { self.mode.time_point() } fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } } impl PhraseViewMode for MidiEditor { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) { self.mode.buffer_size(phrase) } fn redraw (&mut self) { self.mode.redraw() } fn phrase (&self) -> &Option>> { self.mode.phrase() } fn phrase_mut (&mut self) -> &mut Option>> { self.mode.phrase_mut() } fn set_phrase (&mut self, phrase: Option<&Arc>>) { self.mode.set_phrase(phrase) } } impl MidiEditor { /// Put note at current position pub fn put_note (&mut self, advance: bool) { let mut redraw = false; if let Some(phrase) = self.phrase() { let mut phrase = phrase.write().unwrap(); let note_start = self.time_point(); let note_point = self.note_point(); let note_len = self.note_len(); let note_end = note_start + (note_len.saturating_sub(1)); let key: u7 = u7::from(note_point as u8); let vel: u7 = 100.into(); let length = phrase.length; let note_end = note_end % length; let note_on = MidiMessage::NoteOn { key, vel }; if !phrase.notes[note_start].iter().any(|msg|*msg == note_on) { phrase.notes[note_start].push(note_on); } let note_off = MidiMessage::NoteOff { key, vel }; if !phrase.notes[note_end].iter().any(|msg|*msg == note_off) { phrase.notes[note_end].push(note_off); } if advance { self.set_time_point(note_end); } redraw = true; } if redraw { self.mode.redraw(); } } } from!(|phrase: &Arc>|MidiEditor = { let mut model = Self::from(Some(phrase.clone())); model.redraw(); model }); from!(|phrase: Option>>|MidiEditor = { let mut model = Self::default(); *model.phrase_mut() = phrase; model.redraw(); model }); impl std::fmt::Debug for MidiEditor { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("MidiEditor") .field("mode", &self.mode) .finish() } }