From 051fbaa54270a588da59487dba73c350a30ce6a0 Mon Sep 17 00:00:00 2001 From: James Bean Date: Sat, 24 Aug 2019 20:31:58 +0200 Subject: [PATCH] Add ChordDescriptor.inversion(_:) (#125) --- Sources/Pitch/Chord/ChordDescriptor.swift | 25 +++++++++++++++++ .../CompoundIntervalDescriptor.swift | 14 +++++++++- .../IntervalDescriptor/IntervalQuality.swift | 2 +- Tests/PitchTests/ChordDescriptorTests.swift | 27 +++++++++++++++++++ .../CompoundIntervalDescriptorTests.swift | 6 +++++ 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/Sources/Pitch/Chord/ChordDescriptor.swift b/Sources/Pitch/Chord/ChordDescriptor.swift index a8b9142..5b55157 100644 --- a/Sources/Pitch/Chord/ChordDescriptor.swift +++ b/Sources/Pitch/Chord/ChordDescriptor.swift @@ -33,6 +33,28 @@ public struct ChordDescriptor { extension ChordDescriptor { + // MARK: - Instance Methods + + /// **Example Usage:** + /// + /// let major: ChordDescriptor = [.M3, .m3] + /// let firstInversion = major.inversion(1) // => [.m3, .P4] + /// let secondInversion = major.inversion(2) // => [.P4, .M3] + /// + /// let majorSeventh: ChordDescriptor = [.M3, .m3, .M3] + /// let thirdInversion = majorSeventh.inversion(3) // => [.m2, .M3, .m3] + /// + /// - Returns: A `ChordDescriptor` which is the *nth* inversion of this `ChordDescriptor`. + public func inversion(_ value: Int) -> ChordDescriptor { + precondition(value >= 0 && value <= intervals.count) + if value == 0 { return self } + return ChordDescriptor( + (intervals + [.octave - intervals.sum]) + .rotated(by: value) + .dropLast() + ) + } + // MARK: - Type Properties public static let major: ChordDescriptor = [.M3, .m3] @@ -140,3 +162,6 @@ extension ChordDescriptor: ExpressibleByArrayLiteral { self.intervals = intervals } } + +extension ChordDescriptor: Equatable { } +extension ChordDescriptor: Hashable { } diff --git a/Sources/Pitch/IntervalDescriptor/CompoundIntervalDescriptor.swift b/Sources/Pitch/IntervalDescriptor/CompoundIntervalDescriptor.swift index 35690a8..dc3b68d 100644 --- a/Sources/Pitch/IntervalDescriptor/CompoundIntervalDescriptor.swift +++ b/Sources/Pitch/IntervalDescriptor/CompoundIntervalDescriptor.swift @@ -78,7 +78,7 @@ extension CompoundIntervalDescriptor: AdditiveGroup { public static func - (lhs: CompoundIntervalDescriptor, rhs: CompoundIntervalDescriptor) -> CompoundIntervalDescriptor { let semitones = lhs.interval.semitones - rhs.interval.semitones - let steps = lhs.interval.ordinal.steps - rhs.interval.ordinal.steps + let steps = lhs.steps - rhs.steps let stepsModuloOctave = mod(steps,7) let octaves = steps / 7 let interval = OrderedIntervalDescriptor(interval: semitones, steps: stepsModuloOctave) @@ -329,3 +329,15 @@ extension OrderedIntervalDescriptor { return ordinal.idealInterval + quality.adjustment } } + +extension OrderedIntervalDescriptor { + var steps: Int { + return ordinal.steps + } +} + +extension CompoundIntervalDescriptor { + var steps: Int { + return interval.steps + octaveDisplacement * 7 + } +} diff --git a/Sources/Pitch/IntervalDescriptor/IntervalQuality.swift b/Sources/Pitch/IntervalDescriptor/IntervalQuality.swift index 803f3d4..0c2bae7 100644 --- a/Sources/Pitch/IntervalDescriptor/IntervalQuality.swift +++ b/Sources/Pitch/IntervalDescriptor/IntervalQuality.swift @@ -163,7 +163,7 @@ extension IntervalQuality.Imperfect: CustomStringConvertible { /// Printable description of IntervalQuality.Imperfect. public var description: String { - return "P" + return self == .major ? "M" : "m" } } diff --git a/Tests/PitchTests/ChordDescriptorTests.swift b/Tests/PitchTests/ChordDescriptorTests.swift index cf8f808..db4928a 100644 --- a/Tests/PitchTests/ChordDescriptorTests.swift +++ b/Tests/PitchTests/ChordDescriptorTests.swift @@ -16,4 +16,31 @@ class ChordDescriptorTests: XCTestCase { let _: ChordDescriptor = [.m3, .m3] // diminished let _: ChordDescriptor = [.M3, .M3] // augmented } + + func testInversionRootPosition() { + let major = ChordDescriptor([.M3, .m3]) + let result = major.inversion(0) + XCTAssertEqual(major, result) + } + + func testFirstInversion() { + let major: ChordDescriptor = [.M3, .m3] + let result = major.inversion(1) + let expected: ChordDescriptor = [.m3, .P4] + XCTAssertEqual(result, expected) + } + + func testSecondInversion() { + let major: ChordDescriptor = [.M3, .m3] + let result = major.inversion(2) + let expected: ChordDescriptor = [.P4, .M3] + XCTAssertEqual(result, expected) + } + + func testThirdInversion() { + let majorSeventh: ChordDescriptor = [.M3, .m3, .M3] + let result = majorSeventh.inversion(3) + let expected: ChordDescriptor = [.m2, .M3, .m3] + XCTAssertEqual(result, expected) + } } diff --git a/Tests/PitchTests/CompoundIntervalDescriptorTests.swift b/Tests/PitchTests/CompoundIntervalDescriptorTests.swift index e69a601..a19084f 100644 --- a/Tests/PitchTests/CompoundIntervalDescriptorTests.swift +++ b/Tests/PitchTests/CompoundIntervalDescriptorTests.swift @@ -40,4 +40,10 @@ class CompoundIntervalDescriptorTests: XCTestCase { let expected: CompoundIntervalDescriptor = CompoundIntervalDescriptor(orderedInterval) XCTAssertEqual(result, expected) } + + func testOctaveSubtractingCompoundInterval() { + let result: CompoundIntervalDescriptor = .octave - (.M3 + .m3) + let expected: CompoundIntervalDescriptor = .P4 + XCTAssertEqual(result, expected) + } }