performance meter

This commit is contained in:
🪞👃🪞 2024-11-23 22:10:17 +01:00
parent a509db7215
commit 398f676ae3
12 changed files with 123 additions and 32 deletions

25
Cargo.lock generated
View file

@ -1996,6 +1996,21 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quanta"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5"
dependencies = [
"crossbeam-utils",
"libc",
"once_cell",
"raw-cpuid",
"wasi",
"web-sys",
"winapi",
]
[[package]]
name = "quick-xml"
version = "0.36.1"
@ -2064,6 +2079,15 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "raw-cpuid"
version = "11.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0"
dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "raw-window-handle"
version = "0.6.2"
@ -2692,6 +2716,7 @@ dependencies = [
"midly",
"once_cell",
"palette",
"quanta",
"rand",
"ratatui",
"toml",

View file

@ -1,5 +1,5 @@
use tek_api::JackActivate;
use tek_core::{*, clap::{self, Parser}};
use tek_core::{*, jack::{MidiIn, MidiOut}, clap::{self, Parser}};
pub fn main () -> Usually<()> {
SequencerCli::parse().run()
@ -22,6 +22,11 @@ impl SequencerCli {
fn run (&self) -> Usually<()> {
Tui::run(JackClient::new("tek_sequencer")?.activate_with(|jack|{
let mut app = tek_tui::SequencerTui::try_from(jack)?;
// TODO: create from arguments
let midi_in = app.jack.read().unwrap().register_port("in", MidiIn::default())?;
app.player.midi_ins.push(midi_in);
let midi_out = app.jack.read().unwrap().register_port("out", MidiOut::default())?;
app.player.midi_outs.push(midi_out);
if let Some(_) = self.name.as_ref() {
// TODO: sequencer.name = Arc::new(RwLock::new(name.clone()));
}

View file

@ -17,6 +17,7 @@ palette = { version = "0.7.6", features = [ "random" ] }
rand = "0.8.5"
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
toml = "0.8.12"
quanta = "0.12.3"
#no_deadlocks = "1.3.2"
[dev-dependencies]

View file

@ -57,7 +57,6 @@ impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusComman
Right => { state.focus_right(); },
Enter => { state.focus_enter(); },
Exit => { state.focus_exit(); },
_ => {}
}
Ok(None)
}
@ -92,7 +91,7 @@ pub trait HasEnter: HasFocus {
}
/// Exit the currently entered component
fn focus_exit (&mut self) {
self.set_entered(true);
self.set_entered(false);
self.focus_updated();
}
}

View file

@ -359,6 +359,59 @@ pub fn pulses_to_name (pulses: usize) -> &'static str {
for (length, name) in &NOTE_DURATIONS { if *length == pulses { return name } }
""
}
pub struct PerfModel {
pub enabled: bool,
clock: quanta::Clock,
// In nanoseconds
used: AtomicF64,
// In microseconds
period: AtomicF64,
}
impl Default for PerfModel {
fn default () -> Self {
Self {
enabled: true,
clock: quanta::Clock::new(),
used: Default::default(),
period: Default::default(),
}
}
}
impl PerfModel {
pub fn get_t0 (&self) -> Option<u64> {
if self.enabled {
Some(self.clock.raw())
} else {
None
}
}
pub fn update (&self, t0: Option<u64>, scope: &jack::ProcessScope) {
if let Some(t0) = t0 {
let t1 = self.clock.raw();
self.used.store(
self.clock.delta_as_nanos(t0, t1) as f64,
Ordering::Relaxed,
);
self.period.store(
scope.cycle_times().unwrap().period_usecs as f64,
Ordering::Relaxed,
);
}
}
pub fn percentage (&self) -> Option<f64> {
let period = self.period.load(Ordering::Relaxed) * 1000.0;
if period > 0.0 {
let used = self.used.load(Ordering::Relaxed);
Some(100.0 * used / period)
} else {
None
}
}
}
#[cfg(test)]
mod test {
use super::*;

View file

@ -23,6 +23,7 @@ pub struct SequencerTui {
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub focus: FocusState<AppFocus<SequencerFocus>>,
pub perf: PerfModel,
}
/// Root view for standalone `tek_arranger`

View file

@ -11,15 +11,13 @@ impl<T: TransportControl> Command<T> for TransportCommand {
use TransportCommand::{Focus, Clock};
use FocusCommand::{Next, Prev};
use ClockCommand::{SetBpm, SetQuant, SetSync};
Ok(Some(match self {
Focus(Next) => { todo!() }
Focus(Prev) => { todo!() },
Focus(_) => { unimplemented!() },
Clock(SetBpm(bpm)) => Clock(SetBpm(state.bpm().set(bpm))),
Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))),
Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))),
Ok(match self {
Focus(cmd) => cmd.execute(state)?.map(Focus),
Clock(SetBpm(bpm)) => Some(Clock(SetBpm(state.bpm().set(bpm)))),
Clock(SetQuant(quant)) => Some(Clock(SetQuant(state.quant().set(quant)))),
Clock(SetSync(sync)) => Some(Clock(SetSync(state.sync().set(sync)))),
_ => return Ok(None)
}))
})
}
}

