From ce78b95d8a1c6d66ace3faa9251ef0f65aef9cce Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 15 Nov 2024 00:17:36 +0100 Subject: [PATCH] wip: refactor pt.32: 89 errors, traits --- crates/tek_api/src/api_arranger.rs | 140 +++++++ crates/tek_api/src/api_arranger_clip.rs | 20 + crates/tek_api/src/api_arranger_scene.rs | 75 ++++ crates/tek_api/src/api_arranger_track.rs | 51 +++ crates/tek_api/src/api_clock.rs | 56 +++ crates/tek_api/src/{mixer.rs => api_mixer.rs} | 0 .../{mixer_track.rs => api_mixer_track.rs} | 0 .../tek_api/src/{phrase.rs => api_phrase.rs} | 0 crates/tek_api/src/api_playhead.rs | 72 ++++ .../tek_api/src/{plugin.rs => api_plugin.rs} | 0 .../{plugin_kind.rs => api_plugin_kind.rs} | 0 .../src/{plugin_lv2.rs => api_plugin_lv2.rs} | 0 crates/tek_api/src/{pool.rs => api_pool.rs} | 0 .../src/{sampler.rs => api_sampler.rs} | 0 ...ampler_sample.rs => api_sampler_sample.rs} | 0 ...{sampler_voice.rs => api_sampler_voice.rs} | 0 .../src/{sequencer.rs => api_sequencer.rs} | 268 ++++--------- crates/tek_api/src/arrange.rs | 379 ------------------ crates/tek_api/src/clock.rs | 48 --- crates/tek_api/src/impls.rs | 207 ++++++++++ crates/tek_api/src/lib.rs | 47 +-- crates/tek_api/src/models.rs | 101 +++++ crates/tek_api/src/status.rs | 1 - crates/tek_api/src/transport.rs | 97 ----- crates/tek_snd/src/snd_arrange.rs | 4 +- crates/tek_tui/src/tui_arranger.rs | 44 +- crates/tek_tui/src/tui_sequencer.rs | 18 +- crates/tek_tui/src/tui_transport.rs | 140 ++++--- 28 files changed, 946 insertions(+), 822 deletions(-) create mode 100644 crates/tek_api/src/api_arranger.rs create mode 100644 crates/tek_api/src/api_arranger_clip.rs create mode 100644 crates/tek_api/src/api_arranger_scene.rs create mode 100644 crates/tek_api/src/api_arranger_track.rs create mode 100644 crates/tek_api/src/api_clock.rs rename crates/tek_api/src/{mixer.rs => api_mixer.rs} (100%) rename crates/tek_api/src/{mixer_track.rs => api_mixer_track.rs} (100%) rename crates/tek_api/src/{phrase.rs => api_phrase.rs} (100%) create mode 100644 crates/tek_api/src/api_playhead.rs rename crates/tek_api/src/{plugin.rs => api_plugin.rs} (100%) rename crates/tek_api/src/{plugin_kind.rs => api_plugin_kind.rs} (100%) rename crates/tek_api/src/{plugin_lv2.rs => api_plugin_lv2.rs} (100%) rename crates/tek_api/src/{pool.rs => api_pool.rs} (100%) rename crates/tek_api/src/{sampler.rs => api_sampler.rs} (100%) rename crates/tek_api/src/{sampler_sample.rs => api_sampler_sample.rs} (100%) rename crates/tek_api/src/{sampler_voice.rs => api_sampler_voice.rs} (100%) rename crates/tek_api/src/{sequencer.rs => api_sequencer.rs} (68%) delete mode 100644 crates/tek_api/src/arrange.rs delete mode 100644 crates/tek_api/src/clock.rs create mode 100644 crates/tek_api/src/impls.rs create mode 100644 crates/tek_api/src/models.rs delete mode 100644 crates/tek_api/src/status.rs delete mode 100644 crates/tek_api/src/transport.rs diff --git a/crates/tek_api/src/api_arranger.rs b/crates/tek_api/src/api_arranger.rs new file mode 100644 index 00000000..f15139a5 --- /dev/null +++ b/crates/tek_api/src/api_arranger.rs @@ -0,0 +1,140 @@ +use crate::*; + +#[derive(Clone, Debug)] +pub enum ArrangerCommand { + Clear, + Export, + Import, + StopAll, + Scene(ArrangerSceneCommand), + Track(ArrangerTrackCommand), + Clip(ArrangerClipCommand), +} + +pub trait ArrangerApi: HasJack + HasClock { + fn name (&self) -> &Arc>; + + fn tracks (&self) -> &Vec; + fn tracks_mut (&mut self) -> &mut Vec; + + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + + fn track_default_name (&self) -> String { + format!("Track {}", self.tracks().len() + 1) + } + fn track_add ( + &mut self, name: Option<&str>, color: Option + ) -> Usually<&mut ArrangerTrack> { + let name = name.map_or_else(||self.track_default_name(), |x|x.to_string()); + let track = ArrangerTrack { + width: name.len() + 2, + color: color.unwrap_or_else(||ItemColor::random()), + player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?, + name: Arc::new(name.into()), + }; + self.tracks_mut().push(track); + let index = self.tracks().len() - 1; + Ok(&mut self.tracks_mut()[index]) + } + fn track_del (&mut self, index: usize) { + self.tracks_mut().remove(index); + for scene in self.scenes_mut().iter_mut() { + scene.clips.remove(index); + } + } + fn scene_default_name (&self) -> String { + format!("Scene {}", self.scenes().len() + 1) + } + fn scene_add ( + &mut self, name: Option<&str>, color: Option + ) -> Usually<&mut ArrangerScene> { + let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); + let scene = ArrangerScene { + name: Arc::new(name.into()), + clips: vec![None;self.tracks().len()], + color: color.unwrap_or_else(||ItemColor::random()), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok(&mut self.scenes_mut()[index]) + } + fn scene_del (&mut self, index: usize) { + self.scenes_mut().remove(index); + } +} + +impl Command for ArrangerCommand { + fn execute (self, state: &mut ArrangerModel) -> Perhaps { + match self { + Self::Scene(command) => { return Ok(command.execute(state)?.map(Self::Scene)) }, + Self::Track(command) => { return Ok(command.execute(state)?.map(Self::Track)) }, + Self::Clip(command) => { return Ok(command.execute(state)?.map(Self::Clip)) }, + _ => todo!() + } + Ok(None) + } +} + +//impl Command for ArrangerSceneCommand { +//} + //Edit(phrase) => { state.state.phrase = phrase.clone() }, + //ToggleViewMode => { state.state.mode.to_next(); }, + //Delete => { state.state.delete(); }, + //Activate => { state.state.activate(); }, + //ZoomIn => { state.state.zoom_in(); }, + //ZoomOut => { state.state.zoom_out(); }, + //MoveBack => { state.state.move_back(); }, + //MoveForward => { state.state.move_forward(); }, + //RandomColor => { state.state.randomize_color(); }, + //Put => { state.state.phrase_put(); }, + //Get => { state.state.phrase_get(); }, + //AddScene => { state.state.scene_add(None, None)?; }, + //AddTrack => { state.state.track_add(None, None)?; }, + //ToggleLoop => { state.state.toggle_loop() }, + //pub fn zoom_in (&mut self) { + //if let ArrangerEditorMode::Vertical(factor) = self.mode { + //self.mode = ArrangerEditorMode::Vertical(factor + 1) + //} + //} + //pub fn zoom_out (&mut self) { + //if let ArrangerEditorMode::Vertical(factor) = self.mode { + //self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1)) + //} + //} + //pub fn move_back (&mut self) { + //match self.selected { + //ArrangerEditorFocus::Scene(s) => { + //if s > 0 { + //self.scenes.swap(s, s - 1); + //self.selected = ArrangerEditorFocus::Scene(s - 1); + //} + //}, + //ArrangerEditorFocus::Track(t) => { + //if t > 0 { + //self.tracks.swap(t, t - 1); + //self.selected = ArrangerEditorFocus::Track(t - 1); + //// FIXME: also swap clip order in scenes + //} + //}, + //_ => todo!("arrangement: move forward") + //} + //} + //pub fn move_forward (&mut self) { + //match self.selected { + //ArrangerEditorFocus::Scene(s) => { + //if s < self.scenes.len().saturating_sub(1) { + //self.scenes.swap(s, s + 1); + //self.selected = ArrangerEditorFocus::Scene(s + 1); + //} + //}, + //ArrangerEditorFocus::Track(t) => { + //if t < self.tracks.len().saturating_sub(1) { + //self.tracks.swap(t, t + 1); + //self.selected = ArrangerEditorFocus::Track(t + 1); + //// FIXME: also swap clip order in scenes + //} + //}, + //_ => todo!("arrangement: move forward") + //} + //} diff --git a/crates/tek_api/src/api_arranger_clip.rs b/crates/tek_api/src/api_arranger_clip.rs new file mode 100644 index 00000000..5d2b3655 --- /dev/null +++ b/crates/tek_api/src/api_arranger_clip.rs @@ -0,0 +1,20 @@ +use crate::*; + +#[derive(Clone, Debug)] +pub enum ArrangerClipCommand { + Play, + Get(usize, usize), + Set(usize, usize, Option>>), + Edit(Option>>), + SetLoop(bool), + RandomColor, +} + +impl Command for ArrangerClipCommand { + fn execute (self, state: &mut T) -> Perhaps { + match self { + _ => todo!() + } + Ok(None) + } +} diff --git a/crates/tek_api/src/api_arranger_scene.rs b/crates/tek_api/src/api_arranger_scene.rs new file mode 100644 index 00000000..de903db0 --- /dev/null +++ b/crates/tek_api/src/api_arranger_scene.rs @@ -0,0 +1,75 @@ +use crate::*; + +#[derive(Clone, Debug)] +pub enum ArrangerSceneCommand { + Add, + Delete(usize), + RandomColor, + Play(usize), + Swap(usize, usize), + SetSize(usize), + SetZoom(usize), +} + +impl Command for ArrangerSceneCommand { + fn execute (self, state: &mut T) -> Perhaps { + match self { + Self::Delete(index) => { state.scene_del(index); }, + _ => todo!() + } + Ok(None) + } +} + +pub trait ArrangerSceneApi: Sized { + fn name () -> Arc>; + fn clips () -> Vec>>>; + fn color () -> ItemColor; + + fn ppqs (scenes: &[Self], factor: usize) -> Vec<(usize, usize)> { + let mut total = 0; + if factor == 0 { + scenes.iter().map(|scene|{ + let pulses = scene.pulses().max(PPQ); + total = total + pulses; + (pulses, total - pulses) + }).collect() + } else { + (0..=scenes.len()).map(|i|{ + (factor*PPQ, factor*PPQ*i) + }).collect() + } + } + + fn longest_name (scenes: &[Self]) -> usize { + scenes.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) + } + + /// Returns the pulse length of the longest phrase in the scene + fn pulses (&self) -> usize { + self.clips().iter().fold(0, |a, p|{ + a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) + }) + } + + /// Returns true if all phrases in the scene are + /// currently playing on the given collection of tracks. + fn is_playing (&self, tracks: &[ArrangerTrack]) -> bool { + self.clips().iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() + .all(|(track_index, clip)|match clip { + Some(clip) => tracks + .get(track_index) + .map(|track|if let Some((_, Some(phrase))) = &track.player.phrase { + *phrase.read().unwrap() == *clip.read().unwrap() + } else { + false + }) + .unwrap_or(false), + None => true + }) + } + + fn clip (&self, index: usize) -> Option<&Arc>> { + match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None } + } +} diff --git a/crates/tek_api/src/api_arranger_track.rs b/crates/tek_api/src/api_arranger_track.rs new file mode 100644 index 00000000..1759031c --- /dev/null +++ b/crates/tek_api/src/api_arranger_track.rs @@ -0,0 +1,51 @@ +use crate::*; + +#[derive(Clone, Debug)] +pub enum ArrangerTrackCommand { + Add, + Delete(usize), + RandomColor, + Stop, + Swap(usize, usize), + SetSize(usize), + SetZoom(usize), +} + +impl Command for ArrangerTrackCommand { + fn execute (self, state: &mut T) -> Perhaps { + match self { + Self::Delete(index) => { state.track_del(index); }, + _ => todo!() + } + Ok(None) + } +} + +pub trait ArrangerTrackApi: Sized { + /// Name of track + fn name (&self) -> Arc>; + /// Preferred width of track column + fn width (&self) -> usize; + /// Preferred width of track column + fn width_mut (&mut self) -> &mut usize; + /// Identifying color of track + fn color (&self) -> ItemColor; + /// The MIDI player for the track + fn player (&self) -> MIDIPlayer; + + fn longest_name (tracks: &[Self]) -> usize { + tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) + } + + const MIN_WIDTH: usize = 3; + + fn width_inc (&mut self) { + *self.width_mut() += 1; + } + + fn width_dec (&mut self) { + if self.width() > Self::MIN_WIDTH { + *self.width_mut() -= 1; + } + } +} diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs new file mode 100644 index 00000000..453b23ea --- /dev/null +++ b/crates/tek_api/src/api_clock.rs @@ -0,0 +1,56 @@ +use crate::*; + +#[derive(Clone, Debug)] +pub enum ClockCommand { + SetBpm(f64), + SetQuant(f64), + SetSync(f64), +} + +impl Command for ClockCommand { + fn execute (self, state: &mut T) -> Perhaps { + use ClockCommand::*; + Ok(Some(match self { + SetBpm(bpm) => SetBpm(state.timebase().bpm.set(bpm)), + SetQuant(quant) => SetQuant(state.quant().set(quant)), + SetSync(sync) => SetSync(state.sync().set(sync)), + })) + } +} + +pub trait ClockApi { + fn quant (&self) -> &Quantize; + + fn sync (&self) -> &LaunchSync; + + fn current (&self) -> &Instant; + + fn timebase (&self) -> &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 + } +} + +pub trait HasClock { + fn clock (&self) -> &impl ClockApi; +} + +impl ClockApi for T { + fn quant (&self) -> &Quantize { + self.clock().quant() + } + fn sync (&self) -> &LaunchSync { + self.clock().sync() + } + fn current (&self) -> &Instant { + self.clock().current() + } +} diff --git a/crates/tek_api/src/mixer.rs b/crates/tek_api/src/api_mixer.rs similarity index 100% rename from crates/tek_api/src/mixer.rs rename to crates/tek_api/src/api_mixer.rs diff --git a/crates/tek_api/src/mixer_track.rs b/crates/tek_api/src/api_mixer_track.rs similarity index 100% rename from crates/tek_api/src/mixer_track.rs rename to crates/tek_api/src/api_mixer_track.rs diff --git a/crates/tek_api/src/phrase.rs b/crates/tek_api/src/api_phrase.rs similarity index 100% rename from crates/tek_api/src/phrase.rs rename to crates/tek_api/src/api_phrase.rs diff --git a/crates/tek_api/src/api_playhead.rs b/crates/tek_api/src/api_playhead.rs new file mode 100644 index 00000000..0fcf84b2 --- /dev/null +++ b/crates/tek_api/src/api_playhead.rs @@ -0,0 +1,72 @@ +use crate::*; + +#[derive(Clone, Debug)] +pub enum PlayheadCommand { + Play(Option), + Pause(Option), + SeekUsec(f64), + SeekSample(f64), + SeekPulse(f64), +} + +impl Command for PlayheadCommand { + fn execute (self, state: &mut T) -> Perhaps { + 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 { + + fn playing (&self) -> &Arc>; + + fn transport (&self) -> Transport; + + 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(()) + } + fn is_stopped (&self) -> bool { + *self.playing().read().unwrap() == Some(TransportState::Stopped) + } + fn is_rolling (&self) -> bool { + *self.clock.playing.read().unwrap() == Some(TransportState::Rolling) + } +} diff --git a/crates/tek_api/src/plugin.rs b/crates/tek_api/src/api_plugin.rs similarity index 100% rename from crates/tek_api/src/plugin.rs rename to crates/tek_api/src/api_plugin.rs diff --git a/crates/tek_api/src/plugin_kind.rs b/crates/tek_api/src/api_plugin_kind.rs similarity index 100% rename from crates/tek_api/src/plugin_kind.rs rename to crates/tek_api/src/api_plugin_kind.rs diff --git a/crates/tek_api/src/plugin_lv2.rs b/crates/tek_api/src/api_plugin_lv2.rs similarity index 100% rename from crates/tek_api/src/plugin_lv2.rs rename to crates/tek_api/src/api_plugin_lv2.rs diff --git a/crates/tek_api/src/pool.rs b/crates/tek_api/src/api_pool.rs similarity index 100% rename from crates/tek_api/src/pool.rs rename to crates/tek_api/src/api_pool.rs diff --git a/crates/tek_api/src/sampler.rs b/crates/tek_api/src/api_sampler.rs similarity index 100% rename from crates/tek_api/src/sampler.rs rename to crates/tek_api/src/api_sampler.rs diff --git a/crates/tek_api/src/sampler_sample.rs b/crates/tek_api/src/api_sampler_sample.rs similarity index 100% rename from crates/tek_api/src/sampler_sample.rs rename to crates/tek_api/src/api_sampler_sample.rs diff --git a/crates/tek_api/src/sampler_voice.rs b/crates/tek_api/src/api_sampler_voice.rs similarity index 100% rename from crates/tek_api/src/sampler_voice.rs rename to crates/tek_api/src/api_sampler_voice.rs diff --git a/crates/tek_api/src/sequencer.rs b/crates/tek_api/src/api_sequencer.rs similarity index 68% rename from crates/tek_api/src/sequencer.rs rename to crates/tek_api/src/api_sequencer.rs index 794be359..1a54e463 100644 --- a/crates/tek_api/src/sequencer.rs +++ b/crates/tek_api/src/api_sequencer.rs @@ -1,128 +1,65 @@ use crate::*; -pub trait SequencerModelApi: JackModelApi + ClockModelApi + TransportModelApi { +pub trait HasPlayer: HasJack + HasClock { fn player (&self) -> &MIDIPlayer; fn player_mut (&mut self) -> &mut MIDIPlayer; } -impl JackModelApi for SequencerModel { - fn jack (&self) -> &Arc> { - self.transport.jack() - } -} - -impl ClockModelApi for SequencerModel { - fn clock (&self) -> &Arc { - self.transport.clock() - } -} - -impl TransportModelApi for SequencerModel { - fn transport (&self) -> &jack::Transport { - &self.transport.transport() - } - fn metronome (&self) -> bool { - self.transport.metronome() - } -} - -impl PhrasePoolModelApi for SequencerModel { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - -impl SequencerModelApi for SequencerModel { - fn player (&self) -> &MIDIPlayer { - &self.player - } - fn player_mut (&mut self) -> &mut MIDIPlayer { - &mut self.player - } -} - -pub struct SequencerModel { - /// State of the JACK transport. - transport: TransportModel, - /// State of the phrase pool. - phrases: Vec>>, - /// State of the phrase player. - player: MIDIPlayer, -} - -#[derive(Debug)] -pub struct MIDIPlayer { - /// Global timebase - pub clock: Arc, - /// Start time and phrase being played - pub phrase: Option<(Instant, Option>>)>, - /// Start time and next phrase - pub next_phrase: Option<(Instant, Option>>)>, - /// Play input through output. - pub monitoring: bool, - /// Write input to sequence. - pub recording: bool, - /// Overdub input to sequence. - pub overdub: bool, - /// Send all notes off - pub reset: bool, // TODO?: after Some(nframes) - /// Record from MIDI ports to current sequence. - pub midi_inputs: Vec>, - /// Play from current sequence to MIDI ports - pub midi_outputs: Vec>, - /// MIDI output buffer - pub midi_note: Vec, - /// MIDI output buffer - pub midi_chunk: Vec>>, - /// Notes currently held at input - pub notes_in: Arc>, - /// Notes currently held at output - pub notes_out: Arc>, -} - -/// Methods used primarily by the process callback -impl MIDIPlayer { - pub fn new ( - jack: &Arc>, - clock: &Arc, - name: &str - ) -> Usually { - let jack = jack.read().unwrap(); - Ok(Self { - clock: clock.clone(), - phrase: None, - next_phrase: None, - notes_in: Arc::new(RwLock::new([false;128])), - notes_out: Arc::new(RwLock::new([false;128])), - monitoring: false, - recording: false, - overdub: true, - reset: true, - midi_note: Vec::with_capacity(8), - midi_chunk: vec![Vec::with_capacity(16);16384], - midi_outputs: vec![ - jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())? - ], - midi_inputs: vec![ - jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())? - ], - }) - } - pub fn is_rolling (&self) -> bool { - *self.clock.playing.read().unwrap() == Some(TransportState::Rolling) - } - pub fn has_midi_inputs (&self) -> bool { +pub trait MidiInputApi { + fn has_midi_inputs (&self) -> bool { self.midi_inputs.len() > 0 } - pub fn has_midi_outputs (&self) -> bool { + fn toggle_record (&mut self) { + self.recording = !self.recording; + } + fn toggle_overdub (&mut self) { + self.overdub = !self.overdub; + } + fn record (&mut self, scope: &ProcessScope) { + let sample0 = scope.last_frame_time() as usize; + if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) { + let start = started.sample.get() as usize; + let quant = self.clock.quant.get(); + // For highlighting keys and note repeat + let mut notes_in = self.notes_in.write().unwrap(); + // Record from each input + for input in self.midi_inputs.iter() { + for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + if self.monitoring { + self.midi_chunk[sample].push(bytes.to_vec()) + } + if self.recording { + if let Some(phrase) = phrase { + let mut phrase = phrase.write().unwrap(); + let length = phrase.length; + phrase.record_event({ + let sample = (sample0 + sample - start) as f64; + let pulse = self.clock.timebase().samples_to_pulse(sample); + let quantized = (pulse / quant).round() * quant; + let looped = quantized as usize % length; + looped + }, message); + } + } + update_keys(&mut notes_in, &message); + } + } + } + } + if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase) { + // TODO switch to next phrase and record into it + } + } +} + +pub trait MidiOutputApi { + fn has_midi_outputs (&self) -> bool { self.midi_outputs.len() > 0 } /// Clear the section of the output buffer that we will be using, /// emitting "all notes off" at start of buffer if requested. - pub fn clear (&mut self, scope: &ProcessScope, force_reset: bool) { + fn clear (&mut self, scope: &ProcessScope, force_reset: bool) { for frame in &mut self.midi_chunk[0..scope.n_frames() as usize] { frame.clear(); } @@ -130,7 +67,7 @@ impl MIDIPlayer { all_notes_off(&mut self.midi_chunk); self.reset = false; } } - pub fn play (&mut self, scope: &ProcessScope) -> bool { + fn play (&mut self, scope: &ProcessScope) -> bool { let mut next = false; // Write MIDI events from currently playing phrase (if any) to MIDI output buffer if self.is_rolling() { @@ -188,7 +125,37 @@ impl MIDIPlayer { } next } - pub fn switchover (&mut self, scope: &ProcessScope) { + fn write (&mut self, scope: &ProcessScope) { + let samples = scope.n_frames() as usize; + for port in self.midi_outputs.iter_mut() { + let writer = &mut port.writer(scope); + let output = &self.midi_chunk; + for time in 0..samples { + for event in output[time].iter() { + writer.write(&RawMidi { time: time as u32, bytes: &event }) + .expect(&format!("{event:?}")); + } + } + } + } +} + +pub trait MidiMonitorApi: MidiInputApi + MidiOutputApi { + fn monitor (&mut self, scope: &ProcessScope) { + let mut notes_in = self.notes_in.write().unwrap(); + for input in self.midi_inputs.iter() { + for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + self.midi_chunk[sample].push(bytes.to_vec()); + update_keys(&mut notes_in, &message); + } + } + } + } +} + +pub trait MidiLaunchApi: MidiInputApi + MidiOutputApi { + fn switchover (&mut self, scope: &ProcessScope) { if self.is_rolling() { let sample0 = scope.last_frame_time() as usize; //let samples = scope.n_frames() as usize; @@ -213,70 +180,13 @@ impl MIDIPlayer { } } } - pub fn record (&mut self, scope: &ProcessScope) { - let sample0 = scope.last_frame_time() as usize; - if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) { - let start = started.sample.get() as usize; - let quant = self.clock.quant.get(); - // For highlighting keys and note repeat - let mut notes_in = self.notes_in.write().unwrap(); - // Record from each input - for input in self.midi_inputs.iter() { - for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - if self.monitoring { - self.midi_chunk[sample].push(bytes.to_vec()) - } - if self.recording { - if let Some(phrase) = phrase { - let mut phrase = phrase.write().unwrap(); - let length = phrase.length; - phrase.record_event({ - let sample = (sample0 + sample - start) as f64; - let pulse = self.clock.timebase().samples_to_pulse(sample); - let quantized = (pulse / quant).round() * quant; - let looped = quantized as usize % length; - looped - }, message); - } - } - update_keys(&mut notes_in, &message); - } - } - } - } - if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase) { - // TODO switch to next phrase and record into it - } +} + +pub trait MidiPlayerApi: PlayheadApi { + fn toggle_monitor (&mut self) { + self.monitoring = !self.monitoring; } - pub fn monitor (&mut self, scope: &ProcessScope) { - let mut notes_in = self.notes_in.write().unwrap(); - for input in self.midi_inputs.iter() { - for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - self.midi_chunk[sample].push(bytes.to_vec()); - update_keys(&mut notes_in, &message); - } - } - } - } - pub fn write (&mut self, scope: &ProcessScope) { - let samples = scope.n_frames() as usize; - for port in self.midi_outputs.iter_mut() { - let writer = &mut port.writer(scope); - let output = &self.midi_chunk; - for time in 0..samples { - for event in output[time].iter() { - writer.write(&RawMidi { time: time as u32, bytes: &event }) - .expect(&format!("{event:?}")); - } - } - } - } - pub fn toggle_monitor (&mut self) { self.monitoring = !self.monitoring; } - pub fn toggle_record (&mut self) { self.recording = !self.recording; } - pub fn toggle_overdub (&mut self) { self.overdub = !self.overdub; } - pub fn enqueue_next (&mut self, phrase: Option<&Arc>>) { + fn enqueue_next (&mut self, phrase: Option<&Arc>>) { let start = self.clock.next_launch_pulse(); self.next_phrase = Some(( Instant::from_pulse(&self.clock.timebase(), start as f64), @@ -284,7 +194,7 @@ impl MIDIPlayer { )); self.reset = true; } - pub fn pulses_since_start (&self) -> Option { + fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.phrase.as_ref() { Some(self.clock.current.pulse.get() - started.pulse.get()) } else { diff --git a/crates/tek_api/src/arrange.rs b/crates/tek_api/src/arrange.rs deleted file mode 100644 index a6edc930..00000000 --- a/crates/tek_api/src/arrange.rs +++ /dev/null @@ -1,379 +0,0 @@ -use crate::*; - -pub trait ArrangerModelApi: JackModelApi + ClockModelApi { - fn name (&self) -> &Arc>; - - fn tracks (&self) -> &Vec; - fn tracks_mut (&mut self) -> &mut Vec; - - fn scenes (&self) -> &Vec; - fn scenes_mut (&mut self) -> &mut Vec; - - fn track_default_name (&self) -> String { - format!("Track {}", self.tracks().len() + 1) - } - fn track_add ( - &mut self, name: Option<&str>, color: Option - ) -> Usually<&mut ArrangerTrack> { - let name = name.map_or_else(||self.track_default_name(), |x|x.to_string()); - let track = ArrangerTrack { - width: name.len() + 2, - color: color.unwrap_or_else(||ItemColor::random()), - player: MIDIPlayer::new(&self.jack(), &self.clock(), name.as_str())?, - name: Arc::new(name.into()), - }; - self.tracks_mut().push(track); - let index = self.tracks().len() - 1; - Ok(&mut self.tracks_mut()[index]) - } - fn track_del (&mut self, index: usize) { - self.tracks_mut().remove(index); - for scene in self.scenes_mut().iter_mut() { - scene.clips.remove(index); - } - } - fn scene_default_name (&self) -> String { - format!("Scene {}", self.scenes().len() + 1) - } - fn scene_add ( - &mut self, name: Option<&str>, color: Option - ) -> Usually<&mut ArrangerScene> { - let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string()); - let scene = ArrangerScene { - name: Arc::new(name.into()), - clips: vec![None;self.tracks().len()], - color: color.unwrap_or_else(||ItemColor::random()), - }; - self.scenes_mut().push(scene); - let index = self.scenes().len() - 1; - Ok(&mut self.scenes_mut()[index]) - } - fn scene_del (&mut self, index: usize) { - self.scenes_mut().remove(index); - } -} - -impl JackModelApi for ArrangerModel { - fn jack (&self) -> &Arc> { - &self.transport.jack() - } -} - -impl ClockModelApi for ArrangerModel { - fn clock (&self) -> &Arc { - &self.transport.clock() - } -} - -impl TransportModelApi for ArrangerModel { - fn transport (&self) -> &jack::Transport { - &self.transport.transport() - } - fn metronome (&self) -> bool { - self.transport.metronome() - } -} - -impl PhrasePoolModelApi for ArrangerModel { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - -impl ArrangerModelApi for ArrangerModel { - fn name (&self) -> &Arc> { - &self.name - } - fn tracks (&self) -> &Vec { - &self.tracks - } - fn tracks_mut (&mut self) -> &mut Vec { - &mut self.tracks - } - fn scenes (&self) -> &Vec { - &self.scenes - } - fn scenes_mut (&mut self) -> &mut Vec { - &mut self.scenes - } -} - -#[derive(Debug)] -pub struct ArrangerModel { - /// State of the JACK transport. - transport: TransportModel, - /// Collection of phrases. - phrases: Vec>>, - /// Collection of tracks. - tracks: Vec, - /// Collection of scenes. - scenes: Vec, - /// Name of arranger - name: Arc>, -} - -#[derive(Debug)] -pub struct ArrangerTrack { - /// Name of track - pub name: Arc>, - /// Preferred width of track column - pub width: usize, - /// Identifying color of track - pub color: ItemColor, - /// The MIDI player for the track - pub player: MIDIPlayer -} - -#[derive(Default, Debug, Clone)] -pub struct ArrangerScene { - /// Name of scene - pub name: Arc>, - /// Clips in scene, one per track - pub clips: Vec>>>, - /// Identifying color of scene - pub color: ItemColor, -} - -impl ArrangerTrack { - pub fn longest_name (tracks: &[Self]) -> usize { - tracks.iter().map(|s|s.name.read().unwrap().len()).fold(0, usize::max) - } - - pub const MIN_WIDTH: usize = 3; - - pub fn width_inc (&mut self) { - self.width += 1; - } - pub fn width_dec (&mut self) { - if self.width > Self::MIN_WIDTH { - self.width -= 1; - } - } -} - -impl ArrangerScene { - pub fn ppqs (scenes: &[Self], factor: usize) -> Vec<(usize, usize)> { - let mut total = 0; - if factor == 0 { - scenes.iter().map(|scene|{ - let pulses = scene.pulses().max(PPQ); - total = total + pulses; - (pulses, total - pulses) - }).collect() - } else { - (0..=scenes.len()).map(|i|{ - (factor*PPQ, factor*PPQ*i) - }).collect() - } - } - - pub fn longest_name (scenes: &[Self]) -> usize { - scenes.iter().map(|s|s.name.read().unwrap().len()).fold(0, usize::max) - } - - /// Returns the pulse length of the longest phrase in the scene - pub fn pulses (&self) -> usize { - self.clips.iter().fold(0, |a, p|{ - a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) - }) - } - - /// Returns true if all phrases in the scene are - /// currently playing on the given collection of tracks. - pub fn is_playing (&self, tracks: &[ArrangerTrack]) -> bool { - self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() - .all(|(track_index, clip)|match clip { - Some(clip) => tracks - .get(track_index) - .map(|track|if let Some((_, Some(phrase))) = &track.player.phrase { - *phrase.read().unwrap() == *clip.read().unwrap() - } else { - false - }) - .unwrap_or(false), - None => true - }) - } - - pub fn clip (&self, index: usize) -> Option<&Arc>> { - match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } - } - - //TODO - //pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { - //let mut name = None; - //let mut clips = vec![]; - //edn!(edn in args { - //Edn::Map(map) => { - //let key = map.get(&Edn::Key(":name")); - //if let Some(Edn::Str(n)) = key { - //name = Some(*n); - //} else { - //panic!("unexpected key in scene '{name:?}': {key:?}") - //} - //}, - //Edn::Symbol("_") => { - //clips.push(None); - //}, - //Edn::Int(i) => { - //clips.push(Some(*i as usize)); - //}, - //_ => panic!("unexpected in scene '{name:?}': {edn:?}") - //}); - //Ok(ArrangerScene { - //name: Arc::new(name.unwrap_or("").to_string().into()), - //color: ItemColor::random(), - //clips, - //}) - //} -} - -#[derive(Clone, Debug)] -pub enum ArrangerCommand { - Clear, - Export, - Import, - StopAll, - Scene(ArrangerSceneCommand), - Track(ArrangerTrackCommand), - Clip(ArrangerClipCommand), -} - -#[derive(Clone, Debug)] -pub enum ArrangerSceneCommand { - Add, - Delete(usize), - RandomColor, - Play(usize), - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), -} - -#[derive(Clone, Debug)] -pub enum ArrangerTrackCommand { - Add, - Delete(usize), - RandomColor, - Stop, - Swap(usize, usize), - SetSize(usize), - SetZoom(usize), -} - -#[derive(Clone, Debug)] -pub enum ArrangerClipCommand { - Play, - Get(usize, usize), - Set(usize, usize, Option>>), - Edit(Option>>), - SetLoop(bool), - RandomColor, -} - -impl Command for ArrangerCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - Self::Scene(command) => { return Ok(command.execute(state)?.map(Self::Scene)) }, - Self::Track(command) => { return Ok(command.execute(state)?.map(Self::Track)) }, - Self::Clip(command) => { return Ok(command.execute(state)?.map(Self::Clip)) }, - _ => todo!() - } - Ok(None) - } -} - -impl Command for ArrangerSceneCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - Self::Delete(index) => { state.scene_del(index); }, - _ => todo!() - } - Ok(None) - } -} - -impl Command for ArrangerTrackCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - Self::Delete(index) => { state.track_del(index); }, - _ => todo!() - } - Ok(None) - } -} - -impl Command for ArrangerClipCommand { - fn execute (self, state: &mut ArrangerModel) -> Perhaps { - match self { - _ => todo!() - } - Ok(None) - } -} - -//impl Command for ArrangerSceneCommand { -//} - //Edit(phrase) => { state.state.phrase = phrase.clone() }, - //ToggleViewMode => { state.state.mode.to_next(); }, - //Delete => { state.state.delete(); }, - //Activate => { state.state.activate(); }, - //ZoomIn => { state.state.zoom_in(); }, - //ZoomOut => { state.state.zoom_out(); }, - //MoveBack => { state.state.move_back(); }, - //MoveForward => { state.state.move_forward(); }, - //RandomColor => { state.state.randomize_color(); }, - //Put => { state.state.phrase_put(); }, - //Get => { state.state.phrase_get(); }, - //AddScene => { state.state.scene_add(None, None)?; }, - //AddTrack => { state.state.track_add(None, None)?; }, - //ToggleLoop => { state.state.toggle_loop() }, - //pub fn zoom_in (&mut self) { - //if let ArrangerEditorMode::Vertical(factor) = self.mode { - //self.mode = ArrangerEditorMode::Vertical(factor + 1) - //} - //} - //pub fn zoom_out (&mut self) { - //if let ArrangerEditorMode::Vertical(factor) = self.mode { - //self.mode = ArrangerEditorMode::Vertical(factor.saturating_sub(1)) - //} - //} - //pub fn move_back (&mut self) { - //match self.selected { - //ArrangerEditorFocus::Scene(s) => { - //if s > 0 { - //self.scenes.swap(s, s - 1); - //self.selected = ArrangerEditorFocus::Scene(s - 1); - //} - //}, - //ArrangerEditorFocus::Track(t) => { - //if t > 0 { - //self.tracks.swap(t, t - 1); - //self.selected = ArrangerEditorFocus::Track(t - 1); - //// FIXME: also swap clip order in scenes - //} - //}, - //_ => todo!("arrangement: move forward") - //} - //} - //pub fn move_forward (&mut self) { - //match self.selected { - //ArrangerEditorFocus::Scene(s) => { - //if s < self.scenes.len().saturating_sub(1) { - //self.scenes.swap(s, s + 1); - //self.selected = ArrangerEditorFocus::Scene(s + 1); - //} - //}, - //ArrangerEditorFocus::Track(t) => { - //if t < self.tracks.len().saturating_sub(1) { - //self.tracks.swap(t, t + 1); - //self.selected = ArrangerEditorFocus::Track(t + 1); - //// FIXME: also swap clip order in scenes - //} - //}, - //_ => todo!("arrangement: move forward") - //} - //} diff --git a/crates/tek_api/src/clock.rs b/crates/tek_api/src/clock.rs deleted file mode 100644 index 72dc5efd..00000000 --- a/crates/tek_api/src/clock.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::*; - -/// A timer with starting point, current time, and quantization -#[derive(Default, Debug)] -pub struct Clock { - /// Playback state - pub playing: RwLock>, - /// Global sample and usec at which playback started - pub started: RwLock>, - /// Current moment in time - pub current: Instant, - /// Note quantization factor - pub quant: Quantize, - /// Launch quantization factor - pub sync: LaunchSync, -} - -impl Clock { - #[inline] pub fn timebase (&self) -> &Arc { - &self.current.timebase - } - #[inline] pub fn pulse (&self) -> f64 { - self.current.pulse.get() - } - #[inline] pub fn quant (&self) -> f64 { - self.quant.get() - } - #[inline] pub fn sync (&self) -> f64 { - self.sync.get() - } - #[inline] pub 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 } - } -} - -impl From for Clock { - fn from (current: Instant) -> Self { - Self { - playing: Some(TransportState::Stopped).into(), - started: None.into(), - quant: 24.into(), - sync: (current.timebase.ppq.get() * 4.).into(), - current, - } - } -} diff --git a/crates/tek_api/src/impls.rs b/crates/tek_api/src/impls.rs new file mode 100644 index 00000000..bb5a258b --- /dev/null +++ b/crates/tek_api/src/impls.rs @@ -0,0 +1,207 @@ +use crate::*; + +impl ClockApi for Clock { + fn quant (&self) -> &Quantize { + &self.quant + } + fn sync (&self) -> &LaunchSync { + &self.sync + } + fn current (&self) -> &Instant { + &self.current + } +} + +impl PlayheadApi for Clock {} + +impl HasJack for TransportModel { + fn jack (&self) -> &Arc> { + &self.jack + } +} + +impl HasClock for TransportModel { + fn clock (&self) -> &Arc { + &self.clock + } +} + +//impl TransportModelApi for TransportModel { + //fn transport (&self) -> &jack::Transport { + //&self.transport + //} + //fn metronome (&self) -> bool { + //self.metronome + //} +//} + +impl Debug for TransportModel { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { + f.debug_struct("transport") + .field("jack", &self.jack) + .field("transport", &"(JACK transport)") + .field("clock", &self.clock) + .field("metronome", &self.metronome) + .finish() + } +} + +impl HasJack for ArrangerModel { + fn jack (&self) -> &Arc> { + &self.transport.jack() + } +} + +impl HasClock for ArrangerModel { + fn clock (&self) -> &Arc { + &self.transport.clock() + } +} + +//impl TransportModelApi for ArrangerModel { + //fn transport (&self) -> &jack::Transport { + //&self.transport.transport() + //} + //fn metronome (&self) -> bool { + //self.transport.metronome() + //} +//} + +impl PhrasePoolModelApi for ArrangerModel { + fn phrases (&self) -> &Vec>> { + &self.phrases + } + fn phrases_mut (&mut self) -> &mut Vec>> { + &mut self.phrases + } +} + +impl ArrangerApi for ArrangerModel { + fn name (&self) -> &Arc> { + &self.name + } + fn tracks (&self) -> &Vec { + &self.tracks + } + fn tracks_mut (&mut self) -> &mut Vec { + &mut self.tracks + } + fn scenes (&self) -> &Vec { + &self.scenes + } + fn scenes_mut (&mut self) -> &mut Vec { + &mut self.scenes + } +} + +impl ArrangerScene { + + //TODO + //pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { + //let mut name = None; + //let mut clips = vec![]; + //edn!(edn in args { + //Edn::Map(map) => { + //let key = map.get(&Edn::Key(":name")); + //if let Some(Edn::Str(n)) = key { + //name = Some(*n); + //} else { + //panic!("unexpected key in scene '{name:?}': {key:?}") + //} + //}, + //Edn::Symbol("_") => { + //clips.push(None); + //}, + //Edn::Int(i) => { + //clips.push(Some(*i as usize)); + //}, + //_ => panic!("unexpected in scene '{name:?}': {edn:?}") + //}); + //Ok(ArrangerScene { + //name: Arc::new(name.unwrap_or("").to_string().into()), + //color: ItemColor::random(), + //clips, + //}) + //} +} + +impl HasJack for SequencerModel { + fn jack (&self) -> &Arc> { + self.transport.jack() + } +} + +impl HasClock for SequencerModel { + fn clock (&self) -> &Arc { + self.transport.clock() + } +} + +//impl TransportModelApi for SequencerModel { + //fn transport (&self) -> &jack::Transport { + //&self.transport.transport() + //} + //fn metronome (&self) -> bool { + //self.transport.metronome() + //} +//} + +impl PhrasePoolModelApi for SequencerModel { + fn phrases (&self) -> &Vec>> { + &self.phrases + } + fn phrases_mut (&mut self) -> &mut Vec>> { + &mut self.phrases + } +} + +impl HasPlayer for SequencerModel { + fn player (&self) -> &MIDIPlayer { + &self.player + } + fn player_mut (&mut self) -> &mut MIDIPlayer { + &mut self.player + } +} + +impl From for Clock { + fn from (current: Instant) -> Self { + Self { + playing: Some(TransportState::Stopped).into(), + started: None.into(), + quant: 24.into(), + sync: (current.timebase.ppq.get() * 4.).into(), + current, + } + } +} + +/// Methods used primarily by the process callback +impl MIDIPlayer { + pub fn new ( + jack: &Arc>, + clock: &Arc, + name: &str + ) -> Usually { + let jack = jack.read().unwrap(); + Ok(Self { + clock: clock.clone(), + phrase: None, + next_phrase: None, + notes_in: Arc::new(RwLock::new([false;128])), + notes_out: Arc::new(RwLock::new([false;128])), + monitoring: false, + recording: false, + overdub: true, + reset: true, + midi_note: Vec::with_capacity(8), + midi_chunk: vec![Vec::with_capacity(16);16384], + midi_outputs: vec![ + jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())? + ], + midi_inputs: vec![ + jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())? + ], + }) + } +} diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index d6e9eace..36c5fef7 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -5,46 +5,43 @@ 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 + Transport, TransportState, MidiIter, RawMidi }; submod! { //api_jack - arrange + api_arranger + api_arranger_clip + api_arranger_scene + api_arranger_track - clock + api_clock - mixer - mixer_track + //api_mixer + //api_mixer_track - phrase + api_phrase - plugin - plugin_kind - plugin_lv2 + api_playhead - pool + //api_plugin + //api_plugin_kind + //api_plugin_lv2 - sampler - sampler_sample - sampler_voice + api_pool - sequencer + //api_sampler + //api_sampler_sample + //api_sampler_voice - status + api_sequencer - transport + impls + + models } -pub trait JackModelApi { +pub trait HasJack { fn jack (&self) -> &Arc>; } - -pub trait ClockModelApi { - fn clock (&self) -> &Arc; - fn is_stopped (&self) -> bool { - *self.clock().playing.read().unwrap() == Some(TransportState::Stopped) - } -} - diff --git a/crates/tek_api/src/models.rs b/crates/tek_api/src/models.rs new file mode 100644 index 00000000..d2d9cca0 --- /dev/null +++ b/crates/tek_api/src/models.rs @@ -0,0 +1,101 @@ +use crate::*; + +/// A timer with starting point, current time, and quantization +#[derive(Default, Debug)] +pub struct Clock { + /// Playback state + pub playing: RwLock>, + /// Global sample and usec at which playback started + pub started: RwLock>, + /// Current moment in time + pub current: Instant, + /// Note quantization factor + pub quant: Quantize, + /// Launch quantization factor + pub sync: LaunchSync, +} + +pub struct TransportModel { + jack: Arc>, + /// Current sample rate, tempo, and PPQ. + clock: Arc, + /// JACK transport handle. + transport: jack::Transport, + /// Enable metronome? + metronome: bool, +} + +#[derive(Debug)] +pub struct ArrangerModel { + /// State of the JACK transport. + transport: TransportModel, + /// Collection of phrases. + phrases: Vec>>, + /// Collection of tracks. + tracks: Vec, + /// Collection of scenes. + scenes: Vec, + /// Name of arranger + name: Arc>, +} + +#[derive(Default, Debug, Clone)] +pub struct ArrangerScene { + /// Name of scene + pub name: Arc>, + /// Clips in scene, one per track + pub clips: Vec>>>, + /// Identifying color of scene + pub color: ItemColor, +} + +#[derive(Debug)] +pub struct ArrangerTrack { + /// Name of track + pub name: Arc>, + /// Preferred width of track column + pub width: usize, + /// Identifying color of track + pub color: ItemColor, + /// The MIDI player for the track + pub player: MIDIPlayer +} + +pub struct SequencerModel { + /// State of the JACK transport. + transport: TransportModel, + /// State of the phrase pool. + phrases: Vec>>, + /// State of the phrase player. + player: MIDIPlayer, +} + +#[derive(Debug)] +pub struct MIDIPlayer { + /// Global timebase + pub clock: Arc, + /// Start time and phrase being played + pub phrase: Option<(Instant, Option>>)>, + /// Start time and next phrase + pub next_phrase: Option<(Instant, Option>>)>, + /// Play input through output. + pub monitoring: bool, + /// Write input to sequence. + pub recording: bool, + /// Overdub input to sequence. + pub overdub: bool, + /// Send all notes off + pub reset: bool, // TODO?: after Some(nframes) + /// Record from MIDI ports to current sequence. + pub midi_inputs: Vec>, + /// Play from current sequence to MIDI ports + pub midi_outputs: Vec>, + /// MIDI output buffer + pub midi_note: Vec, + /// MIDI output buffer + pub midi_chunk: Vec>>, + /// Notes currently held at input + pub notes_in: Arc>, + /// Notes currently held at output + pub notes_out: Arc>, +} diff --git a/crates/tek_api/src/status.rs b/crates/tek_api/src/status.rs deleted file mode 100644 index c7b7e813..00000000 --- a/crates/tek_api/src/status.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::*; diff --git a/crates/tek_api/src/transport.rs b/crates/tek_api/src/transport.rs deleted file mode 100644 index 10fbb0e9..00000000 --- a/crates/tek_api/src/transport.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::*; - -pub trait TransportModelApi: JackModelApi + ClockModelApi { - fn transport (&self) -> &jack::Transport; - fn metronome (&self) -> bool; -} - -impl JackModelApi for TransportModel { - fn jack (&self) -> &Arc> { - &self.jack - } -} - -impl ClockModelApi for TransportModel { - fn clock (&self) -> &Arc { - &self.clock - } -} - -impl TransportModelApi for TransportModel { - fn transport (&self) -> &jack::Transport { - &self.transport - } - fn metronome (&self) -> bool { - self.metronome - } -} - -pub struct TransportModel { - jack: Arc>, - /// Current sample rate, tempo, and PPQ. - clock: Arc, - /// JACK transport handle. - transport: jack::Transport, - /// Enable metronome? - metronome: bool, -} - -impl Debug for TransportModel { - fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { - f.debug_struct("transport") - .field("jack", &self.jack) - .field("transport", &"(JACK transport)") - .field("clock", &self.clock) - .field("metronome", &self.metronome) - .finish() - } -} - -impl TransportModel { - pub fn toggle_play (&mut self) -> Usually<()> { - let playing = self.clock.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.clock.playing.write().unwrap() = playing; - Ok(()) - } -} - -#[derive(Debug, 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 T) -> Perhaps { - use TransportCommand::*; - match self { - 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_snd/src/snd_arrange.rs b/crates/tek_snd/src/snd_arrange.rs index 7af4782c..3c9028a4 100644 --- a/crates/tek_snd/src/snd_arrange.rs +++ b/crates/tek_snd/src/snd_arrange.rs @@ -6,9 +6,9 @@ impl Audio for ArrangerModel { } } -pub struct ArrangerRefAudio<'a, T: ArrangerModelApi + Send + Sync>(&'a mut T); +pub struct ArrangerRefAudio<'a, T: ArrangerApi + Send + Sync>(&'a mut T); -impl<'a, T: ArrangerModelApi + Send + Sync> Audio for ArrangerRefAudio<'a, T> { +impl<'a, T: ArrangerApi + Send + Sync> Audio for ArrangerRefAudio<'a, T> { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { for track in self.0.tracks_mut().iter_mut() { if MIDIPlayerAudio::from(&mut track.player).process(client, scope) == Control::Quit { diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index ab7e19aa..7f4650e8 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -62,21 +62,21 @@ impl InputToCommand> for ArrangerAppCommand { key!(KeyCode::Enter) => Self::Focus(Enter), key!(KeyCode::Esc) => Self::Focus(Exit), key!(KeyCode::Char(' ')) => { - Self::App(Transport(TransportViewCommand::Transport(TransportCommand::Play(None)))) + Self::App(Transport(TransportCommand::Play(None))) }, _ => Self::App(match view.focused() { Content(ArrangerViewFocus::Transport) => Transport( - TransportViewCommand::input_to_command(&view.sequencer.transport, input)? + TransportCommand::input_to_command(&view.app.sequencer.transport, input)? ), Content(ArrangerViewFocus::PhraseEditor) => Editor( - PhraseEditorCommand::input_to_command(&view.sequencer.editor, input)? + PhraseEditorCommand::input_to_command(&view.app.sequencer.editor, input)? ), Content(ArrangerViewFocus::PhrasePool) => match input.event() { key!(KeyCode::Char('e')) => EditPhrase( - Some(view.sequencer.phrases.phrase().clone()) + Some(view.app.phrases.phrase().clone()) ), _ => Phrases( - PhrasePoolViewCommand::input_to_command(&view.sequencer.phrases, input)? + PhrasePoolViewCommand::input_to_command(&view.app.phrases, input)? ) }, Content(ArrangerViewFocus::Arranger) => { @@ -237,23 +237,23 @@ impl Command> for ArrangerViewCommand { /// Root view for standalone `tek_arranger` pub struct ArrangerView { - pub model: ArrangerModel, - /// Sequencer component - pub sequencer: SequencerView, - /// Height of arrangement - pub split: u16, - /// Currently selected element. - pub selected: ArrangerSelection, - /// Display mode of arranger - pub mode: ArrangerMode, - /// Background color of arrangement - pub color: ItemColor, - /// Whether the arranger is currently focused - pub focused: bool, - /// Whether this is currently in edit mode - pub entered: bool, - /// Width and height of arrangement area at last render - pub size: Measure, + jack: Arc>, + clock: Arc, + transport: jack::Transport, + metronome: bool, + transport: TransportModel, + phrases: Vec>>, + tracks: Vec, + scenes: Vec, + name: Arc>, + sequencer: SequencerView, + splits: [u16;2], + selected: ArrangerSelection, + mode: ArrangerMode, + color: ItemColor, + focused: bool, + entered: bool, + size: Measure, } /// Display mode of arranger diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index 4c6f5713..c6716102 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -72,15 +72,15 @@ impl InputToCommand> for SequencerAppCommand { /// Root view for standalone `tek_sequencer`. pub struct SequencerView { - pub model: SequencerModel, - /// Displays the JACK transport. - pub transport: TransportView, - /// Displays the phrase pool - pub phrases: PhrasePoolView, - /// Displays the phrase editor - pub editor: PhraseEditor, - /// Width of phrase pool - pub split: u16, + jack: Arc>, + clock: Arc, + transport: jack::Transport, + metronome: bool, + phrases: Vec>>, + player: MIDIPlayer, + phrases: PhrasePoolView, + editor: PhraseEditor, + split: u16, } /// Sections in the sequencer app that may be focused diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index 2476b6a2..f26eb251 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -32,48 +32,57 @@ pub type TransportAppCommand = AppViewCommand; impl InputToCommand> for TransportAppCommand { fn input_to_command (app: &TransportApp, input: &TuiInput) -> Option { - use TransportViewFocus as Focus; - use FocusCommand as FocusCmd; - use TransportCommand as Cmd; + use KeyCode::{Left, Right}; + use FocusCommand::{Prev, Next}; + use AppViewCommand::{Focus, App}; + Some(match input.event() { + key!(Left) => Focus(Prev), + key!(Right) => Focus(Next), + _ => TransportCommand::input_to_command(app.app, input).map(App) + }) + } +} + +impl InputToCommand> for TransportCommand { + fn input_to_command (app: &TransportApp, input: &TuiInput) -> Option { + use KeyCode::Char; + use AppViewFocus::Content; + use TransportCommand::{SetBpm, SetQuant, SetSync}; + use TransportViewFocus::{Bpm, Quant, Sync, PlayPause, Clock}; let clock = app.app.model.clock(); Some(match input.event() { - - key!(KeyCode::Left) => Self::Focus(FocusCmd::Prev), - key!(KeyCode::Right) => Self::Focus(FocusCmd::Next), - - key!(KeyCode::Char('.')) => Self::App(match app.focused() { - AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() + 1.0), - AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64), - AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), - AppViewFocus::Content(Focus::PlayPause) => {todo!()}, - AppViewFocus::Content(Focus::Clock) => {todo!()}, + key!(Char('.')) => match app.focused() { + Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 1.0), + Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64), + Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), + Content(PlayPause) => {todo!()}, + Content(Clock) => {todo!()}, _ => {todo!()} - }), - key!(KeyCode::Char(',')) => Self::App(match app.focused() { - AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() - 1.0), - AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64), - AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), - AppViewFocus::Content(Focus::PlayPause) => {todo!()}, - AppViewFocus::Content(Focus::Clock) => {todo!()} + }, + key!(KeyCode::Char(',')) => match app.focused() { + Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 1.0), + Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64), + Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), + Content(PlayPause) => {todo!()}, + Content(Clock) => {todo!()} _ => {todo!()} - }), - key!(KeyCode::Char('>')) => Self::App(match app.focused() { - AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() + 0.001), - AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(next_note_length(clock.quant.get()as usize)as f64), - AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), - AppViewFocus::Content(Focus::PlayPause) => {todo!()}, - AppViewFocus::Content(Focus::Clock) => {todo!()} + }, + key!(KeyCode::Char('>')) => match app.focused() { + Content(Bpm) => SetBpm(clock.timebase().bpm.get() + 0.001), + Content(Quant) => SetQuant(next_note_length(clock.quant.get()as usize)as f64), + Content(Sync) => SetSync(next_note_length(clock.sync.get()as usize)as f64+1.), + Content(PlayPause) => {todo!()}, + Content(Clock) => {todo!()} _ => {todo!()} - }), - key!(KeyCode::Char('<')) => Self::App(match app.focused() { - AppViewFocus::Content(Focus::Bpm) => Cmd::SetBpm(clock.timebase().bpm.get() - 0.001), - AppViewFocus::Content(Focus::Quant) => Cmd::SetQuant(prev_note_length(clock.quant.get()as usize)as f64), - AppViewFocus::Content(Focus::Sync) => Cmd::SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), - AppViewFocus::Content(Focus::PlayPause) => {todo!()}, - AppViewFocus::Content(Focus::Clock) => {todo!()} + }, + key!(KeyCode::Char('<')) => match app.focused() { + Content(Bpm) => SetBpm(clock.timebase().bpm.get() - 0.001), + Content(Quant) => SetQuant(prev_note_length(clock.quant.get()as usize)as f64), + Content(Sync) => SetSync(prev_note_length(clock.sync.get()as usize)as f64+1.), + Content(PlayPause) => {todo!()}, + Content(Clock) => {todo!()} _ => {todo!()} - }), - + }, _ => return None }) } @@ -81,32 +90,37 @@ impl InputToCommand> for TransportAppCommand { impl Command> for TransportAppCommand { fn execute (self, state: &mut TransportApp) -> Perhaps { - let clock = state.app.model.clock(); + use AppViewCommand::{Focus, App}; + use FocusCommand::{Next, Prev}; Ok(Some(match self { - Self::Focus(command) => Self::Focus({ - use FocusCommand::*; - match command { - Next => { todo!() }, - Prev => { todo!() }, - _ => { todo!() } - } - }), - Self::App(command) => Self::App({ - use TransportCommand::*; - match command { - SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)), - SetQuant(quant) => SetQuant(clock.quant.set(quant)), - SetSync(sync) => SetSync(clock.sync.set(sync)), - _ => { - todo!() - } - } + App(command) => if let Some(undo) = TransportCommand::execute(command, state)? { + App(undo) + } else { + return Ok(None) + }, + Focus(command) => Focus(match command { + Next => { todo!() }, + Prev => { todo!() }, + _ => { todo!() } }), _ => todo!() })) } } +impl Command> for TransportCommand { + fn execute (self, state: &mut TransportApp) -> Perhaps { + use TransportCommand::{SetBpm, SetQuant, SetSync}; + let clock = state.app.model.clock(); + Ok(Some(match self { + SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)), + SetQuant(quant) => SetQuant(clock.quant.set(quant)), + SetSync(sync) => SetSync(clock.sync.set(sync)), + _ => return Ok(None) + })) + } +} + impl From for TransportView { fn from (model: TransportModel) -> Self { Self { @@ -122,11 +136,17 @@ impl From for TransportView { /// Stores and displays time-related info. #[derive(Debug)] pub struct TransportView { - _engine: PhantomData, - pub model: TransportModel, - pub focus: TransportViewFocus, - pub focused: bool, - pub size: Measure, + _engine: PhantomData, + jack: Arc>, + /// Current sample rate, tempo, and PPQ. + clock: Arc, + /// JACK transport handle. + transport: jack::Transport, + /// Enable metronome? + metronome: bool, + focus: TransportViewFocus, + focused: bool, + size: Measure, } /// Which item of the transport toolbar is focused