euphony_core/pitch/mode/
mod.rs

1use crate::pitch::{
2    interval::Interval,
3    mode::{intervals::RoundingStrategy, position::ModePosition, system::ModeSystem},
4};
5use core::fmt;
6
7#[macro_use]
8pub mod intervals;
9#[macro_use]
10pub mod system;
11pub mod position;
12
13#[macro_export]
14macro_rules! named_mode {
15    ($name:ident($position:expr, $system:ident)) => {
16        pub const $name: $crate::pitch::mode::Mode =
17            $crate::pitch::mode::Mode::new($position, $system);
18    };
19}
20
21pub mod chromatic;
22pub use chromatic as dodecatonic;
23pub mod ditonic;
24pub mod heptatonic;
25pub mod pentatonic;
26
27pub mod western {
28    pub use super::{chromatic::*, heptatonic::*};
29}
30
31#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
32pub struct Mode {
33    pub ascending: ModePosition,
34    pub descending: ModePosition,
35}
36
37impl Default for Mode {
38    fn default() -> Self {
39        heptatonic::MAJOR
40    }
41}
42
43impl fmt::Debug for Mode {
44    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45        if self.ascending == self.descending {
46            return write!(f, "Mode({})", self.ascending);
47        }
48        write!(f, "Mode(TODO)")
49    }
50}
51
52impl Mode {
53    pub const fn new(position: usize, system: ModeSystem) -> Self {
54        Self {
55            ascending: ModePosition(position, system),
56            descending: ModePosition(position, system),
57        }
58    }
59
60    pub const fn len(&self) -> usize {
61        self.ascending.intervals().len()
62    }
63
64    pub const fn is_empty(&self) -> bool {
65        self.ascending.intervals().is_empty()
66    }
67
68    pub fn collapse(&self, interval: Interval, rounding_strategy: RoundingStrategy) -> Interval {
69        self.checked_collapse(interval, rounding_strategy)
70            .expect("Interval could not be collapsed")
71    }
72
73    pub fn checked_collapse(
74        &self,
75        interval: Interval,
76        rounding_strategy: RoundingStrategy,
77    ) -> Option<Interval> {
78        if interval < 0 {
79            self.descending
80                .checked_collapse(interval, rounding_strategy)
81        } else {
82            self.ascending.checked_collapse(interval, rounding_strategy)
83        }
84    }
85
86    pub fn expand(&self, interval: Interval, rounding_strategy: RoundingStrategy) -> Interval {
87        self.checked_expand(interval, rounding_strategy)
88            .expect("Interval could not be expanded")
89    }
90
91    pub fn checked_expand(
92        &self,
93        interval: Interval,
94        rounding_strategy: RoundingStrategy,
95    ) -> Option<Interval> {
96        if interval < 0 {
97            self.descending.checked_expand(interval, rounding_strategy)
98        } else {
99            self.ascending.checked_expand(interval, rounding_strategy)
100        }
101    }
102}
103
104impl core::ops::Shr<usize> for Mode {
105    type Output = Mode;
106
107    fn shr(self, rhs: usize) -> Self::Output {
108        Self {
109            ascending: self.ascending.shr(rhs),
110            descending: self.descending.shr(rhs),
111        }
112    }
113}
114
115impl core::ops::ShrAssign<usize> for Mode {
116    fn shr_assign(&mut self, rhs: usize) {
117        *self = core::ops::Shr::shr(*self, rhs)
118    }
119}
120
121impl core::ops::Shl<usize> for Mode {
122    type Output = Mode;
123
124    fn shl(self, rhs: usize) -> Self::Output {
125        Self {
126            ascending: self.ascending.shl(rhs),
127            descending: self.descending.shl(rhs),
128        }
129    }
130}
131
132impl core::ops::ShlAssign<usize> for Mode {
133    fn shl_assign(&mut self, rhs: usize) {
134        self.ascending <<= rhs;
135        self.descending <<= rhs;
136    }
137}
138
139impl core::ops::Mul<Interval> for Mode {
140    type Output = Interval;
141
142    fn mul(self, interval: Interval) -> Self::Output {
143        self.expand(interval, Default::default())
144    }
145}
146
147impl core::ops::Div<Mode> for Interval {
148    type Output = Interval;
149
150    fn div(self, mode: Mode) -> Self::Output {
151        mode.collapse(self, Default::default())
152    }
153}
154
155#[test]
156fn shift_test() {
157    use crate::pitch::mode::heptatonic::*;
158    assert_eq!(MAJOR >> 1, DORIAN);
159    assert_eq!(MAJOR << 2, MINOR);
160    assert_eq!(MAJOR << 7, MAJOR);
161}
162
163#[test]
164fn expansion_test() {
165    use crate::pitch::mode::western::*;
166
167    assert_eq!(A + MINOR * I, A);
168    assert_eq!(A + MINOR * II, B);
169    assert_eq!(A + MINOR * III, C);
170    assert_eq!(A + MINOR * IV, D);
171    assert_eq!(A + MINOR * V, E);
172    assert_eq!(A + MINOR * VI, F);
173    assert_eq!(A + MINOR * VII, G);
174
175    assert_eq!(C + MAJOR * I, C);
176    assert_eq!(C + MAJOR * II, D);
177    assert_eq!(C + MAJOR * III, E);
178    assert_eq!(C + MAJOR * IV, F);
179    assert_eq!(C + MAJOR * V, G);
180    assert_eq!(C + MAJOR * VI, A + 1);
181    assert_eq!(C + MAJOR * VII, B + 1);
182
183    assert_eq!(C + MINOR * I, C);
184    assert_eq!(C + MINOR * II, D);
185    assert_eq!(C + MINOR * III, E.flat());
186    assert_eq!(C + MINOR * IV, F);
187    assert_eq!(C + MINOR * V, G);
188    assert_eq!(C + MINOR * VI, A.flat() + 1);
189    assert_eq!(C + MINOR * VII, B.flat() + 1);
190
191    assert_eq!(A + MINOR * -I, A);
192    assert_eq!(A + MINOR * -II, G - 1);
193    assert_eq!(A + MINOR * -III, F - 1);
194    assert_eq!(A + MINOR * -IV, E - 1);
195    assert_eq!(A + MINOR * -V, D - 1);
196    assert_eq!(A + MINOR * -VI, C - 1);
197    assert_eq!(A + MINOR * -VII, B - 1);
198
199    assert_eq!(C + MAJOR * -I, C);
200    assert_eq!(C + MAJOR * -II, B);
201    assert_eq!(C + MAJOR * -III, A);
202    assert_eq!(C + MAJOR * -IV, G - 1);
203    assert_eq!(C + MAJOR * -V, F - 1);
204    assert_eq!(C + MAJOR * -VI, E - 1);
205    assert_eq!(C + MAJOR * -VII, D - 1);
206}
207
208#[test]
209fn collapse_test() {
210    use crate::pitch::mode::western::*;
211
212    assert_eq!(A / MINOR, I);
213    assert_eq!(A.sharp() / MINOR, I);
214    assert_eq!(B / MINOR, II);
215    assert_eq!(C / MINOR, III);
216    assert_eq!(C.sharp() / MINOR, III);
217    assert_eq!(D / MINOR, IV);
218    assert_eq!(D.sharp() / MINOR, IV);
219    assert_eq!(E / MINOR, V);
220    assert_eq!(F / MINOR, VI);
221    assert_eq!(F.sharp() / MINOR, VI);
222    assert_eq!(G / MINOR, VII);
223    assert_eq!(G.sharp() / MINOR, VII);
224    assert_eq!((A + 1) / MINOR, Interval(7, 7));
225    assert_eq!(A.flat() / MINOR, -Interval(1, 7));
226    assert_eq!(A.flat().flat() / MINOR, -Interval(1, 7));
227    assert_eq!(A.flat().flat().flat() / MINOR, -Interval(2, 7));
228}