Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for slash notation and getRankedChords using circle of 5ths and 4ths #34

Merged
merged 4 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 36 additions & 20 deletions Sources/Tonic/Chord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,29 @@ extension Chord: CustomStringConvertible {
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
}
}

/// Bass Note computed from inversion and root note
/// Useful for custom rendering of slash notation
public var bassNote: NoteClass {
switch inversion {
case 1...4:
if let bass = root.canonicalNote.shiftUp(type.intervals[inversion - 1]) {
return bass.noteClass
}
default:
break
}
return root.canonicalNote.noteClass
}
}

extension Chord {
Expand All @@ -146,31 +169,24 @@ extension Chord {

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

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

for chord in Chord.getRankedChords(from: sNotes) {
if !returnArray.contains(chord) {
returnArray.append(chord)
}
for key in Key.circleOfFifths {
noteArrays.insert(pitchSet.array.map { Note(pitch: $0, key: key) })
}
for chord in Chord.getRankedChords(from: bNotes) {
if !returnArray.contains(chord) {
returnArray.append(chord)
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)

for key in Key.circleOfFourths {
noteArrays.insert(pitchSet.array.map { Note(pitch: $0, key: key) })
}
for chord in returnArray {
print(chord, chord.accidentalCount)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)

for noteArray in noteArrays {
returnArray.append(contentsOf: Chord.getRankedChords(from: noteArray))
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)

// sort by alphabetical to prevent nondeterminstic sorting from same number of accdientals
returnArray.sort { $0.root.accidental < $1.root.accidental }

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

Expand Down
4 changes: 2 additions & 2 deletions Sources/Tonic/ChordTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class ChordTable {
for chordType in ChordType.allCases {
ChordTable.generateChords(type: chordType, &r)
}
print("generated \(r.count) chords")
//print("generated \(r.count) chords")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment Spacing Violation: Prefer at least one space after slashes for comments. (comment_spacing)

return r
}

Expand All @@ -61,7 +61,7 @@ public class ChordTable {
}
}
}
print("generated \(returnChords.count) chords")
//print("generated \(returnChords.count) chords")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment Spacing Violation: Prefer at least one space after slashes for comments. (comment_spacing)

return returnChords
}

Expand Down
8 changes: 4 additions & 4 deletions Sources/Tonic/ChordType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,13 @@ extension ChordType: CustomStringConvertible {
case .suspendedFourthTriad: return "sus4"
case .sixth: return "6"
case .minorSixth: return "m6"
case .halfDiminishedSeventh: return "(1/2)°7"
case .halfDiminishedSeventh: return "ø7"
case .diminishedSeventh: return "°7"
case .dominantSeventh: return "7"
case .majorSeventh: return "maj7"
case .minorSeventh: return "m7"
case .minorMajorSeventh: return "mMaj7"
case .halfDiminishedNinth: return "(1/2)°9"
case .halfDiminishedNinth: return "ø9"
case .dominantNinth: return "9"
case .flatNinth: return "7♭9"
case .sharpNinth: return "7♯9"
Expand All @@ -214,7 +214,7 @@ extension ChordType: CustomStringConvertible {
case .majorEleventh: return "maj11"
case .dominantEleventh: return "11"
case .minorEleventh: return "m11"
case .halfDiminishedEleventh: return "(1/2)°11"
case .halfDiminishedEleventh: return "ø11"
case .majorSeventhFlatFifth: return "maj7♭5"
case .minorSeventhSharpFifth: return "maj7♯5"
case .majorNinthSharpEleventh: return "maj9♯11"
Expand All @@ -229,7 +229,7 @@ extension ChordType: CustomStringConvertible {
case .majorThirteenthSharpEleventh: return "maj13♯11"
case .dominantThirteenth: return "13"
case .minorEleventhFlatThirteenth: return "m11♭13"
case .halfDiminishedFlatThirteenth: return "(1/2)°♭13"
case .halfDiminishedFlatThirteenth: return "ø♭13"
}
}
}
4 changes: 4 additions & 0 deletions Sources/Tonic/Key+Shortcuts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import Foundation

public extension Key {

static let circleOfFifths: [Key] = [.C, .G, .D, .A, .E, .B, .Fs, .Cs]
static let circleOfFourths: [Key] = [.F, .Bb, .Eb, .Ab, .Db, .Gb, .Cb]

// MARK: - Major keys

/// C♭ Major
Expand Down
47 changes: 32 additions & 15 deletions Tests/TonicTests/ChordTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ class ChordTests: XCTestCase {
XCTAssertEqual(Chord.Asus2.description, "Asus2")
XCTAssertEqual(Chord.Bsus2.description, "Bsus2")
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)

func testC7() {
XCTAssertEqual(Chord(.C, type: .dominantSeventh).description, "C7")
let notes: [Int8] = [60, 67, 70, 76]
let c7 = PitchSet(pitches: notes.map { Pitch($0) } )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closing Brace Spacing Violation: Closing brace with closing parenthesis should not have any whitespaces in the middle. (closing_brace)
Identifier Name Violation: Variable name should be between 3 and 40 characters long: 'c7' (identifier_name)

let chords = Chord.getRankedChords(from: c7)
XCTAssertEqual(chords.map { $0.description }, ["C7"])

}

func testRomanNumerals() {
XCTAssertEqual(Key.C.primaryTriads.map { $0.romanNumeralNotation(in: Key.C) ?? "" },
Expand Down Expand Up @@ -64,7 +73,7 @@ class ChordTests: XCTestCase {
XCTAssertEqual(Cmaj7?.description, "Cmaj7")

let ChalfDim7 = Chord(notes: [.C, .Eb, .Gb, .Bb])
XCTAssertEqual(ChalfDim7?.description, "C(1/2)°7")
XCTAssertEqual(ChalfDim7?.description, "Cø7")

let Adim7 = Chord(notes: [.A, .C, .Eb, .Gb])
XCTAssertEqual(Adim7?.description, "A°7")
Expand All @@ -85,9 +94,9 @@ class ChordTests: XCTestCase {
let G11 = Chord(notes: [.G, .B, .D, .F, .A, .C])
XCTAssertEqual(G11?.description, "G11")

let BhalfDiminished11NoteSet = NoteSet(notes: [.B, .D, .F, .A, .C, .E])
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(1/2)°11" }))
XCTAssertTrue(chords.contains(where: { $0.description == "Bø11" }))
}

func testThirteenthNaming() {
Expand All @@ -98,21 +107,27 @@ class ChordTests: XCTestCase {
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(1/2)°♭13" }))
XCTAssertTrue(chords.contains(where: { $0.description == "♭13" }))
}

func testInversions() {
let chord = Chord(notes: [.C, .E, .G])!
XCTAssertEqual(chord.inversion, 0)
XCTAssertTrue(chord.isTriad)

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

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

let thirdInversion = Chord(.C, type: .dominantSeventh, inversion: 3)
XCTAssertEqual(thirdInversion.slashDescription, "C7/B♭")

firstInversion = Chord(.Cs, type: .majorTriad, inversion: 1)
XCTAssertEqual(firstInversion.slashDescription, "C♯/E♯")
}

func testTriadsWithRedundantNotes() {
Expand Down Expand Up @@ -146,7 +161,7 @@ class ChordTests: XCTestCase {
let midiNotes: [Int8] = [54, 58, 61]
let fSharp = PitchSet(pitches: midiNotes.map { Pitch($0) } )
let chords = Chord.getRankedChords(from: fSharp)
XCTAssertEqual(chords.map { $0.description }, ["F♯", "G♭"])
XCTAssertEqual(chords.map { $0.description }, ["G♭","F♯"])
}

func testDuplicateRankedChords() {
Expand Down Expand Up @@ -259,13 +274,18 @@ class ChordTests: XCTestCase {
"Notes should match expected notes for 1st inversion"
)
}

func testBassNoteChords() {
// C Major 1st inversion
let notes: [Int8] = [4, 7, 12]
let pitchSet = PitchSet(pitches: notes.map { Pitch($0) })
let chords = Chord.getRankedChords(from: pitchSet)
XCTAssertEqual(chords.map{$0.bassNote}, [NoteClass.E])
}

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

Expand All @@ -289,7 +309,7 @@ class ChordTests: XCTestCase {
assertChords([11, 15, 18], [.B, .Cb])

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

}
Expand All @@ -299,7 +319,6 @@ class ChordTests: XCTestCase {
let results: [Int8] = [60, 64, 67]
let pitchSet = PitchSet(pitches: openNotes.map { Pitch($0) })
let resultSet = PitchSet(pitches: results.map { Pitch($0) })
print(pitchSet.closedVoicing.array)
XCTAssertEqual(pitchSet.closedVoicing, resultSet)
}

Expand All @@ -308,7 +327,6 @@ class ChordTests: XCTestCase {
let results: [Int8] = [0, 4, 7] // another idea
let pitchSet = PitchSet(pitches: openNotes.map { Pitch($0) })
let resultSet = PitchSet(pitches: results.map { Pitch($0) })
print(pitchSet.closedVoicing.transposedBassNoteTo(octave: -1).array)
XCTAssertEqual(pitchSet.closedVoicing.transposedBassNoteTo(octave: -1), resultSet)
}

Expand All @@ -317,7 +335,6 @@ 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) })
print(pitchSet.transposedBassNoteTo(octave: -1).array)
XCTAssertEqual(pitchSet.transposedBassNoteTo(octave: -1), resultSet)
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/TonicTests/PerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class PerformanceTests: XCTestCase {
index_sum += Note(pitch: Pitch(Int8(i))).intValue
}
if index_sum != 23779 {
print("index_sum: \(index_sum)")
//print("index_sum: \(index_sum)")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment Spacing Violation: Prefer at least one space after slashes for comments. (comment_spacing)

abort()
}
}
Expand Down