diff --git a/crates/tek_api/src/api_pool.rs b/crates/tek_api/src/api_pool.rs index 22cb7b2d..f868600e 100644 --- a/crates/tek_api/src/api_pool.rs +++ b/crates/tek_api/src/api_pool.rs @@ -1,26 +1,10 @@ use crate::*; -pub trait PhrasePoolModelApi { +pub trait HasPhrases { fn phrases (&self) -> &Vec>>; fn phrases_mut (&mut self) -> &mut Vec>>; } -impl PhrasePoolModelApi for PhrasePoolModel { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - -/// Contains all phrases in a project -#[derive(Debug)] -pub struct PhrasePoolModel { - /// Phrases in the pool - pub phrases: Vec>>, -} - #[derive(Clone, PartialEq)] pub enum PhrasePoolCommand { Add(usize), @@ -34,8 +18,8 @@ pub enum PhrasePoolCommand { SetLength(usize, usize), } -impl Command for PhrasePoolCommand { - fn execute (self, model: &mut PhrasePoolModel) -> Perhaps { +impl Command for PhrasePoolCommand { + fn execute (self, model: &mut T) -> Perhaps { match self { Self::Add(index) => { //Self::Append => { view.append_new(None, None) }, diff --git a/crates/tek_api/src/api_sequencer.rs b/crates/tek_api/src/api_sequencer.rs index 1a54e463..aebf0cea 100644 --- a/crates/tek_api/src/api_sequencer.rs +++ b/crates/tek_api/src/api_sequencer.rs @@ -5,156 +5,28 @@ pub trait HasPlayer: HasJack + HasClock { fn player_mut (&mut self) -> &mut MIDIPlayer; } -pub trait MidiInputApi { - fn has_midi_inputs (&self) -> bool { - self.midi_inputs.len() > 0 - } - fn toggle_record (&mut self) { - self.recording = !self.recording; - } - fn toggle_overdub (&mut self) { - self.overdub = !self.overdub; - } - fn record (&mut self, scope: &ProcessScope) { - let sample0 = scope.last_frame_time() as usize; - if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) { - let start = started.sample.get() as usize; - let quant = self.clock.quant.get(); - // For highlighting keys and note repeat - let mut notes_in = self.notes_in.write().unwrap(); - // Record from each input - for input in self.midi_inputs.iter() { - for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - if self.monitoring { - self.midi_chunk[sample].push(bytes.to_vec()) - } - if self.recording { - if let Some(phrase) = phrase { - let mut phrase = phrase.write().unwrap(); - let length = phrase.length; - phrase.record_event({ - let sample = (sample0 + sample - start) as f64; - let pulse = self.clock.timebase().samples_to_pulse(sample); - let quantized = (pulse / quant).round() * quant; - let looped = quantized as usize % length; - looped - }, message); - } - } - update_keys(&mut notes_in, &message); - } - } - } - } - if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase) { - // TODO switch to next phrase and record into it - } - } -} +pub trait HasMidiBuffer { + fn midi_buffer (&self) -> &mut Vec>>; + + fn reset (&self) -> bool; + fn reset_mut (&mut self) -> &mut bool; -pub trait MidiOutputApi { - fn has_midi_outputs (&self) -> bool { - self.midi_outputs.len() > 0 - } /// Clear the section of the output buffer that we will be using, /// emitting "all notes off" at start of buffer if requested. fn clear (&mut self, scope: &ProcessScope, force_reset: bool) { - for frame in &mut self.midi_chunk[0..scope.n_frames() as usize] { + for frame in &mut self.midi_buffer()[0..scope.n_frames() as usize] { frame.clear(); } - if self.reset || force_reset { - all_notes_off(&mut self.midi_chunk); self.reset = false; - } - } - fn play (&mut self, scope: &ProcessScope) -> bool { - let mut next = false; - // Write MIDI events from currently playing phrase (if any) to MIDI output buffer - if self.is_rolling() { - 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.phrase.is_none(); - if let Some((started, phrase)) = &self.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 + self.clock.started.read().unwrap().unwrap().0; - 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 = self.clock.timebase().pulses_between_samples(sample, sample + samples); - // Notes active during current chunk. - let notes = &mut self.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 = self.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(); - // 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. - self.midi_note.clear(); - // TODO: support MIDI channels other than CH1. - let channel = 0.into(); - // Serialize MIDI event into message buffer. - LiveEvent::Midi { channel, message: *message } - .write(&mut self.midi_note) - .unwrap(); - // Append serialized message to output buffer. - self.midi_chunk[sample].push(self.midi_note.clone()); - // Update the list of currently held notes. - update_keys(notes, &message); - } - } - } - } - } - next - } - fn write (&mut self, scope: &ProcessScope) { - let samples = scope.n_frames() as usize; - for port in self.midi_outputs.iter_mut() { - let writer = &mut port.writer(scope); - let output = &self.midi_chunk; - for time in 0..samples { - for event in output[time].iter() { - writer.write(&RawMidi { time: time as u32, bytes: &event }) - .expect(&format!("{event:?}")); - } - } + if self.reset() || force_reset { + all_notes_off(&mut self.midi_buffer()); + *self.reset_mut() = false; } } } -pub trait MidiMonitorApi: MidiInputApi + MidiOutputApi { - fn monitor (&mut self, scope: &ProcessScope) { - let mut notes_in = self.notes_in.write().unwrap(); - for input in self.midi_inputs.iter() { - for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - self.midi_chunk[sample].push(bytes.to_vec()); - update_keys(&mut notes_in, &message); - } - } - } - } -} - -pub trait MidiLaunchApi: MidiInputApi + MidiOutputApi { +pub trait HasPhrase { + fn phrase (&self) -> &Option<(Instant, Arc>)>; + fn next_phrase (&self) -> &Option<(Instant, Arc>)>; fn switchover (&mut self, scope: &ProcessScope) { if self.is_rolling() { let sample0 = scope.last_frame_time() as usize; @@ -180,12 +52,6 @@ pub trait MidiLaunchApi: MidiInputApi + MidiOutputApi { } } } -} - -pub trait MidiPlayerApi: PlayheadApi { - fn toggle_monitor (&mut self) { - self.monitoring = !self.monitoring; - } fn enqueue_next (&mut self, phrase: Option<&Arc>>) { let start = self.clock.next_launch_pulse(); self.next_phrase = Some(( @@ -203,6 +69,170 @@ pub trait MidiPlayerApi: PlayheadApi { } } +pub trait MidiInputApi: PlayheadApi + HasMidiBuffer + HasPhrase { + fn midi_ins (&self) -> &Vec>; + fn midi_ins_mut (&self) -> &mut Vec>; + fn has_midi_ins (&self) -> bool { + self.midi_ins().len() > 0 + } + fn recording (&self) -> bool; + fn recording_mut (&mut self) -> &mut bool; + fn toggle_record (&mut self) { + *self.recording_mut() = !self.recording(); + } + + fn monitoring (&self) -> bool; + fn monitoring_mut (&mut self) -> &mut bool; + fn toggle_monitor (&mut self) { + *self.monitoring_mut() = !self.monitoring(); + } + + fn overdub (&self) -> bool; + fn overdub_mut (&mut self) -> &mut bool; + fn toggle_overdub (&mut self) { + *self.overdub_mut() = !self.overdub(); + } + + fn notes_in (&self) -> &Arc>; + + fn record (&mut self, scope: &ProcessScope) { + let sample0 = scope.last_frame_time() as usize; + if let (true, Some((started, phrase))) = (self.is_rolling(), self.phrase()) { + let start = started.sample.get() as usize; + let quant = self.quant().get(); + // For highlighting keys and note repeat + let mut notes_in = self.notes_in().write().unwrap(); + // Record from each input + for input in self.midi_ins_mut().iter() { + for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + if self.monitoring() { + self.midi_buffer()[sample].push(bytes.to_vec()) + } + if self.recording() { + if let Some(phrase) = phrase { + let mut phrase = phrase.write().unwrap(); + let length = phrase.length; + phrase.record_event({ + let sample = (sample0 + sample - start) as f64; + let pulse = self.timebase().samples_to_pulse(sample); + let quantized = (pulse / quant).round() * quant; + let looped = quantized as usize % length; + looped + }, message); + } + } + update_keys(&mut notes_in, &message); + } + } + } + } + if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase()) { + // TODO switch to next phrase and record into it + } + } + + fn monitor (&mut self, scope: &ProcessScope) { + let mut notes_in = self.notes_in().write().unwrap(); + 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 { + self.midi_buffer()[sample].push(bytes.to_vec()); + update_keys(&mut notes_in, &message); + } + } + } + } + +} + +pub trait MidiOutputApi: PlayheadApi + HasMidiBuffer + HasPhrase + HasClock { + fn midi_outs (&self) -> &Vec>; + + fn midi_outs_mut (&self) -> &mut Vec>; + + fn midi_note (&mut self) -> &mut Vec; + + fn notes_out (&mut self) -> &Arc>>; + + fn has_midi_outs (&self) -> bool { + self.midi_outs().len() > 0 + } + + fn play (&mut self, scope: &ProcessScope) -> bool { + let mut next = false; + // Write MIDI events from currently playing phrase (if any) to MIDI output buffer + if self.is_rolling() { + 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.phrase().is_none(); + if let Some((started, phrase)) = &self.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 + self.clock().started.read().unwrap().unwrap().0; + 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 = self.clock().timebase().pulses_between_samples(sample, sample + samples); + // Notes active during current chunk. + let notes = &mut self.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 = self.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(); + // 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. + self.midi_note().clear(); + // TODO: support MIDI channels other than CH1. + let channel = 0.into(); + // Serialize MIDI event into message buffer. + LiveEvent::Midi { channel, message: *message } + .write(&mut self.midi_note()) + .unwrap(); + // Append serialized message to output buffer. + self.midi_buffer()[sample].push(self.midi_note().clone()); + // Update the list of currently held notes. + update_keys(&mut*notes, &message); + } + } + } + } + } + next + } + + fn write (&mut self, scope: &ProcessScope) { + let samples = scope.n_frames() as usize; + for port in self.midi_outs_mut().iter_mut() { + let writer = &mut port.writer(scope); + let output = &self.midi_buffer(); + for time in 0..samples { + for event in output[time].iter() { + writer.write(&RawMidi { time: time as u32, bytes: &event }) + .expect(&format!("{event:?}")); + } + } + } + } +} + /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut [Vec>]) { let mut buf = vec![]; diff --git a/crates/tek_api/src/impls.rs b/crates/tek_api/src/impls.rs deleted file mode 100644 index bb5a258b..00000000 --- a/crates/tek_api/src/impls.rs +++ /dev/null @@ -1,207 +0,0 @@ -use crate::*; - -impl ClockApi for Clock { - fn quant (&self) -> &Quantize { - &self.quant - } - fn sync (&self) -> &LaunchSync { - &self.sync - } - fn current (&self) -> &Instant { - &self.current - } -} - -impl PlayheadApi for Clock {} - -impl HasJack for TransportModel { - fn jack (&self) -> &Arc> { - &self.jack - } -} - -impl HasClock for TransportModel { - fn clock (&self) -> &Arc { - &self.clock - } -} - -//impl TransportModelApi for TransportModel { - //fn transport (&self) -> &jack::Transport { - //&self.transport - //} - //fn metronome (&self) -> bool { - //self.metronome - //} -//} - -impl Debug for TransportModel { - fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { - f.debug_struct("transport") - .field("jack", &self.jack) - .field("transport", &"(JACK transport)") - .field("clock", &self.clock) - .field("metronome", &self.metronome) - .finish() - } -} - -impl HasJack for ArrangerModel { - fn jack (&self) -> &Arc> { - &self.transport.jack() - } -} - -impl HasClock for ArrangerModel { - fn clock (&self) -> &Arc { - &self.transport.clock() - } -} - -//impl TransportModelApi for ArrangerModel { - //fn transport (&self) -> &jack::Transport { - //&self.transport.transport() - //} - //fn metronome (&self) -> bool { - //self.transport.metronome() - //} -//} - -impl PhrasePoolModelApi for ArrangerModel { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - -impl ArrangerApi for ArrangerModel { - fn name (&self) -> &Arc> { - &self.name - } - fn tracks (&self) -> &Vec { - &self.tracks - } - fn tracks_mut (&mut self) -> &mut Vec { - &mut self.tracks - } - fn scenes (&self) -> &Vec { - &self.scenes - } - fn scenes_mut (&mut self) -> &mut Vec { - &mut self.scenes - } -} - -impl ArrangerScene { - - //TODO - //pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { - //let mut name = None; - //let mut clips = vec![]; - //edn!(edn in args { - //Edn::Map(map) => { - //let key = map.get(&Edn::Key(":name")); - //if let Some(Edn::Str(n)) = key { - //name = Some(*n); - //} else { - //panic!("unexpected key in scene '{name:?}': {key:?}") - //} - //}, - //Edn::Symbol("_") => { - //clips.push(None); - //}, - //Edn::Int(i) => { - //clips.push(Some(*i as usize)); - //}, - //_ => panic!("unexpected in scene '{name:?}': {edn:?}") - //}); - //Ok(ArrangerScene { - //name: Arc::new(name.unwrap_or("").to_string().into()), - //color: ItemColor::random(), - //clips, - //}) - //} -} - -impl HasJack for SequencerModel { - fn jack (&self) -> &Arc> { - self.transport.jack() - } -} - -impl HasClock for SequencerModel { - fn clock (&self) -> &Arc { - self.transport.clock() - } -} - -//impl TransportModelApi for SequencerModel { - //fn transport (&self) -> &jack::Transport { - //&self.transport.transport() - //} - //fn metronome (&self) -> bool { - //self.transport.metronome() - //} -//} - -impl PhrasePoolModelApi for SequencerModel { - fn phrases (&self) -> &Vec>> { - &self.phrases - } - fn phrases_mut (&mut self) -> &mut Vec>> { - &mut self.phrases - } -} - -impl HasPlayer for SequencerModel { - fn player (&self) -> &MIDIPlayer { - &self.player - } - fn player_mut (&mut self) -> &mut MIDIPlayer { - &mut self.player - } -} - -impl From for Clock { - fn from (current: Instant) -> Self { - Self { - playing: Some(TransportState::Stopped).into(), - started: None.into(), - quant: 24.into(), - sync: (current.timebase.ppq.get() * 4.).into(), - current, - } - } -} - -/// Methods used primarily by the process callback -impl MIDIPlayer { - pub fn new ( - jack: &Arc>, - clock: &Arc, - name: &str - ) -> Usually { - let jack = jack.read().unwrap(); - Ok(Self { - clock: clock.clone(), - phrase: None, - next_phrase: None, - notes_in: Arc::new(RwLock::new([false;128])), - notes_out: Arc::new(RwLock::new([false;128])), - monitoring: false, - recording: false, - overdub: true, - reset: true, - midi_note: Vec::with_capacity(8), - midi_chunk: vec![Vec::with_capacity(16);16384], - midi_outputs: vec![ - jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())? - ], - midi_inputs: vec![ - jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())? - ], - }) - } -} diff --git a/crates/tek_api/src/lib.rs b/crates/tek_api/src/lib.rs index 36c5fef7..e874f170 100644 --- a/crates/tek_api/src/lib.rs +++ b/crates/tek_api/src/lib.rs @@ -15,31 +15,28 @@ submod! { api_arranger_clip api_arranger_scene api_arranger_track - api_clock - + api_phrase + api_playhead + api_pool + api_sequencer //api_mixer //api_mixer_track - - api_phrase - - api_playhead - //api_plugin //api_plugin_kind //api_plugin_lv2 - - api_pool - //api_sampler //api_sampler_sample //api_sampler_voice - api_sequencer - - impls - - models + model_arranger + model_arranger_scene + model_arranger_track + model_clock + model_player + model_pool + model_sequencer + model_transport } pub trait HasJack { diff --git a/crates/tek_api/src/model_arranger.rs b/crates/tek_api/src/model_arranger.rs new file mode 100644 index 00000000..654cce54 --- /dev/null +++ b/crates/tek_api/src/model_arranger.rs @@ -0,0 +1,63 @@ +use crate::*; + +#[derive(Debug)] +pub struct ArrangerModel { + /// State of the JACK transport. + transport: TransportModel, + /// Collection of phrases. + phrases: Vec>>, + /// Collection of tracks. + tracks: Vec, + /// Collection of scenes. + scenes: Vec, + /// Name of arranger + name: Arc>, +} + +impl HasJack for ArrangerModel { + fn jack (&self) -> &Arc> { + &self.transport.jack() + } +} + +impl HasClock for ArrangerModel { + fn clock (&self) -> &Arc { + &self.transport.clock() + } +} + +//impl TransportModelApi for ArrangerModel { + //fn transport (&self) -> &jack::Transport { + //&self.transport.transport() + //} + //fn metronome (&self) -> bool { + //self.transport.metronome() + //} +//} + +impl HasPhrases for ArrangerModel { + fn phrases (&self) -> &Vec>> { + &self.phrases + } + fn phrases_mut (&mut self) -> &mut Vec>> { + &mut self.phrases + } +} + +impl ArrangerApi for ArrangerModel { + fn name (&self) -> &Arc> { + &self.name + } + fn tracks (&self) -> &Vec { + &self.tracks + } + fn tracks_mut (&mut self) -> &mut Vec { + &mut self.tracks + } + fn scenes (&self) -> &Vec { + &self.scenes + } + fn scenes_mut (&mut self) -> &mut Vec { + &mut self.scenes + } +} diff --git a/crates/tek_api/src/model_arranger_scene.rs b/crates/tek_api/src/model_arranger_scene.rs new file mode 100644 index 00000000..7c0d8fb4 --- /dev/null +++ b/crates/tek_api/src/model_arranger_scene.rs @@ -0,0 +1,42 @@ +use crate::*; + +#[derive(Default, Debug, Clone)] +pub struct ArrangerScene { + /// Name of scene + pub name: Arc>, + /// Clips in scene, one per track + pub clips: Vec>>>, + /// Identifying color of scene + pub color: ItemColor, +} + +impl 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_arranger_track.rs b/crates/tek_api/src/model_arranger_track.rs new file mode 100644 index 00000000..eda31bc5 --- /dev/null +++ b/crates/tek_api/src/model_arranger_track.rs @@ -0,0 +1,13 @@ +use crate::*; + +#[derive(Debug)] +pub struct ArrangerTrack { + /// Name of track + pub name: Arc>, + /// Preferred width of track column + pub width: usize, + /// Identifying color of track + pub color: ItemColor, + /// The MIDI player for the track + pub player: MIDIPlayer +} diff --git a/crates/tek_api/src/model_clock.rs b/crates/tek_api/src/model_clock.rs new file mode 100644 index 00000000..52d06b36 --- /dev/null +++ b/crates/tek_api/src/model_clock.rs @@ -0,0 +1,43 @@ +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 { +} + +impl From for Clock { + fn from (current: Instant) -> Self { + Self { + playing: Some(TransportState::Stopped).into(), + started: None.into(), + quant: 24.into(), + sync: (current.timebase.ppq.get() * 4.).into(), + current, + } + } +} diff --git a/crates/tek_api/src/model_player.rs b/crates/tek_api/src/model_player.rs new file mode 100644 index 00000000..92479f0b --- /dev/null +++ b/crates/tek_api/src/model_player.rs @@ -0,0 +1,61 @@ +use crate::*; + +#[derive(Debug)] +pub struct MIDIPlayer { + /// Global timebase + pub clock: Arc, + /// Start time and phrase being played + pub phrase: Option<(Instant, Option>>)>, + /// Start time and next phrase + pub next_phrase: Option<(Instant, Option>>)>, + /// Play input through output. + pub monitoring: bool, + /// Write input to sequence. + pub recording: bool, + /// Overdub input to sequence. + pub overdub: bool, + /// Send all notes off + pub reset: bool, // TODO?: after Some(nframes) + /// Record from MIDI ports to current sequence. + pub midi_inputs: Vec>, + /// Play from current sequence to MIDI ports + pub midi_outputs: Vec>, + /// MIDI output buffer + pub midi_note: Vec, + /// MIDI output buffer + pub midi_chunk: Vec>>, + /// Notes currently held at input + pub notes_in: Arc>, + /// Notes currently held at output + pub notes_out: Arc>, +} + +/// Methods used primarily by the process callback +impl MIDIPlayer { + pub fn new ( + jack: &Arc>, + clock: &Arc, + name: &str + ) -> Usually { + let jack = jack.read().unwrap(); + Ok(Self { + clock: clock.clone(), + phrase: None, + next_phrase: None, + notes_in: Arc::new(RwLock::new([false;128])), + notes_out: Arc::new(RwLock::new([false;128])), + monitoring: false, + recording: false, + overdub: true, + reset: true, + midi_note: Vec::with_capacity(8), + midi_chunk: vec![Vec::with_capacity(16);16384], + midi_outputs: vec![ + jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())? + ], + midi_inputs: vec![ + jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())? + ], + }) + } +} diff --git a/crates/tek_api/src/model_pool.rs b/crates/tek_api/src/model_pool.rs new file mode 100644 index 00000000..ce8d7156 --- /dev/null +++ b/crates/tek_api/src/model_pool.rs @@ -0,0 +1,17 @@ +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_sequencer.rs b/crates/tek_api/src/model_sequencer.rs new file mode 100644 index 00000000..daa639a0 --- /dev/null +++ b/crates/tek_api/src/model_sequencer.rs @@ -0,0 +1,49 @@ +use crate::*; + +pub struct SequencerModel { + /// State of the JACK transport. + transport: TransportModel, + /// State of the phrase pool. + phrases: Vec>>, + /// State of the phrase player. + player: MIDIPlayer, +} + +impl HasJack for SequencerModel { + fn jack (&self) -> &Arc> { + self.transport.jack() + } +} + +impl HasClock for SequencerModel { + fn clock (&self) -> &Arc { + self.transport.clock() + } +} + +//impl TransportModelApi for SequencerModel { + //fn transport (&self) -> &jack::Transport { + //&self.transport.transport() + //} + //fn metronome (&self) -> bool { + //self.transport.metronome() + //} +//} + +impl HasPhrases for SequencerModel { + fn phrases (&self) -> &Vec>> { + &self.phrases + } + fn phrases_mut (&mut self) -> &mut Vec>> { + &mut self.phrases + } +} + +impl HasPlayer for SequencerModel { + fn player (&self) -> &MIDIPlayer { + &self.player + } + fn player_mut (&mut self) -> &mut MIDIPlayer { + &mut self.player + } +} diff --git a/crates/tek_api/src/model_transport.rs b/crates/tek_api/src/model_transport.rs new file mode 100644 index 00000000..afc40c4a --- /dev/null +++ b/crates/tek_api/src/model_transport.rs @@ -0,0 +1,43 @@ +use crate::*; + +pub struct TransportModel { + jack: Arc>, + /// Current sample rate, tempo, and PPQ. + clock: Arc, + /// JACK transport handle. + transport: jack::Transport, + /// Enable metronome? + metronome: bool, +} + +impl HasJack for TransportModel { + fn jack (&self) -> &Arc> { + &self.jack + } +} + +impl HasClock for TransportModel { + fn clock (&self) -> &Arc { + &self.clock + } +} + +//impl TransportModelApi for TransportModel { + //fn transport (&self) -> &jack::Transport { + //&self.transport + //} + //fn metronome (&self) -> bool { + //self.metronome + //} +//} + +impl Debug for TransportModel { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { + f.debug_struct("transport") + .field("jack", &self.jack) + .field("transport", &"(JACK transport)") + .field("clock", &self.clock) + .field("metronome", &self.metronome) + .finish() + } +} diff --git a/crates/tek_api/src/models.rs b/crates/tek_api/src/models.rs index d2d9cca0..c7b7e813 100644 --- a/crates/tek_api/src/models.rs +++ b/crates/tek_api/src/models.rs @@ -1,101 +1 @@ use crate::*; - -/// A timer with starting point, current time, and quantization -#[derive(Default, Debug)] -pub struct Clock { - /// Playback state - pub playing: RwLock>, - /// Global sample and usec at which playback started - pub started: RwLock>, - /// Current moment in time - pub current: Instant, - /// Note quantization factor - pub quant: Quantize, - /// Launch quantization factor - pub sync: LaunchSync, -} - -pub struct TransportModel { - jack: Arc>, - /// Current sample rate, tempo, and PPQ. - clock: Arc, - /// JACK transport handle. - transport: jack::Transport, - /// Enable metronome? - metronome: bool, -} - -#[derive(Debug)] -pub struct ArrangerModel { - /// State of the JACK transport. - transport: TransportModel, - /// Collection of phrases. - phrases: Vec>>, - /// Collection of tracks. - tracks: Vec, - /// Collection of scenes. - scenes: Vec, - /// Name of arranger - name: Arc>, -} - -#[derive(Default, Debug, Clone)] -pub struct ArrangerScene { - /// Name of scene - pub name: Arc>, - /// Clips in scene, one per track - pub clips: Vec>>>, - /// Identifying color of scene - pub color: ItemColor, -} - -#[derive(Debug)] -pub struct ArrangerTrack { - /// Name of track - pub name: Arc>, - /// Preferred width of track column - pub width: usize, - /// Identifying color of track - pub color: ItemColor, - /// The MIDI player for the track - pub player: MIDIPlayer -} - -pub struct SequencerModel { - /// State of the JACK transport. - transport: TransportModel, - /// State of the phrase pool. - phrases: Vec>>, - /// State of the phrase player. - player: MIDIPlayer, -} - -#[derive(Debug)] -pub struct MIDIPlayer { - /// Global timebase - pub clock: Arc, - /// Start time and phrase being played - pub phrase: Option<(Instant, Option>>)>, - /// Start time and next phrase - pub next_phrase: Option<(Instant, Option>>)>, - /// Play input through output. - pub monitoring: bool, - /// Write input to sequence. - pub recording: bool, - /// Overdub input to sequence. - pub overdub: bool, - /// Send all notes off - pub reset: bool, // TODO?: after Some(nframes) - /// Record from MIDI ports to current sequence. - pub midi_inputs: Vec>, - /// Play from current sequence to MIDI ports - pub midi_outputs: Vec>, - /// MIDI output buffer - pub midi_note: Vec, - /// MIDI output buffer - pub midi_chunk: Vec>>, - /// Notes currently held at input - pub notes_in: Arc>, - /// Notes currently held at output - pub notes_out: Arc>, -} diff --git a/crates/tek_api/src/api_mixer.rs b/crates/tek_api/src/todo_api_mixer.rs similarity index 100% rename from crates/tek_api/src/api_mixer.rs rename to crates/tek_api/src/todo_api_mixer.rs diff --git a/crates/tek_api/src/api_mixer_track.rs b/crates/tek_api/src/todo_api_mixer_track.rs similarity index 100% rename from crates/tek_api/src/api_mixer_track.rs rename to crates/tek_api/src/todo_api_mixer_track.rs diff --git a/crates/tek_api/src/api_plugin.rs b/crates/tek_api/src/todo_api_plugin.rs similarity index 100% rename from crates/tek_api/src/api_plugin.rs rename to crates/tek_api/src/todo_api_plugin.rs diff --git a/crates/tek_api/src/api_plugin_kind.rs b/crates/tek_api/src/todo_api_plugin_kind.rs similarity index 100% rename from crates/tek_api/src/api_plugin_kind.rs rename to crates/tek_api/src/todo_api_plugin_kind.rs diff --git a/crates/tek_api/src/api_plugin_lv2.rs b/crates/tek_api/src/todo_api_plugin_lv2.rs similarity index 100% rename from crates/tek_api/src/api_plugin_lv2.rs rename to crates/tek_api/src/todo_api_plugin_lv2.rs diff --git a/crates/tek_api/src/api_sampler.rs b/crates/tek_api/src/todo_api_sampler.rs similarity index 100% rename from crates/tek_api/src/api_sampler.rs rename to crates/tek_api/src/todo_api_sampler.rs diff --git a/crates/tek_api/src/api_sampler_sample.rs b/crates/tek_api/src/todo_api_sampler_sample.rs similarity index 100% rename from crates/tek_api/src/api_sampler_sample.rs rename to crates/tek_api/src/todo_api_sampler_sample.rs diff --git a/crates/tek_api/src/api_sampler_voice.rs b/crates/tek_api/src/todo_api_sampler_voice.rs similarity index 100% rename from crates/tek_api/src/api_sampler_voice.rs rename to crates/tek_api/src/todo_api_sampler_voice.rs