309 lines
8.0 KiB
Rust
309 lines
8.0 KiB
Rust
use std::sync::Arc;
|
|
|
|
use crate::ops::Op;
|
|
use crate::theory;
|
|
use crate::types::{Dictionary, SourceSpan};
|
|
|
|
use super::{lookup_word, WordCompile::*};
|
|
|
|
pub(super) fn simple_op(name: &str) -> Option<Op> {
|
|
Some(match name {
|
|
"dup" => Op::Dup,
|
|
"dupn" => Op::Dupn,
|
|
"drop" => Op::Drop,
|
|
"swap" => Op::Swap,
|
|
"over" => Op::Over,
|
|
"rot" => Op::Rot,
|
|
"nip" => Op::Nip,
|
|
"tuck" => Op::Tuck,
|
|
"2dup" => Op::Dup2,
|
|
"2drop" => Op::Drop2,
|
|
"2swap" => Op::Swap2,
|
|
"2over" => Op::Over2,
|
|
"rev" => Op::Rev,
|
|
"shuffle" => Op::Shuffle,
|
|
"sort" => Op::Sort,
|
|
"rsort" => Op::RSort,
|
|
"sum" => Op::Sum,
|
|
"prod" => Op::Prod,
|
|
"+" => Op::Add,
|
|
"-" => Op::Sub,
|
|
"*" => Op::Mul,
|
|
"/" => Op::Div,
|
|
"mod" => Op::Mod,
|
|
"neg" => Op::Neg,
|
|
"abs" => Op::Abs,
|
|
"floor" => Op::Floor,
|
|
"ceil" => Op::Ceil,
|
|
"round" => Op::Round,
|
|
"min" => Op::Min,
|
|
"max" => Op::Max,
|
|
"pow" => Op::Pow,
|
|
"sqrt" => Op::Sqrt,
|
|
"sin" => Op::Sin,
|
|
"cos" => Op::Cos,
|
|
"log" => Op::Log,
|
|
"=" => Op::Eq,
|
|
"!=" => Op::Ne,
|
|
"lt" => Op::Lt,
|
|
"gt" => Op::Gt,
|
|
"<=" => Op::Le,
|
|
">=" => Op::Ge,
|
|
"and" => Op::And,
|
|
"or" => Op::Or,
|
|
"not" => Op::Not,
|
|
"xor" => Op::Xor,
|
|
"nand" => Op::Nand,
|
|
"nor" => Op::Nor,
|
|
"ifelse" => Op::IfElse,
|
|
"pick" => Op::Pick,
|
|
"sound" => Op::NewCmd,
|
|
"." => Op::Emit,
|
|
"rand" => Op::Rand(None),
|
|
"exprand" => Op::ExpRand(None),
|
|
"logrand" => Op::LogRand(None),
|
|
"seed" => Op::Seed,
|
|
"cycle" => Op::Cycle(None),
|
|
"pcycle" => Op::PCycle(None),
|
|
"choose" => Op::Choose(None),
|
|
"bounce" => Op::Bounce(None),
|
|
"wchoose" => Op::WChoose(None),
|
|
"every" => Op::Every(None),
|
|
"bjork" => Op::Bjork(None),
|
|
"pbjork" => Op::PBjork(None),
|
|
"chance" => Op::ChanceExec(None),
|
|
"prob" => Op::ProbExec(None),
|
|
"coin" => Op::Coin(None),
|
|
"mtof" => Op::Mtof,
|
|
"ftom" => Op::Ftom,
|
|
"?" => Op::When,
|
|
"!?" => Op::Unless,
|
|
"tempo!" => Op::SetTempo,
|
|
"speed!" => Op::SetSpeed,
|
|
"at" => Op::At,
|
|
"arp" => Op::Arp,
|
|
"adsr" => Op::Adsr,
|
|
"ad" => Op::Ad,
|
|
"apply" => Op::Apply,
|
|
"ramp" => Op::Ramp,
|
|
"triangle" => Op::Triangle,
|
|
"range" => Op::Range,
|
|
"linmap" => Op::LinMap,
|
|
"expmap" => Op::ExpMap,
|
|
"perlin" => Op::Perlin,
|
|
"loop" => Op::Loop,
|
|
"oct" => Op::Oct,
|
|
"clear" => Op::ClearCmd,
|
|
".." => Op::IntRange,
|
|
".," => Op::StepRange,
|
|
"gen" => Op::Generate,
|
|
"geom.." => Op::GeomRange,
|
|
"euclid" => Op::Euclid,
|
|
"euclidrot" => Op::EuclidRot,
|
|
"times" => Op::Times,
|
|
"m." => Op::MidiEmit,
|
|
"ccval" => Op::GetMidiCC,
|
|
"mclock" => Op::MidiClock,
|
|
"mstart" => Op::MidiStart,
|
|
"mstop" => Op::MidiStop,
|
|
"mcont" => Op::MidiContinue,
|
|
"forget" => Op::Forget,
|
|
"lfo" => Op::ModLfo(0),
|
|
"tlfo" => Op::ModLfo(1),
|
|
"wlfo" => Op::ModLfo(2),
|
|
"qlfo" => Op::ModLfo(3),
|
|
"slide" => Op::ModSlide(0),
|
|
"expslide" => Op::ModSlide(1),
|
|
"sslide" => Op::ModSlide(2),
|
|
"jit" => Op::ModRnd(0),
|
|
"sjit" => Op::ModRnd(1),
|
|
"drunk" => Op::ModRnd(2),
|
|
"env" => Op::ModEnv,
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
fn parse_note_name(name: &str) -> Option<i64> {
|
|
let name = name.to_lowercase();
|
|
let bytes = name.as_bytes();
|
|
|
|
if bytes.len() < 2 {
|
|
return None;
|
|
}
|
|
|
|
let base = match bytes[0] {
|
|
b'c' => 0,
|
|
b'd' => 2,
|
|
b'e' => 4,
|
|
b'f' => 5,
|
|
b'g' => 7,
|
|
b'a' => 9,
|
|
b'b' => 11,
|
|
_ => return None,
|
|
};
|
|
|
|
let (modifier, octave_start) = match bytes[1] {
|
|
b'#' | b's' => (1, 2),
|
|
b'b' if bytes.len() > 2 && bytes[2].is_ascii_digit() => (-1, 2),
|
|
b'0'..=b'9' => (0, 1),
|
|
_ => return None,
|
|
};
|
|
|
|
let octave_str = &name[octave_start..];
|
|
let octave: i64 = octave_str.parse().ok()?;
|
|
|
|
if !(-1..=9).contains(&octave) {
|
|
return None;
|
|
}
|
|
|
|
Some((octave + 1) * 12 + base + modifier)
|
|
}
|
|
|
|
fn parse_interval(name: &str) -> Option<i64> {
|
|
let simple = match name {
|
|
"P1" | "unison" => 0,
|
|
"m2" => 1,
|
|
"M2" => 2,
|
|
"m3" => 3,
|
|
"M3" => 4,
|
|
"P4" => 5,
|
|
"aug4" | "dim5" | "tritone" => 6,
|
|
"P5" => 7,
|
|
"m6" => 8,
|
|
"M6" => 9,
|
|
"m7" => 10,
|
|
"M7" => 11,
|
|
"P8" => 12,
|
|
"m9" => 13,
|
|
"M9" => 14,
|
|
"m10" => 15,
|
|
"M10" => 16,
|
|
"P11" => 17,
|
|
"aug11" => 18,
|
|
"P12" => 19,
|
|
"m13" => 20,
|
|
"M13" => 21,
|
|
"m14" => 22,
|
|
"M14" => 23,
|
|
"P15" => 24,
|
|
_ => return None,
|
|
};
|
|
Some(simple)
|
|
}
|
|
|
|
fn attach_span(op: &mut Op, span: SourceSpan) {
|
|
match op {
|
|
Op::Rand(s) | Op::ExpRand(s) | Op::LogRand(s) | Op::Coin(s)
|
|
| Op::Choose(s) | Op::WChoose(s) | Op::Cycle(s) | Op::PCycle(s)
|
|
| Op::Bounce(s) | Op::ChanceExec(s) | Op::ProbExec(s)
|
|
| Op::Every(s)
|
|
| Op::Bjork(s) | Op::PBjork(s) => *s = Some(span),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn compile_word(
|
|
name: &str,
|
|
span: Option<SourceSpan>,
|
|
ops: &mut Vec<Op>,
|
|
dict: &Dictionary,
|
|
) -> bool {
|
|
match name {
|
|
"linramp" => {
|
|
ops.push(Op::PushFloat(1.0, span));
|
|
ops.push(Op::Ramp);
|
|
return true;
|
|
}
|
|
"expramp" => {
|
|
ops.push(Op::PushFloat(3.0, span));
|
|
ops.push(Op::Ramp);
|
|
return true;
|
|
}
|
|
"logramp" => {
|
|
ops.push(Op::PushFloat(0.3, span));
|
|
ops.push(Op::Ramp);
|
|
return true;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
if let Some(pattern) = theory::lookup(name) {
|
|
ops.push(Op::Degree(pattern));
|
|
return true;
|
|
}
|
|
|
|
if let Some(intervals) = theory::chords::lookup(name) {
|
|
ops.push(Op::Chord(intervals));
|
|
return true;
|
|
}
|
|
|
|
if let Some(word) = lookup_word(name) {
|
|
match &word.compile {
|
|
Simple => {
|
|
if let Some(mut op) = simple_op(word.name) {
|
|
if let Some(sp) = span {
|
|
attach_span(&mut op, sp);
|
|
}
|
|
ops.push(op);
|
|
}
|
|
}
|
|
Context(ctx) => ops.push(Op::GetContext(ctx)),
|
|
Param => ops.push(Op::SetParam(word.name)),
|
|
Probability(p) => {
|
|
ops.push(Op::PushFloat(*p, None));
|
|
ops.push(Op::ChanceExec(span));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if let Some(var_name) = name.strip_prefix('@') {
|
|
if !var_name.is_empty() {
|
|
ops.push(Op::PushStr(Arc::from(var_name), span));
|
|
ops.push(Op::Get);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if let Some(var_name) = name.strip_prefix('!') {
|
|
if !var_name.is_empty() {
|
|
ops.push(Op::PushStr(Arc::from(var_name), span));
|
|
ops.push(Op::Set);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if let Some(var_name) = name.strip_prefix(',') {
|
|
if !var_name.is_empty() {
|
|
ops.push(Op::PushStr(Arc::from(var_name), span));
|
|
ops.push(Op::SetKeep);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if let Some(midi) = parse_note_name(name) {
|
|
ops.push(Op::PushInt(midi, span));
|
|
return true;
|
|
}
|
|
|
|
if let Some(semitones) = parse_interval(name) {
|
|
ops.push(Op::Dup);
|
|
ops.push(Op::PushInt(semitones, span));
|
|
ops.push(Op::Add);
|
|
return true;
|
|
}
|
|
|
|
if let Some(op) = simple_op(name) {
|
|
ops.push(op);
|
|
return true;
|
|
}
|
|
|
|
if let Some(body) = dict.lock().get(name) {
|
|
ops.extend(body.iter().cloned());
|
|
return true;
|
|
}
|
|
|
|
ops.push(Op::PushStr(Arc::from(name), span));
|
|
true
|
|
}
|