refactor model into submodules again

This commit is contained in:
🪞👃🪞 2025-01-25 22:37:13 +01:00
parent deeaaa0b8b
commit 30f43eac79
5 changed files with 183 additions and 179 deletions

View file

@ -1,4 +1,7 @@
use crate::*;
mod scene; pub use self::scene::*;
mod select; pub use self::select::*;
mod track; pub use self::track::*;
#[derive(Default, Debug)] pub struct Tek {
/// Must not be dropped for the duration of the process
pub jack: Jack,
@ -182,162 +185,8 @@ impl Tek {
Tui::fg_bg(theme.lightest.rgb, theme.base.rgb, Fixed::y(1, field))
}
}
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
/// The whole mix is selected
#[default] Mix,
/// A track is selected.
Track(usize),
/// A scene is selected.
Scene(usize),
/// A clip (track × scene) is selected.
Clip(usize, usize),
}
/// Focus identification methods
impl Selection {
fn is_mix (&self) -> bool { matches!(self, Self::Mix) }
fn is_track (&self) -> bool { matches!(self, Self::Track(_)) }
fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) }
fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) }
pub fn track (&self) -> Option<usize> {
use Selection::*;
match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None }
}
pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None }
}
fn description (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
format!("Selected: {}", match self {
Self::Mix => "Everything".to_string(),
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
.unwrap_or_else(||"T??".into()),
Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
.unwrap_or_else(||"S??".into()),
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
(Some(_), Some(scene)) => match scene.clip(*t) {
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
None => format!("T{t} S{s}: Empty")
},
_ => format!("T{t} S{s}: Empty"),
}
}).into()
}
}
impl HasSelection for Tek {
fn selected (&self) -> &Selection { &self.selected }
fn selected_mut (&mut self) -> &mut Selection { &mut self.selected }
}
pub trait HasSelection {
fn selected (&self) -> &Selection;
fn selected_mut (&mut self) -> &mut Selection;
}
#[derive(Debug, Default)] pub struct Track {
/// Name of track
pub name: Arc<str>,
/// Preferred width of track column
pub width: usize,
/// Identifying color of track
pub color: ItemPalette,
/// MIDI player state
pub player: MidiPlayer,
/// Device chain
pub devices: Vec<Box<dyn Device>>,
/// Inputs of 1st device
pub audio_ins: Vec<JackAudioIn>,
/// Outputs of last device
pub audio_outs: Vec<JackAudioOut>,
}
has_clock!(|self: Track|self.player.clock);
has_player!(|self: Track|self.player);
impl Track {
const MIN_WIDTH: usize = 9;
fn width_inc (&mut self) { self.width += 1; }
fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } }
}
impl HasTracks for Tek {
fn midi_ins (&self) -> &Vec<JackMidiIn> { &self.midi_ins }
fn midi_outs (&self) -> &Vec<JackMidiOut> { &self.midi_outs }
fn tracks (&self) -> &Vec<Track> { &self.tracks }
fn tracks_mut (&mut self) -> &mut Vec<Track> { &mut self.tracks }
}
pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn midi_ins (&self) -> &Vec<JackMidiIn>;
fn midi_outs (&self) -> &Vec<JackMidiOut>;
fn tracks (&self) -> &Vec<Track>;
fn tracks_mut (&mut self) -> &mut Vec<Track>;
fn track_longest (&self) -> usize {
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
}
fn track_next_name (&self) -> Arc<str> {
format!("Track{:02}", self.tracks().len() + 1).into()
}
fn track (&self) -> Option<&Track> {
self.selected().track().and_then(|s|self.tracks().get(s))
}
fn track_mut (&mut self) -> Option<&mut Track> {
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
}
}
pub trait Device: Send + Sync + std::fmt::Debug {
fn boxed <'a> (self) -> Box<dyn Device + 'a> where Self: Sized + 'a { Box::new(self) }
}
impl Device for Sampler {}
impl Device for Plugin {}
#[derive(Debug, Default)] pub struct Scene {
/// Name of scene
pub name: Arc<str>,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
/// Identifying color of scene
pub color: ItemPalette,
}
impl Scene {
/// Returns the pulse length of the longest clip in the scene
fn pulses (&self) -> usize {
self.clips.iter().fold(0, |a, p|{
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
})
}
/// Returns true if all clips in the scene are
/// currently playing on the given collection of tracks.
fn is_playing (&self, tracks: &[Track]) -> bool {
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
.all(|(track_index, clip)|match clip {
Some(c) => tracks
.get(track_index)
.map(|track|{
if let Some((_, Some(clip))) = track.player().play_clip() {
*clip.read().unwrap() == *c.read().unwrap()
} else {
false
}
})
.unwrap_or(false),
None => true
})
}
fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
}
}
impl HasScenes for Tek {
fn scenes (&self) -> &Vec<Scene> { &self.scenes }
fn scenes_mut (&mut self) -> &mut Vec<Scene> { &mut self.scenes }
}
pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
fn scenes (&self) -> &Vec<Scene>;
fn scenes_mut (&mut self) -> &mut Vec<Scene>;
fn scene_longest (&self) -> usize {
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
}
fn scene (&self) -> Option<&Scene> {
self.selected().scene().and_then(|s|self.scenes().get(s))
}
fn scene_mut (&mut self) -> Option<&mut Scene> {
self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s))
}
fn scene_del (&mut self, index: usize) {
self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index)));
}
}

58
tek/src/model/scene.rs Normal file
View file

@ -0,0 +1,58 @@
use crate::*;
pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
fn scenes (&self) -> &Vec<Scene>;
fn scenes_mut (&mut self) -> &mut Vec<Scene>;
fn scene_longest (&self) -> usize {
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
}
fn scene (&self) -> Option<&Scene> {
self.selected().scene().and_then(|s|self.scenes().get(s))
}
fn scene_mut (&mut self) -> Option<&mut Scene> {
self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s))
}
fn scene_del (&mut self, index: usize) {
self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index)));
}
}
#[derive(Debug, Default)] pub struct Scene {
/// Name of scene
pub name: Arc<str>,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
/// Identifying color of scene
pub color: ItemPalette,
}
impl Scene {
/// Returns the pulse length of the longest clip in the scene
fn pulses (&self) -> usize {
self.clips.iter().fold(0, |a, p|{
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
})
}
/// Returns true if all clips in the scene are
/// currently playing on the given collection of tracks.
fn is_playing (&self, tracks: &[Track]) -> bool {
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
.all(|(track_index, clip)|match clip {
Some(c) => tracks
.get(track_index)
.map(|track|{
if let Some((_, Some(clip))) = track.player().play_clip() {
*clip.read().unwrap() == *c.read().unwrap()
} else {
false
}
})
.unwrap_or(false),
None => true
})
}
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
}
}
impl HasScenes for Tek {
fn scenes (&self) -> &Vec<Scene> { &self.scenes }
fn scenes_mut (&mut self) -> &mut Vec<Scene> { &mut self.scenes }
}

51
tek/src/model/select.rs Normal file
View file

@ -0,0 +1,51 @@
use crate::*;
pub trait HasSelection {
fn selected (&self) -> &Selection;
fn selected_mut (&mut self) -> &mut Selection;
}
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
/// The whole mix is selected
#[default] Mix,
/// A track is selected.
Track(usize),
/// A scene is selected.
Scene(usize),
/// A clip (track × scene) is selected.
Clip(usize, usize),
}
/// Focus identification methods
impl Selection {
fn is_mix (&self) -> bool { matches!(self, Self::Mix) }
fn is_track (&self) -> bool { matches!(self, Self::Track(_)) }
fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) }
fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) }
pub fn track (&self) -> Option<usize> {
use Selection::*;
match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None }
}
pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None }
}
fn description (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
format!("Selected: {}", match self {
Self::Mix => "Everything".to_string(),
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
.unwrap_or_else(||"T??".into()),
Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
.unwrap_or_else(||"S??".into()),
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
(Some(_), Some(scene)) => match scene.clip(*t) {
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
None => format!("T{t} S{s}: Empty")
},
_ => format!("T{t} S{s}: Empty"),
}
}).into()
}
}
impl HasSelection for Tek {
fn selected (&self) -> &Selection { &self.selected }
fn selected_mut (&mut self) -> &mut Selection { &mut self.selected }
}

49
tek/src/model/track.rs Normal file
View file

@ -0,0 +1,49 @@
use crate::*;
pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn midi_ins (&self) -> &Vec<JackMidiIn>;
fn midi_outs (&self) -> &Vec<JackMidiOut>;
fn tracks (&self) -> &Vec<Track>;
fn tracks_mut (&mut self) -> &mut Vec<Track>;
fn track_longest (&self) -> usize {
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
}
const WIDTH_OFFSET: usize = 1;
fn track_next_name (&self) -> Arc<str> {
format!("Track{:02}", self.tracks().len() + 1).into()
}
fn track (&self) -> Option<&Track> {
self.selected().track().and_then(|s|self.tracks().get(s))
}
fn track_mut (&mut self) -> Option<&mut Track> {
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
}
}
#[derive(Debug, Default)] pub struct Track {
/// Name of track
pub name: Arc<str>,
/// Preferred width of track column
pub width: usize,
/// Identifying color of track
pub color: ItemPalette,
/// MIDI player state
pub player: MidiPlayer,
/// Device chain
pub devices: Vec<Box<dyn Device>>,
/// Inputs of 1st device
pub audio_ins: Vec<JackAudioIn>,
/// Outputs of last device
pub audio_outs: Vec<JackAudioOut>,
}
has_clock!(|self: Track|self.player.clock);
has_player!(|self: Track|self.player);
impl Track {
const MIN_WIDTH: usize = 9;
fn width_inc (&mut self) { self.width += 1; }
fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } }
}
impl HasTracks for Tek {
fn midi_ins (&self) -> &Vec<JackMidiIn> { &self.midi_ins }
fn midi_outs (&self) -> &Vec<JackMidiOut> { &self.midi_outs }
fn tracks (&self) -> &Vec<Track> { &self.tracks }
fn tracks_mut (&mut self) -> &mut Vec<Track> { &mut self.tracks }
}

View file

