diff --git a/Semantics.Music/Mode.cs b/Semantics.Music/Mode.cs index 0ab05e6..2cae038 100644 --- a/Semantics.Music/Mode.cs +++ b/Semantics.Music/Mode.cs @@ -10,6 +10,9 @@ namespace ktsu.Semantics.Music; /// public sealed record Mode { + // Named once because the shape table, its aliases and the static instance all refer to it. + private const string AeolianName = "aeolian"; + private static readonly Dictionary Shapes = new(StringComparer.OrdinalIgnoreCase) { // Diatonic modes. @@ -18,7 +21,7 @@ public sealed record Mode ["phrygian"] = [0, 1, 3, 5, 7, 8, 10], ["lydian"] = [0, 2, 4, 6, 7, 9, 11], ["mixolydian"] = [0, 2, 4, 5, 7, 9, 10], - ["aeolian"] = [0, 2, 3, 5, 7, 8, 10], + [AeolianName] = [0, 2, 3, 5, 7, 8, 10], ["locrian"] = [0, 1, 3, 5, 6, 8, 10], // Minor scales. @@ -59,6 +62,8 @@ public sealed record Mode private static readonly Dictionary Aliases = new(StringComparer.OrdinalIgnoreCase) { ["ionian"] = "major", + ["minor"] = AeolianName, + ["natural_minor"] = AeolianName, }; /// Gets the canonical lower-case name of the mode. @@ -89,7 +94,7 @@ public sealed record Mode public static Mode Mixolydian => new() { Name = "mixolydian" }; /// The Aeolian (natural minor) mode. - public static Mode Aeolian => new() { Name = "aeolian" }; + public static Mode Aeolian => new() { Name = AeolianName }; /// The Locrian mode. public static Mode Locrian => new() { Name = "locrian" }; @@ -174,7 +179,7 @@ public static Mode Parse(string name) } /// Tries to parse a mode by name, case-insensitively. - /// An alias such as "ionian" parses to its canonical mode (). + /// An alias parses to its canonical mode: "ionian" to , and "minor" or "natural_minor" to . /// The mode name. /// The matching mode, or null on failure. /// when the name is a known mode. diff --git a/Semantics.Test/Music/KeyTests.cs b/Semantics.Test/Music/KeyTests.cs index 421d505..45a1903 100644 --- a/Semantics.Test/Music/KeyTests.cs +++ b/Semantics.Test/Music/KeyTests.cs @@ -103,6 +103,15 @@ public void Parse_IonianSpelling_IsTheSameKeyAsMajor() Assert.AreEqual(CMajor, Key.Parse("C ionian")); } + [TestMethod] + public void Parse_MinorSpelling_IsTheSameKeyAsAeolian() + { + Key aMinor = Key.Create(PitchClass.Create(NoteLetter.A, Accidental.Natural), Mode.Aeolian); + Assert.AreEqual(aMinor, Key.Parse("A minor")); + Assert.IsTrue(Key.TryParse("A minor", out Key? parsed)); + Assert.AreEqual("A aeolian", parsed!.ToString()); + } + [TestMethod] public void TryParseFailsOnUnknownMode() { diff --git a/Semantics.Test/Music/ModeTests.cs b/Semantics.Test/Music/ModeTests.cs index 4b0475c..f6a27e6 100644 --- a/Semantics.Test/Music/ModeTests.cs +++ b/Semantics.Test/Music/ModeTests.cs @@ -46,6 +46,17 @@ public void TryParse_IonianAlias_EqualsMajor() Assert.AreEqual(Mode.Major, result); } + [TestMethod] + [DataRow("minor")] + [DataRow("Minor")] + [DataRow("natural_minor")] + public void Parse_MinorAlias_EqualsAeolian(string name) + { + Mode parsed = Mode.Parse(name); + Assert.AreEqual(Mode.Aeolian, parsed); + Assert.AreEqual("aeolian", parsed.ToString()); + } + [TestMethod] public void Parse_RejectsUnknown() {