1use std::fmt::{Display, Error, Formatter};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8use crate::core::{
9 base::{HasDescription, HasName, HasPreciseName, Parsable, Res},
10 chord::{Chord, HasChord, HasRoot},
11 interval::{HasIntervals, Interval},
12 mode::Mode,
13 note::Note,
14 scale::Scale,
15};
16
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45#[derive(PartialEq, Eq, Clone, Debug)]
46pub enum Notation {
47 Chord(Chord),
49 Scale(Scale),
51 Mode(Mode),
53}
54
55impl Notation {
58 pub fn is_chord(&self) -> bool {
60 matches!(self, Notation::Chord(_))
61 }
62
63 pub fn is_scale(&self) -> bool {
65 matches!(self, Notation::Scale(_))
66 }
67
68 pub fn is_mode(&self) -> bool {
70 matches!(self, Notation::Mode(_))
71 }
72
73 pub fn as_chord(&self) -> Option<&Chord> {
75 match self {
76 Notation::Chord(c) => Some(c),
77 _ => None,
78 }
79 }
80
81 pub fn as_scale(&self) -> Option<&Scale> {
83 match self {
84 Notation::Scale(s) => Some(s),
85 _ => None,
86 }
87 }
88
89 pub fn as_mode(&self) -> Option<&Mode> {
91 match self {
92 Notation::Mode(m) => Some(m),
93 _ => None,
94 }
95 }
96
97 pub fn into_chord(self) -> Option<Chord> {
99 match self {
100 Notation::Chord(c) => Some(c),
101 _ => None,
102 }
103 }
104
105 pub fn into_scale(self) -> Option<Scale> {
107 match self {
108 Notation::Scale(s) => Some(s),
109 _ => None,
110 }
111 }
112
113 pub fn into_mode(self) -> Option<Mode> {
115 match self {
116 Notation::Mode(m) => Some(m),
117 _ => None,
118 }
119 }
120
121 pub fn notes(&self) -> Vec<Note> {
126 match self {
127 Notation::Chord(c) => c.chord(),
128 Notation::Scale(s) => s.notes(),
129 Notation::Mode(m) => m.notes(),
130 }
131 }
132
133 pub fn kind(&self) -> &'static str {
135 match self {
136 Notation::Chord(_) => "chord",
137 Notation::Scale(_) => "scale",
138 Notation::Mode(_) => "mode",
139 }
140 }
141
142 pub fn parse_with_type(symbol: &str, notation_type: Option<&str>) -> Res<Self> {
161 match notation_type {
162 Some("chord") => Ok(Notation::Chord(Chord::parse(symbol)?)),
163 Some("scale") => Ok(Notation::Scale(Scale::parse(symbol)?)),
164 Some("mode") => Ok(Notation::Mode(Mode::parse(symbol)?)),
165 Some(other) => Err(anyhow::anyhow!("Unknown notation type '{}'. Use 'chord', 'scale', or 'mode'.", other)),
166 None => Notation::parse(symbol),
167 }
168 }
169
170 pub fn format_verbose(&self) -> String {
175 match self {
176 Notation::Chord(chord) => chord.format_with_scale_candidates(),
177 Notation::Scale(scale) => format!("{}", scale),
178 Notation::Mode(mode) => format!("{}", mode),
179 }
180 }
181}
182
183impl HasRoot for Notation {
184 fn root(&self) -> Note {
185 match self {
186 Notation::Chord(c) => c.root(),
187 Notation::Scale(s) => s.root(),
188 Notation::Mode(m) => m.root(),
189 }
190 }
191}
192
193impl HasIntervals for Notation {
194 fn intervals(&self) -> &'static [Interval] {
195 match self {
196 Notation::Chord(c) => c.intervals(),
197 Notation::Scale(s) => s.intervals(),
198 Notation::Mode(m) => m.intervals(),
199 }
200 }
201}
202
203impl HasName for Notation {
204 fn name(&self) -> String {
205 match self {
206 Notation::Chord(c) => c.name(),
207 Notation::Scale(s) => s.name(),
208 Notation::Mode(m) => m.name(),
209 }
210 }
211}
212
213impl HasPreciseName for Notation {
214 fn precise_name(&self) -> String {
215 match self {
216 Notation::Chord(c) => c.precise_name(),
217 Notation::Scale(s) => s.precise_name(),
218 Notation::Mode(m) => m.precise_name(),
219 }
220 }
221}
222
223impl HasDescription for Notation {
224 fn description(&self) -> &'static str {
225 match self {
226 Notation::Chord(c) => c.description(),
227 Notation::Scale(s) => s.description(),
228 Notation::Mode(m) => m.description(),
229 }
230 }
231}
232
233impl Display for Notation {
234 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
235 match self {
236 Notation::Chord(c) => write!(f, "{}", c),
237 Notation::Scale(s) => write!(f, "{}", s),
238 Notation::Mode(m) => write!(f, "{}", m),
239 }
240 }
241}
242
243impl Parsable for Notation {
244 fn parse(input: &str) -> Res<Self>
249 where
250 Self: Sized,
251 {
252 if let Ok(scale) = Scale::parse(input) {
254 return Ok(Notation::Scale(scale));
255 }
256
257 if let Ok(mode) = Mode::parse(input) {
259 return Ok(Notation::Mode(mode));
260 }
261
262 let chord = Chord::parse(input)?;
264 Ok(Notation::Chord(chord))
265 }
266}
267
268impl From<Chord> for Notation {
269 fn from(chord: Chord) -> Self {
270 Notation::Chord(chord)
271 }
272}
273
274impl From<Scale> for Notation {
275 fn from(scale: Scale) -> Self {
276 Notation::Scale(scale)
277 }
278}
279
280impl From<Mode> for Notation {
281 fn from(mode: Mode) -> Self {
282 Notation::Mode(mode)
283 }
284}
285
286#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::core::mode::HasModeKind;
292 use crate::core::mode_kind::ModeKind;
293 use crate::core::note::*;
294 use crate::core::scale::HasScaleKind;
295 use crate::core::scale_kind::ScaleKind;
296 use pretty_assertions::assert_eq;
297
298 #[test]
299 fn test_parse_as_scale() {
300 let notation = Notation::parse("C major pentatonic").unwrap();
301 assert!(notation.is_scale());
302 assert!(!notation.is_chord());
303 assert!(!notation.is_mode());
304 assert_eq!(notation.kind(), "scale");
305
306 let scale = notation.as_scale().unwrap();
307 assert_eq!(scale.root(), C);
308 assert_eq!(scale.kind(), ScaleKind::MajorPentatonic);
309 }
310
311 #[test]
312 fn test_parse_as_mode() {
313 let notation = Notation::parse("D dorian").unwrap();
314 assert!(notation.is_mode());
315 assert!(!notation.is_chord());
316 assert!(!notation.is_scale());
317 assert_eq!(notation.kind(), "mode");
318
319 let mode = notation.as_mode().unwrap();
320 assert_eq!(mode.root(), D);
321 assert_eq!(mode.kind(), ModeKind::Dorian);
322 }
323
324 #[test]
325 fn test_parse_as_chord() {
326 let notation = Notation::parse("Cmaj7").unwrap();
327 assert!(notation.is_chord());
328 assert!(!notation.is_scale());
329 assert!(!notation.is_mode());
330 assert_eq!(notation.kind(), "chord");
331
332 let chord = notation.as_chord().unwrap();
333 assert_eq!(chord.root(), C);
334 }
335
336 #[test]
337 fn test_parse_complex_chord() {
338 let notation = Notation::parse("Em7b9b13").unwrap();
339 assert!(notation.is_chord());
340 assert_eq!(notation.root(), E);
341 }
342
343 #[test]
344 fn test_parse_harmonic_minor() {
345 let notation = Notation::parse("A harmonic minor").unwrap();
346 assert!(notation.is_scale());
347
348 let scale = notation.as_scale().unwrap();
349 assert_eq!(scale.kind(), ScaleKind::HarmonicMinor);
350 }
351
352 #[test]
353 fn test_parse_lydian_mode() {
354 let notation = Notation::parse("F lydian").unwrap();
355 assert!(notation.is_mode());
356
357 let mode = notation.as_mode().unwrap();
358 assert_eq!(mode.kind(), ModeKind::Lydian);
359 }
360
361 #[test]
362 fn test_into_inner() {
363 let notation = Notation::parse("Cm7").unwrap();
364 let chord = notation.into_chord().unwrap();
365 assert_eq!(chord.root(), C);
366
367 let notation = Notation::parse("G mixolydian").unwrap();
368 let mode = notation.into_mode().unwrap();
369 assert_eq!(mode.root(), G);
370
371 let notation = Notation::parse("E blues").unwrap();
372 let scale = notation.into_scale().unwrap();
373 assert_eq!(scale.root(), E);
374 }
375
376 #[test]
377 fn test_notes() {
378 let notation = Notation::parse("C major").unwrap();
379 assert!(notation.is_scale());
380 let notes = notation.notes();
381 assert_eq!(notes.len(), 7);
382 assert_eq!(notes[0], C);
383
384 let notation = Notation::parse("A aeolian").unwrap();
385 assert!(notation.is_mode());
386 let notes = notation.notes();
387 assert_eq!(notes.len(), 7);
388 assert_eq!(notes[0], A);
389 }
390
391 #[test]
392 fn test_intervals() {
393 let notation = Notation::parse("C major").unwrap();
395 assert!(!notation.intervals().is_empty());
396
397 let notation = Notation::parse("D dorian").unwrap();
399 assert!(!notation.intervals().is_empty());
400
401 let notation = Notation::parse("Cmaj7").unwrap();
403 assert!(!notation.intervals().is_empty());
404 assert_eq!(notation.intervals().len(), 4); }
406
407 #[test]
408 fn test_has_name() {
409 let notation = Notation::parse("C major pentatonic").unwrap();
410 assert_eq!(notation.name(), "C major pentatonic");
411
412 let notation = Notation::parse("D dorian").unwrap();
413 assert_eq!(notation.name(), "D dorian");
414
415 let notation = Notation::parse("Cmaj7").unwrap();
416 assert!(notation.name().contains("C"));
417 }
418
419 #[test]
420 fn test_from_impls() {
421 let chord = Chord::parse("Cm7").unwrap();
422 let notation: Notation = chord.into();
423 assert!(notation.is_chord());
424
425 let scale = Scale::parse("C major").unwrap();
426 let notation: Notation = scale.into();
427 assert!(notation.is_scale());
428
429 let mode = Mode::parse("D dorian").unwrap();
430 let notation: Notation = mode.into();
431 assert!(notation.is_mode());
432 }
433
434 #[test]
435 fn test_display() {
436 let notation = Notation::parse("C major").unwrap();
437 let display = format!("{}", notation);
438 assert!(display.contains("C major"));
439
440 let notation = Notation::parse("D dorian").unwrap();
441 let display = format!("{}", notation);
442 assert!(display.contains("D dorian"));
443 }
444
445 #[test]
446 fn test_priority_scale_over_chord() {
447 let notation = Notation::parse("C major").unwrap();
449 assert!(notation.is_scale(), "Expected 'C major' to parse as scale, got {:?}", notation.kind());
450 }
451
452 #[test]
453 fn test_priority_mode_over_chord() {
454 let notation = Notation::parse("C ionian").unwrap();
456 assert!(notation.is_mode(), "Expected 'C ionian' to parse as mode, got {:?}", notation.kind());
457 }
458}