use crate::*; impl +AsMut> HasSequencer for T {} pub trait HasSequencer: AsRef + AsMut { fn sequencer_mut (&mut self) -> &mut Sequencer { self.as_mut() } fn sequencer (&self) -> &Sequencer { self.as_ref() } } /// Contains state for playing a clip /// /// ``` /// let clip = tek::MidiClip::default(); /// println!("Empty clip: {clip:?}"); /// /// let clip = tek::MidiClip::stop_all(); /// println!("Panic clip: {clip:?}"); /// /// let mut clip = tek::MidiClip::new("clip", true, 1, None, None); /// clip.set_length(96); /// clip.toggle_loop(); /// clip.record_event(12, ::tengri::midly::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 = tek::Sequencer::default(); /// println!("{sequencer:?}"); /// ``` 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_has!(Clock: |self: Sequencer| self.clock); impl_has!(Vec: |self: Sequencer| self.midi_ins); impl_has!(Vec: |self: Sequencer| self.midi_outs); impl_default!(Sequencer: Self { clock: Clock::default(), play_clip: None, next_clip: None, midi_ins: vec![], 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() }) } 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.try_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.try_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.try_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.try_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); } } } } 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 } } 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() } } /// 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) } } } pub fn to_key (note: usize) -> &'static str { match note % 12 { 11 | 9 | 7 | 5 | 4 | 2 | 0 => "████▌", 10 | 8 | 6 | 3 | 1 => " ", _ => unreachable!(), } } pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } /// Returns the next shorter length pub fn note_duration_prev (pulses: usize) -> usize { for (length, _) in NOTE_DURATIONS.iter().rev() { if *length < pulses { return *length } } pulses } /// Returns the next longer length pub fn note_duration_next (pulses: usize) -> usize { for (length, _) in NOTE_DURATIONS.iter() { if *length > pulses { return *length } } pulses } pub fn note_duration_to_name (pulses: usize) -> &'static str { for (length, name) in NOTE_DURATIONS.iter() { if *length == pulses { return name } } "" } pub fn note_pitch_to_name (n: usize) -> &'static str { if n > 127 { panic!("to_note_name({n}): must be 0-127"); } NOTE_NAMES[n] }