use crate::*; /// MIDI message structural pub type PhraseData = Vec>; /// MIDI message serialized pub type PhraseMessage = Vec; /// Collection of serialized MIDI messages pub type PhraseChunk = [Vec]; /// Root level object for standalone `tek_sequencer` pub struct Sequencer { /// Which view is focused pub focus_cursor: (usize, usize), /// Controls the JACK transport. pub transport: Option>>>, /// Pool of all phrases available to the sequencer pub phrases: Arc>>, /// Phrase editor view pub editor: PhraseEditor, } /// Sections in the sequencer app that may be focused #[derive(Copy, Clone, PartialEq, Eq)] pub enum SequencerFocus { Transport, PhrasePool, PhraseEditor } /// Contains all phrases in a project pub struct PhrasePool { _engine: PhantomData, /// Scroll offset pub scroll: usize, /// Highlighted phrase pub phrase: usize, /// Phrases in the pool pub phrases: Vec>>, /// Whether this widget is focused pub focused: bool, /// Mode switch pub mode: Option, } /// Modes for phrase pool pub enum PhrasePoolMode { Rename(usize) } /// A MIDI sequence. #[derive(Debug, Clone)] pub struct Phrase { pub uuid: uuid::Uuid, /// Name of phrase pub name: Arc>, /// Temporal resolution in pulses per quarter note pub ppq: usize, /// Length of phrase in pulses pub length: usize, /// Notes in phrase pub notes: PhraseData, /// Whether to loop the phrase or play it once pub loop_on: bool, /// Start of loop pub loop_start: usize, /// Length of loop pub loop_length: usize, /// All notes are displayed with minimum length pub percussive: bool, /// Identifying color of phrase pub color: Color, } /// Contains state for viewing and editing a phrase pub struct PhraseEditor { _engine: PhantomData, /// Phrase being played pub phrase: Option>>, /// The full piano keys are rendered to this buffer pub keys: Buffer, /// The full piano roll is rendered to this buffer pub buffer: BigBuffer, /// Cursor/scroll/zoom in pitch axis pub note_axis: FixedAxis, /// Cursor/scroll/zoom in time axis pub time_axis: ScaledAxis, /// Whether this widget is focused pub focused: bool, /// Whether note enter mode is enabled pub entered: bool, /// Display mode pub mode: bool, /// Notes currently held at input pub notes_in: Arc>, /// Notes currently held at output pub notes_out: Arc>, } /// Phrase player. pub struct PhrasePlayer { _engine: PhantomData, /// Phrase being played pub phrase: Option>>, /// Notes currently held at input pub notes_in: Arc>, /// Notes currently held at output pub notes_out: Arc>, /// Current point in playing phrase pub now: usize, /// Play input through output. pub monitoring: bool, /// Write input to sequence. pub recording: bool, /// Overdub input to sequence. pub overdub: bool, /// Output from current sequence. pub midi_out: Option>, /// MIDI output buffer midi_out_buf: Vec>>, /// Send all notes off pub reset: bool, // TODO?: after Some(nframes) } /// Focus layout of sequencer app impl FocusGrid for Sequencer { fn cursor (&self) -> (usize, usize) { self.focus_cursor } fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor } fn layout (&self) -> &[&[SequencerFocus]] { &[ &[SequencerFocus::Transport], &[SequencerFocus::PhrasePool, SequencerFocus::PhraseEditor], ] } fn update_focus (&mut self) { let focused = *self.focused(); if let Some(transport) = self.transport.as_ref() { transport.write().unwrap().focused = focused == SequencerFocus::Transport } self.phrases.write().unwrap().focused = focused == SequencerFocus::PhrasePool; self.editor.focused = focused == SequencerFocus::PhraseEditor; } } impl PhrasePool { pub fn new () -> Self { Self { _engine: Default::default(), scroll: 0, phrase: 0, phrases: vec![Arc::new(RwLock::new(Phrase::default()))], focused: false, mode: None, } } pub fn phrase (&self) -> &Arc> { &self.phrases[self.phrase] } } impl PhraseEditor { pub fn new () -> Self { Self { _engine: Default::default(), phrase: None, notes_in: Arc::new(RwLock::new([false;128])), notes_out: Arc::new(RwLock::new([false;128])), keys: keys_vert(), buffer: Default::default(), note_axis: FixedAxis { start: 12, point: Some(36) }, time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, focused: false, entered: false, mode: false, } } } pub fn random_color () -> Color { let mut rng = thread_rng(); let color: Okhsl = Okhsl::new( rng.gen::() * 360f32 - 180f32, rng.gen::() * 0.5 + 0.25, rng.gen::() * 0.5 + 0.25, ); let color: Srgb = Srgb::from_color_unclamped(color); Color::Rgb( (color.red * 255.0) as u8, (color.green * 255.0) as u8, (color.blue * 255.0) as u8, ) } impl Phrase { pub fn new ( name: &str, loop_on: bool, length: usize, notes: Option, color: Option, ) -> Self { Self { uuid: uuid::Uuid::new_v4(), name: Arc::new(RwLock::new(name.into())), ppq: PPQ, length, notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), loop_on, loop_start: 0, loop_length: length, percussive: true, color: color.unwrap_or_else(random_color) } } pub fn toggle_loop (&mut self) { self.loop_on = !self.loop_on; } pub fn record_event (&mut self, pulse: usize, message: MidiMessage) { if pulse >= self.length { panic!("extend phrase first") } self.notes[pulse].push(message); } /// Check if a range `start..end` contains MIDI Note On `k` pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { //panic!("{:?} {start} {end}", &self); for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { for event in events.iter() { match event { MidiMessage::NoteOn {key,..} => { if *key == k { return true } } _ => {} } } } return false } /// Write a chunk of MIDI events to an output port. pub fn process_out ( &self, output: &mut PhraseChunk, notes_on: &mut [bool;128], timebase: &Arc, (frame0, frames, _): (usize, usize, f64), ) { let mut buf = Vec::with_capacity(8); for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames( frame0, frame0 + frames ) { let tick = tick % self.length; for message in self.notes[tick].iter() { buf.clear(); let channel = 0.into(); let message = *message; LiveEvent::Midi { channel, message }.write(&mut buf).unwrap(); output[time as usize].push(buf.clone()); match message { MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true, MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false, _ => {} } } } } } impl Default for Phrase { fn default () -> Self { Self::new("", false, 0, None, Some(Color::Rgb(0, 0, 0))) } } impl std::cmp::PartialEq for Phrase { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } impl Eq for Phrase {} impl PhrasePlayer { pub fn new (name: &str) -> Self { Self { _engine: Default::default(), phrase: None, notes_in: Arc::new(RwLock::new([false;128])), notes_out: Arc::new(RwLock::new([false;128])), monitoring: false, recording: false, overdub: true, midi_out: None, midi_out_buf: vec![Vec::with_capacity(16);16384], reset: true, now: 0, } } pub fn toggle_monitor (&mut self) { self.monitoring = !self.monitoring; } pub fn toggle_record (&mut self) { self.recording = !self.recording; } pub fn toggle_overdub (&mut self) { self.overdub = !self.overdub; } pub fn process ( &mut self, input: Option, timebase: &Arc, playing: Option, started: Option<(usize, usize)>, quant: usize, reset: bool, scope: &ProcessScope, (frame0, frames): (usize, usize), (_usec0, _usecs): (usize, usize), period: f64, ) { if self.midi_out.is_some() { // Clear the section of the output buffer that we will be using for frame in &mut self.midi_out_buf[0..frames] { frame.clear(); } // Emit "all notes off" at start of buffer if requested if self.reset { all_notes_off(&mut self.midi_out_buf); self.reset = false; } else if reset { all_notes_off(&mut self.midi_out_buf); } } if let ( Some(TransportState::Rolling), Some((start_frame, _)), Some(ref phrase) ) = (playing, started, &self.phrase) { phrase.read().map(|phrase|{ if self.midi_out.is_some() { phrase.process_out( &mut self.midi_out_buf, &mut self.notes_out.write().unwrap(), timebase, (frame0.saturating_sub(start_frame), frames, period) ); } }).unwrap(); let mut phrase = phrase.write().unwrap(); let length = phrase.length; // Monitor and record input if input.is_some() && (self.recording || self.monitoring) { // For highlighting keys and note repeat let mut notes_in = self.notes_in.write().unwrap(); for (frame, event, bytes) in parse_midi_input(input.unwrap()) { match event { LiveEvent::Midi { message, .. } => { if self.monitoring { self.midi_out_buf[frame].push(bytes.to_vec()) } if self.recording { phrase.record_event({ let pulse = timebase.frame_to_pulse( (frame0 + frame - start_frame) as f64 ); let quantized = ( pulse / quant as f64 ).round() as usize * quant; let looped = quantized % length; looped }, message); } match message { MidiMessage::NoteOn { key, .. } => { notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { notes_in[key.as_int() as usize] = false; }, _ => {} } }, _ => {} } } } } else if input.is_some() && self.midi_out.is_some() && self.monitoring { let mut notes_in = self.notes_in.write().unwrap(); for (frame, event, bytes) in parse_midi_input(input.unwrap()) { match event { LiveEvent::Midi { message, .. } => { self.midi_out_buf[frame].push(bytes.to_vec()); match message { MidiMessage::NoteOn { key, .. } => { notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { notes_in[key.as_int() as usize] = false; }, _ => {} } }, _ => {} } } } if let Some(out) = &mut self.midi_out { let writer = &mut out.writer(scope); let output = &self.midi_out_buf; for time in 0..frames { for event in output[time].iter() { writer.write(&RawMidi { time: time as u32, bytes: &event }) .expect(&format!("{event:?}")); } } } } } /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut PhraseChunk) { 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(); output[0].push(buf); } /// Return boxed iterator of MIDI events pub fn parse_midi_input (input: MidiIter) -> Box + '_> { Box::new(input.map(|RawMidi { time, bytes }|( time as usize, LiveEvent::parse(bytes).unwrap(), bytes ))) }