diff --git a/src/tek_bind.rs b/src/bind.rs similarity index 100% rename from src/tek_bind.rs rename to src/bind.rs diff --git a/src/clap.rs b/src/clap.rs new file mode 100644 index 00000000..46330df3 --- /dev/null +++ b/src/clap.rs @@ -0,0 +1 @@ +//! TODO diff --git a/src/tek_cli.rs b/src/cli.rs similarity index 100% rename from src/tek_cli.rs rename to src/cli.rs diff --git a/src/tek_conf.rs b/src/conf.rs similarity index 100% rename from src/tek_conf.rs rename to src/conf.rs diff --git a/src/device_lv2.rs b/src/lv2.rs similarity index 100% rename from src/device_lv2.rs rename to src/lv2.rs diff --git a/src/device_lv2_gui.rs b/src/lv2_gui.rs similarity index 100% rename from src/device_lv2_gui.rs rename to src/lv2_gui.rs diff --git a/src/midi_clip.rs b/src/midi_clip.rs deleted file mode 100644 index fd347a4c..00000000 --- a/src/midi_clip.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::*; - -/// 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, -} - -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 - } - /// Change the color of a clip, returning the previous color. - /// If value not provided, randomize. - pub fn set_color (&mut self, color: Option) -> ItemTheme { - let mut color = color.unwrap_or_else(||ItemTheme::random()); - std::mem::swap(&mut color, &mut self.color); - color - } - /// Set loop mode for a clip, returning the previous loop state. - /// If value not provided, toggle. - pub fn toggle_loop (&mut self, looping: Option) -> bool { - match looping { - Some(true) => { - self.looped = true; - false - }, - Some(false) => { - self.looped = false; - true - }, - None => { - self.looped = !self.looped; - !self.looped - } - } - } - /// Add a [MidiMessage] at a given `pulse` position of this clip. - 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 - } - /// Create a panic clip. - pub fn stop_all () -> Self { - let color = Some(ItemColor::from_tui(Color::Rgb(32, 32, 32)).into()); - Self::new("Stop", false, 1, Some(vec![vec![MIDI_PANIC]]), color) - } -} - -const MIDI_PANIC: MidiMessage = MidiMessage::Controller { - controller: u7::new(123), value: u7::new(0), -}; - -pub type MidiData = Vec>; -impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } -impl Eq for MidiClip {} diff --git a/src/midi_edit.rs b/src/midi_edit.rs deleted file mode 100644 index cfc362e8..00000000 --- a/src/midi_edit.rs +++ /dev/null @@ -1,416 +0,0 @@ -#![allow(unused)] -use crate::*; - -impl EditorModel for T {} - -pub trait EditorModel: HasEditor { - fn is_editing (&self) -> bool { - self.editor().is_some() - } - fn editor_w (&self) -> usize { - self.editor().as_ref().map(|e|e.size.w()).unwrap_or(0) as usize - } - fn editor_h (&self) -> usize { - self.editor().as_ref().map(|e|e.size.h()).unwrap_or(0) as usize - } -} - -/// Contains state for viewing and editing a clip. -/// -/// ``` -/// use std::sync::Arc; -/// use parking_lot::RwLock; -/// use tek::{MidiClip, MidiEditor, PianoHorizontal}; -/// let mode = PianoHorizontal::default(); -/// let editor = MidiEditor::new(); -/// editor.mode.clip.set(Some(MidiClip::stop_all())); -/// let _ = editor.put_note(true); -/// let _ = editor.put_note(false); -/// ``` -pub struct MidiEditor { - /// View mode and state of editor - pub mode: PianoHorizontal, - /// Size of editor on screen - pub size: Sizer, -} - -impl_default!(MidiEditor: Self { - mode: Default::default(), - size: Sizer( - Arc::new(0usize.into()), - Arc::new(0usize.into()), - ), -}); - -impl MidiEditor { - pub fn new () -> Self { - Self { - mode: PianoHorizontal { - keys_width: 4, - point: MidiCursor { - note_len: Arc::new(1.into()), - ..Default::default() - }, - range: MidiSelection { - time_len: Arc::new(96usize.into()), - time_zoom: Arc::new(1usize.into()), - note_axis: Arc::new(12usize.into()), - ..Default::default() - }, - size: Sizer( - Arc::new(48usize.into()), - Arc::new(12usize.into()), - ), - ..Default::default() - }, - size: Sizer( - Arc::new(52usize.into()), - Arc::new(12usize.into()), - ), - } - } - - /// Put note at current position - pub fn put_note (&self, advance: bool) { - let mut redraw = false; - if let Some(clip) = &*self.mode.clip.read() { - let note_start = self.get_time_pos(); - let note_pos = self.get_note_pos(); - let note_len = self.get_note_len(); - let note_end = note_start + (note_len.saturating_sub(1)); - let key: u7 = u7::from(note_pos as u8); - let vel: u7 = 100.into(); - let mut clip = clip.write(); - let length = clip.length; - let note_end = note_end % length; - let note_on = MidiMessage::NoteOn { key, vel }; - if !clip.notes[note_start].iter().any(|msg|*msg == note_on) { - clip.notes[note_start].push(note_on); - } - let note_off = MidiMessage::NoteOff { key, vel }; - if !clip.notes[note_end].iter().any(|msg|*msg == note_off) { - clip.notes[note_end].push(note_off); - } - if advance { - self.set_time_pos((note_end + 1) % clip.length); - } - redraw = true; - } - if redraw { - self.mode.redraw(); - } - } - fn clip_length (&self) -> usize { - self.mode.clip.map_inner(|p|p.length).unwrap_or(1) - } - fn note_length (&self) -> usize { - self.get_note_len() - } - fn note_pos (&self) -> usize { - self.get_note_pos() - } - fn note_pos_next (&self) -> usize { - self.get_note_pos() + 1 - } - fn note_pos_next_octave (&self) -> usize { - self.get_note_pos() + 12 - } - fn note_pos_prev (&self) -> usize { - self.get_note_pos().saturating_sub(1) - } - fn note_pos_prev_octave (&self) -> usize { - self.get_note_pos().saturating_sub(12) - } - fn note_len (&self) -> usize { - self.get_note_len() - } - fn note_len_next (&self) -> usize { - self.get_note_len() + 1 - } - fn note_len_prev (&self) -> usize { - self.get_note_len().saturating_sub(1) - } - fn note_range (&self) -> usize { - self.get_note_axis() - } - fn note_range_next (&self) -> usize { - self.get_note_axis() + 1 - } - fn note_range_prev (&self) -> usize { - self.get_note_axis().saturating_sub(1) - } - fn time_zoom (&self) -> usize { self.get_time_zoom() } - fn time_zoom_next (&self) -> usize { self.get_time_zoom() + 1 } - fn time_zoom_next_fine (&self) -> usize { self.get_time_zoom() + 1 } - fn time_zoom_prev (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) } - fn time_zoom_prev_fine (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) } - fn time_lock (&self) -> bool { self.get_time_lock() } - fn time_lock_toggled (&self) -> bool { !self.get_time_lock() } - fn time_pos (&self) -> usize { self.get_time_pos() } - fn time_pos_next (&self) -> usize { (self.get_time_pos() + self.get_note_len()) % self.clip_length() } - fn time_pos_next_fine (&self) -> usize { (self.get_time_pos() + 1) % self.clip_length() } - fn time_pos_prev (&self) -> usize { - let step = self.get_note_len(); - self.get_time_pos().overflowing_sub(step) - .0.min(self.clip_length().saturating_sub(step)) - } - fn time_pos_prev_fine (&self) -> usize { - self.get_time_pos().overflowing_sub(1) - .0.min(self.clip_length().saturating_sub(1)) - } -} - -impl MidiPoint for T {} - -impl MidiRange for T {} - -pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync { - - fn buffer_size (&self, clip: &MidiClip) -> (usize, usize); - - fn redraw (&self); - - /// 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().store(note_pos, Relaxed); - } else if note_pos > note_hi { - self.note_lo().store((note_lo + note_pos).saturating_sub(note_hi), Relaxed); - } - } - - /// Make sure time range is within display - fn autozoom (&self) { - if self.time_lock().load(Relaxed) { - 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().load(Relaxed); - 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().store(next_time_zoom, Relaxed); - } 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().store(prev_time_zoom, Relaxed); - } else { - break - } - } - } - if time_zoom != self.time_zoom().load(Relaxed) { - 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); - } - -} - -impl_has!(Sizer: |self: MidiEditor| self.size); - -impl std::fmt::Debug for MidiEditor { - fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("MidiEditor").field("mode", &self.mode).finish() - } -} - -impl NotePoint for MidiEditor { - fn note_len (&self) -> &AtomicUsize { self.mode.note_len() } - fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() } -} - -impl TimePoint for MidiEditor { - fn time_pos (&self) -> &AtomicUsize { self.mode.time_pos() } -} - -impl MidiViewer for MidiEditor { - fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { - self.mode.buffer_size(clip) - } - fn redraw (&self) { - self.mode.redraw() - } -} - -/// 12 piano keys, some highlighted. -/// -/// ``` -/// let keys = tek::OctaveVertical::default(); -/// ``` -#[derive(Copy, Clone)] -pub struct OctaveVertical { - pub on: [bool; 12], - pub colors: [Color; 3] -} - -impl OctaveVertical { - pub fn color (&self, pitch: usize) -> Color { - let pitch = pitch % 12; - self.colors[if self.on[pitch] { 2 } else { match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } }] - } -} - -impl_default!(OctaveVertical: Self { - on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] -}); - -/// A clip, rendered as a horizontal piano roll. -/// -/// Uses full blocks on note on and half blocks on legato: █▄ █▄ █▄ -/// -/// ``` -/// let piano = tek::PianoHorizontal::default(); -/// ``` -#[derive(Clone, Default)] pub struct PianoHorizontal { - pub clip: ClipSlot, - /// Buffer where the whole clip is rerendered on change - pub buffer: Arc>, - /// Size of actual notes area - pub size: Sizer, - /// 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, -} - -impl_has!(Sizer: |self: PianoHorizontal| self.size); - -impl PianoHorizontal { - pub fn new () -> Self { - let size = Sizer::default(); - let mut range = MidiSelection::from((12, true)); - range.time_axis = size.0.clone(); - range.note_axis = size.1.clone(); - let piano = Self { keys_width: 4, size, range, ..Default::default() }; - piano.redraw(); - piano - } -} - -impl TimeRange for PianoHorizontal { - fn time_len (&self) -> &AtomicUsize { self.range.time_len() } - fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() } - fn time_lock (&self) -> &AtomicBool { self.range.time_lock() } - fn time_start (&self) -> &AtomicUsize { self.range.time_start() } - fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() } -} - -impl NoteRange for PianoHorizontal { - fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() } - fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() } -} - -impl NotePoint for PianoHorizontal { - fn note_len (&self) -> &AtomicUsize { self.point.note_len() } - fn note_pos (&self) -> &AtomicUsize { self.point.note_pos() } -} - -impl TimePoint for PianoHorizontal { - fn time_pos (&self) -> &AtomicUsize { self.point.time_pos() } -} - -impl MidiViewer for PianoHorizontal { - /// Determine the required space to render the clip. - fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { - let zoom = self.range.time_zoom().load(Relaxed); - let size = if zoom > 0 { clip.length / zoom } else { 0 }; - (size, 128) - } - /// Draw the piano roll into the buffer. - fn redraw (&self) { - *self.buffer.write() = redraw_piano(&self) - } -} - -impl std::fmt::Debug for PianoHorizontal { - fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - let buffer = self.buffer.read(); - f.debug_struct("PianoHorizontal") - .field("time_zoom", &self.range.time_zoom) - .field("buffer", &format!("{}x{}", buffer.width, buffer.height)) - .finish() - } -} - -//impl App { - ///// Toggle MIDI editor. - ///// - ///// ``` - ///// tek::App::default().toggle_editor(None); - ///// ``` - //pub fn toggle_editor (&mut self, value: Option) { - ////FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); - //if value.unwrap_or_else(||!self.editor().is_some()) { - //self.toggle_editor_on() - //} else { - //self.toggle_editor_off() - //} - //} - - //fn toggle_editor_on (&mut self) { - //// Create new clip in pool when entering empty cell - //if let Selection::TrackClip { track: track_index, scene } = *self.selection() - //&& let Some(scene) = self.project.scenes.get_mut(scene) - //&& scene.clips.get(track_index).is_none() - //&& let Some(track) = self.project.tracks.get_mut(track_index) - //{ - //let color = track.color.base.mix(scene.color.base, 0.5); - //let color = ItemColor::random_near(color, 0.2).into(); - //let clip: MidiClip = MidiClip { color, ..Default::default() }; - //let clip = self.project.clips.append(Some(clip)); - //// autocolor: new clip colors from scene and track color - //if let Some(editor) = &mut self.project.editor { - //*editor.mode.clip.write() = clip.0.read().clone(); - //editor.mode.color = clip.0.read().as_ref().map(|p|p.read().color) - //.unwrap_or(ItemTheme::G[64]); - //} - //*scene.clips.slot(track_index).write() = clip.0.read().clone(); - ////Some(clip) - //} else { - ////None - //} - //} - - //fn toggle_editor_off (&mut self) { - //if let Selection::TrackClip { track, scene } = *self.selection() - //&& let Some(scene) = self.project.scenes.get_mut(scene) - //{ - //// Remove clip from arrangement when exiting empty clip editor - //let mut swapped = None; - //if let Some(count) = scene.clips.slot(track).read().as_ref().map(|c|c.read().count_midi_messages()) - //&& count == 0 - //{ - //std::mem::swap(&mut swapped, &mut scene.clips.slot(track).write()); - //} - //if let Some(clip) = swapped { - //self.project.clips.remove(&*clip.read()); - //} - //} - //} -//} diff --git a/src/midi_mon.rs b/src/midi_mon.rs deleted file mode 100644 index 0a5fbe83..00000000 --- a/src/midi_mon.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::*; - -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 */ } -} - -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 } -} diff --git a/src/midi_play.rs b/src/midi_play.rs deleted file mode 100644 index f732ae51..00000000 --- a/src/midi_play.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::*; - -#[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 - } -} - -impl PlaybackModel for T {} - -pub trait PlaybackModel: HasPlayClip { - - 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().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(); - (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(); - 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(); - 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)) - } -} diff --git a/src/midi_point.rs b/src/midi_point.rs deleted file mode 100644 index ee1b0b0e..00000000 --- a/src/midi_point.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::*; - -/// -/// ``` -/// 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, -} - -impl_default!(MidiCursor: Self { - time_pos: Arc::new(0.into()), - note_pos: Arc::new(36.into()), - note_len: Arc::new(24.into()), -}); - -impl NotePoint for MidiCursor { - fn note_len (&self) -> &AtomicUsize { - &self.note_len - } - fn note_pos (&self) -> &AtomicUsize { - &self.note_pos - } -} - -impl TimePoint for MidiCursor { - fn time_pos (&self) -> &AtomicUsize { - self.time_pos.as_ref() - } -} - -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() - } -} diff --git a/src/midi_range.rs b/src/midi_range.rs deleted file mode 100644 index 973a1e14..00000000 --- a/src/midi_range.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::*; - -/// -/// ``` -/// 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, -} - -pub trait MidiRange: TimeRange + NoteRange {} - -impl TimeRange for MidiEditor { - fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } - fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() } - fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() } - fn time_start (&self) -> &AtomicUsize { self.mode.time_start() } - fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } -} - -impl NoteRange for MidiEditor { - fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() } - fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() } -} - -impl_from!(MidiSelection: |data:(usize, bool)| Self { - time_len: Arc::new(0.into()), - note_axis: Arc::new(0.into()), - note_lo: Arc::new(0.into()), - time_axis: Arc::new(0.into()), - time_start: Arc::new(0.into()), - time_zoom: Arc::new(data.0.into()), - time_lock: Arc::new(data.1.into()), -}); - -impl TimeRange for MidiSelection { - fn time_len (&self) -> &AtomicUsize { &self.time_len } - fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom } - fn time_lock (&self) -> &AtomicBool { &self.time_lock } - fn time_start (&self) -> &AtomicUsize { &self.time_start } - fn time_axis (&self) -> &AtomicUsize { &self.time_axis } -} - -impl NoteRange for MidiSelection { - fn note_lo (&self) -> &AtomicUsize { &self.note_lo } - fn note_axis (&self) -> &AtomicUsize { &self.note_axis } -} - -pub trait TimeRange { - fn time_len (&self) -> &AtomicUsize; - fn get_time_len (&self) -> usize { - self.time_len().load(Relaxed) - } - fn time_zoom (&self) -> &AtomicUsize; - fn get_time_zoom (&self) -> usize { - self.time_zoom().load(Relaxed) - } - fn set_time_zoom (&self, value: usize) -> usize { - self.time_zoom().swap(value, Relaxed) - } - fn time_lock (&self) -> &AtomicBool; - fn get_time_lock (&self) -> bool { - self.time_lock().load(Relaxed) - } - fn set_time_lock (&self, value: bool) -> bool { - self.time_lock().swap(value, Relaxed) - } - fn time_start (&self) -> &AtomicUsize; - fn get_time_start (&self) -> usize { - self.time_start().load(Relaxed) - } - fn set_time_start (&self, value: usize) -> usize { - self.time_start().swap(value, Relaxed) - } - fn time_axis (&self) -> &AtomicUsize; - fn get_time_axis (&self) -> usize { - self.time_axis().load(Relaxed) - } - fn get_time_end (&self) -> usize { - self.time_start().load(Relaxed) + self.time_axis().load(Relaxed) * self.time_zoom().load(Relaxed) - } -} - -pub trait NoteRange { - fn note_lo (&self) -> &AtomicUsize; - fn note_axis (&self) -> &AtomicUsize; - - fn get_note_lo (&self) -> usize { - self.note_lo().load(Relaxed) - } - fn set_note_lo (&self, x: usize) -> usize { - self.note_lo().swap(x, Relaxed) - } - fn get_note_axis (&self) -> usize { - self.note_axis().load(Relaxed) - } - fn get_note_hi (&self) -> usize { - (self.get_note_lo() + self.get_note_axis().saturating_sub(1)).min(127) - } -} diff --git a/src/midi_rec.rs b/src/midi_rec.rs deleted file mode 100644 index 91e469b3..00000000 --- a/src/midi_rec.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::*; - -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.try_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 - } -} - -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 } -} diff --git a/src/midi_seq.rs b/src/midi_seq.rs deleted file mode 100644 index e8da23f7..00000000 --- a/src/midi_seq.rs +++ /dev/null @@ -1,286 +0,0 @@ -use crate::*; - -/// 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(None); -/// 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] -} diff --git a/src/tek_mode.rs b/src/mode.rs similarity index 100% rename from src/tek_mode.rs rename to src/mode.rs diff --git a/src/tek_pool.rs b/src/pool.rs similarity index 100% rename from src/tek_pool.rs rename to src/pool.rs diff --git a/src/smpl.rs b/src/sample.rs similarity index 52% rename from src/smpl.rs rename to src/sample.rs index d5e69c50..cd7f121d 100644 --- a/src/smpl.rs +++ b/src/sample.rs @@ -316,3 +316,327 @@ impl Track { None } } + +/// Collection of samples, one per slot, fixed number of slots. +/// +/// History: Separated to cleanly implement [Default]. +/// +/// ``` +/// let samples = tek::SampleKit([None, None, None, None]); +/// ``` +#[derive(Debug)] pub struct SampleKit ( + pub [Option>>;N] +); + +impl Default for SampleKit { + fn default () -> Self { Self([const { None }; N]) } +} + +impl SampleKit { + pub fn get (&self, index: usize) -> &Option>> { + if index < self.0.len() { + &self.0[index] + } else { + &None + } + } +} + +/// A sound cut. +/// +/// ``` +/// let sample = tek::Sample::default(); +/// let sample = tek::Sample::new("test", 0, 0, vec![]); +/// ``` +#[derive(Default, Debug)] pub struct Sample { + pub name: Arc, + pub start: usize, + pub end: usize, + pub channels: Vec>, + pub rate: Option, + pub gain: f32, + pub color: ItemTheme, +} + +impl Sample { + pub fn new (name: impl AsRef, start: usize, end: usize, channels: Vec>) -> Self { + Self { + name: name.as_ref().into(), + start, + end, + channels, + rate: None, + gain: 1.0, + color: ItemTheme::random(), + } + } + pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { + Voice { + sample: sample.clone(), + after, + position: sample.try_read().unwrap().start, + velocity: velocity.as_int() as f32 / 127.0, + } + } + pub fn handle_cc (&mut self, controller: u7, value: u7) { + let percentage = value.as_int() as f64 / 127.; + match controller.as_int() { + 20 => { + self.start = (percentage * self.end as f64) as usize; + }, + 21 => { + let length = self.channels[0].len(); + self.end = length.min( + self.start + (percentage * (length as f64 - self.start as f64)) as usize + ); + }, + 22 => { /*attack*/ }, + 23 => { /*decay*/ }, + 24 => { + self.gain = percentage as f32 * 2.0; + }, + 26 => { /* pan */ } + 25 => { /* pitch */ } + _ => {} + } + } + /// Read WAV from file + pub fn read_data (src: &str) -> Usually<(usize, Vec>)> { + let mut channels: Vec> = vec![]; + for channel in wavers::Wav::from_path(src)?.channels() { + channels.push(channel); + } + let mut end = 0; + let mut data: Vec> = vec![]; + for samples in channels.iter() { + let channel = Vec::from(samples.as_ref()); + end = end.max(channel.len()); + data.push(channel); + } + Ok((end, data)) + } + pub fn from_file (path: &PathBuf) -> Usually { + let name = path.file_name().unwrap().to_string_lossy().into(); + let mut sample = Self { name, ..Default::default() }; + // Use file extension if present + let mut hint = Hint::new(); + if let Some(ext) = path.extension() { + hint.with_extension(&ext.to_string_lossy()); + } + let probed = symphonia::default::get_probe().format( + &hint, + MediaSourceStream::new( + Box::new(File::open(path)?), + Default::default(), + ), + &Default::default(), + &Default::default() + )?; + let mut format = probed.format; + let params = &format.tracks().iter() + .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) + .expect("no tracks found") + .codec_params; + let mut decoder = get_codecs().make(params, &Default::default())?; + loop { + match format.next_packet() { + Ok(packet) => sample.decode_packet(&mut decoder, packet)?, + Err(symphonia::core::errors::Error::IoError(_)) => break decoder.last_decoded(), + Err(err) => return Err(err.into()), + }; + }; + sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); + Ok(sample) + } + fn decode_packet ( + &mut self, decoder: &mut Box, packet: Packet + ) -> Usually<()> { + // Decode a packet + let decoded = decoder + .decode(&packet) + .map_err(|e|Box::::from(e))?; + // Determine sample rate + let spec = *decoded.spec(); + if let Some(rate) = self.rate { + if rate != spec.rate as usize { + panic!("sample rate changed"); + } + } else { + self.rate = Some(spec.rate as usize); + } + // Determine channel count + while self.channels.len() < spec.channels.count() { + self.channels.push(vec![]); + } + // Load sample + let mut samples = SampleBuffer::new( + decoded.frames() as u64, + spec + ); + if samples.capacity() > 0 { + samples.copy_interleaved_ref(decoded); + for frame in samples.samples().chunks(spec.channels.count()) { + for (chan, frame) in frame.iter().enumerate() { + self.channels[chan].push(*frame) + } + } + } + Ok(()) + } +} + +#[derive(Default, Debug)] pub struct SampleAdd { + pub exited: bool, + pub dir: PathBuf, + pub subdirs: Vec, + pub files: Vec, + pub cursor: usize, + pub offset: usize, + pub sample: Arc>, + pub voices: Arc>>, + pub _search: Option, +} + +impl_draw!(|self: SampleAdd, to: Tui|{ todo!() }); + +#[allow(unused)] +impl SampleAdd { + fn exited (&self) -> bool { + self.exited + } + fn exit (&mut self) { + self.exited = true + } + pub fn new ( + sample: &Arc>, + voices: &Arc>> + ) -> Usually { + let dir = std::env::current_dir()?; + let (subdirs, files) = scan(&dir)?; + Ok(Self { + exited: false, + dir, + subdirs, + files, + cursor: 0, + offset: 0, + sample: sample.clone(), + voices: voices.clone(), + _search: None + }) + } + fn rescan (&mut self) -> Usually<()> { + scan(&self.dir).map(|(subdirs, files)|{ + self.subdirs = subdirs; + self.files = files; + }) + } + fn prev (&mut self) { + self.cursor = self.cursor.saturating_sub(1); + } + fn next (&mut self) { + self.cursor = self.cursor + 1; + } + fn try_preview (&mut self) -> Usually<()> { + if let Some(path) = self.cursor_file() { + if let Ok(sample) = Sample::from_file(&path) { + *self.sample.try_write().unwrap() = sample; + self.voices.try_write().unwrap().push( + Sample::play(&self.sample, 0, &u7::from(100u8)) + ); + } + //load_sample(&path)?; + //let src = std::fs::File::open(&path)?; + //let mss = MediaSourceStream::new(Box::new(src), Default::default()); + //let mut hint = Hint::new(); + //if let Some(ext) = path.extension() { + //hint.with_extension(&ext.to_string_lossy()); + //} + //let meta_opts: MetadataOptions = Default::default(); + //let fmt_opts: FormatOptions = Default::default(); + //if let Ok(mut probed) = symphonia::default::get_probe() + //.format(&hint, mss, &fmt_opts, &meta_opts) + //{ + //panic!("{:?}", probed.format.metadata()); + //}; + } + Ok(()) + } + fn cursor_dir (&self) -> Option { + if self.cursor < self.subdirs.len() { + Some(self.dir.join(&self.subdirs[self.cursor])) + } else { + None + } + } + fn cursor_file (&self) -> Option { + if self.cursor < self.subdirs.len() { + return None + } + let index = self.cursor.saturating_sub(self.subdirs.len()); + if index < self.files.len() { + Some(self.dir.join(&self.files[index])) + } else { + None + } + } + fn pick (&mut self) -> Usually { + if self.cursor == 0 { + if let Some(parent) = self.dir.parent() { + self.dir = parent.into(); + self.rescan()?; + self.cursor = 0; + return Ok(false) + } + } + if let Some(dir) = self.cursor_dir() { + self.dir = dir; + self.rescan()?; + self.cursor = 0; + return Ok(false) + } + if let Some(path) = self.cursor_file() { + let (end, channels) = read_sample_data(&path.to_string_lossy())?; + let mut sample = self.sample.try_write().unwrap(); + sample.name = path.file_name().unwrap().to_string_lossy().into(); + sample.end = end; + sample.channels = channels; + return Ok(true) + } + return Ok(false) + } +} + +#[allow(unused)] +fn read_sample_data (_: &str) -> Usually<(usize, Vec>)> { + todo!(); +} + +/// A currently playing instance of a sample. +#[derive(Default, Debug, Clone)] pub struct Voice { + pub sample: Arc>, + pub after: usize, + pub position: usize, + pub velocity: f32, +} + +impl Iterator for Voice { + type Item = [f32;2]; + fn next (&mut self) -> Option { + if self.after > 0 { + self.after -= 1; + return Some([0.0, 0.0]) + } + let sample = self.sample.try_read().unwrap(); + if self.position < sample.end { + let position = self.position; + self.position += 1; + return sample.channels[0].get(position).map(|_amplitude|[ + sample.channels[0][position] * self.velocity * sample.gain, + sample.channels[0][position] * self.velocity * sample.gain, + ]) + } + None + } +} + diff --git a/src/sequence.rs b/src/sequence.rs new file mode 100644 index 00000000..48896587 --- /dev/null +++ b/src/sequence.rs @@ -0,0 +1,1191 @@ +use crate::*; + +/// 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(None); +/// 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] +} + +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.try_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 + } +} + +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 } +} + +/// +/// ``` +/// 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, +} + +pub trait MidiRange: TimeRange + NoteRange {} + +impl TimeRange for MidiEditor { + fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } + fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() } + fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() } + fn time_start (&self) -> &AtomicUsize { self.mode.time_start() } + fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } +} + +impl NoteRange for MidiEditor { + fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() } + fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() } +} + +impl_from!(MidiSelection: |data:(usize, bool)| Self { + time_len: Arc::new(0.into()), + note_axis: Arc::new(0.into()), + note_lo: Arc::new(0.into()), + time_axis: Arc::new(0.into()), + time_start: Arc::new(0.into()), + time_zoom: Arc::new(data.0.into()), + time_lock: Arc::new(data.1.into()), +}); + +impl TimeRange for MidiSelection { + fn time_len (&self) -> &AtomicUsize { &self.time_len } + fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom } + fn time_lock (&self) -> &AtomicBool { &self.time_lock } + fn time_start (&self) -> &AtomicUsize { &self.time_start } + fn time_axis (&self) -> &AtomicUsize { &self.time_axis } +} + +impl NoteRange for MidiSelection { + fn note_lo (&self) -> &AtomicUsize { &self.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.note_axis } +} + +pub trait TimeRange { + fn time_len (&self) -> &AtomicUsize; + fn get_time_len (&self) -> usize { + self.time_len().load(Relaxed) + } + fn time_zoom (&self) -> &AtomicUsize; + fn get_time_zoom (&self) -> usize { + self.time_zoom().load(Relaxed) + } + fn set_time_zoom (&self, value: usize) -> usize { + self.time_zoom().swap(value, Relaxed) + } + fn time_lock (&self) -> &AtomicBool; + fn get_time_lock (&self) -> bool { + self.time_lock().load(Relaxed) + } + fn set_time_lock (&self, value: bool) -> bool { + self.time_lock().swap(value, Relaxed) + } + fn time_start (&self) -> &AtomicUsize; + fn get_time_start (&self) -> usize { + self.time_start().load(Relaxed) + } + fn set_time_start (&self, value: usize) -> usize { + self.time_start().swap(value, Relaxed) + } + fn time_axis (&self) -> &AtomicUsize; + fn get_time_axis (&self) -> usize { + self.time_axis().load(Relaxed) + } + fn get_time_end (&self) -> usize { + self.time_start().load(Relaxed) + self.time_axis().load(Relaxed) * self.time_zoom().load(Relaxed) + } +} + +pub trait NoteRange { + fn note_lo (&self) -> &AtomicUsize; + fn note_axis (&self) -> &AtomicUsize; + + fn get_note_lo (&self) -> usize { + self.note_lo().load(Relaxed) + } + fn set_note_lo (&self, x: usize) -> usize { + self.note_lo().swap(x, Relaxed) + } + fn get_note_axis (&self) -> usize { + self.note_axis().load(Relaxed) + } + fn get_note_hi (&self) -> usize { + (self.get_note_lo() + self.get_note_axis().saturating_sub(1)).min(127) + } +} + +/// +/// ``` +/// 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, +} + +impl_default!(MidiCursor: Self { + time_pos: Arc::new(0.into()), + note_pos: Arc::new(36.into()), + note_len: Arc::new(24.into()), +}); + +impl NotePoint for MidiCursor { + fn note_len (&self) -> &AtomicUsize { + &self.note_len + } + fn note_pos (&self) -> &AtomicUsize { + &self.note_pos + } +} + +impl TimePoint for MidiCursor { + fn time_pos (&self) -> &AtomicUsize { + self.time_pos.as_ref() + } +} + +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() + } +} + +#[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 + } +} + +impl PlaybackModel for T {} + +pub trait PlaybackModel: HasPlayClip { + + 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().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(); + (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(); + 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(); + 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 */ } +} + +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 } +} + +/// 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, +} + +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 + } + /// Change the color of a clip, returning the previous color. + /// If value not provided, randomize. + pub fn set_color (&mut self, color: Option) -> ItemTheme { + let mut color = color.unwrap_or_else(||ItemTheme::random()); + std::mem::swap(&mut color, &mut self.color); + color + } + /// Set loop mode for a clip, returning the previous loop state. + /// If value not provided, toggle. + pub fn toggle_loop (&mut self, looping: Option) -> bool { + match looping { + Some(true) => { + self.looped = true; + false + }, + Some(false) => { + self.looped = false; + true + }, + None => { + self.looped = !self.looped; + !self.looped + } + } + } + /// Add a [MidiMessage] at a given `pulse` position of this clip. + 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 + } + /// Create a panic clip. + pub fn stop_all () -> Self { + let color = Some(ItemColor::from_tui(Color::Rgb(32, 32, 32)).into()); + Self::new("Stop", false, 1, Some(vec![vec![MIDI_PANIC]]), color) + } +} + +const MIDI_PANIC: MidiMessage = MidiMessage::Controller { + controller: u7::new(123), value: u7::new(0), +}; + +pub type MidiData = Vec>; +impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } +impl Eq for MidiClip {} + +impl EditorModel for T {} + +pub trait EditorModel: HasEditor { + fn is_editing (&self) -> bool { + self.editor().is_some() + } + fn editor_w (&self) -> usize { + self.editor().as_ref().map(|e|e.size.w()).unwrap_or(0) as usize + } + fn editor_h (&self) -> usize { + self.editor().as_ref().map(|e|e.size.h()).unwrap_or(0) as usize + } +} + +/// Contains state for viewing and editing a clip. +/// +/// ``` +/// use std::sync::Arc; +/// use parking_lot::RwLock; +/// use tek::{MidiClip, MidiEditor, PianoHorizontal}; +/// let mode = PianoHorizontal::default(); +/// let editor = MidiEditor::new(); +/// editor.mode.clip.set(Some(MidiClip::stop_all())); +/// let _ = editor.put_note(true); +/// let _ = editor.put_note(false); +/// ``` +pub struct MidiEditor { + /// View mode and state of editor + pub mode: PianoHorizontal, + /// Size of editor on screen + pub size: Sizer, +} + +impl_default!(MidiEditor: Self { + mode: Default::default(), + size: Sizer( + Arc::new(0usize.into()), + Arc::new(0usize.into()), + ), +}); + +impl MidiEditor { + pub fn new () -> Self { + Self { + mode: PianoHorizontal { + keys_width: 4, + point: MidiCursor { + note_len: Arc::new(1.into()), + ..Default::default() + }, + range: MidiSelection { + time_len: Arc::new(96usize.into()), + time_zoom: Arc::new(1usize.into()), + note_axis: Arc::new(12usize.into()), + ..Default::default() + }, + size: Sizer( + Arc::new(48usize.into()), + Arc::new(12usize.into()), + ), + ..Default::default() + }, + size: Sizer( + Arc::new(52usize.into()), + Arc::new(12usize.into()), + ), + } + } + + /// Put note at current position + pub fn put_note (&self, advance: bool) { + let mut redraw = false; + if let Some(clip) = &*self.mode.clip.read() { + let note_start = self.get_time_pos(); + let note_pos = self.get_note_pos(); + let note_len = self.get_note_len(); + let note_end = note_start + (note_len.saturating_sub(1)); + let key: u7 = u7::from(note_pos as u8); + let vel: u7 = 100.into(); + let mut clip = clip.write(); + let length = clip.length; + let note_end = note_end % length; + let note_on = MidiMessage::NoteOn { key, vel }; + if !clip.notes[note_start].iter().any(|msg|*msg == note_on) { + clip.notes[note_start].push(note_on); + } + let note_off = MidiMessage::NoteOff { key, vel }; + if !clip.notes[note_end].iter().any(|msg|*msg == note_off) { + clip.notes[note_end].push(note_off); + } + if advance { + self.set_time_pos((note_end + 1) % clip.length); + } + redraw = true; + } + if redraw { + self.mode.redraw(); + } + } + fn clip_length (&self) -> usize { + self.mode.clip.map_inner(|p|p.length).unwrap_or(1) + } + fn note_length (&self) -> usize { + self.get_note_len() + } + fn note_pos (&self) -> usize { + self.get_note_pos() + } + fn note_pos_next (&self) -> usize { + self.get_note_pos() + 1 + } + fn note_pos_next_octave (&self) -> usize { + self.get_note_pos() + 12 + } + fn note_pos_prev (&self) -> usize { + self.get_note_pos().saturating_sub(1) + } + fn note_pos_prev_octave (&self) -> usize { + self.get_note_pos().saturating_sub(12) + } + fn note_len (&self) -> usize { + self.get_note_len() + } + fn note_len_next (&self) -> usize { + self.get_note_len() + 1 + } + fn note_len_prev (&self) -> usize { + self.get_note_len().saturating_sub(1) + } + fn note_range (&self) -> usize { + self.get_note_axis() + } + fn note_range_next (&self) -> usize { + self.get_note_axis() + 1 + } + fn note_range_prev (&self) -> usize { + self.get_note_axis().saturating_sub(1) + } + fn time_zoom (&self) -> usize { self.get_time_zoom() } + fn time_zoom_next (&self) -> usize { self.get_time_zoom() + 1 } + fn time_zoom_next_fine (&self) -> usize { self.get_time_zoom() + 1 } + fn time_zoom_prev (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) } + fn time_zoom_prev_fine (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) } + fn time_lock (&self) -> bool { self.get_time_lock() } + fn time_lock_toggled (&self) -> bool { !self.get_time_lock() } + fn time_pos (&self) -> usize { self.get_time_pos() } + fn time_pos_next (&self) -> usize { (self.get_time_pos() + self.get_note_len()) % self.clip_length() } + fn time_pos_next_fine (&self) -> usize { (self.get_time_pos() + 1) % self.clip_length() } + fn time_pos_prev (&self) -> usize { + let step = self.get_note_len(); + self.get_time_pos().overflowing_sub(step) + .0.min(self.clip_length().saturating_sub(step)) + } + fn time_pos_prev_fine (&self) -> usize { + self.get_time_pos().overflowing_sub(1) + .0.min(self.clip_length().saturating_sub(1)) + } +} + +impl MidiPoint for T {} + +impl MidiRange for T {} + +pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync { + + fn buffer_size (&self, clip: &MidiClip) -> (usize, usize); + + fn redraw (&self); + + /// 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().store(note_pos, Relaxed); + } else if note_pos > note_hi { + self.note_lo().store((note_lo + note_pos).saturating_sub(note_hi), Relaxed); + } + } + + /// Make sure time range is within display + fn autozoom (&self) { + if self.time_lock().load(Relaxed) { + 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().load(Relaxed); + 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().store(next_time_zoom, Relaxed); + } 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().store(prev_time_zoom, Relaxed); + } else { + break + } + } + } + if time_zoom != self.time_zoom().load(Relaxed) { + 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); + } + +} + +impl_has!(Sizer: |self: MidiEditor| self.size); + +impl std::fmt::Debug for MidiEditor { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("MidiEditor").field("mode", &self.mode).finish() + } +} + +impl NotePoint for MidiEditor { + fn note_len (&self) -> &AtomicUsize { self.mode.note_len() } + fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() } +} + +impl TimePoint for MidiEditor { + fn time_pos (&self) -> &AtomicUsize { self.mode.time_pos() } +} + +impl MidiViewer for MidiEditor { + fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { + self.mode.buffer_size(clip) + } + fn redraw (&self) { + self.mode.redraw() + } +} + +/// 12 piano keys, some highlighted. +/// +/// ``` +/// let keys = tek::OctaveVertical::default(); +/// ``` +#[derive(Copy, Clone)] +pub struct OctaveVertical { + pub on: [bool; 12], + pub colors: [Color; 3] +} + +impl OctaveVertical { + pub fn color (&self, pitch: usize) -> Color { + let pitch = pitch % 12; + self.colors[if self.on[pitch] { 2 } else { match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } }] + } +} + +impl_default!(OctaveVertical: Self { + on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] +}); + +/// A clip, rendered as a horizontal piano roll. +/// +/// Uses full blocks on note on and half blocks on legato: █▄ █▄ █▄ +/// +/// ``` +/// let piano = tek::PianoHorizontal::default(); +/// ``` +#[derive(Clone, Default)] pub struct PianoHorizontal { + pub clip: ClipSlot, + /// Buffer where the whole clip is rerendered on change + pub buffer: Arc>, + /// Size of actual notes area + pub size: Sizer, + /// 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, +} + +impl_has!(Sizer: |self: PianoHorizontal| self.size); + +impl PianoHorizontal { + pub fn new () -> Self { + let size = Sizer::default(); + let mut range = MidiSelection::from((12, true)); + range.time_axis = size.0.clone(); + range.note_axis = size.1.clone(); + let piano = Self { keys_width: 4, size, range, ..Default::default() }; + piano.redraw(); + piano + } +} + +impl TimeRange for PianoHorizontal { + fn time_len (&self) -> &AtomicUsize { self.range.time_len() } + fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() } + fn time_lock (&self) -> &AtomicBool { self.range.time_lock() } + fn time_start (&self) -> &AtomicUsize { self.range.time_start() } + fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() } +} + +impl NoteRange for PianoHorizontal { + fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() } + fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() } +} + +impl NotePoint for PianoHorizontal { + fn note_len (&self) -> &AtomicUsize { self.point.note_len() } + fn note_pos (&self) -> &AtomicUsize { self.point.note_pos() } +} + +impl TimePoint for PianoHorizontal { + fn time_pos (&self) -> &AtomicUsize { self.point.time_pos() } +} + +impl MidiViewer for PianoHorizontal { + /// Determine the required space to render the clip. + fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { + let zoom = self.range.time_zoom().load(Relaxed); + let size = if zoom > 0 { clip.length / zoom } else { 0 }; + (size, 128) + } + /// Draw the piano roll into the buffer. + fn redraw (&self) { + *self.buffer.write() = redraw_piano(&self) + } +} + +impl std::fmt::Debug for PianoHorizontal { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + let buffer = self.buffer.read(); + f.debug_struct("PianoHorizontal") + .field("time_zoom", &self.range.time_zoom) + .field("buffer", &format!("{}x{}", buffer.width, buffer.height)) + .finish() + } +} + +//impl App { + ///// Toggle MIDI editor. + ///// + ///// ``` + ///// tek::App::default().toggle_editor(None); + ///// ``` + //pub fn toggle_editor (&mut self, value: Option) { + ////FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); + //if value.unwrap_or_else(||!self.editor().is_some()) { + //self.toggle_editor_on() + //} else { + //self.toggle_editor_off() + //} + //} + + //fn toggle_editor_on (&mut self) { + //// Create new clip in pool when entering empty cell + //if let Selection::TrackClip { track: track_index, scene } = *self.selection() + //&& let Some(scene) = self.project.scenes.get_mut(scene) + //&& scene.clips.get(track_index).is_none() + //&& let Some(track) = self.project.tracks.get_mut(track_index) + //{ + //let color = track.color.base.mix(scene.color.base, 0.5); + //let color = ItemColor::random_near(color, 0.2).into(); + //let clip: MidiClip = MidiClip { color, ..Default::default() }; + //let clip = self.project.clips.append(Some(clip)); + //// autocolor: new clip colors from scene and track color + //if let Some(editor) = &mut self.project.editor { + //*editor.mode.clip.write() = clip.0.read().clone(); + //editor.mode.color = clip.0.read().as_ref().map(|p|p.read().color) + //.unwrap_or(ItemTheme::G[64]); + //} + //*scene.clips.slot(track_index).write() = clip.0.read().clone(); + ////Some(clip) + //} else { + ////None + //} + //} + + //fn toggle_editor_off (&mut self) { + //if let Selection::TrackClip { track, scene } = *self.selection() + //&& let Some(scene) = self.project.scenes.get_mut(scene) + //{ + //// Remove clip from arrangement when exiting empty clip editor + //let mut swapped = None; + //if let Some(count) = scene.clips.slot(track).read().as_ref().map(|c|c.read().count_midi_messages()) + //&& count == 0 + //{ + //std::mem::swap(&mut swapped, &mut scene.clips.slot(track).write()); + //} + //if let Some(clip) = swapped { + //self.project.clips.remove(&*clip.read()); + //} + //} + //} +//} diff --git a/src/smpl_add.rs b/src/smpl_add.rs deleted file mode 100644 index 5088d3de..00000000 --- a/src/smpl_add.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::*; - -#[derive(Default, Debug)] pub struct SampleAdd { - pub exited: bool, - pub dir: PathBuf, - pub subdirs: Vec, - pub files: Vec, - pub cursor: usize, - pub offset: usize, - pub sample: Arc>, - pub voices: Arc>>, - pub _search: Option, -} - -impl_draw!(|self: SampleAdd, to: Tui|{ todo!() }); - -#[allow(unused)] -impl SampleAdd { - fn exited (&self) -> bool { - self.exited - } - fn exit (&mut self) { - self.exited = true - } - pub fn new ( - sample: &Arc>, - voices: &Arc>> - ) -> Usually { - let dir = std::env::current_dir()?; - let (subdirs, files) = scan(&dir)?; - Ok(Self { - exited: false, - dir, - subdirs, - files, - cursor: 0, - offset: 0, - sample: sample.clone(), - voices: voices.clone(), - _search: None - }) - } - fn rescan (&mut self) -> Usually<()> { - scan(&self.dir).map(|(subdirs, files)|{ - self.subdirs = subdirs; - self.files = files; - }) - } - fn prev (&mut self) { - self.cursor = self.cursor.saturating_sub(1); - } - fn next (&mut self) { - self.cursor = self.cursor + 1; - } - fn try_preview (&mut self) -> Usually<()> { - if let Some(path) = self.cursor_file() { - if let Ok(sample) = Sample::from_file(&path) { - *self.sample.try_write().unwrap() = sample; - self.voices.try_write().unwrap().push( - Sample::play(&self.sample, 0, &u7::from(100u8)) - ); - } - //load_sample(&path)?; - //let src = std::fs::File::open(&path)?; - //let mss = MediaSourceStream::new(Box::new(src), Default::default()); - //let mut hint = Hint::new(); - //if let Some(ext) = path.extension() { - //hint.with_extension(&ext.to_string_lossy()); - //} - //let meta_opts: MetadataOptions = Default::default(); - //let fmt_opts: FormatOptions = Default::default(); - //if let Ok(mut probed) = symphonia::default::get_probe() - //.format(&hint, mss, &fmt_opts, &meta_opts) - //{ - //panic!("{:?}", probed.format.metadata()); - //}; - } - Ok(()) - } - fn cursor_dir (&self) -> Option { - if self.cursor < self.subdirs.len() { - Some(self.dir.join(&self.subdirs[self.cursor])) - } else { - None - } - } - fn cursor_file (&self) -> Option { - if self.cursor < self.subdirs.len() { - return None - } - let index = self.cursor.saturating_sub(self.subdirs.len()); - if index < self.files.len() { - Some(self.dir.join(&self.files[index])) - } else { - None - } - } - fn pick (&mut self) -> Usually { - if self.cursor == 0 { - if let Some(parent) = self.dir.parent() { - self.dir = parent.into(); - self.rescan()?; - self.cursor = 0; - return Ok(false) - } - } - if let Some(dir) = self.cursor_dir() { - self.dir = dir; - self.rescan()?; - self.cursor = 0; - return Ok(false) - } - if let Some(path) = self.cursor_file() { - let (end, channels) = read_sample_data(&path.to_string_lossy())?; - let mut sample = self.sample.try_write().unwrap(); - sample.name = path.file_name().unwrap().to_string_lossy().into(); - sample.end = end; - sample.channels = channels; - return Ok(true) - } - return Ok(false) - } -} - -#[allow(unused)] -fn read_sample_data (_: &str) -> Usually<(usize, Vec>)> { - todo!(); -} diff --git a/src/smpl_kit.rs b/src/smpl_kit.rs deleted file mode 100644 index 382ce678..00000000 --- a/src/smpl_kit.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::*; - -/// Collection of samples, one per slot, fixed number of slots. -/// -/// History: Separated to cleanly implement [Default]. -/// -/// ``` -/// let samples = tek::SampleKit([None, None, None, None]); -/// ``` -#[derive(Debug)] pub struct SampleKit ( - pub [Option>>;N] -); - -impl Default for SampleKit { - fn default () -> Self { Self([const { None }; N]) } -} - -impl SampleKit { - pub fn get (&self, index: usize) -> &Option>> { - if index < self.0.len() { - &self.0[index] - } else { - &None - } - } -} diff --git a/src/smpl_sample.rs b/src/smpl_sample.rs deleted file mode 100644 index 0b034cda..00000000 --- a/src/smpl_sample.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::*; - -/// A sound cut. -/// -/// ``` -/// let sample = tek::Sample::default(); -/// let sample = tek::Sample::new("test", 0, 0, vec![]); -/// ``` -#[derive(Default, Debug)] pub struct Sample { - pub name: Arc, - pub start: usize, - pub end: usize, - pub channels: Vec>, - pub rate: Option, - pub gain: f32, - pub color: ItemTheme, -} - -impl Sample { - pub fn new (name: impl AsRef, start: usize, end: usize, channels: Vec>) -> Self { - Self { - name: name.as_ref().into(), - start, - end, - channels, - rate: None, - gain: 1.0, - color: ItemTheme::random(), - } - } - pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { - Voice { - sample: sample.clone(), - after, - position: sample.try_read().unwrap().start, - velocity: velocity.as_int() as f32 / 127.0, - } - } - pub fn handle_cc (&mut self, controller: u7, value: u7) { - let percentage = value.as_int() as f64 / 127.; - match controller.as_int() { - 20 => { - self.start = (percentage * self.end as f64) as usize; - }, - 21 => { - let length = self.channels[0].len(); - self.end = length.min( - self.start + (percentage * (length as f64 - self.start as f64)) as usize - ); - }, - 22 => { /*attack*/ }, - 23 => { /*decay*/ }, - 24 => { - self.gain = percentage as f32 * 2.0; - }, - 26 => { /* pan */ } - 25 => { /* pitch */ } - _ => {} - } - } - /// Read WAV from file - pub fn read_data (src: &str) -> Usually<(usize, Vec>)> { - let mut channels: Vec> = vec![]; - for channel in wavers::Wav::from_path(src)?.channels() { - channels.push(channel); - } - let mut end = 0; - let mut data: Vec> = vec![]; - for samples in channels.iter() { - let channel = Vec::from(samples.as_ref()); - end = end.max(channel.len()); - data.push(channel); - } - Ok((end, data)) - } - pub fn from_file (path: &PathBuf) -> Usually { - let name = path.file_name().unwrap().to_string_lossy().into(); - let mut sample = Self { name, ..Default::default() }; - // Use file extension if present - let mut hint = Hint::new(); - if let Some(ext) = path.extension() { - hint.with_extension(&ext.to_string_lossy()); - } - let probed = symphonia::default::get_probe().format( - &hint, - MediaSourceStream::new( - Box::new(File::open(path)?), - Default::default(), - ), - &Default::default(), - &Default::default() - )?; - let mut format = probed.format; - let params = &format.tracks().iter() - .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) - .expect("no tracks found") - .codec_params; - let mut decoder = get_codecs().make(params, &Default::default())?; - loop { - match format.next_packet() { - Ok(packet) => sample.decode_packet(&mut decoder, packet)?, - Err(symphonia::core::errors::Error::IoError(_)) => break decoder.last_decoded(), - Err(err) => return Err(err.into()), - }; - }; - sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); - Ok(sample) - } - fn decode_packet ( - &mut self, decoder: &mut Box, packet: Packet - ) -> Usually<()> { - // Decode a packet - let decoded = decoder - .decode(&packet) - .map_err(|e|Box::::from(e))?; - // Determine sample rate - let spec = *decoded.spec(); - if let Some(rate) = self.rate { - if rate != spec.rate as usize { - panic!("sample rate changed"); - } - } else { - self.rate = Some(spec.rate as usize); - } - // Determine channel count - while self.channels.len() < spec.channels.count() { - self.channels.push(vec![]); - } - // Load sample - let mut samples = SampleBuffer::new( - decoded.frames() as u64, - spec - ); - if samples.capacity() > 0 { - samples.copy_interleaved_ref(decoded); - for frame in samples.samples().chunks(spec.channels.count()) { - for (chan, frame) in frame.iter().enumerate() { - self.channels[chan].push(*frame) - } - } - } - Ok(()) - } -} diff --git a/src/smpl_voice.rs b/src/smpl_voice.rs deleted file mode 100644 index 75be5ae9..00000000 --- a/src/smpl_voice.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::*; - -/// A currently playing instance of a sample. -#[derive(Default, Debug, Clone)] pub struct Voice { - pub sample: Arc>, - pub after: usize, - pub position: usize, - pub velocity: f32, -} - -impl Iterator for Voice { - type Item = [f32;2]; - fn next (&mut self) -> Option { - if self.after > 0 { - self.after -= 1; - return Some([0.0, 0.0]) - } - let sample = self.sample.try_read().unwrap(); - if self.position < sample.end { - let position = self.position; - self.position += 1; - return sample.channels[0].get(position).map(|_amplitude|[ - sample.channels[0][position] * self.velocity * sample.gain, - sample.channels[0][position] * self.velocity * sample.gain, - ]) - } - None - } -} - diff --git a/src/tek.rs b/src/tek.rs index 97c57252..aa02cae1 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -8,46 +8,30 @@ macro_rules! def_sizes_iter { } } -mod deps; pub use self::deps::*; -mod clock; pub use self::clock::*; -mod proj; pub use self::proj::*; -mod device; pub use self::device::*; -mod scene; pub use self::scene::*; -mod track; pub use self::track::*; -mod mixer; pub use self::mixer::*; +mod bind; pub use self::bind::*; +mod cli; pub use self::cli::*; +mod clock; pub use self::clock::*; +mod conf; pub use self::conf::*; +mod deps; pub use self::deps::*; +mod device; pub use self::device::*; +mod mixer; pub use self::mixer::*; +mod mode; pub use self::mode::*; +mod pool; pub use self::pool::*; +mod proj; pub use self::proj::*; +mod sample; pub use self::sample::*; +mod sequence; pub use self::sequence::*; +mod scene; pub use self::scene::*; +mod track; pub use self::track::*; +mod view; pub use self::view::*; -mod midi_clip; pub use self::midi_clip::*; -mod midi_edit; pub use self::midi_edit::*; -mod midi_mon; pub use self::midi_mon::*; -mod midi_play; pub use self::midi_play::*; -mod midi_point; pub use self::midi_point::*; -mod midi_range; pub use self::midi_range::*; -mod midi_rec; pub use self::midi_rec::*; -mod midi_seq; pub use self::midi_seq::*; +#[cfg(feature = "lv2")] mod lv2; +#[cfg(feature = "lv2")] pub use self::lv2::*; -mod smpl; pub use self::smpl::*; -mod smpl_add; pub use self::smpl_add::*; -mod smpl_kit; pub use self::smpl_kit::*; -mod smpl_sample; pub use self::smpl_sample::*; -mod smpl_voice; pub use self::smpl_voice::*; +#[cfg(feature = "lv2_gui")] mod lv2_gui; +#[cfg(feature = "lv2_gui")] pub use self::lv2_gui::*; -mod tek_bind; pub use self::tek_bind::*; -mod tek_cli; pub use self::tek_cli::*; -mod tek_conf; pub use self::tek_conf::*; -mod tek_ctrl; pub use self::tek_ctrl::*; -mod tek_has; pub use self::tek_has::*; -mod tek_mode; pub use self::tek_mode::*; -mod tek_pool; pub use self::tek_pool::*; -mod tek_view; pub use self::tek_view::*; - -#[cfg(feature = "lv2")] mod device_lv2; -#[cfg(feature = "lv2")] pub use self::device_lv2::*; - -#[cfg(feature = "lv2_gui")] mod device_lv2_gui; -#[cfg(feature = "lv2_gui")] pub use self::device_lv2_gui::*; - -#[cfg(feature = "vst2")] mod device_vst2; -#[cfg(feature = "vst2")] pub use self::device_vst2::*; +#[cfg(feature = "vst2")] mod vst2; +#[cfg(feature = "vst2")] pub use self::vst2::*; /// Command-line entrypoint. #[allow(unused)] @@ -718,6 +702,1239 @@ impl App { } } +/// Has a stack of currently active interaction modes. +pub trait HasModeStack { + /// Readable mode stack. + fn modes (&self) -> RwLockReadGuard<'_, Vec>>; + /// Writable mode stack. + fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec>>; +} + +/// Has a MIDI editor. +/// +/// ``` +/// use tek::{*, tengri::*}; +/// +/// struct Test(Option); +/// impl_as_ref!(Option: |self: Test|&self.0); +/// impl_as_mut!(Option: |self: Test|&mut self.0); +/// +/// 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(); +/// ``` +impl HasEditor for T where T: + AsRef> + AsMut> {} +pub trait HasEditor: + AsRef> + AsMut> +{ + fn editor (&self) -> &Option { + self.as_ref() + } + fn editor_mut (&mut self) -> &mut Option { + self.as_mut() + } +} + +/// Has a pair of buffers for MIDI IO. +pub trait HasMidiBuffers { + fn note_buf_mut (&mut self) -> &mut Vec; + fn midi_buf_mut (&mut self) -> &mut Vec>>; +} + +/// Can have a currently playing clip. +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>>)>; +} + +/// Has a MIDI sequencer. +impl HasSequencer for T where T: + AsRef + AsMut {} +pub trait HasSequencer: + AsRef + AsMut +{ + fn sequencer (&self) -> &Sequencer { + self.as_ref() + } + fn sequencer_mut (&mut self) -> &mut Sequencer { + self.as_mut() + } +} + +impl HasClock for T where T: + AsRef + AsMut {} + +/// Has a clock. +pub trait HasClock: + AsRef + AsMut +{ + fn clock (&self) -> &Clock { + self.as_ref() + } + fn clock_mut (&mut self) -> &mut Clock { + self.as_mut() + } +} + +/// Has a chain of devices. +pub trait HasDevices: + AsRef> + AsMut> +{ + fn devices (&self) -> &Vec { + self.as_ref() + } + fn devices_mut (&mut self) -> &mut Vec { + self.as_mut() + } +} + +pub trait HasProject: + AsRef> + AsMut> +{} + +/// Has a loaded project. +impl HasProject for T where T: + AsRef> + AsMut> +{} + +impl HasScene for T where T: + AsRefOpt + AsMutOpt + Send + Sync +{} + +pub trait HasScene: + AsRefOpt + AsMutOpt +{ + fn scene (&self) -> Option<&Scene> { + self.as_ref_opt() + } + fn scene_mut (&mut self) -> Option<&mut Scene> { + self.as_mut_opt() + } +} + +pub trait HasScenes { + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + fn scenes_scroll (&self) -> usize { + 0 + } +} + +impl HasSelection for T where T: + AsRef + AsMut +{} + +pub trait HasSelection: AsRef + AsMut { + fn selection (&self) -> &Selection { + self.as_ref() + } + fn selection_mut (&mut self) -> &mut Selection { + self.as_mut() + } +} + +impl HasTrack for T where T: + AsRefOpt + AsMutOpt +{} + +pub trait HasTrack: AsRefOpt + AsMutOpt { + fn track (&self) -> Option<&Track> { + self.as_ref_opt() + } + fn track_mut (&mut self) -> Option<&mut Track> { + self.as_mut_opt() + } +} + +impl HasTracks for T where T: + AsRef> + AsMut> + HasClock + HasTrackScroll +{} + +pub trait HasTracks: AsRef> + AsMut> + HasClock + HasTrackScroll { + /// Read-only reference to collection of [Track]s. + fn tracks (&self) -> &Vec { + self.as_ref() + } + /// Mutable reference to collection of [Track]s. + fn tracks_mut (&mut self) -> &mut Vec { + self.as_mut() + } +} + +pub trait HasTrackScroll { + fn track_scroll (&self) -> usize; + fn tracks_last (&self) -> usize; + fn tracks_last_mut (&mut self) -> &mut usize; +} + +pub trait HasSampler: AsRef + AsMut { + fn sampler (&self) -> &Sampler { + self.as_ref() + } + fn sampler_mut (&mut self) -> &mut Sampler { + self.as_mut() + } +} + +pub trait HasClip { + fn clip_slot (&self) -> Option>; + fn clip (&self) -> Option>>>> { + self.clip_slot().map(|slot|slot.read()) + } + fn clip_mut (&self) -> Option>>>> { + self.clip_slot().map(|slot|slot.write()) + } +} + +pub trait HasWidth { + const MIN_WIDTH: usize; + /// Increment track width. + fn width_inc (&mut self); + /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. + fn width_dec (&mut self); +} + +pub trait HasClipsSize { + /// Size of clips area. + fn clips_size (&self) -> &Sizer; + /// Draw content annotated with value of [Self::clips_size]. + fn with_clips_size (&self, show: bool, content: impl Draw) -> impl Draw { + let size = self.clips_size(); + above( + when(show, fg(Green, east!(size.w() as usize, "x", size.h() as usize)).align_se()), + size.of(content.align_c().full_wh()) + ) + } +} + +tui_keys!(self: App, input { + #[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); + self.input_cmds(input) +}); + +impl App { + pub fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> { + if let Some(bindings) = self.input_cmds_find(input) { + return self.input_cmds_exec(bindings) + } + Ok(()) + } + /// ``` + /// let app = tek::App::default(); + /// tek::Config::load(&app.config, stringify!( + /// (axis Y @down @up) + /// (mode Menu (axis Y prev next) (bind (@enter confirm))) + /// )).unwrap(); + /// panic!("{app:#?}"); + /// assert!(app.input_cmds_find(&"left".into()).is_none()); + /// assert!(app.input_cmds_find(&"enter".into()).is_some()); + /// assert!(app.input_cmds_find(&"up".into()).is_some()); + /// assert!(app.input_cmds_find(&"down".into()).is_some()); + /// ``` + pub fn input_cmds_find (&self, input: &TuiEvent) -> Option>]>> { + for name in self.modes.read().iter().rev() { + let mode = self.config.get_mode(name).as_ref().map(Arc::clone); + if let Some(mode) = mode { + for (ctrl, (dec, inc)) in mode.axis.iter() { + panic!("{ctrl:?} {dec:?} {inc:?}"); + } + for id in mode.keys.iter() { + if let Some(event_map) = self.config.binds.find(id) + && let Some(bindings) = event_map.query(input) { + return Some(bindings.into()) + } + } + } + } + None + } + + pub fn input_cmds_exec (&mut self, bindings: Arc<[Binding>]>) -> Usually<()> { + for binding in bindings.iter() { + for src in binding.commands.iter() { + if let Some(command) = Namespace::::namespace(self, &src)? { + self.cmd_dispatch(command)?; + } + } + } + Ok(()) + } + + pub fn cmd_dispatch (&mut self, command: AppCmd) -> Usually<()> { + match command.clone().dispatch(self) { + Ok(undo) => { + self.history.push((command, undo)); + Ok(()) + }, + Err(err) => { + self.history.push((command, None)); + Err(err) + } + } + } +} + +impl_from!(AppCmd: |x: BrowseCmd| AppCmd::Browse { command: x }); +impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x }); +impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x }); +impl_from!(AppCmd: |x: ProjectCmd| AppCmd::Project { command: x }); +impl_from!(AppCmd: |x: SceneCmd| AppCmd::Scene { command: x }); +impl_from!(AppCmd: |x: ScenesCmd| AppCmd::Scenes { command: x }); +impl_from!(AppCmd: |x: TrackCmd| AppCmd::Track { command: x }); +impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x }); + +#[tek_proc::commands(AppCmd)] impl App { + /// Exit the application. + #[command(Exit = "exit")] pub fn cmd_exit (&mut self) -> Perhaps { + self.exit.exit(); + Ok(None) + } + /// Switch to another set of contextual modes. + #[command(Mode = "mode")] pub fn cmd_mode (&mut self, modes: Vec>) -> Perhaps { + *self.modes_mut() = modes.into(); + Ok(None) + } + /// Enter a contextual mode. + #[command(Enter = "enter")] pub fn enter_mode (&mut self, mode: Arc) -> Perhaps { + let mut modes = self.modes_mut(); + Ok(if modes.last() != Some(&mode) { + modes.push(mode); + Some(AppCmd::Leave) + } else { + None + }) + } + /// Leave a contextual mode. + #[command(Leave = "leave")] fn leave_mode (&mut self) -> Perhaps { + let mut modes = self.modes_mut(); + if modes.len() > 1 { + let mode = modes.pop(); + Ok(mode.map(|mode|AppCmd::Enter { mode })) + } else { + Ok(None) + } + } + /// Increment the given data axis. + #[command(Inc = "inc")] pub fn inc (&mut self, axis: Arc) -> Perhaps { + panic!("inc {axis:?}"); + //Ok(match (&self.dialog, axis) { + //(Dialog::None, _) => todo!(), + //(Dialog::Menu(_, _), ControlAxis::Y) => { + //AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?; + //None + //}, + //_ => todo!() + //}) + } + /// Decrement the given data axis. + #[command(Dec = "dec")] pub fn dec (&mut self, axis: Arc) -> Perhaps { + panic!("dec {axis:?}"); + //Ok(match (&self.dialog, axis) { + //(Dialog::None, _) => None, + //(Dialog::Menu(_, _), ControlAxis::Y) => { + //AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; + //None + //}, + //_ => todo!() + //}) + } + /// Cancel current selection. + #[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps { + todo!("cancel") + } + /// Confirm current selection. + #[command(Confirm = "confirm")] pub fn cmd_confirm (&mut self) -> Perhaps { + Ok(match self.dialog.clone() { + Dialog::Menu(index, items) => { + for command in items[index].1.iter() { + App::cmd_dispatch(self, command.clone())?; + } + None + } + _ => todo!(), + }) + } + /// Swap currently active modal dialog. + /// + /// ``` + /// let _ = tek::App::default().cmd_dialog(tek::Dialog::welcome()); + /// ``` + #[command(Dialog = "dialog")] pub fn cmd_dialog (&mut self, dialog: Dialog) -> Perhaps { + let mut dialog = dialog.clone(); + std::mem::swap(&mut self.dialog, &mut dialog); + Ok(Some(AppCmd::Dialog { dialog })) + } + /// Command the file browser. + #[command(Browse = "browse")] pub fn cmd_browse (&mut self, command: BrowseCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + /// Commands that alter the current session. + #[command(Project = "session")] pub fn cmd_session (&mut self, command: ProjectCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + /// Commands pertaining to MIDI clips. + #[command(MidiClip = "clip")] pub fn cmd_midi_clip (&mut self, command: MidiClipCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + /// Commands pertaining to the MIDI clip editor. + #[command(MidiEdit = "edit")] pub fn cmd_midi_edit (&mut self, command: MidiEditCmd) -> Perhaps { + if matches!(command, MidiEditCmd::Toggle) { + if self.is_editing() { + self.leave_mode()?; + } else { + self.enter_mode("Edit".into())?; + } + } + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + #[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps { + Ok(command.dispatch(self)?.map(Into::into)) + } + //#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps { + //Ok(command.dispatch(self)?.map(Into::into)) + //} +} + +impl ProjectControl for T {} + +#[tek_proc::commands(ProjectCmd)] pub trait ProjectControl: HasProject { + #[command(LoadLast = "load-last")] fn load_last (&mut self) -> Perhaps { + todo!() + } + #[command(Load = "load")] fn load (&mut self) -> Perhaps { + todo!() + } +} + +impl ClockControl for T where T: HasClock + + Namespace + Namespace + Namespace> +{} +#[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock + + Namespace + Namespace + Namespace> +{ + #[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps { + self.clock().playhead.update_from_usec(usec); + Ok(None) + } + #[command(SeekSample = "sample")] fn seek_sample (&mut self, sample: f64) -> Perhaps { + self.clock().playhead.update_from_sample(sample); + Ok(None) + } + #[command(SeekPulse = "pulse")] fn seek_pulse (&mut self, pulse: f64) -> Perhaps { + self.clock().playhead.update_from_pulse(pulse); + Ok(None) + } + #[command(SetBpm = "bpm")] fn set_bpm (&mut self, bpm: f64) -> Perhaps { + Ok(Some(ClockCmd::SetBpm { + bpm: self.clock().timebase().bpm.set(bpm) + })) + } + #[command(SetQuant = "quant")] fn set_quant (&mut self, quant: f64) -> Perhaps { + Ok(Some(ClockCmd::SetQuant { + quant: self.clock().quant.set(quant) + })) + } + #[command(SetSync = "sync")] fn set_sync (&mut self, sync: f64) -> Perhaps { + Ok(Some(ClockCmd::SetSync { + sync: self.clock().sync.set(sync) + })) + } + #[command(Play = "play")] fn play (&mut self, position: Option) -> Perhaps { + self.clock().play_from(position)?; + Ok(None) /* TODO Some(Pause(previousPosition)) */ + } + #[command(Pause = "pause")] fn pause (&mut self, position: Option) -> Perhaps { + self.clock().pause_at(position)?; + Ok(None) + } + #[command(TogglePlayback = "toggle")] fn toggle (&mut self, position: u32) -> Perhaps { + Ok(if self.clock().is_rolling() { + self.clock().pause_at(Some(position))?; + None + } else { + self.clock().play_from(Some(position))?; + None + }) + } +} + +impl TracksControl for T where T: HasTracks + + HasScenes + + HasSelection + + HasJack<'static> + + Namespace +{} + +#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks + + HasScenes + + HasSelection + + HasJack<'static> + + Namespace +{ + /// Stop all playing clips + #[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps { + for track in self.tracks_mut().iter_mut() { + track.sequencer.enqueue_next(None); + } + Ok(None) + } + /// Launch multiple clips + #[command(Launch = "launch")] fn tracks_launch (&mut self, clips: ClipPool) + -> Perhaps + { + if clips.len() == 0 { + for track in self.tracks_mut().iter_mut() { + track.sequencer.enqueue_next(None); + } + return Ok(None) + } + for (clip, track) in clips.0.read().iter().zip(self.tracks_mut()) { + track.sequencer.enqueue_next(clip.read().as_ref()); + } + Ok(None) + } + #[command(Add = "add")] fn tracks_add (&mut self) -> Perhaps + where Self: HasScenes + HasJack<'static> + Sized + { + let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?; + Ok(None) + } + #[command(Select = "select")] fn track_select (&mut self) -> Perhaps { + let tracks = self.tracks().len(); + let select = self.selection_mut().select_track(tracks); + *self.selection_mut() = select; + Ok(None) + } + #[command(Next = "next")] fn track_select_next (&mut self) -> Perhaps { + let tracks = self.tracks().len(); + let select = self.selection().select_track_next(tracks); + *self.selection_mut() = select; + Ok(None) + } + #[command(Prev = "prev")] fn track_select_prev (&mut self) -> Perhaps { + let select = self.selection_mut().select_track_prev(); + *self.selection_mut() = select; + Ok(None) + } + + /// Run audio callbacks for every track and every device + fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control + where Self: HasJack<'static> + { + for track in self.tracks_mut().iter_mut() { + if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { + return Control::Quit + } + for device in track.devices.iter_mut() { + if Control::Quit == DeviceAudio(device).process(client, scope) { + return Control::Quit + } + } + } + Control::Continue + } + + /// Add multiple tracks + fn tracks_add_many ( + &mut self, + count: usize, + width: Option, + mins: Arc<[Connect]>, + mouts: Arc<[Connect]>, + ) -> Usually<()> + where Self: HasJack<'static> + HasScenes + { + let track_color_1 = ItemColor::random(); + let track_color_2 = ItemColor::random(); + for i in 0..count { + let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); + let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1; + if let Some(width) = width { + track.width = width; + } + } + Ok(()) + } + + /// Add a track + fn tracks_add_one ( + &mut self, + name: Option>, + color: Option, + mins: Arc<[Connect]>, + mouts: Arc<[Connect]>, + ) -> Usually<(usize, &mut Track)> + where Self: HasJack<'static> + HasScenes + { + let name: Arc = name.map_or_else( + ||format!("trk{}", self.tracks_last()).into(), + |x|x.to_string().into() + ); + *self.tracks_last_mut() += 1; + let track = Track::new_added(self.jack(), Some(self.clock()), &name, color, None, mins, mouts)?; + self.tracks_mut().push(track); + let len = self.tracks().len(); + let index = len - 1; + for scene in self.scenes_mut().iter_mut() { + while scene.clips.len() < len { + scene.clips.append(None); + } + } + Ok((index, &mut self.tracks_mut()[index])) + } + + fn track_longest_name (&self) -> usize { + self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) + } +} + +impl + + Namespace> + + Namespace + + Namespace> +> TrackControl for T {} + +#[tek_proc::commands(TrackCmd)] pub trait TrackControl: HasTrack + + Namespace + + Namespace> + + Namespace + + Namespace> +{ + #[command(Stop = "track/stop")] + fn track_stop (&mut self) -> Perhaps { + self.track_mut().map(|track|track.sequencer.enqueue_next(None)); + Ok(None) + } + #[command(SetMute = "track/mute")] + fn track_set_mute (&mut self, _mute: Option) -> Perhaps { + todo!() + } + #[command(SetSolo = "track/solo")] + fn track_set_solo (&mut self, _solo: Option) -> Perhaps { + todo!() + } + #[command(SetSize = "track/size")] + fn track_set_size (&mut self, _size: usize) -> Perhaps { + todo!() + } + #[command(SetZoom = "track/zoom")] + fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps { + todo!() + } + #[command(SetName = "track/name")] + fn track_set_name (&mut self, name: Arc) -> Perhaps { + self.track_mut() + .map(|track|swap_value(&mut track.name, &name, |name|TrackCmd::SetName { name })) + .transpose() + .map(Option::flatten) + } + #[command(SetColor = "track/color")] + fn track_set_color (&mut self, color: ItemTheme) -> Perhaps { + self.track_mut() + .map(|track|swap_value(&mut track.color, &color, |color|TrackCmd::SetColor { color })) + .transpose() + .map(Option::flatten) + } + #[command(SetRec = "track/rec")] + fn track_set_rec (&mut self, rec: Option) -> Perhaps { + self.track_mut() + .map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCmd::SetRec { rec })) + .transpose() + .map(Option::flatten) + } + #[command(SetMon = "track/mon")] + fn track_set_mon (&mut self, mon: Option) -> Perhaps { + self.track_mut() + .map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCmd::SetMon { mon })) + .transpose() + .map(Option::flatten) + } +} + +impl +> ScenesControl for T {} + +#[tek_proc::commands(ScenesCmd)] pub trait ScenesControl: HasScenes + + HasTracks + + HasTrackScroll + + HasSelection + + HasJack<'static> +{ + #[command(Add = "add")] fn scenes_add (&mut self) -> Perhaps where Self: Sized { + let (_index, _) = self.scenes_add_one(None, None)?; + Ok(None) + } + #[command(Select = "select")] fn scene_select (&mut self) -> Perhaps { + let scenes = self.scenes().len(); + let select = self.selection_mut().select_scene(scenes); + *self.selection_mut() = select; + Ok(None) + } + #[command(Next = "next")] fn scene_select_next (&mut self) -> Perhaps { + let scenes = self.scenes().len(); + let select = self.selection().select_scene_next(scenes); + *self.selection_mut() = select; + Ok(None) + } + #[command(Prev = "prev")] fn scene_select_prev (&mut self) -> Perhaps { + let select = self.selection_mut().select_scene_prev(); + *self.selection_mut() = select; + Ok(None) + } + /// Generate the default name for a new scene + fn scenes_default_name (&self) -> Arc { + format!("s{:3>}", self.scenes().len() + 1).into() + } + fn scenes_longest_name (&self) -> usize { + self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) + } + /// Add multiple scenes + fn scenes_add_many (&mut self, n: usize) + -> Usually<()> where Self: HasTracks + { + let scene_color_1 = ItemColor::random(); + let scene_color_2 = ItemColor::random(); + for i in 0..n { + let _ = self.scenes_add_one(None, Some( + scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() + ))?; + } + Ok(()) + } + /// Add a scene + fn scenes_add_one (&mut self, name: Option>, color: Option) + -> Usually<(usize, &mut Scene)> + where + Self: HasTracks + { + let scene = Scene { + name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()), + clips: Pool::from(vec![None;self.tracks().len()]), + color: color.unwrap_or_else(ItemTheme::random), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok((index, &mut self.scenes_mut()[index])) + } +} + +impl + Namespace> + Namespace +> SceneControl for T {} + +#[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene + + Namespace + Namespace> + Namespace +{ + #[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps { + todo!() + } + #[command(SetZoom = "zoom")] fn scene_set_zoom (&mut self, _size: usize) -> Perhaps { + todo!() + } + #[command(SetName = "name")] fn scene_set_name (&mut self, name: Arc) -> Perhaps { + Ok(self.scene_mut().map(|scene|swap_value( + &mut scene.name, + &name, + |name|SceneCmd::SetName { name } + )).transpose()?.flatten()) + } + #[command(SetColor = "color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps { + Ok(self.scene_mut().map(|scene|swap_value( + &mut scene.color, + &color, + |color|SceneCmd::SetColor { color } + )).transpose()?.flatten()) + } +} + +#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler + + Namespace +{ + #[command(RecToggle = "rec-toggle")] fn record_toggle (&mut self, slot: usize) -> Perhaps + where Self: Sized + { + let sampler = self.sampler_mut(); + let recording = sampler.recording.as_ref().map(|x|x.0); + let _ = SamplerCmd::RecFinish.dispatch(self)?; + // autoslice: continue recording at next slot + if recording != Some(slot) { + SamplerCmd::RecBegin { slot }.dispatch(self) + } else { + Ok(None) + } + } + #[command(RecBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps { + let sampler = self.sampler_mut(); + sampler.recording = Some(( + slot, + Some(Arc::new(RwLock::new(Sample::new( + "Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()] + )))) + )); + Ok(None) + } + #[command(RecFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps { + let sampler = self.sampler_mut(); + let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ + std::mem::swap(sample, &mut sampler.samples.0[*index]); + sample + }); // TODO: undo + Ok(None) + } + #[command(RecCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps { + self.sampler_mut().recording = None; + Ok(None) + } + #[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps { + let sampler = self.sampler_mut(); + if let Some(ref sample) = sampler.samples.0[slot] { + sampler.voices.write().push(Sample::play(sample, 0, &u7::from(128))); + } + Ok(None) + } + #[command(StopSample = "sample-stop")] fn sample_stop (&mut self, slot: usize) -> Perhaps { + todo!(); + //Ok(None) + } +} + +impl + + Namespace + + Namespace + + Namespace + + Namespace> + + Namespace +> MidiEditControl for T {} + +#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasClip + + HasEditor + + Namespace + + Namespace + + Namespace + + Namespace + + Namespace> + + Namespace +{ + #[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps { + if self.is_editing() { + *self.editor_mut() = None; + } else if let Some(clip) = self.clip() { + let editor = MidiEditor::new(); + *editor.mode.clip.0.write() = clip.clone(); + editor.redraw(); + *self.editor_mut() = Some(editor); + } else { + return Ok(None) + } + Ok(Some(MidiEditCmd::Toggle)) + } + #[command(Show = "show")] fn edit_show (&mut self, clip: ClipSlot) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.mode.clip.0 = clip.0; + editor.redraw(); + None + }).flatten()) + } + #[command(DeleteNote = "delete")] fn edit_note_delete (&mut self) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.redraw(); + todo!() + }).flatten()) + } + #[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.put_note(advance); + editor.redraw(); + None + }).flatten()) + } + #[command(SetNotePos = "note-pos")] fn edit_note_set_pos (&mut self, pos: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_note_pos((pos).min(127)); + editor.redraw(); + None + }).flatten()) + } + #[command(SetNoteLen = "note-len")] fn edit_note_set_len (&mut self, len: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_note_len(len); + editor.redraw(); + None + }).flatten()) + } + #[command(SetNoteScroll = "note-scroll")] fn edit_note_set_scroll (&mut self, scroll: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_note_lo((scroll).min(127)); + editor.redraw(); + None + }).flatten()) + } + #[command(SetTimePos = "time-pos")] fn edit_time_set_pos (&mut self, pos: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_time_pos(pos); + editor.redraw(); + None + }).flatten()) + } + #[command(SetTimeScroll = "time-scroll")] fn edit_time_set_scroll (&mut self, scroll: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_time_start(scroll); + editor.redraw(); + None + }).flatten()) + } + #[command(SetTimeZoom = "time-zoom")] fn edit_time_set_zoom (&mut self, zoom: usize) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_time_zoom(zoom); + editor.redraw(); + None + }).flatten()) + } + #[command(SetTimeLock = "time-lock")] fn edit_time_set_lock (&mut self, lock: bool) -> Perhaps { + Ok(self.editor_mut().as_mut().map(|editor|{ + editor.set_time_lock(lock); + editor.redraw(); + None + }).flatten()) + } + // TODO: 1-9 seek markers that by default start every 8th of the clip +} + +impl MidiClipControl for T where T: HasClip + + Namespace> + Namespace> {} +#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip + + Namespace> + Namespace> +{ + /// Create a clip at the current position if it does not already exist. + #[command(Upsert = "upsert")] fn clip_upsert ( + &mut self + ) -> Perhaps { + Ok(self.clip_mut().map(|mut clip|if clip.is_none() { + *clip = Some(Arc::new(RwLock::new(MidiClip { + ppq: 24, + length: 96, + color: ItemTheme::random(), + ..Default::default() + }))); + None // TODO undo + } else { + None + }).flatten()) + } + #[command(SetColor = "color")] fn clip_set_color ( + &mut self, color: Option + ) -> Perhaps { + Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetColor { + color: Some(clip.set_color(color)) + })) + } + /// Toggle looping for the active clip + #[command(SetLoop = "loop")] fn clip_toggle_loop ( + &mut self, looping: Option + ) -> Perhaps { + Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop { + looping: Some(clip.toggle_loop(looping)) + })) + } +} + +impl BrowseControl for T where T: + Namespace + Namespace + Namespace> {} +#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl: + Namespace + Namespace + Namespace> +{ + /// Toggle visibility of browser + #[command(Show = "show")] fn show (&mut self) -> Perhaps { + todo!() + } + /// Set current directory + #[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps { + todo!() + } + /// Set current filter + #[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc) -> Perhaps { + todo!() + } + /// Set selected item + #[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps { + todo!() + } +} + +#[tek_proc::commands(AxisCmd)] impl App { +} + +#[tek_proc::command(AudioInput)] +#[tek_proc::keyword(AudioInput)] +#[derive(Debug)] +pub enum AudioInputCmd { + Close, + Connect(Arc), +} + +#[tek_proc::command(AudioOutput)] +#[tek_proc::keyword(AudioOutput)] +#[derive(Debug)] +pub enum AudioOutputCmd { + Close, + Connect(Arc), +} + +#[tek_proc::command(MidiInput)] +#[tek_proc::keyword(MidiInput)] +#[derive(Debug)] +pub enum MidiInputCmd { + Close, + Connect(Arc), +} + +#[tek_proc::command(MidiOutput)] +#[tek_proc::keyword(MidiOutput)] +#[derive(Debug)] +pub enum MidiOutputCmd { + Close, + Connect(Arc), +} + +//impl + //+ Namespace + //+ Namespace> + //+ Namespace + //+ Namespace + //+ Namespace + //+ Namespace +//> PoolControl for T {} + +//#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool + //+ Namespace + //+ Namespace + //+ Namespace> + //+ Namespace + //+ Namespace + //+ Namespace + //+ Namespace +//{ + ///// Toggle visibility of pool + //#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps { + //self.pool_mut().visible = visible; + //Ok(Some(PoolCmd::Show { visible: !visible })) + //} + ///// Set selected clip + //#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps { + //self.pool_mut().set_clip_index(index); + //Ok(None) + //} + ///// Delete a clip from the pool + //#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps { + //let clip = self.pool_mut().remove(index).read().clone(); + //Ok(Some(PoolCmd::Add { index, clip })) + //} + ///// Switch places of two clips in pool + //#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps { + //self.pool_mut().swap(index, other); + //Ok(Some(PoolCmd::Swap { index, other })) + //} + ///// Add clip to pool + //#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps { + //let mut index = index; + //let clip = Arc::new(RwLock::new(clip.clone())); + //let mut clips = self.pool_mut().clips_mut(); + //if index >= clips.len() { + //index = clips.len(); + //clips.push(clip) + //} else { + //clips.insert(index, clip); + //} + //Ok(Some(PoolCmd::Delete { index })) + //} + ///// Set name of clip + //#[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc) -> Perhaps { + //Ok(self.pool_mut().clips.read().get(index).map(|clip|{ + //let old_name = clip.read().name.clone(); + //clip.write().name = name.clone(); + //PoolCmd::SetName { index, name: old_name } + //})) + //} + ///// Set length of clip + //#[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps { + //Ok(self.pool_mut().clips.read().get(index).map(|clip|{ + //let old_len = clip.read().length; + //clip.write().length = length; + //PoolCmd::SetLength { index, length: old_len } + //})) + //} + ///// Set color of clip + //#[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps { + //let mut color = ItemTheme::from(color); + //std::mem::swap(&mut color, &mut self.pool().clips.read()[index].write().color); + //Ok(Some(PoolCmd::SetColor { index, color: color.base })) + //} + ///// Enter crop mode + //#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps { + //let index = self.pool().clip_index(); + //let length = self.pool().clips.read()[index].read().length; + //*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar)); + //Ok(None) + //} + ///// Exit crop mode, discard + //#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps { + //if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() { + //*self.pool_mut().mode_mut() = None; + //} + //Ok(None) + //} + //#[command(CropSet = "crop/set")] fn crop_set (&mut self, _length: usize) -> Perhaps { + //if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) + //= self.pool_mut().mode_mut().clone() + //{ + //let old_length; + //{ + //let clip = self.pool().clips.read()[clip].clone();//.write(); + //old_length = Some(clip.read().length); + //clip.write().length = *length; + //} + //*self.pool_mut().mode_mut() = None; + //return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l })) + //} + //Ok(None) + //} + //#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps { + //if let Some(PoolMode::Length( + //_clip, ref mut _length, ref mut focus + //)) = self.pool_mut().mode_mut().clone() { + //focus.next() + //}; + //Ok(None) + //} + //#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps { + //if let Some(PoolMode::Length( + //_clip, ref mut _length, ref mut focus + //)) = self.pool_mut().mode_mut().clone() { + //focus.prev() + //}; + //Ok(None) + //} + //#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps { + //if let Some(PoolMode::Length( + //_clip, ref mut length, ref mut focus + //)) = self.pool_mut().mode_mut().clone() { + //match focus { + //ClipLengthFocus::Bar => { *length += 4 * PPQ }, + //ClipLengthFocus::Beat => { *length += PPQ }, + //ClipLengthFocus::Tick => { *length += 1 }, + //} + //} + //Ok(None) + //} + //#[command(CropDec = "crop/dec")] fn crop_dec (&mut self) -> Perhaps { + //if let Some(PoolMode::Length( + //_clip, ref mut length, ref mut focus + //)) = self.pool_mut().mode_mut().clone() { + //match focus { + //ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, + //ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, + //ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, + //} + //} + //Ok(None) + //} + ///// Enter rename mode + //#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps { + //let index = self.pool().clip_index(); + //let name = self.pool().clips.read()[index].read().name.clone(); + //*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name)); + //Ok(None) + //} + ///// Exit rename mode, discard + //#[command(RenameCancel = "rename/cancel")] fn rename_cancel (&mut self) -> Perhaps { + //if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { + //self.pool().clips.read()[clip].write().name = old_name.clone().into(); + //} + //Ok(None) + //} + ///// Exit rename mode, update name + //#[command(RenameConfirm = "rename/confirm")] fn rename_confirm (&mut self) -> Perhaps { + //Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { + //let old_name = old_name.clone(); + //*self.pool_mut().mode_mut() = None; + //Some(PoolCmd::RenameSet { value: old_name }) + //} else { + //None + //}) + //} + //#[command(RenameSet = "rename/set")] fn rename_set (&mut self, value: Arc) -> Perhaps { + //if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() { + //self.pool().clips.read()[clip].write().name = value.clone(); + //} + //Ok(None) + //} + /////// Import from file + ////#[command(Browse = "browse")] + ////fn browse (&mut self, command: BrowseCmd) -> Perhaps { + ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { + ////command.act(browse)?.map(|command|PoolCmd::Browse{command}) + ////} else { + ////None + ////}) + ////} + ///// Import clip to pool from file + //#[command(Import = "import")] + //fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps + //where Self: Sized + //{ + //let bytes = std::fs::read(&path)?; + //let smf = Smf::parse(bytes.as_slice())?; + //let mut t = 0u32; + //let mut events = vec![]; + //for track in smf.tracks.iter() { + //for event in track.iter() { + //t += event.delta.as_int(); + //if let TrackEventKind::Midi { channel, message } = event.kind { + //events.push((t, channel.as_int(), message)); + //} + //} + //} + //let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); + //for event in events.iter() { + //clip.notes[event.0 as usize].push(event.2); + //} + //Ok(PoolCmd::Add { index, clip }.dispatch(self)?) + //} + /////// Export to file + ////#[command(Export = "export")] + ////fn export (&mut self, command: BrowseCmd) -> Perhaps { + ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { + ////command.act(browse)?.map(|command|PoolCmd::Export{command}) + ////} else { + ////None + ////}) + ////} +//} //pub(crate) struct EntriesIterator<'a, S: Screen> { //pub browser: &'a Browse, diff --git a/src/tek_ctrl.rs b/src/tek_ctrl.rs deleted file mode 100644 index 0d2c9a99..00000000 --- a/src/tek_ctrl.rs +++ /dev/null @@ -1,1022 +0,0 @@ -use crate::*; - -tui_keys!(self: App, input { - #[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); - self.input_cmds(input) -}); - -impl App { - pub fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> { - if let Some(bindings) = self.input_cmds_find(input) { - return self.input_cmds_exec(bindings) - } - Ok(()) - } - /// ``` - /// let app = tek::App::default(); - /// tek::Config::load(&app.config, stringify!( - /// (axis Y @down @up) - /// (mode Menu (axis Y prev next) (bind (@enter confirm))) - /// )).unwrap(); - /// panic!("{app:#?}"); - /// assert!(app.input_cmds_find(&"left".into()).is_none()); - /// assert!(app.input_cmds_find(&"enter".into()).is_some()); - /// assert!(app.input_cmds_find(&"up".into()).is_some()); - /// assert!(app.input_cmds_find(&"down".into()).is_some()); - /// ``` - pub fn input_cmds_find (&self, input: &TuiEvent) -> Option>]>> { - for name in self.modes.read().iter().rev() { - let mode = self.config.get_mode(name).as_ref().map(Arc::clone); - if let Some(mode) = mode { - for (ctrl, (dec, inc)) in mode.axis.iter() { - panic!("{ctrl:?} {dec:?} {inc:?}"); - } - for id in mode.keys.iter() { - if let Some(event_map) = self.config.binds.find(id) - && let Some(bindings) = event_map.query(input) { - return Some(bindings.into()) - } - } - } - } - None - } - - pub fn input_cmds_exec (&mut self, bindings: Arc<[Binding>]>) -> Usually<()> { - for binding in bindings.iter() { - for src in binding.commands.iter() { - if let Some(command) = Namespace::::namespace(self, &src)? { - self.cmd_dispatch(command)?; - } - } - } - Ok(()) - } - - pub fn cmd_dispatch (&mut self, command: AppCmd) -> Usually<()> { - match command.clone().dispatch(self) { - Ok(undo) => { - self.history.push((command, undo)); - Ok(()) - }, - Err(err) => { - self.history.push((command, None)); - Err(err) - } - } - } -} - -impl_from!(AppCmd: |x: BrowseCmd| AppCmd::Browse { command: x }); -impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x }); -impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x }); -impl_from!(AppCmd: |x: ProjectCmd| AppCmd::Project { command: x }); -impl_from!(AppCmd: |x: SceneCmd| AppCmd::Scene { command: x }); -impl_from!(AppCmd: |x: ScenesCmd| AppCmd::Scenes { command: x }); -impl_from!(AppCmd: |x: TrackCmd| AppCmd::Track { command: x }); -impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x }); - -#[tek_proc::commands(AppCmd)] impl App { - /// Exit the application. - #[command(Exit = "exit")] pub fn cmd_exit (&mut self) -> Perhaps { - self.exit.exit(); - Ok(None) - } - /// Switch to another set of contextual modes. - #[command(Mode = "mode")] pub fn cmd_mode (&mut self, modes: Vec>) -> Perhaps { - *self.modes_mut() = modes.into(); - Ok(None) - } - /// Enter a contextual mode. - #[command(Enter = "enter")] pub fn enter_mode (&mut self, mode: Arc) -> Perhaps { - let mut modes = self.modes_mut(); - Ok(if modes.last() != Some(&mode) { - modes.push(mode); - Some(AppCmd::Leave) - } else { - None - }) - } - /// Leave a contextual mode. - #[command(Leave = "leave")] fn leave_mode (&mut self) -> Perhaps { - let mut modes = self.modes_mut(); - if modes.len() > 1 { - let mode = modes.pop(); - Ok(mode.map(|mode|AppCmd::Enter { mode })) - } else { - Ok(None) - } - } - /// Increment the given data axis. - #[command(Inc = "inc")] pub fn inc (&mut self, axis: Arc) -> Perhaps { - panic!("inc {axis:?}"); - //Ok(match (&self.dialog, axis) { - //(Dialog::None, _) => todo!(), - //(Dialog::Menu(_, _), ControlAxis::Y) => { - //AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?; - //None - //}, - //_ => todo!() - //}) - } - /// Decrement the given data axis. - #[command(Dec = "dec")] pub fn dec (&mut self, axis: Arc) -> Perhaps { - panic!("dec {axis:?}"); - //Ok(match (&self.dialog, axis) { - //(Dialog::None, _) => None, - //(Dialog::Menu(_, _), ControlAxis::Y) => { - //AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; - //None - //}, - //_ => todo!() - //}) - } - /// Cancel current selection. - #[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps { - todo!("cancel") - } - /// Confirm current selection. - #[command(Confirm = "confirm")] pub fn cmd_confirm (&mut self) -> Perhaps { - Ok(match self.dialog.clone() { - Dialog::Menu(index, items) => { - for command in items[index].1.iter() { - App::cmd_dispatch(self, command.clone())?; - } - None - } - _ => todo!(), - }) - } - /// Swap currently active modal dialog. - /// - /// ``` - /// let _ = tek::App::default().cmd_dialog(tek::Dialog::welcome()); - /// ``` - #[command(Dialog = "dialog")] pub fn cmd_dialog (&mut self, dialog: Dialog) -> Perhaps { - let mut dialog = dialog.clone(); - std::mem::swap(&mut self.dialog, &mut dialog); - Ok(Some(AppCmd::Dialog { dialog })) - } - /// Command the file browser. - #[command(Browse = "browse")] pub fn cmd_browse (&mut self, command: BrowseCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - /// Commands that alter the current session. - #[command(Project = "session")] pub fn cmd_session (&mut self, command: ProjectCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - /// Commands pertaining to MIDI clips. - #[command(MidiClip = "clip")] pub fn cmd_midi_clip (&mut self, command: MidiClipCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - /// Commands pertaining to the MIDI clip editor. - #[command(MidiEdit = "edit")] pub fn cmd_midi_edit (&mut self, command: MidiEditCmd) -> Perhaps { - if matches!(command, MidiEditCmd::Toggle) { - if self.is_editing() { - self.leave_mode()?; - } else { - self.enter_mode("Edit".into())?; - } - } - Ok(command.dispatch(self)?.map(Into::into)) - } - #[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - #[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - #[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - #[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) - } - //#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps { - //Ok(command.dispatch(self)?.map(Into::into)) - //} -} - -impl ProjectControl for T {} - -#[tek_proc::commands(ProjectCmd)] pub trait ProjectControl: HasProject { - #[command(LoadLast = "load-last")] fn load_last (&mut self) -> Perhaps { - todo!() - } - #[command(Load = "load")] fn load (&mut self) -> Perhaps { - todo!() - } -} - -impl ClockControl for T where T: HasClock - + Namespace + Namespace + Namespace> -{} -#[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock - + Namespace + Namespace + Namespace> -{ - #[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps { - self.clock().playhead.update_from_usec(usec); - Ok(None) - } - #[command(SeekSample = "sample")] fn seek_sample (&mut self, sample: f64) -> Perhaps { - self.clock().playhead.update_from_sample(sample); - Ok(None) - } - #[command(SeekPulse = "pulse")] fn seek_pulse (&mut self, pulse: f64) -> Perhaps { - self.clock().playhead.update_from_pulse(pulse); - Ok(None) - } - #[command(SetBpm = "bpm")] fn set_bpm (&mut self, bpm: f64) -> Perhaps { - Ok(Some(ClockCmd::SetBpm { - bpm: self.clock().timebase().bpm.set(bpm) - })) - } - #[command(SetQuant = "quant")] fn set_quant (&mut self, quant: f64) -> Perhaps { - Ok(Some(ClockCmd::SetQuant { - quant: self.clock().quant.set(quant) - })) - } - #[command(SetSync = "sync")] fn set_sync (&mut self, sync: f64) -> Perhaps { - Ok(Some(ClockCmd::SetSync { - sync: self.clock().sync.set(sync) - })) - } - #[command(Play = "play")] fn play (&mut self, position: Option) -> Perhaps { - self.clock().play_from(position)?; - Ok(None) /* TODO Some(Pause(previousPosition)) */ - } - #[command(Pause = "pause")] fn pause (&mut self, position: Option) -> Perhaps { - self.clock().pause_at(position)?; - Ok(None) - } - #[command(TogglePlayback = "toggle")] fn toggle (&mut self, position: u32) -> Perhaps { - Ok(if self.clock().is_rolling() { - self.clock().pause_at(Some(position))?; - None - } else { - self.clock().play_from(Some(position))?; - None - }) - } -} - -impl TracksControl for T where T: HasTracks - + HasScenes - + HasSelection - + HasJack<'static> - + Namespace -{} - -#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks - + HasScenes - + HasSelection - + HasJack<'static> - + Namespace -{ - /// Stop all playing clips - #[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps { - for track in self.tracks_mut().iter_mut() { - track.sequencer.enqueue_next(None); - } - Ok(None) - } - /// Launch multiple clips - #[command(Launch = "launch")] fn tracks_launch (&mut self, clips: ClipPool) - -> Perhaps - { - if clips.len() == 0 { - for track in self.tracks_mut().iter_mut() { - track.sequencer.enqueue_next(None); - } - return Ok(None) - } - for (clip, track) in clips.0.read().iter().zip(self.tracks_mut()) { - track.sequencer.enqueue_next(clip.read().as_ref()); - } - Ok(None) - } - #[command(Add = "add")] fn tracks_add (&mut self) -> Perhaps - where Self: HasScenes + HasJack<'static> + Sized - { - let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?; - Ok(None) - } - #[command(Select = "select")] fn track_select (&mut self) -> Perhaps { - let tracks = self.tracks().len(); - let select = self.selection_mut().select_track(tracks); - *self.selection_mut() = select; - Ok(None) - } - #[command(Next = "next")] fn track_select_next (&mut self) -> Perhaps { - let tracks = self.tracks().len(); - let select = self.selection().select_track_next(tracks); - *self.selection_mut() = select; - Ok(None) - } - #[command(Prev = "prev")] fn track_select_prev (&mut self) -> Perhaps { - let select = self.selection_mut().select_track_prev(); - *self.selection_mut() = select; - Ok(None) - } - - /// Run audio callbacks for every track and every device - fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control - where Self: HasJack<'static> - { - for track in self.tracks_mut().iter_mut() { - if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { - return Control::Quit - } - for device in track.devices.iter_mut() { - if Control::Quit == DeviceAudio(device).process(client, scope) { - return Control::Quit - } - } - } - Control::Continue - } - - /// Add multiple tracks - fn tracks_add_many ( - &mut self, - count: usize, - width: Option, - mins: Arc<[Connect]>, - mouts: Arc<[Connect]>, - ) -> Usually<()> - where Self: HasJack<'static> + HasScenes - { - let track_color_1 = ItemColor::random(); - let track_color_2 = ItemColor::random(); - for i in 0..count { - let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); - let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1; - if let Some(width) = width { - track.width = width; - } - } - Ok(()) - } - - /// Add a track - fn tracks_add_one ( - &mut self, - name: Option>, - color: Option, - mins: Arc<[Connect]>, - mouts: Arc<[Connect]>, - ) -> Usually<(usize, &mut Track)> - where Self: HasJack<'static> + HasScenes - { - let name: Arc = name.map_or_else( - ||format!("trk{}", self.tracks_last()).into(), - |x|x.to_string().into() - ); - *self.tracks_last_mut() += 1; - let track = Track::new_added(self.jack(), Some(self.clock()), &name, color, None, mins, mouts)?; - self.tracks_mut().push(track); - let len = self.tracks().len(); - let index = len - 1; - for scene in self.scenes_mut().iter_mut() { - while scene.clips.len() < len { - scene.clips.append(None); - } - } - Ok((index, &mut self.tracks_mut()[index])) - } - - fn track_longest_name (&self) -> usize { - self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) - } -} - -impl - + Namespace> - + Namespace - + Namespace> -> TrackControl for T {} - -#[tek_proc::commands(TrackCmd)] pub trait TrackControl: HasTrack - + Namespace - + Namespace> - + Namespace - + Namespace> -{ - #[command(Stop = "track/stop")] - fn track_stop (&mut self) -> Perhaps { - self.track_mut().map(|track|track.sequencer.enqueue_next(None)); - Ok(None) - } - #[command(SetMute = "track/mute")] - fn track_set_mute (&mut self, _mute: Option) -> Perhaps { - todo!() - } - #[command(SetSolo = "track/solo")] - fn track_set_solo (&mut self, _solo: Option) -> Perhaps { - todo!() - } - #[command(SetSize = "track/size")] - fn track_set_size (&mut self, _size: usize) -> Perhaps { - todo!() - } - #[command(SetZoom = "track/zoom")] - fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps { - todo!() - } - #[command(SetName = "track/name")] - fn track_set_name (&mut self, name: Arc) -> Perhaps { - self.track_mut() - .map(|track|swap_value(&mut track.name, &name, |name|TrackCmd::SetName { name })) - .transpose() - .map(Option::flatten) - } - #[command(SetColor = "track/color")] - fn track_set_color (&mut self, color: ItemTheme) -> Perhaps { - self.track_mut() - .map(|track|swap_value(&mut track.color, &color, |color|TrackCmd::SetColor { color })) - .transpose() - .map(Option::flatten) - } - #[command(SetRec = "track/rec")] - fn track_set_rec (&mut self, rec: Option) -> Perhaps { - self.track_mut() - .map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCmd::SetRec { rec })) - .transpose() - .map(Option::flatten) - } - #[command(SetMon = "track/mon")] - fn track_set_mon (&mut self, mon: Option) -> Perhaps { - self.track_mut() - .map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCmd::SetMon { mon })) - .transpose() - .map(Option::flatten) - } -} - -impl -> ScenesControl for T {} - -#[tek_proc::commands(ScenesCmd)] pub trait ScenesControl: HasScenes - + HasTracks - + HasTrackScroll - + HasSelection - + HasJack<'static> -{ - #[command(Add = "add")] fn scenes_add (&mut self) -> Perhaps where Self: Sized { - let (_index, _) = self.scenes_add_one(None, None)?; - Ok(None) - } - #[command(Select = "select")] fn scene_select (&mut self) -> Perhaps { - let scenes = self.scenes().len(); - let select = self.selection_mut().select_scene(scenes); - *self.selection_mut() = select; - Ok(None) - } - #[command(Next = "next")] fn scene_select_next (&mut self) -> Perhaps { - let scenes = self.scenes().len(); - let select = self.selection().select_scene_next(scenes); - *self.selection_mut() = select; - Ok(None) - } - #[command(Prev = "prev")] fn scene_select_prev (&mut self) -> Perhaps { - let select = self.selection_mut().select_scene_prev(); - *self.selection_mut() = select; - Ok(None) - } - /// Generate the default name for a new scene - fn scenes_default_name (&self) -> Arc { - format!("s{:3>}", self.scenes().len() + 1).into() - } - fn scenes_longest_name (&self) -> usize { - self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) - } - /// Add multiple scenes - fn scenes_add_many (&mut self, n: usize) - -> Usually<()> where Self: HasTracks - { - let scene_color_1 = ItemColor::random(); - let scene_color_2 = ItemColor::random(); - for i in 0..n { - let _ = self.scenes_add_one(None, Some( - scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() - ))?; - } - Ok(()) - } - /// Add a scene - fn scenes_add_one (&mut self, name: Option>, color: Option) - -> Usually<(usize, &mut Scene)> - where - Self: HasTracks - { - let scene = Scene { - name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()), - clips: Pool::from(vec![None;self.tracks().len()]), - color: color.unwrap_or_else(ItemTheme::random), - }; - self.scenes_mut().push(scene); - let index = self.scenes().len() - 1; - Ok((index, &mut self.scenes_mut()[index])) - } -} - -impl + Namespace> + Namespace -> SceneControl for T {} - -#[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene - + Namespace + Namespace> + Namespace -{ - #[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps { - todo!() - } - #[command(SetZoom = "zoom")] fn scene_set_zoom (&mut self, _size: usize) -> Perhaps { - todo!() - } - #[command(SetName = "name")] fn scene_set_name (&mut self, name: Arc) -> Perhaps { - Ok(self.scene_mut().map(|scene|swap_value( - &mut scene.name, - &name, - |name|SceneCmd::SetName { name } - )).transpose()?.flatten()) - } - #[command(SetColor = "color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps { - Ok(self.scene_mut().map(|scene|swap_value( - &mut scene.color, - &color, - |color|SceneCmd::SetColor { color } - )).transpose()?.flatten()) - } -} - -#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler - + Namespace -{ - #[command(RecToggle = "rec-toggle")] fn record_toggle (&mut self, slot: usize) -> Perhaps - where Self: Sized - { - let sampler = self.sampler_mut(); - let recording = sampler.recording.as_ref().map(|x|x.0); - let _ = SamplerCmd::RecFinish.dispatch(self)?; - // autoslice: continue recording at next slot - if recording != Some(slot) { - SamplerCmd::RecBegin { slot }.dispatch(self) - } else { - Ok(None) - } - } - #[command(RecBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps { - let sampler = self.sampler_mut(); - sampler.recording = Some(( - slot, - Some(Arc::new(RwLock::new(Sample::new( - "Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()] - )))) - )); - Ok(None) - } - #[command(RecFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps { - let sampler = self.sampler_mut(); - let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ - std::mem::swap(sample, &mut sampler.samples.0[*index]); - sample - }); // TODO: undo - Ok(None) - } - #[command(RecCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps { - self.sampler_mut().recording = None; - Ok(None) - } - #[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps { - let sampler = self.sampler_mut(); - if let Some(ref sample) = sampler.samples.0[slot] { - sampler.voices.write().push(Sample::play(sample, 0, &u7::from(128))); - } - Ok(None) - } - #[command(StopSample = "sample-stop")] fn sample_stop (&mut self, slot: usize) -> Perhaps { - todo!(); - //Ok(None) - } -} - -impl - + Namespace - + Namespace - + Namespace - + Namespace> - + Namespace -> MidiEditControl for T {} - -#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasClip - + HasEditor - + Namespace - + Namespace - + Namespace - + Namespace - + Namespace> - + Namespace -{ - #[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps { - if self.is_editing() { - *self.editor_mut() = None; - } else if let Some(clip) = self.clip() { - let editor = MidiEditor::new(); - *editor.mode.clip.0.write() = clip.clone(); - editor.redraw(); - *self.editor_mut() = Some(editor); - } else { - return Ok(None) - } - Ok(Some(MidiEditCmd::Toggle)) - } - #[command(Show = "show")] fn edit_show (&mut self, clip: ClipSlot) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.mode.clip.0 = clip.0; - editor.redraw(); - None - }).flatten()) - } - #[command(DeleteNote = "delete")] fn edit_note_delete (&mut self) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.redraw(); - todo!() - }).flatten()) - } - #[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.put_note(advance); - editor.redraw(); - None - }).flatten()) - } - #[command(SetNotePos = "note-pos")] fn edit_note_set_pos (&mut self, pos: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_note_pos((pos).min(127)); - editor.redraw(); - None - }).flatten()) - } - #[command(SetNoteLen = "note-len")] fn edit_note_set_len (&mut self, len: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_note_len(len); - editor.redraw(); - None - }).flatten()) - } - #[command(SetNoteScroll = "note-scroll")] fn edit_note_set_scroll (&mut self, scroll: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_note_lo((scroll).min(127)); - editor.redraw(); - None - }).flatten()) - } - #[command(SetTimePos = "time-pos")] fn edit_time_set_pos (&mut self, pos: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_time_pos(pos); - editor.redraw(); - None - }).flatten()) - } - #[command(SetTimeScroll = "time-scroll")] fn edit_time_set_scroll (&mut self, scroll: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_time_start(scroll); - editor.redraw(); - None - }).flatten()) - } - #[command(SetTimeZoom = "time-zoom")] fn edit_time_set_zoom (&mut self, zoom: usize) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_time_zoom(zoom); - editor.redraw(); - None - }).flatten()) - } - #[command(SetTimeLock = "time-lock")] fn edit_time_set_lock (&mut self, lock: bool) -> Perhaps { - Ok(self.editor_mut().as_mut().map(|editor|{ - editor.set_time_lock(lock); - editor.redraw(); - None - }).flatten()) - } - // TODO: 1-9 seek markers that by default start every 8th of the clip -} - -impl MidiClipControl for T where T: HasClip - + Namespace> + Namespace> {} -#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip - + Namespace> + Namespace> -{ - /// Create a clip at the current position if it does not already exist. - #[command(Upsert = "upsert")] fn clip_upsert ( - &mut self - ) -> Perhaps { - Ok(self.clip_mut().map(|mut clip|if clip.is_none() { - *clip = Some(Arc::new(RwLock::new(MidiClip { - ppq: 24, - length: 96, - color: ItemTheme::random(), - ..Default::default() - }))); - None // TODO undo - } else { - None - }).flatten()) - } - #[command(SetColor = "color")] fn clip_set_color ( - &mut self, color: Option - ) -> Perhaps { - Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetColor { - color: Some(clip.set_color(color)) - })) - } - /// Toggle looping for the active clip - #[command(SetLoop = "loop")] fn clip_toggle_loop ( - &mut self, looping: Option - ) -> Perhaps { - Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop { - looping: Some(clip.toggle_loop(looping)) - })) - } -} - -impl BrowseControl for T where T: - Namespace + Namespace + Namespace> {} -#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl: - Namespace + Namespace + Namespace> -{ - /// Toggle visibility of browser - #[command(Show = "show")] fn show (&mut self) -> Perhaps { - todo!() - } - /// Set current directory - #[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps { - todo!() - } - /// Set current filter - #[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc) -> Perhaps { - todo!() - } - /// Set selected item - #[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps { - todo!() - } -} - -#[tek_proc::commands(AxisCmd)] impl App { -} - -#[tek_proc::command(AudioInput)] -#[tek_proc::keyword(AudioInput)] -#[derive(Debug)] -pub enum AudioInputCmd { - Close, - Connect(Arc), -} - -#[tek_proc::command(AudioOutput)] -#[tek_proc::keyword(AudioOutput)] -#[derive(Debug)] -pub enum AudioOutputCmd { - Close, - Connect(Arc), -} - -#[tek_proc::command(MidiInput)] -#[tek_proc::keyword(MidiInput)] -#[derive(Debug)] -pub enum MidiInputCmd { - Close, - Connect(Arc), -} - -#[tek_proc::command(MidiOutput)] -#[tek_proc::keyword(MidiOutput)] -#[derive(Debug)] -pub enum MidiOutputCmd { - Close, - Connect(Arc), -} - -//impl - //+ Namespace - //+ Namespace> - //+ Namespace - //+ Namespace - //+ Namespace - //+ Namespace -//> PoolControl for T {} - -//#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool - //+ Namespace - //+ Namespace - //+ Namespace> - //+ Namespace - //+ Namespace - //+ Namespace - //+ Namespace -//{ - ///// Toggle visibility of pool - //#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps { - //self.pool_mut().visible = visible; - //Ok(Some(PoolCmd::Show { visible: !visible })) - //} - ///// Set selected clip - //#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps { - //self.pool_mut().set_clip_index(index); - //Ok(None) - //} - ///// Delete a clip from the pool - //#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps { - //let clip = self.pool_mut().remove(index).read().clone(); - //Ok(Some(PoolCmd::Add { index, clip })) - //} - ///// Switch places of two clips in pool - //#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps { - //self.pool_mut().swap(index, other); - //Ok(Some(PoolCmd::Swap { index, other })) - //} - ///// Add clip to pool - //#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps { - //let mut index = index; - //let clip = Arc::new(RwLock::new(clip.clone())); - //let mut clips = self.pool_mut().clips_mut(); - //if index >= clips.len() { - //index = clips.len(); - //clips.push(clip) - //} else { - //clips.insert(index, clip); - //} - //Ok(Some(PoolCmd::Delete { index })) - //} - ///// Set name of clip - //#[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc) -> Perhaps { - //Ok(self.pool_mut().clips.read().get(index).map(|clip|{ - //let old_name = clip.read().name.clone(); - //clip.write().name = name.clone(); - //PoolCmd::SetName { index, name: old_name } - //})) - //} - ///// Set length of clip - //#[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps { - //Ok(self.pool_mut().clips.read().get(index).map(|clip|{ - //let old_len = clip.read().length; - //clip.write().length = length; - //PoolCmd::SetLength { index, length: old_len } - //})) - //} - ///// Set color of clip - //#[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps { - //let mut color = ItemTheme::from(color); - //std::mem::swap(&mut color, &mut self.pool().clips.read()[index].write().color); - //Ok(Some(PoolCmd::SetColor { index, color: color.base })) - //} - ///// Enter crop mode - //#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps { - //let index = self.pool().clip_index(); - //let length = self.pool().clips.read()[index].read().length; - //*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar)); - //Ok(None) - //} - ///// Exit crop mode, discard - //#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps { - //if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() { - //*self.pool_mut().mode_mut() = None; - //} - //Ok(None) - //} - //#[command(CropSet = "crop/set")] fn crop_set (&mut self, _length: usize) -> Perhaps { - //if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) - //= self.pool_mut().mode_mut().clone() - //{ - //let old_length; - //{ - //let clip = self.pool().clips.read()[clip].clone();//.write(); - //old_length = Some(clip.read().length); - //clip.write().length = *length; - //} - //*self.pool_mut().mode_mut() = None; - //return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l })) - //} - //Ok(None) - //} - //#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps { - //if let Some(PoolMode::Length( - //_clip, ref mut _length, ref mut focus - //)) = self.pool_mut().mode_mut().clone() { - //focus.next() - //}; - //Ok(None) - //} - //#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps { - //if let Some(PoolMode::Length( - //_clip, ref mut _length, ref mut focus - //)) = self.pool_mut().mode_mut().clone() { - //focus.prev() - //}; - //Ok(None) - //} - //#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps { - //if let Some(PoolMode::Length( - //_clip, ref mut length, ref mut focus - //)) = self.pool_mut().mode_mut().clone() { - //match focus { - //ClipLengthFocus::Bar => { *length += 4 * PPQ }, - //ClipLengthFocus::Beat => { *length += PPQ }, - //ClipLengthFocus::Tick => { *length += 1 }, - //} - //} - //Ok(None) - //} - //#[command(CropDec = "crop/dec")] fn crop_dec (&mut self) -> Perhaps { - //if let Some(PoolMode::Length( - //_clip, ref mut length, ref mut focus - //)) = self.pool_mut().mode_mut().clone() { - //match focus { - //ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, - //ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, - //ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, - //} - //} - //Ok(None) - //} - ///// Enter rename mode - //#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps { - //let index = self.pool().clip_index(); - //let name = self.pool().clips.read()[index].read().name.clone(); - //*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name)); - //Ok(None) - //} - ///// Exit rename mode, discard - //#[command(RenameCancel = "rename/cancel")] fn rename_cancel (&mut self) -> Perhaps { - //if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { - //self.pool().clips.read()[clip].write().name = old_name.clone().into(); - //} - //Ok(None) - //} - ///// Exit rename mode, update name - //#[command(RenameConfirm = "rename/confirm")] fn rename_confirm (&mut self) -> Perhaps { - //Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { - //let old_name = old_name.clone(); - //*self.pool_mut().mode_mut() = None; - //Some(PoolCmd::RenameSet { value: old_name }) - //} else { - //None - //}) - //} - //#[command(RenameSet = "rename/set")] fn rename_set (&mut self, value: Arc) -> Perhaps { - //if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() { - //self.pool().clips.read()[clip].write().name = value.clone(); - //} - //Ok(None) - //} - /////// Import from file - ////#[command(Browse = "browse")] - ////fn browse (&mut self, command: BrowseCmd) -> Perhaps { - ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { - ////command.act(browse)?.map(|command|PoolCmd::Browse{command}) - ////} else { - ////None - ////}) - ////} - ///// Import clip to pool from file - //#[command(Import = "import")] - //fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps - //where Self: Sized - //{ - //let bytes = std::fs::read(&path)?; - //let smf = Smf::parse(bytes.as_slice())?; - //let mut t = 0u32; - //let mut events = vec![]; - //for track in smf.tracks.iter() { - //for event in track.iter() { - //t += event.delta.as_int(); - //if let TrackEventKind::Midi { channel, message } = event.kind { - //events.push((t, channel.as_int(), message)); - //} - //} - //} - //let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); - //for event in events.iter() { - //clip.notes[event.0 as usize].push(event.2); - //} - //Ok(PoolCmd::Add { index, clip }.dispatch(self)?) - //} - /////// Export to file - ////#[command(Export = "export")] - ////fn export (&mut self, command: BrowseCmd) -> Perhaps { - ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { - ////command.act(browse)?.map(|command|PoolCmd::Export{command}) - ////} else { - ////None - ////}) - ////} -//} diff --git a/src/tek_has.rs b/src/tek_has.rs deleted file mode 100644 index 4581dd37..00000000 --- a/src/tek_has.rs +++ /dev/null @@ -1,214 +0,0 @@ -use crate::*; - -/// Has a stack of currently active interaction modes. -pub trait HasModeStack { - /// Readable mode stack. - fn modes (&self) -> RwLockReadGuard<'_, Vec>>; - /// Writable mode stack. - fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec>>; -} - -/// Has a MIDI editor. -/// -/// ``` -/// use tek::{*, tengri::*}; -/// -/// struct Test(Option); -/// impl_as_ref!(Option: |self: Test|&self.0); -/// impl_as_mut!(Option: |self: Test|&mut self.0); -/// -/// 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(); -/// ``` -impl HasEditor for T where T: - AsRef> + AsMut> {} -pub trait HasEditor: - AsRef> + AsMut> -{ - fn editor (&self) -> &Option { - self.as_ref() - } - fn editor_mut (&mut self) -> &mut Option { - self.as_mut() - } -} - -/// Has a pair of buffers for MIDI IO. -pub trait HasMidiBuffers { - fn note_buf_mut (&mut self) -> &mut Vec; - fn midi_buf_mut (&mut self) -> &mut Vec>>; -} - -/// Can have a currently playing clip. -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>>)>; -} - -/// Has a MIDI sequencer. -impl HasSequencer for T where T: - AsRef + AsMut {} -pub trait HasSequencer: - AsRef + AsMut -{ - fn sequencer (&self) -> &Sequencer { - self.as_ref() - } - fn sequencer_mut (&mut self) -> &mut Sequencer { - self.as_mut() - } -} - -impl HasClock for T where T: - AsRef + AsMut {} - -/// Has a clock. -pub trait HasClock: - AsRef + AsMut -{ - fn clock (&self) -> &Clock { - self.as_ref() - } - fn clock_mut (&mut self) -> &mut Clock { - self.as_mut() - } -} - -/// Has a chain of devices. -pub trait HasDevices: - AsRef> + AsMut> -{ - fn devices (&self) -> &Vec { - self.as_ref() - } - fn devices_mut (&mut self) -> &mut Vec { - self.as_mut() - } -} - -pub trait HasProject: - AsRef> + AsMut> -{} - -/// Has a loaded project. -impl HasProject for T where T: - AsRef> + AsMut> -{} - -impl HasScene for T where T: - AsRefOpt + AsMutOpt + Send + Sync -{} - -pub trait HasScene: - AsRefOpt + AsMutOpt -{ - fn scene (&self) -> Option<&Scene> { - self.as_ref_opt() - } - fn scene_mut (&mut self) -> Option<&mut Scene> { - self.as_mut_opt() - } -} - -pub trait HasScenes { - fn scenes (&self) -> &Vec; - fn scenes_mut (&mut self) -> &mut Vec; - fn scenes_scroll (&self) -> usize { - 0 - } -} - -impl HasSelection for T where T: - AsRef + AsMut -{} - -pub trait HasSelection: AsRef + AsMut { - fn selection (&self) -> &Selection { - self.as_ref() - } - fn selection_mut (&mut self) -> &mut Selection { - self.as_mut() - } -} - -impl HasTrack for T where T: - AsRefOpt + AsMutOpt -{} - -pub trait HasTrack: AsRefOpt + AsMutOpt { - fn track (&self) -> Option<&Track> { - self.as_ref_opt() - } - fn track_mut (&mut self) -> Option<&mut Track> { - self.as_mut_opt() - } -} - -impl HasTracks for T where T: - AsRef> + AsMut> + HasClock + HasTrackScroll -{} - -pub trait HasTracks: AsRef> + AsMut> + HasClock + HasTrackScroll { - /// Read-only reference to collection of [Track]s. - fn tracks (&self) -> &Vec { - self.as_ref() - } - /// Mutable reference to collection of [Track]s. - fn tracks_mut (&mut self) -> &mut Vec { - self.as_mut() - } -} - -pub trait HasTrackScroll { - fn track_scroll (&self) -> usize; - fn tracks_last (&self) -> usize; - fn tracks_last_mut (&mut self) -> &mut usize; -} - -pub trait HasSampler: AsRef + AsMut { - fn sampler (&self) -> &Sampler { - self.as_ref() - } - fn sampler_mut (&mut self) -> &mut Sampler { - self.as_mut() - } -} - -pub trait HasClip { - fn clip_slot (&self) -> Option>; - fn clip (&self) -> Option>>>> { - self.clip_slot().map(|slot|slot.read()) - } - fn clip_mut (&self) -> Option>>>> { - self.clip_slot().map(|slot|slot.write()) - } -} - -pub trait HasWidth { - const MIN_WIDTH: usize; - /// Increment track width. - fn width_inc (&mut self); - /// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH]. - fn width_dec (&mut self); -} - -pub trait HasClipsSize { - /// Size of clips area. - fn clips_size (&self) -> &Sizer; - /// Draw content annotated with value of [Self::clips_size]. - fn with_clips_size (&self, show: bool, content: impl Draw) -> impl Draw { - let size = self.clips_size(); - above( - when(show, fg(Green, east!(size.w() as usize, "x", size.h() as usize)).align_se()), - size.of(content.align_c().full_wh()) - ) - } -} diff --git a/src/tek_view.rs b/src/view.rs similarity index 100% rename from src/tek_view.rs rename to src/view.rs diff --git a/src/device_vst2.rs b/src/vst2.rs similarity index 97% rename from src/device_vst2.rs rename to src/vst2.rs index 44643faa..25bbc302 100644 --- a/src/device_vst2.rs +++ b/src/vst2.rs @@ -1,3 +1,5 @@ +//! TODO + use crate::*; impl ::vst::host::Host for Plugin {} diff --git a/src/vst3.rs b/src/vst3.rs new file mode 100644 index 00000000..46330df3 --- /dev/null +++ b/src/vst3.rs @@ -0,0 +1 @@ +//! TODO