redact_composer_musical/scale/
degree.rs

1use std::ops::{Add, Sub};
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6#[cfg(feature = "redact-composer")]
7use redact_composer_core::derive::Element;
8
9/// Scale degree, based on a 7-note scale.
10#[derive(Debug, Copy, Clone, Eq, PartialEq)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12#[cfg_attr(feature = "redact-composer", derive(Element))]
13#[allow(missing_docs)]
14pub enum Degree {
15    I,
16    II,
17    III,
18    IV,
19    V,
20    VI,
21    VII,
22}
23
24impl Degree {
25    const VALUES: [Degree; 7] = [
26        Degree::I,
27        Degree::II,
28        Degree::III,
29        Degree::IV,
30        Degree::V,
31        Degree::VI,
32        Degree::VII,
33    ];
34
35    /// All [`Degree`] variants.
36    /// ```
37    /// # use redact_composer_musical::{Degree, Degree::*};
38    /// assert_eq!(Degree::values(), [I, II, III, IV, V, VI, VII]);
39    /// ```
40    pub fn values() -> [Degree; 7] {
41        Self::VALUES
42    }
43
44    /// Returns the next [`Degree`] following this one.
45    /// ```
46    /// # use redact_composer_musical::Degree;
47    /// assert_eq!(Degree::I.next(), Degree::II);
48    /// assert_eq!(Degree::VII.next(), Degree::I)
49    /// ```
50    pub fn next(&self) -> Degree {
51        match self {
52            Degree::I => Degree::II,
53            Degree::II => Degree::III,
54            Degree::III => Degree::IV,
55            Degree::IV => Degree::V,
56            Degree::V => Degree::VI,
57            Degree::VI => Degree::VII,
58            Degree::VII => Degree::I,
59        }
60    }
61
62    /// Returns the [`Degree`] previous to this one.
63    /// ```
64    /// # use redact_composer_musical::Degree;
65    /// assert_eq!(Degree::I.prev(), Degree::VII);
66    /// assert_eq!(Degree::VII.prev(), Degree::VI);
67    /// ```
68    pub fn prev(&self) -> Degree {
69        match self {
70            Degree::I => Degree::VII,
71            Degree::II => Degree::I,
72            Degree::III => Degree::II,
73            Degree::IV => Degree::III,
74            Degree::V => Degree::IV,
75            Degree::VI => Degree::V,
76            Degree::VII => Degree::VI,
77        }
78    }
79
80    /// Returns the minimum absolute difference from this degree to another.
81    /// ```
82    /// use redact_composer_musical::Degree;
83    /// assert_eq!(Degree::I.diff(&Degree::I), 0);
84    /// assert_eq!(Degree::I.diff(&Degree::II), 1);
85    /// // Even though VI - II = 4, there is a shorter path traversing the cycle boundary
86    /// assert_eq!(Degree::II.diff(&Degree::VI), 3);
87    /// ```
88    pub fn diff(&self, other: &Degree) -> u8 {
89        let (lower, higher) = if *other as u8 >= *self as u8 {
90            (*self as u8, *other as u8)
91        } else {
92            (*other as u8, *self as u8)
93        };
94
95        (higher - lower).min(lower + 7 - higher)
96    }
97}
98
99impl From<Degree> for u8 {
100    /// Returns the 0-based value of this degree.
101    /// ```
102    /// # use redact_composer_musical::Degree;
103    /// assert_eq!(0_u8, Degree::I.into());
104    /// ```
105    fn from(value: Degree) -> Self {
106        value as u8
107    }
108}
109
110impl From<u8> for Degree {
111    /// Produces a [`Degree`] using a 0-based [`u8`] index.
112    /// ```
113    /// # use redact_composer_musical::Degree;
114    /// assert_eq!(Degree::I, Degree::from(0_u8));
115    /// assert_eq!(Degree::I, Degree::from(7_u8));
116    /// ```
117    fn from(value: u8) -> Self {
118        match value % 7 {
119            0 => Degree::I,
120            1 => Degree::II,
121            2 => Degree::III,
122            3 => Degree::IV,
123            4 => Degree::V,
124            5 => Degree::VI,
125            6 => Degree::VII,
126            _ => unreachable!(),
127        }
128    }
129}
130
131impl Add<u8> for Degree {
132    type Output = Degree;
133
134    /// Adds an offset to a scale degree, wrapping after [`Degree::VII`].
135    /// ```
136    /// # use redact_composer_musical::Degree;
137    /// assert_eq!(Degree::I + 1, Degree::II);
138    /// assert_eq!(Degree::I + 7, Degree::I);
139    /// ```
140    fn add(self, rhs: u8) -> Self::Output {
141        Degree::from(self as u8 + rhs)
142    }
143}
144
145impl Sub<u8> for Degree {
146    type Output = Degree;
147
148    /// Subtracts an offset from a scale degree, wrapping at [`Degree::I`].
149    /// ```
150    /// # use redact_composer_musical::Degree;
151    /// assert_eq!(Degree::II - 1, Degree::I);
152    /// assert_eq!(Degree::I - 1, Degree::VII);
153    /// ```
154    fn sub(self, rhs: u8) -> Self::Output {
155        Degree::from(7_u8 + self as u8 - rhs)
156    }
157}