From 1261b07aa2aae284b0323a0cca83c4170366a5d4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 18 Dec 2024 15:50:27 +0100 Subject: [PATCH] refactor some of the larger modules --- crates/tek/src/api.rs | 3 - crates/tek/src/api/note.rs | 110 ------ crates/tek/src/api/player.rs | 523 ----------------------------- crates/tek/src/core.rs | 1 - crates/tek/src/core/audio.rs | 181 ---------- crates/tek/src/core/collect.rs | 0 crates/tek/src/core/perf.rs | 2 +- crates/tek/src/{api => }/jack.rs | 226 +------------ crates/tek/src/jack/activate.rs | 50 +++ crates/tek/src/jack/audio.rs | 78 +++++ crates/tek/src/jack/client.rs | 61 ++++ crates/tek/src/jack/from_jack.rs | 13 + crates/tek/src/jack/jack_event.rs | 69 ++++ crates/tek/src/jack/ports.rs | 39 +++ crates/tek/src/lib.rs | 34 +- crates/tek/src/midi.rs | 36 ++ crates/tek/src/midi/midi_in.rs | 10 + crates/tek/src/midi/midi_launch.rs | 35 ++ crates/tek/src/midi/midi_note.rs | 110 ++++++ crates/tek/src/midi/midi_out.rs | 12 + crates/tek/src/midi/midi_play.rs | 134 ++++++++ crates/tek/src/midi/midi_player.rs | 255 ++++++++++++++ crates/tek/src/midi/midi_rec.rs | 75 +++++ crates/tek/src/tui/app_arranger.rs | 63 ++++ 24 files changed, 1070 insertions(+), 1050 deletions(-) delete mode 100644 crates/tek/src/api/player.rs delete mode 100644 crates/tek/src/core/audio.rs delete mode 100644 crates/tek/src/core/collect.rs rename crates/tek/src/{api => }/jack.rs (54%) create mode 100644 crates/tek/src/jack/activate.rs create mode 100644 crates/tek/src/jack/audio.rs create mode 100644 crates/tek/src/jack/client.rs create mode 100644 crates/tek/src/jack/from_jack.rs create mode 100644 crates/tek/src/jack/jack_event.rs create mode 100644 crates/tek/src/jack/ports.rs create mode 100644 crates/tek/src/midi.rs create mode 100644 crates/tek/src/midi/midi_in.rs create mode 100644 crates/tek/src/midi/midi_launch.rs create mode 100644 crates/tek/src/midi/midi_note.rs create mode 100644 crates/tek/src/midi/midi_out.rs create mode 100644 crates/tek/src/midi/midi_play.rs create mode 100644 crates/tek/src/midi/midi_player.rs create mode 100644 crates/tek/src/midi/midi_rec.rs diff --git a/crates/tek/src/api.rs b/crates/tek/src/api.rs index 96cc6722..4a83fd07 100644 --- a/crates/tek/src/api.rs +++ b/crates/tek/src/api.rs @@ -1,8 +1,5 @@ -mod jack; pub(crate) use self::jack::*; mod phrase; pub(crate) use phrase::*; mod clock; pub(crate) use clock::*; -mod note; pub(crate) use note::*; -mod player; pub(crate) use player::*; mod scene; pub(crate) use scene::*; mod track; pub(crate) use track::*; mod sampler; pub(crate) use sampler::*; diff --git a/crates/tek/src/api/note.rs b/crates/tek/src/api/note.rs index abbc4bc0..e69de29b 100644 --- a/crates/tek/src/api/note.rs +++ b/crates/tek/src/api/note.rs @@ -1,110 +0,0 @@ -use crate::*; -use Ordering::Relaxed; - -pub trait MidiViewport: MidiRange + MidiPoint + HasSize { - /// Make sure cursor is within range - fn autoscroll (&self) { - let note_lo = self.note_lo(); - let note_axis = self.note_axis(); - let note_hi = self.note_hi(); - let note_point = self.note_point().min(127); - if note_point < note_lo { - self.set_note_lo(note_point); - } else if note_point > note_hi { - self.set_note_lo((note_lo + note_point).saturating_sub(note_hi)); - } - } - /// Make sure best usage of screen space is achieved by default - fn autozoom (&self) { - } -} - -#[derive(Debug, Clone)] -pub struct MidiRangeModel { - /// Length of visible time axis - pub time_axis: Arc, - /// Earliest time displayed - pub time_start: Arc, - /// Time step - pub time_zoom: Arc, - /// Auto rezoom to fit in time axis - pub time_lock: Arc, - /// Length of visible note axis - pub note_axis: Arc, - // Lowest note displayed - pub note_lo: Arc, -} -impl From<(usize, bool)> for MidiRangeModel { - fn from ((time_zoom, time_lock): (usize, bool)) -> Self { - Self { - note_axis: Arc::new(0.into()), - note_lo: Arc::new(0.into()), - time_axis: Arc::new(0.into()), - time_start: Arc::new(0.into()), - time_zoom: Arc::new(time_zoom.into()), - time_lock: Arc::new(time_lock.into()), - } - } -} -pub trait MidiRange { - fn time_zoom (&self) -> usize; - fn set_time_zoom (&mut self, x: usize); - fn time_lock (&self) -> bool; - fn set_time_lock (&self, x: bool); - fn time_start (&self) -> usize; - fn set_time_start (&self, x: usize); - fn note_lo (&self) -> usize; - fn set_note_lo (&self, x: usize); - fn note_axis (&self) -> usize; - fn time_axis (&self) -> usize; - fn note_hi (&self) -> usize { (self.note_lo() + self.note_axis().saturating_sub(1)).min(127) } - fn time_end (&self) -> usize { self.time_start() + self.time_axis() * self.time_zoom() } -} -impl MidiRange for MidiRangeModel { - fn time_zoom (&self) -> usize { self.time_zoom.load(Relaxed) } - fn set_time_zoom (&mut self, x: usize) { self.time_zoom.store(x, Relaxed); } - fn time_lock (&self) -> bool { self.time_lock.load(Relaxed) } - fn set_time_lock (&self, x: bool) { self.time_lock.store(x, Relaxed); } - fn time_start (&self) -> usize { self.time_start.load(Relaxed) } - fn set_time_start (&self, x: usize) { self.time_start.store(x, Relaxed); } - fn note_lo (&self) -> usize { self.note_lo.load(Relaxed).min(127) } - fn set_note_lo (&self, x: usize) { self.note_lo.store(x.min(127), Relaxed); } - fn note_axis (&self) -> usize { self.note_axis.load(Relaxed) } - fn time_axis (&self) -> usize { self.time_axis.load(Relaxed) } -} - -#[derive(Debug, Clone)] -pub struct MidiPointModel { - /// Time coordinate of cursor - pub time_point: Arc, - /// Note coordinate of cursor - pub note_point: Arc, - /// Length of note that will be inserted, in pulses - pub note_len: Arc, -} -impl Default for MidiPointModel { - fn default () -> Self { - Self { - time_point: Arc::new(0.into()), - note_point: Arc::new(36.into()), - note_len: Arc::new(24.into()), - } - } -} -pub trait MidiPoint { - fn note_len (&self) -> usize; - fn set_note_len (&self, x: usize); - fn note_point (&self) -> usize; - fn set_note_point (&self, x: usize); - fn time_point (&self) -> usize; - fn set_time_point (&self, x: usize); - fn note_end (&self) -> usize { self.note_point() + self.note_len() } -} -impl MidiPoint for MidiPointModel { - fn note_len (&self) -> usize { self.note_len.load(Relaxed)} - fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } - fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } - fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } - fn time_point (&self) -> usize { self.time_point.load(Relaxed) } - fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } -} diff --git a/crates/tek/src/api/player.rs b/crates/tek/src/api/player.rs deleted file mode 100644 index ff11e44f..00000000 --- a/crates/tek/src/api/player.rs +++ /dev/null @@ -1,523 +0,0 @@ -use crate::*; - -pub trait HasPlayer { - fn player (&self) -> &impl MidiPlayerApi; - fn player_mut (&mut self) -> &mut impl MidiPlayerApi; -} - -#[macro_export] macro_rules! has_player { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasPlayer for $Struct $(<$($L),*$($T),*>)? { - fn player (&$self) -> &impl MidiPlayerApi { &$cb } - fn player_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb } - } - } -} - -/// Contains state for playing a phrase -pub struct PhrasePlayerModel { - /// State of clock and playhead - pub(crate) clock: ClockModel, - /// Start time and phrase being played - pub(crate) play_phrase: Option<(Moment, Option>>)>, - /// Start time and next phrase - pub(crate) next_phrase: Option<(Moment, Option>>)>, - /// Play input through output. - pub(crate) monitoring: bool, - /// Write input to sequence. - pub(crate) recording: bool, - /// Overdub input to sequence. - pub(crate) overdub: bool, - /// Send all notes off - pub(crate) reset: bool, // TODO?: after Some(nframes) - /// Record from MIDI ports to current sequence. - pub midi_ins: Vec>, - /// Play from current sequence to MIDI ports - pub midi_outs: Vec>, - /// Notes currently held at input - pub(crate) notes_in: Arc>, - /// Notes currently held at output - pub(crate) notes_out: Arc>, - /// MIDI output buffer - pub note_buf: Vec, -} - -impl std::fmt::Debug for PhrasePlayerModel { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("PhrasePlayerModel") - .field("clock", &self.clock) - .field("play_phrase", &self.play_phrase) - .field("next_phrase", &self.next_phrase) - .finish() - } -} - -impl From<&ClockModel> for PhrasePlayerModel { - fn from (clock: &ClockModel) -> Self { - Self { - clock: clock.clone(), - midi_ins: vec![], - midi_outs: vec![], - note_buf: vec![0;8], - reset: true, - recording: false, - monitoring: false, - overdub: false, - play_phrase: None, - next_phrase: None, - notes_in: RwLock::new([false;128]).into(), - notes_out: RwLock::new([false;128]).into(), - } - } -} - -impl From<(&ClockModel, &Arc>)> for PhrasePlayerModel { - fn from ((clock, phrase): (&ClockModel, &Arc>)) -> Self { - let mut model = Self::from(clock); - model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone()))); - model - } -} - -has_clock!(|self:PhrasePlayerModel|&self.clock); - -impl HasMidiIns for PhrasePlayerModel { - fn midi_ins (&self) -> &Vec> { - &self.midi_ins - } - fn midi_ins_mut (&mut self) -> &mut Vec> { - &mut self.midi_ins - } -} - -impl HasMidiOuts for PhrasePlayerModel { - fn midi_outs (&self) -> &Vec> { - &self.midi_outs - } - fn midi_outs_mut (&mut self) -> &mut Vec> { - &mut self.midi_outs - } - fn midi_note (&mut self) -> &mut Vec { - &mut self.note_buf - } -} - -pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} - -impl MidiPlayerApi for PhrasePlayerModel {} - -pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { - fn notes_in (&self) -> &Arc>; - - fn recording (&self) -> bool; - fn recording_mut (&mut self) -> &mut bool; - fn toggle_record (&mut self) { - *self.recording_mut() = !self.recording(); - } - fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { - let sample0 = scope.last_frame_time() as usize; - // For highlighting keys and note repeat - let notes_in = self.notes_in().clone(); - if self.clock().is_rolling() { - if let Some((started, ref phrase)) = self.play_phrase().clone() { - let start = started.sample.get() as usize; - let quant = self.clock().quant.get(); - let timebase = self.clock().timebase().clone(); - let monitoring = self.monitoring(); - let recording = self.recording(); - for input in self.midi_ins_mut().iter() { - for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - if monitoring { - midi_buf[sample].push(bytes.to_vec()) - } - if 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 = timebase.samples_to_pulse(sample); - let quantized = (pulse / quant).round() * quant; - let looped = quantized as usize % length; - looped - }, message); - } - } - update_keys(&mut*notes_in.write().unwrap(), &message); - } - } - } - } - if let Some((start_at, phrase)) = &self.next_phrase() { - // TODO switch to next phrase and record into it - } - } - } - - fn monitoring (&self) -> bool; - fn monitoring_mut (&mut self) -> &mut bool; - fn toggle_monitor (&mut self) { - *self.monitoring_mut() = !self.monitoring(); - } - fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { - // For highlighting keys and note repeat - let notes_in = self.notes_in().clone(); - for input in self.midi_ins_mut().iter() { - for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - midi_buf[sample].push(bytes.to_vec()); - update_keys(&mut*notes_in.write().unwrap(), &message); - } - } - } - } - - fn overdub (&self) -> bool; - fn overdub_mut (&mut self) -> &mut bool; - fn toggle_overdub (&mut self) { - *self.overdub_mut() = !self.overdub(); - } -} - -impl MidiRecordApi for PhrasePlayerModel { - fn recording (&self) -> bool { - self.recording - } - fn recording_mut (&mut self) -> &mut bool { - &mut self.recording - } - fn monitoring (&self) -> bool { - self.monitoring - } - fn monitoring_mut (&mut self) -> &mut bool { - &mut self.monitoring - } - fn overdub (&self) -> bool { - self.overdub - } - fn overdub_mut (&mut self) -> &mut bool { - &mut self.overdub - } - fn notes_in (&self) -> &Arc> { - &self.notes_in - } -} - -pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { - - fn notes_out (&self) -> &Arc>; - - /// Clear the section of the output buffer that we will be using, - /// emitting "all notes off" at start of buffer if requested. - fn clear ( - &mut self, scope: &ProcessScope, out_buf: &mut Vec>>, reset: bool - ) { - for frame in &mut out_buf[0..scope.n_frames() as usize] { - frame.clear(); - } - if reset { - all_notes_off(out_buf); - } - } - - /// Output notes from phrase to MIDI output ports. - fn play ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out_buf: &mut Vec>> - ) -> bool { - let mut next = false; - // Write MIDI events from currently playing phrase (if any) to MIDI output buffer - if self.clock().is_rolling() { - let sample0 = scope.last_frame_time() as usize; - let samples = scope.n_frames() as usize; - // If no phrase is playing, prepare for switchover immediately - next = self.play_phrase().is_none(); - let phrase = self.play_phrase(); - let started0 = &self.clock().started; - let timebase = self.clock().timebase(); - let notes_out = self.notes_out(); - let next_phrase = self.next_phrase(); - if let Some((started, phrase)) = phrase { - // First sample to populate. Greater than 0 means that the first - // pulse of the phrase falls somewhere in the middle of the chunk. - let sample = started.sample.get() as usize; - let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize; - let sample = sample0.saturating_sub(sample); - // Iterator that emits sample (index into output buffer at which to write MIDI event) - // paired with pulse (index into phrase from which to take the MIDI event) for each - // sample of the output buffer that corresponds to a MIDI pulse. - let pulses = timebase.pulses_between_samples(sample, sample + samples); - // Notes active during current chunk. - let notes = &mut notes_out.write().unwrap(); - for (sample, pulse) in pulses { - // If a next phrase is enqueued, and we're past the end of the current one, - // break the loop here (FIXME count pulse correctly) - next = next_phrase.is_some() && if let Some(ref phrase) = phrase { - pulse >= phrase.read().unwrap().length - } else { - true - }; - if next { - break - } - // If there's a currently playing phrase, output notes from it to buffer: - if let Some(ref phrase) = phrase { - // Source phrase from which the MIDI events will be taken. - let phrase = phrase.read().unwrap(); - // Phrase with zero length is not processed - if phrase.length > 0 { - // Current pulse index in source phrase - let pulse = pulse % phrase.length; - // Output each MIDI event from phrase at appropriate frames of output buffer: - for message in phrase.notes[pulse].iter() { - // Clear output buffer for this MIDI event. - note_buf.clear(); - // TODO: support MIDI channels other than CH1. - let channel = 0.into(); - // Serialize MIDI event into message buffer. - LiveEvent::Midi { channel, message: *message } - .write(note_buf) - .unwrap(); - // Append serialized message to output buffer. - out_buf[sample].push(note_buf.clone()); - // Update the list of currently held notes. - update_keys(&mut*notes, &message); - } - } - } - } - } - } - next - } - - /// Handle switchover from current to next playing phrase. - fn switchover ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out_buf: &mut Vec>> - ) { - if self.clock().is_rolling() { - let sample0 = scope.last_frame_time() as usize; - //let samples = scope.n_frames() as usize; - if let Some((start_at, phrase)) = &self.next_phrase() { - let start = start_at.sample.get() as usize; - let sample = self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize; - // If it's time to switch to the next phrase: - if start <= sample0.saturating_sub(sample) { - // Samples elapsed since phrase was supposed to start - let skipped = sample0 - start; - // Switch over to enqueued phrase - let started = Moment::from_sample(&self.clock().timebase(), start as f64); - *self.play_phrase_mut() = Some((started, phrase.clone())); - // Unset enqueuement (TODO: where to implement looping?) - *self.next_phrase_mut() = None - } - // TODO fill in remaining ticks of chunk from next phrase. - // ?? just call self.play(scope) again, since enqueuement is off ??? - self.play(scope, note_buf, out_buf); - // ?? or must it be with modified scope ?? - // likely not because start time etc - } - } - } - - /// Write a chunk of MIDI notes to the output buffer. - fn write ( - &mut self, scope: &ProcessScope, out_buf: &Vec>> - ) { - let samples = scope.n_frames() as usize; - for port in self.midi_outs_mut().iter_mut() { - let writer = &mut port.writer(scope); - for time in 0..samples { - for event in out_buf[time].iter() { - writer.write(&RawMidi { time: time as u32, bytes: &event }) - .expect(&format!("{event:?}")); - } - } - } - } -} - -impl MidiPlaybackApi for PhrasePlayerModel { - fn notes_out (&self) -> &Arc> { - &self.notes_in - } -} - -pub trait HasPlayPhrase: HasClock { - fn reset (&self) -> bool; - fn reset_mut (&mut self) -> &mut bool; - fn play_phrase (&self) -> &Option<(Moment, Option>>)>; - fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; - fn next_phrase (&self) -> &Option<(Moment, Option>>)>; - fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; - fn pulses_since_start (&self) -> Option { - if let Some((started, Some(_))) = self.play_phrase().as_ref() { - let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); - Some(elapsed) - } else { - None - } - } - fn pulses_since_start_looped (&self) -> Option { - if let Some((started, Some(phrase))) = self.play_phrase().as_ref() { - let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); - let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase - let elapsed = (elapsed as usize % length) as f64; - Some(elapsed) - } else { - None - } - } - fn enqueue_next (&mut self, phrase: Option<&Arc>>) { - let start = self.clock().next_launch_pulse() as f64; - let instant = Moment::from_pulse(&self.clock().timebase(), start); - let phrase = phrase.map(|p|p.clone()); - *self.next_phrase_mut() = Some((instant, phrase)); - *self.reset_mut() = true; - } -} - -impl HasPlayPhrase for PhrasePlayerModel { - fn reset (&self) -> bool { - self.reset - } - fn reset_mut (&mut self) -> &mut bool { - &mut self.reset - } - fn play_phrase (&self) -> &Option<(Moment, Option>>)> { - &self.play_phrase - } - fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { - &mut self.play_phrase - } - fn next_phrase (&self) -> &Option<(Moment, Option>>)> { - &self.next_phrase - } - fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { - &mut self.next_phrase - } -} - -/// Add "all notes off" to the start of a buffer. -pub fn all_notes_off (output: &mut [Vec>]) { - let mut buf = vec![]; - let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() }; - let evt = LiveEvent::Midi { channel: 0.into(), message: msg }; - evt.write(&mut buf).unwrap(); - output[0].push(buf); -} - -/// Return boxed iterator of MIDI events -pub fn parse_midi_input (input: MidiIter) -> Box + '_> { - Box::new(input.map(|RawMidi { time, bytes }|( - time as usize, - LiveEvent::parse(bytes).unwrap(), - bytes - ))) -} - -/// Update notes_in array -pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { - match message { - MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } - MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, - _ => {} - } -} - -/// Hosts the JACK callback for a single MIDI player -pub struct PlayerAudio<'a, T: MidiPlayerApi>( - /// 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: MidiPlayerApi> Audio for PlayerAudio<'a, T> { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let model = &mut self.0; - let note_buf = &mut self.1; - let midi_buf = &mut self.2; - // Clear output buffer(s) - model.clear(scope, midi_buf, false); - // Write chunk of phrase to output, handle switchover - if model.play(scope, note_buf, midi_buf) { - model.switchover(scope, note_buf, midi_buf); - } - if model.has_midi_ins() { - if model.recording() || model.monitoring() { - // Record and/or monitor input - model.record(scope, midi_buf) - } else if model.has_midi_outs() && model.monitoring() { - // Monitor input to output - model.monitor(scope, midi_buf) - } - } - // Write to output port(s) - model.write(scope, midi_buf); - Control::Continue - } -} - -//#[derive(Debug)] -//pub struct MIDIPlayer { - ///// Global timebase - //pub clock: Arc, - ///// Start time and phrase being played - //pub play_phrase: Option<(Moment, Option>>)>, - ///// Start time and next phrase - //pub next_phrase: Option<(Moment, 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/src/core.rs b/crates/tek/src/core.rs index caacfa57..a7cb629a 100644 --- a/crates/tek/src/core.rs +++ b/crates/tek/src/core.rs @@ -1,4 +1,3 @@ -pub(crate) mod audio; pub(crate) use audio::*; pub(crate) mod color; pub(crate) use color::*; pub(crate) mod command; pub(crate) use command::*; pub(crate) mod engine; pub(crate) use engine::*; diff --git a/crates/tek/src/core/audio.rs b/crates/tek/src/core/audio.rs deleted file mode 100644 index e9a50577..00000000 --- a/crates/tek/src/core/audio.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::*; -use jack::*; - -#[derive(Debug, Clone)] -/// Event enum for JACK events. -pub enum JackEvent { - ThreadInit, - Shutdown(ClientStatus, String), - Freewheel(bool), - SampleRate(Frames), - ClientRegistration(String, bool), - PortRegistration(PortId, bool), - PortRename(PortId, String, String), - PortsConnected(PortId, PortId, bool), - GraphReorder, - XRun, -} - -/// Wraps [Client] or [DynamicAsyncClient] in place. -#[derive(Debug)] -pub enum JackClient { - /// Before activation. - Inactive(Client), - /// During activation. - Activating, - /// After activation. Must not be dropped for JACK thread to persist. - Active(DynamicAsyncClient), -} - -/// Trait for things that wrap a JACK client. -pub trait AudioEngine { - - fn transport (&self) -> Transport { - self.client().transport() - } - - fn port_by_name (&self, name: &str) -> Option> { - self.client().port_by_name(name) - } - - fn register_port (&self, name: &str, spec: PS) -> Usually> { - Ok(self.client().register_port(name, spec)?) - } - - fn client (&self) -> &Client; - - fn activate ( - self, - process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static - ) -> Usually>> where Self: Send + Sync + 'static; - - fn thread_init (&self, _: &Client) {} - - unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {} - - fn freewheel (&mut self, _: &Client, _enabled: bool) {} - - fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {} - - fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {} - - fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {} - - fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control { - Control::Continue - } - - fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control { - Control::Continue - } - - fn graph_reorder (&mut self, _: &Client) -> Control { - Control::Continue - } - - fn xrun (&mut self, _: &Client) -> Control { - Control::Continue - } -} - -impl AudioEngine for JackClient { - fn client(&self) -> &Client { - match self { - Self::Inactive(ref client) => client, - Self::Activating => panic!("jack client has not finished activation"), - Self::Active(ref client) => client.as_client(), - } - } - fn activate( - self, - mut cb: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, - ) -> Usually>> - where - Self: Send + Sync + 'static - { - let client = Client::from(self); - let state = Arc::new(RwLock::new(Self::Activating)); - let event = Box::new(move|_|{/*TODO*/}) as Box; - let events = Notifications(event); - let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)}); - let frames = contrib::ClosureProcessHandler::new(frame as BoxedAudioHandler); - *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); - Ok(state) - } -} - -pub type DynamicAsyncClient = AsyncClient; - -pub type DynamicAudioHandler = contrib::ClosureProcessHandler<(), BoxedAudioHandler>; - -pub type BoxedAudioHandler = Box Control + Send>; - -impl JackClient { - pub fn new (name: &str) -> Usually { - let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; - Ok(Self::Inactive(client)) - } -} - -impl From for Client { - fn from (jack: JackClient) -> Client { - match jack { - JackClient::Inactive(client) => client, - JackClient::Activating => panic!("jack client still activating"), - JackClient::Active(_) => panic!("jack client already activated"), - } - } -} - -/// Notification handler used by the [Jack] factory -/// when constructing [JackDevice]s. -pub type DynamicNotifications = Notifications>; - -/// Generic notification handler that emits [JackEvent] -pub struct Notifications(pub T); - -impl NotificationHandler for Notifications { - fn thread_init(&self, _: &Client) { - self.0(JackEvent::ThreadInit); - } - - unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) { - self.0(JackEvent::Shutdown(status, reason.into())); - } - - fn freewheel(&mut self, _: &Client, enabled: bool) { - self.0(JackEvent::Freewheel(enabled)); - } - - fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control { - self.0(JackEvent::SampleRate(frames)); - Control::Quit - } - - fn client_registration(&mut self, _: &Client, name: &str, reg: bool) { - self.0(JackEvent::ClientRegistration(name.into(), reg)); - } - - fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) { - self.0(JackEvent::PortRegistration(id, reg)); - } - - fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { - self.0(JackEvent::PortRename(id, old.into(), new.into())); - Control::Continue - } - - fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) { - self.0(JackEvent::PortsConnected(a, b, are)); - } - - fn graph_reorder(&mut self, _: &Client) -> Control { - self.0(JackEvent::GraphReorder); - Control::Continue - } - - fn xrun(&mut self, _: &Client) -> Control { - self.0(JackEvent::XRun); - Control::Continue - } -} diff --git a/crates/tek/src/core/collect.rs b/crates/tek/src/core/collect.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek/src/core/perf.rs b/crates/tek/src/core/perf.rs index 05403b11..e389a7c3 100644 --- a/crates/tek/src/core/perf.rs +++ b/crates/tek/src/core/perf.rs @@ -29,7 +29,7 @@ impl PerfModel { None } } - pub fn update (&self, t0: Option, scope: &jack::ProcessScope) { + pub fn update (&self, t0: Option, scope: &ProcessScope) { if let Some(t0) = t0 { let t1 = self.clock.raw(); self.used.store( diff --git a/crates/tek/src/api/jack.rs b/crates/tek/src/jack.rs similarity index 54% rename from crates/tek/src/api/jack.rs rename to crates/tek/src/jack.rs index 6ce07fcd..ac04ed72 100644 --- a/crates/tek/src/api/jack.rs +++ b/crates/tek/src/jack.rs @@ -1,151 +1,14 @@ use crate::*; -/// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. -#[macro_export] macro_rules! from_jack { - (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc>> for $Struct $(<$($L),*$($T),*>)? { - type Error = Box; - fn try_from ($jack: &Arc>) -> Usually { - Ok($cb) - } - } - }; -} - -/// Trait for thing that has 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 - } - } -} - -/// Implement [Audio]: provide JACK callbacks. -#[macro_export] macro_rules! audio { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { - #[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb } - } - } -} - -/// Trait for thing that may receive MIDI. -pub trait HasMidiIns { - fn midi_ins (&self) -> &Vec>; - fn midi_ins_mut (&mut self) -> &mut Vec>; - fn has_midi_ins (&self) -> bool { - self.midi_ins().len() > 0 - } -} - -/// Trait for thing that may output MIDI. -pub trait HasMidiOuts { - fn midi_outs (&self) -> &Vec>; - fn midi_outs_mut (&mut self) -> &mut Vec>; - fn has_midi_outs (&self) -> bool { - self.midi_outs().len() > 0 - } - /// Buffer for serializing a MIDI event. FIXME rename - fn midi_note (&mut self) -> &mut Vec; -} +pub(crate) mod activate; pub(crate) use activate::*; +pub(crate) mod audio; pub(crate) use audio::*; +pub(crate) mod client; pub(crate) use client::*; +pub(crate) mod from_jack; pub(crate) use from_jack::*; +pub(crate) mod jack_event; pub(crate) use jack_event::*; +pub(crate) mod ports; pub(crate) use ports::*; //////////////////////////////////////////////////////////////////////////////////// -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 = 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) - } -} - -/// 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. @@ -373,7 +236,7 @@ fn query_ports(client: &Client, names: Vec) -> BTreeMap), - //contrib::ClosureProcessHandler::new(Box::new({ + //ClosureProcessHandler::new(Box::new({ //let state = state.clone(); //move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s) //}) as BoxedAudioHandler), @@ -406,78 +269,3 @@ fn query_ports(client: &Client, names: Vec) -> BTreeMap 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") - //} - //} - -//impl From for Clock { - //fn from (current: Moment) -> 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/src/jack/activate.rs b/crates/tek/src/jack/activate.rs new file mode 100644 index 00000000..37829f3c --- /dev/null +++ b/crates/tek/src/jack/activate.rs @@ -0,0 +1,50 @@ +use crate::*; + +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 = 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) + } +} + +/// 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 {} diff --git a/crates/tek/src/jack/audio.rs b/crates/tek/src/jack/audio.rs new file mode 100644 index 00000000..b78c7e65 --- /dev/null +++ b/crates/tek/src/jack/audio.rs @@ -0,0 +1,78 @@ +use crate::*; + +/// Implement [Audio]: provide JACK callbacks. +#[macro_export] macro_rules! audio { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { + #[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb } + } + } +} + +/// Trait for thing that has 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 + } + } +} + + +/// Trait for things that wrap a JACK client. +pub trait AudioEngine { + + fn transport (&self) -> Transport { + self.client().transport() + } + + fn port_by_name (&self, name: &str) -> Option> { + self.client().port_by_name(name) + } + + fn register_port (&self, name: &str, spec: PS) -> Usually> { + Ok(self.client().register_port(name, spec)?) + } + + fn client (&self) -> &Client; + + fn activate ( + self, + process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static + ) -> Usually>> where Self: Send + Sync + 'static; + + fn thread_init (&self, _: &Client) {} + + unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {} + + fn freewheel (&mut self, _: &Client, _enabled: bool) {} + + fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {} + + fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {} + + fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {} + + fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control { + Control::Continue + } + + fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control { + Control::Continue + } + + fn graph_reorder (&mut self, _: &Client) -> Control { + Control::Continue + } + + fn xrun (&mut self, _: &Client) -> Control { + Control::Continue + } +} diff --git a/crates/tek/src/jack/client.rs b/crates/tek/src/jack/client.rs new file mode 100644 index 00000000..046990bd --- /dev/null +++ b/crates/tek/src/jack/client.rs @@ -0,0 +1,61 @@ +use crate::*; + +/// Wraps [Client] or [DynamicAsyncClient] in place. +#[derive(Debug)] +pub enum JackClient { + /// Before activation. + Inactive(Client), + /// During activation. + Activating, + /// After activation. Must not be dropped for JACK thread to persist. + Active(DynamicAsyncClient), +} + +impl AudioEngine for JackClient { + fn client(&self) -> &Client { + match self { + Self::Inactive(ref client) => client, + Self::Activating => panic!("jack client has not finished activation"), + Self::Active(ref client) => client.as_client(), + } + } + fn activate( + self, + mut cb: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, + ) -> Usually>> + where + Self: Send + Sync + 'static + { + let client = Client::from(self); + let state = Arc::new(RwLock::new(Self::Activating)); + let event = Box::new(move|_|{/*TODO*/}) as Box; + let events = Notifications(event); + let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)}); + let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler); + *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); + Ok(state) + } +} + +pub type DynamicAsyncClient = AsyncClient; + +pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>; + +pub type BoxedAudioHandler = Box Control + Send>; + +impl JackClient { + pub fn new (name: &str) -> Usually { + let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; + Ok(Self::Inactive(client)) + } +} + +impl From for Client { + fn from (jack: JackClient) -> Client { + match jack { + JackClient::Inactive(client) => client, + JackClient::Activating => panic!("jack client still activating"), + JackClient::Active(_) => panic!("jack client already activated"), + } + } +} diff --git a/crates/tek/src/jack/from_jack.rs b/crates/tek/src/jack/from_jack.rs new file mode 100644 index 00000000..bdd1fd2e --- /dev/null +++ b/crates/tek/src/jack/from_jack.rs @@ -0,0 +1,13 @@ + + +/// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. +#[macro_export] macro_rules! from_jack { + (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc>> for $Struct $(<$($L),*$($T),*>)? { + type Error = Box; + fn try_from ($jack: &Arc>) -> Usually { + Ok($cb) + } + } + }; +} diff --git a/crates/tek/src/jack/jack_event.rs b/crates/tek/src/jack/jack_event.rs new file mode 100644 index 00000000..b817099f --- /dev/null +++ b/crates/tek/src/jack/jack_event.rs @@ -0,0 +1,69 @@ +use crate::*; + +#[derive(Debug, Clone)] +/// Event enum for JACK events. +pub enum JackEvent { + ThreadInit, + Shutdown(ClientStatus, String), + Freewheel(bool), + SampleRate(Frames), + ClientRegistration(String, bool), + PortRegistration(PortId, bool), + PortRename(PortId, String, String), + PortsConnected(PortId, PortId, bool), + GraphReorder, + XRun, +} + +/// Notification handler used by the [Jack] factory +/// when constructing [JackDevice]s. +pub type DynamicNotifications = Notifications>; + +/// Generic notification handler that emits [JackEvent] +pub struct Notifications(pub T); + +impl NotificationHandler for Notifications { + fn thread_init(&self, _: &Client) { + self.0(JackEvent::ThreadInit); + } + + unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) { + self.0(JackEvent::Shutdown(status, reason.into())); + } + + fn freewheel(&mut self, _: &Client, enabled: bool) { + self.0(JackEvent::Freewheel(enabled)); + } + + fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control { + self.0(JackEvent::SampleRate(frames)); + Control::Quit + } + + fn client_registration(&mut self, _: &Client, name: &str, reg: bool) { + self.0(JackEvent::ClientRegistration(name.into(), reg)); + } + + fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) { + self.0(JackEvent::PortRegistration(id, reg)); + } + + fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + self.0(JackEvent::PortRename(id, old.into(), new.into())); + Control::Continue + } + + fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) { + self.0(JackEvent::PortsConnected(a, b, are)); + } + + fn graph_reorder(&mut self, _: &Client) -> Control { + self.0(JackEvent::GraphReorder); + Control::Continue + } + + fn xrun(&mut self, _: &Client) -> Control { + self.0(JackEvent::XRun); + Control::Continue + } +} diff --git a/crates/tek/src/jack/ports.rs b/crates/tek/src/jack/ports.rs new file mode 100644 index 00000000..299f123e --- /dev/null +++ b/crates/tek/src/jack/ports.rs @@ -0,0 +1,39 @@ +use crate::*; + +/// 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 + }) +} diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index f27561eb..b570e28d 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -34,14 +34,20 @@ pub(crate) use tui::*; pub mod edn; pub(crate) use edn::*; +pub mod jack; +pub(crate) use jack::*; + +pub mod midi; +pub(crate) use midi::*; + testmod! { test } pub(crate) use clap::{self, Parser}; -pub use better_panic; +pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity}; -pub use atomic_float; +pub use ::atomic_float; pub(crate) use atomic_float::*; pub(crate) use std::sync::{Arc, Mutex, RwLock}; @@ -58,28 +64,32 @@ pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; pub(crate) use std::cmp::{Ord, Eq, PartialEq}; pub(crate) use std::fmt::{Debug, Display}; -pub use crossterm; +pub use ::crossterm; pub(crate) use crossterm::{ExecutableCommand}; pub(crate) use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}; pub(crate) use crossterm::event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState}; -pub use ratatui; +pub use ::ratatui; pub(crate) use ratatui::{ prelude::{Style, Color, Buffer}, style::{Stylize, Modifier}, backend::{Backend, CrosstermBackend, ClearType} }; -pub use jack; -pub(crate) use jack::{ +pub use ::jack as libjack; +pub(crate) use ::jack::{ contrib::ClosureProcessHandler, - Client, ProcessScope, Control, CycleTimes, - Port, PortSpec, MidiIn, MidiOut, AudioIn, AudioOut, Unowned, + Client, AsyncClient, ClientOptions, ClientStatus, + ProcessScope, Control, CycleTimes, + Port, PortId, + PortSpec, MidiIn, MidiOut, AudioIn, AudioOut, Unowned, Transport, TransportState, MidiIter, RawMidi, + Frames, + NotificationHandler, }; -pub use midly; -pub(crate) use midly::{ +pub use ::midly; +pub(crate) use ::midly::{ Smf, MidiMessage, TrackEventKind, @@ -87,8 +97,8 @@ pub(crate) use midly::{ num::u7 }; -pub use palette; -pub(crate) use palette::{ +pub use ::palette; +pub(crate) use ::palette::{ *, convert::*, okhsl::* diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs new file mode 100644 index 00000000..a7ed508f --- /dev/null +++ b/crates/tek/src/midi.rs @@ -0,0 +1,36 @@ +use crate::*; + +pub(crate) mod midi_note; pub(crate) use midi_note::*; +pub(crate) mod midi_in; pub(crate) use midi_in::*; +pub(crate) mod midi_out; pub(crate) use midi_out::*; +pub(crate) mod midi_player; pub(crate) use midi_player::*; +pub(crate) mod midi_launch; pub(crate) use midi_launch::*; +pub(crate) mod midi_play; pub(crate) use midi_play::*; +pub(crate) mod midi_rec; pub(crate) use midi_rec::*; + +/// Add "all notes off" to the start of a buffer. +pub fn all_notes_off (output: &mut [Vec>]) { + let mut buf = vec![]; + let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() }; + let evt = LiveEvent::Midi { channel: 0.into(), message: msg }; + evt.write(&mut buf).unwrap(); + output[0].push(buf); +} + +/// Return boxed iterator of MIDI events +pub fn parse_midi_input (input: MidiIter) -> Box + '_> { + Box::new(input.map(|RawMidi { time, bytes }|( + time as usize, + LiveEvent::parse(bytes).unwrap(), + bytes + ))) +} + +/// Update notes_in array +pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { + match message { + MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } + MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, + _ => {} + } +} diff --git a/crates/tek/src/midi/midi_in.rs b/crates/tek/src/midi/midi_in.rs new file mode 100644 index 00000000..4750ca9e --- /dev/null +++ b/crates/tek/src/midi/midi_in.rs @@ -0,0 +1,10 @@ +use crate::*; + +/// Trait for thing that may receive MIDI. +pub trait HasMidiIns { + fn midi_ins (&self) -> &Vec>; + fn midi_ins_mut (&mut self) -> &mut Vec>; + fn has_midi_ins (&self) -> bool { + self.midi_ins().len() > 0 + } +} diff --git a/crates/tek/src/midi/midi_launch.rs b/crates/tek/src/midi/midi_launch.rs new file mode 100644 index 00000000..e680601a --- /dev/null +++ b/crates/tek/src/midi/midi_launch.rs @@ -0,0 +1,35 @@ +use crate::*; + +pub trait HasPlayPhrase: HasClock { + fn reset (&self) -> bool; + fn reset_mut (&mut self) -> &mut bool; + fn play_phrase (&self) -> &Option<(Moment, Option>>)>; + fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; + fn next_phrase (&self) -> &Option<(Moment, Option>>)>; + fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; + fn pulses_since_start (&self) -> Option { + if let Some((started, Some(_))) = self.play_phrase().as_ref() { + let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); + Some(elapsed) + } else { + None + } + } + fn pulses_since_start_looped (&self) -> Option { + if let Some((started, Some(phrase))) = self.play_phrase().as_ref() { + let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); + let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase + let elapsed = (elapsed as usize % length) as f64; + Some(elapsed) + } else { + None + } + } + fn enqueue_next (&mut self, phrase: Option<&Arc>>) { + let start = self.clock().next_launch_pulse() as f64; + let instant = Moment::from_pulse(&self.clock().timebase(), start); + let phrase = phrase.map(|p|p.clone()); + *self.next_phrase_mut() = Some((instant, phrase)); + *self.reset_mut() = true; + } +} diff --git a/crates/tek/src/midi/midi_note.rs b/crates/tek/src/midi/midi_note.rs new file mode 100644 index 00000000..abbc4bc0 --- /dev/null +++ b/crates/tek/src/midi/midi_note.rs @@ -0,0 +1,110 @@ +use crate::*; +use Ordering::Relaxed; + +pub trait MidiViewport: MidiRange + MidiPoint + HasSize { + /// Make sure cursor is within range + fn autoscroll (&self) { + let note_lo = self.note_lo(); + let note_axis = self.note_axis(); + let note_hi = self.note_hi(); + let note_point = self.note_point().min(127); + if note_point < note_lo { + self.set_note_lo(note_point); + } else if note_point > note_hi { + self.set_note_lo((note_lo + note_point).saturating_sub(note_hi)); + } + } + /// Make sure best usage of screen space is achieved by default + fn autozoom (&self) { + } +} + +#[derive(Debug, Clone)] +pub struct MidiRangeModel { + /// Length of visible time axis + pub time_axis: Arc, + /// Earliest time displayed + pub time_start: Arc, + /// Time step + pub time_zoom: Arc, + /// Auto rezoom to fit in time axis + pub time_lock: Arc, + /// Length of visible note axis + pub note_axis: Arc, + // Lowest note displayed + pub note_lo: Arc, +} +impl From<(usize, bool)> for MidiRangeModel { + fn from ((time_zoom, time_lock): (usize, bool)) -> Self { + Self { + note_axis: Arc::new(0.into()), + note_lo: Arc::new(0.into()), + time_axis: Arc::new(0.into()), + time_start: Arc::new(0.into()), + time_zoom: Arc::new(time_zoom.into()), + time_lock: Arc::new(time_lock.into()), + } + } +} +pub trait MidiRange { + fn time_zoom (&self) -> usize; + fn set_time_zoom (&mut self, x: usize); + fn time_lock (&self) -> bool; + fn set_time_lock (&self, x: bool); + fn time_start (&self) -> usize; + fn set_time_start (&self, x: usize); + fn note_lo (&self) -> usize; + fn set_note_lo (&self, x: usize); + fn note_axis (&self) -> usize; + fn time_axis (&self) -> usize; + fn note_hi (&self) -> usize { (self.note_lo() + self.note_axis().saturating_sub(1)).min(127) } + fn time_end (&self) -> usize { self.time_start() + self.time_axis() * self.time_zoom() } +} +impl MidiRange for MidiRangeModel { + fn time_zoom (&self) -> usize { self.time_zoom.load(Relaxed) } + fn set_time_zoom (&mut self, x: usize) { self.time_zoom.store(x, Relaxed); } + fn time_lock (&self) -> bool { self.time_lock.load(Relaxed) } + fn set_time_lock (&self, x: bool) { self.time_lock.store(x, Relaxed); } + fn time_start (&self) -> usize { self.time_start.load(Relaxed) } + fn set_time_start (&self, x: usize) { self.time_start.store(x, Relaxed); } + fn note_lo (&self) -> usize { self.note_lo.load(Relaxed).min(127) } + fn set_note_lo (&self, x: usize) { self.note_lo.store(x.min(127), Relaxed); } + fn note_axis (&self) -> usize { self.note_axis.load(Relaxed) } + fn time_axis (&self) -> usize { self.time_axis.load(Relaxed) } +} + +#[derive(Debug, Clone)] +pub struct MidiPointModel { + /// Time coordinate of cursor + pub time_point: Arc, + /// Note coordinate of cursor + pub note_point: Arc, + /// Length of note that will be inserted, in pulses + pub note_len: Arc, +} +impl Default for MidiPointModel { + fn default () -> Self { + Self { + time_point: Arc::new(0.into()), + note_point: Arc::new(36.into()), + note_len: Arc::new(24.into()), + } + } +} +pub trait MidiPoint { + fn note_len (&self) -> usize; + fn set_note_len (&self, x: usize); + fn note_point (&self) -> usize; + fn set_note_point (&self, x: usize); + fn time_point (&self) -> usize; + fn set_time_point (&self, x: usize); + fn note_end (&self) -> usize { self.note_point() + self.note_len() } +} +impl MidiPoint for MidiPointModel { + fn note_len (&self) -> usize { self.note_len.load(Relaxed)} + fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } + fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } + fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } + fn time_point (&self) -> usize { self.time_point.load(Relaxed) } + fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } +} diff --git a/crates/tek/src/midi/midi_out.rs b/crates/tek/src/midi/midi_out.rs new file mode 100644 index 00000000..0010ef4d --- /dev/null +++ b/crates/tek/src/midi/midi_out.rs @@ -0,0 +1,12 @@ +use crate::*; + +/// Trait for thing that may output MIDI. +pub trait HasMidiOuts { + fn midi_outs (&self) -> &Vec>; + fn midi_outs_mut (&mut self) -> &mut Vec>; + fn has_midi_outs (&self) -> bool { + self.midi_outs().len() > 0 + } + /// Buffer for serializing a MIDI event. FIXME rename + fn midi_note (&mut self) -> &mut Vec; +} diff --git a/crates/tek/src/midi/midi_play.rs b/crates/tek/src/midi/midi_play.rs new file mode 100644 index 00000000..e32af28b --- /dev/null +++ b/crates/tek/src/midi/midi_play.rs @@ -0,0 +1,134 @@ +use crate::*; + +pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { + + fn notes_out (&self) -> &Arc>; + + /// Clear the section of the output buffer that we will be using, + /// emitting "all notes off" at start of buffer if requested. + fn clear ( + &mut self, scope: &ProcessScope, out_buf: &mut Vec>>, reset: bool + ) { + for frame in &mut out_buf[0..scope.n_frames() as usize] { + frame.clear(); + } + if reset { + all_notes_off(out_buf); + } + } + + /// Output notes from phrase to MIDI output ports. + fn play ( + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out_buf: &mut Vec>> + ) -> bool { + let mut next = false; + // Write MIDI events from currently playing phrase (if any) to MIDI output buffer + if self.clock().is_rolling() { + let sample0 = scope.last_frame_time() as usize; + let samples = scope.n_frames() as usize; + // If no phrase is playing, prepare for switchover immediately + next = self.play_phrase().is_none(); + let phrase = self.play_phrase(); + let started0 = &self.clock().started; + let timebase = self.clock().timebase(); + let notes_out = self.notes_out(); + let next_phrase = self.next_phrase(); + if let Some((started, phrase)) = phrase { + // First sample to populate. Greater than 0 means that the first + // pulse of the phrase falls somewhere in the middle of the chunk. + let sample = started.sample.get() as usize; + let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize; + let sample = sample0.saturating_sub(sample); + // Iterator that emits sample (index into output buffer at which to write MIDI event) + // paired with pulse (index into phrase from which to take the MIDI event) for each + // sample of the output buffer that corresponds to a MIDI pulse. + let pulses = timebase.pulses_between_samples(sample, sample + samples); + // Notes active during current chunk. + let notes = &mut notes_out.write().unwrap(); + for (sample, pulse) in pulses { + // If a next phrase is enqueued, and we're past the end of the current one, + // break the loop here (FIXME count pulse correctly) + next = next_phrase.is_some() && if let Some(ref phrase) = phrase { + pulse >= phrase.read().unwrap().length + } else { + true + }; + if next { + break + } + // If there's a currently playing phrase, output notes from it to buffer: + if let Some(ref phrase) = phrase { + // Source phrase from which the MIDI events will be taken. + let phrase = phrase.read().unwrap(); + // Phrase with zero length is not processed + if phrase.length > 0 { + // Current pulse index in source phrase + let pulse = pulse % phrase.length; + // Output each MIDI event from phrase at appropriate frames of output buffer: + for message in phrase.notes[pulse].iter() { + // Clear output buffer for this MIDI event. + note_buf.clear(); + // TODO: support MIDI channels other than CH1. + let channel = 0.into(); + // Serialize MIDI event into message buffer. + LiveEvent::Midi { channel, message: *message } + .write(note_buf) + .unwrap(); + // Append serialized message to output buffer. + out_buf[sample].push(note_buf.clone()); + // Update the list of currently held notes. + update_keys(&mut*notes, &message); + } + } + } + } + } + } + next + } + + /// Handle switchover from current to next playing phrase. + fn switchover ( + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out_buf: &mut Vec>> + ) { + if self.clock().is_rolling() { + let sample0 = scope.last_frame_time() as usize; + //let samples = scope.n_frames() as usize; + if let Some((start_at, phrase)) = &self.next_phrase() { + let start = start_at.sample.get() as usize; + let sample = self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize; + // If it's time to switch to the next phrase: + if start <= sample0.saturating_sub(sample) { + // Samples elapsed since phrase was supposed to start + let skipped = sample0 - start; + // Switch over to enqueued phrase + let started = Moment::from_sample(&self.clock().timebase(), start as f64); + *self.play_phrase_mut() = Some((started, phrase.clone())); + // Unset enqueuement (TODO: where to implement looping?) + *self.next_phrase_mut() = None + } + // TODO fill in remaining ticks of chunk from next phrase. + // ?? just call self.play(scope) again, since enqueuement is off ??? + self.play(scope, note_buf, out_buf); + // ?? or must it be with modified scope ?? + // likely not because start time etc + } + } + } + + /// Write a chunk of MIDI notes to the output buffer. + fn write ( + &mut self, scope: &ProcessScope, out_buf: &Vec>> + ) { + let samples = scope.n_frames() as usize; + for port in self.midi_outs_mut().iter_mut() { + let writer = &mut port.writer(scope); + for time in 0..samples { + for event in out_buf[time].iter() { + writer.write(&RawMidi { time: time as u32, bytes: &event }) + .expect(&format!("{event:?}")); + } + } + } + } +} diff --git a/crates/tek/src/midi/midi_player.rs b/crates/tek/src/midi/midi_player.rs new file mode 100644 index 00000000..e0eedd3c --- /dev/null +++ b/crates/tek/src/midi/midi_player.rs @@ -0,0 +1,255 @@ +use crate::*; + +pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} + +impl MidiPlayerApi for PhrasePlayerModel {} + +pub trait HasPlayer { + fn player (&self) -> &impl MidiPlayerApi; + fn player_mut (&mut self) -> &mut impl MidiPlayerApi; +} + +#[macro_export] macro_rules! has_player { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasPlayer for $Struct $(<$($L),*$($T),*>)? { + fn player (&$self) -> &impl MidiPlayerApi { &$cb } + fn player_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb } + } + } +} + +/// Contains state for playing a phrase +pub struct PhrasePlayerModel { + /// State of clock and playhead + pub(crate) clock: ClockModel, + /// Start time and phrase being played + pub(crate) play_phrase: Option<(Moment, Option>>)>, + /// Start time and next phrase + pub(crate) next_phrase: Option<(Moment, Option>>)>, + /// Play input through output. + pub(crate) monitoring: bool, + /// Write input to sequence. + pub(crate) recording: bool, + /// Overdub input to sequence. + pub(crate) overdub: bool, + /// Send all notes off + pub(crate) reset: bool, // TODO?: after Some(nframes) + /// Record from MIDI ports to current sequence. + pub midi_ins: Vec>, + /// Play from current sequence to MIDI ports + pub midi_outs: Vec>, + /// Notes currently held at input + pub(crate) notes_in: Arc>, + /// Notes currently held at output + pub(crate) notes_out: Arc>, + /// MIDI output buffer + pub note_buf: Vec, +} + +impl std::fmt::Debug for PhrasePlayerModel { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("PhrasePlayerModel") + .field("clock", &self.clock) + .field("play_phrase", &self.play_phrase) + .field("next_phrase", &self.next_phrase) + .finish() + } +} + +impl From<&ClockModel> for PhrasePlayerModel { + fn from (clock: &ClockModel) -> Self { + Self { + clock: clock.clone(), + midi_ins: vec![], + midi_outs: vec![], + note_buf: vec![0;8], + reset: true, + recording: false, + monitoring: false, + overdub: false, + play_phrase: None, + next_phrase: None, + notes_in: RwLock::new([false;128]).into(), + notes_out: RwLock::new([false;128]).into(), + } + } +} + +impl From<(&ClockModel, &Arc>)> for PhrasePlayerModel { + fn from ((clock, phrase): (&ClockModel, &Arc>)) -> Self { + let mut model = Self::from(clock); + model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone()))); + model + } +} + +has_clock!(|self:PhrasePlayerModel|&self.clock); + +impl HasMidiIns for PhrasePlayerModel { + fn midi_ins (&self) -> &Vec> { + &self.midi_ins + } + fn midi_ins_mut (&mut self) -> &mut Vec> { + &mut self.midi_ins + } +} + +impl HasMidiOuts for PhrasePlayerModel { + fn midi_outs (&self) -> &Vec> { + &self.midi_outs + } + fn midi_outs_mut (&mut self) -> &mut Vec> { + &mut self.midi_outs + } + fn midi_note (&mut self) -> &mut Vec { + &mut self.note_buf + } +} + +/// Hosts the JACK callback for a single MIDI player +pub struct PlayerAudio<'a, T: MidiPlayerApi>( + /// 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: MidiPlayerApi> Audio for PlayerAudio<'a, T> { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + let model = &mut self.0; + let note_buf = &mut self.1; + let midi_buf = &mut self.2; + // Clear output buffer(s) + model.clear(scope, midi_buf, false); + // Write chunk of phrase to output, handle switchover + if model.play(scope, note_buf, midi_buf) { + model.switchover(scope, note_buf, midi_buf); + } + if model.has_midi_ins() { + if model.recording() || model.monitoring() { + // Record and/or monitor input + model.record(scope, midi_buf) + } else if model.has_midi_outs() && model.monitoring() { + // Monitor input to output + model.monitor(scope, midi_buf) + } + } + // Write to output port(s) + model.write(scope, midi_buf); + Control::Continue + } +} + +impl MidiRecordApi for PhrasePlayerModel { + fn recording (&self) -> bool { + self.recording + } + fn recording_mut (&mut self) -> &mut bool { + &mut self.recording + } + fn monitoring (&self) -> bool { + self.monitoring + } + fn monitoring_mut (&mut self) -> &mut bool { + &mut self.monitoring + } + fn overdub (&self) -> bool { + self.overdub + } + fn overdub_mut (&mut self) -> &mut bool { + &mut self.overdub + } + fn notes_in (&self) -> &Arc> { + &self.notes_in + } +} + +impl MidiPlaybackApi for PhrasePlayerModel { + fn notes_out (&self) -> &Arc> { + &self.notes_in + } +} + +impl HasPlayPhrase for PhrasePlayerModel { + fn reset (&self) -> bool { + self.reset + } + fn reset_mut (&mut self) -> &mut bool { + &mut self.reset + } + fn play_phrase (&self) -> &Option<(Moment, Option>>)> { + &self.play_phrase + } + fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { + &mut self.play_phrase + } + fn next_phrase (&self) -> &Option<(Moment, Option>>)> { + &self.next_phrase + } + fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { + &mut self.next_phrase + } +} + +//#[derive(Debug)] +//pub struct MIDIPlayer { + ///// Global timebase + //pub clock: Arc, + ///// Start time and phrase being played + //pub play_phrase: Option<(Moment, Option>>)>, + ///// Start time and next phrase + //pub next_phrase: Option<(Moment, 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/src/midi/midi_rec.rs b/crates/tek/src/midi/midi_rec.rs new file mode 100644 index 00000000..22a58f74 --- /dev/null +++ b/crates/tek/src/midi/midi_rec.rs @@ -0,0 +1,75 @@ +use crate::*; + +pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { + fn notes_in (&self) -> &Arc>; + + fn recording (&self) -> bool; + fn recording_mut (&mut self) -> &mut bool; + fn toggle_record (&mut self) { + *self.recording_mut() = !self.recording(); + } + fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { + let sample0 = scope.last_frame_time() as usize; + // For highlighting keys and note repeat + let notes_in = self.notes_in().clone(); + if self.clock().is_rolling() { + if let Some((started, ref phrase)) = self.play_phrase().clone() { + let start = started.sample.get() as usize; + let quant = self.clock().quant.get(); + let timebase = self.clock().timebase().clone(); + let monitoring = self.monitoring(); + let recording = self.recording(); + for input in self.midi_ins_mut().iter() { + for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + if monitoring { + midi_buf[sample].push(bytes.to_vec()) + } + if 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 = timebase.samples_to_pulse(sample); + let quantized = (pulse / quant).round() * quant; + let looped = quantized as usize % length; + looped + }, message); + } + } + update_keys(&mut*notes_in.write().unwrap(), &message); + } + } + } + } + if let Some((start_at, phrase)) = &self.next_phrase() { + // TODO switch to next phrase and record into it + } + } + } + + fn monitoring (&self) -> bool; + fn monitoring_mut (&mut self) -> &mut bool; + fn toggle_monitor (&mut self) { + *self.monitoring_mut() = !self.monitoring(); + } + fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { + // For highlighting keys and note repeat + let notes_in = self.notes_in().clone(); + for input in self.midi_ins_mut().iter() { + for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + midi_buf[sample].push(bytes.to_vec()); + update_keys(&mut*notes_in.write().unwrap(), &message); + } + } + } + } + + fn overdub (&self) -> bool; + fn overdub_mut (&mut self) -> &mut bool; + fn toggle_overdub (&mut self) { + *self.overdub_mut() = !self.overdub(); + } +} diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index 67c0fc84..e22156fa 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -1121,3 +1121,66 @@ pub fn arranger_content_horizontal ( //) //) //} + +//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") + //} + //}