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)]
pub enum ClockCommand {
Play(Option<usize>),
Pause(Option<usize>),
SeekUsec(f64),
SeekSample(f64),
SeekPulse(f64),
SetBpm(f64),
SetQuant(f64),
SetSync(f64),
@ -11,6 +16,24 @@ 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)),
@ -19,13 +42,12 @@ impl<T: ClockApi> Command<T> for ClockCommand {
}
pub trait ClockApi: Send + Sync {
/// Current moment in time
fn current (&self) -> &Arc<Instant>;
/// Temporal resolution in all units
fn timebase (&self) -> &Arc<Timebase>;
/// Note quantization factor
fn quant (&self) -> &Quantize;
/// Launch quantization factor
fn sync (&self) -> &LaunchSync;
fn timebase (&self) -> &Arc<Timebase> {
&self.current().timebase
}
fn sr (&self) -> &SampleRate {
&self.timebase().sr
}
@ -36,16 +58,93 @@ pub trait ClockApi: Send + Sync {
&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 }
}
/// 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 HasPhrase: PlayheadApi {
pub trait HasPhrase: ClockApi {
fn reset (&self) -> 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_mut (&self) -> &mut Vec<Port<MidiIn>>;
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_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
next = self.phrase().is_none();
let phrase = self.phrase();
let started0 = self.started();
let started0 = self.transport_offset();
let timebase = self.timebase();
let notes_out = self.notes_out();
let next_phrase = self.next_phrase();
@ -231,7 +231,7 @@ pub trait MidiOutputApi: PlayheadApi + HasPhrase {
//let samples = scope.n_frames() as usize;
if let Some((start_at, phrase)) = &self.next_phrase() {
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 start <= sample0.saturating_sub(sample) {
// 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_phrase
api_player
api_playhead
api_scene
api_track

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,18 +1,18 @@
use crate::*;
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.
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?
pub(crate) metronome: bool,
/// Selected transport component
@ -22,17 +22,17 @@ pub struct TransportModel {
}
impl From<jack::Transport> for TransportModel {
fn from (transport: jack::Transport) -> Self {
fn from (transport: Transport) -> Self {
Self {
current: Instant::default(),
metronome: false,
playing: RwLock::new(None),
quant: Quantize::default(),
started: RwLock::new(None),
sync: LaunchSync::default(),
current: Instant::default().into(),
focus: TransportFocus::PlayPause,
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
#[derive(Debug)]
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
pub(crate) play_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
/// Start time and next phrase
@ -65,6 +76,11 @@ pub struct PhrasePlayerModel {
impl Default for PhrasePlayerModel {
fn default () -> 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_outs: vec![],
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
pub struct PhraseEditorModel {
/// Phrase being played
@ -252,13 +281,13 @@ impl ArrangerTracksApi<ArrangerTrack> for ArrangerTui {
#[derive(Debug)]
pub struct ArrangerTrack {
/// Name of track
pub(crate) name: Arc<RwLock<String>>,
pub(crate) name: Arc<RwLock<String>>,
/// Preferred width of track column
pub(crate) width: usize,
pub(crate) width: usize,
/// Identifying color of track
pub(crate) color: ItemColor,
pub(crate) color: ItemColor,
/// MIDI player state
pub(crate) player: PhrasePlayerModel,
pub(crate) player: PhrasePlayerModel,
}
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 trait TransportViewState: ClockApi + PlayheadApi + Send + Sync {
pub trait TransportViewState: ClockApi + Send + Sync {
fn transport_selected (&self) -> TransportFocus;
fn transport_focused (&self) -> bool;
fn transport_state (&self) -> Option<TransportState>;
fn bpm_value (&self) -> f64 {
self.bpm().get()
}
@ -57,9 +56,6 @@ impl TransportViewState for TransportTui {
fn transport_focused (&self) -> bool {
true
}
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
}
impl TransportViewState for SequencerTui {
@ -69,9 +65,6 @@ impl TransportViewState for SequencerTui {
fn transport_focused (&self) -> bool {
self.focused() == SequencerFocus::Transport
}
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
}
impl TransportViewState for ArrangerTui {
@ -81,9 +74,6 @@ impl TransportViewState for ArrangerTui {
fn transport_focused (&self) -> bool {
self.focused() == ArrangerFocus::Transport
}
fn transport_state (&self) -> Option<TransportState> {
*self.playing().read().unwrap()
}
}
impl ArrangerViewState for ArrangerTui {