//! MIDI sequencer //! ``` //! use crate::*; //! //! let clip = MidiClip::default(); //! println!("Empty clip: {clip:?}"); //! //! let clip = MidiClip::stop_all(); //! println!("Panic clip: {clip:?}"); //! //! let mut clip = MidiClip::new("clip", true, 1, None, None); //! clip.set_length(96); //! clip.toggle_loop(); //! clip.record_event(12, MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }); //! assert!(clip.contains_note_on(36.into(), 6, 18)); //! assert_eq!(&clip.notes, &clip.duplicate().notes); //! //! let clip = std::sync::Arc::new(clip); //! assert_eq!(clip.clone(), clip); //! //! let sequencer = Sequencer::default(); //! println!("{sequencer:?}"); //! ``` use crate::*; impl> HasSequencer for T { fn sequencer (&self) -> &Sequencer { self.get() } fn sequencer_mut (&mut self) -> &mut Sequencer { self.get_mut() } } pub trait HasSequencer { fn sequencer (&self) -> &Sequencer; fn sequencer_mut (&mut self) -> &mut Sequencer; } /// Contains state for playing a clip pub struct Sequencer { /// State of clock and playhead #[cfg(feature = "clock")] pub clock: Clock, /// Start time and clip being played #[cfg(feature = "clip")] pub play_clip: Option<(Moment, Option>>)>, /// Start time and next clip #[cfg(feature = "clip")] pub next_clip: Option<(Moment, Option>>)>, /// Record from MIDI ports to current sequence. #[cfg(feature = "port")] pub midi_ins: Vec, /// Play from current sequence to MIDI ports #[cfg(feature = "port")] pub midi_outs: Vec, /// 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) /// Notes currently held at input pub notes_in: Arc>, /// Notes currently held at output pub notes_out: Arc>, /// MIDI output buffer pub note_buf: Vec, /// MIDI output buffer pub midi_buf: Vec>>, } impl Default for Sequencer { fn default () -> Self { Self { #[cfg(feature = "clock")] clock: Clock::default(), #[cfg(feature = "clip")] play_clip: None, #[cfg(feature = "clip")] next_clip: None, #[cfg(feature = "port")] midi_ins: vec![], #[cfg(feature = "port")] midi_outs: vec![], recording: false, monitoring: true, overdub: false, notes_in: RwLock::new([false;128]).into(), notes_out: RwLock::new([false;128]).into(), note_buf: vec![0;8], midi_buf: vec![], reset: true, } } } impl Sequencer { pub fn new ( name: impl AsRef, jack: &Jack<'static>, #[cfg(feature = "clock")] clock: Option<&Clock>, #[cfg(feature = "clip")] clip: Option<&Arc>>, #[cfg(feature = "port")] midi_from: &[Connect], #[cfg(feature = "port")] midi_to: &[Connect], ) -> Usually { let _name = name.as_ref(); #[cfg(feature = "clock")] let clock = clock.cloned().unwrap_or_default(); Ok(Self { reset: true, notes_in: RwLock::new([false;128]).into(), notes_out: RwLock::new([false;128]).into(), #[cfg(feature = "port")] midi_ins: vec![MidiInput::new(jack, &format!("M/{}", name.as_ref()), midi_from)?,], #[cfg(feature = "port")] midi_outs: vec![MidiOutput::new(jack, &format!("{}/M", name.as_ref()), midi_to)?, ], #[cfg(feature = "clip")] play_clip: clip.map(|clip|(Moment::zero(&clock.timebase), Some(clip.clone()))), #[cfg(feature = "clock")] clock, ..Default::default() }) } } impl std::fmt::Debug for Sequencer { fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("Sequencer") .field("clock", &self.clock) .field("play_clip", &self.play_clip) .field("next_clip", &self.next_clip) .finish() } } #[cfg(feature = "clock")] has!(Clock: |self:Sequencer|self.clock); #[cfg(feature = "port")] has!(Vec: |self:Sequencer|self.midi_ins); #[cfg(feature = "port")] has!(Vec: |self:Sequencer|self.midi_outs); impl MidiMonitor for Sequencer { fn notes_in (&self) -> &Arc> { &self.notes_in } fn monitoring (&self) -> bool { self.monitoring } fn monitoring_mut (&mut self) -> &mut bool { &mut self.monitoring } } impl MidiRecord for Sequencer { fn recording (&self) -> bool { self.recording } fn recording_mut (&mut self) -> &mut bool { &mut self.recording } fn overdub (&self) -> bool { self.overdub } fn overdub_mut (&mut self) -> &mut bool { &mut self.overdub } } #[cfg(feature="clip")] impl HasPlayClip for Sequencer { fn reset (&self) -> bool { self.reset } fn reset_mut (&mut self) -> &mut bool { &mut self.reset } fn play_clip (&self) -> &Option<(Moment, Option>>)> { &self.play_clip } fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)> { &mut self.play_clip } fn next_clip (&self) -> &Option<(Moment, Option>>)> { &self.next_clip } fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)> { &mut self.next_clip } } /// JACK process callback for a sequencer's clip sequencer/recorder. impl Audio for Sequencer { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { if self.clock().is_rolling() { self.process_rolling(scope) } else { self.process_stopped(scope) } } } impl Sequencer { fn process_rolling (&mut self, scope: &ProcessScope) -> Control { self.process_clear(scope, false); // Write chunk of clip to output, handle switchover if self.process_playback(scope) { self.process_switchover(scope); } // Monitor input to output self.process_monitoring(scope); // Record and/or monitor input self.process_recording(scope); // Emit contents of MIDI buffers to JACK MIDI output ports. self.midi_outs_emit(scope); Control::Continue } fn process_stopped (&mut self, scope: &ProcessScope) -> Control { if self.monitoring() && self.midi_ins().len() > 0 && self.midi_outs().len() > 0 { self.process_monitoring(scope) } Control::Continue } fn process_monitoring (&mut self, scope: &ProcessScope) { let notes_in = self.notes_in().clone(); // For highlighting keys and note repeat let monitoring = self.monitoring(); for input in self.midi_ins.iter() { for (sample, event, bytes) in input.parsed(scope) { if let LiveEvent::Midi { message, .. } = event { if monitoring { self.midi_buf[sample].push(bytes.to_vec()); } // FIXME: don't lock on every event! update_keys(&mut notes_in.write().unwrap(), &message); } } } } /// Clear the section of the output buffer that we will be using, /// emitting "all notes off" at start of buffer if requested. fn process_clear (&mut self, scope: &ProcessScope, reset: bool) { let n_frames = (scope.n_frames() as usize).min(self.midi_buf_mut().len()); for frame in &mut self.midi_buf_mut()[0..n_frames] { frame.clear(); } if reset { all_notes_off(self.midi_buf_mut()); } for port in self.midi_outs_mut().iter_mut() { // Clear output buffer(s) port.buffer_clear(scope, false); } } fn process_recording (&mut self, scope: &ProcessScope) { if self.monitoring() { self.monitor(scope); } if let Some((started, ref clip)) = self.play_clip.clone() { self.record_clip(scope, started, clip); } if let Some((_start_at, _clip)) = &self.next_clip() { self.record_next(); } } fn process_playback (&mut self, scope: &ProcessScope) -> bool { // 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. if let Some((started, clip)) = &self.play_clip { // Length of clip, to repeat or stop on end. let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); // Index of first sample to populate. let offset = self.clock().get_sample_offset(scope, &started); // Write MIDI events from clip at sample offsets corresponding to pulses. for (sample, pulse) in self.clock().get_pulses(scope, offset) { // 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 }; // Is it time for switchover? 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(clip) = clip { // 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() { for port in self.midi_outs.iter_mut() { port.buffer_write(sample, LiveEvent::Midi { channel: 0.into(), /* TODO */ message: *message }); } } } } } false } else { true } } /// Handle switchover from current to next playing clip. fn process_switchover (&mut self, scope: &ProcessScope) { let midi_buf = self.midi_buf_mut(); 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.process_playback(scope); } } } } pub trait HasMidiBuffers { fn note_buf_mut (&mut self) -> &mut Vec; fn midi_buf_mut (&mut self) -> &mut Vec>>; } impl HasMidiBuffers for Sequencer { fn note_buf_mut (&mut self) -> &mut Vec { &mut self.note_buf } fn midi_buf_mut (&mut self) -> &mut Vec>> { &mut self.midi_buf } } pub trait MidiMonitor: HasMidiIns + HasMidiBuffers { fn notes_in (&self) -> &Arc>; 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) { } } pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip { fn recording (&self) -> bool; fn recording_mut (&mut self) -> &mut bool; fn toggle_record (&mut self) { *self.recording_mut() = !self.recording(); } fn overdub (&self) -> bool; fn overdub_mut (&mut self) -> &mut bool; fn toggle_overdub (&mut self) { *self.overdub_mut() = !self.overdub(); } fn record_clip ( &mut self, scope: &ProcessScope, started: Moment, clip: &Option>>, ) { if let Some(clip) = clip { let sample0 = scope.last_frame_time() as usize; let start = started.sample.get() as usize; let _recording = self.recording(); let timebase = self.clock().timebase().clone(); let quant = self.clock().quant.get(); let mut clip = clip.write().unwrap(); let length = clip.length; for input in self.midi_ins_mut().iter() { for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { if let LiveEvent::Midi { message, .. } = event { clip.record_event({ let sample = (sample0 + sample - start) as f64; let pulse = timebase.samples_to_pulse(sample); let quantized = (pulse / quant).round() * quant; quantized as usize % length }, message); } } } } } fn record_next (&mut self) { // TODO switch to next clip and record into it } } pub trait MidiViewer: HasSize + MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, clip: &MidiClip) -> (usize, usize); fn redraw (&self); fn clip (&self) -> &Option>>; fn clip_mut (&mut self) -> &mut Option>>; fn set_clip (&mut self, clip: Option<&Arc>>) { *self.clip_mut() = clip.cloned(); self.redraw(); } /// Make sure cursor is within note range fn autoscroll (&self) { let note_pos = self.get_note_pos().min(127); let note_lo = self.get_note_lo(); let note_hi = self.get_note_hi(); if note_pos < note_lo { self.note_lo().set(note_pos); } else if note_pos > note_hi { self.note_lo().set((note_lo + note_pos).saturating_sub(note_hi)); } } /// Make sure time range is within display fn autozoom (&self) { if self.time_lock().get() { let time_len = self.get_time_len(); let time_axis = self.get_time_axis(); let time_zoom = self.get_time_zoom(); loop { let time_zoom = self.time_zoom().get(); let time_area = time_axis * time_zoom; if time_area > time_len { let next_time_zoom = NoteDuration::prev(time_zoom); if next_time_zoom <= 1 { break } let next_time_area = time_axis * next_time_zoom; if next_time_area >= time_len { self.time_zoom().set(next_time_zoom); } else { break } } else if time_area < time_len { let prev_time_zoom = NoteDuration::next(time_zoom); if prev_time_zoom > 384 { break } let prev_time_area = time_axis * prev_time_zoom; if prev_time_area <= time_len { self.time_zoom().set(prev_time_zoom); } else { break } } } if time_zoom != self.time_zoom().get() { self.redraw() } } //while time_len.div_ceil(time_zoom) > time_axis { //println!("\r{time_len} {time_zoom} {time_axis}"); //time_zoom = Note::next(time_zoom); //} //self.time_zoom().set(time_zoom); } } pub trait HasPlayClip: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; fn play_clip (&self) -> &Option<(Moment, Option>>)>; fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn next_clip (&self) -> &Option<(Moment, Option>>)>; fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.play_clip().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); return Some(elapsed) } None } fn pulses_since_start_looped (&self) -> Option<(f64, f64)> { if let Some((started, Some(clip))) = self.play_clip().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); let length = clip.read().unwrap().length.max(1); // prevent div0 on empty clip let times = (elapsed as usize / length) as f64; let elapsed = (elapsed as usize % length) as f64; return Some((times, elapsed)) } None } fn enqueue_next (&mut self, clip: Option<&Arc>>) { *self.next_clip_mut() = Some((self.clock().next_launch_instant(), clip.cloned())); *self.reset_mut() = true; } fn play_status (&self) -> impl Content { let (name, color): (Arc, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() { let MidiClip { ref name, color, .. } = *clip.read().unwrap(); (name.clone(), color) } else { ("".into(), Tui::g(64).into()) }; let time: String = self.pulses_since_start_looped() .map(|(times, time)|format!("{:>3}x {:>}", times+1.0, self.clock().timebase.format_beats_1(time))) .unwrap_or_else(||String::from(" ")).into(); FieldV(color, "Now:", format!("{} {}", time, name)) } fn next_status (&self) -> impl Content { let mut time: Arc = String::from("--.-.--").into(); let mut name: Arc = String::from("").into(); let mut color = ItemTheme::G[64]; let clock = self.clock(); if let Some((t, Some(clip))) = self.next_clip() { let clip = clip.read().unwrap(); name = clip.name.clone(); color = clip.color.clone(); time = { let target = t.pulse.get(); let current = clock.playhead.pulse.get(); if target > current { let remaining = target - current; format!("-{:>}", clock.timebase.format_beats_1(remaining)) } else { String::new() } }.into() } else if let Some((t, Some(clip))) = self.play_clip() { let clip = clip.read().unwrap(); if clip.looped { name = clip.name.clone(); color = clip.color.clone(); let target = t.pulse.get() + clip.length as f64; let current = clock.playhead.pulse.get(); if target > current { time = format!("-{:>}", clock.timebase.format_beats_0(target - current)).into() } } else { name = "Stop".to_string().into(); } }; FieldV(color, "Next:", format!("{} {}", time, name)) } }