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 { /// The transport (toolbar) is focused Transport, /// The phrase list (pool) is focused PhrasePool, /// The phrase editor (sequencer) is focused PhraseEditor, } /// Status bar for sequencer app pub enum SequencerStatusBar { 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 { /// Renaming a pattern Rename(usize, String), /// Editing the length of a pattern Length(usize, usize, PhraseLengthFocus), } /// A MIDI sequence. #[derive(Debug, Clone)] pub struct Phrase { pub uuid: uuid::Uuid, /// Name of phrase pub name: String, /// 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>>, /// Length of note that will be inserted, in pulses pub note_len: usize, /// 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 pub 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] } pub fn index_of (&self, phrase: &Phrase) -> Option { for i in 0..self.phrases.len() { if *self.phrases[i].read().unwrap() == *phrase { return Some(i) } } return None } pub fn len (&self) -> usize { self.phrases.len() } pub fn index_before (&self, index: usize) -> usize { index.overflowing_sub(1).0.min(self.len() - 1) } pub fn index_after (&self, index: usize) -> usize { (index + 1) % self.len() } pub fn select_prev (&mut self) { self.phrase = self.index_before(self.phrase) } pub fn select_next (&mut self) { self.phrase = self.index_after(self.phrase) } pub fn append_new (&mut self, name: Option<&str>, color: Option) { let mut phrase = Phrase::default(); phrase.name = String::from(name.unwrap_or("(new)")); phrase.color = color.unwrap_or_else(random_color); phrase.length = 4 * PPQ; phrase.notes = vec![Vec::with_capacity(16);phrase.length]; self.phrases.push(Arc::new(RwLock::new(phrase))); self.phrase = self.phrases.len() - 1; } pub fn insert_new (&mut self, name: Option<&str>, color: Option) { let mut phrase = Phrase::default(); phrase.name = String::from(name.unwrap_or("(new)")); phrase.color = color.unwrap_or_else(random_color); phrase.length = 4 * PPQ; phrase.notes = vec![Vec::with_capacity(16);phrase.length]; self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); self.phrase += 1; } pub fn insert_dup (&mut self) { let mut phrase = self.phrases[self.phrase].read().unwrap().duplicate(); phrase.color = random_color_near(phrase.color, 0.2); self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); self.phrase += 1; } pub fn randomize_color (&mut self) { let mut phrase = self.phrases[self.phrase].write().unwrap(); phrase.color = random_color(); } pub fn begin_rename (&mut self) { self.mode = Some(PhrasePoolMode::Rename( self.phrase, self.phrases[self.phrase].read().unwrap().name.clone() )); } pub fn begin_length (&mut self) { self.mode = Some(PhrasePoolMode::Length( self.phrase, self.phrases[self.phrase].read().unwrap().length, PhraseLengthFocus::Bar )); } pub fn move_up (&mut self) { if self.phrase > 1 { self.phrases.swap(self.phrase - 1, self.phrase); self.phrase -= 1; } } pub fn move_down (&mut self) { if self.phrase < self.phrases.len().saturating_sub(1) { self.phrases.swap(self.phrase + 1, self.phrase); self.phrase += 1; } } } impl PhraseEditor { pub fn new () -> Self { Self { _engine: Default::default(), phrase: None, note_len: 24, 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, } } } impl Phrase { pub fn new ( name: impl AsRef, loop_on: bool, length: usize, notes: Option, color: Option, ) -> Self { Self { uuid: uuid::Uuid::new_v4(), name: name.as_ref().to_string(), 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 duplicate (&self) -> Self { let mut clone = self.clone(); clone.uuid = uuid::Uuid::new_v4(); clone } 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 } } impl Default for Phrase { fn default () -> Self { Self::new("(empty)", 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 () -> 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; } } /// Displays and edits phrase length pub struct PhraseLength { _engine: PhantomData, /// Pulses per beat (quaver) pub ppq: usize, /// Beats per bar pub bpb: usize, /// Length of phrase in pulses pub pulses: usize, /// Selected subdivision pub focus: Option, } impl PhraseLength { pub fn new (pulses: usize, focus: Option) -> Self { Self { _engine: Default::default(), ppq: PPQ, bpb: 4, pulses, focus } } pub fn bars (&self) -> usize { self.pulses / (self.bpb * self.ppq) } pub fn beats (&self) -> usize { (self.pulses % (self.bpb * self.ppq)) / self.ppq } pub fn ticks (&self) -> usize { self.pulses % self.ppq } pub fn bars_string (&self) -> String { format!("{}", self.bars()) } pub fn beats_string (&self) -> String { format!("{}", self.beats()) } pub fn ticks_string (&self) -> String { format!("{:>02}", self.ticks()) } } #[derive(Copy,Clone)] pub enum PhraseLengthFocus { /// Editing the number of bars Bar, /// Editing the number of beats Beat, /// Editing the number of ticks Tick, } impl PhraseLengthFocus { pub fn next (&mut self) { *self = match self { Self::Bar => Self::Beat, Self::Beat => Self::Tick, Self::Tick => Self::Bar, } } pub fn prev (&mut self) { *self = match self { Self::Bar => Self::Tick, Self::Beat => Self::Bar, Self::Tick => Self::Beat, } } }