tune/pergen.rs
1//! Find generalized notes and names for rank-2 temperaments.
2
3use std::{
4 borrow::Cow,
5 cmp::Ordering,
6 fmt::Write,
7 iter,
8 ops::{Add, Sub},
9};
10
11use crate::math;
12
13#[derive(Clone, Debug)]
14pub struct PerGen {
15 period: u16,
16 generator: u16,
17 num_cycles: u16,
18 generator_inverse: u16,
19}
20
21impl PerGen {
22 pub fn new(period: u16, generator: u16) -> Self {
23 let (num_cycles, _, generator_inverse) =
24 extended_gcd(i32::from(period), i32::from(generator));
25
26 let num_cycles = u16::try_from(num_cycles).unwrap();
27 let generator_inverse = math::i32_rem_u(generator_inverse, period / num_cycles);
28
29 Self {
30 period,
31 generator,
32 num_cycles,
33 generator_inverse,
34 }
35 }
36
37 pub fn period(&self) -> u16 {
38 self.period
39 }
40
41 pub fn generator(&self) -> u16 {
42 self.generator
43 }
44
45 pub fn num_cycles(&self) -> u16 {
46 self.num_cycles
47 }
48
49 pub fn reduced_period(&self) -> u16 {
50 self.period / self.num_cycles
51 }
52
53 pub fn get_generation(&self, index: u16) -> Generation {
54 let reduced_index = index / self.num_cycles;
55
56 let degree = math::i32_rem_u(
57 i32::from(self.generator_inverse) * i32::from(reduced_index),
58 self.reduced_period(),
59 );
60
61 Generation {
62 cycle: (self.num_cycles > 1).then_some(index % self.num_cycles),
63 degree,
64 }
65 }
66
67 pub fn get_accidentals(&self, format: &AccidentalsFormat, index: u16) -> Accidentals {
68 let generation = self.get_generation(index);
69 let num_steps = self.reduced_period();
70
71 if num_steps >= format.num_symbols {
72 let degree = i32::from(format.genchain_origin) + i32::from(generation.degree);
73 let end_of_genchain = format.num_symbols - 1;
74
75 let sharp_coord = math::i32_rem_u(degree, num_steps);
76 let flat_coord = math::i32_rem_u(i32::from(end_of_genchain) - degree, num_steps);
77
78 // genchain: F-->C-->G-->D-->A-->E-->B->F#->C#->G#->D#->A#-->F
79 // sharp_coord: 9 10 11 0 1 2 3 4 5 6 7 8 9
80 // flat_coord: 3 2 1 0 11 10 9 8 7 6 5 4 3
81
82 Accidentals {
83 cycle: generation.cycle,
84 sharp_index: sharp_coord % format.num_symbols,
85 sharp_count: sharp_coord / format.num_symbols,
86 flat_index: end_of_genchain - flat_coord % format.num_symbols,
87 flat_count: flat_coord / format.num_symbols,
88 }
89 } else {
90 let shift = i32::from(generation.degree > 0) * i32::from(num_steps);
91
92 let mut sharp_degree = i32::from(format.genchain_origin) + i32::from(generation.degree);
93 let mut flat_degree = sharp_degree - shift;
94
95 // genchain: F->C->G->D->A->E->B
96 // sharp_degree: 0 1 2 3 4
97 // flat_degree: 1 2 3 4 0
98
99 if sharp_degree >= i32::from(format.num_symbols) {
100 sharp_degree -= shift;
101 }
102 if flat_degree < 0 {
103 flat_degree += shift;
104 }
105
106 // genchain: F->C->G->D->A->E->B
107 // sharp_degree: 4 0 1 2 3
108 // flat_degree: 2 3 4 0 1
109
110 Accidentals {
111 cycle: generation.cycle,
112 sharp_index: u16::try_from(sharp_degree).unwrap(),
113 sharp_count: 0,
114 flat_index: u16::try_from(flat_degree).unwrap(),
115 flat_count: 0,
116 }
117 }
118 }
119
120 pub fn get_moses(&self) -> impl Iterator<Item = Mos> {
121 Mos::<u16>::new_genesis(self.period, self.generator).children()
122 }
123}
124
125#[allow(clippy::many_single_char_names)]
126fn extended_gcd(a: i32, b: i32) -> (i32, i32, i32) {
127 let mut gcd = (a, b);
128 let mut a_inv = (1, 0);
129 let mut b_inv = (0, 1);
130
131 while gcd.1 != 0 {
132 let q = gcd.0 / gcd.1;
133 gcd = (gcd.1, gcd.0 - q * gcd.1);
134 a_inv = (a_inv.1, a_inv.0 - q * a_inv.1);
135 b_inv = (b_inv.1, b_inv.0 - q * b_inv.1);
136 }
137
138 (gcd.0, a_inv.0, b_inv.0)
139}
140
141#[derive(Copy, Clone, Debug)]
142pub struct Generation {
143 pub cycle: Option<u16>,
144 pub degree: u16,
145}
146
147#[derive(Clone, Debug)]
148pub struct AccidentalsFormat {
149 pub num_symbols: u16,
150 pub genchain_origin: u16,
151}
152
153#[derive(Clone, Debug)]
154pub struct Accidentals {
155 pub cycle: Option<u16>,
156 pub sharp_index: u16,
157 pub sharp_count: u16,
158 pub flat_index: u16,
159 pub flat_count: u16,
160}
161
162#[derive(Clone, Debug)]
163pub struct NoteFormatter {
164 pub note_names: Cow<'static, [char]>,
165 pub sharp_sign: char,
166 pub flat_sign: char,
167 pub order: AccidentalsOrder,
168}
169
170impl NoteFormatter {
171 pub fn format(&self, accidentals: &Accidentals) -> String {
172 if accidentals.sharp_count == 0
173 && accidentals.flat_count == 0
174 && accidentals.sharp_index == accidentals.flat_index
175 {
176 return self.render_note_with_cycle(
177 accidentals.cycle,
178 accidentals.sharp_index,
179 0,
180 '\0',
181 );
182 }
183
184 match accidentals.sharp_count.cmp(&accidentals.flat_count) {
185 Ordering::Greater => self.render_note_with_cycle(
186 accidentals.cycle,
187 accidentals.flat_index,
188 accidentals.flat_count,
189 self.flat_sign,
190 ),
191 Ordering::Less => self.render_note_with_cycle(
192 accidentals.cycle,
193 accidentals.sharp_index,
194 accidentals.sharp_count,
195 self.sharp_sign,
196 ),
197 Ordering::Equal => self.render_enharmonic_note_with_cycle(
198 accidentals.cycle,
199 accidentals.sharp_index,
200 accidentals.flat_index,
201 accidentals.sharp_count,
202 ),
203 }
204 }
205
206 fn render_note_with_cycle(
207 &self,
208 cycle: Option<u16>,
209 index: u16,
210 num_accidentals: u16,
211 accidental: char,
212 ) -> String {
213 let mut formatted = String::new();
214
215 self.write_note(&mut formatted, index, num_accidentals, accidental);
216 self.write_cycle(&mut formatted, cycle);
217
218 formatted
219 }
220
221 fn render_enharmonic_note_with_cycle(
222 &self,
223 cycle: Option<u16>,
224 sharp_index: u16,
225 flat_index: u16,
226 num_accidentals: u16,
227 ) -> String {
228 let mut formatted = String::new();
229
230 if cycle.is_some() {
231 formatted.push('(');
232 }
233 match self.order {
234 AccidentalsOrder::SharpFlat => {
235 self.write_note(
236 &mut formatted,
237 sharp_index,
238 num_accidentals,
239 self.sharp_sign,
240 );
241 formatted.push('/');
242 self.write_note(&mut formatted, flat_index, num_accidentals, self.flat_sign);
243 }
244 AccidentalsOrder::FlatSharp => {
245 self.write_note(&mut formatted, flat_index, num_accidentals, self.flat_sign);
246 formatted.push('/');
247 self.write_note(
248 &mut formatted,
249 sharp_index,
250 num_accidentals,
251 self.sharp_sign,
252 );
253 }
254 }
255 if cycle.is_some() {
256 formatted.push(')');
257 }
258 self.write_cycle(&mut formatted, cycle);
259
260 formatted
261 }
262
263 fn write_note(&self, target: &mut String, index: u16, num_accidentals: u16, accidental: char) {
264 target.push(*self.note_names.get(usize::from(index)).unwrap_or(&'?'));
265 for _ in 0..num_accidentals {
266 target.push(accidental);
267 }
268 }
269
270 fn write_cycle(&self, target: &mut String, cycle: Option<u16>) {
271 if let Some(cycle) = cycle {
272 write!(target, "[{cycle}]").unwrap();
273 }
274 }
275}
276
277#[derive(Copy, Clone, Debug, Eq, PartialEq)]
278pub enum AccidentalsOrder {
279 SharpFlat,
280 FlatSharp,
281}
282
283impl AccidentalsOrder {
284 pub fn from_sharpness(sharpness: i16) -> Self {
285 if sharpness >= 0 {
286 AccidentalsOrder::SharpFlat
287 } else {
288 AccidentalsOrder::FlatSharp
289 }
290 }
291}
292
293#[allow(private_bounds)]
294pub trait MosParam: Num {
295 type Sharpness: NumCast<Self>;
296 type TotalSteps: NumCast<Self>;
297}
298
299impl MosParam for u16 {
300 type Sharpness = i32;
301 type TotalSteps = u32;
302}
303
304impl MosParam for f64 {
305 type Sharpness = f64;
306 type TotalSteps = f64;
307}
308
309/// *Moment-of-Symmetry* data structure for theoretical analysis and practical usage within an isomorphic keyboard setup.
310///
311/// Instead of the traditional *x*L*y*s (number of large and small steps) representation, *x*p*y*s (number of primary and secondary steps) is used.
312/// As an enhancement, the sizes of the primary and secondary steps are included as well s.t. it is possible to tell which of the two step counts corresponds to the larger or the smaller step.
313///
314/// The *x*p*y*s representation is utilized primarily to preserve crucial information during calculations.
315/// For example, given a MOS *m* generated by a genesis MOS *g*, we can use the sign of `m.sharpness()` to determine whether `g.primary_step()` refers to the bright or to the dark generator of *m*.
316#[derive(Clone, Copy, Debug)]
317pub struct Mos<StepSize = u16, StepCount = u16> {
318 num_primary_steps: StepCount,
319 num_secondary_steps: StepCount,
320 primary_step: StepSize,
321 secondary_step: StepSize,
322 size: u16,
323}
324
325impl<StepCount: MosParam> Mos<u16, StepCount> {
326 /// Creates a new 1p1s [`Mos<u16>`] with a total size of `period` and a step ratio of `generator` ÷ `period - generator`.
327 ///
328 /// # Examples
329 ///
330 /// ```
331 /// # use tune::pergen::Mos;
332 /// let edo_12_fifth = 7;
333 /// let edo_12_fourth = 5;
334 ///
335 /// let mos = Mos::<u16>::new_genesis(12, edo_12_fifth);
336 /// assert_eq!(mos.size(), 12);
337 /// assert_eq!(mos.num_steps(), 2);
338 /// assert_eq!(mos.num_primary_steps(), 1);
339 /// assert_eq!(mos.num_secondary_steps(), 1);
340 /// assert_eq!(mos.primary_step(), edo_12_fifth);
341 /// assert_eq!(mos.secondary_step(), edo_12_fourth);
342 ///
343 /// let edo_12_tritave = 19;
344 ///
345 /// let mos = Mos::<u16>::new_genesis(12, edo_12_tritave);
346 /// assert_eq!(mos.size(), 12);
347 /// assert_eq!(mos.num_steps(), 2);
348 /// assert_eq!(mos.num_primary_steps(), 1);
349 /// assert_eq!(mos.num_secondary_steps(), 1);
350 /// assert_eq!(mos.primary_step(), edo_12_fifth); // MOS is reduced to the period.
351 /// assert_eq!(mos.secondary_step(), edo_12_fourth);
352 /// ```
353 pub fn new_genesis(period: u16, generator: u16) -> Self {
354 let primary_step = generator % period;
355 Self {
356 num_primary_steps: StepCount::one(),
357 num_secondary_steps: StepCount::one(),
358 primary_step,
359 secondary_step: period - primary_step,
360 size: period,
361 }
362 }
363}
364
365impl<StepCount: MosParam> Mos<f64, StepCount> {
366 /// Creates a new 1p1s [`Mos<f64>`] with a total size of 1 and a step ratio of `generator` ÷ `1.0 - generator`.
367 ///
368 /// # Examples
369 ///
370 /// ```
371 /// # use assert_approx_eq::assert_approx_eq;
372 /// # use tune::pergen::Mos;
373 /// # use tune::pitch::Ratio;
374 /// let just_fifth = Ratio::from_float(3.0 / 2.0).as_octaves();
375 /// let just_fourth = Ratio::from_float(4.0 / 3.0).as_octaves();
376 ///
377 /// let mos = Mos::<f64>::new_genesis(just_fifth);
378 /// assert_eq!(mos.size(), 1);
379 /// assert_eq!(mos.num_steps(), 2);
380 /// assert_eq!(mos.num_primary_steps(), 1);
381 /// assert_eq!(mos.num_secondary_steps(), 1);
382 /// assert_approx_eq!(mos.primary_step(), just_fifth);
383 /// assert_approx_eq!(mos.secondary_step(), just_fourth);
384 ///
385 /// let just_tritave = Ratio::from_float(3.0).as_octaves();
386 ///
387 /// let mos = Mos::<f64>::new_genesis(just_tritave);
388 /// assert_eq!(mos.size(), 1);
389 /// assert_eq!(mos.num_steps(), 2);
390 /// assert_eq!(mos.num_primary_steps(), 1);
391 /// assert_eq!(mos.num_secondary_steps(), 1);
392 /// assert_approx_eq!(mos.primary_step(), just_fifth); // MOS is reduced to the period.
393 /// assert_approx_eq!(mos.secondary_step(), just_fourth);
394 /// ```
395 pub fn new_genesis(generator: f64) -> Self {
396 let primary_step = generator.rem_euclid(1.0);
397 Self {
398 num_primary_steps: StepCount::one(),
399 num_secondary_steps: StepCount::one(),
400 primary_step,
401 secondary_step: 1.0 - primary_step,
402 size: 1,
403 }
404 }
405}
406
407impl<StepSize: MosParam> Mos<StepSize, u16> {
408 /// Creates a collapsed *x*L*y*s [`Mos`] with a step size ratio of 1 ÷ 0 and a sharpness of 1.
409 ///
410 /// # Examples
411 ///
412 /// ```
413 /// # use tune::pergen::Mos;
414 /// let num_diatonic_large_steps = 5;
415 /// let num_diatonic_small_steps = 2;
416 ///
417 /// let mos = Mos::<u16>::new_collapsed(
418 /// num_diatonic_large_steps,
419 /// num_diatonic_small_steps
420 /// );
421 /// assert_eq!(mos.size(), num_diatonic_large_steps);
422 /// assert_eq!(mos.num_steps(), 7);
423 /// assert_eq!(mos.num_primary_steps(), num_diatonic_large_steps);
424 /// assert_eq!(mos.num_secondary_steps(), num_diatonic_small_steps);
425 /// assert_eq!(mos.primary_step(), 1);
426 /// assert_eq!(mos.secondary_step(), 0);
427 /// ```
428 pub fn new_collapsed(num_large_steps: u16, num_small_steps: u16) -> Self {
429 Self {
430 num_primary_steps: num_large_steps,
431 num_secondary_steps: num_small_steps,
432 primary_step: StepSize::one(),
433 secondary_step: StepSize::default(),
434 size: num_large_steps,
435 }
436 }
437}
438
439impl Mos<u16, u16> {
440 /// Creates a custom *x*L*y*s [`Mos`] with the provided parameters.
441 ///
442 /// Returns [`None`] if the total size of the MOS would exceed numeric bounds.
443 ///
444 /// # Example
445 /// ```
446 /// # use tune::pergen::Mos;
447 /// let diatonic_mos = Mos::new(5, 2, 2, 1).unwrap();
448 /// assert_eq!(diatonic_mos.size(), 12);
449 /// assert_eq!(diatonic_mos.num_steps(), 7);
450 /// assert_eq!(diatonic_mos.num_primary_steps(), 5);
451 /// assert_eq!(diatonic_mos.num_secondary_steps(), 2);
452 /// assert_eq!(diatonic_mos.primary_step(), 2);
453 /// assert_eq!(diatonic_mos.secondary_step(), 1);
454 ///
455 /// let too_large_mos = Mos::new(200, 200, 200, 200);
456 /// assert!(too_large_mos.is_none());
457 /// ```
458 pub fn new(
459 num_primary_steps: u16,
460 num_secondary_steps: u16,
461 primary_step: u16,
462 secondary_step: u16,
463 ) -> Option<Self> {
464 Some(Self {
465 num_primary_steps,
466 num_secondary_steps,
467 primary_step,
468 secondary_step,
469 size: num_primary_steps
470 .checked_mul(primary_step)?
471 .checked_add(num_secondary_steps.checked_mul(secondary_step)?)?,
472 })
473 }
474}
475
476impl<StepSize: MosParam, StepCount: MosParam> Mos<StepSize, StepCount> {
477 /// Returns the current MOS' child MOS if possible.
478 ///
479 /// Returns [`None`] if the child MOS would be collapsed or if the step sizes would exceed numeric bounds.
480 ///
481 /// Note that, since the [`Mos`] type includes explicit step sizes, there is only one specific child MOS.
482 ///
483 /// # Examples
484 ///
485 /// ```
486 /// # use tune::pergen::Mos;
487 /// let edo_12_fifth = 7;
488 /// let mos = Mos::<u16>::new_genesis(12, edo_12_fifth);
489 ///
490 /// let child_mos = mos.child().unwrap();
491 /// assert_eq!(child_mos.size(), 12);
492 /// assert_eq!(child_mos.num_steps(), 3);
493 /// assert_eq!(child_mos.num_primary_steps(), 1);
494 /// assert_eq!(child_mos.num_secondary_steps(), 2);
495 /// assert_eq!(child_mos.primary_step(), 2);
496 /// assert_eq!(child_mos.secondary_step(), 5);
497 ///
498 /// let critical_mos = mos.children().last().unwrap();
499 /// assert_eq!(critical_mos.primary_step(), 1);
500 /// assert_eq!(critical_mos.secondary_step(), 1);
501 ///
502 /// // Child MOS would be collapsed since primary_step() == secondary_step().
503 /// assert!(critical_mos.child().is_none());
504 /// ```
505 pub fn child(mut self) -> Option<Self> {
506 if self.primary_step == StepSize::default() || self.secondary_step == StepSize::default() {
507 return None;
508 }
509
510 let num_steps = self
511 .num_secondary_steps
512 .checked_add(self.num_primary_steps)?;
513 let sharpness = self.primary_step.abs_diff(self.secondary_step);
514
515 match self.primary_step.partial_cmp(&self.secondary_step) {
516 Some(Ordering::Greater) => {
517 self.num_secondary_steps = num_steps;
518 self.primary_step = sharpness;
519 }
520 Some(Ordering::Less) => {
521 self.num_primary_steps = num_steps;
522 self.secondary_step = sharpness;
523 }
524 Some(Ordering::Equal) | None => return None,
525 }
526
527 Some(self)
528 }
529
530 /// Retrieves a sequence of child MOSes i.e. the MOSes for a given generator.
531 ///
532 /// The sequence includes the current MOS and will stop once a MOS is no longer properly representable.
533 ///
534 /// # Examples
535 ///
536 /// ```
537 /// # use tune::pergen::Mos;
538 /// # use tune::pitch::Ratio;
539 /// let just_fifth = Ratio::from_float(3.0 / 2.0).as_octaves();
540 /// let mos = Mos::<f64>::new_genesis(just_fifth);
541 /// let children = mos.children().collect::<Vec<_>>();
542 ///
543 /// let genesis_mos = &children[0];
544 /// assert_eq!(genesis_mos.size(), 1);
545 /// assert_eq!(genesis_mos.num_steps(), 2);
546 /// assert_eq!(genesis_mos.num_primary_steps(), 1);
547 /// assert_eq!(genesis_mos.num_secondary_steps(), 1);
548 ///
549 /// let diatonic_mos = &children[3];
550 /// assert_eq!(diatonic_mos.size(), 1);
551 /// assert_eq!(diatonic_mos.num_steps(), 7);
552 /// assert_eq!(diatonic_mos.num_primary_steps(), 5);
553 /// assert_eq!(diatonic_mos.num_secondary_steps(), 2);
554 ///
555 /// let critical_mos = &children[42];
556 /// assert_eq!(critical_mos.size(), 1);
557 /// assert_eq!(critical_mos.num_steps(), 79335);
558 /// assert_eq!(critical_mos.num_primary_steps(), 47468);
559 /// assert_eq!(critical_mos.num_secondary_steps(), 31867);
560 ///
561 /// // Child MOS cannot be represented since num_steps() is not a valid u16.
562 /// assert!(critical_mos.child().is_none());
563 /// ```
564 pub fn children(self) -> impl Iterator<Item = Self> {
565 iter::successors(Some(self), |mos| mos.child())
566 }
567
568 /// The inverse operation of [`Mos::child`].
569 pub fn parent(self) -> Option<Self> {
570 Some(self.dual().child()?.dual())
571 }
572
573 /// The inverse operation of [`Mos::children`].
574 pub fn parents(self) -> impl Iterator<Item = Self> {
575 iter::successors(Some(self), |mos| mos.parent())
576 }
577
578 /// Calculates the generating parent MOS with shape 1p1s to obtain the generator bounds for a MOS with shape *x*p*y*s.
579 ///
580 /// First, we need to calculate the generators of the collapsed *x*L*y*s MOS and the collapsed mirrored *y*L*x*s MOS.
581 /// This is achieved by calling [`Mos::new_collapsed(x, y).genesis()`](Mos::new_collapsed) and [`Mos::new_collapsed(y, x).genesis()`](Mos::new_collapsed).
582 ///
583 /// Since [`Mos::new_collapsed`] yields a MOS with a positive sharpness of 1, the corresponding genesis MOSes will reveal the *bright* generator via [`Mos::primary_step()`] and the *dark* generator via [`Mos::secondary_step()`].
584 ///
585 /// The full generator ranges then become
586 ///
587 /// (a) `mirror_mos.secondary_step() \ mirror_mos.size() .. mos.primary_step() \ mos.size()`
588 ///
589 /// or
590 ///
591 /// (b) `mos.secondary_step() \ mos.size() .. mirror_mos.primary_step() \ mirror_mos.size()`.
592 ///
593 /// Both generator ranges seamlessly interpolate between the mirrored and the unmirrored MOS and are equally valid solutions.
594 /// Note, however, that both ranges have a "bad" end that is affected by the presence of dark generators.
595 /// Thus, if we want to focus on the unmirrored *x*L*y*s MOS, range (a) is preferable.
596 ///
597 /// # Examples
598 ///
599 /// ```
600 /// # use tune::pergen::Mos;
601 /// // Find generator bounds of the diatonic (5L2s) scale.
602 ///
603 /// // Create a collapsed 5L2s MOS.
604 /// let diatonic_mos = Mos::<u16>::new_collapsed(5, 2);
605 ///
606 /// let genesis_mos = diatonic_mos.genesis();
607 /// assert_eq!(genesis_mos.num_primary_steps(), 1);
608 /// assert_eq!(genesis_mos.num_secondary_steps(), 1);
609 /// assert_eq!(genesis_mos.primary_step(), 3); // Bright generator.
610 /// assert_eq!(genesis_mos.secondary_step(), 2); // Dark generator.
611 /// assert_eq!(genesis_mos.size(), 5);
612 ///
613 /// // => The bright generator of 5L2s is 3\5. -> Upper bound!
614 /// // => The dark generator of 5L2s is 2\5.
615 ///
616 /// // Create a collapsed 2L5s mirror MOS.
617 /// let diatonic_mirror_mos = Mos::<u16>::new_collapsed(2, 5);
618 ///
619 /// let genesis_mirror_mos = diatonic_mirror_mos.genesis();
620 /// assert_eq!(genesis_mirror_mos.num_primary_steps(), 1);
621 /// assert_eq!(genesis_mirror_mos.num_secondary_steps(), 1);
622 /// assert_eq!(genesis_mirror_mos.primary_step(), 1); // Bright generator.
623 /// assert_eq!(genesis_mirror_mos.secondary_step(), 1); // Dark generator.
624 /// assert_eq!(genesis_mirror_mos.size(), 2);
625 ///
626 /// // => The bright generator of 2L5s is 1\2.
627 /// // => The dark generator of 2L5s is 1\2. -> Lower bound!
628 ///
629 /// // Result:
630 /// // The total generator range is from 1\2 (2L5s, dark end) to 3\5 (5L2s, bright end).
631 /// // The equal-step generator is (1+3)\(2+5) = 4\7.
632 /// // The proper generator is (1+2*3)/(2+2*5) = 7\12.
633 /// ```
634 pub fn genesis(self) -> Self {
635 self.parents().last().unwrap()
636 }
637
638 /// Creates a MOS with step sizes and step counts swapped.
639 ///
640 /// # Examples
641 ///
642 /// ```
643 /// # use tune::pergen::Mos;
644 /// let diatonic_mos = Mos::new(5, 2, 5, 3).unwrap();
645 /// assert_eq!(diatonic_mos.num_primary_steps(), 5);
646 /// assert_eq!(diatonic_mos.num_secondary_steps(), 2);
647 /// assert_eq!(diatonic_mos.primary_step(), 5);
648 /// assert_eq!(diatonic_mos.secondary_step(), 3);
649 ///
650 /// let dual_mos = diatonic_mos.dual();
651 /// assert_eq!(dual_mos.num_primary_steps(), 5);
652 /// assert_eq!(dual_mos.num_secondary_steps(), 3);
653 /// assert_eq!(dual_mos.primary_step(), 5);
654 /// assert_eq!(dual_mos.secondary_step(), 2);
655 /// ```
656 pub fn dual(self) -> Mos<StepCount, StepSize> {
657 Mos {
658 num_primary_steps: self.primary_step,
659 num_secondary_steps: self.secondary_step,
660 primary_step: self.num_primary_steps,
661 secondary_step: self.num_secondary_steps,
662 size: self.size,
663 }
664 }
665
666 /// Creates a MOS with primary and secondary semantics swapped.
667 ///
668 /// # Examples
669 ///
670 /// ```
671 /// # use tune::pergen::Mos;
672 /// let bright_diatonic_mos = Mos::new(5, 2, 2, 1).unwrap();
673 /// assert_eq!(bright_diatonic_mos.num_primary_steps(), 5);
674 /// assert_eq!(bright_diatonic_mos.num_secondary_steps(), 2);
675 /// assert_eq!(bright_diatonic_mos.primary_step(), 2);
676 /// assert_eq!(bright_diatonic_mos.secondary_step(), 1);
677 ///
678 /// let dark_diatonic_mos = bright_diatonic_mos.mirror();
679 /// assert_eq!(dark_diatonic_mos.num_primary_steps(), 2);
680 /// assert_eq!(dark_diatonic_mos.num_secondary_steps(), 5);
681 /// assert_eq!(dark_diatonic_mos.primary_step(), 1);
682 /// assert_eq!(dark_diatonic_mos.secondary_step(), 2);
683 /// ```
684 pub fn mirror(self) -> Self {
685 Self {
686 num_primary_steps: self.num_secondary_steps,
687 num_secondary_steps: self.num_primary_steps,
688 primary_step: self.secondary_step,
689 secondary_step: self.primary_step,
690 size: self.size,
691 }
692 }
693
694 /// Returns `num_primary_steps * primary_step + num_secondary_steps * secondary_step`.
695 pub fn size(self) -> u16 {
696 self.size
697 }
698
699 /// Returns `num_primary_steps + num_secondary_steps`.
700 pub fn num_steps(self) -> StepCount::TotalSteps {
701 StepCount::TotalSteps::from(self.num_primary_steps)
702 + StepCount::TotalSteps::from(self.num_secondary_steps)
703 }
704
705 pub fn num_primary_steps(self) -> StepCount {
706 self.num_primary_steps
707 }
708
709 pub fn num_secondary_steps(self) -> StepCount {
710 self.num_secondary_steps
711 }
712
713 pub fn primary_step(self) -> StepSize {
714 self.primary_step
715 }
716
717 pub fn secondary_step(self) -> StepSize {
718 self.secondary_step
719 }
720
721 /// Returns `primary_step - secondary_step`.
722 pub fn sharpness(self) -> StepSize::Sharpness {
723 StepSize::Sharpness::from(self.primary_step)
724 - StepSize::Sharpness::from(self.secondary_step)
725 }
726}
727
728impl<StepCount> Mos<u16, StepCount> {
729 /// Returns `gcd(primary_step, secondary_step)`.
730 pub fn num_cycles(self) -> u16 {
731 math::gcd_u16(self.primary_step, self.secondary_step)
732 }
733
734 /// Returns `size / gcd(primary_step, secondary_step)`.
735 pub fn reduced_size(self) -> u16 {
736 self.size / self.num_cycles()
737 }
738}
739
740impl Mos<u16, u16> {
741 /// Generates an automatic color schema for the given MOS.
742 ///
743 /// This is achieved by decomposing the MOS into the following color layers:
744 ///
745 /// - One central layer for the natural notes of the MOS i.e. those without accidentals.
746 /// - An equal number of middle layers arranged symmetrically around the central layer for the notes between the natural ones, i.e. those with accidentals (sharp or flat).
747 /// - An optional outer layer containing the enharmonic notes i.e. those which can be classified as both sharp or flat.
748 ///
749 /// Every layer is a consecutive genchain segment and can have one of the following sizes:
750 ///
751 /// - `num_primary_steps`
752 /// - `num_secondary_steps`
753 /// - `num_primary_steps + num_secondary_steps`
754 ///
755 /// This means there are at most 3 isomorphic layer shapes to memorize.
756 ///
757 /// # Return Value
758 ///
759 /// The color schema is returned as a [`Vec`] of `u16`s in genchain order.
760 /// It is the caller's responsibility to map the returned values to their target colors.
761 ///
762 /// # Examples
763 ///
764 /// ```
765 /// # use tune::layout::IsomorphicLayout;
766 /// // Color layers of 31-EDO: 7 (n) + 7 (#) + 5 (##) + 5 (bb) + 7 (b)
767 /// assert_eq!(
768 /// IsomorphicLayout::find_by_edo(31)[0].mos().get_layers(),
769 /// &[
770 /// 0, 0, 0, 0, 0, 0, 0, // Natural layer (F, C, G, D, A, E, B)
771 /// 1, 1, 1, 1, 1, 1, 1, // Sharp layer (F#, C#, G#, D#, A#, E#, B#)
772 /// 2, 2, 2, 2, 2, // 2nd sharp layer (F##, C##, G##, D##, A##)
773 /// 3, 3, 3, 3, 3, // 2nd flat layer (Gbb, Dbb, Abb, Ebb, Bbb)
774 /// 4, 4, 4, 4, 4, 4, 4, // Flat layer (Fb, Cb, Gb, Db, Ab, Eb, Bb)
775 /// ]
776 /// );
777 ///
778 /// // Color layers of 19-EDO: 7 (n) + 5 (#) + 2 (e) + 5 (b)
779 /// assert_eq!(
780 /// IsomorphicLayout::find_by_edo(19)[0].mos().get_layers(),
781 /// &[
782 /// 0, 0, 0, 0, 0, 0, 0, // Natural layer (F, C, G, D, A, E, B)
783 /// 1, 1, 1, 1, 1, // Sharp layer (F#, C#, G#, D#, A#)
784 /// 2, 2, // Enharmonic layer (E#/Fb, B#/Cb)
785 /// 3, 3, 3, 3, 3, // Flat layer (Gb, Db, Ab, Eb, Bb)
786 /// ]
787 /// );
788 ///
789 /// // Color layers of 24-EDO: 7 (n) + 5 (e), cycles are removed
790 /// assert_eq!(
791 /// IsomorphicLayout::find_by_edo(24)[0].mos().get_layers(),
792 /// &[
793 /// 0, 0, 0, 0, 0, 0, 0, // Natural layer (F, C, G, D, A, E, B)
794 /// 1, 1, 1, 1, 1, // Enharmonic layer (F#/Gb, C#/Db, G#/Ab, D#/Eb, A#/Bb)
795 /// ]
796 /// );
797 ///
798 /// // Color layers of 7-EDO: 7 (n)
799 /// assert_eq!(
800 /// IsomorphicLayout::find_by_edo(7)[0].mos().get_layers(),
801 /// &[
802 /// 0, 0, 0, 0, 0, 0, 0, // Natural layer (F, C, G, D, A, E, B)
803 /// ]
804 /// );
805 /// ```
806 pub fn get_layers(&self) -> Vec<u16> {
807 fn repeat<T: Clone>(count: u16, item: T) -> impl Iterator<Item = T> {
808 iter::repeat(item).take(usize::from(count))
809 }
810
811 let num_natural_primary_layers = u16::from(self.primary_step > 0);
812 let num_natural_secondary_layers = u16::from(self.secondary_step > 0);
813
814 let num_non_natural_primary_layers =
815 self.primary_step / self.num_cycles() - num_natural_primary_layers;
816 let num_non_natural_secondary_layers =
817 self.secondary_step / self.num_cycles() - num_natural_secondary_layers;
818
819 let num_intermediate_primary_layers = num_non_natural_primary_layers / 2;
820 let num_intermediate_secondary_layers = num_non_natural_secondary_layers / 2;
821
822 let num_enharmonic_primary_layers = num_non_natural_primary_layers % 2;
823 let num_enharmonic_secondary_layers = num_non_natural_secondary_layers % 2;
824
825 let size_of_natural_layer = num_natural_primary_layers * self.num_primary_steps
826 + num_natural_secondary_layers * self.num_secondary_steps;
827
828 let size_of_enharmonic_layer = num_enharmonic_primary_layers * self.num_primary_steps
829 + num_enharmonic_secondary_layers * self.num_secondary_steps;
830
831 let mut sizes_of_intermediate_layers = Vec::new();
832 sizes_of_intermediate_layers.extend(repeat(
833 num_intermediate_primary_layers.min(num_intermediate_secondary_layers),
834 self.num_primary_steps() + self.num_secondary_steps(),
835 ));
836 sizes_of_intermediate_layers.extend(repeat(
837 num_intermediate_primary_layers.saturating_sub(num_intermediate_secondary_layers),
838 self.num_primary_steps(),
839 ));
840 sizes_of_intermediate_layers.extend(repeat(
841 num_intermediate_secondary_layers.saturating_sub(num_intermediate_primary_layers),
842 self.num_secondary_steps(),
843 ));
844
845 iter::empty()
846 .chain([&size_of_natural_layer])
847 .chain(&sizes_of_intermediate_layers)
848 .chain([&size_of_enharmonic_layer])
849 .chain(sizes_of_intermediate_layers.iter().rev())
850 .filter(|&&layer_size| layer_size != 0)
851 .zip(0..)
852 .flat_map(|(&layer_size, layer_index)| repeat(layer_size, layer_index))
853 .collect()
854 }
855
856 /// Makes the step sizes of the MOS coprime s.t. all scale degrees are reachable.
857 ///
858 /// This addresses the scenario where not all key degrees can be reached when the step sizes are not coprime.
859 /// For instance, when `primary_step == 4` and `secondary_step == 2`, degrees with odd numbers cannot be obtained.
860 ///
861 /// This function solves the issue by adjusting `secondary_step` to divide the step width along the sharp axis into smaller segments.
862 /// As a result, the total size of the MOS will change.
863 ///
864 /// # Examples
865 ///
866 /// ```
867 /// # use tune::pergen::Mos;
868 /// let already_coprime = Mos::new(5, 2, 3, 2).unwrap().coprime();
869 ///
870 /// // Already coprime => Do nothing
871 /// assert_eq!(already_coprime.primary_step(), 3);
872 /// assert_eq!(already_coprime.secondary_step(), 2);
873 /// assert_eq!(already_coprime.size(), 19);
874 ///
875 /// let positive_sharp_value = Mos::new(5, 2, 4, 2).unwrap().coprime();
876 ///
877 /// // Sharp value is 4-2=2 before and 4-3=1 afterwards
878 /// assert_eq!(positive_sharp_value.primary_step(), 4);
879 /// assert_eq!(positive_sharp_value.secondary_step(), 3);
880 /// assert_eq!(positive_sharp_value.size(), 26);
881 ///
882 /// let negative_sharp_value = Mos::new(2, 5, 2, 4).unwrap().coprime();
883 ///
884 /// // Sharp value is 2-4=-2 before and 2-3=-1 afterwards
885 /// assert_eq!(negative_sharp_value.primary_step(), 2);
886 /// assert_eq!(negative_sharp_value.secondary_step(), 3);
887 /// assert_eq!(negative_sharp_value.size(), 19);
888 ///
889 /// let zero_sharp_value = Mos::new(2, 5, 2, 2).unwrap().coprime();
890 ///
891 /// // Special case: Sharp value is 2-2=0 before and 2-1=1 afterwards
892 /// assert_eq!(zero_sharp_value.primary_step(), 2);
893 /// assert_eq!(zero_sharp_value.secondary_step(), 1);
894 /// assert_eq!(zero_sharp_value.size(), 9);
895 ///
896 /// let large_sharp_value = Mos::new(2, 5, 6, 2).unwrap().coprime();
897 ///
898 /// // Special case: Sharp value is 6-2=4 before and 6-5=1 afterwards
899 /// assert_eq!(large_sharp_value.primary_step(), 6);
900 /// assert_eq!(large_sharp_value.secondary_step(), 5);
901 /// assert_eq!(large_sharp_value.size(), 37);
902 /// ```
903 pub fn coprime(mut self) -> Self {
904 // Special case: Set sharp value to 1 if it is currently 0
905 if self.primary_step == self.secondary_step {
906 self.secondary_step = self.primary_step - 1;
907 }
908
909 loop {
910 let num_cycles = self.num_cycles();
911
912 if num_cycles == 1 {
913 break;
914 }
915
916 let current_sharp_value = self.primary_step.abs_diff(self.secondary_step);
917 let wanted_sharp_value = current_sharp_value / num_cycles;
918 let sharp_delta = current_sharp_value - wanted_sharp_value;
919
920 if self.primary_step > self.secondary_step {
921 self.secondary_step += sharp_delta;
922 } else {
923 self.secondary_step -= sharp_delta;
924 }
925 }
926
927 self.size = self.num_primary_steps * self.primary_step
928 + self.num_secondary_steps * self.secondary_step;
929
930 self
931 }
932
933 /// Get the scale degree of the key at location `(x, y)`.
934 ///
935 /// ```
936 /// # use tune::pergen::Mos;
937 /// let mos = Mos::new(5, 2, 5, 3).unwrap();
938 ///
939 /// assert_eq!(mos.get_key(-2, -2), -16);
940 /// assert_eq!(mos.get_key(-2, -1), -13);
941 /// assert_eq!(mos.get_key(-2, 0), -10);
942 /// assert_eq!(mos.get_key(-1, 0), -5);
943 /// assert_eq!(mos.get_key(0, 0), 0);
944 /// assert_eq!(mos.get_key(0, 1), 3);
945 /// assert_eq!(mos.get_key(0, 2), 6);
946 /// assert_eq!(mos.get_key(1, 2), 11);
947 /// assert_eq!(mos.get_key(2, 2), 16);
948 /// ```
949 pub fn get_key(&self, num_primary_steps: i16, num_secondary_steps: i16) -> i32 {
950 i32::from(num_primary_steps) * i32::from(self.primary_step)
951 + i32::from(num_secondary_steps) * i32::from(self.secondary_step)
952 }
953}
954
955trait NumBase: Copy + Default + PartialOrd + Add<Output = Self> + Sub<Output = Self> {}
956
957impl<T: Copy + Default + PartialOrd + Add<Output = Self> + Sub<Output = Self>> NumBase for T {}
958
959// This trait is visible in the docs.
960trait Num: NumBase {
961 fn one() -> Self;
962
963 fn abs_diff(self, other: Self) -> Self;
964
965 fn checked_add(self, other: Self) -> Option<Self>;
966}
967
968impl Num for u16 {
969 fn one() -> Self {
970 1
971 }
972
973 fn abs_diff(self, other: Self) -> Self {
974 self.abs_diff(other)
975 }
976
977 fn checked_add(self, other: Self) -> Option<Self> {
978 self.checked_add(other)
979 }
980}
981
982impl Num for f64 {
983 fn one() -> Self {
984 1.0
985 }
986
987 fn abs_diff(self, other: Self) -> Self {
988 (self - other).abs()
989 }
990
991 fn checked_add(self, other: Self) -> Option<Self> {
992 Some(self + other)
993 }
994}
995
996// This trait is visible in the docs.
997trait NumCast<T>: NumBase + From<T> {}
998
999impl<T: NumBase + From<U>, U> NumCast<U> for T {}
1000
1001#[cfg(test)]
1002mod tests {
1003 use super::*;
1004
1005 #[test]
1006 fn small_edo_notation_with_different_genchain_origins() {
1007 assert_eq!(hexatonic_names(1, 1, 2), "G");
1008 assert_eq!(heptatonic_names(1, 1, 2), "G");
1009 assert_eq!(hexatonic_names(1, 1, 3), "D");
1010 assert_eq!(heptatonic_names(1, 1, 3), "D");
1011 assert_eq!(hexatonic_names(1, 1, 4), "A");
1012 assert_eq!(heptatonic_names(1, 1, 4), "A");
1013
1014 assert_eq!(hexatonic_names(2, 1, 2), "G, D/C");
1015 assert_eq!(heptatonic_names(2, 1, 2), "G, D/C");
1016 assert_eq!(hexatonic_names(2, 1, 3), "D, A/G");
1017 assert_eq!(heptatonic_names(2, 1, 3), "D, A/G");
1018 assert_eq!(hexatonic_names(2, 1, 4), "A, E/D");
1019 assert_eq!(heptatonic_names(2, 1, 4), "A, E/D");
1020
1021 assert_eq!(hexatonic_names(3, 2, 2), "G, A/C, D/F");
1022 assert_eq!(heptatonic_names(3, 2, 2), "G, A/C, D/F");
1023 assert_eq!(hexatonic_names(3, 2, 3), "D, E/G, A/C");
1024 assert_eq!(heptatonic_names(3, 2, 3), "D, E/G, A/C");
1025 assert_eq!(hexatonic_names(3, 2, 4), "A, D, E/G");
1026 assert_eq!(heptatonic_names(3, 2, 4), "A, B/D, E/G");
1027
1028 assert_eq!(hexatonic_names(4, 3, 2), "G, E/C, A/F, D");
1029 assert_eq!(heptatonic_names(4, 3, 2), "G, E/C, A/F, D");
1030 assert_eq!(hexatonic_names(4, 3, 3), "D, G, E/C, A/F");
1031 assert_eq!(heptatonic_names(4, 3, 3), "D, B/G, E/C, A/F");
1032 assert_eq!(hexatonic_names(4, 3, 4), "A, D, G, E/C");
1033 assert_eq!(heptatonic_names(4, 3, 4), "A, D, B/G, E/C");
1034
1035 assert_eq!(hexatonic_names(5, 3, 2), "G, A, C, D, E/F");
1036 assert_eq!(heptatonic_names(5, 3, 2), "G, A, B/C, D, E/F");
1037 assert_eq!(hexatonic_names(5, 3, 3), "D, E/F, G, A, C");
1038 assert_eq!(heptatonic_names(5, 3, 3), "D, E/F, G, A, B/C");
1039 assert_eq!(hexatonic_names(5, 3, 4), "A, C, D, E/F, G");
1040 assert_eq!(heptatonic_names(5, 3, 4), "A, B/C, D, E/F, G");
1041 }
1042
1043 #[test]
1044 fn heptatonic_12edo_notation() {
1045 // Degree 0 == C (common choice)
1046 assert_eq!(
1047 heptatonic_names(12, 7, 1),
1048 "C, C#/Db, D, D#/Eb, E, F, F#/Gb, G, G#/Ab, A, A#/Bb, B"
1049 );
1050 // Degree 0 == D
1051 assert_eq!(
1052 heptatonic_names(12, 7, 3),
1053 "D, D#/Eb, E, F, F#/Gb, G, G#/Ab, A, A#/Bb, B, C, C#/Db"
1054 );
1055 }
1056
1057 #[test]
1058 fn octatonic_13edo_notation() {
1059 // Degree 0 == A (common choice, see https://en.xen.wiki/w/13edo)
1060 assert_eq!(
1061 octatonic_names(13, 8, 4),
1062 "A, Ab/B#, B, C, Cb/D#, D, Db/E#, E, F, Fb/G#, G, H, Hb/A#"
1063 );
1064 // Degree 0 == D
1065 assert_eq!(
1066 octatonic_names(13, 8, 3),
1067 "D, Db/E#, E, F, Fb/G#, G, H, Hb/A#, A, Ab/B#, B, C, Cb/D#"
1068 );
1069 }
1070
1071 fn hexatonic_names(period: u16, generator: u16, genchain_origin: u16) -> String {
1072 note_name(
1073 period,
1074 generator,
1075 &['F', 'C', 'G', 'D', 'A', 'E'],
1076 genchain_origin,
1077 AccidentalsOrder::SharpFlat,
1078 )
1079 }
1080
1081 fn heptatonic_names(period: u16, generator: u16, genchain_origin: u16) -> String {
1082 note_name(
1083 period,
1084 generator,
1085 &['F', 'C', 'G', 'D', 'A', 'E', 'B'],
1086 genchain_origin,
1087 AccidentalsOrder::SharpFlat,
1088 )
1089 }
1090
1091 fn octatonic_names(period: u16, generator: u16, offset: u16) -> String {
1092 note_name(
1093 period,
1094 generator,
1095 &['E', 'B', 'G', 'D', 'A', 'F', 'C', 'H'],
1096 offset,
1097 AccidentalsOrder::FlatSharp,
1098 )
1099 }
1100
1101 fn note_name(
1102 period: u16,
1103 generator: u16,
1104 note_names: &'static [char],
1105 genchain_origin: u16,
1106 order: AccidentalsOrder,
1107 ) -> String {
1108 let pergen = PerGen::new(period, generator);
1109 let acc_format = AccidentalsFormat {
1110 num_symbols: u16::try_from(note_names.len()).unwrap(),
1111 genchain_origin,
1112 };
1113 let formatter = NoteFormatter {
1114 note_names: note_names.into(),
1115 sharp_sign: '#',
1116 flat_sign: 'b',
1117 order,
1118 };
1119
1120 (0..period)
1121 .map(|index| formatter.format(&pergen.get_accidentals(&acc_format, index)))
1122 .collect::<Vec<_>>()
1123 .join(", ")
1124 }
1125}