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` &div; `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` &div; `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 &div; 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}