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

View file

@ -76,6 +76,8 @@ pub fn main () -> Usually<()> {
let left_tos = PortConnection::collect(&cli.left_to, empty, empty); let left_tos = PortConnection::collect(&cli.left_to, empty, empty);
let right_froms = PortConnection::collect(&cli.right_from, empty, empty); let right_froms = PortConnection::collect(&cli.right_from, empty, empty);
let right_tos = PortConnection::collect(&cli.right_to, empty, empty); let right_tos = PortConnection::collect(&cli.right_to, empty, empty);
let audio_froms = &[&left_froms, &right_froms];
let audio_tos = &[&left_tos, &right_tos ];
let perf = PerfModel::default(); let perf = PerfModel::default();
let size = Measure::new(); let size = Measure::new();
let default_clip = ||Arc::new(RwLock::new(MidiClip::new( let default_clip = ||Arc::new(RwLock::new(MidiClip::new(
@ -116,25 +118,6 @@ pub fn main () -> Usually<()> {
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui { TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui {
jack: jack.clone(), clock: default_clock(jack), jack: jack.clone(), clock: default_clock(jack),
}))?)?, }))?)?,
TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({
let clip = default_clip();
let mut player = default_player(jack, Some(&clip))?;
player.clock = default_bpm(player.clock);
Sequencer {
_jack: jack.clone(),
player,
pool: (&clip).into(),
editor: (&clip).into(),
midi_buf: vec![vec![];65536],
note_buf: vec![],
status: true,
perf,
size,
compact: true,
transport: true,
selectors: true,
}
}))?)?,
TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok( TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
SamplerTui { SamplerTui {
cursor: (0, 0), cursor: (0, 0),
@ -147,53 +130,62 @@ pub fn main () -> Usually<()> {
size, size,
} }
))?)?, ))?)?,
TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({
let clip = default_clip();
let mut player = default_player(jack, Some(&clip))?;
player.clock = default_bpm(player.clock);
App::sequencer(
jack, (&clip).into(), (&clip).into(),
Some(player), &midi_froms, &midi_tos,
)
}))?)?,
TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({ TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({
let clip = default_clip(); let clip = default_clip();
let mut player = default_player(jack, Some(&clip))?; let mut player = default_player(jack, Some(&clip))?;
player.clock = default_bpm(player.clock); player.clock = default_bpm(player.clock);
let sampler = default_sampler(jack)?; let sampler = default_sampler(jack)?;
jack.connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?; jack.connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?;
let app = Groovebox { App::groovebox(
_jack: jack.clone(), jack, (&clip).into(), (&clip).into(),
player, Some(player), &midi_froms, &midi_tos,
sampler, sampler, &audio_froms, &audio_tos,
pool: (&clip).into(), )
editor: (&clip).into(),
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf,
size,
compact: true,
status: true,
};
app
}))?)?, }))?)?,
TekMode::Arranger { scenes, tracks, track_width, .. } => TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({
engine.run(&jack.activate_with(|jack|Ok({ App::arranger(
let clock = default_clock(jack); jack,
let mut app = Arranger { PoolModel::default(),
jack: jack.clone(), MidiEditor::default(), &midi_froms, &midi_tos,
midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,], default_sampler(jack)?, &audio_froms, &audio_tos,
midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ], scenes, tracks, track_width
clock, )
pool: PoolModel::default(),//(&clip).into(), }))?)?,
editor: MidiEditor::default(),//(&clip).into(),
selected: ArrangerSelection::Clip(0, 0),
scenes: vec![], //let clock = default_clock(jack);
tracks: vec![], //let mut app = Arranger {
splits: [12, 20], //jack: jack.clone(),
midi_buf: vec![vec![];65536], //midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
note_buf: vec![], //midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
compact: false, //clock,
editing: true.into(), //pool: PoolModel::default(),//(&clip).into(),
color, //editor: MidiEditor::default(),//(&clip).into(),
perf, //selected: ArrangerSelection::Clip(0, 0),
size, //scenes: vec![],
}; //tracks: vec![],
app.tracks_add(tracks, track_width, &midi_froms, &midi_tos)?; //splits: [12, 20],
app.scenes_add(scenes)?; //midi_buf: vec![vec![];65536],
app //note_buf: vec![],
}))?)?, //compact: false,
//editing: true.into(),
//color,
//perf,
//size,
//};
//app.tracks_add(tracks, track_width, &midi_froms, &midi_tos)?;
//app.scenes_add(scenes)?;
//app
//}))?)?,
_ => todo!() _ => todo!()
}) })

View file

@ -21,8 +21,10 @@ pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioH
/// This is a connection which may be `Inactive`, `Activating`, or `Active`. /// This is a connection which may be `Inactive`, `Activating`, or `Active`.
/// In the `Active` and `Inactive` states, its `client` method returns a /// In the `Active` and `Inactive` states, its `client` method returns a
/// [Client] which you can use to talk to the JACK API. /// [Client] which you can use to talk to the JACK API.
#[derive(Debug)] #[derive(Debug, Default)]
pub enum JackConnection { pub enum JackConnection {
#[default]
Inert,
/// Before activation. /// Before activation.
Inactive(Client), Inactive(Client),
/// During activation. /// During activation.
@ -35,6 +37,7 @@ impl From<JackConnection> for Client {
fn from (jack: JackConnection) -> Self { fn from (jack: JackConnection) -> Self {
match jack { match jack {
JackConnection::Inactive(client) => client, JackConnection::Inactive(client) => client,
JackConnection::Inert => panic!("jack client not activated"),
JackConnection::Activating => panic!("jack client still activating"), JackConnection::Activating => panic!("jack client still activating"),
JackConnection::Active(_) => panic!("jack client already activated"), JackConnection::Active(_) => panic!("jack client already activated"),
} }
@ -49,6 +52,7 @@ impl JackConnection {
/// Return the internal [Client] handle that lets you call the JACK API. /// Return the internal [Client] handle that lets you call the JACK API.
pub fn client (&self) -> &Client { pub fn client (&self) -> &Client {
match self { match self {
Self::Inert => panic!("jack client not activated"),
Self::Inactive(ref client) => client, Self::Inactive(ref client) => client,
Self::Activating => panic!("jack client has not finished activation"), Self::Activating => panic!("jack client has not finished activation"),
Self::Active(ref client) => client.as_client(), Self::Active(ref client) => client.as_client(),

View file

@ -25,7 +25,10 @@ pub trait EdnViewData<E: Output> {
} }
/// Renders from EDN source and context. /// Renders from EDN source and context.
#[derive(Default)]
pub enum EdnView<E: Output, T: EdnViewData<E>> { pub enum EdnView<E: Output, T: EdnViewData<E>> {
#[default]
Inert,
_Unused(PhantomData<E>), _Unused(PhantomData<E>),
Ok(T, EdnItem<String>), Ok(T, EdnItem<String>),
//render: Box<dyn Fn(&'a T)->Box<dyn Render<E> + Send + Sync + 'a> + Send + Sync + 'a> //render: Box<dyn Fn(&'a T)->Box<dyn Render<E> + Send + Sync + 'a> + Send + Sync + 'a>

View file

@ -77,38 +77,33 @@ impl NotePoint for SamplerTui {
fn note_point (&self) -> usize { self.note_pt.load(Relaxed) } fn note_point (&self) -> usize { self.note_pt.load(Relaxed) }
fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); } fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); }
} }
pub struct SampleList<'a> { impl Sampler {
compact: bool, const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
sampler: &'a Sampler, pub fn list <'a> (&'a self, compact: bool, editor: &MidiEditor) -> impl Content<TuiOut> + 'a {
editor: &'a MidiEditor let note_lo = editor.note_lo().load(Relaxed);
} let note_pt = editor.note_point();
impl<'a> SampleList<'a> { let note_hi = editor.note_hi();
pub fn new (compact: bool, sampler: &'a Sampler, editor: &'a MidiEditor) -> Self { Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| {
Self { compact, sampler, editor } let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
} let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
} let mut fg = TuiTheme::g(160);
render!(TuiOut: (self: SampleList<'a>) => { if self.mapped[note].is_some() {
let Self { compact, sampler, editor } = self; fg = TuiTheme::g(224);
let note_lo = editor.note_lo().load(Relaxed); bg = Color::Rgb(0, if note == note_pt { 96 } else { 64 }, 0);
let note_pt = editor.note_point();
let note_hi = editor.note_hi();
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| {
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
let mut fg = TuiTheme::g(160);
if sampler.mapped[note].is_some() {
fg = TuiTheme::g(224);
bg = Color::Rgb(0, if note == note_pt { 96 } else { 64 }, 0);
}
if let Some((index, _)) = sampler.recording {
if note == index {
bg = if note == note_pt { Color::Rgb(96,24,0) } else { Color::Rgb(64,16,0) };
fg = Color::Rgb(224,64,32)
} }
} if let Some((index, _)) = self.recording {
let label = if *compact { if note == index {
bg = if note == note_pt { Color::Rgb(96,24,0) } else { Color::Rgb(64,16,0) };
fg = Color::Rgb(224,64,32)
}
}
offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", self.list_item(note, compact))))
}))
}
pub fn list_item (&self, note: usize, compact: bool) -> String {
if compact {
String::default() String::default()
} else if let Some(sample) = &sampler.mapped[note] { } else if let Some(sample) = &self.mapped[note] {
let sample = sample.read().unwrap(); let sample = sample.read().unwrap();
format!("{:8} {:3} {:6}-{:6}/{:6}", format!("{:8} {:3} {:6}-{:6}/{:6}",
sample.name, sample.name,
@ -119,12 +114,8 @@ render!(TuiOut: (self: SampleList<'a>) => {
) )
} else { } else {
String::from("(none)") String::from("(none)")
}; }
offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", label))) }
}))
});
impl Sampler {
const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
pub fn viewer (&self, note_pt: usize) -> impl Content<TuiOut> { pub fn viewer (&self, note_pt: usize) -> impl Content<TuiOut> {
let sample = if let Some((_, sample)) = &self.recording { let sample = if let Some((_, sample)) = &self.recording {
Some(sample.clone()) Some(sample.clone())

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; mod arranger_h;
use ClockCommand::{Play, Pause}; use ClockCommand::{Play, Pause};
use self::ArrangerCommand as Cmd; 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))); render!(TuiOut: (self: Arranger) => self.size.of(EdnView::from_source(self, Self::EDN)));
impl EdnViewData<TuiOut> for &Arranger { impl EdnViewData<TuiOut> for &Arranger {
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> {

View file

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

View file

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

View file

@ -4,24 +4,6 @@ use KeyCode::{Tab, Char};
use SequencerCommand as Cmd; use SequencerCommand as Cmd;
use MidiEditCommand::*; use MidiEditCommand::*;
use MidiPoolCommand::*; 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))); render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN)));
impl EdnViewData<TuiOut> for &Sequencer { impl EdnViewData<TuiOut> for &Sequencer {
fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { 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 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)
}
}

View file

@ -72,10 +72,10 @@ impl<T: HasClock> Command<T> for ClockCommand {
} }
} }
#[derive(Clone)] #[derive(Clone, Default)]
pub struct Clock { pub struct Clock {
/// JACK transport handle. /// JACK transport handle.
pub transport: Arc<Transport>, pub transport: Arc<Option<Transport>>,
/// Global temporal resolution (shared by [Moment] fields) /// Global temporal resolution (shared by [Moment] fields)
pub timebase: Arc<Timebase>, pub timebase: Arc<Timebase>,
/// Current global sample and usec (monotonic from JACK clock) /// Current global sample and usec (monotonic from JACK clock)
@ -102,7 +102,7 @@ from!(|jack: &Arc<RwLock<JackConnection>>| Clock = {
Self { Self {
quant: Arc::new(24.into()), quant: Arc::new(24.into()),
sync: Arc::new(384.into()), sync: Arc::new(384.into()),
transport: Arc::new(transport), transport: Arc::new(Some(transport)),
chunk: Arc::new((chunk as usize).into()), chunk: Arc::new((chunk as usize).into()),
global: Arc::new(Moment::zero(&timebase)), global: Arc::new(Moment::zero(&timebase)),
playhead: Arc::new(Moment::zero(&timebase)), playhead: Arc::new(Moment::zero(&timebase)),
@ -154,17 +154,21 @@ impl Clock {
} }
/// Start playing, optionally seeking to a given location beforehand /// Start playing, optionally seeking to a given location beforehand
pub fn play_from (&self, start: Option<u32>) -> Usually<()> { pub fn play_from (&self, start: Option<u32>) -> Usually<()> {
if let Some(start) = start { if let Some(transport) = self.transport.as_ref() {
self.transport.locate(start)?; if let Some(start) = start {
transport.locate(start)?;
}
transport.start()?;
} }
self.transport.start()?;
Ok(()) Ok(())
} }
/// Pause, optionally seeking to a given location afterwards /// Pause, optionally seeking to a given location afterwards
pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> { pub fn pause_at (&self, pause: Option<u32>) -> Usually<()> {
self.transport.stop()?; if let Some(transport) = self.transport.as_ref() {
if let Some(pause) = pause { transport.stop()?;
self.transport.locate(pause)?; if let Some(pause) = pause {
transport.locate(pause)?;
}
} }
Ok(()) Ok(())
} }
@ -189,21 +193,24 @@ impl Clock {
self.global.sample.set(current_frames as f64); self.global.sample.set(current_frames as f64);
self.global.usec.set(current_usecs as f64); self.global.usec.set(current_usecs as f64);
let mut started = self.started.write().unwrap();
// If transport has just started or just stopped, // If transport has just started or just stopped,
// update starting point: // update starting point:
let mut started = self.started.write().unwrap(); if let Some(transport) = self.transport.as_ref() {
match (self.transport.query_state()?, started.as_ref()) { match (transport.query_state()?, started.as_ref()) {
(TransportState::Rolling, None) => { (TransportState::Rolling, None) => {
let moment = Moment::zero(&self.timebase); let moment = Moment::zero(&self.timebase);
moment.sample.set(current_frames as f64); moment.sample.set(current_frames as f64);
moment.usec.set(current_usecs as f64); moment.usec.set(current_usecs as f64);
*started = Some(moment); *started = Some(moment);
}, },
(TransportState::Stopped, Some(_)) => { (TransportState::Stopped, Some(_)) => {
*started = None; *started = None;
}, },
_ => {} _ => {}
}; };
}
self.playhead.update_from_sample(started.as_ref() self.playhead.update_from_sample(started.as_ref()
.map(|started|current_frames as f64 - started.sample.get()) .map(|started|current_frames as f64 - started.sample.get())

View file

@ -1,5 +1,6 @@
use crate::*; use crate::*;
#[derive(Default)]
pub struct TuiOut { pub struct TuiOut {
pub buffer: Buffer, pub buffer: Buffer,
pub area: [u16;4] pub area: [u16;4]