reenable global play/pause

This commit is contained in:
🪞👃🪞 2024-11-25 16:10:21 +01:00
parent 86f37fb278
commit 340919830a
12 changed files with 208 additions and 165 deletions

View file

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

View file

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

View file

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

View file

@ -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!()]
);

View file

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

View file

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

View file

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