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().is_empty() } /// Buffer for serializing a MIDI event. FIXME rename fn midi_note (&mut self) -> &mut Vec; } pub trait MidiPlaybackApi: HasPlayClip + 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: &mut [Vec>], reset: bool ) { for frame in &mut out[0..scope.n_frames() as usize] { frame.clear(); } if reset { all_notes_off(out); } } /// Output notes from clip to MIDI output ports. fn play ( &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] ) -> bool { if !self.clock().is_rolling() { return false } // If a clip is playing, write a chunk of MIDI events from it to the output buffer. // If no clip is playing, prepare for switchover immediately. self.play_clip().as_ref().map_or(true, |(started, clip)|{ self.play_chunk(scope, note_buf, out, started, clip) }) } /// Handle switchover from current to next playing clip. fn switchover ( &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] ) { if !self.clock().is_rolling() { return } let sample0 = scope.last_frame_time() as usize; //let samples = scope.n_frames() as usize; if let Some((start_at, clip)) = &self.next_clip() { 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 clip: if start <= sample0.saturating_sub(sample) { // Samples elapsed since clip was supposed to start let _skipped = sample0 - start; // Switch over to enqueued clip let started = Moment::from_sample(self.clock().timebase(), start as f64); // Launch enqueued clip *self.play_clip_mut() = Some((started, clip.clone())); // Unset enqueuement (TODO: where to implement looping?) *self.next_clip_mut() = None; // Fill in remaining ticks of chunk from next clip. self.play(scope, note_buf, out); } } } fn play_chunk ( &self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>], started: &Moment, clip: &Option>> ) -> bool { // First sample to populate. Greater than 0 means that the first // pulse of the clip falls somewhere in the middle of the chunk. let sample = (scope.last_frame_time() as usize).saturating_sub( started.sample.get() as usize + self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize ); // Iterator that emits sample (index into output buffer at which to write MIDI event) // paired with pulse (index into clip 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 + scope.n_frames() as usize); // Notes active during current chunk. let notes = &mut self.notes_out().write().unwrap(); let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); for (sample, pulse) in pulses { // If a next clip is enqueued, and we're past the end of the current one, // break the loop here (FIXME count pulse correctly) let past_end = if clip.is_some() { pulse >= length } else { true }; if self.next_clip().is_some() && past_end { return true } // If there's a currently playing clip, output notes from it to buffer: if let Some(ref clip) = clip { Self::play_pulse(clip, pulse, sample, note_buf, out, notes) } } false } fn play_pulse ( clip: &RwLock, pulse: usize, sample: usize, note_buf: &mut Vec, out: &mut [Vec>], notes: &mut [bool;128] ) { // Source clip from which the MIDI events will be taken. let clip = clip.read().unwrap(); // Clip with zero length is not processed if clip.length > 0 { // Current pulse index in source clip let pulse = pulse % clip.length; // Output each MIDI event from clip at appropriate frames of output buffer: for message in clip.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[sample].push(note_buf.clone()); // Update the list of currently held notes. update_keys(&mut*notes, message); } } } /// Write a chunk of MIDI data from the output buffer to all assigned output ports. fn write (&mut self, scope: &ProcessScope, out: &[Vec>]) { let samples = scope.n_frames() as usize; for port in self.midi_outs_mut().iter_mut() { Self::write_port(&mut port.port_mut().writer(scope), samples, out) } } /// Write a chunk of MIDI data from the output buffer to an output port. fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec>]) { for (time, events) in out.iter().enumerate().take(samples) { for bytes in events.iter() { writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{ panic!("Failed to write MIDI data: {bytes:?}"); }); } } } }