mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: merge components
This commit is contained in:
parent
8e2aed58af
commit
ed90196a60
1 changed files with 109 additions and 141 deletions
250
tek/src/lib.rs
250
tek/src/lib.rs
|
|
@ -110,25 +110,11 @@ impl TekCli {
|
||||||
jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos,
|
jack, self.bpm, &midi_froms, &midi_tos, &audio_froms, &audio_tos,
|
||||||
scenes, tracks, track_width,
|
scenes, tracks, track_width,
|
||||||
))?)?,
|
))?)?,
|
||||||
//TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok(
|
|
||||||
//SamplerTui {
|
|
||||||
//cursor: (0, 0),
|
|
||||||
//editing: None,
|
|
||||||
//mode: None,
|
|
||||||
//note_lo: 36.into(),
|
|
||||||
//note_pt: 36.into(),
|
|
||||||
//size: Measure::new(),
|
|
||||||
//state: Sampler::new(jack, &"sampler", &midi_froms,
|
|
||||||
//&[&left_froms, &right_froms],
|
|
||||||
//&[&left_tos, &right_tos])?,
|
|
||||||
//color,
|
|
||||||
//}
|
|
||||||
//))?)?,
|
|
||||||
_ => todo!()
|
_ => todo!()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Default, Debug)] pub struct Tek {
|
#[derive(Default, Debug)] struct Tek {
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
pub jack: Arc<RwLock<JackConnection>>,
|
||||||
pub edn: String,
|
pub edn: String,
|
||||||
pub clock: Clock,
|
pub clock: Clock,
|
||||||
|
|
@ -156,7 +142,6 @@ impl TekCli {
|
||||||
has_size!(<TuiOut>|self: Tek|&self.size);
|
has_size!(<TuiOut>|self: Tek|&self.size);
|
||||||
has_clock!(|self: Tek|self.clock);
|
has_clock!(|self: Tek|self.clock);
|
||||||
has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips);
|
has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips);
|
||||||
//has_editor!(|self: Tek|self.editor.as_ref().expect("no editor"));
|
|
||||||
has_jack!(|self: Tek|&self.jack);
|
has_jack!(|self: Tek|&self.jack);
|
||||||
has_sampler!(|self: Tek|{
|
has_sampler!(|self: Tek|{
|
||||||
sampler = self.sampler;
|
sampler = self.sampler;
|
||||||
|
|
@ -201,22 +186,20 @@ edn_view!(TuiOut: |self: Tek| self.size.of(EdnView::from_source(self, self.edn.a
|
||||||
// Provide components:
|
// Provide components:
|
||||||
Box<dyn Render<TuiOut> + 'a> {
|
Box<dyn Render<TuiOut> + 'a> {
|
||||||
":editor" => (&self.editor).boxed(),
|
":editor" => (&self.editor).boxed(),
|
||||||
":pool" => self.pool.as_ref().map(|pool|PoolView(self.compact(), pool)).boxed(),
|
":pool" => self.view_pool().boxed(),
|
||||||
":sample" => self.view_sample(self.is_editing()).boxed(),
|
":sample" => self.view_sample(self.is_editing()).boxed(),
|
||||||
":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(),
|
":sampler" => self.view_sampler(self.is_editing(), &self.editor).boxed(),
|
||||||
":status" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(),
|
":status" => self.view_editor().boxed(),
|
||||||
":toolbar" => self.view_clock().boxed(),//ClockView::new(true, &self.clock).boxed(),
|
":toolbar" => self.view_clock().boxed(),
|
||||||
":tracks" => self.row(self.w(), 3, self.track_header(), self.track_cells()).boxed(),
|
":tracks" => self.view_row(self.w(), 3, self.track_header(), self.track_cells()).boxed(),
|
||||||
":inputs" => self.row(self.w(), 3, self.input_header(), self.input_cells()).boxed(),
|
":inputs" => self.view_row(self.w(), 3, self.input_header(), self.input_cells()).boxed(),
|
||||||
":outputs" => self.row(self.w(), 3, self.output_header(), self.output_cells()).boxed(),
|
":outputs" => self.view_row(self.w(), 3, self.output_header(), self.output_cells()).boxed(),
|
||||||
":scenes" => Outer(Style::default().fg(TuiTheme::g(0))).enclose_bg(self.row(
|
":scenes" => Outer(Style::default().fg(TuiTheme::g(0))).enclose_bg(self.view_row(
|
||||||
self.w(),
|
self.w(), self.size.h().saturating_sub(12) as u16,
|
||||||
self.size.h().saturating_sub(12) as u16,
|
self.scene_header(), self.scene_cells()
|
||||||
self.scene_header(),
|
|
||||||
self.scene_cells(self.is_editing())
|
|
||||||
)).boxed() }});
|
)).boxed() }});
|
||||||
impl Tek {
|
impl Tek {
|
||||||
pub fn new_arranger (
|
fn new_arranger (
|
||||||
jack: &Arc<RwLock<JackConnection>>,
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
bpm: Option<f64>,
|
bpm: Option<f64>,
|
||||||
midi_froms: &[PortConnection],
|
midi_froms: &[PortConnection],
|
||||||
|
|
@ -235,7 +218,7 @@ impl Tek {
|
||||||
arranger.tracks_add(tracks, track_width, &[], &[]);
|
arranger.tracks_add(tracks, track_width, &[], &[]);
|
||||||
Ok(arranger)
|
Ok(arranger)
|
||||||
}
|
}
|
||||||
pub fn new_groovebox (
|
fn new_groovebox (
|
||||||
jack: &Arc<RwLock<JackConnection>>,
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
bpm: Option<f64>,
|
bpm: Option<f64>,
|
||||||
midi_froms: &[PortConnection],
|
midi_froms: &[PortConnection],
|
||||||
|
|
@ -259,7 +242,7 @@ impl Tek {
|
||||||
}
|
}
|
||||||
Ok(app)
|
Ok(app)
|
||||||
}
|
}
|
||||||
pub fn new_sequencer (
|
fn new_sequencer (
|
||||||
jack: &Arc<RwLock<JackConnection>>,
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
bpm: Option<f64>,
|
bpm: Option<f64>,
|
||||||
midi_froms: &[PortConnection],
|
midi_froms: &[PortConnection],
|
||||||
|
|
@ -283,7 +266,7 @@ impl Tek {
|
||||||
..Self::new_clock(jack, bpm)?
|
..Self::new_clock(jack, bpm)?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn new_clock (
|
fn new_clock (
|
||||||
jack: &Arc<RwLock<JackConnection>>,
|
jack: &Arc<RwLock<JackConnection>>,
|
||||||
bpm: Option<f64>,
|
bpm: Option<f64>,
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
|
|
@ -317,13 +300,86 @@ impl Tek {
|
||||||
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
|
fn editor (&self) -> impl Content<TuiOut> + '_ { &self.editor }
|
||||||
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
|
fn view_clock (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Outer(Style::default().fg(TuiTheme::g(0))).enclose(row!(
|
Outer(Style::default().fg(TuiTheme::g(0))).enclose(row!(
|
||||||
OutputStats::new(self.compact, &self.clock),
|
self.view_engine_stats(), " ",
|
||||||
" ",
|
self.view_play_pause(), " ",
|
||||||
PlayPause { compact: false, playing: self.clock.is_rolling() },
|
self.view_beat_stats(),
|
||||||
" ",
|
|
||||||
BeatStats::new(self.compact, &self.clock),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
fn view_beat_stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
let Self { compact, ref clock, .. } = self;
|
||||||
|
let now = clock.started.read().unwrap().as_ref().map(|start|clock.global.usec.get() - start.usec.get());
|
||||||
|
let beat = ||now.map(|now|clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)))
|
||||||
|
.unwrap_or("-.-.--".into());
|
||||||
|
let time = ||now.map(|now|format!("{:.3}s", now/1000000.))
|
||||||
|
.unwrap_or("-.---s".into());
|
||||||
|
let bpm = ||format!("{:.3}", clock.timebase.bpm.get());
|
||||||
|
Either::new(self.compact,
|
||||||
|
row!(FieldV(TuiTheme::g(128).into(), "BPM", bpm()),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Beat", beat()),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Time", time()),),
|
||||||
|
col!(Bsp::e(Tui::fg(TuiTheme::g(255), bpm()), " BPM"),
|
||||||
|
Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), beat())),
|
||||||
|
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), time()))))
|
||||||
|
}
|
||||||
|
fn view_engine_stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
let Self { compact, ref clock, .. } = self;
|
||||||
|
let rate = clock.timebase.sr.get();
|
||||||
|
let chunk = clock.chunk.load(Relaxed);
|
||||||
|
let sr = move||format!("{}", if *compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)});
|
||||||
|
let buffer_size = move||format!("{chunk}");
|
||||||
|
let latency = move||format!("{:.1}ms", chunk as f64 / rate * 1000.);
|
||||||
|
Either::new(self.compact,
|
||||||
|
row!(FieldV(TuiTheme::g(128).into(), "SR", sr()),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Buf", buffer_size()),
|
||||||
|
FieldV(TuiTheme::g(128).into(), "Lat", latency())),
|
||||||
|
col!(Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", sr())), " sample rate"),
|
||||||
|
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", buffer_size())), " sample buffer"),
|
||||||
|
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", latency())), " latency")))
|
||||||
|
}
|
||||||
|
fn view_meter <'a> (&'a self, label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
|
||||||
|
col!(
|
||||||
|
Field(TuiTheme::g(128).into(), label, format!("{:>+9.3}", value)),
|
||||||
|
Fixed::xy(if value >= 0.0 { 13 }
|
||||||
|
else if value >= -1.0 { 12 }
|
||||||
|
else if value >= -2.0 { 11 }
|
||||||
|
else if value >= -3.0 { 10 }
|
||||||
|
else if value >= -4.0 { 9 }
|
||||||
|
else if value >= -6.0 { 8 }
|
||||||
|
else if value >= -9.0 { 7 }
|
||||||
|
else if value >= -12.0 { 6 }
|
||||||
|
else if value >= -15.0 { 5 }
|
||||||
|
else if value >= -20.0 { 4 }
|
||||||
|
else if value >= -25.0 { 3 }
|
||||||
|
else if value >= -30.0 { 2 }
|
||||||
|
else if value >= -40.0 { 1 }
|
||||||
|
else { 0 }, 1, Tui::bg(if value >= 0.0 { Color::Red }
|
||||||
|
else if value >= -3.0 { Color::Yellow }
|
||||||
|
else { Color::Green }, ())))
|
||||||
|
}
|
||||||
|
fn view_meters (&self, values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
col!(
|
||||||
|
format!("L/{:>+9.3}", values[0]),
|
||||||
|
format!("R/{:>+9.3}", values[1]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn view_play_pause (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
let playing = self.clock.is_rolling();
|
||||||
|
Tui::bg(
|
||||||
|
if playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
||||||
|
Either::new(self.compact,
|
||||||
|
Thunk::new(move||Fixed::x(9, Either::new(playing,
|
||||||
|
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
|
||||||
|
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
|
||||||
|
Thunk::new(move||Fixed::x(5, Either::new(playing,
|
||||||
|
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
||||||
|
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))))))
|
||||||
|
}
|
||||||
|
fn view_editor (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status()))
|
||||||
|
}
|
||||||
|
fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
self.pool.as_ref().map(|pool|PoolView(self.compact(), pool))
|
||||||
|
}
|
||||||
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
|
fn pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.pool.as_ref()
|
self.pool.as_ref()
|
||||||
.map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool))))
|
.map(|pool|Align::e(Fixed::x(self.sidebar_w(), PoolView(self.compact(), pool))))
|
||||||
|
|
@ -340,7 +396,7 @@ impl Tek {
|
||||||
let w = if self.is_editing() { 8 } else { w };
|
let w = if self.is_editing() { 8 } else { w };
|
||||||
w
|
w
|
||||||
}
|
}
|
||||||
fn row <'a> (
|
fn view_row <'a> (
|
||||||
&'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a
|
&'a self, w: u16, h: u16, a: impl Content<TuiOut> + 'a, b: impl Content<TuiOut> + 'a
|
||||||
) -> impl Content<TuiOut> + 'a {
|
) -> impl Content<TuiOut> + 'a {
|
||||||
Fixed::y(h, Bsp::e(
|
Fixed::y(h, Bsp::e(
|
||||||
|
|
@ -438,9 +494,10 @@ impl Tek {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn scene_cells <'a> (&'a self, editing: bool) -> BoxThunk<'a, TuiOut> {
|
fn scene_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||||
let tracks = move||self.tracks_sizes(self.is_editing(), self.editor_w());
|
let editing = self.is_editing();
|
||||||
let scenes = ||self.scenes_sizes(self.is_editing(), 2, 15);
|
let tracks = move||self.tracks_sizes(editing, self.editor_w());
|
||||||
|
let scenes = move||self.scenes_sizes(editing, 2, 15);
|
||||||
let selected_track = self.selected().track();
|
let selected_track = self.selected().track();
|
||||||
let selected_scene = self.selected().scene();
|
let selected_scene = self.selected().scene();
|
||||||
(move||Align::c(Map::new(tracks, move|(_, track, x1, x2), t| {
|
(move||Align::c(Map::new(tracks, move|(_, track, x1, x2), t| {
|
||||||
|
|
@ -649,19 +706,19 @@ command!(|self: TekCommand, app: Tek|match self {
|
||||||
}
|
}
|
||||||
/// Focus identification methods
|
/// Focus identification methods
|
||||||
impl Selection {
|
impl Selection {
|
||||||
pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) }
|
fn is_mix (&self) -> bool { matches!(self, Self::Mix) }
|
||||||
pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) }
|
fn is_track (&self) -> bool { matches!(self, Self::Track(_)) }
|
||||||
pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) }
|
fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) }
|
||||||
pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) }
|
fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) }
|
||||||
pub fn track (&self) -> Option<usize> {
|
fn track (&self) -> Option<usize> {
|
||||||
use Selection::*;
|
use Selection::*;
|
||||||
match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None }
|
match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None }
|
||||||
}
|
}
|
||||||
pub fn scene (&self) -> Option<usize> {
|
fn scene (&self) -> Option<usize> {
|
||||||
use Selection::*;
|
use Selection::*;
|
||||||
match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None }
|
match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None }
|
||||||
}
|
}
|
||||||
pub fn description (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
|
fn description (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
|
||||||
format!("Selected: {}", match self {
|
format!("Selected: {}", match self {
|
||||||
Self::Mix => "Everything".to_string(),
|
Self::Mix => "Everything".to_string(),
|
||||||
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
|
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
|
||||||
|
|
@ -686,7 +743,7 @@ pub trait HasSelection {
|
||||||
fn selected (&self) -> &Selection;
|
fn selected (&self) -> &Selection;
|
||||||
fn selected_mut (&mut self) -> &mut Selection;
|
fn selected_mut (&mut self) -> &mut Selection;
|
||||||
}
|
}
|
||||||
#[derive(Debug, Default)] pub struct Track {
|
#[derive(Debug, Default)] struct Track {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
/// Preferred width of track column
|
/// Preferred width of track column
|
||||||
|
|
@ -859,7 +916,7 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync
|
||||||
trait Device: Send + Sync + std::fmt::Debug {}
|
trait Device: Send + Sync + std::fmt::Debug {}
|
||||||
impl Device for Sampler {}
|
impl Device for Sampler {}
|
||||||
impl Device for Plugin {}
|
impl Device for Plugin {}
|
||||||
#[derive(Debug, Default)] pub struct Scene {
|
#[derive(Debug, Default)] struct Scene {
|
||||||
/// Name of scene
|
/// Name of scene
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
/// Clips in scene, one per track
|
/// Clips in scene, one per track
|
||||||
|
|
@ -869,14 +926,14 @@ impl Device for Plugin {}
|
||||||
}
|
}
|
||||||
impl Scene {
|
impl Scene {
|
||||||
/// Returns the pulse length of the longest clip in the scene
|
/// Returns the pulse length of the longest clip in the scene
|
||||||
pub fn pulses (&self) -> usize {
|
fn pulses (&self) -> usize {
|
||||||
self.clips.iter().fold(0, |a, p|{
|
self.clips.iter().fold(0, |a, p|{
|
||||||
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
|
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// Returns true if all clips in the scene are
|
/// Returns true if all clips in the scene are
|
||||||
/// currently playing on the given collection of tracks.
|
/// currently playing on the given collection of tracks.
|
||||||
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
fn is_playing (&self, tracks: &[Track]) -> bool {
|
||||||
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
|
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
|
||||||
.all(|(track_index, clip)|match clip {
|
.all(|(track_index, clip)|match clip {
|
||||||
Some(c) => tracks
|
Some(c) => tracks
|
||||||
|
|
@ -892,7 +949,7 @@ impl Scene {
|
||||||
None => true
|
None => true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
|
fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
|
||||||
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1104,95 +1161,6 @@ fn button <'a> (key: &'a str, label: &'a str) -> impl Content<TuiOut> + 'a {
|
||||||
Margin::x(1, Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(96), label)),
|
Margin::x(1, Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(96), label)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
#[derive(Debug, Default)] struct Meter<'a>(pub &'a str, pub f32);
|
|
||||||
content!(TuiOut: |self: Meter<'a>| col!(
|
|
||||||
Field(TuiTheme::g(128).into(), self.0, format!("{:>+9.3}", self.1)),
|
|
||||||
Fixed::xy(if self.1 >= 0.0 { 13 }
|
|
||||||
else if self.1 >= -1.0 { 12 }
|
|
||||||
else if self.1 >= -2.0 { 11 }
|
|
||||||
else if self.1 >= -3.0 { 10 }
|
|
||||||
else if self.1 >= -4.0 { 9 }
|
|
||||||
else if self.1 >= -6.0 { 8 }
|
|
||||||
else if self.1 >= -9.0 { 7 }
|
|
||||||
else if self.1 >= -12.0 { 6 }
|
|
||||||
else if self.1 >= -15.0 { 5 }
|
|
||||||
else if self.1 >= -20.0 { 4 }
|
|
||||||
else if self.1 >= -25.0 { 3 }
|
|
||||||
else if self.1 >= -30.0 { 2 }
|
|
||||||
else if self.1 >= -40.0 { 1 }
|
|
||||||
else { 0 }, 1, Tui::bg(if self.1 >= 0.0 { Color::Red }
|
|
||||||
else if self.1 >= -3.0 { Color::Yellow }
|
|
||||||
else { Color::Green }, ()))));
|
|
||||||
#[derive(Debug, Default)] struct Meters<'a>(pub &'a[f32]);
|
|
||||||
content!(TuiOut: |self: Meters<'a>| col!(
|
|
||||||
format!("L/{:>+9.3}", self.0[0]),
|
|
||||||
format!("R/{:>+9.3}", self.0[1])
|
|
||||||
));
|
|
||||||
pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock }
|
|
||||||
content!(TuiOut: |self: ClockView<'a>| Outer(Style::default().fg(TuiTheme::g(0))).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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(test)] fn test_tek () {
|
#[cfg(test)] fn test_tek () {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
TekCli::command().debug_assert();
|
TekCli::command().debug_assert();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue