wip(p64,e4)

This commit is contained in:
🪞👃🪞 2024-11-21 15:44:32 +01:00
parent f49823b7a7
commit fffd830e15
11 changed files with 194 additions and 217 deletions

View file

@ -2,6 +2,11 @@ use crate::*;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ClockCommand { pub enum ClockCommand {
Play(Option<usize>),
Pause(Option<usize>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
SetBpm(f64), SetBpm(f64),
SetQuant(f64), SetQuant(f64),
SetSync(f64), SetSync(f64),
@ -11,6 +16,24 @@ impl<T: ClockApi> Command<T> for ClockCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
use ClockCommand::*; use ClockCommand::*;
Ok(Some(match self { 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)), SetBpm(bpm) => SetBpm(state.timebase().bpm.set(bpm)),
SetQuant(quant) => SetQuant(state.quant().set(quant)), SetQuant(quant) => SetQuant(state.quant().set(quant)),
SetSync(sync) => SetSync(state.sync().set(sync)), SetSync(sync) => SetSync(state.sync().set(sync)),
@ -19,13 +42,12 @@ impl<T: ClockApi> Command<T> for ClockCommand {
} }
pub trait ClockApi: Send + Sync { pub trait ClockApi: Send + Sync {
/// Current moment in time
fn current (&self) -> &Arc<Instant>;
/// Temporal resolution in all units /// Temporal resolution in all units
fn timebase (&self) -> &Arc<Timebase>; fn timebase (&self) -> &Arc<Timebase> {
/// Note quantization factor &self.current().timebase
fn quant (&self) -> &Quantize; }
/// Launch quantization factor
fn sync (&self) -> &LaunchSync;
fn sr (&self) -> &SampleRate { fn sr (&self) -> &SampleRate {
&self.timebase().sr &self.timebase().sr
} }
@ -36,16 +58,93 @@ pub trait ClockApi: Send + Sync {
&self.timebase().ppq &self.timebase().ppq
} }
/// Note quantization factor
fn quant (&self) -> &Arc<Quantize>;
fn next_quant (&self) -> f64 { fn next_quant (&self) -> f64 {
next_note_length(self.quant().get() as usize) as f64 next_note_length(self.quant().get() as usize) as f64
} }
fn prev_quant (&self) -> f64 { fn prev_quant (&self) -> f64 {
prev_note_length(self.quant().get() as usize) as f64 prev_note_length(self.quant().get() as usize) as f64
} }
/// Launch quantization factor
fn sync (&self) -> &Arc<LaunchSync>;
fn next_sync (&self) -> f64 { fn next_sync (&self) -> f64 {
next_note_length(self.sync().get() as usize) as f64 next_note_length(self.sync().get() as usize) as f64
} }
fn prev_sync (&self) -> f64 { fn prev_sync (&self) -> f64 {
prev_note_length(self.sync().get() as usize) as 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 }
}
/// Handle to JACK transport
fn transport_handle (&self) -> &Arc<Transport>;
/// Playback state
fn transport_state (&self) -> &Arc<RwLock<Option<TransportState>>>;
/// 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 is_rolling (&self) -> bool {
*self.transport_state().read().unwrap() == Some(TransportState::Rolling)
}
fn toggle_play (&self) -> Usually<()> {
let playing = self.transport_state().read().unwrap().expect("1st sample has not been processed yet");
let playing = match playing {
TransportState::Stopped => {
self.transport_handle().start()?;
Some(TransportState::Starting)
},
_ => {
self.transport_handle().stop()?;
self.transport_handle().locate(0)?;
Some(TransportState::Stopped)
},
};
*self.transport_state().write().unwrap() = playing;
Ok(())
}
}
/// Hosts the JACK callback for updating the temporal pointer and playback status.
pub struct ClockAudio<'a, T: ClockApi>(pub &'a mut T);
impl<'a, T: ClockApi> Audio for ClockAudio<'a, T> {
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let state = &mut self.0;
let times = scope.cycle_times().unwrap();
let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times;
let _chunk_size = scope.n_frames() as usize;
let transport = state.transport_handle().query().unwrap();
state.current().sample.set(transport.pos.frame() as f64);
let mut playing = state.transport_state().write().unwrap();
let mut started = state.transport_offset().write().unwrap();
if *playing != Some(transport.state) {
match transport.state {
TransportState::Rolling => {
*started = Some((current_frames as usize, current_usecs as usize))
},
TransportState::Stopped => {
*started = None
},
_ => {}
}
};
*playing = Some(transport.state);
if *playing == Some(TransportState::Stopped) {
*started = None;
}
state.current().update_from_usec(match *started {
Some((_, usecs)) => current_usecs as f64 - usecs as f64,
None => 0.
});
Control::Continue
}
} }

