1use num::integer::{gcd, lcm};
2use std::collections::BTreeMap;
3
4use crate::chord::Chord;
5use crate::defaults::{FloatType, IntegerType, UnsignedIntegerType};
6use crate::duration::Duration;
7use crate::error::{Error, Result};
8use crate::interval::Interval;
9use crate::meter::TimeSignature;
10use crate::note::Note;
11use crate::pitch::Pitch;
12use crate::stream::{Stream, StreamKind};
13use crate::tempo::MetronomeMark;
14
15#[derive(Debug, Clone)]
16#[must_use]
18pub struct Polyrhythm {
19 pub base: UnsignedIntegerType,
21 pub components: Vec<UnsignedIntegerType>,
23 pub tempo: Option<UnsignedIntegerType>,
25 pub cycle: UnsignedIntegerType,
27 current_tick: UnsignedIntegerType,
28}
29
30#[derive(Debug, Clone, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[must_use]
34pub struct PolyrhythmEvent {
35 pub tick: UnsignedIntegerType,
37 pub time_seconds: FloatType,
39 pub triggers: Vec<bool>,
41}
42
43#[derive(Debug, Clone, PartialEq)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45#[must_use]
47pub struct PolyrhythmRatioTone {
48 pub component: UnsignedIntegerType,
50 pub offset: IntegerType,
52 pub ratio: FloatType,
54 pub cents: FloatType,
56}
57
58#[derive(Debug, Clone, PartialEq)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[must_use]
62pub struct PolyrhythmAnalysis {
63 pub base: UnsignedIntegerType,
65 pub components: Vec<UnsignedIntegerType>,
67 pub tempo: UnsignedIntegerType,
69 pub cycle: UnsignedIntegerType,
71 pub tick_duration: FloatType,
73 pub component_intervals: Vec<UnsignedIntegerType>,
75 pub hit_events: Vec<PolyrhythmEvent>,
77 pub ratio_tones: Vec<PolyrhythmRatioTone>,
79}
80
81impl Polyrhythm {
82 pub fn new(base: UnsignedIntegerType, subdivisions: &[UnsignedIntegerType]) -> Result<Self> {
84 if base == 0 {
85 return Err(Error::Polyrhythm("Base must be nonzero".into()));
86 }
87 if subdivisions.is_empty() {
88 return Err(Error::Polyrhythm(
89 "At least one subdivision is required".into(),
90 ));
91 }
92 for &sub in subdivisions {
93 if sub == 0 {
94 return Err(Error::Polyrhythm("Subdivision must be nonzero".into()));
95 }
96 }
97 let cycle = subdivisions.iter().fold(1, |acc, &x| lcm(acc, x));
98 Ok(Self {
99 base,
100 components: subdivisions.to_vec(),
101 tempo: None,
102 cycle,
103 current_tick: 0,
104 })
105 }
106
107 pub fn from_time_signature(
110 beats_per_measure: UnsignedIntegerType,
111 tempo: UnsignedIntegerType,
112 subdivisions: &[UnsignedIntegerType],
113 ) -> Result<Self> {
114 Self::new(beats_per_measure, subdivisions)?.with_tempo(tempo)
115 }
116
117 pub fn with_tempo(mut self, tempo: UnsignedIntegerType) -> Result<Self> {
119 self.set_tempo(tempo)?;
120 Ok(self)
121 }
122
123 pub fn set_tempo(&mut self, tempo: UnsignedIntegerType) -> Result<()> {
125 if tempo == 0 {
126 return Err(Error::Polyrhythm("Tempo must be nonzero".into()));
127 }
128 self.tempo = Some(tempo);
129 Ok(())
130 }
131
132 pub fn tempo(&self) -> Option<UnsignedIntegerType> {
137 self.tempo
138 }
139
140 pub fn components(&self) -> &[UnsignedIntegerType] {
142 &self.components
143 }
144
145 pub fn current_tick(&self) -> UnsignedIntegerType {
147 self.current_tick
148 }
149
150 pub fn reset(&mut self) {
152 self.current_tick = 0;
153 }
154
155 pub fn component_intervals(&self) -> Vec<UnsignedIntegerType> {
157 self.components
158 .iter()
159 .map(|sub| self.cycle / *sub)
160 .collect()
161 }
162
163 pub fn measure_duration(&self) -> Result<FloatType> {
165 match self.tempo {
166 Some(tempo) => Ok(self.base as FloatType * 60.0 / (tempo as FloatType)),
167 None => Err(Error::Polyrhythm("Tempo not set".into())),
168 }
169 }
170
171 pub fn tick_duration(&self) -> Result<FloatType> {
173 Ok(self.measure_duration()? / self.cycle as FloatType)
174 }
175
176 pub fn cycle_len(&self) -> UnsignedIntegerType {
178 self.cycle
179 }
180
181 pub fn beat_timings(&self) -> Result<Vec<Vec<FloatType>>> {
184 let tick_duration = self.tick_duration()?;
185 Ok(self
186 .components
187 .iter()
188 .map(|&sub| {
189 let interval = self.cycle / sub;
190 (0..sub)
191 .map(|i| (i * interval) as FloatType * tick_duration)
192 .collect()
193 })
194 .collect())
195 }
196
197 pub fn events(&self) -> Result<Vec<PolyrhythmEvent>> {
199 let tick_duration = self.tick_duration()?;
200 Ok((0..self.cycle)
201 .map(|tick| {
202 let triggers = self
203 .components
204 .iter()
205 .map(|&sub| {
206 let divisor = self.cycle / sub;
207 divisor != 0 && tick % divisor == 0
208 })
209 .collect::<Vec<_>>();
210 PolyrhythmEvent {
211 tick,
212 time_seconds: tick as FloatType * tick_duration,
213 triggers,
214 }
215 })
216 .collect())
217 }
218
219 pub fn hit_events(&self) -> Result<Vec<PolyrhythmEvent>> {
221 Ok(self
222 .events()?
223 .into_iter()
224 .filter(|event| event.triggers.iter().any(|trigger| *trigger))
225 .collect())
226 }
227
228 pub fn ratio_tones(&self) -> Vec<PolyrhythmRatioTone> {
235 let divisor = self
236 .components
237 .iter()
238 .copied()
239 .reduce(gcd)
240 .unwrap_or(1)
241 .max(1);
242 let reduced_components = self
243 .components
244 .iter()
245 .map(|component| component / divisor)
246 .collect::<Vec<_>>();
247 let root_ratio = reduced_components.iter().copied().min().unwrap_or(1).max(1);
248 let mut tones_by_offset = BTreeMap::new();
249
250 for component in reduced_components {
251 let ratio = component as FloatType / root_ratio as FloatType;
252 let offset = (12.0 * ratio.log2()).round() as IntegerType;
253 tones_by_offset.entry(offset).or_insert(component);
254 }
255
256 tones_by_offset
257 .into_iter()
258 .map(|(offset, component)| {
259 let ratio = component as FloatType / root_ratio as FloatType;
260 PolyrhythmRatioTone {
261 component,
262 offset,
263 ratio,
264 cents: 1200.0 * ratio.log2(),
265 }
266 })
267 .collect()
268 }
269
270 pub fn ratio_pitches<T>(&self, base: T) -> Result<Vec<Pitch>>
276 where
277 T: TryInto<Pitch>,
278 T::Error: Into<Error>,
279 {
280 let base_pitch = base.try_into().map_err(Into::into)?;
281 self.ratio_tones()
282 .into_iter()
283 .map(|tone| pitch_above(&base_pitch, tone.cents))
284 .collect()
285 }
286
287 pub fn to_score<T>(&self, base: T) -> Result<Stream>
309 where
310 T: TryInto<Pitch>,
311 T::Error: Into<Error>,
312 {
313 let base_pitch = base.try_into().map_err(Into::into)?;
314 let divisor = self
315 .components
316 .iter()
317 .copied()
318 .reduce(gcd)
319 .unwrap_or(1)
320 .max(1);
321 let root = self.components.iter().copied().min().unwrap_or(1).max(1) / divisor;
322 let bar = FloatType::from(self.base);
323 let mut score = Stream::with_kind(StreamKind::Score);
324 for (index, &component) in self.components.iter().enumerate() {
325 let ratio = FloatType::from(component / divisor) / FloatType::from(root);
326 let pitch = pitch_above(&base_pitch, 1200.0 * ratio.log2())?;
327 let length = bar / FloatType::from(component);
328 let mut measure = Stream::with_kind(StreamKind::Measure);
329 measure.set_number(1);
330 measure.insert(0.0, TimeSignature::new(self.base, 4)?);
331 if let (0, Some(tempo)) = (index, self.tempo) {
332 measure.insert(0.0, MetronomeMark::new(FloatType::from(tempo)));
333 }
334 for beat in 0..component {
335 let note = Note::from_pitch(pitch.clone()).with_duration(Duration::new(length)?);
336 measure.insert(FloatType::from(beat) * length, note);
337 }
338 let mut part = Stream::with_kind(StreamKind::Part);
339 part.set_name(Some(component.to_string()));
340 part.push(measure);
341 score.push(part);
342 }
343 Ok(score)
344 }
345
346 pub fn ratio_frequencies(&self, base_hertz: FloatType) -> Vec<FloatType> {
352 self.ratio_tones()
353 .into_iter()
354 .map(|tone| base_hertz * tone.ratio)
355 .collect()
356 }
357
358 pub fn ratio_chord<T>(&self, base: T) -> Result<Chord>
360 where
361 T: TryInto<Pitch>,
362 T::Error: Into<Error>,
363 {
364 let pitches = self.ratio_pitches(base)?;
365 Chord::new(pitches.as_slice())
366 }
367
368 pub fn analysis(&self) -> Result<PolyrhythmAnalysis> {
370 let tempo = self
371 .tempo
372 .ok_or_else(|| Error::Polyrhythm("Tempo not set".into()))?;
373 Ok(PolyrhythmAnalysis {
374 base: self.base,
375 components: self.components.clone(),
376 tempo,
377 cycle: self.cycle,
378 tick_duration: self.tick_duration()?,
379 component_intervals: self.component_intervals(),
380 hit_events: self.hit_events()?,
381 ratio_tones: self.ratio_tones(),
382 })
383 }
384
385 pub fn coincidence_ticks(&self, min_simultaneous: usize) -> Vec<UnsignedIntegerType> {
387 if min_simultaneous == 0 {
388 return (0..self.cycle).collect();
389 }
390
391 (0..self.cycle)
392 .filter(|tick| {
393 self.components
394 .iter()
395 .filter(|sub| {
396 let divisor = self.cycle / **sub;
397 divisor != 0 && *tick % divisor == 0
398 })
399 .count()
400 >= min_simultaneous
401 })
402 .collect()
403 }
404}
405
406fn pitch_above(base: &Pitch, cents: FloatType) -> Result<Pitch> {
409 let semitones = (cents / 100.0).round();
410 let mut pitch = base.transpose(&Interval::from_semitones(semitones as IntegerType)?)?;
411 let carried = base.microtone().map_or(0.0, |microtone| microtone.cents());
412 pitch.set_microtone_cents(carried + cents - 100.0 * semitones)?;
413 Ok(pitch)
414}
415
416impl Iterator for Polyrhythm {
417 type Item = (UnsignedIntegerType, Vec<bool>);
418
419 fn next(&mut self) -> Option<Self::Item> {
423 let tick = self.current_tick;
424 let triggers = self
425 .components
426 .iter()
427 .map(|&sub| {
428 let divisor = self.cycle / sub;
429 tick.checked_rem(divisor) == Some(0)
430 })
431 .collect();
432 self.current_tick = (self.current_tick + 1) % self.cycle;
433 Some((tick, triggers))
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440 use crate::stream::StreamElement;
441
442 #[test]
443 fn test_from_time_signature() {
444 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3]).unwrap();
445 assert_eq!(poly.cycle_len(), 6);
447 let tick_dur = poly.tick_duration().unwrap();
449 assert!((tick_dur - 0.3333).abs() < 0.01);
450 }
451
452 #[test]
453 fn test_new_rejects_zero_base() {
454 let err = Polyrhythm::new(0, &[2, 3]).unwrap_err();
455 assert!(err.to_string().contains("Base must be nonzero"));
456 }
457
458 #[test]
459 fn test_new_rejects_empty_and_zero_subdivisions() {
460 let empty = Polyrhythm::new(4, &[]).unwrap_err();
461 assert!(empty.to_string().contains("At least one subdivision"));
462
463 let zero_subdivision = Polyrhythm::new(4, &[2, 0, 3]).unwrap_err();
464 assert!(
465 zero_subdivision
466 .to_string()
467 .contains("Subdivision must be nonzero")
468 );
469 }
470
471 #[test]
472 fn test_set_tempo_rejects_zero() {
473 let mut poly = Polyrhythm::new(4, &[2, 3]).unwrap();
474 let err = poly.set_tempo(0).unwrap_err();
475 assert!(err.to_string().contains("Tempo must be nonzero"));
476 }
477
478 #[test]
479 fn test_with_tempo_sets_tempo() {
480 let poly = Polyrhythm::new(4, &[3, 4]).unwrap().with_tempo(90).unwrap();
481 assert_eq!(poly.tempo(), Some(90));
482 }
483
484 #[test]
485 fn test_without_tempo_rejects_time_queries() {
486 let poly = Polyrhythm::new(4, &[2, 3]).unwrap();
487 assert!(poly.measure_duration().is_err());
488 assert!(poly.tick_duration().is_err());
489 assert!(poly.beat_timings().is_err());
490 assert!(poly.events().is_err());
491 }
492
493 #[test]
494 fn test_beat_timings_are_spaced_by_component_interval() {
495 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3]).unwrap();
496 let timings = poly.beat_timings().unwrap();
497 assert_eq!(timings.len(), 2);
498 assert_eq!(timings[0].len(), 2);
499 assert_eq!(timings[1].len(), 3);
500 assert!((timings[0][1] - 1.0).abs() < 0.001);
501 assert!((timings[1][1] - 0.6666).abs() < 0.01);
502 }
503
504 #[test]
505 fn test_events() {
506 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3]).unwrap();
507 let events = poly.events().unwrap();
508 assert_eq!(events.len(), 6);
509 assert_eq!(events[0].triggers, vec![true, true]);
510 assert_eq!(events[1].triggers, vec![false, false]);
511 assert_eq!(events[2].triggers, vec![false, true]);
512 assert_eq!(events[3].triggers, vec![true, false]);
513
514 let hits = poly.hit_events().unwrap();
515 assert_eq!(hits.len(), 4);
516 assert_eq!(
517 hits.iter().map(|event| event.tick).collect::<Vec<_>>(),
518 vec![0, 2, 3, 4]
519 );
520 }
521
522 #[test]
523 fn ratio_tones_reduce_components_and_project_to_pitches() {
524 let poly = Polyrhythm::from_time_signature(4, 120, &[3, 4, 6]).unwrap();
525 let tones = poly.ratio_tones();
526 assert_eq!(
527 tones
528 .iter()
529 .map(|tone| (tone.component, tone.offset))
530 .collect::<Vec<_>>(),
531 vec![(3, 0), (4, 5), (6, 12)]
532 );
533
534 let pitches = poly.ratio_pitches("C4").unwrap();
535 assert_eq!(
536 pitches
537 .iter()
538 .map(Pitch::name_with_octave)
539 .collect::<Vec<_>>(),
540 vec!["C4", "F4", "C5"]
541 );
542 assert!((tones[1].cents - 498.044_999_134_798).abs() < 1e-9);
544 assert!((pitches[1].microtone().unwrap().cents() + 1.955).abs() < 1e-3);
545 assert!(pitches[2].microtone().is_none());
546
547 let analysis = poly.analysis().unwrap();
548 assert_eq!(analysis.component_intervals, vec![4, 3, 2]);
549 assert_eq!(analysis.ratio_tones, tones);
550 }
551
552 #[test]
553 fn test_coincidence_ticks() {
554 let poly = Polyrhythm::from_time_signature(4, 120, &[2, 3]).unwrap();
555 assert_eq!(poly.coincidence_ticks(0), vec![0, 1, 2, 3, 4, 5]);
556 assert_eq!(poly.coincidence_ticks(2), vec![0]);
557 assert_eq!(poly.coincidence_ticks(1), vec![0, 2, 3, 4]);
558 }
559
560 #[test]
561 fn a_rhythm_of_four_five_and_six_is_a_just_major_triad() {
562 let poly = Polyrhythm::new(4, &[4, 5, 6]).unwrap();
563 let pitches = poly.ratio_pitches("C4").unwrap();
564 let written = pitches
565 .iter()
566 .map(|pitch| {
567 let microtone = pitch.microtone().map(ToString::to_string);
568 format!(
569 "{}{}",
570 pitch.name_with_octave(),
571 microtone.unwrap_or_default()
572 )
573 })
574 .collect::<Vec<_>>();
575 assert_eq!(written, ["C4", "E4(-14c)", "G4(+2c)"]);
576
577 let hertz = poly.ratio_frequencies(200.0);
578 assert_eq!(hertz, [200.0, 250.0, 300.0]);
579 let just = pitches[1].frequency_hz() / pitches[0].frequency_hz();
581 assert!((just - 1.25).abs() < 1e-3);
582 }
583
584 #[test]
585 fn a_score_holds_a_part_for_each_subdivision_in_tuplets() {
586 let poly = Polyrhythm::new(4, &[4, 5, 6])
587 .unwrap()
588 .with_tempo(72)
589 .unwrap();
590 let score = poly.to_score("C4").unwrap();
591 let parts = score.parts();
592 assert_eq!(parts.len(), 3);
593 for (part, (count, name)) in parts.iter().zip([(4, "C4"), (5, "E4"), (6, "G4")]) {
594 let notes = part.measures()[0].notes();
595 assert_eq!(notes.len(), count);
596 let StreamElement::Note(note) = ¬es[0].1 else {
597 panic!("a part plays notes");
598 };
599 assert_eq!(note.pitch().name_with_octave(), name);
600 let total: FloatType = notes
601 .iter()
602 .map(|(_, element)| element.duration().map_or(0.0, |d| d.quarter_length()))
603 .sum();
604 assert!((total - 4.0).abs() < 1e-9);
605 }
606 let StreamElement::Note(quintuplet) = &parts[1].measures()[0].notes()[0].1 else {
607 panic!("a part plays notes");
608 };
609 assert!(!quintuplet.duration().unwrap().tuplets().is_empty());
610 assert_eq!(parts[1].name(), Some("5"));
611 }
612
613 #[cfg(feature = "musicxml")]
614 #[test]
615 fn a_score_writes_as_musicxml_with_its_tuplets() {
616 use crate::musicxml::{ExportOptions, to_musicxml};
617 for components in [[3, 2], [5, 4], [7, 3]] {
618 let score = Polyrhythm::new(4, &components)
619 .unwrap()
620 .with_tempo(60)
621 .unwrap()
622 .to_score("A3")
623 .unwrap();
624 let xml = to_musicxml(&score, &ExportOptions::default()).unwrap();
625 assert!(xml.contains("<time-modification>"), "{components:?}");
626 assert!(
627 xml.contains("<per-minute>60</per-minute>"),
628 "{components:?}"
629 );
630 }
631 }
632
633 #[test]
634 fn test_iterator_state_and_reset() {
635 let mut poly = Polyrhythm::new(4, &[2, 4]).unwrap();
636 assert_eq!(poly.components(), &[2, 4]);
637 assert_eq!(poly.component_intervals(), vec![2, 1]);
638 assert_eq!(poly.current_tick(), 0);
639
640 assert_eq!(poly.next(), Some((0, vec![true, true])));
641 assert_eq!(poly.current_tick(), 1);
642 assert_eq!(poly.next(), Some((1, vec![false, true])));
643 poly.reset();
644 assert_eq!(poly.current_tick(), 0);
645 }
646}