Closed battermann closed 5 years ago
Thanks for putting these together. These are great examples!
As you've demonstrated, there are many scales (like Db minor) that are theoretically possible but rarely or never used in practice. I don't think it should be a goal of ours to prevent users from creating them. If our library supports an arbitrary number of accidentals, I think it must also support an arbitrary number of scales.
Scales of this kind will, I believe, often come from keys, so one way we can encourage people to use the "usual" scales is by exposing a functions like Key.all
, Key.allMajor
and Key.allMinor
that returned lists of the relevant keys in the circle of fifths. This would encourage correct enharmonic keys being used in client applications (e.g. if it were used to populate a select menu).
If this doesn't seem like enough, we could modify the key
constructor function we exposed to accept a parameter like useConventionalRoot
that made sure that any key's tonic was always converted to the correct enharmonic equivalent in the circle of fifths before using it to generate the key's scale degrees. But I think this would be taking functionality away from the user that could be useful in a music theory setting. Maybe someone wants to do something in Cb minor— why should we stop them?
It sounds like you're mainly concerned with correct enharmonic spelling in three example situations:
Here are some ideas about how to handle each of these:
There isn't much to worry about in this case. If you create a C bebop scale and get its PitchClass
es:
scale (pitchClass C Natural) (NonDiatonicClass bebop)
|> toList
|> List.map PitchClass.toString
➜ [ "C", "D", "E", "F", "G", "Ab", "A", "B" ]
(if PitchClass.toString
took a PitchClass
), you'd get C D E F G Ab A B
, because bebop
is defined:
bebop : NonDiatonicScaleClass
bebop =
OctatonicScaleClass
{ secondDegree = Interval.majorSecond
, thirdDegree = Interval.majorThird
, fourthDegree = Interval.perfectFourth
, fifthDegree = Interval.perfectFifth
, sixthDegree = Interval.minorSixth
, seventhDegree = Interval.majorSixth
, eighthDegree = Interval.majorSeventh
}
C D E F G Ab A B
is what you want anyway, so there's no problem here. Same with C major pentatonic.
Let's imagine you could create two ranges from the C half-whole diminished scale, one ascending and the other descending, and append them to each other:
ascendingLine =
scale (pitchClass C Natural) (NonDiatonicClass diminishedHalfWhole)
|> Scale.pitchesInRange (pitch C Natural 4) (pitch C Natural 5)
ascendingAndDescendingLine =
ascendingLine ++ (List.reverse ascendingLine)
|> List.map Pitch.toString
➜ [ "C4", "Db4", "D#4", "E4", "F#4", "G4", "A4", "Bb4", "C5", "C5", "Bb4", "A4", "G4", "F#4", "E4", "D#4", "Db4", "C4" ]
This is clearly wrong, since the pitches are just being generated by the intervals here:
diminishedHalfWhole : NonDiatonicScaleClass
diminishedHalfWhole =
OctatonicScaleClass
{ secondDegree = Interval.minorSecond
, thirdDegree = Interval.augmentedSecond
, fourthDegree = Interval.majorThird
, fifthDegree = Interval.augmentedFourth
, sixthDegree = Interval.perfectFifth
, seventhDegree = Interval.majorSixth
, eighthDegree = Interval.minorSeventh
}
If we wanted these notes to be spelled with sharps going up and flats going down, we might run them through a function that spells them according to some configuration:
ascendingAndDescendingLine =
ascendingLine ++ (List.reverse ascendingLine)
|> Enharmonic.spellPitchList [ Enharmonic.preferSharpsWhenAscending, Enharmonic.preferFlatsWhenDescending ]
|> List.map Pitch.toString
➜ [ "C4", "C#4", "D#4", "E4", "F#4", "G4", "A4", "A#4", "C5", "C5", "Bb4", "A4", "G4", "Gb4", "E4", "Eb4", "Db4", "C4" ]
We would just have to implement spellPitchList
, and make it behave according to those options:
spellPitchList : List Pitch -> List ConfigOption -> List Pitch
Pitch
es would be necessary here, since there's no guarantee that D
is above C
without knowing its octave. And it would be necessary to make this function take a List Pitch
or two Pitch
es (the current one and the previous one), so that it could determine what direction the current pitch was going.
I think a spellPitchList
or a similar Enharmonic.spellPitchClass
function that took a list of ConfigOption
s could make a lot of behavior happen in this case.
We want to spell the C half-whole diminished scale over a C7b9 chord. Starting with our incorrectly spelled scale:
ascendingLine =
scale (pitchClass C Natural) (NonDiatonicClass diminishedHalfWhole)
|> Scale.toList
|> List.map PitchClass.toString
➜ [ "C", "Db", "D#", "E", "F#", "G", "A", "Bb" ]
We're not sure whether these PitchClass
es are right. How do we find out?
You are probably familiar with the idea of "chord scales". This means that, for any given chord, there is a list of scales it fits into.
For C7b9, for example, there are a few scales that it's compatible with that share the root of C: C diminished half-whole, and C Phrygian dominant. It depends on whether the 13 is supposed to be flatted or not.
In this case, the C diminished half-whole scale happens to be the correct choice. But we could make sure to spell it that way by running it through our imaginary spellPitchClass
function with a ConfigOption
that tells it what to do:
scaleOfTheMoment =
scale (pitchClass C Natural) (NonDiatonicClass diminishedHalfWhole)
ascendingLine =
scale (pitchClass C Natural) (NonDiatonicClass diminishedHalfWhole)
|> Scale.toList
|> List.map (Enharmonic.spellPitchList [ Enharmonic.currentScale scaleOfTheMoment ] )
|> List.map PitchClass.toString
➜ [ "C", "Db", "D#", "E", "F#", "G", "A", "Bb" ]
If we were playing these notes over a Cm7b9b13 chord, we might do something like this (although it wouldn't change the spelling in this case, unless there were config options that told it how to spell the A natural):
scaleOfTheMoment =
scale (pitchClass C Natural) (NonDiatonicClass phrygianDominant)
ascendingLine =
scale (pitchClass C Natural) (NonDiatonicClass phrygianDominant)
|> Scale.toList
|> List.map (Enharmonic.spellPitchList [ Enharmonic.currentScale scaleOfTheMoment ] )
|> List.map PitchClass.toString
➜ [ "C", "Db", "D#", "E", "F#", "G", "A", "Bb" ]
That last part about ConfigOptions
defining the "scale of the moment" probably seemed complicated, and it didn't help that the example notes didn't change as a result. But I would be happy to try to illustrate with a different example. I think the approach of a configurable enharmonic speller function is a very promising way to solve these problems.
As you've demonstrated, there are many scales (like Db minor) that are theoretically possible but rarely or never used in practice. I don't think it should be a goal of ours to prevent users from creating them. If our library supports an arbitrary number of accidentals, I think it must also support an arbitrary number of scales.
I agree.
Scales of this kind will, I believe, often come from keys, so one way we can encourage people to use the "usual" scales is by exposing a functions like Key.all, Key.allMajor and Key.allMinor that returned lists of the relevant keys in the circle of fifths. This would encourage correct enharmonic keys being used in client applications (e.g. if it were used to populate a select menu).
Yes.
If this doesn't seem like enough, we could modify the key constructor function we exposed to accept a parameter like useConventionalRoot that made sure that any key's tonic was always converted to the correct enharmonic equivalent in the circle of fifths before using it to generate the key's scale degrees. But I think this would be taking functionality away from the user that could be useful in a music theory setting. Maybe someone wants to do something in Cb minor— why should we stop them?
Sounds right.
The rest looks good in general. But I'd have to look in detail carefully.
I would suggest that we start with small steps. Let's get the simplest possible version of ScaleClass
right. Then Scale
and so on. We don't have to get everything covered in the first version. We can always make breaking changes to the API later when we add more features.
I get the feeling that trying to come up with a perfect design up front is slowing us down big time.
Unambiguous spelling
C major (ionian)
C D E F G A B
C minor (aeolian)
C D Eb F G Ab Bb
Db major (ionian)
Db Eb F Gb Ab Bb C
Db minor (aeolian)
This is technically possible but does not make so much sense. The circle of fifth does not contain Db minor. C# minor would be a better way to represent this, which is the relative minor of E major.
Db Eb Fb Gb Ab Bbb Cb
Technically it would be possible to create a scale from any pitch class. E.g. Cbbb major:
Cbbb Dbbb Ebbbb ...
Ugh (not sure if we can prevent this with the types, maybe it's just the users choice, if they wish to construct these kind of scals we'll let them)C# minor
C# D# E F# G# A# B
Eb minor pentatonic
Eb Gb Ab Bb Db
C bebop scale
C D E F G Ab A B
According to Wikipedia the sixth note is Ab rather than G#. Actually this is a diatonic major scale with a passing note.
Conclusion
With all these the spelling is unambiguous if it can be derived from the circle of fifth.
Ambiguous spelling
C Half tone / whole tone
C C# D# E F# G A A#
(ascending?)or
C Db Eb E Gb G A Bb
(descending?)or
C Db D# E Gb G A Bb
(e.g. a scale over C7b9, it's D# because that is the #9, not sure about F# vs. Gb)or even more possibilities...