View file

@ -7,7 +7,7 @@ pub trait HasPlayer: JackApi {
pub trait MidiPlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {} pub trait MidiPlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {}
pub trait HasPhrase: PlayheadApi { pub trait HasPhrase: ClockApi {
fn reset (&self) -> bool; fn reset (&self) -> bool;
fn reset_mut (&mut self) -> &mut bool; fn reset_mut (&mut self) -> &mut bool;
@ -37,7 +37,7 @@ pub trait HasPhrase: PlayheadApi {
} }
} }
pub trait MidiInputApi: PlayheadApi + HasPhrase { pub trait MidiInputApi: ClockApi + HasPhrase {
fn midi_ins (&self) -> &Vec<Port<MidiIn>>; fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
fn midi_ins_mut (&self) -> &mut Vec<Port<MidiIn>>; fn midi_ins_mut (&self) -> &mut Vec<Port<MidiIn>>;
fn has_midi_ins (&self) -> bool { fn has_midi_ins (&self) -> bool {
@ -122,7 +122,7 @@ pub trait MidiInputApi: PlayheadApi + HasPhrase {
} }
pub trait MidiOutputApi: PlayheadApi + HasPhrase { pub trait MidiOutputApi: ClockApi + HasPhrase {
fn midi_outs (&self) -> &Vec<Port<MidiOut>>; fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>; fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
@ -165,7 +165,7 @@ pub trait MidiOutputApi: PlayheadApi + HasPhrase {
// If no phrase is playing, prepare for switchover immediately // If no phrase is playing, prepare for switchover immediately
next = self.phrase().is_none(); next = self.phrase().is_none();
let phrase = self.phrase(); let phrase = self.phrase();
let started0 = self.started(); let started0 = self.transport_offset();
let timebase = self.timebase(); let timebase = self.timebase();
let notes_out = self.notes_out(); let notes_out = self.notes_out();
let next_phrase = self.next_phrase(); let next_phrase = self.next_phrase();
@ -231,7 +231,7 @@ pub trait MidiOutputApi: PlayheadApi + HasPhrase {
//let samples = scope.n_frames() as usize; //let samples = scope.n_frames() as usize;
if let Some((start_at, phrase)) = &self.next_phrase() { if let Some((start_at, phrase)) = &self.next_phrase() {
let start = start_at.sample.get() as usize; let start = start_at.sample.get() as usize;
let sample = self.started().read().unwrap().unwrap().0; let sample = self.transport_offset().read().unwrap().unwrap().0;
// If it's time to switch to the next phrase: // If it's time to switch to the next phrase:
if start <= sample0.saturating_sub(sample) { if start <= sample0.saturating_sub(sample) {
// Samples elapsed since phrase was supposed to start // Samples elapsed since phrase was supposed to start

View file

@ -1,118 +0,0 @@
use crate::*;
#[derive(Clone, Debug, PartialEq)]
pub enum PlayheadCommand {
Play(Option<usize>),
Pause(Option<usize>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
}
impl<T: PlayheadApi> Command<T> for PlayheadCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> {
use PlayheadCommand::*;
match self {
Play(_start) => {
todo!()
},
Pause(_start) => {
todo!()
},
SeekUsec(usec) => {
state.current().update_from_usec(usec);
},
SeekSample(sample) => {
state.current().update_from_sample(sample);
},
SeekPulse(pulse) => {
state.current().update_from_pulse(pulse);
},
};
Ok(None)
}
}
pub trait PlayheadApi: ClockApi {
/// Current moment in time
fn current (&self) -> &Instant;
/// Handle to JACK transport
fn transport (&self) -> &jack::Transport;
/// Playback state
fn playing (&self) -> &RwLock<Option<TransportState>>;
/// Global sample and usec at which playback started
fn started (&self) -> &RwLock<Option<(usize, usize)>>;
fn is_stopped (&self) -> bool {
*self.playing().read().unwrap() == Some(TransportState::Stopped)
}
fn is_rolling (&self) -> bool {
*self.playing().read().unwrap() == Some(TransportState::Rolling)
}
/// Current pulse
fn pulse (&self) -> f64 {
self.current().pulse.get()
}
fn next_launch_pulse (&self) -> usize {
let sync = self.sync().get() as usize;
let pulse = self.pulse() as usize;
if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync }
}
fn toggle_play (&self) -> Usually<()> {
let playing = self.playing().read().unwrap().expect("1st sample has not been processed yet");
let playing = match playing {
TransportState::Stopped => {
self.transport().start()?;
Some(TransportState::Starting)
},
_ => {
self.transport().stop()?;
self.transport().locate(0)?;
Some(TransportState::Stopped)
},
};
*self.playing().write().unwrap() = playing;
Ok(())
}
}
/// Hosts the JACK callback for updating the temporal pointer and playback status.
pub struct PlayheadAudio<'a, T: PlayheadApi>(pub &'a mut T);
impl<'a, T: PlayheadApi> Audio for PlayheadAudio<'a, T> {
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let state = &mut self.0;
let times = scope.cycle_times().unwrap();
let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times;
let _chunk_size = scope.n_frames() as usize;
let transport = state.transport().query().unwrap();
state.current().sample.set(transport.pos.frame() as f64);
let mut playing = state.playing().write().unwrap();
let mut started = state.started().write().unwrap();
if *playing != Some(transport.state) {
match transport.state {
TransportState::Rolling => {
*started = Some((current_frames as usize, current_usecs as usize))
},
TransportState::Stopped => {
*started = None
},
_ => {}
}
};
*playing = Some(transport.state);
if *playing == Some(TransportState::Stopped) {
*started = None;
}
state.current().update_from_usec(match *started {
Some((_, usecs)) => current_usecs as f64 - usecs as f64,
None => 0.
});
Control::Continue
}
}

View file

@ -15,7 +15,6 @@ submod! {
api_jack api_jack
api_phrase api_phrase
api_player api_player
api_playhead
api_scene api_scene
api_track api_track

View file

@ -4,12 +4,11 @@ use crate::*;
pub enum TransportCommand { pub enum TransportCommand {
Focus(FocusCommand), Focus(FocusCommand),
Clock(ClockCommand), Clock(ClockCommand),
Playhead(PlayheadCommand),
} }
impl<T: TransportControl> Command<T> for TransportCommand { impl<T: TransportControl> Command<T> for TransportCommand {
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
use TransportCommand::{Focus, Clock, Playhead}; use TransportCommand::{Focus, Clock};
use FocusCommand::{Next, Prev}; use FocusCommand::{Next, Prev};
use ClockCommand::{SetBpm, SetQuant, SetSync}; use ClockCommand::{SetBpm, SetQuant, SetSync};
Ok(Some(match self { Ok(Some(match self {
@ -31,23 +30,21 @@ pub enum SequencerCommand {
Redo, Redo,
Clear, Clear,
Clock(ClockCommand), Clock(ClockCommand),
Playhead(PlayheadCommand),
Phrases(PhrasesCommand), Phrases(PhrasesCommand),
Editor(PhraseCommand), Editor(PhraseCommand),
} }
impl<T> Command<T> for SequencerCommand impl<T> Command<T> for SequencerCommand
where where
T: PhrasesControl + PhraseEditorControl + PlayheadApi + FocusGrid<Item = SequencerFocus> T: ClockApi + PhrasesControl + PhraseEditorControl + FocusGrid<Item = SequencerFocus>
{ {
fn execute (self, state: &mut T) -> Perhaps<Self> { fn execute (self, state: &mut T) -> Perhaps<Self> {
use SequencerCommand::*; use SequencerCommand::*;
Ok(match self { Ok(match self {
Focus(cmd) => cmd.execute(state)?.map(Focus), Focus(cmd) => cmd.execute(state)?.map(Focus),
Phrases(cmd) => cmd.execute(state)?.map(Phrases), Phrases(cmd) => cmd.execute(state)?.map(Phrases),
Editor(cmd) => cmd.execute(state)?.map(Editor), Editor(cmd) => cmd.execute(state)?.map(Editor),
Clock(cmd) => cmd.execute(state)?.map(Clock), Clock(cmd) => cmd.execute(state)?.map(Clock),
Playhead(cmd) => cmd.execute(state)?.map(Playhead),
Undo => { todo!() }, Undo => { todo!() },
Redo => { todo!() }, Redo => { todo!() },
Clear => { todo!() }, Clear => { todo!() },
@ -63,7 +60,6 @@ pub enum ArrangerCommand {
Clear, Clear,
Color(ItemColor), Color(ItemColor),
Clock(ClockCommand), Clock(ClockCommand),
Playhead(PlayheadCommand),
Scene(ArrangerSceneCommand), Scene(ArrangerSceneCommand),
Track(ArrangerTrackCommand), Track(ArrangerTrackCommand),
Clip(ArrangerClipCommand), Clip(ArrangerClipCommand),
@ -85,7 +81,6 @@ impl Command<ArrangerTui> for ArrangerCommand {
Phrases(cmd) => cmd.execute(state)?.map(Phrases), Phrases(cmd) => cmd.execute(state)?.map(Phrases),
Editor(cmd) => cmd.execute(state)?.map(Editor), Editor(cmd) => cmd.execute(state)?.map(Editor),
Clock(cmd) => cmd.execute(state)?.map(Clock), Clock(cmd) => cmd.execute(state)?.map(Clock),
Playhead(cmd) => cmd.execute(state)?.map(Playhead),
Zoom(zoom) => { todo!(); }, Zoom(zoom) => { todo!(); },
Select(selected) => { Select(selected) => {
*state.selected_mut() = selected; *state.selected_mut() = selected;

View file

@ -8,7 +8,7 @@ impl<'a, T: TransportViewState> Content for TransportView<'a, T> {
lay!( lay!(
state.transport_selected().wrap(focused, TransportFocus::PlayPause, &Styled( state.transport_selected().wrap(focused, TransportFocus::PlayPause, &Styled(
None, None,
match state.transport_state() { match *state.transport_state().read().unwrap() {
Some(TransportState::Rolling) => "▶ PLAYING", Some(TransportState::Rolling) => "▶ PLAYING",
Some(TransportState::Starting) => "READY ...", Some(TransportState::Starting) => "READY ...",
Some(TransportState::Stopped) => "⏹ STOPPED", Some(TransportState::Stopped) => "⏹ STOPPED",

View file

@ -13,32 +13,22 @@ macro_rules! impl_jack_api {
macro_rules! impl_clock_api { macro_rules! impl_clock_api {
($Struct:ident $(:: $field:ident)*) => { ($Struct:ident $(:: $field:ident)*) => {
impl ClockApi for $Struct { impl ClockApi for $Struct {
fn timebase (&self) -> &Arc<Timebase> { fn quant (&self) -> &Arc<Quantize> {
&self$(.$field)*.current.timebase
}
fn quant (&self) -> &Quantize {
&self$(.$field)*.quant &self$(.$field)*.quant
} }
fn sync (&self) -> &LaunchSync { fn sync (&self) -> &Arc<LaunchSync> {
&self$(.$field)*.sync &self$(.$field)*.sync
} }
} fn current (&self) -> &Arc<Instant> {
}
}
macro_rules! impl_playhead_api {
($Struct:ident $(:: $field:ident)*) => {
impl PlayheadApi for $Struct {
fn current (&self) -> &Instant {
&self$(.$field)*.current &self$(.$field)*.current
} }
fn transport (&self) -> &jack::Transport { fn transport_handle (&self) -> &Arc<Transport> {
&self$(.$field)*.transport &self$(.$field)*.transport
} }
fn playing (&self) -> &RwLock<Option<TransportState>> { fn transport_state (&self) -> &Arc<RwLock<Option<TransportState>>> {
&self$(.$field)*.playing &self$(.$field)*.playing
} }
fn started (&self) -> &RwLock<Option<(usize, usize)>> { fn transport_offset (&self) -> &Arc<RwLock<Option<(usize, usize)>>> {
&self$(.$field)*.started &self$(.$field)*.started
} }
} }
@ -187,14 +177,9 @@ impl_jack_api!(ArrangerTui::jack);
impl_clock_api!(TransportTui::state); impl_clock_api!(TransportTui::state);
impl_clock_api!(SequencerTui::transport); impl_clock_api!(SequencerTui::transport);
impl_clock_api!(ArrangerTui::transport); impl_clock_api!(ArrangerTui::transport);
impl_clock_api!(PhrasePlayerModel::transport); impl_clock_api!(PhrasePlayerModel);
impl_clock_api!(ArrangerTrack); impl_clock_api!(ArrangerTrack::player);
impl_playhead_api!(TransportTui::state); impl_has_phrases!(PhrasesModel);
impl_playhead_api!(SequencerTui::transport);
impl_playhead_api!(ArrangerTui::transport);
impl_playhead_api!(PhrasePlayerModel::transport);
impl_playhead_api!(ArrangerTrack);
impl_has_phrases!(PhrasesModel::phrases);
impl_has_phrases!(SequencerTui::phrases); impl_has_phrases!(SequencerTui::phrases);
impl_has_phrases!(ArrangerTui::phrases); impl_has_phrases!(ArrangerTui::phrases);
impl_midi_player!(SequencerTui::player); impl_midi_player!(SequencerTui::player);

View file

@ -5,7 +5,7 @@ impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
use KeyCode::Char; use KeyCode::Char;
use ClockCommand::{SetBpm, SetQuant, SetSync}; use ClockCommand::{SetBpm, SetQuant, SetSync};
use TransportFocus as Focused; use TransportFocus as Focused;
use TransportCommand::{Focus, Clock, Playhead}; use TransportCommand::{Focus, Clock};
let focused = state.transport_focused(); let focused = state.transport_focused();
Some(match input.event() { Some(match input.event() {
key!(Left) => Focus(FocusCommand::Prev), key!(Left) => Focus(FocusCommand::Prev),
@ -14,32 +14,32 @@ impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
Focused::Bpm => Clock(SetBpm(state.bpm().get() + 1.0)), Focused::Bpm => Clock(SetBpm(state.bpm().get() + 1.0)),
Focused::Quant => Clock(SetQuant(state.next_quant())), Focused::Quant => Clock(SetQuant(state.next_quant())),
Focused::Sync => Clock(SetSync(state.next_sync())), Focused::Sync => Clock(SetSync(state.next_sync())),
Focused::PlayPause => Playhead(todo!()), Focused::PlayPause => Clock(todo!()),
Focused::Clock => Playhead(todo!()), Focused::Clock => Clock(todo!()),
_ => {todo!()} _ => {todo!()}
}, },
key!(Char(',')) => match focused { key!(Char(',')) => match focused {
Focused::Bpm => Clock(SetBpm(state.bpm().get() - 1.0)), Focused::Bpm => Clock(SetBpm(state.bpm().get() - 1.0)),
Focused::Quant => Clock(SetQuant(state.prev_quant())), Focused::Quant => Clock(SetQuant(state.prev_quant())),
Focused::Sync => Clock(SetSync(state.prev_sync())), Focused::Sync => Clock(SetSync(state.prev_sync())),
Focused::PlayPause => Playhead(todo!()), Focused::PlayPause => Clock(todo!()),
Focused::Clock => Playhead(todo!()), Focused::Clock => Clock(todo!()),
_ => {todo!()} _ => {todo!()}
}, },
key!(Char('>')) => match focused { key!(Char('>')) => match focused {
Focused::Bpm => Clock(SetBpm(state.bpm().get() + 0.001)), Focused::Bpm => Clock(SetBpm(state.bpm().get() + 0.001)),
Focused::Quant => Clock(SetQuant(state.next_quant())), Focused::Quant => Clock(SetQuant(state.next_quant())),
Focused::Sync => Clock(SetSync(state.next_sync())), Focused::Sync => Clock(SetSync(state.next_sync())),
Focused::PlayPause => Playhead(todo!()), Focused::PlayPause => Clock(todo!()),
Focused::Clock => Playhead(todo!()), Focused::Clock => Clock(todo!()),
_ => {todo!()} _ => {todo!()}
}, },
key!(Char('<')) => match focused { key!(Char('<')) => match focused {
Focused::Bpm => Clock(SetBpm(state.bpm().get() - 0.001)), Focused::Bpm => Clock(SetBpm(state.bpm().get() - 0.001)),
Focused::Quant => Clock(SetQuant(state.prev_quant())), Focused::Quant => Clock(SetQuant(state.prev_quant())),
Focused::Sync => Clock(SetSync(state.prev_sync())), Focused::Sync => Clock(SetSync(state.prev_sync())),
Focused::PlayPause => Playhead(todo!()), Focused::PlayPause => Clock(todo!()),
Focused::Clock => Playhead(todo!()), Focused::Clock => Clock(todo!()),
_ => {todo!()} _ => {todo!()}
}, },
_ => return None _ => return None
@ -49,7 +49,7 @@ impl<T: TransportControl> InputToCommand<Tui, T> for TransportCommand {
impl<T> InputToCommand<Tui, T> for SequencerCommand impl<T> InputToCommand<Tui, T> for SequencerCommand
where where
T: SequencerControl + TransportControl + PhrasesControl + PhraseEditorControl + PlayheadApi T: SequencerControl + TransportControl + PhrasesControl + PhraseEditorControl
+ HasFocus<Item = SequencerFocus> + HasFocus<Item = SequencerFocus>
+ FocusGrid<Item = SequencerFocus> + FocusGrid<Item = SequencerFocus>
{ {
@ -67,10 +67,9 @@ where
key!(KeyCode::Right) => Some(Self::Focus(Right)), key!(KeyCode::Right) => Some(Self::Focus(Right)),
_ => Some(match state.focused() { _ => Some(match state.focused() {
SequencerFocus::Transport => { SequencerFocus::Transport => {
use TransportCommand::{Clock, Playhead, Focus}; use TransportCommand::{Clock, Focus};
match TransportCommand::input_to_command(state, input)? { match TransportCommand::input_to_command(state, input)? {
Clock(command) => { todo!() }, Clock(command) => { todo!() },
Playhead(command) => { todo!() },
Focus(command) => { todo!() }, Focus(command) => { todo!() },
} }
}, },
@ -99,14 +98,13 @@ impl InputToCommand<Tui, ArrangerTui> for ArrangerCommand {
key!(KeyCode::Right) => Self::Focus(Right), key!(KeyCode::Right) => Self::Focus(Right),
key!(KeyCode::Enter) => Self::Focus(Enter), key!(KeyCode::Enter) => Self::Focus(Enter),
key!(KeyCode::Esc) => Self::Focus(Exit), key!(KeyCode::Esc) => Self::Focus(Exit),
key!(KeyCode::Char(' ')) => Self::Playhead(PlayheadCommand::Play(None)), key!(KeyCode::Char(' ')) => Self::Clock(ClockCommand::Play(None)),
_ => match state.focused() { _ => match state.focused() {
ArrangerFocus::Menu => { todo!() }, ArrangerFocus::Menu => { todo!() },
ArrangerFocus::Transport => { ArrangerFocus::Transport => {
use TransportCommand::{Clock, Playhead, Focus}; use TransportCommand::{Clock, Focus};
match TransportCommand::input_to_command(state, input)? { match TransportCommand::input_to_command(state, input)? {
Clock(command) => { todo!() }, Clock(command) => { todo!() },
Playhead(command) => { todo!() },
Focus(command) => { todo!() } Focus(command) => { todo!() }
} }
}, },

View file

@ -2,13 +2,13 @@ use crate::*;
impl Audio for TransportTui { impl Audio for TransportTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
PlayheadAudio(self).process(client, scope) ClockAudio(self).process(client, scope)
} }
} }
impl Audio for SequencerTui { impl Audio for SequencerTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
if PlayheadAudio(self).process(client, scope) == Control::Quit { if ClockAudio(self).process(client, scope) == Control::Quit {
return Control::Quit return Control::Quit
} }
if PlayerAudio( if PlayerAudio(

View file

@ -1,18 +1,18 @@
use crate::*; use crate::*;
pub struct TransportModel { pub struct TransportModel {
/// Playback state
pub(crate) playing: RwLock<Option<TransportState>>,
/// Global sample and usec at which playback started
pub(crate) started: RwLock<Option<(usize, usize)>>,
/// Current moment in time
pub(crate) current: Instant,
/// Note quantization factor
pub(crate) quant: Quantize,
/// Launch quantization factor
pub(crate) sync: LaunchSync,
/// JACK transport handle. /// JACK transport handle.
pub(crate) transport: jack::Transport, pub(crate) transport: Arc<Transport>,
/// Playback state
pub(crate) playing: Arc<RwLock<Option<TransportState>>>,
/// Global sample and usec at which playback started
pub(crate) started: Arc<RwLock<Option<(usize, usize)>>>,
/// Current moment in time
pub(crate) current: Arc<Instant>,
/// Note quantization factor
pub(crate) quant: Arc<Quantize>,
/// Launch quantization factor
pub(crate) sync: Arc<LaunchSync>,
/// Enable metronome? /// Enable metronome?
pub(crate) metronome: bool, pub(crate) metronome: bool,
/// Selected transport component /// Selected transport component
@ -22,17 +22,17 @@ pub struct TransportModel {
} }
impl From<jack::Transport> for TransportModel { impl From<jack::Transport> for TransportModel {
fn from (transport: jack::Transport) -> Self { fn from (transport: Transport) -> Self {
Self { Self {
current: Instant::default(), current: Instant::default().into(),
metronome: false,
playing: RwLock::new(None),
quant: Quantize::default(),
started: RwLock::new(None),
sync: LaunchSync::default(),
focus: TransportFocus::PlayPause, focus: TransportFocus::PlayPause,
is_focused: true, is_focused: true,
transport, metronome: false,
playing: RwLock::new(None).into(),
quant: Quantize::default().into(),
started: RwLock::new(None).into(),
sync: LaunchSync::default().into(),
transport: transport.into(),
} }
} }
} }
@ -40,6 +40,17 @@ impl From<jack::Transport> for TransportModel {
/// Contains state for playing a phrase /// Contains state for playing a phrase
#[derive(Debug)] #[derive(Debug)]
pub struct PhrasePlayerModel { pub struct PhrasePlayerModel {
/// Playback state
pub(crate) playing: Arc<RwLock<Option<TransportState>>>,
/// Global sample and usec at which playback started
pub(crate) started: Arc<RwLock<Option<(usize, usize)>>>,
/// Current moment in time
pub(crate) current: Arc<Instant>,
/// Note quantization factor
pub(crate) quant: Arc<Quantize>,
/// Launch quantization factor
pub(crate) sync: Arc<LaunchSync>,
/// Start time and phrase being played /// Start time and phrase being played
pub(crate) play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>, pub(crate) play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
/// Start time and next phrase /// Start time and next phrase
@ -65,6 +76,11 @@ pub struct PhrasePlayerModel {
impl Default for PhrasePlayerModel { impl Default for PhrasePlayerModel {
fn default () -> Self { fn default () -> Self {
Self { Self {
playing: RwLock::new(None).into(),
started: RwLock::new(None).into(),
current: Instant::default().into(),
quant: Quantize::default().into(),
sync: LaunchSync::default().into(),
midi_ins: vec![], midi_ins: vec![],
midi_outs: vec![], midi_outs: vec![],
reset: true, reset: true,
@ -79,6 +95,19 @@ impl Default for PhrasePlayerModel {
} }
} }
impl From<&TransportModel> for PhrasePlayerModel {
fn from (transport: &TransportModel) -> Self {
Self {
playing: transport.playing.clone(),
started: transport.started.clone(),
current: transport.current.clone(),
quant: transport.quant.clone(),
sync: transport.sync.clone(),
..Default::default()
}
}
}
/// Contains state for viewing and editing a phrase /// Contains state for viewing and editing a phrase
pub struct PhraseEditorModel { pub struct PhraseEditorModel {
/// Phrase being played /// Phrase being played
@ -252,13 +281,13 @@ impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
#[derive(Debug)] #[derive(Debug)]
pub struct ArrangerTrack { pub struct ArrangerTrack {
/// Name of track /// Name of track
pub(crate) name: Arc<RwLock<String>>, pub(crate) name: Arc<RwLock<String>>,
/// Preferred width of track column /// Preferred width of track column
pub(crate) width: usize, pub(crate) width: usize,
/// Identifying color of track /// Identifying color of track
pub(crate) color: ItemColor, pub(crate) color: ItemColor,
/// MIDI player state /// MIDI player state
pub(crate) player: PhrasePlayerModel, pub(crate) player: PhrasePlayerModel,
} }
impl ArrangerTrackApi for ArrangerTrack { impl ArrangerTrackApi for ArrangerTrack {

View file

@ -6,10 +6,9 @@ pub struct PhrasesView<'a, T: PhrasesViewState>(pub &'a T);
pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T); pub struct PhraseView<'a, T: PhraseViewState>(pub &'a T);
pub trait TransportViewState: ClockApi + PlayheadApi + Send + Sync { pub trait TransportViewState: ClockApi + Send + Sync {
fn transport_selected (&self) -> TransportFocus; fn transport_selected (&self) -> TransportFocus;
fn transport_focused (&self) -> bool; fn transport_focused (&self) -> bool;
fn transport_state (&self) -> Option<TransportState>;
fn bpm_value (&self) -> f64 { fn bpm_value (&self) -> f64 {
self.bpm().get() self.bpm().get()
} }
@ -57,9 +56,6 @@ impl TransportViewState for TransportTui {
fn transport_focused (&self) -> bool { fn transport_focused (&self) -> bool {
true true
} }
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
} }
impl TransportViewState for SequencerTui { impl TransportViewState for SequencerTui {
@ -69,9 +65,6 @@ impl TransportViewState for SequencerTui {
fn transport_focused (&self) -> bool { fn transport_focused (&self) -> bool {
self.focused() == SequencerFocus::Transport self.focused() == SequencerFocus::Transport
} }
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
} }
impl TransportViewState for ArrangerTui { impl TransportViewState for ArrangerTui {
@ -81,9 +74,6 @@ impl TransportViewState for ArrangerTui {
fn transport_focused (&self) -> bool { fn transport_focused (&self) -> bool {
self.focused() == ArrangerFocus::Transport self.focused() == ArrangerFocus::Transport
} }
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
} }
impl ArrangerViewState for ArrangerTui { impl ArrangerViewState for ArrangerTui {