Skip to content

Commit

Permalink
Made "getRankedChords" more robust to extensions, and made the sortin…
Browse files Browse the repository at this point in the history
…g sensible
  • Loading branch information
aure committed Feb 16, 2024
1 parent 7b3e4f7 commit 7f34a6c
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 9 deletions.
48 changes: 39 additions & 9 deletions Sources/Tonic/Chord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,28 +128,58 @@ extension Chord: CustomStringConvertible {
}

extension Chord {

/// Get chords that match a set of pitches, listing enharmonic chords with sharp before flats

public var accidentalCount: Int {
var count = 0
for note in self.noteClasses {
switch note.accidental {
case .natural:
break
case .flat, .sharp:
count += 1
case .doubleFlat, .doubleSharp:
count += 2
}
}
return count
}

/// Get chords that match a set of pitches, ranking by least number of accidentals
public static func getRankedChords(from pitchSet: PitchSet) -> [Chord] {
var flatNotes: [Note] = []
var sharpNotes: [Note] = []
var cNotes: [Note] = []
var bNotes: [Note] = []
var sNotes: [Note] = []
var returnArray: [Chord] = []

for pitch in pitchSet.array {
flatNotes.append(Note(pitch: pitch, key: .F))
sharpNotes.append(Note(pitch: pitch, key: .C))
cNotes.append(Note(pitch: pitch, key: .C))
bNotes.append(Note(pitch: pitch, key: .Cb))
sNotes.append(Note(pitch: pitch, key: .Cs))
}
returnArray.append(contentsOf: Chord.getRankedChords(from: sharpNotes))
returnArray.append(contentsOf: Chord.getRankedChords(from: cNotes))

for chord in Chord.getRankedChords(from: flatNotes) {
for chord in Chord.getRankedChords(from: sNotes) {
if !returnArray.contains(chord) {
returnArray.append(chord)
}
}
for chord in Chord.getRankedChords(from: bNotes) {
if !returnArray.contains(chord) {
returnArray.append(chord)
}
}
}
for chord in returnArray {
print(chord, chord.accidentalCount)
}
// order the array by least number of accidentals
returnArray.sort { $0.accidentalCount < $1.accidentalCount }

return returnArray
}
/// Get chords from actual notes (spelling matters, C# F G# will not return a C# major)
/// Use pitch set version of this function for all enharmonic chords
/// The ranking is based on how low the root note of the chord appears, for example we
/// want to list the notes C, E, G, A as C6 if the C is in the bass
public static func getRankedChords(from notes: [Note]) -> [Chord] {
let potentialChords = ChordTable.shared.getAllChordsForNoteSet(NoteSet(notes: notes))
let orderedNotes = notes.sorted(by: { f, s in f.noteNumber < s.noteNumber })
Expand Down
34 changes: 34 additions & 0 deletions Tests/TonicTests/ChordTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,40 @@ class ChordTests: XCTestCase {
)
}

func assertChords(_ notes: [Int8], _ expected: [Chord]) {
let pitchSet = PitchSet(pitches: notes.map { Pitch($0) })
// print(pitchSet.array.map { Note(pitch: $0)})
let chords = Chord.getRankedChords(from: pitchSet)
// print(chords, expected)
// Note that this is strange that we can't compare the arrays directly
XCTAssertEqual(chords.description, expected.description)
}

func testDiatonicChords() {
// Basic triads
assertChords([2, 6, 9], [.D])

// We prioritize by the number of accidentals
assertChords([1, 5, 8], [.Db, .Cs])

// This test shows that we are aware that A# Major triad is more compactly described as Bb
// because of the required C## in the A# spelling
assertChords([10, 14, 17], [.Bb])
// F should not be reported as E#
assertChords([5, 9, 12], [.F])
// E could be reported as Fb, but its accidental is lower it is first
assertChords([4, 8, 11], [.E, .Fb])
// C should not be reported as B#
assertChords([0, 4, 7], [.C])
// B could be reported as Cb, but its accidental is lower it is first
assertChords([11, 15, 18], [.B, .Cb])

// Extensions that can be spelled only without double accidentals should be found
assertChords([1, 5, 8, 11], [Chord(.Cs, type: .dominantSeventh), Chord(.Db, type: .dominantSeventh)])
assertChords([1, 5, 8, 11, 14], [Chord(.Cs, type: .flatNinth)])

}

func testClosedVoicing() {
let openNotes: [Int8] = [60, 64 + 12, 67 + 24, 60 + 24, 64 + 36]
let results: [Int8] = [60, 64, 67]
Expand Down

0 comments on commit 7f34a6c

Please sign in to comment.