mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip(p64,e4)
This commit is contained in:
parent
f49823b7a7
commit
fffd830e15
11 changed files with 194 additions and 217 deletions
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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!() }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue