refactor(engine): flatten

- add `just stats`
- add basic doctests
This commit is contained in:
stop screaming 2026-02-21 00:03:04 +02:00
parent 7afab8eade
commit 37068784cb
34 changed files with 1285 additions and 1173 deletions

View file

@ -1,6 +1,58 @@
use crate::*;
impl<T: Has<Option<MidiEditor>>> HasEditor for T {}
/// Contains state for viewing and editing a clip.
///
/// ```
/// let mut editor = tek_device::MidiEditor {
/// mode: PianoHorizontal::new(Some(&Arc::new(RwLock::new(MidiClip::stop_all())))),
/// size: Default::default(),
/// //keys: Default::default(),
/// };
/// let _ = editor.put_note(true);
/// let _ = editor.put_note(false);
/// let _ = editor.clip_status();
/// let _ = editor.edit_status();
/// ```
pub struct MidiEditor {
/// Size of editor on screen
pub size: Measure<TuiOut>,
/// View mode and state of editor
pub mode: PianoHorizontal,
}
/// A clip, rendered as a horizontal piano roll.
///
/// ```
/// let piano: tek_device::PianoHorizontal = Default::default();
/// ```
#[derive(Clone, Default)] pub struct PianoHorizontal {
pub clip: Option<Arc<RwLock<MidiClip>>>,
/// Buffer where the whole clip is rerendered on change
pub buffer: Arc<RwLock<BigBuffer>>,
/// Size of actual notes area
pub size: Measure<TuiOut>,
/// The display window
pub range: MidiSelection,
/// The note cursor
pub point: MidiCursor,
/// The highlight color palette
pub color: ItemTheme,
/// Width of the keyboard
pub keys_width: u16,
}
pub struct OctaveVertical { on: [bool; 12], colors: [Color; 3] }
/// ```
/// struct TestEditorHost(Option<MidiEditor>);
/// has!(Option<MidiEditor>: |self: TestEditorHost|self.0);
/// let mut host = TestEditorHost(Some(MidiEditor::default()));
/// let _ = host.editor();
/// let _ = host.editor_mut();
/// let _ = host.is_editing();
/// let _ = host.editor_w();
/// let _ = host.editor_h();
/// ```
pub trait HasEditor: Has<Option<MidiEditor>> {
fn editor (&self) -> Option<&MidiEditor> {
self.get().as_ref()
@ -18,32 +70,11 @@ pub trait HasEditor: Has<Option<MidiEditor>> {
self.editor().map(|e|e.size.h()).unwrap_or(0) as usize
}
}
#[macro_export] macro_rules! has_editor {
(|$self:ident: $Struct:ident|{
editor = $e0:expr;
editor_w = $e1:expr;
editor_h = $e2:expr;
is_editing = $e3:expr;
}) => {
impl HasEditor for $Struct {
fn editor (&$self) -> Option<&MidiEditor> { $e0.as_ref() }
fn editor_mut (&mut $self) -> Option<&mut MidiEditor> { $e0.as_mut() }
fn editor_w (&$self) -> usize { $e1 }
fn editor_h (&$self) -> usize { $e2 }
fn is_editing (&$self) -> bool { $e3 }
}
};
}
/// Contains state for viewing and editing a clip
pub struct MidiEditor {
/// Size of editor on screen
pub size: Measure<TuiOut>,
/// View mode and state of editor
pub mode: PianoHorizontal,
}
impl<T: Has<Option<MidiEditor>>> HasEditor for T {}
has!(Measure<TuiOut>: |self: MidiEditor|self.size);
impl Default for MidiEditor {
fn default () -> Self {
Self {
@ -52,16 +83,19 @@ impl Default for MidiEditor {
}
}
}
impl std::fmt::Debug for MidiEditor {
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("MidiEditor").field("mode", &self.mode).finish()
}
}
from!(MidiEditor: |clip: &Arc<RwLock<MidiClip>>| {
let model = Self::from(Some(clip.clone()));
model.redraw();
model
});
from!(MidiEditor: |clip: Option<Arc<RwLock<MidiClip>>>| {
let mut model = Self::default();
*model.clip_mut() = clip;
@ -100,58 +134,6 @@ impl MidiEditor {
self.mode.redraw();
}
}
}
impl TimeRange for MidiEditor {
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 time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
}
impl NoteRange for MidiEditor {
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
}
impl NotePoint for MidiEditor {
fn note_len (&self) -> &AtomicUsize { self.mode.note_len() }
fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() }
}
impl TimePoint for MidiEditor {
fn time_pos (&self) -> &AtomicUsize { self.mode.time_pos() }
}
impl MidiViewer for MidiEditor {
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) }
fn redraw (&self) { self.mode.redraw() }
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { self.mode.clip() }
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() }
fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) }
}
def_command!(MidiEditCommand: |editor: MidiEditor| {
Show { clip: Option<Arc<RwLock<MidiClip>>> } => {
editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) },
DeleteNote => {
editor.redraw(); todo!() },
AppendNote { advance: bool } => {
editor.put_note(*advance); editor.redraw(); Ok(None) },
SetNotePos { pos: usize } => {
editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) },
SetNoteLen { len: usize } => {
editor.set_note_len(*len); editor.redraw(); Ok(None) },
SetNoteScroll { scroll: usize } => {
editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) },
SetTimePos { pos: usize } => {
editor.set_time_pos(*pos); editor.redraw(); Ok(None) },
SetTimeScroll { scroll: usize } => {
editor.set_time_start(*scroll); editor.redraw(); Ok(None) },
SetTimeZoom { zoom: usize } => {
editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) },
SetTimeLock { lock: bool } => {
editor.set_time_lock(*lock); editor.redraw(); Ok(None) },
// TODO: 1-9 seek markers that by default start every 8th of the clip
});
impl MidiEditor {
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> { todo!() }
fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) }
fn note_length (&self) -> usize { self.get_note_len() }
@ -185,19 +167,6 @@ impl MidiEditor {
self.get_time_pos().overflowing_sub(1)
.0.min(self.clip_length().saturating_sub(1))
}
}
impl Draw<TuiOut> for MidiEditor {
fn draw (&self, to: &mut TuiOut) { self.content().draw(to) }
}
impl Layout<TuiOut> for MidiEditor {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> { self.content().layout(to) }
}
impl HasContent<TuiOut> for MidiEditor {
fn content (&self) -> impl Content<TuiOut> { self.autoscroll(); /*self.autozoom();*/ self.size.of(&self.mode) }
}
impl MidiEditor {
pub fn clip_status (&self) -> impl Content<TuiOut> + '_ {
let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
(clip.color, clip.name.clone(), clip.length, clip.looped)
@ -222,7 +191,7 @@ impl MidiEditor {
let time_zoom = self.get_time_zoom();
let time_lock = if self.get_time_lock() { "[lock]" } else { " " };
let note_pos = self.get_note_pos();
let note_name = format!("{:4}", Note::pitch_to_name(note_pos));
let note_name = format!("{:4}", note_pitch_to_name(note_pos));
let note_pos = format!("{:>3}", note_pos);
let note_len = format!("{:>4}", self.get_note_len());
Fixed::X(20, col!(
@ -242,30 +211,79 @@ impl MidiEditor {
}
}
/// A clip, rendered as a horizontal piano roll.
#[derive(Clone)]
pub struct PianoHorizontal {
pub clip: Option<Arc<RwLock<MidiClip>>>,
/// Buffer where the whole clip is rerendered on change
pub buffer: Arc<RwLock<BigBuffer>>,
/// Size of actual notes area
pub size: Measure<TuiOut>,
/// The display window
pub range: MidiRangeModel,
/// The note cursor
pub point: MidiPointModel,
/// The highlight color palette
pub color: ItemTheme,
/// Width of the keyboard
pub keys_width: u16,
impl TimeRange for MidiEditor {
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 time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
}
impl NoteRange for MidiEditor {
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
}
impl NotePoint for MidiEditor {
fn note_len (&self) -> &AtomicUsize { self.mode.note_len() }
fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() }
}
impl TimePoint for MidiEditor {
fn time_pos (&self) -> &AtomicUsize { self.mode.time_pos() }
}
impl MidiViewer for MidiEditor {
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) }
fn redraw (&self) { self.mode.redraw() }
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { self.mode.clip() }
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() }
fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) }
}
def_command!(MidiEditCommand: |editor: MidiEditor| {
Show { clip: Option<Arc<RwLock<MidiClip>>> } => {
editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) },
DeleteNote => {
editor.redraw(); todo!() },
AppendNote { advance: bool } => {
editor.put_note(*advance); editor.redraw(); Ok(None) },
SetNotePos { pos: usize } => {
editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) },
SetNoteLen { len: usize } => {
editor.set_note_len(*len); editor.redraw(); Ok(None) },
SetNoteScroll { scroll: usize } => {
editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) },
SetTimePos { pos: usize } => {
editor.set_time_pos(*pos); editor.redraw(); Ok(None) },
SetTimeScroll { scroll: usize } => {
editor.set_time_start(*scroll); editor.redraw(); Ok(None) },
SetTimeZoom { zoom: usize } => {
editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) },
SetTimeLock { lock: bool } => {
editor.set_time_lock(*lock); editor.redraw(); Ok(None) },
// TODO: 1-9 seek markers that by default start every 8th of the clip
});
impl Draw<TuiOut> for MidiEditor {
fn draw (&self, to: &mut TuiOut) { self.content().draw(to) }
}
impl Layout<TuiOut> for MidiEditor {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> { self.content().layout(to) }
}
impl HasContent<TuiOut> for MidiEditor {
fn content (&self) -> impl Content<TuiOut> { self.autoscroll(); /*self.autozoom();*/ self.size.of(&self.mode) }
}
has!(Measure<TuiOut>:|self:PianoHorizontal|self.size);
impl PianoHorizontal {
pub fn new (clip: Option<&Arc<RwLock<MidiClip>>>) -> Self {
let size = Measure::new(0, 0);
let mut range = MidiRangeModel::from((12, true));
let mut range = MidiSelection::from((12, true));
range.time_axis = size.x.clone();
range.note_axis = size.y.clone();
let piano = Self {
@ -273,7 +291,7 @@ impl PianoHorizontal {
size,
range,
buffer: RwLock::new(Default::default()).into(),
point: MidiPointModel::default(),
point: MidiCursor::default(),
clip: clip.cloned(),
color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]),
};
@ -454,9 +472,9 @@ impl PianoHorizontal {
continue
}
if note == note_pos {
to.blit(&format!("{:<5}", Note::pitch_to_name(note)), x, screen_y, on_style)
to.blit(&format!("{:<5}", note_pitch_to_name(note)), x, screen_y, on_style)
} else {
to.blit(&Note::pitch_to_name(note), x, screen_y, off_style)
to.blit(&note_pitch_to_name(note), x, screen_y, off_style)
};
}
})))
@ -542,7 +560,6 @@ fn to_key (note: usize) -> &'static str {
}
}
pub struct OctaveVertical { on: [bool; 12], colors: [Color; 3] }
impl Default for OctaveVertical {
fn default () -> Self {
Self { on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] }

View file

@ -1,5 +1,21 @@
use crate::*;
/// A scene consists of a set of clips to play together.
///
/// ```
/// let scene: tek_device::Scene = Default::default();
/// let _ = scene.pulses();
/// let _ = scene.is_playing(&[]);
/// ```
#[derive(Debug, Default)] pub struct Scene {
/// Name of scene
pub name: Arc<str>,
/// Identifying color of scene
pub color: ItemTheme,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
}
def_sizes_iter!(ScenesSizes => Scene);
impl<T: Has<Vec<Scene>> + Send + Sync> HasScenes for T {}
@ -143,16 +159,6 @@ pub trait ScenesView:
}
}
#[derive(Debug, Default)]
pub struct Scene {
/// Name of scene
pub name: Arc<str>,
/// Identifying color of scene
pub color: ItemTheme,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
}
impl Scene {
/// Returns the pulse length of the longest clip in the scene
pub fn pulses (&self) -> usize {

View file

@ -416,7 +416,7 @@ pub trait MidiViewer: Measured<TuiOut> + MidiRange + MidiPoint + Debug + Send +
let time_zoom = self.time_zoom().get();
let time_area = time_axis * time_zoom;
if time_area > time_len {
let next_time_zoom = NoteDuration::prev(time_zoom);
let next_time_zoom = note_duration_prev(time_zoom);
if next_time_zoom <= 1 {
break
}
@ -427,7 +427,7 @@ pub trait MidiViewer: Measured<TuiOut> + MidiRange + MidiPoint + Debug + Send +
break
}
} else if time_area < time_len {
let prev_time_zoom = NoteDuration::next(time_zoom);
let prev_time_zoom = note_duration_next(time_zoom);
if prev_time_zoom > 384 {
break
}

View file

@ -1,5 +1,23 @@
use crate::*;
/// A track consists of a sequencer and zero or more devices chained after it.
///
/// ```
/// let track: tek_device::Track = Default::default();
/// ```
#[derive(Debug, Default)] pub struct Track {
/// Name of track
pub name: Arc<str>,
/// Identifying color of track
pub color: ItemTheme,
/// Preferred width of track column
pub width: usize,
/// MIDI sequencer state
pub sequencer: Sequencer,
/// Device chain
pub devices: Vec<Device>,
}
def_sizes_iter!(TracksSizes => Track);
impl<T: Has<Vec<Track>> + Send + Sync> HasTracks for T {}
@ -72,20 +90,6 @@ impl<T: MaybeHas<Track>> HasTrack for T {
}
}
#[derive(Debug, Default)]
pub struct Track {
/// Name of track
pub name: Arc<str>,
/// Identifying color of track
pub color: ItemTheme,
/// Preferred width of track column
pub width: usize,
/// MIDI sequencer state
pub sequencer: Sequencer,
/// Device chain
pub devices: Vec<Device>,
}
has!(Clock: |self: Track|self.sequencer.clock);
has!(Sequencer: |self: Track|self.sequencer);
impl Track {

View file

@ -0,0 +1 @@
struct Vst3; // TODO