From 8856353eabb2f1f8308ab0affa309ae059dc8a50 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 15 Nov 2024 20:09:49 +0100 Subject: [PATCH] wip: refactor pt.41 (57e) nice --- Cargo.lock | 10 - Cargo.toml | 1 - crates/tek_api/src/api_clock.rs | 2 +- crates/tek_api/src/api_phrase.rs | 140 +++++++++ crates/tek_api/src/api_player.rs | 100 ++++++- crates/tek_api/src/api_playhead.rs | 36 +++ crates/tek_api/src/api_pool.rs | 58 ---- crates/tek_api/src/api_scene.rs | 33 ++- crates/tek_api/src/api_track.rs | 32 ++- crates/tek_api/src/lib.rs | 370 +++++++++++++++++++++++- crates/tek_api/src/model_clock.rs | 38 --- crates/tek_api/src/model_phrase.rs | 84 ------ crates/tek_api/src/model_player.rs | 61 ---- crates/tek_api/src/model_pool.rs | 17 -- crates/tek_api/src/model_scene.rs | 32 --- crates/tek_api/src/model_track.rs | 1 - crates/tek_api/src/todo_api_mixer.rs | 16 ++ crates/tek_api/src/todo_api_plugin.rs | 59 ++++ crates/tek_api/src/todo_api_sampler.rs | 78 +++++ crates/tek_snd/Cargo.toml | 9 - crates/tek_snd/src/lib.rs | 376 ------------------------- crates/tek_snd/src/snd_arrange.rs | 20 -- crates/tek_snd/src/snd_mixer.rs | 17 -- crates/tek_snd/src/snd_plugin.rs | 60 ---- crates/tek_snd/src/snd_sampler.rs | 79 ------ crates/tek_snd/src/snd_sequencer.rs | 55 ---- crates/tek_snd/src/snd_transport.rs | 42 --- crates/tek_tui/Cargo.toml | 2 +- crates/tek_tui/src/lib.rs | 1 - crates/tek_tui/src/tui_arranger.rs | 88 +++--- crates/tek_tui/src/tui_sequencer.rs | 1 - crates/tek_tui/src/tui_transport.rs | 12 +- 32 files changed, 911 insertions(+), 1019 deletions(-) delete mode 100644 crates/tek_api/src/api_pool.rs delete mode 100644 crates/tek_api/src/model_clock.rs delete mode 100644 crates/tek_api/src/model_phrase.rs delete mode 100644 crates/tek_api/src/model_player.rs delete mode 100644 crates/tek_api/src/model_pool.rs delete mode 100644 crates/tek_api/src/model_scene.rs delete mode 100644 crates/tek_api/src/model_track.rs delete mode 100644 crates/tek_snd/Cargo.toml delete mode 100644 crates/tek_snd/src/lib.rs delete mode 100644 crates/tek_snd/src/snd_arrange.rs delete mode 100644 crates/tek_snd/src/snd_mixer.rs delete mode 100644 crates/tek_snd/src/snd_plugin.rs delete mode 100644 crates/tek_snd/src/snd_sampler.rs delete mode 100644 crates/tek_snd/src/snd_sequencer.rs delete mode 100644 crates/tek_snd/src/snd_transport.rs diff --git a/Cargo.lock b/Cargo.lock index 06345338..d347c250 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2697,15 +2697,6 @@ dependencies = [ "toml", ] -[[package]] -name = "tek_snd" -version = "0.1.0" -dependencies = [ - "livi", - "tek_api", - "tek_core", -] - [[package]] name = "tek_tui" version = "0.1.0" @@ -2715,7 +2706,6 @@ dependencies = [ "symphonia", "tek_api", "tek_core", - "tek_snd", "vst", "wavers", "winit", diff --git a/Cargo.toml b/Cargo.toml index 476c1519..bca535cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ resolver = "2" members = [ "crates/tek_core", "crates/tek_api", - "crates/tek_snd", "crates/tek_tui", "crates/tek_cli" ] diff --git a/crates/tek_api/src/api_clock.rs b/crates/tek_api/src/api_clock.rs index d66de4b9..0ed1279c 100644 --- a/crates/tek_api/src/api_clock.rs +++ b/crates/tek_api/src/api_clock.rs @@ -18,7 +18,7 @@ impl Command for ClockCommand { } } -pub trait ClockApi { +pub trait ClockApi: Send + Sync { /// Current moment in time fn current (&self) -> &Instant; /// Note quantization factor diff --git a/crates/tek_api/src/api_phrase.rs b/crates/tek_api/src/api_phrase.rs index c7b7e813..da312755 100644 --- a/crates/tek_api/src/api_phrase.rs +++ b/crates/tek_api/src/api_phrase.rs @@ -1 +1,141 @@ use crate::*; + +pub trait HasPhrases { + fn phrases (&self) -> &Vec>>; + fn phrases_mut (&mut self) -> &mut Vec>>; +} + +#[derive(Clone, PartialEq)] +pub enum PhrasePoolCommand { + Add(usize), + Delete(usize), + Duplicate(usize), + Swap(usize, usize), + RandomColor(usize), + Import(usize, String), + Export(usize, String), + SetName(usize, String), + SetLength(usize, usize), +} + +impl Command for PhrasePoolCommand { + fn execute (self, model: &mut T) -> Perhaps { + match self { + Self::Add(index) => { + //Self::Append => { view.append_new(None, None) }, + //Self::Insert => { view.insert_new(None, None) }, + }, + Self::Delete(index) => { + //if view.phrase > 0 { + //view.model.phrases.remove(view.phrase); + //view.phrase = view.phrase.min(view.model.phrases.len().saturating_sub(1)); + //} + }, + Self::Duplicate(index) => { + //let mut phrase = view.phrase().read().unwrap().duplicate(); + //phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25); + //view.phrases.insert(view.phrase + 1, Arc::new(RwLock::new(phrase))); + //view.phrase += 1; + }, + Self::Swap(index, other) => { + //Self::MoveUp => { view.move_up() }, + //Self::MoveDown => { view.move_down() }, + }, + Self::RandomColor(index) => { + //view.phrase().write().unwrap().color = ItemColorTriplet::random(); + }, + Self::Import(index, path) => { + }, + Self::Export(index, path) => { + }, + Self::SetName(index, name) => { + }, + Self::SetLength(index, length) => { + }, + } + Ok(None) + } +} + +/// 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, +} + +/// MIDI message structural +pub type PhraseData = Vec>; + +impl Phrase { + pub fn new ( + name: impl AsRef, + loop_on: bool, + length: usize, + notes: Option, + color: Option, + ) -> Self { + Self { + uuid: uuid::Uuid::new_v4(), + name: name.as_ref().to_string(), + ppq: PPQ, + length, + notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), + loop_on, + loop_start: 0, + loop_length: length, + percussive: true, + color: color.unwrap_or_else(ItemColorTriplet::random) + } + } + pub fn duplicate (&self) -> Self { + let mut clone = self.clone(); + clone.uuid = uuid::Uuid::new_v4(); + clone + } + pub fn toggle_loop (&mut self) { self.loop_on = !self.loop_on; } + pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { + if pulse >= self.length { panic!("extend phrase first") } + self.notes[pulse].push(message); + } + /// Check if a range `start..end` contains MIDI Note On `k` + pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { + //panic!("{:?} {start} {end}", &self); + for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { + for event in events.iter() { + if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } } + } + } + return false + } +} +impl Default for Phrase { + fn default () -> Self { + 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 Eq for Phrase {} diff --git a/crates/tek_api/src/api_player.rs b/crates/tek_api/src/api_player.rs index 55487421..aaa92f85 100644 --- a/crates/tek_api/src/api_player.rs +++ b/crates/tek_api/src/api_player.rs @@ -5,14 +5,13 @@ pub trait HasPlayer: HasJack { fn player_mut (&mut self) -> &mut impl PlayerApi; } -pub trait PlayerApi: MidiInputApi + MidiOutputApi {} +pub trait PlayerApi: MidiInputApi + MidiOutputApi + Send + Sync {} pub trait HasMidiBuffer { fn midi_buffer (&self) -> &Vec>>; fn midi_buffer_mut (&self) -> &mut Vec>>; fn reset (&self) -> bool; - fn reset_mut (&mut self) -> &mut bool; /// Clear the section of the output buffer that we will be using, @@ -285,3 +284,100 @@ pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { _ => {} } } + +/// Hosts the JACK callback for a single MIDI player +pub struct PlayerAudio<'a, T: PlayerApi>( + /// Player + pub &'a mut T, + /// Note buffer + pub &'a mut Vec, + /// Note chunk buffer + pub &'a mut Vec>>, +); + +/// JACK process callback for a sequencer's phrase player/recorder. +impl<'a, T: PlayerApi> Audio for PlayerAudio<'a, T> { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + let model = &mut self.0; + let note_buffer = &mut self.1; + let output_buffer = &mut self.2; + // Clear output buffer(s) + model.clear(scope, false); + // Write chunk of phrase to output, handle switchover + if model.play(scope, note_buffer, output_buffer) { + model.switchover(scope, note_buffer, output_buffer); + } + if model.has_midi_ins() { + if model.recording() || model.monitoring() { + // Record and/or monitor input + model.record(scope) + } else if model.has_midi_outs() && model.monitoring() { + // Monitor input to output + model.monitor(scope) + } + } + // Write to output port(s) + model.write(scope, output_buffer); + Control::Continue + } +} + +//#[derive(Debug)] +//pub struct MIDIPlayer { + ///// Global timebase + //pub clock: Arc, + ///// Start time and phrase being played + //pub play_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())? + //], + //}) + //} +//} diff --git a/crates/tek_api/src/api_playhead.rs b/crates/tek_api/src/api_playhead.rs index 90d64724..90ea73e3 100644 --- a/crates/tek_api/src/api_playhead.rs +++ b/crates/tek_api/src/api_playhead.rs @@ -75,3 +75,39 @@ pub trait PlayheadApi: ClockApi { *self.playing().read().unwrap() == Some(TransportState::Rolling) } } + +/// Hosts the JACK callback for updating the temporal pointer and playback status. +pub struct PlayheadAudio<'a, T: PlayheadApi>(pub &'a mut T); + +impl<'a, T: PlayheadApi> Audio for PlayheadAudio<'a, T> { + #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + let state = &mut self.0; + let times = scope.cycle_times().unwrap(); + let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times; + let _chunk_size = scope.n_frames() as usize; + let transport = state.transport().query().unwrap(); + state.current().sample.set(transport.pos.frame() as f64); + let mut playing = state.playing().write().unwrap(); + let mut started = state.started().write().unwrap(); + if *playing != Some(transport.state) { + match transport.state { + TransportState::Rolling => { + *started = Some((current_frames as usize, current_usecs as usize)) + }, + TransportState::Stopped => { + *started = None + }, + _ => {} + } + }; + *playing = Some(transport.state); + if *playing == Some(TransportState::Stopped) { + *started = None; + } + state.current().update_from_usec(match *started { + Some((_, usecs)) => current_usecs as f64 - usecs as f64, + None => 0. + }); + Control::Continue + } +} diff --git a/crates/tek_api/src/api_pool.rs b/crates/tek_api/src/api_pool.rs deleted file mode 100644 index f868600e..00000000 --- a/crates/tek_api/src/api_pool.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::*; - -pub trait HasPhrases { - fn phrases (&self) -> &Vec>>; - fn phrases_mut (&mut self) -> &mut Vec>>; -} - -#[derive(Clone, PartialEq)] -pub enum PhrasePoolCommand { - Add(usize), - Delete(usize), - Duplicate(usize), - Swap(usize, usize), - RandomColor(usize), - Import(usize, String), - Export(usize, String), - SetName(usize, String), - SetLength(usize, usize), -} - -impl Command for PhrasePoolCommand { - fn execute (self, model: &mut T) -> Perhaps { - match self { - Self::Add(index) => { - //Self::Append => { view.append_new(None, None) }, - //Self::Insert => { view.insert_new(None, None) }, - }, - Self::Delete(index) => { - //if view.phrase > 0 { - //view.model.phrases.remove(view.phrase); - //view.phrase = view.phrase.min(view.model.phrases.len().saturating_sub(1)); - //} - }, - Self::Duplicate(index) => { - //let mut phrase = view.phrase().read().unwrap().duplicate(); - //phrase.color = ItemColorTriplet::random_near(phrase.color, 0.25); - //view.phrases.insert(view.phrase + 1, Arc::new(RwLock::new(phrase))); - //view.phrase += 1; - }, - Self::Swap(index, other) => { - //Self::MoveUp => { view.move_up() }, - //Self::MoveDown => { view.move_down() }, - }, - Self::RandomColor(index) => { - //view.phrase().write().unwrap().color = ItemColorTriplet::random(); - }, - Self::Import(index, path) => { - }, - Self::Export(index, path) => { - }, - Self::SetName(index, name) => { - }, - Self::SetLength(index, length) => { - }, - } - Ok(None) - } -} diff --git a/crates/tek_api/src/api_scene.rs b/crates/tek_api/src/api_scene.rs index 838cb3d8..2a7e06e6 100644 --- a/crates/tek_api/src/api_scene.rs +++ b/crates/tek_api/src/api_scene.rs @@ -72,7 +72,7 @@ pub trait ArrangerSceneApi: Sized { Some(clip) => tracks .get(track_index) .map(|track|{ - if let Some((_, Some(phrase))) = track.player().phrase() { + if let Some((_, Some(phrase))) = track.phrase() { *phrase.read().unwrap() == *clip.read().unwrap() } else { false @@ -87,3 +87,34 @@ pub trait ArrangerSceneApi: Sized { match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None } } } + +//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, + ////}) + ////} +//} diff --git a/crates/tek_api/src/api_track.rs b/crates/tek_api/src/api_track.rs index b35ec58b..3c6e34a1 100644 --- a/crates/tek_api/src/api_track.rs +++ b/crates/tek_api/src/api_track.rs @@ -1,6 +1,6 @@ use crate::*; -pub trait HasTracks { +pub trait HasTracks: Send + Sync { fn tracks (&self) -> &Vec; fn tracks_mut (&mut self) -> &mut Vec; fn track_add (&mut self, name: Option<&str>, color: Option)-> Usually<&mut T>; @@ -31,7 +31,7 @@ pub enum ArrangerTrackCommand { //} //} -pub trait ArrangerTrackApi: Sized { +pub trait ArrangerTrackApi: PlayerApi + Send + Sync + Sized { /// Name of track fn name (&self) -> Arc>; /// Preferred width of track column @@ -40,8 +40,6 @@ pub trait ArrangerTrackApi: Sized { fn width_mut (&mut self) -> &mut usize; /// Identifying color of track fn color (&self) -> ItemColor; - /// The MIDI player for the track - fn player (&self) -> &impl PlayerApi; fn longest_name (tracks: &[Self]) -> usize { tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max) @@ -59,3 +57,29 @@ pub trait ArrangerTrackApi: Sized { } } } + +/// Hosts the JACK callback for a collection of tracks +pub struct TracksAudio<'a, T: ArrangerTrackApi, H: HasTracks>( + // Track collection + pub &'a mut H, + /// Note buffer + pub &'a mut Vec, + /// Note chunk buffer + pub &'a mut Vec>>, + /// Marker + pub PhantomData, +); + +impl<'a, T: ArrangerTrackApi, H: HasTracks> Audio for TracksAudio<'a, T, H> { + #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + let model = &mut self.0; + let note_buffer = &mut self.1; + let output_buffer = &mut self.2; + for track in model.tracks_mut().iter_mut() { + if PlayerAudio(track, note_buffer, output_buffer).process(client, scope) == Control::Quit { + return Control::Quit + } + } + Control::Continue + } +} diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index d2cd8094..5de273d5 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -16,8 +16,8 @@ submod! { api_clock api_jack api_player + api_phrase api_playhead - api_pool //api_mixer //api_channel //api_plugin @@ -30,11 +30,375 @@ submod! { //model_scene //model_track //model_clock - model_phrase + //model_phrase //model_player - model_pool + //model_pool } +pub trait JackActivate: Sized { + fn activate_with ( + self, + init: impl FnOnce(&Arc>)->Usually + ) + -> Usually>>; +} + +impl JackActivate for JackClient { + fn activate_with ( + self, + init: impl FnOnce(&Arc>)->Usually + ) + -> Usually>> + { + let client = Arc::new(RwLock::new(self)); + let target = Arc::new(RwLock::new(init(&client)?)); + let event = Box::new(move|_|{/*TODO*/}) as Box; + let events = Notifications(event); + let frame = Box::new({ + let target = target.clone(); + move|c: &_, s: &_|if let Ok(mut target) = target.write() { + target.process(c, s) + } else { + Control::Quit + } + }); + let frames = tek_core::jack::contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler); + let mut buffer = Self::Activating; + std::mem::swap(&mut*client.write().unwrap(), &mut buffer); + *client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?); + Ok(target) + } +} + +/// Trait for things that have a JACK process callback. +pub trait Audio: Send + Sync { + fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue + } + fn callback( + state: &Arc>, client: &Client, scope: &ProcessScope + ) -> Control where Self: Sized { + if let Ok(mut state) = state.write() { + state.process(client, scope) + } else { + Control::Quit + } + } +} + +/// A UI component that may be associated with a JACK client by the `Jack` factory. +pub trait AudioComponent: Component + Audio { + /// Perform type erasure for collecting heterogeneous devices. + fn boxed(self) -> Box> + where + Self: Sized + 'static, + { + Box::new(self) + } +} + +/// All things that implement the required traits can be treated as `AudioComponent`. +impl + Audio> AudioComponent for W {} + +/// Trait for things that may expose JACK ports. +pub trait Ports { + fn audio_ins(&self) -> Usually>> { + Ok(vec![]) + } + fn audio_outs(&self) -> Usually>> { + Ok(vec![]) + } + fn midi_ins(&self) -> Usually>> { + Ok(vec![]) + } + fn midi_outs(&self) -> Usually>> { + Ok(vec![]) + } +} + +fn register_ports( + client: &Client, + names: Vec, + spec: T, +) -> Usually>> { + names + .into_iter() + .try_fold(BTreeMap::new(), |mut ports, name| { + let port = client.register_port(&name, spec)?; + ports.insert(name, port); + Ok(ports) + }) +} + +fn query_ports(client: &Client, names: Vec) -> BTreeMap> { + names.into_iter().fold(BTreeMap::new(), |mut ports, name| { + let port = client.port_by_name(&name).unwrap(); + ports.insert(name, port); + ports + }) +} + +///// A [AudioComponent] bound to a JACK client and a set of ports. +//pub struct JackDevice { + ///// The active JACK client of this device. + //pub client: DynamicAsyncClient, + ///// The device state, encapsulated for sharing between threads. + //pub state: Arc>>>, + ///// Unowned copies of the device's JACK ports, for connecting to the device. + ///// The "real" readable/writable `Port`s are owned by the `state`. + //pub ports: UnownedJackPorts, +//} + +//impl std::fmt::Debug for JackDevice { + //fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + //f.debug_struct("JackDevice") + //.field("ports", &self.ports) + //.finish() + //} +//} + +//impl Widget for JackDevice { + //type Engine = E; + //fn layout(&self, to: E::Size) -> Perhaps { + //self.state.read().unwrap().layout(to) + //} + //fn render(&self, to: &mut E::Output) -> Usually<()> { + //self.state.read().unwrap().render(to) + //} +//} + +//impl Handle for JackDevice { + //fn handle(&mut self, from: &E::Input) -> Perhaps { + //self.state.write().unwrap().handle(from) + //} +//} + +//impl Ports for JackDevice { + //fn audio_ins(&self) -> Usually>> { + //Ok(self.ports.audio_ins.values().collect()) + //} + //fn audio_outs(&self) -> Usually>> { + //Ok(self.ports.audio_outs.values().collect()) + //} + //fn midi_ins(&self) -> Usually>> { + //Ok(self.ports.midi_ins.values().collect()) + //} + //fn midi_outs(&self) -> Usually>> { + //Ok(self.ports.midi_outs.values().collect()) + //} +//} + +//impl JackDevice { + ///// Returns a locked mutex of the state's contents. + //pub fn state(&self) -> LockResult>>> { + //self.state.read() + //} + ///// Returns a locked mutex of the state's contents. + //pub fn state_mut(&self) -> LockResult>>> { + //self.state.write() + //} + //pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(port, self.midi_ins()?[index])?) + //} + //pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(self.midi_outs()?[index], port)?) + //} + //pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(port, self.audio_ins()?[index])?) + //} + //pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { + //Ok(self + //.client + //.as_client() + //.connect_ports(self.audio_outs()?[index], port)?) + //} +//} + +///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut]. +//#[derive(Default, Debug)] +//pub struct JackPorts { + //pub audio_ins: BTreeMap>, + //pub midi_ins: BTreeMap>, + //pub audio_outs: BTreeMap>, + //pub midi_outs: BTreeMap>, +//} + +///// Collection of JACK ports as [Unowned]. +//#[derive(Default, Debug)] +//pub struct UnownedJackPorts { + //pub audio_ins: BTreeMap>, + //pub midi_ins: BTreeMap>, + //pub audio_outs: BTreeMap>, + //pub midi_outs: BTreeMap>, +//} + +//impl JackPorts { + //pub fn clone_unowned(&self) -> UnownedJackPorts { + //let mut unowned = UnownedJackPorts::default(); + //for (name, port) in self.midi_ins.iter() { + //unowned.midi_ins.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.midi_outs.iter() { + //unowned.midi_outs.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.audio_ins.iter() { + //unowned.audio_ins.insert(name.clone(), port.clone_unowned()); + //} + //for (name, port) in self.audio_outs.iter() { + //unowned + //.audio_outs + //.insert(name.clone(), port.clone_unowned()); + //} + //unowned + //} +//} + +///// Implement the `Ports` trait. +//#[macro_export] +//macro_rules! ports { + //($T:ty $({ $(audio: { + //$(ins: |$ai_arg:ident|$ai_impl:expr,)? + //$(outs: |$ao_arg:ident|$ao_impl:expr,)? + //})? $(midi: { + //$(ins: |$mi_arg:ident|$mi_impl:expr,)? + //$(outs: |$mo_arg:ident|$mo_impl:expr,)? + //})?})?) => { + //impl Ports for $T {$( + //$( + //$(fn audio_ins <'a> (&'a self) -> Usually>> { + //let cb = |$ai_arg:&'a Self|$ai_impl; + //cb(self) + //})? + //)? + //$( + //$(fn audio_outs <'a> (&'a self) -> Usually>> { + //let cb = (|$ao_arg:&'a Self|$ao_impl); + //cb(self) + //})? + //)? + //)? $( + //$( + //$(fn midi_ins <'a> (&'a self) -> Usually>> { + //let cb = (|$mi_arg:&'a Self|$mi_impl); + //cb(self) + //})? + //)? + //$( + //$(fn midi_outs <'a> (&'a self) -> Usually>> { + //let cb = (|$mo_arg:&'a Self|$mo_impl); + //cb(self) + //})? + //)? + //)?} + //}; +//} + +///// `JackDevice` factory. Creates JACK `Client`s, performs port registration +///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. +//pub struct Jack { + //pub client: Client, + //pub midi_ins: Vec, + //pub audio_ins: Vec, + //pub midi_outs: Vec, + //pub audio_outs: Vec, +//} + +//impl Jack { + //pub fn new(name: &str) -> Usually { + //Ok(Self { + //midi_ins: vec![], + //audio_ins: vec![], + //midi_outs: vec![], + //audio_outs: vec![], + //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, + //}) + //} + //pub fn run<'a: 'static, D, E>( + //self, + //state: impl FnOnce(JackPorts) -> Box, + //) -> Usually> + //where + //D: AudioComponent + Sized + 'static, + //E: Engine + 'static, + //{ + //let owned_ports = JackPorts { + //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, + //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, + //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, + //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, + //}; + //let midi_outs = owned_ports + //.midi_outs + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let midi_ins = owned_ports + //.midi_ins + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let audio_outs = owned_ports + //.audio_outs + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let audio_ins = owned_ports + //.audio_ins + //.values() + //.map(|p| Ok(p.name()?)) + //.collect::>>()?; + //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); + //let client = self.client.activate_async( + //Notifications(Box::new({ + //let _state = state.clone(); + //move |_event| { + //// FIXME: this deadlocks + ////state.lock().unwrap().handle(&event).unwrap(); + //} + //}) as Box), + //contrib::ClosureProcessHandler::new(Box::new({ + //let state = state.clone(); + //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) + //}) as BoxedAudioHandler), + //)?; + //Ok(JackDevice { + //ports: UnownedJackPorts { + //audio_ins: query_ports(&client.as_client(), audio_ins), + //audio_outs: query_ports(&client.as_client(), audio_outs), + //midi_ins: query_ports(&client.as_client(), midi_ins), + //midi_outs: query_ports(&client.as_client(), midi_outs), + //}, + //client, + //state, + //}) + //} + //pub fn audio_in(mut self, name: &str) -> Self { + //self.audio_ins.push(name.to_string()); + //self + //} + //pub fn audio_out(mut self, name: &str) -> Self { + //self.audio_outs.push(name.to_string()); + //self + //} + //pub fn midi_in(mut self, name: &str) -> Self { + //self.midi_ins.push(name.to_string()); + //self + //} + //pub fn midi_out(mut self, name: &str) -> Self { + //self.midi_outs.push(name.to_string()); + //self + //} +//} + //impl Command for ArrangerSceneCommand { //} //Edit(phrase) => { state.state.phrase = phrase.clone() }, diff --git a/crates/tek_api/src/model_clock.rs b/crates/tek_api/src/model_clock.rs deleted file mode 100644 index 0374824c..00000000 --- a/crates/tek_api/src/model_clock.rs +++ /dev/null @@ -1,38 +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 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 { - fn playing (&self) -> &RwLock> { - &self.playing - } - /// Global sample and usec at which playback started - fn started (&self) -> &RwLock> { - &self.started - } -} diff --git a/crates/tek_api/src/model_phrase.rs b/crates/tek_api/src/model_phrase.rs deleted file mode 100644 index 7210202a..00000000 --- a/crates/tek_api/src/model_phrase.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::*; - -/// 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, -} - -/// MIDI message structural -pub type PhraseData = Vec>; - -impl Phrase { - pub fn new ( - name: impl AsRef, - loop_on: bool, - length: usize, - notes: Option, - color: Option, - ) -> Self { - Self { - uuid: uuid::Uuid::new_v4(), - name: name.as_ref().to_string(), - ppq: PPQ, - length, - notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), - loop_on, - loop_start: 0, - loop_length: length, - percussive: true, - color: color.unwrap_or_else(ItemColorTriplet::random) - } - } - pub fn duplicate (&self) -> Self { - let mut clone = self.clone(); - clone.uuid = uuid::Uuid::new_v4(); - clone - } - pub fn toggle_loop (&mut self) { self.loop_on = !self.loop_on; } - pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { - if pulse >= self.length { panic!("extend phrase first") } - self.notes[pulse].push(message); - } - /// Check if a range `start..end` contains MIDI Note On `k` - pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { - //panic!("{:?} {start} {end}", &self); - for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { - for event in events.iter() { - if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } } - } - } - return false - } -} -impl Default for Phrase { - fn default () -> Self { - 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 Eq for Phrase {} diff --git a/crates/tek_api/src/model_player.rs b/crates/tek_api/src/model_player.rs deleted file mode 100644 index 4e906eb2..00000000 --- a/crates/tek_api/src/model_player.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::*; - -#[derive(Debug)] -pub struct MIDIPlayer { - /// Global timebase - pub clock: Arc, - /// Start time and phrase being played - pub play_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())? - ], - }) - } -} diff --git a/crates/tek_api/src/model_pool.rs b/crates/tek_api/src/model_pool.rs deleted file mode 100644 index ce8d7156..00000000 --- a/crates/tek_api/src/model_pool.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::*; - -/// Contains all phrases in a project -#[derive(Debug)] -pub struct PhrasePoolModel { - /// Phrases in the pool - pub phrases: Vec>>, -} - -impl HasPhrases for PhrasePoolModel { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} diff --git a/crates/tek_api/src/model_scene.rs b/crates/tek_api/src/model_scene.rs deleted file mode 100644 index 9b969a38..00000000 --- a/crates/tek_api/src/model_scene.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::*; - -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, - //}) - //} -} diff --git a/crates/tek_api/src/model_track.rs b/crates/tek_api/src/model_track.rs deleted file mode 100644 index c7b7e813..00000000 --- a/crates/tek_api/src/model_track.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::*; diff --git a/crates/tek_api/src/todo_api_mixer.rs b/crates/tek_api/src/todo_api_mixer.rs index 4d8cc6bc..cd8df774 100644 --- a/crates/tek_api/src/todo_api_mixer.rs +++ b/crates/tek_api/src/todo_api_mixer.rs @@ -9,3 +9,19 @@ pub struct Mixer { pub selected_track: usize, pub selected_column: usize, } + +pub struct MixerAudio { + model: Arc> +} + +impl From<&Arc>> for MixerAudio { + fn from (model: &Arc>) -> Self { + Self { model: model.clone() } + } +} + +impl Audio for MixerAudio { + fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue + } +} diff --git a/crates/tek_api/src/todo_api_plugin.rs b/crates/tek_api/src/todo_api_plugin.rs index 22334c85..3edbac42 100644 --- a/crates/tek_api/src/todo_api_plugin.rs +++ b/crates/tek_api/src/todo_api_plugin.rs @@ -53,3 +53,62 @@ impl Plugin { //Ok(jack) //} } + +pub struct PluginAudio(Arc>); + +impl From<&Arc>> for PluginAudio { + fn from (model: &Arc>) -> Self { + Self(model.clone()) + } +} + +impl Audio for PluginAudio { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + let state = &mut*self.0.write().unwrap(); + match state.plugin.as_mut() { + Some(PluginKind::LV2(LV2Plugin { + features, + ref mut instance, + ref mut input_buffer, + .. + })) => { + let urid = features.midi_urid(); + input_buffer.clear(); + for port in state.midi_ins.iter() { + let mut atom = ::livi::event::LV2AtomSequence::new( + &features, + scope.n_frames() as usize + ); + for event in port.iter(scope) { + match event.bytes.len() { + 3 => atom.push_midi_event::<3>( + event.time as i64, + urid, + &event.bytes[0..3] + ).unwrap(), + _ => {} + } + } + input_buffer.push(atom); + } + let mut outputs = vec![]; + for _ in state.midi_outs.iter() { + outputs.push(::livi::event::LV2AtomSequence::new( + &features, + scope.n_frames() as usize + )); + } + let ports = ::livi::EmptyPortConnections::new() + .with_atom_sequence_inputs(input_buffer.iter()) + .with_atom_sequence_outputs(outputs.iter_mut()) + .with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope))) + .with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))); + unsafe { + instance.run(scope.n_frames() as usize, ports).unwrap() + }; + }, + _ => {} + } + Control::Continue + } +} diff --git a/crates/tek_api/src/todo_api_sampler.rs b/crates/tek_api/src/todo_api_sampler.rs index b866676f..2976c08a 100644 --- a/crates/tek_api/src/todo_api_sampler.rs +++ b/crates/tek_api/src/todo_api_sampler.rs @@ -55,3 +55,81 @@ impl Sampler { }) } } + +pub struct SamplerAudio { + model: Arc> +} + +impl From<&Arc>> for SamplerAudio { + fn from (model: &Arc>) -> Self { + Self { model: model.clone() } + } +} + +impl Audio for SamplerAudio { + #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + self.process_midi_in(scope); + self.clear_output_buffer(); + self.process_audio_out(scope); + self.write_output_buffer(scope); + Control::Continue + } +} + +impl SamplerAudio { + + /// Create [Voice]s from [Sample]s in response to MIDI input. + pub fn process_midi_in (&mut self, scope: &ProcessScope) { + let Sampler { midi_in, mapped, voices, .. } = &*self.model.read().unwrap(); + for RawMidi { time, bytes } in midi_in.iter(scope) { + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + if let MidiMessage::NoteOn { ref key, ref vel } = message { + if let Some(sample) = mapped.get(key) { + 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.model.write().unwrap().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 Sampler { ref mut buffer, voices, output_gain, .. } = &mut*self.model.write().unwrap(); + let channel_count = buffer.len(); + 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 + //); + buffer[channel % channel_count][index] += sample * *output_gain; + } + } else { + return false + } + } + return true + }); + } + + /// Write output buffer to output ports. + pub fn write_output_buffer (&mut self, scope: &ProcessScope) { + let Sampler { ref mut audio_outs, buffer, .. } = &mut*self.model.write().unwrap(); + for (i, port) in audio_outs.iter_mut().enumerate() { + let buffer = &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_snd/Cargo.toml b/crates/tek_snd/Cargo.toml deleted file mode 100644 index 343c3219..00000000 --- a/crates/tek_snd/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "tek_snd" -edition = "2021" -version = "0.1.0" - -[dependencies] -tek_core = { path = "../tek_core" } -tek_api = { path = "../tek_api" } -livi = "0.7.4" diff --git a/crates/tek_snd/src/lib.rs b/crates/tek_snd/src/lib.rs deleted file mode 100644 index 2be477ae..00000000 --- a/crates/tek_snd/src/lib.rs +++ /dev/null @@ -1,376 +0,0 @@ -pub use tek_core::{*, jack::*}; -pub use tek_api::*; -pub(crate) use tek_core::midly::{*, live::LiveEvent, num::u7}; - -submod! { - snd_arrange - snd_mixer - snd_plugin - snd_sampler - snd_sequencer - snd_transport -} - -pub trait JackActivate: Sized { - fn activate_with ( - self, - init: impl FnOnce(&Arc>)->Usually - ) - -> Usually>>; -} - -impl JackActivate for JackClient { - fn activate_with ( - self, - init: impl FnOnce(&Arc>)->Usually - ) - -> Usually>> - { - let client = Arc::new(RwLock::new(self)); - let target = Arc::new(RwLock::new(init(&client)?)); - let event = Box::new(move|_|{/*TODO*/}) as Box; - let events = Notifications(event); - let frame = Box::new({ - let target = target.clone(); - move|c: &_, s: &_|if let Ok(mut target) = target.write() { - target.process(c, s) - } else { - Control::Quit - } - }); - let frames = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler); - let mut buffer = Self::Activating; - std::mem::swap(&mut*client.write().unwrap(), &mut buffer); - *client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?); - Ok(target) - } -} - -/// Trait for things that have a JACK process callback. -pub trait Audio: Send + Sync { - fn process(&mut self, _: &Client, _: &ProcessScope) -> Control { - Control::Continue - } - fn callback( - state: &Arc>, client: &Client, scope: &ProcessScope - ) -> Control where Self: Sized { - if let Ok(mut state) = state.write() { - state.process(client, scope) - } else { - Control::Quit - } - } -} - -/// A UI component that may be associated with a JACK client by the `Jack` factory. -pub trait AudioComponent: Component + Audio { - /// Perform type erasure for collecting heterogeneous devices. - fn boxed(self) -> Box> - where - Self: Sized + 'static, - { - Box::new(self) - } -} - -/// All things that implement the required traits can be treated as `AudioComponent`. -impl + Audio> AudioComponent for W {} - -/// Trait for things that may expose JACK ports. -pub trait Ports { - fn audio_ins(&self) -> Usually>> { - Ok(vec![]) - } - fn audio_outs(&self) -> Usually>> { - Ok(vec![]) - } - fn midi_ins(&self) -> Usually>> { - Ok(vec![]) - } - fn midi_outs(&self) -> Usually>> { - Ok(vec![]) - } -} - -fn register_ports( - client: &Client, - names: Vec, - spec: T, -) -> Usually>> { - names - .into_iter() - .try_fold(BTreeMap::new(), |mut ports, name| { - let port = client.register_port(&name, spec)?; - ports.insert(name, port); - Ok(ports) - }) -} - -fn query_ports(client: &Client, names: Vec) -> BTreeMap> { - names.into_iter().fold(BTreeMap::new(), |mut ports, name| { - let port = client.port_by_name(&name).unwrap(); - ports.insert(name, port); - ports - }) -} - -///// A [AudioComponent] bound to a JACK client and a set of ports. -//pub struct JackDevice { - ///// The active JACK client of this device. - //pub client: DynamicAsyncClient, - ///// The device state, encapsulated for sharing between threads. - //pub state: Arc>>>, - ///// Unowned copies of the device's JACK ports, for connecting to the device. - ///// The "real" readable/writable `Port`s are owned by the `state`. - //pub ports: UnownedJackPorts, -//} - -//impl std::fmt::Debug for JackDevice { - //fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - //f.debug_struct("JackDevice") - //.field("ports", &self.ports) - //.finish() - //} -//} - -//impl Widget for JackDevice { - //type Engine = E; - //fn layout(&self, to: E::Size) -> Perhaps { - //self.state.read().unwrap().layout(to) - //} - //fn render(&self, to: &mut E::Output) -> Usually<()> { - //self.state.read().unwrap().render(to) - //} -//} - -//impl Handle for JackDevice { - //fn handle(&mut self, from: &E::Input) -> Perhaps { - //self.state.write().unwrap().handle(from) - //} -//} - -//impl Ports for JackDevice { - //fn audio_ins(&self) -> Usually>> { - //Ok(self.ports.audio_ins.values().collect()) - //} - //fn audio_outs(&self) -> Usually>> { - //Ok(self.ports.audio_outs.values().collect()) - //} - //fn midi_ins(&self) -> Usually>> { - //Ok(self.ports.midi_ins.values().collect()) - //} - //fn midi_outs(&self) -> Usually>> { - //Ok(self.ports.midi_outs.values().collect()) - //} -//} - -//impl JackDevice { - ///// Returns a locked mutex of the state's contents. - //pub fn state(&self) -> LockResult>>> { - //self.state.read() - //} - ///// Returns a locked mutex of the state's contents. - //pub fn state_mut(&self) -> LockResult>>> { - //self.state.write() - //} - //pub fn connect_midi_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.midi_ins()?[index])?) - //} - //pub fn connect_midi_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.midi_outs()?[index], port)?) - //} - //pub fn connect_audio_in(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(port, self.audio_ins()?[index])?) - //} - //pub fn connect_audio_out(&self, index: usize, port: &Port) -> Usually<()> { - //Ok(self - //.client - //.as_client() - //.connect_ports(self.audio_outs()?[index], port)?) - //} -//} - -///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut]. -//#[derive(Default, Debug)] -//pub struct JackPorts { - //pub audio_ins: BTreeMap>, - //pub midi_ins: BTreeMap>, - //pub audio_outs: BTreeMap>, - //pub midi_outs: BTreeMap>, -//} - -///// Collection of JACK ports as [Unowned]. -//#[derive(Default, Debug)] -//pub struct UnownedJackPorts { - //pub audio_ins: BTreeMap>, - //pub midi_ins: BTreeMap>, - //pub audio_outs: BTreeMap>, - //pub midi_outs: BTreeMap>, -//} - -//impl JackPorts { - //pub fn clone_unowned(&self) -> UnownedJackPorts { - //let mut unowned = UnownedJackPorts::default(); - //for (name, port) in self.midi_ins.iter() { - //unowned.midi_ins.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.midi_outs.iter() { - //unowned.midi_outs.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.audio_ins.iter() { - //unowned.audio_ins.insert(name.clone(), port.clone_unowned()); - //} - //for (name, port) in self.audio_outs.iter() { - //unowned - //.audio_outs - //.insert(name.clone(), port.clone_unowned()); - //} - //unowned - //} -//} - -///// Implement the `Ports` trait. -//#[macro_export] -//macro_rules! ports { - //($T:ty $({ $(audio: { - //$(ins: |$ai_arg:ident|$ai_impl:expr,)? - //$(outs: |$ao_arg:ident|$ao_impl:expr,)? - //})? $(midi: { - //$(ins: |$mi_arg:ident|$mi_impl:expr,)? - //$(outs: |$mo_arg:ident|$mo_impl:expr,)? - //})?})?) => { - //impl Ports for $T {$( - //$( - //$(fn audio_ins <'a> (&'a self) -> Usually>> { - //let cb = |$ai_arg:&'a Self|$ai_impl; - //cb(self) - //})? - //)? - //$( - //$(fn audio_outs <'a> (&'a self) -> Usually>> { - //let cb = (|$ao_arg:&'a Self|$ao_impl); - //cb(self) - //})? - //)? - //)? $( - //$( - //$(fn midi_ins <'a> (&'a self) -> Usually>> { - //let cb = (|$mi_arg:&'a Self|$mi_impl); - //cb(self) - //})? - //)? - //$( - //$(fn midi_outs <'a> (&'a self) -> Usually>> { - //let cb = (|$mo_arg:&'a Self|$mo_impl); - //cb(self) - //})? - //)? - //)?} - //}; -//} - -///// `JackDevice` factory. Creates JACK `Client`s, performs port registration -///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`. -//pub struct Jack { - //pub client: Client, - //pub midi_ins: Vec, - //pub audio_ins: Vec, - //pub midi_outs: Vec, - //pub audio_outs: Vec, -//} - -//impl Jack { - //pub fn new(name: &str) -> Usually { - //Ok(Self { - //midi_ins: vec![], - //audio_ins: vec![], - //midi_outs: vec![], - //audio_outs: vec![], - //client: Client::new(name, ClientOptions::NO_START_SERVER)?.0, - //}) - //} - //pub fn run<'a: 'static, D, E>( - //self, - //state: impl FnOnce(JackPorts) -> Box, - //) -> Usually> - //where - //D: AudioComponent + Sized + 'static, - //E: Engine + 'static, - //{ - //let owned_ports = JackPorts { - //audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?, - //audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?, - //midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?, - //midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?, - //}; - //let midi_outs = owned_ports - //.midi_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let midi_ins = owned_ports - //.midi_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_outs = owned_ports - //.audio_outs - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let audio_ins = owned_ports - //.audio_ins - //.values() - //.map(|p| Ok(p.name()?)) - //.collect::>>()?; - //let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); - //let client = self.client.activate_async( - //Notifications(Box::new({ - //let _state = state.clone(); - //move |_event| { - //// FIXME: this deadlocks - ////state.lock().unwrap().handle(&event).unwrap(); - //} - //}) as Box), - //contrib::ClosureProcessHandler::new(Box::new({ - //let state = state.clone(); - //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) - //}) as BoxedAudioHandler), - //)?; - //Ok(JackDevice { - //ports: UnownedJackPorts { - //audio_ins: query_ports(&client.as_client(), audio_ins), - //audio_outs: query_ports(&client.as_client(), audio_outs), - //midi_ins: query_ports(&client.as_client(), midi_ins), - //midi_outs: query_ports(&client.as_client(), midi_outs), - //}, - //client, - //state, - //}) - //} - //pub fn audio_in(mut self, name: &str) -> Self { - //self.audio_ins.push(name.to_string()); - //self - //} - //pub fn audio_out(mut self, name: &str) -> Self { - //self.audio_outs.push(name.to_string()); - //self - //} - //pub fn midi_in(mut self, name: &str) -> Self { - //self.midi_ins.push(name.to_string()); - //self - //} - //pub fn midi_out(mut self, name: &str) -> Self { - //self.midi_outs.push(name.to_string()); - //self - //} -//} diff --git a/crates/tek_snd/src/snd_arrange.rs b/crates/tek_snd/src/snd_arrange.rs deleted file mode 100644 index 3c9028a4..00000000 --- a/crates/tek_snd/src/snd_arrange.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::*; - -impl Audio for ArrangerModel { - fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - ArrangerRefAudio(self).process(client, scope) - } -} - -pub struct ArrangerRefAudio<'a, T: ArrangerApi + Send + Sync>(&'a mut 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 { - return Control::Quit - } - } - Control::Continue - } -} diff --git a/crates/tek_snd/src/snd_mixer.rs b/crates/tek_snd/src/snd_mixer.rs deleted file mode 100644 index e799ace9..00000000 --- a/crates/tek_snd/src/snd_mixer.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::*; - -pub struct MixerAudio { - model: Arc> -} - -impl From<&Arc>> for MixerAudio { - fn from (model: &Arc>) -> Self { - Self { model: model.clone() } - } -} - -impl Audio for MixerAudio { - fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { - Control::Continue - } -} diff --git a/crates/tek_snd/src/snd_plugin.rs b/crates/tek_snd/src/snd_plugin.rs deleted file mode 100644 index e003173b..00000000 --- a/crates/tek_snd/src/snd_plugin.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::*; - -pub struct PluginAudio(Arc>); - -impl From<&Arc>> for PluginAudio { - fn from (model: &Arc>) -> Self { - Self(model.clone()) - } -} - -impl Audio for PluginAudio { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let state = &mut*self.0.write().unwrap(); - match state.plugin.as_mut() { - Some(PluginKind::LV2(LV2Plugin { - features, - ref mut instance, - ref mut input_buffer, - .. - })) => { - let urid = features.midi_urid(); - input_buffer.clear(); - for port in state.midi_ins.iter() { - let mut atom = ::livi::event::LV2AtomSequence::new( - &features, - scope.n_frames() as usize - ); - for event in port.iter(scope) { - match event.bytes.len() { - 3 => atom.push_midi_event::<3>( - event.time as i64, - urid, - &event.bytes[0..3] - ).unwrap(), - _ => {} - } - } - input_buffer.push(atom); - } - let mut outputs = vec![]; - for _ in state.midi_outs.iter() { - outputs.push(::livi::event::LV2AtomSequence::new( - &features, - scope.n_frames() as usize - )); - } - let ports = ::livi::EmptyPortConnections::new() - .with_atom_sequence_inputs(input_buffer.iter()) - .with_atom_sequence_outputs(outputs.iter_mut()) - .with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope))) - .with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))); - unsafe { - instance.run(scope.n_frames() as usize, ports).unwrap() - }; - }, - _ => {} - } - Control::Continue - } -} diff --git a/crates/tek_snd/src/snd_sampler.rs b/crates/tek_snd/src/snd_sampler.rs deleted file mode 100644 index 44f6ece6..00000000 --- a/crates/tek_snd/src/snd_sampler.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::*; - -pub struct SamplerAudio { - model: Arc> -} - -impl From<&Arc>> for SamplerAudio { - fn from (model: &Arc>) -> Self { - Self { model: model.clone() } - } -} - -impl Audio for SamplerAudio { - #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - self.process_midi_in(scope); - self.clear_output_buffer(); - self.process_audio_out(scope); - self.write_output_buffer(scope); - Control::Continue - } -} - -impl SamplerAudio { - - /// Create [Voice]s from [Sample]s in response to MIDI input. - pub fn process_midi_in (&mut self, scope: &ProcessScope) { - let Sampler { midi_in, mapped, voices, .. } = &*self.model.read().unwrap(); - for RawMidi { time, bytes } in midi_in.iter(scope) { - if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { - if let MidiMessage::NoteOn { ref key, ref vel } = message { - if let Some(sample) = mapped.get(key) { - 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.model.write().unwrap().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 Sampler { ref mut buffer, voices, output_gain, .. } = &mut*self.model.write().unwrap(); - let channel_count = buffer.len(); - 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 - //); - buffer[channel % channel_count][index] += sample * *output_gain; - } - } else { - return false - } - } - return true - }); - } - - /// Write output buffer to output ports. - pub fn write_output_buffer (&mut self, scope: &ProcessScope) { - let Sampler { ref mut audio_outs, buffer, .. } = &mut*self.model.write().unwrap(); - for (i, port) in audio_outs.iter_mut().enumerate() { - let buffer = &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_snd/src/snd_sequencer.rs b/crates/tek_snd/src/snd_sequencer.rs deleted file mode 100644 index f4d85c11..00000000 --- a/crates/tek_snd/src/snd_sequencer.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::*; - -impl Audio for SequencerModel { - fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - SequencerRefAudio(self).process(client, scope) - } -} - -pub struct SequencerRefAudio<'a, T: SequencerModelApi + Send + Sync>(&'a mut T); - -impl<'a, T: SequencerModelApi + Send + Sync> Audio for SequencerRefAudio<'a, T> { - #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - if TransportRefAudio(&mut*self.0).process(client, scope) == Control::Quit { - return Control::Quit - } - if MIDIPlayerAudio::from(&mut*self.0.player_mut()).process(client, scope) == Control::Quit { - return Control::Quit - } - Control::Continue - } -} - -pub struct MIDIPlayerAudio<'a>(&'a mut MIDIPlayer); - -impl<'a> From<&'a mut MIDIPlayer> for MIDIPlayerAudio<'a> { - fn from (model: &'a mut MIDIPlayer) -> Self { - Self(model) - } -} - -/// JACK process callback for a sequencer's phrase player/recorder. -impl<'a> Audio for MIDIPlayerAudio<'a> { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let has_midi_outputs = self.0.has_midi_outputs(); - let has_midi_inputs = self.0.has_midi_inputs(); - // Clear output buffer(s) - self.0.clear(scope, false); - // Write chunk of phrase to output, handle switchover - if self.0.play(scope) { - self.0.switchover(scope); - } - if has_midi_inputs { - if self.0.recording || self.0.monitoring { - // Record and/or monitor input - self.0.record(scope) - } else if has_midi_outputs && self.0.monitoring { - // Monitor input to output - self.0.monitor(scope) - } - } - // Write to output port(s) - self.0.write(scope); - Control::Continue - } -} diff --git a/crates/tek_snd/src/snd_transport.rs b/crates/tek_snd/src/snd_transport.rs deleted file mode 100644 index 9c49240d..00000000 --- a/crates/tek_snd/src/snd_transport.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::*; - -impl Audio for TransportModel { - fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - TransportRefAudio(self).process(client, scope) - } -} - -pub struct TransportRefAudio<'a, T: TransportModelApi + Send + Sync>(pub(crate) &'a mut T); - -impl<'a, T: TransportModelApi + Send + Sync> Audio for TransportRefAudio<'a, T> { - #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let state = &mut self.0; - let times = scope.cycle_times().unwrap(); - let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times; - let _chunk_size = scope.n_frames() as usize; - let transport = state.transport().query().unwrap(); - state.clock().current.sample.set(transport.pos.frame() as f64); - let mut playing = state.clock().playing.write().unwrap(); - let mut started = state.clock().started.write().unwrap(); - if *playing != Some(transport.state) { - match transport.state { - TransportState::Rolling => { - *started = Some((current_frames as usize, current_usecs as usize)) - }, - TransportState::Stopped => { - *started = None - }, - _ => {} - } - }; - *playing = Some(transport.state); - if *playing == Some(TransportState::Stopped) { - *started = None; - } - state.clock().current.update_from_usec(match *started { - Some((_, usecs)) => current_usecs as f64 - usecs as f64, - None => 0. - }); - Control::Continue - } -} diff --git a/crates/tek_tui/Cargo.toml b/crates/tek_tui/Cargo.toml index 9e38b995..7338912b 100644 --- a/crates/tek_tui/Cargo.toml +++ b/crates/tek_tui/Cargo.toml @@ -6,7 +6,7 @@ version = "0.1.0" [dependencies] tek_core = { path = "../tek_core" } tek_api = { path = "../tek_api" } -tek_snd = { path = "../tek_snd" } +#tek_snd = { path = "../tek_snd" } livi = "0.7.4" suil-rs = { path = "../suil" } diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 90b3c22c..a9166c08 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -2,7 +2,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_api::*; -pub(crate) use tek_snd::*; pub(crate) use std::collections::BTreeMap; pub(crate) use std::sync::{Arc, Mutex, RwLock}; diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index 2d7cc6bf..c4b0c7a4 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -3,17 +3,15 @@ use crate::*; impl TryFrom<&Arc>> for ArrangerApp { type Error = Box; fn try_from (jack: &Arc>) -> Usually { - Ok(Self::new(ArrangerModel { + Ok(Self::new(ArrangerView { name: Arc::new(RwLock::new(String::new())), phrases: vec![], scenes: vec![], tracks: vec![], - transport: TransportModel { - metronome: false, - transport: jack.read().unwrap().transport(), - clock: Arc::new(Clock::from(Instant::default())), - jack: jack.clone(), - }, + metronome: false, + transport: jack.read().unwrap().transport(), + clock: Arc::new(Clock::from(Instant::default())), + jack: jack.clone(), }.into(), None, None)) } } @@ -25,6 +23,12 @@ pub type ArrangerApp = AppView< ArrangerStatusBar >; +impl Audio for ArrangerApp { + fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { + ArrangerRefAudio(self.app).process(client, scope) + } +} + /// Handle top-level events in standalone arranger. impl Handle for ArrangerApp { fn handle (&mut self, i: &TuiInput) -> Perhaps { @@ -39,10 +43,10 @@ pub enum ArrangerViewCommand { Scene(ArrangerSceneCommand), Track(ArrangerTrackCommand), Clip(ArrangerClipCommand), - Edit(ArrangerCommand), Select(ArrangerSelection), Zoom(usize), - Transport(TransportCommand), + Clock(ClockCommand), + Playhead(PlayheadCommand), Phrases(PhrasePoolViewCommand), Editor(PhraseEditorCommand), EditPhrase(Option>>), @@ -135,63 +139,63 @@ impl InputToCommand> for ArrangerAppCommand { key!(KeyCode::Char(',')) => match view.selected { ArrangerSelection::Mix => Zoom(0), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + ArrangerSelection::Track(t) => Track(Track::Swap(t, t - 1)), + ArrangerSelection::Scene(s) => Scene(Scene::Swap(s, s - 1)), + ArrangerSelection::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Char('.')) => match view.selected { ArrangerSelection::Mix => Zoom(0), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + ArrangerSelection::Track(t) => Track(Track::Swap(t, t + 1)), + ArrangerSelection::Scene(s) => Scene(Scene::Swap(s, s + 1)), + ArrangerSelection::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Char('<')) => match view.selected { ArrangerSelection::Mix => Zoom(0), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t - 1))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s - 1))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + ArrangerSelection::Track(t) => Track(Track::Swap(t, t - 1)), + ArrangerSelection::Scene(s) => Scene(Scene::Swap(s, s - 1)), + ArrangerSelection::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Char('>')) => match view.selected { ArrangerSelection::Mix => Zoom(0), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Swap(t, t + 1))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Swap(s, s + 1))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + ArrangerSelection::Track(t) => Track(Track::Swap(t, t + 1)), + ArrangerSelection::Scene(s) => Scene(Scene::Swap(s, s + 1)), + ArrangerSelection::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, key!(KeyCode::Enter) => match view.selected { ArrangerSelection::Mix => return None, ArrangerSelection::Track(t) => return None, - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Play(s))), + ArrangerSelection::Scene(s) => Scene(Scene::Play(s)), ArrangerSelection::Clip(t, s) => return None, }, key!(KeyCode::Delete) => match view.selected { ArrangerSelection::Mix => Edit(Model::Clear), - ArrangerSelection::Track(t) => Edit(Model::Track(Track::Delete(t))), - ArrangerSelection::Scene(s) => Edit(Model::Scene(Scene::Delete(s))), - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + ArrangerSelection::Track(t) => Track(Track::Delete(t)), + ArrangerSelection::Scene(s) => Scene(Scene::Delete(s)), + ArrangerSelection::Clip(t, s) => Clip(Clip::Set(t, s, None)), }, - key!(KeyCode::Char('c')) => Edit(Model::Clip(Clip::RandomColor)), + key!(KeyCode::Char('c')) => Clip(Clip::RandomColor), key!(KeyCode::Char('s')) => match view.selected { - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Set(t, s, None))), + ArrangerSelection::Clip(t, s) => Clip(Clip::Set(t, s, None)), _ => return None, }, key!(KeyCode::Char('g')) => match view.selected { - ArrangerSelection::Clip(t, s) => Edit(Model::Clip(Clip::Get(t, s))), + ArrangerSelection::Clip(t, s) => Clip(Clip::Get(t, s)), _ => return None, }, - key!(Ctrl-KeyCode::Char('a')) => Edit(Model::Scene(Scene::Add)), + key!(Ctrl-KeyCode::Char('a')) => Scene(Scene::Add), - key!(Ctrl-KeyCode::Char('t')) => Edit(Model::Track(Track::Add)), + key!(Ctrl-KeyCode::Char('t')) => Track(Track::Add), - key!(KeyCode::Char('l')) => Edit(Model::Clip(Clip::SetLoop(false))), + key!(KeyCode::Char('l')) => Clip(Clip::SetLoop(false)), _ => return None } @@ -220,17 +224,15 @@ impl Command> for ArrangerViewCommand { fn execute (self, state: &mut ArrangerApp) -> Perhaps { use ArrangerViewCommand::*; match self { - Scene(cmd) => { delegate(cmd, Scene, &mut state.app) }, - Track(cmd) => { delegate(cmd, Track, &mut state.app) }, - Clip(cmd) => { delegate(cmd, Clip, &mut state.app) }, - Phrases(cmd) => { delegate(cmd, Phrases, &mut state.app) }, - Editor(cmd) => { delegate(cmd, Editor, &mut state.app) }, - Transport(cmd) => { delegate(cmd, Transport, &mut state.app) }, + Scene(cmd) => { delegate(cmd, Scene, &mut state.app) }, + Track(cmd) => { delegate(cmd, Track, &mut state.app) }, + Clip(cmd) => { delegate(cmd, Clip, &mut state.app) }, + Phrases(cmd) => { delegate(cmd, Phrases, &mut state.app) }, + Editor(cmd) => { delegate(cmd, Editor, &mut state.app) }, + Clock(cmd) => { delegate(cmd, Clock, &mut state.app) }, + Playhead(cmd) => { delegate(cmd, Playhead, &mut state.app) }, Zoom(zoom) => { todo!(); }, Select(selected) => { state.selected = selected; Ok(None) }, - Edit(command) => { - return Ok(command.execute(&mut state.model)?.map(ArrangerViewCommand::Edit)) - }, EditPhrase(phrase) => { state.sequencer.editor.phrase = phrase.clone(); state.focus(ArrangerViewFocus::PhraseEditor); @@ -1378,6 +1380,8 @@ pub struct ArrangerScene { pub color: ItemColor, } +impl ArrangerSceneApi for ArrangerTrack {} + #[derive(Debug)] pub struct ArrangerTrack { /// Name of track @@ -1386,8 +1390,6 @@ pub struct ArrangerTrack { pub width: usize, /// Identifying color of track pub color: ItemColor, - /// The MIDI player for the track - pub player: MIDIPlayer /// Start time and phrase being played play_phrase: Option<(Instant, Option>>)>, /// Start time and next phrase @@ -1413,3 +1415,5 @@ pub struct ArrangerTrack { /// Notes currently held at output notes_out: Arc>, } + +impl ArrangerTrackApi for ArrangerTrack {} diff --git a/crates/tek_tui/src/tui_sequencer.rs b/crates/tek_tui/src/tui_sequencer.rs index 8d76f3b8..c3baf63d 100644 --- a/crates/tek_tui/src/tui_sequencer.rs +++ b/crates/tek_tui/src/tui_sequencer.rs @@ -78,7 +78,6 @@ pub struct SequencerView { current: Instant, quant: Quantize, sync: LaunchSync, - clock: Arc, transport: jack::Transport, metronome: bool, phrases: Vec>>, diff --git a/crates/tek_tui/src/tui_transport.rs b/crates/tek_tui/src/tui_transport.rs index a66a8229..9b999f54 100644 --- a/crates/tek_tui/src/tui_transport.rs +++ b/crates/tek_tui/src/tui_transport.rs @@ -8,7 +8,7 @@ impl TryFrom<&Arc>> for TransportApp { metronome: false, transport: jack.read().unwrap().transport(), jack: jack.clone(), - clock: Arc::new(Clock::from(Instant::default())) + clock: Arc::new(Clock::from(Instant::default())), focused: false, focus: TransportViewFocus::PlayPause, size: Measure::new(), @@ -33,6 +33,12 @@ impl Handle for TransportApp { pub type TransportAppCommand = AppViewCommand; +#[derive(Clone, Debug)] +pub enum TransportCommand { + Clock(ClockCommand), + Playhead(PlayheadCommand), +} + impl InputToCommand> for TransportAppCommand { fn input_to_command (app: &TransportApp, input: &TuiInput) -> Option { use KeyCode::{Left, Right}; @@ -50,7 +56,7 @@ 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 ClockCommand::{SetBpm, SetQuant, SetSync}; use TransportViewFocus::{Bpm, Quant, Sync, PlayPause, Clock}; let clock = app.app.model.clock(); Some(match input.event() { @@ -113,7 +119,7 @@ impl Command> for TransportAppCommand { impl Command> for TransportCommand { fn execute (self, state: &mut TransportApp) -> Perhaps { - use TransportCommand::{SetBpm, SetQuant, SetSync}; + use ClockCommand::{SetBpm, SetQuant, SetSync}; let clock = state.app.model.clock(); Ok(Some(match self { SetBpm(bpm) => SetBpm(clock.timebase().bpm.set(bpm)),