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 right_froms = PortConnection::collect(&cli.right_from, 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 size = Measure::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 {
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(
SamplerTui {
cursor: (0, 0),
@ -147,54 +130,63 @@ pub fn main () -> Usually<()> {
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({
let clip = default_clip();
let mut player = default_player(jack, Some(&clip))?;
player.clock = default_bpm(player.clock);
let sampler = default_sampler(jack)?;
jack.connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?;
let app = Groovebox {
_jack: jack.clone(),
player,
sampler,
pool: (&clip).into(),
editor: (&clip).into(),
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf,
size,
compact: true,
status: true,
};
app
App::groovebox(
jack, (&clip).into(), (&clip).into(),
Some(player), &midi_froms, &midi_tos,
sampler, &audio_froms, &audio_tos,
)
}))?)?,
TekMode::Arranger { scenes, tracks, track_width, .. } =>
engine.run(&jack.activate_with(|jack|Ok({
let clock = default_clock(jack);
let mut app = Arranger {
jack: jack.clone(),
midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
clock,
pool: PoolModel::default(),//(&clip).into(),
editor: MidiEditor::default(),//(&clip).into(),
selected: ArrangerSelection::Clip(0, 0),
scenes: vec![],
tracks: vec![],
splits: [12, 20],
midi_buf: vec![vec![];65536],
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
TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({
App::arranger(
jack,
PoolModel::default(),
MidiEditor::default(), &midi_froms, &midi_tos,
default_sampler(jack)?, &audio_froms, &audio_tos,
scenes, tracks, track_width
)
}))?)?,
//let clock = default_clock(jack);
//let mut app = Arranger {
//jack: jack.clone(),
//midi_ins: vec![JackPort::<MidiIn>::new(jack, format!("M/{name}"), &midi_froms)?,],
//midi_outs: vec![JackPort::<MidiOut>::new(jack, format!("{name}/M"), &midi_tos)?, ],
//clock,
//pool: PoolModel::default(),//(&clip).into(),
//editor: MidiEditor::default(),//(&clip).into(),
//selected: ArrangerSelection::Clip(0, 0),
//scenes: vec![],
//tracks: vec![],
//splits: [12, 20],
//midi_buf: vec![vec![];65536],
//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!()
})
}

View file

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

View file

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

View file

@ -77,18 +77,9 @@ impl NotePoint for SamplerTui {
fn note_point (&self) -> usize { self.note_pt.load(Relaxed) }
fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); }
}
pub struct SampleList<'a> {
compact: bool,
sampler: &'a Sampler,
editor: &'a MidiEditor
}
impl<'a> SampleList<'a> {
pub fn new (compact: bool, sampler: &'a Sampler, editor: &'a MidiEditor) -> Self {
Self { compact, sampler, editor }
}
}
render!(TuiOut: (self: SampleList<'a>) => {
let Self { compact, sampler, editor } = self;
impl Sampler {
const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)];
pub fn list <'a> (&'a self, compact: bool, editor: &MidiEditor) -> impl Content<TuiOut> + 'a {
let note_lo = editor.note_lo().load(Relaxed);
let note_pt = editor.note_point();
let note_hi = editor.note_hi();
@ -96,19 +87,23 @@ render!(TuiOut: (self: SampleList<'a>) => {
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() {
if self.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 let Some((index, _)) = self.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)
}
}
let label = if *compact {
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()
} else if let Some(sample) = &sampler.mapped[note] {
} else if let Some(sample) = &self.mapped[note] {
let sample = sample.read().unwrap();
format!("{:8} {:3} {:6}-{:6}/{:6}",
sample.name,
@ -119,12 +114,8 @@ render!(TuiOut: (self: SampleList<'a>) => {
)
} else {
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> {
let sample = if let Some((_, sample)) = &self.recording {
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;
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)
}
}

View file

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

View file

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