tek/crates/tek_sequencer/src/sequencer.rs

411 lines
13 KiB
Rust

use crate::*;
/// MIDI message structural
pub type PhraseData = Vec<Vec<MidiMessage>>;
/// MIDI message serialized
pub type PhraseMessage = Vec<u8>;
/// Collection of serialized MIDI messages
pub type PhraseChunk = [Vec<PhraseMessage>];
/// Root level object for standalone `tek_sequencer`
pub struct Sequencer<E: Engine> {
/// Which view is focused
pub focus_cursor: (usize, usize),
/// Controls the JACK transport.
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
/// Pool of all phrases available to the sequencer
pub phrases: Arc<RwLock<PhrasePool<E>>>,
/// Phrase editor view
pub editor: PhraseEditor<E>,
}
/// 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<E: Engine> {
_engine: PhantomData<E>,
/// Scroll offset
pub scroll: usize,
/// Highlighted phrase
pub phrase: usize,
/// Phrases in the pool
pub phrases: Vec<Arc<RwLock<Phrase>>>,
/// Whether this widget is focused
pub focused: bool,
/// Mode switch
pub mode: Option<PhrasePoolMode>,
}
/// 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<E: Engine> {
_engine: PhantomData<E>,
/// Phrase being played
pub phrase: Option<Arc<RwLock<Phrase>>>,
/// 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<usize>,
/// Cursor/scroll/zoom in time axis
pub time_axis: ScaledAxis<usize>,
/// 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<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
}
/// Phrase player.
pub struct PhrasePlayer<E: Engine> {
_engine: PhantomData<E>,
/// Phrase being played
pub phrase: Option<Arc<RwLock<Phrase>>>,
/// Notes currently held at input
pub notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub notes_out: Arc<RwLock<[bool; 128]>>,
/// 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<Port<MidiOut>>,
/// MIDI output buffer
pub midi_out_buf: Vec<Vec<Vec<u8>>>,
/// Send all notes off
pub reset: bool, // TODO?: after Some(nframes)
}
/// Focus layout of sequencer app
impl<E: Engine> FocusGrid<SequencerFocus> for Sequencer<E> {
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<E: Engine> PhrasePool<E> {
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<RwLock<Phrase>> {
&self.phrases[self.phrase]
}
pub fn index_of (&self, phrase: &Phrase) -> Option<usize> {
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<Color>) {
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<Color>) {
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<E: Engine> PhraseEditor<E> {
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<str>,
loop_on: bool,
length: usize,
notes: Option<PhraseData>,
color: Option<Color>,
) -> 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<E: Engine> PhrasePlayer<E> {
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<E: Engine> {
_engine: PhantomData<E>,
/// 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<PhraseLengthFocus>,
}
impl<E: Engine> PhraseLength<E> {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> 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,
}
}
}