1use num::integer::{gcd, lcm};
2use std::collections::{BTreeMap, BTreeSet};
3
4use crate::chord::Chord;
5use crate::defaults::{FloatType, IntegerType, UnsignedIntegerType};
6use crate::error::{Error, Result};
7use crate::interval::Interval;
8use crate::pitch::Pitch;
9
10#[derive(Debug, Clone)]
11#[must_use]
13pub struct Polyrhythm {
14 pub base: UnsignedIntegerType,
16 pub components: Vec<UnsignedIntegerType>,
18 pub tempo: Option<UnsignedIntegerType>,
20 pub cycle: UnsignedIntegerType,
22 current_tick: UnsignedIntegerType,
23}
24
25#[derive(Debug, Clone, PartialEq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[must_use]
29pub struct PolyrhythmEvent {
30 pub tick: UnsignedIntegerType,
32 pub time_seconds: FloatType,
34 pub triggers: Vec<bool>,
36}
37
38#[derive(Debug, Clone, PartialEq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[must_use]
42pub struct PolyrhythmRatioTone {
43 pub component: UnsignedIntegerType,
45 pub offset: IntegerType,
47 pub ratio: FloatType,
49}
50
51#[derive(Debug, Clone, PartialEq)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53#[must_use]
55pub struct PolyrhythmAnalysis {
56 pub base: UnsignedIntegerType,
58 pub components: Vec<UnsignedIntegerType>,
60 pub tempo: UnsignedIntegerType,
62 pub cycle: UnsignedIntegerType,
64 pub tick_duration: FloatType,
66 pub component_intervals: Vec<UnsignedIntegerType>,
68 pub hit_events: Vec<PolyrhythmEvent>,
70 pub ratio_tones: Vec<PolyrhythmRatioTone>,
72}
73
74impl Polyrhythm {
75 pub fn new(base: UnsignedIntegerType, subdivisions: &[UnsignedIntegerType]) -> Result<Self> {
77 if base == 0 {
78 return Err(Error::Polyrhythm("Base must be nonzero".into()));
79 }
80 if subdivisions.is_empty() {
81 return Err(Error::Polyrhythm(
82 "At least one subdivision is required".into(),
83 ));
84 }
85 for &sub in subdivisions {
86 if sub == 0 {
87 return Err(Error::Polyrhythm("Subdivision must be nonzero".into()));
88 }
89 }
90 let cycle = subdivisions.iter().fold(1, |acc, &x| lcm(acc, x));
91 Ok(Self {
92 base,
93 components: subdivisions.to_vec(),
94 tempo: None,
95 cycle,
96 current_tick: 0,
97 })
98 }
99
100 pub fn from_time_signature(
103 beats_per_measure: UnsignedIntegerType,
104 tempo: UnsignedIntegerType,
105 subdivisions: &[UnsignedIntegerType],
106 ) -> Result<Self> {
107 Self::new(beats_per_measure, subdivisions)?.with_tempo(tempo)
108 }
109
110 pub fn with_tempo(mut self, tempo: UnsignedIntegerType) -> Result<Self> {
112 self.set_tempo(tempo)?;
113 Ok(self)
114 }
115
116 pub fn set_tempo(&mut self, tempo: UnsignedIntegerType) -> Result<()> {
118 if tempo == 0 {
119 return Err(Error::Polyrhythm("Tempo must be nonzero".into()));
120 }
121 self.tempo = Some(tempo);
122 Ok(())
123 }
124
125 pub fn tempo(&self) -> Option<UnsignedIntegerType> {
130 self.tempo
131 }
132
133 pub fn components(&self) -> &[UnsignedIntegerType] {
135 &self.components
136 }
137
138 pub fn current_tick(&self) -> UnsignedIntegerType {
140 self.current_tick
141 }
142
143 pub fn reset(&mut self) {
145 self.current_tick = 0;
146 }
147
148 pub fn component_intervals(&self) -> Vec<UnsignedIntegerType> {
150 self.components
151 .iter()
152 .map(|sub| self.cycle / *sub)
153 .collect()
154 }
155
156 pub fn measure_duration(&self) -> Result<FloatType> {
158 match self.tempo {
159 Some(tempo) => Ok(self.base as FloatType * 60.0 / (tempo as FloatType)),
160 None => Err(Error::Polyrhythm("Tempo not set".into())),
161 }
162 }
163
164 pub fn tick_duration(&self) -> Result<FloatType> {
166 Ok(self.measure_duration()? / self.cycle as FloatType)
167 }
168
169 pub fn cycle_len(&self) -> UnsignedIntegerType {
171 self.cycle
172 }
173
174 pub fn beat_timings(&self) -> Result<Vec<Vec<FloatType>>> {
177 let tick_duration = self.tick_duration()?;
178 Ok(self
179 .components
180 .iter()
181 .map(|&sub| {
182 let interval = self.cycle / sub;
183 (0..sub)
184 .map(|i| (i * interval) as FloatType * tick_duration)
185 .collect()
186 })
187 .collect())
188 }
189
190 pub fn events(&self) -> Result<Vec<PolyrhythmEvent>> {
192 let tick_duration = self.tick_duration()?;
193 Ok((0..self.cycle)
194 .map(|tick| {
195 let triggers = self
196 .components
197 .iter()
198 .map(|&sub| {
199 let divisor = self.cycle / sub;
200 divisor != 0 && tick % divisor == 0
201 })
202 .collect::<Vec<_>>();
203 PolyrhythmEvent {
204 tick,
205 time_seconds: tick as FloatType * tick_duration,
206 triggers,
207 }
208 })
209 .collect())
210 }
211
212 pub fn hit_events(&self) -> Result<Vec<PolyrhythmEvent>> {
214 Ok(self
215 .events()?
216 .into_iter()
217 .filter(|event| event.triggers.iter().any(|trigger| *trigger))
218 .collect())
219 }
220
221 pub fn ratio_tones(&self) -> Vec<PolyrhythmRatioTone> {
228 let divisor = self
229 .components
230 .iter()
231 .copied()
232 .reduce(gcd)
233 .unwrap_or(1)
234 .max(1);
235 let reduced_components = self
236 .components
237 .iter()
238 .map(|component| component / divisor)
239 .collect::<Vec<_>>();
240 let root_ratio = reduced_components.iter().copied().min().unwrap_or(1).max(1);
241 let mut tones_by_offset = BTreeMap::new();
242
243 for component in reduced_components {
244 let ratio = component as FloatType / root_ratio as FloatType;
245 let offset = (12.0 * ratio.log2()).round() as IntegerType;
246 tones_by_offset.entry(offset).or_insert(component);
247 }
248
249 tones_by_offset
250 .into_iter()
251 .map(|(offset, component)| PolyrhythmRatioTone {
252 component,
253 offset,
254 ratio: component as FloatType / root_ratio as FloatType,
255 })
256 .collect()
257 }
258
259 pub fn ratio_pitches<T>(&self, base: T) -> Result<Vec<Pitch>>
261 where
262 T: TryInto<Pitch>,
263 T::Error: Into<Error>,
264 {
265 let base_pitch = base.try_into().map_err(Into::into)?;
266 self.ratio_tones()
267 .into_iter()
268 .map(|tone| {
269 let interval = Interval::from_semitones(tone.offset)?;
270 base_pitch.transpose(&interval)
271 })
272 .collect()
273 }
274
275 pub fn ratio_chord<T>(&self, base: T) -> Result<Chord>
277 where
278 T: TryInto<Pitch>,
279 T::Error: Into<Error>,
280 {
281 let pitches = self.ratio_pitches(base)?;
282 Chord::new(pitches.as_slice())
283 }
284
285 pub fn analysis(&self) -> Result<PolyrhythmAnalysis> {
287 let tempo = self
288 .tempo
289 .ok_or_else(|| Error::Polyrhythm("Tempo not set".into()))?;
290 Ok(PolyrhythmAnalysis {
291 base: self.base,
292 components: self.components.clone(),
293 tempo,
294 cycle: self.cycle,
295 tick_duration: self.tick_duration()?,
296 component_intervals: self.component_intervals(),
297 hit_events: self.hit_events()?,
298 ratio_tones: self.ratio_tones(),
299 })
300 }
301
302 pub fn coincidence_ticks(&self, min_simultaneous: usize) -> Vec<UnsignedIntegerType> {
304 if min_simultaneous == 0 {
305 return (0..self.cycle).collect();
306 }
307
308 (0..self.cycle)
309 .filter(|tick| {
310 self.components
311 .iter()
312 .filter(|sub| {
313 let divisor = self.cycle / **sub;
314 divisor != 0 && *tick % divisor == 0
315 })
316 .count()
317 >= min_simultaneous
318 })
319 .collect()
320 }
321
322 fn chord_from_base_pitch(&self, base_pitch: Pitch) -> Result<Chord> {
323 let mut offsets = BTreeSet::new();
324 for &sub in &self.components {
325 let interval = self.cycle / sub;
326 for i in 0..sub {
327 let tick = i * interval;
328 let ratio = tick as FloatType / self.cycle as FloatType;
329 let semitones = (ratio * 12.0).round() as IntegerType;
330 offsets.insert(semitones);
331 }
332 }
333
334 let notes: Result<Vec<Pitch>, Error> = offsets
335 .into_iter()
336 .map(|offset| {
337 let interval = Interval::from_semitones(offset)?;
338 base_pitch.transpose(&interval)
339 })
340 .collect();
341
342 let notes = notes?;
343 Chord::new(notes.as_slice())
344 }
345
346 pub fn to_chord<T>(&self, base: T) -> Result<Chord>
348 where
349 T: TryInto<Pitch>,
350 T::Error: Into<Error>,
351 {
352 self.chord_from_base_pitch(base.try_into().map_err(Into::into)?)
353 }
354
355 pub fn to_polypitch<T>(&self, base: T) -> Result<Chord>
357 where
358 T: TryInto<Pitch>,
359 T::Error: Into<Error>,
360 {
361 self.to_chord(base)
362 }
363}
364
365impl Iterator for Polyrhythm {
366 type Item = (UnsignedIntegerType, Vec<bool>);
367
368 fn next(&mut self) -> Option<Self::Item> {
372 let tick = self.current_tick;
373 let triggers = self
374 .components
375 .iter()
376 .map(|&sub| {
377 let divisor = self.cycle / sub;
378 tick.checked_rem(divisor) == Some(0)
379 })
380 .collect();
381 self.current_tick = (self.current_tick + 1) % self.cycle;
382 Some((tick, triggers))
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_from_time_signature() {
392 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3]).unwrap();
393 assert_eq!(poly.cycle_len(), 6);
395 let tick_dur = poly.tick_duration().unwrap();
397 assert!((tick_dur - 0.3333).abs() < 0.01);
398 }
399
400 #[test]
401 fn test_new_rejects_zero_base() {
402 let err = Polyrhythm::new(0, &[2, 3]).unwrap_err();
403 assert!(err.to_string().contains("Base must be nonzero"));
404 }
405
406 #[test]
407 fn test_new_rejects_empty_and_zero_subdivisions() {
408 let empty = Polyrhythm::new(4, &[]).unwrap_err();
409 assert!(empty.to_string().contains("At least one subdivision"));
410
411 let zero_subdivision = Polyrhythm::new(4, &[2, 0, 3]).unwrap_err();
412 assert!(
413 zero_subdivision
414 .to_string()
415 .contains("Subdivision must be nonzero")
416 );
417 }
418
419 #[test]
420 fn test_set_tempo_rejects_zero() {
421 let mut poly = Polyrhythm::new(4, &[2, 3]).unwrap();
422 let err = poly.set_tempo(0).unwrap_err();
423 assert!(err.to_string().contains("Tempo must be nonzero"));
424 }
425
426 #[test]
427 fn test_with_tempo_sets_tempo() {
428 let poly = Polyrhythm::new(4, &[3, 4]).unwrap().with_tempo(90).unwrap();
429 assert_eq!(poly.tempo(), Some(90));
430 }
431
432 #[test]
433 fn test_without_tempo_rejects_time_queries() {
434 let poly = Polyrhythm::new(4, &[2, 3]).unwrap();
435 assert!(poly.measure_duration().is_err());
436 assert!(poly.tick_duration().is_err());
437 assert!(poly.beat_timings().is_err());
438 assert!(poly.events().is_err());
439 }
440
441 #[test]
442 fn test_beat_timings_are_spaced_by_component_interval() {
443 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3]).unwrap();
444 let timings = poly.beat_timings().unwrap();
445 assert_eq!(timings.len(), 2);
446 assert_eq!(timings[0].len(), 2);
447 assert_eq!(timings[1].len(), 3);
448 assert!((timings[0][1] - 1.0).abs() < 0.001);
449 assert!((timings[1][1] - 0.6666).abs() < 0.01);
450 }
451
452 #[test]
453 fn test_events() {
454 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3]).unwrap();
455 let events = poly.events().unwrap();
456 assert_eq!(events.len(), 6);
457 assert_eq!(events[0].triggers, vec![true, true]);
458 assert_eq!(events[1].triggers, vec![false, false]);
459 assert_eq!(events[2].triggers, vec![false, true]);
460 assert_eq!(events[3].triggers, vec![true, false]);
461
462 let hits = poly.hit_events().unwrap();
463 assert_eq!(hits.len(), 4);
464 assert_eq!(
465 hits.iter().map(|event| event.tick).collect::<Vec<_>>(),
466 vec![0, 2, 3, 4]
467 );
468 }
469
470 #[test]
471 fn ratio_tones_reduce_components_and_project_to_pitches() {
472 let poly = Polyrhythm::from_time_signature(4, 120, &[3, 4, 6]).unwrap();
473 let tones = poly.ratio_tones();
474 assert_eq!(
475 tones
476 .iter()
477 .map(|tone| (tone.component, tone.offset))
478 .collect::<Vec<_>>(),
479 vec![(3, 0), (4, 5), (6, 12)]
480 );
481
482 let pitches = poly.ratio_pitches("C4").unwrap();
483 assert_eq!(
484 pitches
485 .iter()
486 .map(Pitch::name_with_octave)
487 .collect::<Vec<_>>(),
488 vec!["C4", "F4", "C5"]
489 );
490
491 let analysis = poly.analysis().unwrap();
492 assert_eq!(analysis.component_intervals, vec![4, 3, 2]);
493 assert_eq!(analysis.ratio_tones, tones);
494 }
495
496 #[test]
497 fn test_coincidence_ticks() {
498 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3]).unwrap();
499 assert_eq!(poly.coincidence_ticks(0), vec![0, 1, 2, 3, 4, 5]);
500 assert_eq!(poly.coincidence_ticks(2), vec![0]);
501 assert_eq!(poly.coincidence_ticks(1), vec![0, 2, 3, 4]);
502 }
503
504 #[test]
505 fn test_to_chord_is_public_and_works() {
506 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3, 4]).unwrap();
507 let chord = poly.to_chord("C4").unwrap();
508 assert!(!chord.pitched_common_name().is_empty());
509 }
510
511 #[test]
512 fn test_iterator_state_and_reset() {
513 let mut poly = Polyrhythm::new(4, &[2, 4]).unwrap();
514 assert_eq!(poly.components(), &[2, 4]);
515 assert_eq!(poly.component_intervals(), vec![2, 1]);
516 assert_eq!(poly.current_tick(), 0);
517
518 assert_eq!(poly.next(), Some((0, vec![true, true])));
519 assert_eq!(poly.current_tick(), 1);
520 assert_eq!(poly.next(), Some((1, vec![false, true])));
521 poly.reset();
522 assert_eq!(poly.current_tick(), 0);
523 }
524}