mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
perf counter for render
This commit is contained in:
parent
c9677c87d8
commit
1b7f0e0b93
17 changed files with 331 additions and 358 deletions
|
|
@ -1821,3 +1821,29 @@ from_edn!("mixer/track" => |jack: &Arc<RwLock<JackConnection>>, args| -> MixerTr
|
||||||
////})
|
////})
|
||||||
////}
|
////}
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand {
|
||||||
|
//key(Char(',')) => SetBpm(state.bpm().get() - 1.0),
|
||||||
|
//key(Char('.')) => SetBpm(state.bpm().get() + 1.0),
|
||||||
|
//key(Char('<')) => SetBpm(state.bpm().get() - 0.001),
|
||||||
|
//key(Char('>')) => SetBpm(state.bpm().get() + 0.001),
|
||||||
|
//});
|
||||||
|
//keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand {
|
||||||
|
//key(Char(',')) => SetQuant(state.quant.prev()),
|
||||||
|
//key(Char('.')) => SetQuant(state.quant.next()),
|
||||||
|
//key(Char('<')) => SetQuant(state.quant.prev()),
|
||||||
|
//key(Char('>')) => SetQuant(state.quant.next()),
|
||||||
|
//});
|
||||||
|
//keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand {
|
||||||
|
//key(Char(',')) => SetSync(state.sync.prev()),
|
||||||
|
//key(Char('.')) => SetSync(state.sync.next()),
|
||||||
|
//key(Char('<')) => SetSync(state.sync.prev()),
|
||||||
|
//key(Char('>')) => SetSync(state.sync.next()),
|
||||||
|
//});
|
||||||
|
//keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand {
|
||||||
|
//key(Char(',')) => todo!("transport seek bar"),
|
||||||
|
//key(Char('.')) => todo!("transport seek bar"),
|
||||||
|
//key(Char('<')) => todo!("transport seek beat"),
|
||||||
|
//key(Char('>')) => todo!("transport seek beat"),
|
||||||
|
//});
|
||||||
|
|
|
||||||
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -1507,8 +1507,9 @@ version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic_float",
|
"atomic_float",
|
||||||
"quanta",
|
"quanta",
|
||||||
|
"tek_edn",
|
||||||
|
"tek_input",
|
||||||
"tek_jack",
|
"tek_jack",
|
||||||
"tek_tui",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1523,6 +1524,7 @@ dependencies = [
|
||||||
"tek_edn",
|
"tek_edn",
|
||||||
"tek_input",
|
"tek_input",
|
||||||
"tek_output",
|
"tek_output",
|
||||||
|
"tek_time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
126
cli/tek.rs
126
cli/tek.rs
|
|
@ -1,10 +1,7 @@
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use tek::*;
|
use tek::*;
|
||||||
#[allow(unused_imports)] use clap::{self, Parser, Subcommand, ValueEnum};
|
use clap::{self, Parser, Subcommand};
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
pub struct TekCli {
|
#[derive(Debug, Parser)] pub struct TekCli {
|
||||||
/// Which app to initialize
|
/// Which app to initialize
|
||||||
#[command(subcommand)] mode: TekMode,
|
#[command(subcommand)] mode: TekMode,
|
||||||
/// Name of JACK client
|
/// Name of JACK client
|
||||||
|
|
@ -34,9 +31,7 @@ pub struct TekCli {
|
||||||
/// Audio ins to connect from right output
|
/// Audio ins to connect from right output
|
||||||
#[arg(short='R', long)] right_to: Vec<String>,
|
#[arg(short='R', long)] right_to: Vec<String>,
|
||||||
}
|
}
|
||||||
|
#[derive(Debug, Clone, Subcommand)] pub enum TekMode {
|
||||||
#[derive(Debug, Clone, Subcommand)]
|
|
||||||
pub enum TekMode {
|
|
||||||
/// A standalone transport clock.
|
/// A standalone transport clock.
|
||||||
Clock,
|
Clock,
|
||||||
/// A MIDI sequencer.
|
/// A MIDI sequencer.
|
||||||
|
|
@ -61,7 +56,6 @@ pub enum TekMode {
|
||||||
/// TODO: An audio plugin host
|
/// TODO: An audio plugin host
|
||||||
Plugin,
|
Plugin,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Application entrypoint.
|
/// Application entrypoint.
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
let cli = TekCli::parse();
|
let cli = TekCli::parse();
|
||||||
|
|
@ -78,114 +72,38 @@ pub fn main () -> Usually<()> {
|
||||||
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.as_slice(), right_froms.as_slice()];
|
let audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
||||||
let audio_tos = &[left_tos.as_slice(), right_tos.as_slice() ];
|
let audio_tos = &[left_tos.as_slice(), right_tos.as_slice() ];
|
||||||
let default_clip = ||Arc::new(RwLock::new(MidiClip::new(
|
|
||||||
"Clip", true, 384usize, None, Some(ItemColor::random().into()))));
|
|
||||||
let default_player = |jack: &Arc<RwLock<JackConnection>>, clip: Option<&Arc<RwLock<MidiClip>>>|
|
|
||||||
MidiPlayer::new(&jack, name, clip, &midi_froms, &midi_tos);
|
|
||||||
let default_sampler = |jack: &Arc<RwLock<JackConnection>>|
|
|
||||||
Sampler::new(jack, &"sampler", &midi_froms,
|
|
||||||
&[&left_froms, &right_froms], &[&left_tos, &right_tos]);
|
|
||||||
let default_bpm = |clock: Clock|{
|
|
||||||
if let Some(bpm) = cli.bpm {
|
|
||||||
clock.timebase.bpm.set(bpm);
|
|
||||||
}
|
|
||||||
clock
|
|
||||||
};
|
|
||||||
let default_clock = |jack: &Arc<RwLock<JackConnection>>|default_bpm(Clock::from(jack));
|
|
||||||
// TODO: enable sync master/follow
|
|
||||||
//let sync_clock = |jack: &Arc<RwLock<JackConnection>>, app|{
|
|
||||||
//if cli.sync_lead {
|
|
||||||
//jack.read().unwrap().client().register_timebase_callback(false, |mut state|{
|
|
||||||
//app.clock().playhead.update_from_sample(state.position.frame() as f64);
|
|
||||||
//state.position.bbt = Some(app.clock().bbt());
|
|
||||||
//state.position
|
|
||||||
//})
|
|
||||||
//} else if cli.sync_follow {
|
|
||||||
//jack.read().unwrap().client().register_timebase_callback(false, |state|{
|
|
||||||
//app.clock().playhead.update_from_sample(state.position.frame() as f64);
|
|
||||||
//state.position
|
|
||||||
//})
|
|
||||||
//} else {
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
//};
|
|
||||||
Ok(match cli.mode {
|
Ok(match cli.mode {
|
||||||
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(ClockTui {
|
|
||||||
jack: jack.clone(), clock: default_clock(jack),
|
|
||||||
}))?)?,
|
|
||||||
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),
|
||||||
editing: None,
|
editing: None,
|
||||||
mode: None,
|
mode: None,
|
||||||
note_lo: 36.into(),
|
note_lo: 36.into(),
|
||||||
note_pt: 36.into(),
|
note_pt: 36.into(),
|
||||||
state: default_sampler(jack)?,
|
size: Measure::new(),
|
||||||
|
state: Sampler::new(jack, &"sampler", &midi_froms,
|
||||||
|
&[&left_froms, &right_froms],
|
||||||
|
&[&left_tos, &right_tos])?,
|
||||||
color,
|
color,
|
||||||
size: Measure::new(),
|
|
||||||
}
|
}
|
||||||
))?)?,
|
))?)?,
|
||||||
TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({
|
TekMode::Clock =>
|
||||||
let clip = default_clip();
|
engine.run(&jack.activate_with(|jack|App::clock(
|
||||||
let mut player = default_player(jack, Some(&clip))?;
|
jack, cli.bpm))?)?,
|
||||||
player.clock = default_bpm(player.clock);
|
TekMode::Sequencer =>
|
||||||
App::sequencer(
|
engine.run(&jack.activate_with(|jack|App::sequencer(
|
||||||
jack, (&clip).into(), (&clip).into(),
|
jack, cli.bpm, &midi_froms, &midi_tos))?)?,
|
||||||
Some(player), &midi_froms, &midi_tos,
|
TekMode::Groovebox =>
|
||||||
)
|
engine.run(&jack.activate_with(|jack|App::groovebox(
|
||||||
}))?)?,
|
jack, cli.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos))?)?,
|
||||||
TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({
|
TekMode::Arranger { scenes, tracks, track_width, .. } =>
|
||||||
let clip = default_clip();
|
engine.run(&jack.activate_with(|jack|App::arranger(
|
||||||
let mut player = default_player(jack, Some(&clip))?;
|
jack, cli.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos,
|
||||||
player.clock = default_bpm(player.clock);
|
scenes, tracks, track_width,
|
||||||
let sampler = default_sampler(jack)?;
|
))?)?,
|
||||||
jack.connect_ports(&player.midi_outs[0].port, &sampler.midi_in.as_ref().unwrap().port)?;
|
|
||||||
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({
|
|
||||||
App::arranger(
|
|
||||||
jack,
|
|
||||||
MidiPool::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: MidiPool::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!()
|
_ => todo!()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test] fn verify_cli () {
|
#[test] fn verify_cli () {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
TekCli::command().debug_assert();
|
TekCli::command().debug_assert();
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,7 @@ use EdnItem::*;
|
||||||
$(
|
$(
|
||||||
impl<'a> EdnProvide<'a, $type> for $App {
|
impl<'a> EdnProvide<'a, $type> for $App {
|
||||||
fn get <S: AsRef<str>> (&'a $self, edn: &'a EdnItem<S>) -> Option<$type> {
|
fn get <S: AsRef<str>> (&'a $self, edn: &'a EdnItem<S>) -> Option<$type> {
|
||||||
Some(match edn.to_ref() {
|
Some(match edn.to_ref() { $(EdnItem::Sym($sym) => $value,)* _ => return None })
|
||||||
$(EdnItem::Sym($sym) => $value,)*
|
|
||||||
_ => return None
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,13 @@ pub trait HasSampler {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! has_sampler {
|
||||||
|
(|$self:ident:$Struct:ty| { sampler = $e0:expr; index = $e1:expr; }) => {
|
||||||
|
impl HasSampler for $Struct {
|
||||||
|
fn sampler (&$self) -> &Option<Sampler> { &$e0 }
|
||||||
|
fn sampler_mut (&mut $self) -> &mut Option<Sampler> { &mut $e0 }
|
||||||
|
fn sample_index (&$self) -> usize { $e1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
248
tek/src/lib.rs
248
tek/src/lib.rs
|
|
@ -5,29 +5,24 @@
|
||||||
#![feature(if_let_guard)]
|
#![feature(if_let_guard)]
|
||||||
#![feature(impl_trait_in_assoc_type)]
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::{self, *}}};
|
||||||
|
pub(crate) use std::error::Error;
|
||||||
/// Standard result type.
|
/// Standard result type.
|
||||||
pub type Usually<T> = std::result::Result<T, Box<dyn Error>>;
|
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 use ::tek_tui::{
|
|
||||||
*, tek_edn::*, tek_input::*, tek_output::*,
|
|
||||||
crossterm, crossterm::event::{
|
|
||||||
Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
|
|
||||||
KeyCode::{self, *},
|
|
||||||
},
|
|
||||||
ratatui, ratatui::{
|
|
||||||
prelude::{Color, Style, Stylize, Buffer, Modifier},
|
|
||||||
buffer::Cell,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
pub use ::tek_time::{self, *};
|
pub use ::tek_time::{self, *};
|
||||||
pub use ::tek_jack::{self, *, jack::{*, contrib::*}};
|
pub use ::tek_jack::{self, *, jack::{*, contrib::*}};
|
||||||
pub use ::tek_midi::{self, *, midly::{*, num::*, live::*}};
|
pub use ::tek_midi::{self, *, midly::{*, num::*, live::*}};
|
||||||
pub use ::tek_sampler::{self, *};
|
pub use ::tek_sampler::{self, *};
|
||||||
pub use ::tek_plugin::{self, *};
|
pub use ::tek_plugin::{self, *};
|
||||||
pub(crate) use std::error::Error;
|
pub use ::tek_tui::{
|
||||||
pub(crate) use std::sync::atomic::{AtomicBool, Ordering::{self, *}};
|
*, tek_edn::*, tek_input::*, tek_output::*,
|
||||||
pub(crate) use std::sync::{Arc, RwLock};
|
ratatui, ratatui::{prelude::{Color, Style, Stylize, Buffer, Modifier}, buffer::Cell},
|
||||||
|
crossterm, crossterm::event::{
|
||||||
|
Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyCode::{self, *},
|
||||||
|
},
|
||||||
|
};
|
||||||
#[derive(Default, Debug)] pub struct App {
|
#[derive(Default, Debug)] pub struct App {
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
pub jack: Arc<RwLock<JackConnection>>,
|
||||||
pub edn: String,
|
pub edn: String,
|
||||||
|
|
@ -57,11 +52,10 @@ has_clock!(|self: App|&self.clock);
|
||||||
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
||||||
//has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
//has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
||||||
has_jack!(|self: App|&self.jack);
|
has_jack!(|self: App|&self.jack);
|
||||||
impl HasSampler for App {
|
has_sampler!(|self: App|{
|
||||||
fn sampler (&self) -> &Option<Sampler> { &self.sampler }
|
sampler = self.sampler;
|
||||||
fn sampler_mut (&mut self) -> &mut Option<Sampler> { &mut self.sampler }
|
index = self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0);
|
||||||
fn sample_index (&self) -> usize { self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0) }
|
});
|
||||||
}
|
|
||||||
has_editor!(|self: App|{
|
has_editor!(|self: App|{
|
||||||
editor = self.editor;
|
editor = self.editor;
|
||||||
editor_w = {
|
editor_w = {
|
||||||
|
|
@ -105,53 +99,101 @@ edn_view!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.a
|
||||||
":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16,
|
":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16,
|
||||||
self.scene_header(), self.scene_cells(self.is_editing())).boxed() }});
|
self.scene_header(), self.scene_cells(self.is_editing())).boxed() }});
|
||||||
impl App {
|
impl App {
|
||||||
|
pub fn clock (
|
||||||
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
|
bpm: Option<f64>,
|
||||||
|
) -> Usually<Self> {
|
||||||
|
// TODO: enable sync master/follow
|
||||||
|
//let sync_clock = |jack: &Arc<RwLock<JackConnection>>, app|{
|
||||||
|
//if cli.sync_lead {
|
||||||
|
//jack.read().unwrap().client().register_timebase_callback(false, |mut state|{
|
||||||
|
//app.clock().playhead.update_from_sample(state.position.frame() as f64);
|
||||||
|
//state.position.bbt = Some(app.clock().bbt());
|
||||||
|
//state.position
|
||||||
|
//})
|
||||||
|
//} else if cli.sync_follow {
|
||||||
|
//jack.read().unwrap().client().register_timebase_callback(false, |state|{
|
||||||
|
//app.clock().playhead.update_from_sample(state.position.frame() as f64);
|
||||||
|
//state.position
|
||||||
|
//})
|
||||||
|
//} else {
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
//};
|
||||||
|
Ok(Self {
|
||||||
|
edn: include_str!("./transport-view.edn").to_string(),
|
||||||
|
jack: jack.clone(),
|
||||||
|
color: ItemPalette::random(),
|
||||||
|
clock: Clock::new(jack, bpm),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
pub fn sequencer (
|
pub fn sequencer (
|
||||||
jack: &Arc<RwLock<JackConnection>>, pool: MidiPool, editor: MidiEditor,
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
player: Option<MidiPlayer>, midi_froms: &[PortConnection], midi_tos: &[PortConnection],
|
bpm: Option<f64>,
|
||||||
) -> Self {
|
midi_froms: &[PortConnection],
|
||||||
Self {
|
midi_tos: &[PortConnection],
|
||||||
|
) -> Usually<Self> {
|
||||||
|
let clip = MidiClip::new("Clip", true, 384usize, None, Some(ItemColor::random().into()));
|
||||||
|
let clip = Arc::new(RwLock::new(clip));
|
||||||
|
Ok(Self {
|
||||||
edn: include_str!("./sequencer-view.edn").to_string(),
|
edn: include_str!("./sequencer-view.edn").to_string(),
|
||||||
jack: jack.clone(),
|
pool: Some((&clip).into()),
|
||||||
pool: Some(pool),
|
editor: Some((&clip).into()),
|
||||||
editor: Some(editor),
|
|
||||||
player: player,
|
|
||||||
editing: false.into(),
|
editing: false.into(),
|
||||||
midi_buf: vec![vec![];65536],
|
midi_buf: vec![vec![];65536],
|
||||||
color: ItemPalette::random(),
|
player: Some(MidiPlayer::new(
|
||||||
..Default::default()
|
&jack,
|
||||||
}
|
"sequencer",
|
||||||
|
Some(&clip),
|
||||||
|
&midi_froms,
|
||||||
|
&midi_tos
|
||||||
|
)?),
|
||||||
|
..Self::clock(jack, bpm)?
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn groovebox (
|
pub fn groovebox (
|
||||||
jack: &Arc<RwLock<JackConnection>>, pool: MidiPool, editor: MidiEditor,
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
player: Option<MidiPlayer>, midi_froms: &[PortConnection], midi_tos: &[PortConnection],
|
bpm: Option<f64>,
|
||||||
sampler: Sampler, audio_froms: &[&[PortConnection]], audio_tos: &[&[PortConnection]],
|
midi_froms: &[PortConnection],
|
||||||
) -> Self {
|
midi_tos: &[PortConnection],
|
||||||
Self {
|
audio_froms: &[&[PortConnection];2],
|
||||||
edn: include_str!("./groovebox-view.edn").to_string(),
|
audio_tos: &[&[PortConnection];2],
|
||||||
sampler: Some(sampler),
|
) -> Usually<Self> {
|
||||||
..Self::sequencer(
|
let app = Self {
|
||||||
jack, pool, editor,
|
edn: include_str!("./groovebox-view.edn").to_string(),
|
||||||
player, midi_froms, midi_tos
|
sampler: Some(Sampler::new(
|
||||||
)
|
jack,
|
||||||
|
&"sampler",
|
||||||
|
midi_froms,
|
||||||
|
audio_froms,
|
||||||
|
audio_tos
|
||||||
|
)?),
|
||||||
|
..Self::sequencer(jack, bpm, midi_froms, midi_tos)?
|
||||||
|
};
|
||||||
|
if let Some(sampler) = app.sampler.as_ref().unwrap().midi_in.as_ref() {
|
||||||
|
jack.connect_ports(&app.player.as_ref().unwrap().midi_outs[0].port, &sampler.port)?;
|
||||||
}
|
}
|
||||||
|
Ok(app)
|
||||||
}
|
}
|
||||||
pub fn arranger (
|
pub fn arranger (
|
||||||
jack: &Arc<RwLock<JackConnection>>, pool: MidiPool, editor: MidiEditor,
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
midi_froms: &[PortConnection], midi_tos: &[PortConnection],
|
bpm: Option<f64>,
|
||||||
sampler: Sampler, audio_froms: &[&[PortConnection]], audio_tos: &[&[PortConnection]],
|
midi_froms: &[PortConnection],
|
||||||
scenes: usize, tracks: usize, track_width: usize,
|
midi_tos: &[PortConnection],
|
||||||
) -> Self {
|
audio_froms: &[&[PortConnection];2],
|
||||||
|
audio_tos: &[&[PortConnection];2],
|
||||||
|
scenes: usize,
|
||||||
|
tracks: usize,
|
||||||
|
track_width: usize,
|
||||||
|
) -> Usually<Self> {
|
||||||
let mut arranger = Self {
|
let mut arranger = Self {
|
||||||
edn: include_str!("./arranger-view.edn").to_string(),
|
edn: include_str!("./arranger-view.edn").to_string(),
|
||||||
..Self::groovebox(
|
..Self::groovebox(jack, bpm, midi_froms, midi_tos, audio_froms, audio_tos)?
|
||||||
jack, pool, editor,
|
|
||||||
None, midi_froms, midi_tos,
|
|
||||||
sampler, audio_froms, audio_tos
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
arranger.scenes_add(scenes);
|
arranger.scenes_add(scenes);
|
||||||
arranger.tracks_add(tracks, track_width, &[], &[]);
|
arranger.tracks_add(tracks, track_width, &[], &[]);
|
||||||
arranger
|
Ok(arranger)
|
||||||
}
|
}
|
||||||
fn compact (&self) -> bool { false }
|
fn compact (&self) -> bool { false }
|
||||||
fn is_editing (&self) -> bool { self.editing.load(Relaxed) }
|
fn is_editing (&self) -> bool { self.editing.load(Relaxed) }
|
||||||
|
|
@ -351,20 +393,20 @@ handle!(TuiIn: |self: App, input| Ok(None));
|
||||||
Zoom(Option<usize>),
|
Zoom(Option<usize>),
|
||||||
}
|
}
|
||||||
edn_command!(AppCommand: |state: App| {
|
edn_command!(AppCommand: |state: App| {
|
||||||
("stop-all" [] Self::StopAll)
|
|
||||||
("compact" [c: bool ] Self::Compact(c))
|
|
||||||
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
|
|
||||||
("history" [d: isize] Self::History(d.unwrap_or(0)))
|
|
||||||
("zoom" [z: usize] Self::Zoom(z))
|
|
||||||
("select" [s: Selection] Self::Select(s.expect("no selection")))
|
|
||||||
("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
|
|
||||||
("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b)))
|
|
||||||
("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b)))
|
|
||||||
("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b)))
|
|
||||||
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b)))
|
("clip" [a, ..b] Self::Clip(ClipCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b)))
|
("clock" [a, ..b] Self::Clock(ClockCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
("color" [c: Color] Self::Color(c.map(ItemPalette::from).unwrap_or_default()))
|
||||||
|
("compact" [c: bool ] Self::Compact(c))
|
||||||
("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b)))
|
("editor" [a, ..b] Self::Editor(MidiEditCommand::from_edn(state.editor.as_ref().expect("no editor"), &a.to_ref(), b)))
|
||||||
|
("enqueue" [c: Arc<RwLock<MidiClip>>] Self::Enqueue(c))
|
||||||
|
("history" [d: isize] Self::History(d.unwrap_or(0)))
|
||||||
|
("pool" [a, ..b] Self::Pool(PoolCommand::from_edn(state.pool.as_ref().expect("no pool"), &a.to_ref(), b)))
|
||||||
("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b)))
|
("sampler" [a, ..b] Self::Sampler(SamplerCommand::from_edn(state.sampler.as_ref().expect("no sampler"), &a.to_ref(), b)))
|
||||||
|
("scene" [a, ..b] Self::Scene(SceneCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
("select" [s: Selection] Self::Select(s.expect("no selection")))
|
||||||
|
("stop-all" [] Self::StopAll)
|
||||||
|
("track" [a, ..b] Self::Track(TrackCommand::from_edn(state, &a.to_ref(), b)))
|
||||||
|
("zoom" [z: usize] Self::Zoom(z))
|
||||||
});
|
});
|
||||||
command!(|self: AppCommand, state: App|match self {
|
command!(|self: AppCommand, state: App|match self {
|
||||||
Self::Zoom(_) => { todo!(); },
|
Self::Zoom(_) => { todo!(); },
|
||||||
|
|
@ -815,9 +857,7 @@ audio!(|self: App, client, scope|{
|
||||||
// Start profiling cycle
|
// Start profiling cycle
|
||||||
let t0 = self.perf.get_t0();
|
let t0 = self.perf.get_t0();
|
||||||
// Update transport clock
|
// Update transport clock
|
||||||
if Control::Quit == ClockAudio(self).process(client, scope) {
|
self.clock().update_from_scope(scope).unwrap();
|
||||||
return Control::Quit
|
|
||||||
}
|
|
||||||
// Collect MIDI input (TODO preallocate)
|
// Collect MIDI input (TODO preallocate)
|
||||||
let midi_in = self.midi_ins.iter()
|
let midi_in = self.midi_ins.iter()
|
||||||
.map(|port|port.port.iter(scope)
|
.map(|port|port.port.iter(scope)
|
||||||
|
|
@ -957,6 +997,82 @@ content!(TuiOut: |self: Meters<'a>| col!(
|
||||||
format!("L/{:>+9.3}", self.0[0]),
|
format!("L/{:>+9.3}", self.0[0]),
|
||||||
format!("R/{:>+9.3}", self.0[1])
|
format!("R/{:>+9.3}", self.0[1])
|
||||||
));
|
));
|
||||||
|
/// Transport clock app.
|
||||||
|
pub struct ClockTui { pub jack: Arc<RwLock<JackConnection>>, pub clock: Clock, }
|
||||||
|
handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event()));
|
||||||
|
keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand {
|
||||||
|
key(Char(' ')) =>
|
||||||
|
if state.clock().is_stopped() { ClockCommand::Play(None) } else { ClockCommand::Pause(None) },
|
||||||
|
shift(key(Char(' '))) =>
|
||||||
|
if state.clock().is_stopped() { ClockCommand::Play(Some(0)) } else { ClockCommand::Pause(Some(0)) }
|
||||||
|
});
|
||||||
|
has_clock!(|self: ClockTui|&self.clock);
|
||||||
|
content!(TuiOut:|self: ClockTui|ClockView { compact: false, clock: &self.clock });
|
||||||
|
pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock }
|
||||||
|
content!(TuiOut: |self: ClockView<'a>| Outer(Style::default().fg(TuiTheme::g(255))).enclose(row!(
|
||||||
|
OutputStats::new(self.compact, self.clock),
|
||||||
|
" ",
|
||||||
|
PlayPause { compact: false, playing: self.clock.is_rolling() },
|
||||||
|
" ",
|
||||||
|
BeatStats::new(self.compact, self.clock),
|
||||||
|
)));
|
||||||
|
impl<'a> ClockView<'a> {
|
||||||
|
pub fn new (compact: bool, clock: &'a Clock) -> Self { Self { compact, clock } }
|
||||||
|
}
|
||||||
|
pub struct PlayPause { pub compact: bool, pub playing: bool }
|
||||||
|
content!(TuiOut: |self: PlayPause| Tui::bg(
|
||||||
|
if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
||||||
|
Either::new(self.compact,
|
||||||
|
Thunk::new(||Fixed::x(9, Either::new(self.playing,
|
||||||
|
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
|
||||||
|
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
|
||||||
|
Thunk::new(||Fixed::x(5, Either::new(self.playing,
|
||||||
|
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
||||||
|
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
|
||||||
|
pub struct BeatStats { compact: bool, bpm: Arc<str>, beat: Arc<str>, time: Arc<str>, }
|
||||||
|
content!(TuiOut: |self: BeatStats| Either::new(self.compact,
|
||||||
|
row!(FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Beat", &self.beat),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Time", &self.time),),
|
||||||
|
col!(Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"),
|
||||||
|
Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)),
|
||||||
|
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)))));
|
||||||
|
impl BeatStats {
|
||||||
|
fn new (compact: bool, clock: &Clock) -> Self {
|
||||||
|
let bpm = format!("{:.3}", clock.timebase.bpm.get()).into();
|
||||||
|
let (beat, time) = if let Some(started) = clock.started.read().unwrap().as_ref() {
|
||||||
|
let now = clock.global.usec.get() - started.usec.get();
|
||||||
|
(
|
||||||
|
clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)).into(),
|
||||||
|
format!("{:.3}s", now/1000000.).into()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
("-.-.--".to_string().into(), "-.---s".to_string().into())
|
||||||
|
};
|
||||||
|
Self { compact, bpm, beat, time }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct OutputStats { compact: bool, sample_rate: Arc<str>, buffer_size: Arc<str>, latency: Arc<str>, }
|
||||||
|
content!(TuiOut: |self: OutputStats| Either::new(self.compact,
|
||||||
|
row!(FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Lat", &self.latency)),
|
||||||
|
col!(Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"),
|
||||||
|
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"),
|
||||||
|
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"))));
|
||||||
|
impl OutputStats {
|
||||||
|
fn new (compact: bool, clock: &Clock) -> Self {
|
||||||
|
let rate = clock.timebase.sr.get();
|
||||||
|
let chunk = clock.chunk.load(Relaxed);
|
||||||
|
let sr = if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)};
|
||||||
|
Self {
|
||||||
|
compact,
|
||||||
|
sample_rate: sr.into(),
|
||||||
|
buffer_size: format!("{chunk}").into(),
|
||||||
|
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[cfg(test)] fn test_tek () {
|
#[cfg(test)] fn test_tek () {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
|
||||||
0
tek/src/transport-keys.edn
Normal file
0
tek/src/transport-keys.edn
Normal file
0
tek/src/transport-view.edn
Normal file
0
tek/src/transport-view.edn
Normal file
|
|
@ -4,8 +4,9 @@ edition = "2021"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tek_tui = { path = "../tui" }
|
tek_jack = { path = "../jack" }
|
||||||
tek_jack = { path = "../jack" }
|
tek_edn = { path = "../edn" }
|
||||||
|
tek_input = { path = "../input" }
|
||||||
atomic_float = "1.0.0"
|
atomic_float = "1.0.0"
|
||||||
quanta = "0.12.3"
|
quanta = "0.12.3"
|
||||||
#jack = { path = "../rust-jack" }
|
#jack = { path = "../rust-jack" }
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use EdnItem::*;
|
|
||||||
|
|
||||||
pub trait HasClock: Send + Sync {
|
pub trait HasClock: Send + Sync {
|
||||||
fn clock (&self) -> &Clock;
|
fn clock (&self) -> &Clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_clock {
|
#[macro_export] macro_rules! has_clock {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? {
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
|
@ -12,17 +9,6 @@ pub trait HasClock: Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hosts the JACK callback for updating the temporal pointer and playback status.
|
|
||||||
pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T);
|
|
||||||
|
|
||||||
impl<T: HasClock> Audio for ClockAudio<'_, T> {
|
|
||||||
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
self.0.clock().update_from_scope(scope).unwrap();
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum ClockCommand {
|
pub enum ClockCommand {
|
||||||
Play(Option<u32>),
|
Play(Option<u32>),
|
||||||
|
|
@ -77,25 +63,25 @@ pub struct Clock {
|
||||||
/// Size of buffer in samples
|
/// Size of buffer in samples
|
||||||
pub chunk: Arc<AtomicUsize>,
|
pub chunk: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
impl From<&Arc<RwLock<JackConnection>>> for Clock {
|
||||||
from!(|jack: &Arc<RwLock<JackConnection>>| Clock = {
|
fn from (jack: &Arc<RwLock<JackConnection>>) -> Self {
|
||||||
let jack = jack.read().unwrap();
|
let jack = jack.read().unwrap();
|
||||||
let chunk = jack.client().buffer_size();
|
let chunk = jack.client().buffer_size();
|
||||||
let transport = jack.client().transport();
|
let transport = jack.client().transport();
|
||||||
let timebase = Arc::new(Timebase::default());
|
let timebase = Arc::new(Timebase::default());
|
||||||
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(Some(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)),
|
||||||
offset: Arc::new(Moment::zero(&timebase)),
|
offset: Arc::new(Moment::zero(&timebase)),
|
||||||
started: RwLock::new(None).into(),
|
started: RwLock::new(None).into(),
|
||||||
timebase,
|
timebase,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Clock {
|
impl std::fmt::Debug for Clock {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
f.debug_struct("Clock")
|
f.debug_struct("Clock")
|
||||||
|
|
@ -109,8 +95,14 @@ impl std::fmt::Debug for Clock {
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clock {
|
impl Clock {
|
||||||
|
pub fn new (jack: &Arc<RwLock<JackConnection>>, bpm: Option<f64>) -> Self {
|
||||||
|
let clock = Self::from(jack);
|
||||||
|
if let Some(bpm) = bpm {
|
||||||
|
clock.timebase.bpm.set(bpm);
|
||||||
|
}
|
||||||
|
clock
|
||||||
|
}
|
||||||
pub fn timebase (&self) -> &Arc<Timebase> {
|
pub fn timebase (&self) -> &Arc<Timebase> {
|
||||||
&self.timebase
|
&self.timebase
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use KeyCode::*;
|
|
||||||
use ClockCommand::{Play, Pause};
|
|
||||||
/// Transport clock app.
|
|
||||||
pub struct ClockTui { pub jack: Arc<RwLock<JackConnection>>, pub clock: Clock, }
|
|
||||||
handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event()));
|
|
||||||
keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand {
|
|
||||||
key(Char(' ')) =>
|
|
||||||
if state.clock().is_stopped() { Play(None) } else { Pause(None) },
|
|
||||||
shift(key(Char(' '))) =>
|
|
||||||
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
|
|
||||||
});
|
|
||||||
has_clock!(|self: ClockTui|&self.clock);
|
|
||||||
audio!(|self: ClockTui, client, scope|ClockAudio(self).process(client, scope));
|
|
||||||
content!(TuiOut:|self: ClockTui|ClockView { compact: false, clock: &self.clock });
|
|
||||||
pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock }
|
|
||||||
content!(TuiOut: |self: ClockView<'a>| Outer(Style::default().fg(TuiTheme::g(255))).enclose(row!(
|
|
||||||
OutputStats::new(self.compact, self.clock),
|
|
||||||
" ",
|
|
||||||
PlayPause { compact: false, playing: self.clock.is_rolling() },
|
|
||||||
" ",
|
|
||||||
BeatStats::new(self.compact, self.clock),
|
|
||||||
)));
|
|
||||||
impl<'a> ClockView<'a> {
|
|
||||||
pub fn new (compact: bool, clock: &'a Clock) -> Self { Self { compact, clock } }
|
|
||||||
}
|
|
||||||
pub struct PlayPause { pub compact: bool, pub playing: bool }
|
|
||||||
content!(TuiOut: |self: PlayPause| Tui::bg(
|
|
||||||
if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
|
||||||
Either::new(self.compact,
|
|
||||||
Thunk::new(||Fixed::x(9, Either::new(self.playing,
|
|
||||||
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
|
|
||||||
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
|
|
||||||
Thunk::new(||Fixed::x(5, Either::new(self.playing,
|
|
||||||
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
|
||||||
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
|
|
||||||
pub struct BeatStats { compact: bool, bpm: Arc<str>, beat: Arc<str>, time: Arc<str>, }
|
|
||||||
content!(TuiOut: |self: BeatStats| Either::new(self.compact,
|
|
||||||
row!(FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm),
|
|
||||||
FieldV(TuiTheme::g(128).into(), "Beat", &self.beat),
|
|
||||||
FieldV(TuiTheme::g(128).into(), "Time", &self.time),),
|
|
||||||
col!(Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"),
|
|
||||||
Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)),
|
|
||||||
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)))));
|
|
||||||
impl BeatStats {
|
|
||||||
fn new (compact: bool, clock: &Clock) -> Self {
|
|
||||||
let bpm = format!("{:.3}", clock.timebase.bpm.get()).into();
|
|
||||||
let (beat, time) = if let Some(started) = clock.started.read().unwrap().as_ref() {
|
|
||||||
let now = clock.global.usec.get() - started.usec.get();
|
|
||||||
(
|
|
||||||
clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)).into(),
|
|
||||||
format!("{:.3}s", now/1000000.).into()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
("-.-.--".to_string().into(), "-.---s".to_string().into())
|
|
||||||
};
|
|
||||||
Self { compact, bpm, beat, time }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct OutputStats { compact: bool, sample_rate: Arc<str>, buffer_size: Arc<str>, latency: Arc<str>, }
|
|
||||||
content!(TuiOut: |self: OutputStats| Either::new(self.compact,
|
|
||||||
row!(FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate),
|
|
||||||
FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size),
|
|
||||||
FieldV(TuiTheme::g(128).into(), "Lat", &self.latency)),
|
|
||||||
col!(Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"),
|
|
||||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"),
|
|
||||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"))));
|
|
||||||
impl OutputStats {
|
|
||||||
fn new (compact: bool, clock: &Clock) -> Self {
|
|
||||||
let rate = clock.timebase.sr.get();
|
|
||||||
let chunk = clock.chunk.load(Relaxed);
|
|
||||||
let sr = if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)};
|
|
||||||
Self {
|
|
||||||
compact,
|
|
||||||
sample_rate: sr.into(),
|
|
||||||
buffer_size: format!("{chunk}").into(),
|
|
||||||
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand {
|
|
||||||
//key(Char(',')) => SetBpm(state.bpm().get() - 1.0),
|
|
||||||
//key(Char('.')) => SetBpm(state.bpm().get() + 1.0),
|
|
||||||
//key(Char('<')) => SetBpm(state.bpm().get() - 0.001),
|
|
||||||
//key(Char('>')) => SetBpm(state.bpm().get() + 0.001),
|
|
||||||
//});
|
|
||||||
//keymap!(TRANSPORT_QUANT_KEYS = |state: Clock, input: Event| ClockCommand {
|
|
||||||
//key(Char(',')) => SetQuant(state.quant.prev()),
|
|
||||||
//key(Char('.')) => SetQuant(state.quant.next()),
|
|
||||||
//key(Char('<')) => SetQuant(state.quant.prev()),
|
|
||||||
//key(Char('>')) => SetQuant(state.quant.next()),
|
|
||||||
//});
|
|
||||||
//keymap!(TRANSPORT_SYNC_KEYS = |sync: Clock, input: Event | ClockCommand {
|
|
||||||
//key(Char(',')) => SetSync(state.sync.prev()),
|
|
||||||
//key(Char('.')) => SetSync(state.sync.next()),
|
|
||||||
//key(Char('<')) => SetSync(state.sync.prev()),
|
|
||||||
//key(Char('>')) => SetSync(state.sync.next()),
|
|
||||||
//});
|
|
||||||
//keymap!(TRANSPORT_SEEK_KEYS = |state: Clock, input: Event| ClockCommand {
|
|
||||||
//key(Char(',')) => todo!("transport seek bar"),
|
|
||||||
//key(Char('.')) => todo!("transport seek bar"),
|
|
||||||
//key(Char('<')) => todo!("transport seek beat"),
|
|
||||||
//key(Char('>')) => todo!("transport seek beat"),
|
|
||||||
//});
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
mod clock; pub use self::clock::*;
|
mod clock; pub use self::clock::*;
|
||||||
mod clock_tui; pub use self::clock_tui::*;
|
|
||||||
mod microsecond; pub use self::microsecond::*;
|
mod microsecond; pub use self::microsecond::*;
|
||||||
mod moment; pub use self::moment::*;
|
mod moment; pub use self::moment::*;
|
||||||
mod note_duration; pub use self::note_duration::*;
|
mod note_duration; pub use self::note_duration::*;
|
||||||
|
|
@ -11,17 +10,47 @@ mod timebase; pub use self::timebase::*;
|
||||||
mod unit; pub use self::unit::*;
|
mod unit; pub use self::unit::*;
|
||||||
|
|
||||||
pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}};
|
pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}};
|
||||||
pub(crate) use std::sync::{Arc, Mutex, RwLock, atomic::{AtomicUsize, Ordering::*}};
|
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}};
|
||||||
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
||||||
pub use ::atomic_float; pub(crate) use atomic_float::*;
|
pub use ::atomic_float; pub(crate) use atomic_float::*;
|
||||||
pub(crate) use ::tek_tui::{
|
pub(crate) use ::tek_edn::*;
|
||||||
*,
|
pub(crate) use ::tek_input::*;
|
||||||
tek_output::*,
|
|
||||||
tek_input::*,
|
/// Standard result type.
|
||||||
tek_edn::*,
|
pub(crate) type Usually<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
ratatui::prelude::*,
|
/// Standard optional result type.
|
||||||
crossterm::event::*,
|
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn std::error::Error>>;
|
||||||
};
|
|
||||||
|
pub trait Gettable<T> {
|
||||||
|
/// Returns current value
|
||||||
|
fn get (&self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Mutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&mut self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InteriorMutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gettable<bool> for AtomicBool {
|
||||||
|
fn get (&self) -> bool { self.load(Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteriorMutable<bool> for AtomicBool {
|
||||||
|
fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gettable<usize> for AtomicUsize {
|
||||||
|
fn get (&self) -> usize { self.load(Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteriorMutable<usize> for AtomicUsize {
|
||||||
|
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_time () -> Usually<()> {
|
#[cfg(test)] #[test] fn test_time () -> Usually<()> {
|
||||||
// TODO!
|
// TODO!
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,17 @@ impl PerfModel {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
|
||||||
|
if let Some(t0) = t0 {
|
||||||
|
if self.enabled {
|
||||||
|
Some(self.clock.delta(t0, self.clock.raw()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn update (&self, t0: Option<u64>, scope: &ProcessScope) {
|
pub fn update (&self, t0: Option<u64>, scope: &ProcessScope) {
|
||||||
if let Some(t0) = t0 {
|
if let Some(t0) = t0 {
|
||||||
let t1 = self.clock.raw();
|
let t1 = self.clock.raw();
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,4 @@ better-panic = "0.3.0"
|
||||||
tek_edn = { path = "../edn" }
|
tek_edn = { path = "../edn" }
|
||||||
tek_input = { path = "../input" }
|
tek_input = { path = "../input" }
|
||||||
tek_output = { path = "../output" }
|
tek_output = { path = "../output" }
|
||||||
|
tek_time = { path = "../time" }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ pub(crate) use tek_output::*;
|
||||||
pub use ::tek_edn;
|
pub use ::tek_edn;
|
||||||
pub(crate) use ::tek_edn::*;
|
pub(crate) use ::tek_edn::*;
|
||||||
|
|
||||||
|
pub use ::tek_time;
|
||||||
|
pub(crate) use ::tek_time::*;
|
||||||
|
|
||||||
mod tui_engine; pub use self::tui_engine::*;
|
mod tui_engine; pub use self::tui_engine::*;
|
||||||
mod tui_content; pub use self::tui_content::*;
|
mod tui_content; pub use self::tui_content::*;
|
||||||
mod tui_input; pub use self::tui_input::*;
|
mod tui_input; pub use self::tui_input::*;
|
||||||
|
|
@ -39,37 +42,6 @@ pub(crate) use std::ffi::OsString;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Gettable<T> {
|
|
||||||
/// Returns current value
|
|
||||||
fn get (&self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Mutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&mut self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait InteriorMutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gettable<bool> for AtomicBool {
|
|
||||||
fn get (&self) -> bool { self.load(Relaxed) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InteriorMutable<bool> for AtomicBool {
|
|
||||||
fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gettable<usize> for AtomicUsize {
|
|
||||||
fn get (&self) -> usize { self.load(Relaxed) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InteriorMutable<usize> for AtomicUsize {
|
|
||||||
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
|
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
|
||||||
pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
|
pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
|
||||||
|
|
||||||
|
|
@ -89,7 +61,6 @@ pub(crate) use ratatui::{
|
||||||
buffer::Cell
|
buffer::Cell
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> {
|
#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ pub struct Tui {
|
||||||
pub backend: CrosstermBackend<Stdout>,
|
pub backend: CrosstermBackend<Stdout>,
|
||||||
pub buffer: Buffer,
|
pub buffer: Buffer,
|
||||||
pub area: [u16;4],
|
pub area: [u16;4],
|
||||||
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tui {
|
impl Tui {
|
||||||
|
|
@ -17,6 +18,7 @@ impl Tui {
|
||||||
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }),
|
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }),
|
||||||
area: [0, 0, width, height],
|
area: [0, 0, width, height],
|
||||||
backend,
|
backend,
|
||||||
|
perf: Default::default(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
/// True if done
|
/// True if done
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ impl<T: Render<TuiOut> + Handle<TuiIn> + Sized + 'static> TuiRun<T> for Arc<RwLo
|
||||||
if exited.fetch_and(true, Relaxed) {
|
if exited.fetch_and(true, Relaxed) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
let t0 = engine.read().unwrap().perf.get_t0();
|
||||||
let Size { width, height } = engine.read().unwrap().backend.size()
|
let Size { width, height } = engine.read().unwrap().backend.size()
|
||||||
.expect("get size failed");
|
.expect("get size failed");
|
||||||
if let Ok(state) = state.try_read() {
|
if let Ok(state) = state.try_read() {
|
||||||
|
|
@ -76,6 +77,8 @@ impl<T: Render<TuiOut> + Handle<TuiIn> + Sized + 'static> TuiRun<T> for Arc<RwLo
|
||||||
state.render(&mut output);
|
state.render(&mut output);
|
||||||
buffer = engine.write().unwrap().flip(output.buffer, size);
|
buffer = engine.write().unwrap().flip(output.buffer, size);
|
||||||
}
|
}
|
||||||
|
let t1 = engine.read().unwrap().perf.get_t1(t0).unwrap();
|
||||||
|
buffer.set_string(0, 0, &format!("{:>03}.{:>03}ms", t1.as_millis(), t1.as_micros() % 1000), Style::default());
|
||||||
std::thread::sleep(sleep);
|
std::thread::sleep(sleep);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue