Skip to main content

klib/core/
modifier.rs

1//! A module for working with chord modifiers.
2
3use std::sync::LazyLock;
4
5#[cfg(feature = "wasm")]
6use wasm_bindgen::prelude::*;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use crate::core::base::HasStaticName;
12
13// Traits.
14
15/// A trait for types that have a static name.
16pub trait HasIsDominant {
17    /// Returns whether the type (usually the modifier enum) is dominant.
18    fn is_dominant(&self) -> bool;
19}
20
21// Enum.
22
23/// An enum representing the degree of a dominant chord.
24#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Ord, PartialOrd)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[repr(u8)]
27pub enum Degree {
28    /// Seventh degree.
29    Seven,
30    /// Ninth degree.
31    Nine,
32    /// Eleventh degree.
33    Eleven,
34    /// Thirteenth degree.
35    Thirteen,
36}
37
38/// An enum representing the modifier of a chord.
39///
40/// Modifiers are "special extensions" that essentially have the capacity to _change_
41/// how the chord is interpreted by the system.  E.g., a dominant flat 9 chord is not
42/// _just_ a dominant chord with a flat 9 extension, but rather a chord that is
43/// represented by an entirely specific scale (half/whole/half diminished).
44#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Ord, PartialOrd)]
45#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
46#[repr(u8)]
47pub enum Modifier {
48    /// Minor modifier.
49    Minor,
50
51    /// Flat 5 modifier.
52    Flat5,
53    /// Sharp 5 modifier.
54    Augmented5,
55
56    /// Major 7 modifier.
57    Major7,
58    /// Dominant modifier with degree.
59    Dominant(Degree),
60
61    /// Flat 9 modifier.
62    Flat9,
63    /// Sharp 9 modifier.
64    Sharp9,
65
66    /// Sharp 11 modifier.
67    Sharp11,
68
69    /// Flat 13 modifier.
70    Flat13,
71
72    /// Diminished modifier.
73    Diminished,
74}
75
76/// An enum representing the extension of a chord.
77///
78/// Extensions are not really "special" in the sense that they do not change how the
79/// chord is interpreted by the system.  E.g., an `add2` just adds a 2 to the chord,
80/// and the chord is still interpreted as a major chord.
81#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug, Ord, PartialOrd)]
82#[repr(u8)]
83#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
84#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = KordExtension))]
85pub enum Extension {
86    /// Sus2 extension.
87    Sus2,
88    /// Sus4 extension.
89    Sus4,
90
91    /// Flat 11 extension.
92    Flat11,
93
94    /// Sharp 13 extension.
95    Sharp13,
96
97    /// Add2 extension.
98    Add2,
99    /// Add4 extension.
100    Add4,
101    /// Add6 extension.
102    Add6,
103
104    /// Add9 extension.
105    Add9,
106    /// Add11 extension.
107    Add11,
108    /// Add13 extension.
109    Add13,
110}
111
112// Impls.
113
114impl HasIsDominant for Modifier {
115    fn is_dominant(&self) -> bool {
116        matches!(self, Modifier::Dominant(_))
117    }
118}
119
120impl HasStaticName for Degree {
121    #[coverage(off)]
122    fn static_name(&self) -> &'static str {
123        match self {
124            Degree::Seven => "7",
125            Degree::Nine => "9",
126            Degree::Eleven => "11",
127            Degree::Thirteen => "13",
128        }
129    }
130}
131
132impl HasStaticName for Modifier {
133    #[coverage(off)]
134    fn static_name(&self) -> &'static str {
135        match self {
136            Modifier::Minor => "m",
137
138            Modifier::Flat5 => "♭5",
139            Modifier::Augmented5 => "+",
140
141            Modifier::Major7 => "maj7",
142            Modifier::Dominant(dominant) => dominant.static_name(),
143
144            Modifier::Flat9 => "♭9",
145            Modifier::Sharp9 => "♯9",
146
147            Modifier::Sharp11 => "♯11",
148
149            Modifier::Flat13 => "♭13",
150
151            Modifier::Diminished => "°",
152        }
153    }
154}
155
156impl HasStaticName for Extension {
157    #[coverage(off)]
158    fn static_name(&self) -> &'static str {
159        match self {
160            Extension::Sus2 => "sus2",
161            Extension::Sus4 => "sus4",
162
163            Extension::Flat11 => "♭11",
164
165            Extension::Sharp13 => "♯13",
166
167            Extension::Add2 => "add2",
168            Extension::Add4 => "add4",
169            Extension::Add6 => "add6",
170
171            Extension::Add9 => "add9",
172            Extension::Add11 => "add11",
173            Extension::Add13 => "add13",
174        }
175    }
176}
177
178// Helpers.
179
180/// Returns the sets of modifiers that have associated known chords.
181pub fn known_modifier_sets() -> &'static [Vec<Modifier>] {
182    KNOWN_MODIFIER_SETS.as_ref()
183}
184
185/// Returns the sets of modifiers that can be used as one off extensions.
186pub fn one_off_modifier_sets() -> &'static [Vec<Modifier>] {
187    ONE_OFF_MODIFIER_SETS.as_ref()
188}
189
190/// Returns the sets of extensions that are useful to test when guessing chords.
191pub fn likely_extension_sets() -> &'static [Vec<Extension>] {
192    LIKELY_EXTENSION_SETS.as_ref()
193}
194
195// Statics.
196
197static KNOWN_MODIFIER_SETS: LazyLock<[Vec<Modifier>; 43]> = LazyLock::new(|| {
198    [
199        vec![],
200        vec![Modifier::Minor],
201        vec![Modifier::Major7],
202        vec![Modifier::Dominant(Degree::Seven)],
203        vec![Modifier::Dominant(Degree::Nine)],
204        vec![Modifier::Dominant(Degree::Eleven)],
205        vec![Modifier::Dominant(Degree::Thirteen)],
206        vec![Modifier::Minor, Modifier::Major7],
207        vec![Modifier::Minor, Modifier::Dominant(Degree::Seven)],
208        vec![Modifier::Minor, Modifier::Dominant(Degree::Nine)],
209        vec![Modifier::Minor, Modifier::Dominant(Degree::Eleven)],
210        vec![Modifier::Minor, Modifier::Dominant(Degree::Thirteen)],
211        vec![Modifier::Sharp11, Modifier::Dominant(Degree::Seven)],
212        vec![Modifier::Sharp11, Modifier::Dominant(Degree::Nine)],
213        vec![Modifier::Sharp11, Modifier::Dominant(Degree::Eleven)],
214        vec![Modifier::Sharp11, Modifier::Dominant(Degree::Thirteen)],
215        vec![Modifier::Augmented5],
216        vec![Modifier::Augmented5, Modifier::Major7],
217        vec![Modifier::Augmented5, Modifier::Dominant(Degree::Seven)],
218        vec![Modifier::Augmented5, Modifier::Dominant(Degree::Nine)],
219        vec![Modifier::Augmented5, Modifier::Dominant(Degree::Eleven)],
220        vec![Modifier::Augmented5, Modifier::Dominant(Degree::Thirteen)],
221        vec![Modifier::Minor, Modifier::Flat5, Modifier::Dominant(Degree::Seven)],
222        vec![Modifier::Minor, Modifier::Flat5, Modifier::Dominant(Degree::Nine)],
223        vec![Modifier::Minor, Modifier::Flat5, Modifier::Dominant(Degree::Eleven)],
224        vec![Modifier::Minor, Modifier::Flat5, Modifier::Dominant(Degree::Thirteen)],
225        vec![Modifier::Diminished],
226        vec![Modifier::Flat9, Modifier::Dominant(Degree::Seven)],
227        vec![Modifier::Flat9, Modifier::Dominant(Degree::Nine)],
228        vec![Modifier::Flat9, Modifier::Dominant(Degree::Eleven)],
229        vec![Modifier::Flat9, Modifier::Dominant(Degree::Thirteen)],
230        vec![Modifier::Sharp9, Modifier::Dominant(Degree::Seven)],
231        vec![Modifier::Sharp9, Modifier::Dominant(Degree::Nine)],
232        vec![Modifier::Sharp9, Modifier::Dominant(Degree::Eleven)],
233        vec![Modifier::Sharp9, Modifier::Dominant(Degree::Thirteen)],
234        vec![Modifier::Minor, Modifier::Flat13, Modifier::Dominant(Degree::Seven)],
235        vec![Modifier::Minor, Modifier::Flat13, Modifier::Dominant(Degree::Nine)],
236        vec![Modifier::Minor, Modifier::Flat13, Modifier::Dominant(Degree::Eleven)],
237        vec![Modifier::Minor, Modifier::Flat13, Modifier::Dominant(Degree::Thirteen)],
238        vec![Modifier::Minor, Modifier::Flat9, Modifier::Flat13, Modifier::Dominant(Degree::Seven)],
239        vec![Modifier::Minor, Modifier::Flat9, Modifier::Flat13, Modifier::Dominant(Degree::Nine)],
240        vec![Modifier::Minor, Modifier::Flat9, Modifier::Flat13, Modifier::Dominant(Degree::Eleven)],
241        vec![Modifier::Minor, Modifier::Flat9, Modifier::Flat13, Modifier::Dominant(Degree::Thirteen)],
242    ]
243});
244
245static ONE_OFF_MODIFIER_SETS: LazyLock<[Vec<Modifier>; 7]> = LazyLock::new(|| {
246    [
247        vec![],
248        vec![Modifier::Augmented5],
249        vec![Modifier::Flat5],
250        vec![Modifier::Flat9],
251        vec![Modifier::Sharp9],
252        vec![Modifier::Sharp11],
253        vec![Modifier::Flat13],
254    ]
255});
256
257static LIKELY_EXTENSION_SETS: LazyLock<[Vec<Extension>; 11]> = LazyLock::new(|| {
258    [
259        vec![],
260        vec![Extension::Sus2],
261        vec![Extension::Sus4],
262        vec![Extension::Add2],
263        vec![Extension::Add4],
264        vec![Extension::Add6],
265        vec![Extension::Add9],
266        vec![Extension::Add11],
267        vec![Extension::Add13],
268        vec![Extension::Flat11],
269        vec![Extension::Sharp13],
270    ]
271});