├── .github
└── workflows
│ └── swift.yml
├── .gitignore
├── .swift-version
├── .swiftformat
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .travis.yml
├── LICENSE
├── MusicTheory.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── xcshareddata
│ └── xcschemes
│ ├── MusicTheory Mac.xcscheme
│ ├── MusicTheory TV.xcscheme
│ ├── MusicTheory Watch.xcscheme
│ ├── MusicTheory iOS.xcscheme
│ └── MusicTheoryTests.xcscheme
├── MusicTheorySwift.podspec
├── Package.swift
├── Playground.playground
├── Pages
│ └── Untitled Page.xcplaygroundpage
│ │ └── Contents.swift
└── contents.xcplayground
├── README.md
├── Sources
└── MusicTheory
│ ├── Accidental.swift
│ ├── Chord.swift
│ ├── ChordProgression.swift
│ ├── HarmonicFunctions.swift
│ ├── Info-Mac.plist
│ ├── Info-TV.plist
│ ├── Info-Watch.plist
│ ├── Info-iOS.plist
│ ├── Interval.swift
│ ├── Key.swift
│ ├── MusicTheory.h
│ ├── NoteValue.swift
│ ├── Pitch.swift
│ ├── Scale.swift
│ ├── ScaleType.swift
│ ├── Tempo.swift
│ └── TimeSignature.swift
└── Tests
└── MusicTheoryTests
├── Info.plist
├── MusicTheoryTests.swift
└── XCTestManifests.swift
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Swift project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
3 |
4 | name: Swift
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: macos-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Build
20 | run: swift build -v
21 | - name: Run tests
22 | run: swift test -v
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/swift,xcode
3 |
4 | ### Swift ###
5 | # Xcode
6 | #
7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
8 |
9 | ## Build generated
10 | build/
11 | DerivedData/
12 |
13 | ## Various settings
14 | *.pbxuser
15 | !default.pbxuser
16 | *.mode1v3
17 | !default.mode1v3
18 | *.mode2v3
19 | !default.mode2v3
20 | *.perspectivev3
21 | !default.perspectivev3
22 | xcuserdata/
23 |
24 | ## Other
25 | *.moved-aside
26 | *.xcuserstate
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 | *.ipa
31 | *.dSYM.zip
32 | *.dSYM
33 |
34 | ## Playgrounds
35 | timeline.xctimeline
36 | playground.xcworkspace
37 |
38 | # Swift Package Manager
39 | #
40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
41 | # Packages/
42 | .build/
43 |
44 | # CocoaPods
45 | #
46 | # We recommend against adding the Pods directory to your .gitignore. However
47 | # you should judge for yourself, the pros and cons are mentioned at:
48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
49 | #
50 | Pods/
51 |
52 | # Carthage
53 | #
54 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
55 | Carthage/Checkouts
56 |
57 | Carthage/Build
58 |
59 | # fastlane
60 | #
61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
62 | # screenshots whenever they are needed.
63 | # For more information about the recommended setup visit:
64 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
65 |
66 | fastlane/report.xml
67 | fastlane/Preview.html
68 | fastlane/screenshots
69 | fastlane/test_output
70 |
71 |
72 | ### Xcode ###
73 | # Xcode
74 | #
75 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
76 |
77 | ## Build generated
78 |
79 | ## Various settings
80 |
81 | ## Other
82 | *.xccheckout
83 | *.xcscmblueprint
84 |
85 | # Docs
86 | docs/
87 |
88 | # End of https://www.gitignore.io/api/swift,xcode
89 | .DS_Store
90 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.1
2 |
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | --indent 2
2 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: osx
2 | language: swift
3 | osx_image: xcode10.2
4 | xcode_project: MusicTheory.xcodeproj # path to your xcodeproj folder
5 | xcode_scheme: MusicTheoryTests
6 | script:
7 | - xcodebuild -project MusicTheory.xcodeproj -scheme MusicTheory\ Mac ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
8 | - xcodebuild test -project MusicTheory.xcodeproj -scheme MusicTheoryTests ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2016, Cem Olcay
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a
4 | copy of this software and associated documentation files (the "Software"),
5 | to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 | and/or sell copies of the Software, and to permit persons to whom the
8 | Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 | DEALINGS IN THE SOFTWARE.
20 |
21 |
--------------------------------------------------------------------------------
/MusicTheory.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MusicTheory.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MusicTheory.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MusicTheory.xcodeproj/xcshareddata/xcschemes/MusicTheory Mac.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/MusicTheory.xcodeproj/xcshareddata/xcschemes/MusicTheory TV.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/MusicTheory.xcodeproj/xcshareddata/xcschemes/MusicTheory Watch.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/MusicTheory.xcodeproj/xcshareddata/xcschemes/MusicTheory iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/MusicTheory.xcodeproj/xcshareddata/xcschemes/MusicTheoryTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/MusicTheorySwift.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint MusicTheorySwift.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |s|
10 |
11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
12 | #
13 | # These will help people to find your library, and whilst it
14 | # can feel like a chore to fill in it's definitely to your advantage. The
15 | # summary should be tweet-length, and the description more in depth.
16 | #
17 |
18 | s.name = "MusicTheorySwift"
19 | s.version = "1.3.1"
20 | s.summary = "A music theory library with `Note`, `Interval`, `Tone`, `Scale` and `Chord` representations in swift enums."
21 |
22 | # This description is used to generate tags and improve search results.
23 | # * Think: What does it do? Why did you write it? What is the focus?
24 | # * Try to keep it short, snappy and to the point.
25 | # * Write the description between the DESC delimiters below.
26 | # * Finally, don't worry about the indent, CocoaPods strips it!
27 | s.description = <<-DESC
28 | MusicTheory [](https://travis-ci.org/cemolcay/MusicTheory)
29 | ===
30 |
31 | A music theory library with `Key`, `Pitch`, `Interval`, `Scale` and `Chord` representations in swift enums.
32 |
33 | Requirements
34 | ----
35 | * Swift 5.0+
36 | * iOS 8.0+
37 | * macOS 10.9+
38 | * tvOS 9.0+
39 | * watchOS 2.0+
40 |
41 | Install
42 | ----
43 |
44 | ```
45 | pod 'MusicTheorySwift'
46 | ```
47 |
48 | Usage
49 | ----
50 |
51 | `MusicTheory` adds a bunch of basic enums and structs that you can define pretty much any music related data. Most importants are `Pitch`, `Key`, `Scale` and `Chord`.
52 |
53 | All data types conforms `Codable`, `CustomStringConvertable`.
54 | `Pitch`, and `Accident` structs are `RawPresentable` with `Int` as well as `ExpressibleByIntegerLiteral` that you can represent them directly with `Int`s.
55 |
56 | #### `Pitch` and `Key`
57 |
58 | - All keys can be defined with `Key` struct.
59 | - It has a `KeyType` where you can set the base key like C, D, A, G, and an `Accidental` where it can be `.natural`, `.flat`, `sharp` or more specific like `.sharps(amount: 3)`.
60 | - You can create `Pitch`es with a `Key` and octave.
61 | - Also, you can create `Pitch`es with MIDI note number. `rawValue` of a pitch is its MIDI note number.
62 | - `Pitch`, `Key`, `Accidental` structs are equatable, `+` and `-` custom operators defined for making calulations easier.
63 | - Also, there are other helper functions or properties like frequency of a note.
64 |
65 | ``` swift
66 | let dFlat = Key(type: d, accidental: .flat)
67 | let c4 = Pitch(key: Key(type: .c), octave: 4)
68 | ```
69 |
70 | #### `Interval`
71 |
72 | - Intervals are halfsteps between pitches.
73 | - They are `IntegerLiteral` and you can make add/subsctract them between themselves, notes or note types.
74 | - You can build up a custom interval with its quality, degree and semitone properties.
75 | - You can build scales or chords from intervals.
76 | - Minor, major, perfect, augmented and diminished intervals up to 2 octaves are predefined.
77 |
78 | #### `ScaleType` and `Scale`
79 |
80 | - `ScaleType` enum defines a lot of readymade scales.
81 | - Also, you can create a custom scale type by `ScaleType.custom(intervals: [Interval], description: String)`
82 | - `Scale` defines a scale with a scale type and root key.
83 | - You can generate notes of scale in an octave range.
84 | - Also you can generate `HarmonicField` of a scale.
85 | - Harmonic field is all possible triad, tetrad or extended chords in a scale.
86 |
87 | ``` swift
88 | let c = Key(type: .c)
89 | let maj: ScaleType = .major
90 | let cMaj = Scale(type: maj, key: c)
91 | ```
92 |
93 | #### `ChordType` and `Chord`
94 |
95 | - `ChordType` is a struct with `ChordPart`s which are building blocks of chords.
96 | - You can define any chord existing with `ChordType`.
97 | - Thirds, fifths, sixths, sevenths and extensions are parts of the `ChordType`.
98 | - Each of them also structs which conforms `ChordPart` protocol.
99 | - `Chord` defines chords with type and a root key.
100 | - You can generate notes of chord in any octave range.
101 | - You can generate inversions of any chord.
102 |
103 | ``` swift
104 | let m13 = ChordType(
105 | third: .minor,
106 | seventh: .dominant,
107 | extensions: [
108 | ChordExtensionType(type: .thirteenth)
109 | ])
110 | let cm13 = Chord(type: m13, key: Key(type: .c))
111 | ```
112 |
113 | - You can generate chord progressions with `ChordProgression` enum.
114 | - For any scale, in any harmonic field, for any inversion.
115 |
116 | ``` swift
117 | let progression = ChordProgression.i_ii_vi_iv
118 | let cSharpHarmonicMinorTriadsProgression = progression.chords(
119 | for: cSharpHarmonicMinor,
120 | harmonicField: .triad,
121 | inversion: 0)
122 | ```
123 |
124 | #### `Tempo` and `TimeSignature`
125 |
126 | - Tempo is a helper struct to define timings in your music app.
127 | - TimeSignature is number of beats in per measure and `NoteValue` of each beat.
128 | - You can calculate notes duration in any tempo by ther `NoteValue`.
129 | - Note value defines the note's duration in a beat. It could be whole note, half note, quarter note, 8th, 16th or 32nd note.
130 |
131 | Documentation
132 | ----
133 |
134 | [Full documentation are here](https://cemolcay.github.io/MusicTheory/)
135 |
136 | Unit Tests
137 | ----
138 |
139 | You can find unit tests in `MusicTheoryTests` target.
140 | Press `⌘+U` for running tests.
141 |
142 | AppStore
143 | ----
144 |
145 | This library battle tested in my apps for iOS, macOS, watchOS and tvOS, check them out!
146 | [KeyBud](https://itunes.apple.com/us/app/keybud-music-theory-app/id1203856335?mt=8) (iOS, watchOS, tvOS, macOS)
147 | [FretBud](https://itunes.apple.com/us/app/fretbud-chord-scales-for-guitar-bass-and-more/id1234224249?mt=8) (iOS, watchOS, tvOS)
148 | [ChordBud](https://itunes.apple.com/us/app/chordbud-chord-progressions/id1313017378?mt=8) (iOS)
149 | [ArpBud](https://itunes.apple.com/us/app/arpbud-midi-sequencer-more/id1349342326?ls=1&mt=8) (iOS)
150 | [ScaleBud](https://itunes.apple.com/us/app/scalebud-auv3-midi-keyboard/id1409125865?ls=1&mt=8) (iOS, AUv3)
151 | DESC
152 |
153 | s.homepage = "https://github.com/cemolcay/MusicTheory"
154 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
155 |
156 |
157 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
158 | #
159 | # Licensing your code is important. See http://choosealicense.com for more info.
160 | # CocoaPods will detect a license file if there is a named LICENSE*
161 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
162 | #
163 |
164 | s.license = "MIT"
165 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" }
166 |
167 |
168 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
169 | #
170 | # Specify the authors of the library, with email addresses. Email addresses
171 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
172 | # accepts just a name if you'd rather not provide an email address.
173 | #
174 | # Specify a social_media_url where others can refer to, for example a twitter
175 | # profile URL.
176 | #
177 |
178 | s.author = { "cemolcay" => "ccemolcay@gmail.com" }
179 | # Or just: s.author = "cemolcay"
180 | # s.authors = { "cemolcay" => "ccemolcay@gmail.com" }
181 | s.social_media_url = "http://twitter.com/cemolcay"
182 |
183 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
184 | #
185 | # If this Pod runs only on iOS or OS X, then specify the platform and
186 | # the deployment target. You can optionally include the target after the platform.
187 | #
188 |
189 | # s.platform = :ios
190 | # s.platform = :ios, "5.0"
191 |
192 | # When using multiple platforms
193 | s.ios.deployment_target = "8.0"
194 | s.osx.deployment_target = "10.9"
195 | s.watchos.deployment_target = "2.0"
196 | s.tvos.deployment_target = "9.0"
197 |
198 |
199 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
200 | #
201 | # Specify the location from where the source should be retrieved.
202 | # Supports git, hg, bzr, svn and HTTP.
203 | #
204 |
205 | s.source = { :git => "https://github.com/cemolcay/MusicTheory.git", :tag => "#{s.version}" }
206 |
207 |
208 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
209 | #
210 | # CocoaPods is smart about how it includes source code. For source files
211 | # giving a folder will include any swift, h, m, mm, c & cpp files.
212 | # For header files it will include any header in the folder.
213 | # Not including the public_header_files will make all headers public.
214 | #
215 |
216 | s.source_files = "Sources/MusicTheory/*.{swift}"
217 | # s.exclude_files = "Classes/Exclude"
218 |
219 | # s.public_header_files = "Classes/**/*.h"
220 |
221 |
222 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
223 | #
224 | # A list of resources included with the Pod. These are copied into the
225 | # target bundle with a build phase script. Anything else will be cleaned.
226 | # You can preserve files from being cleaned, please don't preserve
227 | # non-essential files like tests, examples and documentation.
228 | #
229 |
230 | # s.resource = "icon.png"
231 | # s.resources = "Resources/*.png"
232 |
233 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave"
234 |
235 |
236 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
237 | #
238 | # Link your library with frameworks, or libraries. Libraries do not include
239 | # the lib prefix of their name.
240 | #
241 |
242 | s.framework = "Foundation"
243 | # s.frameworks = "SomeFramework", "AnotherFramework"
244 |
245 | # s.library = "iconv"
246 | # s.libraries = "iconv", "xml2"
247 |
248 |
249 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
250 | #
251 | # If your library depends on compiler flags you can set them in the xcconfig hash
252 | # where they will only apply to your library. If you depend on other Podspecs
253 | # you can include multiple dependencies to ensure it works.
254 |
255 | s.requires_arc = true
256 |
257 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
258 | # s.dependency "JSONKit", "~> 1.4"
259 |
260 | end
261 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "MusicTheory",
7 | products: [
8 | .library(
9 | name: "MusicTheory",
10 | targets: ["MusicTheory"]),
11 | ],
12 | targets: [
13 | .target(
14 | name: "MusicTheory",
15 | dependencies: []),
16 | .testTarget(
17 | name: "MusicTheoryTests",
18 | dependencies: ["MusicTheory"]),
19 | ]
20 | )
21 |
--------------------------------------------------------------------------------
/Playground.playground/Pages/Untitled Page.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 | //: Playground - noun: a place where people can play
2 |
3 | import Foundation
4 | import MusicTheory
5 |
6 | let p: Pitch = "cb2"
7 | let b: Key = "b"
8 | p.key == b
9 |
10 | let ds: Key = "d#"
11 | let eb: Key = "eb"
12 | ds == eb
13 | ds === eb
14 |
15 | // e f f# g g# a a# b c c# d d#
16 | Pitch(key: Key(type: .e, accidental: .natural), octave: 0) - .A5
17 | Pitch(key: Key(type: .e, accidental: .natural), octave: 0) - .d5
18 | Pitch(key: Key(type: .e, accidental: .natural), octave: 0) + .A5
19 | Pitch(key: Key(type: .e, accidental: .natural), octave: 0) + .d5
20 |
21 | // c# d d# e f f# g g# a a# b c
22 | let cSharpHarmonicMinor = Scale(type: .harmonicMinor, key: Key(type: .c, accidental: .sharp))
23 | Pitch(key: Key(type: .c, accidental: .sharp), octave: 0) + .M7
24 | Pitch(key: Key(type: .b, accidental: .natural), octave: 1) - .M7
25 |
26 | // A minor pentatonic triads
27 | let aminP = Scale(type: .pentatonicMinor, key: "a")
28 | let aminPtriad = aminP.harmonicField(for: .thirteenth)
29 | print(aminPtriad)
30 |
31 | // chord progression for C# harmonic minor triads
32 | let progression = ChordProgression.i_ii_vi_iv
33 | let cSharpHarmonicMinorTriadsProgression = progression.chords(
34 | for: cSharpHarmonicMinor,
35 | harmonicField: .triad,
36 | inversion: 0
37 | )
38 | print(cSharpHarmonicMinorTriadsProgression)
39 |
40 | let c13 = Chord(
41 | type: ChordType(
42 | third: .major,
43 | fifth: .perfect,
44 | sixth: nil,
45 | seventh: .dominant,
46 | suspended: nil,
47 | extensions: [
48 | ChordExtensionType(type: .thirteenth, accidental: .natural),
49 | ]
50 | ),
51 | key: Key(
52 | type: .c,
53 | accidental: .natural
54 | )
55 | )
56 |
57 | let cdim7 = Chord(
58 | type: ChordType(
59 | third: .major,
60 | fifth: .diminished,
61 | seventh: .diminished),
62 | key: Key(type: .c))
63 | cdim7.notation
64 | print(cdim7.keys)
65 |
66 | print(c13.type.intervals)
67 | Pitch(key: Key(type: .c, accidental: .natural), octave: 1) + .M9
68 | print(c13.pitches(octave: 1))
69 | print(c13.inversions[1].pitches(octave: 1))
70 |
71 | var dmajor = Scale(type: .major, key: Key(type: .d, accidental: .natural))
72 | print(dmajor.pitches(octave: 1))
73 |
74 | // d d# e f f# g g# a a# b c c#
75 | Pitch(key: Key(type: .d, accidental: .natural), octave: 1) + .M7
76 | Interval.M7.degree
77 | Interval.M7.semitones
78 |
79 | // c c# d d# e f f# g g# a a# b c c# d
80 | Pitch(key: Key(type: .c, accidental: .natural), octave: 1) + .M9
81 | Interval.M9.degree
82 | Interval.M9.semitones
83 |
84 | // bb b c cb d db e f gb g ab a bb b c
85 | Pitch(key: Key(type: .b, accidental: .flat), octave: 1) + .M9
86 |
87 | Pitch(key: Key(type: .c, accidental: .natural), octave: 3) - .M9
88 |
89 | let aHarmonicMinor = Scale(type: .harmonicMinor, key: Key(type: .a))
90 | print(aHarmonicMinor)
91 | let harmonicFunctions = HarmonicFunctions(scale: aHarmonicMinor)
92 | HarmonicFunctionType.allCases.forEach({ type in
93 | let relatedKeys = type.direction.map({ related in
94 | harmonicFunctions.harmonicFunction(for: related)!
95 | })
96 | print(type, relatedKeys)
97 | })
98 |
99 | func nearestKey(key: Key, scale: Scale) -> Key {
100 | var nearest = key
101 | var distance = 100
102 | let keyValue = key.type.rawValue + key.accidental.rawValue
103 | for scaleKey in scale.keys {
104 | let scaleKeyValue = scaleKey.type.rawValue + scaleKey.accidental.rawValue
105 | let diff = abs(scaleKeyValue - keyValue)
106 | if diff < distance {
107 | distance = diff
108 | nearest = scaleKey
109 | }
110 | }
111 | return nearest
112 | }
113 |
114 | nearestKey(key: "f", scale: dmajor)
115 |
116 |
--------------------------------------------------------------------------------
/Playground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MusicTheory
2 | ===
3 |
4 | A music theory library with `Key`, `Pitch`, `Interval`, `Scale` and `Chord` representations in swift enums.
5 |
6 | Requirements
7 | ----
8 | * Swift 4.0+
9 | * iOS 8.0+
10 | * macOS 10.9+
11 | * tvOS 9.0+
12 | * watchOS 2.0+
13 |
14 | Install
15 | ----
16 |
17 | ### CocoaPods
18 |
19 | ```
20 | pod 'MusicTheorySwift'
21 | ```
22 |
23 | ### Swift Package Manager
24 |
25 | ``` swift
26 | let package = Package(
27 | name: ...
28 | dependencies: [
29 | .package(url: "https://github.com/cemolcay/MusicTheory.git")
30 | ],
31 | targets: ...
32 | )
33 | ```
34 |
35 | Usage
36 | ----
37 |
38 | `MusicTheory` adds a bunch of basic enums and structs that you can define pretty much any music related data. Most importants are `Pitch`, `Key`, `Scale` and `Chord`.
39 |
40 | All data types conforms `Codable`, `CustomStringConvertable`.
41 | `Pitch`, and `Accident` structs are `RawPresentable` with `Int` as well as `ExpressibleByIntegerLiteral` that you can represent them directly with `Int`s.
42 |
43 | #### `Pitch` and `Key`
44 |
45 | - All keys can be defined with `Key` struct.
46 | - It has a `KeyType` where you can set the base key like C, D, A, G, and an `Accidental` where it can be `.natural`, `.flat`, `sharp` or more specific like `.sharps(amount: 3)`.
47 | - You can create `Pitch`es with a `Key` and octave.
48 | - Also, you can create `Pitch`es with MIDI note number. `rawValue` of a pitch is its MIDI note number.
49 | - `Pitch`, `Key`, `Accidental` structs are equatable, `+` and `-` custom operators defined for making calculations easier.
50 | - Also, there are other helper functions or properties like frequency of a note.
51 | - You can define them with directly string representations as well.
52 |
53 | ``` swift
54 | let dFlat = Key(type: d, accidental: .flat)
55 | let c4 = Pitch(key: Key(type: .c), octave: 4)
56 | let aSharp: Key = "a#" // Key(type: .a, accidental: .sharp)
57 | let gFlat3: Pitch = "gb3" // or "g♭3" or "Gb3" is Pitch(key: (type: .g, accidental: .flat), octave: 3)
58 | ```
59 |
60 | #### `Interval`
61 |
62 | - Intervals are halfsteps between pitches.
63 | - They are `IntegerLiteral` and you can make add/substract them between themselves, notes or note types.
64 | - You can build up a custom interval with its quality, degree and semitone properties.
65 | - You can build scales or chords from intervals.
66 | - Minor, major, perfect, augmented and diminished intervals up to 2 octaves are predefined.
67 |
68 | #### `ScaleType` and `Scale`
69 |
70 | - `ScaleType` enum defines a lot of readymade scales.
71 | - Also, you can create a custom scale type by `ScaleType.custom(intervals: [Interval], description: String)`
72 | - `Scale` defines a scale with a scale type and root key.
73 | - You can generate notes of scale in an octave range.
74 | - Also you can generate `HarmonicField` of a scale.
75 | - Harmonic field is all possible triad, tetrad or extended chords in a scale.
76 |
77 | ``` swift
78 | let c = Key(type: .c)
79 | let maj: ScaleType = .major
80 | let cMaj = Scale(type: maj, key: c)
81 | ```
82 |
83 | #### `ChordType` and `Chord`
84 |
85 | - `ChordType` is a struct with `ChordPart`s which are building blocks of chords.
86 | - You can define any chord existing with `ChordType`.
87 | - Thirds, fifths, sixths, sevenths and extensions are parts of the `ChordType`.
88 | - Each of them also structs which conforms `ChordPart` protocol.
89 | - `Chord` defines chords with type and a root key.
90 | - You can generate notes of chord in any octave range.
91 | - You can generate inversions of any chord.
92 |
93 | ``` swift
94 | let m13 = ChordType(
95 | third: .minor,
96 | seventh: .dominant,
97 | extensions: [
98 | ChordExtensionType(type: .thirteenth)
99 | ])
100 | let cm13 = Chord(type: m13, key: Key(type: .c))
101 | ```
102 |
103 | - You can generate chord progressions with `ChordProgression` enum.
104 | - For any scale, in any harmonic field, for any inversion.
105 |
106 | ``` swift
107 | let progression = ChordProgression.i_ii_vi_iv
108 | let cSharpHarmonicMinorTriadsProgression = progression.chords(
109 | for: cSharpHarmonicMinor,
110 | harmonicField: .triad,
111 | inversion: 0)
112 | ```
113 |
114 | #### `Tempo` and `TimeSignature`
115 |
116 | - Tempo is a helper struct to define timings in your music app.
117 | - TimeSignature is number of beats in per measure and `NoteValue` of each beat.
118 | - You can calculate notes duration in any tempo by ther `NoteValue`.
119 | - Note value defines the note's duration in a beat. It could be whole note, half note, quarter note, 8th, 16th or 32nd note.
120 |
121 |
122 | #### `HarmonicFunctions`
123 |
124 | - Harmonic functions is a utility for finding related notes or chords in a scale when composing.
125 | - You can create recommendation engines or chord generators with that.
126 |
127 | Playgrounds
128 | ----
129 |
130 | - You can experiment with the library right away in the Xcode Playgrounds!
131 | - After cloning the project, build it for the Mac target,
132 | - Go to playground page in the project,
133 | - Make sure the macOS platform is selected,
134 | - And make sure the "Build Active Scheme" option is selected in the playground settings.
135 | - There are some recipes ready in the playground page, you can just run them right away.
136 |
137 | Documentation
138 | ----
139 |
140 | [Full documentation is here](https://cemolcay.github.io/MusicTheory/)
141 |
142 | Unit Tests
143 | ----
144 |
145 | You can find unit tests in `MusicTheoryTests` target.
146 | Press `⌘+U` for running tests.
147 |
148 | AppStore
149 | ----
150 |
151 | This library battle tested in my apps for iOS, macOS, watchOS and tvOS, check them out!
152 | [KeyBud](https://itunes.apple.com/us/app/keybud-music-theory-app/id1203856335?mt=8) (iOS, watchOS, tvOS, macOS)
153 | [FretBud](https://itunes.apple.com/us/app/fretbud-chord-scales-for-guitar-bass-and-more/id1234224249?mt=8) (iOS, watchOS, tvOS)
154 | [ChordBud](https://itunes.apple.com/us/app/chordbud-chord-progressions/id1313017378?mt=8) (iOS)
155 | [ArpBud](https://itunes.apple.com/us/app/arpbud-midi-sequencer-more/id1349342326?ls=1&mt=8) (iOS)
156 | [ScaleBud](https://itunes.apple.com/us/app/scalebud-auv3-midi-keyboard/id1409125865?ls=1&mt=8) (iOS, AUv3, M1)
157 | [StepBud](https://itunes.apple.com/us/app/stepbud-auv3-midi-sequencer/id1453104408?mt=8) (iOS, AUv3, M1)
158 | [RhythmBud](https://apps.apple.com/us/app/rhythmbud-auv3-midi-fx/id1484320891#) (iOS, AUv3, M1)
159 | [ArpBud 2](https://apps.apple.com/us/app/arpbud-2-auv3-midi-arpeggiator/id1500403326) (iOS, AUv3, M1)
160 | [ChordBud 2](https://apps.apple.com/us/app/chordbud-2-auv3-midi-sequencer/id1526221230) (iOS, AUv3, M1)
161 | [LoopBud](https://apps.apple.com/us/app/loopbud-auv3-midi-recorder/id1554773709) (iOS, AUv3, M1)
162 | [Euclid Goes to Party](https://apps.apple.com/us/app/euclid-goes-to-party-auv3-bass/id1565732327) (iOS, AUv3, M1)
163 | [SnakeBud](https://apps.apple.com/us/app/snakebud-auv3-midi-sequencer/id1568600625) (iOS, AUv3, M1)
164 | [MelodyBud](https://apps.apple.com/us/app/melodybud-auv3-midi-sequencer/id1601357369) (iOS, AUv3, M1)
165 | [ScaleBud 2](https://apps.apple.com/us/app/scalebud-2-auv3-midi-keyboard/id1605842538) (iOS, AUv3, M1)
166 | [ShiftBud](https://apps.apple.com/us/app/shiftbud-generative-midi-auv3/id1616169031) (iOS, AUv3, M1)
167 | [PolyBud](https://apps.apple.com/us/app/polybud-polyrhythmic-sequencer/id1624211288) (iOS, AUv3, M1)
168 | [PatternBud](https://apps.apple.com/us/app/patternbud-midi-cc-sequencer/id1608966928) (iOS, AUv3, M1)
169 | [MIDI Motion](https://apps.apple.com/us/app/midi-motion-for-apple-watch/id6444314230) (iOS, watchOS)
170 | [Textquencer](https://apps.apple.com/us/app/textquencer-auv3-midi/id1661316322) (iOS, AUv3, M1)
171 | [In Theory](https://apps.apple.com/us/app/in-theory-interval-keyboard/id1667984658) (iOS, AUv3, M1)
172 | [BrainBud](https://apps.apple.com/us/app/brainbud-bud-app-controller/id6446066258) (iOS, AUv3, M1)
173 | [Binarhythmic](https://apps.apple.com/us/app/binarhythmic-rhythm-generator/id6447797078) (iOS, AUv3, M1)
174 | [Auto Bass](https://apps.apple.com/us/app/auto-bass-auv3-midi-generator/id6450610419) (iOS, AUv3, M1)
175 | [BounceBud](https://apps.apple.com/us/app/bouncebud-physics-based-midi/id6464171933) (iOS, AUv3, M1)
176 | [MuseBud](https://apps.apple.com/us/app/musebud-auv3-midi-generator/id6472487197) (iOS, AUv3, M1)
177 | [Auto Fills](https://apps.apple.com/us/app/auto-fills-drum-fill-generator/id6476319733) (iOS, AUv3, M1)
178 | [Kebarp](https://apps.apple.com/us/app/kebarp-auv3-midi-arpeggiator/id6479562640) (iOS, AUv3, M1)
179 | [FuncBud](https://apps.apple.com/us/app/funcbud-generative-sequencer/id6502771916) (iOS, AUv3, M1)
180 | [Note to Be](https://apps.apple.com/us/app/note-to-be-midi-quantizer/id6596771972) (iOS, AUv3, M1)
181 | [Harmonicc](https://apps.apple.com/us/app/harmonicc-chord-sequencer-auv3/id6692624491) (iOS, AUv3, M1)
182 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Accidental.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Enharmonics.swift
3 | // MusicTheory
4 | //
5 | // Created by Cem Olcay on 21.06.2018.
6 | // Copyright © 2018 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | /// Returns a new accidental by adding up two accidentals in the equation.
14 | ///
15 | /// - Parameters:
16 | /// - lhs: Left hand side of the equation.
17 | /// - rhs: Right hand side of the equation.
18 | /// - Returns: Returns the sum of two accidentals.
19 | public func + (lhs: Accidental, rhs: Accidental) -> Accidental {
20 | return Accidental(integerLiteral: lhs.rawValue + rhs.rawValue)
21 | }
22 |
23 | /// Returns a new accidental by substracting two accidentals in the equation.
24 | ///
25 | /// - Parameters:
26 | /// - lhs: Left hand side of the equation.
27 | /// - rhs: Right hand side of the equation.
28 | /// - Returns: Returns the difference of two accidentals.
29 | public func - (lhs: Accidental, rhs: Accidental) -> Accidental {
30 | return Accidental(integerLiteral: lhs.rawValue - rhs.rawValue)
31 | }
32 |
33 | /// Returns a new accidental by adding up an int to the accidental in the equation.
34 | ///
35 | /// - Parameters:
36 | /// - lhs: Left hand side of the equation.
37 | /// - rhs: Right hand side of the equation.
38 | /// - Returns: Returns the sum of two accidentals.
39 | public func + (lhs: Accidental, rhs: Int) -> Accidental {
40 | return Accidental(integerLiteral: lhs.rawValue + rhs)
41 | }
42 |
43 | /// Returns a new accidental by substracting an int from the accidental in the equation.
44 | ///
45 | /// - Parameters:
46 | /// - lhs: Left hand side of the equation.
47 | /// - rhs: Right hand side of the equation.
48 | /// - Returns: Returns the difference of two accidentals.
49 | public func - (lhs: Accidental, rhs: Int) -> Accidental {
50 | return Accidental(integerLiteral: lhs.rawValue - rhs)
51 | }
52 |
53 | /// Multiples an accidental with a multiplier.
54 | ///
55 | /// - Parameters:
56 | /// - lhs: Accidental you want to multiply.
57 | /// - rhs: Multiplier.
58 | /// - Returns: Returns a multiplied acceident.
59 | public func * (lhs: Accidental, rhs: Int) -> Accidental {
60 | return Accidental(integerLiteral: lhs.rawValue * rhs)
61 | }
62 |
63 | /// Divides an accidental with a multiplier
64 | ///
65 | /// - Parameters:
66 | /// - lhs: Accidental you want to divide.
67 | /// - rhs: Multiplier.
68 | /// - Returns: Returns a divided accidental.
69 | public func / (lhs: Accidental, rhs: Int) -> Accidental {
70 | return Accidental(integerLiteral: lhs.rawValue / rhs)
71 | }
72 |
73 | /// Checks if the two accidental is identical in terms of their halfstep values.
74 | ///
75 | /// - Parameters:
76 | /// - lhs: Left hand side of the equation.
77 | /// - rhs: Right hand side of the equation.
78 | /// - Returns: Returns true if two accidentalals is identical.
79 | public func == (lhs: Accidental, rhs: Accidental) -> Bool {
80 | return lhs.rawValue == rhs.rawValue
81 | }
82 |
83 | /// Checks if the two accidental is exactly identical.
84 | ///
85 | /// - Parameters:
86 | /// - lhs: Left hand side of the equation.
87 | /// - rhs: Right hand side of the equation.
88 | /// - Returns: Returns true if two accidentalals is identical.
89 | public func === (lhs: Accidental, rhs: Accidental) -> Bool {
90 | switch (lhs, rhs) {
91 | case (.natural, .natural):
92 | return true
93 | case let (.sharps(a), .sharps(b)):
94 | return a == b
95 | case let (.flats(a), .flats(b)):
96 | return a == b
97 | default:
98 | return false
99 | }
100 | }
101 |
102 | /// The enum used for calculating values of the `Key`s and `Pitche`s.
103 | public enum Accidental: Codable, Equatable, Hashable, RawRepresentable, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral, CustomStringConvertible {
104 | /// No accidental.
105 | case natural
106 | /// Reduces the `Key` or `Pitch` value amount of halfsteps.
107 | case flats(amount: Int)
108 | /// Increases the `Key` or `Pitch` value amount of halfsteps.
109 | case sharps(amount: Int)
110 |
111 | /// Reduces the `Key` or `Pitch` value one halfstep below.
112 | public static let flat: Accidental = .flats(amount: 1)
113 | /// Increases the `Key` or `Pitch` value one halfstep above.
114 | public static let sharp: Accidental = .sharps(amount: 1)
115 | /// Reduces the `Key` or `Pitch` value amount two halfsteps below.
116 | public static let doubleFlat: Accidental = .flats(amount: 2)
117 | /// Increases the `Key` or `Pitch` value two halfsteps above.
118 | public static let doubleSharp: Accidental = .sharps(amount: 2)
119 |
120 | /// A flag for `description` function that determines if it should use double sharp and double flat symbols.
121 | /// It's useful to set it false where the fonts do not support that symbols. Defaults true.
122 | public static var shouldUseDoubleFlatAndDoubleSharpNotation = true
123 |
124 | // MARK: RawRepresentable
125 |
126 | public typealias RawValue = Int
127 |
128 | /// Value of the accidental in terms of halfsteps.
129 | public var rawValue: Int {
130 | switch self {
131 | case .natural:
132 | return 0
133 | case let .flats(amount):
134 | return -amount
135 | case let .sharps(amount):
136 | return amount
137 | }
138 | }
139 |
140 | /// Initilizes the accidental with an integer that represents the halfstep amount.
141 | ///
142 | /// - Parameter rawValue: Halfstep value of the accidental. Zero if natural, above zero if sharp, below zero if flat.
143 | public init?(rawValue: Accidental.RawValue) {
144 | if rawValue == 0 {
145 | self = .natural
146 | } else if rawValue > 0 {
147 | self = .sharps(amount: rawValue)
148 | } else {
149 | self = .flats(amount: -rawValue)
150 | }
151 | }
152 |
153 | // MARK: ExpressibleByIntegerLiteral
154 |
155 | public typealias IntegerLiteralType = Int
156 |
157 | /// Initilizes the accidental with an integer literal value.
158 | ///
159 | /// - Parameter value: Halfstep value of the accidental. Zero if natural, above zero if sharp, below zero if flat.
160 | public init(integerLiteral value: Accidental.IntegerLiteralType) {
161 | self = Accidental(rawValue: value) ?? .natural
162 | }
163 |
164 | // MARK: ExpressibleByStringLiteral
165 |
166 | public typealias StringLiteralType = String
167 |
168 | public init(stringLiteral value: Accidental.StringLiteralType) {
169 | var sum = 0
170 | for i in 0 ..< value.count {
171 | switch value[value.index(value.startIndex, offsetBy: i)] {
172 | case "#", "♯":
173 | sum += 1
174 | case "b", "♭":
175 | sum -= 1
176 | default:
177 | break
178 | }
179 | }
180 | self = Accidental(rawValue: sum) ?? .natural
181 | }
182 |
183 | // MARK: CustomStringConvertible
184 |
185 | /// Returns the notation string of the accidental.
186 | public var notation: String {
187 | if case .natural = self {
188 | return "♮"
189 | }
190 | return description
191 | }
192 |
193 | /// Returns the notation string of the accidental. Returns empty string if accidental is natural.
194 | public var description: String {
195 | switch self {
196 | case .natural:
197 | return ""
198 | case let .flats(amount):
199 | switch amount {
200 | case 0: return Accidental.natural.description
201 | case 1: return "♭"
202 | case 2 where Accidental.shouldUseDoubleFlatAndDoubleSharpNotation: return "𝄫"
203 | default: return amount > 0 ? (0 ..< amount).map({ _ in Accidental.flats(amount: 1).description }).joined() : ""
204 | }
205 | case let .sharps(amount):
206 | switch amount {
207 | case 0: return Accidental.natural.description
208 | case 1: return "♯"
209 | case 2 where Accidental.shouldUseDoubleFlatAndDoubleSharpNotation: return "𝄪"
210 | default: return amount > 0 ? (0 ..< amount).map({ _ in Accidental.sharps(amount: 1).description }).joined() : ""
211 | }
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Chord.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Chord.swift
3 | // MusicTheory
4 | //
5 | // Created by Cem Olcay on 22.10.2017.
6 | // Copyright © 2017 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | // MARK: - ChordPart
14 |
15 | /// Protocol that defines a printable chord part.
16 | public protocol ChordDescription: CustomStringConvertible, Codable {
17 | /// Notation of chord.
18 | var notation: String { get }
19 | }
20 |
21 | /// Protocol that defines a chord part.
22 | public protocol ChordPart: ChordDescription {
23 | /// Interval between the root.
24 | var interval: Interval { get }
25 | /// Initilize chord part with interval.
26 | init?(interval: Interval)
27 | }
28 |
29 | extension Interval {
30 | /// Returns the sum of two intervals semitones.
31 | ///
32 | /// - Parameters:
33 | /// - lhs: Left hand side of the equation.
34 | /// - rhs: Right hand side of the equation.
35 | /// - Returns: Sum of two intervals in terms of their semitones.
36 | fileprivate static func + (lhs: Interval, rhs: Accidental) -> Int {
37 | return lhs.semitones + rhs.rawValue
38 | }
39 | }
40 |
41 | /// Defines third part of the chord. Second note after the root.
42 | public enum ChordThirdType: Int, ChordPart {
43 | /// Defines major chord. 4 halfsteps between root.
44 | case major
45 | /// Defines minor chord. 3 halfsteps between root.
46 | case minor
47 |
48 | /// Initilize chord part with interval.
49 | public init?(interval: Interval) {
50 | switch interval {
51 | case ChordThirdType.major.interval:
52 | self = .major
53 | case ChordThirdType.minor.interval:
54 | self = .minor
55 | default:
56 | return nil
57 | }
58 | }
59 |
60 | /// Interval between root.
61 | public var interval: Interval {
62 | switch self {
63 | case .major:
64 | return .M3
65 | case .minor:
66 | return .m3
67 | }
68 | }
69 |
70 | /// Notation of chord part.
71 | public var notation: String {
72 | switch self {
73 | case .major: return ""
74 | case .minor: return "m"
75 | }
76 | }
77 |
78 | /// Description of chord part.
79 | public var description: String {
80 | switch self {
81 | case .major: return "Major"
82 | case .minor: return "Minor"
83 | }
84 | }
85 |
86 | /// All values of `ChordThirdType`.
87 | public static var all: [ChordThirdType] {
88 | return [.major, .minor]
89 | }
90 | }
91 |
92 | /// Defines fifth part of the chord. Third note after root note.
93 | public enum ChordFifthType: Int, ChordPart {
94 | /// Perfect fifth interval between root.
95 | case perfect
96 | /// Half step down of perfect fifth.
97 | case diminished
98 | /// Half step up of perfect fifth.
99 | case augmented
100 |
101 | /// Initilize chord part with interval.
102 | public init?(interval: Interval) {
103 | switch interval {
104 | case ChordFifthType.perfect.interval:
105 | self = .perfect
106 | case ChordFifthType.diminished.interval:
107 | self = .diminished
108 | case ChordFifthType.augmented.interval:
109 | self = .augmented
110 | default:
111 | return nil
112 | }
113 | }
114 |
115 | /// Interval between root.
116 | public var interval: Interval {
117 | switch self {
118 | case .perfect:
119 | return .P5
120 | case .diminished:
121 | return .d5
122 | case .augmented:
123 | return .A5
124 | }
125 | }
126 |
127 | /// Notation of chord part.
128 | public var notation: String {
129 | switch self {
130 | case .perfect: return ""
131 | case .augmented: return "♯5"
132 | case .diminished: return "♭5"
133 | }
134 | }
135 |
136 | /// Description of chord part.
137 | public var description: String {
138 | switch self {
139 | case .perfect: return ""
140 | case .augmented: return "Augmented"
141 | case .diminished: return "Diminished"
142 | }
143 | }
144 |
145 | /// All values of `ChordFifthType`.
146 | public static var all: [ChordFifthType] {
147 | return [.perfect, .diminished, .augmented]
148 | }
149 | }
150 |
151 | /// Defiens sixth chords. If you add the sixth note, you have sixth chord.
152 | public struct ChordSixthType: ChordPart {
153 | /// Default initilizer.
154 | public init() {}
155 |
156 | /// Initilize chord part with interval.
157 | public init?(interval: Interval) {
158 | switch interval {
159 | case .M6:
160 | self.init()
161 | default:
162 | return nil
163 | }
164 | }
165 |
166 | /// Interval between root.
167 | public var interval: Interval {
168 | return .M6
169 | }
170 |
171 | /// Notation of chord part.
172 | public var notation: String {
173 | return "6"
174 | }
175 |
176 | /// Description of chord part.
177 | public var description: String {
178 | return "Sixth"
179 | }
180 | }
181 |
182 | /// Defiens seventh chords. If you add seventh note, you have seventh chord.
183 | public enum ChordSeventhType: Int, ChordPart {
184 | /// Seventh note of the chord. 11 halfsteps between the root.
185 | case major
186 | /// Halfstep down of a seventh note. 10 halfsteps between the root.
187 | case dominant
188 | /// Two halfsteps down of a seventh note. 9 halfsteps between the root.
189 | case diminished
190 |
191 | /// Initilize chord part with interval.
192 | public init?(interval: Interval) {
193 | switch interval {
194 | case ChordSeventhType.major.interval:
195 | self = .major
196 | case ChordSeventhType.dominant.interval:
197 | self = .dominant
198 | case ChordSeventhType.diminished.interval:
199 | self = .diminished
200 | default:
201 | return nil
202 | }
203 | }
204 |
205 | /// Interval between root.
206 | public var interval: Interval {
207 | switch self {
208 | case .major:
209 | return .M7
210 | case .dominant:
211 | return .m7
212 | case .diminished:
213 | return Interval(quality: .diminished, degree: 7, semitones: 9)
214 | }
215 | }
216 |
217 | /// Notation of chord part.
218 | public var notation: String {
219 | switch self {
220 | case .major: return "M7"
221 | case .dominant: return "7"
222 | case .diminished: return "°7"
223 | }
224 | }
225 |
226 | /// Description of chord part.
227 | public var description: String {
228 | switch self {
229 | case .major: return "Major 7th"
230 | case .dominant: return "Dominant 7th"
231 | case .diminished: return "Diminished 7th"
232 | }
233 | }
234 |
235 | /// All values of `ChordSeventhType`.
236 | public static var all: [ChordSeventhType] {
237 | return [.major, .dominant, .diminished]
238 | }
239 | }
240 |
241 | /// Defines suspended chords.
242 | /// If you play second or fourth note of chord, instead of thirds, than you have suspended chords.
243 | public enum ChordSuspendedType: Int, ChordPart {
244 | /// Second note of chord instead of third part. 2 halfsteps between root.
245 | case sus2
246 | /// Fourth note of chord instead of third part. 5 halfsteps between root.
247 | case sus4
248 |
249 | /// Initilize chord part with interval
250 | public init?(interval: Interval) {
251 | switch interval {
252 | case ChordSuspendedType.sus2.interval:
253 | self = .sus2
254 | case ChordSuspendedType.sus4.interval:
255 | self = .sus4
256 | default:
257 | return nil
258 | }
259 | }
260 |
261 | /// Interval between root.
262 | public var interval: Interval {
263 | switch self {
264 | case .sus2:
265 | return .M2
266 | case .sus4:
267 | return .P4
268 | }
269 | }
270 |
271 | /// Notation of chord part.
272 | public var notation: String {
273 | switch self {
274 | case .sus2: return "(sus2)"
275 | case .sus4: return "(sus4)"
276 | }
277 | }
278 |
279 | /// Description of chord part.
280 | public var description: String {
281 | switch self {
282 | case .sus2: return "Suspended 2nd"
283 | case .sus4: return "Suspended 4th"
284 | }
285 | }
286 |
287 | /// All values of `ChordSuspendedType`.
288 | public static var all: [ChordSuspendedType] {
289 | return [.sus2, .sus4]
290 | }
291 | }
292 |
293 | /// Defines extended chords.
294 | /// If you add one octave up of second, fourth or sixth notes of the chord, you have extended chords.
295 | /// You can combine extended chords more than one in a chord.
296 | public struct ChordExtensionType: ChordPart {
297 | /// Defines type of the extended chords.
298 | public enum ExtensionType: Int, ChordDescription {
299 | /// 9th chord. Second note of the chord, one octave up from root.
300 | case ninth = 9
301 | /// 11th chord. Eleventh note of the chord, one octave up from root.
302 | case eleventh = 11
303 | /// 13th chord. Sixth note of the chord, one octave up from root.
304 | case thirteenth = 13
305 |
306 | /// Interval between root.
307 | public var interval: Interval {
308 | switch self {
309 | case .ninth:
310 | return .M9
311 | case .eleventh:
312 | return .P11
313 | case .thirteenth:
314 | return .M13
315 | }
316 | }
317 |
318 | /// Notation of the chord part.
319 | public var notation: String {
320 | switch self {
321 | case .ninth:
322 | return "9"
323 | case .eleventh:
324 | return "11"
325 | case .thirteenth:
326 | return "13"
327 | }
328 | }
329 |
330 | /// Description of the chord part.
331 | public var description: String {
332 | switch self {
333 | case .ninth:
334 | return "9th"
335 | case .eleventh:
336 | return "11th"
337 | case .thirteenth:
338 | return "13th"
339 | }
340 | }
341 |
342 | /// All values of `ExtensionType`.
343 | public static var all: [ExtensionType] {
344 | return [.ninth, .eleventh, .thirteenth]
345 | }
346 | }
347 |
348 | /// Type of extended chord.
349 | public var type: ExtensionType
350 | /// Accident of extended chord.
351 | public var accidental: Accidental
352 | /// If there are no seventh note and only one extended part is this. Defaults false
353 | internal var isAdded: Bool
354 |
355 | /// Initilizes extended chord.
356 | ///
357 | /// - Parameters:
358 | /// - type: Type of extended chord.
359 | /// - accident: Accident of extended chord. Defaults natural.
360 | public init(type: ExtensionType, accidental: Accidental = .natural) {
361 | self.type = type
362 | self.accidental = accidental
363 | isAdded = false
364 | }
365 |
366 | /// Initilize chord part with interval
367 | public init?(interval: Interval) {
368 | switch interval.semitones {
369 | case ExtensionType.ninth.interval + Accidental.natural:
370 | self = ChordExtensionType(type: .ninth, accidental: .natural)
371 | case ExtensionType.ninth.interval + Accidental.flat:
372 | self = ChordExtensionType(type: .ninth, accidental: .flat)
373 | case ExtensionType.ninth.interval + Accidental.sharp:
374 | self = ChordExtensionType(type: .ninth, accidental: .sharp)
375 | case ExtensionType.eleventh.interval + Accidental.natural:
376 | self = ChordExtensionType(type: .eleventh, accidental: .natural)
377 | case ExtensionType.eleventh.interval + Accidental.flat:
378 | self = ChordExtensionType(type: .eleventh, accidental: .flat)
379 | case ExtensionType.eleventh.interval + Accidental.sharp:
380 | self = ChordExtensionType(type: .eleventh, accidental: .sharp)
381 | case ExtensionType.thirteenth.interval + Accidental.natural:
382 | self = ChordExtensionType(type: .thirteenth, accidental: .natural)
383 | case ExtensionType.thirteenth.interval + Accidental.flat:
384 | self = ChordExtensionType(type: .thirteenth, accidental: .flat)
385 | case ExtensionType.thirteenth.interval + Accidental.sharp:
386 | self = ChordExtensionType(type: .thirteenth, accidental: .sharp)
387 | default:
388 | return nil
389 | }
390 | }
391 |
392 | /// Interval between root.
393 | public var interval: Interval {
394 | switch (type, accidental) {
395 | case (.ninth, .natural): return .M9
396 | case (.ninth, .flat): return .m9
397 | case (.ninth, .sharp): return .A9
398 |
399 | case (.eleventh, .natural): return .P11
400 | case (.eleventh, .flat): return .P11
401 | case (.eleventh, .sharp): return .A11
402 |
403 | case (.thirteenth, .natural): return .M13
404 | case (.thirteenth, .flat): return .m13
405 | case (.thirteenth, .sharp): return .A13
406 |
407 | case (.ninth, _): return .M9
408 | case (.eleventh, _): return .P11
409 | case (.thirteenth, _): return .M13
410 | }
411 | }
412 |
413 | /// Notation of chord part.
414 | public var notation: String {
415 | return "\(isAdded ? "add" : "")\(accidental.notation)\(type.notation)"
416 | }
417 |
418 | /// Description of chord part.
419 | public var description: String {
420 | return "\(isAdded ? "Added " : "")\(accidental.description) \(type.description)"
421 | }
422 |
423 | /// All values of `ChordExtensionType`
424 | public static var all: [ChordExtensionType] {
425 | var all = [ChordExtensionType]()
426 | for type in ExtensionType.all {
427 | for accident in [Accidental.natural, Accidental.flat, Accidental.sharp] {
428 | all.append(ChordExtensionType(type: type, accidental: accident))
429 | }
430 | }
431 | return all
432 | }
433 | }
434 |
435 | // MARK: - ChordType
436 |
437 | /// Checks the equability between two `ChordType`s by their intervals.
438 | ///
439 | /// - Parameters:
440 | /// - left: Left handside of the equation.
441 | /// - right: Right handside of the equation.
442 | /// - Returns: Returns Bool value of equation of two given chord types.
443 | public func == (left: ChordType?, right: ChordType?) -> Bool {
444 | switch (left, right) {
445 | case let (.some(left), .some(right)):
446 | return left.intervals == right.intervals
447 | case (.none, .none):
448 | return true
449 | default:
450 | return false
451 | }
452 | }
453 |
454 | /// Defines full type of chord with all chord parts.
455 | public struct ChordType: ChordDescription, Hashable {
456 | /// Thirds part. Second note of the chord.
457 | public var third: ChordThirdType
458 | /// Fifths part. Third note of the chord.
459 | public var fifth: ChordFifthType
460 | /// Defines a sixth chord. Defaults nil.
461 | public var sixth: ChordSixthType?
462 | /// Defines a seventh chord. Defaults nil.
463 | public var seventh: ChordSeventhType?
464 | /// Defines a suspended chord. Defaults nil.
465 | public var suspended: ChordSuspendedType?
466 | /// Defines extended chord. Defaults nil.
467 | public var extensions: [ChordExtensionType]? {
468 | didSet {
469 | if extensions?.count == 1 {
470 | extensions![0].isAdded = seventh == nil
471 | // Add other extensions if needed
472 | if let ext = extensions?.first, ext.type == .eleventh, !ext.isAdded {
473 | extensions?.append(ChordExtensionType(type: .ninth))
474 | } else if let ext = extensions?.first, ext.type == .thirteenth, !ext.isAdded {
475 | extensions?.append(ChordExtensionType(type: .ninth))
476 | extensions?.append(ChordExtensionType(type: .eleventh))
477 | }
478 | }
479 | }
480 | }
481 |
482 | /// Initilze the chord type with its parts.
483 | ///
484 | /// - Parameters:
485 | /// - third: Thirds part.
486 | /// - fifth: Fifths part. Defaults perfect fifth.
487 | /// - sixth: Sixth part. Defaults nil.
488 | /// - seventh: Seventh part. Defaults nil.
489 | /// - suspended: Suspended part. Defaults nil.
490 | /// - extensions: Extended chords part. Defaults nil. Could be add more than one extended chord.
491 | /// - custom: Fill that with the custom intervals if it's not represented by the current data structures. Defaults nil.
492 | public init(
493 | third: ChordThirdType,
494 | fifth: ChordFifthType = .perfect,
495 | sixth: ChordSixthType? = nil,
496 | seventh: ChordSeventhType? = nil,
497 | suspended: ChordSuspendedType? = nil,
498 | extensions: [ChordExtensionType]? = nil) {
499 | self.third = third
500 | self.fifth = fifth
501 | self.sixth = sixth
502 | self.seventh = seventh
503 | self.suspended = suspended
504 | self.extensions = extensions
505 |
506 | if extensions?.count == 1 {
507 | self.extensions![0].isAdded = seventh == nil
508 | // Add other extensions if needed
509 | if let ext = self.extensions?.first, ext.type == .eleventh, !ext.isAdded {
510 | self.extensions?.append(ChordExtensionType(type: .ninth))
511 | } else if let ext = self.extensions?.first, ext.type == .thirteenth, !ext.isAdded {
512 | self.extensions?.append(ChordExtensionType(type: .ninth))
513 | self.extensions?.append(ChordExtensionType(type: .eleventh))
514 | }
515 | }
516 | }
517 |
518 | /// Initilze the chord type with its intervals.
519 | ///
520 | /// - Parameters:
521 | /// - intervals: Intervals of chord notes distances between root note for each.
522 | public init?(intervals: [Interval]) {
523 | var third: ChordThirdType?
524 | var fifth: ChordFifthType?
525 | var sixth: ChordSixthType?
526 | var seventh: ChordSeventhType?
527 | var suspended: ChordSuspendedType?
528 | var extensions = [ChordExtensionType]()
529 |
530 | for interval in intervals {
531 | if let thirdPart = ChordThirdType(interval: interval) {
532 | third = thirdPart
533 | } else if let fifthPart = ChordFifthType(interval: interval) {
534 | fifth = fifthPart
535 | } else if let sixthPart = ChordSixthType(interval: interval) {
536 | sixth = sixthPart
537 | } else if let seventhPart = ChordSeventhType(interval: interval) {
538 | seventh = seventhPart
539 | } else if let suspendedPart = ChordSuspendedType(interval: interval) {
540 | suspended = suspendedPart
541 | } else if let extensionPart = ChordExtensionType(interval: interval) {
542 | extensions.append(extensionPart)
543 | }
544 | }
545 |
546 | self = ChordType(
547 | third: third ?? .major,
548 | fifth: fifth ?? .perfect,
549 | sixth: sixth,
550 | seventh: seventh,
551 | suspended: suspended,
552 | extensions: extensions)
553 | }
554 |
555 | /// Intervals of parts between root.
556 | public var intervals: [Interval] {
557 | var parts: [ChordPart?] = [third, suspended, fifth, sixth, seventh]
558 | parts += extensions?.sorted(by: { $0.type.rawValue < $1.type.rawValue }).map({ $0 as ChordPart? }) ?? []
559 | return [.P1] + parts.compactMap({ $0?.interval })
560 | }
561 |
562 | /// Notation of the chord type.
563 | public var notation: String {
564 | var seventhNotation = seventh?.notation ?? ""
565 | var sixthNotation = sixth == nil ? "" : "\(sixth!.notation)\(seventh == nil ? "" : "/")"
566 | let suspendedNotation = suspended?.notation ?? ""
567 | var extensionNotation = ""
568 | let ext = extensions?.sorted(by: { $0.type.rawValue < $1.type.rawValue }) ?? []
569 |
570 | var singleNotation = !ext.isEmpty && true
571 | for i in 0 ..< max(0, ext.count - 1) {
572 | if ext[i].accidental != .natural {
573 | singleNotation = false
574 | }
575 | }
576 |
577 | if singleNotation {
578 | extensionNotation = "(\(ext.last!.notation))"
579 | } else {
580 | extensionNotation = ext
581 | .compactMap({ $0.notation })
582 | .joined(separator: "/")
583 | extensionNotation = extensionNotation.isEmpty ? "" : "(\(extensionNotation))"
584 | }
585 |
586 | if seventh != nil {
587 | // Don't show major seventh note if extended is a major as well
588 | if (extensions ?? []).count > 0 {
589 | switch seventh! {
590 | case .diminished: seventhNotation = "°"
591 | case .major: seventhNotation = "M"
592 | case .dominant: seventhNotation = ""
593 | }
594 | sixthNotation = sixth == nil ? "" : sixth!.notation
595 | }
596 | // Show fifth note after seventh in parenthesis
597 | if fifth == .augmented || fifth == .diminished {
598 | return "\(third.notation)\(sixthNotation)\(seventhNotation)(\(fifth.notation))\(suspendedNotation)\(extensionNotation)"
599 | }
600 | }
601 |
602 | return "\(third.notation)\(fifth.notation)\(sixthNotation)\(seventhNotation)\(suspendedNotation)\(extensionNotation)"
603 | }
604 |
605 | /// Description of the chord type.
606 | public var description: String {
607 | let seventhNotation = seventh?.description
608 | let sixthNotation = sixth?.description
609 | let suspendedNotation = suspended?.description
610 | let extensionsNotation = extensions?
611 | .sorted(by: { $0.type.rawValue < $1.type.rawValue })
612 | .map({ $0.description as String? }) ?? []
613 | let desc = [third.description, fifth.description, sixthNotation, seventhNotation, suspendedNotation] + extensionsNotation
614 | return desc.compactMap({ $0 }).joined(separator: " ")
615 | }
616 |
617 | /// All possible chords could be generated.
618 | public static var all: [ChordType] {
619 | func combinations(_ elements: [ChordExtensionType], taking: Int = 1) -> [[ChordExtensionType]] {
620 | guard elements.count >= taking else { return [] }
621 | guard elements.count > 0 && taking > 0 else { return [[]] }
622 |
623 | if taking == 1 {
624 | return elements.map { [$0] }
625 | }
626 |
627 | var comb = [[ChordExtensionType]]()
628 | for (index, element) in elements.enumerated() {
629 | var reducedElements = elements
630 | reducedElements.removeFirst(index + 1)
631 | comb += combinations(reducedElements, taking: taking - 1).map { [element] + $0 }
632 | }
633 | return comb
634 | }
635 |
636 | var all = [ChordType]()
637 | let allThird = ChordThirdType.all
638 | let allFifth = ChordFifthType.all
639 | let allSixth: [ChordSixthType?] = [ChordSixthType(), nil]
640 | let allSeventh: [ChordSeventhType?] = ChordSeventhType.all + [nil]
641 | let allSus: [ChordSuspendedType?] = ChordSuspendedType.all + [nil]
642 | let allExt = combinations(ChordExtensionType.all) +
643 | combinations(ChordExtensionType.all, taking: 2) +
644 | combinations(ChordExtensionType.all, taking: 3)
645 |
646 | for third in allThird {
647 | for fifth in allFifth {
648 | for sixth in allSixth {
649 | for seventh in allSeventh {
650 | for sus in allSus {
651 | for ext in allExt {
652 | all.append(ChordType(
653 | third: third,
654 | fifth: fifth,
655 | sixth: sixth,
656 | seventh: seventh,
657 | suspended: sus,
658 | extensions: ext
659 | ))
660 | }
661 | }
662 | }
663 | }
664 | }
665 | }
666 | return all
667 | }
668 |
669 | // MARK: Hashable
670 |
671 | public func hash(into hasher: inout Hasher) {
672 | hasher.combine(intervals)
673 | }
674 |
675 | // MARK: Equatable
676 |
677 | public static func == (lhs: ChordType, rhs: ChordType) -> Bool {
678 | return lhs.hashValue == rhs.hashValue
679 | }
680 | }
681 |
682 | // MARK: - Chord
683 |
684 | /// Checks the equability between two chords by their base key and notes.
685 | ///
686 | /// - Parameters:
687 | /// - left: Left handside of the equation.
688 | /// - right: Right handside of the equation.
689 | /// - Returns: Returns Bool value of equation of two given chords.
690 | public func == (left: Chord?, right: Chord?) -> Bool {
691 | switch (left, right) {
692 | case let (.some(left), .some(right)):
693 | return left.key == right.key && left.type == right.type && left.inversion == right.inversion
694 | case (.none, .none):
695 | return true
696 | default:
697 | return false
698 | }
699 | }
700 |
701 | /// Defines a chord with a root note and type.
702 | public struct Chord: ChordDescription {
703 | /// Type of the chord.
704 | public var type: ChordType
705 | /// Root key of the chord.
706 | public var key: Key
707 | /// Inversion index of the chord.
708 | public private(set) var inversion: Int
709 |
710 | /// Initilizes chord with root note and type.
711 | ///
712 | /// - Parameters:
713 | /// - key: Root key of the chord.
714 | /// - type: Tyoe of the chord.
715 | public init(type: ChordType, key: Key, inversion: Int = 0) {
716 | self.type = type
717 | self.key = key
718 | self.inversion = inversion
719 | }
720 |
721 | /// Types of notes in chord.
722 | public var keys: [Key] {
723 | return pitches(octave: 1).map({ $0.key })
724 | }
725 |
726 | /// Possible inversions of the chord.
727 | public var inversions: [Chord] {
728 | return [Int](0 ..< keys.count).map({ Chord(type: type, key: key, inversion: $0) })
729 | }
730 |
731 | /// Notation of the chord.
732 | public var notation: String {
733 | let inversionNotation = inversion > 0 && inversion < keys.count ? "/\(keys[0])" : ""
734 | return "\(key)\(type.notation)\(inversionNotation)"
735 | }
736 |
737 | /// Generates notes of the chord for octave.
738 | ///
739 | /// - Parameter octave: Octave of the root note for the build chord from.
740 | /// - Returns: Generates notes of the chord.
741 | public func pitches(octave: Int) -> [Pitch] {
742 | var intervals = type.intervals
743 | for _ in 0 ..< inversion {
744 | intervals = intervals.shifted
745 | }
746 |
747 | let root = Pitch(key: key, octave: octave)
748 | let invertedPitches = intervals.map({ root + $0 })
749 | return invertedPitches
750 | .enumerated()
751 | .map({ index, item in
752 | index < type.intervals.count - inversion ? item : Pitch(key: item.key, octave: item.octave + 1)
753 | })
754 | }
755 |
756 | /// Generates notes of the chord for octave range.
757 | ///
758 | /// - Parameter octaves: Octaves of the root note to build chord from.
759 | /// - Returns: Generates notes of the chord.
760 | public func pitches(octaves: [Int]) -> [Pitch] {
761 | return octaves.flatMap({ pitches(octave: $0) }).sorted(by: { $0.rawValue < $1.rawValue })
762 | }
763 |
764 | /// Returns the roman numeric string for a chord.
765 | ///
766 | /// - Parameter scale: The scale that the chord in.
767 | /// - Returns: Roman numeric string for the chord in a scale.
768 | public func romanNumeric(for scale: Scale) -> String? {
769 | guard let chordIndex = scale.keys.firstIndex(of: key)
770 | else { return nil }
771 |
772 | var roman = ""
773 | switch chordIndex {
774 | case 0: roman = "I"
775 | case 1: roman = "II"
776 | case 2: roman = "III"
777 | case 3: roman = "IV"
778 | case 4: roman = "V"
779 | case 5: roman = "VI"
780 | case 6: roman = "VII"
781 | default: return nil
782 | }
783 |
784 | // Check if minor
785 | if type.third == .minor {
786 | roman = roman.lowercased()
787 | }
788 | // Check if sixth
789 | if type.sixth != nil {
790 | roman = "\(roman)6"
791 | }
792 | // Check if augmented
793 | if type.fifth == .augmented {
794 | roman = "\(roman)+"
795 | }
796 | // Check if diminished
797 | if type.fifth == .diminished {
798 | roman = "\(roman)°"
799 | }
800 | // Check if sevent
801 | if type.seventh != nil, (type.extensions == nil || type.extensions?.isEmpty == true) {
802 | roman = "\(roman)7"
803 | }
804 | // Check if extended
805 | if let extensions = type.extensions,
806 | let last = extensions.sorted(by: { $0.type.rawValue < $1.type.rawValue }).last {
807 | roman = "\(roman)\(last.type.rawValue)"
808 | }
809 | // Check if inverted
810 | if inversion > 0 {
811 | roman = "\(roman)/\(inversion)"
812 | }
813 |
814 | return roman
815 | }
816 |
817 | // MARK: CustomStringConvertible
818 |
819 | /// Description of the chord.
820 | public var description: String {
821 | let inversionNotation = inversion > 0 ? " \(inversion). Inversion" : ""
822 | return "\(key) \(type)\(inversionNotation)"
823 | }
824 | }
825 |
826 | // MARK: - Extensions
827 |
828 | extension Array {
829 | internal var shifted: Array {
830 | guard let firstElement = first else { return self }
831 | var arr = self
832 | arr.removeFirst()
833 | arr.append(firstElement)
834 | return arr
835 | }
836 | }
837 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/ChordProgression.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChordProgression.swift
3 | //
4 | // Created by Cem Olcay on 2.10.2017.
5 | // Copyright © 2017 cemolcay. All rights reserved.
6 | //
7 | // https://github.com/cemolcay/MusicTheory
8 | //
9 |
10 | import Foundation
11 |
12 | /// A struct for storing custom progressions.
13 | public struct CustomChordProgression: Codable, CustomStringConvertible {
14 | /// Name of the progression.
15 | public var name: String
16 | /// Chord progression with `ChordProgresion.custom` type.
17 | public var progression: ChordProgression
18 |
19 | // MARK: CustomStringConvertible
20 |
21 | public var description: String {
22 | return name
23 | }
24 | }
25 |
26 | /// A node of chord progression in intervals.
27 | public enum ChordProgressionNode: Int, CustomStringConvertible, Codable, Hashable {
28 | /// First-degree node
29 | case i
30 | /// Second-degree node
31 | case ii
32 | /// Third-degree node
33 | case iii
34 | /// Fourth-degree node
35 | case iv
36 | /// Fifth-degree node
37 | case v
38 | /// Sixth-degree node
39 | case vi
40 | /// Seventh-degree node
41 | case vii
42 |
43 | /// Meaningful next nodes, useful for a recommendation engine.
44 | public var next: [ChordProgressionNode] {
45 | switch self {
46 | case .i:
47 | return [.i, .ii, .iii, .iv, .v, .vi, .vii]
48 | case .ii:
49 | return [.v, .iii, .vi, .vii]
50 | case .iii:
51 | return [.ii, .iv, .vi]
52 | case .iv:
53 | return [.i, .iii, .v, .vii]
54 | case .v:
55 | return [.i]
56 | case .vi:
57 | return [.ii, .iv]
58 | case .vii:
59 | return [.vi]
60 | }
61 | }
62 |
63 | /// All nodes.
64 | public static let all: [ChordProgressionNode] = [.i, .ii, .iii, .iv, .v, .vi, .vii]
65 |
66 | // MARK: CustomStringConvertible
67 |
68 | /// Returns roman numeric string of the node.
69 | public var description: String {
70 | switch self {
71 | case .i: return "I"
72 | case .ii: return "II"
73 | case .iii: return "III"
74 | case .iv: return "IV"
75 | case .v: return "V"
76 | case .vi: return "VI"
77 | case .vii: return "VII"
78 | }
79 | }
80 | }
81 |
82 | /// Chord progression enum that you can create hard-coded and custom progressions.
83 | public struct ChordProgression: CustomStringConvertible, Codable, Hashable {
84 | /// All nodes from first to seventh.
85 | public static let allNodes = ChordProgression(nodes: [.i, .ii, .iii, .iv, .v, .vi, .vii])
86 | /// I - V - VI - IV progression.
87 | public static let i_v_vi_iv = ChordProgression(nodes: [.i, .v, .vi, .iv])
88 | /// VI - V - IV - V progression.
89 | public static let vi_v_iv_v = ChordProgression(nodes: [.vi, .v, .iv, .v])
90 | /// I - VI - IV - V progression.
91 | public static let i_vi_iv_v = ChordProgression(nodes: [.i, .vi, .iv, .v])
92 | /// I - IV - VI - V progression.
93 | public static let i_iv_vi_v = ChordProgression(nodes: [.i, .iv, .vi, .v])
94 | /// I - V - IV - V progression.
95 | public static let i_v_iv_v = ChordProgression(nodes: [.i, .v, .iv, .v])
96 | /// VI - II - V - I progression.
97 | public static let vi_ii_v_i = ChordProgression(nodes: [.vi, .ii, .v, .i])
98 | /// I - VI - II - V progression.
99 | public static let i_vi_ii_v = ChordProgression(nodes: [.i, .vi, .ii, .v])
100 | /// I - IV - II - V progression.
101 | public static let i_iv_ii_v = ChordProgression(nodes: [.i, .iv, .ii, .v])
102 | /// VI - IV - I - V progression.
103 | public static let vi_iv_i_v = ChordProgression(nodes: [.vi, .iv, .i, .v])
104 | /// I - VI - III - VII progression.
105 | public static let i_vi_iii_vii = ChordProgression(nodes: [.i, .vi, .iii, .vii])
106 | /// VI - V - IV - III progression.
107 | public static let vi_v_iv_iii = ChordProgression(nodes: [.vi, .v, .iv, .iii])
108 | /// I - V - VI - III - IV - I - IV - V progression.
109 | public static let i_v_vi_iii_iv_i_iv_v = ChordProgression(nodes: [.i, .v, .vi, .iii, .iv, .i, .iv, .v])
110 | /// IV - I - V - IV progression.
111 | public static let iv_i_v_iv = ChordProgression(nodes: [.iv, .i, .v, .iv])
112 | /// I - II - VI - IV progression.
113 | public static let i_ii_vi_iv = ChordProgression(nodes: [.i, .ii, .vi, .iv])
114 | /// I - III - VI - IV progression.
115 | public static let i_iii_vi_iv = ChordProgression(nodes: [.i, .iii, .vi, .iv])
116 | /// I - V - II - IV progression.
117 | public static let i_v_ii_iv = ChordProgression(nodes: [.i, .v, .ii, .iv])
118 | /// II - IV - I - V progression.
119 | public static let ii_iv_i_v = ChordProgression(nodes: [.ii, .iv, .i, .v])
120 |
121 | public let nodes: [ChordProgressionNode]
122 |
123 | /// Initilizes the chord progression with its nodes.
124 | ///
125 | /// - Parameter nodes: Nodes of the chord progression.
126 | public init(nodes: [ChordProgressionNode]) {
127 | self.nodes = nodes
128 | }
129 |
130 | /// All hard-coded chord progressions.
131 | public static var all: [ChordProgression] {
132 | return [
133 | .allNodes,
134 | .i_v_vi_iv,
135 | .vi_v_iv_v,
136 | .i_vi_iv_v,
137 | .i_iv_vi_v,
138 | .i_v_iv_v,
139 | .vi_ii_v_i,
140 | .i_vi_ii_v,
141 | .i_iv_ii_v,
142 | .vi_iv_i_v,
143 | .i_vi_iii_vii,
144 | .vi_v_iv_iii,
145 | .i_v_vi_iii_iv_i_iv_v,
146 | .iv_i_v_iv,
147 | .i_ii_vi_iv,
148 | .i_iii_vi_iv,
149 | .i_v_ii_iv,
150 | .ii_iv_i_v,
151 | ]
152 | }
153 |
154 | /// Generates chord progression for a `Scale` with `Scale.HarmonicField` and optionally inverted chords.
155 | ///
156 | /// - Parameters:
157 | /// - scale: Scale of the chords going to be generated.
158 | /// - harmonicField: Harmonic field of the chords going to be generated.
159 | /// - inversion: Inversion of the chords going to be generated.
160 | /// - Returns: Returns all possible chords for a scale. Returns nil if the chord is not generated for particular `ChordProgressionNode`.
161 | public func chords(for scale: Scale, harmonicField: Scale.HarmonicField, inversion: Int = 0) -> [Chord?] {
162 | let indices = nodes.map({ $0.rawValue })
163 | let harmonics = scale.harmonicField(for: harmonicField, inversion: inversion)
164 | var chords = [Chord?]()
165 | for index in indices {
166 | if index < harmonics.count {
167 | chords.append(harmonics[index])
168 | }
169 | }
170 | return chords
171 | }
172 |
173 | // MARK: CustomStringConvertible
174 |
175 | /// Returns the chord progression name.
176 | public var description: String {
177 | if self == ChordProgression.allNodes {
178 | return "All"
179 | }
180 | return nodes.map({ "\($0)" }).joined(separator: " - ")
181 | }
182 |
183 | // MARK: Codable
184 |
185 | /// Codable protocol `CodingKey`s
186 | ///
187 | /// - nodes: Coding key for the nodes.
188 | private enum CodingKeys: String, CodingKey {
189 | case nodes
190 | }
191 |
192 | /// Initilizes chord progression with a `Decoder`.
193 | ///
194 | /// - Parameter decoder: The decoder.
195 | /// - Throws: Throws error if can not decodes.
196 | public init(from decoder: Decoder) throws {
197 | let values = try decoder.container(keyedBy: CodingKeys.self)
198 | let nodes = try values.decode([ChordProgressionNode].self, forKey: .nodes)
199 | self = ChordProgression(nodes: nodes)
200 | }
201 |
202 | /// Encodes the chord progression.
203 | ///
204 | /// - Parameter encoder: The encoder.
205 | /// - Throws: Throws error if can not encodes.
206 | public func encode(to encoder: Encoder) throws {
207 | var container = encoder.container(keyedBy: CodingKeys.self)
208 | try container.encode(nodes, forKey: .nodes)
209 | }
210 |
211 | // MARK: Hashable
212 |
213 | public func hash(into hasher: inout Hasher) {
214 | hasher.combine(nodes)
215 | }
216 |
217 | // MARK: Equatable
218 |
219 | public static func == (lhs: ChordProgression, rhs: ChordProgression) -> Bool {
220 | return lhs.hashValue == rhs.hashValue
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/HarmonicFunctions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HarmonicFunctions.swift
3 | //
4 | //
5 | // Created by Cem Olcay on 29/06/2020.
6 | // Copyright © 2017 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | /// Represents harmonic functions in music theory.
14 | public enum HarmonicFunctionType: Int, Codable, CaseIterable {
15 | /// First interval/chord. I in roman numeral.
16 | case tonic
17 | /// Second interval/chord. II in roman numeral.
18 | case supertonic
19 | /// Third interval/chord. III in roman numeral.
20 | case mediant
21 | /// Fourth interval/chord. IV in roman numeral.
22 | case subdominant
23 | /// Fifth interval/chord. V in roman numeral.
24 | case dominant
25 | /// Sixth interval/chord. VI in roman numeral.
26 | case submediant
27 | /// Seventh interval/chord. VII in roman numeral.
28 | case leading
29 |
30 | /// Represents tonic prolongation functions.
31 | public static let tonicProlongationFunctions: [HarmonicFunctionType] = [.mediant, .submediant]
32 |
33 | /// Represents the pre dominant functions.
34 | public static let predominantFunctions: [HarmonicFunctionType] = [.supertonic, .submediant]
35 |
36 | /// Represents the dominant functions
37 | public static let dominantFunctions: [HarmonicFunctionType] = [.dominant, .leading]
38 |
39 | /// Represents the possible direction from any harmonic function.
40 | public var direction: [HarmonicFunctionType] {
41 | switch self {
42 | case .tonic:
43 | return HarmonicFunctionType.allCases
44 | case .supertonic:
45 | return HarmonicFunctionType.dominantFunctions
46 | case .mediant:
47 | return HarmonicFunctionType.predominantFunctions
48 | case .subdominant:
49 | return [.supertonic] + HarmonicFunctionType.dominantFunctions
50 | case .dominant:
51 | return [.tonic]
52 | case .submediant:
53 | return HarmonicFunctionType.predominantFunctions
54 | case .leading:
55 | return [.tonic, .dominant, .submediant]
56 | }
57 | }
58 |
59 | /// Returns the roman numeral string representation.
60 | public var romanNumeral: String {
61 | switch self {
62 | case .tonic: return "I"
63 | case .supertonic: return "II"
64 | case .mediant: return "III"
65 | case .subdominant: return "IV"
66 | case .dominant: return "V"
67 | case .submediant: return "VI"
68 | case .leading: return "VII"
69 | }
70 | }
71 | }
72 |
73 | /// A struct for creating harmonic functions from a `Scale`.
74 | public struct HarmonicFunctions {
75 | /// Scale of the harmonic function.
76 | public let scale: Scale
77 |
78 | /// Initilize the harmonic functions for a scale.
79 | /// - Parameter scale: The scale you want to create harmonic functions from.
80 | public init(scale: Scale) {
81 | self.scale = scale
82 | }
83 |
84 | /// Returns the key of the scale's harmonic function.
85 | /// - Parameter type: The harmonic function you want to get from the scale.
86 | /// - Returns: Returns the key representing the harmonic function you want to get, if the scale has it.
87 | public func harmonicFunction(for type: HarmonicFunctionType) -> Key? {
88 | let keys = scale.keys
89 | guard keys.count >= type.rawValue else { return nil }
90 | return keys[type.rawValue]
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Info-Mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2017 prototapp. All rights reserved.
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Info-TV.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Info-Watch.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Info-iOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Interval.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Interval.swift
3 | // MusicTheory
4 | //
5 | // Created by Cem Olcay on 22.06.2018.
6 | // Copyright © 2018 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | /// Checks the equality of two `Interval`s in terms of their semitones.
14 | ///
15 | /// - Parameters:
16 | /// - lhs: Left hand side of the equation.
17 | /// - rhs: Right hand side of the equation.
18 | /// - Returns: Returns true if two `Interval`s are equal.
19 | public func == (lhs: Interval, rhs: Interval) -> Bool {
20 | return lhs.semitones == rhs.semitones
21 | }
22 |
23 | /// Checks the equality of two `Interval`s in terms of their quality, degree and semitones.
24 | ///
25 | /// - Parameters:
26 | /// - lhs: Left hand side of the equation.
27 | /// - rhs: Right hand side of the equation.
28 | /// - Returns: Returns true if two `Interval`s are equal.
29 | public func === (lhs: Interval, rhs: Interval) -> Bool {
30 | return lhs.quality == rhs.quality && rhs.degree == rhs.degree && lhs.semitones == rhs.semitones
31 | }
32 |
33 | /// Defines the interval between `Pitch`es in semitones.
34 | public struct Interval: Codable, Hashable, CustomStringConvertible {
35 | /// Quality type of the interval.
36 | public enum Quality: Int, Codable, Hashable, CaseIterable, CustomStringConvertible {
37 | /// Diminished
38 | case diminished
39 | /// Perfect
40 | case perfect
41 | /// Minor.
42 | case minor
43 | /// Major.
44 | case major
45 | /// Augmented.
46 | case augmented
47 |
48 | // MARK: CustomStringConvertible
49 |
50 | /// Returns the notation of the interval quality.
51 | public var notation: String {
52 | switch self {
53 | case .diminished: return "d"
54 | case .perfect: return "P"
55 | case .minor: return "m"
56 | case .major: return "M"
57 | case .augmented: return "A"
58 | }
59 | }
60 |
61 | /// Returns the name of the interval quality.
62 | public var description: String {
63 | switch self {
64 | case .diminished: return "Diminished"
65 | case .perfect: return "Perfect"
66 | case .minor: return "Minor"
67 | case .major: return "Major"
68 | case .augmented: return "Augmented"
69 | }
70 | }
71 | }
72 |
73 | /// Quality of the interval.
74 | public var quality: Quality
75 | /// Degree of the interval.
76 | public var degree: Int
77 | /// Semitones interval affect on a pitch.
78 | public var semitones: Int
79 |
80 | /// Initilizes the interval with its quality, degree and semitones.
81 | ///
82 | /// - Parameters:
83 | /// - quality: Quality of the interval.
84 | /// - degree: Degree of the interval.
85 | /// - semitones: Semitones of the interval.
86 | public init(quality: Quality, degree: Int, semitones: Int) {
87 | self.quality = quality
88 | self.degree = degree
89 | self.semitones = semitones
90 | }
91 |
92 | /// Unison.
93 | public static let P1 = Interval(quality: .perfect, degree: 1, semitones: 0)
94 | /// Perfect fourth.
95 | public static let P4 = Interval(quality: .perfect, degree: 4, semitones: 5)
96 | /// Perfect fifth.
97 | public static let P5 = Interval(quality: .perfect, degree: 5, semitones: 7)
98 | /// Octave.
99 | public static let P8 = Interval(quality: .perfect, degree: 8, semitones: 12)
100 | /// Perfect eleventh.
101 | public static let P11 = Interval(quality: .perfect, degree: 11, semitones: 17)
102 | /// Perfect twelfth.
103 | public static let P12 = Interval(quality: .perfect, degree: 12, semitones: 19)
104 | /// Perfect fifteenth, double octave.
105 | public static let P15 = Interval(quality: .perfect, degree: 15, semitones: 24)
106 |
107 | /// Minor second.
108 | public static let m2 = Interval(quality: .minor, degree: 2, semitones: 1)
109 | /// Minor third.
110 | public static let m3 = Interval(quality: .minor, degree: 3, semitones: 3)
111 | /// Minor sixth.
112 | public static let m6 = Interval(quality: .minor, degree: 6, semitones: 8)
113 | /// Minor seventh.
114 | public static let m7 = Interval(quality: .minor, degree: 7, semitones: 10)
115 | /// Minor ninth.
116 | public static let m9 = Interval(quality: .minor, degree: 9, semitones: 13)
117 | /// Minor tenth.
118 | public static let m10 = Interval(quality: .minor, degree: 10, semitones: 15)
119 | /// Minor thirteenth.
120 | public static let m13 = Interval(quality: .minor, degree: 13, semitones: 20)
121 | /// Minor fourteenth.
122 | public static let m14 = Interval(quality: .minor, degree: 14, semitones: 22)
123 |
124 | /// Major second.
125 | public static let M2 = Interval(quality: .major, degree: 2, semitones: 2)
126 | /// Major third.
127 | public static let M3 = Interval(quality: .major, degree: 3, semitones: 4)
128 | /// Major sixth.
129 | public static let M6 = Interval(quality: .major, degree: 6, semitones: 9)
130 | /// Major seventh.
131 | public static let M7 = Interval(quality: .major, degree: 7, semitones: 11)
132 | /// Major ninth.
133 | public static let M9 = Interval(quality: .major, degree: 9, semitones: 14)
134 | /// Major tenth.
135 | public static let M10 = Interval(quality: .major, degree: 10, semitones: 16)
136 | /// Major thirteenth.
137 | public static let M13 = Interval(quality: .major, degree: 13, semitones: 21)
138 | /// Major fourteenth.
139 | public static let M14 = Interval(quality: .major, degree: 14, semitones: 23)
140 |
141 | /// Diminished first.
142 | public static let d1 = Interval(quality: .diminished, degree: 1, semitones: -1)
143 | /// Diminished second.
144 | public static let d2 = Interval(quality: .diminished, degree: 2, semitones: 0)
145 | /// Diminished third.
146 | public static let d3 = Interval(quality: .diminished, degree: 3, semitones: 2)
147 | /// Diminished fourth.
148 | public static let d4 = Interval(quality: .diminished, degree: 4, semitones: 4)
149 | /// Diminished fifth.
150 | public static let d5 = Interval(quality: .diminished, degree: 5, semitones: 6)
151 | /// Diminished sixth.
152 | public static let d6 = Interval(quality: .diminished, degree: 6, semitones: 7)
153 | /// Diminished seventh.
154 | public static let d7 = Interval(quality: .diminished, degree: 7, semitones: 9)
155 | /// Diminished eighth.
156 | public static let d8 = Interval(quality: .diminished, degree: 8, semitones: 11)
157 | /// Diminished ninth.
158 | public static let d9 = Interval(quality: .diminished, degree: 9, semitones: 12)
159 | /// Diminished tenth.
160 | public static let d10 = Interval(quality: .diminished, degree: 10, semitones: 14)
161 | /// Diminished eleventh.
162 | public static let d11 = Interval(quality: .diminished, degree: 11, semitones: 16)
163 | /// Diminished twelfth.
164 | public static let d12 = Interval(quality: .diminished, degree: 12, semitones: 18)
165 | /// Diminished thirteenth.
166 | public static let d13 = Interval(quality: .diminished, degree: 13, semitones: 19)
167 | /// Diminished fourteenth.
168 | public static let d14 = Interval(quality: .diminished, degree: 14, semitones: 21)
169 | /// Diminished fifteenth.
170 | public static let d15 = Interval(quality: .diminished, degree: 15, semitones: 23)
171 |
172 | /// Augmented first.
173 | public static let A1 = Interval(quality: .augmented, degree: 1, semitones: 1)
174 | /// Augmented second.
175 | public static let A2 = Interval(quality: .augmented, degree: 2, semitones: 3)
176 | /// Augmented third.
177 | public static let A3 = Interval(quality: .augmented, degree: 3, semitones: 5)
178 | /// Augmented fourth.
179 | public static let A4 = Interval(quality: .augmented, degree: 4, semitones: 6)
180 | /// Augmented fifth.
181 | public static let A5 = Interval(quality: .augmented, degree: 5, semitones: 8)
182 | /// Augmented sixth.
183 | public static let A6 = Interval(quality: .augmented, degree: 6, semitones: 10)
184 | /// Augmented seventh.
185 | public static let A7 = Interval(quality: .augmented, degree: 7, semitones: 12)
186 | /// Augmented octave.
187 | public static let A8 = Interval(quality: .augmented, degree: 8, semitones: 13)
188 | /// Augmented ninth.
189 | public static let A9 = Interval(quality: .augmented, degree: 9, semitones: 15)
190 | /// Augmented tenth.
191 | public static let A10 = Interval(quality: .augmented, degree: 10, semitones: 17)
192 | /// Augmented eleventh.
193 | public static let A11 = Interval(quality: .augmented, degree: 11, semitones: 18)
194 | /// Augmented twelfth.
195 | public static let A12 = Interval(quality: .augmented, degree: 12, semitones: 20)
196 | /// Augmented thirteenth.
197 | public static let A13 = Interval(quality: .augmented, degree: 13, semitones: 22)
198 | /// Augmented fourteenth.
199 | public static let A14 = Interval(quality: .augmented, degree: 14, semitones: 24)
200 | /// Augmented fifteenth.
201 | public static let A15 = Interval(quality: .augmented, degree: 15, semitones: 25)
202 |
203 | /// All pre-defined intervals in a static array. You can filter it out with qualities, degrees or semitones.
204 | public static let all: [Interval] = [
205 | .P1, .P4, .P5, .P8, .P11, .P12, .P15,
206 | .m2, .m3, .m6, .m7, .m9, .m10, .m13, .m14,
207 | .M2, .M3, .M6, .M7, .M9, .M10, .M13, .M14,
208 | .d1, .d2, .d3, .d4, .d5, .d6, .d7, .d8, .d9, .d10, .d11, .d12, .d13, .d14, .d15,
209 | .A1, .A2, .A3, .A4, .A5, .A6, .A7, .A8, .A9, .A10, .A11, .A12, .A13, .A14, .A15,
210 | ]
211 |
212 | // MARK: CustomStringConvertible
213 |
214 | /// Returns the notation of the interval.
215 | public var notation: String {
216 | return "\(quality.notation)\(degree)"
217 | }
218 |
219 | /// Returns the name of the interval.
220 | public var description: String {
221 | var formattedDegree = "\(degree)"
222 |
223 | if #available(OSX 10.11, iOS 9.0, *) {
224 | let formatter = NumberFormatter()
225 | formatter.numberStyle = .ordinal
226 | formattedDegree = formatter.string(from: NSNumber(integerLiteral: degree)) ?? formattedDegree
227 | }
228 |
229 | return "\(quality) \(formattedDegree)"
230 | }
231 |
232 | // MARK: Hashable
233 |
234 | public func hash(into hasher: inout Hasher) {
235 | hasher.combine(quality)
236 | hasher.combine(degree)
237 | hasher.combine(semitones)
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Key.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Key.swift
3 | // MusicTheory iOS
4 | //
5 | // Created by Cem Olcay on 21.06.2018.
6 | // Copyright © 2018 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | /// Checks if two `Key` types are equal in terms of their int values.
14 | ///
15 | /// - Parameters:
16 | /// - lhs: Left hand side of the equation.
17 | /// - rhs: Right hand side of the equation.
18 | /// - Returns: Returns the equation value.
19 | public func == (lhs: Key, rhs: Key) -> Bool {
20 | let lhsMod = (lhs.type.rawValue + lhs.accidental.rawValue) % 12
21 | let normalizedLhs = lhsMod < 0 ? (12 + lhsMod) : lhsMod
22 |
23 | let rhsMod = (rhs.type.rawValue + rhs.accidental.rawValue) % 12
24 | let normalizedRhs = rhsMod < 0 ? (12 + rhsMod) : rhsMod
25 |
26 | return normalizedLhs == normalizedRhs
27 | }
28 |
29 | /// Checks if two `Key` types are equal in terms of their type and accidental values.
30 | ///
31 | /// - Parameters:
32 | /// - lhs: Left hand side of the equation.
33 | /// - rhs: Right hand side of the equation.
34 | /// - Returns: Returns the equation value.
35 | public func === (lhs: Key, rhs: Key) -> Bool {
36 | return lhs.type == rhs.type && lhs.accidental == rhs.accidental
37 | }
38 |
39 | /// Represents the keys that notes and pitches are based on.
40 | public struct Key: Codable, Equatable, Hashable, ExpressibleByStringLiteral, CustomStringConvertible {
41 | /// Base pitch of the key without accidentals. Accidentals will take account in the parent struct, `Key`. Integer values are based on C = 0 on western chromatic scale.
42 | public enum KeyType: Int, Codable, Equatable, Hashable, ExpressibleByStringLiteral, CustomStringConvertible {
43 | /// C key.
44 | case c = 0
45 | /// D key.
46 | case d = 2
47 | /// E key.
48 | case e = 4
49 | /// F key.
50 | case f = 5
51 | /// G key.
52 | case g = 7
53 | /// A key.
54 | case a = 9
55 | /// B key.
56 | case b = 11
57 |
58 | /// Returns all members of the `KeyType`.
59 | public static let all: [KeyType] = [.c, .d, .e, .f, .g, .a, .b]
60 |
61 | /// Returns neighbour `KeyType` at `distance` away. Works on both directions.
62 | /// Use negative distance value for going on left direction, positive distance value for going on right direction.
63 | /// This function iterates the `KeyType.all` array circullar to find the target KeyType.
64 | ///
65 | /// - Parameter distance: Target KeyType distance. Zero is self.
66 | /// - Returns: Returns the neighbouring KeyType distance away.
67 | public func key(at distance: Int) -> KeyType {
68 | guard let index = KeyType.all.firstIndex(of: self)
69 | else { return self }
70 |
71 | let normalizedDistance = (distance + index) % KeyType.all.count
72 | let keyIndex = normalizedDistance < 0 ? (KeyType.all.count + normalizedDistance) : normalizedDistance
73 | return KeyType.all[keyIndex]
74 | }
75 |
76 | /// Calculates the distance of two `KeyType`s.
77 | ///
78 | /// - Parameter keyType: Target `KeyType` you want to compare.
79 | /// - Returns: Returns the integer value of distance in terms of their array index values.
80 | public func distance(from keyType: KeyType) -> Int {
81 | guard let index = KeyType.all.firstIndex(of: self),
82 | let targetIndex = KeyType.all.firstIndex(of: keyType)
83 | else { return 0 }
84 | return targetIndex - index
85 | }
86 |
87 | /// Calculates the octave difference for a neighbouring `KeyType` at given interval away higher or lower.
88 | ///
89 | /// - Parameters:
90 | /// - interval: Interval you want to calculate octave difference.
91 | /// - isHigher: You want to calculate interval higher or lower from current key.
92 | /// - Returns: Returns the octave difference for a given interval higher or lower.
93 | public func octaveDiff(for interval: Interval, isHigher: Bool) -> Int {
94 | var diff = 0
95 | var currentKey = self
96 | for _ in 0 ..< (interval.degree - 1) {
97 | let next = currentKey.key(at: isHigher ? 1 : -1)
98 |
99 | if isHigher {
100 | if currentKey == .b, next == .c {
101 | diff += 1
102 | }
103 | } else {
104 | if currentKey == .c, next == .b {
105 | diff -= 1
106 | }
107 | }
108 |
109 | currentKey = next
110 | }
111 | return diff
112 | }
113 |
114 | // MARK: ExpressibleByStringLiteral
115 |
116 | public typealias StringLiteralType = String
117 |
118 | /// Initilizes with a string.
119 | ///
120 | /// - Parameter value: String representation of type.
121 | public init(stringLiteral value: KeyType.StringLiteralType) {
122 | switch value.lowercased() {
123 | case "a": self = .a
124 | case "b": self = .b
125 | case "c": self = .c
126 | case "d": self = .d
127 | case "e": self = .e
128 | case "f": self = .f
129 | case "g": self = .g
130 | default: self = .c
131 | }
132 | }
133 |
134 | // MARK: CustomStringConvertible
135 |
136 | /// Returns the key notation.
137 | public var description: String {
138 | switch self {
139 | case .c: return "C"
140 | case .d: return "D"
141 | case .e: return "E"
142 | case .f: return "F"
143 | case .g: return "G"
144 | case .a: return "A"
145 | case .b: return "B"
146 | }
147 | }
148 | }
149 |
150 | /// Type of the key.
151 | public var type: KeyType
152 |
153 | /// Accidental of the key.
154 | public var accidental: Accidental
155 |
156 | /// All notes in an octave with sharp notes.
157 | public static let keysWithSharps = [
158 | Key(type: .c, accidental: .natural),
159 | Key(type: .c, accidental: .sharp),
160 | Key(type: .d, accidental: .natural),
161 | Key(type: .d, accidental: .sharp),
162 | Key(type: .e, accidental: .natural),
163 | Key(type: .f, accidental: .natural),
164 | Key(type: .f, accidental: .sharp),
165 | Key(type: .g, accidental: .natural),
166 | Key(type: .g, accidental: .sharp),
167 | Key(type: .a, accidental: .natural),
168 | Key(type: .a, accidental: .sharp),
169 | Key(type: .b, accidental: .natural),
170 | ]
171 |
172 | /// All notes in an octave with flat notes.
173 | public static let keysWithFlats = [
174 | Key(type: .c, accidental: .natural),
175 | Key(type: .d, accidental: .flat),
176 | Key(type: .d, accidental: .natural),
177 | Key(type: .e, accidental: .flat),
178 | Key(type: .e, accidental: .natural),
179 | Key(type: .f, accidental: .natural),
180 | Key(type: .g, accidental: .flat),
181 | Key(type: .g, accidental: .natural),
182 | Key(type: .a, accidental: .flat),
183 | Key(type: .a, accidental: .natural),
184 | Key(type: .b, accidental: .flat),
185 | Key(type: .b, accidental: .natural),
186 | ]
187 |
188 | /// All notes in an octave with both flat and sharp notes.
189 | public static let allKeys = [
190 | Key(type: .c, accidental: .natural),
191 | Key(type: .c, accidental: .sharp),
192 | Key(type: .d, accidental: .flat),
193 | Key(type: .d, accidental: .natural),
194 | Key(type: .d, accidental: .sharp),
195 | Key(type: .e, accidental: .flat),
196 | Key(type: .e, accidental: .natural),
197 | Key(type: .f, accidental: .natural),
198 | Key(type: .f, accidental: .sharp),
199 | Key(type: .g, accidental: .flat),
200 | Key(type: .g, accidental: .natural),
201 | Key(type: .g, accidental: .sharp),
202 | Key(type: .a, accidental: .flat),
203 | Key(type: .a, accidental: .natural),
204 | Key(type: .a, accidental: .sharp),
205 | Key(type: .b, accidental: .flat),
206 | Key(type: .b, accidental: .natural),
207 | ]
208 |
209 | /// Initilizes the key with its type and accidental.
210 | ///
211 | /// - Parameters:
212 | /// - type: The type of the key.
213 | /// - accidental: Accidental of the key. Defaults natural.
214 | public init(type: KeyType, accidental: Accidental = .natural) {
215 | self.type = type
216 | self.accidental = accidental
217 | }
218 |
219 | // MARK: ExpressibleByStringLiteral
220 |
221 | public typealias StringLiteralType = String
222 |
223 | /// Initilizes with a string.
224 | ///
225 | /// - Parameter value: String representation of type.
226 | public init(stringLiteral value: Key.StringLiteralType) {
227 | var keyType = KeyType.c
228 | var accidental = Accidental.natural
229 | let pattern = "([A-Ga-g])([#♯♭b]*)"
230 | let regex = try? NSRegularExpression(pattern: pattern, options: [])
231 | if let regex = regex,
232 | let match = regex.firstMatch(in: value, options: [], range: NSRange(0 ..< value.count)),
233 | let keyTypeRange = Range(match.range(at: 1), in: value),
234 | let accidentalRange = Range(match.range(at: 2), in: value),
235 | match.numberOfRanges == 3 {
236 | // Set key type
237 | keyType = KeyType(stringLiteral: String(value[keyTypeRange]))
238 | // Set accidental
239 | accidental = Accidental(stringLiteral: String(value[accidentalRange]))
240 | }
241 |
242 | self = Key(type: keyType, accidental: accidental)
243 | }
244 |
245 | // MARK: CustomStringConvertible
246 |
247 | /// Returns the key notation with its type and accidental, if has any.
248 | public var description: String {
249 | return "\(type)\(accidental)"
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/MusicTheory.h:
--------------------------------------------------------------------------------
1 | //
2 | // MusicTheory.h
3 | // MusicTheory
4 | //
5 | // Created by Cem Olcay on 29/12/2016.
6 | // Copyright © 2016 prototapp. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for MusicTheory.
12 | FOUNDATION_EXPORT double MusicTheoryVersionNumber;
13 |
14 | //! Project version string for MusicTheory.
15 | FOUNDATION_EXPORT const unsigned char MusicTheoryVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/NoteValue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteValue.swift
3 | // MusicTheory iOS
4 | //
5 | // Created by Cem Olcay on 21.06.2018.
6 | // Copyright © 2018 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | // MARK: - NoteValueType
14 |
15 | /// Defines the types of note values.
16 | public struct NoteValueType: Codable, Hashable, CustomStringConvertible {
17 | /// The note value's duration in beats.
18 | public var rate: Double
19 | /// Name of the note value.
20 | public var description: String
21 |
22 | /// Creates a note value type.
23 | ///
24 | /// - Parameters:
25 | /// - rate: The rate of the note value in beats.
26 | /// - description: The name of the note value.
27 | public init(rate: Double, description: String) {
28 | self.rate = rate
29 | self.description = description
30 | }
31 |
32 | /// Sixteen bar notes.
33 | public static let sixteenBars = NoteValueType(rate: 16.0, description: "16 Bars")
34 | /// Eigth bar notes.
35 | public static let eigthBars = NoteValueType(rate: 8.0, description: "8 Bars")
36 | /// Four bar notes.
37 | public static let fourBars = NoteValueType(rate: 4.0, description: "4 Bars")
38 | /// Two bar notes.
39 | public static let twoBars = NoteValueType(rate: 2.0, description: "2 Bars")
40 | /// One bar note.
41 | public static let oneBar = NoteValueType(rate: 1.0, description: "1 Bar")
42 | /// Two whole notes.
43 | public static let doubleWhole = NoteValueType(rate: 2.0 / 1.0, description: "2/1")
44 | /// Whole note.
45 | public static let whole = NoteValueType(rate: 1.0 / 1.0, description: "1/1")
46 | /// Half note.
47 | public static let half = NoteValueType(rate: 1.0 / 2.0, description: "1/2")
48 | /// Quarter note.
49 | public static let quarter = NoteValueType(rate: 1.0 / 4.0, description: "1/4")
50 | /// Eighth note.
51 | public static let eighth = NoteValueType(rate: 1.0 / 8.0, description: "1/8")
52 | /// Sixteenth note.
53 | public static let sixteenth = NoteValueType(rate: 1.0 / 16.0, description: "1/16")
54 | /// Thirtysecond note.
55 | public static let thirtysecond = NoteValueType(rate: 1.0 / 32.0, description: "1/32")
56 | /// Sixtyfourth note.
57 | public static let sixtyfourth = NoteValueType(rate: 1.0 / 64.0, description: "1/64")
58 |
59 | public static let all: [NoteValueType] = [
60 | .sixteenBars, .eigthBars, .fourBars, .twoBars, .oneBar,
61 | .half, .quarter, .eighth, .sixteenth, .thirtysecond, .sixtyfourth
62 | ]
63 | }
64 |
65 | // MARK: - NoteModifier
66 |
67 | /// Defines the length of a `NoteValue`
68 | public enum NoteModifier: Double, Codable, CaseIterable, CustomStringConvertible {
69 | /// No additional length.
70 | case `default` = 1
71 | /// Adds half of its own value.
72 | case dotted = 1.5
73 | /// Three notes of the same value.
74 | case triplet = 0.6667
75 | /// Five of the indicated note value total the duration normally occupied by four.
76 | case quintuplet = 0.8
77 |
78 | /// The string representation of the modifier.
79 | public var description: String {
80 | switch self {
81 | case .default: return ""
82 | case .dotted: return "D"
83 | case .triplet: return "T"
84 | case .quintuplet: return "Q"
85 | }
86 | }
87 | }
88 |
89 | // MARK: - NoteValue
90 |
91 | /// Calculates how many notes of a single `NoteValueType` is equivalent to a given `NoteValue`.
92 | ///
93 | /// - Parameters:
94 | /// - noteValue: The note value to be measured.
95 | /// - noteValueType: The note value type to measure the length of the note value.
96 | /// - Returns: Returns how many notes of a single `NoteValueType` is equivalent to a given `NoteValue`.
97 | public func / (noteValue: NoteValue, noteValueType: NoteValueType) -> Double {
98 | return (noteValue.type.rate * noteValue.modifier.rawValue) / noteValueType.rate
99 | }
100 |
101 | /// Checks the equality between two `NoteValue` types.
102 | ///
103 | /// - Parameters:
104 | /// - lhs: Left hand side `NoteValue`.
105 | /// - rhs: Right hand side `NoteValue`.
106 | /// - Returns: Returns true if two `NoteValue`s are equal.
107 | public func == (lhs: NoteValue, rhs: NoteValue) -> Bool {
108 | return lhs.type == rhs.type && lhs.modifier == rhs.modifier
109 | }
110 |
111 | /// Defines the duration of a note beatwise.
112 | public struct NoteValue: Codable, Equatable, CustomStringConvertible {
113 | /// Type that represents the duration of note.
114 | public var type: NoteValueType
115 | /// Modifier for `NoteType` that modifies the duration.
116 | public var modifier: NoteModifier
117 |
118 | /// Initilize the NoteValue with its type and optional modifier.
119 | ///
120 | /// - Parameters:
121 | /// - type: Type of note value that represents note duration.
122 | /// - modifier: Modifier of note value. Defaults `default`.
123 | public init(type: NoteValueType, modifier: NoteModifier = .default) {
124 | self.type = type
125 | self.modifier = modifier
126 | }
127 |
128 | /// Note value in beats.
129 | public var rate: Double {
130 | return type.rate * modifier.rawValue
131 | }
132 |
133 | /// Returns the string representation of the note value.
134 | public var description: String {
135 | return "\(type)\(modifier)"
136 | }
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Pitch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Pitch.swift
3 | // MusicTheory
4 | //
5 | // Created by Cem Olcay on 21.06.2018.
6 | // Copyright © 2018 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | /// Returns the pitch above target interval from target pitch.
14 | ///
15 | /// - Parameters:
16 | /// - lhs: Target `Pitch`.
17 | /// - rhs: Target `Interval`.
18 | /// - Returns: Returns new pitch above target interval from target pitch.
19 | public func + (lhs: Pitch, rhs: Interval) -> Pitch {
20 | let degree = rhs.degree - 1
21 | let targetKeyType = lhs.key.type.key(at: degree)
22 | let targetPitch = lhs + rhs.semitones
23 | let targetOctave = lhs.octave + lhs.key.type.octaveDiff(for: rhs, isHigher: true)
24 |
25 | // Convert pitch
26 | var convertedPitch = Pitch(key: Key(type: targetKeyType), octave: targetOctave)
27 | let diff = targetPitch.rawValue - convertedPitch.rawValue
28 | convertedPitch.key.accidental = Accidental(integerLiteral: diff)
29 | return convertedPitch
30 | }
31 |
32 | /// Returns the pitch below target interval from target pitch.
33 | ///
34 | /// - Parameters:
35 | /// - lhs: Target `Pitch`.
36 | /// - rhs: Target `Interval`.
37 | /// - Returns: Returns new pitch below target interval from target pitch.
38 | public func - (lhs: Pitch, rhs: Interval) -> Pitch {
39 | let degree = -(rhs.degree - 1)
40 | let targetKeyType = lhs.key.type.key(at: degree)
41 | let targetPitch = lhs - rhs.semitones
42 | let targetOctave = lhs.octave + lhs.key.type.octaveDiff(for: rhs, isHigher: false)
43 |
44 | // Convert pitch
45 | var convertedPitch = Pitch(key: Key(type: targetKeyType), octave: targetOctave)
46 | let diff = targetPitch.rawValue - convertedPitch.rawValue
47 | convertedPitch.key.accidental = Accidental(integerLiteral: diff)
48 | return convertedPitch
49 | }
50 |
51 | /// Calculates the interval between two pitches.
52 | /// Doesn't matter left hand side and right hand side note places.
53 | ///
54 | /// - Parameters:
55 | /// - lhs: Left hand side of the equation.
56 | /// - rhs: Right hand side of the equation.
57 | /// - Returns: `Intreval` between two pitches. You can get the halfsteps from interval as well.
58 | public func - (lhs: Pitch, rhs: Pitch) -> Interval {
59 | let top = max(lhs, rhs)
60 | let bottom = min(lhs, rhs)
61 | let diff = top.rawValue - bottom.rawValue
62 |
63 | let bottomKeyIndex = Key.KeyType.all.firstIndex(of: bottom.key.type) ?? 0
64 | let topKeyIndex = Key.KeyType.all.firstIndex(of: top.key.type) ?? 0
65 | let degree = abs(topKeyIndex - bottomKeyIndex) + 1
66 | let isMajor = (degree == 2 || degree == 3 || degree == 6 || degree == 7)
67 |
68 | let majorScale = Scale(type: .major, key: bottom.key)
69 | if majorScale.keys.contains(top.key) { // Major or Perfect
70 | return Interval(
71 | quality: isMajor ? .major : .perfect,
72 | degree: degree,
73 | semitones: diff
74 | )
75 | } else { // Augmented, Diminished or Minor
76 | if isMajor {
77 | let majorPitch = bottom + Interval(quality: .major, degree: degree, semitones: diff)
78 | let offset = top.rawValue - majorPitch.rawValue
79 | return Interval(
80 | quality: offset > 0 ? .augmented : .minor,
81 | degree: degree,
82 | semitones: diff
83 | )
84 | } else {
85 | let perfectPitch = bottom + Interval(quality: .perfect, degree: degree, semitones: diff)
86 | let offset = top.rawValue - perfectPitch.rawValue
87 | return Interval(
88 | quality: offset > 0 ? .augmented : .diminished,
89 | degree: degree,
90 | semitones: diff
91 | )
92 | }
93 | }
94 | }
95 |
96 | /// Calculates the `Pitch` above halfsteps.
97 | ///
98 | /// - Parameters:
99 | /// - note: The pitch that is being added halfsteps.
100 | /// - halfstep: Halfsteps above.
101 | /// - Returns: Returns `Pitch` above halfsteps.
102 | public func + (pitch: Pitch, halfstep: Int) -> Pitch {
103 | return Pitch(midiNote: pitch.rawValue + halfstep)
104 | }
105 |
106 | /// Calculates the `Pitch` below halfsteps.
107 | ///
108 | /// - Parameters:
109 | /// - note: The pitch that is being calculated.
110 | /// - halfstep: Halfsteps below.
111 | /// - Returns: Returns `Pitch` below halfsteps.
112 | public func - (pitch: Pitch, halfstep: Int) -> Pitch {
113 | return Pitch(midiNote: pitch.rawValue - halfstep)
114 | }
115 |
116 | /// Compares the equality of two pitches by their MIDI note value.
117 | /// Alternative notes passes this equality. Use `===` function if you want to check exact equality in terms of exact keys.
118 | ///
119 | /// - Parameters:
120 | /// - left: Left handside `Pitch` to be compared.
121 | /// - right: Right handside `Pitch` to be compared.
122 | /// - Returns: Returns the bool value of comparisation of two pitches.
123 | public func == (left: Pitch, right: Pitch) -> Bool {
124 | return left.rawValue == right.rawValue
125 | }
126 |
127 | /// Compares the exact equality of two pitches by their keys and octaves.
128 | /// Alternative notes not passes this equality. Use `==` function if you want to check equality in terms of MIDI note value.
129 | ///
130 | /// - Parameters:
131 | /// - left: Left handside `Pitch` to be compared.
132 | /// - right: Right handside `Pitch` to be compared.
133 | /// - Returns: Returns the bool value of comparisation of two pitches.
134 | public func === (left: Pitch, right: Pitch) -> Bool {
135 | return left.key == right.key && left.octave == right.octave
136 | }
137 |
138 | /// Compares two `Pitch`es in terms of their semitones.
139 | ///
140 | /// - Parameters:
141 | /// - lhs: Left hand side of the equation.
142 | /// - rhs: Right hand side of the equation.
143 | /// - Returns: Returns true if left hand side `Pitch` lower than right hand side `Pitch`.
144 | public func < (lhs: Pitch, rhs: Pitch) -> Bool {
145 | return lhs.rawValue < rhs.rawValue
146 | }
147 |
148 | /// Pitch object with a `Key` and an octave.
149 | /// Could be initilized with MIDI note number and preferred accidental type.
150 | public struct Pitch: RawRepresentable, Codable, Hashable, Comparable, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral, CustomStringConvertible {
151 | /// Key of the pitch like C, D, A, B with accidentals.
152 | public var key: Key
153 |
154 | /// Octave of the pitch.
155 | /// In theory this must be zero or a positive integer.
156 | /// But `Note` does not limit octave and calculates every possible octave including the negative ones.
157 | public var octave: Int
158 |
159 | /// This function returns the nearest pitch to the given frequency in Hz.
160 | ///
161 | /// - Parameter frequency: The frequency in Hz
162 | /// - Returns: The nearest pitch for given frequency
163 | public static func nearest(frequency: Float) -> Pitch? {
164 | let allPitches = Array((1 ... 7).map { octave -> [Pitch] in
165 | Key.keysWithSharps.map { key -> Pitch in
166 | Pitch(key: key, octave: octave)
167 | }
168 | }.joined())
169 |
170 | var results = allPitches.map { pitch -> (pitch: Pitch, distance: Float) in
171 | (pitch: pitch, distance: abs(pitch.frequency - frequency))
172 | }
173 |
174 | results.sort { $0.distance < $1.distance }
175 | return results.first?.pitch
176 | }
177 |
178 | /// Initilizes the `Pitch` with MIDI note number.
179 | ///
180 | /// - Parameter midiNote: Midi note in range of [0 - 127].
181 | /// - Parameter preferSharps: Make it true if preferred accidentals is sharps. Defaults true.
182 | public init(midiNote: Int, preferSharps: Bool = true) {
183 | octave = (midiNote / 12) - 1
184 | let keyIndex = midiNote % 12
185 | key = (preferSharps ? Key.keysWithSharps : Key.keysWithFlats)[circular: keyIndex] ?? "c"
186 | }
187 |
188 | /// Initilizes the `Pitch` with `Key` and octave
189 | ///
190 | /// - Parameters:
191 | /// - key: Key of the pitch.
192 | /// - octave: Octave of the pitch.
193 | public init(key: Key, octave: Int) {
194 | self.key = key
195 | self.octave = octave
196 | }
197 |
198 | /// Calculates and returns the frequency of note on octave based on its location of piano keys.
199 | /// Bases A4 note of 440Hz frequency standard.
200 | public var frequency: Float {
201 | let fn = powf(2.0, Float(rawValue - 69) / 12.0)
202 | return fn * 440.0
203 | }
204 |
205 | // MARK: RawRepresentable
206 |
207 | public typealias RawValue = Int
208 |
209 | /// Returns midi note number.
210 | /// In theory, this must be in range [0 - 127].
211 | /// But it does not limits the midi note value.
212 | public var rawValue: Int {
213 | let semitones = key.type.rawValue + key.accidental.rawValue
214 | return semitones + ((octave + 1) * 12)
215 | }
216 |
217 | /// Initilizes the pitch with an integer value that represents the MIDI note number of the pitch.
218 | ///
219 | /// - Parameter rawValue: MIDI note number of the pitch.
220 | public init?(rawValue: Pitch.RawValue) {
221 | self = Pitch(midiNote: rawValue)
222 | }
223 |
224 | // MARK: ExpressibleByIntegerLiteral
225 |
226 | public typealias IntegerLiteralType = Int
227 |
228 | /// Initilizes the pitch with an integer value that represents the MIDI note number of the pitch.
229 | ///
230 | /// - Parameter value: MIDI note number of the pitch.
231 | public init(integerLiteral value: Pitch.IntegerLiteralType) {
232 | self = Pitch(midiNote: value)
233 | }
234 |
235 | // MARK: ExpressibleByStringLiteral
236 |
237 | public typealias StringLiteralType = String
238 |
239 | /// Initilizes with a string.
240 | ///
241 | /// - Parameter value: String representation of type.
242 | public init(stringLiteral value: Pitch.StringLiteralType) {
243 | var keyType = Key.KeyType.c
244 | var accidental = Accidental.natural
245 | var octave = 0
246 | let pattern = "([A-Ga-g])([#♯♭b]*)(-?)(\\d+)"
247 | let regex = try? NSRegularExpression(pattern: pattern, options: [])
248 | if let regex = regex,
249 | let match = regex.firstMatch(in: value, options: [], range: NSRange(0 ..< value.count)),
250 | let keyTypeRange = Range(match.range(at: 1), in: value),
251 | let accidentalRange = Range(match.range(at: 2), in: value),
252 | let signRange = Range(match.range(at: 3), in: value),
253 | let octaveRange = Range(match.range(at: 4), in: value),
254 | match.numberOfRanges == 5 {
255 | // key type
256 | keyType = Key.KeyType(stringLiteral: String(value[keyTypeRange]))
257 | // accidental
258 | accidental = Accidental(stringLiteral: String(value[accidentalRange]))
259 | // sign
260 | let sign = String(value[signRange])
261 | // octave
262 | octave = (Int(String(value[octaveRange])) ?? 0) * (sign == "-" ? -1 : 1)
263 | }
264 |
265 | self = Pitch(key: Key(type: keyType, accidental: accidental), octave: octave)
266 | }
267 |
268 | // MARK: Hashable
269 |
270 | public func hash(into hasher: inout Hasher) {
271 | hasher.combine(key)
272 | hasher.combine(octave)
273 | }
274 |
275 | // MARK: CustomStringConvertible
276 |
277 | /// Converts `Pitch` to string with its key and octave.
278 | public var description: String {
279 | return "\(key)\(octave)"
280 | }
281 | }
282 |
283 | extension Array {
284 | /// An array subscript extension that returns the element from the positive or negative circular index.
285 | subscript(circular index: Int) -> Element? {
286 | guard count > 0 else { return nil }
287 | let mod = index % count
288 | let offset = index >= 0 ? 0 : count
289 | let idx = mod == 0 ? 0 : mod + offset
290 | return self[idx]
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Scale.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Scale.swift
3 | // MusicTheory
4 | //
5 | // Created by Cem Olcay on 24.10.2017.
6 | // Copyright © 2017 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | /// Checks the equability between two `Scale`s by their base key and notes.
14 | ///
15 | /// - Parameters:
16 | /// - left: Left handside of the equation.
17 | /// - right: Right handside of the equation.
18 | /// - Returns: Returns Bool value of equation of two given scales.
19 | public func == (left: Scale, right: Scale) -> Bool {
20 | return left.key == right.key && left.type == right.type
21 | }
22 |
23 | /// Scale object with `ScaleType` and scale's key of `NoteType`.
24 | /// Could calculate note sequences in [Note] format.
25 | public struct Scale: Hashable, Codable {
26 | /// Type of the scale that has `interval` info.
27 | public var type: ScaleType
28 | /// Root key of the scale that will built onto.
29 | public var key: Key
30 |
31 | /// Initilizes the scale with its type and key.
32 | ///
33 | /// - Parameters:
34 | /// - type: Type of scale being initilized.
35 | /// - key: Key of scale being initilized.
36 | public init(type: ScaleType, key: Key) {
37 | self.type = type
38 | self.key = key
39 | }
40 |
41 | /// Keys generated by the intervals of the scale.
42 | public var keys: [Key] {
43 | return pitches(octave: 1).map({ $0.key })
44 | }
45 |
46 | /// Generates `Pitch` array of scale in given octave.
47 | ///
48 | /// - Parameter octave: Octave value of notes in scale.
49 | /// - Returns: Returns `Pitch` array of the scale in given octave.
50 | public func pitches(octave: Int) -> [Pitch] {
51 | return pitches(octaves: octave)
52 | }
53 |
54 | /// Generates `Pitch` array of scale in given octaves.
55 | ///
56 | /// - Parameter octaves: Variadic value of octaves to generate pitches in scale.
57 | /// - Returns: Returns `Pitch` array of the scale in given octaves.
58 | public func pitches(octaves: Int...) -> [Pitch] {
59 | return pitches(octaves: octaves)
60 | }
61 |
62 | /// Generates `Pitch` array of scale in given octaves.
63 | ///
64 | /// - Parameter octaves: Array value of octaves to generate pitches in scale.
65 | /// - Returns: Returns `Pitch` array of the scale in given octaves.
66 | public func pitches(octaves: [Int]) -> [Pitch] {
67 | var pitches = [Pitch]()
68 | octaves.forEach({ octave in
69 | let root = Pitch(key: key, octave: octave)
70 | pitches += type.intervals.map({ root + $0 })
71 | })
72 | return pitches
73 | }
74 |
75 | // MARK: Hashable
76 |
77 | public func hash(into hasher: inout Hasher) {
78 | hasher.combine(key)
79 | hasher.combine(type)
80 | }
81 | }
82 |
83 | extension Scale {
84 | /// Stack of notes to generate chords for each note in the scale.
85 | public enum HarmonicField: Int, Codable {
86 | /// First, third and fifth degree notes builds a triad chord.
87 | case triad
88 | /// First, third, fifth and seventh notes builds a tetrad chord.
89 | case tetrad
90 | /// First, third, fifth, seventh and ninth notes builds a 9th chord.
91 | case ninth
92 | /// First, third, fifth, seventh, ninth and eleventh notes builds a 11th chord.
93 | case eleventh
94 | /// First, third, fifth, seventh, ninth, eleventh and thirteenth notes builds a 13th chord.
95 | case thirteenth
96 |
97 | /// All possible harmonic fields constructed from.
98 | public static let all: [HarmonicField] = [.triad, .tetrad, .ninth, .eleventh, .thirteenth]
99 | }
100 |
101 | /// Generates chords for harmonic field of scale.
102 | ///
103 | /// - Parameter field: Type of chords you want to generate.
104 | /// - Parameter inversion: Inversion degree of the chords. Defaults 0.
105 | /// - Returns: Returns triads or tetrads of chord for each note in scale.
106 | public func harmonicField(for field: HarmonicField, inversion: Int = 0) -> [Chord?] {
107 | var chords = [Chord?]()
108 |
109 | // Extended notes for picking notes.
110 | let octaves = [0, 1, 2, 3, 4]
111 | let scalePitches = pitches(octaves: octaves)
112 |
113 | // Build chords for each note in scale.
114 | for i in 0 ..< scalePitches.count / octaves.count {
115 | var chordPitches = [Pitch]()
116 | switch field {
117 | case .triad:
118 | chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4]]
119 | case .tetrad:
120 | chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4], scalePitches[i + 6]]
121 | case .ninth:
122 | chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4], scalePitches[i + 6], scalePitches[i + 8]]
123 | case .eleventh:
124 | chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4], scalePitches[i + 6], scalePitches[i + 8], scalePitches[i + 10]]
125 | case .thirteenth:
126 | chordPitches = [scalePitches[i], scalePitches[i + 2], scalePitches[i + 4], scalePitches[i + 6], scalePitches[i + 8], scalePitches[i + 10], scalePitches[i + 12]]
127 | }
128 |
129 | // Build intervals
130 | let root = chordPitches[0]
131 | let intervals = chordPitches.map({ $0 - root })
132 |
133 | // Build chord
134 | if let chordType = ChordType(intervals: intervals) {
135 | let chord = Chord(type: chordType, key: root.key, inversion: inversion)
136 | chords.append(chord)
137 | } else {
138 | chords.append(nil)
139 | }
140 | }
141 |
142 | return chords
143 | }
144 | }
145 |
146 | extension Scale: CustomStringConvertible {
147 | /// Converts `Scale` to string with its key and type.
148 | public var description: String {
149 | return "\(key) \(type): " + keys.map({ "\($0)" }).joined(separator: ", ")
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/ScaleType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScaleType.swift
3 | //
4 | //
5 | // Created by Cem Olcay on 1/16/22.
6 | //
7 | // https://github.com/cemolcay/MusicTheory
8 | //
9 |
10 | import Foundation
11 |
12 | /// Represents scale by the intervals between note sequences.
13 | public struct ScaleType: Codable, Hashable, CustomStringConvertible {
14 | /// Intervals of the scale.
15 | public let intervals: [Interval]
16 | /// Description of the scale.
17 | public let description: String
18 |
19 | /// Initilize the scale with series of its intervals.
20 | ///
21 | /// - Parameters:
22 | /// - intervals: Intervals of the scale.
23 | /// - description: Description of the scale.
24 | public init(intervals: [Interval], description: String) {
25 | self.intervals = intervals
26 | self.description = description
27 | }
28 |
29 | // MARK: Hashable
30 |
31 | public func hash(into hasher: inout Hasher) {
32 | hasher.combine(intervals)
33 | }
34 |
35 | // MARK: Equatable
36 |
37 | /// Checks the equability between two `ScaleType`s by their intervals.
38 | ///
39 | /// - Parameters:
40 | /// - left: Left handside of the equation.
41 | /// - right: Right handside of the equation.
42 | /// - Returns: Returns Bool value of equation of two given scale types.
43 | public static func == (left: ScaleType, right: ScaleType) -> Bool {
44 | return left.intervals == right.intervals && left.description == right.description
45 | }
46 |
47 | // MARK: Codable
48 |
49 | /// Keys that conforms CodingKeys protocol to map properties.
50 | private enum CodingKeys: String, CodingKey {
51 | /// Halfstep property of `Interval`.
52 | case intervals
53 | /// Name of the scale.
54 | case description
55 | }
56 |
57 | /// Decodes struct with a decoder.
58 | ///
59 | /// - Parameter decoder: Decodes encoded struct.
60 | /// - Throws: Tries to initlize struct with a decoder.
61 | public init(from decoder: Decoder) throws {
62 | let values = try decoder.container(keyedBy: CodingKeys.self)
63 | let intervals = try values.decode([Interval].self, forKey: .intervals)
64 | let description = try values.decode(String.self, forKey: .description)
65 | self = ScaleType(intervals: intervals, description: description)
66 | }
67 |
68 | /// Encodes struct with an ecoder.
69 | ///
70 | /// - Parameter encoder: Encodes struct.
71 | /// - Throws: Tries to encode struct.
72 | public func encode(to encoder: Encoder) throws {
73 | var container = encoder.container(keyedBy: CodingKeys.self)
74 | try container.encode(intervals, forKey: .intervals)
75 | try container.encode(description, forKey: .description)
76 | }
77 | }
78 |
79 | extension ScaleType {
80 | /// Major scale.
81 | public static let major = ScaleType(intervals: ScaleType.ionian.intervals, description: "Major")
82 |
83 | /// Minor scale.
84 | public static let minor = ScaleType(intervals: ScaleType.aeolian.intervals, description: "Minor")
85 |
86 | /// Harmonic minor scale.
87 | public static let harmonicMinor = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .m6, .M7], description: "Harmonic Minor")
88 |
89 | /// Melodic minor scale.
90 | public static let melodicMinor = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .M6, .M7], description: "Melodic Minor")
91 |
92 | /// Pentatonic major scale.
93 | public static let pentatonicMajor = ScaleType(intervals: [.P1, .M2, .M3, .P5, .M6], description: "Pentatonic Major")
94 |
95 | /// Pentatonic minor scale.
96 | public static let pentatonicMinor = ScaleType(intervals: [.P1, .m3, .P4, .P5, .m7], description: "Pentatonic Minor")
97 |
98 | /// Pentatonic blues scale.
99 | public static let pentatonicBlues = ScaleType(intervals: [.P1, .m3, .P4, .d5, .P5, .m7], description: "Pentatonic Blues")
100 |
101 | /// Pentatonic neutral scale.
102 | public static let pentatonicNeutral = ScaleType(intervals: [.P1, .M2, .P4, .P5, .m7], description: "Pentatonic Neutral")
103 |
104 | /// Ionian scale.
105 | public static let ionian = ScaleType(intervals: [.P1, .M2, .M3, .P4, .P5, .M6, .M7], description: "Ionian")
106 |
107 | /// Aeolian scale.
108 | public static let aeolian = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .m6, .m7], description: "Aeolian")
109 |
110 | /// Dorian scale.
111 | public static let dorian = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .M6, .m7], description: "Dorian")
112 |
113 | /// Mixolydian scale.
114 | public static let mixolydian = ScaleType(intervals: [.P1, .M2, .M3, .P4, .P5, .M6, .m7], description: "Mixolydian")
115 |
116 | /// Phrygian scale.
117 | public static let phrygian = ScaleType(intervals: [.P1, .m2, .m3, .P4, .P5, .m6, .m7], description: "Phrygian")
118 |
119 | /// Lydian scale.
120 | public static let lydian = ScaleType(intervals: [.P1, .M2, .M3, .A4, .P5, .M6, .M7], description: "Lydian")
121 |
122 | /// Locrian scale.
123 | public static let locrian = ScaleType(intervals: [.P1, .m2, .m3, .P4, .d5, .m6, .m7], description: "Locrian")
124 |
125 | /// Half diminished scale.
126 | public static let halfDiminished = ScaleType(intervals: [.P1, .m2, .m3, .M3, .d5, .P5, .M6, .m7], description: "Half Diminished")
127 |
128 | /// Whole diminished scale.
129 | public static let wholeDiminished = ScaleType(intervals: [.P1, .M2, .m3, .P4, .d5, .m6, .M6, .M7], description: "Whole Diminished")
130 |
131 | /// Whole scale.
132 | public static let whole = ScaleType(intervals: [.P1, .M2, .M3, .d5, .m6, .m7], description: "Whole")
133 |
134 | /// Augmented scale.
135 | public static let augmented = ScaleType(intervals: [.m3, .M3, .P5, .m6, .M7], description: "Augmented")
136 |
137 | /// Chromatic scale.
138 | public static let chromatic = ScaleType(intervals: [.P1, .m2, .M2, .m3, .M3, .P4, .d5, .P5, .m6, .M6, .m7, .M7], description: "Chromatic")
139 |
140 | /// Roumanian minor scale.
141 | public static let romanianMinor = ScaleType(intervals: [.P1, .M2, .m3, .d5, .P5, .M6, .m7], description: "Romanian Minor")
142 |
143 | /// Spanish gypsy scale.
144 | public static let spanishGypsy = ScaleType(intervals: [.P1, .m2, .M3, .P4, .P5, .m6, .m7], description: "Spanish Gypsy")
145 |
146 | /// Diatonic scale.
147 | public static let diatonic = ScaleType(intervals: [.P1, .M2, .M3, .P5, .M6], description: "Diatonic")
148 |
149 | /// Dobule harmonic scale.
150 | public static let doubleHarmonic = ScaleType(intervals: [.P1, .m2, .M3, .P4, .P5, .m6, .M7], description: "Double Harmonic")
151 |
152 | /// Eight tone spanish scale.
153 | public static let eightToneSpanish = ScaleType(intervals: [.P1, .m2, .m3, .M3, .P4, .d5, .m6, .m7], description: "Eight Tone Spanish")
154 |
155 | /// Enigmatic scale.
156 | public static let enigmatic = ScaleType(intervals: [.P1, .m2, .M3, .A4, .A5, .A6, .M7], description: "Enigmatic")
157 |
158 | /// Leading whole tone scale.
159 | public static let leadingWholeTone = ScaleType(intervals: [.P1, .M2, .M3, .d5, .m6, .M6, .m7], description: "Leading Whole Tone")
160 |
161 | /// Lydian augmented scale.
162 | public static let lydianAugmented = ScaleType(intervals: [.P1, .M2, .M3, .A4, .A5, .M6, .M7], description: "Lydian Augmented")
163 |
164 | /// Neopolitan major scale.
165 | public static let neopolitanMajor = ScaleType(intervals: [.P1, .m2, .m3, .P4, .P5, .M6, .M7], description: "Neopolitan Major")
166 |
167 | /// Neopolitan minor scale.
168 | public static let neopolitanMinor = ScaleType(intervals: [.P1, .m2, .m3, .P4, .P5, .m6, .m7], description: "Neopolitan Minor")
169 |
170 | /// Pelog scale.
171 | public static let pelog = ScaleType(intervals: [.P1, .m2, .m3, .d5, .m7, .M7], description: "Pelog")
172 |
173 | /// Prometheus scale.
174 | public static let prometheus = ScaleType(intervals: [.P1, .M2, .M3, .A4, .M6, .m7], description: "Prometheus")
175 |
176 | /// Prometheus neopolitan scale.
177 | public static let prometheusNeopolitan = ScaleType(intervals: [.P1, .m2, .M3, .d5, .M6, .m7], description: "Prometheus Neopolitan")
178 |
179 | /// Six tone symmetrical scale.
180 | public static let sixToneSymmetrical = ScaleType(intervals: [.P1, .m2, .M3, .P4, .m6, .M6], description: "Six Tone Symmetrical")
181 |
182 | /// Super locrian scale.
183 | public static let superLocrian = ScaleType(intervals: [.P1, .m2, .m3, .M3, .d5, .m6, .m7], description: "Super Locrian")
184 |
185 | /// Lydian minor scale.
186 | public static let lydianMinor = ScaleType(intervals: [.P1, .M2, .M3, .d5, .P5, .m6, .m7], description: "Lydian Minor")
187 |
188 | /// Lydian diminished scale.
189 | public static let lydianDiminished = ScaleType(intervals: [.P1, .M2, .m3, .d5, .P5, .m6, .m7], description: "Lydian Diminished")
190 |
191 | /// Nine tone scale.
192 | public static let nineTone = ScaleType(intervals: [.P1, .M2, .m3, .M3, .d5, .P5, .m6, .M6, .M7], description: "Nine Tone")
193 |
194 | /// Auxiliary diminished scale.
195 | public static let auxiliaryDiminished = ScaleType(intervals: [.P1, .M2, .m3, .P4, .d5, .m6, .M6, .M7], description: "Auxiliary Diminished")
196 |
197 | /// Auxiliary augmaneted scale.
198 | public static let auxiliaryAugmented = ScaleType(intervals: [.P1, .M2, .M3, .d5, .m6, .m7], description: "Auxiliary Augmented")
199 |
200 | /// Auxiliary diminished blues scale.
201 | public static let auxiliaryDimBlues = ScaleType(intervals: [.P1, .m2, .m3, .M3, .d5, .P5, .M6, .m7], description: "Auxiliary Diminished Blues")
202 |
203 | /// Major locrian scale.
204 | public static let majorLocrian = ScaleType(intervals: [.P1, .M2, .M3, .P4, .d5, .m6, .m7], description: "Major Locrian")
205 |
206 | /// Overtone scale.
207 | public static let overtone = ScaleType(intervals: [.P1, .M2, .M3, .d5, .P5, .M6, .m7], description: "Overtone")
208 |
209 | /// Diminished whole tone scale.
210 | public static let diminishedWholeTone = ScaleType(intervals: [.P1, .m2, .m3, .M3, .d5, .m6, .m7], description: "Diminished Whole Tone")
211 |
212 | /// Dominant seventh scale.
213 | public static let dominant7th = ScaleType(intervals: [.P1, .M2, .M3, .P4, .P5, .M6, .m7], description: "Dominant 7th")
214 |
215 | /// Altered scale
216 | public static let altered = ScaleType(intervals: [.P1, .m2, .m3, .M3, .d5, .m6, .m7], description: "Altered")
217 |
218 | /// Arabian scale
219 | public static let arabian = ScaleType(intervals: [.P1, .M2, .M3, .P4, .d5, .m6, .m7], description: "Arabian")
220 |
221 | /// Ionian augmented scale
222 | public static let ionianAugmented = ScaleType(intervals: [.P1, .M2, .M3, .P4, .m6, .M6, .M7], description: "Ionian Augmented")
223 |
224 | /// Balinese scale
225 | public static let balinese = ScaleType(intervals: [.P1, .m2, .m3, .P5, .m6], description: "Balinese")
226 |
227 | /// Byzantine scale
228 | public static let byzantine = ScaleType(intervals: [.P1, .m2, .M3, .P4, .P5, .m6, .M7], description: "Byzantine")
229 |
230 | /// Chinese scale
231 | public static let chinese = ScaleType(intervals: [.P1, .M3, .d5, .P5, .M7], description: "Chinese")
232 |
233 | /// Dorian #4 scale
234 | public static let dorianSharp4 = ScaleType(intervals: [.P1, .M2, .m3, .d5, .P5, .M6, .m7], description: "Dorian #4")
235 |
236 | /// Dorian b2 scale
237 | public static let dorianFlat2 = ScaleType(intervals: [.P1, .m2, .m3, .P4, .P5, .M6, .m7], description: "Dorian b2")
238 |
239 | /// Hindu scale
240 | public static let hindu = ScaleType(intervals: [.P1, .M2, .M3, .P4, .P5, .m6, .m7], description: "Hindu")
241 |
242 | /// Hirajoshi scale
243 | public static let hirajoshi = ScaleType(intervals: [.P1, .M2, .m3, .P5, .m6], description: "Hirajoshi")
244 |
245 | /// Hungarian major scale
246 | public static let hungarianMajor = ScaleType(intervals: [.P1, .m3, .M3, .d5, .P5, .M6, .m7], description: "Hungarian Major")
247 |
248 | /// Hungarian minor scale
249 | public static let hungarianMinor = ScaleType(intervals: [.P1, .M2, .m3, .A4, .P5, .m6, .M7], description: "Hungarian Minor")
250 |
251 | /// Ichikosucho scale
252 | public static let ichikosucho = ScaleType(intervals: [.P1, .M2, .M3, .P4, .d5, .P5, .M6, .M7], description: "Ichikosucho")
253 |
254 | /// Kumoi scale
255 | public static let kumoi = ScaleType(intervals: [.P1, .M2, .m3, .P5, .M6], description: "Kumoi")
256 |
257 | /// Locrian 2 scale
258 | public static let locrian2 = ScaleType(intervals: [.P1, .M2, .m3, .P4, .d5, .m6, .m7], description: "Locrian 2")
259 |
260 | /// Locrian 3 scale
261 | public static let locrian3 = ScaleType(intervals: [.P1, .m2, .M3, .P4, .d5, .m6, .m7], description: "Locrian 3")
262 |
263 | /// Locrian 6 scale
264 | public static let locrian6 = ScaleType(intervals: [.P1, .m2, .m3, .P4, .d5, .M6, .m7], description: "Locrian 6")
265 |
266 | /// Lydian #2 scale
267 | public static let lydianSharp2 = ScaleType(intervals: [.P1, .m3, .M3, .d5, .P5, .M6, .M7], description: "Lydian #2")
268 |
269 | /// Lydian b7 scale
270 | public static let lydianFlat7 = ScaleType(intervals: [.P1, .M2, .M3, .d5, .P5, .M6, .m7], description: "Lydian b7")
271 |
272 | /// Phrygian Major scale
273 | public static let phrygianMajor = ScaleType(intervals: [.P1, .m2, .M3, .P4, .P5, .m6, .m7], description: "Phrygian Major")
274 |
275 | /// Mixolydian b6 scale
276 | public static let mixolydianFlat6 = ScaleType(intervals: [.P1, .M2, .M3, .P4, .P5, .m6, .m7], description: "Mixolydian b6")
277 |
278 | /// Mohammedan scale
279 | public static let mohammedan = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .m6, .M7], description: "Mohammedan")
280 |
281 | /// Mongolian scale
282 | public static let mongolian = ScaleType(intervals: [.P1, .M2, .M3, .P5, .M6], description: "Mongolian")
283 |
284 | /// Natural minor scale
285 | public static let naturalMinor = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .m6, .m7], description: "Natural Minor")
286 |
287 | /// Neopolitan scale
288 | public static let neopolitan = ScaleType(intervals: [.P1, .m2, .m3, .P4, .P5, .m6, .M7], description: "Neopolitan")
289 |
290 | /// Persian scale
291 | public static let persian = ScaleType(intervals: [.P1, .m2, .M3, .P4, .d5, .m6, .M7], description: "Persian")
292 |
293 | /// Purvi theta scale
294 | public static let purviTheta = ScaleType(intervals: [.P1, .m2, .M3, .d5, .P5, .m6, .M7], description: "Purvi Theta")
295 |
296 | /// Todi theta scale
297 | public static let todiTheta = ScaleType(intervals: [.P1, .m2, .m3, .d5, .P5, .m6, .M7], description: "Todi Theta")
298 |
299 | /// Major bebop scale
300 | public static let majorBebop = ScaleType(intervals: [.P1, .M2, .M3, .P4, .P5, .m6, .M6, .M7], description: "Major Bebop")
301 |
302 | /// Minor bebop scale
303 | public static let minorBebop = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .M6, .m7, .M7], description: "Minor Bebop")
304 |
305 | /// Bebop dominant scale
306 | public static let bebopDominant = ScaleType(intervals: [.P1, .M2, .M3, .P4, .P5, .M6, .m7, .M7], description: "Bebop Dominant")
307 |
308 | /// Tritone scale
309 | public static let tritone = ScaleType(intervals: [.P1, .m2, .M3, .d5, .P5, .m7], description: "Tritone")
310 |
311 | /// Insen scale
312 | public static let insen = ScaleType(intervals: [.P1, .m2, .P4, .P5, .m7], description: "Insen")
313 |
314 | /// Istrian scale
315 | public static let istrian = ScaleType(intervals: [.P1, .m2, .m3, .d4, .d5, .P5], description: "Istrian")
316 |
317 | /// Gypsy scale
318 | public static let gypsy = ScaleType(intervals: [.P1, .M2, .m3, .A4, .P5, .m6, .m7], description: "Gypsy")
319 |
320 | /// Iwato scale
321 | public static let iwato = ScaleType(intervals: [.P1, .m2, .P4, .d5, .m7], description: "Iwato")
322 |
323 | /// Pfluke scale
324 | public static let pfluke = ScaleType(intervals: [.P1, .M2, .m3, .A4, .P5, .M6, .M7], description: "Pfluke")
325 |
326 | /// Ukrainian dorian scale
327 | public static let ukrainianDorian = ScaleType(intervals: [.P1, .M2, .m3, .A4, .P5, .M6, .m7], description: "Ukrainian Dorian")
328 |
329 | /// Yo scale
330 | public static let yo = ScaleType(intervals: [.P1, .m3, .P4, .P5, .m7], description: "Yo")
331 |
332 | /// Algerian scale
333 | public static let algerian = ScaleType(intervals: [.P1, .M2, .m3, .A4, .P5, .m6, .M7], description: "Algerian")
334 |
335 | /// Flamenco scale
336 | public static let flamenco = ScaleType(intervals: [.P1, .m2, .M3, .P4, .P5, .m6, .M7], description: "Flamenco")
337 |
338 | /// Hawaiian scale
339 | public static let hawaiian = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .M6, .M7], description: "Hawaiian")
340 |
341 | /// Maqam scale
342 | public static let maqam = ScaleType(intervals: [.P1, .m2, .M3, .P4, .P5, .m6, .M7], description: "Maqam")
343 |
344 | /// Oriental scale
345 | public static let oriental = ScaleType(intervals: [.P1, .m2, .M3, .P4, .d5, .M6, .m7], description: "Oriental")
346 |
347 | /// Jazz melodic minor scale
348 | public static let jazzMelodicMinor = ScaleType(intervals: [.P1, .M2, .m3, .P4, .P5, .M6, .M7], description: "Jazz Melodic Minor")
349 |
350 | /// Lydian augmented #6 scale
351 | public static let lydianAugmentedSharp6 = ScaleType(intervals: [.P1, .M2, .M3, .d5, .m6, .m7, .M7], description: "Lydian Augmented #6")
352 |
353 | /// Lydian augmented #2 scale
354 | public static let lydianAugmentedSharp2 = ScaleType(intervals: [.P1, .m3, .M3, .d5, .m6, .M6, .M7], description: "Lydian Augmented #2")
355 |
356 | /// Dorian b5 scale
357 | public static let dorianFlat5 = ScaleType(intervals: [.P1, .M2, .m3, .P4, .d5, .M6, .m7], description: "Dorian b5")
358 |
359 | /// Phrygian b4 scale
360 | public static let phrygianFlat4 = ScaleType(intervals: [.P1, .m2, .m3, .M3, .P5, .m6, .m7], description: "Phrygian b4")
361 |
362 | /// Lydian b3 scale
363 | public static let lydianFlat3 = ScaleType(intervals: [.P1, .M2, .m3, .d5, .P5, .M6, .M7], description: "Lydian b3")
364 |
365 | /// Lydian b6 scale
366 | public static let lydianFlat6 = ScaleType(intervals: [.P1, .M2, .M3, .d5, .P5, .m6, .m7], description: "Lydian b6")
367 |
368 | /// Lydian #6 scale
369 | public static let lydianSharp6 = ScaleType(intervals: [.P1, .M2, .M3, .d5, .P5, .m7, .M7], description: "Lydian #6")
370 |
371 | /// Lydian #2 #6 scale
372 | public static let lydianSharp2Sharp6 = ScaleType(intervals: [.P1, .m3, .M3, .d5, .P5, .m7, .M7], description: "Lydian #2 #6")
373 |
374 | /// Mixolydian b2 scale
375 | public static let mixolydianFlat2 = ScaleType(intervals: [.P1, .m2, .M3, .P4, .P5, .M6, .m7], description: "Mixolydian b2")
376 |
377 | /// Mixolydian augmented scale
378 | public static let mixolydianAugmented = ScaleType(intervals: [.P1, .M2, .M3, .P4, .m6, .M6, .m7], description: "Mixolydian Augmented")
379 |
380 | /// Locrian diminished scale
381 | public static let locrianDiminished = ScaleType(intervals: [.P1, .m2, .m3, .P4, .d5, .m6, .M6], description: "Locrian Diminished")
382 |
383 | /// Locrian diminished bb3 scale
384 | public static let locrianDiminishedFlatFlat3 = ScaleType(intervals: [.P1, .m2, .P4, .d5, .m6, .M6], description: "Locrian Diminished bb3")
385 |
386 | /// Ionian #2 scale
387 | public static let ionianSharp2 = ScaleType(intervals: [.P1, .m3, .M3, .P4, .P5, .M6, .M7], description: "Ionian #2")
388 |
389 | /// Super locrian Diminished bb3 scale
390 | public static let superLocrianDiminshedFlatFlat3 = ScaleType(intervals: [.P1, .m2, .M2, .M3, .d5, .m6, .M6], description: "Super Locrian Diminished bb3")
391 |
392 | /// Ultraphrygian scale
393 | public static let ultraphrygian = ScaleType(intervals: [.P1, .m2, .m3, .M3, .P5, .m6, .M6], description: "Ultraphrygian")
394 |
395 | /// Ionian Augmented #2 scale
396 | public static let ionianAugmentedSharp2 = ScaleType(intervals: [.P1, .m3, .M3, .P4, .m6, .M6, .M7], description: "Ionian Augmented #2")
397 |
398 | /// Major blues hexatonic scale
399 | public static let majorBluesHexatonic = ScaleType(intervals: [.P1, .M2, .m3, .M3, .P5, .M6], description: "Major Blues Hexatonic")
400 |
401 | /// Minor blues hexatonic scale
402 | public static let minorBluesHexatonic = ScaleType(intervals: [.P1, .m3, .P4, .d5, .P5, .m7], description: "Minor Blues Hexatonic")
403 |
404 | /// Man gong scale
405 | public static let manGong = ScaleType(intervals: [.P1, .m3, .P4, .m6, .m7], description: "Man Gong")
406 |
407 | /// Ritsusen scale
408 | public static let ritsusen = ScaleType(intervals: [.P1, .M2, .P4, .P5, .M6], description: "Ritsusen")
409 |
410 | /// An array of all `ScaleType` values.
411 | public static var all: [ScaleType] {
412 | return [
413 | .major,
414 | .minor,
415 | .harmonicMinor,
416 | .melodicMinor,
417 | .naturalMinor,
418 | .ionian,
419 | .ionianSharp2,
420 | .ionianAugmented,
421 | .ionianAugmentedSharp2,
422 | .aeolian,
423 | .dorian,
424 | .dorianSharp4,
425 | .dorianFlat2,
426 | .dorianFlat5,
427 | .mixolydian,
428 | .mixolydianAugmented,
429 | .mixolydianFlat2,
430 | .mixolydianFlat6,
431 | .phrygian,
432 | .phrygianMajor,
433 | .phrygianFlat4,
434 | .ultraphrygian,
435 | .lydian,
436 | .lydianMinor,
437 | .lydianDiminished,
438 | .lydianSharp2,
439 | .lydianSharp6,
440 | .lydianSharp2Sharp6,
441 | .lydianFlat3,
442 | .lydianFlat6,
443 | .lydianFlat7,
444 | .lydianAugmented,
445 | .lydianAugmentedSharp2,
446 | .lydianAugmentedSharp6,
447 | .locrian,
448 | .locrian2,
449 | .locrian3,
450 | .locrian6,
451 | .majorLocrian,
452 | .locrianDiminished,
453 | .locrianDiminishedFlatFlat3,
454 | .superLocrian,
455 | .superLocrianDiminshedFlatFlat3,
456 | .chromatic,
457 | .whole,
458 | .altered,
459 | .augmented,
460 | .dominant7th,
461 | .halfDiminished,
462 | .wholeDiminished,
463 | .leadingWholeTone,
464 | .diminishedWholeTone,
465 | .overtone,
466 | .nineTone,
467 | .diatonic,
468 | .enigmatic,
469 | .doubleHarmonic,
470 | .auxiliaryDiminished,
471 | .auxiliaryAugmented,
472 | .auxiliaryDimBlues,
473 | .sixToneSymmetrical,
474 | .neopolitan,
475 | .neopolitanMajor,
476 | .neopolitanMinor,
477 | .prometheus,
478 | .prometheusNeopolitan,
479 | .pelog,
480 | .pentatonicMajor,
481 | .pentatonicMinor,
482 | .pentatonicBlues,
483 | .pentatonicNeutral,
484 | .majorBluesHexatonic,
485 | .minorBluesHexatonic,
486 | .jazzMelodicMinor,
487 | .spanishGypsy,
488 | .eightToneSpanish,
489 | .hungarianMajor,
490 | .hungarianMinor,
491 | .romanianMinor,
492 | .flamenco,
493 | .gypsy,
494 | .majorBebop,
495 | .minorBebop,
496 | .bebopDominant,
497 | .chinese,
498 | .oriental,
499 | .hirajoshi,
500 | .ichikosucho,
501 | .kumoi,
502 | .yo,
503 | .iwato,
504 | .mongolian,
505 | .hindu,
506 | .byzantine,
507 | .arabian,
508 | .persian,
509 | .mohammedan,
510 | .maqam,
511 | .algerian,
512 | .balinese,
513 | .purviTheta,
514 | .todiTheta,
515 | .tritone,
516 | .insen,
517 | .istrian,
518 | .pfluke,
519 | .ukrainianDorian,
520 | .hawaiian,
521 | .manGong,
522 | .ritsusen
523 | ]
524 | }
525 | }
526 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/Tempo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tempo.swift
3 | // MusicTheory
4 | //
5 | // Created by Cem Olcay on 21.06.2018.
6 | // Copyright © 2018 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | /// Defines the tempo of the music with beats per second and time signature.
14 | public struct Tempo: Codable, Hashable, CustomStringConvertible {
15 | /// Time signature of music.
16 | public var timeSignature: TimeSignature
17 | /// Beats per minutes.
18 | public var bpm: Double
19 |
20 | /// Initilizes tempo with time signature and BPM.
21 | ///
22 | /// - Parameters:
23 | /// - timeSignature: Time Signature.
24 | /// - bpm: Beats per minute.
25 | public init(timeSignature: TimeSignature = TimeSignature(), bpm: Double = 120.0) {
26 | self.timeSignature = timeSignature
27 | self.bpm = bpm
28 | }
29 |
30 | /// Caluclates the duration of a note value in seconds.
31 | public func duration(of noteValue: NoteValue) -> TimeInterval {
32 | let secondsPerBeat = 60.0 / bpm
33 | return secondsPerBeat * (timeSignature.noteValue.rate / noteValue.type.rate) * noteValue.modifier.rawValue
34 | }
35 |
36 | /// Calculates the note length in samples. Useful for sequencing notes sample accurate in the DSP.
37 | ///
38 | /// - Parameters:
39 | /// - noteValue: Rate of the note you want to calculate sample length.
40 | /// - sampleRate: Number of samples in a second. Defaults to 44100.
41 | /// - Returns: Returns the sample length of a note value.
42 | public func sampleLength(of noteValue: NoteValue, sampleRate: Double = 44100.0) -> Double {
43 | let secondsPerBeat = 60.0 / bpm
44 | return secondsPerBeat * sampleRate * ((Double(timeSignature.beats) * noteValue.type.rate) * noteValue.modifier.rawValue)
45 | }
46 |
47 | /// Calculates the LFO speed of a note vaule in hertz.
48 | public func hertz(of noteValue: NoteValue) -> Double {
49 | return 1 / duration(of: noteValue)
50 | }
51 |
52 | // MARK: Equatable
53 |
54 | /// Compares two Tempo instances and returns if they are identical.
55 | /// - Parameters:
56 | /// - lhs: Left hand side of the equation.
57 | /// - rhs: Right hand side of the equation.
58 | /// - Returns: Returns true if two instances are identical.
59 | public static func == (lhs: Tempo, rhs: Tempo) -> Bool {
60 | return lhs.hashValue == rhs.hashValue
61 | }
62 |
63 | // MARK: CustomStringConvertible
64 |
65 | public var description: String {
66 | return "\(bpm)"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/MusicTheory/TimeSignature.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimeSignature.swift
3 | // MusicTheory iOS
4 | //
5 | // Created by Cem Olcay on 21.06.2018.
6 | // Copyright © 2018 cemolcay. All rights reserved.
7 | //
8 | // https://github.com/cemolcay/MusicTheory
9 | //
10 |
11 | import Foundation
12 |
13 | /// Defines how many beats in a measure with which note value.
14 | public struct TimeSignature: Codable, Hashable, CustomStringConvertible {
15 | /// Beats per measure.
16 | public var beats: Int
17 | /// Note value per beat.
18 | public var noteValue: NoteValueType
19 |
20 | /// Initilizes the time signature with beats per measure and the value of the notes in beat.
21 | ///
22 | /// - Parameters:
23 | /// - beats: Number of beats in a measure
24 | /// - noteValue: Note value of the beats.
25 | public init(beats: Int = 4, noteValue: NoteValueType = .quarter) {
26 | self.beats = beats
27 | self.noteValue = noteValue
28 | }
29 |
30 | // MARK: CustomStringConvertible
31 |
32 | public var description: String {
33 | return "\(beats)/\(Int(noteValue.rate))"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Tests/MusicTheoryTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Tests/MusicTheoryTests/MusicTheoryTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MusicTheoryTests.swift
3 | // MusicTheoryTests
4 | //
5 | // Created by Cem Olcay on 30/12/2016.
6 | // Copyright © 2016 prototapp. All rights reserved.
7 | //
8 |
9 | @testable import MusicTheory
10 | import XCTest
11 |
12 | class MusicTheoryTests: XCTestCase {
13 | override func setUp() {
14 | super.setUp()
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | super.tearDown()
21 | }
22 | }
23 |
24 | // MARK: - Note Tests
25 |
26 | extension MusicTheoryTests {
27 | func testIntervals() {
28 | let key = Key(type: .c)
29 | let pitch = Pitch(key: key, octave: 1)
30 | XCTAssert((pitch + 12).octave == pitch.octave + 1)
31 | XCTAssert((pitch + 1).key == Key(type: .c, accidental: .sharp))
32 | XCTAssert((pitch - 1) == Pitch(key: Key(type: .b), octave: 0))
33 |
34 | let c1 = Pitch(key: Key(type: .c), octave: 1)
35 | let d1 = Pitch(key: Key(type: .d), octave: 1)
36 | XCTAssert(d1 - c1 == .M2)
37 | }
38 |
39 | func testAccidentals() {
40 | XCTAssert(Accidental.flat * 2 == Accidental.doubleFlat)
41 | XCTAssert(Accidental.doubleFlat / 2 == Accidental.flat)
42 | XCTAssert(Accidental.sharps(amount: 2) - 2 == Accidental.natural)
43 | XCTAssert(Accidental.flats(amount: 2) + 2 == 0)
44 | XCTAssert(Accidental.sharps(amount: 2) + Accidental.sharps(amount: 1) == Accidental.sharps(amount: 3))
45 | XCTAssert(Accidental(integerLiteral: -3) + Accidental(rawValue: 3)! == 0)
46 | }
47 |
48 | func testKeys() {
49 | let d = Key.KeyType.d
50 | XCTAssert(d.key(at: -2) == .b)
51 | XCTAssert(d.key(at: -19) == .f)
52 | XCTAssert(d.key(at: 12) == .b)
53 | XCTAssert(d.key(at: 0) == .d)
54 | XCTAssert(d.key(at: 1) == .e)
55 | XCTAssert(d.key(at: 2) == .f)
56 | XCTAssert(d.key(at: -3) == .a)
57 | XCTAssert(d.key(at: -301) == .d)
58 |
59 | let f = Key.KeyType.f
60 | XCTAssert(f.key(at: -3) == .c)
61 |
62 | let k: Key = "a##b"
63 | XCTAssert(k.accidental == .sharp && k.type == .a)
64 |
65 | let b = Key(type: .b, accidental: .natural)
66 | XCTAssert(Key(type: .c, accidental: .flat) == b)
67 | XCTAssert(Key(type: .c, accidental: .sharps(amount: 23)) == b)
68 | XCTAssert(Key(type: .c, accidental: .flats(amount: 13)) == b)
69 | XCTAssert(Key(type: .c, accidental: .flats(amount: 25)) == b)
70 | XCTAssert(Key(type: .c, accidental: .flats(amount: 24)) != b)
71 | }
72 |
73 | func testPitches() {
74 | let c0: Pitch = 12
75 | XCTAssert(c0.octave == 0 && c0.key.accidental == .natural && c0.key.type == .c)
76 | XCTAssert(c0 - 12 == 0)
77 |
78 | var pitch = Pitch(midiNote: 127)
79 | XCTAssert(pitch.key == Key(type: .g))
80 | pitch = Pitch(midiNote: 0)
81 | XCTAssert(pitch.key == Key(type: .c))
82 | pitch = Pitch(midiNote: 66, preferSharps: false)
83 | XCTAssert(pitch.key == Key(type: .g, accidental: .flat))
84 |
85 | let c1 = Pitch(key: Key(type: .c), octave: 1)
86 | XCTAssert(c1 + .m2 == Pitch(key: Key(type: .d, accidental: .flat), octave: 1))
87 | XCTAssert(c1 + .M2 == Pitch(key: Key(type: .d, accidental: .natural), octave: 1))
88 | XCTAssert(c1 + .m3 == Pitch(key: Key(type: .e, accidental: .flat), octave: 1))
89 | XCTAssert(c1 + .M3 == Pitch(key: Key(type: .e, accidental: .natural), octave: 1))
90 | XCTAssert(c1 + .P8 == Pitch(key: Key(type: .c, accidental: .natural), octave: 2))
91 |
92 | let d1 = Pitch(key: Key(type: .d), octave: 1)
93 | XCTAssert(d1 - .m2 == Pitch(key: Key(type: .c, accidental: .sharp), octave: 1))
94 | XCTAssert(d1 - .M2 == Pitch(key: Key(type: .c, accidental: .natural), octave: 1))
95 |
96 | let p: Pitch = "f#-5"
97 | XCTAssert(p.key === Key(type: .f, accidental: .sharp))
98 | XCTAssert(p.octave == -5)
99 |
100 | let uppercasePitch: Pitch = "A#3"
101 | XCTAssert(uppercasePitch.key === Key(type: .a, accidental: .sharp))
102 | XCTAssert(uppercasePitch.octave == 3)
103 |
104 | let uppercasePitch2: Pitch = "F4"
105 | XCTAssert(uppercasePitch2.key === Key(type: .f, accidental: .natural))
106 | XCTAssert(uppercasePitch2.octave == 4)
107 | }
108 |
109 | func testFrequency() {
110 | let note = Pitch(key: Key(type: .a), octave: 4)
111 | XCTAssertEqual(note.frequency, 440.0)
112 |
113 | let a4 = Pitch.nearest(frequency: 440.0)
114 | XCTAssertEqual(note, a4)
115 | }
116 | }
117 |
118 | // MARK: - Tempo Tests
119 |
120 | extension MusicTheoryTests {
121 | func testNoteValueConversions() {
122 | var noteValue = NoteValue(type: .half, modifier: .dotted)
123 | XCTAssertEqual(noteValue / NoteValueType.sixteenth, 12)
124 | XCTAssertEqual(noteValue / NoteValueType.whole, 0.75)
125 | noteValue = NoteValue(type: .half, modifier: .default)
126 | XCTAssertEqual(noteValue / NoteValueType.sixteenth, 8)
127 | XCTAssertEqual(noteValue / NoteValueType.whole, 0.5)
128 | XCTAssertEqual(noteValue / NoteValueType.quarter, 2)
129 | XCTAssertEqual(noteValue / NoteValueType.thirtysecond, 16)
130 | }
131 |
132 | func testDurations() {
133 | let timeSignature = TimeSignature(beats: 4, noteValue: .quarter) // 4/4
134 | let tempo = Tempo(timeSignature: timeSignature, bpm: 120) // 120BPM
135 | var noteValue = NoteValue(type: .quarter)
136 | var duration = tempo.duration(of: noteValue)
137 | XCTAssert(duration == 0.5)
138 |
139 | noteValue.modifier = .dotted
140 | duration = tempo.duration(of: noteValue)
141 | XCTAssert(duration == 0.75)
142 | }
143 |
144 | func testSampleLengthCalcuation() {
145 | let rates = [
146 | NoteValue(type: .whole, modifier: .default),
147 | NoteValue(type: .half, modifier: .default),
148 | NoteValue(type: .half, modifier: .dotted),
149 | NoteValue(type: .half, modifier: .triplet),
150 | NoteValue(type: .quarter, modifier: .default),
151 | NoteValue(type: .quarter, modifier: .dotted),
152 | NoteValue(type: .quarter, modifier: .triplet),
153 | NoteValue(type: .eighth, modifier: .default),
154 | NoteValue(type: .eighth, modifier: .dotted),
155 | NoteValue(type: .sixteenth, modifier: .default),
156 | NoteValue(type: .sixteenth, modifier: .dotted),
157 | NoteValue(type: .thirtysecond, modifier: .default),
158 | NoteValue(type: .sixtyfourth, modifier: .default),
159 | ]
160 |
161 | let tempo = Tempo()
162 | let sampleLengths = rates
163 | .map({ tempo.sampleLength(of: $0) })
164 | .map({ round(100 * $0) / 100 })
165 |
166 | let expected: [Double] = [
167 | 88200.0,
168 | 44100.0,
169 | 66150.0,
170 | 29401.47,
171 | 22050.0,
172 | 33075.0,
173 | 14700.73,
174 | 11025.0,
175 | 16537.5,
176 | 5512.5,
177 | 8268.75,
178 | 2756.25,
179 | 1378.13,
180 | ]
181 |
182 | XCTAssertEqual(sampleLengths, expected)
183 | }
184 |
185 | func testTempoHashable() {
186 | let t1 = Tempo(timeSignature: TimeSignature(beats: 1, noteValue: .whole), bpm: 1)
187 | var t2 = Tempo(timeSignature: TimeSignature(beats: 2, noteValue: .half), bpm: 2)
188 | XCTAssertNotEqual(t1.timeSignature.noteValue, t2.timeSignature.noteValue)
189 | XCTAssertNotEqual(t1.timeSignature, t2.timeSignature)
190 | XCTAssertNotEqual(t1, t2)
191 |
192 | t2.timeSignature = TimeSignature(beats: 1, noteValue: .whole)
193 | t2.bpm = 1
194 | XCTAssertEqual(t1.timeSignature.noteValue, t2.timeSignature.noteValue)
195 | XCTAssertEqual(t1.timeSignature, t2.timeSignature)
196 | XCTAssertEqual(t1, t2)
197 | }
198 | }
199 |
200 | // MARK: - Scale Tests
201 |
202 | extension MusicTheoryTests {
203 | func testScale() {
204 | let cMaj: [Key] = [
205 | Key(type: .c),
206 | Key(type: .d),
207 | Key(type: .e),
208 | Key(type: .f),
209 | Key(type: .g),
210 | Key(type: .a),
211 | Key(type: .b),
212 | ]
213 |
214 | let cMajScale = Scale(type: .major, key: Key(type: .c))
215 | XCTAssert(cMajScale.keys == cMaj)
216 |
217 | let cMin: [Key] = [
218 | Key(type: .c),
219 | Key(type: .d),
220 | Key(type: .e, accidental: .flat),
221 | Key(type: .f),
222 | Key(type: .g),
223 | Key(type: .a, accidental: .flat),
224 | Key(type: .b, accidental: .flat),
225 | ]
226 |
227 | let cMinScale = Scale(type: .minor, key: Key(type: .c))
228 | XCTAssert(cMinScale.keys == cMin)
229 | }
230 |
231 | func testHarmonicFields() {
232 | let cmaj = Scale(type: .major, key: Key(type: .c))
233 | let triads = cmaj.harmonicField(for: .triad)
234 | let triadsExpected = [
235 | Chord(type: ChordType(third: .major), key: Key(type: .c)),
236 | Chord(type: ChordType(third: .minor), key: Key(type: .d)),
237 | Chord(type: ChordType(third: .minor), key: Key(type: .e)),
238 | Chord(type: ChordType(third: .major), key: Key(type: .f)),
239 | Chord(type: ChordType(third: .major), key: Key(type: .g)),
240 | Chord(type: ChordType(third: .minor), key: Key(type: .a)),
241 | Chord(type: ChordType(third: .minor, fifth: .diminished), key: Key(type: .b)),
242 | ]
243 | XCTAssert(triads.enumerated().map({ $1 == triadsExpected[$0] }).filter({ $0 == false }).count == 0)
244 | }
245 | }
246 |
247 | // MARK: - Chord Tests
248 |
249 | extension MusicTheoryTests {
250 | func testChords() {
251 | let cmajNotes: [Key] = [Key(type: .c), Key(type: .e), Key(type: .g)]
252 | let cmaj = Chord(type: ChordType(third: .major), key: Key(type: .c))
253 | XCTAssert(cmajNotes == cmaj.keys)
254 |
255 | let cminNotes: [Key] = [
256 | Key(type: .c),
257 | Key(type: .e, accidental: .flat),
258 | Key(type: .g),
259 | ]
260 | let cmin = Chord(type: ChordType(third: .minor), key: Key(type: .c))
261 | XCTAssert(cminNotes == cmin.keys)
262 |
263 | let c13Notes: [Pitch] = [
264 | Pitch(key: Key(type: .c), octave: 1),
265 | Pitch(key: Key(type: .e), octave: 1),
266 | Pitch(key: Key(type: .g), octave: 1),
267 | Pitch(key: Key(type: .b, accidental: .flat), octave: 1),
268 | Pitch(key: Key(type: .d), octave: 2),
269 | Pitch(key: Key(type: .f), octave: 2),
270 | Pitch(key: Key(type: .a), octave: 2),
271 | ]
272 | let c13 = Chord(
273 | type: ChordType(
274 | third: .major,
275 | seventh: .dominant,
276 | extensions: [
277 | ChordExtensionType(type: .thirteenth),
278 | ]
279 | ),
280 | key: Key(type: .c)
281 | )
282 | XCTAssert(c13.pitches(octave: 1) === c13Notes)
283 |
284 | let cm13Notes: [Pitch] = [
285 | Pitch(key: Key(type: .c), octave: 1),
286 | Pitch(key: Key(type: .e, accidental: .flat), octave: 1),
287 | Pitch(key: Key(type: .g), octave: 1),
288 | Pitch(key: Key(type: .b, accidental: .flat), octave: 1),
289 | Pitch(key: Key(type: .d), octave: 2),
290 | Pitch(key: Key(type: .f), octave: 2),
291 | Pitch(key: Key(type: .a), octave: 2),
292 | ]
293 | let cm13 = Chord(
294 | type: ChordType(
295 | third: .minor,
296 | seventh: .dominant,
297 | extensions: [
298 | ChordExtensionType(type: .thirteenth),
299 | ]
300 | ),
301 | key: Key(type: .c)
302 | )
303 | XCTAssert(cm13.pitches(octave: 1) === cm13Notes)
304 |
305 | let minorIntervals: [Interval] = [.P1, .m3, .P5]
306 | guard let minorChord = ChordType(intervals: minorIntervals.map({ $0 })) else { return XCTFail() }
307 | XCTAssert(minorChord == ChordType(third: .minor))
308 |
309 | let majorIntervals: [Interval] = [.P1, .M3, .P5]
310 | guard let majorChord = ChordType(intervals: majorIntervals.map({ $0 })) else { return XCTFail() }
311 | XCTAssert(majorChord == ChordType(third: .major))
312 |
313 | let cmadd13Notes: [Pitch] = [
314 | Pitch(key: Key(type: .c), octave: 1),
315 | Pitch(key: Key(type: .e, accidental: .flat), octave: 1),
316 | Pitch(key: Key(type: .g), octave: 1),
317 | Pitch(key: Key(type: .a), octave: 2),
318 | ]
319 | let cmadd13 = Chord(
320 | type: ChordType(
321 | third: .minor,
322 | extensions: [ChordExtensionType(type: .thirteenth)]
323 | ),
324 | key: Key(type: .c)
325 | )
326 | XCTAssert(cmadd13.pitches(octave: 1) === cmadd13Notes)
327 | }
328 |
329 | func testRomanNumerics() {
330 | let cmaj = Scale(type: .major, key: Key(type: .c))
331 | let cmin = Scale(type: .minor, key: Key(type: .c))
332 | let cmajNumerics = ["I", "ii", "iii", "IV", "V", "vi", "vii°"]
333 | let cminNumerics = ["i", "ii°", "III", "iv", "v", "VI", "VII"]
334 | let cmajChords = cmaj.harmonicField(for: .triad)
335 | let cminChords = cmin.harmonicField(for: .triad)
336 | XCTAssertEqual(cmajNumerics, cmajChords.compactMap({ $0?.romanNumeric(for: cmaj) }))
337 | XCTAssertEqual(cminNumerics, cminChords.compactMap({ $0?.romanNumeric(for: cmin) }))
338 | }
339 |
340 | func testInversions() {
341 | let c7 = Chord(
342 | type: ChordType(third: .major, seventh: .dominant),
343 | key: Key(type: .c)
344 | )
345 | let c7Inversions = [
346 | [
347 | Pitch(key: Key(type: .c), octave: 1),
348 | Pitch(key: Key(type: .e), octave: 1),
349 | Pitch(key: Key(type: .g), octave: 1),
350 | Pitch(key: Key(type: .b, accidental: .flat), octave: 1),
351 | ],
352 | [
353 | Pitch(key: Key(type: .e), octave: 1),
354 | Pitch(key: Key(type: .g), octave: 1),
355 | Pitch(key: Key(type: .b, accidental: .flat), octave: 1),
356 | Pitch(key: Key(type: .c), octave: 2),
357 | ],
358 | [
359 | Pitch(key: Key(type: .g), octave: 1),
360 | Pitch(key: Key(type: .b, accidental: .flat), octave: 1),
361 | Pitch(key: Key(type: .c), octave: 2),
362 | Pitch(key: Key(type: .e), octave: 2),
363 | ],
364 | [
365 | Pitch(key: Key(type: .b, accidental: .flat), octave: 1),
366 | Pitch(key: Key(type: .c), octave: 2),
367 | Pitch(key: Key(type: .e), octave: 2),
368 | Pitch(key: Key(type: .g), octave: 2),
369 | ],
370 | ]
371 | for (index, chord) in c7.inversions.enumerated() {
372 | XCTAssert(chord.pitches(octave: 1) === c7Inversions[index])
373 | }
374 | }
375 | }
376 |
377 | // MARK: - [Pitch] Extension
378 |
379 | // A function for checking pitche arrays exactly equal in terms of their pitches keys and octaves.
380 | func === (lhs: [Pitch], rhs: [Pitch]) -> Bool {
381 | guard lhs.count == rhs.count else { return false }
382 | for i in 0 ..< lhs.count {
383 | if lhs[i] === rhs[i] {
384 | continue
385 | } else {
386 | return false
387 | }
388 | }
389 | return true
390 | }
391 |
--------------------------------------------------------------------------------
/Tests/MusicTheoryTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(MusicTheoryTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------