├── .DS_Store ├── .gitignore ├── Jamulator.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── 2016imac.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── 2016imac.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── Jamulator.xcscheme │ └── xcschememanagement.plist ├── Jamulator ├── .DS_Store ├── AppDelegate.swift ├── Assets.xcassets │ ├── .DS_Store │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-57x57@1x.png │ │ ├── Icon-App-57x57@2x.png │ │ ├── Icon-App-60x60@1x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-72x72@1x.png │ │ ├── Icon-App-72x72@2x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ ├── Icon-App-76x76@3x.png │ │ ├── Icon-App-83.5x83.5@2x.png │ │ ├── Icon-Small-50x50@1x.png │ │ └── Icon-Small-50x50@2x.png │ ├── Contents.json │ ├── rw-logo.imageset │ │ ├── Contents.json │ │ └── Razewarelogo_1024.png │ └── rwdevcon-bg.imageset │ │ ├── Contents.json │ │ └── rwdevcon-bg.png ├── AudioCommon.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CertificateSigningRequest.certSigningRequest ├── Info.plist ├── MIDIInstrumentViewController.swift ├── ScreenUtils.swift ├── Sequencer.swift ├── SoftSynth.swift ├── SoundError.swift ├── VoiceCategoryButton.swift ├── VoiceSelectorButton.swift └── VoiceSelectorView.swift └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | AudioKit.framework/ 2 | FluidR3_GM.sf2 3 | 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | -------------------------------------------------------------------------------- /Jamulator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3616ECE11F61E9A300906228 /* AudioCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3616ECE01F61E9A300906228 /* AudioCommon.swift */; }; 11 | 3616ECE31F61EA0B00906228 /* Sequencer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3616ECE21F61EA0B00906228 /* Sequencer.swift */; }; 12 | 3621D2541F2FA19A00E07518 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3621D2531F2FA19A00E07518 /* AppDelegate.swift */; }; 13 | 3621D2561F2FA19A00E07518 /* MIDIInstrumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3621D2551F2FA19A00E07518 /* MIDIInstrumentViewController.swift */; }; 14 | 3621D2591F2FA19A00E07518 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3621D2571F2FA19A00E07518 /* Main.storyboard */; }; 15 | 3621D25B1F2FA19A00E07518 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3621D25A1F2FA19A00E07518 /* Assets.xcassets */; }; 16 | 3621D25E1F2FA19A00E07518 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3621D25C1F2FA19A00E07518 /* LaunchScreen.storyboard */; }; 17 | 3621D2681F2FB11900E07518 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3621D2661F2FB11900E07518 /* CoreAudio.framework */; }; 18 | 3621D2691F2FB11900E07518 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3621D2671F2FB11900E07518 /* CoreMIDI.framework */; }; 19 | 3621D2991F2FEFEE00E07518 /* SoundError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3621D2981F2FEFEE00E07518 /* SoundError.swift */; }; 20 | 3621D29E1F31431900E07518 /* VoiceSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3621D29D1F31431900E07518 /* VoiceSelectorView.swift */; }; 21 | 3621D2A71F33FB4E00E07518 /* SoftSynth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3621D2A61F33FB4E00E07518 /* SoftSynth.swift */; }; 22 | 363B17911F5781A30067AB52 /* FluidR3_GM.sf2 in Resources */ = {isa = PBXBuildFile; fileRef = 363B17901F5781A30067AB52 /* FluidR3_GM.sf2 */; }; 23 | 369035641F3E755A00E72EDB /* VoiceSelectorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 369035631F3E755A00E72EDB /* VoiceSelectorButton.swift */; }; 24 | 369035671F3FBF1000E72EDB /* VoiceCategoryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 369035661F3FBF1000E72EDB /* VoiceCategoryButton.swift */; }; 25 | 3690358D1F43A77200E72EDB /* ScreenUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3690358C1F43A77000E72EDB /* ScreenUtils.swift */; }; 26 | 36DA47311F50C6A300AE58EE /* AudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36DA47301F50C6A300AE58EE /* AudioKit.framework */; }; 27 | 36DA47321F50C6A300AE58EE /* AudioKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 36DA47301F50C6A300AE58EE /* AudioKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXCopyFilesBuildPhase section */ 31 | 369035711F4228BF00E72EDB /* Embed Frameworks */ = { 32 | isa = PBXCopyFilesBuildPhase; 33 | buildActionMask = 2147483647; 34 | dstPath = ""; 35 | dstSubfolderSpec = 10; 36 | files = ( 37 | 36DA47321F50C6A300AE58EE /* AudioKit.framework in Embed Frameworks */, 38 | ); 39 | name = "Embed Frameworks"; 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | 369035721F422EFF00E72EDB /* CopyFiles */ = { 43 | isa = PBXCopyFilesBuildPhase; 44 | buildActionMask = 2147483647; 45 | dstPath = ""; 46 | dstSubfolderSpec = 10; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXCopyFilesBuildPhase section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | 3616ECE01F61E9A300906228 /* AudioCommon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioCommon.swift; sourceTree = ""; }; 55 | 3616ECE21F61EA0B00906228 /* Sequencer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequencer.swift; sourceTree = ""; }; 56 | 3621D2501F2FA19A00E07518 /* Jamulator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Jamulator.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 3621D2531F2FA19A00E07518 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 58 | 3621D2551F2FA19A00E07518 /* MIDIInstrumentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MIDIInstrumentViewController.swift; sourceTree = ""; }; 59 | 3621D2581F2FA19A00E07518 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 60 | 3621D25A1F2FA19A00E07518 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 3621D25D1F2FA19A00E07518 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | 3621D25F1F2FA19A00E07518 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 3621D2661F2FB11900E07518 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; 64 | 3621D2671F2FB11900E07518 /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; }; 65 | 3621D2981F2FEFEE00E07518 /* SoundError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SoundError.swift; sourceTree = ""; }; 66 | 3621D29D1F31431900E07518 /* VoiceSelectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoiceSelectorView.swift; sourceTree = ""; }; 67 | 3621D2A61F33FB4E00E07518 /* SoftSynth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SoftSynth.swift; sourceTree = ""; }; 68 | 363B17901F5781A30067AB52 /* FluidR3_GM.sf2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = FluidR3_GM.sf2; sourceTree = ""; }; 69 | 369035631F3E755A00E72EDB /* VoiceSelectorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceSelectorButton.swift; sourceTree = ""; }; 70 | 369035661F3FBF1000E72EDB /* VoiceCategoryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceCategoryButton.swift; sourceTree = ""; }; 71 | 3690358C1F43A77000E72EDB /* ScreenUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenUtils.swift; sourceTree = ""; }; 72 | 36DA47301F50C6A300AE58EE /* AudioKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AudioKit.framework; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | 3621D24D1F2FA19A00E07518 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | 36DA47311F50C6A300AE58EE /* AudioKit.framework in Frameworks */, 81 | 3621D2681F2FB11900E07518 /* CoreAudio.framework in Frameworks */, 82 | 3621D2691F2FB11900E07518 /* CoreMIDI.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 3621D2471F2FA19A00E07518 = { 90 | isa = PBXGroup; 91 | children = ( 92 | 363B17901F5781A30067AB52 /* FluidR3_GM.sf2 */, 93 | 36DA47301F50C6A300AE58EE /* AudioKit.framework */, 94 | 3621D29A1F3005CE00E07518 /* Media */, 95 | 3621D2521F2FA19A00E07518 /* Jamulator */, 96 | 3621D2511F2FA19A00E07518 /* Products */, 97 | 3621D2651F2FB11900E07518 /* Frameworks */, 98 | ); 99 | sourceTree = ""; 100 | }; 101 | 3621D2511F2FA19A00E07518 /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 3621D2501F2FA19A00E07518 /* Jamulator.app */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 3621D2521F2FA19A00E07518 /* Jamulator */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 3621D2531F2FA19A00E07518 /* AppDelegate.swift */, 113 | 3621D2551F2FA19A00E07518 /* MIDIInstrumentViewController.swift */, 114 | 3621D2571F2FA19A00E07518 /* Main.storyboard */, 115 | 3621D25A1F2FA19A00E07518 /* Assets.xcassets */, 116 | 3621D25C1F2FA19A00E07518 /* LaunchScreen.storyboard */, 117 | 3621D25F1F2FA19A00E07518 /* Info.plist */, 118 | 3621D2981F2FEFEE00E07518 /* SoundError.swift */, 119 | 3621D2A61F33FB4E00E07518 /* SoftSynth.swift */, 120 | 3621D29D1F31431900E07518 /* VoiceSelectorView.swift */, 121 | 369035661F3FBF1000E72EDB /* VoiceCategoryButton.swift */, 122 | 369035631F3E755A00E72EDB /* VoiceSelectorButton.swift */, 123 | 3690358C1F43A77000E72EDB /* ScreenUtils.swift */, 124 | 3616ECE01F61E9A300906228 /* AudioCommon.swift */, 125 | 3616ECE21F61EA0B00906228 /* Sequencer.swift */, 126 | ); 127 | path = Jamulator; 128 | sourceTree = ""; 129 | }; 130 | 3621D2651F2FB11900E07518 /* Frameworks */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 3621D2661F2FB11900E07518 /* CoreAudio.framework */, 134 | 3621D2671F2FB11900E07518 /* CoreMIDI.framework */, 135 | ); 136 | name = Frameworks; 137 | sourceTree = ""; 138 | }; 139 | 3621D29A1F3005CE00E07518 /* Media */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | ); 143 | name = Media; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 3621D24F1F2FA19A00E07518 /* Jamulator */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 3621D2621F2FA19A00E07518 /* Build configuration list for PBXNativeTarget "Jamulator" */; 152 | buildPhases = ( 153 | D16D24A989D687A085FC30C9 /* [CP] Check Pods Manifest.lock */, 154 | 3621D24C1F2FA19A00E07518 /* Sources */, 155 | 3621D24D1F2FA19A00E07518 /* Frameworks */, 156 | 3621D24E1F2FA19A00E07518 /* Resources */, 157 | F2ECC297598EEE84FF7CDA60 /* [CP] Embed Pods Frameworks */, 158 | 008D1C48F3BD33382B4627E0 /* [CP] Copy Pods Resources */, 159 | 369035711F4228BF00E72EDB /* Embed Frameworks */, 160 | 369035721F422EFF00E72EDB /* CopyFiles */, 161 | 369035771F4234DE00E72EDB /* ShellScript */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | ); 167 | name = Jamulator; 168 | productName = Jamulator; 169 | productReference = 3621D2501F2FA19A00E07518 /* Jamulator.app */; 170 | productType = "com.apple.product-type.application"; 171 | }; 172 | /* End PBXNativeTarget section */ 173 | 174 | /* Begin PBXProject section */ 175 | 3621D2481F2FA19A00E07518 /* Project object */ = { 176 | isa = PBXProject; 177 | attributes = { 178 | LastSwiftUpdateCheck = 0830; 179 | LastUpgradeCheck = 0900; 180 | ORGANIZATIONNAME = "Eric Ford Consulting"; 181 | TargetAttributes = { 182 | 3621D24F1F2FA19A00E07518 = { 183 | CreatedOnToolsVersion = 8.3.3; 184 | DevelopmentTeam = 7RKB3MB466; 185 | LastSwiftMigration = 0900; 186 | ProvisioningStyle = Automatic; 187 | SystemCapabilities = { 188 | com.apple.BackgroundModes = { 189 | enabled = 0; 190 | }; 191 | com.apple.InterAppAudio = { 192 | enabled = 0; 193 | }; 194 | }; 195 | }; 196 | }; 197 | }; 198 | buildConfigurationList = 3621D24B1F2FA19A00E07518 /* Build configuration list for PBXProject "Jamulator" */; 199 | compatibilityVersion = "Xcode 3.2"; 200 | developmentRegion = English; 201 | hasScannedForEncodings = 0; 202 | knownRegions = ( 203 | en, 204 | Base, 205 | ); 206 | mainGroup = 3621D2471F2FA19A00E07518; 207 | productRefGroup = 3621D2511F2FA19A00E07518 /* Products */; 208 | projectDirPath = ""; 209 | projectRoot = ""; 210 | targets = ( 211 | 3621D24F1F2FA19A00E07518 /* Jamulator */, 212 | ); 213 | }; 214 | /* End PBXProject section */ 215 | 216 | /* Begin PBXResourcesBuildPhase section */ 217 | 3621D24E1F2FA19A00E07518 /* Resources */ = { 218 | isa = PBXResourcesBuildPhase; 219 | buildActionMask = 2147483647; 220 | files = ( 221 | 363B17911F5781A30067AB52 /* FluidR3_GM.sf2 in Resources */, 222 | 3621D25E1F2FA19A00E07518 /* LaunchScreen.storyboard in Resources */, 223 | 3621D25B1F2FA19A00E07518 /* Assets.xcassets in Resources */, 224 | 3621D2591F2FA19A00E07518 /* Main.storyboard in Resources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXResourcesBuildPhase section */ 229 | 230 | /* Begin PBXShellScriptBuildPhase section */ 231 | 008D1C48F3BD33382B4627E0 /* [CP] Copy Pods Resources */ = { 232 | isa = PBXShellScriptBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | ); 236 | inputPaths = ( 237 | ); 238 | name = "[CP] Copy Pods Resources"; 239 | outputPaths = ( 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | shellPath = /bin/sh; 243 | shellScript = " 244 | "; 245 | showEnvVarsInLog = 0; 246 | }; 247 | 369035771F4234DE00E72EDB /* ShellScript */ = { 248 | isa = PBXShellScriptBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | ); 252 | inputPaths = ( 253 | ); 254 | outputPaths = ( 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | shellPath = /bin/sh; 258 | shellScript = "cd \"$BUILT_PRODUCTS_DIR/$FRAMEWORKS_FOLDER_PATH\"\n\nframework=./AudioKit.framework\nbinary=\"$framework/AudioKit\"\n\nif [ \"$ACTION\" == \"install\" ]; then\nfind \"$framework/BCSymbolMaps\" -name \\*.bcsymbolmap -type f -exec mv {} \"$CONFIGURATION_BUILD_DIR\" \\;\n\nBINARY_INFO=`lipo -info \"$binary\"`\n\nif echo $BINARY_INFO | fgrep i386 > /dev/null; then\n# Binary has i386\nlipo -remove i386 -output \"$binary\" \"$binary\"\nfi\n\nif echo $BINARY_INFO | fgrep x86_64 > /dev/null; then\n# Binary has x86_64\nlipo -remove x86_64 -output \"$binary\" \"$binary\"\nfi\n\nrm -f \"$framework/fix-framework.sh\"\nfi\n\nrm -rf \"$framework/BCSymbolMaps\""; 259 | }; 260 | D16D24A989D687A085FC30C9 /* [CP] Check Pods Manifest.lock */ = { 261 | isa = PBXShellScriptBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | ); 265 | inputPaths = ( 266 | ); 267 | name = "[CP] Check Pods Manifest.lock"; 268 | outputPaths = ( 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | shellPath = /bin/sh; 272 | shellScript = ""; 273 | showEnvVarsInLog = 0; 274 | }; 275 | F2ECC297598EEE84FF7CDA60 /* [CP] Embed Pods Frameworks */ = { 276 | isa = PBXShellScriptBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | ); 280 | inputPaths = ( 281 | ); 282 | name = "[CP] Embed Pods Frameworks"; 283 | outputPaths = ( 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | shellPath = /bin/sh; 287 | shellScript = " 288 | "; 289 | showEnvVarsInLog = 0; 290 | }; 291 | /* End PBXShellScriptBuildPhase section */ 292 | 293 | /* Begin PBXSourcesBuildPhase section */ 294 | 3621D24C1F2FA19A00E07518 /* Sources */ = { 295 | isa = PBXSourcesBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | 3616ECE11F61E9A300906228 /* AudioCommon.swift in Sources */, 299 | 369035671F3FBF1000E72EDB /* VoiceCategoryButton.swift in Sources */, 300 | 3621D2561F2FA19A00E07518 /* MIDIInstrumentViewController.swift in Sources */, 301 | 3621D2A71F33FB4E00E07518 /* SoftSynth.swift in Sources */, 302 | 3621D2991F2FEFEE00E07518 /* SoundError.swift in Sources */, 303 | 3621D2541F2FA19A00E07518 /* AppDelegate.swift in Sources */, 304 | 3690358D1F43A77200E72EDB /* ScreenUtils.swift in Sources */, 305 | 3616ECE31F61EA0B00906228 /* Sequencer.swift in Sources */, 306 | 3621D29E1F31431900E07518 /* VoiceSelectorView.swift in Sources */, 307 | 369035641F3E755A00E72EDB /* VoiceSelectorButton.swift in Sources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | /* End PBXSourcesBuildPhase section */ 312 | 313 | /* Begin PBXVariantGroup section */ 314 | 3621D2571F2FA19A00E07518 /* Main.storyboard */ = { 315 | isa = PBXVariantGroup; 316 | children = ( 317 | 3621D2581F2FA19A00E07518 /* Base */, 318 | ); 319 | name = Main.storyboard; 320 | sourceTree = ""; 321 | }; 322 | 3621D25C1F2FA19A00E07518 /* LaunchScreen.storyboard */ = { 323 | isa = PBXVariantGroup; 324 | children = ( 325 | 3621D25D1F2FA19A00E07518 /* Base */, 326 | ); 327 | name = LaunchScreen.storyboard; 328 | sourceTree = ""; 329 | }; 330 | /* End PBXVariantGroup section */ 331 | 332 | /* Begin XCBuildConfiguration section */ 333 | 3621D2601F2FA19A00E07518 /* Debug */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | CLANG_ANALYZER_NONNULL = YES; 338 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 340 | CLANG_CXX_LIBRARY = "libc++"; 341 | CLANG_ENABLE_MODULES = YES; 342 | CLANG_ENABLE_OBJC_ARC = YES; 343 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 344 | CLANG_WARN_BOOL_CONVERSION = YES; 345 | CLANG_WARN_COMMA = YES; 346 | CLANG_WARN_CONSTANT_CONVERSION = YES; 347 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 348 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 349 | CLANG_WARN_EMPTY_BODY = YES; 350 | CLANG_WARN_ENUM_CONVERSION = YES; 351 | CLANG_WARN_INFINITE_RECURSION = YES; 352 | CLANG_WARN_INT_CONVERSION = YES; 353 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 356 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 357 | CLANG_WARN_STRICT_PROTOTYPES = YES; 358 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 359 | CLANG_WARN_UNREACHABLE_CODE = YES; 360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 361 | CODE_SIGN_IDENTITY = "iPhone Developer"; 362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 363 | COPY_PHASE_STRIP = NO; 364 | DEBUG_INFORMATION_FORMAT = dwarf; 365 | ENABLE_STRICT_OBJC_MSGSEND = YES; 366 | ENABLE_TESTABILITY = YES; 367 | GCC_C_LANGUAGE_STANDARD = gnu99; 368 | GCC_DYNAMIC_NO_PIC = NO; 369 | GCC_NO_COMMON_BLOCKS = YES; 370 | GCC_OPTIMIZATION_LEVEL = 0; 371 | GCC_PREPROCESSOR_DEFINITIONS = ( 372 | "DEBUG=1", 373 | "$(inherited)", 374 | ); 375 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 376 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 377 | GCC_WARN_UNDECLARED_SELECTOR = YES; 378 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 379 | GCC_WARN_UNUSED_FUNCTION = YES; 380 | GCC_WARN_UNUSED_VARIABLE = YES; 381 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 382 | MACH_O_TYPE = staticlib; 383 | MTL_ENABLE_DEBUG_INFO = YES; 384 | ONLY_ACTIVE_ARCH = YES; 385 | SDKROOT = iphoneos; 386 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 387 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 388 | SWIFT_VERSION = 3.0; 389 | TARGETED_DEVICE_FAMILY = "1,2"; 390 | }; 391 | name = Debug; 392 | }; 393 | 3621D2611F2FA19A00E07518 /* Release */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ALWAYS_SEARCH_USER_PATHS = NO; 397 | CLANG_ANALYZER_NONNULL = YES; 398 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 399 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 400 | CLANG_CXX_LIBRARY = "libc++"; 401 | CLANG_ENABLE_MODULES = YES; 402 | CLANG_ENABLE_OBJC_ARC = YES; 403 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 404 | CLANG_WARN_BOOL_CONVERSION = YES; 405 | CLANG_WARN_COMMA = YES; 406 | CLANG_WARN_CONSTANT_CONVERSION = YES; 407 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 408 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 409 | CLANG_WARN_EMPTY_BODY = YES; 410 | CLANG_WARN_ENUM_CONVERSION = YES; 411 | CLANG_WARN_INFINITE_RECURSION = YES; 412 | CLANG_WARN_INT_CONVERSION = YES; 413 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 414 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 416 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 417 | CLANG_WARN_STRICT_PROTOTYPES = YES; 418 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 419 | CLANG_WARN_UNREACHABLE_CODE = YES; 420 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 421 | CODE_SIGN_IDENTITY = "iPhone Distribution"; 422 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; 423 | COPY_PHASE_STRIP = NO; 424 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 425 | ENABLE_NS_ASSERTIONS = NO; 426 | ENABLE_STRICT_OBJC_MSGSEND = YES; 427 | GCC_C_LANGUAGE_STANDARD = gnu99; 428 | GCC_NO_COMMON_BLOCKS = YES; 429 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 430 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 431 | GCC_WARN_UNDECLARED_SELECTOR = YES; 432 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 433 | GCC_WARN_UNUSED_FUNCTION = YES; 434 | GCC_WARN_UNUSED_VARIABLE = YES; 435 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 436 | MACH_O_TYPE = staticlib; 437 | MTL_ENABLE_DEBUG_INFO = NO; 438 | SDKROOT = iphoneos; 439 | "SWIFT_ACTIVE_COMPILATION_CONDITIONS[arch=*]" = ""; 440 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 441 | SWIFT_VERSION = 3.0; 442 | TARGETED_DEVICE_FAMILY = "1,2"; 443 | VALIDATE_PRODUCT = YES; 444 | }; 445 | name = Release; 446 | }; 447 | 3621D2631F2FA19A00E07518 /* Debug */ = { 448 | isa = XCBuildConfiguration; 449 | buildSettings = { 450 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 451 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 452 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 453 | CODE_SIGN_STYLE = Automatic; 454 | DEVELOPMENT_TEAM = 7RKB3MB466; 455 | FRAMEWORK_SEARCH_PATHS = ( 456 | "$(inherited)", 457 | "$(PROJECT_DIR)", 458 | ); 459 | INFOPLIST_FILE = Jamulator/Info.plist; 460 | "INFOPLIST_FILE[sdk=*]" = Jamulator/Info.plist; 461 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 462 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 463 | MACH_O_TYPE = mh_execute; 464 | PRODUCT_BUNDLE_IDENTIFIER = com.ericfordconsulting.Jamulator; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | PROVISIONING_PROFILE_SPECIFIER = ""; 467 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 468 | SWIFT_VERSION = 3.0; 469 | }; 470 | name = Debug; 471 | }; 472 | 3621D2641F2FA19A00E07518 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 476 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 477 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 478 | CODE_SIGN_STYLE = Automatic; 479 | DEVELOPMENT_TEAM = 7RKB3MB466; 480 | FRAMEWORK_SEARCH_PATHS = ( 481 | "$(inherited)", 482 | "$(PROJECT_DIR)", 483 | ); 484 | INFOPLIST_FILE = Jamulator/Info.plist; 485 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 486 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 487 | MACH_O_TYPE = mh_execute; 488 | PRODUCT_BUNDLE_IDENTIFIER = com.ericfordconsulting.Jamulator; 489 | PRODUCT_NAME = "$(TARGET_NAME)"; 490 | PROVISIONING_PROFILE_SPECIFIER = ""; 491 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 492 | SWIFT_VERSION = 3.0; 493 | }; 494 | name = Release; 495 | }; 496 | /* End XCBuildConfiguration section */ 497 | 498 | /* Begin XCConfigurationList section */ 499 | 3621D24B1F2FA19A00E07518 /* Build configuration list for PBXProject "Jamulator" */ = { 500 | isa = XCConfigurationList; 501 | buildConfigurations = ( 502 | 3621D2601F2FA19A00E07518 /* Debug */, 503 | 3621D2611F2FA19A00E07518 /* Release */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Release; 507 | }; 508 | 3621D2621F2FA19A00E07518 /* Build configuration list for PBXNativeTarget "Jamulator" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | 3621D2631F2FA19A00E07518 /* Debug */, 512 | 3621D2641F2FA19A00E07518 /* Release */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Release; 516 | }; 517 | /* End XCConfigurationList section */ 518 | }; 519 | rootObject = 3621D2481F2FA19A00E07518 /* Project object */; 520 | } 521 | -------------------------------------------------------------------------------- /Jamulator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Jamulator.xcodeproj/project.xcworkspace/xcuserdata/2016imac.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator.xcodeproj/project.xcworkspace/xcuserdata/2016imac.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Jamulator.xcodeproj/xcuserdata/2016imac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Jamulator.xcodeproj/xcuserdata/2016imac.xcuserdatad/xcschemes/Jamulator.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Jamulator.xcodeproj/xcuserdata/2016imac.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Jamulator.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 3621D24F1F2FA19A00E07518 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Jamulator/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/.DS_Store -------------------------------------------------------------------------------- /Jamulator/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 9 | return true 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "57x57", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-57x57@1x.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "57x57", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-57x57@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "Icon-App-60x60@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "60x60", 65 | "idiom" : "iphone", 66 | "filename" : "Icon-App-60x60@3x.png", 67 | "scale" : "3x" 68 | }, 69 | { 70 | "size" : "20x20", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-20x20@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "20x20", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-20x20@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-29x29@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "29x29", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-29x29@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-40x40@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-40x40@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "50x50", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-Small-50x50@1x.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "50x50", 113 | "idiom" : "ipad", 114 | "filename" : "Icon-Small-50x50@2x.png", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "72x72", 119 | "idiom" : "ipad", 120 | "filename" : "Icon-App-72x72@1x.png", 121 | "scale" : "1x" 122 | }, 123 | { 124 | "size" : "72x72", 125 | "idiom" : "ipad", 126 | "filename" : "Icon-App-72x72@2x.png", 127 | "scale" : "2x" 128 | }, 129 | { 130 | "size" : "76x76", 131 | "idiom" : "ipad", 132 | "filename" : "Icon-App-76x76@1x.png", 133 | "scale" : "1x" 134 | }, 135 | { 136 | "size" : "76x76", 137 | "idiom" : "ipad", 138 | "filename" : "Icon-App-76x76@2x.png", 139 | "scale" : "2x" 140 | }, 141 | { 142 | "size" : "83.5x83.5", 143 | "idiom" : "ipad", 144 | "filename" : "Icon-App-83.5x83.5@2x.png", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "idiom" : "ios-marketing", 149 | "size" : "1024x1024", 150 | "scale" : "1x" 151 | }, 152 | { 153 | "size" : "40x40", 154 | "idiom" : "iphone", 155 | "filename" : "Icon-App-40x40@1x.png", 156 | "scale" : "1x" 157 | }, 158 | { 159 | "size" : "60x60", 160 | "idiom" : "iphone", 161 | "filename" : "Icon-App-60x60@1x.png", 162 | "scale" : "1x" 163 | }, 164 | { 165 | "size" : "76x76", 166 | "idiom" : "iphone", 167 | "filename" : "Icon-App-76x76@1x.png", 168 | "scale" : "1x" 169 | }, 170 | { 171 | "size" : "76x76", 172 | "idiom" : "ipad", 173 | "filename" : "Icon-App-76x76@3x.png", 174 | "scale" : "3x" 175 | } 176 | ], 177 | "info" : { 178 | "version" : 1, 179 | "author" : "xcode" 180 | } 181 | } -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/rw-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Razewarelogo_1024.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/rw-logo.imageset/Razewarelogo_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/rw-logo.imageset/Razewarelogo_1024.png -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/rwdevcon-bg.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "rwdevcon-bg.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Jamulator/Assets.xcassets/rwdevcon-bg.imageset/rwdevcon-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EricFordConsulting/Jamulator/cc64688cc0dad4c2a9ab3c2eef637d835181f25c/Jamulator/Assets.xcassets/rwdevcon-bg.imageset/rwdevcon-bg.png -------------------------------------------------------------------------------- /Jamulator/AudioCommon.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Eric Ford Consulting 3 | */ 4 | 5 | import Foundation 6 | import AudioToolbox 7 | 8 | class AudioCommon 9 | { 10 | var audioGraph: AUGraph? 11 | var synthNode = AUNode() 12 | var outputNode = AUNode() 13 | var synthUnit: AudioUnit? 14 | var patch = UInt32(0) 15 | 16 | func initAudio() { 17 | checkError(osstatus: NewAUGraph(&audioGraph)) 18 | createOutputNode(audioGraph: audioGraph!, outputNode: &outputNode) 19 | createSynthNode() 20 | checkError(osstatus: AUGraphOpen(audioGraph!)) 21 | // get the synth unit 22 | checkError(osstatus: AUGraphNodeInfo(audioGraph!, synthNode, nil, &synthUnit)) 23 | let synthOutputElement: AudioUnitElement = 0 24 | let ioUnitInputElement: AudioUnitElement = 0 25 | checkError(osstatus: 26 | AUGraphConnectNodeInput(audioGraph!, synthNode, synthOutputElement, 27 | outputNode, ioUnitInputElement)) 28 | checkError(osstatus: AUGraphInitialize(audioGraph!)) 29 | checkError(osstatus: AUGraphStart(audioGraph!)) 30 | loadSoundFont() 31 | loadPatch(patchNo: 0) 32 | } 33 | 34 | // Mark: - Audio Init Utility Methods 35 | func createOutputNode(audioGraph: AUGraph, outputNode: UnsafeMutablePointer) { 36 | var cd = AudioComponentDescription( 37 | componentType: OSType(kAudioUnitType_Output), 38 | componentSubType: OSType(kAudioUnitSubType_RemoteIO), 39 | componentManufacturer: OSType(kAudioUnitManufacturer_Apple), 40 | componentFlags: 0,componentFlagsMask: 0) 41 | checkError(osstatus: AUGraphAddNode(audioGraph, &cd, outputNode)) 42 | } 43 | 44 | func createSynthNode() { 45 | var cd = AudioComponentDescription( 46 | componentType: OSType(kAudioUnitType_MusicDevice), 47 | componentSubType: OSType(kAudioUnitSubType_MIDISynth), 48 | componentManufacturer: OSType(kAudioUnitManufacturer_Apple), 49 | componentFlags: 0,componentFlagsMask: 0) 50 | checkError(osstatus: AUGraphAddNode(audioGraph!, &cd, &synthNode)) 51 | } 52 | 53 | // In the simulator this takes a long time, so we 54 | // call it in a background thread in the controller 55 | func loadSoundFont() { 56 | var bankURL = Bundle.main.url(forResource: "FluidR3_GM", withExtension: "sf2") 57 | checkError(osstatus: AudioUnitSetProperty(synthUnit!, AudioUnitPropertyID(kMusicDeviceProperty_SoundBankURL), AudioUnitScope(kAudioUnitScope_Global), 0, &bankURL, UInt32(MemoryLayout.size))) 58 | } 59 | 60 | func loadPatch(patchNo: Int) { 61 | let channel = UInt32(0) 62 | var enabled = UInt32(1) 63 | var disabled = UInt32(0) 64 | patch = UInt32(patchNo) 65 | 66 | checkError(osstatus: AudioUnitSetProperty( 67 | synthUnit!, 68 | AudioUnitPropertyID(kAUMIDISynthProperty_EnablePreload), 69 | AudioUnitScope(kAudioUnitScope_Global), 70 | 0, 71 | &enabled, 72 | UInt32(MemoryLayout.size))) 73 | 74 | let programChangeCommand = UInt32(0xC0 | channel) 75 | checkError(osstatus: MusicDeviceMIDIEvent(self.synthUnit!, programChangeCommand, patch, 0, 0)) 76 | 77 | checkError(osstatus: AudioUnitSetProperty( 78 | synthUnit!, 79 | AudioUnitPropertyID(kAUMIDISynthProperty_EnablePreload), 80 | AudioUnitScope(kAudioUnitScope_Global), 81 | 0, 82 | &disabled, 83 | UInt32(MemoryLayout.size))) 84 | 85 | // the previous programChangeCommand just triggered a preload 86 | // this one actually changes to the new voice 87 | checkError(osstatus: MusicDeviceMIDIEvent(synthUnit!, programChangeCommand, patch, 0, 0)) 88 | } 89 | 90 | func checkError(osstatus: OSStatus) { 91 | if osstatus != noErr { 92 | print(SoundError.GetErrorMessage(osstatus)) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Jamulator/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Jamulator/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Jamulator/CertificateSigningRequest.certSigningRequest: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICizCCAXMCAQAwRjEhMB8GCSqGSIb3DQEJARYSYnJhemZvcmRAZ21haWwuY29t 3 | MRQwEgYDVQQDDAtFcmljIEZvcmQgMTELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3 4 | DQEBAQUAA4IBDwAwggEKAoIBAQDiJ9VKFZg1Trh6vnfV8+Zq7sIS2OIB3HmCc0kZ 5 | n5QeKU3XmJ2yhSOayo3obKLivZeFi52TFieVAVl3cbSKfcseKWVY53ba3tyIOwll 6 | j79lbJAeH9omk2IFUqnyaOK6vp+gxXQ9eFYRn4qLuhNdZ/byTnaTmJb51Q0YldXV 7 | RPVBYNHdhce5h0TUDiPLWhk3j4OfmwqyFNVBhBDtsdH9Cll+GNjJTxschlKsUu+A 8 | nd1rbM35kcNr8DuTUU9+cjtcVQf/Tzt7gxp854YI6Cfvfbvwix57w1PAtHududYO 9 | DkEnFno82c9komObT1y75LYgH0KnL3LyZkZz+RIFkRGcdbeLAgMBAAGgADANBgkq 10 | hkiG9w0BAQsFAAOCAQEAuSoC/fKxY1EM6yhl8KB/+U0QnWgJ6hcWr/QL1FygJZ2E 11 | NSBsdyVZglLp3Xkc5EjEuSByMHdA4N4kfxk0sWy/DYUU7x2gzQYrHhULD1NfzGWQ 12 | YkFO7UV8mxZzCfU2OAaVmYh42AosdckqUXY8neDlXgnIeA8VM+FnH4bj1hdRiQXq 13 | w0Soi3KjzhEAIvnNWtQKmOWRHpc+n9ZU2tfECU0ZOOOTYCXrUBHtzNc1wvLehD9i 14 | Bs5Offt7P+Dow0ZtgshvAfzkG+7fnkJLsBMym2cHRQYC/xdlytmhVCcN1piL1Qs/ 15 | l+zY76j641n2ywndrZpBn/eAGAxpXnCljqf/oAcxuA== 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /Jamulator/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0.1 19 | CFBundleVersion 20 | 2 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIRequiresFullScreen 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Jamulator/MIDIInstrumentViewController.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Eric Ford Consulting 3 | */ 4 | 5 | import UIKit 6 | import AudioKit 7 | 8 | class MIDIInstrumentViewController: UIViewController { 9 | 10 | let chooseOctaveControl = UISegmentedControl(items: ["1", "2", "3", "4", "5", "6"]) 11 | let sequencerControl = UISegmentedControl(items: ["Off", "Record", "Play"]) 12 | 13 | let synth = SoftSynth() 14 | let sequencer = Sequencer.shared 15 | // a custom control for choosing from the 128 general MIDI voices 16 | var voiceSelectorView: VoiceSelectorView? 17 | let backgroundColor = UIColor(hex: 0x53893e) 18 | let keyOnColor = UIColor(hex: 0x519657) 19 | let whiteKeyOffColor = UIColor(hex: 0xa5d6a7) 20 | let blackKeyOffColor = UIColor(hex: 0x33691e) 21 | let labelTextColor = UIColor(hex: 0xc7dfc9) 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | self.view.backgroundColor = backgroundColor 26 | voiceSelectorView = VoiceSelectorView(frame: 27 | ScreenUtils.resizeRect(rect: CGRect(x: 40, y: 200, width: 640, height: 160)), synth: synth) 28 | setSpeakersAsDefaultAudioOutput() 29 | setUpPianoKeyboard() 30 | 31 | // cover up garbage on the right side of AKKeyboardView 32 | let coverUp = UIView(frame: ScreenUtils.resizeRect(rect: CGRect(x: 679, y: 0, width: 100, height: 150))) 33 | coverUp.backgroundColor = self.view.backgroundColor 34 | self.view.addSubview(coverUp) 35 | 36 | setUpOctaveControl() 37 | 38 | view.addSubview(voiceSelectorView!) 39 | 40 | loadVoices() 41 | // show the names of the voices while they are loading 42 | // one every second or so 43 | // probably only relevant in the simulator, but just in case... 44 | voiceSelectorView?.setTimer() 45 | } 46 | 47 | // MARK: - utility methods used in viewDidLoad 48 | 49 | // work around for when some devices only play through the headphone jack 50 | func setSpeakersAsDefaultAudioOutput() { 51 | do { 52 | try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: AVAudioSessionCategoryOptions.defaultToSpeaker) 53 | } 54 | catch { 55 | // hard to imagine how we'll get this exception 56 | let alertController = UIAlertController(title: "Speaker Problem", message: "You may be able to hear sound using headphones.", preferredStyle: UIAlertControllerStyle.alert) 57 | let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) { 58 | (result: UIAlertAction) -> Void in 59 | } 60 | 61 | alertController.addAction(okAction) 62 | self.present(alertController, animated: true, completion: nil) 63 | } 64 | } 65 | 66 | // the piano keyboard is provided by Audio Kit 67 | // the protocol extension is at the bottom of this file 68 | // all we have to provide is a noteOn method and a noteOff method 69 | func setUpPianoKeyboard() { 70 | let keyboard = AKKeyboardView(frame: ScreenUtils.resizeRect( 71 | rect: CGRect(x: 40, y: 0, width: 687, height: 150))) 72 | keyboard.delegate = self 73 | keyboard.polyphonicMode = true // allow more than one note at a time 74 | keyboard.keyOnColor = keyOnColor 75 | keyboard.whiteKeyOff = whiteKeyOffColor 76 | keyboard.blackKeyOff = blackKeyOffColor 77 | self.view.addSubview(keyboard) 78 | } 79 | 80 | func setUpOctaveControl() { 81 | let octaveLabel = UILabel(frame: ScreenUtils.resizeRect(rect: CGRect(x: 345, y: 152, width: 60, height: 20))) 82 | octaveLabel.text = "Octave" 83 | octaveLabel.textColor = labelTextColor 84 | octaveLabel.font = ScreenUtils.getAdjustedFont() 85 | self.view.addSubview(octaveLabel) 86 | chooseOctaveControl.selectedSegmentIndex = synth.octave - 1 87 | chooseOctaveControl.frame = ScreenUtils.resizeRect(rect: CGRect(x: 200, y: 170, width: 320, height: 20)) 88 | chooseOctaveControl.layer.cornerRadius = 5.0 89 | chooseOctaveControl.backgroundColor = labelTextColor 90 | chooseOctaveControl.tintColor = blackKeyOffColor 91 | chooseOctaveControl.addTarget(self, action: #selector(MIDIInstrumentViewController.changeOctave(_:)), for: .valueChanged) 92 | self.view.addSubview(chooseOctaveControl) 93 | } 94 | 95 | // a sequencer can record what you play, then play it back 96 | func setUpSequencer() { 97 | let sequencerLabel = UILabel(frame: ScreenUtils.resizeRect(rect: CGRect(x: 338, y: 362, width: 100, height: 20))) 98 | sequencerLabel.text = "Sequencer" 99 | sequencerLabel.textColor = labelTextColor 100 | sequencerLabel.font = ScreenUtils.getAdjustedFont() 101 | self.view.addSubview(sequencerLabel) 102 | 103 | sequencerControl.selectedSegmentIndex = 0 // off 104 | sequencerControl.frame = ScreenUtils.resizeRect(rect: CGRect(x: 200, y: 380, width: 320, height: 20)) 105 | sequencerControl.layer.cornerRadius = 5.0 106 | sequencerControl.backgroundColor = labelTextColor 107 | sequencerControl.tintColor = blackKeyOffColor 108 | sequencerControl.addTarget(self, action: #selector(MIDIInstrumentViewController.changeSequencerMode(_:)), for: .valueChanged) 109 | sequencerControl.setEnabled(false, forSegmentAt: 2) 110 | self.view.addSubview(sequencerControl) 111 | } 112 | 113 | // load the voices in a background thread 114 | // on completion, tell the voice selector so it can display them 115 | // again, might only matter in the simulator 116 | func loadVoices() { 117 | DispatchQueue.global(qos: .background).async { 118 | self.synth.loadSoundFont() 119 | self.synth.loadPatch(patchNo: 0) 120 | DispatchQueue.main.async { 121 | // don't let the user choose a voice until they finish loading 122 | self.voiceSelectorView?.setShowVoices(show: true) 123 | // don't let the user use the sequencer until the voices are loaded 124 | self.setUpSequencer() 125 | } 126 | } 127 | } 128 | 129 | // MARK: - methods for responding to UISegmentedControls change events 130 | 131 | @objc func changeOctave(_ sender: UISegmentedControl) { 132 | synth.octave = sender.selectedSegmentIndex + 1 133 | } 134 | 135 | // the synth owns the sequencer 136 | // tell it to record, play, or stop 137 | // enable the play button when the record button is clicked 138 | // when recording, turn everything reddish 139 | @objc func changeSequencerMode(_ sender: UISegmentedControl) { 140 | sequencer.setSequencerMode(mode: sender.selectedSegmentIndex) 141 | if sender.selectedSegmentIndex == SequencerMode.recording.rawValue { 142 | sender.setEnabled(true, forSegmentAt: 2) 143 | sender.tintColor = .red 144 | } else { 145 | sender.tintColor = blackKeyOffColor 146 | } 147 | } 148 | } 149 | 150 | // MARK: - AKKeyboardDelegate 151 | // the protocol for the piano keyboard needs methods to turn notes on and off 152 | extension MIDIInstrumentViewController: AKKeyboardDelegate { 153 | 154 | func noteOn(note: MIDINoteNumber) { 155 | synth.playNoteOn(channel: 0, note: note, midiVelocity: 127) 156 | } 157 | 158 | func noteOff(note: MIDINoteNumber) { 159 | synth.playNoteOff(channel: 0, note: UInt32(note), midiVelocity: 127) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Jamulator/ScreenUtils.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Eric Ford Consulting 3 | */ 4 | 5 | import UIKit 6 | 7 | class ScreenUtils { 8 | // MARK: - handle different screen sizes 9 | 10 | // base screen size is iPhone 6 plus or 7 plus, 736 x 414 landscape 11 | static let widthRatio = UIScreen.main.bounds.width / 736 12 | static let heightRatio = UIScreen.main.bounds.height / 414 13 | 14 | // scale a rectangle up or down proportional to the screen size of the device 15 | static func resizeRect(rect: CGRect) -> CGRect { 16 | return CGRect(x: rect.origin.x * widthRatio, y: rect.origin.y * heightRatio, 17 | width: rect.width * widthRatio, height: rect.height * heightRatio) 18 | } 19 | 20 | // get a font with a size proportional to the screen size 21 | static func getAdjustedFont() -> UIFont? { 22 | return UIFont(name: "MarkerFelt-Thin", size: getAdjustedFontSize()) 23 | } 24 | 25 | // get a font size proportional to the screen size 26 | static func getAdjustedFontSize() -> CGFloat { 27 | return UIScreen.main.bounds.width / 80 28 | } 29 | 30 | static func getAdjustedTextYOffset() -> CGFloat { 31 | if UIScreen.main.bounds.width < 736 { 32 | return 0 33 | } 34 | return UIScreen.main.bounds.width / 200 35 | } 36 | } 37 | 38 | extension UIColor { 39 | 40 | convenience init(hex: Int, alpha: Double = 1.0) { 41 | self.init(red: CGFloat((hex>>16)&0xFF)/255.0, green:CGFloat((hex>>8)&0xFF)/255.0, blue: CGFloat((hex)&0xFF)/255.0, alpha: CGFloat(255 * alpha) / 255) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Jamulator/Sequencer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Eric Ford Consulting 3 | */ 4 | 5 | import Foundation 6 | import AudioToolbox 7 | import CoreAudio 8 | 9 | enum SequencerMode: Int { 10 | case off = 0, recording, playing 11 | } 12 | 13 | final class Sequencer : AudioCommon { 14 | // var storedPatch = UInt32(0) 15 | var musicSequence: MusicSequence? 16 | var musicPlayer: MusicPlayer? 17 | let sequencerMidiChannel = UInt8(1) 18 | var midiVelocity = UInt8(127) 19 | 20 | var sequencerMode = 0 21 | var sequenceStartTime: Date? 22 | var track: MusicTrack? 23 | 24 | var noteOnTimes = [Date] (repeating: Date(), count:128) 25 | 26 | static let shared = Sequencer() 27 | private override init() 28 | { 29 | super.init() 30 | initAudio() 31 | setUpSequencer() 32 | loadSoundFont() 33 | loadPatch(patchNo: 0) 34 | } 35 | 36 | func setSequencerMode(mode: Int) { 37 | sequencerMode = mode 38 | switch(sequencerMode) { 39 | case SequencerMode.off.rawValue: 40 | checkError(osstatus: MusicPlayerStop(musicPlayer!)) 41 | case SequencerMode.recording.rawValue: 42 | startRecording() 43 | case SequencerMode.playing.rawValue: 44 | musicPlayerPlay() 45 | default: 46 | break 47 | } 48 | } 49 | 50 | func noteOn(note: UInt8) { 51 | if sequencerMode == SequencerMode.recording.rawValue { 52 | noteOnTimes[Int(note)] = Date() 53 | } 54 | } 55 | 56 | func noteOff(note: UInt8) { 57 | if sequencerMode == SequencerMode.recording.rawValue { 58 | let duration: Double = Date().timeIntervalSince(noteOnTimes[Int(note)]) 59 | let onset: Double = noteOnTimes[Int(note)].timeIntervalSince(sequenceStartTime!) 60 | var beat: MusicTimeStamp = 0 61 | checkError(osstatus: MusicSequenceGetBeatsForSeconds(musicSequence!, onset, &beat)) 62 | var mess = MIDINoteMessage(channel: sequencerMidiChannel, 63 | note: note, 64 | velocity: midiVelocity, 65 | releaseVelocity: 0, 66 | duration: Float(duration) ) 67 | checkError(osstatus: MusicTrackNewMIDINoteEvent(track!, beat, &mess)) 68 | } 69 | } 70 | 71 | func musicPlayerPlay() { 72 | var status = noErr 73 | var playing:DarwinBoolean = false 74 | checkError(osstatus: MusicPlayerIsPlaying(musicPlayer!, &playing)) 75 | if playing != false { 76 | status = MusicPlayerStop(musicPlayer!) 77 | if status != noErr { 78 | print("Error stopping \(status)") 79 | checkError(osstatus: status) 80 | return 81 | } 82 | } 83 | 84 | checkError(osstatus: MusicPlayerSetTime(musicPlayer!, 0)) 85 | checkError(osstatus: MusicPlayerStart(musicPlayer!)) 86 | } 87 | 88 | func startRecording() { 89 | sequenceStartTime = Date() 90 | setUpSequencer() 91 | } 92 | 93 | private func setUpSequencer() { 94 | // set the sequencer voice to storedPatch so we can play along with it using patch 95 | var status = NewMusicSequence(&musicSequence) 96 | if status != noErr { 97 | print("\(#line) bad status \(status) creating sequence") 98 | } 99 | 100 | status = MusicSequenceNewTrack(musicSequence!, &track) 101 | if status != noErr { 102 | print("error creating track \(status)") 103 | } 104 | 105 | // 0xB0 = bank select, first we do the most significant byte 106 | var chanmess = MIDIChannelMessage(status: 0xB0 | sequencerMidiChannel, data1: 0, data2: 0, reserved: 0) 107 | status = MusicTrackNewMIDIChannelEvent(track!, 0, &chanmess) 108 | if status != noErr { 109 | print("creating bank select event \(status)") 110 | } 111 | // then the least significant byte 112 | chanmess = MIDIChannelMessage(status: 0xB0 | sequencerMidiChannel, data1: 32, data2: 0, reserved: 0) 113 | status = MusicTrackNewMIDIChannelEvent(track!, 0, &chanmess) 114 | if status != noErr { 115 | print("creating bank select event \(status)") 116 | } 117 | 118 | // set the voice 119 | chanmess = MIDIChannelMessage(status: 0xC0 | sequencerMidiChannel, data1: UInt8(patch), data2: 0, reserved: 0) 120 | status = MusicTrackNewMIDIChannelEvent(track!, 0, &chanmess) 121 | if status != noErr { 122 | print("creating program change event \(status)") 123 | } 124 | 125 | checkError(osstatus: MusicSequenceSetAUGraph(musicSequence!, audioGraph)) 126 | checkError(osstatus: NewMusicPlayer(&musicPlayer)) 127 | checkError(osstatus: MusicPlayerSetSequence(musicPlayer!, musicSequence)) 128 | checkError(osstatus: MusicPlayerPreroll(musicPlayer!)) 129 | } 130 | } 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /Jamulator/SoftSynth.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Eric Ford Consulting 3 | */ 4 | 5 | import CoreAudio 6 | import AudioKit 7 | 8 | final class SoftSynth : AudioCommon { 9 | override init() 10 | { 11 | super.init() 12 | initAudio() 13 | loadSoundFont() 14 | loadPatch(patchNo: 0) 15 | } 16 | 17 | var octave = 4 18 | let midiChannel = 0 19 | var midiVelocity = UInt8(127) 20 | 21 | func playNoteOn(channel: Int, note: UInt8, midiVelocity: Int) { 22 | let noteCommand = UInt32(0x90 | channel) 23 | let base = note - 48 24 | let octaveAdjust = (UInt8(octave) * 12) + base 25 | let pitch = UInt32(octaveAdjust) 26 | checkError(osstatus: MusicDeviceMIDIEvent(synthUnit!, noteCommand, pitch, UInt32(midiVelocity), 0)) 27 | Sequencer.shared.noteOn(note: UInt8(pitch)) 28 | } 29 | 30 | func playNoteOff(channel: Int, note: UInt32, midiVelocity: Int) { 31 | let noteCommand = UInt32(0x80 | channel) 32 | let base = UInt8(note - 48) 33 | let octaveAdjust = (UInt8(octave) * 12) + base 34 | let pitch = UInt32(octaveAdjust) 35 | checkError(osstatus: MusicDeviceMIDIEvent(synthUnit!, noteCommand, pitch, 0, 0)) 36 | Sequencer.shared.noteOff(note: UInt8(pitch)) 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Jamulator/SoundError.swift: -------------------------------------------------------------------------------- 1 | import AudioToolbox 2 | 3 | class SoundError { 4 | class func GetErrorMessage (_ osstatus:OSStatus) -> String { 5 | switch(osstatus) 6 | { 7 | case kAUGraphErr_NodeNotFound: 8 | return "AUGraph Node Not Found" 9 | 10 | case kAUGraphErr_OutputNodeErr: 11 | return "AUGraph Output Node Error" 12 | 13 | case kAUGraphErr_InvalidConnection: 14 | return "AUGraph Invalid Connection" 15 | 16 | case kAUGraphErr_CannotDoInCurrentContext: 17 | return "AUGraph Cannot Do In Current Context" 18 | 19 | case kAUGraphErr_InvalidAudioUnit: 20 | return "AUGraph Invalid Audio Unit" 21 | 22 | case kMIDIInvalidClient : 23 | return "MIDI Invalid Client" 24 | 25 | case kMIDIInvalidPort : 26 | return "MIDI Invalid Port" 27 | 28 | case kMIDIWrongEndpointType : 29 | return "MIDI Wrong Endpoint Type" 30 | 31 | case kMIDINoConnection : 32 | return "MIDI No Connection" 33 | 34 | case kMIDIUnknownEndpoint : 35 | return "MIDI Unknown Endpoint" 36 | 37 | case kMIDIUnknownProperty : 38 | return "MIDI Unknown Property" 39 | 40 | case kMIDIWrongPropertyType : 41 | return "MIDI Wrong Property Type" 42 | 43 | case kMIDINoCurrentSetup : 44 | return "MIDI No Current Setup" 45 | 46 | case kMIDIMessageSendErr : 47 | return "MIDI Message Send Error" 48 | 49 | case kMIDIServerStartErr : 50 | return "MIDI Server Start Error" 51 | 52 | case kMIDISetupFormatErr : 53 | return "MIDI Setup Format Error" 54 | 55 | case kMIDIWrongThread : 56 | return "MIDI Wrong Thread" 57 | 58 | case kMIDIObjectNotFound : 59 | return "MIDI Object Not Found" 60 | 61 | case kMIDIIDNotUnique : 62 | return "MIDI ID Not Unique" 63 | 64 | case kAudioToolboxErr_InvalidSequenceType : 65 | return "AudioToolbox Invalid Sequence Type" 66 | 67 | case kAudioToolboxErr_TrackIndexError : 68 | return "AudioToolbox Track Index Error" 69 | 70 | case kAudioToolboxErr_TrackNotFound : 71 | return "AudioToolbox Track Not Found" 72 | 73 | case kAudioToolboxErr_EndOfTrack : 74 | return "AudioToolbox End Of Track" 75 | 76 | case kAudioToolboxErr_StartOfTrack : 77 | return "AudioToolbox Start Of Track" 78 | 79 | case kAudioToolboxErr_IllegalTrackDestination: 80 | return "AudioToolbox Illegal Track Destination" 81 | 82 | case kAudioToolboxErr_NoSequence : 83 | return "AudioToolbox No Sequence" 84 | 85 | case kAudioToolboxErr_InvalidEventType : 86 | return "AudioToolbox Invalid Event Type" 87 | 88 | case kAudioToolboxErr_InvalidPlayerState: 89 | return "AudioToolbox Invalid Player State" 90 | 91 | case kAudioUnitErr_InvalidProperty : 92 | return "AudioUnit Invalid Property" 93 | 94 | case kAudioUnitErr_InvalidParameter : 95 | return "AudioUnit Invalid Parameter" 96 | 97 | case kAudioUnitErr_InvalidElement : 98 | return "AudioUnit Invalid Element" 99 | 100 | case kAudioUnitErr_NoConnection : 101 | return "AudioUnit No Connection" 102 | 103 | case kAudioUnitErr_FailedInitialization : 104 | return "AudioUnit Failed Initialization" 105 | 106 | case kAudioUnitErr_InvalidFile: 107 | return "AudioUnit Invalid File" 108 | 109 | case kAudioUnitErr_FormatNotSupported : 110 | return "AudioUnit Format Not Supported" 111 | 112 | case kAudioUnitErr_Uninitialized: 113 | return "AudioUnit Uninitialized" 114 | 115 | case kAudioUnitErr_InvalidScope : 116 | return "AudioUnit InvalidScope" 117 | 118 | case kAudioUnitErr_PropertyNotWritable : 119 | return "AudioUnit Property Not Writable" 120 | 121 | case kAudioUnitErr_InvalidPropertyValue : 122 | return "AudioUnitErr_InvalidPropertyValue" 123 | 124 | case kAudioUnitErr_PropertyNotInUse : 125 | return "kAudioUnit Property Not In Use" 126 | 127 | case kAudioUnitErr_Initialized : 128 | return "AudioUnitErr_Initialized" 129 | 130 | case kAudioUnitErr_InvalidOfflineRender : 131 | return "kAudioUnit Invalid Offline Render" 132 | 133 | case kAudioUnitErr_Unauthorized : 134 | return "AudioUnit Unauthorized" 135 | 136 | case kAudioUnitErr_CannotDoInCurrentContext: 137 | return "AudioUnit Cannot Do In Current Context" 138 | 139 | case kAudioUnitErr_FileNotSpecified: 140 | return "AudioUnit File Not Specified" 141 | 142 | case kAudioUnitErr_FormatNotSupported: 143 | return "AudioUnit Format Not Supported" 144 | 145 | case kAudioUnitErr_IllegalInstrument: 146 | return "AudioUnit Illegal Instrument" 147 | 148 | case kAudioUnitErr_Initialized: 149 | return "AudioUnit Initialized" 150 | 151 | case kAudioUnitErr_InstrumentTypeNotFound: 152 | return "AudioUnit Instrument Type Not Found" 153 | 154 | case kAudioUnitErr_InvalidFile: 155 | return "AudioUnit Invalid File" 156 | 157 | case kAudioUnitErr_InvalidOfflineRender: 158 | return "AudioUnit Invalid Offline Render" 159 | 160 | case kAudioUnitErr_InvalidScope: 161 | return "AudioUnit Invalid Scope" 162 | 163 | case kAudioUnitErr_PropertyNotInUse: 164 | return "AudioUnit Property Not In Use" 165 | 166 | case kAudioUnitErr_PropertyNotWritable: 167 | return "AudioUnit Property Not Writable" 168 | 169 | case kAudioUnitErr_TooManyFramesToProcess: 170 | return "AudioUnit Too Many Frames To Process" 171 | 172 | case kAudioUnitErr_UnknownFileType: 173 | return "AudioUnit Unknown File Type" 174 | 175 | case kAudioComponentErr_InstanceInvalidated: 176 | return "AudioComponent Instance Invalidated" 177 | 178 | case kAudioComponentErr_DuplicateDescription: 179 | return "AudioComponent Duplicate Description" 180 | 181 | case kAudioComponentErr_UnsupportedType: 182 | return "AudioComponent Unsupported Type" 183 | 184 | case kAudioComponentErr_TooManyInstances: 185 | return "AudioComponent Too Many Instances" 186 | 187 | case kAudioComponentErr_NotPermitted: 188 | return "AudioComponent Not Permitted " 189 | 190 | case kAudioComponentErr_InitializationTimedOut: 191 | return "AudioComponent Initialization Timed Out" 192 | 193 | case kAudioComponentErr_InvalidFormat: 194 | return "AudioComponent Invalid Format" 195 | 196 | case kAudio_UnimplementedError : 197 | return "Audio Unimplemented Error" 198 | 199 | case kAudio_FileNotFoundError : 200 | return "Audio File Not Found Error" 201 | 202 | case kAudio_FilePermissionError : 203 | return "Audio File Permission Error" 204 | 205 | case kAudio_TooManyFilesOpenError : 206 | return "Audio Too Many Files Open Error" 207 | 208 | case kAudio_BadFilePathError : 209 | return "Audio Bad File Path Error" 210 | 211 | case kAudio_ParamError : 212 | return "Audio Param Error" 213 | 214 | case kAudio_MemFullError : 215 | return "Audio Mem Full Error" 216 | 217 | default: 218 | return "Unknown Audio Error" 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /Jamulator/VoiceCategoryButton.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Eric Ford Consulting 3 | */ 4 | 5 | import Foundation 6 | import UIKit 7 | 8 | class VoiceCategoryButton { 9 | var name: String 10 | var number: Int 11 | var x, y, width, height: CGFloat 12 | var color: UIColor 13 | var hiliteColor: UIColor 14 | var path: UIBezierPath 15 | var selected: Bool = false 16 | var alpha: CGFloat 17 | 18 | init (name: String, number: Int, x: CGFloat, y: CGFloat, 19 | width: CGFloat, height: CGFloat, color: UIColor, hiliteColor: UIColor) { 20 | self.name = name 21 | self.number = number 22 | self.x = CGFloat(x) 23 | self.y = CGFloat(y) 24 | self.width = CGFloat(width) 25 | self.height = CGFloat(height) 26 | self.color = color 27 | self.hiliteColor = hiliteColor 28 | self.path = UIBezierPath() 29 | self.alpha = 1.0 30 | 31 | resetPath() 32 | } 33 | 34 | func resetPath() { 35 | path = UIBezierPath() 36 | self.path.move(to: CGPoint(x: self.x, y: self.y)) 37 | self.path.addLine(to: CGPoint(x: self.x + self.width, y: self.y)) 38 | self.path.addLine(to: CGPoint(x: self.x + self.width, y: self.y + self.height)) 39 | self.path.addLine(to: CGPoint(x: self.x, y: self.y + self.height)) 40 | self.path.addLine(to: CGPoint(x: self.x, y: self.y)) 41 | self.path.lineWidth = 1.0 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Jamulator/VoiceSelectorButton.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Eric Ford Consulting 3 | */ 4 | 5 | import Foundation 6 | import UIKit 7 | 8 | class VoiceSelectorButton { 9 | var name: String 10 | var voiceNumber: Int 11 | var channel: Int 12 | var x, y, width, height: CGFloat 13 | var color: UIColor 14 | var hiliteColor: UIColor 15 | var path: UIBezierPath 16 | var selected: Bool = false 17 | var alpha: CGFloat 18 | 19 | init (voiceName: String, voiceNumber: Int, channel: Int, x: CGFloat, y: CGFloat, 20 | width: CGFloat, height: CGFloat, color: UIColor, hiliteColor: UIColor) { 21 | self.name = voiceName 22 | self.voiceNumber = voiceNumber 23 | self.channel = channel 24 | self.x = CGFloat(x) 25 | self.y = CGFloat(y) 26 | self.width = CGFloat(width) 27 | self.height = CGFloat(height) 28 | self.color = color 29 | self.hiliteColor = hiliteColor 30 | self.path = UIBezierPath() 31 | self.alpha = 1.0 32 | 33 | resetPath() 34 | } 35 | 36 | func resetPath() { 37 | path = UIBezierPath() 38 | self.path.move(to: CGPoint(x: self.x, y: self.y)) 39 | self.path.addLine(to: CGPoint(x: self.x + self.width, y: self.y)) 40 | self.path.addLine(to: CGPoint(x: self.x + self.width, y: self.y + self.height)) 41 | self.path.addLine(to: CGPoint(x: self.x, y: self.y + self.height)) 42 | self.path.addLine(to: CGPoint(x: self.x, y: self.y)) 43 | self.path.lineWidth = 1.0 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Jamulator/VoiceSelectorView.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017 Eric Ford Consulting 3 | */ 4 | 5 | import Foundation 6 | import UIKit 7 | 8 | class VoiceSelectorView : UIView { 9 | var voiceCategoryButtons: [VoiceCategoryButton] 10 | var voiceSelectorButtons: [VoiceSelectorButton] 11 | var synth: SoftSynth? 12 | var voiceNowPlaying: Int 13 | var categorySelected: Int 14 | let borderColor = UIColor(hex: 0x214627) 15 | let categoryTextColor = UIColor(hex: 0x418647) 16 | let voiceTextColor = UIColor(hex: 0xe7ffe9) 17 | let categoryBackgroundColor = UIColor(hex: 0xb5e6b7) 18 | let voiceBackgroundColor = UIColor(hex: 0x73a95e) 19 | let loadingTextColor = UIColor(hex: 0xd7ffd9) 20 | /* 21 | let loadingTextColor = UIColor(red: 0.2, green: 0.2, blue: 0.9, alpha: 1.0) 22 | let categoryBackgroundColor = UIColor(red: 0.9, green: 0.9, blue: 1.0, alpha: 1.0) 23 | let categoryTextColor = UIColor(red: 0.4, green: 0.4, blue: 1.0, alpha: 1.0) 24 | let voiceBackgroundColor = UIColor(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0) 25 | let voiceTextColor = UIColor(red: 0.2, green: 0.2, blue: 0.9, alpha: 1.0) 26 | */ 27 | var showVoices = false 28 | var loadingDisplayIndex = 0 29 | var timer: Timer? 30 | 31 | let voiceCategoryNames = ["Pianos", "Mallets", "Organs", "Guitars", "Basses", "Strings", "Ensmbles", "Brass", 32 | "Reeds", "Pipes", "Synth Leads", "Synth Pads", "Synth Effects", "Ethnic", "Percussive", "Sound Effects"] 33 | 34 | let voices = [ 35 | // pianos 36 | "Grand Piano", "Bright Piano", "Electric Grand", "Honky-Tonk", 37 | "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", 38 | // mallets 39 | "Celesta", "Glockenspiel", "Music Box", "Vibraphone", 40 | "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", 41 | // organs 42 | "Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ", 43 | "Reed Organ", "Accordion", "Harmonica", "Tango Accordion", 44 | // guitars 45 | "Nylon Guitar", "Steel Guitar", "Jazz Guitar", "Clean Guitar", 46 | "Muted Guitar", "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", 47 | // basses 48 | "Acoustic Bass", "Finger Bass", "Pick Bass", "Fretless Bass", 49 | "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", 50 | // strings 51 | "Violin", "Viola", "Cello", "Contrabass", 52 | "Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani", 53 | // ensembles 54 | "String Ensemble 1", "String Ensemble 2", "Synth Strings 1", "Synth Strings 2", 55 | "Choir Aahs", "Voice Oohs", "Synth Choir", "Orchestra Hit", 56 | // brass 57 | "Trumpet", "Trombone", "Tuba", "Muted Trumpet", 58 | "French Horn", "Brass Section", "Synth Brass 1", "Synth Brass 2", 59 | // reeds 60 | "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", 61 | "Oboe", "English Horn", "Bassoon", "Clarinet", 62 | // pipes 63 | "Piccolo", "Flute", "Recorder", "Pan Flute", 64 | "Blown bottle", "Shakuhachi", "Whistle", "Ocarina", 65 | // synth leads 66 | "Square", "Sawtooth", "Calliope", "Chiff", 67 | "Charang", "Voice", "Fifths", "Bass + Lead", 68 | // synth pads 69 | "New Age", "Warm", "Polysynth", "Choir", 70 | "Bowed", "Metallic", "Halo", "Sweep", 71 | // synth effects 72 | "Rain", "Soundtrack", "Crystal", "Atmosphere", 73 | "Brightness", "Goblins", "Echoes", "Sci-fi", 74 | // ethnic 75 | "Sitar", "Banjo", "Shamisen", "Koto", 76 | "Kalimba", "Bagpipe", "Fiddle", "Shanai", 77 | // percussive 78 | "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", 79 | "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", 80 | // sound effects 81 | "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", 82 | "Telephone Ring", "Helicopter", "Applause", "Gunshot" 83 | ] 84 | 85 | convenience init (frame: CGRect, synth: SoftSynth) { 86 | self.init(frame: frame) 87 | self.synth = synth 88 | } 89 | 90 | override init(frame: CGRect) { 91 | voiceCategoryButtons = [] 92 | voiceSelectorButtons = [] 93 | voiceNowPlaying = 0 94 | categorySelected = 0 95 | 96 | for (index, name) in voiceCategoryNames.enumerated() { 97 | let row = CGFloat(index / 4) 98 | let column = CGFloat(index % 4) 99 | let width = frame.width / 4 100 | let height = frame.height / 8 101 | voiceCategoryButtons.append(VoiceCategoryButton( 102 | name: name, number: index + 1, x: column * width, y: row * height, 103 | width: width, height: height, color: categoryBackgroundColor, hiliteColor: categoryTextColor 104 | )) 105 | } 106 | voiceCategoryButtons[categorySelected].selected = true 107 | 108 | for (index, voice) in voices.enumerated() { 109 | let row = CGFloat((index / 4) % 2) 110 | let column = CGFloat(index % 4) 111 | let top = (frame.height / 2) + (row * (frame.height / 4)) 112 | let width = frame.width / 4 113 | let height = (frame.height / 4) 114 | voiceSelectorButtons.append(VoiceSelectorButton( 115 | voiceName: voice, voiceNumber: index + 1, channel: 0, x: column * width, y: top, 116 | width: width, height: height, color: voiceBackgroundColor, hiliteColor: voiceTextColor)) 117 | } 118 | voiceSelectorButtons[voiceNowPlaying].selected = true 119 | 120 | super.init(frame: frame) 121 | backgroundColor = UIColor(red: 0.8, green: 0.8, blue: 1.0, alpha: 1.0) 122 | setNeedsDisplay() 123 | } 124 | 125 | required init?(coder aDecoder: NSCoder) 126 | { 127 | voiceCategoryButtons = [] 128 | voiceSelectorButtons = [] 129 | voiceNowPlaying = 0 130 | categorySelected = 0 131 | super.init(coder: aDecoder) 132 | } 133 | 134 | override func draw(_ rect: CGRect) { 135 | if showVoices { 136 | for button: VoiceCategoryButton in voiceCategoryButtons { 137 | drawCategoryButton(button: button, rect: rect) 138 | } 139 | for button: VoiceSelectorButton in voiceSelectorButtons { 140 | if button.voiceNumber >= ((8 * categorySelected) + 1) && button.voiceNumber <= ((8 * (categorySelected + 1))) { 141 | drawVoiceButton(button: button, rect: rect) 142 | } 143 | } 144 | drawBorders(rect: rect) 145 | } else { 146 | drawLoadingMessage(rect: rect) 147 | } 148 | } 149 | 150 | // MARK: - methods used in draw() 151 | 152 | func drawBorders(rect: CGRect) 153 | { 154 | borderColor.set() 155 | let frame = UIBezierPath(rect: rect) 156 | frame.lineWidth = 4 157 | frame.stroke() 158 | let midLine = UIBezierPath() 159 | midLine.lineWidth = 2 160 | midLine.move(to: CGPoint(x: 0, y: rect.origin.y + (rect.size.height / 2))) 161 | midLine.addLine(to: CGPoint(x: rect.size.width, y: rect.origin.y + (rect.size.height / 2))) 162 | midLine.stroke() 163 | } 164 | 165 | func getTextAttributes(fontSize: CGFloat, textColor: UIColor) -> [String: AnyObject] { 166 | let paragraphStyle = NSMutableParagraphStyle() 167 | paragraphStyle.alignment = .center 168 | var textAttributes: [String: AnyObject] = [ 169 | NSForegroundColorAttributeName: textColor, 170 | NSParagraphStyleAttributeName: paragraphStyle 171 | ] 172 | if let font = UIFont(name: "MarkerFelt-Thin", size: fontSize) { 173 | textAttributes[NSFontAttributeName] = font 174 | } 175 | return textAttributes 176 | } 177 | 178 | func drawButtonName(name: String, fontSize: CGFloat, x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat, color: UIColor) { 179 | let textRect = CGRect(x: x, y: y + ScreenUtils.getAdjustedTextYOffset(), width: width, height: height) 180 | String(name).draw(in: textRect, withAttributes: getTextAttributes(fontSize: fontSize, textColor: color)) 181 | } 182 | 183 | func drawCategoryButton(button: VoiceCategoryButton, rect: CGRect) { 184 | var textColor: UIColor 185 | button.path.lineWidth = 2 186 | button.path.stroke() 187 | if button.selected { 188 | button.hiliteColor.set() 189 | textColor = button.color 190 | } else { 191 | button.color.set() 192 | textColor = button.hiliteColor 193 | } 194 | button.path.fill() 195 | drawButtonName(name: button.name, fontSize: ScreenUtils.getAdjustedFontSize() * 1.5, x: button.x, y: button.y, width: rect.width / 4, height: rect.height / 8, color: textColor) 196 | } 197 | 198 | func drawVoiceButton(button: VoiceSelectorButton, rect: CGRect) { 199 | let fudgeFactor = UIScreen.main.bounds.width / 60 200 | var textColor: UIColor 201 | if button.selected { 202 | button.hiliteColor.set() 203 | textColor = button.color 204 | } else { 205 | button.color.set() 206 | textColor = button.hiliteColor 207 | } 208 | button.path.fill() 209 | drawButtonName(name: button.name, fontSize: ScreenUtils.getAdjustedFontSize() * 1.9, x: button.x, y: button.y + fudgeFactor, width: rect.width / 4, height: rect.height / 4, color: textColor) 210 | } 211 | 212 | // The voice file is 140 meg. The simulator loads it very slowly. 213 | // We'll show one voice name per second until the file is loaded. 214 | // On device hardware this may never actually get seen. 215 | // The completion routine in the controller sets showVoices to true. 216 | @objc func incrementDisplayIndex() { 217 | if showVoices { 218 | timer?.invalidate() 219 | } else { 220 | if loadingDisplayIndex + 1 < voices.count { 221 | loadingDisplayIndex += 1 222 | } else { 223 | loadingDisplayIndex = 0 224 | } 225 | } 226 | setNeedsDisplay() 227 | } 228 | 229 | func drawLoadingMessage(rect: CGRect) { 230 | let textRect = CGRect(x: rect.origin.x, y: rect.height / 6, width: rect.width, height: rect.height / 2) 231 | drawLoadingString(text: "Loading Voice " + String(loadingDisplayIndex) + " of " + String(voices.count), rect: textRect, fontSize: ScreenUtils.getAdjustedFontSize() * 3) 232 | let subtextRect = CGRect(x: rect.origin.x, y: rect.origin.y + rect.height / 2, width: rect.width, height: rect.height / 4) 233 | drawLoadingString(text: voices[loadingDisplayIndex], rect: subtextRect, fontSize: ScreenUtils.getAdjustedFontSize() * 2) 234 | let subsubtextRect = CGRect(x: rect.origin.x, y: rect.origin.y + (rect.height / 4) * 3, width: rect.width, height: rect.height / 4) 235 | drawLoadingString(text: "You can play the piano while you wait, but there's only one voice", rect: subsubtextRect, fontSize: ScreenUtils.getAdjustedFontSize() * 1.5) 236 | } 237 | 238 | func drawLoadingString(text: String, rect: CGRect, fontSize: CGFloat) { 239 | let paragraphStyle = NSMutableParagraphStyle() 240 | paragraphStyle.alignment = .center 241 | var textAttributes: [String: AnyObject] 242 | textAttributes = [ 243 | NSForegroundColorAttributeName: loadingTextColor, 244 | NSParagraphStyleAttributeName: paragraphStyle 245 | ] 246 | if let font = UIFont(name: "MarkerFelt-Thin", size: fontSize) { 247 | textAttributes[NSFontAttributeName] = font 248 | } 249 | text.draw(in: rect, withAttributes: textAttributes) 250 | } 251 | 252 | // MARK: - handle touches 253 | 254 | // UIBezierPath has a contains() method 255 | // Very handy for drawing touchable shapes 256 | func categoryIndexFromTouch(_ touch: UITouch) -> Int { 257 | var result = -1 258 | let location = touch.location(in: self) 259 | for (index, button) in voiceCategoryButtons.enumerated() { 260 | if(button.path.contains(location)) { 261 | result = index 262 | break 263 | } 264 | } 265 | return result 266 | } 267 | 268 | // This is similar to categoryIndexFromTouch. 269 | // Each category of voices has 8 instruments. 270 | // So we just check to make sure it's in the selected category 271 | // before drawing it. 272 | func buttonIndexFromTouch(_ touch: UITouch) -> Int { 273 | var result = -1 274 | let location = touch.location(in: self) 275 | for (index, button) in voiceSelectorButtons.enumerated() { 276 | if button.voiceNumber >= ((8 * categorySelected) + 1) && button.voiceNumber <= ((8 * (categorySelected + 1)) + 1) { 277 | if(button.path.contains(location)) { 278 | result = index 279 | break 280 | } 281 | } 282 | } 283 | return result 284 | } 285 | 286 | // Notice that we are only allowing one touch at a time. 287 | // We could check all of the touches, but in this case the 288 | // upper buttons change the lower ones, so it wouldn't be 289 | // very intuitive. 290 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 291 | let categoryIndex = categoryIndexFromTouch(touches.first!) 292 | if categoryIndex != -1 { 293 | voiceCategoryButtons[categoryIndex].selected = true 294 | voiceCategoryButtons[categorySelected].selected = false 295 | categorySelected = categoryIndex 296 | } 297 | 298 | let buttonIndex = buttonIndexFromTouch(touches.first!) 299 | if buttonIndex != -1 { 300 | voiceSelectorButtons[buttonIndex].selected = true 301 | voiceSelectorButtons[voiceNowPlaying].selected = false 302 | voiceNowPlaying = buttonIndex 303 | synth?.loadPatch(patchNo: voiceNowPlaying) 304 | Sequencer.shared.patch = UInt32(voiceNowPlaying) 305 | } 306 | setNeedsDisplay() 307 | } 308 | 309 | // MARK: - methods called from the controller 310 | func setTimer() { 311 | timer = Timer.scheduledTimer(timeInterval: 0.7, target: self, selector: #selector(incrementDisplayIndex), userInfo: nil, repeats: true) 312 | } 313 | 314 | // called when the voices are available 315 | func setShowVoices(show: Bool) { 316 | showVoices = show 317 | setNeedsDisplay() 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jamulator 2 | --------------------------------------------------------------------------------