Use better heuristics for chord pitch naming.
Previously we used a heuristic for choosing pitch names based on the major scale degree, minimizing radicals. So, for example, in the key of Gb, we render the 4 as Cb rather than B because Bb is the 3 of Gb. In other words, we want 1:Gb, 2:Ab, 3:Bb, 4:Cb instead of re-using B. This is standard practice in western music notation. When rendering non-diatonic notes we prefered choosing the version that minimized the radicals. Again in the key of Gb we would choose to render E as E (the #6) rather than considering it as Fb (the b7) and choose to render D as D (the #5) rather than E𝄫 (the b6). This was chosen to reduce the number of unusual radicals like 𝄫. However, in practice this leads to unusual charts because it is more common when writing chordal harmony to use the b6 rather than the #5. Similarly the b7 is far more common than the #6. This is, I think, due to the prevalence of the major scale and minor scales and the fact that the minor scale is built from flatting the 3, 6, and 7 of the major scale. So when thinking in a key-center agnostic manner (like numbers) we almost always think about these altered scale degrees as being flatted relative to the major scale, not sharped. Because of this, in the key of Gb, we would prefer to render a b6, b7, 1 chord walkup as E𝄫, Fb, Gb rather than D, E, Gb. This change redefines the heuristic used to name chord pitches to follow a heuristic that covers all pitches in the octave based on scale degree: 1, b2, 2, b3, 3, 4, b5, 5, b6, 6, b7, 7
This commit is contained in:
@ -87,14 +87,43 @@ func renderPitchInKey*(
|
|||||||
|
|
||||||
var scaleNames: array[(ord(high(ChordChartPitch)) + 1), string]
|
var scaleNames: array[(ord(high(ChordChartPitch)) + 1), string]
|
||||||
if useSharps.isNone:
|
if useSharps.isNone:
|
||||||
|
# If we aren't told to use sharps or flats, we render the diatonic pitches
|
||||||
|
# according to standard theory (C# in the key of D, Db in the key of Ab)
|
||||||
|
# but we render non-diatonic notes with flats (prefer the b6 and b7 over
|
||||||
|
# the #5 and #6).
|
||||||
|
#
|
||||||
|
# TODO: In the future, we should also remember the scale degree of the
|
||||||
|
# chord when parsing. So, for example, in the key of D we would parse Bb as
|
||||||
|
# the b6 and A# as the #5. The pitch would be the same, but the scale
|
||||||
|
# degree would differ. This would allow us to preserve intentional choices
|
||||||
|
# in the chart when transposing.
|
||||||
scaleNames = case key
|
scaleNames = case key
|
||||||
of A, B, D, E, G:
|
of C:
|
||||||
["G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G"]
|
|
||||||
of Af, Bf, C, Df, Ef, F:
|
|
||||||
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
||||||
|
of G:
|
||||||
|
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "F#", "G"]
|
||||||
|
of D:
|
||||||
|
["A♭", "A", "B♭", "B", "C", "C#", "D", "E♭", "E", "F", "F#", "G"]
|
||||||
|
of A:
|
||||||
|
["G#", "A", "B♭", "B", "C", "C#", "D", "E♭", "E", "F", "F#", "G"]
|
||||||
|
of E:
|
||||||
|
["G#", "A", "B♭", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G"]
|
||||||
|
of B:
|
||||||
|
["G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G"]
|
||||||
of Gf:
|
of Gf:
|
||||||
|
["A♭", "B𝄫", "B♭", "C♭", "D𝄫", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "A𝄫"]
|
||||||
|
of Df:
|
||||||
|
["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "A𝄫"]
|
||||||
|
of Af:
|
||||||
|
["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "E𝄫", "E♭", "F♭", "F", "G♭", "G"]
|
||||||
|
of Ef:
|
||||||
|
["A♭", "B𝄫", "B♭", "C♭", "C", "D♭", "D", "E♭", "F♭", "F", "G♭", "G"]
|
||||||
|
of Bf:
|
||||||
|
["A♭", "A", "B♭", "C♭", "C", "D♭", "D", "E♭", "F♭", "F", "G♭", "G"]
|
||||||
|
of F:
|
||||||
["A♭", "A", "B♭", "C♭", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
["A♭", "A", "B♭", "C♭", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
||||||
|
|
||||||
|
|
||||||
elif useSharps.isSome and useSharps.get:
|
elif useSharps.isSome and useSharps.get:
|
||||||
scaleNames = case key
|
scaleNames = case key
|
||||||
of A, B, C, D, E, G:
|
of A, B, C, D, E, G:
|
||||||
@ -112,7 +141,7 @@ func renderPitchInKey*(
|
|||||||
of F:
|
of F:
|
||||||
["G#", "G𝄪", "A#", "B", "B#", "C#", "C𝄪", "D#", "D𝄪", "E#", "F#", "F𝄪"]
|
["G#", "G𝄪", "A#", "B", "B#", "C#", "C𝄪", "D#", "D𝄪", "E#", "F#", "F𝄪"]
|
||||||
|
|
||||||
else: # useSharps.isSome and not useSharps.get
|
else: # !useSharps (useSharps.isSome and not useSharps.get)
|
||||||
scaleNames = case key
|
scaleNames = case key
|
||||||
of C, Af, Bf, Df, Ef, F:
|
of C, Af, Bf, Df, Ef, F:
|
||||||
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"]
|
||||||
@ -348,7 +377,7 @@ let NOTE_START_PAT = re"\{\{"
|
|||||||
let NOTE_END_PAT = re"\}\}"
|
let NOTE_END_PAT = re"\}\}"
|
||||||
let SPACE_PAT = re"\s"
|
let SPACE_PAT = re"\s"
|
||||||
let CHORD_IN_LYRICS_PAT = re"(\w+)(\[.+)"
|
let CHORD_IN_LYRICS_PAT = re"(\w+)(\[.+)"
|
||||||
let CHORD_AND_LYRICS_PAT = re"^\[([^\]]+)\]([^\s\[]+)(.*)$"
|
let CHORD_AND_LYRICS_PAT = re"^\[([^\]]*)\]([^\s\[]+)(.*)$"
|
||||||
let BRACED_CHORD_PAT = re"^\[([^\]]+)\]$"
|
let BRACED_CHORD_PAT = re"^\[([^\]]+)\]$"
|
||||||
let NAKED_CHORDS_ONLY_PAT = re("^\\s*(" & CHORD_REGEX & "\\s*\\|*\\s*)+$")
|
let NAKED_CHORDS_ONLY_PAT = re("^\\s*(" & CHORD_REGEX & "\\s*\\|*\\s*)+$")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user