mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
new minimal transport bar
This commit is contained in:
parent
d4c96f4b41
commit
7f57465b3a
5 changed files with 141 additions and 376 deletions
|
|
@ -101,7 +101,6 @@ impl ArrangerTui {
|
||||||
}
|
}
|
||||||
render!(Tui: (self: ArrangerTui) => {
|
render!(Tui: (self: ArrangerTui) => {
|
||||||
let play = PlayPause(self.clock.is_rolling());
|
let play = PlayPause(self.clock.is_rolling());
|
||||||
let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true);
|
|
||||||
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
|
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
|
||||||
let with_pool = |x|Bsp::w(Fixed::x(pool_size, PoolView(&self.phrases)), x);
|
let with_pool = |x|Bsp::w(Fixed::x(pool_size, PoolView(&self.phrases)), x);
|
||||||
let status = ArrangerStatus::from(self);
|
let status = ArrangerStatus::from(self);
|
||||||
|
|
@ -117,7 +116,7 @@ render!(Tui: (self: ArrangerTui) => {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
with_size(with_status(with_editbar(with_pool(col!(
|
with_size(with_status(with_editbar(with_pool(col!(
|
||||||
row!(play, transport),
|
TransportView(&self.clock),
|
||||||
Fill::x(Fixed::y(20, arranger())),
|
Fill::x(Fixed::y(20, arranger())),
|
||||||
Fill::xy(&self.editor),
|
Fill::xy(&self.editor),
|
||||||
)))))
|
)))))
|
||||||
|
|
|
||||||
|
|
@ -28,34 +28,20 @@ impl Groovebox {
|
||||||
audio_from: &[&[impl AsRef<str>];2],
|
audio_from: &[&[impl AsRef<str>];2],
|
||||||
audio_to: &[&[impl AsRef<str>];2],
|
audio_to: &[&[impl AsRef<str>];2],
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
let sampler = crate::sampler::Sampler::new(
|
let sampler = crate::sampler::Sampler::new(jack, &"sampler", midi_from, audio_from, audio_to)?;
|
||||||
jack, &"sampler", midi_from, audio_from, audio_to
|
let mut player = crate::midi::MidiPlayer::new(jack, &"sequencer", &midi_from, &midi_to)?;
|
||||||
)?;
|
|
||||||
let mut player = crate::midi::MidiPlayer::new(
|
|
||||||
jack, &"sequencer", &midi_from, &midi_to
|
|
||||||
)?;
|
|
||||||
jack.read().unwrap().client().connect_ports(&player.midi_outs[0], &sampler.midi_in)?;
|
jack.read().unwrap().client().connect_ports(&player.midi_outs[0], &sampler.midi_in)?;
|
||||||
//jack.connect_midi_from(&player.midi_ins[0], &midi_from)?;
|
|
||||||
//jack.connect_midi_from(&sampler.midi_in, &midi_from)?;
|
|
||||||
//jack.connect_midi_to(&player.midi_outs[0], &midi_to)?;
|
|
||||||
//jack.connect_audio_from(&sampler.audio_ins[0], &audio_from[0])?;
|
|
||||||
//jack.connect_audio_from(&sampler.audio_ins[1], &audio_from[1])?;
|
|
||||||
//jack.connect_audio_to(&sampler.audio_outs[0], &audio_to[0])?;
|
|
||||||
//jack.connect_audio_to(&sampler.audio_outs[1], &audio_to[1])?;
|
|
||||||
|
|
||||||
let phrase = Arc::new(RwLock::new(MidiClip::new(
|
let phrase = Arc::new(RwLock::new(MidiClip::new(
|
||||||
"New", true, 4 * player.clock.timebase.ppq.get() as usize,
|
"New", true, 4 * player.clock.timebase.ppq.get() as usize,
|
||||||
None, Some(ItemColor::random().into())
|
None, Some(ItemColor::random().into())
|
||||||
)));
|
)));
|
||||||
player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
|
player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
|
||||||
let pool = crate::pool::PoolModel::from(&phrase);
|
|
||||||
let editor = crate::midi::MidiEditor::from(&phrase);
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
_jack: jack.clone(),
|
|
||||||
player,
|
player,
|
||||||
pool,
|
|
||||||
editor,
|
|
||||||
sampler,
|
sampler,
|
||||||
|
_jack: jack.clone(),
|
||||||
|
pool: crate::pool::PoolModel::from(&phrase),
|
||||||
|
editor: crate::midi::MidiEditor::from(&phrase),
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
midi_buf: vec![vec![];65536],
|
midi_buf: vec![vec![];65536],
|
||||||
note_buf: vec![],
|
note_buf: vec![],
|
||||||
|
|
@ -78,6 +64,7 @@ audio!(|self: Groovebox, client, scope|{
|
||||||
if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) {
|
if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) {
|
||||||
return Control::Quit
|
return Control::Quit
|
||||||
}
|
}
|
||||||
|
// TODO move this to sampler:
|
||||||
for RawMidi { time, bytes } in self.player.midi_ins[0].iter(scope) {
|
for RawMidi { time, bytes } in self.player.midi_ins[0].iter(scope) {
|
||||||
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
||||||
match message {
|
match message {
|
||||||
|
|
@ -120,52 +107,51 @@ render!(Tui: (self: Groovebox) => {
|
||||||
let color = self.player.play_phrase().as_ref()
|
let color = self.player.play_phrase().as_ref()
|
||||||
.and_then(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color))
|
.and_then(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color))
|
||||||
.clone();
|
.clone();
|
||||||
let transport = Fixed::y(3, Bsp::e(
|
Fill::xy(Bsp::b(
|
||||||
PlayPause(self.clock().is_rolling()),
|
&self.size,
|
||||||
TransportView::new(self, color, true),
|
Bsp::s(
|
||||||
));
|
Fill::x(Fixed::y(3, Align::x(TransportView(&self.player.clock)))),
|
||||||
let selector = Fixed::y(1, Bsp::e(
|
Bsp::s(
|
||||||
|
Fill::x(Fixed::y(1, Align::x(Bsp::e(
|
||||||
PhraseSelector::play_phrase(&self.player),
|
PhraseSelector::play_phrase(&self.player),
|
||||||
PhraseSelector::next_phrase(&self.player),
|
PhraseSelector::next_phrase(&self.player),
|
||||||
));
|
)))),
|
||||||
let sampler = Tui::bg(TuiTheme::g(32), Align::w(Fixed::x(sampler_w, Fill::xy(col!(
|
Bsp::n(
|
||||||
Meters(self.sampler.input_meter.as_ref()),
|
Fixed::y(9, col!(
|
||||||
GrooveboxSamples(self)
|
Bsp::e(
|
||||||
)))));
|
self.sampler.mapped[note_pt].as_ref().map(|sample|format!(
|
||||||
let status = EditStatus(&self.sampler, &self.editor, note_pt, ());
|
|
||||||
let pool = PoolView(&self.pool);
|
|
||||||
let with_pool = move|x|Bsp::w(Align::e(Fill::y(Fixed::x(pool_w, pool))), x);
|
|
||||||
Fill::xy(Bsp::a(&self.size,
|
|
||||||
Bsp::s(transport,
|
|
||||||
Bsp::n(status,
|
|
||||||
Bsp::n(selector,
|
|
||||||
with_pool(Fill::xy(sampler)))))))});
|
|
||||||
|
|
||||||
struct EditStatus<'a, T: Content<Tui>>(&'a Sampler, &'a MidiEditor, usize, T);
|
|
||||||
impl<'a, T: Content<Tui>> Content<Tui> for EditStatus<'a, T> {
|
|
||||||
fn content (&self) -> impl Content<Tui> {
|
|
||||||
Bsp::n(Fixed::y(9, col!(
|
|
||||||
row!(
|
|
||||||
self.0.mapped[self.2].as_ref().map(|sample|format!(
|
|
||||||
"Sample {}-{}",
|
"Sample {}-{}",
|
||||||
sample.read().unwrap().start,
|
sample.read().unwrap().start,
|
||||||
sample.read().unwrap().end,
|
sample.read().unwrap().end,
|
||||||
)),
|
)),
|
||||||
MidiEditStatus(&self.1),
|
MidiEditStatus(&self.editor),
|
||||||
),
|
),
|
||||||
lay!(
|
Bsp::a(
|
||||||
Outer(Style::default().fg(TuiTheme::g(128))),
|
Outer(Style::default().fg(TuiTheme::g(128))),
|
||||||
Fill::x(Fixed::y(8, if let Some((_, sample)) = &self.0.recording {
|
Fill::x(Fixed::y(8, if let Some((_, sample)) = &self.sampler.recording {
|
||||||
SampleViewer(Some(sample.clone()))
|
SampleViewer(Some(sample.clone()))
|
||||||
} else if let Some(sample) = &self.0.mapped[self.2] {
|
} else if let Some(sample) = &self.sampler.mapped[note_pt] {
|
||||||
SampleViewer(Some(sample.clone()))
|
SampleViewer(Some(sample.clone()))
|
||||||
} else {
|
} else {
|
||||||
SampleViewer(None)
|
SampleViewer(None)
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)), &self.3)
|
)),
|
||||||
}
|
Bsp::w(
|
||||||
}
|
Align::e(Fill::y(Fixed::x(pool_w, PoolView(&self.pool)))),
|
||||||
|
Fill::xy(Bsp::e(
|
||||||
|
Align::w(Fixed::x(sampler_w, Tui::bg(TuiTheme::g(32), Fill::xy(col!(
|
||||||
|
Meters(self.sampler.input_meter.as_ref()),
|
||||||
|
GrooveboxSamples(self)
|
||||||
|
))))),
|
||||||
|
Fill::xy(Align::c("kyp"))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)});
|
||||||
|
|
||||||
struct GrooveboxSamples<'a>(&'a Groovebox);
|
struct GrooveboxSamples<'a>(&'a Groovebox);
|
||||||
render!(Tui: (self: GrooveboxSamples<'a>) => {
|
render!(Tui: (self: GrooveboxSamples<'a>) => {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
mod piano_h_cursor;
|
mod piano_h_cursor; use self::piano_h_cursor::*;
|
||||||
use self::piano_h_cursor::*;
|
mod piano_h_keys; pub(crate) use self::piano_h_keys::*;
|
||||||
|
|
||||||
mod piano_h_keys;
|
|
||||||
pub(crate) use self::piano_h_keys::*;
|
|
||||||
|
|
||||||
mod piano_h_notes; use self::piano_h_notes::*;
|
mod piano_h_notes; use self::piano_h_notes::*;
|
||||||
|
|
||||||
mod piano_h_time; use self::piano_h_time::*;
|
mod piano_h_time; use self::piano_h_time::*;
|
||||||
|
|
||||||
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator<Item=(usize, u16, usize)> {
|
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator<Item=(usize, u16, usize)> {
|
||||||
|
|
@ -59,41 +54,40 @@ render!(Tui: (self: PianoHorizontal) => {
|
||||||
} else {
|
} else {
|
||||||
(ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false)
|
(ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false)
|
||||||
};
|
};
|
||||||
let field = move|x, y|row!(
|
//let field = move|x, y|row!(
|
||||||
Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)),
|
//Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)),
|
||||||
Tui::fg_bg(color.lighter.rgb, color.dark.rgb, y),
|
//Tui::fg_bg(color.lighter.rgb, color.dark.rgb, y),
|
||||||
);
|
//);
|
||||||
let keys_width = 5;
|
let keys_width = 5;
|
||||||
let keys = move||PianoHorizontalKeys(self);
|
let keys = move||PianoHorizontalKeys(self);
|
||||||
let timeline = move||PianoHorizontalTimeline(self);
|
let timeline = move||PianoHorizontalTimeline(self);
|
||||||
let notes = move||PianoHorizontalNotes(self);
|
let notes = move||PianoHorizontalNotes(self);
|
||||||
let cursor = move||PianoHorizontalCursor(self);
|
let cursor = move||PianoHorizontalCursor(self);
|
||||||
let border = Fill::xy(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb)));
|
Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb)).enclose("kyp");
|
||||||
let with_border = |x|lay!(border, Padding::xy(0, 0, x));
|
//with_border(lay!(
|
||||||
with_border(lay!(
|
//Push::x(0, row!(
|
||||||
Push::x(0, row!(
|
////" ",
|
||||||
//" ",
|
//field("Edit:", name.to_string()), " ",
|
||||||
field("Edit:", name.to_string()), " ",
|
//field("Length:", length.to_string()), " ",
|
||||||
field("Length:", length.to_string()), " ",
|
//field("Loop:", looped.to_string())
|
||||||
field("Loop:", looped.to_string())
|
//)),
|
||||||
)),
|
//Padding::xy(0, 1, Fill::xy(Bsp::s(
|
||||||
Padding::xy(0, 1, Fill::xy(Bsp::s(
|
//Fixed::y(1, Bsp::e(
|
||||||
Fixed::y(1, Bsp::e(
|
//Fixed::x(self.keys_width, ""),
|
||||||
Fixed::x(self.keys_width, ""),
|
//Fill::x(timeline()),
|
||||||
Fill::x(timeline()),
|
//)),
|
||||||
)),
|
//Bsp::e(
|
||||||
Bsp::e(
|
//Fixed::x(self.keys_width, keys()),
|
||||||
Fixed::x(self.keys_width, keys()),
|
//Fill::xy(lay!(
|
||||||
Fill::xy(lay!(
|
//&self.size,
|
||||||
&self.size,
|
//Fill::xy(lay!(
|
||||||
Fill::xy(lay!(
|
//Fill::xy(notes()),
|
||||||
Fill::xy(notes()),
|
//Fill::xy(cursor()),
|
||||||
Fill::xy(cursor()),
|
//))
|
||||||
))
|
//)),
|
||||||
)),
|
//),
|
||||||
),
|
//)))
|
||||||
)))
|
//))
|
||||||
))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
impl PianoHorizontal {
|
impl PianoHorizontal {
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,7 @@ render!(Tui: (self: SequencerTui) => {
|
||||||
p.as_ref().map(|p|p.read().unwrap().color)
|
p.as_ref().map(|p|p.read().unwrap().color)
|
||||||
).flatten().clone();
|
).flatten().clone();
|
||||||
|
|
||||||
let toolbar = Tui::when(self.transport, row!(
|
let toolbar = Tui::when(self.transport, TransportView(&self.clock));
|
||||||
PlayPause(self.clock.is_rolling()),
|
|
||||||
TransportView::new(self, color, true),
|
|
||||||
));
|
|
||||||
|
|
||||||
let play_queue = Tui::when(self.selectors, row!(
|
let play_queue = Tui::when(self.selectors, row!(
|
||||||
PhraseSelector::play_phrase(&self.player),
|
PhraseSelector::play_phrase(&self.player),
|
||||||
|
|
|
||||||
299
src/transport.rs
299
src/transport.rs
|
|
@ -1,15 +1,14 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync};
|
use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync};
|
||||||
use TransportCommand::{Focus, Clock};
|
|
||||||
use FocusCommand::{Next, Prev};
|
use FocusCommand::{Next, Prev};
|
||||||
use KeyCode::{Enter, Left, Right, Char};
|
use KeyCode::{Enter, Left, Right, Char};
|
||||||
|
|
||||||
/// Transport clock app.
|
/// Transport clock app.
|
||||||
pub struct TransportTui {
|
pub struct TransportTui {
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
pub jack: Arc<RwLock<JackConnection>>,
|
||||||
pub clock: ClockModel,
|
pub clock: ClockModel,
|
||||||
pub size: Measure<Tui>,
|
pub size: Measure<Tui>,
|
||||||
pub cursor: (usize, usize),
|
pub cursor: (usize, usize),
|
||||||
pub focus: TransportFocus,
|
|
||||||
pub color: ItemPalette,
|
pub color: ItemPalette,
|
||||||
}
|
}
|
||||||
from_jack!(|jack|TransportTui Self {
|
from_jack!(|jack|TransportTui Self {
|
||||||
|
|
@ -17,16 +16,20 @@ from_jack!(|jack|TransportTui Self {
|
||||||
clock: ClockModel::from(jack),
|
clock: ClockModel::from(jack),
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
cursor: (0, 0),
|
cursor: (0, 0),
|
||||||
focus: TransportFocus::PlayPause,
|
|
||||||
color: ItemPalette::random(),
|
color: ItemPalette::random(),
|
||||||
});
|
});
|
||||||
has_clock!(|self: TransportTui|&self.clock);
|
has_clock!(|self: TransportTui|&self.clock);
|
||||||
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
|
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
|
||||||
handle!(<Tui>|self: TransportTui, from|TransportCommand::execute_with_state(self, from));
|
handle!(<Tui>|self: TransportTui, from|TransportCommand::execute_with_state(self, from));
|
||||||
render!(Tui: (self: TransportTui) => Bsp::e(
|
render!(Tui: (self: TransportTui) => TransportView(&self.clock));
|
||||||
PlayPause(self.clock.is_rolling()),
|
|
||||||
TransportView::new(self, Some(self.color), true)
|
pub struct TransportView<'a>(pub &'a ClockModel);
|
||||||
|
render!(Tui: (self: TransportView<'a>) => row!(
|
||||||
|
BeatStats::new(self.0), " ",
|
||||||
|
PlayPause(self.0.is_rolling()), " ",
|
||||||
|
OutputStats::new(self.0),
|
||||||
));
|
));
|
||||||
|
|
||||||
pub struct PlayPause(pub bool);
|
pub struct PlayPause(pub bool);
|
||||||
render!(Tui: (self: PlayPause) => Tui::bg(
|
render!(Tui: (self: PlayPause) => Tui::bg(
|
||||||
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
||||||
|
|
@ -42,127 +45,43 @@ impl std::fmt::Debug for TransportTui {
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct TransportView {
|
|
||||||
color: ItemPalette,
|
pub struct BeatStats { bpm: String, beat: String, time: String, }
|
||||||
focused: bool,
|
impl BeatStats {
|
||||||
sr: String,
|
fn new (clock: &ClockModel) -> Self {
|
||||||
chunk: String,
|
let (beat, time) = clock.started.read().unwrap().as_ref().map(|started|{
|
||||||
latency: String,
|
let current_usec = clock.global.usec.get() - started.usec.get();
|
||||||
bpm: String,
|
(
|
||||||
ppq: String,
|
clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(current_usec)),
|
||||||
beat: String,
|
format!("{:.3}s", current_usec/1000000.)
|
||||||
global_sample: String,
|
)
|
||||||
global_second: String,
|
}).unwrap_or_else(||("-.-.--".to_string(), "-.---s".to_string()));
|
||||||
started: bool,
|
Self { bpm: format!("{:.3}", clock.timebase.bpm.get()), beat, time }
|
||||||
current_sample: f64,
|
|
||||||
current_second: f64,
|
|
||||||
}
|
}
|
||||||
impl TransportView {
|
}
|
||||||
pub fn new (state: &impl HasClock, color: Option<ItemPalette>, focused: bool) -> Self {
|
render!(Tui: (self: BeatStats) => col!(
|
||||||
let clock = state.clock();
|
Bsp::e(&self.bpm, " BPM"), Bsp::e("Beat ", &self.beat), Bsp::e("Time ", &self.time),
|
||||||
|
));
|
||||||
|
|
||||||
|
pub struct OutputStats { sample_rate: String, buffer_size: String, latency: String, }
|
||||||
|
impl OutputStats {
|
||||||
|
fn new (clock: &ClockModel) -> Self {
|
||||||
let rate = clock.timebase.sr.get();
|
let rate = clock.timebase.sr.get();
|
||||||
let chunk = clock.chunk.load(Relaxed);
|
let chunk = clock.chunk.load(Relaxed);
|
||||||
let latency = chunk as f64 / rate * 1000.;
|
|
||||||
let sr = format!("{:.1}k", rate / 1000.0);
|
|
||||||
let bpm = format!("{:.3}", clock.timebase.bpm.get());
|
|
||||||
let ppq = format!("{:.0}", clock.timebase.ppq.get());
|
|
||||||
let chunk = format!("{chunk}");
|
|
||||||
let latency = format!("{latency}");
|
|
||||||
let color = color.unwrap_or(ItemPalette::from(TuiTheme::g(32)));
|
|
||||||
let (
|
|
||||||
global_sample, global_second, current_sample, current_second, beat
|
|
||||||
) = if let Some(started) = clock.started.read().unwrap().as_ref() {
|
|
||||||
let current_sample = (clock.global.sample.get() - started.sample.get())/1000.;
|
|
||||||
let current_usec = clock.global.usec.get() - started.usec.get();
|
|
||||||
let current_second = current_usec/1000000.;
|
|
||||||
let global_sample = format!("{:.0}k", started.sample.get()/1000.);
|
|
||||||
let global_second = format!("{:.1}s", started.usec.get()/1000.);
|
|
||||||
let beat = clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(current_usec));
|
|
||||||
(global_sample, global_second, current_sample, current_second, beat)
|
|
||||||
} else {
|
|
||||||
let global_sample = format!("{:.0}k", clock.global.sample.get()/1000.);
|
|
||||||
let global_second = format!("{:.1}s", clock.global.usec.get()/1000000.);
|
|
||||||
let current_sample = 0.0;
|
|
||||||
let current_second = 0.0;
|
|
||||||
let beat = format!("000.0.00");
|
|
||||||
(global_sample, global_second, current_sample, current_second, beat)
|
|
||||||
};
|
|
||||||
Self {
|
Self {
|
||||||
color, focused, sr, bpm, ppq, chunk, latency, started: false,
|
sample_rate: format!("{:.1}Hz", rate),
|
||||||
global_sample, global_second, current_sample, current_second, beat
|
buffer_size: format!("{chunk}"),
|
||||||
|
latency: format!("{}", chunk as f64 / rate * 1000.),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(Tui: (self: TransportView) => {
|
render!(Tui: (self: OutputStats) => col!(
|
||||||
let color = self.color;
|
Bsp::e(format!("{}", self.sample_rate), " sample rate"),
|
||||||
let transport_field = move|label, value|row!(
|
Bsp::e(format!("{}", self.buffer_size), " sample buffer"),
|
||||||
Tui::fg_bg(color.lightest.rgb, color.base.rgb, Tui::bold(true, label)),
|
Bsp::e(format!("{:.3}ms", self.latency), " latency"),
|
||||||
Tui::fg_bg(color.base.rgb, color.darkest.rgb, "▌"),
|
));
|
||||||
Tui::fg_bg(color.lightest.rgb, color.darkest.rgb, format!("{:>10}", value)),
|
|
||||||
Tui::fg_bg(color.darkest.rgb, color.base.rgb, "▌"),
|
|
||||||
);
|
|
||||||
Fixed::x(40, Tui::bg(Color::Red, Bsp::e(
|
|
||||||
Tui::bg(Color::Green, Fixed::x(18, col!(
|
|
||||||
transport_field(" Beat", self.beat.clone()),
|
|
||||||
transport_field(" Time", format!("{:.1}s", self.current_second)),
|
|
||||||
transport_field(" BPM", self.bpm.clone()),
|
|
||||||
))),
|
|
||||||
Tui::bg(Color::Blue, Fixed::x(18, col!(
|
|
||||||
transport_field(" Rate", format!("{}", self.sr)),
|
|
||||||
transport_field(" Chunk", format!("{}", self.chunk)),
|
|
||||||
transport_field(" Lag", format!("{:.3}ms", self.latency)),
|
|
||||||
))),
|
|
||||||
)))
|
|
||||||
});
|
|
||||||
impl HasFocus for TransportTui {
|
|
||||||
type Item = TransportFocus;
|
|
||||||
fn focused (&self) -> Self::Item {
|
|
||||||
self.focus
|
|
||||||
}
|
|
||||||
fn set_focused (&mut self, to: Self::Item) {
|
|
||||||
self.focus = to
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Which item of the transport toolbar is focused
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum TransportFocus {
|
|
||||||
Bpm,
|
|
||||||
Sync,
|
|
||||||
PlayPause,
|
|
||||||
Clock,
|
|
||||||
Quant,
|
|
||||||
}
|
|
||||||
impl FocusWrap<TransportFocus> for TransportFocus {
|
|
||||||
fn wrap <'a, W: Content<Tui>> (self, focus: TransportFocus, content: &'a W)
|
|
||||||
-> impl Content<Tui> + 'a
|
|
||||||
{
|
|
||||||
let focused = focus == self;
|
|
||||||
let corners = focused.then_some(CORNERS);
|
|
||||||
//let highlight = focused.then_some(Tui::bg(Color::Rgb(60, 70, 50)));
|
|
||||||
lay!(corners, /*highlight,*/ content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FocusWrap<TransportFocus> for Option<TransportFocus> {
|
|
||||||
fn wrap <'a, W: Content<Tui>> (self, focus: TransportFocus, content: &'a W)
|
|
||||||
-> impl Content<Tui> + 'a
|
|
||||||
{
|
|
||||||
let focused = Some(focus) == self;
|
|
||||||
let corners = focused.then_some(CORNERS);
|
|
||||||
//let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
|
|
||||||
lay!(corners, /*highlight,*/ content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub trait TransportControl<T>: HasClock + {
|
|
||||||
fn transport_focused (&self) -> Option<TransportFocus>;
|
|
||||||
}
|
|
||||||
impl TransportControl<TransportFocus> for TransportTui {
|
|
||||||
fn transport_focused (&self) -> Option<TransportFocus> {
|
|
||||||
Some(self.focus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum TransportCommand {
|
pub enum TransportCommand {
|
||||||
Focus(FocusCommand<TransportFocus>),
|
|
||||||
Clock(ClockCommand),
|
Clock(ClockCommand),
|
||||||
}
|
}
|
||||||
command!(|self:TransportCommand,state:TransportTui|match self {
|
command!(|self:TransportCommand,state:TransportTui|match self {
|
||||||
|
|
@ -170,24 +89,10 @@ command!(|self:TransportCommand,state:TransportTui|match self {
|
||||||
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
|
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
});
|
});
|
||||||
//command!(|self:TransportFocus,state:TransportTui|{
|
|
||||||
//if let FocusCommand::Set(to) = self { state.set_focused(to); }
|
|
||||||
//Ok(None)
|
|
||||||
//});
|
|
||||||
impl InputToCommand<Tui, TransportTui> for TransportCommand {
|
impl InputToCommand<Tui, TransportTui> for TransportCommand {
|
||||||
fn input_to_command (state: &TransportTui, input: &TuiIn) -> Option<Self> {
|
fn input_to_command (state: &TransportTui, input: &TuiIn) -> Option<Self> {
|
||||||
to_transport_command(state, input)
|
use TransportCommand::*;
|
||||||
.or_else(||to_focus_command(input).map(TransportCommand::Focus))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn to_transport_command <T, U> (state: &T, input: &TuiIn) -> Option<TransportCommand>
|
|
||||||
where
|
|
||||||
T: TransportControl<U>,
|
|
||||||
U: Into<Option<TransportFocus>>,
|
|
||||||
{
|
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Left) => Focus(Prev),
|
|
||||||
key_pat!(Right) => Focus(Next),
|
|
||||||
key_pat!(Char(' ')) => Clock(if state.clock().is_stopped() {
|
key_pat!(Char(' ')) => Clock(if state.clock().is_stopped() {
|
||||||
Play(None)
|
Play(None)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -198,32 +103,12 @@ where
|
||||||
} else {
|
} else {
|
||||||
Pause(Some(0))
|
Pause(Some(0))
|
||||||
}),
|
}),
|
||||||
_ => match state.transport_focused().unwrap() {
|
_ => return None
|
||||||
TransportFocus::Bpm => to_bpm_command(input, state.clock().bpm().get())?,
|
|
||||||
TransportFocus::Quant => to_quant_command(input, &state.clock().quant)?,
|
|
||||||
TransportFocus::Sync => to_sync_command(input, &state.clock().sync)?,
|
|
||||||
TransportFocus::Clock => to_seek_command(input)?,
|
|
||||||
TransportFocus::PlayPause => match input.event() {
|
|
||||||
key_pat!(Enter) => Clock(
|
|
||||||
if state.clock().is_stopped() {
|
|
||||||
Play(None)
|
|
||||||
} else {
|
|
||||||
Pause(None)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
key_pat!(Shift-Enter) => Clock(
|
|
||||||
if state.clock().is_stopped() {
|
|
||||||
Play(Some(0))
|
|
||||||
} else {
|
|
||||||
Pause(Some(0))
|
|
||||||
}
|
|
||||||
),
|
|
||||||
_ => return None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn to_bpm_command (input: &TuiIn, bpm: f64) -> Option<TransportCommand> {
|
fn to_bpm_command (input: &TuiIn, bpm: f64) -> Option<TransportCommand> {
|
||||||
|
use TransportCommand::*;
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Char(',')) => Clock(SetBpm(bpm - 1.0)),
|
key_pat!(Char(',')) => Clock(SetBpm(bpm - 1.0)),
|
||||||
key_pat!(Char('.')) => Clock(SetBpm(bpm + 1.0)),
|
key_pat!(Char('.')) => Clock(SetBpm(bpm + 1.0)),
|
||||||
|
|
@ -233,6 +118,7 @@ fn to_bpm_command (input: &TuiIn, bpm: f64) -> Option<TransportCommand> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn to_quant_command (input: &TuiIn, quant: &Quantize) -> Option<TransportCommand> {
|
fn to_quant_command (input: &TuiIn, quant: &Quantize) -> Option<TransportCommand> {
|
||||||
|
use TransportCommand::*;
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Char(',')) => Clock(SetQuant(quant.prev())),
|
key_pat!(Char(',')) => Clock(SetQuant(quant.prev())),
|
||||||
key_pat!(Char('.')) => Clock(SetQuant(quant.next())),
|
key_pat!(Char('.')) => Clock(SetQuant(quant.next())),
|
||||||
|
|
@ -242,6 +128,7 @@ fn to_quant_command (input: &TuiIn, quant: &Quantize) -> Option<TransportCommand
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn to_sync_command (input: &TuiIn, sync: &LaunchSync) -> Option<TransportCommand> {
|
fn to_sync_command (input: &TuiIn, sync: &LaunchSync) -> Option<TransportCommand> {
|
||||||
|
use TransportCommand::*;
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Char(',')) => Clock(SetSync(sync.prev())),
|
key_pat!(Char(',')) => Clock(SetSync(sync.prev())),
|
||||||
key_pat!(Char('.')) => Clock(SetSync(sync.next())),
|
key_pat!(Char('.')) => Clock(SetSync(sync.next())),
|
||||||
|
|
@ -251,6 +138,7 @@ fn to_sync_command (input: &TuiIn, sync: &LaunchSync) -> Option<TransportCommand
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn to_seek_command (input: &TuiIn) -> Option<TransportCommand> {
|
fn to_seek_command (input: &TuiIn) -> Option<TransportCommand> {
|
||||||
|
use TransportCommand::*;
|
||||||
Some(match input.event() {
|
Some(match input.event() {
|
||||||
key_pat!(Char(',')) => todo!("transport seek bar"),
|
key_pat!(Char(',')) => todo!("transport seek bar"),
|
||||||
key_pat!(Char('.')) => todo!("transport seek bar"),
|
key_pat!(Char('.')) => todo!("transport seek bar"),
|
||||||
|
|
@ -259,102 +147,3 @@ fn to_seek_command (input: &TuiIn) -> Option<TransportCommand> {
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
//struct Field(&'static str, String);
|
|
||||||
|
|
||||||
//render!(|self: Field|{
|
|
||||||
//Tui::to_east("│", Tui::to_east(
|
|
||||||
//Tui::bold(true, self.0),
|
|
||||||
//Tui::bg(Color::Rgb(0, 0, 0), self.1.as_str()),
|
|
||||||
//))
|
|
||||||
//});
|
|
||||||
|
|
||||||
//pub struct TransportView {
|
|
||||||
//pub(crate) state: Option<TransportState>,
|
|
||||||
//pub(crate) selected: Option<TransportFocus>,
|
|
||||||
//pub(crate) focused: bool,
|
|
||||||
//pub(crate) bpm: f64,
|
|
||||||
//pub(crate) sync: f64,
|
|
||||||
//pub(crate) quant: f64,
|
|
||||||
//pub(crate) beat: String,
|
|
||||||
//pub(crate) msu: String,
|
|
||||||
//}
|
|
||||||
////)?;
|
|
||||||
////match *state {
|
|
||||||
////Some(TransportState::Rolling) => {
|
|
||||||
////add(&row!(
|
|
||||||
////"│",
|
|
||||||
////TuiStyle::fg("▶ PLAYING", Color::Rgb(0, 255, 0)),
|
|
||||||
////format!("│0 (0)"),
|
|
||||||
////format!("│00m00s000u"),
|
|
||||||
////format!("│00B 0b 00/00")
|
|
||||||
////))?;
|
|
||||||
////add(&row!("│Now ", row!(
|
|
||||||
////format!("│0 (0)"), //sample(chunk)
|
|
||||||
////format!("│00m00s000u"), //msu
|
|
||||||
////format!("│00B 0b 00/00"), //bbt
|
|
||||||
////)))?;
|
|
||||||
////},
|
|
||||||
////_ => {
|
|
||||||
////add(&row!("│", TuiStyle::fg("⏹ STOPPED", Color::Rgb(255, 128, 0))))?;
|
|
||||||
////add(&"")?;
|
|
||||||
////}
|
|
||||||
////}
|
|
||||||
////Ok(())
|
|
||||||
////}).fill_x().bg(Color::Rgb(40, 50, 30))
|
|
||||||
////});
|
|
||||||
|
|
||||||
//impl<'a, T: HasClock> From<&'a T> for TransportView where Option<TransportFocus>: From<&'a T> {
|
|
||||||
//fn from (state: &'a T) -> Self {
|
|
||||||
//let selected = state.into();
|
|
||||||
//Self {
|
|
||||||
//selected,
|
|
||||||
//focused: selected.is_some(),
|
|
||||||
//state: Some(state.clock().transport.query_state().unwrap()),
|
|
||||||
//bpm: state.clock().bpm().get(),
|
|
||||||
//sync: state.clock().sync.get(),
|
|
||||||
//quant: state.clock().quant.get(),
|
|
||||||
//beat: state.clock().playhead.format_beat(),
|
|
||||||
//msu: state.clock().playhead.usec.format_msu(),
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//row!(
|
|
||||||
////selected.wrap(TransportFocus::PlayPause, &play_pause.fixed_xy(10, 3)),
|
|
||||||
//row!(
|
|
||||||
//col!(
|
|
||||||
//Field("SR ", format!("192000")),
|
|
||||||
//Field("BUF ", format!("1024")),
|
|
||||||
//Field("LEN ", format!("21300")),
|
|
||||||
//Field("CPU ", format!("00.0%"))
|
|
||||||
//),
|
|
||||||
//col!(
|
|
||||||
//Field("PUL ", format!("000000000")),
|
|
||||||
//Field("PPQ ", format!("96")),
|
|
||||||
//Field("BBT ", format!("00B0b00p"))
|
|
||||||
//),
|
|
||||||
//col!(
|
|
||||||
//Field("SEC ", format!("000000.000")),
|
|
||||||
//Field("BPM ", format!("000.000")),
|
|
||||||
//Field("MSU ", format!("00m00s00u"))
|
|
||||||
//),
|
|
||||||
//),
|
|
||||||
//selected.wrap(TransportFocus::Bpm, &Margin::X(1u16, {
|
|
||||||
//row! {
|
|
||||||
//"BPM ",
|
|
||||||
//format!("{}.{:03}", *bpm as usize, (bpm * 1000.0) % 1000.0)
|
|
||||||
//}
|
|
||||||
//})),
|
|
||||||
//selected.wrap(TransportFocus::Sync, &Margin::X(1u16, row! {
|
|
||||||
//"SYNC ", pulses_to_name(*sync as usize)
|
|
||||||
//})),
|
|
||||||
//selected.wrap(TransportFocus::Quant, &Margin::X(1u16, row! {
|
|
||||||
//"QUANT ", pulses_to_name(*quant as usize)
|
|
||||||
//})),
|
|
||||||
//selected.wrap(TransportFocus::Clock, &{
|
|
||||||
//row!("B" , beat.as_str(), " T", msu.as_str()).margin_x(1)
|
|
||||||
//}).align_e().fill_x(),
|
|
||||||
//).fill_x().bg(Color::Rgb(40, 50, 30))
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue