diff --git a/Swift Musicology/.DS_Store b/Swift Musicology/.DS_Store index 9d61b84..2530f3f 100644 Binary files a/Swift Musicology/.DS_Store and b/Swift Musicology/.DS_Store differ diff --git a/Swift Musicology/Swift Musicology.xcodeproj/project.pbxproj b/Swift Musicology/Swift Musicology.xcodeproj/project.pbxproj index 27d01a5..4d8fbf2 100644 --- a/Swift Musicology/Swift Musicology.xcodeproj/project.pbxproj +++ b/Swift Musicology/Swift Musicology.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 0BA905CF245DCFA800D83B29 /* PitchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BA905CE245DCFA800D83B29 /* PitchTests.swift */; }; + 0BA905D1245DCFB400D83B29 /* TempoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BA905D0245DCFB400D83B29 /* TempoTests.swift */; }; + 0BA905D3245DCFC300D83B29 /* Scale Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BA905D2245DCFC300D83B29 /* Scale Tests.swift */; }; + 0BA905D5245DCFD100D83B29 /* ChordTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BA905D4245DCFD100D83B29 /* ChordTests.swift */; }; 0BFB65E1245DBD280019EBCB /* SwiftMusicology_iOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFB65DF245DBD280019EBCB /* SwiftMusicology_iOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0BFB65EE245DBD380019EBCB /* SwiftMusicology_watchOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFB65EC245DBD380019EBCB /* SwiftMusicology_watchOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0BFB65FB245DBD420019EBCB /* SwiftMusicology_tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFB65F9245DBD420019EBCB /* SwiftMusicology_tvOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -75,6 +79,10 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 0BA905CE245DCFA800D83B29 /* PitchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PitchTests.swift; sourceTree = ""; }; + 0BA905D0245DCFB400D83B29 /* TempoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempoTests.swift; sourceTree = ""; }; + 0BA905D2245DCFC300D83B29 /* Scale Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Scale Tests.swift"; sourceTree = ""; }; + 0BA905D4245DCFD100D83B29 /* ChordTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChordTests.swift; sourceTree = ""; }; 0BFB65B6245DBC0E0019EBCB /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; 0BFB65B7245DBC0E0019EBCB /* Info-Mac.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-Mac.plist"; sourceTree = ""; }; 0BFB65B8245DBC0E0019EBCB /* Info-TV.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-TV.plist"; sourceTree = ""; }; @@ -258,8 +266,12 @@ 1207E0D221AAE05300915CCD /* Swift MusicologyTests */ = { isa = PBXGroup; children = ( - 1207E0D321AAE05300915CCD /* Swift_MusicologyTests.swift */, 1207E0D521AAE05300915CCD /* Info.plist */, + 1207E0D321AAE05300915CCD /* Swift_MusicologyTests.swift */, + 0BA905CE245DCFA800D83B29 /* PitchTests.swift */, + 0BA905D0245DCFB400D83B29 /* TempoTests.swift */, + 0BA905D2245DCFC300D83B29 /* Scale Tests.swift */, + 0BA905D4245DCFD100D83B29 /* ChordTests.swift */, ); path = "Swift MusicologyTests"; sourceTree = ""; @@ -571,7 +583,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0BA905CF245DCFA800D83B29 /* PitchTests.swift in Sources */, + 0BA905D1245DCFB400D83B29 /* TempoTests.swift in Sources */, 1207E0D421AAE05300915CCD /* Swift_MusicologyTests.swift in Sources */, + 0BA905D3245DCFC300D83B29 /* Scale Tests.swift in Sources */, + 0BA905D5245DCFD100D83B29 /* ChordTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcuserdata/roux.xcuserdatad/UserInterfaceState.xcuserstate b/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcuserdata/roux.xcuserdatad/UserInterfaceState.xcuserstate index 8f8e403..e65eea0 100644 Binary files a/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcuserdata/roux.xcuserdatad/UserInterfaceState.xcuserstate and b/Swift Musicology/Swift Musicology.xcodeproj/project.xcworkspace/xcuserdata/roux.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Swift Musicology/Swift Musicology.xcodeproj/xcuserdata/roux.xcuserdatad/xcschemes/xcschememanagement.plist b/Swift Musicology/Swift Musicology.xcodeproj/xcuserdata/roux.xcuserdatad/xcschemes/xcschememanagement.plist index 95fb99a..2059e8f 100644 --- a/Swift Musicology/Swift Musicology.xcodeproj/xcuserdata/roux.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Swift Musicology/Swift Musicology.xcodeproj/xcuserdata/roux.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,25 +9,30 @@ orderHint 0 - SwiftMusicology iOS.xcscheme_^#shared#^_ + Swift MusicologyTests.xcscheme_^#shared#^_ orderHint 2 + SwiftMusicology iOS.xcscheme_^#shared#^_ + + orderHint + 1 + SwiftMusicology macOS.xcscheme_^#shared#^_ orderHint - 5 + 3 SwiftMusicology tvOS.xcscheme_^#shared#^_ orderHint - 4 + 0 SwiftMusicology watchOS.xcscheme_^#shared#^_ orderHint - 3 + 4 SwiftMusicology.xcscheme_^#shared#^_ diff --git a/Swift Musicology/Swift Musicology/ChordProgression.swift b/Swift Musicology/Swift Musicology/ChordProgression.swift index 8c0c143..8b10892 100644 --- a/Swift Musicology/Swift Musicology/ChordProgression.swift +++ b/Swift Musicology/Swift Musicology/ChordProgression.swift @@ -9,23 +9,24 @@ import Foundation /// A struct for storing custom progressions. -public struct CustomChordProgression: Codable, CustomStringConvertible { +public struct CustomChordProgression: Codable { /// Name of the progression. public var name: String /// Chord progression with `ChordProgresion.custom` type. public var progression: ChordProgression - - - // MARK: CustomStringConvertible +} +extension CustomChordProgression: CustomStringConvertible { public var description: String { return name } - } + +// MARK: - ChordProgressionNode + /// A node of chord progression in intervals. -public enum ChordProgressionNode: Int, CustomStringConvertible, Codable { +public enum ChordProgressionNode: Int, Codable { /// First-degree node case i /// Second-degree node @@ -63,9 +64,9 @@ public enum ChordProgressionNode: Int, CustomStringConvertible, Codable { /// All nodes. public static let all: [ChordProgressionNode] = [.i, .ii, .iii, .iv, .v, .vi, .vii] - - // MARK: CustomStringConvertible - +} + +extension ChordProgressionNode: CustomStringConvertible { /// Returns roman numeric string of the node. public var description: String { switch self { @@ -80,8 +81,11 @@ public enum ChordProgressionNode: Int, CustomStringConvertible, Codable { } } + +// MARK: ChordProgression Definition + /// Chord progression enum that you can create hard-coded and custom progressions. -public enum ChordProgression: CustomStringConvertible, Codable { +public struct ChordProgression { /// All nodes from first to seventh. public static let allNodes = ChordProgression(nodes: [.i, .ii, .iii, .iv, .v, .vi, .vii]) /// I - V - VI - IV progression. @@ -170,9 +174,10 @@ public enum ChordProgression: CustomStringConvertible, Codable { } return chords } +} - // MARK: CustomStringConvertible - +extension ChordProgression: CustomStringConvertible { + /// Returns the chord progression name. public var description: String { if self == ChordProgression.allNodes { @@ -180,9 +185,10 @@ public enum ChordProgression: CustomStringConvertible, Codable { } return nodes.map({ "\($0)" }).joined(separator: " - ") } +} - // MARK: Codable - +extension ChordProgression: Codable { + /// Codable protocol `CodingKey`s /// /// - nodes: Coding key for the nodes. @@ -208,10 +214,10 @@ public enum ChordProgression: CustomStringConvertible, Codable { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(nodes, forKey: .nodes) } +} - // MARK: Equatable - +extension ChordProgression: Equatable { public static func == (lhs: ChordProgression, rhs: ChordProgression) -> Bool { - return lhs.nodes == rhs.nodes + return lhs.nodes == rhs.nodes } } diff --git a/Swift Musicology/Swift MusicologyTests/ChordTests.swift b/Swift Musicology/Swift MusicologyTests/ChordTests.swift new file mode 100644 index 0000000..3508066 --- /dev/null +++ b/Swift Musicology/Swift MusicologyTests/ChordTests.swift @@ -0,0 +1,149 @@ +// +// ChordTests.swift +// Swift MusicologyTests +// +// Created by roux g. buciu on 2020-05-02. +// Copyright © 2020 roux g. buciu. All rights reserved. +// + +import XCTest +@testable import Swift_Musicology + +class ChordTests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testChords() { + let cmajNotes: [Key] = [Key(type: .c), Key(type: .e), Key(type: .g)] + let cmaj = Chord(type: ChordType(third: .major), key: Key(type: .c)) + XCTAssert(cmajNotes == cmaj.keys) + + let cminNotes: [Key] = [ + Key(type: .c), + Key(type: .e, accidental: .flat), + Key(type: .g), + ] + let cmin = Chord(type: ChordType(third: .minor), key: Key(type: .c)) + XCTAssert(cminNotes == cmin.keys) + + let c13Notes: [Pitch] = [ + Pitch(key: Key(type: .c), octave: 1), + Pitch(key: Key(type: .e), octave: 1), + Pitch(key: Key(type: .g), octave: 1), + Pitch(key: Key(type: .b, accidental: .flat), octave: 1), + Pitch(key: Key(type: .d), octave: 2), + Pitch(key: Key(type: .f), octave: 2), + Pitch(key: Key(type: .a), octave: 2), + ] + let c13 = Chord( + type: ChordType( + third: .major, + seventh: .dominant, + extensions: [ + ChordExtensionType(type: .thirteenth), + ] + ), + key: Key(type: .c) + ) + XCTAssert(c13.pitches(octave: 1) === c13Notes) + + let cm13Notes: [Pitch] = [ + Pitch(key: Key(type: .c), octave: 1), + Pitch(key: Key(type: .e, accidental: .flat), octave: 1), + Pitch(key: Key(type: .g), octave: 1), + Pitch(key: Key(type: .b, accidental: .flat), octave: 1), + Pitch(key: Key(type: .d), octave: 2), + Pitch(key: Key(type: .f), octave: 2), + Pitch(key: Key(type: .a), octave: 2), + ] + let cm13 = Chord( + type: ChordType( + third: .minor, + seventh: .dominant, + extensions: [ + ChordExtensionType(type: .thirteenth), + ] + ), + key: Key(type: .c) + ) + XCTAssert(cm13.pitches(octave: 1) === cm13Notes) + + let minorIntervals: [Interval] = [.P1, .m3, .P5] + guard let minorChord = ChordType(intervals: minorIntervals.map({ $0 })) else { return XCTFail() } + XCTAssert(minorChord == ChordType(third: .minor)) + + let majorIntervals: [Interval] = [.P1, .M3, .P5] + guard let majorChord = ChordType(intervals: majorIntervals.map({ $0 })) else { return XCTFail() } + XCTAssert(majorChord == ChordType(third: .major)) + + let cmadd13Notes: [Pitch] = [ + Pitch(key: Key(type: .c), octave: 1), + Pitch(key: Key(type: .e, accidental: .flat), octave: 1), + Pitch(key: Key(type: .g), octave: 1), + Pitch(key: Key(type: .a), octave: 2), + ] + let cmadd13 = Chord( + type: ChordType( + third: .minor, + extensions: [ChordExtensionType(type: .thirteenth)] + ), + key: Key(type: .c) + ) + XCTAssert(cmadd13.pitches(octave: 1) === cmadd13Notes) + } + + func testRomanNumerics() { + let cmaj = Scale(type: .major, key: Key(type: .c)) + let cmin = Scale(type: .minor, key: Key(type: .c)) + let cmajNumerics = ["I", "ii", "iii", "IV", "V", "vi", "vii°"] + let cminNumerics = ["i", "ii°", "III", "iv", "v", "VI", "VII"] + let cmajChords = cmaj.harmonicField(for: .triad) + let cminChords = cmin.harmonicField(for: .triad) + XCTAssertEqual(cmajNumerics, cmajChords.compactMap({ $0?.romanNumeric(for: cmaj) })) + XCTAssertEqual(cminNumerics, cminChords.compactMap({ $0?.romanNumeric(for: cmin) })) + } + + func testInversions() { + let c7 = Chord( + type: ChordType(third: .major, seventh: .dominant), + key: Key(type: .c) + ) + let c7Inversions = [ + [ + Pitch(key: Key(type: .c), octave: 1), + Pitch(key: Key(type: .e), octave: 1), + Pitch(key: Key(type: .g), octave: 1), + Pitch(key: Key(type: .b, accidental: .flat), octave: 1), + ], + [ + Pitch(key: Key(type: .e), octave: 1), + Pitch(key: Key(type: .g), octave: 1), + Pitch(key: Key(type: .b, accidental: .flat), octave: 1), + Pitch(key: Key(type: .c), octave: 2), + ], + [ + Pitch(key: Key(type: .g), octave: 1), + Pitch(key: Key(type: .b, accidental: .flat), octave: 1), + Pitch(key: Key(type: .c), octave: 2), + Pitch(key: Key(type: .e), octave: 2), + ], + [ + Pitch(key: Key(type: .b, accidental: .flat), octave: 1), + Pitch(key: Key(type: .c), octave: 2), + Pitch(key: Key(type: .e), octave: 2), + Pitch(key: Key(type: .g), octave: 2), + ], + ] + for (index, chord) in c7.inversions.enumerated() { + XCTAssert(chord.pitches(octave: 1) === c7Inversions[index]) + } + } +} diff --git a/Swift Musicology/Swift MusicologyTests/PitchTests.swift b/Swift Musicology/Swift MusicologyTests/PitchTests.swift new file mode 100644 index 0000000..5aed788 --- /dev/null +++ b/Swift Musicology/Swift MusicologyTests/PitchTests.swift @@ -0,0 +1,127 @@ +// +// PitchTests.swift +// Swift MusicologyTests +// +// Created by roux g. buciu on 2020-05-02. +// Copyright © 2020 roux g. buciu. All rights reserved. +// + +import XCTest +@testable import Swift_Musicology + +class PitchTests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testIntervals() { + let key = Key(type: .c) + let pitch = Pitch(key: key, octave: 1) + XCTAssert((pitch + 12).octave == pitch.octave + 1) + XCTAssert((pitch + 1).key == Key(type: .c, accidental: .sharp)) + XCTAssert((pitch - 1) == Pitch(key: Key(type: .b), octave: 0)) + + let c1 = Pitch(key: Key(type: .c), octave: 1) + let d1 = Pitch(key: Key(type: .d), octave: 1) + XCTAssert(d1 - c1 == .M2) + } + + func testAccidentals() { + XCTAssert(Accidental.flat * 2 == Accidental.doubleFlat) + XCTAssert(Accidental.doubleFlat / 2 == Accidental.flat) + XCTAssert(Accidental.sharps(amount: 2) - 2 == Accidental.natural) + XCTAssert(Accidental.flats(amount: 2) + 2 == 0) + XCTAssert(Accidental.sharps(amount: 2) + Accidental.sharps(amount: 1) == Accidental.sharps(amount: 3)) + XCTAssert(Accidental(integerLiteral: -3) + Accidental(rawValue: 3)! == 0) + } + + func testKeys() { + let d = Key.KeyType.d + XCTAssert(d.key(at: -2) == .b) + XCTAssert(d.key(at: -19) == .f) + XCTAssert(d.key(at: 12) == .b) + XCTAssert(d.key(at: 0) == .d) + XCTAssert(d.key(at: 1) == .e) + XCTAssert(d.key(at: 2) == .f) + XCTAssert(d.key(at: -3) == .a) + XCTAssert(d.key(at: -301) == .d) + + let f = Key.KeyType.f + XCTAssert(f.key(at: -3) == .c) + + let k: Key = "a##b" + XCTAssert(k.accidental == .sharp && k.type == .a) + + let b = Key(type: .b, accidental: .natural) + XCTAssert(Key(type: .c, accidental: .flat) == b) + XCTAssert(Key(type: .c, accidental: .sharps(amount: 23)) == b) + XCTAssert(Key(type: .c, accidental: .flats(amount: 13)) == b) + XCTAssert(Key(type: .c, accidental: .flats(amount: 25)) == b) + XCTAssert(Key(type: .c, accidental: .flats(amount: 24)) != b) + } + + func testPitches() { + let c0: Pitch = 12 + XCTAssert(c0.octave == 0 && c0.key.accidental == .natural && c0.key.type == .c) + XCTAssert(c0 - 12 == 0) + + var pitch = Pitch(midiNote: 127) + XCTAssert(pitch.key == Key(type: .g)) + pitch = Pitch(midiNote: 0) + XCTAssert(pitch.key == Key(type: .c)) + pitch = Pitch(midiNote: 66, isPreferredAccidentalSharps: false) + XCTAssert(pitch.key == Key(type: .g, accidental: .flat)) + + let c1 = Pitch(key: Key(type: .c), octave: 1) + XCTAssert(c1 + .m2 == Pitch(key: Key(type: .d, accidental: .flat), octave: 1)) + XCTAssert(c1 + .M2 == Pitch(key: Key(type: .d, accidental: .natural), octave: 1)) + XCTAssert(c1 + .m3 == Pitch(key: Key(type: .e, accidental: .flat), octave: 1)) + XCTAssert(c1 + .M3 == Pitch(key: Key(type: .e, accidental: .natural), octave: 1)) + XCTAssert(c1 + .P8 == Pitch(key: Key(type: .c, accidental: .natural), octave: 2)) + + let d1 = Pitch(key: Key(type: .d), octave: 1) + XCTAssert(d1 - .m2 == Pitch(key: Key(type: .c, accidental: .sharp), octave: 1)) + XCTAssert(d1 - .M2 == Pitch(key: Key(type: .c, accidental: .natural), octave: 1)) + + let p: Pitch = "f#-5" + XCTAssert(p.key === Key(type: .f, accidental: .sharp)) + XCTAssert(p.octave == -5) + + let uppercasePitch: Pitch = "A#3" + XCTAssert(uppercasePitch.key === Key(type: .a, accidental: .sharp)) + XCTAssert(uppercasePitch.octave == 3) + + let uppercasePitch2: Pitch = "F4" + XCTAssert(uppercasePitch2.key === Key(type: .f, accidental: .natural)) + XCTAssert(uppercasePitch2.octave == 4) + } + + func testFrequency() { + let note = Pitch(key: Key(type: .a), octave: 4) + XCTAssertEqual(note.frequency, 440.0) + + let a4 = Pitch.nearest(frequency: 440.0) + XCTAssertEqual(note, a4) + } +} + +// MARK: - [Pitch] Extension + +// A function for checking that pitch arrays are exactly equal in terms of their pitches, keys, and octaves. +func === (lhs: [Pitch], rhs: [Pitch]) -> Bool { + guard lhs.count == rhs.count else { return false } + for i in 0 ..< lhs.count { + if lhs[i] === rhs[i] { + continue + } else { + return false + } + } + return true +} diff --git a/Swift Musicology/Swift MusicologyTests/Scale Tests.swift b/Swift Musicology/Swift MusicologyTests/Scale Tests.swift new file mode 100644 index 0000000..b2b3333 --- /dev/null +++ b/Swift Musicology/Swift MusicologyTests/Scale Tests.swift @@ -0,0 +1,66 @@ +// +// Scale Tests.swift +// Swift MusicologyTests +// +// Created by roux g. buciu on 2020-05-02. +// Copyright © 2020 roux g. buciu. All rights reserved. +// + +import XCTest +@testable import Swift_Musicology + +class ScaleTests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testScale() { + let cMaj: [Key] = [ + Key(type: .c), + Key(type: .d), + Key(type: .e), + Key(type: .f), + Key(type: .g), + Key(type: .a), + Key(type: .b), + ] + + let cMajScale = Scale(type: .major, key: Key(type: .c)) + XCTAssert(cMajScale.keys == cMaj) + + let cMin: [Key] = [ + Key(type: .c), + Key(type: .d), + Key(type: .e, accidental: .flat), + Key(type: .f), + Key(type: .g), + Key(type: .a, accidental: .flat), + Key(type: .b, accidental: .flat), + ] + + let cMinScale = Scale(type: .minor, key: Key(type: .c)) + XCTAssert(cMinScale.keys == cMin) + } + + func testHarmonicFields() { + let cmaj = Scale(type: .major, key: Key(type: .c)) + let triads = cmaj.harmonicField(for: .triad) + let triadsExpected = [ + Chord(type: ChordType(third: .major), key: Key(type: .c)), + Chord(type: ChordType(third: .minor), key: Key(type: .d)), + Chord(type: ChordType(third: .minor), key: Key(type: .e)), + Chord(type: ChordType(third: .major), key: Key(type: .f)), + Chord(type: ChordType(third: .major), key: Key(type: .g)), + Chord(type: ChordType(third: .minor), key: Key(type: .a)), + Chord(type: ChordType(third: .minor, fifth: .diminished), key: Key(type: .b)), + ] + XCTAssert(triads.enumerated().map({ $1 == triadsExpected[$0] }).filter({ $0 == false }).count == 0) + } +} diff --git a/Swift Musicology/Swift MusicologyTests/Swift_MusicologyTests.swift b/Swift Musicology/Swift MusicologyTests/Swift_MusicologyTests.swift index 6407606..a588f56 100644 --- a/Swift Musicology/Swift MusicologyTests/Swift_MusicologyTests.swift +++ b/Swift Musicology/Swift MusicologyTests/Swift_MusicologyTests.swift @@ -10,25 +10,13 @@ import XCTest @testable import Swift_Musicology class Swift_MusicologyTests: XCTestCase { - override func setUp() { + super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - } diff --git a/Swift Musicology/Swift MusicologyTests/TempoTests.swift b/Swift Musicology/Swift MusicologyTests/TempoTests.swift new file mode 100644 index 0000000..8a03524 --- /dev/null +++ b/Swift Musicology/Swift MusicologyTests/TempoTests.swift @@ -0,0 +1,82 @@ +// +// TempoTests.swift +// Swift MusicologyTests +// +// Created by roux g. buciu on 2020-05-02. +// Copyright © 2020 roux g. buciu. All rights reserved. +// + +import XCTest +@testable import Swift_Musicology + +class TempoTests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testNoteValueConversions() { + let noteValue = NoteValue(type: .half, modifier: .dotted) + XCTAssertEqual(noteValue / NoteValueType.sixteenth, 12) + XCTAssertEqual(noteValue / NoteValueType.whole, 0.75) + } + + func testDurations() { + let timeSignature = TimeSignature(beats: 4, noteValue: .quarter) // 4/4 + let tempo = Tempo(timeSignature: timeSignature, bpm: 120) // 120BPM + var noteValue = NoteValue(type: .quarter) + var duration = tempo.duration(of: noteValue) + XCTAssert(duration == 0.5) + + noteValue.modifier = .dotted + duration = tempo.duration(of: noteValue) + XCTAssert(duration == 0.75) + } + + func testSampleLengthCalcuation() { + let rates = [ + NoteValue(type: .whole, modifier: .default), + NoteValue(type: .half, modifier: .default), + NoteValue(type: .half, modifier: .dotted), + NoteValue(type: .half, modifier: .triplet), + NoteValue(type: .quarter, modifier: .default), + NoteValue(type: .quarter, modifier: .dotted), + NoteValue(type: .quarter, modifier: .triplet), + NoteValue(type: .eighth, modifier: .default), + NoteValue(type: .eighth, modifier: .dotted), + NoteValue(type: .sixteenth, modifier: .default), + NoteValue(type: .sixteenth, modifier: .dotted), + NoteValue(type: .thirtysecond, modifier: .default), + NoteValue(type: .sixtyfourth, modifier: .default), + ] + + let tempo = Tempo() + let sampleLengths = rates + .map({ tempo.sampleLength(of: $0) }) + .map({ round(100 * $0) / 100 }) + + let expected: [Double] = [ + 88200.0, + 44100.0, + 66150.0, + 29401.47, + 22050.0, + 33075.0, + 14700.73, + 11025.0, + 16537.5, + 5512.5, + 8268.75, + 2756.25, + 1378.13, + ] + + XCTAssertEqual(sampleLengths, expected) + } +}