diff --git a/Cargo.lock b/Cargo.lock index d347c250..d5775d8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/tek_cli/src/cli_sequencer.rs b/crates/tek_cli/src/cli_sequencer.rs index 54a6abea..3b9433e7 100644 --- a/crates/tek_cli/src/cli_sequencer.rs +++ b/crates/tek_cli/src/cli_sequencer.rs @@ -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())); } diff --git a/crates/tek_core/Cargo.toml b/crates/tek_core/Cargo.toml index 80ec514e..dbee0657 100644 --- a/crates/tek_core/Cargo.toml +++ b/crates/tek_core/Cargo.toml @@ -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] diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index 18bb706c..b3d27923 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -57,7 +57,6 @@ impl Command 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(); } } diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index e189ec71..5416beb8 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -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 { + if self.enabled { + Some(self.clock.raw()) + } else { + None + } + } + pub fn update (&self, t0: Option, 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 { + 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::*; diff --git a/crates/tek_tui/src/tui_apps.rs b/crates/tek_tui/src/tui_apps.rs index 988b3ffd..17daf512 100644 --- a/crates/tek_tui/src/tui_apps.rs +++ b/crates/tek_tui/src/tui_apps.rs @@ -23,6 +23,7 @@ pub struct SequencerTui { pub note_buf: Vec, pub midi_buf: Vec>>, pub focus: FocusState>, + pub perf: PerfModel, } /// Root view for standalone `tek_arranger` diff --git a/crates/tek_tui/src/tui_command.rs b/crates/tek_tui/src/tui_command.rs index 92203079..7a0cd718 100644 --- a/crates/tek_tui/src/tui_command.rs +++ b/crates/tek_tui/src/tui_command.rs @@ -11,15 +11,13 @@ impl Command 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) - })) + }) } } diff --git a/crates/tek_tui/src/tui_content.rs b/crates/tek_tui/src/tui_content.rs index 3ac092fd..29d94162 100644 --- a/crates/tek_tui/src/tui_content.rs +++ b/crates/tek_tui/src/tui_content.rs @@ -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 { - 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(), ) } } diff --git a/crates/tek_tui/src/tui_control.rs b/crates/tek_tui/src/tui_control.rs index 085be65c..224ca03c 100644 --- a/crates/tek_tui/src/tui_control.rs +++ b/crates/tek_tui/src/tui_control.rs @@ -1,9 +1,20 @@ use crate::*; -pub trait TransportControl: ClockApi { +pub trait TransportControl: ClockApi + FocusGrid + HasEnter { fn transport_focused (&self) -> Option; } +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>>; + fn toggle_loop (&mut self); + fn randomize_color (&mut self); +} + impl TransportControl for TransportTui { fn transport_focused (&self) -> Option { 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>>; - fn toggle_loop (&mut self); - fn randomize_color (&mut self); -} - impl ArrangerControl for ArrangerTui { fn selected (&self) -> ArrangerSelection { self.selected diff --git a/crates/tek_tui/src/tui_init.rs b/crates/tek_tui/src/tui_init.rs index d6073291..613c1816 100644 --- a/crates/tek_tui/src/tui_init.rs +++ b/crates/tek_tui/src/tui_init.rs @@ -30,7 +30,8 @@ impl TryFrom<&Arc>> 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(), }) } } diff --git a/crates/tek_tui/src/tui_jack.rs b/crates/tek_tui/src/tui_jack.rs index 4a2b0d5b..768f2f51 100644 --- a/crates/tek_tui/src/tui_jack.rs +++ b/crates/tek_tui/src/tui_jack.rs @@ -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 } } diff --git a/crates/tek_tui/src/tui_model.rs b/crates/tek_tui/src/tui_model.rs index 04b88e00..41b47178 100644 --- a/crates/tek_tui/src/tui_model.rs +++ b/crates/tek_tui/src/tui_model.rs @@ -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>, + pub midi_ins: Vec>, /// Play from current sequence to MIDI ports - pub(crate) midi_outs: Vec>, + pub midi_outs: Vec>, /// Notes currently held at input pub(crate) notes_in: Arc>, /// Notes currently held at output