wip: unify apps

This commit is contained in:
🪞👃🪞 2025-01-11 20:16:46 +01:00
parent a9cad18891
commit cff87657b9
12 changed files with 291 additions and 247 deletions

157
tek/src/app.rs Normal file
View file

@ -0,0 +1,157 @@
use crate::*;
#[derive(Default)]
pub struct App {
//jack: Arc<RwLock<JackConnection>>,
//view: EdnView<TuiOut, &'a Self>,
//pool: Option<PoolModel>,
//editor: Option<MidiEditor>,
//player: Option<MidiPlayer>,
//compact: AtomicBool,
//size: Measure<TuiOut>,
//perf: PerfModel,
//note_buf: Vec<u8>,
//midi_buf: Vec<Vec<Vec<u8>>>
pub jack: Arc<RwLock<JackConnection>>,
pub edn: String,
pub clock: Clock,
pub color: ItemPalette,
pub editing: AtomicBool,
pub pool: Option<PoolModel>,
pub editor: Option<MidiEditor>,
pub player: Option<MidiPlayer>,
pub sampler: Option<Sampler>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub midi_ins: Vec<JackPort<MidiIn>>,
pub midi_outs: Vec<JackPort<MidiOut>>,
pub audio_ins: Vec<JackPort<AudioIn>>,
pub audio_outs: Vec<JackPort<AudioOut>>,
pub note_buf: Vec<u8>,
pub tracks: Vec<ArrangerTrack>,
pub scenes: Vec<ArrangerScene>,
pub selected: ArrangerSelection,
pub splits: Vec<u16>,
pub size: Measure<TuiOut>,
pub perf: PerfModel,
}
impl EdnViewData<TuiOut> for &App {}
impl App {
pub fn sequencer (
jack: &Arc<RwLock<JackConnection>>,
pool: PoolModel,
editor: MidiEditor,
player: Option<MidiPlayer>,
midi_froms: &[PortConnection],
midi_tos: &[PortConnection],
) -> Self {
Self {
edn: include_str!("sequencer.edn").to_string(),
jack: jack.clone(),
pool: Some(pool),
editor: Some(editor),
player: player,
editing: false.into(),
midi_buf: vec![vec![];65536],
color: ItemPalette::random(),
..Default::default()
}
}
pub fn groovebox (
jack: &Arc<RwLock<JackConnection>>,
pool: PoolModel,
editor: MidiEditor,
player: Option<MidiPlayer>,
midi_froms: &[PortConnection],
midi_tos: &[PortConnection],
sampler: Sampler,
audio_froms: &[&[PortConnection]],
audio_tos: &[&[PortConnection]],
) -> Self {
Self {
edn: include_str!("groovebox.edn").to_string(),
sampler: Some(sampler),
..Self::sequencer(
jack, pool, editor,
player, midi_froms, midi_tos
)
}
}
pub fn arranger (
jack: &Arc<RwLock<JackConnection>>,
pool: PoolModel,
editor: MidiEditor,
midi_froms: &[PortConnection],
midi_tos: &[PortConnection],
sampler: Sampler,
audio_froms: &[&[PortConnection]],
audio_tos: &[&[PortConnection]],
scenes: usize,
tracks: usize,
track_width: usize,
) -> Self {
Self {
edn: include_str!("arranger.edn").to_string(),
..Self::groovebox(
jack, pool, editor,
None, midi_froms, midi_tos,
sampler, audio_froms, audio_tos
)
}
}
}
render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref())));
/// Root view for standalone `tek_sequencer`.
pub struct Sequencer {
pub _jack: Arc<RwLock<JackConnection>>,
pub pool: PoolModel,
pub editor: MidiEditor,
pub player: MidiPlayer,
pub transport: bool,
pub selectors: bool,
pub compact: bool,
pub size: Measure<TuiOut>,
pub status: bool,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub perf: PerfModel,
}
pub struct Groovebox {
pub _jack: Arc<RwLock<JackConnection>>,
pub player: MidiPlayer,
pub pool: PoolModel,
pub editor: MidiEditor,
pub sampler: Sampler,
pub compact: bool,
pub size: Measure<TuiOut>,
pub status: bool,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub perf: PerfModel,
}
/// Root view for standalone `tek_arranger`
pub struct Arranger {
pub jack: Arc<RwLock<JackConnection>>,
pub midi_ins: Vec<JackPort<MidiIn>>,
pub midi_outs: Vec<JackPort<MidiOut>>,
pub clock: Clock,
pub pool: PoolModel,
pub tracks: Vec<ArrangerTrack>,
pub scenes: Vec<ArrangerScene>,
pub splits: [u16;2],
pub selected: ArrangerSelection,
pub color: ItemPalette,
pub size: Measure<TuiOut>,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub editor: MidiEditor,
pub editing: AtomicBool,
pub perf: PerfModel,
pub compact: bool,
}

View file

