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}