diff --git a/src/device/chain.rs b/src/device/chain.rs index c7fd1da8..22eca5ac 100644 --- a/src/device/chain.rs +++ b/src/device/chain.rs @@ -48,11 +48,11 @@ pub fn render (state: &Chain, buf: &mut Buffer, area: Rect) }, ChainView::Column => { let (area, areas) = draw_column(&state.items, buf, area, 0)?; - draw_box_styled(buf, area, Some(if state.focused { - Style::default().dim() - } else { - Style::default().not_dim() - })); + //draw_box_styled(buf, area, Some(if state.focused { + //Style::default().dim() + //} else { + //Style::default().not_dim() + //})); draw_box_styled(buf, areas[state.focus], Some(if state.focused { Style::default().green().not_dim() } else { diff --git a/src/device/plugin.rs b/src/device/plugin.rs index 3e5abc7d..e2220805 100644 --- a/src/device/plugin.rs +++ b/src/device/plugin.rs @@ -5,10 +5,12 @@ mod vst2; mod vst3; pub struct Plugin { - name: String, - path: String, - plugin: Option, - parameter_offset: usize, + name: String, + path: String, + plugin: Option, + offset: usize, + selected: usize, + midi_map_enable: bool, } enum PluginKind { @@ -22,10 +24,12 @@ const HELM: &'static str = "file:///nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-h impl Plugin { pub fn new (name: &str) -> Result, Box> { let device = DynamicDevice::new(render, handle, process, Self { - name: name.into(), - path: HELM.into(), - plugin: None, - parameter_offset: 0, + name: name.into(), + path: HELM.into(), + plugin: None, + offset: 0, + selected: 0, + midi_map_enable: false }); device.state.lock().unwrap().plugin = Some(self::lv2::plug_in(HELM)?); Ok(device) @@ -38,27 +42,36 @@ pub fn process (_: &mut Plugin, _: &Client, _: &ProcessScope) -> Control { Control::Continue } -pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, .. }: Rect) +pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect) -> Usually { - let style = Style::default().gray(); - buf.set_string(x + 1, y + 1, &format!(" {}", state.name), style.white().bold()); - buf.set_string(x + 13, y + 1, &format!("│ ...{}...", &HELM[13..30]), style.not_dim()); - buf.set_string(x + 0, y + 2, &format!("├------------------------------------------------┤"), style.dim()); + let header = draw_header(state, buf, area)?; + let Rect { x, y, width, height } = area; + let height = height - header.height; + let style = Style::default().gray(); match &state.plugin { Some(PluginKind::LV2(ports, instance)) => { let mut height = 3; - for (i, port) in ports.iter().skip(state.parameter_offset).enumerate() { - if i >= 30 { + for (i, port) in ports + .iter() + .filter(|port|port.port_type == ::livi::PortType::ControlInput) + .skip(state.offset) + .enumerate() + { + if i >= 20 { break } - buf.set_string(x + 2, y + 3 + i as u16, &format!("{:30} = {:03}", + buf.set_string(x + 2, y + 3 + i as u16, &format!("C·· M·· {:25} = {:03}", port.name, port.default_value - ), Style::default()); + ), if i + state.offset == state.selected { + Style::default().white().bold() + } else { + Style::default().not_dim() + }); height = height + 1; } - Ok(draw_box(buf, Rect { x, y, width: 50, height: height.max(30) })) + Ok(draw_box(buf, Rect { x, y, width, height: height.max(20) })) }, _ => { buf.set_string(x + 1, y + 3, &format!(" Parameter 1 0.0"), style); @@ -70,16 +83,38 @@ pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, .. }: Rect) } } +fn draw_header (state: &Plugin, buf: &mut Buffer, Rect {x, y, width, ..}: Rect) -> Usually { + let style = Style::default().gray(); + buf.set_string(x + 1, y + 1, + &format!(" {}", state.name), style.white().bold()); + buf.set_string(x + 13, y + 1, + &format!("│ {}...", &HELM[..(width as usize - 20).min(HELM.len())]), style.not_dim()); + buf.set_string(x + 0, y + 2, + &format!("├{}┤", "-".repeat(width as usize - 2)), style.dim()); + Ok(Rect { x, y, width, height: 3 }) +} + pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually { handle_keymap(s, event, keymap!(Plugin { [Up, NONE, "cursor_up", "move cursor up", |s: &mut Plugin|{ - s.parameter_offset = s.parameter_offset.saturating_sub(1); + s.selected = s.selected.saturating_sub(1); + if s.selected < s.offset { + s.offset = s.selected; + } Ok(true) }], [Down, NONE, "cursor_down", "move cursor down", |s: &mut Plugin|{ - s.parameter_offset = s.parameter_offset + 1; + s.selected = s.selected + 1; + if s.selected >= s.offset + 19 { + s.offset = s.offset + 1; + } + Ok(true) + }], + [Char('m'), NONE, "toggle_midi_map", "toggle midi map mode", + |s: &mut Plugin|{ + s.midi_map_enable = !s.midi_map_enable; Ok(true) }] })) diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index c70d6edd..32a90f93 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -3,6 +3,15 @@ use ratatui::style::Stylize; type Sequence = std::collections::BTreeMap>; +mod keys; +use keys::*; + +mod horizontal; +use horizontal::*; + +mod vertical; +use vertical::*; + pub struct Sequencer { name: String, /// JACK transport handle. @@ -10,8 +19,10 @@ pub struct Sequencer { /// Holds info about tempo timebase: Arc, /// Sequencer resolution, e.g. 16 steps per beat. + /// FIXME: grid in ppm will simplify calculations resolution: usize, /// Steps in sequence, e.g. 64 16ths = 4 beat loop. + /// FIXME: play start / end / loop in ppm steps: usize, /// JACK MIDI input port that will be created. @@ -105,37 +116,36 @@ fn process_in (s: &mut Sequencer, scope: &ProcessScope, transport: &::jack::Tran let step = steps % s.steps; let tick = step * s.timebase.ppq() / s.resolution; let mut sequence = &mut s.sequences[s.sequence]; + let mut writer = s.output_port.writer(scope); for event in s.input_port.iter(scope) { - match midly::live::LiveEvent::parse(event.bytes).unwrap() { - - midly::live::LiveEvent::Midi { channel: _, message } => match message { - - midly::MidiMessage::NoteOn { key, vel: _ } => { - s.notes_on[key.as_int() as usize] = true; - let contains = sequence.contains_key(&(tick as u32)); - if contains { - sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); - } else { - sequence.insert(tick as u32, vec![message.clone()]); - } + if s.monitoring { + writer.write(&event).expect(&format!("{event:?}")) + } + if s.recording { + match midly::live::LiveEvent::parse(event.bytes).unwrap() { + midly::live::LiveEvent::Midi { channel: _, message } => match message { + midly::MidiMessage::NoteOn { key, vel: _ } => { + s.notes_on[key.as_int() as usize] = true; + let contains = sequence.contains_key(&(tick as u32)); + if contains { + sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); + } else { + sequence.insert(tick as u32, vec![message.clone()]); + } + }, + midly::MidiMessage::NoteOff { key, vel: _ } => { + s.notes_on[key.as_int() as usize] = false; + let contains = sequence.contains_key(&(tick as u32)); + if contains { + sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); + } else { + sequence.insert(tick as u32, vec![message.clone()]); + } + }, + _ => {} }, - - midly::MidiMessage::NoteOff { key, vel: _ } => { - s.notes_on[key.as_int() as usize] = false; - let contains = sequence.contains_key(&(tick as u32)); - if contains { - sequence.get_mut(&(tick as u32)).unwrap().push(message.clone()); - } else { - sequence.insert(tick as u32, vec![message.clone()]); - } - }, - _ => {} - - }, - - _ => {} - + } } } } @@ -198,7 +208,7 @@ fn render (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually { y: y + header.height, width: area.width.max(3 + time1 - time0), height: 3 + note1 - note0, - })?, + }, steps)?, }; Ok(draw_box(buf, Rect { x, @@ -230,23 +240,23 @@ fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usu //Style::default().dim() //}); buf.set_string(x + 10, y + 1, - &format!("⏺ MON"), if s.monitoring { - Style::default().bold().green() - } else { - Style::default().bold().dim() - }); - buf.set_string(x + 17, y + 1, &format!("⏺ REC"), if s.recording { Style::default().bold().red() } else { Style::default().bold().dim() }); - buf.set_string(x + 24, y + 1, + buf.set_string(x + 17, y + 1, &format!("⏺ DUB"), if s.overdub { Style::default().bold().yellow() } else { Style::default().bold().dim() }); + buf.set_string(x + 24, y + 1, + &format!("⏺ MON"), if s.monitoring { + Style::default().bold().green() + } else { + Style::default().bold().dim() + }); let clips = draw_clips(s, buf, area)?; Ok(Rect { x, y, width: area.width, height: 3 }) } @@ -268,106 +278,7 @@ fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { Ok(Rect { x, y, width: 14, height: 14 }) } -const KEYS_VERTICAL: [&'static str; 6] = [ - "▀", "▀", "▀", "█", "▄", "▄", -]; - -fn draw_keys_vertical (s: &Sequencer, buf: &mut Buffer, mut area: Rect, beat: usize) { - let ppq = s.timebase.ppq() as u32; - let Rect { x, y, .. } = area; - let (note0, note1) = s.note_axis; - for key in note0..note1 { - let x = x + 5 + key - note0; - if key % 12 == 0 { - let octave = format!("C{}", (key / 12) as i8 - 4); - buf.set_string(x, y, &octave, Style::default()); - } - let mut color = KEY_STYLE[key as usize % 12]; - let mut is_on = s.notes_on[key as usize]; - let step = beat % s.steps; - let (a, b, c) = ( - (step + 0) as u32 * ppq / s.resolution as u32, - (step + 1) as u32 * ppq / s.resolution as u32, - (step + 2) as u32 * ppq / s.resolution as u32, - ); - let key = ::midly::num::u7::from(key as u8); - is_on = is_on || contains_note_on(&s.sequences[s.sequence], key, a, b); - is_on = is_on || contains_note_on(&s.sequences[s.sequence], key, b, c); - if is_on { - color = Style::default().red(); - } - buf.set_string(x, y - 1, &format!("▄"), color); - } -} - -fn draw_vertical (s: &Sequencer, buf: &mut Buffer, mut area: Rect, beat: usize) -> Usually { - let ppq = s.timebase.ppq() as u32; - area.x = area.x + 13; - let Rect { x, y, .. } = area; - let (time0, time1) = s.time_axis; - let (note0, note1) = s.note_axis; - let bw = Style::default().dim().on_black(); - let bg = Style::default().on_black(); - draw_keys_vertical(s, buf, area, beat); - for step in time0..time1 { - let y = y - time0 + step / 2; - let step = step as usize; - //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); - if step % s.resolution == 0 { - buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); - } - for k in note0..note1 { - let key = ::midly::num::u7::from_int_lossy(k as u8); - if step % 2 == 0 { - let (a, b, c) = ( - (step + 0) as u32 * ppq / s.resolution as u32, - (step + 1) as u32 * ppq / s.resolution as u32, - (step + 2) as u32 * ppq / s.resolution as u32, - ); - let (character, style) = match ( - contains_note_on(&s.sequences[s.sequence], key, a, b), - contains_note_on(&s.sequences[s.sequence], key, b, c), - ) { - (true, true) => ("█", bg), - (true, false) => ("▀", bg), - (false, true) => ("▄", bg), - (false, false) => ("·", bw), - }; - buf.set_string(x + 5 + k - note0, y, character, style); - } - } - if beat % s.steps == step as usize { - buf.set_string(x + 4, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); - for key in note0..note1 { - let _color = if s.notes_on[key as usize] { - Style::default().red() - } else { - KEY_STYLE[key as usize % 12] - }; - } - } - } - let height = (time1-time0)/2; - buf.set_string(x + 2, y + height + 1, format!( - "Q 1/{} | N {} ({}-{}) | T {} ({}-{})", - 4 * s.resolution, - s.note_axis.0 + s.note_cursor, - s.note_axis.0, - s.note_axis.1 - 1, - s.time_axis.0 + s.time_cursor + 1, - s.time_axis.0 + 1, - s.time_axis.1, - ), Style::default().dim()); - buf.set_string( - x + 5 + s.note_cursor, - y + s.time_cursor / 2, - if s.time_cursor % 2 == 0 { "▀" } else { "▄" }, - Style::default() - ); - Ok(Rect { x, y, width: area.width, height: height + 1 }) -} - -fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool { +pub fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool { for (_, (_, events)) in sequence.range(start..end).enumerate() { for event in events.iter() { match event { @@ -383,91 +294,6 @@ fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: return false } - -const KEY_WHITE: Style = Style { - fg: Some(Color::Gray), - bg: None, - underline_color: None, - add_modifier: ::ratatui::style::Modifier::empty(), - sub_modifier: ::ratatui::style::Modifier::empty(), -}; - -const KEY_BLACK: Style = Style { - fg: Some(Color::Black), - bg: None, - underline_color: None, - add_modifier: ::ratatui::style::Modifier::empty(), - sub_modifier: ::ratatui::style::Modifier::empty(), -}; - -const KEY_STYLE: [Style;12] = [ - KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, - KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, -]; - -fn draw_keys_horizontal (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually { - let Rect { x, y, .. } = area; - let (note0, note1) = s.note_axis; - let (time0, time1) = s.time_axis; - let bw = Style::default().dim(); - let bg = Style::default().on_black(); - for i in 0..32.max(note1-note0)/2 { - let y = y + i; - buf.set_string(x + 2, y, KEYS_VERTICAL[(i % 6) as usize], bw); - buf.set_string(x + 3, y, "█", bw); - buf.set_string(x + 6, y, &"·".repeat((time1 - time0) as usize), bg.dim()); - if i % 6 == 0 { - let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4); - buf.set_string(x + 4, y, &octave, Style::default()); - } - } - Ok(area) -} - -fn draw_horizontal (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually { - area.x = area.x + 13; - let Rect { x, y, width, .. } = area; - let (time0, time1) = s.time_axis; - let (note0, note1) = s.note_axis; - let bw = Style::default().dim(); - let bg = Style::default().on_black(); - draw_keys_horizontal(s, buf, area)?; - for step in time0..time1 { - let time_start = step as u32 * s.timebase.ppq() as u32; - let time_end = (step + 1) as u32 * s.timebase.ppq() as u32; - for (_, (_, events)) in s.sequences[s.sequence].range(time_start..time_end).enumerate() { - if events.len() > 0 { - buf.set_string(x + 5 + step as u16, y, "█", bw); - } - } - } - let height = 32.max(note1 - note0) / 2; - buf.set_string(x - 13, y + height, format!("├{}┤", "-".repeat((width - 2).into())), - Style::default().dim()); - buf.set_string(x - 11, y + height + 1, format!( - "Sync 4/4 Quantize 1/{} Notes {} ({}-{}) Time {} ({}-{})", - 4 * s.resolution, - s.note_axis.0 + s.note_cursor, - s.note_axis.0, - s.note_axis.1 - 1, - s.time_axis.0 + s.time_cursor + 1, - s.time_axis.0 + 1, - s.time_axis.1, - ), Style::default().not_dim()); - buf.set_string( - x + 6 + s.time_cursor, - y + s.note_cursor / 2, - if s.note_cursor % 2 == 0 { "▀" } else { "▄" }, - Style::default() - ); - Ok(Rect { - x: x - 13, - y, - width: time1 - time0 + 19, - height: height + 3 - }) -} - pub fn handle (s: &mut Sequencer, event: &AppEvent) -> Usually { handle_keymap(s, event, KEYMAP) } diff --git a/src/device/sequencer/horizontal.rs b/src/device/sequencer/horizontal.rs new file mode 100644 index 00000000..e1f6fe1b --- /dev/null +++ b/src/device/sequencer/horizontal.rs @@ -0,0 +1,99 @@ +use crate::prelude::*; +use super::*; + +pub fn draw_horizontal ( + s: &Sequencer, + buf: &mut Buffer, + mut area: Rect, + beat: usize +) -> Usually { + let ppq = s.timebase.ppq() as u32; + area.x = area.x + 13; + + draw_keys_horizontal(s, buf, area)?; + + let Rect { x, y, width, .. } = area; + let (time0, time1) = s.time_axis; + let (note0, note1) = s.note_axis; + let bw = Style::default().dim(); + let bg = Style::default().on_black(); + + for step in time0..time1 { + buf.set_string(x + 6 + step, y - 1, &"-", if beat % s.steps == step as usize { + Style::default().yellow().bold().not_dim() + } else { + Style::default() + }); + } + for step in time0..time1 { + let time_start = step as u32 * ppq; + let time_end = (step + 1) as u32 * ppq; + if step % s.resolution as u16 == 0 { + buf.set_string(x + 6 + step, y - 1, &format!("{}", step + 1), Style::default()); + } + for (_, (_, events)) in s.sequences[s.sequence].range(time_start..time_end).enumerate() { + if events.len() > 0 { + buf.set_string(x + 5 + step as u16, y, "█", bw); + } + } + } + + let height = 32.max(note1 - note0) / 2; + buf.set_string(x - 13, y + height, format!("├{}┤", "-".repeat((width - 2).into())), + Style::default().dim()); + { + let mut x = x - 11; + for (i, [letter, title, value]) in [ + ["S", &format!("ync"), &format!("<4/4>")], + ["Q", &format!("uant"), &format!("<1/{}>", 4 * s.resolution)], + ["N", &format!("ote"), &format!("{} ({}-{})", + s.note_axis.0 + s.note_cursor, + s.note_axis.0, + s.note_axis.1 - 1)], + ["T", &format!("ime"), &format!("{} ({}-{})", + s.time_axis.0 + s.time_cursor + 1, + s.time_axis.0 + 1, + s.time_axis.1)], + ].iter().enumerate() { + buf.set_string(x, y + height + 1, letter, Style::default().bold().yellow().dim()); + x = x + 1; + buf.set_string(x, y + height + 1, &title, Style::default().bold().dim()); + x = x + title.len() as u16 + 1; + buf.set_string(x, y + height + 1, &value, Style::default().not_dim()); + x = x + value.len() as u16; + buf.set_string(x, y + height + 1, " ", Style::default().dim()); + x = x + 2; + } + } + buf.set_string( + x + 6 + s.time_cursor, + y + s.note_cursor / 2, + if s.note_cursor % 2 == 0 { "▀" } else { "▄" }, + Style::default() + ); + Ok(Rect { + x: x - 13, + y, + width: time1 - time0 + 19, + height: height + 3 + }) +} + +pub fn draw_keys_horizontal (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually { + let Rect { x, y, .. } = area; + let (note0, note1) = s.note_axis; + let (time0, time1) = s.time_axis; + let bw = Style::default().dim(); + let bg = Style::default().on_black(); + for i in 0..32.max(note1-note0)/2 { + let y = y + i; + buf.set_string(x + 2, y, KEYS_VERTICAL[(i % 6) as usize], bw); + buf.set_string(x + 3, y, "█", bw); + buf.set_string(x + 6, y, &"·".repeat((time1 - time0) as usize), bg.dim()); + if i % 6 == 0 { + let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4); + buf.set_string(x + 4, y, &octave, Style::default()); + } + } + Ok(area) +} diff --git a/src/device/sequencer/keys.rs b/src/device/sequencer/keys.rs new file mode 100644 index 00000000..3627eb2c --- /dev/null +++ b/src/device/sequencer/keys.rs @@ -0,0 +1,27 @@ +use crate::prelude::*; +use super::*; + +pub const KEY_WHITE: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: ::ratatui::style::Modifier::empty(), + sub_modifier: ::ratatui::style::Modifier::empty(), +}; + +pub const KEY_BLACK: Style = Style { + fg: Some(Color::Black), + bg: None, + underline_color: None, + add_modifier: ::ratatui::style::Modifier::empty(), + sub_modifier: ::ratatui::style::Modifier::empty(), +}; + +pub const KEY_STYLE: [Style;12] = [ + KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, + KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, +]; + +pub const KEYS_VERTICAL: [&'static str; 6] = [ + "▀", "▀", "▀", "█", "▄", "▄", +]; diff --git a/src/device/sequencer/vertical.rs b/src/device/sequencer/vertical.rs new file mode 100644 index 00000000..acd50460 --- /dev/null +++ b/src/device/sequencer/vertical.rs @@ -0,0 +1,106 @@ +use crate::prelude::*; +use super::*; + +pub fn draw_vertical ( + s: &Sequencer, + buf: &mut Buffer, + mut area: Rect, + beat: usize +) -> Usually { + let ppq = s.timebase.ppq() as u32; + area.x = area.x + 13; + + draw_keys_vertical(s, buf, area, beat); + + let Rect { x, y, .. } = area; + let (time0, time1) = s.time_axis; + let (note0, note1) = s.note_axis; + let bw = Style::default().dim().on_black(); + let bg = Style::default().on_black(); + + for step in time0..time1 { + let y = y - time0 + step / 2; + let step = step as usize; + //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); + if step % s.resolution == 0 { + buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); + } + for k in note0..note1 { + let key = ::midly::num::u7::from_int_lossy(k as u8); + if step % 2 == 0 { + let (a, b, c) = ( + (step + 0) as u32 * ppq / s.resolution as u32, + (step + 1) as u32 * ppq / s.resolution as u32, + (step + 2) as u32 * ppq / s.resolution as u32, + ); + let (character, style) = match ( + contains_note_on(&s.sequences[s.sequence], key, a, b), + contains_note_on(&s.sequences[s.sequence], key, b, c), + ) { + (true, true) => ("█", bg), + (true, false) => ("▀", bg), + (false, true) => ("▄", bg), + (false, false) => ("·", bw), + }; + buf.set_string(x + 5 + k - note0, y, character, style); + } + } + if beat % s.steps == step as usize { + buf.set_string(x + 4, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); + for key in note0..note1 { + let _color = if s.notes_on[key as usize] { + Style::default().red() + } else { + KEY_STYLE[key as usize % 12] + }; + } + } + } + + let height = (time1-time0)/2; + buf.set_string(x + 2, y + height + 1, format!( + "Q 1/{} | N {} ({}-{}) | T {} ({}-{})", + 4 * s.resolution, + s.note_axis.0 + s.note_cursor, + s.note_axis.0, + s.note_axis.1 - 1, + s.time_axis.0 + s.time_cursor + 1, + s.time_axis.0 + 1, + s.time_axis.1, + ), Style::default().dim()); + buf.set_string( + x + 5 + s.note_cursor, + y + s.time_cursor / 2, + if s.time_cursor % 2 == 0 { "▀" } else { "▄" }, + Style::default() + ); + Ok(Rect { x, y, width: area.width, height: height + 1 }) +} + +fn draw_keys_vertical (s: &Sequencer, buf: &mut Buffer, mut area: Rect, beat: usize) { + let ppq = s.timebase.ppq() as u32; + let Rect { x, y, .. } = area; + let (note0, note1) = s.note_axis; + for key in note0..note1 { + let x = x + 5 + key - note0; + if key % 12 == 0 { + let octave = format!("C{}", (key / 12) as i8 - 4); + buf.set_string(x, y, &octave, Style::default()); + } + let mut color = KEY_STYLE[key as usize % 12]; + let mut is_on = s.notes_on[key as usize]; + let step = beat % s.steps; + let (a, b, c) = ( + (step + 0) as u32 * ppq / s.resolution as u32, + (step + 1) as u32 * ppq / s.resolution as u32, + (step + 2) as u32 * ppq / s.resolution as u32, + ); + let key = ::midly::num::u7::from(key as u8); + is_on = is_on || contains_note_on(&s.sequences[s.sequence], key, a, b); + is_on = is_on || contains_note_on(&s.sequences[s.sequence], key, b, c); + if is_on { + color = Style::default().red(); + } + buf.set_string(x, y - 1, &format!("▄"), color); + } +} diff --git a/src/main.rs b/src/main.rs index 09dbb645..1dd277ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ fn main () -> Result<(), Box> { let transport = Transport::new("Transport")?; let timebase = transport.state.lock().unwrap().timebase(); crate::device::run(Chain::new("Chain#0000", vec![ - Box::new(Sequencer::new("Melody#000", &timebase)?), + Box::new(Sequencer::new("Phrase#000", &timebase)?), Box::new(Plugin::new("Plugin#000")?), ])?) } diff --git a/src/prelude.rs b/src/prelude.rs index e4fb784c..7cda7f01 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -128,8 +128,7 @@ pub fn handle_keymap ( AppEvent::Input(Event::Key(event)) => { for (code, modifiers, _, _, command) in keymap.iter() { if *code == event.code && modifiers.bits() == event.modifiers.bits() { - command(state); - return Ok(true) + return command(state) } } },