@ -5,26 +5,6 @@ mod arranger_track; pub use self::arranger_track::*;
mod arranger_h;
use ClockCommand::{Play, Pause};
use self::ArrangerCommand as Cmd;
/// Root view for standalone `tek_arranger`
pub struct Arranger {
pub jack: Arc<RwLock<JackConnection>>,
pub midi_ins: Vec<JackPort<MidiIn>>,
pub midi_outs: Vec<JackPort<MidiOut>>,
pub clock: Clock,
pub pool: PoolModel,
pub tracks: Vec<ArrangerTrack>,
pub scenes: Vec<ArrangerScene>,
pub splits: [u16;2],
pub selected: ArrangerSelection,
pub color: ItemPalette,
pub size: Measure<TuiOut>,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub editor: MidiEditor,
pub editing: AtomicBool,
pub perf: PerfModel,
pub compact: bool,
}
render!(TuiOut: (self: Arranger) => self.size.of(EdnView::from_source(self, Self::EDN)));
impl EdnViewData<TuiOut> for &Arranger {
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {

View file

@ -1,9 +1,9 @@
use crate::*;
#[derive(PartialEq, Clone, Copy, Debug)]
#[derive(PartialEq, Clone, Copy, Debug, Default)]
/// Represents the current user selection in the arranger
pub enum ArrangerSelection {
/// The whole mix is selected
Mix,
#[default] Mix,
/// A track is selected.
Track(usize),
/// A scene is selected.

View file

@ -7,28 +7,7 @@ use MidiEditCommand::*;
use MidiPoolCommand::*;
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
use std::marker::ConstParamTy;
pub struct Groovebox {
pub _jack: Arc<RwLock<JackConnection>>,
pub player: MidiPlayer,
pub pool: PoolModel,
pub editor: MidiEditor,
pub sampler: Sampler,
pub compact: bool,
pub size: Measure<TuiOut>,
pub status: bool,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub perf: PerfModel,
}
render!(TuiOut: (self: Groovebox) => self.size.of(EdnView::from_source(self, Self::EDN)));
// this works:
//render!(TuiOut: (self: Groovebox) => self.size.of(
//Bsp::s(self.toolbar_view(),
//Bsp::n(self.selector_view(),
//Bsp::n(self.sample_view(),
//Bsp::n(self.status_view(),
//Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor)))))))));
impl EdnViewData<TuiOut> for &Groovebox {
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {
use EdnItem::*;
@ -36,11 +15,11 @@ impl EdnViewData<TuiOut> for &Groovebox {
Nil => Box::new(()),
Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())),
Sym(":editor") => (&self.editor).boxed(),
Sym(":pool") => self.pool_view().boxed(),
Sym(":status") => self.status_view().boxed(),
Sym(":toolbar") => self.toolbar_view().boxed(),
Sym(":sampler") => self.sampler_view().boxed(),
Sym(":sample") => self.sample_view().boxed(),
Sym(":pool") => self.pool().boxed(),
Sym(":status") => self.status().boxed(),
Sym(":toolbar") => self.toolbar().boxed(),
Sym(":sampler") => self.sampler().boxed(),
Sym(":sample") => self.sample().boxed(),
_ => panic!("no content for {item:?}")
}
}
@ -60,14 +39,14 @@ impl EdnViewData<TuiOut> for &Groovebox {
}
impl Groovebox {
const EDN: &'static str = include_str!("groovebox.edn");
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
fn toolbar (&self) -> impl Content<TuiOut> + use<'_> {
Fill::x(Fixed::y(2, lay!(
Fill::x(Align::w(Meter("L/", self.sampler.input_meter[0]))),
Fill::x(Align::e(Meter("R/", self.sampler.input_meter[1]))),
Align::x(TransportView::new(true, &self.player.clock)),
)))
}
fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
fn status (&self) -> impl Content<TuiOut> + use<'_> {
row!(
self.player.play_status(),
self.player.next_status(),
@ -75,7 +54,7 @@ impl Groovebox {
self.editor.edit_status(),
)
}
fn sample_view (&self) -> impl Content<TuiOut> + use<'_> {
fn sample (&self) -> impl Content<TuiOut> + use<'_> {
let note_pt = self.editor.note_point();
let sample_h = if self.compact { 0 } else { 5 };
Max::y(sample_h, Fill::xy(
@ -83,21 +62,19 @@ impl Groovebox {
Fill::x(Align::w(Fixed::y(1, self.sampler.status(note_pt)))),
self.sampler.viewer(note_pt))))
}
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
let w = self.size.w();
let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
Fixed::x(if self.compact { 5 } else { pool_w },
PoolView(self.compact, &self.pool))
}
fn sampler_view (&self) -> impl Content<TuiOut> + use<'_> {
fn sampler (&self) -> impl Content<TuiOut> + use<'_> {
let note_pt = self.editor.note_point();
let sampler_w = if self.compact { 4 } else { 40 };
let sampler_y = if self.compact { 1 } else { 0 };
Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(
SampleList::new(self.compact, &self.sampler, &self.editor))))
Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(self.sampler.list(self.compact, &self.editor))))
}
}
audio!(|self: Groovebox, client, scope|{
let t0 = self.perf.get_t0();
if Control::Quit == ClockAudio(&mut self.player).process(client, scope) {
@ -130,7 +107,6 @@ audio!(|self: Groovebox, client, scope|{
self.perf.update(t0, scope);
Control::Continue
});
has_clock!(|self: Groovebox|self.player.clock());
pub enum GrooveboxCommand {
Compact(bool),
@ -141,7 +117,6 @@ pub enum GrooveboxCommand {
Enqueue(Option<Arc<RwLock<MidiClip>>>),
Sampler(SamplerCommand),
}
command!(|self: GrooveboxCommand, state: Groovebox|match self {
Self::Enqueue(clip) => {
state.player.enqueue_next(clip.as_ref());
@ -173,10 +148,7 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self {
None
},
});
handle!(TuiIn: |self: Groovebox, input|
GrooveboxCommand::execute_with_state(self, input.event()));
handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event()));
keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand {
// Tab: Toggle compact mode
key(Tab) => Cmd::Compact(!state.compact),
@ -268,21 +240,6 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand
//row!(&self.cpu, &self.size)
//}
//}
//render!(TuiOut: (self: Groovebox) => self.size.of(
//Bsp::s(self.toolbar_view(),
//Bsp::n(self.selector_view(),
//Bsp::n(self.sample_view(),
//Bsp::n(self.status_view(),
//Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor)))))))));
//const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn");
//impl Content<TuiOut> for Groovebox {
//fn content (&self) -> impl Content<TuiOut> {
//EdnView::parse(self.edn.as_slice())
//}
//}
//macro_rules! edn_context {
//($Struct:ident |$l:lifetime, $state:ident| {
//$($key:literal = $field:ident: $Type:ty => $expr:expr,)*
@ -328,17 +285,17 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand
//}
////impl Groovebox {
////fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
////fn status (&self) -> impl Content<TuiOut> + use<'_> {
////let note_pt = self.editor.note_point();
////Align::w(Fixed::y(1, ))
////}
////fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
////fn pool (&self) -> impl Content<TuiOut> + use<'_> {
////let w = self.size.w();
////let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
////Fixed::x(if self.compact { 5 } else { pool_w },
////)
////}
////fn sampler_view (&self) -> impl Content<TuiOut> + use<'_> {
////fn sampler (&self) -> impl Content<TuiOut> + use<'_> {
////let sampler_w = if self.compact { 4 } else { 11 };
////let sampler_y = if self.compact { 1 } else { 0 };
////Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(

View file

@ -10,6 +10,7 @@ pub type Usually<T> = std::result::Result<T, Box<dyn Error>>;
/// Standard optional result type.
pub type Perhaps<T> = std::result::Result<Option<T>, Box<dyn Error>>;
pub mod app; pub use self::app::*;
pub mod arranger; pub use self::arranger::*;
pub mod groovebox; pub use self::groovebox::*;
pub mod mixer; pub use self::mixer::*;

View file

@ -4,24 +4,6 @@ use KeyCode::{Tab, Char};
use SequencerCommand as Cmd;
use MidiEditCommand::*;
use MidiPoolCommand::*;
/// Root view for standalone `tek_sequencer`.
pub struct Sequencer {
pub _jack: Arc<RwLock<JackConnection>>,
pub pool: PoolModel,
pub editor: MidiEditor,
pub player: MidiPlayer,
pub transport: bool,
pub selectors: bool,
pub compact: bool,
pub size: Measure<TuiOut>,
pub status: bool,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub perf: PerfModel,
}
render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN)));
impl EdnViewData<TuiOut> for &Sequencer {
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {
@ -179,34 +161,3 @@ command!(|self: SequencerCommand, state: Sequencer|match self {
None
},
});
/// Status bar for sequencer app
#[derive(Clone)]
pub struct SequencerStatus {
pub(crate) width: usize,
pub(crate) cpu: Option<Arc<str>>,
pub(crate) size: Arc<str>,
pub(crate) playing: bool,
}
from!(|state:&Sequencer|SequencerStatus = {
let samples = state.clock().chunk.load(Relaxed);
let rate = state.clock().timebase.sr.get();
let buffer = samples as f64 / rate;
let width = state.size.w();
Self {
width,
playing: state.clock().is_rolling(),
cpu: state.perf.percentage().map(|cpu|format!("{cpu:.01}%").into()),
size: format!("{}x{}│", width, state.size.h()).into(),
}
});
render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!(
Sequencer::help(),
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
)));
impl SequencerStatus {
fn stats (&self) -> impl Content<TuiOut> + use<'_> {
row!(&self.cpu, &self.size)
}
}