mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +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",
|
||||
]
|
||||
|
||||
[[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",
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue