diff --git a/Justfile b/Justfile index 9db61252..ac4de6d5 100644 --- a/Justfile +++ b/Justfile @@ -4,3 +4,6 @@ status: cargo c cloc --by-file src/ git status +push: + git push -u codeberg main + git push -u origin main diff --git a/src/control.rs b/src/control.rs index 386be6cc..604384c0 100644 --- a/src/control.rs +++ b/src/control.rs @@ -78,11 +78,11 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding] = keymap!(App { Ok(true) }], [Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| { - app.sequencer.time_zoom = prev_note_length(app.sequencer.time_zoom); + app.sequencer.time_axis.scale_mut(&prev_note_length); Ok(true) }], [Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| { - app.sequencer.time_zoom = next_note_length(app.sequencer.time_zoom); + app.sequencer.time_axis.scale_mut(&next_note_length); Ok(true) }], [Char('x'), NONE, "extend", "double the current clip", |app: &mut App| { diff --git a/src/control/arranger.rs b/src/control/arranger.rs index cce4b0e2..92295d50 100644 --- a/src/control/arranger.rs +++ b/src/control/arranger.rs @@ -11,7 +11,7 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding] = keymap!(App { false => app.arranger.scene_prev(), true => app.arranger.track_prev(), }; - app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + app.sequencer.show(app.arranger.phrase())?; Ok(true) }], [Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| { @@ -19,7 +19,7 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding] = keymap!(App { false => app.arranger.scene_next(), true => app.arranger.track_next(), }; - app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + app.sequencer.show(app.arranger.phrase())?; Ok(true) }], [Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| { @@ -27,7 +27,7 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding] = keymap!(App { false => app.arranger.track_prev(), true => app.arranger.scene_prev(), }; - app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + app.sequencer.show(app.arranger.phrase())?; Ok(true) }], [Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| { @@ -35,7 +35,7 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding] = keymap!(App { false => app.arranger.track_next(), true => app.arranger.scene_next() }; - app.sequencer.phrase = app.arranger.phrase().map(Clone::clone); + app.sequencer.show(app.arranger.phrase())?; Ok(true) }], [Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| { diff --git a/src/control/sequencer.rs b/src/control/sequencer.rs index 5f8d0e16..f640cba7 100644 --- a/src/control/sequencer.rs +++ b/src/control/sequencer.rs @@ -3,26 +3,30 @@ use crate::{core::*, model::App}; /// Key bindings for phrase editor. pub const KEYMAP_SEQUENCER: &'static [KeyBinding] = keymap!(App { [Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| { - app.sequencer.note_cursor = app.sequencer.note_cursor.saturating_sub(1); + match app.sequencer.entered { + true => { app.sequencer.note_axis.point_dec(); }, + false => { app.sequencer.note_axis.start_dec(); }, + } Ok(true) }], - [Down, NONE, "seq_cursor_down", "move cursor up", |app: &mut App| { - app.sequencer.note_cursor = app.sequencer.note_cursor + 1; + [Down, NONE, "seq_cursor_down", "move cursor down", |app: &mut App| { + match app.sequencer.entered { + true => { app.sequencer.note_axis.point_inc(); }, + false => { app.sequencer.note_axis.start_inc(); }, + } Ok(true) }], [Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| { - if app.sequencer.entered { - app.sequencer.time_cursor = app.sequencer.time_cursor.saturating_sub(1); - } else { - app.sequencer.time_start = app.sequencer.time_start.saturating_sub(1); + match app.sequencer.entered { + true => { app.sequencer.time_axis.point_dec(); }, + false => { app.sequencer.time_axis.start_dec(); }, } Ok(true) }], [Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| { - if app.sequencer.entered { - app.sequencer.time_cursor = app.sequencer.time_cursor + 1; - } else { - app.sequencer.time_start = app.sequencer.time_start + 1; + match app.sequencer.entered { + true => { app.sequencer.time_axis.point_inc(); }, + false => { app.sequencer.time_axis.start_inc(); }, } Ok(true) }], @@ -35,4 +39,3 @@ pub const KEYMAP_SEQUENCER: &'static [KeyBinding] = keymap!(App { // [CapsLock, NONE, "advance", "Toggle auto advance", nop], // [Char('w'), NONE, "rest", "Advance by note duration", nop], }); - diff --git a/src/core.rs b/src/core.rs index faa9782a..d34ceed4 100644 --- a/src/core.rs +++ b/src/core.rs @@ -80,7 +80,7 @@ pub fn run (state: Arc>) -> Usually>> terminal_setup()?; panic_hook_setup(); let main_thread = main_thread(&exited, &state)?; - main_thread.join().unwrap(); + main_thread.join().expect("main thread failed"); terminal_teardown()?; Ok(state) } diff --git a/src/core/midi.rs b/src/core/midi.rs index daa0177d..c9db6570 100644 --- a/src/core/midi.rs +++ b/src/core/midi.rs @@ -33,8 +33,8 @@ pub fn write_midi_output (writer: &mut ::jack::MidiWriter, output: &MIDIChunk, f } } -/// (ppq, name) -pub const NOTE_DURATIONS: [(usize, &str);26] = [ +/// (pulses, name) +pub const NOTE_DURATIONS: [(u16, &str);26] = [ (1, "1/384"), (2, "1/192"), (3, "1/128"), @@ -63,16 +63,7 @@ pub const NOTE_DURATIONS: [(usize, &str);26] = [ (6144, "16/1"), ]; -pub fn ppq_to_name (ppq: usize) -> &'static str { - for (length, name) in &NOTE_DURATIONS { - if *length == ppq { - return name - } - } - "" -} - -pub fn prev_note_length (ppq: usize) -> usize { +pub fn prev_note_length (ppq: u16) -> u16 { for i in 1..=16 { let length = NOTE_DURATIONS[16-i].0; if length < ppq { @@ -82,7 +73,7 @@ pub fn prev_note_length (ppq: usize) -> usize { ppq } -pub fn next_note_length (ppq: usize) -> usize { +pub fn next_note_length (ppq: u16) -> u16 { for (length, _) in &NOTE_DURATIONS { if *length > ppq { return *length @@ -90,3 +81,12 @@ pub fn next_note_length (ppq: usize) -> usize { } ppq } + +pub fn ppq_to_name (ppq: u16) -> &'static str { + for (length, name) in &NOTE_DURATIONS { + if *length == ppq { + return name + } + } + "" +} diff --git a/src/core/render.rs b/src/core/render.rs index 50a19896..908e6003 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -2,49 +2,35 @@ use crate::core::*; pub(crate) use ratatui::prelude::CrosstermBackend; pub(crate) use ratatui::style::{Stylize, Style, Color}; pub(crate) use ratatui::layout::Rect; -pub(crate) use ratatui::buffer::Buffer; +pub(crate) use ratatui::buffer::{Buffer, Cell}; use ratatui::widgets::WidgetRef; -pub fn fill_fg (buf: &mut Buffer, area: Rect, color: Color) { - let Rect { x, y, width, height } = area; - for y in y..y+height { - if y >= buf.area.height { - break - } - for x in x..x+width { - if x >= buf.area.width { - break - } - buf.get_mut(x, y).set_fg(color); +pub fn buffer_update ( + buffer: &mut Buffer, area: Rect, callback: &impl Fn(&mut Cell, u16, u16) +) { + for row in 0..area.height { + let y = area.y + row; + for col in 0..area.width { + let x = area.x + col; + callback(buffer.get_mut(x, y), col, row); } } } +pub fn fill_fg (buf: &mut Buffer, area: Rect, color: Color) { + buffer_update(buf, area, &|cell,_,_|{cell.set_fg(color);}) +} pub fn fill_bg (buf: &mut Buffer, area: Rect, color: Color) { - let Rect { x, y, width, height } = area; - for y in y..y+height { - if y >= buf.area.height { - break - } - for x in x..x+width { - if x >= buf.area.width { - break - } - buf.get_mut(x, y).set_bg(color); - } - } + buffer_update(buf, area, &|cell,_,_|{cell.set_bg(color);}) } pub fn fill_char (buf: &mut Buffer, area: Rect, c: char) { - let Rect { x, y, width, height } = area; - for y in y..y+height { - if y >= buf.area.height { - break - } - for x in x..x+width { - if x >= buf.area.width { - break - } - buf.get_mut(x, y).set_char(c); - } + buffer_update(buf, area, &|cell,_,_|{cell.set_char(c);}) +} +pub fn half_block (lower: bool, upper: bool) -> Option { + match (lower, upper) { + (true, true) => Some('█'), + (true, false) => Some('▄'), + (false, true) => Some('▀'), + _ => None } } diff --git a/src/model.rs b/src/model.rs index 193f5bae..99be44a6 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,6 +1,6 @@ //! Application state. -submod! { arranger looper mixer phrase plugin sampler sequencer scene track transport } +submod! { axis arranger looper mixer phrase plugin sampler sequencer scene track transport } use crate::core::*; @@ -71,7 +71,7 @@ process!(App |self, _client, scope| { &self.transport.timebase, self.transport.playing, self.transport.started, - self.transport.quant, + self.transport.quant as usize, reset, &scope, (current_frames as usize, self.chunk_size), diff --git a/src/model/axis.rs b/src/model/axis.rs new file mode 100644 index 00000000..2a66be00 --- /dev/null +++ b/src/model/axis.rs @@ -0,0 +1,33 @@ +macro_rules! impl_axis_common { ($A:ident $T:ty) => { + impl $A<$T> { + pub fn start_inc (&mut self) -> $T { + self.start = self.start + 1; + self.start + } + pub fn start_dec (&mut self) -> $T { + self.start = self.start.saturating_sub(1); + self.start + } + pub fn point_inc (&mut self) -> Option<$T> { + self.point = self.point.map(|p|p + 1); + self.point + } + pub fn point_dec (&mut self) -> Option<$T> { + self.point = self.point.map(|p|p.saturating_sub(1)); + self.point + } + } +} } + +pub struct FixedAxis { pub start: T, pub point: Option } +impl_axis_common!(FixedAxis u16); +impl_axis_common!(FixedAxis usize); + +pub struct ScaledAxis { pub start: T, pub scale: T, pub point: Option } +impl_axis_common!(ScaledAxis u16); +impl_axis_common!(ScaledAxis usize); +impl ScaledAxis { + pub fn scale_mut (&mut self, cb: &impl Fn(T)->T) { + self.scale = cb(self.scale) + } +} diff --git a/src/model/phrase.rs b/src/model/phrase.rs index af2b1fcf..f9a06299 100644 --- a/src/model/phrase.rs +++ b/src/model/phrase.rs @@ -19,7 +19,9 @@ pub struct Phrase { pub name: String, pub length: usize, pub notes: PhraseData, - pub looped: Option<(usize, usize)> + pub looped: Option<(usize, usize)>, + /// Immediate note-offs in view + pub percussive: bool } impl Default for Phrase { @@ -34,7 +36,8 @@ impl Phrase { name: name.to_string(), length, notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), - looped: Some((0, length)) + looped: Some((0, length)), + percussive: true, } } pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { diff --git a/src/model/sequencer.rs b/src/model/sequencer.rs index bfe18082..1bd5096f 100644 --- a/src/model/sequencer.rs +++ b/src/model/sequencer.rs @@ -1,46 +1,160 @@ -use crate::{core::*, model::Phrase}; +use crate::{core::*, model::{FixedAxis, ScaledAxis, Phrase}}; pub struct Sequencer { - pub phrase: Option>>, - pub mode: bool, - pub buffer: Buffer, - pub now: usize, - pub ppq: usize, - pub note_cursor: usize, - pub note_start: usize, - pub time_cursor: usize, - pub time_start: usize, - pub time_zoom: usize, - - pub focused: bool, - pub entered: bool, + pub mode: bool, + pub focused: bool, + pub entered: bool, + pub phrase: Option>>, + pub buffer: Buffer, + pub keys: Buffer, /// Highlight input keys - pub notes_in: [bool; 128], + pub keys_in: [bool; 128], /// Highlight output keys - pub notes_out: [bool; 128], + pub keys_out: [bool; 128], + + pub now: usize, + pub ppq: usize, + pub note_axis: FixedAxis, + pub time_axis: ScaledAxis, + } impl Sequencer { pub fn new () -> Self { Self { - buffer: Buffer::empty(Rect::default()), - entered: false, - focused: false, - mode: false, - note_cursor: 0, - note_start: 0, - notes_in: [false;128], - notes_out: [false;128], - phrase: None, - time_cursor: 0, - time_start: 0, - time_zoom: 12, - now: 0, - ppq: 96 + buffer: Buffer::empty(Rect::default()), + keys: keys_vert(), + entered: false, + focused: false, + mode: false, + keys_in: [false;128], + keys_out: [false;128], + phrase: None, + now: 0, + ppq: 96, + note_axis: FixedAxis { + start: 12, + point: Some(36) + }, + time_axis: ScaledAxis { + start: 0, + scale: 24, + point: Some(0) + }, } } - pub fn show (&mut self, phrase: Option<&Arc>>) { + /// Select which pattern to display. This pre-renders it to the buffer at full resolution. + /// FIXME: Support phrases longer that 65536 ticks + pub fn show (&mut self, phrase: Option<&Arc>>) -> Usually<()> { self.phrase = phrase.map(Clone::clone); + if let Some(ref phrase) = self.phrase { + let width = u16::MAX.min(phrase.read().unwrap().length as u16); + let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height: 64 }); + let phrase = phrase.read().unwrap(); + fill_seq_bg(&mut buffer, phrase.length, self.ppq)?; + fill_seq_fg(&mut buffer, &phrase)?; + self.buffer = buffer; + } else { + self.buffer = Buffer::empty(Rect::default()) + } + Ok(()) } } + +fn keys_vert () -> Buffer { + let area = Rect { x: 0, y: 0, width: 5, height: 64 }; + let mut buffer = Buffer::empty(area); + buffer_update(&mut buffer, area, &|cell, x, y| { + cell.set_char('▀'); + match x { + 0 => { + let (fg, bg) = key_colors(y); + cell.set_fg(fg); + cell.set_bg(bg); + }, + 1 => { + cell.set_fg(Color::White); + cell.set_bg(Color::White); + } + _ => {} + } + }); + buffer +} + +fn key_colors (index: u16) -> (Color, Color) { + match index % 6 { + 0 => (Color::White, Color::Black), + 1 => (Color::White, Color::Black), + 2 => (Color::White, Color::White), + 3 => (Color::Black, Color::White), + 4 => (Color::Black, Color::White), + 5 => (Color::Black, Color::White), + _ => unreachable!() + } +} + +fn fill_seq_bg (buf: &mut Buffer, length: usize, ppq: usize) -> Usually<()> { + for x in 0 .. buf.area.width - buf.area.x { + if x as usize >= length { + break + } + let style = Style::default(); + let cell = buf.get_mut(x, buf.area.y); + cell.set_char('-'); + cell.set_style(style); + let character = if ppq > 0 && x as usize % ppq == 0 { '|' } else { '·' }; + for y in 0 .. buf.area.height - buf.area.y { + let cell = buf.get_mut(x, y); + cell.set_char(character); + cell.set_fg(Color::Gray); + } + } + Ok(()) +} + +fn fill_seq_fg (buf: &mut Buffer, phrase: &Phrase) -> Usually<()> { + let mut notes_on = [false;128]; + for x in 0 .. buf.area.width - buf.area.x { + if x as usize >= phrase.length { + break + } + if let Some(notes) = phrase.notes.get(x as usize) { + for note in notes { + if phrase.percussive { + match note { + MidiMessage::NoteOn { key, .. } => + notes_on[key.as_int() as usize] = true, + _ => {} + } + } else { + match note { + MidiMessage::NoteOn { key, .. } => + notes_on[key.as_int() as usize] = true, + MidiMessage::NoteOff { key, .. } => + notes_on[key.as_int() as usize] = false, + _ => {} + } + } + } + for y in 0 .. (buf.area.height - buf.area.y) / 2 { + if y >= 64 { + break + } + if let Some(block) = half_block( + notes_on[y as usize * 2], + notes_on[y as usize * 2 + 1], + ) { + let cell = buf.get_mut(x, y); + cell.set_char(block); + cell.set_fg(Color::White); + } + } + if phrase.percussive { + notes_on.fill(false); + } + } + } + Ok(()) +} diff --git a/src/model/track.rs b/src/model/track.rs index b0565a02..4eb7153a 100644 --- a/src/model/track.rs +++ b/src/model/track.rs @@ -123,7 +123,7 @@ impl Track { (frame0.saturating_sub(start_frame), frames, period) ); } - }); + }).unwrap(); let mut phrase = phrase.write().unwrap(); let length = phrase.length; // Monitor and record input diff --git a/src/model/transport.rs b/src/model/transport.rs index 3bc43182..3973dc98 100644 --- a/src/model/transport.rs +++ b/src/model/transport.rs @@ -33,9 +33,9 @@ pub struct TransportToolbar { /// JACK transport handle. transport: Option, /// Quantization factor - pub quant: usize, + pub quant: u16, /// Global sync quant - pub sync: usize, + pub sync: u16, /// Current transport state pub playing: Option, /// Current position according to transport @@ -56,7 +56,7 @@ impl TransportToolbar { playing: Some(TransportState::Stopped), started: None, quant: 24, - sync: timebase.ppq() as usize * 4, + sync: timebase.ppq() as u16 * 4, transport, timebase, } diff --git a/src/view/sequencer.rs b/src/view/sequencer.rs index 5604daf8..1e9e03bc 100644 --- a/src/view/sequencer.rs +++ b/src/view/sequencer.rs @@ -24,12 +24,13 @@ impl Sequencer { Style::default() } } - fn index_to_color (&self, index: usize, default: Color) -> Color { - if self.notes_in[index] && self.notes_out[index] { + fn index_to_color (&self, index: u16, default: Color) -> Color { + let index = index as usize; + if self.keys_in[index] && self.keys_out[index] { Color::Yellow - } else if self.notes_in[index] { + } else if self.keys_in[index] { Color::Red - } else if self.notes_out[index] { + } else if self.keys_out[index] { Color::Green } else { default @@ -44,166 +45,77 @@ impl Sequencer { fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> { self.horizontal_keys(buf, area)?; - self.horizontal_quant(buf, area)?; - self.horizontal_timer(buf, area, &self.phrase)?; - self.horizontal_lanes(buf, area, &self.phrase)?; + if let Some(ref phrase) = self.phrase { + self.horizontal_timer(buf, area, phrase)?; + } + self.horizontal_notes(buf, area)?; self.horizontal_cursor(buf, area)?; + self.horizontal_quant(buf, area)?; Ok(()) } + fn horizontal_notes (&self, buf: &mut Buffer, area: Rect) -> Usually { + let area = Rect { + x: area.x + Self::H_KEYS_OFFSET, + y: area.y + 1, + width: area.width - Self::H_KEYS_OFFSET, + height: area.height - 1 + }; + buffer_update(buf, area, &|cell, x, y|{ + let src_x = (x + self.time_axis.start) * self.time_axis.scale; + let src_y = y + self.note_axis.start; + if src_x < self.buffer.area.width && src_y < self.buffer.area.height { + let src = self.buffer.get(src_x, src_y); + cell.set_symbol(src.symbol()); + cell.set_fg(src.fg); + } + }); + Ok(area) + } + + fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually { + let area = Rect { + x: area.x, + y: area.y + 1, + width: 2, + height: area.height - 2 + }; + buffer_update(buf, area, &|cell, x, y|{ + *cell = self.keys.get(x, y % 6).clone() + }); + Ok(area) + } + fn horizontal_quant (&self, buf: &mut Buffer, area: Rect) -> Usually { - let quant = ppq_to_name(self.time_zoom); + let quant = ppq_to_name(self.time_axis.scale); let quant_x = area.x + area.width - 1 - quant.len() as u16; let quant_y = area.y + area.height - 2; quant.blit(buf, quant_x, quant_y, self.style_focus()) } fn horizontal_cursor (&self, buf: &mut Buffer, area: Rect) -> Usually { - let (time, note) = (self.time_cursor, self.note_cursor); - let x = area.x + Self::H_KEYS_OFFSET + time as u16; - let y = area.y + 1 + note as u16 / 2; - let c = if note % 2 == 0 { "▀" } else { "▄" }; - c.blit(buf, x, y, self.style_focus()) + if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) { + let x = area.x + Self::H_KEYS_OFFSET + time as u16; + let y = area.y + 1 + note as u16 / 2; + let c = if note % 2 == 0 { "▀" } else { "▄" }; + c.blit(buf, x, y, self.style_focus()) + } else { + Ok(Rect::default()) + } } fn horizontal_timer ( - &self, buf: &mut Buffer, area: Rect, phrase: &Option>> + &self, buf: &mut Buffer, area: Rect, phrase: &RwLock ) -> Usually { - if let Some(phrase) = phrase { - let phrase = phrase.read().unwrap(); - let (time0, time_z, now) = (self.time_start, self.time_zoom, self.now % phrase.length); - let Rect { x, width, .. } = area; - for x in x+Self::H_KEYS_OFFSET..x+width { - let step = (time0 + (x-Self::H_KEYS_OFFSET) as usize) * time_z; - let next_step = (time0 + (x-Self::H_KEYS_OFFSET) as usize + 1) * time_z; - let style = Self::style_timer_step(now, step, next_step); - "-".blit(buf, x, area.y, Some(style))?; - } + let phrase = phrase.read().unwrap(); + let (time0, time_z, now) = (self.time_axis.start, self.time_axis.scale, self.now % phrase.length); + let Rect { x, width, .. } = area; + for x in x+Self::H_KEYS_OFFSET..x+width { + let step = (time0 + (x-Self::H_KEYS_OFFSET)) * time_z; + let next_step = (time0 + (x-Self::H_KEYS_OFFSET) + 1) * time_z; + let style = Self::style_timer_step(now, step as usize, next_step as usize); + "-".blit(buf, x, area.y, Some(style))?; } return Ok(Rect { x: area.x, y: area.y, width: area.width, height: 1 }) } - - fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually { - let note0 = self.note_start; - let not_dim = Style::default().not_dim(); - let Rect { x, y, height, .. } = area; - let height = height.min(128); - let h = height.saturating_sub(3); - for index in 0..h { - let y = y + h - index; - let key1 = buf.get_mut(x + 1, y); - key1.set_char('▄'); - key1.set_style(not_dim); - let (fg, bg) = match index % 6 { - 0 => (Color::White, Color::Black), - 1 => (Color::White, Color::Black), - 2 => (Color::White, Color::White), - 3 => (Color::Black, Color::White), - 4 => (Color::Black, Color::White), - 5 => (Color::Black, Color::White), - _ => { unreachable!(); } - }; - key1.set_fg(self.index_to_color(index as usize * 2, fg)); - key1.set_bg(self.index_to_color(index as usize * 2 + 1, bg)); - let key2 = buf.get_mut(x + 2, y); - key2.set_char('▄'); - key2.set_style(not_dim); - key2.set_fg(self.index_to_color(index as usize * 2, Color::White)); - key2.set_bg(self.index_to_color(index as usize * 2 + 1, Color::White)); - //for x in x+Self::H_KEYS_OFFSET..x+width-1 { - //let cell = buf.get_mut(x, y); - //cell.set_char('░'); - //cell.set_style(black); - //} - let note_a = note0 + (index * 2) as usize; - if note_a % 12 == 0 { - let octave = format!("C{}", (note_a / 12) as i8 - 2); - octave.blit(buf, x + 3, y, None)?; - continue - } - let note_b = note0 + (index * 2) as usize; - if note_b % 12 == 0 { - let octave = format!("C{}", (note_b / 12) as i8 - 2); - octave.blit(buf, x + 3, y, None)?; - continue - } - } - Ok(area) - } - - fn horizontal_lanes ( - &self, buf: &mut Buffer, area: Rect, phrase: &Option>> - ) -> Usually { - if let Some(phrase) = phrase { - let Rect { x, y, width, height } = area; - let phrase = phrase.read().unwrap(); - let now = self.now % phrase.length; - let ppq = self.ppq; - let time_z = self.time_zoom; - let time0 = self.time_start; - let note0 = self.note_start; - let dim = Style::default().dim(); - let offset = Self::H_KEYS_OFFSET; - let phrase_area = Rect { x: x + offset, y, width: width - offset, height: height - 2 }; - let mut steps = Vec::with_capacity(phrase_area.width as usize); - for x in phrase_area.x .. phrase_area.x + phrase_area.width { - let x0 = x.saturating_sub(phrase_area.x) as usize; - let step = (0 + time0 + x0) * time_z; - let next = (1 + time0 + x0) * time_z; - if step >= phrase.length { - break - } - let style = Self::style_timer_step(now, step, next); - let cell = buf.get_mut(x, area.y); - cell.set_char('-'); - cell.set_style(style); - steps.push((x, step, next)); - for y in phrase_area.y .. phrase_area.y + phrase_area.height { - if y == phrase_area.y { - if step % (4 * ppq) == 0 { - format!("{}", 1 + step / (4 * ppq)).blit(buf, x, y, None)?; - } else if step % ppq == 0 { - let cell = buf.get_mut(x, y); - cell.set_char(if step % ppq == 0 { '|' } else { '·' }); - cell.set_style(dim); - } - } else { - let cell = buf.get_mut(x, y); - cell.set_char(if step % ppq == 0 { '|' } else { '·' }); - cell.set_style(dim); - } - } - } - - let wh = Style::default().white().not_dim(); - for index in 0..height-2 { - let note_a = note0 + index as usize * 2; - let note_b = note0 + index as usize * 2 + 1; - for (x, step, next_step) in steps.iter() { - let (a, b) = ( - phrase.contains_note_on(u7::from_int_lossy(note_a as u8), *step, *next_step), - phrase.contains_note_on(u7::from_int_lossy(note_b as u8), *step, *next_step), - ); - if let Some(block) = half_block(a, b) { - let y = y + height.saturating_sub(index+2) as u16; - let cell = buf.get_mut(*x, y); - cell.set_char(block); - cell.set_style(wh); - } - } - } - Ok(area) - } else { - return Ok(Rect { x: area.x, y: area.x, width: 0, height: 0 }) - } - } -} - -fn half_block (lower: bool, upper: bool) -> Option { - match (lower, upper) { - (true, true) => Some('█'), - (true, false) => Some('▄'), - (false, true) => Some('▀'), - _ => None - } } diff --git a/src/view/transport.rs b/src/view/transport.rs index 14a4b615..8744855f 100644 --- a/src/view/transport.rs +++ b/src/view/transport.rs @@ -48,7 +48,7 @@ render!(TransportToolbar |self, buf, area| { // Quantization &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ "QUANT".blit(buf, x, y, Some(not_dim))?; - let width = ppq_to_name(*quant).blit(buf, x, y + 1, Some(not_dim_bold))?.width; + let width = ppq_to_name(*quant as u16).blit(buf, x, y + 1, Some(not_dim_bold))?.width; let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; if self.focused && self.entered && self.selected == TransportFocus::Quant { corners.draw(buf, Rect { x: area.x - 1, ..area })?; @@ -59,7 +59,7 @@ render!(TransportToolbar |self, buf, area| { // Clip launch sync &|buf: &mut Buffer, Rect { x, y, .. }: Rect|{ "SYNC".blit(buf, x, y, Some(not_dim))?; - let width = ppq_to_name(*sync).blit(buf, x, y + 1, Some(not_dim_bold))?.width; + let width = ppq_to_name(*sync as u16).blit(buf, x, y + 1, Some(not_dim_bold))?.width; let area = Rect { x, y, width: (width + 2).max(10), height: 2 }; if self.focused && self.entered && self.selected == TransportFocus::Sync { corners.draw(buf, Rect { x: area.x - 1, ..area })?;