refactor PhrasePlayer::process callback

This commit is contained in:
🪞👃🪞 2024-11-01 20:06:42 +02:00
parent 0820c10f8b
commit 3df8e87840
4 changed files with 118 additions and 87 deletions

View file

@ -22,6 +22,8 @@ pub struct Arranger<E: Engine> {
pub arrangement_split: u16, pub arrangement_split: u16,
/// Width of phrase pool /// Width of phrase pool
pub phrases_split: u16, pub phrases_split: u16,
/// Width and height of app at last render
pub size: Measure<E>,
} }
/// Sections in the arranger app that may be focused /// Sections in the arranger app that may be focused
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
@ -135,6 +137,7 @@ impl<E: Engine> Arranger<E> {
transport, transport,
arrangement, arrangement,
phrases, phrases,
size: Measure::new(),
}; };
app.update_focus(); app.update_focus();
app app

View file

@ -118,7 +118,7 @@ impl Content for Arrangement<Tui> {
ArrangementViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor)), ArrangementViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor)),
}?; }?;
let color = if self.focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)}; let color = if self.focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)};
add(&TuiStyle::fg("Session", color).push_x(1))?; add(&TuiStyle::fg("Session", color).push_x(2))?;
add(&self.size) add(&self.size)
}) })
} }
@ -137,7 +137,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
let border_hi = Color::Rgb(100, 110, 40); let border_hi = Color::Rgb(100, 110, 40);
let border_lo = Color::Rgb(70, 80, 50); let border_lo = Color::Rgb(70, 80, 50);
let border_fg = if self.0.focused { border_hi } else { border_lo }; let border_fg = if self.0.focused { border_hi } else { border_lo };
let border = Lozenge(Style::default().bg(border_bg).fg(border_fg)); //let border = Lozenge(Style::default().bg(border_bg).fg(border_fg));
let track_title_h = 2u16; let track_title_h = 2u16;
let tracks_footer = 3u16; let tracks_footer = 3u16;
let scene_title_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track let scene_title_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track

View file

