mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: reenabling sequencer pane
This commit is contained in:
parent
6e81082c17
commit
4c640301c6
2 changed files with 501 additions and 512 deletions
|
|
@ -3,19 +3,7 @@ include!("lib.rs");
|
||||||
use tek_core::clap::{self, Parser};
|
use tek_core::clap::{self, Parser};
|
||||||
|
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
Tui::run(Arc::new(RwLock::new(crate::ArrangerStandalone::from_args()?)))?;
|
ArrangerCli::parse().run()
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
|
@ -38,51 +26,69 @@ pub struct ArrangerCli {
|
||||||
scenes: usize,
|
scenes: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArrangerStandalone<Tui> {
|
impl ArrangerCli {
|
||||||
pub fn from_args () -> Usually<Self> {
|
fn run (&self) -> Usually<()> {
|
||||||
let args = ArrangerCli::parse();
|
|
||||||
let mut arranger = Arranger::new("");
|
let mut arranger = Arranger::new("");
|
||||||
let mut transport = match args.transport {
|
let mut transport = match self.transport {
|
||||||
Some(true) => Some(TransportToolbar::new(None)),
|
Some(true) => Some(TransportToolbar::new(None)),
|
||||||
_ => None
|
_ => None
|
||||||
};
|
};
|
||||||
if let Some(name) = args.name {
|
if let Some(name) = self.name.as_ref() {
|
||||||
*arranger.name.write().unwrap() = name.clone();
|
*arranger.name.write().unwrap() = name.clone();
|
||||||
}
|
}
|
||||||
for _ in 0..args.tracks {
|
for _ in 0..self.tracks {
|
||||||
let track = arranger.track_add(None)?;
|
let track = arranger.track_add(None)?;
|
||||||
for _ in 0..args.scenes {
|
for _ in 0..self.scenes {
|
||||||
track.phrases.push(
|
track.phrases.push(
|
||||||
Arc::new(RwLock::new(Phrase::new("", 96 * 4, None)))
|
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)?;
|
let _scene = arranger.scene_add(None)?;
|
||||||
//for i in 0..args.tracks {
|
//for i in 0..self.tracks {
|
||||||
//scene.clips[i] = Some(i);
|
//scene.clips[i] = Some(i);
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
transport.set_focused(true);
|
transport.set_focused(true);
|
||||||
Ok(ArrangerStandalone {
|
let state = Arc::new(RwLock::new(ArrangerStandalone {
|
||||||
transport,
|
transport,
|
||||||
show_sequencer: Some(tek_core::Direction::Down),
|
show_sequencer: Some(tek_core::Direction::Down),
|
||||||
arranger,
|
arranger,
|
||||||
focus: 0
|
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> {
|
impl Content for ArrangerStandalone<Tui> {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
Split::down(|add|{
|
Split::down(|add|{
|
||||||
add(&self.transport)?;
|
add(&self.transport)?;
|
||||||
|
if let Some(direction) = self.show_sequencer {
|
||||||
|
add(&Split::new(direction, |add|{
|
||||||
add(&self.arranger)?;
|
add(&self.arranger)?;
|
||||||
if let Some(sequencer) = self.arranger.sequencer() {
|
add(&Min::Y(10, self.arranger.sequencer().map(
|
||||||
add(sequencer)?;
|
|x|x as &dyn Widget<Engine = Tui>
|
||||||
}
|
)))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
add(&self.arranger)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
//if let Some(ref modal) = self.arranger.modal {
|
//if let Some(ref modal) = self.arranger.modal {
|
||||||
//to.fill_bg(area, Nord::bg_lo(false, false));
|
//to.fill_bg(area, Nord::bg_lo(false, false));
|
||||||
|
|
|
||||||
|
|
@ -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.
|
/// Represents the tracks and scenes of the composition.
|
||||||
pub struct Arranger<E: Engine> {
|
pub struct Arranger<E: Engine> {
|
||||||
/// Name of arranger
|
/// 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;
|
let index = index as usize;
|
||||||
if self.keys_in[index] && self.keys_out[index] {
|
if self.keys_in[index] && self.keys_out[index] {
|
||||||
Color::Yellow
|
Color::Yellow
|
||||||
|
|
@ -1795,18 +1351,29 @@ pub type MIDIMessage = Vec<u8>;
|
||||||
/// Collection of serialized MIDI messages
|
/// Collection of serialized MIDI messages
|
||||||
pub type MIDIChunk = [Vec<MIDIMessage>];
|
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;
|
type Engine = Tui;
|
||||||
fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
todo!()
|
Split::right(move |add|{
|
||||||
}
|
add(&Split::down(|add|{
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
add(&SequenceName(&self))?;
|
||||||
self.horizontal_draw(to)?;
|
add(&SequenceRange)?;
|
||||||
if self.focused && self.entered {
|
add(&SequenceLoopRange)?;
|
||||||
Corners(Style::default().green().not_dim()).draw(to)?;
|
add(&SequenceNoteRange)?;
|
||||||
}
|
|
||||||
//Ok(Some(to.area()))
|
|
||||||
Ok(())
|
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
|
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>);
|
struct SequenceName<'a>(&'a Sequencer<Tui>);
|
||||||
|
|
@ -2438,3 +1977,447 @@ impl Phrase {
|
||||||
Ok(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())
|
||||||
|
})))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue