Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
squarefrog committed Aug 5, 2023
2 parents 03c77e9 + 2d958e8 commit 582a05d
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 7 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [3.1.0] - 2023-08-05

### Added

- `Hashable` conformance for `Note`
- `CustomStringConvertable` conformance
- `Identifiable` conformance for `Note`

## [3.0.0] - 2020-07-29

### Breaking
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

extension HarmonicMinorMode: CustomStringConvertible {
public var description: String {
switch self {
case .harmonicMinor:
return "Harmonic Minor"
case .locrianSharpSix:
return "Locrian ♯6"
case .ionianSharpFive:
return "Ionian ♯5"
case .dorianSharpFour:
return "Dorian ♯4"
case .phrygianDominant:
return "Phrygian Dominant"
case .lydianSharpTwo:
return "Lydian ♯2"
case .ultraLocrian:
return "Ultra Locrian"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

extension MajorMode: CustomStringConvertible {
public var description: String {
return self.rawValue.capitalized
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

extension MelodicMinorMode: CustomStringConvertible {
public var description: String {
switch self {
case .melodicMinor:
return "Melodic Minor"
case .dorianFlatTwo:
return "Dorian ♭2"
case .lydianAugmented:
return "Lydian Augmented"
case .lydianDominant:
return "Lydian Dominant"
case .mixolydianFlatSix:
return "Mixolydian ♭6"
case .aeolianFlatFive:
return "Aeolian ♭5"
case .superLocrian:
return "Super Locrian"
}
}
}
15 changes: 15 additions & 0 deletions Sources/Scaletor/Models/Accidental.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,19 @@ extension Accidental {
self = .natural
}
}

internal var id: Int {
switch self {
case .doubleFlat:
return -2
case .flat:
return -1
case .natural:
return 0
case .sharp:
return 1
case .doubleSharp:
return 2
}
}
}
4 changes: 4 additions & 0 deletions Sources/Scaletor/Models/HarmonicMinorMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ public enum HarmonicMinorMode: String, CaseIterable {
}

extension HarmonicMinorMode: Mode {
public static var description: String {
"Harmonic Minor"
}

public var chords: [ChordVoicing] {
[.minor, .diminished, .augmented, .minor, .major, .major, .diminished]
.offset(by: index)
Expand Down
4 changes: 4 additions & 0 deletions Sources/Scaletor/Models/MajorMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ public enum MajorMode: String, CaseIterable {
}

extension MajorMode: Mode {
public static var description: String {
"Major"
}

public var chords: [ChordVoicing] {
[.major, .minor, .minor, .major, .major, .minor, .diminished]
.offset(by: index)
Expand Down
4 changes: 4 additions & 0 deletions Sources/Scaletor/Models/MelodicMinorMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ public enum MelodicMinorMode: String, CaseIterable {
}

extension MelodicMinorMode: Mode {
public static var description: String {
"Melodic Minor"
}

public var chords: [ChordVoicing] {
[.minor, .diminished, .augmented, .major, .major, .diminished, .diminished]
.offset(by: index)
Expand Down
25 changes: 21 additions & 4 deletions Sources/Scaletor/Models/Note.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import Foundation

public struct Note: Equatable {
public struct Note: Equatable, Identifiable {
public let id: Int
public let pitch: Pitch
public let accidental: Accidental

public init(pitch: Pitch, accidental: Accidental = .natural) {
self.pitch = pitch
self.accidental = accidental
self.id = Self.makeId(from: pitch, accidental: accidental)
}

/// Create a note from a string.
Expand All @@ -27,13 +29,28 @@ public struct Note: Equatable {
guard (1...2).contains(input.count) else { throw NoteError.outOfBounds }

let letter = String(input.prefix(1))
let accidental = String(input.suffix(1))
let symbol = String(input.suffix(1))

guard let pitch = Pitch(rawValue: letter.lowercased()) else {
throw NoteError.invalidNote
}

self.pitch = pitch
self.accidental = input.count == 2 ? Accidental(rawValue: accidental) : .natural
let accidental = input.count == 2 ? Accidental(rawValue: symbol) : .natural

self.init(pitch: pitch, accidental: accidental)
}
}

extension Note: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(description)
}
}

extension Note {
private static func makeId(from pitch: Pitch, accidental: Accidental) -> Int {
let id = pitch.id + accidental.id
let max = 12
return ((id % max) + max) % max
}
}
21 changes: 21 additions & 0 deletions Sources/Scaletor/Models/Pitch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,24 @@ import Foundation
public enum Pitch: String, CaseIterable {
case a, b, c, d, e, f, g
}

extension Pitch {
internal var id: Int {
switch self {
case .a:
return 0
case .b:
return 2
case .c:
return 3
case .d:
return 5
case .e:
return 7
case .f:
return 8
case .g:
return 10
}
}
}
5 changes: 3 additions & 2 deletions Sources/Scaletor/Protocols/Mode.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation

public protocol Mode {
var chords: [ChordVoicing] { get }
public protocol Mode: CustomStringConvertible {
static var description: String { get }
var intervals: [Interval] { get }
var chords: [ChordVoicing] { get }
}
37 changes: 36 additions & 1 deletion Tests/ScaletorTests/NoteTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Scaletor
@testable import Scaletor
import XCTest

class NoteTests: XCTestCase {
Expand Down Expand Up @@ -48,4 +48,39 @@ class NoteTests: XCTestCase {
let note = Note(pitch: .b, accidental: .sharp)
XCTAssertEqual("\(note)", "B♯")
}

func test_Id_MatchesNaturalPitch() {
let note = Note(pitch: .c, accidental: .natural)
XCTAssertEqual(note.id, Pitch.c.id)
}

func test_Id_MatchesFlattenedPitch() {
let note = Note(pitch: .c, accidental: .flat)
XCTAssertEqual(note.id, Pitch.b.id)
}

func test_Id_MatchesDoubleFlattenedPitch() {
let note = Note(pitch: .c, accidental: .doubleFlat)
XCTAssertEqual(note.id, Pitch.b.id - 1)
}

func test_Id_MatchesSharpenedPitch() {
let note = Note(pitch: .c, accidental: .sharp)
XCTAssertEqual(note.id, Pitch.c.id + 1)
}

func test_Id_MatchesDoubleSharpenedPitch() {
let note = Note(pitch: .c, accidental: .doubleSharp)
XCTAssertEqual(note.id, Pitch.d.id)
}

func test_Id_WrapsUpperBounds() {
let note = Note(pitch: .g, accidental: .doubleSharp)
XCTAssertEqual(note.id, Pitch.a.id)
}

func test_Id_WrapsLowerBounds() {
let note = Note(pitch: .a, accidental: .doubleFlat)
XCTAssertEqual(note.id, Pitch.g.id)
}
}

0 comments on commit 582a05d

Please sign in to comment.