use crate::prelude::*; use ratatui::style::Stylize; mod keys; use keys::*; pub mod horizontal; pub mod vertical; pub struct Sequence { pub name: String, pub notes: std::collections::BTreeMap> } impl Sequence { pub fn new (name: &str) -> Self { Self { name: name.to_string(), notes: std::collections::BTreeMap::new() } } } pub struct Sequencer { pub name: String, /// JACK transport handle. transport: ::jack::Transport, /// JACK MIDI input port that will be created. input_port: Port, /// JACK MIDI output port that will be created. output_port: Port, /// 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, /// Sequence selector sequence: usize, /// Map: tick -> MIDI events at tick pub sequences: Vec, /// Red keys on piano roll. notes_on: Vec, /// Play sequence to output. playing: TransportState, /// Play input through output. monitoring: bool, /// Write input to sequence. recording: bool, /// Don't delete when recording. overdub: bool, /// 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)?; let transport = client.transport(); let state = transport.query_state()?; DynamicDevice::new(render, handle, Self::process, Self { name: name.into(), input_port: client.register_port("in", MidiIn::default())?, output_port: client.register_port("out", MidiOut::default())?, timebase: timebase.clone(), steps: 64, resolution: 4, sequence: 0, sequences: vec![ Sequence::new(&"Phrase#01"), Sequence::new(&"Phrase#02"), Sequence::new(&"Phrase#03"), Sequence::new(&"Phrase#04"), Sequence::new(&"Phrase#05"), Sequence::new(&"Phrase#06"), Sequence::new(&"Phrase#07"), Sequence::new(&"Phrase#08"), ], notes_on: vec![false;128], playing: TransportState::Starting, monitoring: true, recording: true, overdub: true, transport, 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 { // Update time let mut sequence = &mut self.sequences[self.sequence].notes; let transport = self.transport.query().unwrap(); self.playing = transport.state; let pos = &transport.pos; let usecs = self.timebase.frame_to_usec(pos.frame() as usize); let steps = usecs / self.timebase.usec_per_step(self.resolution as usize); let step = steps % self.steps; let tick = step * self.timebase.ppq() / self.resolution; // Prepare output buffer let frames = scope.n_frames() as usize; let mut output: Vec>>> = vec![None;frames]; // Read from sequence into output buffer if self.playing == TransportState::Rolling { let frame = transport.pos.frame() as usize; let quant = self.timebase.fpb() as usize * self.steps / self.resolution; let ticks = self.timebase.frames_to_ticks(frame, frame + frames, quant); for (time, tick) in ticks.iter() { if let Some(events) = sequence.get(&(*tick as u32)) { for message in events.iter() { let mut buf = vec![]; let channel = 0.into(); let message = *message; ::midly::live::LiveEvent::Midi { channel, message } .write(&mut buf) .unwrap(); let t = *time as usize; if output[t].is_none() { output[t] = Some(vec![]); } if let Some(Some(frame)) = output.get_mut(t) { frame.push(buf); } } } } } // Read from input, write inputs to sequence and/or output buffer for event in self.input_port.iter(scope) { let tick = tick as u32; let msg = midly::live::LiveEvent::parse(event.bytes).unwrap(); match msg { midly::live::LiveEvent::Midi { channel: _, message } => match message { midly::MidiMessage::NoteOn { key, vel: _ } => { self.notes_on[key.as_int() as usize] = true; if self.monitoring { let t = event.time as usize; if output[t].is_none() { output[t] = Some(vec![]); } if let Some(Some(frame)) = output.get_mut(t) { frame.push(event.bytes.into()) } } if self.recording { let contains = sequence.contains_key(&tick); if contains { sequence.get_mut(&tick).unwrap().push(message.clone()); } else { sequence.insert(tick, vec![message.clone()]); } } }, midly::MidiMessage::NoteOff { key, vel: _ } => { self.notes_on[key.as_int() as usize] = false; if self.monitoring { let t = event.time as usize; if output[t].is_none() { output[t] = Some(vec![]); } if let Some(Some(frame)) = output.get_mut(t) { frame.push(event.bytes.into()) } } if self.recording { let contains = sequence.contains_key(&tick); if contains { sequence.get_mut(&tick).unwrap().push(message.clone()); } else { sequence.insert(tick, vec![message.clone()]); } } }, _ => {} }, _ => {} } } // Write to port from output buffer // (containing notes from sequence and/or monitor) let mut writer = self.output_port.writer(scope); for time in 0..scope.n_frames() { if let Some(Some(frame)) = output.get_mut(time as usize) { for event in frame.iter() { writer.write(&::jack::RawMidi { time, bytes: &event }) .expect(&format!("{event:?}")); } } } Control::Continue } } 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 => self::vertical::draw(s, buf, Rect { x, y: y + header.height, width: 3 + note1 - note0, height: 3 + time1 - time0, }, steps)?, SequencerView::Horizontal => self::horizontal::draw(s, buf, Rect { x, y: y + header.height, width: area.width.max(3 + time1 - time0), height: 3 + note1 - note0, }, steps)?, }; 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, &match s.playing { TransportState::Rolling => format!("▶ PLAYING"), TransportState::Starting => format!("READY ..."), TransportState::Stopped => format!("⏹ STOPPED") }, match s.playing { TransportState::Stopped => style.dim().bold(), TransportState::Starting => style.not_dim().bold(), TransportState::Rolling => 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 + 13, y + 1, &format!("⏺ REC"), if s.recording { Style::default().bold().red() } else { Style::default().bold().dim() }); buf.set_string(x + 20, y + 1, &format!("⏺ DUB"), if s.overdub { Style::default().bold().yellow() } else { Style::default().bold().dim() }); buf.set_string(x + 27, 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 }) } fn draw_clips (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, .. } = area; let style = Style::default().gray(); for (i, sequence) in s.sequences.iter().enumerate() { let label = format!("▶ {}", &sequence.name); buf.set_string(x + 2, y + 3 + (i as u16)*2, &label, if i == s.sequence { match s.playing { TransportState::Rolling => style.white().bold(), _ => style.not_dim().bold() } } else { style.dim() }); } Ok(Rect { x, y, width: 14, height: 14 }) } pub fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool { for (_, (_, events)) in sequence.notes.range(start..end).enumerate() { for event in events.iter() { match event { ::midly::MidiMessage::NoteOn {key,..} => { if *key == k { return true } } _ => {} } } } return false } 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(' '), NONE, "toggle_play", "Toggle play/pause", toggle_play], [Char('r'), NONE, "toggle_record", "Toggle recording", toggle_record], [Char('d'), NONE, "toggle_overdub", "Toggle overdub", toggle_overdub], [Char('m'), NONE, "toggle_monitor", "Toggle input monitoring", toggle_monitor], [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].notes; 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.transport.stop()?; s.transport.locate(0)?; s.playing = TransportState::Stopped; Ok(true) } fn toggle_play (s: &mut Sequencer) -> Usually { s.playing = match s.playing { TransportState::Stopped => { s.transport.start()?; TransportState::Starting }, _ => { s.transport.stop()?; s.transport.locate(0)?; TransportState::Stopped }, }; 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 toggle_monitor (s: &mut Sequencer) -> Usually { s.monitoring = !s.monitoring; 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(()) } } impl DevicePorts for Sequencer { fn midi_ins (&self) -> Usually> { Ok(vec!["in".into()]) } fn midi_outs (&self) -> Usually> { Ok(vec!["out".into()]) } }