mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
performance meter
This commit is contained in:
parent
a509db7215
commit
398f676ae3
12 changed files with 123 additions and 32 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
|
@ -1996,6 +1996,21 @@ dependencies = [
|
||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
|
|
@ -2064,6 +2079,15 @@ dependencies = [
|
||||||
"unicode-width",
|
"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]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
|
@ -2692,6 +2716,7 @@ dependencies = [
|
||||||
"midly",
|
"midly",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"palette",
|
"palette",
|
||||||
|
"quanta",
|
||||||
"rand",
|
"rand",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"toml",
|
"toml",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use tek_api::JackActivate;
|
use tek_api::JackActivate;
|
||||||
use tek_core::{*, clap::{self, Parser}};
|
use tek_core::{*, jack::{MidiIn, MidiOut}, clap::{self, Parser}};
|
||||||
|
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
SequencerCli::parse().run()
|
SequencerCli::parse().run()
|
||||||
|
|
@ -22,6 +22,11 @@ impl SequencerCli {
|
||||||
fn run (&self) -> Usually<()> {
|
fn run (&self) -> Usually<()> {
|
||||||
Tui::run(JackClient::new("tek_sequencer")?.activate_with(|jack|{
|
Tui::run(JackClient::new("tek_sequencer")?.activate_with(|jack|{
|
||||||
let mut app = tek_tui::SequencerTui::try_from(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() {
|
if let Some(_) = self.name.as_ref() {
|
||||||
// TODO: sequencer.name = Arc::new(RwLock::new(name.clone()));
|
// TODO: sequencer.name = Arc::new(RwLock::new(name.clone()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ palette = { version = "0.7.6", features = [ "random" ] }
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||||
toml = "0.8.12"
|
toml = "0.8.12"
|
||||||
|
quanta = "0.12.3"
|
||||||
#no_deadlocks = "1.3.2"
|
#no_deadlocks = "1.3.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusComman
|
||||||
Right => { state.focus_right(); },
|
Right => { state.focus_right(); },
|
||||||
Enter => { state.focus_enter(); },
|
Enter => { state.focus_enter(); },
|
||||||
Exit => { state.focus_exit(); },
|
Exit => { state.focus_exit(); },
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +91,7 @@ pub trait HasEnter: HasFocus {
|
||||||
}
|
}
|
||||||
/// Exit the currently entered component
|
/// Exit the currently entered component
|
||||||
fn focus_exit (&mut self) {
|
fn focus_exit (&mut self) {
|
||||||
self.set_entered(true);
|
self.set_entered(false);
|
||||||
self.focus_updated();
|
self.focus_updated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -359,6 +359,59 @@ pub fn pulses_to_name (pulses: usize) -> &'static str {
|
||||||
for (length, name) in &NOTE_DURATIONS { if *length == pulses { return name } }
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ pub struct SequencerTui {
|
||||||
pub note_buf: Vec<u8>,
|
pub note_buf: Vec<u8>,
|
||||||
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
pub midi_buf: Vec<Vec<Vec<u8>>>,
|
||||||
pub focus: FocusState<AppFocus<SequencerFocus>>,
|
pub focus: FocusState<AppFocus<SequencerFocus>>,
|
||||||
|
pub perf: PerfModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Root view for standalone `tek_arranger`
|
/// Root view for standalone `tek_arranger`
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,13 @@ impl<T: TransportControl> Command<T> for TransportCommand {
|
||||||
use TransportCommand::{Focus, Clock};
|
use TransportCommand::{Focus, Clock};
|
||||||
use FocusCommand::{Next, Prev};
|
use FocusCommand::{Next, Prev};
|
||||||
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
use ClockCommand::{SetBpm, SetQuant, SetSync};
|
||||||
Ok(Some(match self {
|
Ok(match self {
|
||||||
Focus(Next) => { todo!() }
|
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||||
Focus(Prev) => { todo!() },
|
Clock(SetBpm(bpm)) => Some(Clock(SetBpm(state.bpm().set(bpm)))),
|
||||||
Focus(_) => { unimplemented!() },
|
Clock(SetQuant(quant)) => Some(Clock(SetQuant(state.quant().set(quant)))),
|
||||||
Clock(SetBpm(bpm)) => Clock(SetBpm(state.bpm().set(bpm))),
|
Clock(SetSync(sync)) => Some(Clock(SetSync(state.sync().set(sync)))),
|
||||||
Clock(SetQuant(quant)) => Clock(SetQuant(state.quant().set(quant))),
|
|
||||||
Clock(SetSync(sync)) => Clock(SetSync(state.sync().set(sync))),
|
|
||||||
_ => return Ok(None)
|
_ => return Ok(None)
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,18 @@ impl<'a, T: TransportViewState> Content for TransportView<'a, T> {
|
||||||
impl Content for SequencerTui {
|
impl Content for SequencerTui {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
col!(
|
lay!(
|
||||||
widget(&TransportView(self)),
|
col!(
|
||||||
Split::right(20,
|
widget(&TransportView(self)),
|
||||||
widget(&PhrasesView(self)),
|
Split::right(20,
|
||||||
widget(&PhraseView(self)),
|
widget(&PhrasesView(self)),
|
||||||
).min_y(20)
|
widget(&PhraseView(self)),
|
||||||
|
).min_y(20)
|
||||||
|
),
|
||||||
|
self.perf.percentage()
|
||||||
|
.map(|cpu|format!("{cpu:.03}%"))
|
||||||
|
.fg(Color::Rgb(255,128,0))
|
||||||
|
.align_sw(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,20 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub trait TransportControl: ClockApi {
|
pub trait TransportControl: ClockApi + FocusGrid + HasEnter {
|
||||||
fn transport_focused (&self) -> Option<TransportFocus>;
|
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 {
|
impl TransportControl for TransportTui {
|
||||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
fn transport_focused (&self) -> Option<TransportFocus> {
|
||||||
if let AppFocus::Content(focus) = self.focus.inner() {
|
if let AppFocus::Content(focus) = self.focus.inner() {
|
||||||
|
|
@ -34,19 +45,8 @@ impl TransportControl for ArrangerTui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SequencerControl: TransportControl {}
|
|
||||||
|
|
||||||
impl SequencerControl for SequencerTui {}
|
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 {
|
impl ArrangerControl for ArrangerTui {
|
||||||
fn selected (&self) -> ArrangerSelection {
|
fn selected (&self) -> ArrangerSelection {
|
||||||
self.selected
|
self.selected
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerTui {
|
||||||
midi_buf: vec![vec![];65536],
|
midi_buf: vec![vec![];65536],
|
||||||
note_buf: vec![],
|
note_buf: vec![],
|
||||||
clock,
|
clock,
|
||||||
focus: FocusState::Entered(AppFocus::Content(SequencerFocus::Transport(TransportFocus::Bpm)))
|
focus: FocusState::Entered(AppFocus::Content(SequencerFocus::Transport(TransportFocus::Bpm))),
|
||||||
|
perf: PerfModel::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ impl Audio for TransportTui {
|
||||||
|
|
||||||
impl Audio for SequencerTui {
|
impl Audio for SequencerTui {
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
let t0 = self.perf.get_t0();
|
||||||
if ClockAudio(self).process(client, scope) == Control::Quit {
|
if ClockAudio(self).process(client, scope) == Control::Quit {
|
||||||
return Control::Quit
|
return Control::Quit
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +19,7 @@ impl Audio for SequencerTui {
|
||||||
).process(client, scope) == Control::Quit {
|
).process(client, scope) == Control::Quit {
|
||||||
return Control::Quit
|
return Control::Quit
|
||||||
}
|
}
|
||||||
|
self.perf.update(t0, scope);
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,9 @@ pub struct PhrasePlayerModel {
|
||||||
/// Send all notes off
|
/// Send all notes off
|
||||||
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
||||||
/// Record from MIDI ports to current sequence.
|
/// 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
|
/// 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
|
/// Notes currently held at input
|
||||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
/// Notes currently held at output
|
/// Notes currently held at output
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue