perf counter for render

This commit is contained in:
🪞👃🪞 2025-01-14 16:45:58 +01:00
parent c9677c87d8
commit 1b7f0e0b93
17 changed files with 331 additions and 358 deletions

View 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" }

View file

@ -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
}

View file

@ -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"),
//});

View file

@ -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!

View file

@ -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();