diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index 1da57eb8..e5300f8a 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -2,8 +2,8 @@ use crate::*; #[derive(Clone, Debug, PartialEq)] pub enum ClockCommand { - Play(Option), - Pause(Option), + Play(Option), + Pause(Option), SeekUsec(f64), SeekSample(f64), SeekPulse(f64), @@ -15,73 +15,35 @@ pub enum ClockCommand { impl Command for ClockCommand { fn execute (self, state: &mut T) -> Perhaps { 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; - /// Temporal resolution in all units - fn timebase (&self) -> &Arc { - &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; - 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; - 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; + /// Current temporal resolutions + fn timebase (&self) -> &Arc { &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; /// Playback state @@ -89,12 +51,39 @@ pub trait ClockApi: Send + Sync { /// Global sample and usec at which playback started fn transport_offset (&self) -> &Arc>>; - 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) -> 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) -> 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 { diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index b0c6f497..a4e9e8b7 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -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}; diff --git a/crates/tek_core/src/color.rs b/crates/tek_core/src/color.rs index ed4d68b7..5cee8353 100644 --- a/crates/tek_core/src/color.rs +++ b/crates/tek_core/src/color.rs @@ -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)] diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index 6a8bc071..f72d0ad2 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -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}; diff --git a/crates/tek_core/src/time.rs b/crates/tek_core/src/time.rs index 5416beb8..8a568877 100644 --- a/crates/tek_core/src/time.rs +++ b/crates/tek_core/src/time.rs @@ -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)] diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index c94c97f2..230b907a 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -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; diff --git a/crates/tek_tui/src/tui_command.rs b/crates/tek_tui/src/tui_command.rs index f7e37329..4e4f8250 100644 --- a/crates/tek_tui/src/tui_command.rs +++ b/crates/tek_tui/src/tui_command.rs @@ -21,7 +21,7 @@ impl Command for TransportCommand { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] pub enum SequencerCommand { Focus(FocusCommand), Undo, @@ -62,29 +62,24 @@ pub enum ArrangerCommand { Zoom(usize), Phrases(PhrasesCommand), Editor(PhraseCommand), - EditPhrase(Option>>), } impl Command for ArrangerCommand { fn execute (self, state: &mut ArrangerTui) -> Perhaps { use ArrangerCommand::*; Ok(match self { - Focus(cmd) => cmd.execute(state)?.map(Focus), - Scene(cmd) => cmd.execute(state)?.map(Scene), - Track(cmd) => cmd.execute(state)?.map(Track), - Clip(cmd) => cmd.execute(state)?.map(Clip), - Phrases(cmd) => cmd.execute(state)?.map(Phrases), - Editor(cmd) => cmd.execute(state)?.map(Editor), - Clock(cmd) => cmd.execute(state)?.map(Clock), - Zoom(zoom) => { todo!(); }, + Focus(cmd) => cmd.execute(state)?.map(Focus), + Scene(cmd) => cmd.execute(state)?.map(Scene), + Track(cmd) => cmd.execute(state)?.map(Track), + Clip(cmd) => cmd.execute(state)?.map(Clip), + Phrases(cmd) => cmd.execute(state)?.map(Phrases), + Editor(cmd) => cmd.execute(state)?.map(Editor), + Clock(cmd) => cmd.execute(state)?.map(Clock), + Zoom(zoom) => { todo!(); }, Select(selected) => { *state.selected_mut() = selected; None }, - EditPhrase(phrase) => { - state.edit_phrase(&phrase); - None - }, _ => { todo!() } }) } @@ -309,7 +304,7 @@ impl Command 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), TimeScrollSet(usize), TimeZoomSet(usize), + Show(Option>>), } impl Command for PhraseCommand { fn execute (self, state: &mut T) -> Perhaps { 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(); diff --git a/crates/tek_tui/src/tui_control.rs b/crates/tek_tui/src/tui_control.rs index 1ff4f320..519e9278 100644 --- a/crates/tek_tui/src/tui_control.rs +++ b/crates/tek_tui/src/tui_control.rs @@ -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>>; 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>> { 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>>); - fn editing_phrase (&self) -> &Option>>; + fn edit_phrase (&mut self, phrase: Option>>); + fn phrase_to_edit (&self) -> Option>>; + fn phrase_editing (&self) -> &Option>>; fn phrase_editor_entered (&self) -> bool; fn time_axis (&self) -> &RwLock>; fn note_axis (&self) -> &RwLock>; @@ -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); } diff --git a/crates/tek_tui/src/tui_impls.rs b/crates/tek_tui/src/tui_impls.rs index 3eaaec74..00132683 100644 --- a/crates/tek_tui/src/tui_impls.rs +++ b/crates/tek_tui/src/tui_impls.rs @@ -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>>) { + fn phrase_to_edit (&$self1) -> Option>> { + $phrase_to_edit + } + fn edit_phrase (&mut $self2, $phrase: Option>>) { + $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>> { - todo!("editing_phrase") + fn phrase_editing (&self) -> &Option>> { + 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!()] +); diff --git a/crates/tek_tui/src/tui_input.rs b/crates/tek_tui/src/tui_input.rs index 7ca1ae5d..d68f9735 100644 --- a/crates/tek_tui/src/tui_input.rs +++ b/crates/tek_tui/src/tui_input.rs @@ -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,70 +90,89 @@ where fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option { use SequencerCommand::*; + use KeyCode::Char; if !state.entered() { return None } - Some(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), - } - }, + 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::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 { use ArrangerCommand as Cmd; + use KeyCode::Char; if !state.entered() { return None } - Some(match state.focused() { - AppFocus::Menu => { - todo!() - }, - AppFocus::Content(focused) => match focused { - ArrangerFocus::Transport(_) => { - use TransportCommand::{Clock, Focus}; - match TransportCommand::input_to_command(state, input)? { - Clock(_) => { todo!() }, - Focus(command) => Cmd::Focus(command) - } - }, - 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()) + 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}; + match TransportCommand::input_to_command(state, input)? { + Clock(_) => { todo!() }, + Focus(command) => Cmd::Focus(command) + } }, - _ => { + ArrangerFocus::PhraseEditor => { + Cmd::Editor(PhraseCommand::input_to_command(state, input)?) + }, + 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 - key!(Char('_')) => Cmd::Zoom(0), // TODO - key!(Char('-')) => Cmd::Zoom(0), // TODO - key!(Char('`')) => { todo!("toggle state mode") }, - key!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add), - key!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add), - _ => match state.selected() { - Mix => to_arranger_mix_command(input)?, - Track(t) => to_arranger_track_command(input, t)?, - Scene(s) => to_arranger_scene_command(input, s)?, - Clip(t, s) => to_arranger_clip_command(input, t, s)?, + }, + ArrangerFocus::Arranger => { + use ArrangerSelection::*; + match input.event() { + key!(Char('l')) => Cmd::Clip(ArrangerClipCommand::SetLoop(false)), + key!(Char('+')) => Cmd::Zoom(0), // TODO + key!(Char('=')) => Cmd::Zoom(0), // TODO + key!(Char('_')) => Cmd::Zoom(0), // TODO + key!(Char('-')) => Cmd::Zoom(0), // TODO + key!(Char('`')) => { todo!("toggle state mode") }, + key!(Ctrl-Char('a')) => Cmd::Scene(ArrangerSceneCommand::Add), + key!(Ctrl-Char('t')) => Cmd::Track(ArrangerTrackCommand::Add), + _ => match state.selected() { + Mix => to_arranger_mix_command(input)?, + Track(t) => to_arranger_track_command(input, t)?, + Scene(s) => to_arranger_scene_command(input, s)?, + Clip(t, s) => to_arranger_clip_command(input, t, s)?, + } } } } diff --git a/crates/tek_tui/src/tui_model.rs b/crates/tek_tui/src/tui_model.rs index f45f6a33..5e42f242 100644 --- a/crates/tek_tui/src/tui_model.rs +++ b/crates/tek_tui/src/tui_model.rs @@ -49,9 +49,9 @@ pub struct PhrasePlayerModel { /// Send all notes off pub(crate) reset: bool, // TODO?: after Some(nframes) /// Record from MIDI ports to current sequence. - pub midi_ins: Vec>, + pub midi_ins: Vec>, /// Play from current sequence to MIDI ports - pub midi_outs: Vec>, + pub midi_outs: Vec>, /// Notes currently held at input pub(crate) notes_in: Arc>, /// Notes currently held at output diff --git a/crates/tek_tui/src/tui_view.rs b/crates/tek_tui/src/tui_view.rs index 30b6100f..e3fa4961 100644 --- a/crates/tek_tui/src/tui_view.rs +++ b/crates/tek_tui/src/tui_view.rs @@ -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>>) { + pub fn show (&mut self, phrase: Option>>) { if let Some(phrase) = phrase { self.phrase = Some(phrase.clone()); self.time_axis.write().unwrap().clamp = Some(phrase.read().unwrap().length);