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

@ -2,8 +2,8 @@ use crate::*;
#[derive(Clone, Debug, PartialEq)]
pub enum ClockCommand {
Play(Option<usize>),
Pause(Option<usize>),
Play(Option<u32>),
Pause(Option<u32>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
@ -15,73 +15,35 @@ pub enum ClockCommand {
impl<T: ClockApi> Command<T> for ClockCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
use ClockCommand::*;
Ok(Some(match self {
Play(_start) => {
todo!()
},
Pause(_start) => {
todo!()
},
SeekUsec(usec) => {
state.current().update_from_usec(usec);
return Ok(None)
},
SeekSample(sample) => {
state.current().update_from_sample(sample);
return Ok(None)
},
SeekPulse(pulse) => {
state.current().update_from_pulse(pulse);
return Ok(None)
},
SetBpm(bpm) => SetBpm(state.timebase().bpm.set(bpm)),
SetQuant(quant) => SetQuant(state.quant().set(quant)),
SetSync(sync) => SetSync(state.sync().set(sync)),
}))
match self {
Play(start) => state.play_from(start)?,
Pause(pause) => state.pause_at(pause)?,
SeekUsec(usec) => state.current().update_from_usec(usec),
SeekSample(sample) => state.current().update_from_sample(sample),
SeekPulse(pulse) => state.current().update_from_pulse(pulse),
SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))),
SetQuant(quant) => return Ok(Some(SetQuant(state.quant().set(quant)))),
SetSync(sync) => return Ok(Some(SetSync(state.sync().set(sync)))),
};
Ok(None)
}
}
pub trait ClockApi: Send + Sync {
/// Current moment in time
fn current (&self) -> &Arc<Instant>;
/// Temporal resolution in all units
fn timebase (&self) -> &Arc<Timebase> {
&self.current().timebase
}
fn sr (&self) -> &SampleRate {
&self.timebase().sr
}
fn bpm (&self) -> &BeatsPerMinute {
&self.timebase().bpm
}
fn ppq (&self) -> &PulsesPerQuaver {
&self.timebase().ppq
}
/// Note quantization factor
fn quant (&self) -> &Arc<Quantize>;
fn next_quant (&self) -> f64 {
next_note_length(self.quant().get() as usize) as f64
}
fn prev_quant (&self) -> f64 {
prev_note_length(self.quant().get() as usize) as f64
}
/// Launch quantization factor
fn sync (&self) -> &Arc<LaunchSync>;
fn next_sync (&self) -> f64 {
next_note_length(self.sync().get() as usize) as f64
}
fn prev_sync (&self) -> f64 {
prev_note_length(self.sync().get() as usize) as f64
}
fn next_launch_pulse (&self) -> usize {
let sync = self.sync().get() as usize;
let pulse = self.current().pulse.get() as usize;
if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync }
}
/// Current moment in time
fn current (&self) -> &Arc<Instant>;
/// Current temporal resolutions
fn timebase (&self) -> &Arc<Timebase> { &self.current().timebase }
/// Current sample rate
fn sr (&self) -> &SampleRate { &self.timebase().sr }
/// Current tempo
fn bpm (&self) -> &BeatsPerMinute { &self.timebase().bpm }
/// Current MIDI resolution
fn ppq (&self) -> &PulsesPerQuaver { &self.timebase().ppq }
/// Handle to JACK transport
fn transport_handle (&self) -> &Arc<Transport>;
/// Playback state
@ -89,12 +51,39 @@ pub trait ClockApi: Send + Sync {
/// Global sample and usec at which playback started
fn transport_offset (&self) -> &Arc<RwLock<Option<(usize, usize)>>>;
fn is_stopped (&self) -> bool {
*self.transport_state().read().unwrap() == Some(TransportState::Stopped)
fn next_launch_pulse (&self) -> usize {
let sync = self.sync().get() as usize;
let pulse = self.current().pulse.get() as usize;
if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync }
}
fn play_from (&mut self, start: Option<u32>) -> Usually<()> {
if let Some(start) = start {
self.transport_handle().locate(start)?;
}
self.transport_handle().start()?;
self.update_transport_state()
}
fn is_rolling (&self) -> bool {
*self.transport_state().read().unwrap() == Some(TransportState::Rolling)
}
fn pause_at (&mut self, pause: Option<u32>) -> Usually<()> {
self.transport_handle().stop()?;
if let Some(pause) = pause {
self.transport_handle().locate(pause)?;
}
self.update_transport_state()
}
fn is_stopped (&self) -> bool {
*self.transport_state().read().unwrap() == Some(TransportState::Stopped)
}
fn update_transport_state (&self) -> Usually<()> {
*self.transport_state().write().unwrap() = Some(self.transport_handle().query_state()?);
Ok(())
}
fn toggle_play (&self) -> Usually<()> {
let playing = self.transport_state().read().unwrap().expect("1st sample has not been processed yet");
let playing = match playing {

View file

@ -1,4 +1,4 @@
pub use tek_core::*;
pub(crate) use tek_core::*;
pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7};
pub(crate) use std::thread::JoinHandle;
pub(crate) use std::fmt::{Debug, Formatter, Error};

View file

@ -1,6 +1,5 @@
use crate::*;
use rand::{thread_rng, distributions::uniform::UniformSampler};
pub use palette::{*, convert::*, okhsl::*};
/// A color in OKHSL and RGB representations.
#[derive(Debug, Default, Copy, Clone, PartialEq)]

View file

@ -15,6 +15,7 @@ pub(crate) use std::io::{stdout};
pub(crate) use std::thread::{spawn, JoinHandle};
pub(crate) use std::time::Duration;
pub(crate) use atomic_float::*;
pub(crate) use palette::{*, convert::*, okhsl::*};
use better_panic::{Settings, Verbosity};
use std::ops::{Add, Sub, Mul, Div, Rem};
use std::cmp::{Ord, Eq, PartialEq};

View file

@ -116,10 +116,26 @@ impl_time_unit!(Pulse);
/// Quantization setting for launching clips
#[derive(Debug, Default)] pub struct LaunchSync(AtomicF64);
impl_time_unit!(LaunchSync);
impl LaunchSync {
pub fn next (&self) -> f64 {
next_note_length(self.get() as usize) as f64
}
pub fn prev (&self) -> f64 {
prev_note_length(self.get() as usize) as f64
}
}
/// Quantization setting for notes
#[derive(Debug, Default)] pub struct Quantize(AtomicF64);
impl_time_unit!(Quantize);
impl Quantize {
pub fn next (&self) -> f64 {
next_note_length(self.get() as usize) as f64
}
pub fn prev (&self) -> f64 {
prev_note_length(self.get() as usize) as f64
}
}
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
#[derive(Debug, Clone)]

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,7 +62,6 @@ pub enum ArrangerCommand {
Zoom(usize),
Phrases(PhrasesCommand),
Editor(PhraseCommand),
EditPhrase(Option<Arc<RwLock<Phrase>>>),
}
impl Command<ArrangerTui> for ArrangerCommand {
@ -81,10 +80,6 @@ impl Command<ArrangerTui> for ArrangerCommand {
*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,33 +90,58 @@ 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() {
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::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),
}
},
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!()
},
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};
@ -128,19 +153,12 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
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())
},
_ => {
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
@ -158,6 +176,7 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
}
}
}
}
})
}

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