mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
wip: refactor pt.8, 512 errors lol
This commit is contained in:
parent
a1818a8504
commit
a784f7a6f2
19 changed files with 238 additions and 183 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use scene::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct Arrangement {
|
pub struct Arrangement {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
|
|
@ -56,8 +56,8 @@ pub enum ArrangementCommand {
|
||||||
Go(Direction),
|
Go(Direction),
|
||||||
Edit(Option<Arc<RwLock<Phrase>>>),
|
Edit(Option<Arc<RwLock<Phrase>>>),
|
||||||
Scene(SceneCommand),
|
Scene(SceneCommand),
|
||||||
Track(TrackCommand),
|
Track(ArrangementTrackCommand),
|
||||||
Clip(ClipCommand),
|
Clip(ArrangementClipCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
pub(crate) use tek_core::*;
|
pub use tek_core::*;
|
||||||
pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7};
|
pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7};
|
||||||
pub(crate) use std::thread::JoinHandle;
|
pub(crate) use std::thread::JoinHandle;
|
||||||
pub(crate) use std::fmt::{Debug, Formatter, Error};
|
pub(crate) use std::fmt::{Debug, Formatter, Error};
|
||||||
|
|
@ -9,6 +9,8 @@ pub(crate) use tek_core::jack::{
|
||||||
};
|
};
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
|
api_jack
|
||||||
|
arrange
|
||||||
clock
|
clock
|
||||||
mixer
|
mixer
|
||||||
phrase
|
phrase
|
||||||
|
|
@ -16,14 +18,13 @@ submod! {
|
||||||
plugin_kind
|
plugin_kind
|
||||||
plugin_lv2
|
plugin_lv2
|
||||||
pool
|
pool
|
||||||
sampler
|
|
||||||
sample
|
sample
|
||||||
scene scene_cmd
|
sampler
|
||||||
|
scene
|
||||||
|
scene_cmd
|
||||||
sequencer
|
sequencer
|
||||||
track
|
track
|
||||||
transport transport_cmd
|
transport
|
||||||
|
transport_cmd
|
||||||
voice
|
voice
|
||||||
|
|
||||||
api_cmd
|
|
||||||
api_jack
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,17 @@ pub struct Transport {
|
||||||
pub metronome: bool,
|
pub metronome: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Transport {
|
||||||
|
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
|
||||||
|
f.debug_struct("transport")
|
||||||
|
.field("jack", &self.jack)
|
||||||
|
.field("transport", &"(JACK transport)")
|
||||||
|
.field("clock", &self.clock)
|
||||||
|
.field("metronome", &self.metronome)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Audio for Transport {
|
impl Audio for Transport {
|
||||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
let times = scope.cycle_times().unwrap();
|
let times = scope.cycle_times().unwrap();
|
||||||
|
|
@ -41,3 +52,22 @@ impl Audio for Transport {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Transport {
|
||||||
|
pub fn toggle_play (&mut self) -> Usually<()> {
|
||||||
|
let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet");
|
||||||
|
let playing = match playing {
|
||||||
|
TransportState::Stopped => {
|
||||||
|
self.transport.start()?;
|
||||||
|
Some(TransportState::Starting)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.transport.stop()?;
|
||||||
|
self.transport.locate(0)?;
|
||||||
|
Some(TransportState::Stopped)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
*self.clock.playing.write().unwrap() = playing;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ impl ArrangerCli {
|
||||||
/// Run the arranger TUI from CLI arguments.
|
/// Run the arranger TUI from CLI arguments.
|
||||||
fn run (&self) -> Usually<()> {
|
fn run (&self) -> Usually<()> {
|
||||||
Tui::run(JackClient::new("tek_arranger")?.activate_with(|jack|{
|
Tui::run(JackClient::new("tek_arranger")?.activate_with(|jack|{
|
||||||
let transport = TransportToolbar::new(jack, None);
|
let transport = TransportView::new(jack, None);
|
||||||
let phrases = Arc::new(RwLock::new(PhrasePool::new()));
|
let phrases = Arc::new(RwLock::new(PhrasePool::new()));
|
||||||
let mut arrangement = Arrangement::new(&jack, &transport.clock, "", &phrases);
|
let mut arrangement = Arrangement::new(&jack, &transport.clock, "", &phrases);
|
||||||
let transport = Arc::new(RwLock::new(transport));
|
let transport = Arc::new(RwLock::new(transport));
|
||||||
|
|
@ -43,7 +43,7 @@ impl ArrangerCli {
|
||||||
Some(scene_color_1.mix(scene_color_2, i as f32 / self.scenes as f32))
|
Some(scene_color_1.mix(scene_color_2, i as f32 / self.scenes as f32))
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Ok(ArrangerApp::new(
|
Ok(ArrangerView::new(
|
||||||
jack,
|
jack,
|
||||||
self.transport.then_some(transport),
|
self.transport.then_some(transport),
|
||||||
arrangement,
|
arrangement,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub struct SequencerCli {
|
||||||
impl SequencerCli {
|
impl SequencerCli {
|
||||||
fn run (&self) -> Usually<()> {
|
fn run (&self) -> Usually<()> {
|
||||||
Tui::run(JackClient::new("tek_sequencer")?.activate_with(|jack|{
|
Tui::run(JackClient::new("tek_sequencer")?.activate_with(|jack|{
|
||||||
let transport = TransportToolbar::new(jack, None);
|
let transport = TransportView::new(jack, None);
|
||||||
if let Some(_) = self.name.as_ref() {
|
if let Some(_) = self.name.as_ref() {
|
||||||
// TODO: sequencer.name = Arc::new(RwLock::new(name.clone()));
|
// TODO: sequencer.name = Arc::new(RwLock::new(name.clone()));
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +31,7 @@ impl SequencerCli {
|
||||||
//phrase.write().unwrap().length = length;
|
//phrase.write().unwrap().length = length;
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
Ok(SequencerApp {
|
Ok(SequencerView {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
focus_cursor: (1, 1),
|
focus_cursor: (1, 1),
|
||||||
entered: false,
|
entered: false,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ include!("lib.rs");
|
||||||
/// Application entrypoint.
|
/// Application entrypoint.
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{
|
Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{
|
||||||
let mut transport = TransportToolbarView::new(jack, None);
|
let mut transport = TransportView::new(jack, None);
|
||||||
transport.focused = true;
|
transport.focused = true;
|
||||||
Ok(transport)
|
Ok(transport)
|
||||||
})?)?;
|
})?)?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
pub(crate) use tek_core::*;
|
pub(crate) use tek_api::*;
|
||||||
pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers};
|
pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers};
|
||||||
pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage};
|
pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage};
|
||||||
pub(crate) use tek_core::jack::*;
|
pub(crate) use tek_core::jack::*;
|
||||||
|
|
@ -10,6 +10,7 @@ pub(crate) use std::ffi::OsString;
|
||||||
pub(crate) use std::fs::read_dir;
|
pub(crate) use std::fs::read_dir;
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
|
tui_app
|
||||||
tui_arrangement
|
tui_arrangement
|
||||||
tui_arranger
|
tui_arranger
|
||||||
tui_arranger_bar
|
tui_arranger_bar
|
||||||
|
|
@ -37,5 +38,6 @@ submod! {
|
||||||
tui_sequencer_cmd
|
tui_sequencer_cmd
|
||||||
tui_sequencer_foc
|
tui_sequencer_foc
|
||||||
tui_transport
|
tui_transport
|
||||||
|
tui_transport_bar
|
||||||
tui_transport_cmd
|
tui_transport_cmd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
crates/tek_tui/src/tui_app.rs
Normal file
39
crates/tek_tui/src/tui_app.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub struct App<E, T, C, S>
|
||||||
|
where
|
||||||
|
E: Engine,
|
||||||
|
T: Widget<Engine = E> + Handle<E> + Audio,
|
||||||
|
C: Command<T>,
|
||||||
|
S: Widget<Engine = E>
|
||||||
|
{
|
||||||
|
state: T,
|
||||||
|
cursor: (usize, usize),
|
||||||
|
entered: bool,
|
||||||
|
menu_bar: Option<MenuBar<E, T, C>>,
|
||||||
|
status_bar: Option<S>
|
||||||
|
history: Vec<C>,
|
||||||
|
size: Measure<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App<Tui, TransportView<Tui>, TransportViewCommand, TransportStatusBar> {
|
||||||
|
fn new () -> Self {
|
||||||
|
Self {
|
||||||
|
state: TransportView {
|
||||||
|
_engine: Default::default(),
|
||||||
|
state: Transport {
|
||||||
|
jack: Default::default(),
|
||||||
|
transport: Default::default(),
|
||||||
|
clock: Default::default(),
|
||||||
|
metronome: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cursor: (0, 0),
|
||||||
|
entered: false,
|
||||||
|
menu_bar: None,
|
||||||
|
status_bar: None,
|
||||||
|
history: vec![],
|
||||||
|
size: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ pub struct ArrangementEditor<E: Engine> {
|
||||||
/// Global JACK client
|
/// Global JACK client
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
/// Global timebase
|
/// Global timebase
|
||||||
pub clock: Arc<TransportTime>,
|
pub clock: Arc<Clock>,
|
||||||
/// Name of arranger
|
/// Name of arranger
|
||||||
pub name: Arc<RwLock<String>>,
|
pub name: Arc<RwLock<String>>,
|
||||||
/// Collection of phrases.
|
/// Collection of phrases.
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,28 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Root level object for standalone `tek_arranger`
|
/// Root level object for standalone `tek_arranger`
|
||||||
pub struct ArrangerApp<E: Engine> {
|
pub struct ArrangerView<E: Engine> {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// Sequencer component
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
pub sequencer: SequencerView<E>,
|
||||||
/// Which view is focused
|
|
||||||
pub focus_cursor: (usize, usize),
|
|
||||||
/// Whether the focused view is entered
|
|
||||||
pub entered: bool,
|
|
||||||
/// Controls the JACK transport.
|
|
||||||
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
|
|
||||||
/// Global timebase
|
|
||||||
pub clock: Arc<TransportTime>,
|
|
||||||
/// Contains all the sequencers.
|
/// Contains all the sequencers.
|
||||||
pub arrangement: ArrangementEditor<E>,
|
pub arrangement: ArrangementEditor<E>,
|
||||||
/// Pool of all phrases in the arrangement
|
|
||||||
pub phrases: Arc<RwLock<PhrasePool<E>>>,
|
|
||||||
/// Phrase editor view
|
|
||||||
pub editor: PhraseEditor<E>,
|
|
||||||
/// Status bar
|
/// Status bar
|
||||||
pub status: ArrangerStatusBar,
|
pub status: ArrangerStatusBar,
|
||||||
/// Height of arrangement
|
/// Height of arrangement
|
||||||
pub arrangement_split: u16,
|
pub split: u16,
|
||||||
/// Width of phrase pool
|
|
||||||
pub phrases_split: u16,
|
|
||||||
/// Width and height of app at last render
|
/// Width and height of app at last render
|
||||||
pub size: Measure<E>,
|
pub size: Measure<E>,
|
||||||
/// Menu bar
|
/// Menu bar
|
||||||
pub menu: MenuBar<E, Self, ArrangerAppCommand>,
|
pub menu: MenuBar<E, Self, ArrangerViewCommand>,
|
||||||
/// Command history
|
/// Command history
|
||||||
pub history: Vec<ArrangerAppCommand>,
|
pub history: Vec<ArrangerViewCommand>,
|
||||||
|
/// Which view is focused
|
||||||
|
pub cursor: (usize, usize),
|
||||||
|
/// Whether the focused view is entered
|
||||||
|
pub entered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Audio for ArrangerApp<E> {
|
impl<E: Engine> Audio for ArrangerView<E> {
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
if let Some(ref transport) = self.transport {
|
if let Some(ref transport) = self.transport {
|
||||||
transport.write().unwrap().process(client, scope);
|
transport.write().unwrap().process(client, scope);
|
||||||
|
|
@ -63,7 +53,7 @@ impl<E: Engine> Audio for ArrangerApp<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout for standalone arranger app.
|
/// Layout for standalone arranger app.
|
||||||
impl Content for ArrangerApp<Tui> {
|
impl Content for ArrangerView<Tui> {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
let focused = self.arrangement.focused;
|
let focused = self.arrangement.focused;
|
||||||
|
|
@ -103,7 +93,7 @@ impl Content for ArrangerApp<Tui> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// General methods for arranger
|
/// General methods for arranger
|
||||||
impl<E: Engine> ArrangerApp<E> {
|
impl<E: Engine> ArrangerView<E> {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
jack: &Arc<RwLock<JackClient>>,
|
jack: &Arc<RwLock<JackClient>>,
|
||||||
transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
|
transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
|
||||||
|
|
@ -126,10 +116,10 @@ impl<E: Engine> ArrangerApp<E> {
|
||||||
clock: if let Some(ref transport) = transport {
|
clock: if let Some(ref transport) = transport {
|
||||||
transport.read().unwrap().clock.clone()
|
transport.read().unwrap().clock.clone()
|
||||||
} else {
|
} else {
|
||||||
Arc::new(TransportTime::default())
|
Arc::new(Clock::default())
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
use ArrangerAppCommand::*;
|
use ArrangerViewCommand::*;
|
||||||
MenuBar::new()
|
MenuBar::new()
|
||||||
.add({
|
.add({
|
||||||
use ArrangementCommand::*;
|
use ArrangementCommand::*;
|
||||||
|
|
@ -139,7 +129,7 @@ impl<E: Engine> ArrangerApp<E> {
|
||||||
.cmd("s", "Save project", Arrangement(Save))
|
.cmd("s", "Save project", Arrangement(Save))
|
||||||
})
|
})
|
||||||
.add({
|
.add({
|
||||||
use TransportAppCommand::*;
|
use TransportViewCommand::*;
|
||||||
Menu::new("Transport")
|
Menu::new("Transport")
|
||||||
.cmd("p", "Play", Transport(Play))
|
.cmd("p", "Play", Transport(Play))
|
||||||
.cmd("s", "Play from start", Transport(PlayFromStart))
|
.cmd("s", "Play from start", Transport(PlayFromStart))
|
||||||
|
|
@ -188,7 +178,7 @@ impl<E: Engine> ArrangerApp<E> {
|
||||||
|
|
||||||
//pub fn new (
|
//pub fn new (
|
||||||
//jack: &Arc<RwLock<JackClient>>,
|
//jack: &Arc<RwLock<JackClient>>,
|
||||||
//clock: &Arc<TransportTime>,
|
//clock: &Arc<Clock>,
|
||||||
//name: &str,
|
//name: &str,
|
||||||
//phrases: &Arc<RwLock<PhrasePool<E>>>
|
//phrases: &Arc<RwLock<PhrasePool<E>>>
|
||||||
//) -> Self {
|
//) -> Self {
|
||||||
|
|
@ -234,7 +224,7 @@ impl<E: Engine> ArrangerApp<E> {
|
||||||
self.arrangement.phrase_put();
|
self.arrangement.phrase_put();
|
||||||
}
|
}
|
||||||
self.show_phrase();
|
self.show_phrase();
|
||||||
self.focus(ArrangerAppFocus::PhraseEditor);
|
self.focus(ArrangerViewFocus::PhraseEditor);
|
||||||
self.editor.entered = true;
|
self.editor.entered = true;
|
||||||
}
|
}
|
||||||
/// Rename the selected track, scene, or clip
|
/// Rename the selected track, scene, or clip
|
||||||
|
|
@ -247,7 +237,7 @@ impl<E: Engine> ArrangerApp<E> {
|
||||||
ArrangementEditorFocus::Clip(t, s) => if let Some(ref phrase) = scenes[s].clips[t] {
|
ArrangementEditorFocus::Clip(t, s) => if let Some(ref phrase) = scenes[s].clips[t] {
|
||||||
let index = self.phrases.read().unwrap().index_of(&*phrase.read().unwrap());
|
let index = self.phrases.read().unwrap().index_of(&*phrase.read().unwrap());
|
||||||
if let Some(index) = index {
|
if let Some(index) = index {
|
||||||
self.focus(ArrangerAppFocus::PhrasePool);
|
self.focus(ArrangerViewFocus::PhrasePool);
|
||||||
self.phrases.write().unwrap().phrase = index;
|
self.phrases.write().unwrap().phrase = index;
|
||||||
self.phrases.write().unwrap().begin_rename();
|
self.phrases.write().unwrap().begin_rename();
|
||||||
}
|
}
|
||||||
|
|
@ -257,15 +247,15 @@ impl<E: Engine> ArrangerApp<E> {
|
||||||
/// Update status bar
|
/// Update status bar
|
||||||
pub fn update_status (&mut self) {
|
pub fn update_status (&mut self) {
|
||||||
self.status = match self.focused() {
|
self.status = match self.focused() {
|
||||||
ArrangerAppFocus::Transport => ArrangerStatusBar::Transport,
|
ArrangerViewFocus::Transport => ArrangerStatusBar::Transport,
|
||||||
ArrangerAppFocus::Arrangement => match self.arrangement.selected {
|
ArrangerViewFocus::Arrangement => match self.arrangement.selected {
|
||||||
ArrangementEditorFocus::Mix => ArrangerStatusBar::ArrangementMix,
|
ArrangementEditorFocus::Mix => ArrangerStatusBar::ArrangementMix,
|
||||||
ArrangementEditorFocus::Track(_) => ArrangerStatusBar::ArrangementTrack,
|
ArrangementEditorFocus::Track(_) => ArrangerStatusBar::ArrangementTrack,
|
||||||
ArrangementEditorFocus::Scene(_) => ArrangerStatusBar::ArrangementScene,
|
ArrangementEditorFocus::Scene(_) => ArrangerStatusBar::ArrangementScene,
|
||||||
ArrangementEditorFocus::Clip(_, _) => ArrangerStatusBar::ArrangementClip,
|
ArrangementEditorFocus::Clip(_, _) => ArrangerStatusBar::ArrangementClip,
|
||||||
},
|
},
|
||||||
ArrangerAppFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
|
ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool,
|
||||||
ArrangerAppFocus::PhraseEditor => match self.editor.entered {
|
ArrangerViewFocus::PhraseEditor => match self.editor.entered {
|
||||||
true => ArrangerStatusBar::PhraseEdit,
|
true => ArrangerStatusBar::PhraseEdit,
|
||||||
false => ArrangerStatusBar::PhraseView,
|
false => ArrangerStatusBar::PhraseView,
|
||||||
},
|
},
|
||||||
|
|
@ -563,7 +553,7 @@ impl<E: Engine> Arrangement<E> {
|
||||||
impl ArrangementTrack {
|
impl ArrangementTrack {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
jack: &Arc<RwLock<JackClient>>,
|
jack: &Arc<RwLock<JackClient>>,
|
||||||
clock: &Arc<TransportTime>,
|
clock: &Arc<Clock>,
|
||||||
name: &str,
|
name: &str,
|
||||||
color: Option<ItemColor>
|
color: Option<ItemColor>
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ArrangerAppCommand {
|
pub enum ArrangerViewCommand {
|
||||||
Focus(FocusCommand),
|
Focus(FocusCommand),
|
||||||
Transport(TransportCommand),
|
Transport(TransportCommand),
|
||||||
Phrases(PhrasePoolCommand),
|
Phrases(PhrasePoolCommand),
|
||||||
|
|
@ -11,10 +11,10 @@ pub enum ArrangerAppCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle top-level events in standalone arranger.
|
/// Handle top-level events in standalone arranger.
|
||||||
impl Handle<Tui> for ArrangerApp<Tui> {
|
impl Handle<Tui> for ArrangerView<Tui> {
|
||||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||||
if let Some(entered) = self.entered() {
|
if let Some(entered) = self.entered() {
|
||||||
use ArrangerAppFocus::*;
|
use ArrangerViewFocus::*;
|
||||||
if let Some(true) = match entered {
|
if let Some(true) = match entered {
|
||||||
Transport => self.transport.as_mut().map(|t|t.handle(i)).transpose()?.flatten(),
|
Transport => self.transport.as_mut().map(|t|t.handle(i)).transpose()?.flatten(),
|
||||||
Arrangement => self.arrangement.handle(i)?,
|
Arrangement => self.arrangement.handle(i)?,
|
||||||
|
|
@ -24,14 +24,14 @@ impl Handle<Tui> for ArrangerApp<Tui> {
|
||||||
return Ok(Some(true))
|
return Ok(Some(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ArrangerAppCommand::execute_with_state(self, i)
|
ArrangerViewCommand::execute_with_state(self, i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputToCommand<Tui, ArrangerApp<Tui>> for ArrangerAppCommand {
|
impl InputToCommand<Tui, ArrangerView<Tui>> for ArrangerViewCommand {
|
||||||
fn input_to_command (state: &ArrangerApp<Tui>, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &ArrangerView<Tui>, input: &TuiInput) -> Option<Self> {
|
||||||
use FocusCommand::*;
|
use FocusCommand::*;
|
||||||
use ArrangerAppCommand::*;
|
use ArrangerViewCommand::*;
|
||||||
match input.event() {
|
match input.event() {
|
||||||
key!(KeyCode::Tab) => Some(Focus(Next)),
|
key!(KeyCode::Tab) => Some(Focus(Next)),
|
||||||
key!(Shift-KeyCode::Tab) => Some(Focus(Prev)),
|
key!(Shift-KeyCode::Tab) => Some(Focus(Prev)),
|
||||||
|
|
@ -45,11 +45,11 @@ impl InputToCommand<Tui, ArrangerApp<Tui>> for ArrangerAppCommand {
|
||||||
key!(KeyCode::Esc) => Some(Focus(Exit)),
|
key!(KeyCode::Esc) => Some(Focus(Exit)),
|
||||||
key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)),
|
key!(KeyCode::Char(' ')) => Some(Transport(TransportCommand::PlayToggle)),
|
||||||
_ => match state.focused() {
|
_ => match state.focused() {
|
||||||
ArrangerAppFocus::Transport => state.transport.as_ref()
|
ArrangerViewFocus::Transport => state.transport.as_ref()
|
||||||
.map(|t|TransportCommand::input_to_command(&*t.read().unwrap(), input)
|
.map(|t|TransportCommand::input_to_command(&*t.read().unwrap(), input)
|
||||||
.map(Transport))
|
.map(Transport))
|
||||||
.flatten(),
|
.flatten(),
|
||||||
ArrangerAppFocus::PhrasePool => {
|
ArrangerViewFocus::PhrasePool => {
|
||||||
let phrases = state.phrases.read().unwrap();
|
let phrases = state.phrases.read().unwrap();
|
||||||
match input.event() {
|
match input.event() {
|
||||||
key!(KeyCode::Char('e')) => Some(EditPhrase(Some(phrases.phrase().clone()))),
|
key!(KeyCode::Char('e')) => Some(EditPhrase(Some(phrases.phrase().clone()))),
|
||||||
|
|
@ -57,10 +57,10 @@ impl InputToCommand<Tui, ArrangerApp<Tui>> for ArrangerAppCommand {
|
||||||
.map(Phrases)
|
.map(Phrases)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ArrangerAppFocus::PhraseEditor =>
|
ArrangerViewFocus::PhraseEditor =>
|
||||||
PhraseEditorCommand::input_to_command(&state.editor, input)
|
PhraseEditorCommand::input_to_command(&state.editor, input)
|
||||||
.map(Editor),
|
.map(Editor),
|
||||||
ArrangerAppFocus::Arrangement => match input.event() {
|
ArrangerViewFocus::Arrangement => match input.event() {
|
||||||
key!(KeyCode::Char('e')) => Some(EditPhrase(state.arrangement.phrase())),
|
key!(KeyCode::Char('e')) => Some(EditPhrase(state.arrangement.phrase())),
|
||||||
_ => ArrangementCommand::input_to_command(&state.arrangement, &input)
|
_ => ArrangementCommand::input_to_command(&state.arrangement, &input)
|
||||||
.map(Arrangement)
|
.map(Arrangement)
|
||||||
|
|
@ -108,14 +108,14 @@ impl InputToCommand<Tui, Arrangement<Tui>> for ArrangementCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl ArrangerApp<Tui> {
|
//impl ArrangerView<Tui> {
|
||||||
///// Helper for event passthru to focused component
|
///// Helper for event passthru to focused component
|
||||||
//fn handle_focused (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
//fn handle_focused (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||||
//match self.focused() {
|
//match self.focused() {
|
||||||
//ArrangerAppFocus::Transport => self.transport.handle(from),
|
//ArrangerViewFocus::Transport => self.transport.handle(from),
|
||||||
//ArrangerAppFocus::PhrasePool => self.handle_pool(from),
|
//ArrangerViewFocus::PhrasePool => self.handle_pool(from),
|
||||||
//ArrangerAppFocus::PhraseEditor => self.editor.handle(from),
|
//ArrangerViewFocus::PhraseEditor => self.editor.handle(from),
|
||||||
//ArrangerAppFocus::Arrangement => self.handle_arrangement(from)
|
//ArrangerViewFocus::Arrangement => self.handle_arrangement(from)
|
||||||
//.and_then(|result|{self.show_phrase();Ok(result)}),
|
//.and_then(|result|{self.show_phrase();Ok(result)}),
|
||||||
//}
|
//}
|
||||||
//}
|
//}
|
||||||
|
|
@ -187,8 +187,8 @@ pub enum ArrangementCommand {
|
||||||
Edit(Option<Arc<RwLock<Phrase>>>),
|
Edit(Option<Arc<RwLock<Phrase>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Command<ArrangerApp<E>> for ArrangerAppCommand {
|
impl<E: Engine> Command<ArrangerView<E>> for ArrangerViewCommand {
|
||||||
fn execute (self, state: &mut ArrangerApp<E>) -> Perhaps<Self> {
|
fn execute (self, state: &mut ArrangerView<E>) -> Perhaps<Self> {
|
||||||
let undo = match self {
|
let undo = match self {
|
||||||
Self::Focus(cmd) => {
|
Self::Focus(cmd) => {
|
||||||
delegate(cmd, Self::Focus, state)
|
delegate(cmd, Self::Focus, state)
|
||||||
|
|
@ -209,7 +209,7 @@ impl<E: Engine> Command<ArrangerApp<E>> for ArrangerAppCommand {
|
||||||
},
|
},
|
||||||
Self::EditPhrase(phrase) => {
|
Self::EditPhrase(phrase) => {
|
||||||
state.editor.phrase = phrase.clone();
|
state.editor.phrase = phrase.clone();
|
||||||
state.focus(ArrangerAppFocus::PhraseEditor);
|
state.focus(ArrangerViewFocus::PhraseEditor);
|
||||||
state.focus_enter();
|
state.focus_enter();
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use crate::*;
|
||||||
|
|
||||||
/// Sections in the arranger app that may be focused
|
/// Sections in the arranger app that may be focused
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum ArrangerAppFocus {
|
pub enum ArrangerViewFocus {
|
||||||
/// The transport (toolbar) is focused
|
/// The transport (toolbar) is focused
|
||||||
Transport,
|
Transport,
|
||||||
/// The arrangement (grid) is focused
|
/// The arrangement (grid) is focused
|
||||||
|
|
@ -14,15 +14,15 @@ pub enum ArrangerAppFocus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Focus layout of arranger app
|
/// Focus layout of arranger app
|
||||||
impl<E: Engine> FocusGrid for ArrangerApp<E> {
|
impl<E: Engine> FocusGrid for ArrangerView<E> {
|
||||||
type Item = ArrangerAppFocus;
|
type Item = ArrangerViewFocus;
|
||||||
fn cursor (&self) -> (usize, usize) { self.focus_cursor }
|
fn cursor (&self) -> (usize, usize) { self.focus_cursor }
|
||||||
fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor }
|
fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor }
|
||||||
fn focus_enter (&mut self) {
|
fn focus_enter (&mut self) {
|
||||||
let focused = self.focused();
|
let focused = self.focused();
|
||||||
if !self.entered {
|
if !self.entered {
|
||||||
self.entered = true;
|
self.entered = true;
|
||||||
use ArrangerAppFocus::*;
|
use ArrangerViewFocus::*;
|
||||||
if let Some(transport) = self.transport.as_ref() {
|
if let Some(transport) = self.transport.as_ref() {
|
||||||
//transport.write().unwrap().entered = focused == Transport
|
//transport.write().unwrap().entered = focused == Transport
|
||||||
}
|
}
|
||||||
|
|
@ -39,15 +39,15 @@ impl<E: Engine> FocusGrid for ArrangerApp<E> {
|
||||||
self.phrases.write().unwrap().entered = false;
|
self.phrases.write().unwrap().entered = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn entered (&self) -> Option<ArrangerAppFocus> {
|
fn entered (&self) -> Option<ArrangerViewFocus> {
|
||||||
if self.entered {
|
if self.entered {
|
||||||
Some(self.focused())
|
Some(self.focused())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn layout (&self) -> &[&[ArrangerAppFocus]] {
|
fn layout (&self) -> &[&[ArrangerViewFocus]] {
|
||||||
use ArrangerAppFocus::*;
|
use ArrangerViewFocus::*;
|
||||||
&[
|
&[
|
||||||
&[Transport, Transport],
|
&[Transport, Transport],
|
||||||
&[Arrangement, Arrangement],
|
&[Arrangement, Arrangement],
|
||||||
|
|
@ -55,7 +55,7 @@ impl<E: Engine> FocusGrid for ArrangerApp<E> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
fn update_focus (&mut self) {
|
fn update_focus (&mut self) {
|
||||||
use ArrangerAppFocus::*;
|
use ArrangerViewFocus::*;
|
||||||
let focused = self.focused();
|
let focused = self.focused();
|
||||||
if let Some(transport) = self.transport.as_ref() {
|
if let Some(transport) = self.transport.as_ref() {
|
||||||
transport.write().unwrap().focused = focused == Transport
|
transport.write().unwrap().focused = focused == Transport
|
||||||
|
|
|
||||||
|
|
@ -2,44 +2,40 @@ use crate::*;
|
||||||
use std::cmp::PartialEq;
|
use std::cmp::PartialEq;
|
||||||
|
|
||||||
/// Root level object for standalone `tek_sequencer`
|
/// Root level object for standalone `tek_sequencer`
|
||||||
pub struct SequencerApp<E: Engine> {
|
pub struct SequencerView<E: Engine> {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
|
||||||
/// Controls the JACK transport.
|
/// Controls the JACK transport.
|
||||||
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
|
pub transport: TransportView<E>,
|
||||||
/// Global timebase
|
/// Width of phrase pool
|
||||||
pub clock: Arc<TransportTime>,
|
pub split: u16,
|
||||||
/// Pool of all phrases available to the sequencer
|
/// Pool of all phrases available to the sequencer
|
||||||
pub phrases: Arc<RwLock<PhrasePool<E>>>,
|
pub phrases: PhrasePoolView<E>,
|
||||||
/// Phrase editor view
|
/// Phrase editor view
|
||||||
pub editor: PhraseEditor<E>,
|
pub editor: PhraseEditor<E>,
|
||||||
/// Phrase player
|
/// Phrase player
|
||||||
pub player: PhrasePlayer,
|
pub player: MIDIPlayer,
|
||||||
/// Which view is focused
|
/// Which view is focused
|
||||||
pub focus_cursor: (usize, usize),
|
pub cursor: (usize, usize),
|
||||||
/// Whether the currently focused item is entered
|
/// Whether the currently focused item is entered
|
||||||
pub entered: bool,
|
pub entered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content for SequencerApp<Tui> {
|
/// JACK process callback for sequencer app
|
||||||
|
impl<E: Engine> Audio for SequencerView<E> {
|
||||||
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
self.transport.process(client, scope);
|
||||||
|
self.player.process(client, scope);
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content for SequencerView<Tui> {
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
Stack::down(move|add|{
|
Stack::down(move|add|{
|
||||||
add(&self.transport)?;
|
add(&self.transport)?;
|
||||||
add(&self.phrases.clone()
|
add(&self.phrases
|
||||||
.split(Direction::Right, 20, &self.editor as &dyn Widget<Engine = Tui>)
|
.split(Direction::Right, 20, &self.editor as &dyn Widget<Engine = Tui>)
|
||||||
.min_y(20))
|
.min_y(20))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JACK process callback for sequencer app
|
|
||||||
impl Audio for SequencerApp {
|
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
if let Some(ref transport) = self.transport {
|
|
||||||
transport.write().unwrap().process(client, scope);
|
|
||||||
}
|
|
||||||
self.player.process(client, scope);
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum SequencerAppCommand {
|
pub enum SequencerViewCommand {
|
||||||
Focus(FocusCommand),
|
Focus(FocusCommand),
|
||||||
Transport(TransportCommand),
|
Transport(TransportCommand),
|
||||||
Phrases(PhrasePoolCommand),
|
Phrases(PhrasePoolCommand),
|
||||||
Editor(PhraseEditorCommand),
|
Editor(PhraseEditorCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handle<Tui> for SequencerApp<Tui> {
|
impl Handle<Tui> for SequencerView<Tui> {
|
||||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||||
if let Some(entered) = self.entered() {
|
if let Some(entered) = self.entered() {
|
||||||
use SequencerFocus::*;
|
use SequencerFocus::*;
|
||||||
|
|
@ -28,8 +28,8 @@ impl Handle<Tui> for SequencerApp<Tui> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Command<SequencerApp<E>> for SequencerAppCommand {
|
impl<E: Engine> Command<SequencerView<E>> for SequencerViewCommand {
|
||||||
fn execute (self, state: &mut SequencerApp<E>) -> Perhaps<Self> {
|
fn execute (self, state: &mut SequencerView<E>) -> Perhaps<Self> {
|
||||||
match self {
|
match self {
|
||||||
Self::Focus(cmd) => {
|
Self::Focus(cmd) => {
|
||||||
return delegate(cmd, Self::Focus, state)
|
return delegate(cmd, Self::Focus, state)
|
||||||
|
|
@ -48,9 +48,9 @@ impl<E: Engine> Command<SequencerApp<E>> for SequencerAppCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputToCommand<Tui, SequencerApp<Tui>> for SequencerAppCommand {
|
impl InputToCommand<Tui, SequencerView<Tui>> for SequencerViewCommand {
|
||||||
fn input_to_command (state: &SequencerApp<Tui>, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &SequencerView<Tui>, input: &TuiInput) -> Option<Self> {
|
||||||
use SequencerAppCommand::*;
|
use SequencerViewCommand::*;
|
||||||
use FocusCommand::*;
|
use FocusCommand::*;
|
||||||
match input.event() {
|
match input.event() {
|
||||||
key!(KeyCode::Tab) => Some(Focus(Next)),
|
key!(KeyCode::Tab) => Some(Focus(Next)),
|
||||||
|
|
@ -61,10 +61,10 @@ impl InputToCommand<Tui, SequencerApp<Tui>> for SequencerAppCommand {
|
||||||
key!(KeyCode::Down) => Some(Focus(Down)),
|
key!(KeyCode::Down) => Some(Focus(Down)),
|
||||||
key!(KeyCode::Left) => Some(Focus(Left)),
|
key!(KeyCode::Left) => Some(Focus(Left)),
|
||||||
key!(KeyCode::Right) => Some(Focus(Right)),
|
key!(KeyCode::Right) => Some(Focus(Right)),
|
||||||
key!(KeyCode::Char(' ')) => Some(Transport(TransportAppCommand::PlayToggle)),
|
key!(KeyCode::Char(' ')) => Some(Transport(TransportViewCommand::PlayToggle)),
|
||||||
_ => match state.focused() {
|
_ => match state.focused() {
|
||||||
SequencerFocus::Transport => if let Some(t) = state.transport.as_ref() {
|
SequencerFocus::Transport => if let Some(t) = state.transport.as_ref() {
|
||||||
TransportAppCommand::input_to_command(&*t.read().unwrap(), input).map(Transport)
|
TransportViewCommand::input_to_command(&*t.read().unwrap(), input).map(Transport)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,30 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use tek_api::Transport;
|
||||||
|
|
||||||
/// Stores and displays time-related state.
|
/// Stores and displays time-related state.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TransportView<E: Engine> {
|
pub struct TransportView<E: Engine> {
|
||||||
_engine: PhantomData<E>,
|
_engine: PhantomData<E>,
|
||||||
state: TransportToolbar,
|
state: Transport,
|
||||||
|
focus: TransportViewFocus,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
focus: TransportFocus,
|
|
||||||
}
|
}
|
||||||
/// Which item of the transport toolbar is focused
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
/// JACK process callback for transport app
|
||||||
pub enum TransportFocus {
|
impl<E: Engine> Audio for TransportView<E> {
|
||||||
Bpm,
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
Sync,
|
self.state.process(client, scope);
|
||||||
PlayPause,
|
Control::Continue
|
||||||
Clock,
|
|
||||||
Quant,
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<E: Engine> TransportView<E> {
|
impl<E: Engine> TransportView<E> {
|
||||||
pub fn new (jack: &Arc<RwLock<JackClient>>, clock: Option<&Arc<TransportTime>>) -> Self {
|
pub fn new (jack: &Arc<RwLock<JackClient>>, clock: Option<&Arc<Clock>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
_engine: Default::default(),
|
_engine: Default::default(),
|
||||||
focused: false,
|
focused: false,
|
||||||
focus: TransportFocus::PlayPause,
|
focus: TransportViewFocus::PlayPause,
|
||||||
state: TransportToolbar {
|
state: Transport {
|
||||||
metronome: false,
|
metronome: false,
|
||||||
transport: jack.read().unwrap().transport(),
|
transport: jack.read().unwrap().transport(),
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
|
|
@ -30,7 +32,7 @@ impl<E: Engine> TransportView<E> {
|
||||||
Some(clock) => clock.clone(),
|
Some(clock) => clock.clone(),
|
||||||
None => {
|
None => {
|
||||||
let timebase = Arc::new(Timebase::default());
|
let timebase = Arc::new(Timebase::default());
|
||||||
Arc::new(TransportTime {
|
Arc::new(Clock {
|
||||||
playing: Some(TransportState::Stopped).into(),
|
playing: Some(TransportState::Stopped).into(),
|
||||||
quant: 24.into(),
|
quant: 24.into(),
|
||||||
sync: (timebase.ppq.get() * 4.).into(),
|
sync: (timebase.ppq.get() * 4.).into(),
|
||||||
|
|
@ -42,48 +44,13 @@ impl<E: Engine> TransportView<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn toggle_play (&mut self) -> Usually<()> {
|
|
||||||
let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet");
|
|
||||||
let playing = match playing {
|
|
||||||
TransportState::Stopped => {
|
|
||||||
self.transport.start()?;
|
|
||||||
Some(TransportState::Starting)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
self.transport.stop()?;
|
|
||||||
self.transport.locate(0)?;
|
|
||||||
Some(TransportState::Stopped)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
*self.clock.playing.write().unwrap() = playing;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl TransportFocus {
|
impl Content for TransportView<Tui> {
|
||||||
pub fn next (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::PlayPause => Self::Bpm,
|
|
||||||
Self::Bpm => Self::Quant,
|
|
||||||
Self::Quant => Self::Sync,
|
|
||||||
Self::Sync => Self::Clock,
|
|
||||||
Self::Clock => Self::PlayPause,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn prev (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::PlayPause => Self::Clock,
|
|
||||||
Self::Bpm => Self::PlayPause,
|
|
||||||
Self::Quant => Self::Bpm,
|
|
||||||
Self::Sync => Self::Quant,
|
|
||||||
Self::Clock => Self::Sync,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Content for TransportToolbar<Tui> {
|
|
||||||
type Engine = Tui;
|
type Engine = Tui;
|
||||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||||
lay!(
|
lay!(
|
||||||
self.focus.wrap(self.focused, TransportToolbarFocus::PlayPause, &Styled(
|
self.focus.wrap(self.focused, TransportViewFocus::PlayPause, &Styled(
|
||||||
None,
|
None,
|
||||||
match *self.clock.playing.read().unwrap() {
|
match *self.clock.playing.read().unwrap() {
|
||||||
Some(TransportState::Rolling) => "▶ PLAYING",
|
Some(TransportState::Rolling) => "▶ PLAYING",
|
||||||
|
|
@ -94,19 +61,19 @@ impl Content for TransportToolbar<Tui> {
|
||||||
).min_xy(11, 2).push_x(1)).align_x().fill_x(),
|
).min_xy(11, 2).push_x(1)).align_x().fill_x(),
|
||||||
|
|
||||||
row!(
|
row!(
|
||||||
self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, {
|
self.focus.wrap(self.focused, TransportViewFocus::Bpm, &Outset::X(1u16, {
|
||||||
let bpm = self.clock.timebase().bpm.get();
|
let bpm = self.clock.timebase().bpm.get();
|
||||||
row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) }
|
row! { "BPM ", format!("{}.{:03}", bpm as usize, (bpm * 1000.0) % 1000.0) }
|
||||||
})),
|
})),
|
||||||
//let quant = self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, row! {
|
//let quant = self.focus.wrap(self.focused, TransportViewFocus::Quant, &Outset::X(1u16, row! {
|
||||||
//"QUANT ", ppq_to_name(self.quant as usize)
|
//"QUANT ", ppq_to_name(self.quant as usize)
|
||||||
//})),
|
//})),
|
||||||
self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, row! {
|
self.focus.wrap(self.focused, TransportViewFocus::Sync, &Outset::X(1u16, row! {
|
||||||
"SYNC ", pulses_to_name(self.clock.sync.get() as usize)
|
"SYNC ", pulses_to_name(self.clock.sync.get() as usize)
|
||||||
}))
|
}))
|
||||||
).align_w().fill_x(),
|
).align_w().fill_x(),
|
||||||
|
|
||||||
self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{
|
self.focus.wrap(self.focused, TransportViewFocus::Clock, &{
|
||||||
let time1 = self.clock.current.format_beat();
|
let time1 = self.clock.current.format_beat();
|
||||||
let time2 = self.clock.current.usec.format_msu();
|
let time2 = self.clock.current.usec.format_msu();
|
||||||
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)
|
row!("B" ,time1.as_str(), " T", time2.as_str()).outset_x(1)
|
||||||
|
|
@ -115,13 +82,3 @@ impl Content for TransportToolbar<Tui> {
|
||||||
).fill_x().bg(Color::Rgb(40, 50, 30))
|
).fill_x().bg(Color::Rgb(40, 50, 30))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl TransportToolbarFocus {
|
|
||||||
pub fn wrap <'a, W: Widget<Engine = Tui>> (
|
|
||||||
self, parent_focus: bool, focus: Self, widget: &'a W
|
|
||||||
) -> impl Widget<Engine = Tui> + 'a {
|
|
||||||
let focused = parent_focus && focus == self;
|
|
||||||
let corners = focused.then_some(CORNERS);
|
|
||||||
let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
|
|
||||||
lay!(corners, highlight, *widget)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum TransportAppCommand {
|
pub enum TransportViewCommand {
|
||||||
Focus(FocusCommand),
|
Focus(FocusCommand),
|
||||||
Transport(TransportCommand),
|
Transport(TransportCommand),
|
||||||
}
|
}
|
||||||
impl Handle<Tui> for TransportView<Tui> {
|
impl Handle<Tui> for TransportView<Tui> {
|
||||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||||
TransportAppCommand::execute_with_state(self, from)
|
TransportViewCommand::execute_with_state(self, from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InputToCommand<Tui, TransportView<Tui>> for TransportAppCommand {
|
impl InputToCommand<Tui, TransportView<Tui>> for TransportViewCommand {
|
||||||
fn input_to_command (_: &TransportView<Tui>, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (_: &TransportView<Tui>, input: &TuiInput) -> Option<Self> {
|
||||||
match input.event() {
|
match input.event() {
|
||||||
key!(KeyCode::Char(' ')) => Some(Self::FocusPrev),
|
key!(KeyCode::Char(' ')) => Some(Self::FocusPrev),
|
||||||
|
|
|
||||||
40
crates/tek_tui/src/tui_transport_foc.rs
Normal file
40
crates/tek_tui/src/tui_transport_foc.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl TransportViewFocus {
|
||||||
|
pub fn next (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::PlayPause => Self::Bpm,
|
||||||
|
Self::Bpm => Self::Quant,
|
||||||
|
Self::Quant => Self::Sync,
|
||||||
|
Self::Sync => Self::Clock,
|
||||||
|
Self::Clock => Self::PlayPause,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn prev (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::PlayPause => Self::Clock,
|
||||||
|
Self::Bpm => Self::PlayPause,
|
||||||
|
Self::Quant => Self::Bpm,
|
||||||
|
Self::Sync => Self::Quant,
|
||||||
|
Self::Clock => Self::Sync,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn wrap <'a, W: Widget<Engine = Tui>> (
|
||||||
|
self, parent_focus: bool, focus: Self, widget: &'a W
|
||||||
|
) -> impl Widget<Engine = Tui> + 'a {
|
||||||
|
let focused = parent_focus && focus == self;
|
||||||
|
let corners = focused.then_some(CORNERS);
|
||||||
|
let highlight = focused.then_some(Background(Color::Rgb(60, 70, 50)));
|
||||||
|
lay!(corners, highlight, *widget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which item of the transport toolbar is focused
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum TransportViewFocus {
|
||||||
|
Bpm,
|
||||||
|
Sync,
|
||||||
|
PlayPause,
|
||||||
|
Clock,
|
||||||
|
Quant,
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue