simplify MidiView and midi_note

This commit is contained in:
🪞👃🪞 2024-12-21 18:11:41 +01:00
parent c1da3fac13
commit 66e8acc811
9 changed files with 171 additions and 184 deletions

View file

@ -78,19 +78,19 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
Ok(true)
}],
[Char('+'), NONE, "quant_inc", "quantize coarser", |app: &mut App| {
app.transport.quant = next_note_length(app.transport.quant);
app.transport.quant = Note::next(app.transport.quant);
Ok(true)
}],
[Char('_'), NONE, "quant_dec", "quantize finer", |app: &mut App| {
app.transport.quant = prev_note_length(app.transport.quant);
app.transport.quant = Note::prev(app.transport.quant);
Ok(true)
}],
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
app.arranger.sequencer_mut().map(|s|s.time_axis.scale_mut(&prev_note_length));
app.arranger.sequencer_mut().map(|s|s.time_axis.scale_mut(&Note::prev));
Ok(true)
}],
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
app.arranger.sequencer_mut().map(|s|s.time_axis.scale_mut(&next_note_length));
app.arranger.sequencer_mut().map(|s|s.time_axis.scale_mut(&Note::next));
Ok(true)
}],
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {

View file

@ -7,12 +7,26 @@ pub(crate) mod focus; pub(crate) use focus::*;
pub(crate) mod input; pub(crate) use input::*;
pub(crate) mod output; pub(crate) use output::*;
pub(crate) use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
pub(crate) use Ordering::Relaxed;
pub use self::{
engine::Engine,
input::Handle,
output::Render
};
/// Standard result type.
pub type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard optional result type.
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
/// Define test modules.
#[macro_export] macro_rules! testmod {
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
}
/// Prototypal case of implementor macro.
/// Saves 4loc per data pats.
#[macro_export] macro_rules! from {
@ -38,13 +52,15 @@ pub trait InteriorMutable<T>: Gettable<T> {
fn set (&self, value: T) -> T;
}
/// Standard result type.
pub type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard optional result type.
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
/// Define test modules.
#[macro_export] macro_rules! testmod {
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
impl Gettable<bool> for AtomicBool {
fn get (&self) -> bool { self.load(Ordering::Relaxed) }
}
impl InteriorMutable<bool> for AtomicBool {
fn set (&self, value: bool) -> bool { self.swap(value, Ordering::Relaxed) }
}
impl Gettable<usize> for AtomicUsize {
fn get (&self) -> usize { self.load(Ordering::Relaxed) }
}
impl InteriorMutable<usize> for AtomicUsize {
fn set (&self, value: usize) -> usize { self.swap(value, Ordering::Relaxed) }
}

View file

@ -1,28 +1,65 @@
use crate::*;
use Ordering::Relaxed;
pub trait MidiViewport<E: Engine>: MidiRange + MidiPoint + HasSize<E> {
pub struct Note;
impl Note {
/// (pulses, name), assuming 96 PPQ
pub const DURATIONS: [(usize, &str);26] = [
(1, "1/384"), (2, "1/192"),
(3, "1/128"), (4, "1/96"),
(6, "1/64"), (8, "1/48"),
(12, "1/32"), (16, "1/24"),
(24, "1/16"), (32, "1/12"),
(48, "1/8"), (64, "1/6"),
(96, "1/4"), (128, "1/3"),
(192, "1/2"), (256, "2/3"),
(384, "1/1"), (512, "4/3"),
(576, "3/2"), (768, "2/1"),
(1152, "3/1"), (1536, "4/1"),
(2304, "6/1"), (3072, "8/1"),
(3456, "9/1"), (6144, "16/1"),
];
/// Returns the next shorter length
pub fn prev (pulses: usize) -> usize {
for i in 1..=16 { let length = Note::DURATIONS[16-i].0; if length < pulses { return length } }
pulses
}
/// Returns the next longer length
pub fn next (pulses: usize) -> usize {
for (length, _) in &Note::DURATIONS { if *length > pulses { return *length } }
pulses
}
pub fn pulses_to_name (pulses: usize) -> &'static str {
for (length, name) in &Note::DURATIONS { if *length == pulses { return name } }
""
}
}
pub trait MidiView<E: Engine>: MidiRange + MidiPoint + HasSize<E> {
/// Make sure cursor is within range
fn autoscroll (&self) {
let note_lo = self.note_lo();
let note_axis = self.note_axis();
let note_hi = self.note_hi();
let note_point = self.note_point().min(127);
let note_lo = self.note_lo().get();
let note_hi = self.note_hi();
if note_point < note_lo {
self.set_note_lo(note_point);
self.note_lo().set(note_point);
} else if note_point > note_hi {
self.set_note_lo((note_lo + note_point).saturating_sub(note_hi));
self.note_lo().set((note_lo + note_point).saturating_sub(note_hi));
}
}
/// Make sure best usage of screen space is achieved by default
/// Make sure range is within display
fn autozoom (&self) {
}
fn autozoom_n (&self) {
}
fn autozoom_t (&self) {
let time_len = self.time_len().get();
let time_axis = self.time_axis().get();
let mut time_zoom = self.time_zoom().get();
//while time_len.div_ceil(time_zoom) > time_axis {
//println!("\r{time_len} {time_zoom} {time_axis}");
//time_zoom = Note::next(time_zoom);
//}
//self.time_zoom().set(time_zoom);
}
}
#[derive(Debug, Clone)]
pub struct MidiRangeModel {
pub time_len: Arc<AtomicUsize>,
/// Length of visible time axis
pub time_axis: Arc<AtomicUsize>,
/// Earliest time displayed
@ -37,6 +74,7 @@ pub struct MidiRangeModel {
pub note_lo: Arc<AtomicUsize>,
}
from!(|data:(usize, bool)|MidiRangeModel = Self {
time_len: Arc::new(0.into()),
note_axis: Arc::new(0.into()),
note_lo: Arc::new(0.into()),
time_axis: Arc::new(0.into()),
@ -45,30 +83,28 @@ from!(|data:(usize, bool)|MidiRangeModel = Self {
time_lock: Arc::new(data.1.into()),
});
pub trait MidiRange {
fn time_zoom (&self) -> usize;
fn set_time_zoom (&mut 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 time_axis (&self) -> usize;
fn note_hi (&self) -> usize { (self.note_lo() + self.note_axis().saturating_sub(1)).min(127) }
fn time_end (&self) -> usize { self.time_start() + self.time_axis() * self.time_zoom() }
fn time_len (&self) -> &AtomicUsize;
fn time_zoom (&self) -> &AtomicUsize;
fn time_lock (&self) -> &AtomicBool;
fn time_start (&self) -> &AtomicUsize;
fn note_lo (&self) -> &AtomicUsize;
fn note_axis (&self) -> &AtomicUsize;
fn time_axis (&self) -> &AtomicUsize;
fn note_hi (&self) -> usize {
(self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127)
}
fn time_end (&self) -> usize {
self.time_start().get() + self.time_axis().get() * self.time_zoom().get()
}
}
impl MidiRange for MidiRangeModel {
fn time_zoom (&self) -> usize { self.time_zoom.load(Relaxed) }
fn set_time_zoom (&mut 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 note_lo (&self) -> usize { self.note_lo.load(Relaxed).min(127) }
fn set_note_lo (&self, x: usize) { self.note_lo.store(x.min(127), Relaxed); }
fn note_axis (&self) -> usize { self.note_axis.load(Relaxed) }
fn time_axis (&self) -> usize { self.time_axis.load(Relaxed) }
fn time_len (&self) -> &AtomicUsize { &self.time_len }
fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom }
fn time_lock (&self) -> &AtomicBool { &self.time_lock }
fn time_start (&self) -> &AtomicUsize { &self.time_start }
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
fn note_axis (&self) -> &AtomicUsize { &self.note_axis }
fn time_axis (&self) -> &AtomicUsize { &self.time_axis }
}
#[derive(Debug, Clone)]

View file

@ -5,51 +5,6 @@ pub(crate) mod pulse; pub(crate) use pulse::*;
pub(crate) mod sr; pub(crate) use sr::*;
pub(crate) mod unit; pub(crate) use unit::*;
/// (pulses, name), assuming 96 PPQ
pub const NOTE_DURATIONS: [(usize, &str);26] = [
(1, "1/384"),
(2, "1/192"),
(3, "1/128"),
(4, "1/96"),
(6, "1/64"),
(8, "1/48"),
(12, "1/32"),
(16, "1/24"),
(24, "1/16"),
(32, "1/12"),
(48, "1/8"),
(64, "1/6"),
(96, "1/4"),
(128, "1/3"),
(192, "1/2"),
(256, "2/3"),
(384, "1/1"),
(512, "4/3"),
(576, "3/2"),
(768, "2/1"),
(1152, "3/1"),
(1536, "4/1"),
(2304, "6/1"),
(3072, "8/1"),
(3456, "9/1"),
(6144, "16/1"),
];
/// Returns the next shorter length
pub fn prev_note_length (pulses: usize) -> usize {
for i in 1..=16 { let length = NOTE_DURATIONS[16-i].0; if length < pulses { return length } }
pulses
}
/// Returns the next longer length
pub fn next_note_length (pulses: usize) -> usize {
for (length, _) in &NOTE_DURATIONS { if *length > pulses { return *length } }
pulses
}
pub fn pulses_to_name (pulses: usize) -> &'static str {
for (length, name) in &NOTE_DURATIONS { if *length == pulses { return name } }
""
}
//#[cfg(test)]
//mod test {
//use super::*;

View file

@ -22,10 +22,10 @@ impl_time_unit!(BeatsPerMinute);
impl_time_unit!(LaunchSync);
impl LaunchSync {
pub fn next (&self) -> f64 {
next_note_length(self.get() as usize) as f64
Note::next(self.get() as usize) as f64
}
pub fn prev (&self) -> f64 {
prev_note_length(self.get() as usize) as f64
Note::prev(self.get() as usize) as f64
}
}
@ -34,10 +34,10 @@ impl LaunchSync {
impl_time_unit!(Quantize);
impl Quantize {
pub fn next (&self) -> f64 {
next_note_length(self.get() as usize) as f64
Note::next(self.get() as usize) as f64
}
pub fn prev (&self) -> f64 {
prev_note_length(self.get() as usize) as f64
Note::prev(self.get() as usize) as f64
}
}

View file

@ -29,13 +29,13 @@ from_jack!(|jack|SequencerTui {
phrases: PhraseListModel::from(&phrase),
editor: PhraseEditorModel::from(&phrase),
player: PhrasePlayerModel::from((&clock, &phrase)),
clock,
size: Measure::new(),
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf: PerfModel::default(),
show_pool: true,
status: true,
clock,
}
});
render!(<Tui>|self: SequencerTui|{
@ -91,11 +91,13 @@ handle!(<Tui>|self:SequencerTui,input|SequencerCommand::execute_with_state(self,
}
input_to_command!(SequencerCommand: <Tui>|state:SequencerTui,input|match input.event() {
// Transport: Play/pause
key_pat!(Char(' ')) => Clock(if state.clock().is_stopped() {
Play(None) } else { Pause(None) }),
key_pat!(Char(' ')) => Clock(
if state.clock().is_stopped() { Play(None) } else { Pause(None) }
),
// Transport: Play from start or rewind to start
key_pat!(Shift-Char(' ')) => Clock(if state.clock().is_stopped() {
Play(Some(0)) } else { Pause(Some(0)) }),
key_pat!(Shift-Char(' ')) => Clock(
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
),
// TODO: u: undo
key_pat!(Char('u')) => { todo!("undo") },
// TODO: Shift-U: redo
@ -151,13 +153,13 @@ command!(|self: SequencerCommand, state: SequencerTui|match self {
_ => default(cmd)?
}
},
Self::Editor(cmd) => {
Self::Editor(cmd) => {
let default = ||cmd.execute(&mut state.editor).map(|x|x.map(Editor));
match cmd {
_ => default()?
}
},
Self::Clock(cmd) => cmd.execute(state)?.map(Clock),
Self::Clock(cmd) => cmd.execute(state)?.map(Clock),
Self::Enqueue(phrase) => {
state.player.enqueue_next(phrase.as_ref());
None

View file

@ -1,19 +1,6 @@
use crate::*;
const HEADER_H: u16 = 3;
const SCENES_W_OFFSET: u16 = 3;
fn tracks_with_widths (tracks: &[ArrangerTrack])
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
{
let mut x = 0;
tracks.iter().enumerate().map(move |(index, track)|{
let data = (index, track, x, x + track.width);
x += track.width;
data
})
}
pub struct ArrangerVHead<'a> {
scenes_w: u16,
timebase: &'a Arc<Timebase>,

View file

@ -33,10 +33,12 @@ pub enum PhraseCommand {
impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option<Self> {
let length = ||state.phrase().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
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_lo = ||state.note_lo().get();
let time_start = ||state.time_start().get();
let time_zoom = ||state.time_zoom().get();
let time_lock = ||state.time_lock().get();
let note_point = ||state.note_point();
let time_point = ||state.time_point();
let note_len = ||state.note_len();
@ -59,16 +61,16 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
key_pat!(Right) => SetTimeCursor((time_point() + note_len()) % length()),
key_pat!(Char('`')) => ToggleDirection,
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())),
key_pat!(Char('+')) => SetTimeZoom(prev_note_length(time_zoom())),
key_pat!(Char('-')) => SetTimeZoom(Note::next(time_zoom())),
key_pat!(Char('_')) => SetTimeZoom(Note::next(time_zoom())),
key_pat!(Char('=')) => SetTimeZoom(Note::prev(time_zoom())),
key_pat!(Char('+')) => SetTimeZoom(Note::prev(time_zoom())),
key_pat!(Enter) => PutNote,
key_pat!(Ctrl-Enter) => AppendNote,
key_pat!(Char(',')) => SetNoteLength(prev_note_length(note_len())), // TODO: no 3plet
key_pat!(Char('.')) => SetNoteLength(next_note_length(note_len())),
key_pat!(Char('<')) => SetNoteLength(prev_note_length(note_len())), // TODO: 3plet
key_pat!(Char('>')) => SetNoteLength(next_note_length(note_len())),
key_pat!(Char(',')) => SetNoteLength(Note::prev(note_len())), // TODO: no 3plet
key_pat!(Char('.')) => SetNoteLength(Note::next(note_len())),
key_pat!(Char('<')) => SetNoteLength(Note::prev(note_len())), // TODO: 3plet
key_pat!(Char('>')) => SetNoteLength(Note::next(note_len())),
// TODO: key_pat!(Char('/')) => // toggle 3plet
// TODO: key_pat!(Char('?')) => // toggle dotted
_ => return None
@ -80,20 +82,16 @@ impl Command<PhraseEditorModel> for PhraseCommand {
fn execute (self, state: &mut PhraseEditorModel) -> Perhaps<Self> {
use PhraseCommand::*;
match self {
Show(phrase) => { state.set_phrase(phrase.as_ref()); },
PutNote => { state.put_note(false); },
AppendNote => { state.put_note(true); },
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.min(127)); },
SetNoteLength(x) => { state.set_note_len(x); },
SetTimeCursor(x) => {
state.set_time_point(x);
},
SetNoteCursor(note) => {
state.set_note_point(note.min(127));
},
Show(phrase) => { state.set_phrase(phrase.as_ref()); },
PutNote => { state.put_note(false); },
AppendNote => { state.put_note(true); },
SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); },
SetTimeLock(x) => { state.time_lock().set(x); },
SetTimeScroll(x) => { state.time_start().set(x); },
SetNoteScroll(x) => { state.note_lo().set(x.min(127)); },
SetNoteLength(x) => { state.set_note_len(x); },
SetTimeCursor(x) => { state.set_time_point(x); },
SetNoteCursor(note) => { state.set_note_point(note.min(127)); },
_ => todo!("{:?}", self)
}
Ok(None)
@ -118,6 +116,7 @@ impl Default for PhraseEditorModel {
has_size!(<Tui>|self:PhraseEditorModel|&self.size);
render!(<Tui>|self: PhraseEditorModel|{
self.autoscroll();
self.autozoom();
&self.mode
});
//render!(<Tui>|self: PhraseEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks
@ -133,19 +132,16 @@ pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + D
}
}
impl MidiViewport<Tui> for PhraseEditorModel {}
impl MidiView<Tui> for PhraseEditorModel {}
impl MidiRange for PhraseEditorModel {
fn time_zoom (&self) -> usize { self.mode.time_zoom() }
fn set_time_zoom (&mut 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_axis() }
fn time_axis (&self) -> usize { self.mode.time_axis() }
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 note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
}
impl MidiPoint for PhraseEditorModel {
@ -253,10 +249,10 @@ render!(<Tui>|self:PhraseEditStatus<'a>|row!(|add|{
field(" Time", format!("{}",
self.0.time_point())),
field(" View", format!("{}-{} ({}*{})",
self.0.time_start(),
self.0.time_start().get(),
self.0.time_end(),
self.0.time_axis(),
self.0.time_zoom()))
self.0.time_axis().get(),
self.0.time_zoom().get()))
])))?;
add(&Fixed::wh(25, 3, col!(![
field(" Note", format!("{:4} ({:3}) {:4}",
@ -264,12 +260,12 @@ render!(<Tui>|self:PhraseEditStatus<'a>|row!(|add|{
self.0.note_point(),
self.0.note_len())),
field(" View", format!("{}-{} ({})",
to_note_name(self.0.note_lo()),
to_note_name(self.0.note_lo().get()),
to_note_name(self.0.note_hi()),
self.0.note_axis()))
self.0.note_axis().get()))
])))?;
add(&Fixed::wh(16, 3, col!(![
row!(!["TimeLock ", Tui::bold(true, format!("{}", self.0.time_lock()))])])))?;
row!(!["TimeLock ", Tui::bold(true, format!("{}", self.0.time_lock().get()))])])))?;
Ok(())
}))))
}));

View file

@ -40,8 +40,8 @@ pub struct PianoHorizontal {
impl PianoHorizontal {
pub fn new (phrase: Option<&Arc<RwLock<Phrase>>>) -> 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 phrase = phrase.map(|p|p.clone());
@ -80,7 +80,7 @@ render!(<Tui>|self: PianoHorizontalTimeline<'a>|render(|to|{
let style = Some(Style::default().dim());
let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
let t = area_x as usize * self.0.time_zoom();
let t = area_x as usize * self.0.time_zoom().get();
if t < length {
to.blit(&"|", screen_x, y, style);
}
@ -96,7 +96,7 @@ render!(<Tui>|self: PianoHorizontalTimeline<'a>|render(|to|{
pub struct PianoHorizontalKeys<'a>(&'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalKeys<'a>|render(|to|Ok({
let color = self.0.color;
let note_lo = self.0.note_lo();
let note_lo = self.0.note_lo().get();
let note_hi = self.0.note_hi();
let note_point = self.0.note_point();
let [x, y0, w, h] = to.area().xywh();
@ -130,11 +130,11 @@ pub struct PianoHorizontalCursor<'a>(&'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to|Ok({
let note_hi = self.0.note_hi();
let note_len = self.0.note_len();
let note_lo = self.0.note_lo();
let note_lo = self.0.note_lo().get();
let note_point = self.0.note_point();
let time_point = self.0.time_point();
let time_start = self.0.time_start();
let time_zoom = self.0.time_zoom();
let time_start = self.0.time_start().get();
let time_zoom = self.0.time_zoom().get();
let [x0, y0, w, _] = to.area().xywh();
let style = Some(Style::default().fg(Color::Rgb(0,255,0)));
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
@ -159,13 +159,13 @@ render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to|Ok({
pub struct PianoHorizontalNotes<'a>(&'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalNotes<'a>|render(|to|Ok({
let time_start = self.0.time_start();
let time_start = self.0.time_start().get();
let note_lo = self.0.note_lo().get();
let note_hi = self.0.note_hi();
let note_lo = self.0.note_lo();
let note_point = self.0.note_point();
let source = &self.0.buffer;
let [x0, y0, w, h] = to.area().xywh();
if h as usize != self.0.note_axis() {
if h as usize != self.0.note_axis().get() {
panic!("area height mismatch");
}
for (area_x, screen_x) in (x0..x0+w).enumerate() {
@ -264,19 +264,13 @@ impl PianoHorizontal {
has_size!(<Tui>|self:PianoHorizontal|&self.size);
impl MidiRange for PianoHorizontal {
fn time_zoom (&self) -> usize { self.range.time_zoom() }
fn set_time_zoom (&mut self, x: usize) {
self.range.set_time_zoom(x);
self.redraw();
}
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_axis() }
fn time_axis (&self) -> usize { self.range.time_axis() }
fn time_len (&self) -> &AtomicUsize { self.range.time_len() }
fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() }
fn time_lock (&self) -> &AtomicBool { self.range.time_lock() }
fn time_start (&self) -> &AtomicUsize { self.range.time_start() }
fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() }
fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() }
}
impl MidiPoint for PianoHorizontal {
fn note_len (&self) -> usize { self.point.note_len()}
@ -295,15 +289,16 @@ impl PhraseViewMode for PianoHorizontal {
}
/// Determine the required space to render the phrase.
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) {
(phrase.length / self.range.time_zoom(), 128)
(phrase.length / self.range.time_zoom().get(), 128)
}
fn redraw (&mut self) {
let buffer = if let Some(phrase) = self.phrase.as_ref() {
let phrase = phrase.read().unwrap();
let buf_size = self.buffer_size(&phrase);
let mut buffer = BigBuffer::from(buf_size);
let note_len = self.point.note_len();
let time_zoom = self.range.time_zoom();
let note_len = self.note_len();
let time_zoom = self.time_zoom().get();
self.time_len().set(phrase.length);
PianoHorizontal::draw_bg(&mut buffer, &phrase, time_zoom, note_len);
PianoHorizontal::draw_fg(&mut buffer, &phrase, time_zoom);
buffer