traits MIDIRange and MIDIPoint

This commit is contained in:
🪞👃🪞 2024-12-14 21:17:49 +01:00
parent d06b95df2c
commit 8f0decbe4d
7 changed files with 236 additions and 235 deletions

View file

@ -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::*;

View file

@ -0,0 +1,94 @@
use crate::*;
use Ordering::Relaxed;
#[derive(Debug, Clone)]
pub struct MIDIRangeModel {
/// 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>,
}
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<AtomicUsize>,
/// Note coordinate of cursor
pub note_point: Arc<AtomicUsize>,
/// Length of note that will be inserted, in pulses
pub note_len: Arc<AtomicUsize>,
}
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) }
}

View file

@ -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::*;

View file

@ -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<u64> {
if self.enabled {
Some(self.clock.raw())
} else {
None
}
}
pub fn update (&self, t0: Option<u64>, 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<f64> {
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
}
}
}

View file

@ -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<u64> {
if self.enabled {
Some(self.clock.raw())
} else {
None
}
}
pub fn update (&self, t0: Option<u64>, 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<f64> {
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::*;

View file

@ -28,14 +28,13 @@ 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 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<Tui, PhraseEditorModel> 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<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.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<Tui> + Debug + Send + Sync {
fn range (&self) -> &PhraseEditorRange;
fn point (&self) -> &PhraseEditorPoint;
pub trait PhraseViewMode: Render<Tui> + MIDIRange + MIDIPoint + Debug + Send + Sync {
fn buffer_size (&self, phrase: &Phrase) -> (usize, usize);
fn redraw (&mut self);
fn phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
@ -129,13 +123,27 @@ pub trait PhraseViewMode: Render<Tui> + 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<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>,
}
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<AtomicUsize>,
/// Note coordinate of cursor
pub note_point: Arc<AtomicUsize>,
/// Length of note that will be inserted, in pulses
pub note_len: Arc<AtomicUsize>,
}
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<RwLock<Phrase>>> for PhraseEditorModel {
@ -301,46 +230,7 @@ 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("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(),
//}
//}
//}

View file

@ -9,9 +9,9 @@ pub struct PianoHorizontal {
/// Width and height of notes area at last render
size: Measure<Tui>,
/// 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<RwLock<Phrase>>>) -> 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<Arc<RwLock<Phrase>>> {
&self.phrase
@ -248,12 +268,6 @@ impl PhraseViewMode for PianoHorizontal {
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<Phrase>>> {
&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)