use super::mini::MiniNotation;
#[derive(Debug, Clone, PartialEq)]
pub struct Program {
pub lines: Vec<SourceLine>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SourceLine {
Bpm(u32),
Sig(u8, u8),
Phrase(u32),
Scale(PitchRoot, ScaleMode),
Load(String),
Arrange(Vec<ArrangeItem>),
Tail(f64),
Seed(u64),
Meta(String, String),
Include(String),
Def(Box<super::instrument::InstrumentDef>),
Pattern(PatternDef),
GroupStart { muted: bool, name: String },
GroupEnd(Vec<Transform>),
SectionStart {
muted: bool,
name: String,
cycles: u32,
},
SectionEnd,
Comment(String),
Blank,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GroupDef {
pub muted: bool,
pub name: String,
pub transforms: Vec<Transform>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PatternDef {
pub muted: bool,
pub name: String,
pub instrument: String,
pub notation: MiniNotation,
pub transforms: Vec<Transform>,
pub group: Option<String>,
pub section: Option<String>,
pub span: Option<Span>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SectionDef {
pub muted: bool,
pub name: String,
pub cycles: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ArrangeItem {
pub section: String,
pub repeat: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub from: Option<u32>,
pub to: Option<u32>,
}
impl Span {
pub fn at(cycle: u32) -> Self {
Self {
from: Some(cycle),
to: Some(cycle),
}
}
pub fn start(&self) -> u32 {
self.from.unwrap_or(1)
}
pub fn end(&self, cycles: u32) -> u32 {
self.to.unwrap_or(cycles)
}
pub fn contains(&self, cycle: u32, cycles: u32) -> bool {
cycle >= self.start() && cycle <= self.end(cycles)
}
pub fn conflict(&self, cycles: u32) -> Option<String> {
if let (Some(from), Some(to)) = (self.from, self.to)
&& from > to
{
return Some(format!("span starts at cycle {from} but ends at {to}"));
}
if self.start() == 0 {
return Some("cycle 0: spans are 1-based".to_string());
}
let last = self.end(cycles);
if last > cycles {
return Some(format!(
"span reaches cycle {last}, past the section's {cycles}"
));
}
if self.start() > cycles {
return Some(format!(
"span starts at cycle {}, past the section's {cycles}",
self.start()
));
}
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PitchRoot {
pub name: NoteLetter,
pub accidental: Accidental,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoteLetter {
C,
D,
E,
F,
G,
A,
B,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Accidental {
#[default]
Natural,
Sharp,
DoubleSharp,
Flat,
DoubleFlat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScaleMode {
Major,
Minor,
Dorian,
Phrygian,
Lydian,
Mixolydian,
Aeolian,
Locrian,
Chromatic,
Pentatonic,
Blues,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Transform {
Rev,
Fast(Ramp<f64>),
Slow(Ramp<f64>),
Every(u32, Box<Transform>),
Arp(ArpMode),
Scale(PitchRoot, ScaleMode),
Oct(Ramp<i32>),
Vel(Ramp<f64>),
RampSpan {
cycles: u32,
curve: RampCurve,
},
Gain(Ramp<f64>),
Pan(Ramp<f64>),
AutoPan(PanSweep),
Lpf(Ramp<f64>),
Hpf(Ramp<f64>),
Fx(FxCall),
Delay(Ramp<f64>, Ramp<f64>, Option<Ramp<f64>>),
Reverb(Ramp<f64>),
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum RampCurve {
#[default]
Linear,
Exponential,
Oscillate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArpMode {
Up,
Down,
UpDown,
Random,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PanSweep {
pub wave: LfoWave,
pub rate: LfoRate,
pub depth: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LfoWave {
Sine,
Triangle,
Square,
Saw,
Random,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LfoRate {
Cycles(f64),
Hertz(f64),
}
#[derive(Debug, Clone, PartialEq)]
pub struct FxCall {
pub filter: String,
pub args: Vec<FxArg>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FxArg {
Positional(FxValue),
Named(String, FxValue),
}
#[derive(Debug, Clone, PartialEq)]
pub enum FxValue {
Plain(Ramp<f64>),
Hertz(Ramp<f64>),
}
impl FxValue {
pub fn ramp(&self) -> &Ramp<f64> {
match self {
Self::Plain(ramp) | Self::Hertz(ramp) => ramp,
}
}
pub fn travels(&self) -> bool {
self.ramp().travels()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Ramp<T> {
Fixed(T),
Sweep { from: T, to: T },
Steps { first: T, rest: Vec<T> },
Timed {
ramp: Box<Ramp<T>>,
span_divisions: f64,
curve: RampCurve,
},
}
impl<T: Copy> Ramp<T> {
pub fn fixed(value: T) -> Self {
Self::Fixed(value)
}
pub fn steps(first: T, rest: Vec<T>) -> Self {
if rest.is_empty() {
Self::Fixed(first)
} else {
Self::Steps { first, rest }
}
}
pub fn start(&self) -> T {
match self {
Self::Fixed(value) => *value,
Self::Sweep { from, .. } => *from,
Self::Steps { first, .. } => *first,
Self::Timed { ramp, .. } => ramp.start(),
}
}
pub fn travels(&self) -> bool {
!matches!(self, Self::Fixed(_))
}
pub fn values(&self) -> Vec<T> {
match self {
Self::Fixed(value) => vec![*value],
Self::Sweep { from, to } => vec![*from, *to],
Self::Steps { first, rest } => {
let mut all = vec![*first];
all.extend(rest.iter().copied());
all
}
Self::Timed { ramp, .. } => ramp.values(),
}
}
pub fn own_window(&self) -> Option<(f64, RampCurve)> {
match self {
Self::Timed {
span_divisions,
curve,
..
} => Some((*span_divisions, *curve)),
_ => None,
}
}
}