wip: some trickery with piano roll size

This commit is contained in:
🪞👃🪞 2024-12-13 01:14:14 +01:00
parent 51351a16dc
commit 66c29525be
5 changed files with 165 additions and 119 deletions

View file

@ -2,38 +2,46 @@ use crate::*;
/// A widget that tracks its render width and height /// A widget that tracks its render width and height
#[derive(Default)] #[derive(Default)]
pub struct Measure<E: Engine>(PhantomData<E>, AtomicUsize, AtomicUsize, bool); pub struct Measure<E: Engine> {
_engine: PhantomData<E>,
pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>,
}
impl<E: Engine> Clone for Measure<E> { impl<E: Engine> Clone for Measure<E> {
fn clone (&self) -> Self { fn clone (&self) -> Self {
Self( Self {
Default::default(), _engine: Default::default(),
AtomicUsize::from(self.1.load(Ordering::Relaxed)), x: self.x.clone(),
AtomicUsize::from(self.2.load(Ordering::Relaxed)), y: self.y.clone(),
self.3 }
)
} }
} }
impl<E: Engine> std::fmt::Debug for Measure<E> { impl<E: Engine> std::fmt::Debug for Measure<E> {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("Measure") f.debug_struct("Measure")
.field("width", &self.1) .field("width", &self.x)
.field("height", &self.2) .field("height", &self.y)
.finish() .finish()
} }
} }
impl<E: Engine> Measure<E> { impl<E: Engine> Measure<E> {
pub fn w (&self) -> usize { self.1.load(Ordering::Relaxed) } pub fn w (&self) -> usize { self.x.load(Ordering::Relaxed) }
pub fn h (&self) -> usize { self.2.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 wh (&self) -> [usize;2] { [self.w(), self.h()] }
pub fn set_w (&self, w: impl Into<usize>) { self.1.store(w.into(), Ordering::Relaxed) } pub fn set_w (&self, w: impl Into<usize>) { self.x.store(w.into(), Ordering::Relaxed) }
pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.into(), Ordering::Relaxed) } pub fn set_h (&self, h: impl Into<usize>) { self.y.store(h.into(), Ordering::Relaxed) }
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); } pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { 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 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<Tui> for Measure<Tui> { impl Render<Tui> for Measure<Tui> {
@ -41,14 +49,24 @@ impl Render<Tui> for Measure<Tui> {
Ok(Some([0u16.into(), 0u16.into()].into())) Ok(Some([0u16.into(), 0u16.into()].into()))
} }
fn render (&self, to: &mut TuiOutput) -> Usually<()> { fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let w = to.area().w(); self.set_w(to.area().w());
self.set_w(w); self.set_h(to.area().h());
let h = to.area().h(); Ok(())
self.set_h(h); }
Ok(if self.3 { }
impl Measure<Tui> {
pub fn debug (&self) -> ShowMeasure {
let measure: Measure<Tui> = (*self).clone();
ShowMeasure(measure)
}
}
pub struct ShowMeasure(Measure<Tui>);
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( 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)) Style::default().bold().italic().bg(Color::Rgb(255, 0, 255)).fg(Color::Rgb(0,0,0))
)) ))
}) })));
}
}

View file

@ -95,7 +95,7 @@ impl SamplerTui {
} }
impl Audio for SamplerTui { impl Audio for SamplerTui {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
todo!() Control::Continue
} }
} }

View file

