diff --git a/midi/src/lib.rs b/midi/src/lib.rs index 56c1804f..b2ef11bd 100644 --- a/midi/src/lib.rs +++ b/midi/src/lib.rs @@ -58,42 +58,3 @@ pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { _ => {} } } - -/// A phrase, rendered as a horizontal piano roll. -pub struct PianoHorizontal { - phrase: Option>>, - /// Buffer where the whole phrase is rerendered on change - buffer: Arc>, - /// Size of actual notes area - size: Measure, - /// The display window - range: MidiRangeModel, - /// The note cursor - point: MidiPointModel, - /// The highlight color palette - color: ItemPalette, - /// Width of the keyboard - keys_width: u16, -} - -impl PianoHorizontal { - pub fn new (phrase: Option<&Arc>>) -> Self { - let size = Measure::new(); - let mut range = MidiRangeModel::from((24, true)); - range.time_axis = size.x.clone(); - range.note_axis = size.y.clone(); - let mut piano = Self { - keys_width: 5, - size, - range, - buffer: RwLock::new(Default::default()).into(), - point: MidiPointModel::default(), - phrase: phrase.cloned(), - color: phrase.as_ref() - .map(|p|p.read().unwrap().color) - .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))), - }; - piano.redraw(); - piano - } -} diff --git a/midi/src/midi_clip.rs b/midi/src/midi_clip.rs index fe8438c1..93b3b7cc 100644 --- a/midi/src/midi_clip.rs +++ b/midi/src/midi_clip.rs @@ -1,13 +1,13 @@ use crate::*; pub trait HasMidiClip { - fn phrase (&self) -> &Arc>; + fn clip (&self) -> &Arc>; } -#[macro_export] macro_rules! has_phrase { +#[macro_export] macro_rules! has_clip { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { - fn phrase (&$self) -> &Arc> { &$cb } + fn clip (&$self) -> &Arc> { &$cb } } } } @@ -16,15 +16,15 @@ pub trait HasMidiClip { #[derive(Debug, Clone)] pub struct MidiClip { pub uuid: uuid::Uuid, - /// Name of phrase + /// Name of clip pub name: Arc, /// Temporal resolution in pulses per quarter note pub ppq: usize, - /// Length of phrase in pulses + /// Length of clip in pulses pub length: usize, - /// Notes in phrase + /// Notes in clip pub notes: MidiData, - /// Whether to loop the phrase or play it once + /// Whether to loop the clip or play it once pub looped: bool, /// Start of loop pub loop_start: usize, @@ -32,7 +32,7 @@ pub struct MidiClip { pub loop_length: usize, /// All notes are displayed with minimum length pub percussive: bool, - /// Identifying color of phrase + /// Identifying color of clip pub color: ItemPalette, } @@ -71,7 +71,7 @@ impl MidiClip { } pub fn toggle_loop (&mut self) { self.looped = !self.looped; } pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { - if pulse >= self.length { panic!("extend phrase first") } + if pulse >= self.length { panic!("extend clip first") } self.notes[pulse].push(message); } /// Check if a range `start..end` contains MIDI Note On `k` diff --git a/midi/src/midi_editor.rs b/midi/src/midi_editor.rs index 42a59caa..53214825 100644 --- a/midi/src/midi_editor.rs +++ b/midi/src/midi_editor.rs @@ -12,19 +12,19 @@ pub trait HasEditor { } } } -/// Contains state for viewing and editing a phrase +/// Contains state for viewing and editing a clip pub struct MidiEditor { pub mode: PianoHorizontal, pub size: Measure } -from!(|phrase: &Arc>|MidiEditor = { - let model = Self::from(Some(phrase.clone())); +from!(|clip: &Arc>|MidiEditor = { + let model = Self::from(Some(clip.clone())); model.redraw(); model }); -from!(|phrase: Option>>|MidiEditor = { +from!(|clip: Option>>|MidiEditor = { let mut model = Self::default(); - *model.phrase_mut() = phrase; + *model.clip_mut() = clip; model.redraw(); model }); @@ -63,33 +63,33 @@ impl TimePoint for MidiEditor { fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } } impl MidiViewer for MidiEditor { - fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) { self.mode.buffer_size(phrase) } + fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) } fn redraw (&self) { self.mode.redraw() } - fn phrase (&self) -> &Option>> { self.mode.phrase() } - fn phrase_mut (&mut self) -> &mut Option>> { self.mode.phrase_mut() } - fn set_phrase (&mut self, p: Option<&Arc>>) { self.mode.set_phrase(p) } + fn clip (&self) -> &Option>> { self.mode.clip() } + fn clip_mut (&mut self) -> &mut Option>> { self.mode.clip_mut() } + fn set_clip (&mut self, p: Option<&Arc>>) { self.mode.set_clip(p) } } impl MidiEditor { /// Put note at current position pub fn put_note (&mut self, advance: bool) { let mut redraw = false; - if let Some(phrase) = self.phrase() { - let mut phrase = phrase.write().unwrap(); + if let Some(clip) = self.clip() { + let mut clip = clip.write().unwrap(); let note_start = self.time_point(); let note_point = self.note_point(); let note_len = self.note_len(); let note_end = note_start + (note_len.saturating_sub(1)); let key: u7 = u7::from(note_point as u8); let vel: u7 = 100.into(); - let length = phrase.length; + let length = clip.length; let note_end = note_end % length; let note_on = MidiMessage::NoteOn { key, vel }; - if !phrase.notes[note_start].iter().any(|msg|*msg == note_on) { - phrase.notes[note_start].push(note_on); + 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 !phrase.notes[note_end].iter().any(|msg|*msg == note_off) { - phrase.notes[note_end].push(note_off); + if !clip.notes[note_end].iter().any(|msg|*msg == note_off) { + clip.notes[note_end].push(note_off); } if advance { self.set_time_point(note_end); @@ -102,8 +102,8 @@ impl MidiEditor { } pub fn clip_status (&self) -> impl Content + '_ { - let (color, name, length, looped) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) { - (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) + let (color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { + (clip.color, clip.name.clone(), clip.length, clip.looped) } else { (ItemPalette::from(TuiTheme::g(64)), String::new().into(), 0, false) }; @@ -114,8 +114,8 @@ impl MidiEditor { } pub fn edit_status (&self) -> impl Content + '_ { - let (color, length) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) { - (phrase.color, phrase.length) + let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { + (clip.color, clip.length) } else { (ItemPalette::from(TuiTheme::g(64)), 0) }; @@ -140,7 +140,7 @@ impl std::fmt::Debug for MidiEditor { } #[derive(Clone, Debug)] pub enum MidiEditCommand { - // TODO: 1-9 seek markers that by default start every 8th of the phrase + // TODO: 1-9 seek markers that by default start every 8th of the clip AppendNote, PutNote, SetNoteCursor(usize), @@ -160,11 +160,11 @@ keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand { key(Char('s')) => SetNoteCursor(s.note_point().saturating_sub(1)), key(Left) => SetTimeCursor(s.time_point().saturating_sub(s.note_len())), key(Char('a')) => SetTimeCursor(s.time_point().saturating_sub(s.note_len())), - key(Right) => SetTimeCursor((s.time_point() + s.note_len()) % s.phrase_length()), + key(Right) => SetTimeCursor((s.time_point() + s.note_len()) % s.clip_length()), ctrl(alt(key(Up))) => SetNoteScroll(s.note_point() + 3), ctrl(alt(key(Down))) => SetNoteScroll(s.note_point().saturating_sub(3)), ctrl(alt(key(Left))) => SetTimeScroll(s.time_point().saturating_sub(s.time_zoom().get())), - ctrl(alt(key(Right))) => SetTimeScroll((s.time_point() + s.time_zoom().get()) % s.phrase_length()), + ctrl(alt(key(Right))) => SetTimeScroll((s.time_point() + s.time_zoom().get()) % s.clip_length()), ctrl(key(Up)) => SetNoteScroll(s.note_lo().get() + 1), ctrl(key(Down)) => SetNoteScroll(s.note_lo().get().saturating_sub(1)), ctrl(key(Left)) => SetTimeScroll(s.time_start().get().saturating_sub(s.note_len())), @@ -172,8 +172,8 @@ keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand { alt(key(Up)) => SetNoteCursor(s.note_point() + 3), alt(key(Down)) => SetNoteCursor(s.note_point().saturating_sub(3)), alt(key(Left)) => SetTimeCursor(s.time_point().saturating_sub(s.time_zoom().get())), - alt(key(Right)) => SetTimeCursor((s.time_point() + s.time_zoom().get()) % s.phrase_length()), - key(Char('d')) => SetTimeCursor((s.time_point() + s.note_len()) % s.phrase_length()), + alt(key(Right)) => SetTimeCursor((s.time_point() + s.time_zoom().get()) % s.clip_length()), + key(Char('d')) => SetTimeCursor((s.time_point() + s.note_len()) % s.clip_length()), key(Char('z')) => SetTimeLock(!s.time_lock().get()), key(Char('-')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }), key(Char('_')) => SetTimeZoom(if s.time_lock().get() { s.time_zoom().get() } else { NoteDuration::next(s.time_zoom().get()) }), @@ -189,15 +189,15 @@ keymap!(KEYS_MIDI_EDITOR = |s: MidiEditor, _input: Event| MidiEditCommand { //// TODO: kpat!(Char('?')) => // toggle dotted }); impl MidiEditor { - fn phrase_length (&self) -> usize { - self.phrase().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) + fn clip_length (&self) -> usize { + self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) } } impl Command for MidiEditCommand { fn execute (self, state: &mut MidiEditor) -> Perhaps { use MidiEditCommand::*; match self { - Show(phrase) => { state.set_phrase(phrase.as_ref()); }, + Show(clip) => { state.set_clip(clip.as_ref()); }, PutNote => { state.put_note(false); }, AppendNote => { state.put_note(true); }, SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); }, diff --git a/midi/src/midi_in.rs b/midi/src/midi_in.rs index 30e24c30..3452367c 100644 --- a/midi/src/midi_in.rs +++ b/midi/src/midi_in.rs @@ -44,10 +44,10 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { if !self.clock().is_rolling() { return } - if let Some((started, ref clip)) = self.play_phrase().clone() { + if let Some((started, ref clip)) = self.play_clip().clone() { self.record_clip(scope, started, clip, midi_buf); } - if let Some((start_at, phrase)) = &self.next_phrase() { + if let Some((start_at, clip)) = &self.next_clip() { self.record_next(); } } @@ -55,21 +55,21 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { &mut self, scope: &ProcessScope, started: Moment, - phrase: &Option>>, + clip: &Option>>, midi_buf: &mut Vec>> ) { - if let Some(phrase) = phrase { + 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 phrase = phrase.write().unwrap(); - let length = phrase.length; + let mut clip = clip.write().unwrap(); + let length = clip.length; for input in self.midi_ins_mut().iter() { for (sample, event, bytes) in parse_midi_input(input.port.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { - phrase.record_event({ + clip.record_event({ let sample = (sample0 + sample - start) as f64; let pulse = timebase.samples_to_pulse(sample); let quantized = (pulse / quant).round() * quant; diff --git a/midi/src/midi_launch.rs b/midi/src/midi_launch.rs index c196b5a4..66a21596 100644 --- a/midi/src/midi_launch.rs +++ b/midi/src/midi_launch.rs @@ -3,12 +3,12 @@ use crate::*; pub trait HasPlayPhrase: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; - fn play_phrase (&self) -> &Option<(Moment, Option>>)>; - fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; - fn next_phrase (&self) -> &Option<(Moment, Option>>)>; - fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; + fn play_clip (&self) -> &Option<(Moment, Option>>)>; + fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; + fn next_clip (&self) -> &Option<(Moment, Option>>)>; + fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn pulses_since_start (&self) -> Option { - if let Some((started, Some(_))) = self.play_phrase().as_ref() { + if let Some((started, Some(_))) = self.play_clip().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); Some(elapsed) } else { @@ -16,9 +16,9 @@ pub trait HasPlayPhrase: HasClock { } } fn pulses_since_start_looped (&self) -> Option<(f64, f64)> { - if let Some((started, Some(phrase))) = self.play_phrase().as_ref() { + if let Some((started, Some(clip))) = self.play_clip().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); - let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase + let length = clip.read().unwrap().length.max(1); // prevent div0 on empty clip let times = (elapsed as usize / length) as f64; let elapsed = (elapsed as usize % length) as f64; Some((times, elapsed)) @@ -26,10 +26,10 @@ pub trait HasPlayPhrase: HasClock { None } } - fn enqueue_next (&mut self, phrase: Option<&Arc>>) { + fn enqueue_next (&mut self, clip: Option<&Arc>>) { let start = self.clock().next_launch_pulse() as f64; let instant = Moment::from_pulse(self.clock().timebase(), start); - *self.next_phrase_mut() = Some((instant, phrase.cloned())); + *self.next_clip_mut() = Some((instant, clip.cloned())); *self.reset_mut() = true; } } diff --git a/midi/src/midi_out.rs b/midi/src/midi_out.rs index e686bf12..3b312de5 100644 --- a/midi/src/midi_out.rs +++ b/midi/src/midi_out.rs @@ -28,21 +28,21 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { } } - /// Output notes from phrase to MIDI output ports. + /// Output notes from clip to MIDI output ports. fn play ( &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] ) -> bool { if !self.clock().is_rolling() { return false } - // If a phrase is playing, write a chunk of MIDI events from it to the output buffer. - // If no phrase is playing, prepare for switchover immediately. - self.play_phrase().as_ref().map_or(true, |(started, phrase)|{ - self.play_chunk(scope, note_buf, out, started, phrase) + // 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. + self.play_clip().as_ref().map_or(true, |(started, clip)|{ + self.play_chunk(scope, note_buf, out, started, clip) }) } - /// Handle switchover from current to next playing phrase. + /// Handle switchover from current to next playing clip. fn switchover ( &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] ) { @@ -51,21 +51,21 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { } let sample0 = scope.last_frame_time() as usize; //let samples = scope.n_frames() as usize; - if let Some((start_at, phrase)) = &self.next_phrase() { + if let Some((start_at, clip)) = &self.next_clip() { let start = start_at.sample.get() as usize; let sample = self.clock().started.read().unwrap() .as_ref().unwrap().sample.get() as usize; - // If it's time to switch to the next phrase: + // If it's time to switch to the next clip: if start <= sample0.saturating_sub(sample) { - // Samples elapsed since phrase was supposed to start + // Samples elapsed since clip was supposed to start let _skipped = sample0 - start; - // Switch over to enqueued phrase + // Switch over to enqueued clip let started = Moment::from_sample(self.clock().timebase(), start as f64); - // Launch enqueued phrase - *self.play_phrase_mut() = Some((started, phrase.clone())); + // Launch enqueued clip + *self.play_clip_mut() = Some((started, clip.clone())); // Unset enqueuement (TODO: where to implement looping?) - *self.next_phrase_mut() = None; - // Fill in remaining ticks of chunk from next phrase. + *self.next_clip_mut() = None; + // Fill in remaining ticks of chunk from next clip. self.play(scope, note_buf, out); } } @@ -77,52 +77,52 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { note_buf: &mut Vec, out: &mut [Vec>], started: &Moment, - phrase: &Option>> + clip: &Option>> ) -> bool { // First sample to populate. Greater than 0 means that the first - // pulse of the phrase falls somewhere in the middle of the chunk. + // pulse of the clip falls somewhere in the middle of the chunk. let sample = (scope.last_frame_time() as usize).saturating_sub( started.sample.get() as usize + self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize ); // Iterator that emits sample (index into output buffer at which to write MIDI event) - // paired with pulse (index into phrase from which to take the MIDI event) for each + // paired with pulse (index into clip from which to take the MIDI event) for each // sample of the output buffer that corresponds to a MIDI pulse. let pulses = self.clock().timebase().pulses_between_samples(sample, sample + scope.n_frames() as usize); // Notes active during current chunk. let notes = &mut self.notes_out().write().unwrap(); - let length = phrase.as_ref().map_or(0, |p|p.read().unwrap().length); + let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); for (sample, pulse) in pulses { - // If a next phrase is enqueued, and we're past the end of the current one, + // 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 phrase.is_some() { pulse >= length } else { true }; - if self.next_phrase().is_some() && past_end { + let past_end = if clip.is_some() { pulse >= length } else { true }; + if self.next_clip().is_some() && past_end { return true } - // If there's a currently playing phrase, output notes from it to buffer: - if let Some(ref phrase) = phrase { - Self::play_pulse(phrase, pulse, sample, note_buf, out, notes) + // If there's a currently playing clip, output notes from it to buffer: + if let Some(ref clip) = clip { + Self::play_pulse(clip, pulse, sample, note_buf, out, notes) } } false } fn play_pulse ( - phrase: &RwLock, + clip: &RwLock, pulse: usize, sample: usize, note_buf: &mut Vec, out: &mut [Vec>], notes: &mut [bool;128] ) { - // Source phrase from which the MIDI events will be taken. - let phrase = phrase.read().unwrap(); + // Source clip from which the MIDI events will be taken. + let clip = clip.read().unwrap(); // Phrase with zero length is not processed - if phrase.length > 0 { - // Current pulse index in source phrase - let pulse = pulse % phrase.length; - // Output each MIDI event from phrase at appropriate frames of output buffer: - for message in phrase.notes[pulse].iter() { + 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() { // Clear output buffer for this MIDI event. note_buf.clear(); // TODO: support MIDI channels other than CH1. diff --git a/midi/src/midi_player.rs b/midi/src/midi_player.rs index 3edc47e1..271fa4ae 100644 --- a/midi/src/midi_player.rs +++ b/midi/src/midi_player.rs @@ -18,14 +18,14 @@ pub trait HasPlayer { } } -/// Contains state for playing a phrase +/// Contains state for playing a clip pub struct MidiPlayer { /// State of clock and playhead pub clock: Clock, - /// Start time and phrase being played - pub play_phrase: Option<(Moment, Option>>)>, - /// Start time and next phrase - pub next_phrase: Option<(Moment, Option>>)>, + /// Start time and clip being played + pub play_clip: Option<(Moment, Option>>)>, + /// Start time and next clip + pub next_clip: Option<(Moment, Option>>)>, /// Play input through output. pub monitoring: bool, /// Write input to sequence. @@ -56,8 +56,8 @@ impl MidiPlayer { let name = name.as_ref(); let clock = Clock::from(jack); Ok(Self { - play_phrase: Some((Moment::zero(&clock.timebase), clip.cloned())), - next_phrase: None, + play_clip: Some((Moment::zero(&clock.timebase), clip.cloned())), + next_clip: None, recording: false, monitoring: false, overdub: false, @@ -73,18 +73,18 @@ impl MidiPlayer { }) } pub fn play_status (&self) -> impl Content { - ClipSelected::play_phrase(self) + ClipSelected::play_clip(self) } pub fn next_status (&self) -> impl Content { - ClipSelected::next_phrase(self) + ClipSelected::next_clip(self) } } impl std::fmt::Debug for MidiPlayer { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("MidiPlayer") .field("clock", &self.clock) - .field("play_phrase", &self.play_phrase) - .field("next_phrase", &self.next_phrase) + .field("play_clip", &self.play_clip) + .field("next_clip", &self.next_clip) .finish() } } @@ -97,15 +97,15 @@ from!(|clock: &Clock| MidiPlayer = Self { recording: false, monitoring: false, overdub: false, - play_phrase: None, - next_phrase: None, + play_clip: None, + next_clip: None, notes_in: RwLock::new([false;128]).into(), notes_out: RwLock::new([false;128]).into(), }); from!(|state: (&Clock, &Arc>)|MidiPlayer = { - let (clock, phrase) = state; + let (clock, clip) = state; let mut model = Self::from(clock); - model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone()))); + model.play_clip = Some((Moment::zero(&clock.timebase), Some(clip.clone()))); model }); has_clock!(|self: MidiPlayer|&self.clock); @@ -129,7 +129,7 @@ pub struct PlayerAudio<'a, T: MidiPlayerApi>( pub &'a mut Vec>>, ); -/// JACK process callback for a sequencer's phrase player/recorder. +/// JACK process callback for a sequencer's clip player/recorder. impl Audio for PlayerAudio<'_, T> { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { let model = &mut self.0; @@ -137,7 +137,7 @@ impl Audio for PlayerAudio<'_, T> { let midi_buf = &mut self.2; // Clear output buffer(s) model.clear(scope, midi_buf, false); - // Write chunk of phrase to output, handle switchover + // Write chunk of clip to output, handle switchover if model.play(scope, note_buf, midi_buf) { model.switchover(scope, note_buf, midi_buf); } @@ -193,17 +193,17 @@ impl HasPlayPhrase for MidiPlayer { fn reset_mut (&mut self) -> &mut bool { &mut self.reset } - fn play_phrase (&self) -> &Option<(Moment, Option>>)> { - &self.play_phrase + fn play_clip (&self) -> &Option<(Moment, Option>>)> { + &self.play_clip } - fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { - &mut self.play_phrase + fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)> { + &mut self.play_clip } - fn next_phrase (&self) -> &Option<(Moment, Option>>)> { - &self.next_phrase + fn next_clip (&self) -> &Option<(Moment, Option>>)> { + &self.next_clip } - fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { - &mut self.next_phrase + fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)> { + &mut self.next_clip } } @@ -211,10 +211,10 @@ impl HasPlayPhrase for MidiPlayer { //pub struct MIDIPlayer { ///// Global timebase //pub clock: Arc, - ///// Start time and phrase being played - //pub play_phrase: Option<(Moment, Option>>)>, - ///// Start time and next phrase - //pub next_phrase: Option<(Moment, Option>>)>, + ///// Start time and clip being played + //pub play_clip: Option<(Moment, Option>>)>, + ///// Start time and next clip + //pub next_clip: Option<(Moment, Option>>)>, ///// Play input through output. //pub monitoring: bool, ///// Write input to sequence. @@ -247,8 +247,8 @@ impl HasPlayPhrase for MidiPlayer { //let jack = jack.read().unwrap(); //Ok(Self { //clock: clock.clone(), - //phrase: None, - //next_phrase: None, + //clip: None, + //next_clip: None, //notes_in: Arc::new(RwLock::new([false;128])), //notes_out: Arc::new(RwLock::new([false;128])), //monitoring: false, diff --git a/midi/src/midi_pool.rs b/midi/src/midi_pool.rs index 3fdd5c23..9e1e3c88 100644 --- a/midi/src/midi_pool.rs +++ b/midi/src/midi_pool.rs @@ -1,15 +1,15 @@ use crate::*; pub trait HasPhrases { - fn phrases (&self) -> &Vec>>; - fn phrases_mut (&mut self) -> &mut Vec>>; + fn clips (&self) -> &Vec>>; + fn clips_mut (&mut self) -> &mut Vec>>; } -#[macro_export] macro_rules! has_phrases { +#[macro_export] macro_rules! has_clips { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? { - fn phrases (&$self) -> &Vec>> { &$cb } - fn phrases_mut (&mut $self) -> &mut Vec>> { &mut$cb } + fn clips (&$self) -> &Vec>> { &$cb } + fn clips_mut (&mut $self) -> &mut Vec>> { &mut$cb } } } } @@ -30,23 +30,23 @@ impl Command for MidiPoolCommand { fn execute (self, model: &mut T) -> Perhaps { use MidiPoolCommand::*; Ok(match self { - Add(mut index, phrase) => { - let phrase = Arc::new(RwLock::new(phrase)); - let phrases = model.phrases_mut(); - if index >= phrases.len() { - index = phrases.len(); - phrases.push(phrase) + Add(mut index, clip) => { + let clip = Arc::new(RwLock::new(clip)); + let clips = model.clips_mut(); + if index >= clips.len() { + index = clips.len(); + clips.push(clip) } else { - phrases.insert(index, phrase); + clips.insert(index, clip); } Some(Self::Delete(index)) }, Delete(index) => { - let phrase = model.phrases_mut().remove(index).read().unwrap().clone(); - Some(Self::Add(index, phrase)) + let clip = model.clips_mut().remove(index).read().unwrap().clone(); + Some(Self::Add(index, clip)) }, Swap(index, other) => { - model.phrases_mut().swap(index, other); + model.clips_mut().swap(index, other); Some(Self::Swap(index, other)) }, Import(index, path) => { @@ -62,30 +62,30 @@ impl Command for MidiPoolCommand { } } } - let mut phrase = MidiClip::new("imported", true, t as usize + 1, None, None); + let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); for event in events.iter() { - phrase.notes[event.0 as usize].push(event.2); + clip.notes[event.0 as usize].push(event.2); } - Self::Add(index, phrase).execute(model)? + Self::Add(index, clip).execute(model)? }, Export(_index, _path) => { - todo!("export phrase to midi file"); + todo!("export clip to midi file"); }, SetName(index, name) => { - let mut phrase = model.phrases()[index].write().unwrap(); - let old_name = phrase.name.clone(); - phrase.name = name; + let mut clip = model.clips()[index].write().unwrap(); + let old_name = clip.name.clone(); + clip.name = name; Some(Self::SetName(index, old_name)) }, SetLength(index, length) => { - let mut phrase = model.phrases()[index].write().unwrap(); - let old_len = phrase.length; - phrase.length = length; + let mut clip = model.clips()[index].write().unwrap(); + let old_len = clip.length; + clip.length = length; Some(Self::SetLength(index, old_len)) }, SetColor(index, color) => { let mut color = ItemPalette::from(color); - std::mem::swap(&mut color, &mut model.phrases()[index].write().unwrap().color); + std::mem::swap(&mut color, &mut model.clips()[index].write().unwrap().color); Some(Self::SetColor(index, color.base)) }, }) diff --git a/midi/src/midi_select.rs b/midi/src/midi_select.rs index 0f79add0..81ac72f4 100644 --- a/midi/src/midi_select.rs +++ b/midi/src/midi_select.rs @@ -12,10 +12,10 @@ render!(TuiOut: (self: ClipSelected) => impl ClipSelected { - /// Shows currently playing phrase with beats elapsed - pub fn play_phrase (state: &T) -> Self { - let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { - let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); + /// Shows currently playing clip with beats elapsed + pub fn play_clip (state: &T) -> Self { + let (name, color) = if let Some((_, Some(clip))) = state.play_clip() { + let MidiClip { ref name, color, .. } = *clip.read().unwrap(); (name.clone().into(), color) } else { ("".to_string().into(), TuiTheme::g(64).into()) @@ -32,15 +32,15 @@ impl ClipSelected { } } - /// Shows next phrase with beats remaining until switchover - pub fn next_phrase (state: &T) -> Self { + /// Shows next clip with beats remaining until switchover + pub fn next_clip (state: &T) -> Self { let mut time: Arc = String::from("--.-.--").into(); let mut name: Arc = String::from("").into(); let mut color = ItemPalette::from(TuiTheme::g(64)); - if let Some((t, Some(phrase))) = state.next_phrase() { - let phrase = phrase.read().unwrap(); - name = phrase.name.clone(); - color = phrase.color.clone(); + if let Some((t, Some(clip))) = state.next_clip() { + let clip = clip.read().unwrap(); + name = clip.name.clone(); + color = clip.color.clone(); time = { let target = t.pulse.get(); let current = state.clock().playhead.pulse.get(); @@ -51,12 +51,12 @@ impl ClipSelected { String::new() } }.into() - } else if let Some((t, Some(phrase))) = state.play_phrase() { - let phrase = phrase.read().unwrap(); - if phrase.looped { - name = phrase.name.clone(); - color = phrase.color.clone(); - let target = t.pulse.get() + phrase.length as f64; + } else if let Some((t, Some(clip))) = state.play_clip() { + let clip = clip.read().unwrap(); + if clip.looped { + name = clip.name.clone(); + color = clip.color.clone(); + let target = t.pulse.get() + clip.length as f64; let current = state.clock().playhead.pulse.get(); if target > current { time = format!("-{:>}", state.clock().timebase.format_beats_0( diff --git a/midi/src/midi_view.rs b/midi/src/midi_view.rs index bc05973e..24357631 100644 --- a/midi/src/midi_view.rs +++ b/midi/src/midi_view.rs @@ -1,12 +1,12 @@ use crate::*; pub trait MidiViewer: HasSize + MidiRange + MidiPoint + Debug + Send + Sync { - fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize); + fn buffer_size (&self, clip: &MidiClip) -> (usize, usize); fn redraw (&self); - fn phrase (&self) -> &Option>>; - fn phrase_mut (&mut self) -> &mut Option>>; - fn set_phrase (&mut self, phrase: Option<&Arc>>) { - *self.phrase_mut() = phrase.cloned(); + fn clip (&self) -> &Option>>; + fn clip_mut (&mut self) -> &mut Option>>; + fn set_clip (&mut self, clip: Option<&Arc>>) { + *self.clip_mut() = clip.cloned(); self.redraw(); } /// Make sure cursor is within note range diff --git a/midi/src/piano_h.rs b/midi/src/piano_h.rs index dbe0f36f..f3984318 100644 --- a/midi/src/piano_h.rs +++ b/midi/src/piano_h.rs @@ -1,5 +1,42 @@ use crate::*; use super::*; +/// A clip, rendered as a horizontal piano roll. +pub struct PianoHorizontal { + pub clip: Option>>, + /// Buffer where the whole clip is rerendered on change + pub buffer: Arc>, + /// Size of actual notes area + pub size: Measure, + /// The display window + pub range: MidiRangeModel, + /// The note cursor + pub point: MidiPointModel, + /// The highlight color palette + pub color: ItemPalette, + /// Width of the keyboard + pub keys_width: u16, +} +impl PianoHorizontal { + pub fn new (clip: Option<&Arc>>) -> Self { + let size = Measure::new(); + let mut range = MidiRangeModel::from((24, true)); + range.time_axis = size.x.clone(); + range.note_axis = size.y.clone(); + let mut piano = Self { + keys_width: 5, + size, + range, + buffer: RwLock::new(Default::default()).into(), + point: MidiPointModel::default(), + clip: clip.cloned(), + color: clip.as_ref() + .map(|p|p.read().unwrap().color) + .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))), + }; + piano.redraw(); + piano + } +} 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)) } @@ -10,17 +47,22 @@ render!(TuiOut: (self: PianoHorizontal) => Bsp::s( // the freeze is in the piano )), Fill::xy(Bsp::e( Fixed::x(self.keys_width, PianoHorizontalKeys(self)), - Fill::xy(self.size.of(lay!(self.notes(), self.cursor()))), + Fill::xy(self.size.of("")), + //"", + //Fill::xy(self.size.of(lay!( + ////self.notes(), + ////self.cursor() + //))), )), )); impl PianoHorizontal { /// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄ - fn draw_bg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize, note_len: usize) { + fn draw_bg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize) { for (y, note) in (0..=127).rev().enumerate() { for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) { let cell = buf.get_mut(x, y).unwrap(); - cell.set_bg(phrase.color.darkest.rgb); - cell.set_fg(phrase.color.darker.rgb); + cell.set_bg(clip.color.darkest.rgb); + cell.set_fg(clip.color.darker.rgb); cell.set_char(if time % 384 == 0 { '│' } else if time % 96 == 0 { @@ -38,10 +80,10 @@ impl PianoHorizontal { } } /// Draw the piano roll background using full blocks on note on and half blocks on legato: █▄ █▄ █▄ - fn draw_fg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize) { - let style = Style::default().fg(phrase.color.base.rgb);//.bg(Color::Rgb(0, 0, 0)); + fn draw_fg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize) { + let style = Style::default().fg(clip.color.base.rgb);//.bg(Color::Rgb(0, 0, 0)); let mut notes_on = [false;128]; - for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() { + for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() { for (y, note) in (0..=127).rev().enumerate() { if let Some(cell) = buf.get_mut(x, note) { @@ -53,8 +95,8 @@ impl PianoHorizontal { } let time_end = time_start + zoom; - for time in time_start..time_end.min(phrase.length) { - for event in phrase.notes[time].iter() { + for time in time_start..time_end.min(clip.length) { + for event in clip.notes[time].iter() { match event { MidiMessage::NoteOn { key, .. } => { let note = key.as_int() as usize; @@ -80,31 +122,32 @@ impl PianoHorizontal { let note_hi = self.note_hi(); let note_point = self.note_point(); let buffer = self.buffer.clone(); - RenderThunk::new(move|render: &mut TuiOut|{ - let source = buffer.read().unwrap(); - let [x0, y0, w, h] = render.area().xywh(); - if h as usize != note_axis { - panic!("area height mismatch: {h} <> {note_axis}"); - } - for (area_x, screen_x) in (x0..x0+w).enumerate() { - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - let source_x = time_start + area_x; - let source_y = note_hi - area_y; - // TODO: enable loop rollover: - //let source_x = (time_start + area_x) % source.width.max(1); - //let source_y = (note_hi - area_y) % source.height.max(1); - let is_in_x = source_x < source.width; - let is_in_y = source_y < source.height; - if is_in_x && is_in_y { - if let Some(source_cell) = source.get(source_x, source_y) { - if let Some(cell) = render.buffer.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) { - *cell = source_cell.clone(); - } - } - } - } - } - }) + return "" + //RenderThunk::new(move|render: &mut TuiOut|{ + //let source = buffer.read().unwrap(); + //let [x0, y0, w, h] = render.area().xywh(); + //if h as usize != note_axis { + //panic!("area height mismatch: {h} <> {note_axis}"); + //} + //for (area_x, screen_x) in (x0..x0+w).enumerate() { + //for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + //let source_x = time_start + area_x; + //let source_y = note_hi - area_y; + //// TODO: enable loop rollover: + ////let source_x = (time_start + area_x) % source.width.max(1); + ////let source_y = (note_hi - area_y) % source.height.max(1); + //let is_in_x = source_x < source.width; + //let is_in_y = source_y < source.height; + //if is_in_x && is_in_y { + //if let Some(source_cell) = source.get(source_x, source_y) { + //if let Some(cell) = render.buffer.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) { + //*cell = source_cell.clone(); + //} + //} + //} + //} + //} + //}) } fn cursor (&self) -> impl Content { let style = Some(Style::default().fg(self.color.lightest.rgb)); @@ -115,27 +158,28 @@ impl PianoHorizontal { let time_point = self.time_point(); let time_start = self.time_start().get(); let time_zoom = self.time_zoom().get(); - RenderThunk::new(move|render: &mut TuiOut|{ - let [x0, y0, w, _] = render.area().xywh(); - for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - if note == note_point { - for x in 0..w { - let screen_x = x0 + x; - let time_1 = time_start + x as usize * time_zoom; - let time_2 = time_1 + time_zoom; - if time_1 <= time_point && time_point < time_2 { - render.blit(&"█", screen_x, screen_y, style); - let tail = note_len as u16 / time_zoom as u16; - for x_tail in (screen_x + 1)..(screen_x + tail) { - render.blit(&"▂", x_tail, screen_y, style); - } - break - } - } - break - } - } - }) + "" + //RenderThunk::new(move|render: &mut TuiOut|{ + ////let [x0, y0, w, _] = render.area().xywh(); + ////for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + ////if note == note_point { + ////for x in 0..w { + ////let screen_x = x0 + x; + ////let time_1 = time_start + x as usize * time_zoom; + ////let time_2 = time_1 + time_zoom; + ////if time_1 <= time_point && time_point < time_2 { + ////render.blit(&"█", screen_x, screen_y, style); + ////let tail = note_len as u16 / time_zoom as u16; + ////for x_tail in (screen_x + 1)..(screen_x + tail) { + ////render.blit(&"▂", x_tail, screen_y, style); + ////} + ////break + ////} + ////} + ////break + ////} + ////} + //}) } } @@ -163,35 +207,35 @@ impl TimePoint for PianoHorizontal { fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } } impl MidiViewer for PianoHorizontal { - fn phrase (&self) -> &Option>> { - &self.phrase + fn clip (&self) -> &Option>> { + &self.clip } - fn phrase_mut (&mut self) -> &mut Option>> { - &mut self.phrase + fn clip_mut (&mut self) -> &mut Option>> { + &mut self.clip } - /// Determine the required space to render the phrase. - fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) { - (phrase.length / self.range.time_zoom().get(), 128) + /// Determine the required space to render the clip. + fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { + (clip.length / self.range.time_zoom().get(), 128) } fn redraw (&self) { - let buffer = if let Some(phrase) = self.phrase.as_ref() { - let phrase = phrase.read().unwrap(); - let buf_size = self.buffer_size(&phrase); + let buffer = if let Some(clip) = self.clip.as_ref() { + let clip = clip.read().unwrap(); + let buf_size = self.buffer_size(&clip); let mut buffer = BigBuffer::from(buf_size); let note_len = self.note_len(); let time_zoom = self.time_zoom().get(); - self.time_len().set(phrase.length); - PianoHorizontal::draw_bg(&mut buffer, &phrase, time_zoom, note_len); - PianoHorizontal::draw_fg(&mut buffer, &phrase, time_zoom); + self.time_len().set(clip.length); + PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom, note_len); + PianoHorizontal::draw_fg(&mut buffer, &clip, time_zoom); buffer } else { Default::default() }; *self.buffer.write().unwrap() = buffer } - fn set_phrase (&mut self, phrase: Option<&Arc>>) { - *self.phrase_mut() = phrase.cloned(); - self.color = phrase.map(|p|p.read().unwrap().color) + fn set_clip (&mut self, clip: Option<&Arc>>) { + *self.clip_mut() = clip.cloned(); + self.color = clip.map(|p|p.read().unwrap().color) .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); self.redraw(); } @@ -208,12 +252,12 @@ impl std::fmt::Debug for PianoHorizontal { } // Update sequencer playhead indicator //self.now().set(0.); - //if let Some((ref started_at, Some(ref playing))) = self.player.play_phrase { - //let phrase = phrase.read().unwrap(); - //if *playing.read().unwrap() == *phrase { + //if let Some((ref started_at, Some(ref playing))) = self.player.play_clip { + //let clip = clip.read().unwrap(); + //if *playing.read().unwrap() == *clip { //let pulse = self.current().pulse.get(); //let start = started_at.pulse.get(); - //let now = (pulse - start) % phrase.length as f64; + //let now = (pulse - start) % clip.length as f64; //self.now().set(now); //} //} diff --git a/midi/src/piano_h_time.rs b/midi/src/piano_h_time.rs index 0fbd57e9..fcfd1f5b 100644 --- a/midi/src/piano_h_time.rs +++ b/midi/src/piano_h_time.rs @@ -5,7 +5,7 @@ pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal); render!(TuiOut: |self: PianoHorizontalTimeline<'a>, render|{ let [x, y, w, h] = render.area(); let style = Some(Style::default().dim()); - let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); + let length = self.0.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { let t = area_x as usize * self.0.time_zoom().get(); if t < length { diff --git a/output/src/measure.rs b/output/src/measure.rs index 1db15cca..aec48204 100644 --- a/output/src/measure.rs +++ b/output/src/measure.rs @@ -66,7 +66,7 @@ impl Measure { } } pub fn of > (&self, item: T) -> Bsp, T> { - Bsp::b(Fill::xy(&self), item) + Bsp::b(Fill::xy(self), item) } } diff --git a/tek/src/arranger.rs b/tek/src/arranger.rs index b85158c6..9a37b36b 100644 --- a/tek/src/arranger.rs +++ b/tek/src/arranger.rs @@ -42,15 +42,15 @@ audio!(|self: Arranger, client, scope|{ // FIXME: one of these per playing track //self.now.set(0.); //if let ArrangerSelection::Clip(t, s) = self.selected { - //let phrase = self.scenes.get(s).map(|scene|scene.clips.get(t)); - //if let Some(Some(Some(phrase))) = phrase { + //let clip = self.scenes.get(s).map(|scene|scene.clips.get(t)); + //if let Some(Some(Some(clip))) = clip { //if let Some(track) = self.tracks().get(t) { - //if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase { - //let phrase = phrase.read().unwrap(); - //if *playing.read().unwrap() == *phrase { + //if let Some((ref started_at, Some(ref playing))) = track.player.play_clip { + //let clip = clip.read().unwrap(); + //if *playing.read().unwrap() == *clip { //let pulse = self.current().pulse.get(); //let start = started_at.pulse.get(); - //let now = (pulse - start) % phrase.length as f64; + //let now = (pulse - start) % clip.length as f64; //self.now.set(now); //} //} @@ -62,7 +62,7 @@ audio!(|self: Arranger, client, scope|{ return Control::Continue }); has_clock!(|self: Arranger|&self.clock); -has_phrases!(|self: Arranger|self.pool.phrases); +has_clips!(|self: Arranger|self.pool.clips); has_editor!(|self: Arranger|self.editor); handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event())); impl Arranger { @@ -75,26 +75,26 @@ impl Arranger { pub fn activate (&mut self) -> Usually<()> { if let ArrangerSelection::Scene(s) = self.selected { for (t, track) in self.tracks.iter_mut().enumerate() { - let phrase = self.scenes[s].clips[t].clone(); - if track.player.play_phrase.is_some() || phrase.is_some() { - track.player.enqueue_next(phrase.as_ref()); + let clip = self.scenes[s].clips[t].clone(); + if track.player.play_clip.is_some() || clip.is_some() { + track.player.enqueue_next(clip.as_ref()); } } if self.clock().is_stopped() { self.clock().play_from(Some(0))?; } } else if let ArrangerSelection::Clip(t, s) = self.selected { - let phrase = self.scenes[s].clips[t].clone(); - self.tracks[t].player.enqueue_next(phrase.as_ref()); + let clip = self.scenes[s].clips[t].clone(); + self.tracks[t].player.enqueue_next(clip.as_ref()); }; Ok(()) } - pub fn selected_phrase (&self) -> Option>> { + pub fn selected_clip (&self) -> Option>> { self.selected_scene()?.clips.get(self.selected.track()?)?.clone() } pub fn toggle_loop (&mut self) { - if let Some(phrase) = self.selected_phrase() { - phrase.write().unwrap().toggle_loop() + if let Some(clip) = self.selected_clip() { + clip.write().unwrap().toggle_loop() } } pub fn randomize_color (&mut self) { @@ -102,8 +102,8 @@ impl Arranger { ArrangerSelection::Mix => { self.color = ItemPalette::random() }, ArrangerSelection::Track(t) => { self.tracks[t].color = ItemPalette::random() }, ArrangerSelection::Scene(s) => { self.scenes[s].color = ItemPalette::random() }, - ArrangerSelection::Clip(t, s) => if let Some(phrase) = &self.scenes[s].clips[t] { - phrase.write().unwrap().color = ItemPalette::random(); + ArrangerSelection::Clip(t, s) => if let Some(clip) = &self.scenes[s].clips[t] { + clip.write().unwrap().color = ItemPalette::random(); } } } diff --git a/tek/src/arranger/arranger_command.rs b/tek/src/arranger/arranger_command.rs index 06672ccb..316e9655 100644 --- a/tek/src/arranger/arranger_command.rs +++ b/tek/src/arranger/arranger_command.rs @@ -61,10 +61,10 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { key(Char(' ')) => Cmd::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }), // Transport: Play from start or rewind to start shift(key(Char(' '))) => Cmd::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }), - key(Char('e')) => Cmd::Editor(MidiEditCommand::Show(Some(state.pool.phrase().clone()))), + key(Char('e')) => Cmd::Editor(MidiEditCommand::Show(Some(state.pool.clip().clone()))), ctrl(key(Char('a'))) => Cmd::Scene(ArrangerSceneCommand::Add), ctrl(key(Char('t'))) => Cmd::Track(ArrangerTrackCommand::Add), - // Tab: Toggle visibility of phrase pool column + // Tab: Toggle visibility of clip pool column key(Tab) => Cmd::Phrases(PoolCommand::Show(!state.pool.visible)), }, { use ArrangerSelection as Selected; @@ -81,7 +81,7 @@ keymap!(KEYS_ARRANGER = |state: Arranger, input: Event| ArrangerCommand { kpat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), kpat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))), kpat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))), - kpat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.phrase().clone())))), + kpat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.pool.clip().clone())))), kpat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))), kpat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))), @@ -178,16 +178,16 @@ command!(|self: ArrangerCommand, state: Arranger|match self { }, Self::Phrases(cmd) => { match cmd { - // autoselect: automatically load selected phrase in editor + // autoselect: automatically load selected clip in editor PoolCommand::Select(_) => { let undo = cmd.delegate(&mut state.pool, Self::Phrases)?; - state.editor.set_phrase(Some(state.pool.phrase())); + state.editor.set_clip(Some(state.pool.clip())); undo }, - // reload phrase in editor to update color + // reload clip in editor to update color PoolCommand::Phrase(MidiPoolCommand::SetColor(index, _)) => { let undo = cmd.delegate(&mut state.pool, Self::Phrases)?; - state.editor.set_phrase(Some(state.pool.phrase())); + state.editor.set_clip(Some(state.pool.clip())); undo }, _ => cmd.delegate(&mut state.pool, Self::Phrases)? @@ -234,9 +234,9 @@ command!(|self: ArrangerSceneCommand, state: Arranger|match self { }); command!(|self: ArrangerClipCommand, state: Arranger|match self { Self::Get(track, scene) => { todo!() }, - Self::Put(track, scene, phrase) => { + Self::Put(track, scene, clip) => { let old = state.scenes[scene].clips[track].clone(); - state.scenes[scene].clips[track] = phrase; + state.scenes[scene].clips[track] = clip; Some(Self::Put(track, scene, old)) }, Self::Enqueue(track, scene) => { diff --git a/tek/src/arranger/arranger_scene.rs b/tek/src/arranger/arranger_scene.rs index e733a7d8..e1fb875b 100644 --- a/tek/src/arranger/arranger_scene.rs +++ b/tek/src/arranger/arranger_scene.rs @@ -56,22 +56,22 @@ impl ArrangerScene { pub fn longest_name (scenes: &[Self]) -> usize { scenes.iter().map(|s|s.name.len()).fold(0, usize::max) } - /// Returns the pulse length of the longest phrase in the scene + /// Returns the pulse length of the longest clip in the scene pub fn pulses (&self) -> usize { self.clips().iter().fold(0, |a, p|{ a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) }) } - /// Returns true if all phrases in the scene are + /// Returns true if all clips in the scene are /// currently playing on the given collection of tracks. pub fn is_playing (&self, tracks: &[ArrangerTrack]) -> bool { self.clips().iter().any(|clip|clip.is_some()) && self.clips().iter().enumerate() .all(|(track_index, clip)|match clip { - Some(clip) => tracks + Some(c) => tracks .get(track_index) .map(|track|{ - if let Some((_, Some(phrase))) = track.player().play_phrase() { - *phrase.read().unwrap() == *clip.read().unwrap() + if let Some((_, Some(clip))) = track.player().play_clip() { + *clip.read().unwrap() == *c.read().unwrap() } else { false } diff --git a/tek/src/arranger/arranger_tui.rs b/tek/src/arranger/arranger_tui.rs index 56118c5d..94f431b3 100644 --- a/tek/src/arranger/arranger_tui.rs +++ b/tek/src/arranger/arranger_tui.rs @@ -55,8 +55,8 @@ impl Arranger { let color: ItemPalette = track.color().dark.into(); let timebase = self.clock().timebase(); let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb, - if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() { - let length = phrase.read().unwrap().length; + if let Some((_, Some(clip))) = track.player.play_clip().as_ref() { + let length = clip.read().unwrap().length; let elapsed = track.player.pulses_since_start().unwrap() as usize; format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64)) } else { @@ -229,7 +229,7 @@ impl Arranger { { let timebase = ¤t.timebase; let mut result = String::new(); - if let Some((t, _)) = track.player.next_phrase().as_ref() { + if let Some((t, _)) = track.player.next_clip().as_ref() { let target = t.pulse.get(); let current = current.pulse.get(); if target > current { @@ -292,15 +292,15 @@ impl Arranger { fn cell_clip <'a> ( scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 ) -> impl Content + use<'a> { - scene.clips.get(index).map(|clip|clip.as_ref().map(|phrase|{ - let phrase = phrase.read().unwrap(); + scene.clips.get(index).map(|clip|clip.as_ref().map(|clip|{ + let clip = clip.read().unwrap(); let mut bg = TuiTheme::border_bg(); - let name = phrase.name.to_string(); + let name = clip.name.to_string(); let max_w = name.len().min((w as usize).saturating_sub(2)); - let color = phrase.color; + let color = clip.color; bg = color.dark.rgb; - if let Some((_, Some(ref playing))) = track.player.play_phrase() { - if *playing.read().unwrap() == *phrase { + if let Some((_, Some(ref playing))) = track.player.play_clip() { + if *playing.read().unwrap() == *clip { bg = color.light.rgb } }; @@ -312,8 +312,8 @@ impl Arranger { } fn pool_view (&self) -> impl Content + use<'_> { let w = self.size.w(); - let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let pool_w = if self.pool.visible { phrase_w } else { 0 }; + let clip_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + let pool_w = if self.pool.visible { clip_w } else { 0 }; let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))) } diff --git a/tek/src/groovebox/groovebox_command.rs b/tek/src/groovebox/groovebox_command.rs index 3c2390f0..0bc61109 100644 --- a/tek/src/groovebox/groovebox_command.rs +++ b/tek/src/groovebox/groovebox_command.rs @@ -15,21 +15,21 @@ pub enum GrooveboxCommand { } command!(|self: GrooveboxCommand, state: Groovebox|match self { - Self::Enqueue(phrase) => { - state.player.enqueue_next(phrase.as_ref()); + Self::Enqueue(clip) => { + state.player.enqueue_next(clip.as_ref()); None }, Self::Pool(cmd) => match cmd { - // autoselect: automatically load selected phrase in editor + // autoselect: automatically load selected clip in editor PoolCommand::Select(_) => { let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - state.editor.set_phrase(Some(state.pool.phrase())); + state.editor.set_clip(Some(state.pool.clip())); undo }, // update color in all places simultaneously PoolCommand::Phrase(SetColor(index, _)) => { let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - state.editor.set_phrase(Some(state.pool.phrase())); + state.editor.set_clip(Some(state.pool.clip())); undo }, _ => cmd.delegate(&mut state.pool, Self::Pool)? @@ -52,10 +52,10 @@ handle!(TuiIn: |self: Groovebox, input| keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { // Tab: Toggle compact mode key(Tab) => Cmd::Compact(!state.compact), - // q: Enqueue currently edited phrase - key(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())), - // 0: Enqueue phrase 0 (stop all) - key(Char('0')) => Cmd::Enqueue(Some(state.pool.phrases()[0].clone())), + // q: Enqueue currently edited clip + key(Char('q')) => Cmd::Enqueue(Some(state.pool.clip().clone())), + // 0: Enqueue clip 0 (stop all) + key(Char('0')) => Cmd::Enqueue(Some(state.pool.clips()[0].clone())), // TODO: k: toggle on-screen keyboard ctrl(key(Char('k'))) => todo!("keyboard"), // Transport: Play from start or rewind to start @@ -72,10 +72,10 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand shift(key(Delete)) => Cmd::Sampler( SamplerCommand::SetSample(u7::from(state.editor.note_point() as u8), None) ), - // e: Toggle between editing currently playing or other phrase - shift(key(Char('e'))) => if let Some((_, Some(playing))) = state.player.play_phrase() { - let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone()); - let selected = state.pool.phrase().clone(); + // e: Toggle between editing currently playing or other clip + shift(key(Char('e'))) => if let Some((_, Some(playing))) = state.player.play_clip() { + let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone()); + let selected = state.pool.clip().clone(); Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { selected } else { diff --git a/tek/src/pool.rs b/tek/src/pool.rs index 8a62d3e8..f8abaf5d 100644 --- a/tek/src/pool.rs +++ b/tek/src/pool.rs @@ -1,17 +1,16 @@ mod pool_tui; pub use self::pool_tui::*; mod clip_length; pub use self::clip_length::*; mod clip_rename; pub use self::clip_rename::*; -mod clip_select; pub use self::clip_select::*; use super::*; #[derive(Debug)] pub struct PoolModel { pub(crate) visible: bool, - /// Collection of phrases - pub(crate) phrases: Vec>>, - /// Selected phrase - pub(crate) phrase: AtomicUsize, + /// Collection of clips + pub(crate) clips: Vec>>, + /// Selected clip + pub(crate) clip: AtomicUsize, /// Mode switch pub(crate) mode: Option, /// Rendered size @@ -20,29 +19,29 @@ pub struct PoolModel { scroll: usize, } -/// Modes for phrase pool +/// Modes for clip pool #[derive(Debug, Clone)] pub enum PoolMode { /// Renaming a pattern Rename(usize, Arc), /// Editing the length of a pattern Length(usize, usize, PhraseLengthFocus), - /// Load phrase from disk + /// Load clip from disk Import(usize, FileBrowser), - /// Save phrase to disk + /// Save clip to disk Export(usize, FileBrowser), } #[derive(Clone, PartialEq, Debug)] pub enum PoolCommand { Show(bool), - /// Update the contents of the phrase pool + /// Update the contents of the clip pool Phrase(MidiPoolCommand), - /// Select a phrase from the phrase pool + /// Select a clip from the clip pool Select(usize), - /// Rename a phrase + /// Rename a clip Rename(PhraseRenameCommand), - /// Change the length of a phrase + /// Change the length of a clip Length(PhraseLengthCommand), /// Import from file Import(FileBrowserCommand), @@ -59,9 +58,9 @@ command!(|self:PoolCommand, state: PoolModel|{ } Rename(command) => match command { PhraseRenameCommand::Begin => { - let length = state.phrases()[state.phrase_index()].read().unwrap().length; - *state.phrases_mode_mut() = Some( - PoolMode::Length(state.phrase_index(), length, PhraseLengthFocus::Bar) + let length = state.clips()[state.clip_index()].read().unwrap().length; + *state.clips_mode_mut() = Some( + PoolMode::Length(state.clip_index(), length, PhraseLengthFocus::Bar) ); None }, @@ -69,9 +68,9 @@ command!(|self:PoolCommand, state: PoolModel|{ }, Length(command) => match command { PhraseLengthCommand::Begin => { - let name = state.phrases()[state.phrase_index()].read().unwrap().name.clone(); - *state.phrases_mode_mut() = Some( - PoolMode::Rename(state.phrase_index(), name) + let name = state.clips()[state.clip_index()].read().unwrap().name.clone(); + *state.clips_mode_mut() = Some( + PoolMode::Rename(state.clip_index(), name) ); None }, @@ -79,8 +78,8 @@ command!(|self:PoolCommand, state: PoolModel|{ }, Import(command) => match command { FileBrowserCommand::Begin => { - *state.phrases_mode_mut() = Some( - PoolMode::Import(state.phrase_index(), FileBrowser::new(None)?) + *state.clips_mode_mut() = Some( + PoolMode::Import(state.clip_index(), FileBrowser::new(None)?) ); None }, @@ -88,34 +87,34 @@ command!(|self:PoolCommand, state: PoolModel|{ }, Export(command) => match command { FileBrowserCommand::Begin => { - *state.phrases_mode_mut() = Some( - PoolMode::Export(state.phrase_index(), FileBrowser::new(None)?) + *state.clips_mode_mut() = Some( + PoolMode::Export(state.clip_index(), FileBrowser::new(None)?) ); None }, _ => command.execute(state)?.map(Export) }, - Select(phrase) => { - state.set_phrase_index(phrase); + Select(clip) => { + state.set_clip_index(clip); None }, Phrase(command) => command.execute(state)?.map(Phrase), } }); -input_to_command!(PoolCommand: |state: PoolModel, input: Event|match state.phrases_mode() { +input_to_command!(PoolCommand: |state: PoolModel, input: Event|match state.clips_mode() { Some(PoolMode::Rename(..)) => Self::Rename(PhraseRenameCommand::input_to_command(state, input)?), Some(PoolMode::Length(..)) => Self::Length(PhraseLengthCommand::input_to_command(state, input)?), Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?), Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?), - _ => to_phrases_command(state, input)? + _ => to_clips_command(state, input)? }); -fn to_phrases_command (state: &PoolModel, input: &Event) -> Option { +fn to_clips_command (state: &PoolModel, input: &Event) -> Option { use KeyCode::{Up, Down, Delete, Char}; use PoolCommand as Cmd; - let index = state.phrase_index(); - let count = state.phrases().len(); + let index = state.clip_index(); + let count = state.clips().len(); Some(match input { kpat!(Char('n')) => Cmd::Rename(PhraseRenameCommand::Begin), kpat!(Char('t')) => Cmd::Length(PhraseLengthCommand::Begin), @@ -123,25 +122,25 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option kpat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin), kpat!(Char('c')) => Cmd::Phrase(MidiPoolCommand::SetColor(index, ItemColor::random())), kpat!(Char('[')) | kpat!(Up) => Cmd::Select( - index.overflowing_sub(1).0.min(state.phrases().len() - 1) + index.overflowing_sub(1).0.min(state.clips().len() - 1) ), kpat!(Char(']')) | kpat!(Down) => Cmd::Select( - index.saturating_add(1) % state.phrases().len() + index.saturating_add(1) % state.clips().len() ), kpat!(Char('<')) => if index > 1 { - state.set_phrase_index(state.phrase_index().saturating_sub(1)); + state.set_clip_index(state.clip_index().saturating_sub(1)); Cmd::Phrase(MidiPoolCommand::Swap(index - 1, index)) } else { return None }, kpat!(Char('>')) => if index < count.saturating_sub(1) { - state.set_phrase_index(state.phrase_index() + 1); + state.set_clip_index(state.clip_index() + 1); Cmd::Phrase(MidiPoolCommand::Swap(index + 1, index)) } else { return None }, kpat!(Delete) => if index > 0 { - state.set_phrase_index(index.min(count.saturating_sub(1))); + state.set_clip_index(index.min(count.saturating_sub(1))); Cmd::Phrase(MidiPoolCommand::Delete(index)) } else { return None @@ -153,9 +152,9 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option "Clip", true, 4 * PPQ, None, Some(ItemPalette::random()) ))), kpat!(Char('d')) | kpat!(Shift-Char('D')) => { - let mut phrase = state.phrases()[index].read().unwrap().duplicate(); - phrase.color = ItemPalette::random_near(phrase.color, 0.25); - Cmd::Phrase(MidiPoolCommand::Add(index + 1, phrase)) + let mut clip = state.clips()[index].read().unwrap().duplicate(); + clip.color = ItemPalette::random_near(clip.color, 0.25); + Cmd::Phrase(MidiPoolCommand::Add(index + 1, clip)) }, _ => return None }) @@ -164,33 +163,33 @@ impl Default for PoolModel { fn default () -> Self { Self { visible: true, - phrases: vec![RwLock::new(MidiClip::default()).into()], - phrase: 0.into(), + clips: vec![RwLock::new(MidiClip::default()).into()], + clip: 0.into(), scroll: 0, mode: None, size: Measure::new(), } } } -from!(|phrase:&Arc>|PoolModel = { +from!(|clip:&Arc>|PoolModel = { let mut model = Self::default(); - model.phrases.push(phrase.clone()); - model.phrase.store(1, Relaxed); + model.clips.push(clip.clone()); + model.clip.store(1, Relaxed); model }); -has_phrases!(|self: PoolModel|self.phrases); -has_phrase!(|self: PoolModel|self.phrases[self.phrase_index()]); +has_clips!(|self: PoolModel|self.clips); +has_clip!(|self: PoolModel|self.clips[self.clip_index()]); impl PoolModel { - pub(crate) fn phrase_index (&self) -> usize { - self.phrase.load(Relaxed) + pub(crate) fn clip_index (&self) -> usize { + self.clip.load(Relaxed) } - pub(crate) fn set_phrase_index (&self, value: usize) { - self.phrase.store(value, Relaxed); + pub(crate) fn set_clip_index (&self, value: usize) { + self.clip.store(value, Relaxed); } - pub(crate) fn phrases_mode (&self) -> &Option { + pub(crate) fn clips_mode (&self) -> &Option { &self.mode } - pub(crate) fn phrases_mode_mut (&mut self) -> &mut Option { + pub(crate) fn clips_mode_mut (&mut self) -> &mut Option { &mut self.mode } pub fn file_picker (&self) -> Option<&FileBrowser> { diff --git a/tek/src/pool/clip_length.rs b/tek/src/pool/clip_length.rs index d9e54258..db24596b 100644 --- a/tek/src/pool/clip_length.rs +++ b/tek/src/pool/clip_length.rs @@ -4,14 +4,14 @@ use PhraseLengthFocus::*; use PhraseLengthCommand::*; use KeyCode::{Up, Down, Left, Right, Enter, Esc}; -/// Displays and edits phrase length. +/// Displays and edits clip length. #[derive(Clone)] pub struct PhraseLength { /// Pulses per beat (quaver) pub ppq: usize, /// Beats per bar pub bpb: usize, - /// Length of phrase in pulses + /// Length of clip in pulses pub pulses: usize, /// Selected subdivision pub focus: Option, @@ -97,9 +97,9 @@ pub enum PhraseLengthCommand { } command!(|self:PhraseLengthCommand,state:PoolModel|{ - match state.phrases_mode_mut().clone() { - Some(PoolMode::Length(phrase, ref mut length, ref mut focus)) => match self { - Cancel => { *state.phrases_mode_mut() = None; }, + match state.clips_mode_mut().clone() { + Some(PoolMode::Length(clip, ref mut length, ref mut focus)) => match self { + Cancel => { *state.clips_mode_mut() = None; }, Prev => { focus.prev() }, Next => { focus.next() }, Inc => match focus { @@ -113,11 +113,11 @@ command!(|self:PhraseLengthCommand,state:PoolModel|{ Tick => { *length = length.saturating_sub(1) }, }, Set(length) => { - let mut phrase = state.phrases()[phrase].write().unwrap(); - let old_length = phrase.length; - phrase.length = length; - std::mem::drop(phrase); - *state.phrases_mode_mut() = None; + let mut clip = state.clips()[clip].write().unwrap(); + let old_length = clip.length; + clip.length = length; + std::mem::drop(clip); + *state.clips_mode_mut() = None; return Ok(Some(Self::Set(old_length))) }, _ => unreachable!() @@ -128,7 +128,7 @@ command!(|self:PhraseLengthCommand,state:PoolModel|{ }); input_to_command!(PhraseLengthCommand: |state: PoolModel, input: Event|{ - if let Some(PoolMode::Length(_, length, _)) = state.phrases_mode() { + if let Some(PoolMode::Length(_, length, _)) = state.clips_mode() { match input { kpat!(Up) => Self::Inc, kpat!(Down) => Self::Dec, diff --git a/tek/src/pool/clip_rename.rs b/tek/src/pool/clip_rename.rs index ba027565..ff552f42 100644 --- a/tek/src/pool/clip_rename.rs +++ b/tek/src/pool/clip_rename.rs @@ -12,19 +12,19 @@ pub enum PhraseRenameCommand { impl Command for PhraseRenameCommand { fn execute (self, state: &mut PoolModel) -> Perhaps { use PhraseRenameCommand::*; - match state.phrases_mode_mut().clone() { - Some(PoolMode::Rename(phrase, ref mut old_name)) => match self { + match state.clips_mode_mut().clone() { + Some(PoolMode::Rename(clip, ref mut old_name)) => match self { Set(s) => { - state.phrases()[phrase].write().unwrap().name = s; + state.clips()[clip].write().unwrap().name = s; return Ok(Some(Self::Set(old_name.clone().into()))) }, Confirm => { let old_name = old_name.clone(); - *state.phrases_mode_mut() = None; + *state.clips_mode_mut() = None; return Ok(Some(Self::Set(old_name))) }, Cancel => { - state.phrases()[phrase].write().unwrap().name = old_name.clone().into(); + state.clips()[clip].write().unwrap().name = old_name.clone().into(); }, _ => unreachable!() }, @@ -37,7 +37,7 @@ impl Command for PhraseRenameCommand { impl InputToCommand for PhraseRenameCommand { fn input_to_command (state: &PoolModel, input: &Event) -> Option { use KeyCode::{Char, Backspace, Enter, Esc}; - if let Some(PoolMode::Rename(_, ref old_name)) = state.phrases_mode() { + if let Some(PoolMode::Rename(_, ref old_name)) = state.clips_mode() { Some(match input { kpat!(Char(c)) => { let mut new_name = old_name.clone().to_string(); diff --git a/tek/src/pool/clip_select.rs b/tek/src/pool/clip_select.rs deleted file mode 100644 index c7b7e813..00000000 --- a/tek/src/pool/clip_select.rs +++ /dev/null @@ -1 +0,0 @@ -use crate::*; diff --git a/tek/src/pool/pool_tui.rs b/tek/src/pool/pool_tui.rs index 4ed2a028..528885a4 100644 --- a/tek/src/pool/pool_tui.rs +++ b/tek/src/pool/pool_tui.rs @@ -3,14 +3,14 @@ use crate::*; pub struct PoolView<'a>(pub bool, pub &'a PoolModel); render!(TuiOut: (self: PoolView<'a>) => { let Self(compact, model) = self; - let PoolModel { phrases, mode, .. } = self.1; - let color = self.1.phrase().read().unwrap().color; + let PoolModel { clips, mode, .. } = self.1; + let color = self.1.clip().read().unwrap().color; Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, Outer( Style::default().fg(color.dark.rgb).bg(color.darkest.rgb) - ).enclose(Map::new(||model.phrases().iter(), |clip, i|{ + ).enclose(Map::new(||model.clips().iter(), |clip, i|{ let item_height = 1; let item_offset = i as u16 * item_height; - let selected = i == model.phrase_index(); + let selected = i == model.clip_index(); let MidiClip { ref name, color, length, .. } = *clip.read().unwrap(); let name = if *compact { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; let length = if *compact { String::default() } else { format!("{length} ") }; diff --git a/tek/src/sequencer.rs b/tek/src/sequencer.rs index 2540ac42..c4e8b4a4 100644 --- a/tek/src/sequencer.rs +++ b/tek/src/sequencer.rs @@ -51,8 +51,8 @@ impl Sequencer { } fn pool_view (&self) -> impl Content + use<'_> { let w = self.size.w(); - let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let pool_w = if self.pool.visible { phrase_w } else { 0 }; + let clip_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + let pool_w = if self.pool.visible { clip_w } else { 0 }; let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))) } @@ -70,7 +70,7 @@ impl Sequencer { double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), double(("a", "append"), ("s", "set note"),), double((",.", "length"), ("<>", "triplet"), ), - double(("[]", "phrase"), ("{}", "order"), ), + double(("[]", "clip"), ("{}", "order"), ), double(("q", "enqueue"), ("e", "edit"), ), double(("c", "color"), ("", ""),), )) @@ -95,7 +95,7 @@ audio!(|self:Sequencer, client, scope|{ }); has_size!(|self:Sequencer|&self.size); has_clock!(|self:Sequencer|&self.player.clock); -has_phrases!(|self:Sequencer|self.pool.phrases); +has_clips!(|self:Sequencer|self.pool.clips); has_editor!(|self:Sequencer|self.editor); handle!(TuiIn: |self:Sequencer,input|SequencerCommand::execute_with_state(self, input.event())); #[derive(Clone, Debug)] pub enum SequencerCommand { @@ -119,14 +119,14 @@ keymap!(KEYS_SEQUENCER = |state: Sequencer, input: Event| SequencerCommand { key(Char('U')) => Cmd::History( 1), // Tab: Toggle compact mode key(Tab) => Cmd::Compact(!state.compact), - // q: Enqueue currently edited phrase - key(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())), - // 0: Enqueue phrase 0 (stop all) - key(Char('0')) => Cmd::Enqueue(Some(state.phrases()[0].clone())), - // e: Toggle between editing currently playing or other phrase - key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() { - let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone()); - let selected = state.pool.phrase().clone(); + // q: Enqueue currently edited clip + key(Char('q')) => Cmd::Enqueue(Some(state.pool.clip().clone())), + // 0: Enqueue clip 0 (stop all) + key(Char('0')) => Cmd::Enqueue(Some(state.clips()[0].clone())), + // e: Toggle between editing currently playing or other clip + key(Char('e')) => if let Some((_, Some(playing))) = state.player.play_clip() { + let editing = state.editor.clip().as_ref().map(|p|p.read().unwrap().clone()); + let selected = state.pool.clip().clone(); Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { selected } else { @@ -143,21 +143,21 @@ keymap!(KEYS_SEQUENCER = |state: Sequencer, input: Event| SequencerCommand { return None }); command!(|self: SequencerCommand, state: Sequencer|match self { - Self::Enqueue(phrase) => { - state.player.enqueue_next(phrase.as_ref()); + Self::Enqueue(clip) => { + state.player.enqueue_next(clip.as_ref()); None }, Self::Pool(cmd) => match cmd { - // autoselect: automatically load selected phrase in editor + // autoselect: automatically load selected clip in editor PoolCommand::Select(_) => { let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - state.editor.set_phrase(Some(state.pool.phrase())); + state.editor.set_clip(Some(state.pool.clip())); undo }, // update color in all places simultaneously PoolCommand::Phrase(SetColor(index, _)) => { let undo = cmd.delegate(&mut state.pool, Self::Pool)?; - state.editor.set_phrase(Some(state.pool.phrase())); + state.editor.set_clip(Some(state.pool.clip())); undo }, _ => cmd.delegate(&mut state.pool, Self::Pool)?