mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
reenable global play/pause
This commit is contained in:
parent
86f37fb278
commit
340919830a
12 changed files with 208 additions and 165 deletions
|
|
@ -1,6 +1,6 @@
|
|||
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::jack::*;
|
||||
pub(crate) use tek_core::{*, jack::*};
|
||||
pub(crate) use tek_api::*;
|
||||
|
||||
pub(crate) use std::collections::BTreeMap;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ impl<T: TransportControl> Command<T> for TransportCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SequencerCommand {
|
||||
Focus(FocusCommand),
|
||||
Undo,
|
||||
|
|
@ -62,29 +62,24 @@ pub enum ArrangerCommand {
|
|||
Zoom(usize),
|
||||
Phrases(PhrasesCommand),
|
||||
Editor(PhraseCommand),
|
||||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||
}
|
||||
|
||||
impl Command<ArrangerTui> for ArrangerCommand {
|
||||
fn execute (self, state: &mut ArrangerTui) -> Perhaps<Self> {
|
||||
use ArrangerCommand::*;
|
||||
Ok(match self {
|
||||
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||
Scene(cmd) => cmd.execute(state)?.map(Scene),
|
||||
Track(cmd) => cmd.execute(state)?.map(Track),
|
||||
Clip(cmd) => cmd.execute(state)?.map(Clip),
|
||||
Phrases(cmd) => cmd.execute(state)?.map(Phrases),
|
||||
Editor(cmd) => cmd.execute(state)?.map(Editor),
|
||||
Clock(cmd) => cmd.execute(state)?.map(Clock),
|
||||
Zoom(zoom) => { todo!(); },
|
||||
Focus(cmd) => cmd.execute(state)?.map(Focus),
|
||||
Scene(cmd) => cmd.execute(state)?.map(Scene),
|
||||
Track(cmd) => cmd.execute(state)?.map(Track),
|
||||
Clip(cmd) => cmd.execute(state)?.map(Clip),
|
||||
Phrases(cmd) => cmd.execute(state)?.map(Phrases),
|
||||
Editor(cmd) => cmd.execute(state)?.map(Editor),
|
||||
Clock(cmd) => cmd.execute(state)?.map(Clock),
|
||||
Zoom(zoom) => { todo!(); },
|
||||
Select(selected) => {
|
||||
*state.selected_mut() = selected;
|
||||
None
|
||||
},
|
||||
EditPhrase(phrase) => {
|
||||
state.edit_phrase(&phrase);
|
||||
None
|
||||
},
|
||||
_ => { todo!() }
|
||||
})
|
||||
}
|
||||
|
|
@ -309,7 +304,7 @@ impl<T: PhrasesControl> Command<T> for FileBrowserCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PhraseCommand {
|
||||
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||
ToggleDirection,
|
||||
|
|
@ -323,15 +318,26 @@ pub enum PhraseCommand {
|
|||
TimeCursorSet(Option<usize>),
|
||||
TimeScrollSet(usize),
|
||||
TimeZoomSet(usize),
|
||||
Show(Option<Arc<RwLock<Phrase>>>),
|
||||
}
|
||||
|
||||
impl<T: PhraseEditorControl + HasEnter> Command<T> for PhraseCommand {
|
||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
||||
use PhraseCommand::*;
|
||||
Ok(match self {
|
||||
Show(phrase) => {
|
||||
state.edit_phrase(phrase);
|
||||
None
|
||||
},
|
||||
ToggleDirection => { todo!() },
|
||||
EnterEditMode => { state.focus_enter(); None },
|
||||
ExitEditMode => { state.focus_exit(); None },
|
||||
EnterEditMode => {
|
||||
state.focus_enter();
|
||||
None
|
||||
},
|
||||
ExitEditMode => {
|
||||
state.focus_exit();
|
||||
None
|
||||
},
|
||||
NoteAppend => {
|
||||
if state.phrase_editor_entered() {
|
||||
state.put_note();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pub trait SequencerControl: TransportControl {}
|
|||
pub trait ArrangerControl: TransportControl {
|
||||
fn selected (&self) -> ArrangerSelection;
|
||||
fn selected_mut (&mut self) -> &mut ArrangerSelection;
|
||||
fn activate (&mut self);
|
||||
fn activate (&mut self) -> Usually<()>;
|
||||
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>>;
|
||||
fn toggle_loop (&mut self);
|
||||
fn randomize_color (&mut self);
|
||||
|
|
@ -54,7 +54,7 @@ impl ArrangerControl for ArrangerTui {
|
|||
fn selected_mut (&mut self) -> &mut ArrangerSelection {
|
||||
&mut self.selected
|
||||
}
|
||||
fn activate (&mut self) {
|
||||
fn activate (&mut self) -> Usually<()> {
|
||||
if let ArrangerSelection::Scene(s) = self.selected {
|
||||
for (t, track) in self.tracks.iter_mut().enumerate() {
|
||||
let phrase = self.scenes[s].clips[t].clone();
|
||||
|
|
@ -62,15 +62,14 @@ impl ArrangerControl for ArrangerTui {
|
|||
track.enqueue_next(phrase.as_ref());
|
||||
}
|
||||
}
|
||||
// TODO make transport available here, so that
|
||||
// activating a scene when stopped starts playback
|
||||
//if self.is_stopped() {
|
||||
//self.transport.toggle_play()
|
||||
//}
|
||||
if self.is_stopped() {
|
||||
self.play_from(Some(0))?;
|
||||
}
|
||||
} else if let ArrangerSelection::Clip(t, s) = self.selected {
|
||||
let phrase = self.scenes()[s].clips[t].clone();
|
||||
self.tracks_mut()[t].enqueue_next(phrase.as_ref());
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
fn selected_phrase (&self) -> Option<Arc<RwLock<Phrase>>> {
|
||||
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
|
||||
|
|
@ -114,8 +113,9 @@ pub trait PhrasesControl: HasPhrases {
|
|||
}
|
||||
|
||||
pub trait PhraseEditorControl: HasFocus {
|
||||
fn edit_phrase (&self, phrase: &Option<Arc<RwLock<Phrase>>>);
|
||||
fn editing_phrase (&self) -> &Option<Arc<RwLock<Phrase>>>;
|
||||
fn edit_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>);
|
||||
fn phrase_to_edit (&self) -> Option<Arc<RwLock<Phrase>>>;
|
||||
fn phrase_editing (&self) -> &Option<Arc<RwLock<Phrase>>>;
|
||||
fn phrase_editor_entered (&self) -> bool;
|
||||
fn time_axis (&self) -> &RwLock<ScaledAxis<usize>>;
|
||||
fn note_axis (&self) -> &RwLock<FixedAxis<usize>>;
|
||||
|
|
@ -124,7 +124,7 @@ pub trait PhraseEditorControl: HasFocus {
|
|||
fn put_note (&mut self);
|
||||
fn time_cursor_advance (&self) {
|
||||
let point = self.time_axis().read().unwrap().point;
|
||||
let length = self.editing_phrase().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||
let length = self.phrase_editing().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||
let forward = |time|(time + self.note_len()) % length;
|
||||
self.time_axis().write().unwrap().point = point.map(forward);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,17 +137,26 @@ macro_rules! impl_phrases_control {
|
|||
}
|
||||
|
||||
macro_rules! impl_phrase_editor_control {
|
||||
($Struct:ident $(:: $field:ident)* [$Focus:expr]) => {
|
||||
(
|
||||
$Struct:ident $(:: $field:ident)*
|
||||
[$Focus:expr]
|
||||
[$self1:ident: $phrase_to_edit:expr]
|
||||
[$self2:ident, $phrase:ident: $edit_phrase:expr]
|
||||
) => {
|
||||
impl PhraseEditorControl for $Struct {
|
||||
fn edit_phrase (&self, phrase: &Option<Arc<RwLock<Phrase>>>) {
|
||||
fn phrase_to_edit (&$self1) -> Option<Arc<RwLock<Phrase>>> {
|
||||
$phrase_to_edit
|
||||
}
|
||||
fn edit_phrase (&mut $self2, $phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||
$edit_phrase
|
||||
//self.editor.show(self.selected_phrase().as_ref());
|
||||
//state.editor.phrase = phrase.clone();
|
||||
//state.focus(ArrangerFocus::PhraseEditor);
|
||||
//state.focus_enter();
|
||||
todo!("edit_phrase")
|
||||
//todo!("edit_phrase")
|
||||
}
|
||||
fn editing_phrase (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
||||
todo!("editing_phrase")
|
||||
fn phrase_editing (&self) -> &Option<Arc<RwLock<Phrase>>> {
|
||||
todo!("phrase_editing")
|
||||
}
|
||||
fn phrase_editor_entered (&self) -> bool {
|
||||
self.entered && self.focused() == $Focus
|
||||
|
|
@ -192,9 +201,13 @@ impl_midi_player!(PhrasePlayerModel);
|
|||
impl_phrases_control!(SequencerTui);
|
||||
impl_phrases_control!(ArrangerTui);
|
||||
|
||||
impl_phrase_editor_control!(SequencerTui [
|
||||
AppFocus::Content(SequencerFocus::PhraseEditor)
|
||||
]);
|
||||
impl_phrase_editor_control!(ArrangerTui [
|
||||
AppFocus::Content(ArrangerFocus::PhraseEditor)
|
||||
]);
|
||||
impl_phrase_editor_control!(SequencerTui
|
||||
[AppFocus::Content(SequencerFocus::PhraseEditor)]
|
||||
[self: Some(self.phrases.phrases[self.phrases.phrase.load(Ordering::Relaxed)].clone())]
|
||||
[self, phrase: self.editor.show(phrase)]
|
||||
);
|
||||
impl_phrase_editor_control!(ArrangerTui
|
||||
[AppFocus::Content(ArrangerFocus::PhraseEditor)]
|
||||
[self: todo!()]
|
||||
[self, phrase: todo!()]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -59,17 +59,17 @@ where
|
|||
_ => return None,
|
||||
},
|
||||
TransportFocus::Quant => match input.event() {
|
||||
key!(Char(',')) => Clock(SetQuant(state.prev_quant())),
|
||||
key!(Char('.')) => Clock(SetQuant(state.next_quant())),
|
||||
key!(Char('<')) => Clock(SetQuant(state.prev_quant())),
|
||||
key!(Char('>')) => Clock(SetQuant(state.next_quant())),
|
||||
key!(Char(',')) => Clock(SetQuant(state.quant().prev())),
|
||||
key!(Char('.')) => Clock(SetQuant(state.quant().next())),
|
||||
key!(Char('<')) => Clock(SetQuant(state.quant().prev())),
|
||||
key!(Char('>')) => Clock(SetQuant(state.quant().next())),
|
||||
_ => return None,
|
||||
},
|
||||
TransportFocus::Sync => match input.event() {
|
||||
key!(Char(',')) => Clock(SetSync(state.prev_sync())),
|
||||
key!(Char('.')) => Clock(SetSync(state.next_sync())),
|
||||
key!(Char('<')) => Clock(SetSync(state.prev_sync())),
|
||||
key!(Char('>')) => Clock(SetSync(state.next_sync())),
|
||||
key!(Char(',')) => Clock(SetSync(state.sync().prev())),
|
||||
key!(Char('.')) => Clock(SetSync(state.sync().next())),
|
||||
key!(Char('<')) => Clock(SetSync(state.sync().prev())),
|
||||
key!(Char('>')) => Clock(SetSync(state.sync().next())),
|
||||
_ => return None,
|
||||
},
|
||||
TransportFocus::Clock => match input.event() {
|
||||
|
|
@ -90,70 +90,89 @@ where
|
|||
|
||||
fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<SequencerCommand> {
|
||||
use SequencerCommand::*;
|
||||
use KeyCode::Char;
|
||||
if !state.entered() {
|
||||
return None
|
||||
}
|
||||
Some(match state.focused() {
|
||||
AppFocus::Menu => { todo!() },
|
||||
AppFocus::Content(focused) => match focused {
|
||||
SequencerFocus::Phrases => Phrases(PhrasesCommand::input_to_command(state, input)?),
|
||||
SequencerFocus::PhraseEditor => Editor(PhraseCommand::input_to_command(state, input)?),
|
||||
SequencerFocus::Transport(_) => {
|
||||
match TransportCommand::input_to_command(state, input)? {
|
||||
TransportCommand::Clock(_) => { todo!() },
|
||||
TransportCommand::Focus(command) => Focus(command),
|
||||
}
|
||||
},
|
||||
Some(match input.event() {
|
||||
key!(Char('e')) => Editor(
|
||||
PhraseCommand::Show(state.phrase_to_edit().clone())
|
||||
),
|
||||
key!(Char(' ')) => Clock(
|
||||
if let Some(TransportState::Stopped) = *state.clock.playing.read().unwrap() {
|
||||
ClockCommand::Play(None)
|
||||
} else {
|
||||
ClockCommand::Pause(None)
|
||||
}
|
||||
),
|
||||
key!(Shift-Char(' ')) => Clock(
|
||||
if let Some(TransportState::Stopped) = *state.clock.playing.read().unwrap() {
|
||||
ClockCommand::Play(Some(0))
|
||||
} else {
|
||||
ClockCommand::Pause(Some(0))
|
||||
}
|
||||
),
|
||||
_ => match state.focused() {
|
||||
AppFocus::Menu => { todo!() },
|
||||
AppFocus::Content(focused) => match focused {
|
||||
SequencerFocus::Transport(_) => {
|
||||
match TransportCommand::input_to_command(state, input)? {
|
||||
TransportCommand::Clock(_) => { todo!() },
|
||||
TransportCommand::Focus(command) => Focus(command),
|
||||
}
|
||||
},
|
||||
SequencerFocus::Phrases => Phrases(
|
||||
PhrasesCommand::input_to_command(state, input)?
|
||||
),
|
||||
SequencerFocus::PhraseEditor => Editor(
|
||||
PhraseCommand::input_to_command(state, input)?
|
||||
),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<ArrangerCommand> {
|
||||
use ArrangerCommand as Cmd;
|
||||
use KeyCode::Char;
|
||||
if !state.entered() {
|
||||
return None
|
||||
}
|
||||
Some(match state.focused() {
|
||||
AppFocus::Menu => {
|
||||
todo!()
|
||||
},
|
||||
AppFocus::Content(focused) => match focused {
|
||||
ArrangerFocus::Transport(_) => {
|
||||
use TransportCommand::{Clock, Focus};
|
||||
match TransportCommand::input_to_command(state, input)? {
|
||||
Clock(_) => { todo!() },
|
||||
Focus(command) => Cmd::Focus(command)
|
||||
}
|
||||
},
|
||||
ArrangerFocus::PhraseEditor => {
|
||||
Cmd::Editor(PhraseCommand::input_to_command(state, input)?)
|
||||
},
|
||||
ArrangerFocus::Phrases => match input.event() {
|
||||
key!(KeyCode::Char('e')) => {
|
||||
Cmd::EditPhrase(state.phrase_editing().clone())
|
||||
Some(match input.event() {
|
||||
key!(Char('e')) => Cmd::Editor(PhraseCommand::Show(state.phrase_to_edit().clone())),
|
||||
_ => match state.focused() {
|
||||
AppFocus::Menu => { todo!() },
|
||||
AppFocus::Content(focused) => match focused {
|
||||
ArrangerFocus::Transport(_) => {
|
||||
use TransportCommand::{Clock, Focus};
|
||||
match TransportCommand::input_to_command(state, input)? {
|
||||
Clock(_) => { todo!() },
|
||||
Focus(command) => Cmd::Focus(command)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
ArrangerFocus::PhraseEditor => {
|
||||
Cmd::Editor(PhraseCommand::input_to_command(state, input)?)
|
||||
},
|
||||
ArrangerFocus::Phrases => {
|
||||
Cmd::Phrases(PhrasesCommand::input_to_command(state, input)?)
|
||||
}
|
||||
},
|
||||
ArrangerFocus::Arranger => {
|
||||
use ArrangerSelection::*;
|
||||
use KeyCode::Char;
|
||||
match input.event() {
|
||||
key!(Char('e')) => Cmd::EditPhrase(state.phrase_editing().clone()),
|
||||
key!(Char('l')) => Cmd::Clip(ArrangerClipCommand::SetLoop(false)),
|
||||
key!(Char('+')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('=')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('_')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('-')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('`')) => { todo!("toggle state mode") },
|
||||
key!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||
key!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||
_ => match state.selected() {
|
||||
Mix => to_arranger_mix_command(input)?,
|
||||
Track(t) => to_arranger_track_command(input, t)?,
|
||||
Scene(s) => to_arranger_scene_command(input, s)?,
|
||||
Clip(t, s) => to_arranger_clip_command(input, t, s)?,
|
||||
},
|
||||
ArrangerFocus::Arranger => {
|
||||
use ArrangerSelection::*;
|
||||
match input.event() {
|
||||
key!(Char('l')) => Cmd::Clip(ArrangerClipCommand::SetLoop(false)),
|
||||
key!(Char('+')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('=')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('_')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('-')) => Cmd::Zoom(0), // TODO
|
||||
key!(Char('`')) => { todo!("toggle state mode") },
|
||||
key!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add),
|
||||
key!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add),
|
||||
_ => match state.selected() {
|
||||
Mix => to_arranger_mix_command(input)?,
|
||||
Track(t) => to_arranger_track_command(input, t)?,
|
||||
Scene(s) => to_arranger_scene_command(input, s)?,
|
||||
Clip(t, s) => to_arranger_clip_command(input, t, s)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ pub struct PhrasePlayerModel {
|
|||
/// Send all notes off
|
||||
pub(crate) reset: bool, // TODO?: after Some(nframes)
|
||||
/// Record from MIDI ports to current sequence.
|
||||
pub midi_ins: Vec<Port<MidiIn>>,
|
||||
pub midi_ins: Vec<Port<MidiIn>>,
|
||||
/// Play from current sequence to MIDI ports
|
||||
pub midi_outs: Vec<Port<MidiOut>>,
|
||||
pub midi_outs: Vec<Port<MidiOut>>,
|
||||
/// Notes currently held at input
|
||||
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
|
||||
/// Notes currently held at output
|
||||
|
|
|
|||
|
|
@ -616,7 +616,7 @@ impl PhraseEditorModel {
|
|||
}
|
||||
}
|
||||
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
|
||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||
pub fn show (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||
if let Some(phrase) = phrase {
|
||||
self.phrase = Some(phrase.clone());
|
||||
self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue