Feat: new euclidean words and sugar for floating point numbers
Some checks failed
Deploy Website / deploy (push) Failing after 4m47s

This commit is contained in:
2026-02-05 01:30:34 +01:00
parent de56598fca
commit 91bc9011b2
9 changed files with 237 additions and 2 deletions

View File

@@ -86,9 +86,25 @@ fn tokenize(input: &str) -> Vec<Token> {
}
let span = SourceSpan { start, end };
if let Ok(i) = word.parse::<i64>() {
// Normalize shorthand float syntax: .25 -> 0.25, -.5 -> -0.5
let word_to_parse = if word.starts_with('.')
&& word.len() > 1
&& word.as_bytes()[1].is_ascii_digit()
{
format!("0{word}")
} else if word.starts_with("-.")
&& word.len() > 2
&& word.as_bytes()[2].is_ascii_digit()
{
format!("-0{}", &word[1..])
} else {
word.clone()
};
if let Ok(i) = word_to_parse.parse::<i64>() {
tokens.push(Token::Int(i, span));
} else if let Ok(f) = word.parse::<f64>() {
} else if let Ok(f) = word_to_parse.parse::<f64>() {
tokens.push(Token::Float(f, span));
} else {
tokens.push(Token::Word(word, span));

View File

@@ -100,6 +100,8 @@ pub enum Op {
StepRange,
Generate,
GeomRange,
Euclid,
EuclidRot,
Times,
Chord(&'static [i64]),
// MIDI

View File

@@ -1025,6 +1025,29 @@ impl Forth {
}
}
Op::Euclid => {
let n = stack.pop().ok_or("stack underflow")?.as_int()?;
let k = stack.pop().ok_or("stack underflow")?.as_int()?;
if k < 0 || n < 0 {
return Err("euclid: k and n must be >= 0".into());
}
for idx in euclidean_rhythm(k as usize, n as usize, 0) {
stack.push(Value::Int(idx, None));
}
}
Op::EuclidRot => {
let r = stack.pop().ok_or("stack underflow")?.as_int()?;
let n = stack.pop().ok_or("stack underflow")?.as_int()?;
let k = stack.pop().ok_or("stack underflow")?.as_int()?;
if k < 0 || n < 0 || r < 0 {
return Err("euclidrot: k, n, and r must be >= 0".into());
}
for idx in euclidean_rhythm(k as usize, n as usize, r as usize) {
stack.push(Value::Int(idx, None));
}
}
// MIDI operations
Op::MidiEmit => {
let (_, params) = cmd.snapshot().unwrap_or((None, &[]));
@@ -1216,6 +1239,58 @@ fn emit_output(
outputs.push(out);
}
fn euclidean_rhythm(k: usize, n: usize, rotation: usize) -> Vec<i64> {
if k == 0 || n == 0 {
return Vec::new();
}
if k >= n {
return (0..n as i64).collect();
}
let mut groups: Vec<Vec<bool>> = (0..k).map(|_| vec![true]).collect();
groups.extend((0..(n - k)).map(|_| vec![false]));
while groups.len() > 1 {
let ones_count = groups.iter().filter(|g| g[0]).count();
let zeros_count = groups.len() - ones_count;
if zeros_count == 0 || ones_count == 0 {
break;
}
let min_count = ones_count.min(zeros_count);
let mut new_groups = Vec::with_capacity(groups.len() - min_count);
let (mut ones, mut zeros): (Vec<_>, Vec<_>) =
groups.into_iter().partition(|g| g[0]);
for _ in 0..min_count {
let mut one = ones.pop().unwrap();
one.extend(zeros.pop().unwrap());
new_groups.push(one);
}
new_groups.extend(ones);
new_groups.extend(zeros);
groups = new_groups;
}
let pattern: Vec<bool> = groups.into_iter().flatten().collect();
let rotated = if rotation > 0 && !pattern.is_empty() {
let r = rotation % pattern.len();
pattern.iter().cycle().skip(r).take(pattern.len()).copied().collect()
} else {
pattern
};
rotated
.into_iter()
.enumerate()
.filter_map(|(i, hit)| if hit { Some(i as i64) } else { None })
.collect()
}
fn perlin_grad(hash_input: i64) -> f64 {
let mut h = (hash_input as u64)
.wrapping_mul(6364136223846793005)

View File

@@ -92,6 +92,8 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
".," => Op::StepRange,
"gen" => Op::Generate,
"geom.." => Op::GeomRange,
"euclid" => Op::Euclid,
"euclidrot" => Op::EuclidRot,
"times" => Op::Times,
"m." => Op::MidiEmit,
"ccval" => Op::GetMidiCC,

View File

@@ -420,4 +420,24 @@ pub(super) const WORDS: &[Word] = &[
compile: Simple,
varargs: false,
},
Word {
name: "euclid",
aliases: &[],
category: "Generator",
stack: "(k n -- i1 i2 ... ik)",
desc: "Push indices for k hits evenly distributed over n steps",
example: "4 8 euclid => 0 2 4 6",
compile: Simple,
varargs: false,
},
Word {
name: "euclidrot",
aliases: &[],
category: "Generator",
stack: "(k n r -- i1 i2 ... ik)",
desc: "Push Euclidean indices with rotation r",
example: "3 8 2 euclidrot => 1 4 6",
compile: Simple,
varargs: false,
},
];