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 { 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 { 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 { 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, ops: &mut Vec, 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 }