@ -1,4 +1,5 @@
use crate::*; use crate::*;
use Ordering::Relaxed;
pub trait HasEditor { pub trait HasEditor {
fn editor (&self) -> &PhraseEditorModel; fn editor (&self) -> &PhraseEditorModel;
@ -26,17 +27,19 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option<Self> { fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option<Self> {
use PhraseCommand::*; use PhraseCommand::*;
use KeyCode::{Char, Up, Down, PageUp, PageDown, Left, Right}; use KeyCode::{Char, Up, Down, PageUp, PageDown, Left, Right};
let note_lo = state.range.note_lo.load(Ordering::Relaxed); let point = state.point();
let note_point = state.point.note_point.load(Ordering::Relaxed); let note_point = point.note_point.load(Relaxed);
let time_start = state.range.time_start.load(Ordering::Relaxed); let time_point = point.time_point.load(Relaxed);
let time_point = state.point.time_point.load(Ordering::Relaxed); let note_len = point.note_len.load(Relaxed);
let time_zoom = state.mode.time_zoom(); 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() let length = state.phrase().read().unwrap().as_ref()
.map(|p|p.read().unwrap().length).unwrap_or(1); .map(|p|p.read().unwrap().length).unwrap_or(1);
let note_len = state.point.note_len.load(Ordering::Relaxed);
Some(match from.event() { Some(match from.event() {
key!(Char('`')) => ToggleDirection, 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(next_note_length(time_zoom)), key!(Char('_')) => SetTimeZoom(next_note_length(time_zoom)),
key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)), key!(Char('=')) => SetTimeZoom(prev_note_length(time_zoom)),
@ -75,22 +78,24 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
impl Command<PhraseEditorModel> for PhraseCommand { impl Command<PhraseEditorModel> for PhraseCommand {
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> { fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
use PhraseCommand::*; use PhraseCommand::*;
let range = state.range();
let point = state.point();
match self { match self {
Show(phrase) => { state.set_phrase(phrase); }, Show(phrase) => { state.set_phrase(phrase); },
PutNote => { state.put_note(false); }, PutNote => { state.put_note(false); },
AppendNote => { state.put_note(true); }, AppendNote => { state.put_note(true); },
SetTimeZoom(zoom) => { state.mode.set_time_zoom(zoom); }, SetTimeZoom(x) => { range.set_time_zoom(x); },
SetTimeZoomLock(lock) => { state.mode.set_time_zoom_lock(lock); }, SetTimeZoomLock(x) => { range.set_time_lock(x); },
SetTimeScroll(time) => { state.range.time_start.store(time, Ordering::Relaxed); }, SetTimeScroll(x) => { range.set_time_start(x); },
SetTimeCursor(time) => { state.point.time_point.store(time, Ordering::Relaxed); }, SetNoteScroll(x) => { range.set_note_lo(x); },
SetNoteLength(time) => { state.point.note_len.store(time, Ordering::Relaxed); }, SetNoteLength(x) => { point.set_note_len(x); },
SetNoteScroll(note) => { state.range.note_lo.store(note, Ordering::Relaxed); }, SetTimeCursor(x) => { point.set_time_point(x); },
SetNoteCursor(note) => { SetNoteCursor(note) => {
let note = 127.min(note); let note = 127.min(note);
let start = state.range.note_lo.load(Ordering::Relaxed); let start = range.note_lo.load(Relaxed);
state.point.note_point.store(note, Ordering::Relaxed); point.note_point.store(note, Relaxed);
if note < start { 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<RwLock<Option<Arc<RwLock<Phrase>>>>>, pub phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
/// Renders the phrase /// Renders the phrase
pub mode: Box<dyn PhraseViewMode>, pub mode: Box<dyn PhraseViewMode>,
/// The display window
pub range: PhraseEditorRange,
/// The note cursor
pub point: PhraseEditorPoint,
} }
impl Default for PhraseEditorModel { impl Default for PhraseEditorModel {
fn default () -> Self { fn default () -> Self {
let phrase = Arc::new(RwLock::new(None)); let phrase = Arc::new(RwLock::new(None));
let range = PhraseEditorRange::default(); let mode = PianoHorizontal::new(&phrase);
let point = PhraseEditorPoint::default(); Self { phrase, mode: Box::new(mode) }
let mode = PianoHorizontal::new(&phrase, &range, &point);
Self { phrase, mode: Box::new(mode), range, point }
} }
} }
render!(|self: PhraseEditorModel|self.mode); render!(|self: PhraseEditorModel|self.mode);
pub trait PhraseViewMode: Render<Tui> + Debug + Send + Sync { pub trait PhraseViewMode: Render<Tui> + Debug + Send + Sync {
fn time_zoom (&self) -> usize; fn range (&self) -> &PhraseEditorRange;
fn set_time_zoom (&mut self, time_zoom: usize); fn point (&self) -> &PhraseEditorPoint;
fn time_zoom_lock (&self) -> bool;
fn set_time_zoom_lock (&mut self, time_zoom: bool);
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize); fn buffer_size (&self, phrase: &Phrase) -> (usize, usize);
fn redraw (&mut self); fn redraw (&mut self);
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>; fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>;
@ -139,17 +136,11 @@ pub trait PhraseViewMode: Render<Tui> + Debug + Send + Sync {
} }
impl PhraseViewMode for PhraseEditorModel { impl PhraseViewMode for PhraseEditorModel {
fn time_zoom (&self) -> usize { fn range (&self) -> &PhraseEditorRange {
self.mode.time_zoom() self.mode.range()
} }
fn set_time_zoom (&mut self, time_zoom: usize) { fn point (&self) -> &PhraseEditorPoint {
self.mode.set_time_zoom(time_zoom) self.mode.point()
}
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 buffer_size (&self, phrase: &Phrase) -> (usize, usize) { fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) {
self.mode.buffer_size(phrase) self.mode.buffer_size(phrase)
@ -164,10 +155,16 @@ impl PhraseViewMode for PhraseEditorModel {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PhraseEditorRange { pub struct PhraseEditorRange {
/// Length of visible time axis
pub time_axis: Arc<AtomicUsize>,
/// Earliest time displayed /// Earliest time displayed
pub time_start: Arc<AtomicUsize>, pub time_start: Arc<AtomicUsize>,
/// Time step /// Time step
pub time_zoom: Arc<AtomicUsize>, pub time_zoom: Arc<AtomicUsize>,
/// Auto rezoom to fit in time axis
pub time_lock: Arc<AtomicBool>,
/// Length of visible note axis
pub note_axis: Arc<AtomicUsize>,
// Lowest note displayed // Lowest note displayed
pub note_lo: Arc<AtomicUsize>, pub note_lo: Arc<AtomicUsize>,
} }
@ -175,12 +172,35 @@ pub struct PhraseEditorRange {
impl Default for PhraseEditorRange { impl Default for PhraseEditorRange {
fn default () -> Self { fn default () -> Self {
Self { Self {
time_axis: Arc::new(0.into()),
time_start: Arc::new(0.into()), time_start: Arc::new(0.into()),
time_zoom: Arc::new(24.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()), 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)] #[derive(Debug, Clone)]
pub struct PhraseEditorPoint { pub struct PhraseEditorPoint {
@ -201,14 +221,29 @@ 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 { impl PhraseEditorModel {
/// Put note at current position /// Put note at current position
pub fn put_note (&mut self, advance: bool) { pub fn put_note (&mut self, advance: bool) {
if let Some(phrase) = &*self.phrase.read().unwrap() { if let Some(phrase) = &*self.phrase.read().unwrap() {
let note_len = self.point.note_len.load(Ordering::Relaxed); let point = self.point().clone();
let time = self.point.time_point.load(Ordering::Relaxed); let note_len = point.note_len.load(Relaxed);
let note = self.point.note_point.load(Ordering::Relaxed); let time = point.time_point.load(Relaxed);
let note = point.note_point.load(Relaxed);
let mut phrase = phrase.write().unwrap(); let mut phrase = phrase.write().unwrap();
let key: u7 = u7::from(note as u8); let key: u7 = u7::from(note as u8);
let vel: u7 = 100.into(); let vel: u7 = 100.into();
@ -218,10 +253,10 @@ impl PhraseEditorModel {
phrase.notes[end].push(MidiMessage::NoteOff { key, vel }); phrase.notes[end].push(MidiMessage::NoteOff { key, vel });
self.mode.redraw(); self.mode.redraw();
if advance { if advance {
let point = self.point.time_point.load(Ordering::Relaxed); let time = point.time_point.load(Relaxed);
let length = phrase.length; let length = phrase.length;
let forward = |time|(time + note_len) % 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<Option<Arc<RwLock<Phrase>>>> for PhraseEditorModel {
impl std::fmt::Debug for PhraseEditorModel { impl std::fmt::Debug for PhraseEditorModel {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("PhraseEditorModel") f.debug_struct("PhraseEditorModel")
.field("range", &self.range) .field("range", &self.range())
.field("point", &self.point) .field("point", &self.point())
.finish() .finish()
} }
} }
@ -253,13 +288,13 @@ impl std::fmt::Debug for PhraseEditorModel {
fn autoscroll_notes ( fn autoscroll_notes (
range: &PhraseEditorRange, point: &PhraseEditorPoint, height: usize range: &PhraseEditorRange, point: &PhraseEditorPoint, height: usize
) -> (usize, (usize, usize)) { ) -> (usize, (usize, usize)) {
let note_point = point.note_point.load(Ordering::Relaxed); let note_point = point.note_point.load(Relaxed);
let mut note_lo = range.note_lo.load(Ordering::Relaxed); let mut note_lo = range.note_lo.load(Relaxed);
let mut note_hi = 127.min((note_lo + height).saturating_sub(2)); let mut note_hi = 127.min((note_lo + height).saturating_sub(2));
if note_point > note_hi { if note_point > note_hi {
note_lo += note_point - note_hi; note_lo += note_point - note_hi;
note_hi = note_point; 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)) (note_point, (note_lo, note_hi))
} }
@ -275,9 +310,9 @@ fn autoscroll_notes (
//Self { //Self {
//note_point, //note_point,
//note_range, //note_range,
//time_start: editor.range.time_start.load(Ordering::Relaxed), //time_start: editor.range.time_start.load(Relaxed),
//time_point: editor.point.time_point.load(Ordering::Relaxed), //time_point: editor.point.time_point.load(Relaxed),
//note_len: editor.point.note_len.load(Ordering::Relaxed), //note_len: editor.point.note_len.load(Relaxed),
//phrase: editor.phrase.clone(), //phrase: editor.phrase.clone(),
//mode: &editor.mode, //mode: &editor.mode,
//size: &editor.size, //size: &editor.size,

View file

@ -4,27 +4,27 @@ use super::*;
/// A phrase, rendered as a horizontal piano roll. /// A phrase, rendered as a horizontal piano roll.
pub struct PianoHorizontal { pub struct PianoHorizontal {
phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>, phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
time_lock: bool,
time_zoom: Arc<AtomicUsize>,
note_len: Arc<AtomicUsize>,
buffer: BigBuffer, buffer: BigBuffer,
/// Width and height of notes area at last render /// Width and height of notes area at last render
size: Measure<Tui>, size: Measure<Tui>,
/// The display window
range: PhraseEditorRange,
/// The note cursor
point: PhraseEditorPoint,
} }
impl PianoHorizontal { impl PianoHorizontal {
pub fn new ( pub fn new (phrase: &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>) -> Self {
phrase: &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>, let size = Measure::new();
range: &PhraseEditorRange, let mut range = PhraseEditorRange::default();
point: &PhraseEditorPoint, range.time_axis = size.x.clone();
) -> Self { range.note_axis = size.y.clone();
Self { Self {
phrase: phrase.clone(),
buffer: Default::default(), buffer: Default::default(),
time_lock: true, phrase: phrase.clone(),
time_zoom: range.time_zoom.clone(), point: PhraseEditorPoint::default(),
note_len: point.note_len.clone(), range,
size: Measure::new() size,
} }
} }
} }
@ -229,31 +229,24 @@ impl PhraseViewMode for PianoHorizontal {
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>> { fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>> {
&self.phrase &self.phrase
} }
fn time_zoom (&self) -> usize { fn range (&self) -> &PhraseEditorRange {
self.time_zoom.load(Ordering::Relaxed) &self.range
} }
fn set_time_zoom (&mut self, time_zoom: usize) { fn point (&self) -> &PhraseEditorPoint {
self.time_zoom.store(time_zoom, Ordering::Relaxed); &self.point
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()
} }
/// Determine the required space to render the phrase. /// Determine the required space to render the phrase.
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) { 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) { fn redraw (&mut self) {
let buffer = if let Some(phrase) = &*self.phrase().read().unwrap() { 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 mut buffer = BigBuffer::from(self.buffer_size(&phrase));
let note_len = self.note_len.load(Ordering::Relaxed); let note_len = self.point.note_len();
PianoHorizontal::draw_bg(&mut buffer, &phrase, self.time_zoom(), note_len); let time_zoom = self.range.time_zoom();
PianoHorizontal::draw_fg(&mut buffer, &phrase, self.time_zoom()); PianoHorizontal::draw_bg(&mut buffer, &phrase, time_zoom, note_len);
PianoHorizontal::draw_fg(&mut buffer, &phrase, time_zoom);
buffer buffer
} else { } else {
Default::default() Default::default()
@ -265,7 +258,7 @@ impl PhraseViewMode for PianoHorizontal {
impl std::fmt::Debug for PianoHorizontal { impl std::fmt::Debug for PianoHorizontal {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("PianoHorizontal") 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)) .field("buffer", &format!("{}x{}", self.buffer.width, self.buffer.height))
.finish() .finish()
} }