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
#[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> {
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<E: Engine> std::fmt::Debug for Measure<E> {
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<E: Engine> Measure<E> {
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<usize>) { self.1.store(w.into(), Ordering::Relaxed) }
pub fn set_h (&self, h: impl Into<usize>) { self.2.store(h.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.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 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<Tui> for Measure<Tui> {
@ -41,14 +49,24 @@ impl Render<Tui> for Measure<Tui> {
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<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(
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 {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
todo!()
Control::Continue
}
}

View file

@ -1,4 +1,5 @@
use crate::*;
use Ordering::Relaxed;
pub trait HasEditor {
fn editor (&self) -> &PhraseEditorModel;
@ -26,17 +27,19 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option<Self> {
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<Tui, PhraseEditorModel> for PhraseCommand {
impl Command<PhraseEditorModel> for PhraseCommand {
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
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<RwLock<Option<Arc<RwLock<Phrase>>>>>,
/// Renders the phrase
pub mode: Box<dyn PhraseViewMode>,
/// 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<Tui> + 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<RwLock<Option<Arc<RwLock<Phrase>>>>>;
@ -139,17 +136,11 @@ pub trait PhraseViewMode: Render<Tui> + 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<AtomicUsize>,
/// Earliest time displayed
pub time_start: Arc<AtomicUsize>,
/// Time step
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
pub note_lo: Arc<AtomicUsize>,
}
@ -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<Option<Arc<RwLock<Phrase>>>> 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,

View file

@ -3,28 +3,28 @@ use super::*;
/// A phrase, rendered as a horizontal piano roll.
pub struct PianoHorizontal {
phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
time_lock: bool,
time_zoom: Arc<AtomicUsize>,
note_len: Arc<AtomicUsize>,
buffer: BigBuffer,
phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
buffer: BigBuffer,
/// 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 {
pub fn new (
phrase: &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
range: &PhraseEditorRange,
point: &PhraseEditorPoint,
) -> Self {
pub fn new (phrase: &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>) -> 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<RwLock<Option<Arc<RwLock<Phrase>>>>> {
&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()
}