diff --git a/src/event.rs b/src/event.rs index ea13af41..16f47f07 100644 --- a/src/event.rs +++ b/src/event.rs @@ -5,6 +5,8 @@ pub struct EventMap<'a, const N: usize, E, T, U>( pub Option<&'a dyn Fn(T) -> U>, ); +pub type KeyMapping = [(E, &'static dyn Fn(&T)->U);N]; + impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> { pub fn handle (&self, context: T, event: &E) -> Option { for (binding, handler) in self.0.iter() { diff --git a/src/groovebox.rs b/src/groovebox.rs index 3c1b8b06..d46c3457 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -143,7 +143,7 @@ render!(Tui: (self: Groovebox) => { ))), Bsp::n( MidiEditStatus(&self.editor), - Fill::xy(Align::c("kyp")) + Fill::xy(&self.editor), ), ), )) diff --git a/src/midi/midi_editor.rs b/src/midi/midi_editor.rs index aa81c71b..e1329847 100644 --- a/src/midi/midi_editor.rs +++ b/src/midi/midi_editor.rs @@ -5,7 +5,6 @@ use MidiEditCommand::*; 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),*>)? { @@ -14,6 +13,144 @@ pub trait HasEditor { } } +/// Contains state for viewing and editing a phrase +pub struct MidiEditor { + pub mode: PianoHorizontal, + pub size: Measure +} + +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 Default for MidiEditor { + fn default () -> Self { + let mut mode = PianoHorizontal::new(None); + mode.redraw(); + Self { mode, size: Measure::new() } + } +} + +has_size!(|self: MidiEditor|&self.size); + +render!(Tui: (self: MidiEditor) => { + self.autoscroll(); + self.autozoom(); + Bsp::a(&self.size, &self.mode) +}); + +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 MidiViewMode 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(); + } + } +} + +pub trait MidiViewMode: 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 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() + } +} + #[derive(Clone, Debug)] pub enum MidiEditCommand { // TODO: 1-9 seek markers that by default start every 8th of the phrase @@ -31,8 +168,6 @@ pub enum MidiEditCommand { event_map_input_to_command!(Tui: MidiEditor: MidiEditCommand: MidiEditor::KEYS); -pub(crate) type KeyMapping = [(E, &'static dyn Fn(&T)->U);N]; - impl MidiEditor { const KEYS: KeyMapping<31, Event, Self, MidiEditCommand> = [ (kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)), @@ -93,144 +228,3 @@ impl Command for MidiEditCommand { 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() - } -} diff --git a/src/piano.rs b/src/piano.rs index 81c75119..6590f04a 100644 --- a/src/piano.rs +++ b/src/piano.rs @@ -26,21 +26,22 @@ pub struct PianoHorizontal { impl PianoHorizontal { pub fn new (phrase: Option<&Arc>>) -> Self { - let size = Measure::new(); - let mut range = MidiRangeModel::from((24, true)); + let size = Measure::new(); + let mut range = MidiRangeModel::from((24, true)); range.time_axis = size.x.clone(); range.note_axis = size.y.clone(); - let color = phrase.as_ref() - .map(|p|p.read().unwrap().color) - .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); - Self { + let mut piano = Self { + keys_width: 5, + size, + range, buffer: Default::default(), point: MidiPointModel::default(), phrase: phrase.cloned(), - size, - range, - color, - keys_width: 5 - } + color: phrase.as_ref() + .map(|p|p.read().unwrap().color) + .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))), + }; + piano.redraw(); + piano } } diff --git a/src/piano/piano_h.rs b/src/piano/piano_h.rs index f9711b06..bc806ba9 100644 --- a/src/piano/piano_h.rs +++ b/src/piano/piano_h.rs @@ -130,7 +130,7 @@ impl TimePoint for PianoHorizontal { fn time_point (&self) -> usize { self.point.time_point() } fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } } -impl PhraseViewMode for PianoHorizontal { +impl MidiViewMode for PianoHorizontal { fn phrase (&self) -> &Option>> { &self.phrase }