mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +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 = [
|
||||
"atomic_float",
|
||||
"quanta",
|
||||
"tek_edn",
|
||||
"tek_input",
|
||||
"tek_jack",
|
||||
"tek_tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1523,6 +1524,7 @@ dependencies = [
|
|||
"tek_edn",
|
||||
"tek_input",
|
||||
"tek_output",
|
||||
"tek_time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
126
cli/tek.rs
126
cli/tek.rs
|
|
@ -1,10 +1,7 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
use tek::*;
|
||||
#[allow(unused_imports)] use clap::{self, Parser, Subcommand, ValueEnum};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
use clap::{self, Parser, Subcommand};
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct TekCli {
|
||||
#[derive(Debug, Parser)] pub struct TekCli {
|
||||
/// Which app to initialize
|
||||
#[command(subcommand)] mode: TekMode,
|
||||
/// Name of JACK client
|
||||
|
|
@ -34,9 +31,7 @@ pub struct TekCli {
|
|||
/// Audio ins to connect from right output
|
||||
#[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.
|
||||
Clock,
|
||||
/// A MIDI sequencer.
|
||||
|
|
@ -61,7 +56,6 @@ pub enum TekMode {
|
|||
/// TODO: An audio plugin host
|
||||
Plugin,
|
||||
}
|
||||
|
||||
/// Application entrypoint.
|
||||
pub fn main () -> Usually<()> {
|
||||
let cli = TekCli::parse();
|
||||
|
|
@ -78,114 +72,38 @@ pub fn main () -> Usually<()> {
|
|||
let right_tos = PortConnection::collect(&cli.right_to, empty, empty);
|
||||
let audio_froms = &[left_froms.as_slice(), right_froms.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 {
|
||||
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(
|
||||
SamplerTui {
|
||||
cursor: (0, 0),
|
||||
editing: None,
|
||||
mode: None,
|
||||
mode: None,
|
||||
note_lo: 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,
|
||||
size: Measure::new(),
|
||||
}
|
||||
))?)?,
|
||||
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.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
|
||||
//}))?)?,
|
||||
|
||||
TekMode::Clock =>
|
||||
engine.run(&jack.activate_with(|jack|App::clock(
|
||||
jack, cli.bpm))?)?,
|
||||
TekMode::Sequencer =>
|
||||
engine.run(&jack.activate_with(|jack|App::sequencer(
|
||||
jack, cli.bpm, &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::Arranger { scenes, tracks, track_width, .. } =>
|
||||
engine.run(&jack.activate_with(|jack|App::arranger(
|
||||
jack, cli.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos,
|
||||
scenes, tracks, track_width,
|
||||
))?)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
#[test] fn verify_cli () {
|
||||
use clap::CommandFactory;
|
||||
TekCli::command().debug_assert();
|
||||
|
|
|
|||
|
|
@ -12,10 +12,7 @@ use EdnItem::*;
|
|||
$(
|
||||
impl<'a> EdnProvide<'a, $type> for $App {
|
||||
fn get <S: AsRef<str>> (&'a $self, edn: &'a EdnItem<S>) -> Option<$type> {
|
||||
Some(match edn.to_ref() {
|
||||
$(EdnItem::Sym($sym) => $value,)*
|
||||
_ => return None
|
||||
})
|
||||
Some(match edn.to_ref() { $(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(impl_trait_in_assoc_type)]
|
||||
#![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.
|
||||
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 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_jack::{self, *, jack::{*, contrib::*}};
|
||||
pub use ::tek_midi::{self, *, midly::{*, num::*, live::*}};
|
||||
pub use ::tek_sampler::{self, *};
|
||||
pub use ::tek_plugin::{self, *};
|
||||
pub(crate) use std::error::Error;
|
||||
pub(crate) use std::sync::atomic::{AtomicBool, Ordering::{self, *}};
|
||||
pub(crate) use std::sync::{Arc, RwLock};
|
||||
pub use ::tek_tui::{
|
||||
*, tek_edn::*, tek_input::*, tek_output::*,
|
||||
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 {
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
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_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
||||
has_jack!(|self: App|&self.jack);
|
||||
impl HasSampler for App {
|
||||
fn sampler (&self) -> &Option<Sampler> { &self.sampler }
|
||||
fn sampler_mut (&mut self) -> &mut Option<Sampler> { &mut self.sampler }
|
||||
fn sample_index (&self) -> usize { self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0) }
|
||||
}
|
||||
has_sampler!(|self: App|{
|
||||
sampler = self.sampler;
|
||||
index = self.editor.as_ref().map(|e|e.note_point()).unwrap_or(0);
|
||||
});
|
||||
has_editor!(|self: App|{
|
||||
editor = self.editor;
|
||||
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,
|
||||
self.scene_header(), self.scene_cells(self.is_editing())).boxed() }});
|
||||
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 (
|
||||
jack: &Arc<RwLock<JackConnection>>, pool: MidiPool, editor: MidiEditor,
|
||||
player: Option<MidiPlayer>, midi_froms: &[PortConnection], midi_tos: &[PortConnection],
|
||||
) -> Self {
|
||||
Self {
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
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(),
|
||||
jack: jack.clone(),
|
||||
pool: Some(pool),
|
||||
editor: Some(editor),
|
||||
player: player,
|
||||
pool: Some((&clip).into()),
|
||||
editor: Some((&clip).into()),
|
||||
editing: false.into(),
|
||||
midi_buf: vec![vec![];65536],
|
||||
color: ItemPalette::random(),
|
||||
..Default::default()
|
||||
}
|
||||
player: Some(MidiPlayer::new(
|
||||
&jack,
|
||||
"sequencer",
|
||||
Some(&clip),
|
||||
&midi_froms,
|
||||
&midi_tos
|
||||
)?),
|
||||
..Self::clock(jack, bpm)?
|
||||
})
|
||||
}
|
||||
pub fn groovebox (
|
||||
jack: &Arc<RwLock<JackConnection>>, pool: MidiPool, 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-view.edn").to_string(),
|
||||
sampler: Some(sampler),
|
||||
..Self::sequencer(
|
||||
jack, pool, editor,
|
||||
player, midi_froms, midi_tos
|
||||
)
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
audio_froms: &[&[PortConnection];2],
|
||||
audio_tos: &[&[PortConnection];2],
|
||||
) -> Usually<Self> {
|
||||
let app = Self {
|
||||
edn: include_str!("./groovebox-view.edn").to_string(),
|
||||
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 (
|
||||
jack: &Arc<RwLock<JackConnection>>, pool: MidiPool, editor: MidiEditor,
|
||||
midi_froms: &[PortConnection], midi_tos: &[PortConnection],
|
||||
sampler: Sampler, audio_froms: &[&[PortConnection]], audio_tos: &[&[PortConnection]],
|
||||
scenes: usize, tracks: usize, track_width: usize,
|
||||
) -> Self {
|
||||
jack: &Arc<RwLock<JackConnection>>,
|
||||
bpm: Option<f64>,
|
||||
midi_froms: &[PortConnection],
|
||||
midi_tos: &[PortConnection],
|
||||
audio_froms: &[&[PortConnection];2],
|
||||
audio_tos: &[&[PortConnection];2],
|
||||
scenes: usize,
|
||||
tracks: usize,
|
||||
track_width: usize,
|
||||
) -> Usually<Self> {
|
||||
let mut arranger = Self {
|
||||
edn: include_str!("./arranger-view.edn").to_string(),
|
||||
..Self::groovebox(
|
||||
jack, pool, editor,
|
||||
None, midi_froms, midi_tos,
|
||||
sampler, audio_froms, audio_tos
|
||||
)
|
||||
..Self::groovebox(jack, bpm, midi_froms, midi_tos, audio_froms, audio_tos)?
|
||||
};
|
||||
arranger.scenes_add(scenes);
|
||||
arranger.tracks_add(tracks, track_width, &[], &[]);
|
||||
arranger
|
||||
Ok(arranger)
|
||||
}
|
||||
fn compact (&self) -> bool { false }
|
||||
fn is_editing (&self) -> bool { self.editing.load(Relaxed) }
|
||||
|
|
@ -351,20 +393,20 @@ handle!(TuiIn: |self: App, input| Ok(None));
|
|||
Zoom(Option<usize>),
|
||||
}
|
||||
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)))
|
||||
("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)))
|
||||
("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)))
|
||||
("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 {
|
||||
Self::Zoom(_) => { todo!(); },
|
||||
|
|
@ -815,9 +857,7 @@ audio!(|self: App, client, scope|{
|
|||
// Start profiling cycle
|
||||
let t0 = self.perf.get_t0();
|
||||
// Update transport clock
|
||||
if Control::Quit == ClockAudio(self).process(client, scope) {
|
||||
return Control::Quit
|
||||
}
|
||||
self.clock().update_from_scope(scope).unwrap();
|
||||
// Collect MIDI input (TODO preallocate)
|
||||
let midi_in = self.midi_ins.iter()
|
||||
.map(|port|port.port.iter(scope)
|
||||
|
|
@ -957,6 +997,82 @@ content!(TuiOut: |self: Meters<'a>| col!(
|
|||
format!("L/{:>+9.3}", self.0[0]),
|
||||
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 () {
|
||||
// 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"
|
||||
|
||||
[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"
|
||||
quanta = "0.12.3"
|
||||
#jack = { path = "../rust-jack" }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
use crate::*;
|
||||
use EdnItem::*;
|
||||
|
||||
pub trait HasClock: Send + Sync {
|
||||
fn clock (&self) -> &Clock;
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clock {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
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)]
|
||||
pub enum ClockCommand {
|
||||
Play(Option<u32>),
|
||||
|
|
@ -77,25 +63,25 @@ pub struct Clock {
|
|||
/// Size of buffer in samples
|
||||
pub chunk: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
from!(|jack: &Arc<RwLock<JackConnection>>| Clock = {
|
||||
let jack = jack.read().unwrap();
|
||||
let chunk = jack.client().buffer_size();
|
||||
let transport = jack.client().transport();
|
||||
let timebase = Arc::new(Timebase::default());
|
||||
Self {
|
||||
quant: Arc::new(24.into()),
|
||||
sync: Arc::new(384.into()),
|
||||
transport: Arc::new(Some(transport)),
|
||||
chunk: Arc::new((chunk as usize).into()),
|
||||
global: Arc::new(Moment::zero(&timebase)),
|
||||
playhead: Arc::new(Moment::zero(&timebase)),
|
||||
offset: Arc::new(Moment::zero(&timebase)),
|
||||
started: RwLock::new(None).into(),
|
||||
timebase,
|
||||
impl From<&Arc<RwLock<JackConnection>>> for Clock {
|
||||
fn from (jack: &Arc<RwLock<JackConnection>>) -> Self {
|
||||
let jack = jack.read().unwrap();
|
||||
let chunk = jack.client().buffer_size();
|
||||
let transport = jack.client().transport();
|
||||
let timebase = Arc::new(Timebase::default());
|
||||
Self {
|
||||
quant: Arc::new(24.into()),
|
||||
sync: Arc::new(384.into()),
|
||||
transport: Arc::new(Some(transport)),
|
||||
chunk: Arc::new((chunk as usize).into()),
|
||||
global: Arc::new(Moment::zero(&timebase)),
|
||||
playhead: Arc::new(Moment::zero(&timebase)),
|
||||
offset: Arc::new(Moment::zero(&timebase)),
|
||||
started: RwLock::new(None).into(),
|
||||
timebase,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
impl std::fmt::Debug for Clock {
|
||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
f.debug_struct("Clock")
|
||||
|
|
@ -109,8 +95,14 @@ impl std::fmt::Debug for Clock {
|
|||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
&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_tui; pub use self::clock_tui::*;
|
||||
mod microsecond; pub use self::microsecond::*;
|
||||
mod moment; pub use self::moment::*;
|
||||
mod note_duration; pub use self::note_duration::*;
|
||||
|
|
@ -11,17 +10,47 @@ mod timebase; pub use self::timebase::*;
|
|||
mod unit; pub use self::unit::*;
|
||||
|
||||
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 use ::atomic_float; pub(crate) use atomic_float::*;
|
||||
pub(crate) use ::tek_tui::{
|
||||
*,
|
||||
tek_output::*,
|
||||
tek_input::*,
|
||||
tek_edn::*,
|
||||
ratatui::prelude::*,
|
||||
crossterm::event::*,
|
||||
};
|
||||
pub(crate) use ::tek_edn::*;
|
||||
pub(crate) use ::tek_input::*;
|
||||
|
||||
/// Standard result type.
|
||||
pub(crate) type Usually<T> = Result<T, Box<dyn std::error::Error>>;
|
||||
/// Standard optional result type.
|
||||
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<()> {
|
||||
// TODO!
|
||||
|
|
|
|||
|
|
@ -34,6 +34,17 @@ impl PerfModel {
|
|||
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) {
|
||||
if let Some(t0) = t0 {
|
||||
let t1 = self.clock.raw();
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@ better-panic = "0.3.0"
|
|||
tek_edn = { path = "../edn" }
|
||||
tek_input = { path = "../input" }
|
||||
tek_output = { path = "../output" }
|
||||
tek_time = { path = "../time" }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ pub(crate) use tek_output::*;
|
|||
pub 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_content; pub use self::tui_content::*;
|
||||
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 ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
|
||||
|
||||
|
|
@ -89,7 +61,6 @@ pub(crate) use ratatui::{
|
|||
buffer::Cell
|
||||
};
|
||||
|
||||
|
||||
#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> {
|
||||
use crate::*;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ pub struct Tui {
|
|||
pub backend: CrosstermBackend<Stdout>,
|
||||
pub buffer: Buffer,
|
||||
pub area: [u16;4],
|
||||
pub perf: PerfModel,
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
|
|
@ -17,6 +18,7 @@ impl Tui {
|
|||
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }),
|
||||
area: [0, 0, width, height],
|
||||
backend,
|
||||
perf: Default::default(),
|
||||
})))
|
||||
}
|
||||
/// 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) {
|
||||
break
|
||||
}
|
||||
let t0 = engine.read().unwrap().perf.get_t0();
|
||||
let Size { width, height } = engine.read().unwrap().backend.size()
|
||||
.expect("get size failed");
|
||||
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);
|
||||
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);
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue