use crate::core::cell::Cell;
use crate::core::fixed::fixed::Q15;
use crate::core::sample::Sample;
use alloc::{string::String, vec::Vec};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum AudioSource {
Inline(Sample),
Pooled(usize),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Track {
Notes {
name: String,
instrument: usize,
rows: Vec<Cell>,
muted: bool,
},
Euclidean {
name: String,
instrument: usize,
muted: bool,
events: u8,
steps: u8,
rotation: u8,
prototype: Cell,
humanize_advance_max_ticks: u8,
humanize_probability: Q15,
},
Audio {
name: String,
muted: bool,
source: AudioSource,
#[serde(default)]
source_rate: u32,
},
}
impl Default for Track {
fn default() -> Self {
Track::Notes {
name: String::new(),
instrument: 0,
rows: Vec::new(),
muted: false,
}
}
}
impl Track {
#[inline]
pub fn name(&self) -> &str {
match self {
Track::Notes { name, .. }
| Track::Euclidean { name, .. }
| Track::Audio { name, .. } => name,
}
}
#[inline]
pub fn name_mut(&mut self) -> &mut String {
match self {
Track::Notes { name, .. }
| Track::Euclidean { name, .. }
| Track::Audio { name, .. } => name,
}
}
#[inline]
pub fn instrument(&self) -> usize {
match self {
Track::Notes { instrument, .. } | Track::Euclidean { instrument, .. } => *instrument,
Track::Audio { .. } => 0,
}
}
#[inline]
pub fn set_instrument(&mut self, idx: usize) {
match self {
Track::Notes { instrument, .. } | Track::Euclidean { instrument, .. } => {
*instrument = idx;
}
Track::Audio { .. } => {}
}
}
#[inline]
pub fn muted(&self) -> bool {
match self {
Track::Notes { muted, .. }
| Track::Euclidean { muted, .. }
| Track::Audio { muted, .. } => *muted,
}
}
#[inline]
pub fn set_muted(&mut self, m: bool) {
match self {
Track::Notes { muted, .. }
| Track::Euclidean { muted, .. }
| Track::Audio { muted, .. } => {
*muted = m;
}
}
}
#[inline]
pub fn is_euclidean(&self) -> bool {
matches!(self, Track::Euclidean { .. })
}
#[inline]
pub fn rows(&self) -> Option<&[Cell]> {
match self {
Track::Notes { rows, .. } => Some(rows),
Track::Euclidean { .. } | Track::Audio { .. } => None,
}
}
#[inline]
pub fn rows_mut(&mut self) -> Option<&mut Vec<Cell>> {
match self {
Track::Notes { rows, .. } => Some(rows),
Track::Euclidean { .. } | Track::Audio { .. } => None,
}
}
#[doc(hidden)]
#[track_caller]
pub fn expect_notes_rows(&self) -> &[Cell] {
self.rows().expect("Track is not Track::Notes")
}
#[doc(hidden)]
#[track_caller]
pub fn expect_notes_rows_mut(&mut self) -> &mut Vec<Cell> {
self.rows_mut().expect("Track is not Track::Notes")
}
pub fn cell_at(&self, row: u32) -> Cell {
match self {
Track::Notes { rows, .. } => rows.get(row as usize).cloned().unwrap_or_default(),
Track::Euclidean {
events,
steps,
rotation,
prototype,
..
} => {
let steps_u32 = (*steps).max(1) as u32;
let pos = row % steps_u32;
let rot = (*rotation as u32) % steps_u32;
let bjork_idx = ((pos + steps_u32 - rot) % steps_u32) as usize;
let canonical = crate::core::daw::euclidean::euclidean_pattern(*events, *steps);
if canonical.get(bjork_idx).copied().unwrap_or(false) {
prototype.clone()
} else {
Cell::default()
}
}
Track::Audio { .. } => Cell::default(),
}
}
pub fn natural_length(&self) -> usize {
match self {
Track::Notes { rows, .. } => rows.len(),
Track::Euclidean { steps, .. } => *steps as usize,
Track::Audio { .. } => 0,
}
}
#[inline]
pub fn is_audio(&self) -> bool {
matches!(self, Track::Audio { .. })
}
#[inline]
pub fn audio_source(&self) -> Option<&AudioSource> {
match self {
Track::Audio { source, .. } => Some(source),
_ => None,
}
}
#[inline]
pub fn audio_sample(&self) -> Option<&Sample> {
match self {
Track::Audio {
source: AudioSource::Inline(sample),
..
} => Some(sample),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::cell::CellEvent;
use crate::core::fixed::units::{ChannelVolume, Finetune, Panning, Volume};
use crate::core::sample::{LoopType, Sample, SampleDataType};
fn audio_track() -> Track {
let sample = Sample {
name: "rec".into(),
relative_pitch: 0,
finetune: Finetune::default(),
volume: ChannelVolume::FULL,
default_note_volume: Volume::FULL,
panning: Panning::CENTER,
loop_flag: LoopType::No,
loop_start: 0,
loop_length: 0,
sustain_loop_flag: LoopType::No,
sustain_loop_start: 0,
sustain_loop_length: 0,
data: Some(SampleDataType::Mono8([0i8; 8].into())),
};
Track::Audio {
name: "rec".into(),
muted: false,
source: AudioSource::Inline(sample),
source_rate: 0,
}
}
#[test]
fn audio_track_api_is_additive_and_inert_on_cells() {
let mut t = audio_track();
assert!(t.is_audio());
assert!(!t.is_euclidean());
assert_eq!(t.name(), "rec");
assert!(!t.muted());
t.set_muted(true);
assert!(t.muted());
assert_eq!(t.instrument(), 0);
t.set_instrument(3); assert_eq!(t.instrument(), 0);
assert!(t.rows().is_none());
assert!(t.rows_mut().is_none());
assert_eq!(t.natural_length(), 0);
assert_eq!(t.cell_at(0).event, CellEvent::None);
assert_eq!(t.cell_at(99).event, CellEvent::None);
let s = t.audio_sample().expect("audio sample");
assert_eq!(s.len(), 8);
}
}