diff --git a/demos/project.edn b/demos/project.edn index 0da335b2..1cd2f4b0 100644 --- a/demos/project.edn +++ b/demos/project.edn @@ -19,18 +19,21 @@ (:08 (36 128)) (:12 (36 100)) (:14 (36 110))) - (phrase { :name "D Beat" :beats 4 }) - ;(:00 (44 :100) (:34 :100) (:35 :100)) - ;(:02 (42 :100) ) - ;(:04 (42 :080) (:38 :100) ) - ;(:06 (44 :120) ) - ;(:08 (42 :100) (:34 :100) (:35 :100)) - ;(:10 (42 :100) (:34 :100) (:35 :100)) - ;(:12 (44 :100) (:40 :100) ) - ;(:13 (44 :100) ) - ;(:14 (44 :100) ) - ;(:15 (42 :100) ))) - (phrase { :name "Garage" :beats 4 } + (phrase { :name "D Beat" :beats 4 :steps 16 } + (:00 (36 128)) + (:00 (44 50)) + (:02 (44 30)) + (:04 (40 100)) + (:04 (44 80)) + (:06 (44 50)) + (:08 (36 100)) + (:08 (44 30)) + (:10 (36 100)) + (:10 (44 50)) + (:12 (40 100)) + (:12 (44 80)) + (:14 (44 50))) + (phrase { :name "Garage" :beats 4 :steps 16 } (:00 (44 100) (36 100) (35 100)) (:01 (44 100)) (:02 (44 100) (35 100)) diff --git a/src/model/phrase.rs b/src/model/phrase.rs index 12dfc765..294b8385 100644 --- a/src/model/phrase.rs +++ b/src/model/phrase.rs @@ -54,7 +54,7 @@ impl Phrase { pub fn process_out ( &self, output: &mut MIDIChunk, - notes_on: &mut Vec, + notes_on: &mut [bool;128], timebase: &Arc, (frame0, frames, _): (usize, usize, f64), ) { diff --git a/src/model/track.rs b/src/model/track.rs index 2e6da4a2..0f4ec6ed 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -16,15 +16,17 @@ pub struct Track { /// Output from current sequence. pub midi_out: Port, midi_out_buf: Vec>>, - /// Red keys on piano roll. - pub notes_on: Vec, /// Device chain pub devices: Vec, /// Device selector pub device: usize, /// Send all notes off /// FIXME: Some(nframes)? - pub reset: bool + pub reset: bool, + /// Highlight keys on piano roll. + pub notes_in: [bool;128], + /// Highlight keys on piano roll. + pub notes_out: [bool;128], } ports!(Track { audio: { @@ -46,7 +48,8 @@ impl Track { name: name.to_string(), midi_out: jack.register_port(name, MidiOut)?, midi_out_buf: vec![vec![];16384], - notes_on: vec![false;128], + notes_in: [false;128], + notes_out: [false;128], monitoring: false, recording: false, overdub: true, @@ -147,14 +150,13 @@ impl Track { ) { phrase.process_out( &mut self.midi_out_buf, - &mut self.notes_on, + &mut self.notes_out, timebase, (frame0.saturating_sub(start_frame), frames, period) ); // Monitor and record input if self.recording || self.monitoring { // For highlighting keys and note repeat - let notes_on = &mut self.notes_on; for (frame, event, bytes) in parse_midi_input(input) { match event { LiveEvent::Midi { message, .. } => { @@ -175,10 +177,10 @@ impl Track { } match message { MidiMessage::NoteOn { key, .. } => { - notes_on[key.as_int() as usize] = true; + self.notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { - notes_on[key.as_int() as usize] = false; + self.notes_in[key.as_int() as usize] = false; }, _ => {} } @@ -218,10 +220,10 @@ impl Track { fn process_monitor_message (&mut self, message: &MidiMessage) { match message { MidiMessage::NoteOn { key, .. } => { - self.notes_on[key.as_int() as usize] = true; + self.notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { - self.notes_on[key.as_int() as usize] = false; + self.notes_in[key.as_int() as usize] = false; }, _ => {} } diff --git a/src/view.rs b/src/view.rs index 2ed63f7e..92e1b43f 100644 --- a/src/view.rs +++ b/src/view.rs @@ -129,6 +129,7 @@ impl App { }.render(buf, area) } fn draw_phrase (&self, buf: &mut Buffer, area: Rect) -> Usually { + let track = self.tracks.get(self.track_cursor - 1); let phrase = self.phrase(); let seq_area = SequencerView { phrase, @@ -140,8 +141,10 @@ impl App { time_zoom: self.time_zoom, note_cursor: self.note_cursor, note_start: self.note_start, + notes_in: if let Some(track) = track { &track.notes_in } else { &[false;128] }, + notes_out: if let Some(track) = track { &track.notes_out } else { &[false;128] }, }.render(buf, area)?; - if let Some(track) = self.tracks.get(self.track_cursor - 1) { + if let Some(track) = track { if phrase.is_none() && self.section == AppSection::Sequencer { let label = format!("[ENTER] Create new clip: {}", track.name); let x = area.x + seq_area.width / 2 - (label.len() / 2) as u16; diff --git a/src/view/sequencer.rs b/src/view/sequencer.rs index c1d20b7a..7166e29e 100644 --- a/src/view/sequencer.rs +++ b/src/view/sequencer.rs @@ -20,7 +20,12 @@ pub struct SequencerView<'a> { /// Position of cursor within time range pub time_cursor: usize, /// Current time - pub now: usize + pub now: usize, + + /// Highlight input keys + pub notes_in: &'a [bool; 128], + /// Highlight output keys + pub notes_out: &'a [bool; 128], } impl<'a> Render for SequencerView<'a> { @@ -39,8 +44,7 @@ impl<'a> SequencerView<'a> { } else { Style::default().green().dim() }); - let notes = &[]; - self::horizontal::keys(buf, area, self.note_start, notes)?; + self::horizontal::keys(buf, area, self.note_start, self.notes_in, self.notes_out)?; let quant = ppq_to_name(self.time_zoom); quant.blit( buf, @@ -76,10 +80,17 @@ mod horizontal { } } - pub fn keys (buf: &mut Buffer, area: Rect, note0: usize, _notes: &[bool]) - -> Usually - { - let dim = Style::default().dim(); + pub fn keys ( + buf: &mut Buffer, + area: Rect, + note0: usize, + notes_in: &[bool;128], + notes_out: &[bool;128], + ) -> Usually { + let dim = Style::default().not_dim(); + let red = Style::default().red(); + let yellow = Style::default().yellow(); + let green = Style::default().green(); let mut cell_bg = Cell::default(); cell_bg.set_char('░'); @@ -102,12 +113,82 @@ mod horizontal { let Rect { x, y, width, height } = area; let height = height.min(128); let h = height.saturating_sub(2); + let index_to_color = |index: usize, default: Color| + if notes_in[index] && notes_out[index] { + Color::Yellow + } else if notes_in[index] { + Color::Red + } else if notes_out[index] { + Color::Green + } else { + default + }; + for index in 0..h { let y = y + h - index; - let key1 = buf.get_mut(x + 1, y); - *key1 = cell_keys[(index % 6) as usize].clone(); - let key2 = buf.get_mut(x + 2, y); - *key2 = cell_full.clone(); + match index % 6 { + 0 => { + let key1 = buf.get_mut(x + 1, y); + *key1 = cell_lo.clone(); + key1.set_fg(index_to_color(index as usize * 2, Color::White)); + key1.set_bg(index_to_color(index as usize * 2 + 1, Color::Black)); + let key2 = buf.get_mut(x + 2, y); + *key2 = cell_lo.clone(); + key2.set_fg(index_to_color(index as usize * 2, Color::White)); + key2.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + }, + 1 => { + let key1 = buf.get_mut(x + 1, y); + *key1 = cell_lo.clone(); + key1.set_fg(index_to_color(index as usize * 2, Color::White)); + key1.set_bg(index_to_color(index as usize * 2 + 1, Color::Black)); + let key2 = buf.get_mut(x + 2, y); + *key2 = cell_lo.clone(); + key2.set_fg(index_to_color(index as usize * 2, Color::White)); + key2.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + }, + 2 => { + let key1 = buf.get_mut(x + 1, y); + *key1 = cell_lo.clone(); + key1.set_fg(index_to_color(index as usize * 2, Color::White)); + key1.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + let key2 = buf.get_mut(x + 2, y); + *key2 = cell_lo.clone(); + key2.set_fg(index_to_color(index as usize * 2, Color::White)); + key2.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + }, + 3 => { + let key1 = buf.get_mut(x + 1, y); + *key1 = cell_lo.clone(); + key1.set_fg(index_to_color(index as usize * 2, Color::Black)); + key1.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + let key2 = buf.get_mut(x + 2, y); + *key2 = cell_lo.clone(); + key2.set_fg(index_to_color(index as usize * 2, Color::White)); + key2.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + }, + 4 => { + let key1 = buf.get_mut(x + 1, y); + *key1 = cell_lo.clone(); + key1.set_fg(index_to_color(index as usize * 2, Color::Black)); + key1.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + let key2 = buf.get_mut(x + 2, y); + *key2 = cell_lo.clone(); + key2.set_fg(index_to_color(index as usize * 2, Color::White)); + key2.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + }, + 5 => { + let key1 = buf.get_mut(x + 1, y); + *key1 = cell_lo.clone(); + key1.set_fg(index_to_color(index as usize * 2, Color::Black)); + key1.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + let key2 = buf.get_mut(x + 2, y); + *key2 = cell_lo.clone(); + key2.set_fg(index_to_color(index as usize * 2, Color::White)); + key2.set_bg(index_to_color(index as usize * 2 + 1, Color::White)); + }, + _ => { unreachable!(); } + } for x in x+5..x+width-1 { *buf.get_mut(x, y) = cell_bg.clone(); }