diff --git a/crates/tek/src/audio.rs b/crates/tek/src/audio.rs index 650bb769..3d287f09 100644 --- a/crates/tek/src/audio.rs +++ b/crates/tek/src/audio.rs @@ -1,9 +1,5 @@ use crate::*; -mod audio_in; - -mod audio_out; - mod sampler; pub(crate) use self::sampler::*; pub use self::sampler::{Sampler, Sample, Voice}; diff --git a/crates/tek/src/audio/audio_in.rs b/crates/tek/src/audio/audio_in.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek/src/audio/audio_out.rs b/crates/tek/src/audio/audio_out.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index 73379924..7e97a594 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -1,14 +1,36 @@ use crate::*; -pub(crate) mod midi_in; pub(crate) use midi_in::*; -pub(crate) mod midi_launch; pub(crate) use midi_launch::*; -pub(crate) mod midi_note; pub(crate) use midi_note::*; -pub(crate) mod midi_out; pub(crate) use midi_out::*; -pub(crate) mod midi_clip; pub(crate) use midi_clip::*; -pub(crate) mod midi_play; pub(crate) use midi_play::*; pub(crate) mod midi_pool; pub(crate) use midi_pool::*; +pub(crate) mod midi_clip; pub(crate) use midi_clip::*; +pub(crate) mod midi_launch; pub(crate) use midi_launch::*; +pub(crate) mod midi_play; pub(crate) use midi_play::*; pub(crate) mod midi_rec; pub(crate) use midi_rec::*; +pub(crate) mod midi_note; pub(crate) use midi_note::*; +pub(crate) mod midi_range; pub(crate) use midi_range::*; +pub(crate) mod midi_point; pub(crate) use midi_point::*; +pub(crate) mod midi_view; pub(crate) use midi_view::*; + +/// Trait for thing that may receive MIDI. +pub trait HasMidiIns { + fn midi_ins (&self) -> &Vec>; + fn midi_ins_mut (&mut self) -> &mut Vec>; + fn has_midi_ins (&self) -> bool { + !self.midi_ins().is_empty() + } +} + +/// Trait for thing that may output MIDI. +pub trait HasMidiOuts { + fn midi_outs (&self) -> &Vec>; + fn midi_outs_mut (&mut self) -> &mut Vec>; + fn has_midi_outs (&self) -> bool { + !self.midi_outs().is_empty() + } + /// Buffer for serializing a MIDI event. FIXME rename + fn midi_note (&mut self) -> &mut Vec; +} + /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut [Vec>]) { let mut buf = vec![]; diff --git a/crates/tek/src/midi/midi_in.rs b/crates/tek/src/midi/midi_in.rs deleted file mode 100644 index 29e16946..00000000 --- a/crates/tek/src/midi/midi_in.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::*; - -/// Trait for thing that may receive MIDI. -pub trait HasMidiIns { - fn midi_ins (&self) -> &Vec>; - fn midi_ins_mut (&mut self) -> &mut Vec>; - fn has_midi_ins (&self) -> bool { - !self.midi_ins().is_empty() - } -} diff --git a/crates/tek/src/midi/midi_note.rs b/crates/tek/src/midi/midi_note.rs index e13127cc..ca08b5ea 100644 --- a/crates/tek/src/midi/midi_note.rs +++ b/crates/tek/src/midi/midi_note.rs @@ -1,5 +1,7 @@ use crate::*; + pub struct Note; + impl Note { /// (pulses, name), assuming 96 PPQ pub const DURATIONS: [(usize, &str);26] = [ @@ -32,131 +34,3 @@ impl Note { "" } } -pub trait MidiView: MidiRange + MidiPoint + HasSize { - /// Make sure cursor is within range - fn autoscroll (&self) { - let note_point = self.note_point().min(127); - let note_lo = self.note_lo().get(); - let note_hi = self.note_hi(); - if note_point < note_lo { - self.note_lo().set(note_point); - } else if note_point > note_hi { - self.note_lo().set((note_lo + note_point).saturating_sub(note_hi)); - } - } - /// Make sure range is within display - fn autozoom (&self) { - let time_len = self.time_len().get(); - let time_axis = self.time_axis().get(); - let time_zoom = self.time_zoom().get(); - //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); - } -} -#[derive(Debug, Clone)] -pub struct MidiRangeModel { - 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, -} -from!(|data:(usize, bool)|MidiRangeModel = 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()), -}); -pub trait TimeRange { - fn time_len (&self) -> &AtomicUsize; - fn time_zoom (&self) -> &AtomicUsize; - fn time_lock (&self) -> &AtomicBool; - fn time_start (&self) -> &AtomicUsize; - fn time_axis (&self) -> &AtomicUsize; - fn time_end (&self) -> usize { - self.time_start().get() + self.time_axis().get() * self.time_zoom().get() - } -} -pub trait NoteRange { - fn note_lo (&self) -> &AtomicUsize; - fn note_axis (&self) -> &AtomicUsize; - fn note_hi (&self) -> usize { - (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) - } -} -pub trait MidiRange: TimeRange + NoteRange {} -impl MidiRange for T {} -impl TimeRange for MidiRangeModel { - 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 MidiRangeModel { - fn note_lo (&self) -> &AtomicUsize { &self.note_lo } - fn note_axis (&self) -> &AtomicUsize { &self.note_axis } -} - -#[derive(Debug, Clone)] -pub struct MidiPointModel { - /// Time coordinate of cursor - pub time_point: Arc, - /// Note coordinate of cursor - pub note_point: Arc, - /// Length of note that will be inserted, in pulses - pub note_len: Arc, -} - -impl Default for MidiPointModel { - fn default () -> Self { - Self { - time_point: Arc::new(0.into()), - note_point: Arc::new(36.into()), - note_len: Arc::new(24.into()), - } - } -} - -pub trait NotePoint { - fn note_len (&self) -> usize; - fn set_note_len (&self, x: usize); - fn note_point (&self) -> usize; - fn set_note_point (&self, x: usize); - fn note_end (&self) -> usize { self.note_point() + self.note_len() } -} - -pub trait TimePoint { - fn time_point (&self) -> usize; - fn set_time_point (&self, x: usize); -} - -pub trait MidiPoint: NotePoint + TimePoint {} - -impl MidiPoint for T {} - -impl NotePoint for MidiPointModel { - fn note_len (&self) -> usize { self.note_len.load(Relaxed)} - fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } - fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } - fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } -} - -impl TimePoint for MidiPointModel { - fn time_point (&self) -> usize { self.time_point.load(Relaxed) } - fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } -} diff --git a/crates/tek/src/midi/midi_out.rs b/crates/tek/src/midi/midi_out.rs deleted file mode 100644 index ca9d34c8..00000000 --- a/crates/tek/src/midi/midi_out.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::*; - -/// Trait for thing that may output MIDI. -pub trait HasMidiOuts { - fn midi_outs (&self) -> &Vec>; - fn midi_outs_mut (&mut self) -> &mut Vec>; - fn has_midi_outs (&self) -> bool { - !self.midi_outs().is_empty() - } - /// Buffer for serializing a MIDI event. FIXME rename - fn midi_note (&mut self) -> &mut Vec; -} diff --git a/crates/tek/src/midi/midi_point.rs b/crates/tek/src/midi/midi_point.rs new file mode 100644 index 00000000..bc85de88 --- /dev/null +++ b/crates/tek/src/midi/midi_point.rs @@ -0,0 +1,50 @@ +use crate::*; + +#[derive(Debug, Clone)] +pub struct MidiPointModel { + /// Time coordinate of cursor + pub time_point: Arc, + /// Note coordinate of cursor + pub note_point: Arc, + /// Length of note that will be inserted, in pulses + pub note_len: Arc, +} + +impl Default for MidiPointModel { + fn default () -> Self { + Self { + time_point: Arc::new(0.into()), + note_point: Arc::new(36.into()), + note_len: Arc::new(24.into()), + } + } +} + +pub trait NotePoint { + fn note_len (&self) -> usize; + fn set_note_len (&self, x: usize); + fn note_point (&self) -> usize; + fn set_note_point (&self, x: usize); + fn note_end (&self) -> usize { self.note_point() + self.note_len() } +} + +pub trait TimePoint { + fn time_point (&self) -> usize; + fn set_time_point (&self, x: usize); +} + +pub trait MidiPoint: NotePoint + TimePoint {} + +impl MidiPoint for T {} + +impl NotePoint for MidiPointModel { + fn note_len (&self) -> usize { self.note_len.load(Relaxed)} + fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } + fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } + fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } +} + +impl TimePoint for MidiPointModel { + fn time_point (&self) -> usize { self.time_point.load(Relaxed) } + fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } +} diff --git a/crates/tek/src/midi/midi_range.rs b/crates/tek/src/midi/midi_range.rs new file mode 100644 index 00000000..308a4ae2 --- /dev/null +++ b/crates/tek/src/midi/midi_range.rs @@ -0,0 +1,64 @@ +use crate::*; + +#[derive(Debug, Clone)] +pub struct MidiRangeModel { + 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, +} + +from!(|data:(usize, bool)|MidiRangeModel = 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()), +}); + +pub trait TimeRange { + fn time_len (&self) -> &AtomicUsize; + fn time_zoom (&self) -> &AtomicUsize; + fn time_lock (&self) -> &AtomicBool; + fn time_start (&self) -> &AtomicUsize; + fn time_axis (&self) -> &AtomicUsize; + fn time_end (&self) -> usize { + self.time_start().get() + self.time_axis().get() * self.time_zoom().get() + } +} + +pub trait NoteRange { + fn note_lo (&self) -> &AtomicUsize; + fn note_axis (&self) -> &AtomicUsize; + fn note_hi (&self) -> usize { + (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) + } +} + +pub trait MidiRange: TimeRange + NoteRange {} + +impl MidiRange for T {} + +impl TimeRange for MidiRangeModel { + 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 MidiRangeModel { + fn note_lo (&self) -> &AtomicUsize { &self.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.note_axis } +} diff --git a/crates/tek/src/midi/midi_view.rs b/crates/tek/src/midi/midi_view.rs new file mode 100644 index 00000000..0d617adb --- /dev/null +++ b/crates/tek/src/midi/midi_view.rs @@ -0,0 +1,26 @@ +use crate::*; + +pub trait MidiView: MidiRange + MidiPoint + HasSize { + /// Make sure cursor is within range + fn autoscroll (&self) { + let note_point = self.note_point().min(127); + let note_lo = self.note_lo().get(); + let note_hi = self.note_hi(); + if note_point < note_lo { + self.note_lo().set(note_point); + } else if note_point > note_hi { + self.note_lo().set((note_lo + note_point).saturating_sub(note_hi)); + } + } + /// Make sure range is within display + fn autozoom (&self) { + let time_len = self.time_len().get(); + let time_axis = self.time_axis().get(); + let time_zoom = self.time_zoom().get(); + //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); + } +} diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/tui/app_groovebox.rs index 05f1ece1..a04774c2 100644 --- a/crates/tek/src/tui/app_groovebox.rs +++ b/crates/tek/src/tui/app_groovebox.rs @@ -55,13 +55,11 @@ pub enum GrooveboxCommand { handle!(|self: GrooveboxTui, input|GrooveboxCommand::execute_with_state(self, input)); input_to_command!(GrooveboxCommand: |state: GrooveboxTui,input|match input.event() { - key_pat!(Up) | - key_pat!(Down) | - key_pat!(Left) | - key_pat!(Right) => - GrooveboxCommand::Sampler(SamplerCommand::input_to_command(&state.sampler, input)?), + key_pat!(Up) | key_pat!(Down) | key_pat!(Left) | key_pat!(Right) | + key_pat!(Shift-Char('L')) => + SamplerCommand::input_to_command(&state.sampler, input).map(Self::Sampler)?, _ => - GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?), + SequencerCommand::input_to_command(&state.sequencer, input).map(Self::Sequencer)?, }); command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 881ce6e7..60ca1ee9 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -62,27 +62,25 @@ render!(|self: SamplerTui|{ let fg = self.color.base.rgb; let bg = self.color.darkest.rgb; let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); - let inset = 0; - let with_border = |x|lay!([border, Tui::inset_xy(inset, inset, Fill::wh(&x))]); + let with_border = |x|lay!([border, Fill::wh(&x)]); let with_size = |x|lay!([self.size, x]); Tui::bg(bg, Fill::wh(with_border(Bsp::s( - Tui::push_x(1, Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler"))), + Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")), with_size(Tui::shrink_y(1, Bsp::e( Fixed::w(keys_width, keys()), Fill::wh(render(|to: &mut TuiOutput|Ok({ - let x = to.area.x() + 1; - let rows = self.size.h() as u16; + let x = to.area.x(); let bg_base = self.color.darkest.rgb; let bg_selected = self.color.darker.rgb; let style_empty = Style::default().fg(self.color.base.rgb); let style_full = Style::default().fg(self.color.lighter.rgb); let note_hi = self.note_hi(); let note_pt = self.note_point(); - for y in 0..rows { + for y in 0..self.size.h() { let note = note_hi - y as usize; let bg = if note == note_pt { bg_selected } else { bg_base }; let style = Some(style_empty.bg(bg)); - to.blit(&" (no sample) ", x, to.area.y() + y, style) + to.blit(&" (no sample) ", x, to.area.y() + y as u16, style) } }))) ))), @@ -135,7 +133,9 @@ input_to_command!(SamplerCommand: |state: SamplerTui, input|match state.mod ), _ => match input.event() { // load sample - key_pat!(Char('l')) => Self::Import(FileBrowserCommand::Begin), + key_pat!(Shift-Char('L')) => { + Self::Import(FileBrowserCommand::Begin) + }, key_pat!(KeyCode::Up) => { Self::SelectNote(state.note_point().overflowing_add(1).0.min(127)) },