diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index 68b89655..b0270c89 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -1,6 +1,8 @@ use crate::prelude::*; use ratatui::style::Stylize; +type Sequence = std::collections::BTreeMap>; + pub struct Sequencer { name: String, mode: SequencerView, @@ -11,7 +13,7 @@ pub struct Sequencer { rate: Hz, tempo: Tempo, transport: ::jack::Transport, - sequence: std::collections::BTreeMap>>, + sequence: Sequence, ppq: u32, input_port: Port, input_connect: Vec, @@ -64,14 +66,33 @@ pub fn process (state: &mut Sequencer, client: &Client, scope: &ProcessScope) -> } fn process_in (state: &mut Sequencer, scope: &ProcessScope) { + let pos = state.transport.query().unwrap().pos; + let frame = pos.frame(); + let rate = pos.frame_rate().unwrap(); + let usecs = Frame(frame).to_usec(&Hz(rate)).0 as u64; + let usec_per_beat = state.tempo.usec_per_beat().0 as u64; + let usec_per_tick = state.tempo.usec_per_tick(state.ppq).0 as u64; + let beats = usecs / usec_per_beat; + let time = beats as u32 * state.ppq; + for event in state.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 } => { - state.notes_on[key.as_int() as usize] = true + state.notes_on[key.as_int() as usize] = true; + if state.sequence.contains_key(&time) { + state.sequence.get_mut(&time).unwrap().push(message.clone()); + } else { + state.sequence.insert(time, vec![message.clone()]); + } }, midly::MidiMessage::NoteOff { key, vel } => { - state.notes_on[key.as_int() as usize] = false + state.notes_on[key.as_int() as usize] = false; + if state.sequence.contains_key(&time) { + state.sequence.get_mut(&time).unwrap().push(message.clone()); + } else { + state.sequence.insert(time, vec![message.clone()]); + } }, _ => {} }, @@ -152,13 +173,14 @@ const COMMANDS: [(KeyCode, &'static str, &'static str, &'static dyn Fn(&mut Sequ fn nop (_: &mut Sequencer) { } fn note_add (s: &mut Sequencer) { - let time = (s.time_axis.0 + s.time_cursor) as u32; + let time = (s.time_axis.0 + s.time_cursor) as u32; let time_start = time * s.ppq; let time_end = (time + 1) * s.ppq; - let note_on = vec![0, 0, 0]; - let note_off = vec![0, 0, 0]; + let key = ::midly::num::u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8); + let note_on = ::midly::MidiMessage::NoteOn { key, vel: 100.into() }; + let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() }; if s.sequence.contains_key(&time_start) { - s.sequence.get_mut(&time_start).unwrap().push(note_off.clone()); + s.sequence.get_mut(&time_start).unwrap().push(note_on.clone()); } else { s.sequence.insert(time_start, vec![note_on]); } @@ -234,7 +256,17 @@ fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) let Rect { x, y, width, height } = area; let (time0, time1) = sequencer.time_axis; let (note0, note1) = sequencer.note_axis; - let header = draw_sequencer_header(sequencer, buf, area)?; + let pos = sequencer.transport.query().unwrap().pos; + let frame = pos.frame(); + let rate = pos.frame_rate().unwrap(); + let usecs = Frame(frame).to_usec(&Hz(rate)).0 as u64; + let usec_per_beat = sequencer.tempo.usec_per_beat().0 as u64; + let usec_per_tick = sequencer.tempo.usec_per_tick(sequencer.ppq).0 as u64; + let beats = usecs / usec_per_beat; + let bar = beats / 4; + let beat = beats % 4; + let tick = (usecs % usec_per_beat) / usec_per_tick; + let header = draw_sequencer_header(sequencer, buf, area, bar, beat, tick)?; let piano = match sequencer.mode { SequencerView::Tiny => Rect { x, y, width, height: 0 }, @@ -245,7 +277,7 @@ fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) y: y + header.height, width: 3 + note1 - note0, height: 3 + time1 - time0, - })?, + }, beats)?, SequencerView::Horizontal => draw_sequencer_horizontal(sequencer, buf, Rect { x, y: y + header.height, @@ -261,10 +293,10 @@ fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) })) } -fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { +fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect, bar: u64, beat: u64, tick: u64) -> Usually { let Rect { x, y, width, height } = area; let style = Style::default().gray(); - buf.set_string(x + 1, y + 1, &format!(" │ 00:00.00 / 00:00.00"), style); + buf.set_string(x + 1, y + 1, &format!(" │ {bar}.{beat}.{tick:03}"), style); buf.set_string(x + 2, y + 1, &format!("{}", &sequencer.name), style.white().bold()); buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ │"), style); buf.set_string(x + 2, y + 2, &format!("▶ PLAY"), if sequencer.playing { @@ -306,8 +338,9 @@ const KEY_HORIZONTAL_STYLE: [Style;12] = [ KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, ]; -fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { +fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect, beat: u64) -> Usually { let Rect { x, y, .. } = area; + let transport = sequencer.transport.query().unwrap(); let (time0, time1) = sequencer.time_axis; let (note0, note1) = sequencer.note_axis; let bw = Style::default().dim(); @@ -327,41 +360,33 @@ fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) } for step in time0..time1 { let y = y - time0 + step / 2; - buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); + //buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg); if step % 8 == 0 { buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default()); } - if step % 2 == 0 { - let mut has_1 = false; - let (start, end) = ( - (step + 0) as u32 * sequencer.ppq, - (step + 1) as u32 * sequencer.ppq); - for (i, (t, events)) in sequencer.sequence.range(start..end).enumerate() { - if events.len() > 0 { - has_1 = true; - break - } - } - let mut has_2 = false; - let (start, end) = ( - (step + 1) as u32 * sequencer.ppq, - (step + 2) as u32 * sequencer.ppq - ); - for (i, (t, events)) in sequencer.sequence.range(start..end).enumerate() { - if events.len() > 0 { - has_2 = true; - break - } - } - if has_1 || has_2 { - buf.set_string(x + 4, y, &match (has_1, has_2) { - (true, true) => "█", - (true, false) => "▀", - (false, true) => "▄", - _ => " ", - }, Style::default()); + for k in note0..note1.max(note0+32) { + let key = ::midly::num::u7::from_int_lossy(k as u8); + if step % 2 == 0 { + let (a, b, c) = ( + (step + 0) as u32 * sequencer.ppq, + (step + 1) as u32 * sequencer.ppq, + (step + 2) as u32 * sequencer.ppq, + ); + let (character, style) = match ( + contains_note_on(&sequencer.sequence, key, a, b), + contains_note_on(&sequencer.sequence, key, b, c), + ) { + (true, true) => ("█", bg), + (true, false) => ("▀", bg), + (false, true) => ("▄", bg), + (false, false) => (" ", bg), + }; + buf.set_string(x + 5 + k - note0, y, character, style); } } + if beat == step as u64 { + buf.set_string(x + 1, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow()); + } } buf.set_string( x + 5 + sequencer.note_cursor, @@ -372,6 +397,22 @@ fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 }) } +fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool { + for (i, (t, events)) in sequence.range(start..end).enumerate() { + for event in events.iter() { + match event { + ::midly::MidiMessage::NoteOn {key,..} => { + if *key == k { + return true + } + } + _ => {} + } + } + } + return false +} + const KEYS_VERTICAL: [&'static str; 6] = [ "▀", "▀", "▀", "█", "▄", "▄", ]; diff --git a/src/time.rs b/src/time.rs index 59498b3c..c49939b4 100644 --- a/src/time.rs +++ b/src/time.rs @@ -5,7 +5,7 @@ pub struct Hz(pub u32); pub struct Frame(pub u32); /// One microsecond. -pub struct Usec(pub u32); +pub struct Usec(pub u64); /// Beats per minute as `120000` = 120.000BPM pub struct Tempo(pub u32); @@ -24,29 +24,29 @@ pub enum NoteDuration { impl Frame { #[inline] pub fn to_usec (&self, rate: &Hz) -> Usec { - Usec((self.0 * 1000) / rate.0) + Usec((self.0 as u64 * 1000000) / rate.0 as u64) } } impl Usec { #[inline] pub fn to_frame (&self, rate: &Hz) -> Frame { - Frame((self.0 * rate.0) / 1000) + Frame(((self.0 * rate.0 as u64) / 1000) as u32) } } impl Tempo { #[inline] - pub fn to_usec_per_beat (&self) -> Usec { - Usec((60_000_000_000 / self.0 as usize) as u32) + pub fn usec_per_beat (&self) -> Usec { + Usec(60_000_000_000 / self.0 as u64) } #[inline] - pub fn to_usec_per_quarter (&self) -> Usec { - Usec(self.to_usec_per_quarter().0 / 4) + pub fn usec_per_quarter (&self) -> Usec { + Usec(self.usec_per_beat().0 / 4) } #[inline] - pub fn to_usec_per_tick (&self, ppq: u32) -> Usec { - Usec(self.to_usec_per_quarter().0 / ppq) + pub fn usec_per_tick (&self, ppq: u32) -> Usec { + Usec(self.usec_per_quarter().0 / ppq as u64) } #[inline] pub fn quantize (&self, step: &NoteDuration, time: Usec) -> Usec { @@ -69,11 +69,11 @@ impl NoteDuration { fn to_usec (&self, bpm: &Tempo) -> Usec { Usec(match self { Self::Nth(time, flies) => - bpm.to_usec_per_beat().0 * *time as u32 / *flies as u32, + bpm.usec_per_beat().0 * *time as u64 / *flies as u64, Self::Dotted(duration) => duration.to_usec(bpm).0 * 3 / 2, Self::Tuplet(n, duration) => - duration.to_usec(bpm).0 * 2 / *n as u32, + duration.to_usec(bpm).0 * 2 / *n as u64, }) } fn to_frame (&self, bpm: &Tempo, rate: &Hz) -> Frame {