diff --git a/crates/tek/src/core/tui.rs b/crates/tek/src/core/tui.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek/src/layout/measure.rs b/crates/tek/src/layout/measure.rs index cc528435..798366f8 100644 --- a/crates/tek/src/layout/measure.rs +++ b/crates/tek/src/layout/measure.rs @@ -2,38 +2,46 @@ use crate::*; /// A widget that tracks its render width and height #[derive(Default)] -pub struct Measure(PhantomData, AtomicUsize, AtomicUsize, bool); +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} impl Clone for Measure { fn clone (&self) -> Self { - Self( - Default::default(), - AtomicUsize::from(self.1.load(Ordering::Relaxed)), - AtomicUsize::from(self.2.load(Ordering::Relaxed)), - self.3 - ) + Self { + _engine: Default::default(), + x: self.x.clone(), + y: self.y.clone(), + } } } impl std::fmt::Debug for Measure { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("Measure") - .field("width", &self.1) - .field("height", &self.2) + .field("width", &self.x) + .field("height", &self.y) .finish() } } impl Measure { - pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) } - pub fn h (&self) -> usize { self.2.load(Ordering::Relaxed) } + pub fn w (&self) -> usize { self.x.load(Ordering::Relaxed) } + pub fn h (&self) -> usize { self.y.load(Ordering::Relaxed) } pub fn wh (&self) -> [usize;2] { [self.w(), self.h()] } - pub fn set_w (&self, w: impl Into) { self.1.store(w.into(), Ordering::Relaxed) } - pub fn set_h (&self, h: impl Into) { self.2.store(h.into(), Ordering::Relaxed) } + pub fn set_w (&self, w: impl Into) { self.x.store(w.into(), Ordering::Relaxed) } + pub fn set_h (&self, h: impl Into) { self.y.store(h.into(), Ordering::Relaxed) } pub fn set_wh (&self, w: impl Into, h: impl Into) { self.set_w(w); self.set_h(h); } - pub fn new () -> Self { Self(PhantomData::default(), 0.into(), 0.into(), false) } - pub fn debug () -> Self { Self(PhantomData::default(), 0.into(), 0.into(), true) } pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) } + pub fn new () -> Self { + Self { + _engine: PhantomData::default(), + x: Arc::new(0.into()), + y: Arc::new(0.into()), + } + } } impl Render for Measure { @@ -41,14 +49,24 @@ impl Render for Measure { Ok(Some([0u16.into(), 0u16.into()].into())) } fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let w = to.area().w(); - self.set_w(w); - let h = to.area().h(); - self.set_h(h); - Ok(if self.3 { - to.blit(&format!(" {w} x {h} "), to.area.x(), to.area.y(), Some( - Style::default().bold().italic().bg(Color::Rgb(255, 0, 255)).fg(Color::Rgb(0,0,0)) - )) - }) + self.set_w(to.area().w()); + self.set_h(to.area().h()); + Ok(()) } } + +impl Measure { + pub fn debug (&self) -> ShowMeasure { + let measure: Measure = (*self).clone(); + ShowMeasure(measure) + } +} + +pub struct ShowMeasure(Measure); +render!(|self: ShowMeasure|render(|to|Ok({ + let w = self.0.w(); + let h = self.0.h(); + to.blit(&format!(" {w} x {h} "), to.area.x(), to.area.y(), Some( + Style::default().bold().italic().bg(Color::Rgb(255, 0, 255)).fg(Color::Rgb(0,0,0)) + )) +}))); diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 92b3843d..728d0b6c 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -95,7 +95,7 @@ impl SamplerTui { } impl Audio for SamplerTui { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - todo!() + Control::Continue } } diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 96518b3f..9314d37b 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -1,4 +1,5 @@ use crate::*; +use Ordering::Relaxed; pub trait HasEditor { fn editor (&self) -> &PhraseEditorModel; @@ -26,17 +27,19 @@ impl InputToCommand for PhraseCommand { fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option { use PhraseCommand::*; use KeyCode::{Char, Up, Down, PageUp, PageDown, Left, Right}; - let note_lo = state.range.note_lo.load(Ordering::Relaxed); - let note_point = state.point.note_point.load(Ordering::Relaxed); - let time_start = state.range.time_start.load(Ordering::Relaxed); - let time_point = state.point.time_point.load(Ordering::Relaxed); - let time_zoom = state.mode.time_zoom(); + let point = state.point(); + let note_point = point.note_point.load(Relaxed); + let time_point = point.time_point.load(Relaxed); + let note_len = point.note_len.load(Relaxed); + 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 length = state.phrase().read().unwrap().as_ref() .map(|p|p.read().unwrap().length).unwrap_or(1); - let note_len = state.point.note_len.load(Ordering::Relaxed); Some(match from.event() { key!(Char('`')) => ToggleDirection, - key!(Char('z')) => SetTimeZoomLock(!state.mode.time_zoom_lock()), + key!(Char('z')) => SetTimeZoomLock(!state.range().time_lock()), key!(Char('-')) => SetTimeZoom(next_note_length(time_zoom)), key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)), key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)), @@ -75,22 +78,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); }, - PutNote => { state.put_note(false); }, - AppendNote => { state.put_note(true); }, - SetTimeZoom(zoom) => { state.mode.set_time_zoom(zoom); }, - SetTimeZoomLock(lock) => { state.mode.set_time_zoom_lock(lock); }, - SetTimeScroll(time) => { state.range.time_start.store(time, Ordering::Relaxed); }, - SetTimeCursor(time) => { state.point.time_point.store(time, Ordering::Relaxed); }, - SetNoteLength(time) => { state.point.note_len.store(time, Ordering::Relaxed); }, - SetNoteScroll(note) => { state.range.note_lo.store(note, Ordering::Relaxed); }, - SetNoteCursor(note) => { + Show(phrase) => { state.set_phrase(phrase); }, + PutNote => { state.put_note(false); }, + AppendNote => { state.put_note(true); }, + SetTimeZoom(x) => { range.set_time_zoom(x); }, + SetTimeZoomLock(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); }, + SetNoteCursor(note) => { let note = 127.min(note); - let start = state.range.note_lo.load(Ordering::Relaxed); - state.point.note_point.store(note, Ordering::Relaxed); + let start = range.note_lo.load(Relaxed); + point.note_point.store(note, Relaxed); if note < start { - state.range.note_lo.store(note, Ordering::Relaxed); + range.note_lo.store(note, Relaxed); } }, @@ -106,29 +111,21 @@ pub struct PhraseEditorModel { pub phrase: Arc>>>>, /// Renders the phrase pub mode: Box, - /// The display window - pub range: PhraseEditorRange, - /// The note cursor - pub point: PhraseEditorPoint, } impl Default for PhraseEditorModel { fn default () -> Self { let phrase = Arc::new(RwLock::new(None)); - let range = PhraseEditorRange::default(); - let point = PhraseEditorPoint::default(); - let mode = PianoHorizontal::new(&phrase, &range, &point); - Self { phrase, mode: Box::new(mode), range, point } + let mode = PianoHorizontal::new(&phrase); + Self { phrase, mode: Box::new(mode) } } } render!(|self: PhraseEditorModel|self.mode); pub trait PhraseViewMode: Render + Debug + Send + Sync { - fn time_zoom (&self) -> usize; - fn set_time_zoom (&mut self, time_zoom: usize); - fn time_zoom_lock (&self) -> bool; - fn set_time_zoom_lock (&mut self, time_zoom: bool); + fn range (&self) -> &PhraseEditorRange; + fn point (&self) -> &PhraseEditorPoint; fn buffer_size (&self, phrase: &Phrase) -> (usize, usize); fn redraw (&mut self); fn phrase (&self) -> &Arc>>>>; @@ -139,17 +136,11 @@ pub trait PhraseViewMode: Render + Debug + Send + Sync { } impl PhraseViewMode for PhraseEditorModel { - fn time_zoom (&self) -> usize { - self.mode.time_zoom() + fn range (&self) -> &PhraseEditorRange { + self.mode.range() } - fn set_time_zoom (&mut self, time_zoom: usize) { - self.mode.set_time_zoom(time_zoom) - } - fn time_zoom_lock (&self) -> bool { - self.mode.time_zoom_lock() - } - fn set_time_zoom_lock (&mut self, time_lock: bool) { - self.mode.set_time_zoom_lock(time_lock); + fn point (&self) -> &PhraseEditorPoint { + self.mode.point() } fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) { self.mode.buffer_size(phrase) @@ -164,10 +155,16 @@ 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, } @@ -175,12 +172,35 @@ pub struct PhraseEditorRange { 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 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); + } +} #[derive(Debug, Clone)] pub struct PhraseEditorPoint { @@ -201,27 +221,42 @@ impl Default for PhraseEditorPoint { } } } +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 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) { if let Some(phrase) = &*self.phrase.read().unwrap() { - let note_len = self.point.note_len.load(Ordering::Relaxed); - let time = self.point.time_point.load(Ordering::Relaxed); - let note = self.point.note_point.load(Ordering::Relaxed); + let point = self.point().clone(); + let note_len = point.note_len.load(Relaxed); + let time = point.time_point.load(Relaxed); + let note = point.note_point.load(Relaxed); let mut phrase = phrase.write().unwrap(); - let key: u7 = u7::from(note as u8); - let vel: u7 = 100.into(); - let start = time; - let end = (start + note_len) % phrase.length; + let key: u7 = u7::from(note as u8); + let vel: u7 = 100.into(); + let start = time; + let end = (start + note_len) % phrase.length; phrase.notes[time].push(MidiMessage::NoteOn { key, vel }); phrase.notes[end].push(MidiMessage::NoteOff { key, vel }); self.mode.redraw(); if advance { - let point = self.point.time_point.load(Ordering::Relaxed); + let time = point.time_point.load(Relaxed); let length = phrase.length; let forward = |time|(time + note_len) % length; - self.point.time_point.store(forward(point), Ordering::Relaxed); + point.set_time_point(forward(time)); } } } @@ -244,8 +279,8 @@ 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("range", &self.range()) + .field("point", &self.point()) .finish() } } @@ -253,13 +288,13 @@ impl std::fmt::Debug for PhraseEditorModel { fn autoscroll_notes ( range: &PhraseEditorRange, point: &PhraseEditorPoint, height: usize ) -> (usize, (usize, usize)) { - let note_point = point.note_point.load(Ordering::Relaxed); - let mut note_lo = range.note_lo.load(Ordering::Relaxed); + 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, Ordering::Relaxed); + range.note_lo.store(note_lo, Relaxed); } (note_point, (note_lo, note_hi)) } @@ -275,9 +310,9 @@ fn autoscroll_notes ( //Self { //note_point, //note_range, - //time_start: editor.range.time_start.load(Ordering::Relaxed), - //time_point: editor.point.time_point.load(Ordering::Relaxed), - //note_len: editor.point.note_len.load(Ordering::Relaxed), + //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, diff --git a/crates/tek/src/tui/piano_horizontal.rs b/crates/tek/src/tui/piano_horizontal.rs index d005c424..09ba70b2 100644 --- a/crates/tek/src/tui/piano_horizontal.rs +++ b/crates/tek/src/tui/piano_horizontal.rs @@ -3,28 +3,28 @@ use super::*; /// A phrase, rendered as a horizontal piano roll. pub struct PianoHorizontal { - phrase: Arc>>>>, - time_lock: bool, - time_zoom: Arc, - note_len: Arc, - buffer: BigBuffer, + phrase: Arc>>>>, + buffer: BigBuffer, /// Width and height of notes area at last render - size: Measure, + size: Measure, + /// The display window + range: PhraseEditorRange, + /// The note cursor + point: PhraseEditorPoint, } impl PianoHorizontal { - pub fn new ( - phrase: &Arc>>>>, - range: &PhraseEditorRange, - point: &PhraseEditorPoint, - ) -> Self { + pub fn new (phrase: &Arc>>>>) -> Self { + let size = Measure::new(); + let mut range = PhraseEditorRange::default(); + range.time_axis = size.x.clone(); + range.note_axis = size.y.clone(); Self { - phrase: phrase.clone(), - buffer: Default::default(), - time_lock: true, - time_zoom: range.time_zoom.clone(), - note_len: point.note_len.clone(), - size: Measure::new() + buffer: Default::default(), + phrase: phrase.clone(), + point: PhraseEditorPoint::default(), + range, + size, } } } @@ -229,31 +229,24 @@ impl PhraseViewMode for PianoHorizontal { fn phrase (&self) -> &Arc>>>> { &self.phrase } - fn time_zoom (&self) -> usize { - self.time_zoom.load(Ordering::Relaxed) + fn range (&self) -> &PhraseEditorRange { + &self.range } - fn set_time_zoom (&mut self, time_zoom: usize) { - self.time_zoom.store(time_zoom, Ordering::Relaxed); - self.redraw() - } - fn time_zoom_lock (&self) -> bool { - self.time_lock - } - fn set_time_zoom_lock (&mut self, time_lock: bool) { - self.time_lock = time_lock; - self.redraw() + 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.time_zoom(), 128) + (phrase.length / self.range.time_zoom(), 128) } fn redraw (&mut self) { let buffer = if let Some(phrase) = &*self.phrase().read().unwrap() { - let phrase = phrase.read().unwrap(); + let phrase = phrase.read().unwrap(); let mut buffer = BigBuffer::from(self.buffer_size(&phrase)); - let note_len = self.note_len.load(Ordering::Relaxed); - PianoHorizontal::draw_bg(&mut buffer, &phrase, self.time_zoom(), note_len); - PianoHorizontal::draw_fg(&mut buffer, &phrase, self.time_zoom()); + let note_len = self.point.note_len(); + let time_zoom = self.range.time_zoom(); + PianoHorizontal::draw_bg(&mut buffer, &phrase, time_zoom, note_len); + PianoHorizontal::draw_fg(&mut buffer, &phrase, time_zoom); buffer } else { Default::default() @@ -265,7 +258,7 @@ impl PhraseViewMode for PianoHorizontal { impl std::fmt::Debug for PianoHorizontal { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("PianoHorizontal") - .field("time_zoom", &self.time_zoom) + .field("time_zoom", &self.range.time_zoom) .field("buffer", &format!("{}x{}", self.buffer.width, self.buffer.height)) .finish() }