├── .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 [![Build Status](https://travis-ci.org/cemolcay/MusicTheory.svg?branch=master)](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 | --------------------------------------------------------------------------------