View file

@ -42,12 +42,18 @@ impl<'a, T: TransportViewState> Content for TransportView<'a, T> {
impl Content for SequencerTui {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
col!(
widget(&TransportView(self)),
Split::right(20,
widget(&PhrasesView(self)),
widget(&PhraseView(self)),
).min_y(20)
lay!(
col!(
widget(&TransportView(self)),
Split::right(20,
widget(&PhrasesView(self)),
widget(&PhraseView(self)),
).min_y(20)
),
self.perf.percentage()
.map(|cpu|format!("{cpu:.03}%"))
.fg(Color::Rgb(255,128,0))
.align_sw(),
)
}
}

View file

@ -1,9 +1,20 @@
use crate::*;
pub trait TransportControl: ClockApi {
pub trait TransportControl: ClockApi + FocusGrid + HasEnter {
fn transport_focused (&self) -> Option<TransportFocus>;
}
pub trait SequencerControl: TransportControl {}
pub trait ArrangerControl: TransportControl {
fn selected (&self) -> ArrangerSelection;
fn selected_mut (&mut self) -> &mut ArrangerSelection;
fn activate (&mut self);
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>>;
fn toggle_loop (&mut self);
fn randomize_color (&mut self);
}
impl TransportControl for TransportTui {
fn transport_focused (&self) -> Option<TransportFocus> {
if let AppFocus::Content(focus) = self.focus.inner() {
@ -34,19 +45,8 @@ impl TransportControl for ArrangerTui {
}
}
pub trait SequencerControl: TransportControl {}
impl SequencerControl for SequencerTui {}
pub trait ArrangerControl: TransportControl {
fn selected (&self) -> ArrangerSelection;
fn selected_mut (&mut self) -> &mut ArrangerSelection;
fn activate (&mut self);
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>>;
fn toggle_loop (&mut self);
fn randomize_color (&mut self);
}
impl ArrangerControl for ArrangerTui {
fn selected (&self) -> ArrangerSelection {
self.selected

View file

@ -30,7 +30,8 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
midi_buf: vec![vec![];65536],
note_buf: vec![],
clock,
focus: FocusState::Entered(AppFocus::Content(SequencerFocus::Transport(TransportFocus::Bpm)))
focus: FocusState::Entered(AppFocus::Content(SequencerFocus::Transport(TransportFocus::Bpm))),
perf: PerfModel::default(),
})
}
}

View file

@ -8,6 +8,7 @@ impl Audio for TransportTui {
impl Audio for SequencerTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
let t0 = self.perf.get_t0();
if ClockAudio(self).process(client, scope) == Control::Quit {
return Control::Quit
}
@ -18,6 +19,7 @@ impl Audio for SequencerTui {
).process(client, scope) == Control::Quit {
return Control::Quit
}
self.perf.update(t0, scope);
Control::Continue
}
}

View file

@ -49,9 +49,9 @@ pub struct PhrasePlayerModel {
/// Send all notes off
pub(crate) reset: bool, // TODO?: after Some(nframes)
/// Record from MIDI ports to current sequence.
pub(crate) midi_ins: Vec<Port<MidiIn>>,
pub midi_ins: Vec<Port<MidiIn>>,
/// Play from current sequence to MIDI ports
pub(crate) midi_outs: Vec<Port<MidiOut>>,
pub midi_outs: Vec<Port<MidiOut>>,
/// Notes currently held at input
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output