Skip to content

Commit

Permalink
Making the chord ranking smarter based on inversions and accidental p…
Browse files Browse the repository at this point in the history
…references
  • Loading branch information
aure committed Feb 16, 2024
1 parent 028bb5e commit bd04fb3
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 10 deletions.
13 changes: 10 additions & 3 deletions Sources/Tonic/Chord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,19 @@ extension Chord {
returnArray.append(contentsOf: Chord.getRankedChords(from: noteArray))
}

// sort by alphabetical to prevent nondeterminstic sorting from same number of accdientals
returnArray.sort { $0.root.accidental < $1.root.accidental }
// Sorts anti-alphabetical, but the net effect is to pefer flats to sharps
returnArray.sort { $0.root.letter > $1.root.letter }

// order the array by least number of accidentals
returnArray.sort { $0.accidentalCount < $1.accidentalCount }


// order the array preferring root position
returnArray.sort { $0.inversion < ($1.inversion > 0 ? 1 : 0) }

// prefer root notes not being uncommon enharmonics
returnArray.sort { ($0.root.canonicalNote.isUncommonEnharmonic ? 1 : 0) < ($1.root.canonicalNote.isUncommonEnharmonic ? 1 : 0) }


return returnArray
}
/// Get chords from actual notes (spelling matters, C# F G# will not return a C# major)
Expand Down
11 changes: 11 additions & 0 deletions Sources/Tonic/Note.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public struct Note: Equatable, Hashable, Codable {
noteInKey = note
}
}
// The octave is wrong if the letter is C and we've flattened into the previous octave
if noteInKey?.noteClass.letter == .C && [Accidental.flat, Accidental.doubleFlat].contains(noteInKey?.accidental) {
octave += 1
}

if let note = noteInKey {
noteClass = note.noteClass
Expand Down Expand Up @@ -170,3 +174,10 @@ extension Note: CustomStringConvertible {
"\(noteClass)\(octave)"
}
}

extension Note {
var isUncommonEnharmonic: Bool {
let uglyNotes: [NoteClass] = [.Cb, .Es, .Fb, .Bs]
return uglyNotes.contains(self.noteClass)
}
}
19 changes: 12 additions & 7 deletions Tests/TonicTests/ChordTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ class ChordTests: XCTestCase {
func testC7() {
XCTAssertEqual(Chord(.C, type: .dominantSeventh).description, "C7")
let notes: [Int8] = [60, 67, 70, 76]
let c7 = PitchSet(pitches: notes.map { Pitch($0) } )
let chords = Chord.getRankedChords(from: c7)
XCTAssertEqual(chords.map { $0.description }, ["C7"])
let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } )
let c7 = Chord.getRankedChords(from: pitchSet)
XCTAssertEqual(c7.map { $0.description }, ["C7"])
}

func testAugmentedDiminishededChordsPreferNoInversions() {
let notes: [Int8] = [60, 64, 68]
let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } )
let cAug = Chord.getRankedChords(from: pitchSet)
XCTAssertEqual(cAug.map { $0.slashDescription }.first, "C⁺")
}

func testRomanNumerals() {
Expand Down Expand Up @@ -119,11 +125,11 @@ class ChordTests: XCTestCase {
XCTAssertEqual(firstInversion.inversion, 1)
XCTAssertEqual(firstInversion.slashDescription, "Am/C")

var secondInversion = Chord(notes: [Note(.E, octave: 1), .A, .C])!
let secondInversion = Chord(notes: [Note(.E, octave: 1), .A, .C])!
XCTAssertEqual(secondInversion.inversion, 2)
XCTAssertEqual(secondInversion.slashDescription, "Am/E")

var thirdInversion = Chord(.C, type: .dominantSeventh, inversion: 3)
let thirdInversion = Chord(.C, type: .dominantSeventh, inversion: 3)

XCTAssertEqual(thirdInversion.slashDescription, "C7/B♭")

Expand Down Expand Up @@ -287,7 +293,7 @@ class ChordTests: XCTestCase {
func assertChords(_ notes: [Int8], _ expected: [Chord]) {
let pitchSet = PitchSet(pitches: notes.map { Pitch($0) })
let chords = Chord.getRankedChords(from: pitchSet)
XCTAssertEqual(chords.description, expected.description)
XCTAssertEqual(chords.map { $0.slashDescription }, expected.map { $0.slashDescription })
}

func testDiatonicChords() {
Expand All @@ -312,7 +318,6 @@ class ChordTests: XCTestCase {
// Extensions that can be spelled only without double accidentals should be found
assertChords([1, 5, 8, 11], [Chord(.Db, type: .dominantSeventh), Chord(.Cs, type: .dominantSeventh),])
assertChords([1, 5, 8, 11, 14], [Chord(.Cs, type: .flatNinth)])

}

func testClosedVoicing() {
Expand Down

0 comments on commit bd04fb3

Please sign in to comment.