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