Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Semantics.Music/Mode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
/// </summary>
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<string, int[]> Shapes = new(StringComparer.OrdinalIgnoreCase)
{
// Diatonic modes.
["major"] = [0, 2, 4, 5, 7, 9, 11],

Check warning on line 19 in Semantics.Music/Mode.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'major' 4 times.

Check warning on line 19 in Semantics.Music/Mode.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Define a constant instead of using this literal 'major' 4 times.
["dorian"] = [0, 2, 3, 5, 7, 9, 10],
["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.
Expand Down Expand Up @@ -59,6 +62,8 @@
private static readonly Dictionary<string, string> Aliases = new(StringComparer.OrdinalIgnoreCase)
{
["ionian"] = "major",
["minor"] = AeolianName,
["natural_minor"] = AeolianName,
};

/// <summary>Gets the canonical lower-case name of the mode.</summary>
Expand Down Expand Up @@ -89,7 +94,7 @@
public static Mode Mixolydian => new() { Name = "mixolydian" };

/// <summary>The Aeolian (natural minor) mode.</summary>
public static Mode Aeolian => new() { Name = "aeolian" };
public static Mode Aeolian => new() { Name = AeolianName };

/// <summary>The Locrian mode.</summary>
public static Mode Locrian => new() { Name = "locrian" };
Expand Down Expand Up @@ -174,7 +179,7 @@
}

/// <summary>Tries to parse a mode by name, case-insensitively.</summary>
/// <remarks>An alias such as "ionian" parses to its canonical mode (<see cref="Major"/>).</remarks>
/// <remarks>An alias parses to its canonical mode: "ionian" to <see cref="Major"/>, and "minor" or "natural_minor" to <see cref="Aeolian"/>.</remarks>
/// <param name="name">The mode name.</param>
/// <param name="result">The matching mode, or null on failure.</param>
/// <returns><see langword="true"/> when the name is a known mode.</returns>
Expand Down
9 changes: 9 additions & 0 deletions Semantics.Test/Music/KeyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
11 changes: 11 additions & 0 deletions Semantics.Test/Music/ModeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Loading