Skip to content

Commit

Permalink
WIP: Added new static Chord function to provide chord possibilities f…
Browse files Browse the repository at this point in the history
…rom [Notes] (#19)

* Added new static Chord function to provide chord possibilities from notes

* Added Suspended chords

---------

Co-authored-by: Aurelius Prochazka <aure@aure.com>
  • Loading branch information
maksutovic and aure authored Jan 23, 2024
1 parent 3baf460 commit e7bfca2
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 50 deletions.
11 changes: 9 additions & 2 deletions Demo/Shared/ChordIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import Tonic
class ChordIdentifier: ObservableObject {
func noteOn(pitch: Pitch, position: CGPoint = .zero) {
pitchSet.add(pitch)
notes.append(Note(pitch: pitch))
}

func noteOff(pitch: Pitch) {
pitchSet.remove(pitch)
notes.removeAll(where: {$0.pitch == pitch})
}

@Published var notes: [Note] = [] {
didSet { potentialChords = getPotentialChords() }
}

@Published var pitchSet: PitchSet = .init() {
Expand Down Expand Up @@ -90,7 +96,8 @@ class ChordIdentifier: ObservableObject {
}

private func getPotentialChords() -> [Chord] {
return ChordTable.shared.getAllChordsForNoteSet(noteSet)
return Chord.getRankedChords(from: notes)
//return ChordTable.shared.getAllChordsForNoteSet(noteSet)
}

private func getDefaultChord() -> Chord? {
Expand Down
147 changes: 105 additions & 42 deletions Sources/Tonic/Chord+Shortcuts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,26 +97,47 @@ public extension Chord {

// MARK: - Natural Suspended chords

/// C Suspended - Csus (Csus4)
static var Csus = Chord(.C, type: .suspendedTriad)
/// C Suspended Fourth - Csus4
static var Csus = Chord(.C, type: .suspendedFourthTriad)

/// D Suspended - Dsus (Dsus4)
static var Dsus = Chord(.D, type: .suspendedTriad)
/// D Suspended Fourth - Dsus4
static var Dsus = Chord(.D, type: .suspendedFourthTriad)

/// E Suspended - Esus (Esus4)
static var Esus = Chord(.E, type: .suspendedTriad)
/// E Suspended Fourth - Esus4
static var Esus = Chord(.E, type: .suspendedFourthTriad)

/// F Suspended - Fsus (Fsus4)
static var Fsus = Chord(.F, type: .suspendedTriad)
/// F Suspended Fourth - Fsus4
static var Fsus = Chord(.F, type: .suspendedFourthTriad)

/// G Suspended - Gsus (Gsus4)
static var Gsus = Chord(.G, type: .suspendedTriad)
/// G Suspended Fourth - Gsus4
static var Gsus = Chord(.G, type: .suspendedFourthTriad)

/// A Suspended - Asus (Asus4)
static var Asus = Chord(.A, type: .suspendedTriad)
/// A Suspended Fourth - Asus4
static var Asus4 = Chord(.A, type: .suspendedFourthTriad)

/// B Suspended - Bsus (Bsus4)
static var Bsus = Chord(.B, type: .suspendedTriad)
/// B Suspended Fourth - Bsus4
static var Bsus4 = Chord(.B, type: .suspendedFourthTriad)

/// C Suspended Second - Csus2
static var Csus2 = Chord(.C, type: .suspendedSecondTriad)

/// D Suspended Second - Dsus2
static var Dsus2 = Chord(.D, type: .suspendedSecondTriad)

/// E Suspended Second - Esus2
static var Esus2 = Chord(.E, type: .suspendedSecondTriad)

/// F Suspended Second - Fsus2
static var Fsus2 = Chord(.F, type: .suspendedSecondTriad)

/// G Suspended Second - Gsus2
static var Gsus2 = Chord(.G, type: .suspendedSecondTriad)

/// A Suspended Second - Asus2
static var Asus2 = Chord(.A, type: .suspendedSecondTriad)

/// B Suspended Second - Bsus2
static var Bsus2 = Chord(.B, type: .suspendedSecondTriad)

// MARK: - Sharp Major chords

Expand Down Expand Up @@ -189,26 +210,47 @@ public extension Chord {

// MARK: - Sharp Suspended chords

/// C♯ Susended - C♯sus (C♯sus4)
static var Cssus = Chord(.Cs, type: .suspendedTriad)
/// C♯ Suspended Fourth - C♯sus4
static var Cssus4 = Chord(.Cs, type: .suspendedFourthTriad)

/// D♯ Suspended Fourth - D♯sus4
static var Dssus4 = Chord(.Ds, type: .suspendedFourthTriad)

/// E♯ Suspended Fourth - E♯sus4
static var Essus4 = Chord(.Es, type: .suspendedFourthTriad)

/// F♯ Suspended Fourth - F♯sus4
static var Fssus4 = Chord(.Fs, type: .suspendedFourthTriad)

/// G♯ Suspended Fourth - G♯sus4
static var Gssus4 = Chord(.Gs, type: .suspendedFourthTriad)

/// D♯ Susended - D♯sus (D♯sus4)
static var Dssus = Chord(.Ds, type: .suspendedTriad)
/// A♯ Suspended Fourth - A♯sus4
static var Assus4 = Chord(.As, type: .suspendedFourthTriad)

/// E♯ Susended - E♯sus (E♯sus4)
static var Essus = Chord(.Es, type: .suspendedTriad)
/// B♯ Suspended Fourth - B♯sus4
static var Bssus4 = Chord(.Bs, type: .suspendedFourthTriad)

/// F♯ Susended - F♯sus (F♯sus4)
static var Fssus = Chord(.Fs, type: .suspendedTriad)
/// C♯ Suspended Second - C♯sus2
static var Cssus2 = Chord(.Cs, type: .suspendedSecondTriad)

/// G♯ Susended - G♯sus (G♯sus4)
static var Gssus = Chord(.Gs, type: .suspendedTriad)
/// D♯ Suspended Second - D♯sus2
static var Dssus2 = Chord(.Ds, type: .suspendedSecondTriad)

/// A♯ Susended - A♯sus (A♯sus4)
static var Assus = Chord(.As, type: .suspendedTriad)
/// E♯ Suspended Second - E♯sus2
static var Essus2 = Chord(.Es, type: .suspendedSecondTriad)

/// B♯ Susended - B♯sus (B♯sus4)
static var Bssus = Chord(.Bs, type: .suspendedTriad)
/// F♯ Suspended Second - F♯sus2
static var Fssus2 = Chord(.Fs, type: .suspendedSecondTriad)

/// G♯ Suspended Second - G♯sus2
static var Gssus2 = Chord(.Gs, type: .suspendedSecondTriad)

/// A♯ Suspended Second - A♯sus2
static var Assus2 = Chord(.As, type: .suspendedSecondTriad)

/// B♯ Suspended Second - B♯sus2
static var Bssus2 = Chord(.Bs, type: .suspendedSecondTriad)

// MARK: - Flat Major chords

Expand Down Expand Up @@ -281,24 +323,45 @@ public extension Chord {

// MARK: - Flat Suspended chords

/// C♭ Suspended - C♭sus (C♭sus4)
static var Cbsus = Chord(.Cb, type: .suspendedTriad)
/// C♭ Suspended Fourth - C♭sus4
static var Cbsus4 = Chord(.Cb, type: .suspendedFourthTriad)

/// D♭ Suspended Fourth - D♭sus4
static var Dbsus4 = Chord(.Db, type: .suspendedFourthTriad)

/// E♭ Suspended Fourth - E♭sus4
static var Ebsus4 = Chord(.Eb, type: .suspendedFourthTriad)

/// F♭ Suspended Fourth - F♭sus4
static var Fbsus4 = Chord(.Fb, type: .suspendedFourthTriad)

/// G♭ Suspended Fourth - G♭sus4
static var Gbsus4 = Chord(.Gb, type: .suspendedFourthTriad)

/// A♭ Suspended Fourth - A♭sus4
static var Absus4 = Chord(.Ab, type: .suspendedFourthTriad)

/// B♭ Suspended Fourth - B♭sus4
static var Bbsus4 = Chord(.Bb, type: .suspendedFourthTriad)

/// C♭ Suspended Fourth - C♭sus2
static var Cbsus2 = Chord(.Cb, type: .suspendedSecondTriad)

/// D♭ Suspended - D♭sus (D♭sus4)
static var Dbsus = Chord(.Db, type: .suspendedTriad)
/// D♭ Suspended Fourth - D♭sus2
static var Dbsus2 = Chord(.Db, type: .suspendedSecondTriad)

/// E♭ Suspended - E♭sus (E♭sus4)
static var Ebsus = Chord(.Eb, type: .suspendedTriad)
/// E♭ Suspended Fourth - E♭sus2
static var Ebsus2 = Chord(.Eb, type: .suspendedSecondTriad)

/// F♭ Suspended - F♭sus (F♭sus4)
static var Fbsus = Chord(.Fb, type: .suspendedTriad)
/// F♭ Suspended Fourth - F♭sus2
static var Fbsus2 = Chord(.Fb, type: .suspendedSecondTriad)

/// G♭ Suspended - G♭sus (G♭sus4)
static var Gbsus = Chord(.Gb, type: .suspendedTriad)
/// G♭ Suspended Fourth - G♭sus2
static var Gbsus2 = Chord(.Gb, type: .suspendedSecondTriad)

/// A♭ Suspended - A♭sus (A♭sus4)
static var Absus = Chord(.Ab, type: .suspendedTriad)
/// A♭ Suspended Fourth - A♭sus2
static var Absus2 = Chord(.Ab, type: .suspendedSecondTriad)

/// B♭ Suspended - B♭sus (B♭sus4)
static var Bbsus = Chord(.Bb, type: .suspendedTriad)
/// B♭ Suspended Fourth - B♭sus2
static var Bbsus2 = Chord(.Bb, type: .suspendedSecondTriad)
}
16 changes: 16 additions & 0 deletions Sources/Tonic/Chord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,19 @@ extension Chord: CustomStringConvertible {
return "\(root)\(type)"
}
}

extension Chord {

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 })
var ranks: [(Int, Chord)] = []
for chord in potentialChords {
let rank = orderedNotes.firstIndex(where: { $0.noteClass == chord.root })
ranks.append((rank ?? 0, chord))
}
let sortedRanks = ranks.sorted(by: { $0.0 < $1.0 })

return sortedRanks.map({ $0.1 })
}
}
13 changes: 9 additions & 4 deletions Sources/Tonic/ChordType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ public enum ChordType: Int, CaseIterable {
/// Augmented Triad: Major Third, Augmented Fifth
case augmentedTriad

/// Suspended Triad: Perfect Fourth, Perfect Fifth
case suspendedTriad
/// Suspended 2 Triad: Major Second, Perfect Fifth
case suspendedSecondTriad

/// Suspended 4 Triad: Perfect Fourth, Perfect Fifth
case suspendedFourthTriad

/// Sixth: Major Third, Perfect Fifth, Major Sixth
case sixth
Expand Down Expand Up @@ -136,7 +139,8 @@ public enum ChordType: Int, CaseIterable {
case .minorTriad: return [.m3, .P5]
case .diminishedTriad: return [.m3, .d5]
case .augmentedTriad: return [.M3, .A5]
case .suspendedTriad: return [.P4, .P5]
case .suspendedSecondTriad: return [.M2, .P5]
case .suspendedFourthTriad: return [.P4, .P5]
case .sixth: return [.M3, .P5, .M6]
case .minorSixth: return [.m3, .P5, .M6]
case .halfDiminishedSeventh: return [.m3, .d5, .m7]
Expand Down Expand Up @@ -186,7 +190,8 @@ extension ChordType: CustomStringConvertible {
case .minorTriad: return "m"
case .diminishedTriad: return "°"
case .augmentedTriad: return ""
case .suspendedTriad: return "sus"
case .suspendedSecondTriad: return "sus2"
case .suspendedFourthTriad: return "sus4"
case .sixth: return "6"
case .minorSixth: return "m6"
case .halfDiminishedSeventh: return "(1/2)°7"
Expand Down
32 changes: 30 additions & 2 deletions Tests/TonicTests/ChordTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ class ChordTests: XCTestCase {
XCTAssertEqual(Chord.Aaug.description, "A⁺")
XCTAssertEqual(Chord(.Db, type: .augmentedTriad).description, "D♭⁺")

XCTAssertEqual(Chord.Asus.description, "Asus")
XCTAssertEqual(Chord.Bsus.description, "Bsus")
XCTAssertEqual(Chord.Asus4.description, "Asus4")
XCTAssertEqual(Chord.Bsus4.description, "Bsus4")

XCTAssertEqual(Chord.Asus2.description, "Asus2")
XCTAssertEqual(Chord.Bsus2.description, "Bsus2")
}

func testRomanNumerals() {
Expand Down Expand Up @@ -116,4 +119,29 @@ class ChordTests: XCTestCase {
let chord = Chord(notes: [.C, .E, .G, Note(.C, octave: 5)])!
XCTAssertEqual(chord.description, "C")
}

func testEnharmonicSuspensions() {
// Here we show how it is problematic to use a chord initializer if you worry about
// enharmonic chords

let cSus2 = Chord(notes: [.C, .D, .G])!
let gSus4 = Chord(notes: [.C, .D, Note(.G, octave: 3)])!

// See how both of these are returning the same chord
XCTAssertEqual(cSus2.description, "Gsus4")
XCTAssertEqual(gSus4.description, "Gsus4")

// To deal with this, you have to tell Tonic that you want an array of potential chords
let gChords = Chord.getRankedChords(from: [.C, .D, Note(.G, octave: 3)])

// What we want is for this to list "Gsus4 first and Csus2 second whereas
let cChords = Chord.getRankedChords(from: [.C, .D, .G])

// should return the same array, in reversed order
XCTAssertEqual(gChords.map { $0.description }, ["Gsus4", "Csus2"])
XCTAssertEqual(cChords.map { $0.description }, ["Csus2", "Gsus4"])



}
}

0 comments on commit e7bfca2

Please sign in to comment.