From 5df08409e564dfd56b2b922d6fe4f0a51e7e4c4d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 10 Nov 2024 01:34:17 +0100 Subject: [PATCH] wip: refactor pt.5, no translate --- crates/tek_api/src/api_cmd.rs | 65 ----------- crates/tek_api/src/lib.rs | 10 +- crates/tek_api/src/phrase.rs | 10 +- crates/tek_api/src/plugin.rs | 85 --------------- crates/tek_api/src/plugin_kind.rs | 21 ++++ crates/tek_api/src/plugin_lv2.rs | 67 ++++++++++++ crates/tek_api/src/pool.rs | 52 +++++++-- crates/tek_api/src/sampler.rs | 71 ++++++++++-- crates/tek_api/src/sequencer.rs | 25 ++++- crates/tek_api/src/transport.rs | 14 --- crates/tek_api/src/transport_cmd.rs | 31 ++++++ crates/tek_api/src/voice.rs | 10 ++ crates/tek_core/src/command.rs | 10 +- crates/tek_tui/src/tui_arranger_cmd.rs | 14 +-- crates/tek_tui/src/tui_sampler.rs | 84 ++------------ crates/tek_tui/src/tui_sequencer.rs | 85 +++++---------- crates/tek_tui/src/tui_sequencer_cmd.rs | 29 +++++ crates/tek_tui/src/tui_transport.rs | 24 ---- crates/tek_tui/src/tui_transport_cmd.rs | 139 ++++++++---------------- 19 files changed, 389 insertions(+), 457 deletions(-) create mode 100644 crates/tek_api/src/plugin_kind.rs create mode 100644 crates/tek_api/src/plugin_lv2.rs create mode 100644 crates/tek_api/src/transport_cmd.rs diff --git a/crates/tek_api/src/api_cmd.rs b/crates/tek_api/src/api_cmd.rs index 1ad131fe..c7b7e813 100644 --- a/crates/tek_api/src/api_cmd.rs +++ b/crates/tek_api/src/api_cmd.rs @@ -1,66 +1 @@ use crate::*; - -#[derive(Clone, PartialEq)] -pub enum SequencerCommand { - Focus(FocusCommand), - Transport(TransportCommand), - Phrases(PhrasePoolCommand), - Editor(PhraseEditorCommand), -} - -#[derive(Clone, PartialEq)] -pub enum PhrasePoolCommand { - Prev, - Next, - MoveUp, - MoveDown, - Delete, - Append, - Insert, - Duplicate, - RandomColor, - Edit, - Import, - Export, - Rename(PhraseRenameCommand), - Length(PhraseLengthCommand), -} - -#[derive(Clone, PartialEq)] -pub enum PhraseRenameCommand { - Begin, - Backspace, - Append(char), - Set(String), - Confirm, - Cancel, -} - -#[derive(Clone, PartialEq)] -pub enum PhraseLengthCommand { - Begin, - Next, - Prev, - Inc, - Dec, - Set(usize), - Confirm, - Cancel, -} - -#[derive(Clone, PartialEq)] -pub enum PhraseEditorCommand { - // TODO: 1-9 seek markers that by default start every 8th of the phrase - ToggleDirection, - EnterEditMode, - ExitEditMode, - NoteAppend, - NoteSet, - NoteCursorSet(usize), - NoteLengthSet(usize), - NoteScrollSet(usize), - TimeCursorSet(usize), - TimeScrollSet(usize), - TimeZoomSet(usize), - Go(Direction), -} diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index 4be2cb82..6ab881cd 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -1,21 +1,27 @@ pub(crate) use tek_core::*; -pub(crate) use tek_core::jack::*; pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7}; pub(crate) use std::thread::JoinHandle; pub(crate) use std::fmt::{Debug, Formatter, Error}; +pub(crate) use tek_core::jack::{ + Client, ProcessScope, Control, CycleTimes, + Port, MidiIn, MidiOut, AudioIn, AudioOut, Unowned, + TransportState, MidiIter, RawMidi +}; submod! { clock mixer phrase plugin + plugin_kind + plugin_lv2 pool sampler sample scene sequencer track - transport + transport transport_cmd voice api_cmd diff --git a/crates/tek_api/src/phrase.rs b/crates/tek_api/src/phrase.rs index 7420eede..7210202a 100644 --- a/crates/tek_api/src/phrase.rs +++ b/crates/tek_api/src/phrase.rs @@ -1,7 +1,7 @@ use crate::*; /// A MIDI sequence. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct Phrase { pub uuid: uuid::Uuid, /// Name of phrase @@ -74,5 +74,11 @@ impl Default for Phrase { Self::new("(empty)", false, 0, None, Some(ItemColor::from(Color::Rgb(0, 0, 0)).into())) } } -impl PartialEq for Phrase { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } + +impl PartialEq for Phrase { + fn eq (&self, other: &Self) -> bool { + self.uuid == other.uuid + } +} + impl Eq for Phrase {} diff --git a/crates/tek_api/src/plugin.rs b/crates/tek_api/src/plugin.rs index 58efc0f7..327758e6 100644 --- a/crates/tek_api/src/plugin.rs +++ b/crates/tek_api/src/plugin.rs @@ -46,91 +46,6 @@ impl Plugin { //} } -/// Supported plugin formats. -#[derive(Default)] -pub enum PluginKind { - #[default] None, - LV2(LV2Plugin), - VST2 { instance: ::vst::host::PluginInstance }, - VST3, -} -impl Debug for PluginKind { - fn fmt (&self, f: &mut Formatter<'_>) -> Result<(), Error> { - write!(f, "{}", match self { - Self::None => "(none)", - Self::LV2(_) => "LV2", - Self::VST2{..} => "VST2", - Self::VST3 => "VST3", - }) - } -} - -/// A LV2 plugin. -#[derive(Debug)] -pub struct LV2Plugin { - pub world: livi::World, - pub instance: livi::Instance, - pub plugin: livi::Plugin, - pub features: Arc, - pub port_list: Vec, - pub input_buffer: Vec, - pub ui_thread: Option>, -} - -impl LV2Plugin { - const INPUT_BUFFER: usize = 1024; - pub fn new (uri: &str) -> Usually { - let world = livi::World::with_load_bundle(&uri); - let features = world - .build_features(livi::FeaturesBuilder { - min_block_length: 1, - max_block_length: 65536, - }); - let plugin = world - .iter_plugins() - .nth(0) - .expect(&format!("plugin not found: {uri}")); - Ok(Self { - instance: unsafe { - plugin - .instantiate(features.clone(), 48000.0) - .expect(&format!("instantiate failed: {uri}")) - }, - port_list: plugin.ports().collect::>(), - input_buffer: Vec::with_capacity(Self::INPUT_BUFFER), - ui_thread: None, - world, - features, - plugin, - }) - } -} - -impl LV2Plugin { - pub fn from_edn <'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { - let mut name = String::new(); - let mut path = String::new(); - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = String::from(*n); - } - if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) { - path = String::from(*p); - } - }, - _ => panic!("unexpected in lv2 '{name}'"), - }); - Plugin::new_lv2(jack, &name, &path) - } -} - -impl Audio for LV2Plugin { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - Control::Continue - } -} - impl Audio for Plugin { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { match self.plugin.as_mut() { diff --git a/crates/tek_api/src/plugin_kind.rs b/crates/tek_api/src/plugin_kind.rs new file mode 100644 index 00000000..0f35ca3a --- /dev/null +++ b/crates/tek_api/src/plugin_kind.rs @@ -0,0 +1,21 @@ +use crate::*; + +/// Supported plugin formats. +#[derive(Default)] +pub enum PluginKind { + #[default] None, + LV2(LV2Plugin), + VST2 { instance: ::vst::host::PluginInstance }, + VST3, +} + +impl Debug for PluginKind { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { + write!(f, "{}", match self { + Self::None => "(none)", + Self::LV2(_) => "LV2", + Self::VST2{..} => "VST2", + Self::VST3 => "VST3", + }) + } +} diff --git a/crates/tek_api/src/plugin_lv2.rs b/crates/tek_api/src/plugin_lv2.rs new file mode 100644 index 00000000..404f0fc4 --- /dev/null +++ b/crates/tek_api/src/plugin_lv2.rs @@ -0,0 +1,67 @@ +use crate::*; + +/// A LV2 plugin. +#[derive(Debug)] +pub struct LV2Plugin { + pub world: livi::World, + pub instance: livi::Instance, + pub plugin: livi::Plugin, + pub features: Arc, + pub port_list: Vec, + pub input_buffer: Vec, + pub ui_thread: Option>, +} + +impl LV2Plugin { + const INPUT_BUFFER: usize = 1024; + pub fn new (uri: &str) -> Usually { + let world = livi::World::with_load_bundle(&uri); + let features = world + .build_features(livi::FeaturesBuilder { + min_block_length: 1, + max_block_length: 65536, + }); + let plugin = world + .iter_plugins() + .nth(0) + .expect(&format!("plugin not found: {uri}")); + Ok(Self { + instance: unsafe { + plugin + .instantiate(features.clone(), 48000.0) + .expect(&format!("instantiate failed: {uri}")) + }, + port_list: plugin.ports().collect::>(), + input_buffer: Vec::with_capacity(Self::INPUT_BUFFER), + ui_thread: None, + world, + features, + plugin, + }) + } +} + +impl LV2Plugin { + pub fn from_edn <'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { + let mut name = String::new(); + let mut path = String::new(); + edn!(edn in args { + Edn::Map(map) => { + if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { + name = String::from(*n); + } + if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) { + path = String::from(*p); + } + }, + _ => panic!("unexpected in lv2 '{name}'"), + }); + Plugin::new_lv2(jack, &name, &path) + } +} + +impl Audio for LV2Plugin { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + Control::Continue + } +} diff --git a/crates/tek_api/src/pool.rs b/crates/tek_api/src/pool.rs index e6481341..2f2ed845 100644 --- a/crates/tek_api/src/pool.rs +++ b/crates/tek_api/src/pool.rs @@ -2,16 +2,48 @@ use crate::*; /// Contains all phrases in a project pub struct PhrasePool { - /// Scroll offset - pub scroll: usize, - /// Highlighted phrase - pub phrase: usize, /// Phrases in the pool pub phrases: Vec>>, - /// Mode switch - pub mode: Option, - /// Whether this widget is focused - pub focused: bool, - /// Whether this widget is entered - pub entered: bool, + /// Highlighted phrase + pub phrase: usize, +} + +#[derive(Clone, PartialEq)] +pub enum PhrasePoolCommand { + Prev, + Next, + MoveUp, + MoveDown, + Delete, + Append, + Insert, + Duplicate, + RandomColor, + Edit, + Import, + Export, + Rename(PhraseRenameCommand), + Length(PhraseLengthCommand), +} + +#[derive(Clone, PartialEq)] +pub enum PhraseRenameCommand { + Begin, + Backspace, + Append(char), + Set(String), + Confirm, + Cancel, +} + +#[derive(Clone, PartialEq)] +pub enum PhraseLengthCommand { + Begin, + Next, + Prev, + Inc, + Dec, + Set(usize), + Confirm, + Cancel, } diff --git a/crates/tek_api/src/sampler.rs b/crates/tek_api/src/sampler.rs index 14f1c024..c9cf029c 100644 --- a/crates/tek_api/src/sampler.rs +++ b/crates/tek_api/src/sampler.rs @@ -62,13 +62,66 @@ impl Sampler { output_gain: 0. }) } -} - -/// A currently playing instance of a sample. -#[derive(Default, Debug, Clone)] -pub struct Voice { - pub sample: Arc>, - pub after: usize, - pub position: usize, - pub velocity: f32, + /// Immutable reference to sample at cursor. + pub fn sample (&self) -> Option<&Arc>> { + for (i, sample) in self.mapped.values().enumerate() { + if i == self.cursor.0 { + return Some(sample) + } + } + for (i, sample) in self.unmapped.iter().enumerate() { + if i + self.mapped.len() == self.cursor.0 { + return Some(sample) + } + } + None + } + /// Create [Voice]s from [Sample]s in response to MIDI input. + pub fn process_midi_in (&mut self, scope: &ProcessScope) { + for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) { + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + if let MidiMessage::NoteOn { ref key, ref vel } = message { + if let Some(sample) = self.mapped.get(key) { + self.voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + } + } + } + } + } + /// Zero the output buffer. + pub fn clear_output_buffer (&mut self) { + for buffer in self.buffer.iter_mut() { + buffer.fill(0.0); + } + } + /// Mix all currently playing samples into the output. + pub fn process_audio_out (&mut self, scope: &ProcessScope) { + let channel_count = self.buffer.len(); + self.voices.write().unwrap().retain_mut(|voice|{ + for index in 0..scope.n_frames() as usize { + if let Some(frame) = voice.next() { + for (channel, sample) in frame.iter().enumerate() { + // Averaging mixer: + //self.buffer[channel % channel_count][index] = ( + //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 + //); + self.buffer[channel % channel_count][index] += + sample * self.output_gain; + } + } else { + return false + } + } + return true + }); + } + /// Write output buffer to output ports. + pub fn write_output_buffer (&mut self, scope: &ProcessScope) { + for (i, port) in self.ports.audio_outs.values_mut().enumerate() { + let buffer = &self.buffer[i]; + for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { + *value = *buffer.get(i).unwrap_or(&0.0); + } + } + } } diff --git a/crates/tek_api/src/sequencer.rs b/crates/tek_api/src/sequencer.rs index 392b6927..4cc9473d 100644 --- a/crates/tek_api/src/sequencer.rs +++ b/crates/tek_api/src/sequencer.rs @@ -1,6 +1,29 @@ use crate::*; -pub enum SequencerCommand {} +#[derive(Clone, PartialEq)] +pub enum SequencerCommand { + Focus(FocusCommand), + Transport(TransportCommand), + Phrases(PhrasePoolCommand), + Editor(PhraseEditorCommand), +} + +#[derive(Clone, PartialEq)] +pub enum PhraseEditorCommand { + // TODO: 1-9 seek markers that by default start every 8th of the phrase + ToggleDirection, + EnterEditMode, + ExitEditMode, + NoteAppend, + NoteSet, + NoteCursorSet(usize), + NoteLengthSet(usize), + NoteScrollSet(usize), + TimeCursorSet(usize), + TimeScrollSet(usize), + TimeZoomSet(usize), + Go(Direction), +} #[derive(Debug)] pub struct SequencerTrack { diff --git a/crates/tek_api/src/transport.rs b/crates/tek_api/src/transport.rs index 5c8199f4..73372d7a 100644 --- a/crates/tek_api/src/transport.rs +++ b/crates/tek_api/src/transport.rs @@ -1,19 +1,5 @@ use crate::*; -#[derive(Copy, Clone, PartialEq)] -pub enum TransportCommand { - FocusNext, - FocusPrev, - Play(Option), - Pause(Option), - SeekUsec(f64), - SeekSample(f64), - SeekPulse(f64), - SetBpm(f64), - SetQuant(f64), - SetSync(f64), -} - pub struct Transport { pub jack: Arc>, /// JACK transport handle. diff --git a/crates/tek_api/src/transport_cmd.rs b/crates/tek_api/src/transport_cmd.rs new file mode 100644 index 00000000..3981bbc6 --- /dev/null +++ b/crates/tek_api/src/transport_cmd.rs @@ -0,0 +1,31 @@ +use crate::*; + +#[derive(Copy, Clone, PartialEq)] +pub enum TransportCommand { + Play(Option), + Pause(Option), + SeekUsec(f64), + SeekSample(f64), + SeekPulse(f64), + SetBpm(f64), + SetQuant(f64), + SetSync(f64), +} + +impl Command for TransportCommand { + fn execute (self, state: &mut Transport) -> Perhaps { + use TransportCommand::*; + match self.translate(&state) { + Play(start) => {todo!()}, + Pause(start) => {todo!()}, + SeekUsec(usec) => {state.clock.current.update_from_usec(usec);}, + SeekSample(sample) => {state.clock.current.update_from_sample(sample);}, + SeekPulse(pulse) => {state.clock.current.update_from_pulse(pulse);}, + SetBpm(bpm) => {return Ok(Some(Self::SetBpm(state.clock.timebase().bpm.set(bpm))))}, + SetQuant(quant) => {return Ok(Some(Self::SetQuant(state.clock.quant.set(quant))))}, + SetSync(sync) => {return Ok(Some(Self::SetSync(state.clock.sync.set(sync))))}, + _ => { unreachable!() } + } + Ok(None) + } +} diff --git a/crates/tek_api/src/voice.rs b/crates/tek_api/src/voice.rs index e69de29b..7375f5ea 100644 --- a/crates/tek_api/src/voice.rs +++ b/crates/tek_api/src/voice.rs @@ -0,0 +1,10 @@ +use crate::*; + +/// A currently playing instance of a sample. +#[derive(Default, Debug, Clone)] +pub struct Voice { + pub sample: Arc>, + pub after: usize, + pub position: usize, + pub velocity: f32, +} diff --git a/crates/tek_core/src/command.rs b/crates/tek_core/src/command.rs index 6bba1f5d..67aa325e 100644 --- a/crates/tek_core/src/command.rs +++ b/crates/tek_core/src/command.rs @@ -12,8 +12,16 @@ pub fn delegate , S> ( Ok(cmd.execute(state)?.map(|x|wrap(x))) } -pub trait InputToCommand: Sized { +pub trait InputToCommand: Command + Sized { fn input_to_command (state: &S, input: &E::Input) -> Option; + fn execute_with_state (state: &mut S, input: &E::Input) -> Perhaps { + Ok(if let Some(command) = Self::input_to_command(state, input) { + let _undo = command.execute(state)?; + Some(true) + } else { + None + }) + } } pub struct MenuBar> { pub menus: Vec>, diff --git a/crates/tek_tui/src/tui_arranger_cmd.rs b/crates/tek_tui/src/tui_arranger_cmd.rs index 5298d1c7..502ef851 100644 --- a/crates/tek_tui/src/tui_arranger_cmd.rs +++ b/crates/tek_tui/src/tui_arranger_cmd.rs @@ -24,12 +24,7 @@ impl Handle for Arranger { return Ok(Some(true)) } } - Ok(if let Some(command) = ArrangerCommand::input_to_command(self, i) { - let _undo = command.execute(self)?; - Some(true) - } else { - None - }) + ArrangerCommand::execute_with_state(self, i) } } @@ -78,12 +73,7 @@ impl InputToCommand> for ArrangerCommand { /// Handle events for arrangement. impl Handle for Arrangement { fn handle (&mut self, from: &TuiInput) -> Perhaps { - Ok(if let Some(command) = ArrangementCommand::input_to_command(self, from) { - let _undo = command.execute(self)?; - Some(true) - } else { - None - }) + ArrangementCommand::execute_with_state(self, from) } } diff --git a/crates/tek_tui/src/tui_sampler.rs b/crates/tek_tui/src/tui_sampler.rs index 44c263e4..fd565166 100644 --- a/crates/tek_tui/src/tui_sampler.rs +++ b/crates/tek_tui/src/tui_sampler.rs @@ -1,22 +1,16 @@ use crate::*; /// The sampler plugin plays sounds. -pub struct Sampler { - _engine: PhantomData, - pub jack: Arc>, - pub name: String, - pub cursor: (usize, usize), - pub editing: Option>>, - pub mapped: BTreeMap>>, - pub unmapped: Vec>>, - pub voices: Arc>>, - pub ports: JackPorts, - pub buffer: Vec>, - pub modal: Arc>>>, - pub output_gain: f32 +pub struct SamplerView { + _engine: PhantomData, + pub state: Sampler, + pub cursor: (usize, usize), + pub editing: Option>>, + pub buffer: Vec>, + pub modal: Arc>>>, } -impl Sampler { +impl SamplerView { pub fn new ( jack: &Arc>, name: &str, @@ -43,68 +37,6 @@ impl Sampler { modal: Default::default() })) } - /// Immutable reference to sample at cursor. - pub fn sample (&self) -> Option<&Arc>> { - for (i, sample) in self.mapped.values().enumerate() { - if i == self.cursor.0 { - return Some(sample) - } - } - for (i, sample) in self.unmapped.iter().enumerate() { - if i + self.mapped.len() == self.cursor.0 { - return Some(sample) - } - } - None - } - /// Create [Voice]s from [Sample]s in response to MIDI input. - pub fn process_midi_in (&mut self, scope: &ProcessScope) { - for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) { - if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { - if let MidiMessage::NoteOn { ref key, ref vel } = message { - if let Some(sample) = self.mapped.get(key) { - self.voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); - } - } - } - } - } - /// Zero the output buffer. - pub fn clear_output_buffer (&mut self) { - for buffer in self.buffer.iter_mut() { - buffer.fill(0.0); - } - } - /// Mix all currently playing samples into the output. - pub fn process_audio_out (&mut self, scope: &ProcessScope) { - let channel_count = self.buffer.len(); - self.voices.write().unwrap().retain_mut(|voice|{ - for index in 0..scope.n_frames() as usize { - if let Some(frame) = voice.next() { - for (channel, sample) in frame.iter().enumerate() { - // Averaging mixer: - //self.buffer[channel % channel_count][index] = ( - //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 - //); - self.buffer[channel % channel_count][index] += - sample * self.output_gain; - } - } else { - return false - } - } - return true - }); - } - /// Write output buffer to output ports. - pub fn write_output_buffer (&mut self, scope: &ProcessScope) { - for (i, port) in self.ports.audio_outs.values_mut().enumerate() { - let buffer = &self.buffer[i]; - for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { - *value = *buffer.get(i).unwrap_or(&0.0); - } - } - } } /// A sound sample. diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index 1e21cb76..fe0a6b04 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -7,7 +7,7 @@ pub type PhraseMessage = Vec; /// Collection of serialized MIDI messages pub type PhraseChunk = [Vec]; /// Root level object for standalone `tek_sequencer` -pub struct Sequencer { +pub struct SequencerView { /// JACK client handle (needs to not be dropped for standalone mode to work). pub jack: Arc>, /// Controls the JACK transport. @@ -25,6 +25,18 @@ pub struct Sequencer { /// Whether the currently focused item is entered pub entered: bool, } +pub struct PhrasePoolView { + _engine: PhantomData, + state: PhrasePool, + /// Scroll offset + pub scroll: usize, + /// Mode switch + pub mode: Option, + /// Whether this widget is focused + pub focused: bool, + /// Whether this widget is entered + pub entered: bool, +} /// Sections in the sequencer app that may be focused #[derive(Copy, Clone, PartialEq, Eq)] pub enum SequencerFocus { /// The transport (toolbar) is focused @@ -47,28 +59,6 @@ pub enum PhrasePoolMode { /// Editing the length of a pattern Length(usize, usize, PhraseLengthFocus), } -/// A MIDI sequence. -#[derive(Debug, Clone)] pub struct Phrase { - pub uuid: uuid::Uuid, - /// Name of phrase - pub name: String, - /// Temporal resolution in pulses per quarter note - pub ppq: usize, - /// Length of phrase in pulses - pub length: usize, - /// Notes in phrase - pub notes: PhraseData, - /// Whether to loop the phrase or play it once - pub loop_on: bool, - /// Start of loop - pub loop_start: usize, - /// Length of loop - pub loop_length: usize, - /// All notes are displayed with minimum length - pub percussive: bool, - /// Identifying color of phrase - pub color: ItemColorTriplet, -} /// Contains state for viewing and editing a phrase pub struct PhraseEditor { _engine: PhantomData, @@ -154,14 +144,22 @@ pub struct PhraseLength { /// Focus layout of sequencer app impl FocusGrid for Sequencer { type Item = SequencerFocus; - fn cursor (&self) -> (usize, usize) { self.focus_cursor } - fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor } + fn cursor (&self) -> (usize, usize) { + self.focus_cursor + } + fn cursor_mut (&mut self) -> &mut (usize, usize) { + &mut self.focus_cursor + } fn layout (&self) -> &[&[SequencerFocus]] { &[ &[SequencerFocus::Transport], &[SequencerFocus::PhrasePool, SequencerFocus::PhraseEditor], ] } - fn focus_enter (&mut self) { self.entered = true } - fn focus_exit (&mut self) { self.entered = false } + fn focus_enter (&mut self) { + self.entered = true + } + fn focus_exit (&mut self) { + self.entered = false + } fn entered (&self) -> Option { if self.entered { Some(self.focused()) } else { None } } @@ -874,36 +872,3 @@ pub(crate) fn keys_vert () -> Buffer { }); buffer } -impl Handle for PhraseEditor { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let Some(command) = PhraseEditorCommand::input_to_command(self, from) { - let _undo = command.execute(self)?; - return Ok(Some(true)) - } - Ok(None) - } -} -impl InputToCommand> for PhraseEditorCommand { - fn input_to_command (_: &PhraseEditor, from: &TuiInput) -> Option { - match from.event() { - key!(KeyCode::Char('`')) => Some(Self::ToggleDirection), - key!(KeyCode::Enter) => Some(Self::EnterEditMode), - key!(KeyCode::Esc) => Some(Self::ExitEditMode), - key!(KeyCode::Char('[')) => Some(Self::NoteLengthDec), - key!(KeyCode::Char(']')) => Some(Self::NoteLengthInc), - key!(KeyCode::Char('a')) => Some(Self::NoteAppend), - key!(KeyCode::Char('s')) => Some(Self::NoteSet), - key!(KeyCode::Char('-')) => Some(Self::TimeZoomOut), - key!(KeyCode::Char('_')) => Some(Self::TimeZoomOut), - key!(KeyCode::Char('=')) => Some(Self::TimeZoomIn), - key!(KeyCode::Char('+')) => Some(Self::TimeZoomIn), - key!(KeyCode::PageUp) => Some(Self::NotePageUp), - key!(KeyCode::PageDown) => Some(Self::NotePageDown), - key!(KeyCode::Up) => Some(Self::GoUp), - key!(KeyCode::Down) => Some(Self::GoDown), - key!(KeyCode::Left) => Some(Self::GoLeft), - key!(KeyCode::Right) => Some(Self::GoRight), - _ => None - } - } -} diff --git a/crates/tek_tui/src/tui_sequencer_cmd.rs b/crates/tek_tui/src/tui_sequencer_cmd.rs index 14603faa..c797cbce 100644 --- a/crates/tek_tui/src/tui_sequencer_cmd.rs +++ b/crates/tek_tui/src/tui_sequencer_cmd.rs @@ -255,3 +255,32 @@ impl Command> for PhraseEditorCommand { Ok(None) } } +impl Handle for PhraseEditor { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + PhraseEditorCommand::execute_with_state(self, from) + } +} +impl InputToCommand> for PhraseEditorCommand { + fn input_to_command (_: &PhraseEditor, from: &TuiInput) -> Option { + match from.event() { + key!(KeyCode::Char('`')) => Some(Self::ToggleDirection), + key!(KeyCode::Enter) => Some(Self::EnterEditMode), + key!(KeyCode::Esc) => Some(Self::ExitEditMode), + key!(KeyCode::Char('[')) => Some(Self::NoteLengthDec), + key!(KeyCode::Char(']')) => Some(Self::NoteLengthInc), + key!(KeyCode::Char('a')) => Some(Self::NoteAppend), + key!(KeyCode::Char('s')) => Some(Self::NoteSet), + key!(KeyCode::Char('-')) => Some(Self::TimeZoomOut), + key!(KeyCode::Char('_')) => Some(Self::TimeZoomOut), + key!(KeyCode::Char('=')) => Some(Self::TimeZoomIn), + key!(KeyCode::Char('+')) => Some(Self::TimeZoomIn), + key!(KeyCode::PageUp) => Some(Self::NotePageUp), + key!(KeyCode::PageDown) => Some(Self::NotePageDown), + key!(KeyCode::Up) => Some(Self::GoUp), + key!(KeyCode::Down) => Some(Self::GoDown), + key!(KeyCode::Left) => Some(Self::GoLeft), + key!(KeyCode::Right) => Some(Self::GoRight), + _ => None + } + } +} diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index 4c5fed04..70eaf8de 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -125,27 +125,3 @@ impl TransportToolbarFocus { lay!(corners, highlight, *widget) } } -impl Handle for TransportToolbar { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let Some(command) = TransportCommand::input_to_command(self, from) { - let _undo = command.execute(self)?; - return Ok(Some(true)) - } - Ok(None) - } -} -impl InputToCommand> for TransportCommand { - fn input_to_command (_: &TransportToolbar, input: &TuiInput) -> Option { - match input.event() { - key!(KeyCode::Char(' ')) => Some(Self::FocusPrev), - key!(Shift-KeyCode::Char(' ')) => Some(Self::FocusPrev), - key!(KeyCode::Left) => Some(Self::FocusPrev), - key!(KeyCode::Right) => Some(Self::FocusNext), - key!(KeyCode::Char('.')) => Some(Self::Increment), - key!(KeyCode::Char(',')) => Some(Self::Decrement), - key!(KeyCode::Char('>')) => Some(Self::FineIncrement), - key!(KeyCode::Char('<')) => Some(Self::FineDecrement), - _ => None - } - } -} diff --git a/crates/tek_tui/src/tui_transport_cmd.rs b/crates/tek_tui/src/tui_transport_cmd.rs index 1debf27f..49b448c4 100644 --- a/crates/tek_tui/src/tui_transport_cmd.rs +++ b/crates/tek_tui/src/tui_transport_cmd.rs @@ -1,103 +1,50 @@ use crate::*; #[derive(Copy, Clone, PartialEq)] -pub enum TransportCommand { - FocusNext, - FocusPrev, - Play, - Pause, - PlayToggle, - PlayFromStart, - Increment, - Decrement, - FineIncrement, - FineDecrement, - SeekUsec(f64), - SeekSample(f64), - SeekPulse(f64), - SetBpm(f64), - SetQuant(f64), - SetSync(f64), +pub enum TransportViewCommand { + Focus(FocusCommand), + Transport(TransportCommand), } -impl Command> for TransportCommand { - fn translate (self, state: &TransportView) -> Self { - use TransportCommand::*; - use TransportViewFocus::*; - match self { - Increment => match state.focus { - Bpm => - {return SetBpm(state.clock.timebase().bpm.get() + 1.0) }, - Quant => - {return SetQuant(next_note_length(state.clock.quant.get()as usize)as f64)}, - Sync => - {return SetSync(next_note_length(state.clock.sync.get()as usize)as f64+1.)}, - PlayPause => - {/*todo seek*/}, - Clock => - {/*todo seek*/}, - }, - FineIncrement => match state.focus { - Bpm => - {return SetBpm(state.clock.timebase().bpm.get() + 0.001)}, - Quant => - {return Increment}, - Sync => - {return Increment}, - PlayPause => - {/*todo seek*/}, - Clock => - {/*todo seek*/}, - }, - Decrement => match state.focus { - Bpm => - {return SetBpm(state.clock.timebase().bpm.get() - 1.0)}, - Quant => - {return SetQuant(prev_note_length(state.clock.quant.get()as usize)as f64)}, - Sync => - {return SetSync(prev_note_length(state.clock.sync.get()as usize)as f64)}, - PlayPause => - {/*todo seek*/}, - Clock => - {/*todo seek*/}, - }, - FineDecrement => match state.focus { - Bpm => - {return SetBpm(state.clock.timebase().bpm.get() - 0.001)}, - Quant => - {return Decrement}, - Sync => - {return Decrement}, - PlayPause => - {/*todo seek*/}, - Clock => - {/*todo seek*/}, - }, - _ => {} - }; - return self +impl Handle for TransportView { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + TransportViewCommand::execute_with_state(self, from) } - fn execute (self, state: &mut TransportView) -> Perhaps { - use TransportCommand::*; - match self.translate(&state) { - FocusNext => - { state.focus.next(); }, - FocusPrev => - { state.focus.prev(); }, - PlayToggle => - { state.toggle_play()?; }, - SeekUsec(usec) => - { state.clock.current.update_from_usec(usec); }, - SeekSample(sample) => - { state.clock.current.update_from_sample(sample); }, - SeekPulse(pulse) => - { state.clock.current.update_from_pulse(pulse); }, - SetBpm(bpm) => - { return Ok(Some(Self::SetBpm(state.clock.timebase().bpm.set(bpm)))) }, - SetQuant(quant) => - { return Ok(Some(Self::SetQuant(state.clock.quant.set(quant)))) }, - SetSync(sync) => - { return Ok(Some(Self::SetSync(state.clock.sync.set(sync)))) }, - _ => { unreachable!() } +} +impl InputToCommand> for TransportViewCommand { + fn input_to_command (_: &TransportView, input: &TuiInput) -> Option { + match input.event() { + key!(KeyCode::Char(' ')) => Some(Self::FocusPrev), + key!(Shift-KeyCode::Char(' ')) => Some(Self::FocusPrev), + key!(KeyCode::Left) => Some(Self::FocusPrev), + key!(KeyCode::Right) => Some(Self::FocusNext), + key!(KeyCode::Char('.')) => Some(match state.focus { + Bpm => SetBpm(state.clock.timebase().bpm.get() + 1.0), + Quant => SetQuant(next_note_length(state.clock.quant.get()as usize)as f64), + Sync => SetSync(next_note_length(state.clock.sync.get()as usize)as f64+1.), + PlayPause => {todo!()}, + Clock => {todo!()} + }), + key!(KeyCode::Char(',')) => Some(match state.focus { + Bpm => SetBpm(state.clock.timebase().bpm.get() - 1.0), + Quant => SetQuant(prev_note_length(state.clock.quant.get()as usize)as f64), + Sync => SetSync(prev_note_length(state.clock.sync.get()as usize)as f64+1.), + PlayPause => {todo!()}, + Clock => {todo!()} + }), + key!(KeyCode::Char('>')) => Some(match state.focus { + Bpm => SetBpm(state.clock.timebase().bpm.get() + 0.001), + Quant => SetQuant(next_note_length(state.clock.quant.get()as usize)as f64), + Sync => SetSync(next_note_length(state.clock.sync.get()as usize)as f64+1.), + PlayPause => {todo!()}, + Clock => {todo!()} + }), + key!(KeyCode::Char('<')) => Some(match state.focus { + Bpm => SetBpm(state.clock.timebase().bpm.get() - 0.001), + Quant => SetQuant(prev_note_length(state.clock.quant.get()as usize)as f64), + Sync => SetSync(prev_note_length(state.clock.sync.get()as usize)as f64+1.), + PlayPause => {todo!()}, + Clock => {todo!()} + }), + _ => None } - Ok(None) } }