-
Notifications
You must be signed in to change notification settings - Fork 4
/
Pitch.Spelling.swift
194 lines (146 loc) · 6.51 KB
/
Pitch.Spelling.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//
// Pitch.Spelling.swift
// Pitch
//
// Created by James Bean on 3/17/16.
// Copyright © 2016 James Bean. All rights reserved.
//
import Pitch
extension Pitch {
/// Spelled representation of a `Pitch`.
public struct Spelling {
/// The modification of a `Pitch.Spelling` from the given `LetterName`.
public struct Modifier {
public static let doubleFlat = Modifier(.doubleFlat)
public static let threeQuarterFlat = Modifier(.flat, [.undecimal(-1)])
public static let flat = Modifier(.flat)
public static let quarterFlat = Modifier(.natural, [.undecimal(-1)])
public static let natural = Modifier(.natural)
public static let quarterSharp = Modifier(.natural, [.undecimal(1)])
public static let sharp = Modifier(.sharp)
public static let threeQuarterSharp = Modifier(.sharp, [.undecimal(1)])
public static let doubleSharp = Modifier(.doubleSharp)
// MARK: - Nested Types
/// Base modifier (natural, sharps, flats).
public struct Pythagorean {
// MARK: - Type Properties
public static let doubleFlat = Pythagorean(-2)
public static let flat = Pythagorean(-1)
public static let natural = Pythagorean(0)
public static let sharp = Pythagorean(1)
public static let doubleSharp = Pythagorean(2)
// MARK: - Computed Properties
/// Adjustment to letter name in semitones.
var adjustment: Double {
return Double(amount)
}
// MARK: - Instance Properties
/// The amount of semitones above or below a given letter name.
let amount: Int
// MARK: - Initializers
/// Creates a `Pythagorean` base modifier with the given `amount`.
init(_ amount: Int) {
self.amount = amount
}
}
/// Alteration to the base pythagorean modifier.
///
/// - TODO: Prime intervals in limits 16-64.
public enum Alteration {
/// Alteration by one syntonic comma (81/80). +/- 21.5 cents.
case ptolemaic(Int)
/// Alteration by one septimal comma (64/63). +/- 27.3 cents.
case septimal(Int)
/// Alteration by one undecimal quarter tone (33/32). +/- 53.3 cents.
case undecimal(Int)
/// Alteration by one tridecimal third tone (27/26). +/- 65.3 cents.
case tridecimal(Int)
/// Alteration by the given amount of cents.
case cents(Double)
/// - Returns: The amount of adjustment in semitones.
var adjustment: Double {
switch self {
case .ptolemaic(let amount): return 0.215 * Double(amount)
case .septimal(let amount): return 0.273 * Double(amount)
// FIXME: This should return 0.535, but forcing 0.5 for previous tests
case .undecimal(let amount): return 0.5 * Double(amount)
case .tridecimal(let amount): return 0.653 * Double(amount)
case .cents(let amount): return amount / 100
}
}
}
// MARK: - Computed Properties
/// The adjustment to letter name in semitones.
public var adjustment: Double {
return base.adjustment + alterations.map { $0.adjustment }.sum
}
// MARK: Instance Properties
/// The natural, sharp, or flat base modifier.
let base: Pythagorean
/// The additional alterations made to the base modifier.
let alterations: [Alteration]
// MARK: - Initializers
/// Creates a `Modifier` with the given `Pythagorean` base modifier and the given
/// `Alteration` values.
public init(_ base: Pythagorean = .natural, _ alterations: [Alteration] = []) {
self.base = base
self.alterations = alterations
}
}
// MARK: - Computed Properties
/// `Pitch.Class` represented by this `Pitch.Spelling` value.
public var pitchClass: Pitch.Class {
return Pitch.Class(letterName.pitchClass + modifier.adjustment)
}
// MARK: - Instance Properties
/// `LetterName` of a `Pitch.Spelling`.
public let letterName: LetterName
/// `Modifier` of a `Pitch.Spelling`.
public let modifier: Modifier
// MARK: - Initializers
/// Creates a Pitch.Spelling with the given `letterName` and the given `modifier`.
public init(_ letterName: LetterName, _ modifier: Modifier = .natural) {
self.letterName = letterName
self.modifier = modifier
}
public init(
_ letterName: LetterName,
_ base: Modifier.Pythagorean = .natural,
_ alterations: Modifier.Alteration...
)
{
self.letterName = letterName
self.modifier = Modifier(base, alterations)
}
}
}
extension Pitch.Spelling {
// MARK: - Static Properties
/// The `c natural` closest to the middle of a piano keyboard.
public static let middleC = Pitch.Spelling(.c, .natural)
}
extension Pitch.Spelling: Comparable {
// MARK: - Comparable
/// - Returns: `true` if the left hand `Pitch.Spelling` value is less than the right hand
/// `Pitch.Spelling` value.
public static func < (lhs: Pitch.Spelling, rhs: Pitch.Spelling) -> Bool {
if lhs.letterName.steps == rhs.letterName.steps {
return lhs.modifier.adjustment < rhs.modifier.adjustment
}
return lhs.letterName.steps < rhs.letterName.steps
}
}
extension Pitch.Spelling.Modifier.Pythagorean: Equatable { }
extension Pitch.Spelling.Modifier.Pythagorean: Hashable { }
extension Pitch.Spelling.Modifier.Alteration: Equatable { }
extension Pitch.Spelling.Modifier.Alteration: Hashable { }
extension Pitch.Spelling.Modifier: Equatable { }
extension Pitch.Spelling.Modifier: Hashable { }
extension Pitch.Spelling: Equatable { }
extension Pitch.Spelling: Hashable { }
extension Pitch.Spelling {
// MARK: - Spelling Distance
public var spellingDistance: LineOfFifths.Distance {
return LineOfFifths.distance(ofPitchSpelling: self)
}
}