wip: reenabling sequencer pane

This commit is contained in:
🪞👃🪞 2024-09-19 01:08:13 +03:00
parent 6e81082c17
commit 4c640301c6
2 changed files with 501 additions and 512 deletions

View file

@ -3,19 +3,7 @@ include!("lib.rs");
use tek_core::clap::{self, Parser};
pub fn main () -> Usually<()> {
Tui::run(Arc::new(RwLock::new(crate::ArrangerStandalone::from_args()?)))?;
Ok(())
}
struct ArrangerStandalone<E: Engine> {
/// Contains all the sequencers.
arranger: Arranger<E>,
/// Controls the JACK transport.
transport: Option<TransportToolbar<E>>,
/// This allows the sequencer view to be moved or hidden.
show_sequencer: Option<tek_core::Direction>,
///
focus: usize,
ArrangerCli::parse().run()
}
#[derive(Debug, Parser)]
@ -38,51 +26,69 @@ pub struct ArrangerCli {
scenes: usize,
}
impl ArrangerStandalone<Tui> {
pub fn from_args () -> Usually<Self> {
let args = ArrangerCli::parse();
impl ArrangerCli {
fn run (&self) -> Usually<()> {
let mut arranger = Arranger::new("");
let mut transport = match args.transport {
let mut transport = match self.transport {
Some(true) => Some(TransportToolbar::new(None)),
_ => None
};
if let Some(name) = args.name {
if let Some(name) = self.name.as_ref() {
*arranger.name.write().unwrap() = name.clone();
}
for _ in 0..args.tracks {
for _ in 0..self.tracks {
let track = arranger.track_add(None)?;
for _ in 0..args.scenes {
for _ in 0..self.scenes {
track.phrases.push(
Arc::new(RwLock::new(Phrase::new("", 96 * 4, None)))
);
}
}
for _ in 0..args.scenes {
for _ in 0..self.scenes {
let _scene = arranger.scene_add(None)?;
//for i in 0..args.tracks {
//for i in 0..self.tracks {
//scene.clips[i] = Some(i);
//}
}
transport.set_focused(true);
Ok(ArrangerStandalone {
let state = Arc::new(RwLock::new(ArrangerStandalone {
transport,
show_sequencer: Some(tek_core::Direction::Down),
arranger,
focus: 0
})
}));
Tui::run(state)?;
Ok(())
}
}
struct ArrangerStandalone<E: Engine> {
/// Controls the JACK transport.
transport: Option<TransportToolbar<E>>,
/// Contains all the sequencers.
arranger: Arranger<E>,
/// This allows the sequencer view to be moved or hidden.
show_sequencer: Option<tek_core::Direction>,
///
focus: usize,
}
impl Content for ArrangerStandalone<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Split::down(|add|{
add(&self.transport)?;
add(&self.arranger)?;
if let Some(sequencer) = self.arranger.sequencer() {
add(sequencer)?;
if let Some(direction) = self.show_sequencer {
add(&Split::new(direction, |add|{
add(&self.arranger)?;
add(&Min::Y(10, self.arranger.sequencer().map(
|x|x as &dyn Widget<Engine = Tui>
)))?;
Ok(())
}))
} else {
add(&self.arranger)
}
Ok(())
})
//if let Some(ref modal) = self.arranger.modal {
//to.fill_bg(area, Nord::bg_lo(false, false));

View file

@ -21,450 +21,6 @@ tui_style!(STYLE_VALUE =
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Stores and displays time-related state.
pub struct TransportToolbar<E: Engine> {
/// Enable metronome?
pub metronome: bool,
/// Current sample rate, tempo, and PPQ.
pub timebase: Arc<Timebase>,
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Option<JackClient>,
/// JACK transport handle.
pub transport: Option<Transport>,
/// Quantization factor
/// Global frame and usec at which playback started
pub started: Option<(usize, usize)>,
pub focused: bool,
pub focus: usize,
pub playing: TransportPlayPauseButton<E>,
pub bpm: TransportBPM<E>,
pub quant: TransportQuantize<E>,
pub sync: TransportSync<E>,
pub clock: TransportClock<E>,
}
impl<E: Engine> TransportToolbar<E> {
pub fn standalone () -> Usually<Arc<RwLock<Self>>> where Self: 'static {
let mut transport = Self::new(None);
transport.focused = true;
let jack = JackClient::Inactive(
Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0
);
transport.transport = Some(jack.transport());
transport.playing.transport = Some(jack.transport());
let transport = Arc::new(RwLock::new(transport));
transport.write().unwrap().jack = Some(
jack.activate(
&transport.clone(),
|state: &Arc<RwLock<TransportToolbar<E>>>, client, scope| {
state.write().unwrap().process(client, scope)
}
)?
);
Ok(transport)
}
pub fn new (transport: Option<Transport>) -> Self {
let timebase = Arc::new(Timebase::default());
Self {
focused: false,
focus: 0,
playing: TransportPlayPauseButton {
_engine: Default::default(),
transport: None,
value: Some(TransportState::Stopped),
focused: true
},
bpm: TransportBPM {
_engine: Default::default(),
value: timebase.bpm(),
focused: false
},
quant: TransportQuantize {
_engine: Default::default(),
value: 24,
focused: false
},
sync: TransportSync {
_engine: Default::default(),
value: timebase.ppq() as usize * 4,
focused: false
},
clock: TransportClock {
_engine: Default::default(),
frame: 0,
pulse: 0,
ppq: 0,
usecs: 0,
focused: false
},
transport,
timebase,
metronome: false,
started: None,
jack: None,
}
}
pub fn toggle_play (&mut self) -> Usually<()> {
self.playing.toggle()?;
Ok(())
}
pub fn update (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, usize, f64) {
let CycleTimes {
current_frames,
current_usecs,
next_usecs,
period_usecs
} = scope.cycle_times().unwrap();
let chunk_size = scope.n_frames() as usize;
let transport = self.transport.as_ref().unwrap().query().unwrap();
self.clock.frame = transport.pos.frame() as usize;
let mut reset = false;
if self.playing.value != Some(transport.state) {
match transport.state {
TransportState::Rolling => {
self.started = Some((
current_frames as usize,
current_usecs as usize,
));
},
TransportState::Stopped => {
self.started = None;
reset = true;
},
_ => {}
}
}
self.playing.value = Some(transport.state);
(
reset,
current_frames as usize,
chunk_size as usize,
current_usecs as usize,
next_usecs as usize,
period_usecs as f64
)
}
pub fn bpm (&self) -> usize {
self.timebase.bpm() as usize
}
pub fn ppq (&self) -> usize {
self.timebase.ppq() as usize
}
pub fn pulse (&self) -> usize {
self.timebase.frame_to_pulse(self.clock.frame as f64) as usize
}
pub fn usecs (&self) -> usize {
self.timebase.frame_to_usec(self.clock.frame as f64) as usize
}
pub fn quant (&self) -> usize {
self.quant.value
}
pub fn sync (&self) -> usize {
self.sync.value
}
}
impl<E: Engine> Audio for TransportToolbar<E> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
self.update(&scope);
Control::Continue
}
}
impl Handle<Tui> for TransportToolbar<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Left) => { self.focus_prev(); },
key!(KeyCode::Right) => { self.focus_next(); },
_ => return self.focused_mut().handle(from)
}
Ok(Some(true))
}
}
impl Content for TransportToolbar<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Split::right(|add|{
let focus_wrap = |focused, component|Layers::new(move |add|{
if focused {
add(&CORNERS)?;
add(&Background(COLOR_BG1))?;
}
add(component)
});
add(&focus_wrap(self.focused && self.playing.focused, &self.playing))?;
add(&focus_wrap(self.focused && self.bpm.focused, &self.bpm))?;
add(&focus_wrap(self.focused && self.quant.focused, &self.quant))?;
add(&focus_wrap(self.focused && self.sync.focused, &self.sync))?;
add(&focus_wrap(self.focused && self.clock.focused, &self.clock))?;
Ok(())
})
}
}
impl Focus<5, Tui> for TransportToolbar<Tui> {
fn focus (&self) -> usize {
self.focus
}
fn focus_mut (&mut self) -> &mut usize {
&mut self.focus
}
fn focusable (&self) -> [&dyn Focusable<Tui>;5] {
[
&self.playing as &dyn Focusable<Tui>,
&self.bpm as &dyn Focusable<Tui>,
&self.quant as &dyn Focusable<Tui>,
&self.sync as &dyn Focusable<Tui>,
&self.clock as &dyn Focusable<Tui>,
]
}
fn focusable_mut (&mut self) -> [&mut dyn Focusable<Tui>;5] {
[
&mut self.playing as &mut dyn Focusable<Tui>,
&mut self.bpm as &mut dyn Focusable<Tui>,
&mut self.quant as &mut dyn Focusable<Tui>,
&mut self.sync as &mut dyn Focusable<Tui>,
&mut self.clock as &mut dyn Focusable<Tui>,
]
}
}
impl Focusable<Tui> for TransportToolbar<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportPlayPauseButton<E: Engine> {
pub _engine: PhantomData<E>,
pub transport: Option<Transport>,
pub value: Option<TransportState>,
pub focused: bool
}
impl<E: Engine> TransportPlayPauseButton<E> {
fn toggle (&mut self) -> Usually<()> {
let transport = self.transport.as_ref().unwrap();
self.value = match self.value.expect("1st frame has not been processed yet") {
TransportState::Stopped => {
transport.start()?;
Some(TransportState::Starting)
},
_ => {
transport.stop()?;
transport.locate(0)?;
Some(TransportState::Stopped)
},
};
Ok(())
}
}
impl Focusable<Tui> for TransportPlayPauseButton<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportPlayPauseButton<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Enter) => self.toggle().map(|_|Some(true)),
_ => Ok(None)
}
}
}
impl Content for TransportPlayPauseButton<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Layers::new(|add|{
add(&Plus::X(1, Min::XY(11, 2, Styled(match self.value {
Some(TransportState::Stopped) => Some(GRAY_DIM.bold()),
Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD),
Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD),
_ => unreachable!(),
}, match self.value {
Some(TransportState::Rolling) => "▶ PLAYING",
Some(TransportState::Starting) => "READY ...",
Some(TransportState::Stopped) => "⏹ STOPPED",
_ => unreachable!(),
}))))?;
Ok(())
})
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportBPM<E: Engine> {
pub _engine: PhantomData<E>,
pub value: f64,
pub focused: bool
}
impl Focusable<Tui> for TransportBPM<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportBPM<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => { self.value -= 1.0; },
key!(KeyCode::Char('.')) => { self.value += 1.0; },
key!(KeyCode::Char('<')) => { self.value -= 0.001; },
key!(KeyCode::Char('>')) => { self.value += 0.001; },
_ => return Ok(None)
}
Ok(Some(true))
}
}
impl Content for TransportBPM<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { value, .. } = self;
Outset::X(1u16, Split::down(move |add|{
add(&"BPM")?;
add(&format!("{}.{:03}", *value as usize, (value * 1000.0) % 1000.0).as_str())
}))
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportQuantize<E: Engine> {
pub _engine: PhantomData<E>,
pub value: usize,
pub focused: bool
}
impl Focusable<Tui> for TransportQuantize<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportQuantize<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => {
self.value = prev_note_length(self.value);
Ok(Some(true))
},
key!(KeyCode::Char('.')) => {
self.value = next_note_length(self.value);
Ok(Some(true))
},
_ => Ok(None)
}
}
}
impl Content for TransportQuantize<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { value, .. } = self;
Outset::X(1u16, Split::down(|add|{
add(&"QUANT")?;
add(&ppq_to_name(*value as usize))
}))
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportSync<E: Engine> {
pub _engine: PhantomData<E>,
pub value: usize,
pub focused: bool
}
impl Focusable<Tui> for TransportSync<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportSync<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => {
self.value = prev_note_length(self.value);
Ok(Some(true))
},
key!(KeyCode::Char('.')) => {
self.value = next_note_length(self.value);
Ok(Some(true))
},
_ => Ok(None)
}
}
}
impl Content for TransportSync<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { value, .. } = self;
Outset::X(1u16, Split::down(|add|{
add(&"SYNC")?;
add(&ppq_to_name(*value as usize))
}))
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportClock<E: Engine> {
pub _engine: PhantomData<E>,
pub frame: usize,
pub pulse: usize,
pub ppq: usize,
pub usecs: usize,
pub focused: bool,
}
impl Focusable<Tui> for TransportClock<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportClock<Tui> {
fn handle (&mut self, _: &TuiInput) -> Perhaps<bool> {
Ok(None)
}
}
impl Content for TransportClock<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { frame: _frame, pulse, ppq, usecs, .. } = self;
Layers::new(move|add|{
let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
let (minutes, seconds) = (seconds / 60, seconds % 60);
add(&Outset::X(1u16, Split::down(|add|{
add(&format!("{bars}.{beats}.{pulses:02}").as_str())?;
add(&format!("{minutes}:{seconds:02}:{msecs:03}").as_str())
})))
})
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Represents the tracks and scenes of the composition.
pub struct Arranger<E: Engine> {
/// Name of arranger
@ -1747,7 +1303,7 @@ impl<E: Engine> Sequencer<E> {
}
}
fn index_to_color (&self, index: u16, default: Color) -> Color {
pub fn index_to_color (&self, index: u16, default: Color) -> Color {
let index = index as usize;
if self.keys_in[index] && self.keys_out[index] {
Color::Yellow
@ -1795,18 +1351,29 @@ pub type MIDIMessage = Vec<u8>;
/// Collection of serialized MIDI messages
pub type MIDIChunk = [Vec<MIDIMessage>];
impl Widget for Sequencer<Tui> {
impl Sequencer<Tui> {
const H_KEYS_OFFSET: usize = 5;
}
impl Content for Sequencer<Tui> {
type Engine = Tui;
fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
self.horizontal_draw(to)?;
if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(to)?;
}
//Ok(Some(to.area()))
Ok(())
fn content (&self) -> impl Widget<Engine = Tui> {
Split::right(move |add|{
add(&Split::down(|add|{
add(&SequenceName(&self))?;
add(&SequenceRange)?;
add(&SequenceLoopRange)?;
add(&SequenceNoteRange)?;
Ok(())
}))?;
add(&Layers::new(|add|{
add(&SequenceKeys(&self))?;
add(&self.phrase.as_ref().map(|phrase|SequenceTimer(&self, phrase.clone())))?;
add(&SequenceNotes(&self))?;
add(&SequenceCursor(&self))?;
add(&SequenceZoom(&self))
}))
})
}
}
@ -1996,34 +1563,6 @@ pub(crate) fn keys_vert () -> Buffer {
buffer
}
impl Sequencer<Tui> {
const H_KEYS_OFFSET: usize = 5;
pub(crate) fn horizontal_draw <'a> (&self, to: &mut TuiOutput) -> Usually<()> {
let area = to.area();
Split::down(|add|{
add(&SequenceName(&self))?;
add(&SequenceRange)?;
add(&SequenceLoopRange)?;
add(&SequenceNoteRange)?;
Ok(())
}).render(to.with_rect([area.x(), area.y(), 10, area.h()]))?;
let area = [area.x() + 10, area.y(), area.w().saturating_sub(10), area.h().min(66)];
Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(area))?;
let area = [area.x() + 1, area.y(), area.w().saturating_sub(1), area.h()];
Layers::new(|add|{
add(&SequenceKeys(&self))?;
add(&self.phrase.as_ref().map(|phrase|SequenceTimer(&self, phrase.clone())))?;
add(&SequenceNotes(&self))?;
add(&SequenceCursor(&self))?;
add(&SequenceZoom(&self))
}).render(to.with_rect(area))?;
Ok(())
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
struct SequenceName<'a>(&'a Sequencer<Tui>);
@ -2438,3 +1977,447 @@ impl Phrase {
Ok(phrase)
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Stores and displays time-related state.
pub struct TransportToolbar<E: Engine> {
/// Enable metronome?
pub metronome: bool,
/// Current sample rate, tempo, and PPQ.
pub timebase: Arc<Timebase>,
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Option<JackClient>,
/// JACK transport handle.
pub transport: Option<Transport>,
/// Quantization factor
/// Global frame and usec at which playback started
pub started: Option<(usize, usize)>,
pub focused: bool,
pub focus: usize,
pub playing: TransportPlayPauseButton<E>,
pub bpm: TransportBPM<E>,
pub quant: TransportQuantize<E>,
pub sync: TransportSync<E>,
pub clock: TransportClock<E>,
}
impl<E: Engine> TransportToolbar<E> {
pub fn standalone () -> Usually<Arc<RwLock<Self>>> where Self: 'static {
let mut transport = Self::new(None);
transport.focused = true;
let jack = JackClient::Inactive(
Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0
);
transport.transport = Some(jack.transport());
transport.playing.transport = Some(jack.transport());
let transport = Arc::new(RwLock::new(transport));
transport.write().unwrap().jack = Some(
jack.activate(
&transport.clone(),
|state: &Arc<RwLock<TransportToolbar<E>>>, client, scope| {
state.write().unwrap().process(client, scope)
}
)?
);
Ok(transport)
}
pub fn new (transport: Option<Transport>) -> Self {
let timebase = Arc::new(Timebase::default());
Self {
focused: false,
focus: 0,
playing: TransportPlayPauseButton {
_engine: Default::default(),
transport: None,
value: Some(TransportState::Stopped),
focused: true
},
bpm: TransportBPM {
_engine: Default::default(),
value: timebase.bpm(),
focused: false
},
quant: TransportQuantize {
_engine: Default::default(),
value: 24,
focused: false
},
sync: TransportSync {
_engine: Default::default(),
value: timebase.ppq() as usize * 4,
focused: false
},
clock: TransportClock {
_engine: Default::default(),
frame: 0,
pulse: 0,
ppq: 0,
usecs: 0,
focused: false
},
transport,
timebase,
metronome: false,
started: None,
jack: None,
}
}
pub fn toggle_play (&mut self) -> Usually<()> {
self.playing.toggle()?;
Ok(())
}
pub fn update (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, usize, f64) {
let CycleTimes {
current_frames,
current_usecs,
next_usecs,
period_usecs
} = scope.cycle_times().unwrap();
let chunk_size = scope.n_frames() as usize;
let transport = self.transport.as_ref().unwrap().query().unwrap();
self.clock.frame = transport.pos.frame() as usize;
let mut reset = false;
if self.playing.value != Some(transport.state) {
match transport.state {
TransportState::Rolling => {
self.started = Some((
current_frames as usize,
current_usecs as usize,
));
},
TransportState::Stopped => {
self.started = None;
reset = true;
},
_ => {}
}
}
self.playing.value = Some(transport.state);
(
reset,
current_frames as usize,
chunk_size as usize,
current_usecs as usize,
next_usecs as usize,
period_usecs as f64
)
}
pub fn bpm (&self) -> usize {
self.timebase.bpm() as usize
}
pub fn ppq (&self) -> usize {
self.timebase.ppq() as usize
}
pub fn pulse (&self) -> usize {
self.timebase.frame_to_pulse(self.clock.frame as f64) as usize
}
pub fn usecs (&self) -> usize {
self.timebase.frame_to_usec(self.clock.frame as f64) as usize
}
pub fn quant (&self) -> usize {
self.quant.value
}
pub fn sync (&self) -> usize {
self.sync.value
}
}
impl<E: Engine> Audio for TransportToolbar<E> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
self.update(&scope);
Control::Continue
}
}
impl Handle<Tui> for TransportToolbar<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Left) => { self.focus_prev(); },
key!(KeyCode::Right) => { self.focus_next(); },
_ => return self.focused_mut().handle(from)
}
Ok(Some(true))
}
}
impl Content for TransportToolbar<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Split::right(|add|{
let focus_wrap = |focused, component|Layers::new(move |add|{
if focused {
add(&CORNERS)?;
add(&Background(COLOR_BG1))?;
}
add(component)
});
add(&focus_wrap(self.focused && self.playing.focused, &self.playing))?;
add(&focus_wrap(self.focused && self.bpm.focused, &self.bpm))?;
add(&focus_wrap(self.focused && self.quant.focused, &self.quant))?;
add(&focus_wrap(self.focused && self.sync.focused, &self.sync))?;
add(&focus_wrap(self.focused && self.clock.focused, &self.clock))?;
Ok(())
})
}
}
impl Focus<5, Tui> for TransportToolbar<Tui> {
fn focus (&self) -> usize {
self.focus
}
fn focus_mut (&mut self) -> &mut usize {
&mut self.focus
}
fn focusable (&self) -> [&dyn Focusable<Tui>;5] {
[
&self.playing as &dyn Focusable<Tui>,
&self.bpm as &dyn Focusable<Tui>,
&self.quant as &dyn Focusable<Tui>,
&self.sync as &dyn Focusable<Tui>,
&self.clock as &dyn Focusable<Tui>,
]
}
fn focusable_mut (&mut self) -> [&mut dyn Focusable<Tui>;5] {
[
&mut self.playing as &mut dyn Focusable<Tui>,
&mut self.bpm as &mut dyn Focusable<Tui>,
&mut self.quant as &mut dyn Focusable<Tui>,
&mut self.sync as &mut dyn Focusable<Tui>,
&mut self.clock as &mut dyn Focusable<Tui>,
]
}
}
impl Focusable<Tui> for TransportToolbar<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportPlayPauseButton<E: Engine> {
pub _engine: PhantomData<E>,
pub transport: Option<Transport>,
pub value: Option<TransportState>,
pub focused: bool
}
impl<E: Engine> TransportPlayPauseButton<E> {
fn toggle (&mut self) -> Usually<()> {
let transport = self.transport.as_ref().unwrap();
self.value = match self.value.expect("1st frame has not been processed yet") {
TransportState::Stopped => {
transport.start()?;
Some(TransportState::Starting)
},
_ => {
transport.stop()?;
transport.locate(0)?;
Some(TransportState::Stopped)
},
};
Ok(())
}
}
impl Focusable<Tui> for TransportPlayPauseButton<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportPlayPauseButton<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Enter) => self.toggle().map(|_|Some(true)),
_ => Ok(None)
}
}
}
impl Content for TransportPlayPauseButton<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
Layers::new(|add|{
add(&Plus::X(1, Min::XY(11, 2, Styled(match self.value {
Some(TransportState::Stopped) => Some(GRAY_DIM.bold()),
Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD),
Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD),
_ => unreachable!(),
}, match self.value {
Some(TransportState::Rolling) => "▶ PLAYING",
Some(TransportState::Starting) => "READY ...",
Some(TransportState::Stopped) => "⏹ STOPPED",
_ => unreachable!(),
}))))?;
Ok(())
})
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportBPM<E: Engine> {
pub _engine: PhantomData<E>,
pub value: f64,
pub focused: bool
}
impl Focusable<Tui> for TransportBPM<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportBPM<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => { self.value -= 1.0; },
key!(KeyCode::Char('.')) => { self.value += 1.0; },
key!(KeyCode::Char('<')) => { self.value -= 0.001; },
key!(KeyCode::Char('>')) => { self.value += 0.001; },
_ => return Ok(None)
}
Ok(Some(true))
}
}
impl Content for TransportBPM<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { value, .. } = self;
Outset::X(1u16, Split::down(move |add|{
add(&"BPM")?;
add(&format!("{}.{:03}", *value as usize, (value * 1000.0) % 1000.0).as_str())
}))
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportQuantize<E: Engine> {
pub _engine: PhantomData<E>,
pub value: usize,
pub focused: bool
}
impl Focusable<Tui> for TransportQuantize<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportQuantize<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => {
self.value = prev_note_length(self.value);
Ok(Some(true))
},
key!(KeyCode::Char('.')) => {
self.value = next_note_length(self.value);
Ok(Some(true))
},
_ => Ok(None)
}
}
}
impl Content for TransportQuantize<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { value, .. } = self;
Outset::X(1u16, Split::down(|add|{
add(&"QUANT")?;
add(&ppq_to_name(*value as usize))
}))
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportSync<E: Engine> {
pub _engine: PhantomData<E>,
pub value: usize,
pub focused: bool
}
impl Focusable<Tui> for TransportSync<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportSync<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() {
key!(KeyCode::Char(',')) => {
self.value = prev_note_length(self.value);
Ok(Some(true))
},
key!(KeyCode::Char('.')) => {
self.value = next_note_length(self.value);
Ok(Some(true))
},
_ => Ok(None)
}
}
}
impl Content for TransportSync<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { value, .. } = self;
Outset::X(1u16, Split::down(|add|{
add(&"SYNC")?;
add(&ppq_to_name(*value as usize))
}))
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct TransportClock<E: Engine> {
pub _engine: PhantomData<E>,
pub frame: usize,
pub pulse: usize,
pub ppq: usize,
pub usecs: usize,
pub focused: bool,
}
impl Focusable<Tui> for TransportClock<Tui> {
fn is_focused (&self) -> bool {
self.focused
}
fn set_focused (&mut self, focused: bool) {
self.focused = focused
}
}
impl Handle<Tui> for TransportClock<Tui> {
fn handle (&mut self, _: &TuiInput) -> Perhaps<bool> {
Ok(None)
}
}
impl Content for TransportClock<Tui> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
let Self { frame: _frame, pulse, ppq, usecs, .. } = self;
Layers::new(move|add|{
let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
let (minutes, seconds) = (seconds / 60, seconds % 60);
add(&Outset::X(1u16, Split::down(|add|{
add(&format!("{bars}.{beats}.{pulses:02}").as_str())?;
add(&format!("{minutes}:{seconds:02}:{msecs:03}").as_str())
})))
})
}
}