use crate::*; #[derive(Clone, PartialEq)] pub enum SequencerCommand { Focus(FocusCommand), Transport(TransportCommand), Phrases(PhrasePoolCommand), Editor(PhraseEditorCommand), } #[derive(Clone, PartialEq)] pub enum PhraseEditorCommand { // TODO: 1-9 seek markers that by default start every 8th of the phrase ToggleDirection, EnterEditMode, ExitEditMode, NoteAppend, NoteSet, NoteCursorSet(usize), NoteLengthSet(usize), NoteScrollSet(usize), TimeCursorSet(usize), TimeScrollSet(usize), TimeZoomSet(usize), Go(Direction), } #[derive(Debug)] pub struct SequencerTrack { /// Name of track pub name: Arc>, /// Preferred width of track column pub width: usize, /// Identifying color of track pub color: ItemColor, /// Global timebase pub clock: Arc, /// Start time and phrase being played pub phrase: Option<(Instant, Option>>)>, /// Start time and next phrase pub next_phrase: Option<(Instant, Option>>)>, /// Play input through output. pub monitoring: bool, /// Write input to sequence. pub recording: bool, /// Overdub input to sequence. pub overdub: bool, /// Send all notes off pub reset: bool, // TODO?: after Some(nframes) /// Record from MIDI ports to current sequence. pub midi_inputs: Vec>, /// Play from current sequence to MIDI ports pub midi_outputs: Vec>, /// MIDI output buffer pub midi_note: Vec, /// MIDI output buffer pub midi_chunk: Vec>>, /// Notes currently held at input pub notes_in: Arc>, /// Notes currently held at output pub notes_out: Arc>, } /// JACK process callback for a sequencer's phrase player/recorder. impl Audio for SequencerTrack { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { let has_midi_outputs = self.has_midi_outputs(); let has_midi_inputs = self.has_midi_inputs(); // Clear output buffer(s) self.clear(scope, false); // Write chunk of phrase to output, handle switchover if self.play(scope) { self.switchover(scope); } if has_midi_inputs { if self.recording || self.monitoring { // Record and/or monitor input self.record(scope) } else if has_midi_outputs && self.monitoring { // Monitor input to output self.monitor(scope) } } // Write to output port(s) self.write(scope); Control::Continue } } /// Methods used primarily by the process callback impl SequencerTrack { fn is_rolling (&self) -> bool { *self.clock.playing.read().unwrap() == Some(TransportState::Rolling) } fn has_midi_inputs (&self) -> bool { self.midi_inputs.len() > 0 } fn has_midi_outputs (&self) -> bool { self.midi_outputs.len() > 0 } /// Clear the section of the output buffer that we will be using, /// emitting "all notes off" at start of buffer if requested. fn clear (&mut self, scope: &ProcessScope, force_reset: bool) { for frame in &mut self.midi_chunk[0..scope.n_frames() as usize] { frame.clear(); } if self.reset || force_reset { all_notes_off(&mut self.midi_chunk); self.reset = false; } } fn play (&mut self, scope: &ProcessScope) -> bool { let mut next = false; // Write MIDI events from currently playing phrase (if any) to MIDI output buffer if self.is_rolling() { let sample0 = scope.last_frame_time() as usize; let samples = scope.n_frames() as usize; // If no phrase is playing, prepare for switchover immediately next = self.phrase.is_none(); if let Some((started, phrase)) = &self.phrase { // First sample to populate. Greater than 0 means that the first // pulse of the phrase falls somewhere in the middle of the chunk. let sample = started.sample.get() as usize; let sample = sample + self.clock.started.read().unwrap().unwrap().0; let sample = sample0.saturating_sub(sample); // Iterator that emits sample (index into output buffer at which to write MIDI event) // paired with pulse (index into phrase from which to take the MIDI event) for each // sample of the output buffer that corresponds to a MIDI pulse. let pulses = self.clock.timebase().pulses_between_samples(sample, sample + samples); // Notes active during current chunk. let notes = &mut self.notes_out.write().unwrap(); for (sample, pulse) in pulses { // If a next phrase is enqueued, and we're past the end of the current one, // break the loop here (FIXME count pulse correctly) next = self.next_phrase.is_some() && if let Some(ref phrase) = phrase { pulse >= phrase.read().unwrap().length } else { true }; if next { break } // If there's a currently playing phrase, output notes from it to buffer: if let Some(ref phrase) = phrase { // Source phrase from which the MIDI events will be taken. let phrase = phrase.read().unwrap(); // Current pulse index in source phrase let pulse = pulse % phrase.length; // Output each MIDI event from phrase at appropriate frames of output buffer: for message in phrase.notes[pulse].iter() { // Clear output buffer for this MIDI event. self.midi_note.clear(); // TODO: support MIDI channels other than CH1. let channel = 0.into(); // Serialize MIDI event into message buffer. LiveEvent::Midi { channel, message: *message } .write(&mut self.midi_note) .unwrap(); // Append serialized message to output buffer. self.midi_chunk[sample].push(self.midi_note.clone()); // Update the list of currently held notes. update_keys(notes, &message); } } } } } next } fn switchover (&mut self, scope: &ProcessScope) { if self.is_rolling() { let sample0 = scope.last_frame_time() as usize; //let samples = scope.n_frames() as usize; if let Some((start_at, phrase)) = &self.next_phrase { let start = start_at.sample.get() as usize; let sample = self.clock.started.read().unwrap().unwrap().0; // If it's time to switch to the next phrase: if start <= sample0.saturating_sub(sample) { // Samples elapsed since phrase was supposed to start let skipped = sample0 - start; // Switch over to enqueued phrase let started = Instant::from_sample(&self.clock.timebase(), start as f64); self.phrase = Some((started, phrase.clone())); // Unset enqueuement (TODO: where to implement looping?) self.next_phrase = None } // TODO fill in remaining ticks of chunk from next phrase. // ?? just call self.play(scope) again, since enqueuement is off ??? self.play(scope); // ?? or must it be with modified scope ?? // likely not because start time etc } } } fn record (&mut self, scope: &ProcessScope) { let sample0 = scope.last_frame_time() as usize; if let (true, Some((started, phrase))) = (self.is_rolling(), &self.phrase) { let start = started.sample.get() as usize; let quant = self.clock.quant.get(); // For highlighting keys and note repeat let mut notes_in = self.notes_in.write().unwrap(); // Record from each input for input in self.midi_inputs.iter() { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { if self.monitoring { self.midi_chunk[sample].push(bytes.to_vec()) } if self.recording { if let Some(phrase) = phrase { let mut phrase = phrase.write().unwrap(); let length = phrase.length; phrase.record_event({ let sample = (sample0 + sample - start) as f64; let pulse = self.clock.timebase().samples_to_pulse(sample); let quantized = (pulse / quant).round() * quant; let looped = quantized as usize % length; looped }, message); } } update_keys(&mut notes_in, &message); } } } } if let (true, Some((start_at, phrase))) = (self.is_rolling(), &self.next_phrase) { // TODO switch to next phrase and record into it } } fn monitor (&mut self, scope: &ProcessScope) { let mut notes_in = self.notes_in.write().unwrap(); for input in self.midi_inputs.iter() { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { self.midi_chunk[sample].push(bytes.to_vec()); update_keys(&mut notes_in, &message); } } } } fn write (&mut self, scope: &ProcessScope) { let samples = scope.n_frames() as usize; for port in self.midi_outputs.iter_mut() { let writer = &mut port.writer(scope); let output = &self.midi_chunk; for time in 0..samples { 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 ))) } /// Update notes_in array pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { match message { MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, _ => {} } }