use crate::{*, clock::*, device::*}; impl +AsMut> HasSequencer for T {} /// A MIDI sequence. /// /// ``` /// let clip = tek::MidiClip::default(); /// ``` #[derive(Debug, Clone, Default)] pub struct MidiClip { pub uuid: uuid::Uuid, /// Name of clip pub name: Arc, /// Temporal resolution in pulses per quarter note pub ppq: usize, /// Length of clip in pulses pub length: usize, /// Notes in clip pub notes: MidiData, /// Whether to loop the clip or play it once pub looped: bool, /// Start of loop pub loop_start: usize, /// Length of loop pub loop_length: usize, /// All notes are displayed with minimum length pub percussive: bool, /// Identifying color of clip pub color: ItemTheme, } /// 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, 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>>, } 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 Draw { 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(), ItemTheme::G[64].into()) }; field_v(color, "Now:", format!("{} {}", self.play_status_time(), name)) } fn play_status_time (&self) -> 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() } fn next_status (&self) -> impl Draw { 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(); } }; field_v(color, "Next:", format!("{} {}", time, name)) } } pub trait MidiMonitor: HasMidiIns + HasMidiBuffers { /// Input note flags. fn notes_in (&self) -> &Arc>; /// Current monitoring status. fn monitoring (&self) -> bool; /// Mutable monitoring status. fn monitoring_mut (&mut self) -> &mut bool; /// Enable or disable monitoring. fn toggle_monitor (&mut self) { *self.monitoring_mut() = !self.monitoring(); } /// Perform monitoring. fn monitor (&mut self, _scope: &ProcessScope) { /* do nothing by default */ } } 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 type MidiData = Vec>; pub type ClipPool = Vec>>; pub type CollectedMidiInput<'a> = Vec, MidiError>)>>; pub trait HasClips { fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; fn add_clip (&self) -> (usize, Arc>) { let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); self.clips_mut().push(clip.clone()); (self.clips().len() - 1, clip) } } pub trait HasMidiClip { fn clip (&self) -> Option>>; } pub trait HasSequencer: AsRef + AsMut { fn sequencer_mut (&mut self) -> &mut Sequencer { self.as_mut() } fn sequencer (&self) -> &Sequencer { self.as_ref() } } pub trait HasMidiBuffers { fn note_buf_mut (&mut self) -> &mut Vec; fn midi_buf_mut (&mut self) -> &mut Vec>>; } impl MidiClip { pub fn new ( name: impl AsRef, looped: bool, length: usize, notes: Option, color: Option, ) -> Self { Self { uuid: uuid::Uuid::new_v4(), name: name.as_ref().into(), ppq: PPQ, length, notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), looped, loop_start: 0, loop_length: length, percussive: true, color: color.unwrap_or_else(ItemTheme::random) } } pub fn count_midi_messages (&self) -> usize { let mut count = 0; for tick in self.notes.iter() { count += tick.len(); } count } pub fn set_length (&mut self, length: usize) { self.length = length; self.notes = vec![Vec::with_capacity(16);length]; } pub fn duplicate (&self) -> Self { let mut clone = self.clone(); clone.uuid = uuid::Uuid::new_v4(); clone } pub fn toggle_loop (&mut self) { self.looped = !self.looped; } pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { if pulse >= self.length { panic!("extend clip first") } self.notes[pulse].push(message); } /// Check if a range `start..end` contains MIDI Note On `k` pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { for event in events.iter() { if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } } } } false } pub fn stop_all () -> Self { Self::new( "Stop", false, 1, Some(vec![vec![MidiMessage::Controller { controller: 123.into(), value: 0.into() }]]), Some(ItemColor::from_tui(Color::Rgb(32, 32, 32)).into()) ) } } impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } impl Eq for MidiClip {} impl MidiClip { fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } fn _todo_bool_stub_ (&self) -> bool { todo!() } fn _todo_usize_stub_ (&self) -> usize { todo!() } fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } } impl_has!(Sequencer: |self: Track| self.sequencer); 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.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); } } } } 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() } } impl MidiMonitor for Sequencer { fn monitoring (&self) -> bool { self.monitoring } fn monitoring_mut (&mut self) -> &mut bool { &mut self.monitoring } fn notes_in (&self) -> &Arc> { &self.notes_in } } 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 Iterator for Ticker { type Item = (usize, usize); fn next (&mut self) -> Option { loop { if self.sample > self.end { return None } let spp = self.spp; let sample = self.sample as f64; let start = self.start; let end = self.end; self.sample += 1; //println!("{spp} {sample} {start} {end}"); let jitter = sample.rem_euclid(spp); // ramps let next_jitter = (sample + 1.0).rem_euclid(spp); if jitter > next_jitter { // at crossing: let time = (sample as usize) % (end as usize-start as usize); let tick = (sample / spp) as usize; return Some((time, tick)) } } } } 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)) } /// Return boxed iterator of MIDI events pub fn parse_midi_input <'a> (input: ::tengri::jack::MidiIter<'a>) -> Box, &'a [u8])> + 'a> { Box::new(input.map(|::tengri::jack::RawMidi { time, bytes }|( time as usize, LiveEvent::parse(bytes).unwrap(), bytes ))) } /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut [Vec>]) { let mut buf = vec![]; let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() }; let evt = LiveEvent::Midi { channel: 0.into(), message: msg }; evt.write(&mut buf).unwrap(); output[0].push(buf); } /// Update notes_in array pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { match message { MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, _ => {} } } /// 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] }