use crate::*; use super::*; 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)) } render!(Tui: (self: PianoHorizontal) => { 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) } else { (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) }; //let field = move|x, y|row!( //Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), //Tui::fg_bg(color.lighter.rgb, color.dark.rgb, y), //); let keys_width = 5; let keys = move||PianoHorizontalKeys(self); let timeline = move||PianoHorizontalTimeline(self); let notes = move||PianoHorizontalNotes(self); let cursor = move||PianoHorizontalCursor(self); Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb)).enclose("kyp"); //with_border(lay!( //Push::x(0, row!( ////" ", //field("Edit:", name.to_string()), " ", //field("Length:", length.to_string()), " ", //field("Loop:", looped.to_string()) //)), //Padding::xy(0, 1, Fill::xy(Bsp::s( //Fixed::y(1, Bsp::e( //Fixed::x(self.keys_width, ""), //Fill::x(timeline()), //)), //Bsp::e( //Fixed::x(self.keys_width, keys()), //Fill::xy(lay!( //&self.size, //Fill::xy(lay!( //Fill::xy(notes()), //Fill::xy(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) { 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_char(if time % 384 == 0 { '│' } else if time % 96 == 0 { '╎' } else if time % note_len == 0 { '┊' } else if (127 - note) % 12 == 1 { '=' } else { '·' }); } } } /// 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)); let mut notes_on = [false;128]; for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() { for (y, note) in (0..127).rev().enumerate() { if let Some(cell) = buf.get_mut(x, note) { if notes_on[note] { cell.set_char('▂'); cell.set_style(style); } } } let time_end = time_start + zoom; for time in time_start..time_end.min(phrase.length) { for event in phrase.notes[time].iter() { match event { MidiMessage::NoteOn { key, .. } => { let note = key.as_int() as usize; let cell = buf.get_mut(x, note).unwrap(); cell.set_char('█'); cell.set_style(style); notes_on[note] = true }, MidiMessage::NoteOff { key, .. } => { notes_on[key.as_int() as usize] = false }, _ => {} } } } } } } has_size!(|self:PianoHorizontal|&self.size); impl TimeRange for PianoHorizontal { fn time_len (&self) -> &AtomicUsize { self.range.time_len() } fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() } fn time_lock (&self) -> &AtomicBool { self.range.time_lock() } fn time_start (&self) -> &AtomicUsize { self.range.time_start() } fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() } } impl NoteRange for PianoHorizontal { fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() } fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() } } impl NotePoint for PianoHorizontal { fn note_len (&self) -> usize { self.point.note_len() } fn set_note_len (&self, x: usize) { self.point.set_note_len(x) } fn note_point (&self) -> usize { self.point.note_point() } fn set_note_point (&self, x: usize) { self.point.set_note_point(x) } } impl TimePoint for PianoHorizontal { fn time_point (&self) -> usize { self.point.time_point() } fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } } impl PhraseViewMode for PianoHorizontal { fn phrase (&self) -> &Option>> { &self.phrase } fn phrase_mut (&mut self) -> &mut Option>> { &mut self.phrase } /// Determine the required space to render the phrase. fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) { (phrase.length / self.range.time_zoom().get(), 128) } fn redraw (&mut self) { let buffer = if let Some(phrase) = self.phrase.as_ref() { let phrase = phrase.read().unwrap(); let buf_size = self.buffer_size(&phrase); 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); buffer } else { Default::default() }; self.buffer = buffer } fn set_phrase (&mut self, phrase: Option<&Arc>>) { *self.phrase_mut() = phrase.cloned(); self.color = phrase.map(|p|p.read().unwrap().color) .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); self.redraw(); } } impl std::fmt::Debug for PianoHorizontal { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("PianoHorizontal") .field("time_zoom", &self.range.time_zoom) .field("buffer", &format!("{}x{}", self.buffer.width, self.buffer.height)) .finish() } } // 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 { //let pulse = self.current().pulse.get(); //let start = started_at.pulse.get(); //let now = (pulse - start) % phrase.length as f64; //self.now().set(now); //} //}