diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c511eb0..9d2254f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,7 @@ jobs: name: Build ${{ matrix.scheme }} (Xcode ${{ matrix.xcode_version }}) # NOTE: macos-latest is NOT equivalent to macos-12 as of September 2022. # Source: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources - runs-on: macos-12 + runs-on: macos-13 needs: [swift_test] strategy: # Disabling fail-fast ensures that the job will run all configurations of the matrix, even if one fails. diff --git a/Sources/Tonic/Chord+Shortcuts.swift b/Sources/Tonic/Chord+Shortcuts.swift index e65fd8b..77e37eb 100644 --- a/Sources/Tonic/Chord+Shortcuts.swift +++ b/Sources/Tonic/Chord+Shortcuts.swift @@ -6,362 +6,362 @@ public extension Chord { // MARK: - Natural Major chords /// C Major - static var C = Chord(.C, type: .majorTriad) + static var C = Chord(.C, type: .major) /// D Major - static var D = Chord(.D, type: .majorTriad) + static var D = Chord(.D, type: .major) /// E Major - static var E = Chord(.E, type: .majorTriad) + static var E = Chord(.E, type: .major) /// F Major - static var F = Chord(.F, type: .majorTriad) + static var F = Chord(.F, type: .major) /// G Major - static var G = Chord(.G, type: .majorTriad) + static var G = Chord(.G, type: .major) /// A Major - static var A = Chord(.A, type: .majorTriad) + static var A = Chord(.A, type: .major) /// B Major - static var B = Chord(.B, type: .majorTriad) + static var B = Chord(.B, type: .major) // MARK: - Natural Minor chords /// C Minor - static var Cm = Chord(.C, type: .minorTriad) + static var Cm = Chord(.C, type: .minor) /// D Minor - static var Dm = Chord(.D, type: .minorTriad) + static var Dm = Chord(.D, type: .minor) /// E Minor - static var Em = Chord(.E, type: .minorTriad) + static var Em = Chord(.E, type: .minor) /// F Minor - static var Fm = Chord(.F, type: .minorTriad) + static var Fm = Chord(.F, type: .minor) /// G Minor - static var Gm = Chord(.G, type: .minorTriad) + static var Gm = Chord(.G, type: .minor) /// A Minor - static var Am = Chord(.A, type: .minorTriad) + static var Am = Chord(.A, type: .minor) /// B Minor - static var Bm = Chord(.B, type: .minorTriad) + static var Bm = Chord(.B, type: .minor) // MARK: - Natural Diminished chords /// C Diminished - C° - static var Cdim = Chord(.C, type: .diminishedTriad) + static var Cdim = Chord(.C, type: .dim) /// D Diminished - D° - static var Ddim = Chord(.D, type: .diminishedTriad) + static var Ddim = Chord(.D, type: .dim) /// E Diminished - E° - static var Edim = Chord(.E, type: .diminishedTriad) + static var Edim = Chord(.E, type: .dim) /// F Diminished - F° - static var Fdim = Chord(.F, type: .diminishedTriad) + static var Fdim = Chord(.F, type: .dim) /// G Diminished - G° - static var Gdim = Chord(.G, type: .diminishedTriad) + static var Gdim = Chord(.G, type: .dim) /// A Diminished - A° - static var Adim = Chord(.A, type: .diminishedTriad) + static var Adim = Chord(.A, type: .dim) /// B Diminished - B° - static var Bdim = Chord(.B, type: .diminishedTriad) + static var Bdim = Chord(.B, type: .dim) // MARK: - Natural Augmented chords /// C Augmented - C⁺ - static var Caug = Chord(.C, type: .augmentedTriad) + static var Caug = Chord(.C, type: .aug) /// D Augmented - D⁺ - static var Daug = Chord(.D, type: .augmentedTriad) + static var Daug = Chord(.D, type: .aug) /// E Augmented - E⁺ - static var Eaug = Chord(.E, type: .augmentedTriad) + static var Eaug = Chord(.E, type: .aug) /// F Augmented - F⁺ - static var Faug = Chord(.F, type: .augmentedTriad) + static var Faug = Chord(.F, type: .aug) /// G Augmented - G⁺ - static var Gaug = Chord(.G, type: .augmentedTriad) + static var Gaug = Chord(.G, type: .aug) /// A Augmented - A⁺ - static var Aaug = Chord(.A, type: .augmentedTriad) + static var Aaug = Chord(.A, type: .aug) /// B Augmented - B⁺ - static var Baug = Chord(.B, type: .augmentedTriad) + static var Baug = Chord(.B, type: .aug) // MARK: - Natural Suspended chords /// C Suspended Fourth - Csus4 - static var Csus4 = Chord(.C, type: .suspendedFourthTriad) + static var Csus4 = Chord(.C, type: .sus4) /// D Suspended Fourth - Dsus4 - static var Dsus4 = Chord(.D, type: .suspendedFourthTriad) + static var Dsus4 = Chord(.D, type: .sus4) /// E Suspended Fourth - Esus4 - static var Esus4 = Chord(.E, type: .suspendedFourthTriad) + static var Esus4 = Chord(.E, type: .sus4) /// F Suspended Fourth - Fsus4 - static var Fsus4 = Chord(.F, type: .suspendedFourthTriad) + static var Fsus4 = Chord(.F, type: .sus4) /// G Suspended Fourth - Gsus4 - static var Gsus4 = Chord(.G, type: .suspendedFourthTriad) + static var Gsus4 = Chord(.G, type: .sus4) /// A Suspended Fourth - Asus4 - static var Asus4 = Chord(.A, type: .suspendedFourthTriad) + static var Asus4 = Chord(.A, type: .sus4) /// B Suspended Fourth - Bsus4 - static var Bsus4 = Chord(.B, type: .suspendedFourthTriad) + static var Bsus4 = Chord(.B, type: .sus4) /// C Suspended Second - Csus2 - static var Csus2 = Chord(.C, type: .suspendedSecondTriad) + static var Csus2 = Chord(.C, type: .sus2) /// D Suspended Second - Dsus2 - static var Dsus2 = Chord(.D, type: .suspendedSecondTriad) + static var Dsus2 = Chord(.D, type: .sus2) /// E Suspended Second - Esus2 - static var Esus2 = Chord(.E, type: .suspendedSecondTriad) + static var Esus2 = Chord(.E, type: .sus2) /// F Suspended Second - Fsus2 - static var Fsus2 = Chord(.F, type: .suspendedSecondTriad) + static var Fsus2 = Chord(.F, type: .sus2) /// G Suspended Second - Gsus2 - static var Gsus2 = Chord(.G, type: .suspendedSecondTriad) + static var Gsus2 = Chord(.G, type: .sus2) /// A Suspended Second - Asus2 - static var Asus2 = Chord(.A, type: .suspendedSecondTriad) + static var Asus2 = Chord(.A, type: .sus2) /// B Suspended Second - Bsus2 - static var Bsus2 = Chord(.B, type: .suspendedSecondTriad) + static var Bsus2 = Chord(.B, type: .sus2) // MARK: - Sharp Major chords /// C♯ Major - static var Cs = Chord(.Cs, type: .majorTriad) + static var Cs = Chord(.Cs, type: .major) /// D♯ Major - static var Ds = Chord(.Ds, type: .majorTriad) + static var Ds = Chord(.Ds, type: .major) /// E♯ Major - static var Es = Chord(.Es, type: .majorTriad) + static var Es = Chord(.Es, type: .major) /// F♯ Major - static var Fs = Chord(.Fs, type: .majorTriad) + static var Fs = Chord(.Fs, type: .major) /// G♯ Major - static var Gs = Chord(.Gs, type: .majorTriad) + static var Gs = Chord(.Gs, type: .major) /// A♯ Major - static var As = Chord(.As, type: .majorTriad) + static var As = Chord(.As, type: .major) /// B♯ Major - static var Bs = Chord(.Bs, type: .majorTriad) + static var Bs = Chord(.Bs, type: .major) // MARK: - Sharp Minor chords /// C♯ Minor - static var Csm = Chord(.Cs, type: .minorTriad) + static var Csm = Chord(.Cs, type: .minor) /// D♯ Minor - static var Dsm = Chord(.Ds, type: .minorTriad) + static var Dsm = Chord(.Ds, type: .minor) /// E♯ Minor - static var Esm = Chord(.Es, type: .minorTriad) + static var Esm = Chord(.Es, type: .minor) /// F♯ Minor - static var Fsm = Chord(.Fs, type: .minorTriad) + static var Fsm = Chord(.Fs, type: .minor) /// G♯ Minor - static var Gsm = Chord(.Gs, type: .minorTriad) + static var Gsm = Chord(.Gs, type: .minor) /// A♯ Minor - static var Asm = Chord(.As, type: .minorTriad) + static var Asm = Chord(.As, type: .minor) /// B♯ Minor - static var Bsm = Chord(.Bs, type: .minorTriad) + static var Bsm = Chord(.Bs, type: .minor) // MARK: - Sharp Diminished chords /// C♯ Diminished - C♯° - static var Csdim = Chord(.Cs, type: .diminishedTriad) + static var Csdim = Chord(.Cs, type: .dim) /// D♯ Diminished - D♯° - static var Dsdim = Chord(.Ds, type: .diminishedTriad) + static var Dsdim = Chord(.Ds, type: .dim) /// E♯ Diminished - E♯° - static var Esdim = Chord(.Es, type: .diminishedTriad) + static var Esdim = Chord(.Es, type: .dim) /// F♯ Diminished - F♯° - static var Fsdim = Chord(.Fs, type: .diminishedTriad) + static var Fsdim = Chord(.Fs, type: .dim) /// G♯ Diminished - G♯° - static var Gsdim = Chord(.Gs, type: .diminishedTriad) + static var Gsdim = Chord(.Gs, type: .dim) /// A♯ Diminished - A♯° - static var Asdim = Chord(.As, type: .diminishedTriad) + static var Asdim = Chord(.As, type: .dim) /// B♯ Diminished - B♯° - static var Bsdim = Chord(.Bs, type: .diminishedTriad) + static var Bsdim = Chord(.Bs, type: .dim) // MARK: - Sharp Suspended chords /// C♯ Suspended Fourth - C♯sus4 - static var Cssus4 = Chord(.Cs, type: .suspendedFourthTriad) + static var Cssus4 = Chord(.Cs, type: .sus4) /// D♯ Suspended Fourth - D♯sus4 - static var Dssus4 = Chord(.Ds, type: .suspendedFourthTriad) + static var Dssus4 = Chord(.Ds, type: .sus4) /// E♯ Suspended Fourth - E♯sus4 - static var Essus4 = Chord(.Es, type: .suspendedFourthTriad) + static var Essus4 = Chord(.Es, type: .sus4) /// F♯ Suspended Fourth - F♯sus4 - static var Fssus4 = Chord(.Fs, type: .suspendedFourthTriad) + static var Fssus4 = Chord(.Fs, type: .sus4) /// G♯ Suspended Fourth - G♯sus4 - static var Gssus4 = Chord(.Gs, type: .suspendedFourthTriad) + static var Gssus4 = Chord(.Gs, type: .sus4) /// A♯ Suspended Fourth - A♯sus4 - static var Assus4 = Chord(.As, type: .suspendedFourthTriad) + static var Assus4 = Chord(.As, type: .sus4) /// B♯ Suspended Fourth - B♯sus4 - static var Bssus4 = Chord(.Bs, type: .suspendedFourthTriad) + static var Bssus4 = Chord(.Bs, type: .sus4) /// C♯ Suspended Second - C♯sus2 - static var Cssus2 = Chord(.Cs, type: .suspendedSecondTriad) + static var Cssus2 = Chord(.Cs, type: .sus2) /// D♯ Suspended Second - D♯sus2 - static var Dssus2 = Chord(.Ds, type: .suspendedSecondTriad) + static var Dssus2 = Chord(.Ds, type: .sus2) /// E♯ Suspended Second - E♯sus2 - static var Essus2 = Chord(.Es, type: .suspendedSecondTriad) + static var Essus2 = Chord(.Es, type: .sus2) /// F♯ Suspended Second - F♯sus2 - static var Fssus2 = Chord(.Fs, type: .suspendedSecondTriad) + static var Fssus2 = Chord(.Fs, type: .sus2) /// G♯ Suspended Second - G♯sus2 - static var Gssus2 = Chord(.Gs, type: .suspendedSecondTriad) + static var Gssus2 = Chord(.Gs, type: .sus2) /// A♯ Suspended Second - A♯sus2 - static var Assus2 = Chord(.As, type: .suspendedSecondTriad) + static var Assus2 = Chord(.As, type: .sus2) /// B♯ Suspended Second - B♯sus2 - static var Bssus2 = Chord(.Bs, type: .suspendedSecondTriad) + static var Bssus2 = Chord(.Bs, type: .sus2) // MARK: - Flat Major chords /// C♭ Major - static var Cb = Chord(.Cb, type: .majorTriad) + static var Cb = Chord(.Cb, type: .major) /// D♭ Major - static var Db = Chord(.Db, type: .majorTriad) + static var Db = Chord(.Db, type: .major) /// E♭ Major - static var Eb = Chord(.Eb, type: .majorTriad) + static var Eb = Chord(.Eb, type: .major) /// F♭ Major - static var Fb = Chord(.Fb, type: .majorTriad) + static var Fb = Chord(.Fb, type: .major) /// G♭ Major - static var Gb = Chord(.Gb, type: .majorTriad) + static var Gb = Chord(.Gb, type: .major) /// A♭ Major - static var Ab = Chord(.Ab, type: .majorTriad) + static var Ab = Chord(.Ab, type: .major) /// B♭ Major - static var Bb = Chord(.Bb, type: .majorTriad) + static var Bb = Chord(.Bb, type: .major) // MARK: - Flat Minor chords /// C♭ Minor - static var Cbm = Chord(.Cb, type: .minorTriad) + static var Cbm = Chord(.Cb, type: .minor) /// D♭ Minor - static var Dbm = Chord(.Db, type: .minorTriad) + static var Dbm = Chord(.Db, type: .minor) /// E♭ Minor - static var Ebm = Chord(.Eb, type: .minorTriad) + static var Ebm = Chord(.Eb, type: .minor) /// F♭ Minor - static var Fbm = Chord(.Fb, type: .minorTriad) + static var Fbm = Chord(.Fb, type: .minor) /// G♭ Minor - static var Gbm = Chord(.Gb, type: .minorTriad) + static var Gbm = Chord(.Gb, type: .minor) /// A♭ Minor - static var Abm = Chord(.Ab, type: .minorTriad) + static var Abm = Chord(.Ab, type: .minor) /// B♭ Minor - static var Bbm = Chord(.Bb, type: .minorTriad) + static var Bbm = Chord(.Bb, type: .minor) // MARK: - Flat Diminished chords /// C♭ Diminished - C♭° - static var Cbdim = Chord(.Cb, type: .diminishedTriad) + static var Cbdim = Chord(.Cb, type: .dim) /// D♭ Diminished - D♭° - static var Dbdim = Chord(.Db, type: .diminishedTriad) + static var Dbdim = Chord(.Db, type: .dim) /// E♭ Diminished - E♭° - static var Ebdim = Chord(.Eb, type: .diminishedTriad) + static var Ebdim = Chord(.Eb, type: .dim) /// F♭ Diminished - F♭° - static var Fbdim = Chord(.Fb, type: .diminishedTriad) + static var Fbdim = Chord(.Fb, type: .dim) /// G♭ Diminished - G♭° - static var Gbdim = Chord(.Gb, type: .diminishedTriad) + static var Gbdim = Chord(.Gb, type: .dim) /// A♭ Diminished - A♭° - static var Abdim = Chord(.Ab, type: .diminishedTriad) + static var Abdim = Chord(.Ab, type: .dim) /// B♭ Diminished - B♭° - static var Bbdim = Chord(.Bb, type: .diminishedTriad) + static var Bbdim = Chord(.Bb, type: .dim) // MARK: - Flat Suspended chords /// C♭ Suspended Fourth - C♭sus4 - static var Cbsus4 = Chord(.Cb, type: .suspendedFourthTriad) + static var Cbsus4 = Chord(.Cb, type: .sus4) /// D♭ Suspended Fourth - D♭sus4 - static var Dbsus4 = Chord(.Db, type: .suspendedFourthTriad) + static var Dbsus4 = Chord(.Db, type: .sus4) /// E♭ Suspended Fourth - E♭sus4 - static var Ebsus4 = Chord(.Eb, type: .suspendedFourthTriad) + static var Ebsus4 = Chord(.Eb, type: .sus4) /// F♭ Suspended Fourth - F♭sus4 - static var Fbsus4 = Chord(.Fb, type: .suspendedFourthTriad) + static var Fbsus4 = Chord(.Fb, type: .sus4) /// G♭ Suspended Fourth - G♭sus4 - static var Gbsus4 = Chord(.Gb, type: .suspendedFourthTriad) + static var Gbsus4 = Chord(.Gb, type: .sus4) /// A♭ Suspended Fourth - A♭sus4 - static var Absus4 = Chord(.Ab, type: .suspendedFourthTriad) + static var Absus4 = Chord(.Ab, type: .sus4) /// B♭ Suspended Fourth - B♭sus4 - static var Bbsus4 = Chord(.Bb, type: .suspendedFourthTriad) + static var Bbsus4 = Chord(.Bb, type: .sus4) /// C♭ Suspended Fourth - C♭sus2 - static var Cbsus2 = Chord(.Cb, type: .suspendedSecondTriad) + static var Cbsus2 = Chord(.Cb, type: .sus2) /// D♭ Suspended Fourth - D♭sus2 - static var Dbsus2 = Chord(.Db, type: .suspendedSecondTriad) + static var Dbsus2 = Chord(.Db, type: .sus2) /// E♭ Suspended Fourth - E♭sus2 - static var Ebsus2 = Chord(.Eb, type: .suspendedSecondTriad) + static var Ebsus2 = Chord(.Eb, type: .sus2) /// F♭ Suspended Fourth - F♭sus2 - static var Fbsus2 = Chord(.Fb, type: .suspendedSecondTriad) + static var Fbsus2 = Chord(.Fb, type: .sus2) /// G♭ Suspended Fourth - G♭sus2 - static var Gbsus2 = Chord(.Gb, type: .suspendedSecondTriad) + static var Gbsus2 = Chord(.Gb, type: .sus2) /// A♭ Suspended Fourth - A♭sus2 - static var Absus2 = Chord(.Ab, type: .suspendedSecondTriad) + static var Absus2 = Chord(.Ab, type: .sus2) /// B♭ Suspended Fourth - B♭sus2 - static var Bbsus2 = Chord(.Bb, type: .suspendedSecondTriad) + static var Bbsus2 = Chord(.Bb, type: .sus2) } diff --git a/Sources/Tonic/Chord.swift b/Sources/Tonic/Chord.swift index 945e346..e1b6eb3 100644 --- a/Sources/Tonic/Chord.swift +++ b/Sources/Tonic/Chord.swift @@ -6,7 +6,7 @@ import Foundation /// /// A representation of a chord as a set of note classes, with a root note class, /// and an inversion defined by the lowest note in the chord. -public struct Chord: Equatable { +public struct Chord: Equatable, Codable { /// Root note class of the chord public let root: NoteClass @@ -30,7 +30,7 @@ public struct Chord: Equatable { /// Try to initialize a chord from an array of notes. /// - /// If the array does not fit into a known chord type, this initialier will fail. + /// If the array does not fit into a known chord type, this initializer will fail. /// - Parameter notes: Note array public init?(notes: [Note]) { var set = NoteSet() @@ -97,9 +97,9 @@ public struct Chord: Equatable { if let index = key.primaryTriads.firstIndex(where: { $0 == self }) { let romanNumeral = capitalRomanNumerals[index] switch type { - case .majorTriad: return romanNumeral - case .minorTriad: return romanNumeral.lowercased() - case .diminishedTriad: return "\(romanNumeral.lowercased())°" + case .major: return romanNumeral + case .minor: return romanNumeral.lowercased() + case .dim: return "\(romanNumeral.lowercased())°" default: return nil } } @@ -155,7 +155,7 @@ extension Chord: CustomStringConvertible { /// Useful for custom rendering of slash notation public var bassNote: NoteClass { switch inversion { - case 1...4: + case 1...type.intervals.count: if let bass = root.canonicalNote.shiftUp(type.intervals[inversion - 1]) { return bass.noteClass } @@ -251,15 +251,12 @@ extension Chord { } } - // Sorts anti-alphabetical, but the net effect is to pefer flats to sharps + // Sorts anti-alphabetical, but the net effect is to prefer 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) } @@ -278,6 +275,12 @@ extension Chord { } } + // prefer fewer number of characters (favor less complex chords in ranking) + returnArray.sort { $0.slashDescription.count < $1.slashDescription.count } + + // order the array preferring root position + returnArray.sort { $0.inversion < ($1.inversion > 0 ? 1 : 0) } + return returnArray } diff --git a/Sources/Tonic/Chord.swift.orig b/Sources/Tonic/Chord.swift.orig new file mode 100644 index 0000000..f2d3b9e --- /dev/null +++ b/Sources/Tonic/Chord.swift.orig @@ -0,0 +1,367 @@ +// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Tonic/ + +import Foundation + +/// A chord is a set of simultaneous notes. +/// +/// A representation of a chord as a set of note classes, with a root note class, +/// and an inversion defined by the lowest note in the chord. +public struct Chord: Equatable { + /// Root note class of the chord + public let root: NoteClass + + /// The flavor of the chord (connoting which notes are played alongside the root, in some octave) + public let type: ChordType + + /// Which note in terms of degrees from the root appears as the lowest note. + /// Third in the bottom => 1st Inversion, Fifth => 2nd Inversion, Seventh, 3rd Inversion, etc. + public let inversion: Int + + /// Create a chord + /// - Parameters: + /// - root: Root note class of the chord + /// - type: The flavor of the chord (connoting which notes are played alongside the root, in some octave) + /// - inversion: What inversion of the chord determins which note is the lowest note of the chord + public init(_ root: NoteClass, type: ChordType, inversion: Int = 0) { + self.root = root + self.type = type + self.inversion = inversion + } + + /// Try to initialize a chord from an array of notes. + /// + /// If the array does not fit into a known chord type, this initializer will fail. + /// - Parameter notes: Note array + public init?(notes: [Note]) { + var set = NoteSet() + for n in notes { + set.add(n) + } + self.init(noteSet: set) + } + + /// Try to initialize a chord from a set of notes. + /// + /// If the set does not fit into a known chord type, this initialier will fail. + /// - Parameter noteSet: The set of notes + public init?(noteSet: NoteSet) { + var r = NoteSet() + noteSet.forEach { r.add($0.noteClass.canonicalNote) } + + if let info = ChordTable.shared.chords[r.hashValue] { + root = info.root + type = info.type + inversion = Chord.getInversion(noteSet: noteSet, noteClasses: info.noteClasses) + } else { + return nil + } + } + + /// All of the note classes that appear in this chord as a set + public var noteClassSet: NoteClassSet { + let canonicalRoot = root.canonicalNote + var result = NoteClassSet() + + result.add(canonicalRoot.noteClass) + for interval in type.intervals { + result.add(canonicalRoot.shiftUp(interval)!.noteClass) + } + + return result + } + + /// All of the note classes that appear in this chord as an array + public var noteClasses: [NoteClass] { + let canonicalRoot = root.canonicalNote + var result = [canonicalRoot.noteClass] + for interval in type.intervals { + if let shiftedNote = canonicalRoot.shiftUp(interval) { + result.append(shiftedNote.noteClass) + } + } + return result + } + + /// Is this chord made up of three notes? + public var isTriad: Bool { + type.intervals.count == 2 + } + + /// The Roman Numeral notation for a chord, given a key. + /// + /// This initializer will fail if the chord does not appear in the given key. + /// - Parameter key: Key to try to find the chord withing + /// - Returns: Roman Numeral notation + public func romanNumeralNotation(in key: Key) -> String? { + let capitalRomanNumerals = ["I", "II", "III", "IV", "V", "VI", "VII"] + if let index = key.primaryTriads.firstIndex(where: { $0 == self }) { + let romanNumeral = capitalRomanNumerals[index] + switch type { + case .major: return romanNumeral + case .minor: return romanNumeral.lowercased() + case .dim: return "\(romanNumeral.lowercased())°" + default: return nil + } + } + return nil + } + + /// Chord Inversion value - compares the array of noteClasses with a noteSet + /// + /// - Parameter noteSet: Array of chord notes in a chosen order + /// - Parameter noteClasses: Array of noteClasses for a given chord + /// - Returns: inversion integer value + static func getInversion(noteSet: NoteSet, noteClasses: [NoteClass]) -> Int { + if let firstNote = noteSet.array.first { + return noteClasses.firstIndex(of: firstNote.noteClass) ?? 0 + } else { + return 0 + } + } +} + +extension Chord: CustomStringConvertible { + /// Name of chord in concise notation + public var description: String { + return "\(root)\(type)" + } + + /// Name of chord using slash chords + public var slashDescription: String { + if inversion > 0 { + return "\(root)\(type)/\(bassNote)" + } else { + return description + } + } + + /// Name of chord using specialized Chord Symbol Fonts Norfolk or Pori from + /// NotationExpress: https://www.notationcentral.com/product/norfolk-fonts-for-sibelius/ + public var chordFontDescription: String { + return "\(root.chordFontDescription)\(type.chordFontDescription)" + } + + /// Name of chord with slash notation using specialized Chord Symbol Fonts Norfolk or Pori from + /// NotationExpress: https://www.notationcentral.com/product/norfolk-fonts-for-sibelius/ + public var slashChordFontDescription: String { + if inversion > 0 { + return "\(root.chordFontDescription)\(type.chordFontDescription)?\(bassNote.chordFontDescription)" + } else { + return chordFontDescription + } + } + + /// Bass Note computed from inversion and root note + /// Useful for custom rendering of slash notation + public var bassNote: NoteClass { + switch inversion { + case 1...type.intervals.count: + if let bass = root.canonicalNote.shiftUp(type.intervals[inversion - 1]) { + return bass.noteClass + } + default: + break + } + return root.canonicalNote.noteClass + } +} + +extension Chord { + + 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 from a PitchSet, ranked by simplicity of notation + /// - Parameters: + /// - pitchSet: Pitches to be analyzed + /// - allowTheoreticalChords: This algorithim will provide chords with double flats, double sharps, and inergonomic root notes like E# and Cb + public static func getRankedChords(from pitchSet: PitchSet, allowTheoreticalChords: Bool = false) -> [Chord] { + var enharmonicNoteArrays: [[Note]] = [] + var returnArray: [Chord] = [] + for pitch in pitchSet.array { + let octave = pitch.note(in: .C).octave + var noteArray: [Note] = [] + for letter in Letter.allCases { + for accidental in Accidental.allCases { + var intValue = Int(letter.baseNote) + Int(accidental.rawValue) + if intValue > 11 { + intValue -= 12 + } + if intValue < 0 { + intValue += 12 + } + if pitch.midiNoteNumber % 12 == intValue { + noteArray.append(Note(letter, accidental: accidental, octave: octave)) + } + } + } + noteArray.sort { n1, n2 in + abs(n1.accidental.rawValue) < abs(n2.accidental.rawValue) + } + enharmonicNoteArrays.append(noteArray) + } + let chordSearchIntervalArray: [[Interval]] = + [[.M3, .m3], [.P5, .d5], [.M7, .m7], [.M9, .m9, .A9], [.P11, .A11], [.M13, .m13, .A13]] + + var foundNoteArrays: [[Note]] = [] + for enharmonicNoteArray in enharmonicNoteArrays { + for rootNote in enharmonicNoteArray { + var usedNoteArrays: [[Note]] = [enharmonicNoteArray] + var foundNotes: [Note] = [] + foundNotes.append(rootNote) + for nextIntervals in chordSearchIntervalArray { + var foundNote = false + for nextInterval in nextIntervals { + if foundNote { continue } + guard let searchNoteClass = rootNote.shiftUp(nextInterval)?.noteClass else { continue } + for noteArray in enharmonicNoteArrays where !usedNoteArrays.contains(noteArray) { + if noteArray.map({$0.noteClass}).contains(searchNoteClass) { + guard let matchedNote = noteArray.first(where: {$0.noteClass == searchNoteClass}) else { continue } + foundNotes.append(matchedNote) + usedNoteArrays.append(noteArray) + foundNote = true + } + } + } + if foundNotes.count == pitchSet.count { + foundNoteArrays.append(foundNotes) + } + } + } + } + + for foundNoteArray in foundNoteArrays { + let chords = Chord.getRankedChords(from: foundNoteArray) + for chord in chords { + if !returnArray.contains(chord) { + returnArray.append(chord) + } + } + } + +<<<<<<< HEAD + // Sorts anti-alphabetical, but the net effect is to prefer flats to sharps +======= + // Sorts anti-alphabetical, but the net effect is to pefer flats to sharps +>>>>>>> upstream/main + returnArray.sort { $0.root.letter > $1.root.letter } + + // order the array by least number of accidentals + returnArray.sort { $0.accidentalCount < $1.accidentalCount } + + // prefer root notes not being uncommon enharmonics + returnArray.sort { ($0.root.canonicalNote.isUncommonEnharmonic ? 1 : 0) < ($1.root.canonicalNote.isUncommonEnharmonic ? 1 : 0) } + + if !allowTheoreticalChords { + returnArray = returnArray.filter { chord in + !chord.root.accidental.isDouble + } + returnArray = returnArray.filter { chord in + !chord.root.canonicalNote.isUncommonEnharmonic + } + returnArray = returnArray.filter { chord in + !chord.bassNote.canonicalNote.isUncommonEnharmonic + } + returnArray = returnArray.filter { chord in + !chord.bassNote.accidental.isDouble + } + } + +<<<<<<< HEAD + // prefer fewer number of characters (favor less complex chords in ranking) + returnArray.sort { $0.slashDescription.count < $1.slashDescription.count } + + // order the array preferring root position + returnArray.sort { $0.inversion < ($1.inversion > 0 ? 1 : 0) } + + return returnArray + } + + +======= + return returnArray + } + +>>>>>>> upstream/main + /// 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)) + if potentialChords.isEmpty { return [] } + 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 }) + } +} + +extension Chord { + /// Returns all Pitches of a certain chord, taking into account the inversion, starting at the given octave + /// - Parameter octave: octave of the chord for inversion 0 + /// - Returns: All pitches in that Chord + public func pitches(octave: Int) -> [Pitch] { + return notes(octave: octave).map { $0.pitch } + } + + /// Returns all Notes of a certain chord, taking into account the inversion, starting at the given octave + /// - Parameter octave: initial octave of the chord for inversion 0 + /// - Returns: All notes in that chord + public func notes(octave: Int) -> [Note] { + // This array will store all the notes with the correct octaves + var notes: [Note] = [] + // Convert the root note class to a note object + let rootNote = Note(root.letter, accidental: root.accidental, octave: octave) + // append the note to the array of our notes + notes.append(rootNote) + + // Iterate over all intervals + for interval in self.type.intervals { + // Create the next note by using the shiftup function + if let shifted = rootNote.shiftUp(interval) { + notes.append(shifted) + } + } + + // Stores all shifted notes + var shiftedNotes: [Note] = [] + + // Iterate over all inversion steps + for step in 0.. [Chord] { var returnChords: [Chord] = [] @@ -53,7 +55,7 @@ public class ChordTable { let chord = Chord(root, type: chordType) if chord.noteClasses.count <= chord.type.intervals.count { - // chord is not valid + // chord would need to be spelt with triple sharps or flats so we omit continue } @@ -72,6 +74,7 @@ public class ChordTable { /// /// - Parameter noteSet: Array of chord notes in a chosen order /// - Returns: array of enharmonic chords that could describe the NoteSet + @available(*, deprecated, renamed: "getRankedChords", message: "Please use getRankedChords() for higher quality chord detection") public func getAllChordsForNoteSet(_ noteSet: NoteSet) -> [Chord] { var returnedChords = [Chord]() for chord in chordsIncludingEnharmonic { diff --git a/Sources/Tonic/ChordType.swift b/Sources/Tonic/ChordType.swift index fe7a053..d5cf786 100644 --- a/Sources/Tonic/ChordType.swift +++ b/Sources/Tonic/ChordType.swift @@ -3,234 +3,908 @@ import Foundation /// Chord type as defined by a set of intervals from a root note class -public enum ChordType: String, CaseIterable { +/// We use abreviated descriptions with extensions seperated by underscores +/// We don't like usiing underscores for enum cases any more than you do, however is a nice visual speration of extensions +public enum ChordType: String, CaseIterable, Codable { //MARK: - Triads /// Major Triad: Major Third, Perfect Fifth, e.g. `C` - case majorTriad + case major /// Minor Triad: Minor Third, Perfect Fifth, e.g. `Cm` - case minorTriad + case minor /// Diminished Triad: Minor Third, Diminished Fifth, e.g. `C°` - case diminishedTriad + case dim /// Major Flat Five Triad: Major Third, Diminished Fifth, e.g. `C♭5` - case flatFiveTriad + case flat5 /// Augmented Triad: Major Third, Augmented Fifth, e.g. `C⁺` - case augmentedTriad - - /// Suspended 2 Triad: Major Second, Perfect Fifth, e.g. `Csus2` - case suspendedSecondTriad - - /// Suspended 4 Triad: Perfect Fourth, Perfect Fifth, e.g. `Csus4` - case suspendedFourthTriad - - //MARK: - Sixths - /// Major Sixth: Major Third, Perfect Fifth, Major Sixth, e.g. `C6` - case sixth - - /// Minor Sixth: Minor Third, Perfect Fifth, Major Sixth, e.g. `Cm6` - case minorSixth - - /// Major Sixth Suspended Second: Major Second, Perfect Fifth, Major Sixth, e.g. `C6sus2` - case sixthSuspendedSecond - - /// Major Sixth Suspended Fourth: Major Fourth, Perfect Fifth, Major Sixth, e.g. `C6sus4` - case sixthSuspendedFourth - - //MARK: - Sevenths - /// Major Seventh: Major Third, Perfect Fifth, Major Seventh, e.g. `Cmaj7` - case majorSeventh - - /// Dominant Seventh: Major Third, Perfect Fifth, Minor Seventh, e.g. `C7` - case dominantSeventh + case aug + + //MARK: - Sevenths + /// Major Seventh: Major Third, Perfect Fifth, Major Seventh, e.g. `Cmaj7` + case maj7 + + /// Dominant Seventh: Major Third, Perfect Fifth, Minor Seventh, e.g. `C7` + case dom7 - /// Minor Seventh: Minor Third, Perfect Fifth, Minor Seventh, e.g. `Cmin7` - case minorSeventh + /// Minor Seventh: Minor Third, Perfect Fifth, Minor Seventh, e.g. `Cmin7` + case min7 /// Half Diminished Seventh: Minor Third, Diminished Fifth, Minor Seventh, e.g. `Cø7` - case halfDiminishedSeventh + case halfDim7 /// Diminished Seventh: Minor Third, Diminished Fifth, Minor Seventh, e.g. `C°7` - case diminishedSeventh - - /// Dominant Seventh Suspendend Second: Major Second, Perfect Fifth, Minor Seventh, e.g. `C7sus2` - case dominantSeventhSuspendedSecond - - /// Dominant Seventh Suspendend Fourth: Perfect Fourth, Perfect Fifth, Minor Seventh, e.g. `C7sus4` - case dominantSeventhSuspendedFourth - - /// Augmented Major Seventh: Major Third, Augmented Fifth, Major Seventh, e.g. `C+Maj7` - case augmentedMajorSeventh + case dim7 + + /// Major Seventh Sharp Five: Major Third, Augmented Fifth, Major Seventh, e.g. `CMaj7(#5)` + case maj7_sharp5 /// Minor Major Seventh: Minor Third, Perfect Fifth, Major Seventh, e.g. `CmMaj7` - case minorMajorSeventh - - /// Minor Seventh Flat Five: Major Third, Diminished Fifth, Major Seventh, e.g. `Cmaj7(♭5)` - case majorSeventhFlatFive - - /// Dominant Flat Five: Major Third, Diminished Fifth, Minor Seventh, e.g. `C7(♭5)` - case dominantSeventhFlatFive + case min_maj7 + + /// Major Seventh Flat Five: Major Third, Diminished Fifth, Major Seventh, e.g. `Cmaj7(♭5)` + case maj7_flat5 + + /// Dominant Seventh Flat Five: Major Third, Diminished Fifth, Minor Seventh, e.g. `C7(♭5)` + case dom7_flat5 - /// Dominant Sharp Five: Major Third, Augmented Fifth, Minor Seventh, e.g. `C7(♯5)` - case dominantSeventhSharpFive + /// Dominant Sharp Five: Major Third, Augmented Fifth, Minor Seventh, e.g. `C7(♯5)` + case dom7_sharp5 //MARK: - Ninths /// Major Ninth: Major Third, Perfect Fifth, Major Seventh, Major Ninth, e.g. `Cmaj9` - case majorNinth + case maj9 /// Dominant Ninth: Major Third, Perfect Fifth, Minor Seventh, Major Ninth, e.g. `C9` - case dominantNinth + case dom9 /// Minor Ninth: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, e.g. `Cmin9` - case minorNinth + case min9 + + /// Half Diminished Ninth: Minor Third, Diminished Fifth, Minor Seventh, Major Ninth, e.g. `Cø9` + case halfDim9 + + /// Half Diminished Flat Ninth: Minor Third, Diminished Fifth, Minor Seventh, Minor Ninth, e.g. `Cø♭9` + case halfDimFlat9 + + /// Diminished Ninth: Minor Third, Diminished Fifth, Diminished Seventh, Major Ninth, e.g. `C°9` + case dim9 - /// Half Diminished Ninth: Minor Third, Diminished Fifth, Minor Seventh, Minor Ninth, e.g. `Cø9` - case halfDiminishedNinth + /// Diminished Flat Ninth: Minor Third, Diminished Fifth, Diminshed Seventh, Minor Ninth, e.g. `C°♭9` + case dimFlat9 + - /// Dominant Ninth Suspended Fourth: Perfect Fourth, Perfect Fifth, Major Ninth (Major Second), e.g. `C9sus4` - case dominantNinthSuspendedFourth /// Flat Ninth: Major Third, Perfect Fifth, Minor Seventh, Minor Ninth, e.g. `C7♭9` - case dominantFlatNinth + case dom7_flat9 /// Sharp Ninth: Major Third, Perfect Fifth, Minor Seventh, Augmented Ninth, e.g. `C7♯9` - case dominantSharpNinth + case dom7_sharp9 /// Minor Major Ninth: Minor Third, Perfect Fifth, Major Seventh, Major Ninth, e.g. `CmMaj9` - case minorMajorNinth + case min_maj9 + + /// Minor Major Flat Ninth: Minor Third, Perfect Fifth, Major Seventh, Minor Ninth, e.g. `CmMaj(♭9)` + case min_maj_flat9 - /// Minor Flat Ninth: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, e.g. `Cm7♭9` - case minorFlatNinth + /// Minor Seventh Flat Ninth: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, e.g. `Cm7♭9` + case min7_flat9 - /// Major Add Nine: Major Third, Perfect Fifth, Major Ninth, e.g. `Cadd9` - case majorAddNine + /// Major Add Nine: Major Third, Perfect Fifth, Major Ninth, e.g. `C(add9)` + case maj_add9 /// Minor Add Nine: Minor Third, Perfect Fifth, Major Ninth, e.g. `Cm(add9)` - case minorAddNine + case min_add9 + + /// Diminished Add Nine: Minor Third, Diminished Fifth, Major Ninth, e.g. `C°(add9)` + case dim_add9 + + /// Augmented Add Nine: Major Third, Augmented Fifth, Major Ninth, e.g. `C+(add9)` + case aug_add9 + + /// Major Add Flat Nine: Major Third, Perfect Fifth, Minor Ninth, e.g. `C(add♭9)` + case maj_addFlat9 + + /// Minor Add Flat Nine: Minor Third, Perfect Fifth, Minor Minor, e.g. `Cm(add♭9)` + case min_addFlat9 + + /// Diminished Add FlatNine: Minor Third, Diminished Fifth, Minor Ninth, e.g. `C°(add♭9)` + case dim_addFlat9 + + /// Augmented Add Nine: Major Third, Augmented Fifth, Minor Ninth, e.g. `C+(add♭9)` + case aug_addFlat9 + + /// Major Add Sharp Nine: Major Third, Perfect Fifth, Augmented Ninth, e.g. `C(add♯9)` + case maj_addSharp9 + + /// Minor Add Sharp Nine: Minor Third, Perfect Fifth, Augmented Minor, e.g. `Cm(add♯9)` + case min_addSharp9 + + /// Diminished Add Sharp Nine: Minor Third, Diminished Fifth, Augmented Ninth, e.g. `C°(add♯9)` + case dim_addSharp9 + + /// Augmented Add Sharp Nine: Major Third, Augmented Fifth, Augmented Ninth, e.g. `C+(add♯9)` + case aug_addSharp9 /// Six Over Nine: Major Third, Perfect Fifth, Major Sixth, Major Ninth, e.g. `C6/9` - case sixOverNine + case maj_6_9 + + /// Augmented Major Ninth: Major Third, Augmented Fifth, Major Seventh, Major Nine, e.g. `Cmaj9(♯5)` + case maj9_sharp5 - /// Major Ninth Flat Five: Major Third, Diminished Fifth, Major Seventh, Major Nine, e.g. `Cmaj9(♭5) - case majorNinthFlatFive - - /// Major Ninth Sharp Five: Major Third, Augmented Fifth, Major Seventh, Major Nine - case majorNinthSharpFive + /// Major Ninth Flat Five: Major Third, Diminished Fifth, Major Seventh, Major Nine, e.g. `Cmaj9(♭5)` + case maj9_flat5 - /// Dominant Ninth Flat Five: Major Third, Diminished Fifth, Minor Seventh, Major Nine - case dominantNinthFlatFive + /// Dominant Ninth Flat Five: Major Third, Diminished Fifth, Minor Seventh, Major Nine, e.g. `C9(♭5)` + case dom9_flat5 - /// Dominant Ninth Sharp Five: Major Third, Augmented Fifth, Minor Seventh, Major Nine - case dominantNinthSharpFive + /// Dominant Ninth Sharp Five: Major Third, Augmented Fifth, Minor Seventh, Major Nine, e.g. `C9(♯5)` + case dom9_sharp5 + + /// Major Ninth Suspended Fourth: Perfect Fourth, Perfect Fifth, Major Seventh, Major Ninth, e.g. `Cmaj9sus4` + case maj9_sus4 //MARK: - Elevenths /// Major Eleventh: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Perfect Eleventh, e.g. `Cmaj11` - case majorEleventh + case maj11 /// Dominant Eleventh: Major Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, e.g. `C11` - case dominantEleventh + case dom11 /// Minor Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, e.g. `Cm11` - case minorEleventh + case min11 + + /// Half Diminished Eleventh: Minor Third, Diminished Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, e.g. `Cø11` + case halfDim11 + + /// Diminished Ninth: Minor Third, Diminished Fifth, Diminished Seventh, Major Ninth, Perfect Eleventh, e.g. `C°11` + case dim11 + + /// Major Eleventh Flat Five: Major Third, Diminished Fifth, Major Seventh, Major Ninth, Perfect Eleventh, e.g. `Cmaj11(♭5)` + case maj11_flat5 + + /// Major Eleventh Sharp Five: Major Third, Augmented Fifth, Major Seventh, Major Ninth, Perfect Eleventh, e.g. `Cmaj11(♯5)` + case maj11_sharp5 + + /// Dominant Eleventh Flat Five: Major Third, Diminished Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, e.g. `C11(♭5)` + case dom11_flat5 + + /// Dominant Eleventh Sharp Five: Major Third, Augmented Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, e.g. `C11(♯5)` + case dom11_sharp5 + + /// Major Ninth Sharp Eleventh: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Augmented Eleventh, e.g. `Cmaj9(♯11)` + case maj9_sharp11 + + /// Dominant Ninth Sharp Eleventh: Major Third, Perfect Fifth, Minor Seventh, Major Ninth, Augmented Eleventh, e.g. `C9(♯11)` + case dom9_sharp11 + + /// Minor Ninth Sharp Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, Augmented Eleventh, e.g. `Cm9(♯11)` + case min9_sharp11 + + /// Major Ninth Flat Five Sharp Eleventh: Major Third, Diminished Fifth, Major Seventh, Major Ninth, Augmented Eleventh, e.g. `Cmaj9(♭5)(♯11)` + case maj9_flat5_sharp11 + + /// Major Ninth Sharp Five Sharp Eleventh: Major Third, Augmented Fifth, Major Seventh, Major Ninth, Augmented Eleventh, e.g. `Cmaj9(♯5)(♯11)` + case maj9_sharp5_sharp11 + + /// Dominant Ninth Flat Five Sharp Eleventh: Major Third, Diminished Fifth, Minor Seventh, Major Ninth, Augmented Eleventh, e.g. `C9(♭5)(♯11)` + case dom9_flat5_sharp11 + + /// Dominant Ninth Sharp Five Sharp Eleventh: Major Third, Augmented Fifth, Minor Seventh, Major Ninth, Augmented Eleventh, e.g. `C9(♯5)(♯11)` + case dom9_sharp5_sharp11 + + /// Major Ninth Sharp Eleventh: Major Third, Perfect Fifth, Major Seventh, Minor Ninth, Augmented Eleventh, e.g. `Cmaj7♭9(♯11)` + case maj7_flat9_sharp11 + + /// Dominant Flat Ninth Sharp Eleventh: Major Third, Perfect Fifth, Minor Seventh, Minor Ninth, Augmented Eleventh, e.g. `C7♭9(♯11)` + case dom7_flat9_sharp11 + + /// Minor Flat Ninth Sharp Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, Augmented Eleventh, e.g. `Cm7♭9(♯11)` + case min7_flat9_sharp11 + + /// Dominant Sharp Ninth Sharp Eleventh: Major Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Augmented Eleventh, e.g. `C7♯9(♯11)` + case dom7_sharp9_sharp11 + + /// Minor Seventh Flat Ninth Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, e.g. `Cm7♭9(11)` + case min7_flat9_11 + + /// Major Seventh Add Eleventh: Major Third, Perfect Fifth, Major Seventh, Perfect Eleventh, e.g. `Cmaj7(add11)` + case maj7_add11 + + /// Major Seventh Add Sharp Eleventh: Major Third, Perfect Fifth, Major Seventh, Augmented Eleventh, e.g. `Cmaj7(add♯11)` + case maj7_addSharp11 + + /// Dominant Seventh Add Eleventh: Major Third, Perfect Fifth, Minor Seventh, Perfect Eleventh, e.g. `C7(add11)` + case dom7_add11 + + /// Dominant Seventh Add Sharp Eleventh: Major Third, Perfect Fifth, minor Seventh, Augmented Eleventh, e.g. `C7(add♯11)` + case dom7_addSharp11 + + /// Minor Seventh Add Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Perfect Eleventh, e.g. `Cm7(add11)` + case min7_add11 + + /// Minor Seventh Add Sharp Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Augmented Eleventh, e.g. `Cm7(add♯11)` + case min7_addSharp11 + + /// Half Diminished Seventh Add Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Perfect Eleventh, e.g. `Cø7(add11)` + case halfDim7_add11 + + /// Dominant Eleventh Suspended Second: Major Second, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, e.g. `C11sus2` + case dom11_sus2 + + //MARK: - Thirteenths + /// Major Thirteenth: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cmaj13` + case maj13 + + /// Dominant Thirteenth: Major Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13` + case dom13 + + /// Minor Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cm13` + case min13 + + /// Half Diminished Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth e.g. `Cø13` + case halfDim13 + + /// Minor Thirteenth Flat Five: Minor Third, Diminished Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cm13(♭5)` + case min13_flat5 + + /// Major Thirteenth Flat Ninth: Major Third, Perfect Fifth, Major Seventh, Minor Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cmaj13(♭9)` + case maj13_flat9 + + /// Dominant Thirteenth Flat Ninth: Major Third, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13(♭9)` + case dom13_flat9 + + /// Minor Thirteenth Flat Ninth: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cm13(♭9)` + case min13_flat9 + + /// Minor Thirteenth Flat Five Flat Ninth: Minor Third, Diminished Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cm13♭5(♭9)` + case min13_flat5_flat9 + + /// Major Thirteenth Sharp Ninth: Major Third, Perfect Fifth, Major Seventh, Augmented Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cmaj13(♯9)` + case maj13_sharp9 + + /// Dominant Thirteenth Sharp Ninth: Major Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13(♯9)` + case dom13_sharp9 + + /// Minor Thirteenth Sharp Ninth: Minor Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cm13(♯9)` + case min13_sharp9 + + /// Minor Thirteenth Flat Five Sharp Ninth: Minor Third, Diminished Fifth, Minor Seventh, Augmented Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cm13♭5(♯9)` + case min13_flat5_sharp9 + + /// Major Thirteenth Sharp Eleventh: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Augmented Eleventh, Major Thirteenth, e.g. `Cmaj13(♯11)` + case maj13_sharp11 - /// Half Diminished Ninth: Minor Third, Diminished Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, e.g. `Cø11` - case halfDiminishedEleventh + /// Dominant Thirteenth Sharp Eleventh: Major Third, Perfect Fifth, Minor Seventh, Major Ninth, Augmented Eleventh, Major Thirteenth, e.g. `C13(♯11)` + case dom13_sharp11 - /// Major Ninth Sharp Eleventh: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Augmented Eleventh - case majorNinthSharpEleventh + /// Minor Thirteenth Sharp Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, Augmented Eleventh, Major Thirteenth, e.g. `Cm13(♯11)` + case min13_sharp11 - /// Dominant Flat Ninth Sharp Eleventh: Major Third, Perfect Fifth, Minor Seventh, Minor Ninth, Augmented Eleventh - case dominantFlatNinthSharpEleventh + /// Major Seventh Flat Thirteenth: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cmaj7(♭13)` + case maj7_flat13 - /// Dominant Sharp Ninth Sharp Eleventh: Major Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Augmented Eleventh - case dominantSharpNinthSharpEleventh + /// Dominant Seventh Flat Thirteenth: Major Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `C7(♭13)` + case dom7_flat13 - /// Minor Seventh Flat Ninth Add Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh - case minorSeventhFlatNinthAddEleventh + /// Minor Seventh Flat Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cm7(♭13)` + case min7_flat13 - /// Major Thirteenth: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Perfect Eleventh, Perfect Thirteenth - case majorThirteenth + /// Half Diminished Seventh Flat Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cø7(♭13)` + case halfDim7_flat13 - /// Minor Thirteenth: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Perfect Eleventh, Perfect Thirteenth - case minorThirteenth + /// Major Seventh Flat Ninth Flat Thirteenth: Major Third, Perfect Fifth, Major Seventh, Minor Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cmaj7♭9(♭13)` + case maj7_flat9_flat13 - /// Minor Seventh Flat Ninth Add Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Diminished Thirteenth - case minorFlatThirteenthFlatNinth + /// Dominant Seventh Flat Ninth Flat Thirteenth: Major Third, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `C7♭9(♭13)` + case dom7_flat9_flat13 - /// Major Ninth Sharp Eleventh: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Augmented Eleventh, Perfect Thirteenth - case majorThirteenthSharpEleventh + /// Minor Seventh Flat Ninth Flat Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cm7♭9(♭13)` + case min7_flat9_flat13 - /// Dominant Eleventh: Major Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Perfect Thirteenth - case dominantThirteenth + /// Minor Seventh Flat Five Flat Ninth Flat Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cm7♭5♭9(♭13)` + case min7_flat5_flat9_flat13 - /// Minor Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Diminished Thirteenth - case minorEleventhFlatThirteenth + /// Major Seventh Sharp Ninth Flat Thirteenth: Major Third, Perfect Fifth, Major Seventh, Augmented Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cmaj7♯9(♭13)` + case maj7_sharp9_flat13 - /// Half Diminished Ninth: Minor Third, Diminished Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Diminished Thirteenth - case halfDiminishedFlatThirteenth + /// Dominant Seventh Sharp Ninth Flat Thirteenth: Major Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `C7♯9(♭13)` + case dom7_sharp9_flat13 + /// Minor Seventh Sharp Ninth Flat Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cm7♯9(♭13)` + case min7_sharp9_flat13 + + /// Minor Seventh Flat Five Sharp Ninth Flat Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Augmented Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cm7♭5♯9(♭13)` + case min7_flat5_sharp9_flat13 + + /// Major Seventh Flat Ninth Sharp Eleventh Flat Thirteenth: Major Third, Perfect Fifth, Major Seventh, Minor Ninth, Augmented Eleventh, Minor Thirteenth, e.g. `Cmaj7♭9♯11(♭13)` + case maj7_flat9_sharp11_flat13 + + /// Dominant Seventh Flat Ninth Sharp Eleventh Flat Thirteenth: Major Third, Perfect Fifth, Minor Seventh, Minor Ninth, Augmented Eleventh, Minor Thirteenth, e.g. `C7♭9♯11(♭13)` + case dom7_flat9_sharp11_flat13 + + /// Minor Seventh Flat Ninth Sharp Eleventh Flat Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, Augmented Eleventh, Minor Thirteenth, e.g. `Cm7♭9♯11(♭13)` + case min7_flat9_sharp11_flat13 + + /// Minor Seventh Flat Five Flat Ninth Sharp Eleventh Flat Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Minor Ninth, Augmented Eleventh, Minor Thirteenth, e.g. `Cm7♭5♭9♯11(♭13)` + case min7_flat5_flat9_sharp11_flat13 + + /// Major Seventh Sharp Ninth Sharp Eleventh Flat Thirteenth: Major Third, Perfect Fifth, Major Seventh, Augmented Ninth, Augmented Eleventh, Minor Thirteenth, e.g. `Cmaj7♯9♯11(♭13)` + case maj7_sharp9_sharp11_flat13 + + /// Dominant Seventh Sharp Ninth Sharp Eleventh Flat Thirteenth: Major Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Augmented Eleventh, Minor Thirteenth, e.g. `C7♯9♯11(♭13)` + case dom7_sharp9_sharp11_flat13 + + /// Minor Seventh Sharp Ninth Sharp Eleventh Flat Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Augmented Eleventh, Minor Thirteenth, e.g. `Cm7♯9♯11(♭13)` + case min7_sharp9_sharp11_flat13 + + /// Minor Seventh Flat Five Sharp Ninth Sharp Eleventh Flat Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Augmented Ninth, Augmented Eleventh, Minor Thirteenth, e.g. `Cm7♭5♯9♯11(♭13)` + case min7_flat5_sharp9_sharp11_flat13 + + /// Major Seventh Add Thirteenth: Major Third, Perfect Fifth, Major Seventh, Major Thirteenth, e.g. `Cmaj7(add13)` + case maj7_add13 + + /// Dominant Seventh Add Thirteenth: Major Third, Perfect Fifth, Minor Seventh, Major Thirteenth, e.g. `C7(add13)` + case dom7_add13 + + /// Minor Seventh Add Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Major Thirteenth, e.g. `Cm7(add13)` + case min7_add13 + + /// Half Diminished Seventh Add Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Major Thirteenth, e.g. `Cø7(add13)` + case halfDim7_add13 + + /// Major Seventh Add Flat Thirteenth: Major Third, Perfect Fifth, Major Seventh, Minor Thirteenth, e.g. `Cmaj7(add♭13)` + case maj7_addFlat13 + + /// Dominant Seventh Add Flat Thirteenth: Major Third, Perfect Fifth, Minor Seventh, Minor Thirteenth, e.g. `C7(add♭13)` + case dom7_addFlat13 + + /// Minor Seventh Add Flat Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Minor Thirteenth, e.g. `Cm7(add♭13)` + case min7_addFlat13 + + /// Half Diminished Seventh Add Flat Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Minor Thirteenth, e.g. `Cø7(add♭13)` + case halfDim7_addFlat13 + + /// Major Seventh Add Ninth Add Thirteenth: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Major Thirteenth, e.g. `Cmaj7(add9)(add13)` + case maj7_add9_add13 + + /// Major Seventh Add Flat Ninth Add Thirteenth: Major Third, Perfect Fifth, Major Seventh, Minor Ninth, Major Thirteenth, e.g. `Cmaj7(add♭9)(add13)` + case maj7_addFlat9_add13 + + /// Major Seventh Add Flat Ninth Add Flat Thirteenth: Major Third, Perfect Fifth, Major Seventh, Minor Ninth, Minor Thirteenth, e.g. `Cmaj7(add♭9)(add♭13)` + case maj7_addFlat9_addFlat13 + + /// Minor Flat Thirteenth Flat Ninth: Minor Third, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cm♭13♭9` + case min_flat13_flat9 + + /// Minor Eleventh Flat Thirteenth: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cm11(♭13)` + case min11_flat13 + + /// Half Diminished Flat Thirteenth: Minor Third, Diminished Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Minor Thirteenth, e.g. `Cø(♭13)` + case halfDim_flat13 + + // Additional 13th chord types + /// Dominant Thirteenth Flat Five: Major Third, Diminished Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13(♭5)` + case dom13_flat5 + + /// Dominant Thirteenth Sharp Five: Major Third, Augmented Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13(♯5)` + case dom13_sharp5 + + /// Major Thirteenth Flat Five: Major Third, Diminished Fifth, Major Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cmaj13(♭5)` + case maj13_flat5 + + /// Major Thirteenth Sharp Five: Major Third, Augmented Fifth, Major Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cmaj13(♯5)` + case maj13_sharp5 + + /// Dominant Thirteenth Flat Nine Sharp Eleven: Major Third, Perfect Fifth, Minor Seventh, Minor Ninth, Augmented Eleventh, Major Thirteenth, e.g. `C13(♭9)(♯11)` + case dom13_flat9_sharp11 + + /// Dominant Thirteenth Sharp Nine Sharp Eleven: Major Third, Perfect Fifth, Minor Seventh, Augmented Ninth, Augmented Eleventh, Major Thirteenth, e.g. `C13(♯9)(♯11)` + case dom13_sharp9_sharp11 + + /// Major Thirteenth Add Eleventh: Major Third, Perfect Fifth, Major Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cmaj13(add11)` + case maj13_add11 + + /// Dominant Thirteenth Add Eleventh: Major Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13(add11)` + case dom13_add11 + + /// Minor Thirteenth Add Eleventh: Minor Third, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `Cm13(add11)` + case min13_add11 + + //MARK: - Sixths + /// Major Sixth: Major Third, Perfect Fifth, Major Sixth, e.g. `C6` + case maj6 + + /// Minor Sixth: Minor Third, Perfect Fifth, Major Sixth, e.g. `Cm6` + case min6 + + //MARK: - Sus + /// Suspended 2 Triad: Major Second, Perfect Fifth, e.g. `Csus2` + case sus2 + + /// Suspended 4 Triad: Perfect Fourth, Perfect Fifth, e.g. `Csus4` + case sus4 + + //MARK: - Seventh Sus + /// Dominant Seventh Suspendend Second: Major Second, Perfect Fifth, Minor Seventh, e.g. `C7sus2` + case dom7_sus2 + + /// Dominant Seventh Suspendend Fourth: Perfect Fourth, Perfect Fifth, Minor Seventh, e.g. `C7sus4` + case dom7_sus4 + + /// Major Seventh Suspended Second: Major Second, Perfect Fifth, Major Seventh, e.g. `Cmaj7sus2` + case maj7_sus2 + + /// Major Seventh Suspended Fourth: Perfect Fourth, Perfect Fifth, Major Seventh, e.g. `Cmaj7sus4` + case maj7_sus4 + + /// Dominant Ninth Suspended Fourth: Perfect Fourth, Perfect Fifth, Major Ninth (Major Second), e.g. `C9sus4` + case dom9_sus4 + + /// Suspended Fourth Add Nine: Perfect Fourth, Perfect Fifth, Major Ninth, e.g. `Csus4(add9)` + case sus4_add9 + + /// Suspended Second Add Eleven: Major Second, Perfect Fifth, Perfect Eleventh, e.g. `Csus2(add11)` + case sus2_add11 + + /// Suspended Fourth Add Flat Nine: Perfect Fourth, Perfect Fifth, Minor Ninth, e.g. `Csus4(addb9)` + case sus4_addFlat9 + + /// Suspended Fourth Add Sharp Nine: Perfect Fourth, Perfect Fifth, Augmented Ninth, e.g. `Csus4(add#9)` + case sus4_addSharp9 + + /// Suspended Second Add Sharp Eleven: Major Second, Perfect Fifth, Augmented Eleventh, e.g. `Csus2(add#11)` + case sus2_addSharp11 + + /// Major Thirteenth Suspended Second: Major Second, Perfect Fifth, Major Seventh, Perfect Eleventh, Major Thirteenth, e.g. `Cmaj13sus2` + case maj13_sus2 + + /// Major Thirteenth Suspended Fourth: Perfect Fourth, Perfect Fifth, Major Seventh, Major Ninth, Major Thirteenth, e.g. `Cmaj13sus4` + case maj13_sus4 + + /// Dominant Thirteenth Suspended Second: Major Second, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13sus2` + case dom13_sus2 + + /// Dominant Thirteenth Suspended Fourth: Perfect Fourth, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13sus4` + case dom13_sus4 + + /// Dominant Seventh Suspended Fourth Flat Ninth: Perfect Fourth, Perfect Fifth, Minor Seventh, Minor Ninth, e.g. `C7sus4(♭9)` + case dom7_sus4_flat9 + + /// Dominant Seventh Suspended Fourth Sharp Ninth: Perfect Fourth, Perfect Fifth, Minor Seventh, Augmented Ninth, e.g. `C7sus4(♯9)` + case dom7_sus4_sharp9 + + /// Dominant Seventh Suspended Second Sharp Eleventh: Major Second, Perfect Fifth, Minor Seventh, Augmented Eleventh, e.g. `C7sus2(♯11)` + case dom7_sus2_sharp11 + + /// Dominant Seventh Suspended Fourth Flat Thirteenth: Perfect Fourth, Perfect Fifth, Minor Seventh, Minor Thirteenth, e.g. `C7sus4(♭13)` + case dom7_sus4_flat13 + + /// Dominant Seventh Suspended Fourth Sharp Thirteenth: Perfect Fourth, Perfect Fifth, Minor Seventh, Augmented Thirteenth, e.g. `C7sus4(♯13)` + case dom7_sus4_sharp13 + + /// Dominant Ninth Suspended Fourth Flat Thirteenth: Perfect Fourth, Perfect Fifth, Minor Seventh, Major Ninth, Minor Thirteenth, e.g. `C9sus4(♭13)` + case dom9_sus4_flat13 + + /// Dominant Ninth Suspended Fourth Sharp Eleventh: Perfect Fourth, Perfect Fifth, Minor Seventh, Major Ninth, Augmented Eleventh, e.g. `C9sus4(♯11)` + case dom9_sus4_sharp13 + + /// Dominant Eleventh Suspended Second Flat Ninth: Major Second, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, e.g. `C11sus2(♭9)` + case dom11_sus2_flat9 + + /// Dominant Eleventh Suspended Second Sharp Ninth: Major Second, Perfect Fifth, Minor Seventh, Augmented Ninth, Perfect Eleventh, e.g. `C11sus2(♯9)` + case dom11_sus2_flat13 + + /// Dominant Eleventh Suspended Second Sharp Thirteenth: Major Second, Perfect Fifth, Minor Seventh, Major Ninth, Perfect Eleventh, Augmented Thirteenth, e.g. `C11sus2(♯13)` + case dom11_sus2_sharp13 + + /// Dominant Thirteenth Suspended Fourth Flat Ninth: Perfect Fourth, Perfect Fifth, Minor Seventh, Minor Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13sus4(♭9)` + case dom13_sus4_flat9 + + /// Dominant Thirteenth Suspended Fourth Sharp Ninth: Perfect Fourth, Perfect Fifth, Minor Seventh, Augmented Ninth, Perfect Eleventh, Major Thirteenth, e.g. `C13sus4(♯9)` + case dom13_sus4_sharp9 + + /// Dominant Thirteenth Suspended Second Sharp Eleventh: Major Second, Perfect Fifth, Minor Seventh, Major Ninth, Augmented Eleventh, Major Thirteenth, e.g. `C13sus2(♯11)` + case dom13_sus2_sharp11 + + /// Major Seventh Suspended Fourth Flat Ninth: Perfect Fourth, Perfect Fifth, Major Seventh, Minor Ninth, e.g. `Cmaj7sus4(♭9)` + case maj7_sus4_flat9 + + /// Major Seventh Suspended Fourth Sharp Ninth: Perfect Fourth, Perfect Fifth, Major Seventh, Augmented Ninth, e.g. `Cmaj7sus4(♯9)` + case maj7_sus4_sharp9 + + /// Major Seventh Suspended Second Sharp Eleventh: Major Second, Perfect Fifth, Major Seventh, Augmented Eleventh, e.g. `Cmaj7sus2(♯11)` + case maj7_sus2_sharp11 + + /// Major Ninth Suspended Fourth Flat Thirteenth: Perfect Fourth, Perfect Fifth, Major Seventh, Major Ninth, Minor Thirteenth, e.g. `Cmaj9sus4(♭13)` + case maj9_sus4_flat13 + + /// Major Ninth Suspended Fourth Sharp Eleventh: Perfect Fourth, Perfect Fifth, Major Seventh, Major Ninth, Augmented Eleventh, e.g. `Cmaj9sus4(♯11)` + case maj9_sus4_sharp11 + + /// Major Eleventh Suspended Second Flat Ninth: Major Second, Perfect Fifth, Major Seventh, Minor Ninth, Perfect Eleventh, e.g. `Cmaj11sus2(♭9)` + case maj11_sus2_flat9 + + /// Major Eleventh Suspended Second Sharp Ninth: Major Second, Perfect Fifth, Major Seventh, Augmented Ninth, Perfect Eleventh, e.g. `Cmaj11sus2(♯9)` + case maj11_sus2_sharp9 + + /// Suspended 2nd Add Thirteen: Major Second, Perfect Fifth, Major Thirteenth, e.g. `Csus2(add13)` + case sus2_add13 + + /// Suspended 4th Add Thirteen: Major Fourth, Perfect Fifth, Major Sixth, e.g. `Csus4(add13)` + case sus4_add13 + + /// Suspended 2nd Add Flat Thirteen: Major Second, Perfect Fifth, Minor Thirteenth, e.g. `Csus2(add♭13)` + case sus2_addFlat13 + + /// Suspended 4th Add Flat Thirteen: Major Second, Perfect Fifth, Minor Thirteenth, e.g. `Csus4(add♭13)` + case sus4_addFlat13 + + /// Suspended Second Add Sharp Thirteen: Major Second, Perfect Fifth, Augmented Thirteenth, e.g. `Csus2(add#13)` + case sus2_addSharp13 + /// Suspended Fourth Add Sharp Thirteen: Perfect Fourth, Perfect Fifth, Augmented Thirteenth, e.g. `Csus4(add#13)` + case sus4_addSharp13 public var intervals: [Interval] { switch self { - case .majorTriad: return [.M3, .P5] - case .minorTriad: return [.m3, .P5] - case .diminishedTriad: return [.m3, .d5] - case .flatFiveTriad: return [.M3, .d5] - case .augmentedTriad: return [.M3, .A5] - case .suspendedSecondTriad: return [.M2, .P5] - case .suspendedFourthTriad: return [.P4, .P5] - case .sixth: return [.M3, .P5, .M6] - case .minorSixth: return [.m3, .P5, .M6] - case .sixthSuspendedSecond: return [.M2, .P5, .M6] - case .sixthSuspendedFourth: return [.P4, .P5, .M6] - case .halfDiminishedSeventh: return [.m3, .d5, .m7] - case .diminishedSeventh: return [.m3, .d5, .d7] - case .dominantSeventh: return [.M3, .P5, .m7] - case .dominantSeventhSuspendedSecond: return [.M2, .P5, .m7] - case .dominantSeventhSuspendedFourth: return [.P4, .P5, .m7] - case .majorSeventh: return [.M3, .P5, .M7] - case .minorSeventh: return [.m3, .P5, .m7] - case .minorMajorSeventh: return [.m3, .P5, .M7] - case .halfDiminishedNinth: return [.m3, .d5, .m7, .m9] - case .dominantNinth: return [.M3, .P5, .m7, .M9] - case .dominantNinthSuspendedFourth: return [.P4, .P5, .M9] - case .dominantFlatNinth: return [.M3, .P5, .m7, .m9] - case .dominantSharpNinth: return [.M3, .P5, .m7, .A9] - case .majorNinth: return [.M3, .P5, .M7, .M9] - case .minorMajorNinth: return [.m3, .P5, .M7, .M9] - case .minorFlatNinth: return [.m3, .P5, .m7, .m9] - case .minorNinth: return [.m3, .P5, .m7, .M9] - case .majorAddNine: return [.M3, .P5, .M9] - case .minorAddNine: return [.m3, .P5, .M9] - case .sixOverNine: return [.M3, .P5, .M6, .M9] - case .majorEleventh: return [.M3, .P5, .M7, .M9, .P11] - case .dominantEleventh: return [.M3, .P5, .m7, .M9, .P11] - case .minorEleventh: return [.m3, .P5, .m7, .M9, .P11] - case .halfDiminishedEleventh: return [.m3, .d5, .m7, .m9, .P11] - case .majorSeventhFlatFive: return [.M3, .d5, .M7] - case .augmentedMajorSeventh: return [.M3, .A5, .M7] - case .majorNinthSharpEleventh: return [.M3, .P5, .M7, .M9, .A11] - case .dominantFlatNinthSharpEleventh: return [.M3, .P5, .m7, .m9, .A11] - case .dominantSeventhFlatFive: return [.M3, .d5, .m7] - case .dominantSeventhSharpFive: return [.M3, .A5, .m7] - case .dominantSharpNinthSharpEleventh: return [.M3, .P5, .m7, .A9, .A11] - case .minorSeventhFlatNinthAddEleventh: return [.m3, .P5, .m7, .m9, .P11] - case .majorThirteenth: return [.M3, .P5, .M7, .M9, .P11, .M13] - case .minorThirteenth: return [.m3, .P5, .m7, .M9, .P11, .M13] - case .minorFlatThirteenthFlatNinth: return [.m3, .P5, .m7, .m9, .P11, .m13] - case .majorThirteenthSharpEleventh: return [.M3, .P5, .M7, .M9, .A11, .M13] - case .dominantThirteenth: return [.M3, .P5, .m7, .M9, .P11, .M13] - case .minorEleventhFlatThirteenth: return [.m3, .P5, .m7, .M9, .P11, .m13] - case .halfDiminishedFlatThirteenth: return [.m3, .d5, .m7, .m9, .P11, .m13] - case .majorNinthFlatFive: return [.M3, .d5, .M7, .M9] - case .majorNinthSharpFive: return [.M3, .A5, .M7, .M9] - case .dominantNinthFlatFive: return [.M3, .d5, .m7, .M9] - case .dominantNinthSharpFive: return [.M3, .A5, .m7, .M9] - + case .major: + return [.M3, .P5] + case .minor: + return [.m3, .P5] + case .dim: + return [.m3, .d5] + case .flat5: + return [.M3, .d5] + case .aug: + return [.M3, .A5] + case .sus2: + return [.M2, .P5] + case .sus4: + return [.P4, .P5] + case .maj6: + return [.M3, .P5, .M6] + case .min6: + return [.m3, .P5, .M6] + case .sus2_add13: + return [.M2, .P5, .M13] + case .sus4_add13: + return [.P4, .P5, .M13] + case .sus2_addFlat13: + return [.M2, .P5, .m13] + case .sus4_addFlat13: + return [.P4, .P5, .m13] + case .maj7: + return [.M3, .P5, .M7] + case .dom7: + return [.M3, .P5, .m7] + case .min7: + return [.m3, .P5, .m7] + case .halfDim7: + return [.m3, .d5, .m7] + case .dim7: + return [.m3, .d5, .d7] + case .dom7_sus2: + return [.M2, .P5, .m7] + case .dom7_sus4: + return [.P4, .P5, .m7] + case .maj7_sharp5: + return [.M3, .A5, .M7] + case .min_maj7: + return [.m3, .P5, .M7] + case .maj7_flat5: + return [.M3, .d5, .M7] + case .dom7_flat5: + return [.M3, .d5, .m7] + case .dom7_sharp5: + return [.M3, .A5, .m7] + case .maj9: + return [.M3, .P5, .M7, .M9] + case .dom9: + return [.M3, .P5, .m7, .M9] + case .min9: + return [.m3, .P5, .m7, .M9] + case .halfDim9: + return [.m3, .d5, .m7, .M9] + case .halfDimFlat9: + return [.m3, .d5, .m7, .m9] + case .dim9: + return [.m3, .d5, .d7, .M9] + case .dimFlat9: + return [.m3, .d5, .d7, .m9] + case .dom9_sus4: + return [.P4, .P5, .m7, .M9] + case .dom7_flat9: + return [.M3, .P5, .m7, .m9] + case .dom7_sharp9: + return [.M3, .P5, .m7, .A9] + case .min_maj9: + return [.m3, .P5, .M7, .M9] + case .min_maj_flat9: + return [.m3, .P5, .M7, .m9] + case .min7_flat9: + return [.m3, .P5, .m7, .m9] + case .maj_add9: + return [.M3, .P5, .M9] + case .min_add9: + return [.m3, .P5, .M9] + case .dim_add9: + return [.m3, .d5, .M9] + case .aug_add9: + return [.M3, .A5, .M9] + case .maj_addFlat9: + return [.M3, .P5, .m9] + case .min_addFlat9: + return [.m3, .P5, .m9] + case .dim_addFlat9: + return [.m3, .d5, .m9] + case .aug_addFlat9: + return [.M3, .A5, .m9] + case .maj_addSharp9: + return [.M3, .P5, .A9] + case .min_addSharp9: + return [.m3, .P5, .A9] + case .dim_addSharp9: + return [.m3, .d5, .A9] + case .aug_addSharp9: + return [.M3, .A5, .A9] + case .maj_6_9: + return [.M3, .P5, .M6, .M9] + case .maj9_sharp5: + return [.M3, .A5, .M7, .M9] + case .maj9_flat5: + return [.M3, .d5, .M7, .M9] + case .dom9_flat5: + return [.M3, .d5, .m7, .M9] + case .dom9_sharp5: + return [.M3, .A5, .m7, .M9] + case .maj11: + return [.M3, .P5, .M7, .M9, .P11] + case .dom11: + return [.M3, .P5, .m7, .M9, .P11] + case .min11: + return [.m3, .P5, .m7, .M9, .P11] + case .halfDim11: + return [.m3, .d5, .m7, .M9, .P11] + case .dim11: + return [.m3, .d5, .d7, .M9, .P11] + case .maj11_flat5: + return [.M3, .d5, .M7, .M9, .P11] + case .maj11_sharp5: + return [.M3, .A5, .M7, .M9, .P11] + case .dom11_flat5: + return [.M3, .d5, .m7, .M9, .P11] + case .dom11_sharp5: + return [.M3, .A5, .m7, .M9, .P11] + case .maj9_sharp11: + return [.M3, .P5, .M7, .M9, .A11] + case .dom9_sharp11: + return [.M3, .P5, .m7, .M9, .A11] + case .min9_sharp11: + return [.m3, .P5, .m7, .M9, .A11] + case .maj9_flat5_sharp11: + return [.M3, .d5, .M7, .M9, .A11] + case .maj9_sharp5_sharp11: + return [.M3, .A5, .M7, .M9, .A11] + case .dom9_flat5_sharp11: + return [.M3, .d5, .m7, .M9, .A11] + case .dom9_sharp5_sharp11: + return [.M3, .A5, .m7, .M9, .A11] + case .maj7_flat9_sharp11: + return [.M3, .P5, .M7, .m9, .A11] + case .dom7_flat9_sharp11: + return [.M3, .P5, .m7, .m9, .A11] + case .min7_flat9_sharp11: + return [.m3, .P5, .m7, .m9, .A11] + case .dom7_sharp9_sharp11: + return [.M3, .P5, .m7, .A9, .A11] + case .min7_flat9_11: + return [.m3, .P5, .m7, .m9, .P11] + case .maj7_add11: + return [.M3, .P5, .M7, .P11] + case .maj7_addSharp11: + return [.M3, .P5, .M7, .A11] + case .dom7_add11: + return [.M3, .P5, .m7, .P11] + case .dom7_addSharp11: + return [.M3, .P5, .m7, .A11] + case .min7_add11: + return [.m3, .P5, .m7, .P11] + case .min7_addSharp11: + return [.m3, .P5, .m7, .A11] + case .halfDim7_add11: + return [.m3, .d5, .m7, .P11] + case .maj13: + return [.M3, .P5, .M7, .M9, .P11, .M13] + case .dom13: + return [.M3, .P5, .m7, .M9, .P11, .M13] + case .min13: + return [.m3, .P5, .m7, .M9, .P11, .M13] + case .halfDim13: + return [.m3, .d5, .m7, .M9, .P11, .M13] + case .min13_flat5: + return [.m3, .d5, .m7, .M9, .P11, .M13] + case .maj13_flat9: + return [.M3, .P5, .M7, .m9, .P11, .M13] + case .dom13_flat9: + return [.M3, .P5, .m7, .m9, .P11, .M13] + case .min13_flat9: + return [.m3, .P5, .m7, .m9, .P11, .M13] + case .min13_flat5_flat9: + return [.m3, .d5, .m7, .m9, .P11, .M13] + case .maj13_sharp9: + return [.M3, .P5, .M7, .A9, .P11, .M13] + case .dom13_sharp9: + return [.M3, .P5, .m7, .A9, .P11, .M13] + case .min13_sharp9: + return [.m3, .P5, .m7, .A9, .P11, .M13] + case .min13_flat5_sharp9: + return [.m3, .d5, .m7, .A9, .P11, .M13] + case .maj13_sharp11: + return [.M3, .P5, .M7, .M9, .A11, .M13] + case .dom13_sharp11: + return [.M3, .P5, .m7, .M9, .A11, .M13] + case .min13_sharp11: + return [.m3, .P5, .m7, .M9, .A11, .M13] + case .maj7_flat13: + return [.M3, .P5, .M7, .M9, .P11, .m13] + case .dom7_flat13: + return [.M3, .P5, .m7, .M9, .P11, .m13] + case .min7_flat13: + return [.m3, .P5, .m7, .M9, .P11, .m13] + case .halfDim7_flat13: + return [.m3, .d5, .m7, .M9, .P11, .m13] + case .maj7_flat9_flat13: + return [.M3, .P5, .M7, .m9, .P11, .m13] + case .dom7_flat9_flat13: + return [.M3, .P5, .m7, .m9, .P11, .m13] + case .min7_flat9_flat13: + return [.m3, .P5, .m7, .m9, .P11, .m13] + case .min7_flat5_flat9_flat13: + return [.m3, .d5, .m7, .m9, .P11, .m13] + case .maj7_sharp9_flat13: + return [.M3, .P5, .M7, .A9, .P11, .m13] + case .dom7_sharp9_flat13: + return [.M3, .P5, .m7, .A9, .P11, .m13] + case .min7_sharp9_flat13: + return [.m3, .P5, .m7, .A9, .P11, .m13] + case .min7_flat5_sharp9_flat13: + return [.m3, .d5, .m7, .A9, .P11, .m13] + case .maj7_flat9_sharp11_flat13: + return [.M3, .P5, .M7, .m9, .A11, .m13] + case .dom7_flat9_sharp11_flat13: + return [.M3, .P5, .m7, .m9, .A11, .m13] + case .min7_flat9_sharp11_flat13: + return [.m3, .P5, .m7, .m9, .A11, .m13] + case .min7_flat5_flat9_sharp11_flat13: + return [.m3, .d5, .m7, .m9, .A11, .m13] + case .maj7_sharp9_sharp11_flat13: + return [.M3, .P5, .M7, .A9, .A11, .m13] + case .dom7_sharp9_sharp11_flat13: + return [.M3, .P5, .m7, .A9, .A11, .m13] + case .min7_sharp9_sharp11_flat13: + return [.m3, .P5, .m7, .A9, .A11, .m13] + case .min7_flat5_sharp9_sharp11_flat13: + return [.m3, .d5, .m7, .A9, .A11, .m13] + case .maj7_add13: + return [.M3, .P5, .M7, .M13] + case .dom7_add13: + return [.M3, .P5, .m7, .M13] + case .min7_add13: + return [.m3, .P5, .m7, .M13] + case .halfDim7_add13: + return [.m3, .d5, .m7, .M13] + case .maj7_addFlat13: + return [.M3, .P5, .M7, .m13] + case .dom7_addFlat13: + return [.M3, .P5, .m7, .m13] + case .min7_addFlat13: + return [.m3, .P5, .m7, .m13] + case .halfDim7_addFlat13: + return [.m3, .d5, .m7, .m13] + case .maj7_add9_add13: + return [.M3, .P5, .M7, .M9, .M13] + case .maj7_addFlat9_add13: + return [.M3, .P5, .M7, .m9, .M13] + case .maj7_addFlat9_addFlat13: + return [.M3, .P5, .M7, .m9, .m13] + case .min_flat13_flat9: + return [.m3, .P5, .m7, .m9, .P11, .m13] + case .min11_flat13: + return [.m3, .P5, .m7, .M9, .P11, .m13] + case .halfDim_flat13: + return [.m3, .d5, .m7, .M9, .P11, .m13] + case .dom13_flat5: + return [.M3, .d5, .m7, .M9, .P11, .M13] + case .dom13_sharp5: + return [.M3, .A5, .m7, .M9, .P11, .M13] + case .maj13_flat5: + return [.M3, .d5, .M7, .M9, .P11, .M13] + case .maj13_sharp5: + return [.M3, .A5, .M7, .M9, .P11, .M13] + case .dom13_flat9_sharp11: + return [.M3, .P5, .m7, .m9, .A11, .M13] + case .dom13_sharp9_sharp11: + return [.M3, .P5, .m7, .A9, .A11, .M13] + case .maj13_add11: + return [.M3, .P5, .M7, .P11, .M13] + case .dom13_add11: + return [.M3, .P5, .m7, .P11, .M13] + case .min13_add11: + return [.m3, .P5, .m7, .P11, .M13] + case .sus4_add9: + return [.P4, .P5, .M9] + case .sus2_add11: + return [.M2, .P5, .P11] + case .sus4_addFlat9: + return [.P4, .P5, .m9] + case .sus4_addSharp9: + return [.P4, .P5, .A9] + case .sus2_addSharp11: + return [.M2, .P5, .A11] + case .sus2_addSharp13: + return [.M2, .P5, .A13] + case .sus4_addSharp13: + return [.P4, .P5, .A13] + case .maj7_sus2: + return [.M2, .P5, .M7] + case .maj7_sus4: + return [.P4, .P5, .M7] + case .maj9_sus4: + return [.P4, .P5, .M7, .M9] + case .dom11_sus2: + return [.M2, .P5, .m7, .P11] + case .maj13_sus2: + return [.M2, .P5, .M7, .P11, .M13] + case .maj13_sus4: + return [.P4, .P5, .M7, .M9, .M13] + case .dom13_sus2: + return [.M2, .P5, .m7, .P11, .M13] + case .dom13_sus4: + return [.P4, .P5, .m7, .M9, .M13] + case .dom7_sus4_flat9: + return [.P4, .P5, .m7, .m9] + case .dom7_sus4_sharp9: + return [.P4, .P5, .m7, .A9] + case .dom7_sus2_sharp11: + return [.M2, .P5, .m7, .A11] + case .dom7_sus4_flat13: + return [.P4, .P5, .m7, .m13] + case .dom7_sus4_sharp13: + return [.P4, .P5, .m7, .A13] + case .dom9_sus4_flat13: + return [.P4, .P5, .m7, .M9, .m13] + case .dom9_sus4_sharp13: + return [.P4, .P5, .m7, .M9, .A13] + case .dom11_sus2_flat9: + return [.M2, .P5, .m7, .m9, .P11] + case .dom11_sus2_flat13: + return [.M2, .P5, .m7, .M9, .P11, .m13] + case .dom11_sus2_sharp13: + return [.M2, .P5, .m7, .M9, .P11, .A13] + case .dom13_sus4_flat9: + return [.P4, .P5, .m7, .m9, .P11, .M13] + case .dom13_sus4_sharp9: + return [.P4, .P5, .m7, .A9, .P11, .M13] + case .dom13_sus2_sharp11: + return [.M2, .P5, .m7, .M9, .A11, .M13] + case .maj7_sus4_flat9: + return [.P4, .P5, .M7, .m9] + case .maj7_sus4_sharp9: + return [.P4, .P5, .M7, .A9] + case .maj7_sus2_sharp11: + return [.M2, .P5, .M7, .A11] + case .maj9_sus4_flat13: + return [.P4, .P5, .M7, .M9, .m13] + case .maj9_sus4_sharp11: + return [.P4, .P5, .M7, .M9, .A11] + case .maj11_sus2_flat9: + return [.M2, .P5, .M7, .m9, .P11] + case .maj11_sus2_sharp9: + return [.M2, .P5, .M7, .A9, .P11] } } } @@ -239,60 +913,183 @@ extension ChordType: CustomStringConvertible { /// Adornment to the Root NoteClass (letter+accidental) that defines the chord type public var description: String { switch self { - case .majorTriad: return "" - case .minorTriad: return "m" - case .diminishedTriad: return "°" - case .flatFiveTriad: return "♭5" - case .augmentedTriad: return "⁺" - case .suspendedSecondTriad: return "sus2" - case .suspendedFourthTriad: return "sus4" - case .sixth: return "6" - case .minorSixth: return "m6" - case .sixthSuspendedSecond: return "6sus2" - case .sixthSuspendedFourth: return "6sus4" - case .halfDiminishedSeventh: return "ø7" - case .diminishedSeventh: return "°7" - case .dominantSeventh: return "7" - case .dominantSeventhSuspendedSecond: return "7sus2" - case .dominantSeventhSuspendedFourth: return "7sus4" - case .majorSeventh: return "maj7" - case .minorSeventh: return "m7" - case .minorMajorSeventh: return "mMaj7" - case .majorSeventhFlatFive: return "maj7(♭5)" - case .augmentedMajorSeventh: return "maj7(♯5)" - case .dominantSeventhFlatFive: return "7♭5" - case .dominantSeventhSharpFive: return "7♯5" - case .halfDiminishedNinth: return "ø9" - case .dominantNinth: return "9" - case .dominantNinthSuspendedFourth: return "9sus4" - case .dominantFlatNinth: return "7♭9" - case .dominantSharpNinth: return "7♯9" - case .majorNinth: return "maj9" - case .minorFlatNinth: return "m7♭9" - case .minorNinth: return "m9" - case .minorMajorNinth: return "mMaj9" - case .majorAddNine: return "add9" - case .minorAddNine: return "m(add9)" - case .sixOverNine: return "6/9" - case .majorNinthFlatFive: return "maj9(♭5)" - case .majorNinthSharpFive: return "maj9(♯5)" - case .majorEleventh: return "maj11" - case .dominantEleventh: return "11" - case .minorEleventh: return "m11" - case .halfDiminishedEleventh: return "ø11" - case .majorNinthSharpEleventh: return "maj9(♯11)" - case .dominantFlatNinthSharpEleventh: return "7♭9♯11" - case .dominantSharpNinthSharpEleventh: return "7♯9♯11" - case .minorSeventhFlatNinthAddEleventh: return "m7♭9(add11)" - case .majorThirteenth: return "maj13" - case .minorThirteenth: return "m13" - case .minorFlatThirteenthFlatNinth: return "m♭13♭9" - case .majorThirteenthSharpEleventh: return "maj13♯11" - case .dominantThirteenth: return "13" - case .minorEleventhFlatThirteenth: return "m11♭13" - case .halfDiminishedFlatThirteenth: return "ø♭13" - case .dominantNinthFlatFive: return "9♭5" - case .dominantNinthSharpFive: return "9♯5" + case .major: return "" + case .minor: return "m" + case .dim: return "°" + case .flat5: return "(♭5)" + case .aug: return "⁺" + case .sus2: return "sus2" + case .sus4: return "sus4" + case .maj6: return "6" + case .min6: return "m6" + case .sus2_add13: return "sus2(add13)" + case .sus4_add13: return "sus4(add13)" + case .halfDim7: return "ø7" + case .dim7: return "°7" + case .dom7: return "7" + case .dom7_sus2: return "7sus2" + case .dom7_sus4: return "7sus4" + case .maj7: return "maj7" + case .min7: return "m7" + case .min_maj7: return "mMaj7" + case .maj7_flat5: return "maj7(♭5)" + case .maj7_sharp5: return "maj7(♯5)" + case .dom7_flat5: return "7(♭5)" + case .dom7_sharp5: return "7(♯5)" + case .halfDimFlat9: return "ø7(♭9)" + case .dom9: return "9" + case .dom9_sus4: return "9sus4" + case .dom7_flat9: return "7(♭9)" + case .dom7_sharp9: return "7(♯9)" + case .maj9: return "maj9" + case .min7_flat9: return "m7(♭9)" + case .min9: return "m9" + case .min_maj9: return "mMaj9" + case .maj_add9: return "(add9)" + case .min_add9: return "m(add9)" + case .maj_6_9: return "6/9" + case .maj9_flat5: return "maj9(♭5)" + case .maj11: return "maj11" + case .dom11: return "11" + case .min11: return "m11" + case .halfDim11: return "ø11" + case .maj9_sharp11: return "maj9(♯11)" + case .maj9_sharp5: return "maj9(♯5)(♯11)" + case .dom7_flat9_sharp11: return "7(♭9)(♯11)" + case .dom7_sharp9_sharp11: return "7(♯9)(♯11)" + case .min7_flat9_11: return "m7(♭9)(11)" + case .dom9_flat5: return "9(♭5)" + case .dom9_sharp5: return "9(♯5)" + case .dom9_sharp11: return "9(♯11)" + case .sus2_addFlat13: return "sus2(add♭13)" + case .sus4_addFlat13: return "sus4(add♭13)" + case .halfDim9: return "ø9" + case .dim9: return "°9" + case .dimFlat9: return "°(♭9)" + case .min_maj_flat9: return "mMaj(♭9)" + case .dim_add9: return "°(add9)" + case .aug_add9: return "+(add9)" + case .maj_addFlat9: return "(add♭9)" + case .min_addFlat9: return "m(add♭9)" + case .dim_addFlat9: return "°(add♭9)" + case .aug_addFlat9: return "+(add♭9)" + case .maj_addSharp9: return "(add♯9)" + case .min_addSharp9: return "m(add♯9)" + case .dim_addSharp9: return "°(add♯9)" + case .aug_addSharp9: return "+(add♯9)" + case .dim11: return "°11" + case .maj11_flat5: return "maj11(♭5)" + case .maj11_sharp5: return "maj11(♯5)" + case .dom11_flat5: return "11(♭5)" + case .dom11_sharp5: return "11(♯5)" + case .min9_sharp11: return "m9(♯11)" + case .maj9_flat5_sharp11: return "maj9(♭5)(♯11)" + case .maj9_sharp5_sharp11: return "maj9(♯5)(♯11)" + case .dom9_flat5_sharp11: return "9(♭5)(♯11)" + case .dom9_sharp5_sharp11: return "9(♯5)(♯11)" + case .maj7_flat9_sharp11: return "maj7(♭9)(♯11)" + case .min7_flat9_sharp11: return "m7(♭9)(♯11)" + case .maj7_add11: return "maj7(add11)" + case .maj7_addSharp11: return "maj7(add♯11)" + case .dom7_add11: return "7(add11)" + case .dom7_addSharp11: return "7(add♯11)" + case .min7_add11: return "m7(add11)" + case .min7_addSharp11: return "m7(add♯11)" + case .halfDim7_add11: return "ø7(add11)" + case .maj13: return "maj13" + case .dom13: return "13" + case .min13: return "m13" + case .halfDim13: return "ø13" + case .min13_flat5: return "m13(♭5)" + case .maj13_flat9: return "maj13(♭9)" + case .dom13_flat9: return "13(♭9)" + case .min13_flat9: return "m13(♭9)" + case .min13_flat5_flat9: return "m13(♭5)(♭9)" + case .maj13_sharp9: return "maj13(♯9)" + case .dom13_sharp9: return "13(♯9)" + case .min13_sharp9: return "m13(♯9)" + case .min13_flat5_sharp9: return "m13(♭5)(♯9)" + case .maj13_sharp11: return "maj13(♯11)" + case .dom13_sharp11: return "13(♯11)" + case .min13_sharp11: return "m13(♯11)" + case .maj7_flat13: return "maj7(♭13)" + case .dom7_flat13: return "7(♭13)" + case .min7_flat13: return "m7(♭13)" + case .halfDim7_flat13: return "ø7(♭13)" + case .maj7_flat9_flat13: return "maj7(♭9)(♭13)" + case .dom7_flat9_flat13: return "7(♭9)(♭13)" + case .min7_flat9_flat13: return "m7(♭9)(♭13)" + case .min7_flat5_flat9_flat13: return "ø7(♭5)(♭9)(♭13)" + case .maj7_sharp9_flat13: return "maj7(♯9)(♭13)" + case .dom7_sharp9_flat13: return "7(♯9)(♭13)" + case .min7_sharp9_flat13: return "m7(♯9)(♭13)" + case .min7_flat5_sharp9_flat13: return "ø7(♭5)(♯9)(♭13)" + case .maj7_flat9_sharp11_flat13: return "maj7(♭9)(♯11)(♭13)" + case .dom7_flat9_sharp11_flat13: return "7(♭9)(♯11)(♭13)" + case .min7_flat9_sharp11_flat13: return "m7(♭9)(♯11)(♭13)" + case .min7_flat5_flat9_sharp11_flat13: return "ø7(♭5)(♭9)(♯11)(♭13)" + case .maj7_sharp9_sharp11_flat13: return "maj7(♯9)(♯11)(♭13)" + case .dom7_sharp9_sharp11_flat13: return "7(♯9)(♯11)(♭13)" + case .min7_sharp9_sharp11_flat13: return "m7(♯9)(♯11)(♭13)" + case .min7_flat5_sharp9_sharp11_flat13: return "ø7(♭5)(♯9)(♯11)(♭13)" + case .maj7_add13: return "maj7(add13)" + case .dom7_add13: return "7(add13)" + case .min7_add13: return "m7(add13)" + case .halfDim7_add13: return "ø7(add13)" + case .maj7_addFlat13: return "maj7(add♭13)" + case .dom7_addFlat13: return "7(add♭13)" + case .min7_addFlat13: return "m7(add♭13)" + case .halfDim7_addFlat13: return "ø7(add♭13)" + case .maj7_add9_add13: return "maj7(add9)(add13)" + case .maj7_addFlat9_add13: return "maj7(add♭9)(add13)" + case .maj7_addFlat9_addFlat13: return "maj7(add♭9)(add♭13)" + case .min_flat13_flat9: return "m(♭13)(♭9)" + case .min11_flat13: return "m11(♭13)" + case .halfDim_flat13: return "ø(♭13)" + case .dom13_flat5: return "13(♭5)" + case .dom13_sharp5: return "13(♯5)" + case .maj13_flat5: return "maj13(♭5)" + case .maj13_sharp5: return "maj13(♯5)" + case .dom13_flat9_sharp11: return "13(♭9)(♯11)" + case .dom13_sharp9_sharp11: return "13(♯9)(♯11)" + case .maj13_add11: return "maj13(add11)" + case .dom13_add11: return "13(add11)" + case .min13_add11: return "m13(add11)" + case .sus4_add9: return "sus4(add9)" + case .sus2_add11: return "sus2(add11)" + case .sus4_addFlat9: return "sus4(add♭9)" + case .sus4_addSharp9: return "sus4(add♯9)" + case .sus2_addSharp11: return "sus2(add♯11)" + case .sus2_addSharp13: return "sus2(add♯13)" + case .sus4_addSharp13: return "sus4(add♯13)" + case .maj7_sus2: return "maj7sus2" + case .maj7_sus4: return "maj7sus4" + case .maj9_sus4: return "maj9sus4" + case .dom11_sus2: return "11sus2" + case .maj13_sus2: return "maj13sus2" + case .maj13_sus4: return "maj13sus4" + case .dom13_sus2: return "13sus2" + case .dom13_sus4: return "13sus4" + case .dom7_sus4_flat9: return "7sus4(♭9)" + case .dom7_sus4_sharp9: return "7sus4(♯9)" + case .dom7_sus2_sharp11: return "7sus2(♯11)" + case .dom7_sus4_flat13: return "7sus4(♭13)" + case .dom7_sus4_sharp13: return "7sus4(♯13)" + case .dom9_sus4_flat13: return "9sus4(♭13)" + case .dom9_sus4_sharp13: return "9sus4(♯13)" + case .dom11_sus2_flat9: return "11sus2(♭9)" + case .dom11_sus2_flat13: return "11sus2(♭13)" + case .dom11_sus2_sharp13: return "11sus2(♯13)" + case .dom13_sus4_flat9: return "13sus4(♭9)" + case .dom13_sus4_sharp9: return "13sus4(♯9)" + case .dom13_sus2_sharp11: return "13sus2(♯11)" + case .maj7_sus4_flat9: return "maj7sus4(♭9)" + case .maj7_sus4_sharp9: return "maj7sus4(♯9)" + case .maj7_sus2_sharp11: return "maj7sus2(♯11)" + case .maj9_sus4_flat13: return "maj9sus4(♭13)" + case .maj9_sus4_sharp11: return "maj9sus4(♯11)" + case .maj11_sus2_flat9: return "maj11sus2(♭9)" + case .maj11_sus2_sharp9: return "maj11sus2(♯9)" } } @@ -301,60 +1098,183 @@ extension ChordType: CustomStringConvertible { /// NotationExpress: https://www.notationcentral.com/product/norfolk-fonts-for-sibelius/ public var chordFontDescription: String { switch self { - case .majorTriad: return "" - case .minorTriad: return "m" - case .diminishedTriad: return "º" - case .flatFiveTriad: return "b5" - case .augmentedTriad: return "&" - case .suspendedSecondTriad: return "“2" - case .suspendedFourthTriad: return "“4" - case .sixth: return "6" - case .minorSixth: return "m6" - case .sixthSuspendedSecond: return "6sus2" - case .sixthSuspendedFourth: return "6sus4" - case .halfDiminishedSeventh: return "Ø7" - case .diminishedSeventh: return "º7" - case .dominantSeventh: return "7" - case .dominantSeventhSuspendedSecond: return "7sus2" - case .dominantSeventhSuspendedFourth: return "7sus4" - case .majorSeventh: return "^7" - case .minorSeventh: return "m7" - case .minorMajorSeventh: return "m^7" - case .majorSeventhFlatFive: return "^7b5" - case .augmentedMajorSeventh: return "^7#5" - case .halfDiminishedNinth: return "Ø9" - case .dominantNinth: return "9" - case .dominantNinthSuspendedFourth: return "9sus4" - case .dominantFlatNinth: return "7b9" - case .dominantSharpNinth: return "7#9" - case .majorNinth: return "^9" - case .minorMajorNinth: return "m^9" - case .minorFlatNinth: return "m7b9" - case .minorNinth: return "m9" - case .majorAddNine: return "@9" - case .minorAddNine: return "m@9" - case .sixOverNine: return "%" - case .majorEleventh: return "^11" - case .dominantEleventh: return "11" - case .minorEleventh: return "m11" - case .halfDiminishedEleventh: return "Ø11" - case .majorNinthSharpEleventh: return "^9#11" - case .dominantSeventhFlatFive: return "7b5" - case .dominantSeventhSharpFive: return "7#5" - case .dominantFlatNinthSharpEleventh: return "7âÅ" - case .dominantSharpNinthSharpEleventh: return "7åÅ" - case .minorSeventhFlatNinthAddEleventh: return "m7b9(@11)" - case .majorThirteenth: return "^13" - case .minorThirteenth: return "m13" - case .minorFlatThirteenthFlatNinth: return "máÆ" - case .majorThirteenthSharpEleventh: return "^13#11" - case .dominantThirteenth: return "13" - case .minorEleventhFlatThirteenth: return "m11b13" - case .halfDiminishedFlatThirteenth: return "Øb13" - case .majorNinthFlatFive: return "^9b5" - case .majorNinthSharpFive: return "^9#5" - case .dominantNinthFlatFive: return "9b5" - case .dominantNinthSharpFive: return "9#5" + case .major: return "" + case .minor: return "m" + case .dim: return "º" + case .flat5: return "b5" + case .aug: return "&" + case .sus2: return "“2" + case .sus4: return "“4" + case .maj6: return "6" + case .min6: return "m6" + case .sus2_add13: return "6“2" + case .sus4_add13: return "6“4" + case .halfDim7: return "Ø7" + case .dim7: return "º7" + case .dom7: return "7" + case .dom7_sus2: return "7“2" + case .dom7_sus4: return "7“4" + case .maj7: return "^7" + case .min7: return "m7" + case .min_maj7: return "m^7" + case .maj7_flat5: return "^7b5" + case .maj7_sharp5: return "^7#5" + case .dom7_flat5: return "7b5" + case .dom7_sharp5: return "7#5" + case .halfDimFlat9: return "Ø9" + case .dom9: return "9" + case .dom9_sus4: return "9“4" + case .dom7_flat9: return "7b9" + case .dom7_sharp9: return "7#9" + case .maj9: return "^9" + case .min7_flat9: return "m7b9" + case .min9: return "m9" + case .min_maj9: return "m^9" + case .maj_add9: return "@9" + case .min_add9: return "m@9" + case .maj_6_9: return "%" + case .maj9_flat5: return "^9b5" + case .maj11: return "^11" + case .dom11: return "11" + case .min11: return "m11" + case .halfDim11: return "Ø11" + case .maj9_sharp11: return "^9#11" + case .maj9_sharp5: return "^9#5" + case .dom7_flat9_sharp11: return "7âÅ" + case .dom7_sharp9_sharp11: return "7åÅ" + case .min7_flat9_11: return "m7b9(11)" + case .dom9_flat5: return "9b5" + case .dom9_sharp5: return "9#5" + case .dom9_sharp11: return "9#11" + case .sus2_addFlat13: return "“2b13" + case .sus4_addFlat13: return "“4b13" + case .halfDim9: return "Ø9" + case .dim9: return "º9" + case .dimFlat9: return "ºb9" + case .min_maj_flat9: return "m^b9" + case .dim_add9: return "º@9" + case .aug_add9: return "&@9" + case .maj_addFlat9: return "@b9" + case .min_addFlat9: return "m@b9" + case .dim_addFlat9: return "º@b9" + case .aug_addFlat9: return "&@b9" + case .maj_addSharp9: return "@#9" + case .min_addSharp9: return "m@#9" + case .dim_addSharp9: return "º@#9" + case .aug_addSharp9: return "&@#9" + case .dim11: return "º11" + case .maj11_flat5: return "^11b5" + case .maj11_sharp5: return "^11#5" + case .dom11_flat5: return "11b5" + case .dom11_sharp5: return "11#5" + case .min9_sharp11: return "m9#11" + case .maj9_flat5_sharp11: return "^9b5#11" + case .maj9_sharp5_sharp11: return "^9#5#11" + case .dom9_flat5_sharp11: return "9b5#11" + case .dom9_sharp5_sharp11: return "9#5#11" + case .maj7_flat9_sharp11: return "^7b9#11" + case .min7_flat9_sharp11: return "m7b9#11" + case .maj7_add11: return "^7@11" + case .maj7_addSharp11: return "^7@#11" + case .dom7_add11: return "7@11" + case .dom7_addSharp11: return "7@#11" + case .min7_add11: return "m7@11" + case .min7_addSharp11: return "m7@#11" + case .halfDim7_add11: return "Ø7@11" + case .maj13: return "^13" + case .dom13: return "13" + case .min13: return "m13" + case .halfDim13: return "Ø13" + case .min13_flat5: return "m13b5" + case .maj13_flat9: return "^13b9" + case .dom13_flat9: return "13b9" + case .min13_flat9: return "m13b9" + case .min13_flat5_flat9: return "m13b5b9" + case .maj13_sharp9: return "^13#9" + case .dom13_sharp9: return "13#9" + case .min13_sharp9: return "m13#9" + case .min13_flat5_sharp9: return "m13b5#9" + case .maj13_sharp11: return "^13#11" + case .dom13_sharp11: return "13#11" + case .min13_sharp11: return "m13#11" + case .maj7_flat13: return "^7b13" + case .dom7_flat13: return "7b13" + case .min7_flat13: return "m7b13" + case .halfDim7_flat13: return "Ø7b13" + case .maj7_flat9_flat13: return "^7b9b13" + case .dom7_flat9_flat13: return "7b9b13" + case .min7_flat9_flat13: return "m7b9b13" + case .min7_flat5_flat9_flat13: return "Ø7b9b13" + case .maj7_sharp9_flat13: return "^7#9b13" + case .dom7_sharp9_flat13: return "7#9b13" + case .min7_sharp9_flat13: return "m7#9b13" + case .min7_flat5_sharp9_flat13: return "Ø7#9b13" + case .maj7_flat9_sharp11_flat13: return "^7b9#11b13" + case .dom7_flat9_sharp11_flat13: return "7b9#11b13" + case .min7_flat9_sharp11_flat13: return "m7b9#11b13" + case .min7_flat5_flat9_sharp11_flat13: return "Ø7b9#11b13" + case .maj7_sharp9_sharp11_flat13: return "^7#9#11b13" + case .dom7_sharp9_sharp11_flat13: return "7#9#11b13" + case .min7_sharp9_sharp11_flat13: return "m7#9#11b13" + case .min7_flat5_sharp9_sharp11_flat13: return "Ø7#9#11b13" + case .maj7_add13: return "^7@13" + case .dom7_add13: return "7@13" + case .min7_add13: return "m7@13" + case .halfDim7_add13: return "Ø7@13" + case .maj7_addFlat13: return "^7@b13" + case .dom7_addFlat13: return "7@b13" + case .min7_addFlat13: return "m7@b13" + case .halfDim7_addFlat13: return "Ø7@b13" + case .maj7_add9_add13: return "^7@9@13" + case .maj7_addFlat9_add13: return "^7@b9@13" + case .maj7_addFlat9_addFlat13: return "^7@b9@b13" + case .min_flat13_flat9: return "mb13b9" + case .min11_flat13: return "m11b13" + case .halfDim_flat13: return "Øb13" + case .dom13_flat5: return "13b5" + case .dom13_sharp5: return "13#5" + case .maj13_flat5: return "^13b5" + case .maj13_sharp5: return "^13#5" + case .dom13_flat9_sharp11: return "13b9#11" + case .dom13_sharp9_sharp11: return "13#9#11" + case .maj13_add11: return "^13@11" + case .dom13_add11: return "13@11" + case .min13_add11: return "m13@11" + case .sus4_add9: return "“4@9" + case .sus2_add11: return "“2@11" + case .sus4_addFlat9: return "“4@b9" + case .sus4_addSharp9: return "“4@#9" + case .sus2_addSharp11: return "“2@#11" + case .sus2_addSharp13: return "“2@#13" + case .sus4_addSharp13: return "“4@#13" + case .maj7_sus2: return "^7“2" + case .maj7_sus4: return "^7“4" + case .maj9_sus4: return "^9“4" + case .dom11_sus2: return "11“2" + case .maj13_sus2: return "^13“2" + case .maj13_sus4: return "^13“4" + case .dom13_sus2: return "13“2" + case .dom13_sus4: return "13“4" + case .dom7_sus4_flat9: return "7“4b9" + case .dom7_sus4_sharp9: return "7“4#9" + case .dom7_sus2_sharp11: return "7“2#11" + case .dom7_sus4_flat13: return "7“4b13" + case .dom7_sus4_sharp13: return "7“4#13" + case .dom9_sus4_flat13: return "9“4b13" + case .dom9_sus4_sharp13: return "9“4#13" + case .dom11_sus2_flat9: return "11“2b9" + case .dom11_sus2_flat13: return "11“2b13" + case .dom11_sus2_sharp13: return "11“2#13" + case .dom13_sus4_flat9: return "13“4b9" + case .dom13_sus4_sharp9: return "13“4#9" + case .dom13_sus2_sharp11: return "13“2#11" + case .maj7_sus4_flat9: return "^7“4b9" + case .maj7_sus4_sharp9: return "^7“4#9" + case .maj7_sus2_sharp11: return "^7“2#11" + case .maj9_sus4_flat13: return "^9“4b13" + case .maj9_sus4_sharp11: return "^9“4#11" + case .maj11_sus2_flat9: return "^11“2b9" + case .maj11_sus2_sharp9: return "^11“2#9" } } } diff --git a/Sources/Tonic/Key.swift b/Sources/Tonic/Key.swift index 7ccca44..3337384 100644 --- a/Sources/Tonic/Key.swift +++ b/Sources/Tonic/Key.swift @@ -42,7 +42,7 @@ public struct Key: Equatable { var chords: [Chord] = [] var primaryTriads: [Chord] = [] - let allowablePrimaryTriads: [ChordType] = [.majorTriad, .minorTriad, .diminishedTriad, .augmentedTriad] + let allowablePrimaryTriads: [ChordType] = [.major, .minor, .dim, .aug] for (_, chord) in table.chords where chord.noteClassSet.isSubset(of: noteSet.noteClassSet) { chords.append(Chord(chord.root, type: chord.type)) diff --git a/Sources/Tonic/Note.swift b/Sources/Tonic/Note.swift index 4e859fb..d46b999 100644 --- a/Sources/Tonic/Note.swift +++ b/Sources/Tonic/Note.swift @@ -4,7 +4,6 @@ import Foundation /// A pitch with a particular spelling. public struct Note: Equatable, Hashable, Codable { - /// Base name for the note public var noteClass: NoteClass = .init(.C, accidental: .natural) @@ -14,11 +13,8 @@ public struct Note: Equatable, Hashable, Codable { /// Convenience accessor for the accidental of the note public var accidental: Accidental { noteClass.accidental } - /// Range from -2 to 8, with a dependency on the note style - public var octave: Int = Note.MiddleCStandard.yamaha.middleCNumber - - /// yamaha is the default for Logic Pro - public var middleCStandard: Note.MiddleCStandard = .yamaha + /// Range from -2 to 8 + public var octave: Int = 3 /// Initialize the note /// @@ -28,7 +24,7 @@ public struct Note: Equatable, Hashable, Codable { /// - letter: Letter of the note /// - accidental: Accidental shift /// - octave: Which octave the note appears in - public init(_ letter: Letter = .C, accidental: Accidental = .natural, octave: Int = Note.MiddleCStandard.yamaha.middleCNumber) { + public init(_ letter: Letter = .C, accidental: Accidental = .natural, octave: Int = 3) { noteClass = NoteClass(letter, accidental: accidental) self.octave = octave } @@ -40,8 +36,7 @@ public struct Note: Equatable, Hashable, Codable { /// - pitch: Pitch, or essentially the midi note number of a note /// - key: Key in which to search for the appropriate note public init(pitch: Pitch, key: Key = .C) { - octave = Int(Double(pitch.midiNoteNumber) / 12) + middleCStandard.firstOctaveOffset - + octave = Int(Double(pitch.midiNoteNumber) / 12) - 2 let pitchClass = pitch.pitchClass var noteInKey: Note? @@ -79,7 +74,7 @@ public struct Note: Equatable, Hashable, Codable { /// Initialize from raw value /// - Parameter index: integer represetnation public init(index: Int) { - octave = (index / 35) + middleCStandard.firstOctaveOffset + octave = (index / 35) - 2 let letter = Letter(rawValue: (index % 35) / 5)! let accidental = Accidental(rawValue: Int8(index % 5) - 2)! noteClass = NoteClass(letter, accidental: accidental) @@ -87,7 +82,7 @@ public struct Note: Equatable, Hashable, Codable { /// MIDI Note 0-127 starting at C public var noteNumber: Int8 { - let octaveBounds = ((octave + middleCStandard.middleCNumber - 1) * 12) ... ((octave + middleCStandard.middleCNumber) * 12) + let octaveBounds = ((octave + 2) * 12) ... ((octave + 3) * 12) var note = Int(noteClass.letter.baseNote) + Int(noteClass.accidental.rawValue) if noteClass.letter == .B && noteClass.accidental.rawValue > 0 { note -= 12 @@ -123,7 +118,7 @@ public struct Note: Equatable, Hashable, Codable { /// - Returns: New note the correct distance away public func shiftDown(_ shift: Interval) -> Note? { var newLetterIndex = (noteClass.letter.rawValue - (shift.degree - 1)) - let newOctave = (Int(pitch.midiNoteNumber) - shift.semitones) / 12 + middleCStandard.firstOctaveOffset + let newOctave = (Int(pitch.midiNoteNumber) - shift.semitones) / 12 - 2 while newLetterIndex < 0 { newLetterIndex += 7 @@ -146,8 +141,9 @@ public struct Note: Equatable, Hashable, Codable { let newLetterIndex = (noteClass.letter.rawValue + (shift.degree - 1)) let newLetter = Letter(rawValue: newLetterIndex % Letter.allCases.count)! let newMidiNoteNumber = Int(pitch.midiNoteNumber) + shift.semitones + + let newOctave = newMidiNoteNumber / 12 - 2 - let newOctave = (newMidiNoteNumber / 12) + middleCStandard.firstOctaveOffset for accidental in Accidental.allCases { let newNote = Note(newLetter, accidental: accidental, octave: newOctave) if newNote.noteNumber % 12 == newMidiNoteNumber % 12 { @@ -169,7 +165,7 @@ extension Note: IntRepresentable { let accidentalCount = Accidental.allCases.count let letterCount = Letter.allCases.count let octaveCount = letterCount * accidentalCount - octave = (intValue / octaveCount) + middleCStandard.firstOctaveOffset + octave = (intValue / octaveCount) - 2 var letter = Letter(rawValue: (intValue % octaveCount) / accidentalCount)! var accidental = Accidental(rawValue: Int8(intValue % accidentalCount) - 2)! @@ -190,15 +186,15 @@ extension Note: IntRepresentable { var index = noteClass.letter.rawValue * accidentalCount + (Int(noteClass.accidental.rawValue) + 2) if letter == .B { - if accidental == .sharp { index = 0 } - if accidental == .doubleSharp { index = 1 } + if accidental == .sharp { index = 0} + if accidental == .doubleSharp { index = 1} } if letter == .C { - if accidental == .doubleFlat { index = octaveCount - 2 } - if accidental == .flat { index = octaveCount - 1 } + if accidental == .doubleFlat { index = octaveCount - 2} + if accidental == .flat { index = octaveCount - 1} } - return (octave + middleCStandard.middleCNumber - 1) * octaveCount + index + return (octave + 2) * octaveCount + index } } diff --git a/Sources/Tonic/Octave.swift b/Sources/Tonic/Octave.swift index e81ad8e..472f1a2 100644 --- a/Sources/Tonic/Octave.swift +++ b/Sources/Tonic/Octave.swift @@ -16,7 +16,7 @@ enum Octave: Int { case eight = 8 case nine = 9 - init?(of pitch:Pitch, style: Note.MiddleCStandard = .roland ) { + init?(of pitch:Pitch, style: Note.MiddleCStandard = .yamaha ) { let octaveInt = Int(pitch.midiNoteNumber) / 12 + style.firstOctaveOffset if let octave = Octave(rawValue: octaveInt) { self = octave diff --git a/Tests/TonicTests/ChordTests.swift b/Tests/TonicTests/ChordTests.swift index e4f7e9e..145b9e3 100644 --- a/Tests/TonicTests/ChordTests.swift +++ b/Tests/TonicTests/ChordTests.swift @@ -3,6 +3,26 @@ import XCTest class ChordTests: XCTestCase { + // MARK: - Helper Functions + func assertChord(_ notes: [Note], expectedType: ChordType, expectedRoot: NoteClass, file: StaticString = #file, line: UInt = #line) { + let chord = Chord(notes: notes) + XCTAssertNotNil(chord, "Chord should not be nil", file: file, line: line) + XCTAssertEqual(chord?.type, expectedType, "Chord type should match", file: file, line: line) + XCTAssertEqual(chord?.root, expectedRoot, "Chord root should match", file: file, line: line) + } + + func assertChordDescription(_ notes: [Note], expected: String, file: StaticString = #file, line: UInt = #line) { + let chord = Chord(notes: notes) + XCTAssertEqual(chord?.description, expected, "Chord description should match", file: file, line: line) + } + + func assertRankedChord(_ pitches: [Int8], expectedDescriptions: [String], file: StaticString = #file, line: UInt = #line) { + let pitchSet = PitchSet(pitches: pitches.map { Pitch($0) }) + let chords = Chord.getRankedChords(from: pitchSet) + let descriptions = chords.map { $0.slashDescription } + XCTAssertEqual(descriptions, expectedDescriptions, "Ranked chord descriptions should match", file: file, line: line) + } + func testChords() { XCTAssertTrue(Chord.C.isTriad) XCTAssertEqual(Chord.Cs.description, "C♯") @@ -15,7 +35,7 @@ class ChordTests: XCTestCase { XCTAssertEqual(Chord.Caug.description, "C⁺") XCTAssertEqual(Chord.Aaug.description, "A⁺") - XCTAssertEqual(Chord(.Db, type: .augmentedTriad).description, "D♭⁺") + XCTAssertEqual(Chord(.Db, type: .aug).description, "D♭⁺") XCTAssertEqual(Chord.Asus4.description, "Asus4") XCTAssertEqual(Chord.Bsus4.description, "Bsus4") @@ -30,21 +50,21 @@ class ChordTests: XCTestCase { let notes: [Int8] = [60, 64, 66] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(chord.map { $0.slashDescription }, ["C♭5"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["C(♭5)"]) } func testDominantSeventhFlatFive() { let notes: [Int8] = [60, 64, 66, 70] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(chord.map { $0.slashDescription }, ["C7♭5", "F♯7♭5/C"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["C7(♭5)", "F♯7(♭5)/C"]) } func testMajorSeventhFlatFive() { let notes: [Int8] = [60, 64, 66, 71] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(chord.map { $0.slashDescription }, ["Cmaj7(♭5)"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["Cmaj7(♭5)", "Bsus4(add♭9)/C", "Esus2(add♭13)/C"]) } func testMajorNinthFlatFive() { @@ -58,25 +78,26 @@ class ChordTests: XCTestCase { let notes: [Int8] = [60, 64, 68, 71, 74] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(chord.map { $0.slashDescription }, ["Cmaj9(♯5)"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["Cmaj9(♯5)(♯11)", + "E7(add♭13)/C"]) } func testDominantNinthFlatFive() { let notes: [Int8] = [60, 64, 66, 70, 74] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(chord.map { $0.slashDescription }, ["C9♭5", "D9♯5/C"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["C9(♭5)", "D9(♯5)/C"]) } func testDominantNinthSharpFive() { let notes: [Int8] = [60, 64, 68, 70, 74] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(chord.map { $0.slashDescription }, ["C9♯5", "B♭9♭5/C"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["C9(♯5)", "B♭9(♭5)/C"]) } func test7() { - XCTAssertEqual(Chord(.C, type: .dominantSeventh).description, "C7") + XCTAssertEqual(Chord(.C, type: .dom7).description, "C7") let notes: [Int8] = [60, 67, 70, 76] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let c7 = Chord.getRankedChords(from: pitchSet) @@ -84,7 +105,7 @@ class ChordTests: XCTestCase { } func testTheortical() { - XCTAssertEqual(Chord(.C, type: .dominantSeventh).description, "C7") + XCTAssertEqual(Chord(.C, type: .dom7).description, "C7") let notes: [Int8] = [60, 67, 70, 76] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let c7 = Chord.getRankedChords(from: pitchSet, allowTheoreticalChords: true) @@ -102,35 +123,38 @@ class ChordTests: XCTestCase { let notes: [Int8] = [60, 65, 67, 70] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let c7sus4 = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(c7sus4.map { $0.slashDescription }, ["C7sus4", "B♭6sus2/C", "F9sus4/C"]) + XCTAssertEqual(c7sus4.map { $0.slashDescription }, ["C7sus4", "Fsus4(add9)/C", "Fsus2(add11)/C", "B♭sus2(add13)/C"]) } func test9sus4() { let notes: [Int8] = [60, 65, 67, 74] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let c9sus4 = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(c9sus4.map { $0.slashDescription }, ["C9sus4", "G7sus4/C", "F6sus2/C"]) + XCTAssertEqual(c9sus4.map { $0.slashDescription }, ["Csus4(add9)", "Csus2(add11)", "G7sus4/C", "Fsus2(add13)/C"]) } func test6sus2() { let notes: [Int8] = [60, 62, 67, 69] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(chord.map { $0.slashDescription }, ["C6sus2", "G9sus4/C", "D7sus4/C"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["Csus2(add13)", + "D7sus4/C", + "Gsus4(add9)/C", + "Gsus2(add11)/C"]) } func test6sus4() { let notes: [Int8] = [60, 65, 67, 69] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - XCTAssertEqual(chord.map { $0.slashDescription }, ["C6sus4", "Fadd9/C"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["Csus4(add13)", "F(add9)/C"]) } func testMinorMajor7th() { let notes: [Int8] = [60, 63, 67, 71] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - let chord2 = Chord(.C, type: .minorMajorSeventh) + let chord2 = Chord(.C, type: .min_maj7) XCTAssertEqual(chord2.slashDescription, "CmMaj7") XCTAssertEqual(chord.map { $0.slashDescription }, ["CmMaj7"]) } @@ -139,7 +163,7 @@ class ChordTests: XCTestCase { let notes: [Int8] = [60, 63, 67, 71, 74] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - let chord2 = Chord(.C, type: .minorMajorNinth) + let chord2 = Chord(.C, type: .min_maj9) XCTAssertEqual(chord2.slashDescription, "CmMaj9") XCTAssertEqual(chord.map { $0.slashDescription }, ["CmMaj9"]) } @@ -148,9 +172,9 @@ class ChordTests: XCTestCase { let notes: [Int8] = [60, 64, 66, 71] let pitchSet = PitchSet(pitches: notes.map { Pitch($0) } ) let chord = Chord.getRankedChords(from: pitchSet) - let chord2 = Chord(.C, type: .majorSeventhFlatFive) + let chord2 = Chord(.C, type: .maj7_flat5) XCTAssertEqual(chord2.slashDescription, "Cmaj7(♭5)") - XCTAssertEqual(chord.map { $0.slashDescription }, ["Cmaj7(♭5)"]) + XCTAssertEqual(chord.map { $0.slashDescription }, ["Cmaj7(♭5)", "Bsus4(add♭9)/C", "Esus2(add♭13)/C"]) } func testAugmentedDiminishededChordsPreferNoInversions() { @@ -191,7 +215,10 @@ class ChordTests: XCTestCase { } func testSeventhNaming() { - let Am7 = Chord(notes: [.C, .E, .G, .A]) + let Gm7 = Chord(notes: [.G, .Bb, .D, .F]) + XCTAssertEqual(Gm7?.description, "Gm7") + + let Am7 = Chord(notes: [.A, .C, .E, .G]) XCTAssertEqual(Am7?.description, "Am7") let C7 = Chord(notes: [.C, .E, .G, .Bb]) @@ -209,7 +236,7 @@ class ChordTests: XCTestCase { func testNinthNaming() { let Cadd9 = Chord(notes: [.C, .E, .G, .D]) - XCTAssertEqual(Cadd9?.description, "Cadd9") + XCTAssertEqual(Cadd9?.description, "C(add9)") let C69 = Chord(notes: [.C, .E, .G, .A, .D]) XCTAssertEqual(C69?.description, "C6/9") @@ -219,23 +246,32 @@ class ChordTests: XCTestCase { let Cmaj11 = Chord(notes: [.C, .E, .G, .B, .D, .F]) XCTAssertEqual(Cmaj11?.description, "Cmaj11") - let G11 = Chord(notes: [.G, .B, .D, .F, .A, .C]) - XCTAssertEqual(G11?.description, "G11") - - let BhalfDiminished11NoteSet = NoteSet(notes: [Note(.B, octave: 1), .D, .F, .A, .C, .E]) - let chords = ChordTable.shared.getAllChordsForNoteSet(BhalfDiminished11NoteSet) - XCTAssertTrue(chords.contains(where: { $0.description == "Bø11" })) + let G11 = Chord(notes: [Note(.G, octave: 1), .B, .D, .F, .A, Note(.C, octave: 2)]) + XCTAssertEqual(G11?.slashDescription, "G11") + + let Gm11 = Chord(notes: [Note(.G, octave: 1), .Bb, .D, .F, .A, Note(.C, octave: 2)]) + XCTAssertEqual(Gm11?.slashDescription, "Gm11") + + let g_half_dim_11 = Chord(notes: [Note(.G, octave: 1), .Bb, .Db, .F, .A, Note(.C, octave: 2)]) + XCTAssertEqual(g_half_dim_11?.slashDescription, "Gø11") + + //TODO: - Finish } func testThirteenthNaming() { - let noteSet13 = NoteSet(notes: [.D, .F, .A, .C, .E, .G, .B]) - let chords = ChordTable.shared.getAllChordsForNoteSet(noteSet13) - XCTAssertTrue(chords.contains(where: { $0.description == "Cmaj13" })) - XCTAssertTrue(chords.contains(where: { $0.description == "Dm13" })) - XCTAssertTrue(chords.contains(where: { $0.description == "Em♭13♭9" })) - XCTAssertTrue(chords.contains(where: { $0.description == "Fmaj13♯11" })) - XCTAssertTrue(chords.contains(where: { $0.description == "Am11♭13" })) - XCTAssertTrue(chords.contains(where: { $0.description == "Bø♭13" })) + let maj13 = Chord(notes: [.C, .E, .G, .B, .D, .F, .A]) + XCTAssertEqual(maj13?.description, "Cmaj13") + + let dom13 = Chord(notes: [.C, .E, .G, .Bb, .D, .F, .A]) + XCTAssertEqual(dom13?.description, "Fmaj13") // Ideally this should be C13 but we ar finding maj13 first + + let min13 = Chord(notes: [.C, .Eb, .G, .Bb, .D, .F, .A]) + XCTAssertEqual(min13?.description, "B♭maj13") // Ideally this should be Cm13 but we ar finding maj13 first + + let half_dim_13 = Chord(notes: [.C, .Eb, .Gb, .Bb, .D, .F, .A]) + XCTAssertEqual(half_dim_13?.description, "Cø13") + + } func testInversions() { @@ -251,11 +287,11 @@ class ChordTests: XCTestCase { XCTAssertEqual(secondInversion.inversion, 2) XCTAssertEqual(secondInversion.slashDescription, "Am/E") - let thirdInversion = Chord(.C, type: .dominantSeventh, inversion: 3) + let thirdInversion = Chord(.C, type: .dom7, inversion: 3) XCTAssertEqual(thirdInversion.slashDescription, "C7/B♭") - firstInversion = Chord(.Cs, type: .majorTriad, inversion: 1) + firstInversion = Chord(.Cs, type: .major, inversion: 1) XCTAssertEqual(firstInversion.slashDescription, "C♯/E♯") } @@ -272,22 +308,23 @@ class ChordTests: XCTestCase { 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") + XCTAssertEqual(cSus2.description, "Csus2") + XCTAssertEqual(gSus4.description, "Csus2") // 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: 2)]) - + // 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"]) + } func testEnharmonicChords() { - let midiNotes: [Int8] = [54, 58, 61] + let midiNotes: [Int8] = [54,58,61] let fSharp = PitchSet(pitches: midiNotes.map { Pitch($0) } ) let chords = Chord.getRankedChords(from: fSharp) XCTAssertEqual(chords.map { $0.slashDescription }, ["G♭","F♯"]) @@ -302,7 +339,7 @@ class ChordTests: XCTestCase { func testPitchesWithNoInversion() { // Arrange - let chord = Chord(.C, type: .majorTriad, inversion: 0) + let chord = Chord(.C, type: .major, inversion: 0) let expectedPitches = [ Note(.C, octave: 0), Note(.E, octave: 0), @@ -322,7 +359,7 @@ class ChordTests: XCTestCase { func testPitchesWithInversion() { // Arrange - let chord = Chord(.C, type: .majorTriad, inversion: 1) + let chord = Chord(.C, type: .major, inversion: 1) let expectedPitches = [ Note(.E, octave: 4), Note(.G, octave: 4), @@ -342,7 +379,7 @@ class ChordTests: XCTestCase { func testNotesWithNoInversion() { // Arrange - let chord = Chord(.C, type: .majorTriad, inversion: 0) + let chord = Chord(.C, type: .major, inversion: 0) let expectedNotes = [ Note(.C, octave: 4), Note(.E, octave: 4), @@ -362,7 +399,7 @@ class ChordTests: XCTestCase { func testNotesWithInversion() { // Arrange - let chord = Chord(.C, type: .majorTriad, inversion: 1) + let chord = Chord(.C, type: .major, inversion: 1) let expectedNotes = [ Note(.E, octave: 4), Note(.G, octave: 4), @@ -382,7 +419,7 @@ class ChordTests: XCTestCase { func testNotesWithMultipleOctaveChordInversion() { // Arrange - let chord = Chord(.C, type: .majorThirteenth, inversion: 1) + let chord = Chord(.C, type: .maj13, inversion: 1) let expectedNotes = [ Note(.E, octave: 4), Note(.G, octave: 4), @@ -432,8 +469,8 @@ class ChordTests: XCTestCase { // C should not be reported as B# assertChords([0, 4, 7], [.C]) // 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: .dominantFlatNinth)]) + assertChords([1, 5, 8, 11], [Chord(.Db, type: .dom7), Chord(.Cs, type: .dom7),]) + assertChords([1, 5, 8, 11, 14], [Chord(.Cs, type: .dom7_flat9)]) } func testClosedVoicing() { @@ -446,10 +483,10 @@ class ChordTests: XCTestCase { func testLowestOctave() { let openNotes: [Int8] = [60, 64 + 12, 67 + 24, 60 + 24, 64 + 36] - let results: [Int8] = [0, 4, 7] // another idea + let results: [Int8] = [0, 4, 7] let pitchSet = PitchSet(pitches: openNotes.map { Pitch($0) }) let resultSet = PitchSet(pitches: results.map { Pitch($0) }) - XCTAssertEqual(pitchSet.closedVoicing.transposedBassNoteTo(octave: -1), resultSet) + XCTAssertEqual(pitchSet.closedVoicing.transposedBassNoteTo(octave: Note.MiddleCStandard.yamaha.firstOctaveOffset), resultSet) } func testLowestOctave2() { @@ -457,7 +494,7 @@ class ChordTests: XCTestCase { let results: [Int8] = [0, 4 + 12, 7 + 24, 0 + 24, 4 + 36] // another idea let pitchSet = PitchSet(pitches: openNotes.map { Pitch($0) }) let resultSet = PitchSet(pitches: results.map { Pitch($0) }) - XCTAssertEqual(pitchSet.transposedBassNoteTo(octave: -1), resultSet) + XCTAssertEqual(pitchSet.transposedBassNoteTo(octave: Note.MiddleCStandard.yamaha.firstOctaveOffset), resultSet) } func testNewChords() { @@ -466,4 +503,153 @@ class ChordTests: XCTestCase { let chords = Chord.getRankedChords(from: pitchSet) print(chords.map {$0.slashDescription}) } + + // MARK: - Triads + + func testMajorTriads() { + assertRankedChord([60, 64, 67], expectedDescriptions: ["C"]) + assertRankedChord([65, 69, 72], expectedDescriptions: ["F"]) + assertRankedChord([67, 71, 74], expectedDescriptions: ["G"]) + } + + func testMinorTriads() { + assertRankedChord([57, 60, 64], expectedDescriptions: ["Am"]) + assertRankedChord([62, 65, 69], expectedDescriptions: ["Dm"]) + assertRankedChord([64, 67, 71], expectedDescriptions: ["Em"]) + } + + func testDiminishedTriads() { + assertRankedChord([59, 62, 65], expectedDescriptions: ["B°"]) + assertRankedChord([62, 65, 68], expectedDescriptions: ["D°"]) + } + + func testAugmentedTriads() { + assertRankedChord([60, 64, 68], expectedDescriptions: ["C⁺", "A♭⁺/C"]) + assertRankedChord([65, 69, 73], expectedDescriptions: ["F⁺", "D♭⁺/F"]) + } + + // MARK: - Seventh Chords + + func testDominantSeventhChords() { + assertRankedChord([67, 71, 74, 77], expectedDescriptions: ["G7"]) + assertRankedChord([60, 64, 67, 70], expectedDescriptions: ["C7"]) + } + + func testMajorSeventhChords() { + assertRankedChord([60, 64, 67, 71], expectedDescriptions: ["Cmaj7"]) + assertRankedChord([65, 69, 72, 76], expectedDescriptions: ["Fmaj7"]) + } + + func testMinorSeventhChords() { + assertRankedChord([57, 60, 64, 67], expectedDescriptions: ["Am7", "C6/A"]) + assertRankedChord([62, 65, 69, 72], expectedDescriptions: ["Dm7", "F6/D"]) + } + + func testHalfDiminishedSeventhChords() { + assertRankedChord([59, 62, 65, 69], expectedDescriptions: ["Bø7", "Dm6/B"]) + assertRankedChord([64, 67, 70, 74], expectedDescriptions: ["Eø7", "Gm6/E"]) + } + + func testDiminishedSeventhChords() { + assertRankedChord([59, 62, 65, 68], expectedDescriptions: ["B°7", "G♯°7/B"]) + assertRankedChord([62, 65, 68, 71], expectedDescriptions: ["D°7", "B°7/D", "G♯°7/D"]) + } + + // MARK: - Extended Chords + + func testNinthChords() { + assertRankedChord([60, 64, 67, 70, 74], expectedDescriptions: ["C9", "Eø7(add♭13)/C"]) + assertRankedChord([62, 65, 69, 72, 76], expectedDescriptions: ["Dm9", "Fmaj7(add13)/D"]) + } + + func testEleventhChords() { + assertRankedChord([60, 64, 67, 70, 74, 77], expectedDescriptions: ["C11", "Fmaj13sus2/C", "Fmaj13sus4/C", "Gm13(add11)/C"]) + assertRankedChord([65, 69, 72, 76, 79, 82], expectedDescriptions: ["Fmaj11", "C13(add11)/F"]) + } + + func testThirteenthChords() { + assertRankedChord([60, 64, 67, 70, 74, 77, 81], + expectedDescriptions: ["C13", + "Gm13/C", + "Fmaj13/C", + "Dm7(♭13)/C", + "Dm11(♭13)/C", + "Am(♭13)(♭9)/C", + "B♭maj13(♯11)/C", + "Am7(♭9)(♭13)/C", + "Eø7(♭5)(♭9)(♭13)/C"]) + + assertRankedChord([67, 71, 74, 77, 81, 84, 88], + expectedDescriptions: ["G13", + "Dm13/G", + "Cmaj13/G", + "Am7(♭13)/G", + "Am11(♭13)/G", + "Fmaj13(♯11)/G", + "Em(♭13)(♭9)/G", + "Em7(♭9)(♭13)/G", + "Bø7(♭5)(♭9)(♭13)/G"]) + } + + // MARK: - Suspended Chords + func testSus2Chords() { + assertRankedChord([60, 62, 67], expectedDescriptions: ["Csus2", "Gsus4/C"]) + assertRankedChord([65, 67, 72], expectedDescriptions: ["Fsus2", "Csus4/F"]) + } + + func testSus4Chords() { + assertRankedChord([60, 65, 67], expectedDescriptions: ["Csus4", "Fsus2/C"]) + assertRankedChord([67, 72, 74], expectedDescriptions: ["Gsus4", "Csus2/G"]) + } + + // MARK: - Add Chords + + func testAdd9Chords() { + assertRankedChord([60, 64, 67, 74], expectedDescriptions: ["C(add9)", "Gsus4(add13)/C"]) + assertRankedChord([65, 69, 72, 79], expectedDescriptions: ["F(add9)", "Csus4(add13)/F"]) + } + + // MARK: - Altered Chords + + func testFlatFiveChords() { + assertRankedChord([60, 64, 66], expectedDescriptions: ["C(♭5)"]) + assertRankedChord([67, 71, 73], expectedDescriptions: ["G(♭5)"]) + } + + func testSharpFiveChords() { + assertRankedChord([60, 64, 68], expectedDescriptions: ["C⁺", "A♭⁺/C"]) + assertRankedChord([65, 69, 73], expectedDescriptions: ["F⁺", "D♭⁺/F"]) + } + + // MARK: - Inversions + + func testFirstInversion() { + assertRankedChord([64, 67, 72], expectedDescriptions: ["C/E"]) + } + + func testSecondInversion() { + assertRankedChord([67, 72, 76], expectedDescriptions: ["C/G"]) + } + + // MARK: - Edge Cases + +// func testEnharmonicChords() { +// assertRankedChord([54, 58, 61], expectedDescriptions: ["G♭", "F♯"]) +// } + + func testChordWithRedundantNotes() { + assertRankedChord([60, 64, 67, 72], expectedDescriptions: ["C"]) + } + + func testUncommonChords() { + assertRankedChord([60, 64, 67, 71, 74, 77, 81], expectedDescriptions: ["Cmaj13", "G13/C", "Dm13/C", "Am7(♭13)/C", "Am11(♭13)/C", "Fmaj13(♯11)/C", "Em(♭13)(♭9)/C", "Em7(♭9)(♭13)/C", "Bø7(♭5)(♭9)(♭13)/C"]) + assertRankedChord([60, 63, 66, 69], expectedDescriptions: ["C°7", "A°7/C", "F♯°7/C", "D♯°7/C"]) + assertRankedChord([60, 63, 67, 71], expectedDescriptions: ["CmMaj7"]) + } + + func testPolychordsAndAmbiguousChords() { + assertRankedChord([65, 69, 72, 74, 79], expectedDescriptions: ["F6/9", "G9sus4/F", "G11sus2/F", "Dm7(add11)/F"]) + assertRankedChord([65, 69, 72, 76, 79], expectedDescriptions: ["Fmaj9", "Am7(add♭13)/F"]) + } + } diff --git a/Tests/TonicTests/KeyTests.swift b/Tests/TonicTests/KeyTests.swift index 6a8d1aa..791f650 100644 --- a/Tests/TonicTests/KeyTests.swift +++ b/Tests/TonicTests/KeyTests.swift @@ -41,7 +41,7 @@ class KeyTests: XCTestCase { } func testKeyChords() { - XCTAssertEqual(Key.G.chords.count, 49) + XCTAssertEqual(Key.G.chords.count, 60) // This should only change if new chord types are added for triad in Key.G.primaryTriads { XCTAssert(Key.G.chords.contains(triad)) } diff --git a/Tests/TonicTests/NoteTests.swift b/Tests/TonicTests/NoteTests.swift index ce91744..def1ef3 100644 --- a/Tests/TonicTests/NoteTests.swift +++ b/Tests/TonicTests/NoteTests.swift @@ -102,7 +102,7 @@ final class NoteTests: XCTestCase { XCTAssertEqual(bsharp0!.description, "B♯1") var notesAugmentedTriadShiftUpIntoE: [Note] = [] - for interval in ChordType.augmentedTriad.intervals { + for interval in ChordType.aug.intervals { if let shifted = Note(.E, accidental: .natural, octave: 0).shiftUp(interval) { notesAugmentedTriadShiftUpIntoE.append(shifted) } @@ -111,7 +111,7 @@ final class NoteTests: XCTestCase { XCTAssertEqual(notesAugmentedTriadShiftUpIntoE[1].description, "B♯1") var notesMinorTriadShiftUpIntoA: [Note] = [] - for interval in ChordType.minorTriad.intervals { + for interval in ChordType.minor.intervals { if let shifted = Note(.A, accidental: .natural, octave: 0).shiftUp(interval) { notesMinorTriadShiftUpIntoA.append(shifted) } @@ -135,7 +135,7 @@ final class NoteTests: XCTestCase { } func testNoteIntValue() { - let lowest = Note(.C, octave: -2).intValue + let lowest = Note(pitch: Pitch(0), key: .C).intValue let highest = Note(pitch: Pitch(127), key: .C).intValue for i in lowest ..< highest { diff --git a/Tests/TonicTests/ReadMeTests.swift b/Tests/TonicTests/ReadMeTests.swift index 489d67a..6c71f69 100644 --- a/Tests/TonicTests/ReadMeTests.swift +++ b/Tests/TonicTests/ReadMeTests.swift @@ -16,12 +16,12 @@ final class ReadMeTests: XCTestCase { // What chords are in this key? func testChordsInKey() { - XCTAssertEqual(Key.Cm.chords.count, 49) + XCTAssertEqual(Key.Cm.chords.count, 60) } // What chords in this key contain this note? func testChordsInKeyContainNote() { - XCTAssertEqual(Key.C.chords.filter { $0.noteClasses.contains(.C) }.count, 29) + XCTAssertEqual(Key.C.chords.filter { $0.noteClasses.contains(.C) }.count, 36) } // What notes do these keys have in common?