@ -403,15 +403,6 @@ impl PhrasePlayer {
.map(|(started,_)|started.sample().get()) .map(|(started,_)|started.sample().get())
.map(|started|(started - self.clock.instant.sample().get()) as usize) .map(|started|(started - self.clock.instant.sample().get()) as usize)
} }
pub fn playing_phrase (&self) -> Option<(usize, Arc<RwLock<Phrase>>)> {
if let (
Some(TransportState::Rolling), Some((started, Some(ref phrase)))
) = (*self.clock.playing.read().unwrap(), &self.phrase) {
Some((started.sample().get() as usize, phrase.clone()))
} else {
None
}
}
} }
/// Displays and edits phrase length /// Displays and edits phrase length
pub struct PhraseLength<E: Engine> { pub struct PhraseLength<E: Engine> {

View file

@ -10,24 +10,70 @@ impl<E: Engine> Audio for Sequencer<E> {
} }
impl Audio for PhrasePlayer { impl Audio for PhrasePlayer {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let frame0 = scope.last_frame_time() as usize;
let frames = scope.n_frames() as usize;
let has_midi_outputs = self.has_midi_outputs(); let has_midi_outputs = self.has_midi_outputs();
if has_midi_outputs {
self.clear_midi_out_buf(frames);
self.reset_midi_out_buf(false /* FIXME where did force-reset come from? */);
}
let has_midi_inputs = self.has_midi_inputs(); let has_midi_inputs = self.has_midi_inputs();
let quant = self.clock.quant().get();
if let Some((start_frame, phrase)) = self.playing_phrase() {
// Write chunk of phrase to output
phrase.read().map(|phrase|{
if has_midi_outputs { if has_midi_outputs {
// Clear output buffer(s)
self.clear(scope, false);
// Write chunk of phrase to output
self.play(scope);
}
if has_midi_inputs {
if self.recording || self.monitoring {
// Record and/or monitor input
self.record(scope)
} else if has_midi_outputs && self.monitoring {
// Monitor input to output
self.monitor(scope)
}
}
// Write to output port(s)
self.write(scope);
Control::Continue
}
}
impl PhrasePlayer {
fn has_midi_inputs (&self) -> bool { self.midi_inputs.len() > 0 }
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_out_buf[0..scope.n_frames() as usize] {
frame.clear();
}
if self.reset || force_reset {
all_notes_off(&mut self.midi_out_buf); self.reset = false;
}
}
/// Return playing phrase with starting point
fn playing (&self) -> Option<(usize, Arc<RwLock<Phrase>>)> {
if let (
Some(TransportState::Rolling), Some((started, Some(ref phrase)))
) = (*self.clock.playing.read().unwrap(), &self.phrase) {
Some((started.sample().get() as usize, phrase.clone()))
} else {
None
}
}
/// Return next phrase with starting point
fn enqueued (&self) -> Option<(usize, Arc<RwLock<Phrase>>)> {
if let (
Some(TransportState::Rolling), Some((start_at, Some(ref phrase)))
) = (*self.clock.playing.read().unwrap(), &self.next_phrase) {
Some((start_at.sample().get() as usize, phrase.clone()))
} else {
None
}
}
fn play (&mut self, scope: &ProcessScope) {
let sample0 = scope.last_frame_time() as usize;
let samples = scope.n_frames() as usize;
if let Some((start, ref phrase)) = self.playing() {
let sample = sample0.saturating_sub(start);
let ticks = self.clock.timebase().pulses_between_samples(sample, sample + samples);
phrase.read().map(|phrase|{
let output = &mut self.midi_out_buf; let output = &mut self.midi_out_buf;
let notes_on = &mut self.notes_out.write().unwrap(); let notes_on = &mut self.notes_out.write().unwrap();
let frame0 = frame0.saturating_sub(start_frame);
let frame_n = frame0 + frames;
let ticks = self.clock.timebase().pulses_between_samples(frame0, frame_n);
let mut buf = Vec::with_capacity(8); let mut buf = Vec::with_capacity(8);
for (sample, tick) in ticks { for (sample, tick) in ticks {
let tick = tick % phrase.length; let tick = tick % phrase.length;
@ -40,23 +86,26 @@ impl Audio for PhrasePlayer {
update_keys(notes_on, &message); update_keys(notes_on, &message);
} }
} }
}).unwrap()
} }
}).unwrap(); }
fn record (&mut self, scope: &ProcessScope) {
let sample0 = scope.last_frame_time() as usize;
if let Some((start, ref phrase)) = self.playing() {
let quant = self.clock.quant().get();
let mut phrase = phrase.write().unwrap(); let mut phrase = phrase.write().unwrap();
let length = phrase.length; let length = phrase.length;
// Monitor and record input
if has_midi_inputs && (self.recording || self.monitoring) {
// For highlighting keys and note repeat // For highlighting keys and note repeat
let mut notes_in = self.notes_in.write().unwrap(); let mut notes_in = self.notes_in.write().unwrap();
// Record from each input // Record from each input
for input in self.midi_inputs.iter() { for input in self.midi_inputs.iter() {
for (frame, event, bytes) in parse_midi_input(input.iter(scope)) { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event { if let LiveEvent::Midi { message, .. } = event {
if self.monitoring { self.midi_out_buf[frame].push(bytes.to_vec()) } if self.monitoring { self.midi_out_buf[sample].push(bytes.to_vec()) }
if self.recording { if self.recording {
phrase.record_event({ phrase.record_event({
let pulse = self.clock.timebase().samples_to_pulse( let pulse = self.clock.timebase().samples_to_pulse(
(frame0 + frame - start_frame) as f64 (sample0 + sample - start) as f64
); );
let quantized = (pulse / quant).round() * quant; let quantized = (pulse / quant).round() * quant;
let looped = quantized as usize % length; let looped = quantized as usize % length;
@ -68,42 +117,30 @@ impl Audio for PhrasePlayer {
} }
} }
} }
} else if has_midi_inputs && has_midi_outputs && self.monitoring { }
fn monitor (&mut self, scope: &ProcessScope) {
let mut notes_in = self.notes_in.write().unwrap(); let mut notes_in = self.notes_in.write().unwrap();
// Monitor each input
for input in self.midi_inputs.iter() { for input in self.midi_inputs.iter() {
for (frame, event, bytes) in parse_midi_input(input.iter(scope)) { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event { if let LiveEvent::Midi { message, .. } = event {
self.midi_out_buf[frame].push(bytes.to_vec()); self.midi_out_buf[sample].push(bytes.to_vec());
update_keys(&mut notes_in, &message); update_keys(&mut notes_in, &message);
} }
} }
} }
} }
// Write to midi output fn write (&mut self, scope: &ProcessScope) {
let samples = scope.n_frames() as usize;
for port in self.midi_outputs.iter_mut() { for port in self.midi_outputs.iter_mut() {
let writer = &mut port.writer(scope); let writer = &mut port.writer(scope);
let output = &self.midi_out_buf; let output = &self.midi_out_buf;
for time in 0..frames { for time in 0..samples {
for event in output[time].iter() { for event in output[time].iter() {
writer.write(&RawMidi { time: time as u32, bytes: &event }) writer.write(&RawMidi { time: time as u32, bytes: &event })
.expect(&format!("{event:?}")); .expect(&format!("{event:?}"));
} }
} }
} }
Control::Continue
}
}
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 || force_reset { all_notes_off(&mut self.midi_out_buf); self.reset = false; }
} }
} }
/// Add "all notes off" to the start of a buffer. /// Add "all notes off" to the start of a buffer.