Skip to content

Commit cfdd67a

Browse files
Merge pull request #406 from ktsu-dev/fix/304-leading-tone-sharp-seven
[patch] Spell the raised seventh above a flat seventh as #7, not a lowered tonic
2 parents 7587c41 + 23af506 commit cfdd67a

3 files changed

Lines changed: 37 additions & 2 deletions

File tree

‎Semantics.Music/Scale.cs‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ public ScaleDegree DegreeOf(PitchClass pitchClass)
7272
}
7373

7474
// Chromatic: spell against the lower neighbour (raised/sharp) or the upper
75-
// neighbour (lowered/flat), whichever is closer; tie prefers the flat.
75+
// neighbour (lowered/flat), whichever is closer; tie prefers the flat, except
76+
// above the last degree, where a flat would wrap to a lowered tonic and the
77+
// raised last degree (the minor-key leading tone, #7) is the conventional reading.
7678
int lowerIndex = -1;
7779
for (int i = 0; i < offsets.Count; i++)
7880
{
@@ -98,7 +100,12 @@ public ScaleDegree DegreeOf(PitchClass pitchClass)
98100
int flatDegree = upperIndex >= 0 ? upperIndex + 1 : 1;
99101
int flatAlteration = semitone - (upperIndex >= 0 ? offsets[upperIndex] : offsets[0] + 12);
100102

101-
return Math.Abs(flatAlteration) <= Math.Abs(sharpAlteration)
103+
bool flatWraps = upperIndex < 0;
104+
bool preferFlat = flatWraps
105+
? Math.Abs(flatAlteration) < Math.Abs(sharpAlteration)
106+
: Math.Abs(flatAlteration) <= Math.Abs(sharpAlteration);
107+
108+
return preferFlat
102109
? new ScaleDegree(flatDegree, flatAlteration)
103110
: new ScaleDegree(sharpDegree, sharpAlteration);
104111
}

‎Semantics.Test/Music/KeyTests.cs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ public void RomanNumeral_ChromaticGetsAccidental()
3636
Assert.AreEqual("bII", CMajor.RomanNumeralOf(Chord.Parse("Db")));
3737
}
3838

39+
[TestMethod]
40+
public void RomanNumeral_MinorLeadingToneChordIsSharpSeven()
41+
{
42+
Key aMinor = Key.Parse("A aeolian");
43+
Assert.AreEqual("#vii°", aMinor.RomanNumeralOf(Chord.Parse("G#dim")));
44+
Assert.AreEqual("#vii°", Key.Parse("G mixolydian").RomanNumeralOf(Chord.Parse("F#dim")));
45+
}
46+
47+
[TestMethod]
48+
public void RomanNumeral_MinorLeadingToneChordRoundTrips()
49+
{
50+
Key aMinor = Key.Parse("A aeolian");
51+
Chord leadingTone = Chord.Parse("G#dim");
52+
Chord roundTripped = aMinor.ChordFromRomanNumeral(aMinor.RomanNumeralOf(leadingTone));
53+
Assert.AreEqual(leadingTone, roundTripped);
54+
}
55+
3956
[TestMethod]
4057
public void RomanNumeral_DistinctDegreesNeverShareALabel()
4158
{

‎Semantics.Test/Music/ScaleTests.cs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ public void DegreeOf_SharpFourIsCloserToFourthRaised()
5050
Assert.AreEqual(-1, tritone.Alteration);
5151
}
5252

53+
[TestMethod]
54+
public void DegreeOf_RaisedSeventhAboveAFlatSeventhIsSharpSeven()
55+
{
56+
// G# in A aeolian sits between the flat seventh (G) and the octave; it is the
57+
// leading tone (#7), not a lowered tonic wrapped round the octave.
58+
Scale aMinor = Scale.Create(PitchClass.Parse("A"), Mode.Aeolian);
59+
ScaleDegree leadingTone = aMinor.DegreeOf(PitchClass.Parse("G#"));
60+
Assert.AreEqual(7, leadingTone.Degree);
61+
Assert.AreEqual(1, leadingTone.Alteration);
62+
}
63+
5364
[TestMethod]
5465
public void ToStringIsRootSpaceMode()
5566
{

0 commit comments

Comments
 (0)