@ -67,7 +67,6 @@ view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
":scenes" => self.view_scenes().boxed(),
":status" => self.view_editor().boxed(),
":toolbar" => self.view_clock().boxed(),
":track-add" => self.view_track_add().boxed(),
":tracks" => self.view_tracks().boxed(),
});
provide_num!(u16: |self: Tek| {
@ -291,12 +290,6 @@ impl Tek {
let content = Fixed::y(1, f(index, track));
let styled = Tui::fg_bg(track.color.lightest.rgb, track.color.base.rgb, content);
map_east(x1 as u16, width, Fixed::x(width, styled)) }) }
fn io_heading <'a, T: PortsSizes<'a>> (
&'a self, key: &'a str, label: &'a str, count: usize,
fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a,
) -> impl Content<TuiOut> + 'a {
self.heading(key, label, count, self.io_ports(fg, bg, iter))
}
fn io_ports <'a, T: PortsSizes<'a>> (
&'a self, fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
@ -305,7 +298,7 @@ impl Tek {
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))),
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
&connect.info[..(self.w_sidebar() as usize).min(connect.info.len())])))))))))}
&connect.info)))))))))}
fn io_connections <'a, T: PortsSizes<'a>> (
&'a self, fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
@ -351,10 +344,15 @@ impl Tek {
// TRACKS /////////////////////////////////////////////////////////////////////////////////////
fn view_tracks (&self) -> impl Content<TuiOut> + use<'_> {
let height = 3;
let heading: ThunkBox<_> =
(move||Tui::bg(Tui::g(32), Fill::x(Align::w(self.view_track_add()))).boxed()).into();
let content = self.per_track(|t, track|{
self.row(self.w(), 3, Tui::bg(Tui::g(32), Fill::x(Align::w({
let data = (self.selected.track().unwrap_or(0), self.tracks().len());
self.fmtd.write().unwrap().trks.update(Some(data),
rewrite!(buf, "({}/{})", data.0, data.1));
Bsp::e(
self.button3(" t", "track", self.fmtd.read().unwrap().trks.view.clone()),
self.button2(" T", "add track"),
)
}))), self.per_track(|t, track|{
let active = self.selected().track() == Some(t+1);
let name = &track.name;
let fg = track.color.lightest.rgb;
@ -362,14 +360,7 @@ impl Tek {
let bg2 = Reset;//if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset };
let bfg = if active { Rgb(255,255,255) } else { Rgb(0,0,0) };
Self::wrap(bg, fg, Tui::bold(true, Fill::x(Align::nw(name))))
});
self.row(self.w(), height, heading, content)
}
fn view_track_add (&self) -> impl Content<TuiOut> + use<'_> {
let data = (self.selected.track().unwrap_or(0), self.tracks().len());
self.fmtd.write().unwrap().trks.update(Some(data),
rewrite!(buf, "({}/{})", data.0, data.1));
self.button3(" T", "track", self.fmtd.read().unwrap().trks.view.clone())
}))
}
// ACTIVE CLIPS ///////////////////////////////////////////////////////////////////////////////
@ -392,13 +383,16 @@ impl Tek {
// INPUTS /////////////////////////////////////////////////////////////////////////////////////
fn view_inputs (&self) -> impl Content<TuiOut> + use<'_> {
let fg = Tui::g(224);
let key = " I";
let key = " i";
let label = "midi ins";
let count = self.midi_ins.len();
Bsp::s(
Bsp::s(
self.row_top(self.w(), 1,
Fill::x(Align::w(self.button3(key, label, format!("{count}")))),
Fill::x(Align::w(Bsp::e(
self.button3(" i", "midi ins", format!("{count}")),
self.button2(" I", "add midi in"),
))),
self.per_track_top(move|t, track|{
let rec = track.player.recording;
let mon = track.player.monitoring;
@ -412,7 +406,7 @@ impl Tek {
let bg2 = if t > 0 { self.tracks()[t - 1].color.base.rgb } else { Reset };
Self::wrap(bg, fg, Tui::bold(true, Fill::x(Bsp::e(
Tui::fg_bg(rec, bg, "Rec "),
Tui::fg_bg(rec, bg, "Mon ")))))})),
Tui::fg_bg(mon, bg, "Mon ")))))})),
self.row_top(self.w(), self.h_inputs() - 1,
self.io_ports(fg, Tui::g(32), ||self.inputs_sizes()),
self.per_track_top(move|t, track|self.io_connections(
@ -428,7 +422,7 @@ impl Tek {
// OUTPUTS ////////////////////////////////////////////////////////////////////////////////////
fn view_outputs (&self) -> impl Content<TuiOut> + use<'_> {
let fg = Tui::g(224);
let key = " O";
let key = " o";
let label = "midi outs";
let count = self.midi_outs.len();
Align::n(Bsp::s(
@ -438,7 +432,10 @@ impl Tek {
),
Bsp::s(
self.row_top(self.w(), 1,
Fill::x(Align::w(self.button3(key, label, format!("{count}")))),
Fill::x(Align::w(Bsp::e(
self.button3(" o", "midi outs", format!("{count}")),
self.button2(" O", "add midi out"),
))),
self.per_track_top(move|t, track|{
let mute = false;
let solo = false;