diff --git a/src/tidal/cycle.pest b/src/tidal/cycle.pest index d4b8d83..e2bc4b3 100644 --- a/src/tidal/cycle.pest +++ b/src/tidal/cycle.pest @@ -49,11 +49,11 @@ split_op = {"."} sections = _{ section ~ ((stack_op | split_op | choice_op) ~ section)* } /// groups -subdivision = { "[" ~ sections? ~ "]" } -alternating = { "<" ~ sections? ~ ">" } +subdivision = { "[" ~ sections? ~ "]" ~ index_tail? } +alternating = { "<" ~ sections? ~ ">" ~ index_tail? } -polymeter_tail = { "%" ~ parameter } -polymeter = { "{" ~ sections? ~ "}" ~ polymeter_tail? } +index_tail = { "%" ~ parameter } +polymeter = { "{" ~ sections? ~ "}" ~ index_tail? } group = _{ subdivision | alternating | polymeter } diff --git a/src/tidal/cycle.rs b/src/tidal/cycle.rs index 3adb0e4..27ceedd 100644 --- a/src/tidal/cycle.rs +++ b/src/tidal/cycle.rs @@ -359,11 +359,19 @@ impl Pitch { // ------------------------------------------------------------------------------------------------- +#[derive(Clone, Debug, PartialEq)] +pub struct Indexed { + steps: Box, + index: Box, + alternating: bool, +} + #[derive(Clone, Debug, PartialEq)] enum Step { Var(Rc), Single(Single), Subdivision(Subdivision), + Indexed(Indexed), Polymeter(Polymeter), Stack(Stack), Choices(Choices), @@ -398,6 +406,7 @@ impl Step { Step::Single(_s) => vec![], Step::Polymeter(p) => p.stack.iter().collect(), Step::Subdivision(sd) => sd.steps.iter().collect(), + Step::Indexed(sd) => sd.steps.inner_steps(), Step::Choices(cs) => cs.choices.iter().collect(), Step::Stack(st) => st.stack.iter().collect(), Step::SpeedExpression(e) => vec![&e.step, &e.mult], @@ -423,6 +432,10 @@ impl Step { vars.insert(Rc::clone(name)); } Step::Single(_s) => (), + Step::Indexed(i) => { + i.steps.get_vars(vars); + i.index.get_vars(vars); + } Step::Polymeter(pm) => { pm.stack.iter().for_each(|s| s.get_vars(vars)); if let Some(c) = pm.count.as_ref() { @@ -484,6 +497,30 @@ impl Step { Self::Subdivision(Subdivision { steps }) } + fn subdivision_with_index(steps: Vec, index: Option) -> Self { + if let Some(index) = index { + Self::Indexed(Indexed { + steps: Box::new(Self::subdivision(steps)), + index: Box::new(index), + alternating: false, + }) + } else { + Self::subdivision(steps) + } + } + + fn alternating_with_index(steps: Vec, index: Option) -> Self { + if let Some(index) = index { + Self::Indexed(Indexed { + steps: Box::new(Self::subdivision(steps)), + index: Box::new(index), + alternating: true, + }) + } else { + Self::alternating(steps) + } + } + fn alternating(steps: Vec) -> Self { Self::polymeter( vec![steps], @@ -761,6 +798,17 @@ impl Constant { } } + fn to_index(&self, len: usize) -> Option { + match *self { + Self::Float(float) | Self::Target(Target::NamedFloat(_, float)) => { + Some((float.rem_euclid(1.0) * len as f64).floor() as usize) + } + _ => self + .to_integer() + .map(|int| (if int > 0 { int - 1 } else { 0 } as usize).rem_euclid(len)), + } + } + fn to_chance(&self) -> Option { match &self { Self::Null => None, @@ -1064,6 +1112,15 @@ impl Events { } } + fn set_span(&mut self, span: Span) { + match self { + Events::Single(s) => s.span = span, + Events::Multi(m) => m.span = span, + Events::Poly(p) => p.span = span, + } + // self.set_length(span.length()); + } + fn first(&self) -> Option<&Event> { match self { Events::Single(s) => Some(s), @@ -1170,6 +1227,27 @@ impl Events { } } + fn mutate_singles(&mut self, fun: &mut F) -> Result<(), String> + where + F: FnMut(Event, &mut Events) -> Result<(), String>, + { + match self { + Events::Single(s) => fun(s.clone(), self), + Events::Multi(m) => { + for e in &mut m.events { + e.mutate_singles(fun)?; + } + Ok(()) + } + Events::Poly(p) => { + for e in &mut p.channels { + e.mutate_singles(fun)?; + } + Ok(()) + } + } + } + /// recursively transform the spans of events from 0..1 to a given span fn transform_spans(&mut self, span: &Span) { let unit = span.length(); @@ -1361,8 +1439,8 @@ impl CycleParser { Rule::variable => Self::variable(pair), Rule::single => Ok(Self::single(pair)?), Rule::repeat => Ok(Step::Repeat), - Rule::subdivision | Rule::mini => Self::group(pair, Step::subdivision), - Rule::alternating => Self::group(pair, Step::alternating), + Rule::subdivision | Rule::mini => Self::group(pair, Step::subdivision_with_index), + Rule::alternating => Self::group(pair, Step::alternating_with_index), Rule::polymeter => Self::polymeter(pair), Rule::range => Self::range(pair), Rule::expression => Self::expression(pair), @@ -1572,8 +1650,14 @@ impl CycleParser { Ok(stacks) } - fn group(pair: Pair, fun: fn(Vec) -> Step) -> Result { - let stacks = Self::stacks(pair.into_inner().collect())?; + fn group(pair: Pair, fun: fn(Vec, Option) -> Step) -> Result { + let (stacked_pairs, count_pairs): (Vec>, Vec>) = pair + .into_inner() + .partition(|p| p.as_rule() != Rule::index_tail); + + let stacks = Self::stacks(stacked_pairs)?; + + let index = Self::index_tail(count_pairs)?; match stacks.len() { 0 => Ok(Step::rest()), @@ -1582,36 +1666,40 @@ impl CycleParser { if steps.is_empty() { Ok(Step::rest()) } else { - Ok(fun(steps.to_owned())) + Ok(fun(steps.to_owned(), index)) } } _ => Ok(Step::Stack(Stack { - stack: stacks.into_iter().map(fun).collect(), + stack: stacks + .into_iter() + .map(|steps| fun(steps, index.clone())) + .collect(), })), } } - fn polymeter_tail(pair: Pair) -> Result { - if let Some(count) = pair.clone().into_inner().next() { - Self::step(count) + fn index_tail(pairs: Vec>) -> Result, String> { + if let Some(pair) = pairs.first() { + if let Some(count) = pair.clone().into_inner().next() { + Ok(Some(Self::step(count)?)) + } else { + Err(format!( + "error in grammar, missing polymeter count '{}'", + pair.as_str() + )) + } + // Some(Self::index_tail(pair.to_owned())?) } else { - Err(format!( - "error in grammar, missing polymeter count '{}'", - pair.as_str() - )) + Ok(None) } } fn polymeter(pair: Pair) -> Result { let (stacked_pairs, count_pairs): (Vec>, Vec>) = pair .into_inner() - .partition(|p| p.as_rule() != Rule::polymeter_tail); + .partition(|p| p.as_rule() != Rule::index_tail); - let count: Option = if let Some(pair) = count_pairs.first() { - Some(Self::polymeter_tail(pair.to_owned())?) - } else { - None - }; + let count = Self::index_tail(count_pairs)?; let stacks = Self::stacks(stacked_pairs)?; @@ -2148,6 +2236,32 @@ impl Cycle { targets: vec![], }) } + Step::Indexed(s) => { + let mut indices = + Self::output(s.index.as_ref(), state, cycle, limit, overlap, vars)?; + let length = Self::sub_length(&s.steps, state, cycle, limit, overlap, vars)?; + let offset = if s.alternating { cycle } else { 0 }; + indices.mutate_singles(&mut |event: Event, events: &mut Events| { + *events = if let Some(i) = event.value.to_index(length.to_integer() as usize) { + let mut out = Self::output_with_speed( + &s.steps, + &SpeedOp::Fit(length), + &Step::constant(Constant::Integer(1), None), + state, + offset + i as u32, + limit, + overlap, + vars, + )?; + out.set_span(events.get_span()); + out + } else { + Events::empty() + }; + Ok(()) + })?; + indices + } Step::Subdivision(sd) => { if sd.steps.is_empty() { Events::empty() @@ -2386,6 +2500,7 @@ impl Cycle { _ => format!("{:?} {:?}", s.value, s.string), }, Step::Subdivision(sd) => format!("Subdivision [{}]", sd.steps.len()), + Step::Indexed(sd) => format!("Indexed [{:?}]", sd.index.as_ref()), Step::Polymeter(pm) => format!("Polymeter {{{:?}}}", pm.count), Step::Choices(cs) => format!("Choices |{}|", cs.choices.len()), Step::Stack(st) => format!("Stack ({})", st.stack.len()), diff --git a/src/tidal/cycle/tests.rs b/src/tidal/cycle/tests.rs index ca6bbf1..aa07a22 100644 --- a/src/tidal/cycle/tests.rs +++ b/src/tidal/cycle/tests.rs @@ -22,6 +22,17 @@ fn assert_cycle_equality(a: &str, b: &str) -> Result<(), String> { ); Ok(()) } +fn assert_cycles_equality(a: &str, bs: &[&str]) -> Result<(), String> { + let seed = rand::rng().random(); + let mut cycle = Cycle::from(a)?.with_seed(seed); + for b in bs { + assert_eq!( + cycle.generate()?, + Cycle::from(b)?.with_seed(seed).generate()?, + ); + } + Ok(()) +} fn assert_cycle_advancing(input: &str) -> Result<(), String> { let seed = rand::rng().random(); @@ -60,6 +71,29 @@ fn weight_and_replicate() -> Result<(), String> { Ok(()) } +#[test] +fn indexing() -> Result<(), String> { + assert_cycle_equality("[a b c d]%0", "a")?; + assert_cycle_equality("[a b c d]%1", "a")?; + assert_cycle_equality("[a b c d]%2", "b")?; + assert_cycle_equality("[a b c d]%3", "c")?; + assert_cycle_equality("[a b c d]%4", "d")?; + assert_cycle_equality("[a b c d]%5", "a")?; + assert_cycle_equality("[a b c d]%0.1", "a")?; + assert_cycle_equality("[a b c d]%0.25", "b")?; + assert_cycle_equality("[a b c d]%0.499", "b")?; + assert_cycle_equality("[a b c d]%0.5", "c")?; + assert_cycle_equality("[a b c d]%0.999", "d")?; + assert_cycle_equality("[a b c d]%[1 3 3]", "a c c")?; + assert_cycle_equality("[a b c d]%[5 4 3]", "a d c")?; + assert_cycle_equality("[a b c d]%[1, 3, 4]", "a, c, d")?; + assert_cycle_equality("[a b c d]%<1 2 3>", "a")?; + assert_cycle_equality("[a b c d]%<[1*8 2*4 3*2 4]>", "a*8 b*4 c*2 d")?; + + assert_cycles_equality("%2", &["b", "c", "d", "a"])?; + Ok(()) +} + #[test] fn variables() -> Result<(), String> { assert!(SubCycle::from("a b c d").is_ok());