mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: refactor pt.30: 74 errors
This commit is contained in:
parent
ab85a86b6b
commit
28e15d3f56
12 changed files with 631 additions and 622 deletions
|
|
@ -230,3 +230,150 @@ impl ArrangerScene {
|
|||
//})
|
||||
//}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerCommand {
|
||||
Clear,
|
||||
Export,
|
||||
Import,
|
||||
StopAll,
|
||||
Scene(ArrangerSceneCommand),
|
||||
Track(ArrangerTrackCommand),
|
||||
Clip(ArrangerClipCommand),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerSceneCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
RandomColor,
|
||||
Play(usize),
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerTrackCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
RandomColor,
|
||||
Stop,
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerClipCommand {
|
||||
Play,
|
||||
Get(usize, usize),
|
||||
Set(usize, usize, Option<Arc<RwLock<Phrase>>>),
|
||||
Edit(Option<Arc<RwLock<Phrase>>>),
|
||||
SetLoop(bool),
|
||||
RandomColor,
|
||||
}
|
||||
|
||||
impl Command<ArrangerModel> for ArrangerCommand {
|
||||
fn execute (self, state: &mut ArrangerModel) -> Perhaps<Self> {
|
||||
match self {
|
||||
Self::Scene(command) => { return Ok(command.execute(state)?.map(Self::Scene)) },
|
||||
Self::Track(command) => { return Ok(command.execute(state)?.map(Self::Track)) },
|
||||
Self::Clip(command) => { return Ok(command.execute(state)?.map(Self::Clip)) },
|
||||
_ => todo!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerModel> for ArrangerSceneCommand {
|
||||
fn execute (self, state: &mut ArrangerModel) -> Perhaps<Self> {
|
||||
match self {
|
||||
Self::Delete(index) => { state.scene_del(index); },
|
||||
_ => todo!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerModel> for ArrangerTrackCommand {
|
||||
fn execute (self, state: &mut ArrangerModel) -> Perhaps<Self> {
|
||||
match self {
|
||||
Self::Delete(index) => { state.track_del(index); },
|
||||
_ => todo!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerModel> for ArrangerClipCommand {
|
||||
fn execute (self, state: &mut ArrangerModel) -> Perhaps<Self> {
|
||||
match self {
|
||||
_ => todo!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
//impl Command<ArrangerModel> for ArrangerSceneCommand {
|
||||
//}
|
||||
//Edit(phrase) => { state.state.phrase = phrase.clone() },
|
||||
//ToggleViewMode => { state.state.mode.to_next(); },
|
||||
//Delete => { state.state.delete(); },
|
||||
//Activate => { state.state.activate(); },
|
||||
//ZoomIn => { state.state.zoom_in(); },
|
||||
//ZoomOut => { state.state.zoom_out(); },
|
||||
//MoveBack => { state.state.move_back(); },
|
||||
//MoveForward => { state.state.move_forward(); },
|
||||
//RandomColor => { state.state.randomize_color(); },
|
||||
//Put => { state.state.phrase_put(); },
|
||||
//Get => { state.state.phrase_get(); },
|
||||
//AddScene => { state.state.scene_add(None, None)?; },
|
||||
//AddTrack => { state.state.track_add(None, None)?; },
|
||||
//ToggleLoop => { state.state.toggle_loop() },
|
||||
//pub fn zoom_in (&mut self) {
|
||||
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
|
||||
//self.mode = ArrangerEditorMode::Vertical(factor + 1)
|
||||
//}
|
||||
//}
|
||||
//pub fn zoom_out (&mut self) {
|
||||
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
|
||||
//self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1))
|
||||
//}
|
||||
//}
|
||||
//pub fn move_back (&mut self) {
|
||||
//match self.selected {
|
||||
//ArrangerEditorFocus::Scene(s) => {
|
||||
//if s > 0 {
|
||||
//self.scenes.swap(s, s - 1);
|
||||
//self.selected = ArrangerEditorFocus::Scene(s - 1);
|
||||
//}
|
||||
//},
|
||||
//ArrangerEditorFocus::Track(t) => {
|
||||
//if t > 0 {
|
||||
//self.tracks.swap(t, t - 1);
|
||||
//self.selected = ArrangerEditorFocus::Track(t - 1);
|
||||
//// FIXME: also swap clip order in scenes
|
||||
//}
|
||||
//},
|
||||
//_ => todo!("arrangement: move forward")
|
||||
//}
|
||||
//}
|
||||
//pub fn move_forward (&mut self) {
|
||||
//match self.selected {
|
||||
//ArrangerEditorFocus::Scene(s) => {
|
||||
//if s < self.scenes.len().saturating_sub(1) {
|
||||
//self.scenes.swap(s, s + 1);
|
||||
//self.selected = ArrangerEditorFocus::Scene(s + 1);
|
||||
//}
|
||||
//},
|
||||
//ArrangerEditorFocus::Track(t) => {
|
||||
//if t < self.tracks.len().saturating_sub(1) {
|
||||
//self.tracks.swap(t, t + 1);
|
||||
//self.selected = ArrangerEditorFocus::Track(t + 1);
|
||||
//// FIXME: also swap clip order in scenes
|
||||
//}
|
||||
//},
|
||||
//_ => todo!("arrangement: move forward")
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,148 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ArrangerCommand {
|
||||
Clear,
|
||||
Export,
|
||||
Import,
|
||||
StopAll,
|
||||
Scene(ArrangerSceneCommand),
|
||||
Track(ArrangerTrackCommand),
|
||||
Clip(ArrangerClipCommand),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ArrangerSceneCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
RandomColor,
|
||||
Play(usize),
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ArrangerTrackCommand {
|
||||
Add,
|
||||
Delete(usize),
|
||||
RandomColor,
|
||||
Stop,
|
||||
Swap(usize, usize),
|
||||
SetSize(usize),
|
||||
SetZoom(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ArrangerClipCommand {
|
||||
Play,
|
||||
Get(usize, usize),
|
||||
Set(usize, usize, Option<Arc<RwLock<Phrase>>>),
|
||||
Edit(Option<Arc<RwLock<Phrase>>>),
|
||||
SetLoop(bool),
|
||||
RandomColor,
|
||||
}
|
||||
|
||||
impl Command<ArrangerModel> for ArrangerCommand {
|
||||
fn execute (self, state: &mut ArrangerModel) -> Perhaps<Self> {
|
||||
match self {
|
||||
Self::Scene(command) => { return Ok(command.execute(state)?.map(Self::Scene)) },
|
||||
Self::Track(command) => { return Ok(command.execute(state)?.map(Self::Track)) },
|
||||
Self::Clip(command) => { return Ok(command.execute(state)?.map(Self::Clip)) },
|
||||
_ => todo!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerModel> for ArrangerSceneCommand {
|
||||
fn execute (self, state: &mut ArrangerModel) -> Perhaps<Self> {
|
||||
match self {
|
||||
Self::Delete(index) => { state.scene_del(index); },
|
||||
_ => todo!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerModel> for ArrangerTrackCommand {
|
||||
fn execute (self, state: &mut ArrangerModel) -> Perhaps<Self> {
|
||||
match self {
|
||||
Self::Delete(index) => { state.track_del(index); },
|
||||
_ => todo!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerModel> for ArrangerClipCommand {
|
||||
fn execute (self, state: &mut ArrangerModel) -> Perhaps<Self> {
|
||||
match self {
|
||||
_ => todo!()
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
//impl Command<ArrangerModel> for ArrangerSceneCommand {
|
||||
//}
|
||||
//Edit(phrase) => { state.state.phrase = phrase.clone() },
|
||||
//ToggleViewMode => { state.state.mode.to_next(); },
|
||||
//Delete => { state.state.delete(); },
|
||||
//Activate => { state.state.activate(); },
|
||||
//ZoomIn => { state.state.zoom_in(); },
|
||||
//ZoomOut => { state.state.zoom_out(); },
|
||||
//MoveBack => { state.state.move_back(); },
|
||||
//MoveForward => { state.state.move_forward(); },
|
||||
//RandomColor => { state.state.randomize_color(); },
|
||||
//Put => { state.state.phrase_put(); },
|
||||
//Get => { state.state.phrase_get(); },
|
||||
//AddScene => { state.state.scene_add(None, None)?; },
|
||||
//AddTrack => { state.state.track_add(None, None)?; },
|
||||
//ToggleLoop => { state.state.toggle_loop() },
|
||||
//pub fn zoom_in (&mut self) {
|
||||
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
|
||||
//self.mode = ArrangerEditorMode::Vertical(factor + 1)
|
||||
//}
|
||||
//}
|
||||
//pub fn zoom_out (&mut self) {
|
||||
//if let ArrangerEditorMode::Vertical(factor) = self.mode {
|
||||
//self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1))
|
||||
//}
|
||||
//}
|
||||
//pub fn move_back (&mut self) {
|
||||
//match self.selected {
|
||||
//ArrangerEditorFocus::Scene(s) => {
|
||||
//if s > 0 {
|
||||
//self.scenes.swap(s, s - 1);
|
||||
//self.selected = ArrangerEditorFocus::Scene(s - 1);
|
||||
//}
|
||||
//},
|
||||
//ArrangerEditorFocus::Track(t) => {
|
||||
//if t > 0 {
|
||||
//self.tracks.swap(t, t - 1);
|
||||
//self.selected = ArrangerEditorFocus::Track(t - 1);
|
||||
//// FIXME: also swap clip order in scenes
|
||||
//}
|
||||
//},
|
||||
//_ => todo!("arrangement: move forward")
|
||||
//}
|
||||
//}
|
||||
//pub fn move_forward (&mut self) {
|
||||
//match self.selected {
|
||||
//ArrangerEditorFocus::Scene(s) => {
|
||||
//if s < self.scenes.len().saturating_sub(1) {
|
||||
//self.scenes.swap(s, s + 1);
|
||||
//self.selected = ArrangerEditorFocus::Scene(s + 1);
|
||||
//}
|
||||
//},
|
||||
//ArrangerEditorFocus::Track(t) => {
|
||||
//if t < self.tracks.len().saturating_sub(1) {
|
||||
//self.tracks.swap(t, t + 1);
|
||||
//self.selected = ArrangerEditorFocus::Track(t + 1);
|
||||
//// FIXME: also swap clip order in scenes
|
||||
//}
|
||||
//},
|
||||
//_ => todo!("arrangement: move forward")
|
||||
//}
|
||||
//}
|
||||
|
|
@ -12,7 +12,6 @@ submod! {
|
|||
//api_jack
|
||||
|
||||
arrange
|
||||
arrange_cmd
|
||||
|
||||
clock
|
||||
|
||||
|
|
@ -36,7 +35,6 @@ submod! {
|
|||
status
|
||||
|
||||
transport
|
||||
transport_cmd
|
||||
}
|
||||
|
||||
pub trait JackModelApi {
|
||||
|
|
|
|||
|
|
@ -65,3 +65,33 @@ impl TransportModel {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum TransportCommand {
|
||||
Play(Option<usize>),
|
||||
Pause(Option<usize>),
|
||||
SeekUsec(f64),
|
||||
SeekSample(f64),
|
||||
SeekPulse(f64),
|
||||
SetBpm(f64),
|
||||
SetQuant(f64),
|
||||
SetSync(f64),
|
||||
}
|
||||
|
||||
impl<T: TransportModelApi> Command<T> for TransportCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
use TransportCommand::*;
|
||||
match self {
|
||||
Play(start) => {todo!()},
|
||||
Pause(start) => {todo!()},
|
||||
SeekUsec(usec) => {state.clock().current.update_from_usec(usec);},
|
||||
SeekSample(sample) => {state.clock().current.update_from_sample(sample);},
|
||||
SeekPulse(pulse) => {state.clock().current.update_from_pulse(pulse);},
|
||||
SetBpm(bpm) => {return Ok(Some(Self::SetBpm(state.clock().timebase().bpm.set(bpm))))},
|
||||
SetQuant(quant) => {return Ok(Some(Self::SetQuant(state.clock().quant.set(quant))))},
|
||||
SetSync(sync) => {return Ok(Some(Self::SetSync(state.clock().sync.set(sync))))},
|
||||
_ => { unreachable!() }
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum TransportCommand {
|
||||
Play(Option<usize>),
|
||||
Pause(Option<usize>),
|
||||
SeekUsec(f64),
|
||||
SeekSample(f64),
|
||||
SeekPulse(f64),
|
||||
SetBpm(f64),
|
||||
SetQuant(f64),
|
||||
SetSync(f64),
|
||||
}
|
||||
|
||||
impl<T: TransportModelApi> Command<T> for TransportCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
use TransportCommand::*;
|
||||
match self {
|
||||
Play(start) => {todo!()},
|
||||
Pause(start) => {todo!()},
|
||||
SeekUsec(usec) => {state.clock().current.update_from_usec(usec);},
|
||||
SeekSample(sample) => {state.clock().current.update_from_sample(sample);},
|
||||
SeekPulse(pulse) => {state.clock().current.update_from_pulse(pulse);},
|
||||
SetBpm(bpm) => {return Ok(Some(Self::SetBpm(state.clock().timebase().bpm.set(bpm))))},
|
||||
SetQuant(quant) => {return Ok(Some(Self::SetQuant(state.clock().quant.set(quant))))},
|
||||
SetSync(sync) => {return Ok(Some(Self::SetSync(state.clock().sync.set(sync))))},
|
||||
_ => { unreachable!() }
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
@ -35,21 +35,21 @@ submod! {
|
|||
pub struct AppView<E, A, C, S>
|
||||
where
|
||||
E: Engine,
|
||||
A: Widget<Engine = E> + Handle<E> + Audio,
|
||||
C: Command<A>,
|
||||
A: Widget<Engine = E> + Audio,
|
||||
C: Command<Self>,
|
||||
S: StatusBar,
|
||||
{
|
||||
pub app: A,
|
||||
pub cursor: (usize, usize),
|
||||
pub entered: bool,
|
||||
pub menu_bar: Option<MenuBar<E, A, C>>,
|
||||
pub menu_bar: Option<MenuBar<E, Self, C>>,
|
||||
pub status_bar: Option<S>,
|
||||
pub history: Vec<C>,
|
||||
pub size: Measure<E>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum AppViewCommand<T: Debug + Copy + Clone> {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AppViewCommand<T: Debug + Clone> {
|
||||
Focus(FocusCommand),
|
||||
Undo,
|
||||
Redo,
|
||||
|
|
@ -65,13 +65,13 @@ pub enum AppViewFocus<F: Debug + Copy + Clone + PartialEq> {
|
|||
impl<E, A, C, S> AppView<E, A, C, S>
|
||||
where
|
||||
E: Engine,
|
||||
A: Widget<Engine = E> + Handle<E> + Audio,
|
||||
C: Command<A>,
|
||||
A: Widget<Engine = E> + Audio,
|
||||
C: Command<Self>,
|
||||
S: StatusBar
|
||||
{
|
||||
pub fn new (
|
||||
app: A,
|
||||
menu_bar: Option<MenuBar<E, A, C>>,
|
||||
menu_bar: Option<MenuBar<E, Self, C>>,
|
||||
status_bar: Option<S>,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
|
@ -88,8 +88,8 @@ where
|
|||
|
||||
impl<A, C, S> Content for AppView<Tui, A, C, S>
|
||||
where
|
||||
A: Widget<Engine = Tui> + Handle<Tui> + Audio,
|
||||
C: Command<A>,
|
||||
A: Widget<Engine = Tui> + Audio,
|
||||
C: Command<Self>,
|
||||
S: StatusBar,
|
||||
{
|
||||
type Engine = Tui;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,246 @@
|
|||
use crate::*;
|
||||
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerApp<Tui> {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
Ok(Self::new(ArrangerModel {
|
||||
name: Arc::new(RwLock::new(String::new())),
|
||||
phrases: vec![],
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
transport: TransportModel {
|
||||
metronome: false,
|
||||
transport: jack.read().unwrap().transport(),
|
||||
clock: Arc::new(Clock::from(Instant::default())),
|
||||
jack: jack.clone(),
|
||||
},
|
||||
}.into(), None, None))
|
||||
}
|
||||
}
|
||||
|
||||
pub type ArrangerApp<E: Engine> = AppView<
|
||||
E,
|
||||
ArrangerView<E>,
|
||||
ArrangerViewCommand,
|
||||
ArrangerAppCommand,
|
||||
ArrangerStatusBar
|
||||
>;
|
||||
|
||||
/// Root level object for standalone `tek_arranger`
|
||||
/// Handle top-level events in standalone arranger.
|
||||
impl Handle<Tui> for ArrangerApp<Tui> {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
ArrangerAppCommand::execute_with_state(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
pub type ArrangerAppCommand = AppViewCommand<ArrangerViewCommand>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ArrangerViewCommand {
|
||||
Edit(ArrangerCommand),
|
||||
Select(ArrangerSelection),
|
||||
Zoom(usize),
|
||||
Transport(TransportCommand),
|
||||
Phrases(PhrasePoolViewCommand),
|
||||
Editor(PhraseEditorCommand),
|
||||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, ArrangerApp<Tui>> for ArrangerAppCommand {
|
||||
fn input_to_command (view: &ArrangerApp<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
use AppViewFocus::*;
|
||||
use FocusCommand::*;
|
||||
use ArrangerViewCommand::*;
|
||||
Some(match input.event() {
|
||||
key!(KeyCode::Tab) => Self::Focus(Next),
|
||||
key!(Shift-KeyCode::Tab) => Self::Focus(Prev),
|
||||
key!(KeyCode::BackTab) => Self::Focus(Prev),
|
||||
key!(Shift-KeyCode::BackTab) => Self::Focus(Prev),
|
||||
key!(KeyCode::Up) => Self::Focus(Up),
|
||||
key!(KeyCode::Down) => Self::Focus(Down),
|
||||
key!(KeyCode::Left) => Self::Focus(Left),
|
||||
key!(KeyCode::Right) => Self::Focus(Right),
|
||||
key!(KeyCode::Enter) => Self::Focus(Enter),
|
||||
key!(KeyCode::Esc) => Self::Focus(Exit),
|
||||
key!(KeyCode::Char(' ')) => {
|
||||
Self::App(Transport(TransportViewCommand::Transport(TransportCommand::Play(None))))
|
||||
},
|
||||
_ => match view.focused() {
|
||||
Content(ArrangerViewFocus::Transport) => Transport(
|
||||
TransportViewCommand::input_to_command(&view.sequencer.transport, input)?
|
||||
),
|
||||
Content(ArrangerViewFocus::PhraseEditor) => Editor(
|
||||
PhraseEditorCommand::input_to_command(&view.sequencer.editor, input)?
|
||||
),
|
||||
Content(ArrangerViewFocus::PhrasePool) => match input.event() {
|
||||
key!(KeyCode::Char('e')) => EditPhrase(
|
||||
Some(view.sequencer.phrases.phrase().clone())
|
||||
),
|
||||
_ => Phrases(
|
||||
PhrasePoolViewCommand::input_to_command(&view.sequencer.phrases, input)?
|
||||
)
|
||||
},
|
||||
Content(ArrangerViewFocus::Arranger) => {
|
||||
use ArrangerSelection as Focus;
|
||||
use ArrangerCommand as Model;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
match input.event() {
|
||||
key!(KeyCode::Char('e')) => EditPhrase(
|
||||
view.selected_phrase()
|
||||
),
|
||||
_ => match input.event() {
|
||||
// FIXME: boundary conditions
|
||||
|
||||
key!(KeyCode::Up) => match view.selected {
|
||||
ArrangerSelection::Mix => return None,
|
||||
ArrangerSelection::Track(t) => return None,
|
||||
ArrangerSelection::Scene(s) => Select(Focus::Scene(s - 1)),
|
||||
ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s - 1)),
|
||||
},
|
||||
|
||||
key!(KeyCode::Down) => match view.selected {
|
||||
ArrangerSelection::Mix => Select(Focus::Scene(0)),
|
||||
ArrangerSelection::Track(t) => Select(Focus::Clip(t, 0)),
|
||||
ArrangerSelection::Scene(s) => Select(Focus::Scene(s + 1)),
|
||||
ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s + 1)),
|
||||
},
|
||||
|
||||
key!(KeyCode::Left) => match view.selected {
|
||||
ArrangerSelection::Mix => return None,
|
||||
ArrangerSelection::Track(t) => Select(Focus::Track(t - 1)),
|
||||
ArrangerSelection::Scene(s) => return None,
|
||||
ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t - 1, s)),
|
||||
},
|
||||
|
||||
key!(KeyCode::Right) => match view.selected {
|
||||
ArrangerSelection::Mix => return None,
|
||||
ArrangerSelection::Track(t) => Select(Focus::Track(t + 1)),
|
||||
ArrangerSelection::Scene(s) => Select(Focus::Clip(0, s)),
|
||||
ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s - 1)),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('+')) => Zoom(0),
|
||||
|
||||
key!(KeyCode::Char('=')) => Zoom(0),
|
||||
|
||||
key!(KeyCode::Char('_')) => Zoom(0),
|
||||
|
||||
key!(KeyCode::Char('-')) => Zoom(0),
|
||||
|
||||
key!(KeyCode::Char('`')) => { todo!("toggle view mode") },
|
||||
|
||||
key!(KeyCode::Char(',')) => match view.selected {
|
||||
ArrangerSelection::Mix => Zoom(0),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('.')) => match view.selected {
|
||||
ArrangerSelection::Mix => Zoom(0),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('<')) => match view.selected {
|
||||
ArrangerSelection::Mix => Zoom(0),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('>')) => match view.selected {
|
||||
ArrangerSelection::Mix => Zoom(0),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Enter) => match view.selected {
|
||||
ArrangerSelection::Mix => return None,
|
||||
ArrangerSelection::Track(t) => return None,
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Play(s))),
|
||||
ArrangerSelection::Clip(t, s) => return None,
|
||||
},
|
||||
|
||||
key!(KeyCode::Delete) => match view.selected {
|
||||
ArrangerSelection::Mix => Edit(Model::Clear),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Delete(t))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Delete(s))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('c')) => Edit(Model::Clip(Clip::RandomColor)),
|
||||
|
||||
key!(KeyCode::Char('s')) => match view.selected {
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
_ => return None,
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('g')) => match view.selected {
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Get(t, s))),
|
||||
_ => return None,
|
||||
},
|
||||
|
||||
key!(Ctrl-KeyCode::Char('a')) => Edit(Model::Scene(Scene::Add)),
|
||||
|
||||
key!(Ctrl-KeyCode::Char('t')) => Edit(Model::Track(Track::Add)),
|
||||
|
||||
key!(KeyCode::Char('l')) => Edit(Model::Clip(Clip::SetLoop(false))),
|
||||
|
||||
_ => return None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<ArrangerApp<Tui>> for ArrangerAppCommand {
|
||||
fn execute (self, state: &mut ArrangerApp<Tui>) -> Perhaps<Self> {
|
||||
let undo = match self {
|
||||
Self::Focus(cmd) => {
|
||||
delegate(cmd, Self::Focus, state)
|
||||
},
|
||||
Self::App(cmd) => match cmd {
|
||||
ArrangerViewCommand::Phrases(cmd) => {
|
||||
delegate(cmd, |x|Self::App(ArrangerViewCommand::Phrases(x)), &mut state.app)
|
||||
},
|
||||
ArrangerViewCommand::Editor(cmd) => {
|
||||
delegate(cmd, |x|Self::App(ArrangerViewCommand::Editor(x)), &mut state.app)
|
||||
},
|
||||
ArrangerViewCommand::Transport(cmd) => {
|
||||
delegate(cmd, |x|Self::App(ArrangerViewCommand::Transport(x)), &mut state.app)
|
||||
},
|
||||
ArrangerViewCommand::Zoom(zoom) => {
|
||||
todo!();
|
||||
},
|
||||
ArrangerViewCommand::Select(selected) => {
|
||||
state.selected = selected;
|
||||
Ok(None)
|
||||
},
|
||||
ArrangerViewCommand::Edit(command) => {
|
||||
return Ok(command.execute(&mut state.model)?.map(ArrangerViewCommand::Edit))
|
||||
},
|
||||
ArrangerViewCommand::EditPhrase(phrase) => {
|
||||
app.sequencer.editor.phrase = phrase.clone();
|
||||
state.focus(ArrangerViewFocus::PhraseEditor);
|
||||
state.focus_enter();
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
_ => {todo!()}
|
||||
}?;
|
||||
state.show_phrase();
|
||||
state.update_status();
|
||||
return Ok(undo);
|
||||
}
|
||||
}
|
||||
|
||||
/// Root view for standalone `tek_arranger`
|
||||
pub struct ArrangerView<E: Engine> {
|
||||
pub model: ArrangerModel,
|
||||
/// Sequencer component
|
||||
|
|
@ -37,18 +270,6 @@ pub enum ArrangerMode {
|
|||
Vertical(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ArrangerViewCommand {
|
||||
Focus(FocusCommand),
|
||||
Edit(ArrangerCommand),
|
||||
Select(ArrangerSelection),
|
||||
Zoom(usize),
|
||||
Transport(TransportViewCommand),
|
||||
Phrases(PhrasePoolViewCommand),
|
||||
Editor(PhraseEditorCommand),
|
||||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||
}
|
||||
|
||||
/// Sections in the arranger app that may be focused
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ArrangerViewFocus {
|
||||
|
|
@ -62,7 +283,7 @@ pub enum ArrangerViewFocus {
|
|||
PhraseEditor,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
/// Represents the current user selection in the arranger
|
||||
pub enum ArrangerSelection {
|
||||
/// The whole mix is selected
|
||||
|
|
@ -76,7 +297,7 @@ pub enum ArrangerSelection {
|
|||
}
|
||||
|
||||
/// Status bar for arranger app
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ArrangerStatusBar {
|
||||
Transport,
|
||||
ArrangerMix,
|
||||
|
|
@ -88,24 +309,6 @@ pub enum ArrangerStatusBar {
|
|||
PhraseEdit,
|
||||
}
|
||||
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for ArrangerApp<Tui> {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
Ok(Self::new(ArrangerModel {
|
||||
name: Arc::new(RwLock::new(String::new())),
|
||||
phrases: vec![],
|
||||
scenes: vec![],
|
||||
tracks: vec![],
|
||||
transport: TransportModel {
|
||||
metronome: false,
|
||||
transport: jack.read().unwrap().transport(),
|
||||
clock: Arc::new(Clock::from(Instant::default())),
|
||||
jack: jack.clone(),
|
||||
},
|
||||
}.into(), None, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> From<ArrangerModel> for ArrangerView<E> {
|
||||
fn from (model: ArrangerModel) -> Self {
|
||||
let mut view = Self {
|
||||
|
|
@ -262,7 +465,7 @@ impl<E: Engine> ArrangerView<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Audio for ArrangerView<Tui> {
|
||||
impl<E: Engine> Audio for ArrangerView<E> {
|
||||
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
if self.model.process(client, scope) == Control::Quit {
|
||||
return Control::Quit
|
||||
|
|
@ -290,200 +493,6 @@ impl Audio for ArrangerView<Tui> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle top-level events in standalone arranger.
|
||||
impl Handle<Tui> for ArrangerView<Tui> {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
ArrangerViewCommand::execute_with_state(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, ArrangerView<Tui>> for ArrangerViewCommand {
|
||||
fn input_to_command (view: &ArrangerView<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
use FocusCommand::*;
|
||||
use ArrangerViewCommand::*;
|
||||
Some(match input.event() {
|
||||
key!(KeyCode::Tab) => Focus(Next),
|
||||
key!(Shift-KeyCode::Tab) => Focus(Prev),
|
||||
key!(KeyCode::BackTab) => Focus(Prev),
|
||||
key!(Shift-KeyCode::BackTab) => Focus(Prev),
|
||||
key!(KeyCode::Up) => Focus(Up),
|
||||
key!(KeyCode::Down) => Focus(Down),
|
||||
key!(KeyCode::Left) => Focus(Left),
|
||||
key!(KeyCode::Right) => Focus(Right),
|
||||
key!(KeyCode::Enter) => Focus(Enter),
|
||||
key!(KeyCode::Esc) => Focus(Exit),
|
||||
key!(KeyCode::Char(' ')) => {
|
||||
Transport(TransportViewCommand::Transport(TransportCommand::Play(None)))
|
||||
},
|
||||
_ => match view.focused() {
|
||||
ArrangerViewFocus::Transport => Transport(
|
||||
TransportViewCommand::input_to_command(&view.sequencer.transport, input)?
|
||||
),
|
||||
ArrangerViewFocus::PhraseEditor => Editor(
|
||||
PhraseEditorCommand::input_to_command(&view.sequencer.editor, input)?
|
||||
),
|
||||
ArrangerViewFocus::PhrasePool => match input.event() {
|
||||
key!(KeyCode::Char('e')) => EditPhrase(
|
||||
Some(view.sequencer.phrases.phrase().clone())
|
||||
),
|
||||
_ => Phrases(
|
||||
PhrasePoolViewCommand::input_to_command(&view.sequencer.phrases, input)?
|
||||
)
|
||||
},
|
||||
ArrangerViewFocus::Arranger => {
|
||||
use ArrangerSelection as Focus;
|
||||
use ArrangerCommand as Model;
|
||||
use ArrangerTrackCommand as Track;
|
||||
use ArrangerClipCommand as Clip;
|
||||
use ArrangerSceneCommand as Scene;
|
||||
match input.event() {
|
||||
key!(KeyCode::Char('e')) => EditPhrase(
|
||||
view.selected_phrase()
|
||||
),
|
||||
_ => match input.event() {
|
||||
// FIXME: boundary conditions
|
||||
|
||||
key!(KeyCode::Up) => match view.selected {
|
||||
ArrangerSelection::Mix => return None,
|
||||
ArrangerSelection::Track(t) => return None,
|
||||
ArrangerSelection::Scene(s) => Select(Focus::Scene(s - 1)),
|
||||
ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s - 1)),
|
||||
},
|
||||
|
||||
key!(KeyCode::Down) => match view.selected {
|
||||
ArrangerSelection::Mix => Select(Focus::Scene(0)),
|
||||
ArrangerSelection::Track(t) => Select(Focus::Clip(t, 0)),
|
||||
ArrangerSelection::Scene(s) => Select(Focus::Scene(s + 1)),
|
||||
ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s + 1)),
|
||||
},
|
||||
|
||||
key!(KeyCode::Left) => match view.selected {
|
||||
ArrangerSelection::Mix => return None,
|
||||
ArrangerSelection::Track(t) => Select(Focus::Track(t - 1)),
|
||||
ArrangerSelection::Scene(s) => return None,
|
||||
ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t - 1, s)),
|
||||
},
|
||||
|
||||
key!(KeyCode::Right) => match view.selected {
|
||||
ArrangerSelection::Mix => return None,
|
||||
ArrangerSelection::Track(t) => Select(Focus::Track(t + 1)),
|
||||
ArrangerSelection::Scene(s) => Select(Focus::Clip(0, s)),
|
||||
ArrangerSelection::Clip(t, s) => Select(Focus::Clip(t, s - 1)),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('+')) => Zoom(0),
|
||||
|
||||
key!(KeyCode::Char('=')) => Zoom(0),
|
||||
|
||||
key!(KeyCode::Char('_')) => Zoom(0),
|
||||
|
||||
key!(KeyCode::Char('-')) => Zoom(0),
|
||||
|
||||
key!(KeyCode::Char('`')) => { todo!("toggle view mode") },
|
||||
|
||||
key!(KeyCode::Char(',')) => match view.selected {
|
||||
ArrangerSelection::Mix => Zoom(0),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('.')) => match view.selected {
|
||||
ArrangerSelection::Mix => Zoom(0),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('<')) => match view.selected {
|
||||
ArrangerSelection::Mix => Zoom(0),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('>')) => match view.selected {
|
||||
ArrangerSelection::Mix => Zoom(0),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Enter) => match view.selected {
|
||||
ArrangerSelection::Mix => return None,
|
||||
ArrangerSelection::Track(t) => return None,
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Play(s))),
|
||||
ArrangerSelection::Clip(t, s) => return None,
|
||||
},
|
||||
|
||||
key!(KeyCode::Delete) => match view.selected {
|
||||
ArrangerSelection::Mix => Edit(Model::Clear),
|
||||
ArrangerSelection::Track(t) => Edit(Model::Track(Track::Delete(t))),
|
||||
ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Delete(s))),
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('c')) => Edit(Model::Clip(Clip::RandomColor)),
|
||||
|
||||
key!(KeyCode::Char('s')) => match view.selected {
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))),
|
||||
_ => return None,
|
||||
},
|
||||
|
||||
key!(KeyCode::Char('g')) => match view.selected {
|
||||
ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Get(t, s))),
|
||||
_ => return None,
|
||||
},
|
||||
|
||||
key!(Ctrl-KeyCode::Char('a')) => Edit(Model::Scene(Scene::Add)),
|
||||
|
||||
key!(Ctrl-KeyCode::Char('t')) => Edit(Model::Track(Track::Add)),
|
||||
|
||||
key!(KeyCode::Char('l')) => Edit(Model::Clip(Clip::SetLoop(false))),
|
||||
|
||||
_ => return None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Command<ArrangerView<E>> for ArrangerViewCommand {
|
||||
fn execute (self, view: &mut ArrangerView<E>) -> Perhaps<Self> {
|
||||
let undo = match self {
|
||||
Self::Focus(cmd) =>
|
||||
delegate(cmd, Self::Focus, view),
|
||||
Self::Phrases(cmd) =>
|
||||
delegate(cmd, Self::Phrases, &mut view.sequencer.phrases),
|
||||
Self::Editor(cmd) =>
|
||||
delegate(cmd, Self::Editor, &mut view.sequencer.editor),
|
||||
Self::Transport(cmd) =>
|
||||
delegate(cmd, Self::Transport, &mut view.sequencer.transport),
|
||||
Self::Zoom(zoom) => {
|
||||
todo!();
|
||||
},
|
||||
Self::Select(selected) => {
|
||||
view.selected = selected;
|
||||
Ok(None)
|
||||
},
|
||||
Self::Edit(command) => {
|
||||
return Ok(command.execute(&mut view.model)?.map(Self::Edit))
|
||||
},
|
||||
Self::EditPhrase(phrase) => {
|
||||
view.sequencer.editor.phrase = phrase.clone();
|
||||
view.focus(ArrangerViewFocus::PhraseEditor);
|
||||
view.focus_enter();
|
||||
Ok(None)
|
||||
}
|
||||
}?;
|
||||
view.show_phrase();
|
||||
view.update_status();
|
||||
return Ok(undo);
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn phrase_next (&mut self) {
|
||||
//if let ArrangerSelection::Clip(track, scene) = self.selected {
|
||||
//if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] {
|
||||
|
|
@ -617,7 +626,7 @@ impl FocusGrid for ArrangerApp<Tui> {
|
|||
self.app.sequencer.transport.focused = focused == Content(Transport);
|
||||
self.app.sequencer.phrases.focused = focused == Content(PhrasePool);
|
||||
self.app.sequencer.editor.focused = focused == Content(PhraseEditor);
|
||||
if let Some(status_bar) = self.status_bar {
|
||||
if let Some(mut status_bar) = self.status_bar {
|
||||
status_bar.update(&(
|
||||
self.focused(),
|
||||
self.app.selected,
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ impl Content for PhrasePoolView<Tui> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum PhrasePoolViewCommand {
|
||||
Select(usize),
|
||||
Edit(PhrasePoolCommand),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum PhraseLengthCommand {
|
||||
Begin,
|
||||
Next,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PhraseRenameCommand {
|
||||
Begin,
|
||||
Set(String),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,45 @@ pub type SequencerApp<E: Engine> = AppView<
|
|||
SequencerStatusBar,
|
||||
>;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum SequencerViewCommand {
|
||||
Transport(TransportCommand),
|
||||
Phrases(PhrasePoolViewCommand),
|
||||
Editor(PhraseEditorCommand),
|
||||
}
|
||||
|
||||
/// Root view for standalone `tek_sequencer`.
|
||||
pub struct SequencerView<E: Engine> {
|
||||
pub model: SequencerModel,
|
||||
/// Displays the JACK transport.
|
||||
pub transport: TransportView<E>,
|
||||
/// Displays the phrase pool
|
||||
pub phrases: PhrasePoolView<E>,
|
||||
/// Displays the phrase editor
|
||||
pub editor: PhraseEditor<E>,
|
||||
/// Width of phrase pool
|
||||
pub split: u16,
|
||||
}
|
||||
|
||||
/// Sections in the sequencer app that may be focused
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum SequencerFocus {
|
||||
/// The transport (toolbar) is focused
|
||||
Transport,
|
||||
/// The phrase list (pool) is focused
|
||||
PhrasePool,
|
||||
/// The phrase editor (sequencer) is focused
|
||||
PhraseEditor,
|
||||
}
|
||||
|
||||
/// Status bar for sequencer app
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SequencerStatusBar {
|
||||
Transport,
|
||||
PhrasePool,
|
||||
PhraseEditor,
|
||||
}
|
||||
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for SequencerApp<Tui> {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
|
|
@ -28,54 +67,14 @@ impl<E: Engine> From<SequencerModel> for SequencerView<E> {
|
|||
fn from (model: SequencerModel) -> Self {
|
||||
Self {
|
||||
split: 20,
|
||||
transport: TransportView::from(&model.transport),
|
||||
phrases: PhrasePoolView::from(&model.phrases),
|
||||
transport: TransportView::from(&model.transport()),
|
||||
phrases: PhrasePoolView::from(&model.phrases()),
|
||||
editor: PhraseEditor::new(),
|
||||
model,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Root level object for standalone `tek_sequencer`.
|
||||
pub struct SequencerView<E: Engine> {
|
||||
pub model: SequencerModel,
|
||||
/// Displays the JACK transport.
|
||||
pub transport: TransportView<E>,
|
||||
/// Displays the phrase pool
|
||||
pub phrases: PhrasePoolView<E>,
|
||||
/// Displays the phrase editor
|
||||
pub editor: PhraseEditor<E>,
|
||||
/// Width of phrase pool
|
||||
pub split: u16,
|
||||
}
|
||||
|
||||
/// Status bar for sequencer app
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SequencerStatusBar {
|
||||
Transport,
|
||||
PhrasePool,
|
||||
PhraseEditor,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum SequencerViewCommand {
|
||||
Focus(FocusCommand),
|
||||
Transport(TransportViewCommand),
|
||||
Phrases(PhrasePoolViewCommand),
|
||||
Editor(PhraseEditorCommand),
|
||||
}
|
||||
|
||||
/// Sections in the sequencer app that may be focused
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum SequencerFocus {
|
||||
/// The transport (toolbar) is focused
|
||||
Transport,
|
||||
/// The phrase list (pool) is focused
|
||||
PhrasePool,
|
||||
/// The phrase editor (sequencer) is focused
|
||||
PhraseEditor,
|
||||
}
|
||||
|
||||
impl Content for SequencerView<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
|
|
@ -113,14 +112,14 @@ impl Content for SequencerStatusBar {
|
|||
}
|
||||
}
|
||||
|
||||
impl Handle<Tui> for SequencerView<Tui> {
|
||||
impl Handle<Tui> for SequencerApp<Tui> {
|
||||
fn handle (&mut self, i: &TuiInput) -> Perhaps<bool> {
|
||||
SequencerViewCommand::execute_with_state(self, i)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Command<SequencerView<E>> for SequencerViewCommand {
|
||||
fn execute (self, state: &mut SequencerView<E>) -> Perhaps<Self> {
|
||||
impl<E: Engine> Command<SequencerApp<E>> for SequencerViewCommand {
|
||||
fn execute (self, state: &mut SequencerApp<E>) -> Perhaps<Self> {
|
||||
match self {
|
||||
Self::Focus(cmd) => delegate(cmd, Self::Focus, state),
|
||||
Self::Phrases(cmd) => delegate(cmd, Self::Phrases, &mut state.phrases),
|
||||
|
|
@ -130,8 +129,9 @@ impl<E: Engine> Command<SequencerView<E>> for SequencerViewCommand {
|
|||
}
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, SequencerView<Tui>> for SequencerViewCommand {
|
||||
fn input_to_command (state: &SequencerView<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
impl InputToCommand<Tui, SequencerApp<Tui>> for SequencerViewCommand {
|
||||
fn input_to_command (state: &SequencerApp<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
use AppViewFocus::*;
|
||||
use FocusCommand::*;
|
||||
match input.event() {
|
||||
key!(KeyCode::Tab) => Some(Self::Focus(Next)),
|
||||
|
|
@ -143,16 +143,16 @@ impl InputToCommand<Tui, SequencerView<Tui>> for SequencerViewCommand {
|
|||
key!(KeyCode::Left) => Some(Self::Focus(Left)),
|
||||
key!(KeyCode::Right) => Some(Self::Focus(Right)),
|
||||
_ => match state.focused() {
|
||||
SequencerFocus::Transport => TransportViewCommand::input_to_command(
|
||||
&state.transport, input
|
||||
).map(Self::Transport),
|
||||
SequencerFocus::PhrasePool => PhrasePoolViewCommand::input_to_command(
|
||||
&state.phrases, input
|
||||
).map(Self::Phrases),
|
||||
SequencerFocus::PhraseEditor => PhraseEditorCommand::input_to_command(
|
||||
&state.editor, input
|
||||
).map(Self::Editor),
|
||||
|
||||
Content(SequencerFocus::Transport) =>
|
||||
TransportViewCommand::input_to_command(&state.transport, input)
|
||||
.map(Self::Transport),
|
||||
Content(SequencerFocus::PhrasePool) =>
|
||||
PhrasePoolViewCommand::input_to_command(&state.phrases, input)
|
||||
.map(Self::Phrases),
|
||||
Content(SequencerFocus::PhraseEditor) =>
|
||||
PhraseEditorCommand::input_to_command(&state.editor, input)
|
||||
.map(Self::Editor),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
pub type TransportApp<E: Engine> = AppView<
|
||||
E,
|
||||
TransportView<E>,
|
||||
TransportViewCommand,
|
||||
TransportStatusBar
|
||||
>;
|
||||
|
||||
/// Create app state from JACK handle.
|
||||
impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from (jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
|
||||
|
|
@ -19,14 +13,98 @@ impl TryFrom<&Arc<RwLock<JackClient>>> for TransportApp<Tui> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Stores and displays time-related info.
|
||||
#[derive(Debug)]
|
||||
pub struct TransportView<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
pub model: TransportModel,
|
||||
pub focus: TransportViewFocus,
|
||||
pub focused: bool,
|
||||
pub size: Measure<E>,
|
||||
/// Root type of application.
|
||||
pub type TransportApp<E: Engine> = AppView<
|
||||
E,
|
||||
TransportView<E>,
|
||||
TransportAppCommand,
|
||||
TransportStatusBar
|
||||
>;
|
||||
|
||||
/// Handle input.
|
||||
impl Handle<Tui> for TransportApp<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
TransportAppCommand::execute_with_state(self, from)
|
||||
}
|
||||
}
|
||||
|
||||
pub type TransportAppCommand = AppViewCommand<TransportCommand>;
|
||||
|
||||
impl InputToCommand<Tui, TransportApp<Tui>> for TransportAppCommand {
|
||||
fn input_to_command (app: &TransportApp<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
use TransportViewFocus as Focus;
|
||||
use FocusCommand as FocusCmd;
|
||||
use TransportCommand as Cmd;
|
||||
let clock = app.app.model.clock();
|
||||
Some(match input.event() {
|
||||
|
||||
key!(KeyCode::Left) => Self::Focus(FocusCmd::Prev),
|
||||
key!(KeyCode::Right) => Self::Focus(FocusCmd::Next),
|
||||
|
||||
key!(KeyCode::Char('.')) => Self::App(match app.focused() {
|
||||
AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() + 1.0),
|
||||
AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64),
|
||||
AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.),
|
||||
AppViewFocus::Content(Focus::PlayPause) => {todo!()},
|
||||
AppViewFocus::Content(Focus::Clock) => {todo!()},
|
||||
_ => {todo!()}
|
||||
}),
|
||||
key!(KeyCode::Char(',')) => Self::App(match app.focused() {
|
||||
AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() - 1.0),
|
||||
AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64),
|
||||
AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.),
|
||||
AppViewFocus::Content(Focus::PlayPause) => {todo!()},
|
||||
AppViewFocus::Content(Focus::Clock) => {todo!()}
|
||||
_ => {todo!()}
|
||||
}),
|
||||
key!(KeyCode::Char('>')) => Self::App(match app.focused() {
|
||||
AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() + 0.001),
|
||||
AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64),
|
||||
AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.),
|
||||
AppViewFocus::Content(Focus::PlayPause) => {todo!()},
|
||||
AppViewFocus::Content(Focus::Clock) => {todo!()}
|
||||
_ => {todo!()}
|
||||
}),
|
||||
key!(KeyCode::Char('<')) => Self::App(match app.focused() {
|
||||
AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() - 0.001),
|
||||
AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64),
|
||||
AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.),
|
||||
AppViewFocus::Content(Focus::PlayPause) => {todo!()},
|
||||
AppViewFocus::Content(Focus::Clock) => {todo!()}
|
||||
_ => {todo!()}
|
||||
}),
|
||||
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Command<TransportApp<Tui>> for TransportAppCommand {
|
||||
fn execute (self, state: &mut TransportApp<Tui>) -> Perhaps<Self> {
|
||||
let clock = state.app.model.clock();
|
||||
Ok(Some(match self {
|
||||
Self::Focus(command) => Self::Focus({
|
||||
use FocusCommand::*;
|
||||
match command {
|
||||
Next => { todo!() },
|
||||
Prev => { todo!() },
|
||||
_ => { todo!() }
|
||||
}
|
||||
}),
|
||||
Self::App(command) => Self::App({
|
||||
use TransportCommand::*;
|
||||
match command {
|
||||
SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)),
|
||||
SetQuant(quant) => SetQuant(clock.quant.set(quant)),
|
||||
SetSync(sync) => SetSync(clock.sync.set(sync)),
|
||||
_ => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}),
|
||||
_ => todo!()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> From<TransportModel> for TransportView<E> {
|
||||
|
|
@ -41,6 +119,58 @@ impl<E: Engine> From<TransportModel> for TransportView<E> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Stores and displays time-related info.
|
||||
#[derive(Debug)]
|
||||
pub struct TransportView<E: Engine> {
|
||||
_engine: PhantomData<E>,
|
||||
pub model: TransportModel,
|
||||
pub focus: TransportViewFocus,
|
||||
pub focused: bool,
|
||||
pub size: Measure<E>,
|
||||
}
|
||||
|
||||
/// Which item of the transport toolbar is focused
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TransportViewFocus {
|
||||
Bpm,
|
||||
Sync,
|
||||
PlayPause,
|
||||
Clock,
|
||||
Quant,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TransportStatusBar;
|
||||
|
||||
impl Content for TransportView<Tui> {
|
||||
type Engine = Tui;
|
||||
fn content (&self) -> impl Widget<Engine = Tui> {
|
||||
|
|
@ -84,9 +214,6 @@ impl Audio for TransportView<Tui> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct TransportStatusBar;
|
||||
|
||||
impl StatusBar for TransportStatusBar {
|
||||
type State = ();
|
||||
fn hotkey_fg () -> Color {
|
||||
|
|
@ -105,129 +232,6 @@ impl Content for TransportStatusBar {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum TransportViewCommand {
|
||||
Focus(FocusCommand),
|
||||
Transport(TransportCommand),
|
||||
}
|
||||
|
||||
impl Handle<Tui> for TransportView<Tui> {
|
||||
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
|
||||
TransportViewCommand::execute_with_state(self, from)
|
||||
}
|
||||
}
|
||||
|
||||
impl InputToCommand<Tui, TransportView<Tui>> for TransportViewCommand {
|
||||
fn input_to_command (view: &TransportView<Tui>, input: &TuiInput) -> Option<Self> {
|
||||
use TransportViewFocus as Focus;
|
||||
use FocusCommand as FocusCmd;
|
||||
use TransportCommand as Cmd;
|
||||
let clock = view.model.clock();
|
||||
Some(match input.event() {
|
||||
|
||||
key!(KeyCode::Left) => Self::Focus(FocusCmd::Prev),
|
||||
key!(KeyCode::Right) => Self::Focus(FocusCmd::Next),
|
||||
|
||||
key!(KeyCode::Char('.')) => Self::Transport(match view.focus {
|
||||
Focus::Bpm => Cmd::SetBpm(clock.timebase().bpm.get() + 1.0),
|
||||
Focus::Quant => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64),
|
||||
Focus::Sync => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.),
|
||||
Focus::PlayPause => {todo!()},
|
||||
Focus::Clock => {todo!()}
|
||||
}),
|
||||
key!(KeyCode::Char(',')) => Self::Transport(match view.focus {
|
||||
Focus::Bpm => Cmd::SetBpm(clock.timebase().bpm.get() - 1.0),
|
||||
Focus::Quant => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64),
|
||||
Focus::Sync => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.),
|
||||
Focus::PlayPause => {todo!()},
|
||||
Focus::Clock => {todo!()}
|
||||
}),
|
||||
key!(KeyCode::Char('>')) => Self::Transport(match view.focus {
|
||||
Focus::Bpm => Cmd::SetBpm(clock.timebase().bpm.get() + 0.001),
|
||||
Focus::Quant => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64),
|
||||
Focus::Sync => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.),
|
||||
Focus::PlayPause => {todo!()},
|
||||
Focus::Clock => {todo!()}
|
||||
}),
|
||||
key!(KeyCode::Char('<')) => Self::Transport(match view.focus {
|
||||
Focus::Bpm => Cmd::SetBpm(clock.timebase().bpm.get() - 0.001),
|
||||
Focus::Quant => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64),
|
||||
Focus::Sync => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.),
|
||||
Focus::PlayPause => {todo!()},
|
||||
Focus::Clock => {todo!()}
|
||||
}),
|
||||
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Command<TransportView<E>> for TransportViewCommand {
|
||||
fn execute (self, view: &mut TransportView<E>) -> Perhaps<Self> {
|
||||
let clock = view.model.clock();
|
||||
Ok(Some(match self {
|
||||
Self::Focus(command) => Self::Focus({
|
||||
use FocusCommand::*;
|
||||
match command {
|
||||
Next => { todo!() },
|
||||
Prev => { todo!() },
|
||||
_ => { todo!() }
|
||||
}
|
||||
}),
|
||||
Self::Transport(command) => Self::Transport({
|
||||
use TransportCommand::*;
|
||||
match command {
|
||||
SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)),
|
||||
SetQuant(quant) => SetQuant(clock.quant.set(quant)),
|
||||
SetSync(sync) => SetSync(clock.sync.set(sync)),
|
||||
_ => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
impl FocusGrid for TransportApp<Tui> {
|
||||
type Item = AppViewFocus<TransportViewFocus>;
|
||||
fn cursor (&self) -> (usize, usize) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue