use crate::prelude::*; use ratatui::style::Stylize; pub struct Sequencer { name: String, jack: Jack, playing: Arc, recording: Arc, overdub: Arc, inputs_open: Arc, outputs_open: Arc, cursor: (u16, u16, u16), timesig: (f32, f32), sequence: Arc>>>>, sequences: Arc>>, mode: SequencerMode, } pub enum SequencerMode { Tiny, Compact, Vertical, Horizontal, } impl Sequencer { pub fn new (name: &str) -> Result, Box> { let beats = 4; let steps = 16; let bpm = 120.0; let rate = 44100; // Hz let frame = 1f64 / rate as f64; // msec let buf = 512; // frames let t_beat = 60.0 / bpm; // msec let t_loop = t_beat * beats as f64; // msec let t_step = t_beat / steps as f64; // msec let exited = Arc::new(AtomicBool::new(false)); let playing = Arc::new(AtomicBool::new(true)); let recording = Arc::new(AtomicBool::new(true)); let overdub = Arc::new(AtomicBool::new(false)); let sequence: Arc>>>> = Arc::new( Mutex::new(vec![vec![None;64];128]) ); let mut step_frames = vec![]; for step in 0..beats*steps { let step_index = (step as f64 * t_step / frame) as usize; step_frames.push(step_index); } let loop_frames = (t_loop*rate as f64) as usize; let mut frame_steps: Vec> = vec![None;loop_frames]; for (index, frame) in step_frames.iter().enumerate() { frame_steps[*frame] = Some(index); } let (client, _status) = Client::new( name, ClientOptions::NO_START_SERVER )?; let mut input = client.register_port( "input", ::jack::MidiIn::default() )?; let mut output = client.register_port( "output", ::jack::MidiOut::default() )?; Ok(DynamicDevice::new(render, handle, |_|{}, Self { mode: SequencerMode::Vertical, name: name.into(), playing: playing.clone(), recording: recording.clone(), overdub: overdub.clone(), sequence: sequence.clone(), inputs_open: Arc::new(AtomicBool::new(false)), outputs_open: Arc::new(AtomicBool::new(false)), sequences: Arc::new(Mutex::new(vec![ ])), cursor: (11, 0, 0), timesig: (4.0, 4.0), jack: SequencerJack.activate(client, ClosureProcessHandler::new( Box::new(move |client: &Client, scope: &ProcessScope| process(client, scope)) as BoxedProcessHandler ))? })) } } pub fn process ( client: &Client, scope: &ProcessScope ) -> Control { //if exited.fetch_and(true, Ordering::Relaxed) { //return Control::Quit //} //if client.transport().query_state().unwrap() == TransportState::Rolling { //let chunk_start = scope.last_frame_time(); //let chunk_size = scope.n_frames(); //let chunk_end = chunk_start + chunk_size; //let start_looped = chunk_start as usize % loop_frames; //let end_looped = chunk_end as usize % loop_frames; //// Write MIDI notes from input to sequence //if recording.fetch_and(true, Ordering::Relaxed) { ////let overdub = overdub.fetch_and(true, Ordering::Relaxed); //for event in input.iter(scope) { //let ::jack::RawMidi { time, bytes } = event; //println!("\n{time} {bytes:?}"); //} //} //// Write MIDI notes from sequence to output //if playing.fetch_and(true, Ordering::Relaxed) { //let mut writer = output.writer(scope); //let sequence = sequence.lock().unwrap(); //for frame in 0..chunk_size { //let value = frame_steps[(start_looped + frame as usize) % loop_frames]; //if let Some(step) = value { //for track in sequence.iter() { //for event in track[step].iter() { //writer.write(&::jack::RawMidi { //time: frame as u32, //bytes: &match event { //SequencerEvent::NoteOn(pitch, velocity) => [ //0b10010000, //*pitch, //*velocity //], //SequencerEvent::NoteOff(pitch) => [ //0b10000000, //*pitch, //0b00000000 //], //} //}).unwrap() //} //} //} //} //} //} Control::Continue } pub const ACTIONS: [(&'static str, &'static str);4] = [ ("+/-", "Zoom"), ("A/D", "Add/delete note"), ("]/[", "Duration"), ("CapsLock", "Auto advance"), ]; pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box> { if let EngineEvent::Focus = event { } if let EngineEvent::Input(Event::Key(event)) = event { match event.code { KeyCode::Down => { state.cursor.0 = if state.cursor.0 >= 23 { 0 } else { state.cursor.0 + 1 } }, KeyCode::Up => { state.cursor.0 = if state.cursor.0 == 0 { 23 } else { state.cursor.0 - 1 } }, KeyCode::Left => { state.cursor.1 = if state.cursor.1 == 0 { 63 } else { state.cursor.1 - 1 } }, KeyCode::Right => { state.cursor.1 = if state.cursor.1 == 63 { 0 } else { state.cursor.1 + 1 } }, KeyCode::Char('[') => { if state.cursor.2 > 0 { state.cursor.2 = state.cursor.2 - 1 } }, KeyCode::Char(']') => { state.cursor.2 = state.cursor.2 + 1 }, KeyCode::Char('i') => { state.inputs_open.fetch_xor(true, Ordering::Relaxed); }, KeyCode::Char('o') => { state.outputs_open.fetch_xor(true, Ordering::Relaxed); }, KeyCode::Char('a') => { let row = state.cursor.0 as usize; let step = state.cursor.1 as usize; let duration = state.cursor.2 as usize; let mut sequence = state.sequence.lock().unwrap(); sequence[row][step] = Some(SequencerEvent::NoteOn(48 - row as u8, 128)); if state.cursor.2 > 0 { sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35)); } }, _ => { println!("{event:?}"); } } } Ok(()) } fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually { let header = draw_sequencer_header(sequencer, buf, area)?; let piano = match sequencer.mode { SequencerMode::Tiny => Rect { x: area.x, y: area.y, width: area.width, height: 0 }, SequencerMode::Compact => Rect { x: area.x, y: area.y, width: area.width, height: 0 }, SequencerMode::Vertical => draw_sequencer_vertical(sequencer, buf, Rect { x: area.x, y: area.y + header.height, width: area.width, height: area.height - header.height })?, _ => unimplemented!() }; //draw_box_styled(buf, area, Some(Style::default().red())); Ok(Rect { x: area.x, y: area.y, width: header.width.max(piano.width), height: header.height + piano.height }) } fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> 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 + 2, y + 1, &format!("{}", &sequencer.name), style.white().bold()); buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ ⏺ REC │ ⏺ DUB "), style); Ok(Rect { x, y, width, height: 4 }) } 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_HORIZONTAL_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_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, .. } = area; for i in 0..area.width-7 { let color = KEY_HORIZONTAL_STYLE[i as usize % 12]; buf.set_string(x + 5 + i, y - 1, &format!("▄"), color); buf.set_string(x + 5 + i, y, &format!("▀"), color); } let bg = Style::default().on_black(); for i in 0..area.height - 8 { buf.set_string(x + 5, y + 1 + i, &" ".repeat((area.width-7)as usize), bg); if i % 4 == 0 { buf.set_string(x + 2, y + 1 + i, &format!("{:2}", i / 4 + 1), Style::default()); } } for octave in -2i16..=8i16 { let x = x + 5 + (24i16 + octave * 12) as u16; if x >= area.x + area.width - 6 { break } buf.set_string( x, y + 1, &format!("C{octave}"), Style::default() ); } Ok(Rect { x, y, width: area.width, height: area.height - 6 }) } //{ //let mut area = area.clone(); //area.y = area.y + 3; //area.x = area.x + 1; //area.height = area.height - 2; //draw_sequence_keys(area, buf, //&sequencer.jack_client.as_client().transport().query().unwrap(), //&sequencer.sequence, //&sequencer.cursor); //draw_sequence_cursor(area, buf, //&sequencer.cursor); //} //draw_box(buf, Rect { x, y, width: 18, height }); //draw_box(buf, Rect { x, y, width: 40, height: 8 }); //buf.set_string(x + 1, y + 3, //&format!(" │ INPUT │ OUTPUT │"), //Style::default().gray()); //buf.set_string(x + 1, y + 5, //&format!(" {} │ IN │ ⏺ REC │ ⏺ DUB │ ▶ PLAY │ ⏹ STOP │ OUT │ 00:00.00 / 00:00.00", &sequencer.name), //Style::default().gray()); //if sequencer.inputs_open.fetch_and(true, Ordering::Relaxed) { //buf.set_string(x + 15, y + 1, "IN", Style::default().green().bold()); //} //if sequencer.outputs_open.fetch_and(true, Ordering::Relaxed) { //buf.set_string(x + 54, y + 1, "OUT", Style::default().green().bold()); //} //let mut command = |y2: u16, c: &str, ommand: &str, value: &str| { //buf.set_string(x + 1, y + y2, c, Style::default().bold()); //buf.set_string(x + 2, y + y2, ommand, Style::default().dim()); //buf.set_string(x + 4 + ommand.len() as u16, y + y2, value, Style::default().bold()); //}; //for (y, c, ommand, value) in [ ////(5, "I", "nputs", "[+]"), ////(6, "O", "utputs", "[+]"), ////(7, "C", "hannel", "01"), //(8, "G", "rid", "1/16"), //(9, "Z", "oom", "1/64"), //(10, "R", "ate", "1/1"), //(11, "S", "ync", "1 bar"), //(12, "A", "dd note", ""), //(13, "D", "elete note", ""), //] { //command(y, c, ommand, value) //} //draw_box(buf, Rect { x, y: y + height - 1, width, height: 3 }); //buf.set_string(x + 1, y + height, //&format!(" Grid 1/16 │ Zoom 1/64 │ Rate 1/1 │ Sync 1 bar │ Add note │ Delete note "), //Style::default().gray()); //buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", &sequencer.name), //Style::default().white().bold().not_dim()); //buf.set_string(x + 4, y + 0, "┬", Style::default().gray()); //buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim()); //buf.set_string(x + 4, y + 2, "┴", Style::default().gray()); //buf.set_string(x + 21, y + 1, " ⏺ REC DUB ", //Style::default().white().bold().not_dim()); //buf.set_string(x + 18, y + 0, "┬", Style::default().gray()); //buf.set_string(x + 18, y + 1, "│", Style::default().gray().dim()); //buf.set_string(x + 18, y + 2, "┴", Style::default().gray()); //draw_rec_dub_button(buf, x + 17, y); //draw_sequence_button(buf, x, y, &sequencer.name); //draw_leaf(buf, Rect { x, y: y + height, width, height: 2 }, 0, 0, "Inputs "); //draw_leaf(buf, Rect { x, y: y + height, width, height: 2 }, 0, 8, "Outputs"); //for x2 in 0..4 { //for y2 in 0..4 { //buf.set_string( //x + 2 + x2 * 3, //y + 5 + y2, //format!("{:>02} ", 1 + x2 + y2 * 4), //Style::default().green().bold().not_dim() //) //} //} // fn draw_sequence_button ( buf: &mut Buffer, x: u16, y: u16, name: &str ) { draw_box(buf, Rect { x, y, width: 18, height: 3 }); buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", name), Style::default().white().bold().not_dim()); buf.set_string(x + 4, y + 0, "┬", Style::default().gray()); buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim()); buf.set_string(x + 4, y + 2, "┴", Style::default().gray()); } fn draw_rec_dub_button ( buf: &mut Buffer, x: u16, y: u16, ) { draw_box(buf, Rect { x, y, width: 18, height: 3 }); buf.set_string(x + 1, y + 1, " ⏺ REC DUB ", Style::default().white().bold().not_dim()); buf.set_string(x + 5, y + 0, "┬", Style::default().gray()); buf.set_string(x + 5, y + 1, "│", Style::default().gray().dim()); buf.set_string(x + 5, y + 2, "┴", Style::default().gray()); buf.set_string(x + 11, y + 0, "┬", Style::default().gray()); buf.set_string(x + 11, y + 1, "│", Style::default().gray().dim()); buf.set_string(x + 11, y + 2, "┴", Style::default().gray()); } const NOTE_NAMES: [&'static str;12] = [ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", ]; const KEYS_VERTICAL: [&'static str; 6] = [ "▀", "▀", "▀", "█", "▄", "▄", ]; fn draw_sequence_keys ( area: Rect, buf: &mut Buffer, transport: &::jack::TransportStatePosition, sequence: &Arc>>>>, cursor: &(u16, u16, u16) ) { buf.set_string(area.x + 3, area.y, "╭1.1.", Style::default().dim()); buf.set_string(area.x + 3 + 16, area.y, "╭1.2.", Style::default().dim()); buf.set_string(area.x + 3 + 32, area.y, "╭1.3.", Style::default().dim()); buf.set_string(area.x + 3 + 48, area.y, "╭1.4.", Style::default().dim()); //buf.set_string(area.x + 2, area.y, "╭", Style::default().dim()); //buf.set_string(area.x + 2, area.y + 13, "╰", Style::default().dim()); buf.set_string(area.x + 2 + 65, area.y, "╮", Style::default().dim()); buf.set_string(area.x + 2 + 65, area.y + 13, "╯", Style::default().dim()); let frame = transport.pos.frame(); let rate = transport.pos.frame_rate().unwrap(); let second = (frame as f64) / (rate as f64); let minute = second / 60f64; let bpm = 120f64; let div = 4; let beats = minute * bpm; let bars = beats as u32 / div as u32; let beat = beats as u32 % div as u32 + 1; let beat_sub = beats % 1.0; //buf.set_string( //area.x - 18, area.y + area.height, //format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32), //Style::default() //); let sequence = sequence.lock().unwrap(); for key in 0..12 { buf.set_string(area.x, area.y + 1 + key, KEYS_VERTICAL[(key % 6) as usize], Style::default().dim()); buf.set_string(area.x + 1, area.y + 1 + key, "█", Style::default().dim()); for step in 0..64 { let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 { ratatui::style::Color::Gray } else if step == cursor.1 as usize || key == cursor.0/2 { ratatui::style::Color::Black } else { ratatui::style::Color::Reset }; let top = sequence[(key * 2) as usize][step].is_some(); let bottom = sequence[(key * 2 + 1) as usize][step].is_some(); match (top, bottom) { (true, true) => { buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "█", Style::default().yellow().not_dim().bold().bg(bg)); }, (true, false) => { buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▀", Style::default().yellow().not_dim().bold().bg(bg)); }, (false, true) => { buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▄", Style::default().yellow().not_dim().bold().bg(bg)); }, (false, false) => if step % 16 == 0 { buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "┊", Style::default().dim().bg(bg)) } else { buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "·", Style::default().dim().bg(bg)) }, } } } for step in 0..64 { if step % 8 == 0 { buf.set_string(area.x + 3 + step as u16, area.y + 1 + 12, [ "|A", "|B", "|C", "|D", "|E", "|F", "|G", "|H" ][step / 8 as usize], Style::default().dim()) } } } fn draw_sequence_cursor ( area: Rect, buf: &mut Buffer, cursor: &(u16, u16, u16) ) { let cursor_character = match cursor.0 % 2 { 0 => "▀", 1 => "▄", _ => unreachable!() }; let cursor_y = cursor.0 / 2; let cursor_text = if cursor.2 == 0 { cursor_character.into() } else { cursor_character.repeat(cursor.2 as usize) }; let style = Style::default().yellow().dim(); buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, cursor_text, style); } pub struct SequencerJack; impl SequencerJack { fn activate (self, client: Client, handler: ClosureProcessHandler) -> Usually>> { Ok(client.activate_async(self, handler)?) } } impl NotificationHandler for SequencerJack { fn thread_init (&self, _: &Client) { } fn shutdown (&mut self, status: ClientStatus, reason: &str) { } fn freewheel (&mut self, _: &Client, is_enabled: bool) { } fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { Control::Quit } fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { } fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { } fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { Control::Continue } fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { } fn graph_reorder (&mut self, _: &Client) -> Control { Control::Continue } fn xrun (&mut self, _: &Client) -> Control { Control::Continue } } pub struct MIDISequence { channels: [MIDISequenceChannel;16], } pub struct MIDISequenceChannel { number: u8, notes: [MIDISequenceVoice;128], } pub type MIDISequenceVoice = std::collections::BTreeMap; #[derive(Clone)] pub enum NoteEvent { On(u8), Off(u8), } const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new(); impl MIDISequence { fn new () -> Self { Self { channels: [ MIDISequenceChannel::new(1), MIDISequenceChannel::new(2), MIDISequenceChannel::new(3), MIDISequenceChannel::new(4), MIDISequenceChannel::new(5), MIDISequenceChannel::new(6), MIDISequenceChannel::new(7), MIDISequenceChannel::new(8), MIDISequenceChannel::new(9), MIDISequenceChannel::new(10), MIDISequenceChannel::new(11), MIDISequenceChannel::new(12), MIDISequenceChannel::new(13), MIDISequenceChannel::new(14), MIDISequenceChannel::new(15), MIDISequenceChannel::new(16), ] } } } impl MIDISequenceChannel { fn new (number: u8) -> Self { Self { number, notes: [VOICE_EMPTY;128] } } } #[derive(Clone)] pub enum SequencerEvent { NoteOn(u8, u8), NoteOff(u8) } #[cfg(test)] mod test { #[test] fn test_midi_frames () { let beats = 4; let steps = 16; let bpm = 120; let rate = 44100; // Hz let frame = 1f64 / rate as f64; // msec let buf = 512; // frames let t_beat = 60.0 / bpm as f64; // msec let t_loop = t_beat * beats as f64; // msec let t_step = t_beat / steps as f64; // msec let assign = |chunk: usize| { let start = chunk * buf; // frames let end = (chunk + 1) * buf; // frames println!("{chunk}: {start} .. {end}"); let mut steps: Vec<(usize, usize, f64)> = vec![]; for frame_index in start..end { let frame_msec = frame_index as f64 * frame; let offset = (frame_msec * 1000.0) % (t_step * 1000.0); if offset < 0.1 { let time = frame_index - start; let step_index = (frame_msec % t_loop / t_step) as usize; println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})"); steps.push((time, step_index, offset)); } } steps }; for chunk in 0..10 { let chunk = assign(chunk); //println!("{chunk} {:#?}", assign(chunk)); } } #[test] fn test_midi_frames_2 () { let beats = 4; let steps = 16; let bpm = 120; let rate = 44100; // Hz let frame = 1f64 / rate as f64; // msec let buf = 512; // frames let t_beat = 60.0 / bpm as f64; // msec let t_loop = t_beat * beats as f64; // msec let t_step = t_beat / steps as f64; // msec let mut step_frames = vec![]; for step in 0..beats*steps { let step_index = (step as f64 * t_step / frame) as usize; step_frames.push(step_index); } let loop_frames = (t_loop*rate as f64) as usize; let mut frame_steps: Vec> = vec![None;loop_frames]; for (index, frame) in step_frames.iter().enumerate() { println!("{index} {frame}"); frame_steps[*frame] = Some(index); } let assign = |chunk: usize| { let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames let (start_looped, end_looped) = (start % loop_frames, end % loop_frames); println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})"); let mut steps: Vec> = vec![None;buf]; for frame in 0..buf { let value = frame_steps[(start_looped + frame) as usize % loop_frames]; if value.is_some() { println!("{frame:03} = {value:?}, ") }; steps[frame as usize] = value; } steps }; for chunk in 0..1000 { let chunk = assign(chunk); //println!("{chunk} {:#?}", assign(chunk)); } } }