use crate::core::*; use crate::layout::*; mod keys; use self::keys::*; mod handle; pub use self::handle::*; mod phrase; pub use self::phrase::*; pub mod horizontal; pub mod vertical; pub struct Sequencer { pub name: String, /// JACK transport handle. pub transport: ::jack::Transport, /// JACK MIDI input port that will be created. pub midi_in: Port, /// JACK MIDI output port that will be created. pub midi_out: Port, /// Holds info about tempo pub timebase: Arc, /// Phrase selector pub sequence: Option, /// Map: tick -> MIDI events at tick pub phrases: Vec, /// Red keys on piano roll. pub notes_on: Vec, /// Play sequence to output. pub playing: TransportState, /// Play input through output. pub monitoring: bool, /// Write input to sequence. pub recording: bool, /// Don't delete when recording. pub overdub: bool, /// Display mode pub view: SequencerView, /// Range of notes to display pub note_start: usize, /// Position of cursor within note range pub note_cursor: usize, /// PPM per display unit pub time_zoom: usize, /// Range of time steps to display pub time_start: usize, /// Position of cursor within time range pub time_cursor: usize, } #[derive(Debug, Clone)] pub enum SequencerView { Tiny, Compact, Horizontal, Vertical, } impl Sequencer { pub fn new ( name: &str, timebase: &Arc, phrases: Option>, ) -> Usually> { let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; let transport = client.transport(); DynamicDevice::new(render, handle, Self::process, Self { name: name.into(), timebase: timebase.clone(), phrases: phrases.unwrap_or_else(||vec![Phrase::default()]), sequence: Some(0), transport, midi_in: client.register_port("in", MidiIn::default())?, monitoring: true, recording: true, midi_out: client.register_port("out", MidiOut::default())?, playing: TransportState::Starting, overdub: true, view: SequencerView::Horizontal, notes_on: vec![false;128], note_start: 12, note_cursor: 0, time_zoom: 24, time_start: 0, time_cursor: 0, }).activate(client) } pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> { self.phrases.get(self.sequence?) } pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { if self.sequence.is_none() { return Control::Continue } let phrase = self.phrases.get_mut(self.sequence.unwrap()); if phrase.is_none() { return Control::Continue } let phrase = phrase.unwrap(); let frame = scope.last_frame_time() as usize; let frames = scope.n_frames() as usize; let mut output: Vec>>> = vec![None;frames]; let transport = self.transport.query().unwrap(); if transport.state != self.playing { all_notes_off(&mut output); } self.playing = transport.state; // Play from phrase into output buffer if self.playing == TransportState::Rolling { phrase.process_out( &mut output, &mut self.notes_on, &self.timebase, frame, frames ); } // Play from input to monitor, and record into phrase. phrase.process_in( self.midi_in.iter(scope), &mut self.notes_on, if self.monitoring { Some(&mut output) } else { None }, self.recording && self.playing == TransportState::Rolling, &self.timebase, frame, ); write_output(&mut self.midi_out.writer(scope), &mut output, frames); Control::Continue } } impl PortList for Sequencer { fn midi_ins (&self) -> Usually> { Ok(vec![self.midi_in.name()?]) } fn midi_outs (&self) -> Usually> { Ok(vec![self.midi_out.name()?]) } } fn render (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, height } = area; let header = draw_header(s, buf, area)?; let piano = match s.view { 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, height, })?, SequencerView::Horizontal => self::horizontal::draw( buf, Rect { x, y: y + header.height, width, height, }, s.phrase(), s.timebase.ppq() as usize, s.time_cursor, s.time_start, s.time_zoom, s.note_cursor, s.note_start, None )?, }; Ok(draw_box(buf, Rect { x, y, width: header.width.max(piano.width), height: header.height + piano.height })) } pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, .. } = area; let style = Style::default().gray(); crate::device::transport::draw_play_stop(buf, x + 2, y + 1, &s.playing); let separator = format!("├{}┤", "-".repeat((width - 2).into())); separator.blit(buf, x, y + 2, Some(style.dim())); crate::device::transport::draw_rec(buf, x + 13, y + 1, s.recording); crate::device::transport::draw_dub(buf, x + 20, y + 1, s.overdub); crate::device::transport::draw_mon(buf, x + 27, y + 1, s.monitoring); let _ = draw_clips(s, buf, area)?; Ok(Rect { x, y, width, height: 3 }) } pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timer: &str) { let style = Some(Style::default().gray().bold().not_dim()); timer.blit(buf, x - timer.len() as u16, y, style); } pub 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.phrases.iter().enumerate() { let label = format!("▶ {}", &sequence.name); label.blit(buf, x + 2, y + 3 + (i as u16)*2, Some(if Some(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 }) } /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut MIDIChunk) { output[0] = Some(vec![]); if let Some(Some(frame)) = output.get_mut(0) { let mut buf = vec![]; let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() }; let evt = LiveEvent::Midi { channel: 0.into(), message: msg }; evt.write(&mut buf).unwrap(); frame.push(buf); } } /// Write to JACK port from output buffer (containing notes from sequence and/or monitor) fn write_output (writer: &mut ::jack::MidiWriter, output: &mut MIDIChunk, frames: usize) { for time in 0..frames { if let Some(Some(frame)) = output.get_mut(time ) { for event in frame.iter() { writer.write(&::jack::RawMidi { time: time as u32, bytes: &event }) .expect(&format!("{event:?}")); } } } }