/// Contains state for viewing and editing a clip. /// /// ``` /// use std::sync::{Arc, RwLock}; /// let clip = tek::MidiClip::stop_all(); /// let mut editor = tek::MidiEditor { /// mode: tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip)))), /// size: Default::default(), /// //keys: Default::default(), /// }; /// let _ = editor.put_note(true); /// let _ = editor.put_note(false); /// let _ = editor.clip_status(); /// let _ = editor.edit_status(); /// ``` pub struct MidiEditor { /// Size of editor on screen pub size: Measure, /// View mode and state of editor pub mode: PianoHorizontal, } /// A clip, rendered as a horizontal piano roll. /// /// ``` /// let piano = tek::PianoHorizontal::default(); /// ``` #[derive(Clone, Default)] pub struct PianoHorizontal { pub clip: Option>>, /// Buffer where the whole clip is rerendered on change pub buffer: Arc>, /// Size of actual notes area pub size: Measure, /// The display window pub range: MidiSelection, /// The note cursor pub point: MidiCursor, /// The highlight color palette pub color: ItemTheme, /// Width of the keyboard pub keys_width: u16, } /// 12 piano keys, some highlighted. /// /// ``` /// let keys = tek::OctaveVertical::default(); /// ``` #[derive(Copy, Clone)] pub struct OctaveVertical { pub on: [bool; 12], pub colors: [Color; 3] } /// 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>>, } /// A track consists of a sequencer and zero or more devices chained after it. /// /// ``` /// let track: tek::Track = Default::default(); /// ``` #[derive(Debug, Default)] pub struct Track { /// Name of track pub name: Arc, /// Identifying color of track pub color: ItemTheme, /// Preferred width of track column pub width: usize, /// MIDI sequencer state pub sequencer: Sequencer, /// Device chain pub devices: 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(), 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(); field_v(color, "Now:", format!("{} {}", time, name)) } 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 trait MidiViewer: 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 = note_duration_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 = note_duration_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 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) } } /// ``` /// use tek::{*, tengri::*}; /// /// struct Test(Option); /// impl_as_ref_opt!(MidiEditor: |self: Test|self.0.as_ref()); /// impl_as_mut_opt!(MidiEditor: |self: Test|self.0.as_mut()); /// /// let mut host = Test(Some(MidiEditor::default())); /// let _ = host.editor(); /// let _ = host.editor_mut(); /// let _ = host.is_editing(); /// let _ = host.editor_w(); /// let _ = host.editor_h(); /// ``` pub trait HasEditor: AsRefOpt + AsMutOpt { fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() } fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() } fn is_editing (&self) -> bool { self.editor().is_some() } fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize } fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize } } /// Trait for thing that may receive MIDI. pub trait HasMidiIns { fn midi_ins (&self) -> &Vec; fn midi_ins_mut (&mut self) -> &mut Vec; /// Collect MIDI input from app ports (TODO preallocate large buffers) fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> { self.midi_ins().iter() .map(|port|port.port().iter(scope) .map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes))) .collect::>()) .collect::>() } fn midi_ins_with_sizes <'a> (&'a self) -> impl Iterator, &'a [Connect], usize, usize)> + Send + Sync + 'a { let mut y = 0; self.midi_ins().iter().enumerate().map(move|(i, input)|{ let height = 1 + input.connections().len(); let data = (i, input.port_name(), input.connections(), y, y + height); y += height; data }) } } /// Trait for thing that may output MIDI. pub trait HasMidiOuts { fn midi_outs (&self) -> &Vec; fn midi_outs_mut (&mut self) -> &mut Vec; fn midi_outs_with_sizes <'a> (&'a self) -> impl Iterator, &'a [Connect], usize, usize)> + Send + Sync + 'a { let mut y = 0; self.midi_outs().iter().enumerate().map(move|(i, output)|{ let height = 1 + output.connections().len(); let data = (i, output.port_name(), output.connections(), y, y + height); y += height; data }) } fn midi_outs_emit (&mut self, scope: &ProcessScope) { for port in self.midi_outs_mut().iter_mut() { port.buffer_emit(scope) } } } 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>>; } pub trait NotePoint { fn note_len (&self) -> &AtomicUsize; /// Get the current length of the note cursor. fn get_note_len (&self) -> usize { self.note_len().load(Relaxed) } /// Set the length of the note cursor, returning the previous value. fn set_note_len (&self, x: usize) -> usize { self.note_len().swap(x, Relaxed) } fn note_pos (&self) -> &AtomicUsize; /// Get the current pitch of the note cursor. fn get_note_pos (&self) -> usize { self.note_pos().load(Relaxed).min(127) } /// Set the current pitch fo the note cursor, returning the previous value. fn set_note_pos (&self, x: usize) -> usize { self.note_pos().swap(x.min(127), Relaxed) } } pub trait TimePoint { fn time_pos (&self) -> &AtomicUsize; /// Get the current time position of the note cursor. fn get_time_pos (&self) -> usize { self.time_pos().load(Relaxed) } /// Set the current time position of the note cursor, returning the previous value. fn set_time_pos (&self, x: usize) -> usize { self.time_pos().swap(x, Relaxed) } } pub trait MidiPoint: NotePoint + TimePoint { /// Get the current end of the note cursor. fn get_note_end (&self) -> usize { self.get_time_pos() + self.get_note_len() } } pub trait TimeRange { fn time_len (&self) -> &AtomicUsize; fn get_time_len (&self) -> usize { self.time_len().load(Ordering::Relaxed) } fn time_zoom (&self) -> &AtomicUsize; fn get_time_zoom (&self) -> usize { self.time_zoom().load(Ordering::Relaxed) } fn set_time_zoom (&self, value: usize) -> usize { self.time_zoom().swap(value, Ordering::Relaxed) } fn time_lock (&self) -> &AtomicBool; fn get_time_lock (&self) -> bool { self.time_lock().load(Ordering::Relaxed) } fn set_time_lock (&self, value: bool) -> bool { self.time_lock().swap(value, Ordering::Relaxed) } fn time_start (&self) -> &AtomicUsize; fn get_time_start (&self) -> usize { self.time_start().load(Ordering::Relaxed) } fn set_time_start (&self, value: usize) -> usize { self.time_start().swap(value, Ordering::Relaxed) } fn time_axis (&self) -> &AtomicUsize; fn get_time_axis (&self) -> usize { self.time_axis().load(Ordering::Relaxed) } fn get_time_end (&self) -> usize { self.time_start().get() + self.time_axis().get() * self.time_zoom().get() } } pub trait NoteRange { fn note_lo (&self) -> &AtomicUsize; fn get_note_lo (&self) -> usize { self.note_lo().load(Ordering::Relaxed) } fn set_note_lo (&self, x: usize) -> usize { self.note_lo().swap(x, Ordering::Relaxed) } fn note_axis (&self) -> &AtomicUsize; fn get_note_axis (&self) -> usize { self.note_axis().load(Ordering::Relaxed) } fn get_note_hi (&self) -> usize { (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) } } pub trait MidiRange: TimeRange + NoteRange {} /// /// ``` /// let _ = tek::MidiCursor::default(); /// ``` #[derive(Debug, Clone)] pub struct MidiCursor { /// Time coordinate of cursor pub time_pos: Arc, /// Note coordinate of cursor pub note_pos: Arc, /// Length of note that will be inserted, in pulses pub note_len: Arc, } /// /// ``` /// use tek::{TimeRange, NoteRange}; /// let model = tek::MidiSelection::from((1, false)); /// /// let _ = model.get_time_len(); /// let _ = model.get_time_zoom(); /// let _ = model.get_time_lock(); /// let _ = model.get_time_start(); /// let _ = model.get_time_axis(); /// let _ = model.get_time_end(); /// /// let _ = model.get_note_lo(); /// let _ = model.get_note_axis(); /// let _ = model.get_note_hi(); /// ``` #[derive(Debug, Clone, Default)] pub struct MidiSelection { pub time_len: Arc, /// Length of visible time axis pub time_axis: Arc, /// Earliest time displayed pub time_start: Arc, /// Time step pub time_zoom: Arc, /// Auto rezoom to fit in time axis pub time_lock: Arc, /// Length of visible note axis pub note_axis: Arc, // Lowest note displayed pub note_lo: Arc, }