mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
reenable global play/pause
This commit is contained in:
parent
86f37fb278
commit
340919830a
12 changed files with 208 additions and 165 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!()]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue