use crate::prelude::*; use ratatui::style::Stylize; type Sequence = std::collections::BTreeMap>; pub struct Sequencer { name: String, /// JACK transport handle. transport: ::jack::Transport, /// Holds info about tempo timebase: Arc, /// Sequencer resolution, e.g. 16 steps per beat. resolution: usize, /// Steps in sequence, e.g. 64 16ths = 4 beat loop. steps: usize, /// JACK MIDI input port that will be created. input_port: Port, /// Port name patterns to connect to. input_connect: Vec, /// Play input through output. monitoring: bool, /// Red keys on piano roll. notes_on: Vec, /// Write input to sequence. recording: bool, /// Sequence selector sequence: usize, /// Map: tick -> MIDI events at tick sequences: Vec, /// Don't delete when recording. overdub: bool, /// Play sequence to output. playing: bool, /// JACK MIDI output port that will be created. output_port: Port, /// Port name patterns to connect to. output_connect: Vec, /// Display mode mode: SequencerView, /// Range of notes to display note_axis: (u16, u16), /// Position of cursor within note range note_cursor: u16, /// Range of time steps to display time_axis: (u16, u16), /// Position of cursor within time range time_cursor: u16, } #[derive(Debug, Clone)] enum SequencerView { Tiny, Compact, Horizontal, Vertical, } impl Sequencer { pub fn new (name: &str, timebase: &Arc) -> Usually> { let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; DynamicDevice::new(render, handle, Self::process, Self { name: name.into(), transport: client.transport(), timebase: timebase.clone(), steps: 64, resolution: 8, input_port: client.register_port("in", MidiIn::default())?, input_connect: vec!["nanoKEY Studio * (capture): *".into()], monitoring: true, notes_on: vec![false;128], recording: false, overdub: true, sequence: 0, sequences: vec![std::collections::BTreeMap::new();8], playing: true, output_port: client.register_port("out", MidiOut::default())?, output_connect: vec![], mode: SequencerView::Horizontal, note_axis: (36, 68), note_cursor: 0, time_axis: (0, 64), time_cursor: 0, }).activate(client) } fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { let transport = self.transport.query().unwrap(); process_out(self, scope, &transport); process_in(self, scope, &transport); Control::Continue } } fn process_in (s: &mut Sequencer, scope: &ProcessScope, transport: &::jack::TransportStatePosition) { if !s.recording { return } let pos = &transport.pos; let usecs = s.timebase.frame_to_usec(pos.frame() as usize); let steps = usecs / s.timebase.usec_per_step(s.resolution as usize); let step = steps % s.steps; let tick = step * s.timebase.ppq() / s.resolution; let mut sequence = &mut s.sequences[s.sequence]; 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()]); } }, 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()]); } }, _ => {} }, _ => {} } } } fn process_out (s: &mut Sequencer, scope: &ProcessScope, transport: &::jack::TransportStatePosition) { if !s.playing { return } if transport.state != ::jack::TransportState::Rolling { return } let frame = transport.pos.frame() as usize; let ticks = s.timebase.frames_to_ticks( frame, frame + scope.n_frames() as usize, s.timebase.fpb() as usize * s.steps / s.resolution ); let mut writer = s.output_port.writer(scope); for (time, tick) in ticks.iter() { if let Some(events) = s.sequences[s.sequence].get(&(*tick as u32)) { for message in events.iter() { let mut buf = vec![]; ::midly::live::LiveEvent::Midi { channel: 1.into(), message: *message, }.write(&mut buf).unwrap(); let midi = ::jack::RawMidi { time: *time as u32, bytes: &buf }; writer.write(&midi).expect(&format!("{midi:?}")) } } } } fn render (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually { let Rect { x, y, width, .. } = area; let (time0, time1) = s.time_axis; let (note0, note1) = s.note_axis; let pos = s.transport.query().unwrap().pos; let frame = pos.frame(); let usecs = s.timebase.frame_to_usec(frame as usize); let usec_per_step = s.timebase.usec_per_step(s.resolution as usize); let steps = usecs / usec_per_step; let header = draw_header(s, buf, area, steps)?; let piano = match s.mode { SequencerView::Tiny => Rect { x, y, width, height: 0 }, SequencerView::Compact => Rect { x, y, width, height: 0 }, SequencerView::Vertical => draw_vertical(s, buf, Rect { x, y: y + header.height, width: 3 + note1 - note0, height: 3 + time1 - time0, }, steps)?, SequencerView::Horizontal => draw_horizontal(s, buf, Rect { x, y: y + header.height, width: area.width.max(3 + time1 - time0), height: 3 + note1 - note0, })?, }; Ok(draw_box(buf, Rect { x, y, width: header.width.max(piano.width), height: header.height + piano.height })) } fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually { let rep = beat / s.steps; let step = beat % s.steps; let reps = s.steps / s.resolution; let steps = s.steps % s.resolution; let Rect { x, y, width, .. } = area; let style = Style::default().gray(); let timer = format!("{rep}.{step:02} / {reps}.{steps}"); buf.set_string(x + width - 2 - timer.len() as u16, y + 1, &timer, style.bold().not_dim()); buf.set_string(x + 2, y + 1, &format!("⏹ STOP"), if s.playing { style.dim().bold() } else { style.not_dim().white().bold() }); buf.set_string(x, y + 2, format!("├{}┤", "-".repeat((area.width - 2).into())), style.dim()); //buf.set_string(x + 2, y + 2, //&format!("▶ PLAY"), if s.playing { //Style::default().green() //} else { //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, &format!("⏺ DUB"), if s.overdub { Style::default().bold().yellow() } else { Style::default().bold().dim() }); let clips = draw_clips(s, buf, area)?; Ok(Rect { x, y, width: area.width, height: 3 }) } fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, .. } = area; let style = Style::default().gray(); for i in 0..8 { buf.set_string(x + 2, y + 3 + i*2, &format!("▶ {}", &s.name), if i as usize == s.sequence { if s.playing { style.white().bold() } else { style.not_dim().bold() } } else { style.dim() }); } 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 { for (_, (_, 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 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) } pub const KEYMAP: &'static [KeyBinding] = keymap!(Sequencer { [Up, NONE, "cursor_up", "move cursor up", cursor_up], [Down, NONE, "cursor_down", "move cursor down", cursor_down], [Left, NONE, "cursor_left", "move cursor left", cursor_left], [Right, NONE, "cursor_right", "move cursor right", cursor_right], [Char(']'), NONE, "cursor_inc", "increase note duration", cursor_duration_inc], [Char('['), NONE, "cursor_dec", "decrease note duration", cursor_duration_dec], [Char('`'), NONE, "mode_next", "Next view mode", mode_next], [Char('+'), NONE, "zoom_in", "Zoom in", nop], [Char('-'), NONE, "zoom_out", "Zoom out", nop], [Char('a'), NONE, "note_add", "Add note", note_add], [Char('z'), NONE, "note_del", "Delete note", note_del], [CapsLock, NONE, "advance", "Toggle auto advance", nop], [Char('w'), NONE, "rest", "Advance by note duration", nop], [Char('r'), NONE, "toggle_record", "Toggle recording", toggle_record], [Char('d'), NONE, "toggle_overdub", "Toggle overdub", toggle_overdub], [Char(' '), NONE, "toggle_play", "Toggle play/pause", toggle_play], [Char('s'), NONE, "stop_and_rewind", "Stop and rewind", stop_and_rewind], [Char('q'), NONE, "quantize_next", "Next quantize value", quantize_next], [Char('Q'), SHIFT, "quantize_prev", "Previous quantize value", quantize_prev], [Char('n'), NONE, "note_axis", "Focus note axis", nop], [Char('t'), NONE, "time_axis", "Focus time axis", nop], [Char('v'), NONE, "variations", "Focus variation selector", nop], [Char('s'), SHIFT, "sync", "Focus sync selector", nop], [Char('1'), NONE, "seq_1", "Sequence 1", focus_seq(0)], [Char('2'), NONE, "seq_2", "Sequence 2", focus_seq(1)], [Char('3'), NONE, "seq_3", "Sequence 3", focus_seq(2)], [Char('4'), NONE, "seq_4", "Sequence 4", focus_seq(3)], [Char('5'), NONE, "seq_5", "Sequence 5", focus_seq(4)], [Char('6'), NONE, "seq_6", "Sequence 6", focus_seq(5)], [Char('7'), NONE, "seq_7", "Sequence 7", focus_seq(6)], [Char('8'), NONE, "seq_8", "Sequence 8", focus_seq(7)], }); const fn focus_seq (i: usize) -> impl Fn(&mut Sequencer)->Usually { move |s: &mut Sequencer| { s.sequence = i; Ok(true) } } fn nop (_: &mut Sequencer) -> Usually { Ok(false) } fn note_add (s: &mut Sequencer) -> Usually { let pos = s.transport.query().unwrap().pos; let usecs = s.timebase.frame_to_usec(pos.frame() as usize); let step = (s.time_axis.0 + s.time_cursor) as u32; let start = (step as usize * s.timebase.ppq() / s.resolution) as u32; let end = ((step + 1) as usize * s.timebase.ppq() / s.resolution) as u32; 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() }; let mut sequence = &mut s.sequences[s.sequence]; if sequence.contains_key(&start) { sequence.get_mut(&start).unwrap().push(note_on.clone()); } else { sequence.insert(start, vec![note_on]); } if sequence.contains_key(&end) { sequence.get_mut(&end).unwrap().push(note_off.clone()); } else { sequence.insert(end, vec![note_off]); }; Ok(true) } fn note_del (_: &mut Sequencer) -> Usually { Ok(true) } fn time_cursor_inc (s: &mut Sequencer) -> Usually { let time = s.time_axis.1 - s.time_axis.0; s.time_cursor = ((time + s.time_cursor) + 1) % time; Ok(true) } fn time_cursor_dec (s: &mut Sequencer) -> Usually { let time = s.time_axis.1 - s.time_axis.0; s.time_cursor = ((time + s.time_cursor) - 1) % time; Ok(true) } fn note_cursor_inc (s: &mut Sequencer) -> Usually { let note = s.note_axis.1 - s.note_axis.0; s.note_cursor = ((note + s.note_cursor) + 1) % note; Ok(true) } fn note_cursor_dec (s: &mut Sequencer) -> Usually { let note = s.note_axis.1 - s.note_axis.0; s.note_cursor = ((note + s.note_cursor) - 1) % note; Ok(true) } fn cursor_up (s: &mut Sequencer) -> Usually { match s.mode { SequencerView::Vertical => time_cursor_dec(s), SequencerView::Horizontal => note_cursor_dec(s), _ => Ok(false) }; Ok(true) } fn cursor_down (s: &mut Sequencer) -> Usually { match s.mode { SequencerView::Vertical => time_cursor_inc(s), SequencerView::Horizontal => note_cursor_inc(s), _ => Ok(false) }; Ok(true) } fn cursor_left (s: &mut Sequencer) -> Usually { match s.mode { SequencerView::Vertical => note_cursor_dec(s), SequencerView::Horizontal => time_cursor_dec(s), _ => Ok(false) }; Ok(true) } fn cursor_right (s: &mut Sequencer) -> Usually { match s.mode { SequencerView::Vertical => note_cursor_inc(s), SequencerView::Horizontal => time_cursor_inc(s), _ => Ok(false) }; Ok(true) } fn cursor_duration_inc (_: &mut Sequencer) -> Usually { //s.cursor.2 = s.cursor.2 + 1 Ok(true) } fn cursor_duration_dec (_: &mut Sequencer) -> Usually { //if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 } Ok(true) } fn mode_next (s: &mut Sequencer) -> Usually { s.mode = s.mode.next(); Ok(true) } impl SequencerView { fn next (&self) -> Self { match self { Self::Horizontal => Self::Vertical, Self::Vertical => Self::Tiny, Self::Tiny => Self::Horizontal, _ => self.clone() } } } fn stop_and_rewind (s: &mut Sequencer) -> Usually { s.playing = false; Ok(true) } fn toggle_play (s: &mut Sequencer) -> Usually { s.playing = !s.playing; if s.playing { s.transport.start()?; } else { s.transport.stop()?; s.transport.locate(0)?; } Ok(true) } fn toggle_record (s: &mut Sequencer) -> Usually { s.recording = !s.recording; Ok(true) } fn toggle_overdub (s: &mut Sequencer) -> Usually { s.overdub = !s.overdub; Ok(true) } fn quantize_next (s: &mut Sequencer) -> Usually { if s.resolution < 64 { s.resolution = s.resolution * 2; } Ok(true) } fn quantize_prev (s: &mut Sequencer) -> Usually { if s.resolution > 1 { s.resolution = s.resolution / 2; } Ok(true) } #[cfg(test)] mod test { use super::*; #[test] fn test_sequencer_output () -> Usually<()> { let sequencer = Sequencer::new("test")?; let mut s = sequencer.state.lock().unwrap(); s.rate = Hz(48000); s.tempo = Tempo(240_000); println!("F/S = {:.03}", s.fps()); println!("B/S = {:.03}", s.bps()); println!("F/B = {:.03}", s.fpb()); println!("T/B = {:.03}", s.tpb()); println!("F/T = {:.03}", s.fpt()); println!("F/L = {:.03}", s.fpl()); println!("T/L = {:.03}", s.tpl()); let fpt = s.fpt(); let frames_per_chunk = 240; let chunk = |chunk: usize| s.frames_to_ticks( chunk * frames_per_chunk, (chunk + 1) * frames_per_chunk ); //for i in 0..2000 { //println!("{i} {:?}", chunk(i)); //} assert_eq!(chunk(0), vec![(0, 0), (125, 1)]); assert_eq!(chunk(1), vec![(10, 2), (135, 3)]); assert_eq!(chunk(12), vec![(120, 24)]); assert_eq!(chunk(412), vec![(120, 24)]); assert_eq!(chunk(413), vec![(5, 25), (130, 26)]); Ok(()) } }