diff --git a/crates/tek/src/api.rs b/crates/tek/src/api.rs index 013b4388..e7219c7d 100644 --- a/crates/tek/src/api.rs +++ b/crates/tek/src/api.rs @@ -2,6 +2,7 @@ mod phrase; pub(crate) use phrase::*; mod jack; pub(crate) use self::jack::*; mod clip; pub(crate) use clip::*; mod clock; pub(crate) use clock::*; +mod note; pub(crate) use note::*; mod player; pub(crate) use player::*; mod scene; pub(crate) use scene::*; mod track; pub(crate) use track::*; diff --git a/crates/tek/src/api/note.rs b/crates/tek/src/api/note.rs new file mode 100644 index 00000000..4cf55cbd --- /dev/null +++ b/crates/tek/src/api/note.rs @@ -0,0 +1,94 @@ +use crate::*; +use Ordering::Relaxed; + +#[derive(Debug, Clone)] +pub struct MIDIRangeModel { + /// Length of visible time axis + pub time_axis: Arc, + /// Earliest time displayed + pub time_start: Arc, + /// Time step + pub time_zoom: Arc, + /// Auto rezoom to fit in time axis + pub time_lock: Arc, + /// Length of visible note axis + pub note_axis: Arc, + // Lowest note displayed + pub note_lo: Arc, +} + +impl From<(usize, bool)> for MIDIRangeModel { + fn from ((time_zoom, time_lock): (usize, bool)) -> Self { + Self { + note_axis: Arc::new(0.into()), + note_lo: Arc::new(0.into()), + time_axis: Arc::new(0.into()), + time_start: Arc::new(0.into()), + time_zoom: Arc::new(time_zoom.into()), + time_lock: Arc::new(time_lock.into()), + } + } +} + +#[derive(Debug, Clone)] +pub struct MIDIPointModel { + /// Time coordinate of cursor + pub time_point: Arc, + /// Note coordinate of cursor + pub note_point: Arc, + /// Length of note that will be inserted, in pulses + pub note_len: Arc, +} + +impl Default for MIDIPointModel { + fn default () -> Self { + Self { + time_point: Arc::new(0.into()), + note_point: Arc::new(0.into()), + note_len: Arc::new(24.into()), + } + } +} + +pub trait MIDIRange { + fn time_zoom (&self) -> usize; + fn set_time_zoom (&self, x: usize); + fn time_lock (&self) -> bool; + fn set_time_lock (&self, x: bool); + fn time_start (&self) -> usize; + fn set_time_start (&self, x: usize); + fn note_lo (&self) -> usize; + fn set_note_lo (&self, x: usize); + fn note_axis (&self) -> usize; + fn note_hi (&self) -> usize; +} + +pub trait MIDIPoint { + fn note_len (&self) -> usize; + fn set_note_len (&self, x: usize); + fn note_point (&self) -> usize; + fn set_note_point (&self, x: usize); + fn time_point (&self) -> usize; + fn set_time_point (&self, x: usize); +} + +impl MIDIRange for MIDIRangeModel { + fn time_zoom (&self) -> usize { self.time_zoom.load(Relaxed) } + fn set_time_zoom (&self, x: usize) { self.time_zoom.store(x, Relaxed); } + fn time_lock (&self) -> bool { self.time_lock.load(Relaxed) } + fn set_time_lock (&self, x: bool) { self.time_lock.store(x, Relaxed); } + fn time_start (&self) -> usize { self.time_start.load(Relaxed) } + fn set_time_start (&self, x: usize) { self.time_start.store(x, Relaxed); } + fn set_note_lo (&self, x: usize) { self.note_lo.store(x, Relaxed); } + fn note_lo (&self) -> usize { self.note_lo.load(Relaxed) } + fn note_axis (&self) -> usize { self.note_lo.load(Relaxed) } + fn note_hi (&self) -> usize { self.note_lo() + self.note_axis() } +} +impl MIDIPoint for MIDIPointModel { + fn note_len (&self) -> usize { self.note_len.load(Relaxed)} + fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } + fn note_point (&self) -> usize { self.note_point.load(Relaxed) } + fn set_note_point (&self, x: usize) { self.note_point.store(x, Relaxed) } + fn time_point (&self) -> usize { self.time_point.load(Relaxed) } + fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } +} diff --git a/crates/tek/src/core.rs b/crates/tek/src/core.rs index c22c504e..029f46ac 100644 --- a/crates/tek/src/core.rs +++ b/crates/tek/src/core.rs @@ -6,6 +6,7 @@ mod engine; pub(crate) use engine::*; mod focus; pub(crate) use focus::*; mod input; pub(crate) use input::*; mod output; pub(crate) use output::*; +mod perf; pub(crate) use perf::*; mod pitch; pub(crate) use pitch::*; mod space; pub(crate) use space::*; mod time; pub(crate) use time::*; diff --git a/crates/tek/src/core/perf.rs b/crates/tek/src/core/perf.rs new file mode 100644 index 00000000..05403b11 --- /dev/null +++ b/crates/tek/src/core/perf.rs @@ -0,0 +1,54 @@ +use crate::*; + +/// Performance counter +pub struct PerfModel { + pub enabled: bool, + clock: quanta::Clock, + // In nanoseconds + used: AtomicF64, + // In microseconds + period: AtomicF64, +} + +impl Default for PerfModel { + fn default () -> Self { + Self { + enabled: true, + clock: quanta::Clock::new(), + used: Default::default(), + period: Default::default(), + } + } +} + +impl PerfModel { + pub fn get_t0 (&self) -> Option { + if self.enabled { + Some(self.clock.raw()) + } else { + None + } + } + pub fn update (&self, t0: Option, scope: &jack::ProcessScope) { + if let Some(t0) = t0 { + let t1 = self.clock.raw(); + self.used.store( + self.clock.delta_as_nanos(t0, t1) as f64, + Ordering::Relaxed, + ); + self.period.store( + scope.cycle_times().unwrap().period_usecs as f64, + Ordering::Relaxed, + ); + } + } + pub fn percentage (&self) -> Option { + let period = self.period.load(Ordering::Relaxed) * 1000.0; + if period > 0.0 { + let used = self.used.load(Ordering::Relaxed); + Some(100.0 * used / period) + } else { + None + } + } +} diff --git a/crates/tek/src/core/time.rs b/crates/tek/src/core/time.rs index 536e403b..6677431f 100644 --- a/crates/tek/src/core/time.rs +++ b/crates/tek/src/core/time.rs @@ -386,59 +386,6 @@ pub fn pulses_to_name (pulses: usize) -> &'static str { "" } -/// Performance counter -pub struct PerfModel { - pub enabled: bool, - clock: quanta::Clock, - // In nanoseconds - used: AtomicF64, - // In microseconds - period: AtomicF64, -} - -impl Default for PerfModel { - fn default () -> Self { - Self { - enabled: true, - clock: quanta::Clock::new(), - used: Default::default(), - period: Default::default(), - } - } -} - -impl PerfModel { - pub fn get_t0 (&self) -> Option { - if self.enabled { - Some(self.clock.raw()) - } else { - None - } - } - pub fn update (&self, t0: Option, scope: &jack::ProcessScope) { - if let Some(t0) = t0 { - let t1 = self.clock.raw(); - self.used.store( - self.clock.delta_as_nanos(t0, t1) as f64, - Ordering::Relaxed, - ); - self.period.store( - scope.cycle_times().unwrap().period_usecs as f64, - Ordering::Relaxed, - ); - } - } - pub fn percentage (&self) -> Option { - let period = self.period.load(Ordering::Relaxed) * 1000.0; - if period > 0.0 { - let used = self.used.load(Ordering::Relaxed); - Some(100.0 * used / period) - } else { - None - } - } -} - //#[cfg(test)] //mod test { //use super::*; diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 57263333..44c0e703 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -28,14 +28,13 @@ pub enum PhraseCommand { impl InputToCommand for PhraseCommand { fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option { let length = ||state.phrase().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); - let range = state.range(); - let note_lo = ||range.note_lo.load(Relaxed); - let time_start = ||range.time_start.load(Relaxed); - let time_zoom = ||range.time_zoom(); - let point = state.point(); - let note_point = ||point.note_point(); - let time_point = ||point.time_point(); - let note_len = ||point.note_len(); + let note_lo = ||state.note_lo(); + let time_start = ||state.time_start(); + let time_zoom = ||state.time_zoom(); + let time_lock = ||state.time_lock(); + let note_point = ||state.note_point(); + let time_point = ||state.time_point(); + let note_len = ||state.note_len(); Some(match from.event() { key_pat!(Ctrl-Alt-Up) => SetNoteScroll(note_point() + 3), key_pat!(Ctrl-Alt-Down) => SetNoteScroll(note_point().saturating_sub(3)), @@ -54,7 +53,7 @@ impl InputToCommand for PhraseCommand { key_pat!(Left) => SetTimeCursor(time_point().saturating_sub(note_len())), key_pat!(Right) => SetTimeCursor((time_point() + note_len()) % length()), key_pat!(Char('`')) => ToggleDirection, - key_pat!(Char('z')) => SetTimeLock(!state.range().time_lock()), + key_pat!(Char('z')) => SetTimeLock(!time_lock()), key_pat!(Char('-')) => SetTimeZoom(next_note_length(time_zoom())), key_pat!(Char('_')) => SetTimeZoom(next_note_length(time_zoom())), key_pat!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom())), @@ -75,27 +74,24 @@ impl InputToCommand for PhraseCommand { impl Command for PhraseCommand { fn execute (self, state: &mut PhraseEditorModel) -> Perhaps { use PhraseCommand::*; - let range = state.range(); - let point = state.point(); match self { Show(phrase) => { state.set_phrase(phrase.as_ref()); }, PutNote => { state.put_note(false); }, AppendNote => { state.put_note(true); }, - SetTimeZoom(x) => { range.set_time_zoom(x); }, - SetTimeLock(x) => { range.set_time_lock(x); }, - SetTimeScroll(x) => { range.set_time_start(x); }, - SetNoteScroll(x) => { range.set_note_lo(x); }, - SetNoteLength(x) => { point.set_note_len(x); }, - SetTimeCursor(x) => { point.set_time_point(x); }, + SetTimeZoom(x) => { state.set_time_zoom(x); }, + SetTimeLock(x) => { state.set_time_lock(x); }, + SetTimeScroll(x) => { state.set_time_start(x); }, + SetNoteScroll(x) => { state.set_note_lo(x); }, + SetNoteLength(x) => { state.set_note_len(x); }, + SetTimeCursor(x) => { state.set_time_point(x); }, SetNoteCursor(note) => { let note = 127.min(note); - let start = range.note_lo.load(Relaxed); - point.note_point.store(note, Relaxed); + let start = state.note_lo(); + state.set_note_point(note); if note < start { - range.note_lo.store(note, Relaxed); + state.set_note_lo(note) } }, - _ => todo!("{:?}", self) } Ok(None) @@ -116,9 +112,7 @@ impl Default for PhraseEditorModel { render!(|self: PhraseEditorModel|self.mode); -pub trait PhraseViewMode: Render + Debug + Send + Sync { - fn range (&self) -> &PhraseEditorRange; - fn point (&self) -> &PhraseEditorPoint; +pub trait PhraseViewMode: Render + MIDIRange + MIDIPoint + Debug + Send + Sync { fn buffer_size (&self, phrase: &Phrase) -> (usize, usize); fn redraw (&mut self); fn phrase (&self) -> &Option>>; @@ -129,13 +123,27 @@ pub trait PhraseViewMode: Render + Debug + Send + Sync { } } +impl MIDIRange for PhraseEditorModel { + fn time_zoom (&self) -> usize { self.mode.time_zoom() } + fn set_time_zoom (&self, x: usize) { self.mode.set_time_zoom(x); } + fn time_lock (&self) -> bool { self.mode.time_lock() } + fn set_time_lock (&self, x: bool) { self.mode.set_time_lock(x); } + fn time_start (&self) -> usize { self.mode.time_start() } + fn set_time_start (&self, x: usize) { self.mode.set_time_start(x); } + fn set_note_lo (&self, x: usize) { self.mode.set_note_lo(x); } + fn note_lo (&self) -> usize { self.mode.note_lo() } + fn note_axis (&self) -> usize { self.mode.note_lo() } + fn note_hi (&self) -> usize { self.note_lo() + self.note_axis() } +} +impl MIDIPoint for PhraseEditorModel { + 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) } + 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 PhraseEditorModel { - fn range (&self) -> &PhraseEditorRange { - self.mode.range() - } - fn point (&self) -> &PhraseEditorPoint { - self.mode.point() - } fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) { self.mode.buffer_size(phrase) } @@ -153,113 +161,15 @@ impl PhraseViewMode for PhraseEditorModel { } } -#[derive(Debug, Clone)] -pub struct PhraseEditorRange { - /// Length of visible time axis - pub time_axis: Arc, - /// Earliest time displayed - pub time_start: Arc, - /// Time step - pub time_zoom: Arc, - /// Auto rezoom to fit in time axis - pub time_lock: Arc, - /// Length of visible note axis - pub note_axis: Arc, - // Lowest note displayed - pub note_lo: Arc, -} - -impl Default for PhraseEditorRange { - fn default () -> Self { - Self { - time_axis: Arc::new(0.into()), - time_start: Arc::new(0.into()), - time_zoom: Arc::new(24.into()), - time_lock: Arc::new(true.into()), - note_axis: Arc::new(0.into()), - note_lo: Arc::new(0.into()), - } - } -} -impl PhraseEditorRange { - pub fn time_zoom (&self) -> usize { - self.time_zoom.load(Relaxed) - } - pub fn set_time_zoom (&self, x: usize) { - self.time_zoom.store(x, Relaxed); - } - pub fn time_lock (&self) -> bool { - self.time_lock.load(Relaxed) - } - pub fn set_time_lock (&self, x: bool) { - self.time_lock.store(x, Relaxed); - } - pub fn time_start (&self) -> usize { - self.time_start.load(Relaxed) - } - pub fn set_time_start (&self, x: usize) { - self.time_start.store(x, Relaxed); - } - pub fn set_note_lo (&self, x: usize) { - self.note_lo.store(x, Relaxed); - } - pub fn note_lo (&self) -> usize { - self.note_lo.load(Relaxed) - } - pub fn note_axis (&self) -> usize { - self.note_lo.load(Relaxed) - } - pub fn note_hi (&self) -> usize { - self.note_lo() + self.note_axis() - } -} - -#[derive(Debug, Clone)] -pub struct PhraseEditorPoint { - /// Time coordinate of cursor - pub time_point: Arc, - /// Note coordinate of cursor - pub note_point: Arc, - /// Length of note that will be inserted, in pulses - pub note_len: Arc, -} - -impl Default for PhraseEditorPoint { - fn default () -> Self { - Self { - time_point: Arc::new(0.into()), - note_point: Arc::new(0.into()), - note_len: Arc::new(24.into()), - } - } -} -impl PhraseEditorPoint { - pub fn note_len (&self) -> usize { - self.note_len.load(Relaxed) - } - pub fn set_note_len (&self, x: usize) { - self.note_len.store(x, Relaxed) - } - pub fn note_point (&self) -> usize { - self.note_point.load(Relaxed) - } - pub fn time_point (&self) -> usize { - self.time_point.load(Relaxed) - } - pub fn set_time_point (&self, x: usize) { - self.time_point.store(x, Relaxed) - } -} - impl PhraseEditorModel { /// 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.point().time_point(); - let note_point = self.point().note_point(); - let note_len = self.point().note_len(); + 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; let key: u7 = u7::from(note_point as u8); let vel: u7 = 100.into(); @@ -274,7 +184,7 @@ impl PhraseEditorModel { phrase.notes[note_end].push(note_off); } if advance { - self.point().set_time_point(note_end); + self.set_time_point(note_end); } redraw = true; } @@ -282,6 +192,25 @@ impl PhraseEditorModel { self.mode.redraw(); } } + /// Make sure cursor is within range + fn autoscroll ( + range: &impl MIDIRange, + point: &impl MIDIPoint, + height: usize + ) -> (usize, (usize, usize)) { + let note_point = point.note_point(); + let mut note_lo = range.note_lo(); + let mut note_hi = 127.min((note_lo + height).saturating_sub(2)); + if note_point > note_hi { + note_lo += note_point - note_hi; + note_hi = note_point; + range.set_note_lo(note_lo); + } + (note_point, (note_lo, note_hi)) + } + /// Make sure best usage of screen space is achieved by default + fn autozoom (&self) { + } } impl From<&Arc>> for PhraseEditorModel { @@ -301,46 +230,7 @@ impl From>>> for PhraseEditorModel { impl std::fmt::Debug for PhraseEditorModel { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("PhraseEditorModel") - .field("range", &self.range()) - .field("point", &self.point()) + .field("point", &self) .finish() } } - -fn autoscroll_notes ( - range: &PhraseEditorRange, point: &PhraseEditorPoint, height: usize -) -> (usize, (usize, usize)) { - let note_point = point.note_point.load(Relaxed); - let mut note_lo = range.note_lo.load(Relaxed); - let mut note_hi = 127.min((note_lo + height).saturating_sub(2)); - if note_point > note_hi { - note_lo += note_point - note_hi; - note_hi = note_point; - range.note_lo.store(note_lo, Relaxed); - } - (note_point, (note_lo, note_hi)) -} - -//impl<'a, T: HasEditor> From<&'a T> for PhraseView<'a> { - //fn from (state: &'a T) -> Self { - //let editor = state.editor(); - //let (note_point, note_range) = autoscroll_notes( - //&editor.range, - //&editor.point, - //editor.size.h() - //); - //Self { - //note_point, - //note_range, - //time_start: editor.range.time_start.load(Relaxed), - //time_point: editor.point.time_point.load(Relaxed), - //note_len: editor.point.note_len.load(Relaxed), - //phrase: editor.phrase.clone(), - //mode: &editor.mode, - //size: &editor.size, - //now: &editor.now, - //focused: state.editor_focused(), - //entered: state.editor_entered(), - //} - //} -//} diff --git a/crates/tek/src/tui/piano_horizontal.rs b/crates/tek/src/tui/piano_horizontal.rs index 572f1f10..ca559a2e 100644 --- a/crates/tek/src/tui/piano_horizontal.rs +++ b/crates/tek/src/tui/piano_horizontal.rs @@ -9,9 +9,9 @@ pub struct PianoHorizontal { /// Width and height of notes area at last render size: Measure, /// The display window - range: PhraseEditorRange, + range: MIDIRangeModel, /// The note cursor - point: PhraseEditorPoint, + point: MIDIPointModel, /// The highlight color palette color: ItemPalette, } @@ -19,7 +19,7 @@ pub struct PianoHorizontal { impl PianoHorizontal { pub fn new (phrase: Option<&Arc>>) -> Self { let size = Measure::new(); - let mut range = PhraseEditorRange::default(); + let mut range = MIDIRangeModel::from((24, true)); range.time_axis = size.x.clone(); range.note_axis = size.y.clone(); let phrase = phrase.map(|p|p.clone()); @@ -28,7 +28,7 @@ impl PianoHorizontal { .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); Self { buffer: Default::default(), - point: PhraseEditorPoint::default(), + point: MIDIPointModel::default(), size, range, phrase, @@ -241,6 +241,26 @@ impl PianoHorizontal { } } +impl MIDIRange for PianoHorizontal { + fn time_zoom (&self) -> usize { self.range.time_zoom() } + fn set_time_zoom (&self, x: usize) { self.range.set_time_zoom(x); } + fn time_lock (&self) -> bool { self.range.time_lock() } + fn set_time_lock (&self, x: bool) { self.range.set_time_lock(x); } + fn time_start (&self) -> usize { self.range.time_start() } + fn set_time_start (&self, x: usize) { self.range.set_time_start(x); } + fn set_note_lo (&self, x: usize) { self.range.set_note_lo(x); } + fn note_lo (&self) -> usize { self.range.note_lo() } + fn note_axis (&self) -> usize { self.range.note_lo() } + fn note_hi (&self) -> usize { self.note_lo() + self.note_axis() } +} +impl MIDIPoint for PianoHorizontal { + fn note_len (&self) -> usize { self.point.note_len()} + fn set_note_len (&self, x: usize) { self.point.set_note_len(x) } + fn note_point (&self) -> usize { self.point.note_point() } + fn set_note_point (&self, x: usize) { self.point.set_note_point(x) } + 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 { fn phrase (&self) -> &Option>> { &self.phrase @@ -248,12 +268,6 @@ impl PhraseViewMode for PianoHorizontal { fn phrase_mut (&mut self) -> &mut Option>> { &mut self.phrase } - fn range (&self) -> &PhraseEditorRange { - &self.range - } - fn point (&self) -> &PhraseEditorPoint { - &self.point - } /// Determine the required space to render the phrase. fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) { (phrase.length / self.range.time_zoom(), 128)