From eba7044916053a973bfc012f9c1f9fd542a2cb50 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 27 Oct 2024 16:53:05 +0200 Subject: [PATCH] reenable phrase recording/playback, pt.1 --- crates/tek_sequencer/src/sequencer.rs | 12 +- crates/tek_sequencer/src/sequencer_snd.rs | 127 ++++++++++++---------- crates/tek_sequencer/src/transport.rs | 23 ++-- 3 files changed, 92 insertions(+), 70 deletions(-) diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index fe2bbba7..77ac6b97 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -119,6 +119,8 @@ pub struct PhrasePlayer { pub clock: Arc, /// Start time and phrase being played pub phrase: Option<(AtomicUsize, Option>>)>, + /// Start time (FIXME move into phrase, using Instant) + pub started: Option<(usize, usize)>, /// Start time and next phrase pub next_phrase: Option<(AtomicUsize, Option>>)>, /// Play input through output. @@ -129,8 +131,10 @@ pub struct PhrasePlayer { pub overdub: bool, /// Send all notes off pub reset: bool, // TODO?: after Some(nframes) - /// Output from current sequence. - pub midi_out: Option>, + /// 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_out_buf: Vec>>, /// Notes currently held at input @@ -333,13 +337,15 @@ impl PhrasePlayer { _engine: Default::default(), clock: clock.clone(), phrase: None, + started: 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, - midi_out: None, + midi_inputs: vec![], + midi_outputs: vec![], midi_out_buf: vec![Vec::with_capacity(16);16384], reset: true, } diff --git a/crates/tek_sequencer/src/sequencer_snd.rs b/crates/tek_sequencer/src/sequencer_snd.rs index 4514476d..f1ba8d8e 100644 --- a/crates/tek_sequencer/src/sequencer_snd.rs +++ b/crates/tek_sequencer/src/sequencer_snd.rs @@ -7,73 +7,37 @@ impl Audio for Sequencer { Control::Continue } } -impl Phrase { - /// Write a chunk of MIDI events to an output port. - pub fn process_out ( - &self, - output: &mut PhraseChunk, - notes_on: &mut [bool;128], - timebase: &Arc, - (frame0, frames, _): (usize, usize, f64), - ) { - let mut buf = Vec::with_capacity(8); - for (time, tick) in Ticks(timebase.pulses_per_sample()).between_samples( - frame0, frame0 + frames - ) { - let tick = tick % self.length; - for message in self.notes[tick].iter() { - buf.clear(); - let channel = 0.into(); - let message = *message; - LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); - output[time as usize].push(buf.clone()); - match message { - MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true, - MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false, - _ => {} - } - } - } - } -} impl PhrasePlayer { pub fn process ( &mut self, - input: Option, - timebase: &Arc, - playing: Option, - started: Option<(usize, usize)>, - quant: usize, - reset: bool, - scope: &ProcessScope, + input: Option, + scope: &ProcessScope, (frame0, frames): (usize, usize), (_usec0, _usecs): (usize, usize), - period: f64, + period: f64, ) { - if self.midi_out.is_some() { - // Clear the section of the output buffer that we will be using - for frame in &mut self.midi_out_buf[0..frames] { - frame.clear(); - } - // Emit "all notes off" at start of buffer if requested - if self.reset { - all_notes_off(&mut self.midi_out_buf); - self.reset = false; - } else if reset { - all_notes_off(&mut self.midi_out_buf); - } + let has_midi_inputs = self.has_midi_inputs(); + let has_midi_outputs = self.has_midi_outputs(); + let quant = self.clock.quant(); + if has_midi_outputs { + self.clear_midi_out_buf(frames); + self.reset_midi_out_buf(false /* FIXME where did force-reset come from? */); } if let ( Some(TransportState::Rolling), Some((start_frame, _)), Some((_started, Some(ref phrase))) - ) = (playing, started, &self.phrase) { + ) = ( + *self.clock.playing.read().unwrap(), + self.started, + &self.phrase + ) { phrase.read().map(|phrase|{ - if self.midi_out.is_some() { + if has_midi_outputs { phrase.process_out( &mut self.midi_out_buf, &mut self.notes_out.write().unwrap(), - timebase, + &self.clock.timebase, (frame0.saturating_sub(start_frame), frames, period) ); } @@ -92,7 +56,7 @@ impl PhrasePlayer { } if self.recording { phrase.record_event({ - let pulse = timebase.samples_to_pulse( + let pulse = self.clock.timebase.samples_to_pulse( (frame0 + frame - start_frame) as f64 ); let quantized = ( @@ -116,7 +80,7 @@ impl PhrasePlayer { } } } - } else if input.is_some() && self.midi_out.is_some() && self.monitoring { + } else if input.is_some() && has_midi_outputs && self.monitoring { let mut notes_in = self.notes_in.write().unwrap(); for (frame, event, bytes) in parse_midi_input(input.unwrap()) { match event { @@ -136,8 +100,9 @@ impl PhrasePlayer { } } } - if let Some(out) = &mut self.midi_out { - let writer = &mut out.writer(scope); + + for port in self.midi_outputs.iter_mut() { + let writer = &mut port.writer(scope); let output = &self.midi_out_buf; for time in 0..frames { for event in output[time].iter() { @@ -147,6 +112,56 @@ impl PhrasePlayer { } } } + pub fn has_midi_inputs (&self) -> bool { + self.midi_inputs.len() > 0 + } + pub fn has_midi_outputs (&self) -> bool { + self.midi_outputs.len() > 0 + } + /// Clear the section of the output buffer that we will be using + pub fn clear_midi_out_buf (&mut self, frames: usize) { + for frame in &mut self.midi_out_buf[0..frames] { + frame.clear(); + } + } + /// Emit "all notes off" at start of buffer if requested + pub fn reset_midi_out_buf (&mut self, force_reset: bool) { + if self.reset { + all_notes_off(&mut self.midi_out_buf); + self.reset = false; + } else if force_reset { + all_notes_off(&mut self.midi_out_buf); + } + } +} +impl Phrase { + /// Write a chunk of MIDI events to an output port. + pub fn process_out ( + &self, + output: &mut PhraseChunk, + notes_on: &mut [bool;128], + timebase: &Timebase, + (frame0, frames, _): (usize, usize, f64), + ) { + let mut buf = Vec::with_capacity(8); + for (time, tick) in Ticks(timebase.pulses_per_sample()).between_samples( + frame0, frame0 + frames + ) { + let tick = tick % self.length; + for message in self.notes[tick].iter() { + buf.clear(); + let channel = 0.into(); + let message = *message; + LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); + output[time as usize].push(buf.clone()); + match message { + MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true, + MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false, + _ => {} + } + } + } + } } /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut PhraseChunk) { diff --git a/crates/tek_sequencer/src/transport.rs b/crates/tek_sequencer/src/transport.rs index a2049d9e..32b740b8 100644 --- a/crates/tek_sequencer/src/transport.rs +++ b/crates/tek_sequencer/src/transport.rs @@ -85,17 +85,18 @@ impl TransportToolbar { started: None, jack: None, transport, - clock: if let Some(clock) = clock { - clock.clone() - } else { - let timebase = Timebase::default(); - Arc::new(TransportTime { - playing: Some(TransportState::Stopped).into(), - quant: 24.into(), - sync: (timebase.ppq() as usize * 4).into(), - instant: Instant::default(), - timebase, - }) + clock: match clock { + Some(clock) => clock.clone(), + None => { + let timebase = Timebase::default(); + Arc::new(TransportTime { + playing: Some(TransportState::Stopped).into(), + quant: 24.into(), + sync: (timebase.ppq() as usize * 4).into(), + instant: Instant::default(), + timebase, + }) + } } } }