wip: refactor pt.8, 512 errors lol

This commit is contained in:
🪞👃🪞 2024-11-10 15:40:45 +01:00
parent a1818a8504
commit a784f7a6f2
19 changed files with 238 additions and 183 deletions

View file

@ -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)]

View file

@ -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
} }

View file

@ -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(())
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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)
})?)?; })?)?;

View file

@ -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
} }

View 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(),
}
}
}

View file

@ -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.

View file

@ -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> {

View file

@ -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)
} }

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}, },

View file

@ -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 {
pub fn next (&mut self) { impl Content for TransportView<Tui> {
*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)
}
}

View file

@ -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),

View 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,
}