├── .gitignore ├── .travis.yml ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── RomPlayer.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── RomPlayer.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist └── RomPlayer ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ ├── Contents.json │ ├── Icon-60@2x.png │ ├── Icon-60@3x.png │ ├── Icon-76.png │ ├── Icon-76@2x.png │ └── Icon-83.5@2x.png ├── Contents.json ├── LaunchImage.launchimage │ ├── Contents.json │ ├── RomPlayerLaunchImage.png │ └── RomPlayerLaunchImage@2x.png ├── au_main.imageset │ ├── Contents.json │ └── au_main@2x.png ├── bluetooth.imageset │ ├── Contents.json │ └── bluetooth@2x.png ├── dice.imageset │ ├── Contents.json │ └── dice@2x.png ├── headerback.imageset │ ├── Contents.json │ └── headerback@2x.png ├── left-arrow.imageset │ ├── Contents.json │ └── left-arrow@2x.png ├── mockup_blackkeys.imageset │ ├── Contents.json │ └── mockup_blackkeys@2x.png ├── mockup_whitekeys.imageset │ ├── Contents.json │ └── mockup_whitekeys@2x.png └── right-arrow.imageset │ ├── Contents.json │ └── right-arrow@2x.png ├── AudioSystem ├── AutoPan.swift ├── Conductor.swift ├── Fatten.swift ├── FilterSection.swift └── PingPongDelay.swift ├── Base.lproj └── Main.storyboard ├── Cells └── PresetCell.swift ├── Controllers ├── AUMainController.swift └── ParentViewController.swift ├── Controls ├── Buttons │ ├── ButtonStyleKit.swift │ ├── FlatToggleStyleKit.swift │ ├── LedButton.swift │ ├── LedToggleStyleKit.swift │ ├── MIDIButton.swift │ ├── RadioButton.swift │ ├── Stepper.swift │ ├── StepperStyleKit.swift │ ├── SynthUIButton.swift │ ├── ToggleButton.swift │ ├── TopUIButton.swift │ ├── TopUIButtonStyleKit.swift │ └── UrlButton.swift ├── Knobs │ ├── FlatKnob.swift │ ├── FlatKnobStyleKit.swift │ ├── Knob+Touches.swift │ ├── Knob.swift │ ├── KnobStyleKit2.swift │ └── MIDIKnob.swift ├── MIDILearnable.swift └── SynthKeyboard.swift ├── Extensions ├── Double+Extensions.swift ├── UILabel+Rotate.swift └── ViewController+DisplayAlert.swift ├── Images.xcassets └── LaunchImage.launchimage │ ├── Contents.json │ └── launchscreen@2x.png ├── Info.plist ├── PopUpControllers ├── AboutViewController.swift ├── PopUpKeySettingsController.swift ├── PopUpMIDIController.swift └── PopUpPresetController.swift ├── RomPlayer.entitlements └── Sounds ├── .DS_Store ├── midi ├── rom_bass.mid ├── rom_lead.mid └── rom_poly.mid └── sfz ├── .DS_Store ├── TX Brass.sfz ├── TX LoTine81z.sfz ├── TX Metalimba.sfz ├── TX Pluck Bass.sfz └── samples ├── TX_Chorus_Bras-000-048-c2.wv ├── TX_Chorus_Bras-000-054-f#2.wv ├── TX_Chorus_Bras-000-060-c3.wv ├── TX_Chorus_Bras-000-066-f#3.wv ├── TX_Chorus_Bras-000-072-c4.wv ├── TX_Chorus_Bras-000-078-f#4.wv ├── TX_Chorus_Bras-000-084-c5.wv ├── TX_LoTine81z_ms0_048_c2.wv ├── TX_LoTine81z_ms0_054_f#2.wv ├── TX_LoTine81z_ms0_060_c3.wv ├── TX_LoTine81z_ms0_066_f#3.wv ├── TX_LoTine81z_ms0_072_c4.wv ├── TX_LoTine81z_ms0_078_f#4.wv ├── TX_LoTine81z_ms0_084_c5.wv ├── TX_LoTine81z_ms1_048_c2.wv ├── TX_LoTine81z_ms1_054_f#2.wv ├── TX_LoTine81z_ms1_060_c3.wv ├── TX_LoTine81z_ms1_066_f#3.wv ├── TX_LoTine81z_ms1_072_c4.wv ├── TX_LoTine81z_ms1_078_f#4.wv ├── TX_LoTine81z_ms1_084_c5.wv ├── TX_LoTine81z_ms2_048_c2.wv ├── TX_LoTine81z_ms2_054_f#2.wv ├── TX_LoTine81z_ms2_060_c3.wv ├── TX_LoTine81z_ms2_066_f#3.wv ├── TX_LoTine81z_ms2_072_c4.wv ├── TX_LoTine81z_ms2_078_f#4.wv ├── TX_LoTine81z_ms2_084_c5.wv ├── TX_Metalimba-000-048-c2.wv ├── TX_Metalimba-000-054-f#2.wv ├── TX_Metalimba-000-060-c3.wv ├── TX_Metalimba-000-066-f#3.wv ├── TX_Metalimba-000-072-c4.wv ├── TX_Metalimba-000-078-f#4.wv ├── TX_Metalimba-000-084-c5.wv ├── TX_Pluck_Bass-000-048-c2.wv ├── TX_Pluck_Bass-000-054-f#2.wv ├── TX_Pluck_Bass-000-060-c3.wv ├── TX_Pluck_Bass-000-066-f#3.wv ├── TX_Pluck_Bass-000-072-c4.wv ├── TX_Pluck_Bass-000-078-f#4.wv └── TX_Pluck_Bass-000-084-c5.wv /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | 69 | # CocoaPods 70 | Pods/* 71 | 72 | # macOS Noise 73 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode11.3 3 | env: 4 | - LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 5 | before_install: 6 | - gem install xcpretty -N 7 | - pod install 8 | script: 9 | - xcodebuild -workspace RomPlayer.xcworkspace -sdk iphonesimulator -scheme RomPlayer -arch x86_64 ONLY_ACTIVE_ARCH=YES CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" clean build 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Aurelius Prochazka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '12.0' 2 | use_frameworks! 3 | 4 | target 'RomPlayer' do 5 | pod 'AudioKit' 6 | end -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AudioKit (4.9.3): 3 | - AudioKit/Core (= 4.9.3) 4 | - AudioKit/UI (= 4.9.3) 5 | - AudioKit/Core (4.9.3) 6 | - AudioKit/UI (4.9.3): 7 | - AudioKit/Core 8 | 9 | DEPENDENCIES: 10 | - AudioKit 11 | 12 | SPEC REPOS: 13 | trunk: 14 | - AudioKit 15 | 16 | SPEC CHECKSUMS: 17 | AudioKit: d025ecbb68f567bba15dab7ea80b8991155729f9 18 | 19 | PODFILE CHECKSUM: 3b2463eec4d0e3087dd3aaa060c070490cce1fbb 20 | 21 | COCOAPODS: 1.8.3 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AudioKit ROM / Sample Player 2 | 3 | [![Build Status](https://travis-ci.org/AudioKit/ROMPlayer.svg)](https://travis-ci.org/AudioKit/ROMPlayer) 4 | [![License](https://img.shields.io/cocoapods/l/AudioKit.svg?style=flat)](https://github.com/AudioKit/ROMPlayer/blob/master/LICENSE) 5 | [![Twitter Follow](https://img.shields.io/twitter/follow/AudioKitPro.svg?style=social)](http://twitter.com/AudioKitPro) 6 | 7 | ![AK ROM Player](https://i.imgur.com/B9NJ6Ps.png) 8 | 9 | Welcome to the official AudioKit example of a sample-based music instrument written in Swift. It can be modified to play EXS24, Wave, or Sound Fonts. This code is lightweight and demonstrates how you can make a beautiful sounding, pro-level instrument with small amount of code. 10 | 11 | If you're new to [AudioKit](https://audiokitpro.com/), you can learn more and view getting started links: [here](https://audiokitpro.com/audiokit/). 12 | 13 | ## App Store Example 14 | 15 | Video introduction for this app and project: 16 | [https://vimeo.com/244897673](https://vimeo.com/244897673) 17 | 18 | The AudioKit FM Player is built with this code: 19 | [Download in App Store here](https://itunes.apple.com/us/app/fm-player-classic-dx-synths/id1307785646?ls=1&mt=8). 20 | 21 | ## New AKSampler Version ## 22 | This updated code is powered by the new, superior [AKSampler](https://github.com/AudioKit/AudioKit/blob/v4.2/Documentation/AKSampler.md) code written by Shane Dunne. Special thanks to [Shane Dunne](https://github.com/getdunne) for his great work in making this happen. There is a [legacy branch](https://github.com/AudioKit/ROMPlayer/tree/legacy-avsampler) for the previous code based on Apple's sample-playback code. The legacy code plays EXS files. While this new code plays SFZ files. 23 | 24 | Shane has also documented tips for [preparing samples](https://github.com/AudioKit/AudioKit/blob/v4.2/Documentation/PreparingSampleSets.md) to use with the new AKSampler. 25 | 26 | ## Code Features 27 | 28 | - Beautiful sound engine 29 | - MIDI input for notes, pitch bend, mod wheel, after touch 30 | - On screen "Piano" keyboard that can be customized 31 | - Reverb, Delay, Bitcrush, AutoPan, Stereo Fatten 32 | - Lowpass Filter and LFO 33 | - MIDI Learn knobs 34 | - Features the new cross-platform AKSampler engine 35 | - Example code written entirely in Swift 4 & AudioKit 4 36 | - Attack and Release Knobs 37 | 38 | ## Getting Started 39 | 40 | I have tried to streamline this code. There is a focus on the core-functionality to make it easy-to-understand. That being said, audio programming can be challenging. This may be difficult for complete beginners. And, new frameworks can be overwhelming, even for experienced developers. 41 | 42 | **CocoaPods** 43 | This repo uses CocoaPods to easily add AudioKit to your project. 44 | 45 | Using the `Terminal` app in your mac, change directories to the folder that contains this project. The correct directory contains a file called `podfile` 46 | 47 | Run `pod install` from the command line. This will add AudioKit & AudioKit UI to project 48 | 49 | Then open `RomPlayer.xcworkspace` in Xcode 50 | 51 | 52 | ## Requirements 53 | 54 | - Mac or computer running Xcode 9 ([Free Download](https://itunes.apple.com/us/app/xcode/id497799835?mt=12)) 55 | - Some knowledge of programming, specifically Swift & the iOS SDK 56 | 57 | If you are new to iOS development, I highly recommend the [Ray Wenderlich](https://www.raywenderlich.com/) videos. There is also a great tutorial on basic synthesis with AudioKit [here.](https://www.raywenderlich.com/145770/audiokit-tutorial-getting-started) 58 | 59 | 60 | ## Included Sounds 61 | 62 | ![AK Sample Player](https://i.imgur.com/8FiDeJH.png) 63 | 64 | In this repo, I've included four preset sounds I sampled from my TX81z hardware FM synthesizer using the sampling software [SampleRobot](http://www.samplerobot.com). The LoTine81z sound includes 3 velocity layers. The other sounds include a few samples to demonstrate the sounds while keeping the repo size tight. 65 | 66 | 67 | You are free to use the instruments included in this repo as you see fit- In a game, music app, or just for whatever. It would be cool if you didn't resell them. 68 | 69 | AKSampler reads `.wv` files compressed using the open-source [Wavpack](http://www.wavpack.com) software. On the Mac, you must first install the Wavpack command-line tools. Then you can use the following Python 2 script to compress a whole folder-full of `.wav` files. See this excellent [documentation](https://github.com/AudioKit/AudioKit/blob/v4.2/Documentation/PreparingSampleSets.md) by Shane Dunne for more info on preparing samples. 70 | 71 | 72 | ## Using Samples 73 | 74 | You can replace the included example sample instruments with your own instruments. 75 | 76 | ![add samples](https://i.imgur.com/TX0j9dy.jpg) 77 | 78 | 1. Add your SFZ files and samples to the `/Sounds/sfz/` directory 79 | 2. Type in the name of the instruments in the ParentViewController.swift file 80 | 81 | **Other Sampler File Formats** 82 | You can use a tool you already know (such as Kontakt) to create and arrange the sample instrument key mapping and velocity layers. Then, you can easily convert Kontakt instruments to SFZ with tools such as [this](http://www.chickensys.com/translator/). Then, remake the effects using AudioKit. That way, you'll have dynamic sounds and complete control over the effects. 83 | 84 | This example code loads SFZ intruments. It is recommended that you convert your file formats to SFZ. If you have to use EXS or SF2, there are [other types](https://github.com/AudioKit/AudioKit/blob/v4.2/Documentation/Samplers.md) of sample playback routines in AudioKit you can use instead. 85 | 86 | 87 | ## Sound Manipulation 88 | 89 | There are all kinds of filters, effects, and other audio manipulation classes included with AudioKit to get you started. You can browse the documentation [here](http://audiokit.io/docs/index.html). 90 | 91 | And, explore over [100+ playgrounds](http://audiokit.io/playgrounds/), created by the lovely & talented [Aure Prochazka](https://twitter.com/audiokitman). These byte size code playgrounds allow you to easily experiment with sound & code. 92 | 93 | You may also want to explore AU Lab, a free tool from Apple. It is available from the "more" section of the [Apple Developer portal](https://developer.apple.com/download/more/). As of this text, the current version is in the [Additional Tools for Xcode 9 package](https://download.developer.apple.com/Developer_Tools/Additional_Tools_for_Xcode_9/Additional_Tools_for_Xcode_9.dmg). 94 | 95 | Additionally, these [docs and tips](https://developer.apple.com/library/content/technotes/tn2331/_index.html) will also prove valuable if you want to dive in at a deeper level than the AKSampler. 96 | 97 | ## Making Graphics 98 | 99 | IMPORTANT: You need to change the graphics to upload an app to the app store. Apple does not allow apps to use stock template graphics. Plus, you want your app to be part of the expression of your artistic vision. 100 | 101 | For example, if you were releasing a new music album, you would not want to use someone else's album artwork. You would want your own! 102 | 103 | Think of the GUI as an extension of your sample/music artform. It is a way to impress upon users your own style and give them a feel for your sonic personality. 104 | 105 | If graphic coding is not your cup of tea, the easiest way to make synth controls and knobs with code is to use [PaintCode](https://www.paintcodeapp.com/). I made almost all the graphic elements for this app with PaintCode. I've included the PaintCode source files for most of these UI elements [here](https://github.com/AudioKit/AudioKitGraphics). You can use them as a starting place to learn how to make controls. You can start with these files and change the color, sizes, etc. 106 | 107 | Luckily, I've already included the coding part of handling knobs in this repo. You only have to worry about the graphics. 108 | 109 | ![knob in ib](https://i.imgflip.com/1svkul.gif) 110 | 111 | Or, if you want to just completely use graphics instead of code - 112 | 113 | If you'd rather make knobs and controls with a graphic rendering software packgage that exports image frames (or a dedicated tool like KnobMan), here's some example code I wrote demonstrating using images to create knobs [here](https://github.com/analogcode/3D-Knobs). 114 | 115 | ![Knobs](http://audiokitpro.com/images/knob.gif) 116 | 117 | ## Code Usage 118 | 119 | You are free to: 120 | 121 | (1) Use this app as a learning tool. 122 | (2) Re-skin this app (change the graphics), use your own sound samples, and upload to the app store. 123 | (3) Change the graphics, use your own sounds, and include this as part of a bigger app you are building. 124 | (4) Contribute code back to this project and improve this code for other people. 125 | 126 | If you use any code, it would be great if you gave this project some credit or a mention. The more love this code receives, the better we can make it for everyone. And, always give AudioKit a shout-out when you can! :) 127 | 128 | If you make an app with this code, please let us know! We think you're awesome, and would love to hear from you and/or feature your app. 129 | 130 | IMPORTANT: You must change the graphics and sounds if you upload this to the app store. 131 | 132 | ## What Sounds Can You Use In Your App? 133 | 134 | ![DX](https://i.imgur.com/g86gqfI.png) 135 | 136 | Please get permission from the content creators before you use any free sounds on the internet. Even if sounds are available for free, it does not mean they are licensed to be used in an interactive app. 137 | 138 | The best thing to do is to create or sample your own custom instruments. Generally, you can sample an acoustic instrument or voice without worry. This includes things like Pianos, Flutes, Horns, Human Voice, Guitars, Hand Claps, Foot stomps, etc. 139 | 140 | There is a gray area when it comes to keyboards. You can sample pure synthesizers. However, you can not sample keyboards and synthesizers based on PCM or wavetable samples. E.g. A vintage Juno 106 can be legally sampled. But, a modern Juno can not. The modern version uses recorded oscillator waveforms for its source sounds. A Mini-Moog or DX7 can be sampled. But, from the same era, the Roland D-50 or Korg M1 can not. As they use short PCM samples mixed with the oscillators. 141 | 142 | More examples: A Korg MS-20 can be sampled. However, a microKORG can not. (As the microKORG uses digital audio samples for its oscillators). Modern soft synths like Massive and Serum are also waveform based and can not be sampled. It is also illegal to sample other people's sample libraries and sample based apps (like the app store version of this code). Additionally, modern hardware keyboard workstations are almost completely sample-based and you can not sample anything from those legally. 143 | 144 | Many companies will not hesitate to send you a Cease & Desist notice. For example, hardware manufacturers have shut down and/or sued many apps, VSTs, and sample libraries (including popular apps like Rebirth). They have even shut down a free and open-source web app drum machine reminiscent of a TR-808. 145 | 146 | Bottom line: Even if your app is free and doesn't make any money, don't violate copyright laws. It will save you loads of headaches. And, allows you to focus on making something unique and creative. 147 | 148 | ## AudioBus & IAA MIDI 149 | 150 | AudioBus requires a unique key for every app. And, adds complexity to the code. As we have other examples of using AudioBus, it was left out of this example to make the ROM Player code focused and easier-to-understand. 151 | 152 | However, here's some tips: 153 | There's information on adding [AudioBus with AudioKit](http://audiokit.io/audiobus/) on our doc site. Plus, our [Analog Synth X](https://github.com/AudioKit/AudioKit/tree/master/Examples/iOS/AnalogSynthX) example code is an example of an app using AudioBus. 154 | 155 | Need more hints? I got you covered! 156 | 157 | Here's a gist of the AudioBus 3 MIDI listening code in FM Player: 158 | [https://gist.github.com/analogcode/fa097afb59ee57ccd29e59dfb2526977](https://gist.github.com/analogcode/fa097afb59ee57ccd29e59dfb2526977) 159 | 160 | **Host Icon** 161 | Here's how to add a Host Icon to your app (the icons for AudioBus 3, GarageBand, etc). 162 | 163 | Add these methods to your ParentViewController: 164 | [https://gist.github.com/analogcode/3b3dac2699a6e85c5d3fb86fe48e4ccb](https://gist.github.com/analogcode/3b3dac2699a6e85c5d3fb86fe48e4ccb) 165 | 166 | Then, add a button/imageview to your storyboard where you want the icon to appear. Connect it to the IBAction in the gist. 167 | 168 | **Is there a way to listen for IAA MIDI in Swift?** 169 | Here's a bit of code used in the FM Player to listen for IAA MIDI. I'm pretty sure there's a better way to do this. If anyone has any tips, please let us know. It was added to the ViewDidLoad method in the ParentViewController: 170 | [https://gist.github.com/analogcode/27b327d3ca71187ddc47715b19a50977](https://gist.github.com/analogcode/27b327d3ca71187ddc47715b19a50977) 171 | 172 | Transport Controls - I don't have any experience adding transport controls with Swift. If you get them to work, please get in touch so that we may help other people. 173 | 174 | 175 | ## FAQ 176 | 177 | Q: Is this free? I really don't have to pay you anything if I make an app with this code? 178 | 179 | A: Yes! You are correct. This is open source, you can do whatever you want with it. It's usually cool to thank the project if you use the code. Go build stuff. Enjoy. 180 | 181 | Q: How can I ever repay you? 182 | 183 | A: If you want to contribute to the AudioKit code, or ROM Player code, there are many things that can be improved. Or, you could pay it forward by helping other developers. 184 | 185 | Q: How do I save FX settings with each patch like in FM Player? 186 | 187 | A: Every developer has their own favorite solution for file persistance (saving/loading), we left that out to keep this code focused on music functionality. There are many robust solutions including Core Data and Realm. In FM Player, we used something lightweight and easy, [Disk](https://github.com/saoudrizwan/Disk). It is a wrapper for native Swift 4 functions. And, beginner friendly. Saving and loading data is a very common thing to do in iOS. There are many blogs, books, videos, and even non-music developers that can help you. 188 | 189 | Q: My EXS24 files work in the simulator. But, not in a device? 190 | 191 | A: The most common cause of this is that the file references in your EXS24 files are pointing to a folder specifically on your computer, and not relative to their current location. Users of your app will not have access to your computer. This is a good error to catch before uploading to the app store! 192 | 193 | Fixing EXS24 file references is beyond the scope of this readme... 194 | 195 | You can fix file refrences with Logic. Or, if you're making Kontakt Libraries and then converting to EXS24 using something like [Chicken Systems Translator](http://www.chickensys.com/translator/), you can fix the file references by making sure you have everything checked properly on [this screen](http://www.chickensys.com/translator/documentation/options/fixreferences.html). And, a good tip is to run the file translating on the EXS24 files again once they are ALREADY in your app bundle folder (/Sounds/Sampler Instruments/). Sometimes I run the translator on EXS24 files twice for good measure. 196 | 197 | If this is driving you mad, this [thread](https://github.com/audiokit/AudioKit/issues/903) will help. 198 | 199 | Q: How did you make sounds loop and sustain? (See the 'TX Brass' sample instrument for example) 200 | 201 | A: Luckily, there is no extra code needed. The loop points are set in the EXS24 files. For the looping presets in FM Player, I used Kontakt to set loop points in the audio files. When I converted to EXS, the loop points remained. The same is done with velocity layers. Those are all set in the sample files (and not in code). 202 | 203 | Q: How many notes and layers should I sample for an instrument? 204 | 205 | A: This is up to you. Sampling is the part of the process where you really get to use your artistic vision and ears. For FM Player, every preset sound was sampled with different settings. Here's the challenge: iOS storage space is at a premium. A sample library that takes up more space is not always better. More samples could lead to artifacts or loop point mistakes. The key is to find the right balance between storage space and musicality. Sample too much, eat up storage space. Sample not enough, miss out on tone variations and introduce aliasing. 206 | 207 | Q: That makes sense. But, what's a good starting place for sampling settings? 208 | 209 | A: For many instrument/synth sounds, 3 or 4 velocity layers is a good compromise between sound quality and storage space. Please keep in mind that many instruments do not have a linear tone curve. i.e. You may have to sample at different velocity points for each sound to capture its essence. 210 | 211 | Q: Do you recommend Mainstage AutoSampler? 212 | 213 | A: If you are making instruments for your own purposes, it might do the trick. It may not achieve the quality for a commercial "pro-level" iOS instrument. Professional sound designers and sample library producers spend as much time handcrafting, sampling, and fine-tuning sounds as developers spend writing code. If sampling is not your thing, you may want to partner with a talented sound designer. 214 | 215 | Q: I need help getting started. Can you Skype/help me? 216 | 217 | A: We are all volunteers. As we have many commitments, obligations, and projects, it is hard to find time for one-on-one assistance. Please do not be offended. For support, please visit [StackOverflow](https://stackoverflow.com/questions/tagged/audiokit?mixed=1) and tag your question “AudioKit”. We try to help where we can. Unfortunately, we do not have the bandwidth for phone or video support. By using StackOverflow it allows more people to benefit from your questions and answers. Thus, allowing us to help more people at once. We try to upvote and encourage all AudioKit questions. There is no shame with needing help or asking questions! You can often receive points on StackOverflow just for asking AudioKit related questions. 218 | 219 | ## Thanks and Credits 220 | 221 | Huge thanks to all the beta testers and the folks on the AudioKit Slack Group, AudioBus Forum, & Facebook iPad Musician group! Without your support and positive feedback and reviews, this would not be possible. 222 | 223 | Original ROM Player Code, UI, and Sounds by 224 | [Matthew M. Fecher](mailto:matthew@audiokitpro.com) | Twitter [@analogmatthew](http://twitter.com/analogmatthew) | Github [analogcode](http://github.com/analogcode) 225 | 226 | New AKSampler by 227 | [Shane Dunne](http://github.com/getdunne) 228 | 229 | AudioKit Founder [Aure Prochazka](http://twitter.com/audiokitman) 230 | 231 | 3D Renderings by [Kevin Loustau](https://twitter.com/KevinLoustau) 232 | 233 | Additional MIDI Enhancements by [Mark Jeschke](https://twitter.com/drumkickapp) 234 | 235 | This app would not be possible without all the AudioKit contributors: 236 | [AudioKit Contributions](https://github.com/AudioKit/AudioKit/graphs/contributors) 237 | 238 | ## Legal Notices 239 | 240 | This is an open-source project intended to bring joy and music to people, and enlighten people on how to build custom instruments and iOS apps. All product names and images, trademarks and artists names are the property of their respective owners, which are in no way associated or affiliated with the creators of this app, including AudioKit, AudioKit Pro, LLC, and the other contributors. Product names and images are used solely for the purpose of identifying the specific products related to synthesizers, sampling, sound design, and music making. Use of these names and images does not imply any cooperation or endorsement. Kontakt is a trademark property of Native Instruments. Roland, TR-808, D-50, Juno, and Juno 106 are trademarks property of Roland Corporation. Korg, microKorg, and MS-20 are trademarks of Korg Inc. Yamaha, DX7, DX7II, and TX81z are trademarks property of Yamaha Corporation. We appreciate their amazing work in creating such classic and inspiring instruments. 241 | 242 | This Readme text does not constitute legal advice. We take no responsibility for any actions resulting from using this code or sampling based on any of our advice or text. If you are unsure of your usage, please contact a music licensing attorney or the content creators of the sources you are sampling. 243 | -------------------------------------------------------------------------------- /RomPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RomPlayer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RomPlayer.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /RomPlayer.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RomPlayer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 7/20/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "size" : "20x20", 48 | "scale" : "1x" 49 | }, 50 | { 51 | "idiom" : "ipad", 52 | "size" : "20x20", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "idiom" : "ipad", 57 | "size" : "29x29", 58 | "scale" : "1x" 59 | }, 60 | { 61 | "idiom" : "ipad", 62 | "size" : "29x29", 63 | "scale" : "2x" 64 | }, 65 | { 66 | "idiom" : "ipad", 67 | "size" : "40x40", 68 | "scale" : "1x" 69 | }, 70 | { 71 | "idiom" : "ipad", 72 | "size" : "40x40", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "76x76", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-76.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "76x76", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-76@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "83.5x83.5", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-83.5@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "idiom" : "ios-marketing", 95 | "size" : "1024x1024", 96 | "scale" : "1x" 97 | } 98 | ], 99 | "info" : { 100 | "version" : 1, 101 | "author" : "xcode" 102 | } 103 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "ipad", 6 | "filename" : "RomPlayerLaunchImage.png", 7 | "extent" : "full-screen", 8 | "minimum-system-version" : "7.0", 9 | "scale" : "1x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "ipad", 14 | "filename" : "RomPlayerLaunchImage@2x.png", 15 | "extent" : "full-screen", 16 | "minimum-system-version" : "7.0", 17 | "scale" : "2x" 18 | } 19 | ], 20 | "info" : { 21 | "version" : 1, 22 | "author" : "xcode" 23 | } 24 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/LaunchImage.launchimage/RomPlayerLaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/LaunchImage.launchimage/RomPlayerLaunchImage.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/LaunchImage.launchimage/RomPlayerLaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/LaunchImage.launchimage/RomPlayerLaunchImage@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/au_main.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "au_main@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/au_main.imageset/au_main@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/au_main.imageset/au_main@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/bluetooth.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "bluetooth@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/bluetooth.imageset/bluetooth@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/bluetooth.imageset/bluetooth@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/dice.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "dice@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/dice.imageset/dice@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/dice.imageset/dice@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/headerback.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "headerback@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/headerback.imageset/headerback@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/headerback.imageset/headerback@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/left-arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "left-arrow@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/left-arrow.imageset/left-arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/left-arrow.imageset/left-arrow@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/mockup_blackkeys.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "mockup_blackkeys@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/mockup_blackkeys.imageset/mockup_blackkeys@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/mockup_blackkeys.imageset/mockup_blackkeys@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/mockup_whitekeys.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "mockup_whitekeys@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/mockup_whitekeys.imageset/mockup_whitekeys@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/mockup_whitekeys.imageset/mockup_whitekeys@2x.png -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/right-arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "right-arrow@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RomPlayer/Assets.xcassets/right-arrow.imageset/right-arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Assets.xcassets/right-arrow.imageset/right-arrow@2x.png -------------------------------------------------------------------------------- /RomPlayer/AudioSystem/AutoPan.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutoPan.swift 3 | // ROM Player 4 | // 5 | // Created by Matthew Fecher on 7/26/16. 6 | // Copyright © 2016 AudioKit. All rights reserved. 7 | // 8 | 9 | import AudioKit 10 | 11 | class AutoPan: AKNode { 12 | 13 | var freq = 0.1 { 14 | didSet { 15 | output.parameters = [freq, mix] 16 | } 17 | } 18 | 19 | var mix = 1.0 { 20 | didSet { 21 | output.parameters = [freq, mix] 22 | } 23 | } 24 | 25 | fileprivate var output: AKOperationEffect 26 | 27 | init(_ input: AKNode) { 28 | 29 | output = AKOperationEffect(input) { input, parameters in 30 | 31 | let autoPanFrequency = AKOperation.parameters[0] 32 | let autoPanMix = AKOperation.parameters[1] 33 | 34 | // Now all of our operation work is in this block, nicely indented, away from harm's way 35 | let oscillator = AKOperation.sineWave(frequency: autoPanFrequency) 36 | 37 | let panner = input.pan(oscillator * autoPanMix) 38 | return panner 39 | } 40 | 41 | super.init() 42 | self.avAudioNode = output.avAudioNode 43 | //input.connect(to: output) 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /RomPlayer/AudioSystem/Conductor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Conductor 3 | // ROM Player 4 | // 5 | // Created by Matthew Fecher on 7/20/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | 8 | import AudioKit 9 | 10 | class Conductor { 11 | 12 | // Globally accessible 13 | static let sharedInstance = Conductor() 14 | 15 | var sequencer: AKAppleSequencer! 16 | var sampler1 = AKSampler() 17 | var decimator: AKDecimator 18 | var tremolo: AKTremolo 19 | var fatten: Fatten 20 | var filterSection: FilterSection 21 | 22 | var autoPanMixer: AKDryWetMixer 23 | var autopan: AutoPan 24 | 25 | var multiDelay: PingPongDelay 26 | var masterVolume = AKMixer() 27 | var reverb: AKCostelloReverb 28 | var reverbMixer: AKDryWetMixer 29 | let midi = AKMIDI() 30 | 31 | init() { 32 | 33 | // MIDI Configure 34 | midi.createVirtualPorts() 35 | midi.openInput(name: "Session 1") 36 | midi.openOutput() 37 | 38 | // Session settings 39 | AKAudioFile.cleanTempDirectory() 40 | AKSettings.bufferLength = .medium 41 | AKSettings.enableLogging = false 42 | 43 | // Allow audio to play while the iOS device is muted. 44 | AKSettings.playbackWhileMuted = true 45 | 46 | do { 47 | try AKSettings.setSession(category: .playAndRecord, with: [.defaultToSpeaker, .allowBluetooth, .mixWithOthers]) 48 | } catch { 49 | AKLog("Could not set session category.") 50 | } 51 | 52 | // Signal Chain 53 | tremolo = AKTremolo(sampler1, waveform: AKTable(.sine)) 54 | decimator = AKDecimator(tremolo) 55 | filterSection = FilterSection(decimator) 56 | filterSection.output.stop() 57 | 58 | autopan = AutoPan(filterSection) 59 | autoPanMixer = AKDryWetMixer(filterSection, autopan) 60 | autoPanMixer.balance = 0 61 | 62 | fatten = Fatten(autoPanMixer) 63 | 64 | multiDelay = PingPongDelay(fatten) 65 | 66 | masterVolume = AKMixer(multiDelay) 67 | 68 | reverb = AKCostelloReverb(masterVolume) 69 | 70 | reverbMixer = AKDryWetMixer(masterVolume, reverb, balance: 0.3) 71 | 72 | // Set Output & Start AudioKit 73 | AudioKit.output = reverbMixer 74 | do { 75 | try AudioKit.start() 76 | } catch { 77 | print("AudioKit.start() failed") 78 | } 79 | 80 | // Set a few sampler parameters 81 | sampler1.releaseDuration = 0.5 82 | 83 | // Init sequencer 84 | midiLoad("rom_poly") 85 | } 86 | 87 | func addMidiListener(listener: AKMIDIListener) { 88 | midi.addListener(listener) 89 | } 90 | 91 | func playNote(note: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel) { 92 | sampler1.play(noteNumber: note, velocity: velocity) 93 | } 94 | 95 | func stopNote(note: MIDINoteNumber, channel: MIDIChannel) { 96 | sampler1.stop(noteNumber: note) 97 | } 98 | 99 | func useSound(_ sound: String) { 100 | let soundsFolder = Bundle.main.bundleURL.appendingPathComponent("Sounds/sfz").path 101 | sampler1.unloadAllSamples() 102 | sampler1.loadSFZ(path: soundsFolder, fileName: sound + ".sfz") 103 | } 104 | 105 | func midiLoad(_ midiFile: String) { 106 | let path = "Sounds/midi/\(midiFile)" 107 | sequencer = AKAppleSequencer(filename: path) 108 | sequencer.enableLooping() 109 | sequencer.setGlobalMIDIOutput(midi.virtualInput) 110 | sequencer.setTempo(100) 111 | } 112 | 113 | func sequencerToggle(_ value: Double) { 114 | allNotesOff() 115 | 116 | if value == 1 { 117 | sequencer.play() 118 | } else { 119 | sequencer.stop() 120 | } 121 | } 122 | 123 | func allNotesOff() { 124 | for note in 0 ... 127 { 125 | sampler1.stop(noteNumber: MIDINoteNumber(note)) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /RomPlayer/AudioSystem/Fatten.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fatten.swift 3 | // ROM Player 4 | // 5 | // Created by Aurelius Prochazka, revision history on Github. 6 | // Copyright © 2016 AudioKit. All rights reserved. 7 | // 8 | 9 | import AudioKit 10 | 11 | class Fatten: AKNode, AKInput { 12 | var dryWetMix: AKDryWetMixer 13 | var delay: AKDelay 14 | var pannedDelay: AKPanner 15 | var pannedSource: AKPanner 16 | var wet: AKMixer 17 | var inputMixer = AKMixer() 18 | 19 | var inputNode: AVAudioNode { 20 | return inputMixer.avAudioNode 21 | } 22 | 23 | init(_ input: AKNode) { 24 | input.connect(to: inputMixer) 25 | delay = AKDelay(inputMixer, time: 0.04, dryWetMix: 1) 26 | pannedDelay = AKPanner(delay, pan: 1) 27 | pannedSource = AKPanner(inputMixer, pan: -1) 28 | wet = AKMixer(pannedDelay, pannedSource) 29 | dryWetMix = AKDryWetMixer(inputMixer, wet, balance: 0) 30 | super.init() 31 | self.avAudioNode = dryWetMix.avAudioNode 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /RomPlayer/AudioSystem/FilterSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterSection.swift 3 | // ROM Player 4 | // 5 | // Created by Aurelius Prochazka, revision history on Github. 6 | // Copyright © 2016 AudioKit. All rights reserved. 7 | // 8 | 9 | import AudioKit 10 | 11 | class FilterSection: AKNode, AKInput { 12 | var parameters: [Double] = [1_000, 0.9, 1_000, 1, 0] 13 | 14 | var cutoffFrequency: Double = 1_000 { 15 | didSet { 16 | parameters[0] = cutoffFrequency 17 | output.parameters = parameters 18 | } 19 | } 20 | 21 | var resonance: Double = 0.9 { 22 | didSet { 23 | parameters[1] = resonance 24 | output.parameters = parameters 25 | } 26 | } 27 | 28 | var lfoAmplitude: Double = 1_000 { 29 | didSet { 30 | parameters[2] = lfoAmplitude 31 | output.parameters = parameters 32 | } 33 | } 34 | 35 | var lfoRate: Double = 1 { 36 | didSet { 37 | parameters[3] = lfoRate 38 | output.parameters = parameters 39 | } 40 | } 41 | 42 | var lfoIndex: Double = 0 { 43 | didSet { 44 | parameters[4] = lfoIndex 45 | output.parameters = parameters 46 | } 47 | } 48 | 49 | var output: AKOperationEffect 50 | 51 | var inputNode: AVAudioNode { 52 | return output.avAudioNode 53 | } 54 | 55 | init(_ input: AKNode) { 56 | 57 | output = AKOperationEffect(input) { input, parameters in 58 | 59 | let cutoff = parameters[0] 60 | let rez = parameters[1] 61 | let oscAmp = parameters[2] 62 | let oscRate = parameters[3] 63 | let oscIndex = parameters[4] 64 | 65 | let lfo = AKOperation.morphingOscillator(frequency: oscRate, 66 | amplitude: oscAmp, 67 | index: oscIndex) 68 | 69 | return input.moogLadderFilter(cutoffFrequency: max(lfo + cutoff, 0), 70 | resonance: rez) 71 | } 72 | output.parameters = parameters 73 | 74 | super.init() 75 | self.avAudioNode = output.avAudioNode 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /RomPlayer/AudioSystem/PingPongDelay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PingPongDelay.swift 3 | // FMPlayer 4 | // 5 | // Created by Aurelius Prochazka on 11/8/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import AudioKit 10 | 11 | class PingPongDelay: AKNode, AKInput { 12 | var rampDuration = 0.2 13 | 14 | var mixer: AKDryWetMixer 15 | var leftDelay = AKVariableDelay() 16 | var leftDelayDelay = AKVariableDelay() 17 | var leftCompensator = AKVariableDelay() 18 | var leftMix = AKMixer() 19 | 20 | var rightDelay = AKVariableDelay() 21 | var delayMixer = AKMixer() 22 | 23 | var leftPanner = AKPanner() 24 | var rightPanner = AKPanner() 25 | 26 | var finalBooster = AKBooster() 27 | 28 | var time = 0.0 { 29 | didSet { 30 | leftCompensator.time = time / 2.0 31 | leftDelay.time = time 32 | leftDelayDelay.time = time / 2.0 33 | rightDelay.time = time 34 | } 35 | } 36 | var feedback = 0.0 { 37 | didSet { 38 | leftDelay.feedback = feedback 39 | rightDelay.feedback = feedback 40 | } 41 | } 42 | 43 | var balance = 0.0 { 44 | didSet { 45 | mixer.balance = balance 46 | } 47 | } 48 | 49 | 50 | var inputNode: AVAudioNode { 51 | return mixer.avAudioNode 52 | } 53 | 54 | func start() { 55 | finalBooster.gain = 1.0 56 | } 57 | 58 | func stop() { 59 | finalBooster.gain = 0.0 60 | } 61 | 62 | func clear() { 63 | // leftDelay.clear() 64 | // leftDelayDelay.clear() 65 | // leftCompensator.clear() 66 | // rightDelay.clear() 67 | } 68 | 69 | init(_ input: AKNode) { 70 | 71 | leftPanner.pan = -1 72 | rightPanner.pan = 1 73 | 74 | leftCompensator.rampDuration = rampDuration 75 | leftDelayDelay.rampDuration = rampDuration 76 | leftDelay.rampDuration = rampDuration 77 | rightDelay.rampDuration = rampDuration 78 | finalBooster.rampDuration = rampDuration 79 | 80 | input >>> leftDelay 81 | input >>> rightDelay 82 | input >>> leftCompensator 83 | 84 | leftDelay >>> leftDelayDelay 85 | 86 | [leftCompensator, leftDelayDelay] >>> leftMix 87 | 88 | leftMix >>> leftPanner 89 | rightDelay >>> rightPanner 90 | 91 | [leftPanner, rightPanner] >>> delayMixer >>> finalBooster 92 | 93 | mixer = AKDryWetMixer(input, finalBooster, balance: 0.0) 94 | 95 | super.init() 96 | self.avAudioNode = mixer.avAudioNode 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /RomPlayer/Cells/PresetCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryCell.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 9/2/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PresetCell: UITableViewCell { 12 | 13 | // ********************************************************* 14 | // MARK: - Properties / Outlets 15 | // ********************************************************* 16 | 17 | @IBOutlet weak var presetLabel: UILabel! 18 | 19 | // ********************************************************* 20 | // MARK: - Lifecycle 21 | // ********************************************************* 22 | 23 | override func awakeFromNib() { 24 | super.awakeFromNib() 25 | // Initialization code 26 | 27 | // set cell selection color 28 | let selectedView = UIView(frame: CGRect.zero) 29 | selectedView.backgroundColor = UIColor.clear 30 | selectedBackgroundView = selectedView 31 | 32 | presetLabel?.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) 33 | } 34 | 35 | override func setSelected(_ selected: Bool, animated: Bool) { 36 | // let color = editButton.backgroundColor 37 | super.setSelected(selected, animated: animated) 38 | 39 | // Configure the view for the selected state 40 | if selected { 41 | presetLabel?.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) 42 | backgroundColor = #colorLiteral(red: 0.2431372549, green: 0.2431372549, blue: 0.262745098, alpha: 1) 43 | } else { 44 | presetLabel?.textColor = #colorLiteral(red: 0.7333333333, green: 0.7333333333, blue: 0.7333333333, alpha: 1) 45 | backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0) 46 | } 47 | } 48 | 49 | 50 | // ********************************************************* 51 | // MARK: - Configure Cell 52 | // ********************************************************* 53 | 54 | func configureCell(presetName: String) { 55 | presetLabel.text = "\(presetName)" 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /RomPlayer/Controllers/AUMainController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AUMainController 3 | // ROM Player 4 | // 5 | // Created by Matthew Fecher on 9/20/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AudioKit 11 | import AudioKitUI 12 | 13 | class AUMainController: UIViewController { 14 | 15 | let conductor = Conductor.sharedInstance 16 | 17 | @IBOutlet weak var reverbAmtKnob: MIDIKnob! 18 | @IBOutlet weak var reverbMixKnob: MIDIKnob! 19 | 20 | @IBOutlet weak var delayTimeKnob: MIDIKnob! 21 | @IBOutlet weak var delayFeedbackKnob: MIDIKnob! 22 | @IBOutlet weak var delayMixKnob: MIDIKnob! 23 | 24 | @IBOutlet weak var vol1Knob: MIDIKnob! 25 | @IBOutlet weak var masterVolume: MIDIKnob! 26 | @IBOutlet weak var outputLabel: UILabel! 27 | 28 | @IBOutlet weak var freqKnob: MIDIKnob! 29 | @IBOutlet weak var rezKnob: MIDIKnob! 30 | @IBOutlet weak var lfoRateKnob: MIDIKnob! 31 | @IBOutlet weak var lfoAmtKnob: MIDIKnob! 32 | 33 | @IBOutlet weak var autoPanRateKnob: MIDIKnob! 34 | @IBOutlet weak var distortKnob: MIDIKnob! 35 | @IBOutlet weak var crushKnob: MIDIKnob! 36 | 37 | @IBOutlet weak var sub24Toggle: ToggleButton! 38 | @IBOutlet weak var fattenToggle: ToggleButton! 39 | @IBOutlet weak var filterToggle: ToggleButton! 40 | @IBOutlet weak var reverbToggle: LedButton! 41 | @IBOutlet weak var delayToggle: LedButton! 42 | @IBOutlet weak var autoPanToggle: ToggleButton! 43 | @IBOutlet weak var distortionToggle: ToggleButton! 44 | 45 | @IBOutlet weak var auditionPoly: RadioButton! 46 | @IBOutlet weak var auditionLead: RadioButton! 47 | @IBOutlet weak var auditionBass: RadioButton! 48 | 49 | @IBOutlet weak var attackKnob: MIDIKnob! 50 | @IBOutlet weak var releaseKnob: MIDIKnob! 51 | 52 | @IBOutlet weak var displayContainer: UIView! 53 | 54 | var auditionButtons = [RadioButton]() 55 | var midiKnobs = [MIDIKnob]() 56 | 57 | override func viewDidLoad() { 58 | super.viewDidLoad() 59 | 60 | // Set sound defaults and interface control callbacks 61 | setDefaults() 62 | setupCallbacks() 63 | 64 | // Visualization 65 | let plot = AKNodeFFTPlot(conductor.reverbMixer, frame: CGRect(x: 0, y: 0, width: 2400, height: 86)) 66 | plot.shouldFill = true 67 | plot.shouldMirror = true 68 | plot.shouldCenterYAxis = true 69 | plot.color = #colorLiteral(red: 0.537254902, green: 0.9019607843, blue: 0.9764705882, alpha: 1) 70 | plot.backgroundColor = #colorLiteral(red: 0.2431372549, green: 0.2431372549, blue: 0.262745098, alpha: 0) 71 | plot.gain = 5 72 | displayContainer.addSubview(plot) 73 | 74 | // Create array of MIDIKnobs 75 | midiKnobs = self.view.subviews.filter { $0 is MIDIKnob } as! [MIDIKnob] 76 | setupMIDILearnCallbacks() 77 | } 78 | 79 | //***************************************************************** 80 | // MARK: - Set Defaults 81 | //***************************************************************** 82 | 83 | func setDefaults() { 84 | 85 | // Set Default Knob/Control Values 86 | vol1Knob.range = -9 ... 3 87 | vol1Knob.value = -2 88 | conductor.sampler1.masterVolume = 0.8 89 | 90 | masterVolume.range = 0 ... 5.0 91 | masterVolume.value = 2.5 92 | conductor.masterVolume.volume = 2.5 93 | 94 | autoPanRateKnob.range = 0 ... 5 95 | autoPanRateKnob.taper = 2 96 | 97 | reverbAmtKnob.knobValue = 0.7 98 | conductor.reverb.feedback = 0.7 99 | reverbMixKnob.value = 0.3 100 | reverbToggle.value = 1 101 | 102 | delayTimeKnob.range = 0 ... 1.0 103 | delayTimeKnob.value = 0.85 104 | conductor.multiDelay.time = 0.85 105 | 106 | delayFeedbackKnob.range = 0.0 ... 1.0 107 | delayFeedbackKnob.value = 0.25 108 | 109 | delayMixKnob.value = 0.0 110 | delayToggle.value = 1 111 | 112 | freqKnob.range = 100 ... 16000 113 | freqKnob.taper = 4 114 | freqKnob.value = 1000 115 | conductor.filterSection.cutoffFrequency = 1000 116 | 117 | rezKnob.range = 0 ... 0.98 118 | rezKnob.value = 0.4 119 | conductor.filterSection.resonance = 0.4 120 | 121 | lfoAmtKnob.range = 0 ... 1 122 | lfoAmtKnob.value = 0.5 123 | conductor.filterSection.lfoAmplitude = 0.5 124 | 125 | lfoRateKnob.range = 0 ... 5 126 | lfoRateKnob.taper = 2.5 127 | lfoRateKnob.value = 0.5 128 | conductor.filterSection.lfoRate = 0.5 129 | 130 | conductor.tremolo.depth = 0.5 131 | conductor.tremolo.frequency = 0 132 | 133 | distortKnob.range = 0.6 ... 0.99 134 | distortKnob.value = 0.6 135 | conductor.decimator.rounding = 0.0 136 | conductor.decimator.mix = 0.0 137 | distortionToggle.value = 0 138 | 139 | crushKnob.range = 0 ... 0.06 140 | crushKnob.taper = 1.0 141 | crushKnob.value = 0.0 142 | conductor.decimator.decimation = 0 143 | 144 | attackKnob.range = 0.001 ... 6 145 | releaseKnob.range = 0.01 ... 5 146 | releaseKnob.value = 0.8 147 | 148 | // radio buttons 149 | auditionButtons = [auditionBass, auditionLead, auditionPoly] 150 | auditionButtons.forEach { $0.alternateButton = auditionButtons } 151 | } 152 | 153 | //***************************************************************** 154 | // MARK: - Callbacks 155 | //***************************************************************** 156 | 157 | func setupMIDILearnCallbacks() { 158 | midiKnobs.forEach { 159 | $0.midiLearnCallback = { 160 | self.outputLabel.text = "Twist Knob on your MIDI Controller" 161 | } 162 | } 163 | } 164 | 165 | func setupCallbacks() { 166 | 167 | vol1Knob.callback = { value in 168 | self.conductor.sampler1.masterVolume = pow(10.0, value / 20.0) 169 | self.outputLabel.text = "Vol Boost: \(value.decimalString) db" 170 | } 171 | 172 | masterVolume.callback = { value in 173 | self.conductor.masterVolume.volume = value 174 | self.outputLabel.text = "Master Vol: \((value/self.masterVolume.range.upperBound).percentageString)" 175 | } 176 | 177 | distortKnob.callback = { value in 178 | self.conductor.decimator.rounding = value 179 | self.outputLabel.text = "Distort: \(Double(self.distortKnob.knobValue).percentageString)" 180 | } 181 | 182 | crushKnob.callback = { value in 183 | self.conductor.decimator.decimation = value 184 | self.outputLabel.text = "Crusher: \(Double(self.crushKnob.knobValue).percentageString)" 185 | } 186 | 187 | freqKnob.callback = { value in 188 | self.conductor.filterSection.cutoffFrequency = value 189 | self.outputLabel.text = "Freq: \(value.decimalString) Hz" 190 | 191 | // Adjust LFO Knob 192 | self.conductor.filterSection.lfoAmplitude = Double(self.lfoAmtKnob.knobValue) * value 193 | } 194 | 195 | rezKnob.callback = { value in 196 | self.conductor.filterSection.resonance = value 197 | self.outputLabel.text = "Rez/Q: \(value.decimalString)" 198 | } 199 | 200 | lfoRateKnob.callback = { value in 201 | self.conductor.filterSection.lfoRate = value 202 | self.outputLabel.text = "LFO Rate: \(value.decimalString) Hz" 203 | } 204 | 205 | lfoAmtKnob.callback = { value in 206 | // Calculate percentage of frequency 207 | let lfoAmp = value * self.conductor.filterSection.cutoffFrequency 208 | self.conductor.filterSection.lfoAmplitude = lfoAmp 209 | self.outputLabel.text = "LFO Amt: \(value.percentageString), Freq: \(lfoAmp.decimalString)Hz" 210 | } 211 | 212 | reverbAmtKnob.callback = { value in 213 | self.conductor.reverb.feedback = value 214 | if value == 1.0 { 215 | self.outputLabel.text = "Reverb Level: Blackhole!" 216 | } else { 217 | self.outputLabel.text = "Reverb Level: \(value.percentageString)" 218 | } 219 | 220 | } 221 | 222 | reverbMixKnob.callback = { value in 223 | if self.reverbToggle.isOn { self.conductor.reverbMixer.balance = value } 224 | self.outputLabel.text = "Reverb Wet: \(value.percentageString)" 225 | } 226 | 227 | delayTimeKnob.callback = { value in 228 | self.conductor.multiDelay.time = value 229 | self.outputLabel.text = "Time Between Taps: \(value.decimalString)ms" 230 | } 231 | 232 | delayFeedbackKnob.callback = { value in 233 | self.conductor.multiDelay.feedback = value 234 | switch value { 235 | case 0: 236 | self.outputLabel.text = "Feedback: Basic Multi-taps" 237 | case 0.99: 238 | self.outputLabel.text = "Feedback: Blackhole!" 239 | default: 240 | self.outputLabel.text = "Feedback knob: \(value.decimalString)" 241 | } 242 | } 243 | 244 | delayMixKnob.callback = { value in 245 | guard self.delayToggle.value == 1 else { return } 246 | self.conductor.multiDelay.balance = value 247 | self.outputLabel.text = "Delay Dry/Wet: \(value.percentageString)" 248 | } 249 | 250 | autoPanRateKnob.callback = { value in 251 | self.conductor.autopan.freq = value 252 | self.outputLabel.text = "Auto Pan Rate: \(value.decimalString) Hz" 253 | } 254 | 255 | auditionBass.callback = { value in 256 | if value == 1 { self.conductor.midiLoad("rom_bass") } 257 | self.conductor.sequencerToggle(value) 258 | } 259 | 260 | auditionLead.callback = { value in 261 | if value == 1 { self.conductor.midiLoad("rom_lead") } 262 | self.conductor.sequencerToggle(value) 263 | } 264 | 265 | auditionPoly.callback = { value in 266 | if value == 1 { self.conductor.midiLoad("rom_poly") } 267 | self.conductor.sequencerToggle(value) 268 | } 269 | 270 | autoPanToggle.callback = { value in 271 | if value == 0 { 272 | self.outputLabel.text = "Auto Pan Off" 273 | self.conductor.autoPanMixer.balance = 0 274 | } else { 275 | self.outputLabel.text = "Auto Pan On" 276 | self.conductor.autoPanMixer.balance = 1 277 | } 278 | } 279 | 280 | reverbToggle.callback = { value in 281 | if value == 0 { 282 | self.outputLabel.text = "Reverb Off" 283 | self.conductor.reverbMixer.balance = 0.0 284 | } else { 285 | self.outputLabel.text = "Reverb On" 286 | self.conductor.reverbMixer.balance = self.reverbMixKnob.value 287 | } 288 | } 289 | 290 | distortionToggle.callback = { value in 291 | if value == 0 { 292 | self.outputLabel.text = "Distortion Off" 293 | self.conductor.decimator.mix = 0 294 | } else { 295 | self.outputLabel.text = "Distortion On" 296 | self.conductor.decimator.mix = 1 297 | } 298 | } 299 | 300 | delayToggle.callback = { value in 301 | if value == 0 { 302 | self.outputLabel.text = "Delay Off" 303 | self.conductor.multiDelay.stop() 304 | } else { 305 | self.outputLabel.text = "Delay On" 306 | self.conductor.multiDelay.start() 307 | self.conductor.multiDelay.balance = self.delayMixKnob.value 308 | } 309 | } 310 | 311 | filterToggle.callback = { value in 312 | if value == 0 { 313 | self.outputLabel.text = "Filter Off" 314 | self.conductor.filterSection.output.stop() 315 | } else { 316 | self.outputLabel.text = "Filter On" 317 | self.conductor.filterSection.output.start() 318 | } 319 | } 320 | 321 | fattenToggle.callback = { value in 322 | if value == 0 { 323 | self.outputLabel.text = "Stereo Widen Off" 324 | self.conductor.fatten.dryWetMix.balance = 0 325 | } else { 326 | self.outputLabel.text = "Stereo Widen On" 327 | self.conductor.fatten.dryWetMix.balance = 1 328 | } 329 | } 330 | 331 | attackKnob.callback = { value in 332 | self.conductor.sampler1.attackDuration = value 333 | self.outputLabel.text = "Attack: \(Int(value))" 334 | } 335 | 336 | releaseKnob.callback = { value in 337 | self.conductor.sampler1.releaseDuration = value 338 | self.outputLabel.text = "Release: \(Int(value))" 339 | } 340 | 341 | } 342 | 343 | //***************************************************************** 344 | // MARK: - IBActions 345 | //***************************************************************** 346 | 347 | @IBAction func githubPressed(_ sender: Any) { 348 | if let url = URL(string: "https://github.com/AudioKit/AudioKit") { 349 | UIApplication.shared.open(url) 350 | } 351 | } 352 | 353 | 354 | @IBAction func websitePressed(_ sender: Any) { 355 | if let url = URL(string: "http://audiokitpro.com") { 356 | UIApplication.shared.open(url) 357 | } 358 | } 359 | 360 | 361 | //***************************************************************** 362 | // MARK: - Helpers 363 | //***************************************************************** 364 | 365 | func toggleMIDILearn(isOn: Bool) { 366 | let midiKnobs = self.view.subviews.filter { $0 is MIDIKnob } as! [MIDIKnob] 367 | midiKnobs.forEach { $0.midiLearnMode = isOn } 368 | } 369 | 370 | } 371 | 372 | -------------------------------------------------------------------------------- /RomPlayer/Controllers/ParentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParentViewController.swift 3 | // ROM Player 4 | // 5 | // Created by Matthew Fecher on 9/24/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AudioKit 11 | import AudioKitUI 12 | import GameplayKit 13 | 14 | class ParentViewController: UIViewController { 15 | 16 | @IBOutlet weak var containerView: UIView! 17 | @IBOutlet weak var displayLabel: UILabel! 18 | @IBOutlet weak var keyboardView: SynthKeyboard! 19 | @IBOutlet weak var diceButton: UIButton! 20 | @IBOutlet weak var monoToggle: SynthUIButton! 21 | @IBOutlet weak var holdToggle: SynthUIButton! 22 | @IBOutlet weak var configureKeyboardButton: SynthUIButton! 23 | @IBOutlet weak var midiPanicButton: TopUIButton! 24 | @IBOutlet weak var aboutButton: TopUIButton! 25 | @IBOutlet weak var octaveStepper: Stepper! 26 | @IBOutlet weak var bluetoothButton: AKBluetoothMIDIButton! 27 | @IBOutlet weak var midiSettingsButton: SynthUIButton! 28 | @IBOutlet weak var midiLearnToggle: SynthUIButton! 29 | 30 | var auMainController: AUMainController! 31 | let conductor = Conductor.sharedInstance 32 | var randomNumbers: GKRandomDistribution! 33 | 34 | var midiChannelIn: MIDIChannel = 0 35 | var omniMode = true 36 | 37 | var currentPresetIndex = 0 { 38 | didSet { 39 | if currentPresetIndex > exsPresets.count - 1 { 40 | currentPresetIndex = 0 41 | } 42 | if currentPresetIndex < 0 { 43 | currentPresetIndex = exsPresets.count - 1 44 | } 45 | } 46 | } 47 | 48 | let exsPresets = [ 49 | "TX LoTine81z", 50 | "TX Metalimba", 51 | "TX Pluck Bass", 52 | "TX Brass" 53 | ] 54 | 55 | // ********************************************************** 56 | // MARK: - Life Cycle 57 | // ********************************************************** 58 | 59 | override func viewDidLoad() { 60 | super.viewDidLoad() 61 | 62 | // Setup Keyboard 🎹 63 | keyboardView?.delegate = self 64 | keyboardView?.polyphonicMode = true 65 | keyboardView?.firstOctave = 2 66 | octaveStepper.minValue = -2 67 | octaveStepper.maxValue = 3 68 | 69 | // Add MIDI Listener 👂 70 | conductor.addMidiListener(listener: self) 71 | 72 | // Generate random presets 🎲 73 | randomNumbers = GKShuffledDistribution(lowestValue: 0, highestValue: exsPresets.count - 1) 74 | currentPresetIndex = 0 75 | 76 | // Load Preset 🔊 77 | updatePreset(position: currentPresetIndex) 78 | 79 | // Add Gesture Recognizer to Display Label for Presets Popup 80 | let tap = UITapGestureRecognizer(target: self, action: #selector(ParentViewController.displayLabelTapped)) 81 | tap.numberOfTapsRequired = 1 82 | displayLabel.addGestureRecognizer(tap) 83 | 84 | // Make bluetooth button look pretty 85 | bluetoothButton.layer.cornerRadius = 2 86 | bluetoothButton.layer.borderWidth = 1 87 | 88 | // Get Embedded AUController 89 | if let embeddedController = self.children.first as? AUMainController { 90 | auMainController = embeddedController 91 | } 92 | 93 | // Setup Callbacks 94 | setupCallbacks() 95 | } 96 | 97 | // ********************************************************** 98 | // MARK: - Actions 99 | // ********************************************************** 100 | 101 | @IBAction func rightPresetPressed() { 102 | currentPresetIndex += 1 103 | DispatchQueue.main.async { 104 | self.updatePreset(position: self.currentPresetIndex) 105 | } 106 | } 107 | 108 | @IBAction func leftPresetPressed() { 109 | currentPresetIndex -= 1 110 | DispatchQueue.main.async { 111 | self.updatePreset(position: self.currentPresetIndex) 112 | } 113 | } 114 | 115 | @IBAction func keyboardOctChanged(_ sender: UIStepper) { 116 | keyboardView.firstOctave = Int(sender.value) 117 | } 118 | 119 | @IBAction func randomPressed() { 120 | // Animate Dice 121 | UIView.animate(withDuration: 0.4, animations: { 122 | for _ in 0 ... 1 { 123 | self.diceButton.transform = self.diceButton.transform.rotated(by: CGFloat(Double.pi)) 124 | } 125 | }) 126 | 127 | // Pick random Preset 128 | var newIndex = randomNumbers.nextInt() 129 | if newIndex == currentPresetIndex { newIndex = randomNumbers.nextInt() } 130 | currentPresetIndex = newIndex 131 | updatePreset(position: currentPresetIndex) 132 | } 133 | 134 | // ********************************************************** 135 | // MARK: - Callbacks 136 | // ********************************************************** 137 | 138 | func setupCallbacks() { 139 | octaveStepper.callback = { value in 140 | self.keyboardView.firstOctave = Int(value) + 2 141 | } 142 | 143 | midiSettingsButton.callback = { _ in 144 | self.performSegue(withIdentifier: "SegueToMIDISettingsPopOver", sender: self) 145 | self.midiSettingsButton.value = 0 146 | } 147 | 148 | monoToggle.callback = { value in 149 | self.keyboardView.polyphonicMode = !self.monoToggle.isSelected 150 | } 151 | 152 | holdToggle.callback = { value in 153 | self.keyboardView.holdMode = !self.keyboardView.holdMode 154 | if value == 0.0 { 155 | self.conductor.allNotesOff() 156 | } 157 | } 158 | 159 | configureKeyboardButton.callback = { _ in 160 | self.configureKeyboardButton.value = 0 161 | self.performSegue(withIdentifier: "SegueToKeySettingsPopOver", sender: self) 162 | } 163 | 164 | midiPanicButton.callback = { _ in 165 | self.stopAllNotes() 166 | self.displayAlertController("Midi Panic", message: "All notes have been turned off.") 167 | } 168 | 169 | midiLearnToggle.callback = { _ in 170 | // Toggle MIDI Learn Knobs in subview 171 | self.auMainController.midiKnobs.forEach { $0.midiLearnMode = self.midiLearnToggle.isSelected } 172 | 173 | // Update display label 174 | if self.midiLearnToggle.isSelected { 175 | self.auMainController.outputLabel.text = "MIDI Learn: Touch a knob to assign" 176 | } else { 177 | self.auMainController.outputLabel.text = "MIDI Learn Off" 178 | } 179 | } 180 | 181 | aboutButton.callback = { _ in 182 | self.performSegue(withIdentifier: "SegueToAbout", sender: self) 183 | } 184 | } 185 | 186 | // ********************************************************** 187 | // MARK: - Helper Methods 188 | // ********************************************************** 189 | 190 | func stopAllNotes() { 191 | conductor.allNotesOff() 192 | keyboardView.allNotesOff() 193 | } 194 | 195 | func updatePreset(position: Int) { 196 | stopAllNotes() 197 | let newPreset = exsPresets[position] 198 | displayLabel.text = newPreset 199 | conductor.useSound(newPreset) 200 | 201 | // reset attack/release knob positions 202 | auMainController?.attackKnob.knobValue = 0.0 203 | auMainController?.releaseKnob.knobValue = 0.33 204 | } 205 | 206 | @objc func displayLabelTapped() { 207 | self.performSegue(withIdentifier: "SegueToPresetController", sender: self) 208 | } 209 | 210 | // ********************************************************** 211 | // MARK: - View Navigation/Embed Helper Methods 212 | // ********************************************************** 213 | 214 | override public func prepare(for segue: UIStoryboardSegue, sender: Any?) { 215 | 216 | if segue.identifier == "SegueToKeySettingsPopOver" { 217 | let popOverController = segue.destination as! PopUpKeySettingsController 218 | popOverController.delegate = self 219 | popOverController.octaveRange = keyboardView.octaveCount 220 | popOverController.labelMode = keyboardView.labelMode 221 | popOverController.darkMode = keyboardView.darkMode 222 | 223 | popOverController.preferredContentSize = CGSize(width: 420, height: 400) 224 | if let presentation = popOverController.popoverPresentationController { 225 | presentation.backgroundColor = #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 1) 226 | presentation.sourceRect = configureKeyboardButton.bounds 227 | } 228 | } 229 | 230 | if segue.identifier == "SegueToMIDISettingsPopOver" { 231 | let popOverController = segue.destination as! PopUpMIDIController 232 | popOverController.delegate = self 233 | let userMIDIChannel = omniMode ? -1 : Int(midiChannelIn) 234 | popOverController.userChannelIn = userMIDIChannel 235 | 236 | popOverController.preferredContentSize = CGSize(width: 300, height: 240) 237 | if let presentation = popOverController.popoverPresentationController { 238 | presentation.backgroundColor = #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 1) 239 | presentation.sourceRect = midiSettingsButton.bounds 240 | } 241 | } 242 | 243 | if segue.identifier == "SegueToPresetController" { 244 | let popOverController = segue.destination as! PopUpPresetController 245 | popOverController.delegate = self 246 | popOverController.presets = exsPresets 247 | popOverController.presetIndex = currentPresetIndex 248 | popOverController.preferredContentSize = CGSize(width: 228, height: 175) 249 | if let presentation = popOverController.popoverPresentationController { 250 | presentation.sourceView = self.view 251 | presentation.sourceRect = CGRect(x: self.view.bounds.midX, y: 126, width: 0, height: 0) 252 | presentation.permittedArrowDirections = [] 253 | presentation.backgroundColor = #colorLiteral(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) 254 | } 255 | } 256 | } 257 | 258 | } 259 | 260 | // ********************************************************** 261 | // MARK: - Keyboard Pop Over Delegate 262 | // ********************************************************** 263 | 264 | extension ParentViewController: KeySettingsPopOverDelegate { 265 | 266 | func didFinishSelecting(octaveRange: Int, labelMode: Int, darkMode: Bool) { 267 | keyboardView.octaveCount = octaveRange 268 | keyboardView.labelMode = labelMode 269 | keyboardView.darkMode = darkMode 270 | keyboardView.setNeedsDisplay() 271 | } 272 | } 273 | 274 | // ********************************************************** 275 | // MARK: - MIDI Settings Pop Over Delegate 276 | // ********************************************************** 277 | 278 | extension ParentViewController: MIDISettingsPopOverDelegate { 279 | 280 | func resetMIDILearn() { 281 | auMainController.midiKnobs.forEach { $0.midiCC = 255 } 282 | } 283 | 284 | func didSelectMIDIChannel(newChannel: Int) { 285 | if newChannel > -1 { 286 | midiChannelIn = MIDIByte(newChannel) 287 | omniMode = false 288 | } else { 289 | midiChannelIn = 0 290 | omniMode = true 291 | } 292 | } 293 | } 294 | 295 | //***************************************************************** 296 | // MARK: - Pop Up Preset Selector 297 | //***************************************************************** 298 | 299 | extension ParentViewController: PresetPopOverDelegate { 300 | func didSelectNewPreset(presetIndex: Int) { 301 | currentPresetIndex = presetIndex 302 | updatePreset(position: currentPresetIndex) 303 | } 304 | } 305 | 306 | // ********************************************************** 307 | // MARK: - Keyboard Delegate Note on/off 308 | // ********************************************************** 309 | 310 | extension ParentViewController: AKKeyboardDelegate { 311 | 312 | public func noteOn(note: MIDINoteNumber, velocity: MIDIVelocity = 127) { 313 | conductor.playNote(note: note, velocity: velocity, channel: midiChannelIn) 314 | } 315 | 316 | public func noteOff(note: MIDINoteNumber) { 317 | DispatchQueue.main.async { 318 | self.conductor.stopNote(note: note, channel: self.midiChannelIn) 319 | } 320 | } 321 | } 322 | 323 | // ********************************************************** 324 | // MARK: - AKMIDIListener protocol functions 325 | // ********************************************************** 326 | 327 | extension ParentViewController: AKMIDIListener { 328 | 329 | func receivedMIDINoteOn(noteNumber: MIDINoteNumber, 330 | velocity: MIDIVelocity, 331 | channel: MIDIChannel, 332 | portID: MIDIUniqueID? = nil, 333 | offset: MIDITimeStamp = 0) { 334 | guard channel == midiChannelIn || omniMode else { return } 335 | 336 | DispatchQueue.main.async { 337 | self.keyboardView.pressAdded(noteNumber, velocity: velocity) 338 | } 339 | } 340 | 341 | func receivedMIDINoteOff(noteNumber: MIDINoteNumber, 342 | velocity: MIDIVelocity, 343 | channel: MIDIChannel, 344 | portID: MIDIUniqueID? = nil, 345 | offset: MIDITimeStamp = 0) { 346 | guard (channel == midiChannelIn || omniMode) && !keyboardView.holdMode else { return } 347 | 348 | DispatchQueue.main.async { 349 | self.keyboardView.pressRemoved(noteNumber) 350 | } 351 | } 352 | 353 | // Assign MIDI CC to active MIDI Learn knobs 354 | func assignMIDIControlToKnobs(cc: MIDIByte) { 355 | let activeMIDILearnKnobs = auMainController.midiKnobs.filter { $0.isActive } 356 | activeMIDILearnKnobs.forEach { 357 | $0.midiCC = cc 358 | $0.isActive = false 359 | } 360 | } 361 | 362 | // MIDI Controller input 363 | func receivedMIDIController(_ controller: MIDIByte, 364 | value: MIDIByte, channel: MIDIChannel, 365 | portID: MIDIUniqueID? = nil, 366 | offset: MIDITimeStamp = 0) { 367 | guard channel == midiChannelIn || omniMode else { return } 368 | //print("Channel: \(channel+1) controller: \(controller) value: \(value)") 369 | 370 | // If any MIDI Learn knobs are active, assign the CC 371 | DispatchQueue.main.async { 372 | if self.midiLearnToggle.isSelected { self.assignMIDIControlToKnobs(cc: controller) } 373 | } 374 | 375 | // Handle MIDI Control Messages 376 | switch controller { 377 | case AKMIDIControl.modulationWheel.rawValue: 378 | self.conductor.tremolo.frequency = Double(value)/12.0 379 | case AKMIDIControl.damperOnOff.rawValue: 380 | self.conductor.sampler1.sustainPedal(pedalDown: value > 0) 381 | default: 382 | break 383 | } 384 | 385 | // Check for MIDI learn knobs that match controller 386 | let matchingKnobs = auMainController.midiKnobs.filter { $0.midiCC == controller } 387 | 388 | // Set new knob values from MIDI for matching knobs 389 | matchingKnobs.forEach { midiKnob in 390 | DispatchQueue.main.async { 391 | midiKnob.setKnobValueFrom(midiValue: value) 392 | } 393 | } 394 | } 395 | 396 | // MIDI Program/Patch Change 397 | func receivedMIDIProgramChange(_ program: MIDIByte, 398 | channel: MIDIChannel, 399 | portID: MIDIUniqueID? = nil, 400 | offset: MIDITimeStamp = 0) { 401 | guard channel == midiChannelIn || omniMode else { return } 402 | 403 | // Smoothly cycle through presets if MIDI input is greater than preset count 404 | currentPresetIndex = Int(program) % exsPresets.count 405 | 406 | DispatchQueue.main.async { 407 | self.updatePreset(position: self.currentPresetIndex) 408 | } 409 | } 410 | 411 | // MIDI Pitch Wheel 412 | func receivedMIDIPitchWheel(_ pitchWheelValue: MIDIWord, 413 | channel: MIDIChannel, 414 | portID: MIDIUniqueID? = nil, 415 | offset: MIDITimeStamp = 0) { 416 | guard channel == midiChannelIn || omniMode else { return } 417 | 418 | var bendSemi = 0.0 419 | if pitchWheelValue >= 8192 { 420 | let scale1 = (Double(pitchWheelValue - 8192) / 8192.0) // * conductor.midiBendRange 421 | bendSemi = scale1 * 12 422 | } else { 423 | let scale1 = Double.scaleEntireRange(Double(pitchWheelValue), fromRangeMin: 0, fromRangeMax: 8192, toRangeMin: 12, toRangeMax: 0) 424 | bendSemi = -(Double.scaleEntireRange(scale1, fromRangeMin: 0, fromRangeMax: 144, toRangeMin: 0, toRangeMax: 12)) * 12 425 | } 426 | conductor.sampler1.pitchBend = bendSemi 427 | } 428 | 429 | // After touch 430 | func receivedMIDIAftertouch(_ pressure: MIDIByte, 431 | channel: MIDIChannel, 432 | portID: MIDIUniqueID? = nil, 433 | offset: MIDITimeStamp = 0) { 434 | guard channel == midiChannelIn || omniMode else { return } 435 | self.conductor.tremolo.frequency = Double(pressure)/20.0 436 | } 437 | 438 | // MIDI Setup Change 439 | func receivedMIDISetupChange() { 440 | print("midi setup change, midi.inputNames: \(conductor.midi.inputNames)") 441 | let inputNames = conductor.midi.inputNames 442 | inputNames.forEach { inputName in 443 | conductor.midi.openInput(name: inputName) 444 | } 445 | } 446 | 447 | } 448 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/ButtonStyleKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonStyleKit.swift 3 | // UISynthSpike 4 | // 5 | // Created by Matthew Fecher on 7/22/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | // Generated by PaintCode 9 | // http://www.paintcodeapp.com 10 | // 11 | 12 | 13 | 14 | import UIKit 15 | 16 | public class ButtonStyleKit : NSObject { 17 | 18 | //// Drawing Methods 19 | 20 | @objc dynamic public class func drawRoundButton(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 34, height: 34), resizing: ResizingBehavior = .aspectFit, isToggled: Bool = false) { 21 | //// General Declarations 22 | let context = UIGraphicsGetCurrentContext()! 23 | 24 | //// Resize to Target Frame 25 | context.saveGState() 26 | let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 34, height: 34), target: targetFrame) 27 | context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) 28 | context.scaleBy(x: resizedFrame.width / 34, y: resizedFrame.height / 34) 29 | let resizedShadowScale: CGFloat = min(resizedFrame.width / 34, resizedFrame.height / 34) 30 | 31 | 32 | //// Color Declarations 33 | let orange = UIColor(red: 0.902, green: 0.533, blue: 0.008, alpha: 1.000) 34 | let gray = UIColor(red: 0.306, green: 0.306, blue: 0.325, alpha: 1.000) 35 | let darkerGray = UIColor(red: 0.243, green: 0.243, blue: 0.263, alpha: 1.000) 36 | 37 | //// Shadow Declarations 38 | let shadow = NSShadow() 39 | shadow.shadowColor = orange 40 | shadow.shadowOffset = CGSize(width: 0, height: 0) 41 | shadow.shadowBlurRadius = 8 42 | 43 | //// buttonOff 44 | //// buttonBackground 2 Drawing 45 | let buttonBackground2Path = UIBezierPath(ovalIn: CGRect(x: 2, y: 2, width: 30, height: 30)) 46 | gray.setFill() 47 | buttonBackground2Path.fill() 48 | 49 | 50 | //// Rectangle 2 Drawing 51 | let rectangle2Path = UIBezierPath(rect: CGRect(x: 15, y: 11, width: 4, height: 12)) 52 | darkerGray.setFill() 53 | rectangle2Path.fill() 54 | 55 | 56 | 57 | 58 | if (isToggled) { 59 | //// buttonOn 60 | //// buttonBackground Drawing 61 | let buttonBackgroundPath = UIBezierPath(ovalIn: CGRect(x: 2, y: 2, width: 30, height: 30)) 62 | gray.setFill() 63 | buttonBackgroundPath.fill() 64 | 65 | 66 | //// Rectangle Drawing 67 | let rectanglePath = UIBezierPath(rect: CGRect(x: 15, y: 11, width: 4, height: 12)) 68 | context.saveGState() 69 | context.setShadow(offset: CGSize(width: shadow.shadowOffset.width * resizedShadowScale, height: shadow.shadowOffset.height * resizedShadowScale), blur: shadow.shadowBlurRadius * resizedShadowScale, color: (shadow.shadowColor as! UIColor).cgColor) 70 | orange.setFill() 71 | rectanglePath.fill() 72 | context.restoreGState() 73 | 74 | 75 | 76 | } 77 | 78 | context.restoreGState() 79 | 80 | } 81 | 82 | 83 | 84 | 85 | @objc(ButtonStyleKitResizingBehavior) 86 | public enum ResizingBehavior: Int { 87 | case aspectFit /// The content is proportionally resized to fit into the target rectangle. 88 | case aspectFill /// The content is proportionally resized to completely fill the target rectangle. 89 | case stretch /// The content is stretched to match the entire target rectangle. 90 | case center /// The content is centered in the target rectangle, but it is NOT resized. 91 | 92 | public func apply(rect: CGRect, target: CGRect) -> CGRect { 93 | if rect == target || target == CGRect.zero { 94 | return rect 95 | } 96 | 97 | var scales = CGSize.zero 98 | scales.width = abs(target.width / rect.width) 99 | scales.height = abs(target.height / rect.height) 100 | 101 | switch self { 102 | case .aspectFit: 103 | scales.width = min(scales.width, scales.height) 104 | scales.height = scales.width 105 | case .aspectFill: 106 | scales.width = max(scales.width, scales.height) 107 | scales.height = scales.width 108 | case .stretch: 109 | break 110 | case .center: 111 | scales.width = 1 112 | scales.height = 1 113 | } 114 | 115 | var result = rect.standardized 116 | result.size.width *= scales.width 117 | result.size.height *= scales.height 118 | result.origin.x = target.minX + (target.width - result.width) / 2 119 | result.origin.y = target.minY + (target.height - result.height) / 2 120 | return result 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/FlatToggleStyleKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlatToggleStyleKit.swift 3 | // AudioKitSynth 4 | // 5 | // Created by Matthew Fecher on 9/19/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | // Generated by PaintCode 9 | // http://www.paintcodeapp.com 10 | // 11 | 12 | 13 | 14 | import UIKit 15 | 16 | public class FlatToggleStyleKit : NSObject { 17 | 18 | //// Drawing Methods 19 | 20 | @objc dynamic public class func drawRoundButton(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 35, height: 35), resizing: ResizingBehavior = .aspectFit, isToggled: Bool = false) { 21 | //// General Declarations 22 | let context = UIGraphicsGetCurrentContext()! 23 | 24 | //// Resize to Target Frame 25 | context.saveGState() 26 | let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 35, height: 35), target: targetFrame) 27 | context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) 28 | context.scaleBy(x: resizedFrame.width / 35, y: resizedFrame.height / 35) 29 | 30 | 31 | //// Color Declarations 32 | let onColor = UIColor(red: 0.863, green: 1.000, blue: 0.973, alpha: 1.000) 33 | let offColor = UIColor(red: 0.169, green: 0.169, blue: 0.169, alpha: 1.000) 34 | 35 | //// Variable Declarations 36 | let expression = isToggled ? onColor : offColor 37 | 38 | //// Button 39 | //// buttonBackground Drawing 40 | let buttonBackgroundPath = UIBezierPath(ovalIn: CGRect(x: 2, y: 2, width: 30, height: 30)) 41 | expression.setFill() 42 | buttonBackgroundPath.fill() 43 | 44 | context.restoreGState() 45 | 46 | } 47 | 48 | 49 | 50 | 51 | @objc(FlatToggleStyleKitResizingBehavior) 52 | public enum ResizingBehavior: Int { 53 | case aspectFit /// The content is proportionally resized to fit into the target rectangle. 54 | case aspectFill /// The content is proportionally resized to completely fill the target rectangle. 55 | case stretch /// The content is stretched to match the entire target rectangle. 56 | case center /// The content is centered in the target rectangle, but it is NOT resized. 57 | 58 | public func apply(rect: CGRect, target: CGRect) -> CGRect { 59 | if rect == target || target == CGRect.zero { 60 | return rect 61 | } 62 | 63 | var scales = CGSize.zero 64 | scales.width = abs(target.width / rect.width) 65 | scales.height = abs(target.height / rect.height) 66 | 67 | switch self { 68 | case .aspectFit: 69 | scales.width = min(scales.width, scales.height) 70 | scales.height = scales.width 71 | case .aspectFill: 72 | scales.width = max(scales.width, scales.height) 73 | scales.height = scales.width 74 | case .stretch: 75 | break 76 | case .center: 77 | scales.width = 1 78 | scales.height = 1 79 | } 80 | 81 | var result = rect.standardized 82 | result.size.width *= scales.width 83 | result.size.height *= scales.height 84 | result.origin.x = target.minX + (target.width - result.width) / 2 85 | result.origin.y = target.minY + (target.height - result.height) / 2 86 | return result 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/LedButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LedButton.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 9/19/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | 12 | @IBDesignable 13 | class LedButton: ToggleButton { 14 | 15 | public override func draw(_ rect: CGRect) { 16 | LedToggleStyleKit.drawLedButton(isToggled: isOn) 17 | } 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/LedToggleStyleKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LedToggleStyleKit.swift 3 | // AudioKitSynth 4 | // 5 | // Created by Matthew Fecher on 9/19/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | // Generated by PaintCode 9 | // http://www.paintcodeapp.com 10 | // 11 | 12 | 13 | 14 | import UIKit 15 | 16 | public class LedToggleStyleKit : NSObject { 17 | 18 | //// Drawing Methods 19 | 20 | @objc dynamic public class func drawLedButton(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 33, height: 24), resizing: ResizingBehavior = .aspectFit, isToggled: Bool = true) { 21 | //// General Declarations 22 | let context = UIGraphicsGetCurrentContext()! 23 | 24 | //// Resize to Target Frame 25 | context.saveGState() 26 | let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 33, height: 24), target: targetFrame) 27 | context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) 28 | context.scaleBy(x: resizedFrame.width / 33, y: resizedFrame.height / 24) 29 | let resizedShadowScale: CGFloat = min(resizedFrame.width / 33, resizedFrame.height / 24) 30 | 31 | 32 | //// Color Declarations 33 | let onColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000) 34 | let offColor = UIColor(red: 0.169, green: 0.169, blue: 0.169, alpha: 1.000) 35 | 36 | //// Shadow Declarations 37 | let shadow = NSShadow() 38 | shadow.shadowColor = onColor 39 | shadow.shadowOffset = CGSize(width: 0, height: 0) 40 | shadow.shadowBlurRadius = 8 41 | 42 | //// Variable Declarations 43 | let expression = isToggled ? onColor : offColor 44 | 45 | //// LedBack Drawing 46 | let ledBackPath = UIBezierPath(rect: CGRect(x: 8, y: 8, width: 15, height: 7)) 47 | expression.setFill() 48 | ledBackPath.fill() 49 | 50 | 51 | if (isToggled) { 52 | //// LED_On Drawing 53 | let lED_OnPath = UIBezierPath(rect: CGRect(x: 8, y: 8, width: 15, height: 7)) 54 | context.saveGState() 55 | context.setShadow(offset: CGSize(width: shadow.shadowOffset.width * resizedShadowScale, height: shadow.shadowOffset.height * resizedShadowScale), blur: shadow.shadowBlurRadius * resizedShadowScale, color: (shadow.shadowColor as! UIColor).cgColor) 56 | onColor.setFill() 57 | lED_OnPath.fill() 58 | context.restoreGState() 59 | 60 | } 61 | 62 | context.restoreGState() 63 | 64 | } 65 | 66 | 67 | 68 | 69 | @objc(LedToggleStyleKitResizingBehavior) 70 | public enum ResizingBehavior: Int { 71 | case aspectFit /// The content is proportionally resized to fit into the target rectangle. 72 | case aspectFill /// The content is proportionally resized to completely fill the target rectangle. 73 | case stretch /// The content is stretched to match the entire target rectangle. 74 | case center /// The content is centered in the target rectangle, but it is NOT resized. 75 | 76 | public func apply(rect: CGRect, target: CGRect) -> CGRect { 77 | if rect == target || target == CGRect.zero { 78 | return rect 79 | } 80 | 81 | var scales = CGSize.zero 82 | scales.width = abs(target.width / rect.width) 83 | scales.height = abs(target.height / rect.height) 84 | 85 | switch self { 86 | case .aspectFit: 87 | scales.width = min(scales.width, scales.height) 88 | scales.height = scales.width 89 | case .aspectFill: 90 | scales.width = max(scales.width, scales.height) 91 | scales.height = scales.width 92 | case .stretch: 93 | break 94 | case .center: 95 | scales.width = 1 96 | scales.height = 1 97 | } 98 | 99 | var result = rect.standardized 100 | result.size.width *= scales.width 101 | result.size.height *= scales.height 102 | result.origin.x = target.minX + (target.width - result.width) / 2 103 | result.origin.y = target.minY + (target.height - result.height) / 2 104 | return result 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/MIDIButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MIDIButton.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 10/18/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import AudioKit 10 | 11 | @IBDesignable 12 | class MIDIButton: ToggleButton, MIDILearnable { 13 | 14 | var midiCC: MIDIByte = 255 // MIDI CC 15 | var midiPC: MIDIByte = 255 // MIDI ProgramChange 16 | var midiControlNote: MIDINoteNumber = 255 // Control Button w/ Midi Note 17 | 18 | var midiLearnMode = false 19 | var isActive = false 20 | var hotspotView = UIView() 21 | 22 | // add your implementation code here 23 | } 24 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/RadioButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RadioButton.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 7/26/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RadioButton: UIButton { 12 | var alternateButton:Array? 13 | 14 | var callback: (Double)->Void = { _ in } 15 | 16 | var value: Double = 0.0 { 17 | didSet { 18 | setNeedsDisplay() 19 | } 20 | } 21 | 22 | override var isSelected: Bool { 23 | didSet { 24 | backgroundColor = isSelected ? #colorLiteral(red: 0.3058823529, green: 0.3058823529, blue: 0.3254901961, alpha: 1) : #colorLiteral(red: 0.2, green: 0.2, blue: 0.2196078431, alpha: 1) 25 | value = isSelected ? 1.0 : 0 26 | } 27 | } 28 | 29 | override func awakeFromNib() { 30 | layer.cornerRadius = 14 31 | layer.borderWidth = 2.0 32 | layer.borderColor = #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 1) 33 | layer.masksToBounds = true 34 | } 35 | 36 | func unselectAlternateButtons() { 37 | if let alternateButton = alternateButton { 38 | alternateButton.forEach { 39 | if $0 != self { 40 | $0.isSelected = false 41 | } 42 | } 43 | } 44 | } 45 | 46 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 47 | unselectAlternateButtons() 48 | super.touchesBegan(touches, with: event) 49 | self.isSelected = !isSelected 50 | callback(value) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/Stepper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrowButton.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 8/2/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | class Stepper: UIView { 13 | 14 | public var callback: (Double)->Void = { _ in } 15 | 16 | var minusPath = UIBezierPath(roundedRect: CGRect(x: 0.5, y: 2, width: 35, height: 32), cornerRadius: 1) 17 | var plusPath = UIBezierPath(roundedRect: CGRect(x: 70.5, y: 2, width: 35, height: 32), cornerRadius: 1) 18 | 19 | var minValue = 0 20 | var maxValue = 4 21 | 22 | var value = 0 23 | 24 | private var valuePressed: CGFloat = 0 25 | 26 | /// Text / label to display 27 | open var text = "0" 28 | 29 | // ********************************************************* 30 | // MARK: - Draw 31 | // ********************************************************* 32 | 33 | override func draw(_ rect: CGRect) { 34 | StepperStyleKit.drawStepper(valuePressed: valuePressed, text: "\(value)") 35 | } 36 | 37 | // ********************************************************* 38 | // MARK: - Handle Touches 39 | // ********************************************************* 40 | 41 | /// Handle new touches 42 | override open func touchesBegan(_ touches: Set, with event: UIEvent?) { 43 | if let touch = touches.first { 44 | let touchLocation = touch.location(in: self) 45 | if minusPath.contains(touchLocation) { 46 | if value > minValue { 47 | value -= 1 48 | valuePressed = 1 49 | } 50 | } 51 | if plusPath.contains(touchLocation) { 52 | if value < maxValue { 53 | value += 1 54 | valuePressed = 2 55 | } 56 | } 57 | self.callback(Double(value)) 58 | self.setNeedsDisplay() 59 | } 60 | } 61 | 62 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 63 | for _ in touches { 64 | valuePressed = 0 65 | self.setNeedsDisplay() 66 | } 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/StepperStyleKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepperStyleKit.swift 3 | // AudioKitSynthOne 4 | // 5 | // Created by Matthew Fecher on 8/2/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | // Generated by PaintCode 9 | // http://www.paintcodeapp.com 10 | // 11 | 12 | 13 | 14 | import UIKit 15 | 16 | public class StepperStyleKit : NSObject { 17 | 18 | //// Drawing Methods 19 | 20 | @objc dynamic public class func drawStepper(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 109, height: 37), resizing: ResizingBehavior = .aspectFit, valuePressed: CGFloat = 0, text: String = "1") { 21 | //// General Declarations 22 | let context = UIGraphicsGetCurrentContext()! 23 | 24 | //// Resize to Target Frame 25 | context.saveGState() 26 | let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 109, height: 37), target: targetFrame) 27 | context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) 28 | context.scaleBy(x: resizedFrame.width / 109, y: resizedFrame.height / 37) 29 | 30 | 31 | //// Color Declarations 32 | let selectedColor = UIColor(red: 0.369, green: 0.369, blue: 0.388, alpha: 1.000) 33 | let offColor = UIColor(red: 0.306, green: 0.306, blue: 0.325, alpha: 1.000) 34 | let buttonBack = UIColor(red: 0.243, green: 0.243, blue: 0.263, alpha: 1.000) 35 | let textColor = UIColor(red: 0.855, green: 0.855, blue: 0.855, alpha: 1.000) 36 | 37 | //// Variable Declarations 38 | let downSeleted = valuePressed == 1 ? selectedColor : offColor 39 | let upSelected = valuePressed == 2 ? selectedColor : offColor 40 | 41 | //// Button 42 | //// buttonMinus Drawing 43 | let buttonMinusPath = UIBezierPath(roundedRect: CGRect(x: 0.5, y: 2, width: 35, height: 32), cornerRadius: 1) 44 | downSeleted.setFill() 45 | buttonMinusPath.fill() 46 | UIColor.black.setStroke() 47 | buttonMinusPath.lineWidth = 1 48 | buttonMinusPath.stroke() 49 | 50 | 51 | //// buttonPlus Drawing 52 | let buttonPlusPath = UIBezierPath(roundedRect: CGRect(x: 70.5, y: 2, width: 35, height: 32), cornerRadius: 1) 53 | upSelected.setFill() 54 | buttonPlusPath.fill() 55 | UIColor.black.setStroke() 56 | buttonPlusPath.lineWidth = 1 57 | buttonPlusPath.stroke() 58 | 59 | 60 | //// background Drawing 61 | let backgroundRect = CGRect(x: 35.5, y: 2, width: 35, height: 32) 62 | let backgroundPath = UIBezierPath(roundedRect: backgroundRect, cornerRadius: 1) 63 | buttonBack.setFill() 64 | backgroundPath.fill() 65 | UIColor.black.setStroke() 66 | backgroundPath.lineWidth = 1 67 | backgroundPath.stroke() 68 | let backgroundStyle = NSMutableParagraphStyle() 69 | backgroundStyle.alignment = .center 70 | let backgroundFontAttributes = [ 71 | NSAttributedString.Key.font: UIFont(name: "AvenirNextCondensed-Regular", size: UIFont.systemFontSize)!, 72 | NSAttributedString.Key.foregroundColor: textColor, 73 | NSAttributedString.Key.paragraphStyle: backgroundStyle, 74 | ] 75 | 76 | let backgroundTextHeight: CGFloat = text.boundingRect(with: CGSize(width: backgroundRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: backgroundFontAttributes, context: nil).height 77 | context.saveGState() 78 | context.clip(to: backgroundRect) 79 | text.draw(in: CGRect(x: backgroundRect.minX, y: backgroundRect.minY + (backgroundRect.height - backgroundTextHeight) / 2, width: backgroundRect.width, height: backgroundTextHeight), withAttributes: backgroundFontAttributes) 80 | context.restoreGState() 81 | 82 | 83 | //// Rectangle Drawing 84 | context.saveGState() 85 | context.translateBy(x: 89.5, y: 18) 86 | context.rotate(by: -180 * CGFloat.pi/180) 87 | 88 | let rectanglePath = UIBezierPath() 89 | rectanglePath.move(to: CGPoint(x: 3.5, y: -5.5)) 90 | rectanglePath.addLine(to: CGPoint(x: -3.5, y: -0)) 91 | rectanglePath.addLine(to: CGPoint(x: 3.5, y: 5.5)) 92 | textColor.setFill() 93 | rectanglePath.fill() 94 | 95 | context.restoreGState() 96 | 97 | 98 | //// Rectangle 4 Drawing 99 | context.saveGState() 100 | context.translateBy(x: 17, y: 18) 101 | 102 | let rectangle4Path = UIBezierPath() 103 | rectangle4Path.move(to: CGPoint(x: 3.5, y: -5.5)) 104 | rectangle4Path.addLine(to: CGPoint(x: -3.5, y: 0)) 105 | rectangle4Path.addLine(to: CGPoint(x: 3.5, y: 5.5)) 106 | textColor.setFill() 107 | rectangle4Path.fill() 108 | 109 | context.restoreGState() 110 | 111 | context.restoreGState() 112 | 113 | } 114 | 115 | 116 | 117 | 118 | @objc(StepperStyleKitResizingBehavior) 119 | public enum ResizingBehavior: Int { 120 | case aspectFit /// The content is proportionally resized to fit into the target rectangle. 121 | case aspectFill /// The content is proportionally resized to completely fill the target rectangle. 122 | case stretch /// The content is stretched to match the entire target rectangle. 123 | case center /// The content is centered in the target rectangle, but it is NOT resized. 124 | 125 | public func apply(rect: CGRect, target: CGRect) -> CGRect { 126 | if rect == target || target == CGRect.zero { 127 | return rect 128 | } 129 | 130 | var scales = CGSize.zero 131 | scales.width = abs(target.width / rect.width) 132 | scales.height = abs(target.height / rect.height) 133 | 134 | switch self { 135 | case .aspectFit: 136 | scales.width = min(scales.width, scales.height) 137 | scales.height = scales.width 138 | case .aspectFill: 139 | scales.width = max(scales.width, scales.height) 140 | scales.height = scales.width 141 | case .stretch: 142 | break 143 | case .center: 144 | scales.width = 1 145 | scales.height = 1 146 | } 147 | 148 | var result = rect.standardized 149 | result.size.width *= scales.width 150 | result.size.height *= scales.height 151 | result.origin.x = target.minX + (target.width - result.width) / 2 152 | result.origin.y = target.minY + (target.height - result.height) / 2 153 | return result 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/SynthUIButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SynthUIButton.swift 3 | // AudioKitSynthOne 4 | // 5 | // Created by Matthew Fecher on 8/8/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class SynthUIButton: UIButton { 12 | 13 | var callback: (Double)->Void = { _ in } 14 | 15 | var isOn: Bool { 16 | return value == 1 17 | } 18 | 19 | override public var isSelected: Bool { 20 | didSet { 21 | self.backgroundColor = isOn ? #colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.4509803922, alpha: 1) : #colorLiteral(red: 0.2588235294, green: 0.2588235294, blue: 0.2823529412, alpha: 1) 22 | setNeedsDisplay() 23 | } 24 | } 25 | 26 | var value: Double = 0.0 { 27 | didSet { 28 | isSelected = value == 1.0 29 | } 30 | } 31 | 32 | required public init?(coder aDecoder: NSCoder) { 33 | super.init(coder: aDecoder) 34 | 35 | clipsToBounds = true 36 | layer.cornerRadius = 2 37 | layer.borderWidth = 1 38 | // layer.borderColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 1) as! CGColor 39 | } 40 | 41 | // ********************************************************* 42 | // MARK: - Handle Touches 43 | // ********************************************************* 44 | 45 | 46 | override public func touchesBegan(_ touches: Set, with event: UIEvent?) { 47 | for _ in touches { 48 | value = isOn ? 0 : 1 49 | self.setNeedsDisplay() 50 | callback(value) 51 | } 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/ToggleButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToggleButton.swift 3 | // AudioKit Synth One 4 | // 5 | // Created by Matthew Fecher on 7/22/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | class ToggleButton: UIView, AKSynthOneControl { 13 | 14 | // ********************************************************* 15 | // MARK: - ToggleButton 16 | // ********************************************************* 17 | 18 | var isOn: Bool { 19 | return value == 1 20 | } 21 | 22 | var callback: (Double)->Void = { _ in } 23 | 24 | var value: Double = 0.0 { 25 | didSet { 26 | setNeedsDisplay() 27 | } 28 | } 29 | 30 | override func draw(_ rect: CGRect) { 31 | FlatToggleStyleKit.drawRoundButton(frame: CGRect(x:0,y:0, width: self.bounds.width, height: self.bounds.height), isToggled: isOn) 32 | } 33 | 34 | // ********************************************************* 35 | // MARK: - Handle Touches 36 | // ********************************************************* 37 | 38 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 39 | for _ in touches { 40 | value = isOn ? 0 : 1 41 | setNeedsDisplay() 42 | callback(value) 43 | } 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/TopUIButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopUIButton.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 10/3/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | class TopUIButton: ToggleButton { 13 | 14 | @IBInspectable open var buttonText: String = "Hello" 15 | @IBInspectable open var textSize: Int = 13 16 | 17 | public override func draw(_ rect: CGRect) { 18 | TopUIButtonStyleKit.drawUIButton(frame: CGRect(x:0,y:0, width: self.bounds.width, height: self.bounds.height), resizing: .aspectFit, isOn: isOn, text: buttonText, textSize: CGFloat(textSize)) 19 | } 20 | 21 | // ********************************************************* 22 | // MARK: - Handle Touches 23 | // ********************************************************* 24 | 25 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 26 | for _ in touches { 27 | // value = isOn ? 0 : 1 28 | setNeedsDisplay() 29 | callback(value) 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/TopUIButtonStyleKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButtonStyleKit.swift 3 | // AudioKit 4 | // 5 | // Created by Matthew Fecher on 10/5/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | // Generated by PaintCode 9 | // http://www.paintcodeapp.com 10 | // 11 | 12 | 13 | 14 | import UIKit 15 | 16 | public class TopUIButtonStyleKit : NSObject { 17 | 18 | //// Drawing Methods 19 | 20 | @objc dynamic public class func drawUIButton(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 70, height: 34), resizing: ResizingBehavior = .aspectFit, isOn: Bool = false, text: String = "AudioKit", textSize: CGFloat = 13) { 21 | //// General Declarations 22 | let context = UIGraphicsGetCurrentContext()! 23 | 24 | //// Resize to Target Frame 25 | context.saveGState() 26 | let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 70, height: 34), target: targetFrame) 27 | context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) 28 | context.scaleBy(x: resizedFrame.width / 70, y: resizedFrame.height / 34) 29 | 30 | 31 | //// Color Declarations 32 | let selectedColor = UIColor(red: 0.341, green: 0.349, blue: 0.345, alpha: 1.000) 33 | let offColor = UIColor(red: 0.169, green: 0.169, blue: 0.169, alpha: 1.000) 34 | let color = UIColor(red: 0.067, green: 0.067, blue: 0.067, alpha: 1.000) 35 | 36 | //// Variable Declarations 37 | let isToggled = isOn ? selectedColor : offColor 38 | 39 | //// Button 40 | //// Rectangle Drawing 41 | let rectangleRect = CGRect(x: 2, y: 2, width: 66, height: 30) 42 | let rectanglePath = UIBezierPath(roundedRect: rectangleRect, cornerRadius: 2) 43 | isToggled.setFill() 44 | rectanglePath.fill() 45 | color.setStroke() 46 | rectanglePath.lineWidth = 1 47 | rectanglePath.stroke() 48 | let rectangleStyle = NSMutableParagraphStyle() 49 | rectangleStyle.alignment = .center 50 | let rectangleFontAttributes = [ 51 | .font: UIFont(name: "AvenirNext-Regular", size: textSize)!, 52 | .foregroundColor: UIColor.lightGray, 53 | .paragraphStyle: rectangleStyle, 54 | ] as [NSAttributedString.Key: Any] 55 | 56 | let rectangleTextHeight: CGFloat = text.boundingRect(with: CGSize(width: rectangleRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: rectangleFontAttributes, context: nil).height 57 | context.saveGState() 58 | context.clip(to: rectangleRect) 59 | text.draw(in: CGRect(x: rectangleRect.minX, y: rectangleRect.minY + (rectangleRect.height - rectangleTextHeight) / 2, width: rectangleRect.width, height: rectangleTextHeight), withAttributes: rectangleFontAttributes) 60 | context.restoreGState() 61 | 62 | context.restoreGState() 63 | 64 | } 65 | 66 | 67 | 68 | 69 | @objc(UIButtonStyleKitResizingBehavior) 70 | public enum ResizingBehavior: Int { 71 | case aspectFit /// The content is proportionally resized to fit into the target rectangle. 72 | case aspectFill /// The content is proportionally resized to completely fill the target rectangle. 73 | case stretch /// The content is stretched to match the entire target rectangle. 74 | case center /// The content is centered in the target rectangle, but it is NOT resized. 75 | 76 | public func apply(rect: CGRect, target: CGRect) -> CGRect { 77 | if rect == target || target == CGRect.zero { 78 | return rect 79 | } 80 | 81 | var scales = CGSize.zero 82 | scales.width = abs(target.width / rect.width) 83 | scales.height = abs(target.height / rect.height) 84 | 85 | switch self { 86 | case .aspectFit: 87 | scales.width = min(scales.width, scales.height) 88 | scales.height = scales.width 89 | case .aspectFill: 90 | scales.width = max(scales.width, scales.height) 91 | scales.height = scales.width 92 | case .stretch: 93 | break 94 | case .center: 95 | scales.width = 1 96 | scales.height = 1 97 | } 98 | 99 | var result = rect.standardized 100 | result.size.width *= scales.width 101 | result.size.height *= scales.height 102 | result.origin.x = target.minX + (target.width - result.width) / 2 103 | result.origin.y = target.minY + (target.height - result.height) / 2 104 | return result 105 | } 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Buttons/UrlButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UrlButton.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 10/5/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UrlButton: UIButton { 12 | 13 | /* 14 | // Only override draw() if you perform custom drawing. 15 | // An empty implementation adversely affects performance during animation. 16 | override func draw(_ rect: CGRect) { 17 | // Drawing code 18 | } 19 | */ 20 | 21 | required public init?(coder aDecoder: NSCoder) { 22 | super.init(coder: aDecoder) 23 | 24 | clipsToBounds = true 25 | layer.cornerRadius = 2 26 | layer.borderWidth = 1 27 | // layer.borderColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 1) as! CGColor 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Knobs/FlatKnob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlatKnob.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 9/19/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | 12 | @IBDesignable 13 | public class FlatKnob: Knob { 14 | 15 | public override func draw(_ rect: CGRect) { 16 | FlatKnobStyleKit.drawFlatKnob(frame: CGRect(x:0,y:0, width: self.bounds.width, height: self.bounds.height), knobValue: knobValue) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Knobs/FlatKnobStyleKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlatKnobStyleKit.swift 3 | // AudioKit 4 | // 5 | // Created by Matthew Fecher on 9/19/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | // Generated by PaintCode 9 | // http://www.paintcodeapp.com 10 | // 11 | 12 | 13 | 14 | import UIKit 15 | 16 | public class FlatKnobStyleKit : NSObject { 17 | 18 | //// Drawing Methods 19 | 20 | @objc dynamic public class func drawFlatKnob(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 130, height: 132), resizing: ResizingBehavior = .aspectFit, knobValue: CGFloat = 0.5) { 21 | //// General Declarations 22 | let context = UIGraphicsGetCurrentContext()! 23 | 24 | //// Resize to Target Frame 25 | context.saveGState() 26 | let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 130, height: 132), target: targetFrame) 27 | context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) 28 | context.scaleBy(x: resizedFrame.width / 130, y: resizedFrame.height / 132) 29 | let resizedShadowScale: CGFloat = min(resizedFrame.width / 130, resizedFrame.height / 132) 30 | 31 | 32 | //// Color Declarations 33 | let indicatorColor = UIColor(red: 0.855, green: 0.914, blue: 0.918, alpha: 1.000) 34 | let baseColor = UIColor(red: 0.169, green: 0.169, blue: 0.169, alpha: 1.000) 35 | 36 | //// Shadow Declarations 37 | let shadow = NSShadow() 38 | shadow.shadowColor = indicatorColor.withAlphaComponent(0.5 * indicatorColor.cgColor.alpha) 39 | shadow.shadowOffset = CGSize(width: 0, height: 0) 40 | shadow.shadowBlurRadius = 8 41 | let shadow2 = NSShadow() 42 | shadow2.shadowColor = indicatorColor.withAlphaComponent(0.5 * indicatorColor.cgColor.alpha) 43 | shadow2.shadowOffset = CGSize(width: 0, height: 0) 44 | shadow2.shadowBlurRadius = 11 45 | 46 | //// Variable Declarations 47 | let knobAngle: CGFloat = -240 * knobValue 48 | 49 | //// Knob 50 | context.saveGState() 51 | context.translateBy(x: 0, y: 1) 52 | 53 | 54 | 55 | //// GradientKnob 2 Drawing 56 | let gradientKnob2Path = UIBezierPath(ovalIn: CGRect(x: 13, y: 13, width: 102, height: 102)) 57 | baseColor.setFill() 58 | gradientKnob2Path.fill() 59 | 60 | 61 | //// IndicatorGroup 62 | context.saveGState() 63 | context.setShadow(offset: CGSize(width: shadow2.shadowOffset.width * resizedShadowScale, height: shadow2.shadowOffset.height * resizedShadowScale), blur: shadow2.shadowBlurRadius * resizedShadowScale, color: (shadow2.shadowColor as! UIColor).cgColor) 64 | context.beginTransparencyLayer(auxiliaryInfo: nil) 65 | 66 | 67 | //// Indicator Drawing 68 | context.saveGState() 69 | context.translateBy(x: 64, y: 64) 70 | context.rotate(by: -(knobAngle - 240) * CGFloat.pi/180) 71 | 72 | let indicatorPath = UIBezierPath(rect: CGRect(x: -3.42, y: -53.11, width: 6.95, height: 35.8)) 73 | context.saveGState() 74 | context.setShadow(offset: CGSize(width: shadow.shadowOffset.width * resizedShadowScale, height: shadow.shadowOffset.height * resizedShadowScale), blur: shadow.shadowBlurRadius * resizedShadowScale, color: (shadow.shadowColor as! UIColor).cgColor) 75 | indicatorColor.setFill() 76 | indicatorPath.fill() 77 | context.restoreGState() 78 | 79 | 80 | context.restoreGState() 81 | 82 | 83 | context.endTransparencyLayer() 84 | context.restoreGState() 85 | 86 | 87 | 88 | context.restoreGState() 89 | 90 | context.restoreGState() 91 | 92 | } 93 | 94 | 95 | 96 | 97 | @objc(FlatKnobStyleKitResizingBehavior) 98 | public enum ResizingBehavior: Int { 99 | case aspectFit /// The content is proportionally resized to fit into the target rectangle. 100 | case aspectFill /// The content is proportionally resized to completely fill the target rectangle. 101 | case stretch /// The content is stretched to match the entire target rectangle. 102 | case center /// The content is centered in the target rectangle, but it is NOT resized. 103 | 104 | public func apply(rect: CGRect, target: CGRect) -> CGRect { 105 | if rect == target || target == CGRect.zero { 106 | return rect 107 | } 108 | 109 | var scales = CGSize.zero 110 | scales.width = abs(target.width / rect.width) 111 | scales.height = abs(target.height / rect.height) 112 | 113 | switch self { 114 | case .aspectFit: 115 | scales.width = min(scales.width, scales.height) 116 | scales.height = scales.width 117 | case .aspectFill: 118 | scales.width = max(scales.width, scales.height) 119 | scales.height = scales.width 120 | case .stretch: 121 | break 122 | case .center: 123 | scales.width = 1 124 | scales.height = 1 125 | } 126 | 127 | var result = rect.standardized 128 | result.size.width *= scales.width 129 | result.size.height *= scales.height 130 | result.origin.x = target.minX + (target.width - result.width) / 2 131 | result.origin.y = target.minY + (target.height - result.height) / 2 132 | return result 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Knobs/Knob+Touches.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Knob+Touches.swift 3 | // Swift Synth 4 | // 5 | // Created by Matthew Fecher on 1/9/16. 6 | // Copyright © 2016 AudioKit. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension Knob { 12 | 13 | override public func touchesBegan(_ touches: Set, with event: UIEvent?) { 14 | for touch in touches { 15 | let touchPoint = touch.location(in: self) 16 | lastX = touchPoint.x 17 | lastY = touchPoint.y 18 | } 19 | } 20 | 21 | override public func touchesMoved(_ touches: Set, with event: UIEvent?) { 22 | for touch in touches { 23 | let touchPoint = touch.location(in: self) 24 | setPercentagesWithTouchPoint(touchPoint) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Knobs/Knob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KnobView.swift 3 | // AudioKit Synth One 4 | // 5 | // Created by Matthew Fecher on 7/20/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AudioKit 11 | 12 | protocol AKSynthOneControl { 13 | var value: Double { get set } 14 | var callback: (Double)->Void { get set } 15 | } 16 | 17 | @IBDesignable 18 | public class Knob: UIView, AKSynthOneControl { 19 | 20 | var onlyIntegers: Bool = false 21 | var callback: (Double)->Void = { _ in } 22 | 23 | public var taper: Double = 1.0 // Linear by default 24 | 25 | var range: ClosedRange = 0.0...1.0 { 26 | didSet { 27 | knobValue = CGFloat(Double(knobValue).normalized(from: range, taper: taper)) 28 | } 29 | } 30 | 31 | private var _value: Double = 0 32 | 33 | var value: Double { 34 | get { 35 | return _value 36 | } 37 | set(newValue) { 38 | _value = range.clamp(newValue) 39 | 40 | _value = onlyIntegers ? round(_value) : _value 41 | knobValue = CGFloat(newValue.normalized(from: range, taper: taper)) 42 | } 43 | } 44 | 45 | // Knob properties 46 | var knobValue: CGFloat = 0.1 { 47 | didSet(newValue) { 48 | _value = Double(newValue).denormalized(to: range, taper: taper) 49 | //callback(_value) 50 | self.setNeedsDisplay() 51 | } 52 | } 53 | var knobFill: CGFloat = 0 54 | var knobSensitivity: CGFloat = 0.005 55 | var lastX: CGFloat = 0 56 | var lastY: CGFloat = 0 57 | 58 | // Init / Lifecycle 59 | override init(frame: CGRect) { 60 | super.init(frame: frame) 61 | contentMode = .redraw 62 | } 63 | 64 | required public init?(coder: NSCoder) { 65 | super.init(coder: coder) 66 | self.isUserInteractionEnabled = true 67 | contentMode = .redraw 68 | } 69 | 70 | override public func prepareForInterfaceBuilder() { 71 | super.prepareForInterfaceBuilder() 72 | 73 | contentMode = .scaleAspectFit 74 | clipsToBounds = true 75 | } 76 | 77 | public class override var requiresConstraintBasedLayout: Bool { 78 | return true 79 | } 80 | 81 | public override func draw(_ rect: CGRect) { 82 | KnobStyleKit2.drawFMKnob(frame: CGRect(x:0,y:0, width: self.bounds.width, height: self.bounds.height), knobValue: knobValue) 83 | } 84 | 85 | // Helper 86 | func setPercentagesWithTouchPoint(_ touchPoint: CGPoint) { 87 | // Knobs assume up or right is increasing, and down or left is decreasing 88 | 89 | knobValue += (touchPoint.x - lastX) * knobSensitivity 90 | knobValue -= (touchPoint.y - lastY) * knobSensitivity 91 | 92 | knobValue = (0.0 ... 1.0).clamp(knobValue) 93 | 94 | value = Double(knobValue).denormalized(to: range, taper: taper) 95 | 96 | callback(value) 97 | lastX = touchPoint.x 98 | lastY = touchPoint.y 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Knobs/KnobStyleKit2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KnobStyleKit2.swift 3 | // UISpike 4 | // 5 | // Created by Matthew Fecher on 9/19/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | // Generated by PaintCode 9 | // http://www.paintcodeapp.com 10 | // 11 | 12 | 13 | 14 | import UIKit 15 | 16 | public class KnobStyleKit2 : NSObject { 17 | 18 | //// Drawing Methods 19 | 20 | @objc dynamic public class func drawFMKnob(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 124, height: 124), resizing: ResizingBehavior = .aspectFit, knobValue: CGFloat = 0.5) { 21 | //// General Declarations 22 | let context = UIGraphicsGetCurrentContext()! 23 | 24 | //// Resize to Target Frame 25 | context.saveGState() 26 | let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 124, height: 124), target: targetFrame) 27 | context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) 28 | context.scaleBy(x: resizedFrame.width / 124, y: resizedFrame.height / 124) 29 | let resizedShadowScale: CGFloat = min(resizedFrame.width / 124, resizedFrame.height / 124) 30 | 31 | 32 | //// Color Declarations 33 | let black = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000) 34 | let indicatorColor = UIColor(red: 0.855, green: 0.914, blue: 0.918, alpha: 1.000) 35 | let knobBottom = UIColor(red: 0.180, green: 0.180, blue: 0.192, alpha: 1.000) 36 | let knobLight = UIColor(red: 0.498, green: 0.498, blue: 0.510, alpha: 1.000) 37 | let knobTop2 = UIColor(red: 0.141, green: 0.141, blue: 0.161, alpha: 1.000) 38 | 39 | //// Gradient Declarations 40 | let edge2 = CGGradient(colorsSpace: nil, colors: [knobBottom.cgColor, knobBottom.blended(withFraction: 0.5, of: knobTop2).cgColor, knobTop2.cgColor, knobTop2.blended(withFraction: 0.5, of: knobLight).cgColor, knobLight.cgColor] as CFArray, locations: [0, 0.23, 0.41, 0.73, 1])! 41 | let lowerKnobGradient2 = CGGradient(colorsSpace: nil, colors: [knobTop2.cgColor, knobBottom.cgColor, knobLight.cgColor] as CFArray, locations: [0, 0.51, 1])! 42 | 43 | //// Shadow Declarations 44 | let shadow2 = NSShadow() 45 | shadow2.shadowColor = UIColor.black.withAlphaComponent(0.46) 46 | shadow2.shadowOffset = CGSize(width: 2, height: 8) 47 | shadow2.shadowBlurRadius = 5 48 | let shadow3 = NSShadow() 49 | shadow3.shadowColor = knobLight.withAlphaComponent(0.41 * knobLight.cgColor.alpha) 50 | shadow3.shadowOffset = CGSize(width: 0, height: 10) 51 | shadow3.shadowBlurRadius = 5 52 | let shadow = NSShadow() 53 | shadow.shadowColor = indicatorColor 54 | shadow.shadowOffset = CGSize(width: 0, height: 0) 55 | shadow.shadowBlurRadius = 8 56 | 57 | //// Variable Declarations 58 | let knobAngle: CGFloat = -240 * knobValue 59 | 60 | //// Knob 61 | context.saveGState() 62 | 63 | 64 | 65 | //// BlackBackground Drawing 66 | let blackBackgroundPath = UIBezierPath(ovalIn: CGRect(x: 4, y: 4, width: 120, height: 120)) 67 | black.setFill() 68 | blackBackgroundPath.fill() 69 | 70 | 71 | //// GradientKnob 2 Drawing 72 | let gradientKnob2Path = UIBezierPath(ovalIn: CGRect(x: 13, y: 13, width: 102, height: 102)) 73 | context.saveGState() 74 | gradientKnob2Path.addClip() 75 | context.drawLinearGradient(lowerKnobGradient2, start: CGPoint(x: 64, y: 115), end: CGPoint(x: 64, y: 13), options: []) 76 | context.restoreGState() 77 | 78 | 79 | //// GradientKnob Drawing 80 | let gradientKnobPath = UIBezierPath(ovalIn: CGRect(x: 19, y: 19, width: 90, height: 90)) 81 | context.saveGState() 82 | context.setShadow(offset: CGSize(width: shadow2.shadowOffset.width * resizedShadowScale, height: shadow2.shadowOffset.height * resizedShadowScale), blur: shadow2.shadowBlurRadius * resizedShadowScale, color: (shadow2.shadowColor as! UIColor).cgColor) 83 | context.beginTransparencyLayer(auxiliaryInfo: nil) 84 | gradientKnobPath.addClip() 85 | context.drawLinearGradient(edge2, start: CGPoint(x: 64, y: 109), end: CGPoint(x: 64, y: 19), options: []) 86 | context.endTransparencyLayer() 87 | 88 | ////// GradientKnob Inner Shadow 89 | context.saveGState() 90 | context.clip(to: gradientKnobPath.bounds) 91 | context.setShadow(offset: CGSize.zero, blur: 0) 92 | context.setAlpha((shadow3.shadowColor as! UIColor).cgColor.alpha) 93 | context.beginTransparencyLayer(auxiliaryInfo: nil) 94 | let gradientKnobOpaqueShadow = (shadow3.shadowColor as! UIColor).withAlphaComponent(1) 95 | context.setShadow(offset: CGSize(width: shadow3.shadowOffset.width * resizedShadowScale, height: shadow3.shadowOffset.height * resizedShadowScale), blur: shadow3.shadowBlurRadius * resizedShadowScale, color: gradientKnobOpaqueShadow.cgColor) 96 | context.setBlendMode(.sourceOut) 97 | context.beginTransparencyLayer(auxiliaryInfo: nil) 98 | 99 | gradientKnobOpaqueShadow.setFill() 100 | gradientKnobPath.fill() 101 | 102 | context.endTransparencyLayer() 103 | context.endTransparencyLayer() 104 | context.restoreGState() 105 | 106 | context.restoreGState() 107 | 108 | 109 | 110 | //// IndicatorGroup 111 | //// Indicator Drawing 112 | context.saveGState() 113 | context.translateBy(x: 64, y: 64) 114 | context.rotate(by: -(knobAngle - 240) * CGFloat.pi/180) 115 | 116 | let indicatorPath = UIBezierPath(rect: CGRect(x: -3, y: -45, width: 6, height: 35)) 117 | context.saveGState() 118 | context.setShadow(offset: CGSize(width: shadow.shadowOffset.width * resizedShadowScale, height: shadow.shadowOffset.height * resizedShadowScale), blur: shadow.shadowBlurRadius * resizedShadowScale, color: (shadow.shadowColor as! UIColor).cgColor) 119 | indicatorColor.setFill() 120 | indicatorPath.fill() 121 | context.restoreGState() 122 | 123 | 124 | context.restoreGState() 125 | 126 | 127 | 128 | 129 | 130 | context.restoreGState() 131 | 132 | context.restoreGState() 133 | 134 | } 135 | 136 | 137 | 138 | 139 | @objc(KnobStyleKit2ResizingBehavior) 140 | public enum ResizingBehavior: Int { 141 | case aspectFit /// The content is proportionally resized to fit into the target rectangle. 142 | case aspectFill /// The content is proportionally resized to completely fill the target rectangle. 143 | case stretch /// The content is stretched to match the entire target rectangle. 144 | case center /// The content is centered in the target rectangle, but it is NOT resized. 145 | 146 | public func apply(rect: CGRect, target: CGRect) -> CGRect { 147 | if rect == target || target == CGRect.zero { 148 | return rect 149 | } 150 | 151 | var scales = CGSize.zero 152 | scales.width = abs(target.width / rect.width) 153 | scales.height = abs(target.height / rect.height) 154 | 155 | switch self { 156 | case .aspectFit: 157 | scales.width = min(scales.width, scales.height) 158 | scales.height = scales.width 159 | case .aspectFill: 160 | scales.width = max(scales.width, scales.height) 161 | scales.height = scales.width 162 | case .stretch: 163 | break 164 | case .center: 165 | scales.width = 1 166 | scales.height = 1 167 | } 168 | 169 | var result = rect.standardized 170 | result.size.width *= scales.width 171 | result.size.height *= scales.height 172 | result.origin.x = target.minX + (target.width - result.width) / 2 173 | result.origin.y = target.minY + (target.height - result.height) / 2 174 | return result 175 | } 176 | } 177 | } 178 | 179 | 180 | 181 | private extension UIColor { 182 | func blended(withFraction fraction: CGFloat, of color: UIColor) -> UIColor { 183 | var r1: CGFloat = 1, g1: CGFloat = 1, b1: CGFloat = 1, a1: CGFloat = 1 184 | var r2: CGFloat = 1, g2: CGFloat = 1, b2: CGFloat = 1, a2: CGFloat = 1 185 | 186 | self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) 187 | color.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) 188 | 189 | return UIColor(red: r1 * (1 - fraction) + r2 * fraction, 190 | green: g1 * (1 - fraction) + g2 * fraction, 191 | blue: b1 * (1 - fraction) + b2 * fraction, 192 | alpha: a1 * (1 - fraction) + a2 * fraction); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /RomPlayer/Controls/Knobs/MIDIKnob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MIDIKnob.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 10/18/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import AudioKit 10 | 11 | @IBDesignable 12 | public class MIDIKnob: Knob, MIDILearnable { 13 | 14 | var midiLearnCallback: ()->Void = { } 15 | var hotspotView = UIView() 16 | 17 | var isActive = false { 18 | didSet { 19 | // toggle the border color if a user touches knob 20 | hotspotView.layer.borderColor = isActive ? #colorLiteral(red: 0.4549019608, green: 0.6235294118, blue: 0.7254901961, alpha: 1) : #colorLiteral(red: 0.2431372549, green: 0.2431372549, blue: 0.262745098, alpha: 1) 21 | } 22 | } 23 | 24 | var midiCC: MIDIByte = 255 { 25 | didSet { 26 | // toggle color of assigned knobs 27 | hotspotView.backgroundColor = (midiCC == 255) ? #colorLiteral(red: 0.8705882353, green: 0.9098039216, blue: 0.9176470588, alpha: 0.1977002641) : #colorLiteral(red: 0.8705882353, green: 0.9098039216, blue: 0.9176470588, alpha: 0.5) 28 | } 29 | } 30 | 31 | var midiLearnMode = false { 32 | didSet { 33 | if midiLearnMode { 34 | addHotspot() 35 | } else { 36 | removeHotspot() 37 | isActive = false 38 | } 39 | } 40 | } 41 | 42 | // ********************************************************** 43 | // MIDIKnob 44 | // ********************************************************** 45 | 46 | override public func touchesBegan(_ touches: Set, with event: UIEvent?) { 47 | super.touchesBegan(touches, with: event) 48 | 49 | if midiLearnMode { 50 | isActive = !isActive // Toggles knob to be active & ready to receive CC 51 | 52 | // Callback to update display label in AUMainController 53 | // MIDIKnob does not require a callback to work 54 | if isActive { midiLearnCallback() } 55 | } 56 | 57 | } 58 | 59 | // ********************************************************** 60 | // UI Helper 61 | // ********************************************************** 62 | 63 | func addHotspot() { 64 | hotspotView = UIView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)) 65 | hotspotView.backgroundColor = (midiCC == 255) ? #colorLiteral(red: 0.8705882353, green: 0.9098039216, blue: 0.9176470588, alpha: 0.1977002641) : #colorLiteral(red: 0.8705882353, green: 0.9098039216, blue: 0.9176470588, alpha: 0.5) 66 | hotspotView.layer.borderColor = #colorLiteral(red: 0.2431372549, green: 0.2431372549, blue: 0.262745098, alpha: 1) 67 | hotspotView.layer.borderWidth = 2 68 | hotspotView.layer.cornerRadius = 10 69 | self.addSubview(hotspotView) 70 | } 71 | 72 | func removeHotspot() { 73 | hotspotView.removeFromSuperview() 74 | } 75 | 76 | // ********************************************************** 77 | // Helper Function 78 | // ********************************************************** 79 | 80 | // Linear Scale MIDI 0...127 to 0.0...1.0 81 | func setKnobValueFrom(midiValue: MIDIByte) { 82 | knobValue = CGFloat(Double.scaleRangeZeroToOne(Double(midiValue), rangeMin: 0, rangeMax: 127)) 83 | } 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /RomPlayer/Controls/MIDILearnable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MIDILearnable.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 10/21/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import AudioKit 10 | 11 | protocol MIDILearnable { 12 | var midiCC: MIDIByte { get set } 13 | var midiLearnMode: Bool { get set } 14 | var isActive: Bool { get set } 15 | } 16 | -------------------------------------------------------------------------------- /RomPlayer/Controls/SynthKeyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SynthKeyboard.swift 3 | // AudioKitSynthOne 4 | // 5 | // Created by Matthew Fecher on 8/14/17. 6 | // Copyright © 2017 AudioKit. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AudioKit 11 | 12 | /// Delegate for keyboard events 13 | public protocol AKKeyboardDelegate: class { 14 | /// Note on evenets 15 | func noteOn(note: MIDINoteNumber, velocity: MIDIVelocity) 16 | /// Note off events 17 | func noteOff(note: MIDINoteNumber) 18 | } 19 | 20 | /// Clickable keyboard mainly used for AudioKit playgrounds 21 | @IBDesignable open class SynthKeyboard: UIView, AKMIDIListener { 22 | 23 | /// Number of octaves displayed at once 24 | @IBInspectable open var octaveCount: Int = 2 25 | 26 | /// Lowest octave dispayed 27 | @IBInspectable open var firstOctave: Int = 3 { 28 | didSet { 29 | self.setNeedsDisplay() 30 | } 31 | } 32 | 33 | /// Relative measure of the height of the black keys 34 | @IBInspectable open var topKeyHeightRatio: CGFloat = 0.55 35 | 36 | // Key Labels: 0 = don't display, 1 = every C note, 2 = every note 37 | @IBInspectable open var labelMode: Int = 2 38 | 39 | /// Color of the polyphonic toggle button 40 | @IBInspectable open var polyphonicButton: UIColor = #colorLiteral(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000) 41 | 42 | /// White key color 43 | @IBInspectable open var whiteKeyOff: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) 44 | 45 | /// Black key color 46 | @IBInspectable open var blackKeyOff: UIColor = #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 1) 47 | 48 | /// Activated key color 49 | @IBInspectable open var keyOnUserColor: UIColor = #colorLiteral(red: 0.4549019608, green: 0.6235294118, blue: 0.7254901961, alpha: 1) 50 | 51 | var keyOnColor: UIColor = #colorLiteral(red: 0.4549019608, green: 0.6235294118, blue: 0.7254901961, alpha: 1) 52 | 53 | /// Keyboard Mode, white or dark 54 | @IBInspectable open var darkMode: Bool = false 55 | 56 | /// Class to handle user actions 57 | open weak var delegate: AKKeyboardDelegate? 58 | 59 | var oneOctaveSize = CGSize.zero 60 | var xOffset: CGFloat = 1 61 | var onKeys = Set() 62 | var topKeyWidthIncrease: CGFloat = 4 63 | var holdMode = false { 64 | didSet { 65 | if !holdMode { 66 | allNotesOff() 67 | } 68 | } 69 | } 70 | 71 | let baseMIDINote = 24 // MIDINote 24 is C0 72 | 73 | /// Allows multiple notes to play concurrently 74 | open var polyphonicMode = false { 75 | didSet { 76 | allNotesOff() 77 | } 78 | } 79 | 80 | let naturalNotes = ["C", "D", "E", "F", "G", "A", "B"] 81 | let notesWithSharps = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] 82 | let topKeyNotes = [0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 11] 83 | let whiteKeyNotes = [0, 2, 4, 5, 7, 9, 11] 84 | 85 | func getNoteName(_ note: Int) -> String { 86 | let keyInOctave = note % 12 87 | return notesWithSharps[keyInOctave] 88 | } 89 | 90 | func getWhiteNoteName(_ keyIndex: Int) -> String { 91 | return naturalNotes[keyIndex] 92 | } 93 | 94 | // MARK: - Initialization 95 | 96 | /// Initialize the keyboard with default info 97 | public override init(frame: CGRect) { 98 | super.init(frame: frame) 99 | isMultipleTouchEnabled = true 100 | } 101 | 102 | /// Initialize the keyboard 103 | public init(width: Int, height: Int, firstOctave: Int = 4, octaveCount: Int = 3, 104 | polyphonic: Bool = false) { 105 | self.octaveCount = octaveCount 106 | self.firstOctave = firstOctave 107 | super.init(frame: CGRect(x: 0, y: 0, width: width, height: height)) 108 | oneOctaveSize = CGSize(width: Double(width / octaveCount - width / (octaveCount * octaveCount * 7)), 109 | height: Double(height)) 110 | isMultipleTouchEnabled = true 111 | setNeedsDisplay() 112 | } 113 | 114 | /// Initialization within Interface Builder 115 | required public init?(coder aDecoder: NSCoder) { 116 | super.init(coder: aDecoder) 117 | isMultipleTouchEnabled = true 118 | } 119 | 120 | // MARK: - Storyboard Rendering 121 | 122 | /// Set up the view for rendering in Interface Builder 123 | override open func prepareForInterfaceBuilder() { 124 | super.prepareForInterfaceBuilder() 125 | 126 | let width = Int(self.frame.width) 127 | let height = Int(self.frame.height) 128 | oneOctaveSize = CGSize(width: Double(width / octaveCount - width / (octaveCount * octaveCount * 7)), 129 | height: Double(height)) 130 | 131 | contentMode = .redraw 132 | clipsToBounds = true 133 | } 134 | 135 | /// Keyboard view size 136 | override open var intrinsicContentSize: CGSize { 137 | return CGSize(width: 1_024, height: 84) 138 | } 139 | 140 | /// Require constraints 141 | open class override var requiresConstraintBasedLayout: Bool { 142 | return true 143 | } 144 | 145 | // MARK: - Drawing 146 | 147 | /// Draw the view 148 | override open func draw(_ rect: CGRect) { 149 | 150 | let width = Int(self.frame.width) 151 | let height = Int(self.frame.height) 152 | oneOctaveSize = CGSize(width: Double(width / octaveCount - width / (octaveCount * octaveCount * 7)), 153 | height: Double(height)) 154 | 155 | for i in 0 ..< octaveCount { 156 | drawOctaveCanvas(i) 157 | } 158 | 159 | let tempWidth = CGFloat(width) - CGFloat((octaveCount * 7) - 1) * whiteKeySize.width - 1 160 | let backgroundPath = UIBezierPath(rect: CGRect(x: oneOctaveSize.width * CGFloat(octaveCount), 161 | y: 0, 162 | width: tempWidth, 163 | height: oneOctaveSize.height)) 164 | UIColor.black.setFill() 165 | backgroundPath.fill() 166 | 167 | 168 | let lastCRect = CGRect(x: whiteKeyX(0, octaveNumber: octaveCount), 169 | y: 1, 170 | width: tempWidth/2, 171 | height: whiteKeySize.height) 172 | let lastC = UIBezierPath(roundedRect: lastCRect, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 5, height: 5)) 173 | 174 | whiteKeyColor(0, octaveNumber: octaveCount).setFill() 175 | lastC.fill() 176 | 177 | addLabels(i: 0, octaveNumber: octaveCount, whiteKeysRect: lastCRect) 178 | } 179 | 180 | /// Draw one octave 181 | func drawOctaveCanvas(_ octaveNumber: Int) { 182 | 183 | let width = Int(self.frame.width) 184 | let height = Int(self.frame.height) 185 | oneOctaveSize = CGSize(width: Double(width / octaveCount - width / (octaveCount * octaveCount * 7)), 186 | height: Double(height)) 187 | 188 | //// background Drawing 189 | let backgroundPath = UIBezierPath(rect: CGRect(x: 0 + oneOctaveSize.width * CGFloat(octaveNumber), 190 | y: 0, 191 | width: oneOctaveSize.width, 192 | height: oneOctaveSize.height)) 193 | UIColor.black.setFill() 194 | backgroundPath.fill() 195 | 196 | var whiteKeysPaths = [UIBezierPath]() 197 | 198 | for i in 0 ..< 7 { 199 | 200 | let whiteKeysRect = CGRect(x: whiteKeyX(i, octaveNumber: octaveNumber), y: 1, width: whiteKeySize.width - 1, height: whiteKeySize.height) 201 | whiteKeysPaths.append(UIBezierPath(roundedRect: whiteKeysRect, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 5, height: 5))) 202 | 203 | whiteKeyColor(i, octaveNumber: octaveNumber).setFill() 204 | whiteKeysPaths[i].fill() 205 | 206 | addLabels(i: i, octaveNumber: octaveNumber, whiteKeysRect: whiteKeysRect) 207 | } 208 | 209 | var topKeyPaths = [UIBezierPath]() 210 | 211 | for i in 0 ..< 28 { 212 | let topKeysRect = CGRect(x: topKeyX(i, octaveNumber: octaveNumber), 213 | y: 1, 214 | width: topKeySize.width + topKeyWidthIncrease, 215 | height: topKeySize.height) 216 | topKeyPaths.append(UIBezierPath(roundedRect: topKeysRect, 217 | byRoundingCorners: [.bottomLeft, .bottomRight], 218 | cornerRadii: CGSize(width: 3, height: 3))) 219 | topKeyColor(i, octaveNumber: octaveNumber).setFill() 220 | topKeyPaths[i].fill() 221 | 222 | // Add fancy paintcode blackkey code 223 | } 224 | } 225 | 226 | func addLabels(i: Int, octaveNumber: Int, whiteKeysRect: CGRect) { 227 | var textColor: UIColor = #colorLiteral(red: 0.5098039216, green: 0.5098039216, blue: 0.5294117647, alpha: 1) 228 | if darkMode { 229 | textColor = #colorLiteral(red: 0.3176470588, green: 0.337254902, blue: 0.3647058824, alpha: 1) 230 | } 231 | 232 | // labelMode == 1, Only C, labelMode == 2, All notes 233 | if labelMode == 1 && i == 0 || labelMode == 2 { 234 | // Add Label 235 | let context = UIGraphicsGetCurrentContext()! 236 | let whiteKeysTextContent = getWhiteNoteName(i) + String(firstOctave + octaveNumber) 237 | let whiteKeysStyle = NSMutableParagraphStyle() 238 | whiteKeysStyle.alignment = .center 239 | let whiteKeysFontAttributes = [ 240 | .font: UIFont(name: "AvenirNextCondensed-Regular", size: 14)!, 241 | .foregroundColor: textColor, 242 | .paragraphStyle: whiteKeysStyle, 243 | ] as [NSAttributedString.Key: Any] 244 | 245 | let whiteKeysTextHeight: CGFloat = whiteKeysTextContent.boundingRect(with: CGSize(width: whiteKeysRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: whiteKeysFontAttributes, context: nil).height 246 | context.saveGState() 247 | context.clip(to: whiteKeysRect) 248 | whiteKeysTextContent.draw(in: CGRect(x: whiteKeysRect.minX, y: whiteKeysRect.minY + whiteKeysRect.height - whiteKeysTextHeight - 6, width: whiteKeysRect.width, height: whiteKeysTextHeight), withAttributes: whiteKeysFontAttributes) 249 | context.restoreGState() 250 | } 251 | } 252 | 253 | // MARK: - Touch Handling 254 | 255 | func notesFromTouches(_ touches: Set) -> [MIDINoteNumber] { 256 | var notes = [MIDINoteNumber]() 257 | for touch in touches { 258 | if let note = noteFromTouchLocation(touch.location(in: self)) { 259 | notes.append(note) 260 | } 261 | } 262 | return notes 263 | } 264 | 265 | func noteFromTouchLocation(_ location: CGPoint ) -> MIDINoteNumber? { 266 | guard bounds.contains(location) else { 267 | return nil 268 | } 269 | 270 | let x = location.x - xOffset 271 | let y = location.y 272 | 273 | var note = 0 274 | 275 | if y > oneOctaveSize.height * topKeyHeightRatio { 276 | let octNum = Int(x / oneOctaveSize.width) 277 | let scaledX = x - CGFloat(octNum) * oneOctaveSize.width 278 | note = (firstOctave + octNum) * 12 + whiteKeyNotes[max(0, Int(scaledX / whiteKeySize.width))] + baseMIDINote 279 | } else { 280 | let octNum = Int(x / oneOctaveSize.width) 281 | let scaledX = x - CGFloat(octNum) * oneOctaveSize.width 282 | note = (firstOctave + octNum) * 12 + topKeyNotes[max(0, Int(scaledX / topKeySize.width))] + baseMIDINote 283 | } 284 | if note >= 0 { 285 | return MIDINoteNumber(note) 286 | } else { 287 | return nil 288 | } 289 | 290 | } 291 | 292 | /// Handle new touches 293 | override open func touchesBegan(_ touches: Set, with event: UIEvent?) { 294 | let notes = notesFromTouches(touches) 295 | 296 | for note in notes { 297 | pressAdded(note) 298 | } 299 | if !holdMode { verifyTouches(event?.allTouches) } 300 | setNeedsDisplay() 301 | } 302 | 303 | /// Handle touches completed 304 | override open func touchesEnded(_ touches: Set, with event: UIEvent?) { 305 | guard !holdMode else { return } 306 | for touch in touches { 307 | if let note = noteFromTouchLocation(touch.location(in: self)) { 308 | // verify that there isn't still a touch remaining on same key from another finger 309 | if var otherTouches = event?.allTouches { 310 | otherTouches.remove(touch) 311 | if ❗️notesFromTouches(otherTouches).contains(note) { 312 | pressRemoved(note, touches: event?.allTouches) 313 | } 314 | } 315 | } 316 | } 317 | verifyTouches(event?.allTouches) 318 | setNeedsDisplay() 319 | } 320 | 321 | /// Handle moved touches 322 | override open func touchesMoved(_ touches: Set, with event: UIEvent?) { 323 | guard !holdMode else { return } 324 | for touch in touches { 325 | if let key = noteFromTouchLocation(touch.location(in: self)), 326 | key != noteFromTouchLocation(touch.previousLocation(in: self)) { 327 | pressAdded(key) 328 | setNeedsDisplay() 329 | } 330 | 331 | } 332 | verifyTouches(event?.allTouches) 333 | } 334 | 335 | /// Handle stopped touches 336 | override open func touchesCancelled(_ touches: Set?, with event: UIEvent?) { 337 | verifyTouches(event?.allTouches) 338 | } 339 | 340 | // MARK: - Executing Key Presses 341 | 342 | func pressAdded(_ newNote: MIDINoteNumber, velocity: MIDIVelocity = 127) { 343 | 344 | var noteIsAlreadyOn = false 345 | if holdMode { 346 | for key in onKeys where key == newNote { 347 | noteIsAlreadyOn = true 348 | pressRemoved(key) 349 | } 350 | } 351 | 352 | if ❗️polyphonicMode { 353 | for key in onKeys where key != newNote { 354 | pressRemoved(key) 355 | } 356 | } 357 | 358 | if ❗️onKeys.contains(newNote) && !noteIsAlreadyOn { 359 | onKeys.insert(newNote) 360 | delegate?.noteOn(note: newNote, velocity: velocity) 361 | } 362 | setNeedsDisplay() 363 | } 364 | 365 | 366 | func pressRemoved(_ note: MIDINoteNumber, touches: Set? = nil) { 367 | guard onKeys.contains(note) else { 368 | return 369 | } 370 | onKeys.remove(note) 371 | delegate?.noteOff(note: note) 372 | if ❗️polyphonicMode { 373 | // in mono mode, replace with note from highest remaining touch, if it exists 374 | var remainingNotes = notesFromTouches(touches ?? Set()) 375 | remainingNotes = remainingNotes.filter { $0 != note } 376 | if let highest = remainingNotes.max() { 377 | pressAdded(highest) 378 | } 379 | } 380 | setNeedsDisplay() 381 | } 382 | 383 | func allNotesOff() { 384 | for note in onKeys { 385 | delegate?.noteOff(note: note) 386 | } 387 | onKeys.removeAll() 388 | setNeedsDisplay() 389 | } 390 | 391 | private func verifyTouches(_ touches: Set?) { 392 | // check that current touches conforms to onKeys, remove stuck notes 393 | let notes = notesFromTouches(touches ?? Set() ) 394 | let disjunct = onKeys.subtracting(notes) 395 | if !disjunct.isEmpty { 396 | for note in disjunct { 397 | pressRemoved(note) 398 | } 399 | } 400 | } 401 | 402 | // MARK: - Private helper properties and functions 403 | 404 | var whiteKeySize: CGSize { 405 | return CGSize(width: oneOctaveSize.width / 7.0, height: oneOctaveSize.height - 2) 406 | } 407 | 408 | var topKeySize: CGSize { 409 | return CGSize(width: oneOctaveSize.width / (4 * 7), height: oneOctaveSize.height * topKeyHeightRatio) 410 | } 411 | 412 | // swiftlint:disable variable_name 413 | func whiteKeyX(_ n: Int, octaveNumber: Int) -> CGFloat { 414 | return CGFloat(n) * whiteKeySize.width + xOffset + oneOctaveSize.width * CGFloat(octaveNumber) 415 | } 416 | 417 | func topKeyX(_ n: Int, octaveNumber: Int) -> CGFloat { 418 | return CGFloat(n) * topKeySize.width - (topKeyWidthIncrease/2) + xOffset + oneOctaveSize.width * CGFloat(octaveNumber) 419 | } 420 | 421 | func whiteKeyColor(_ n: Int, octaveNumber: Int) -> UIColor { 422 | if darkMode { 423 | whiteKeyOff = #colorLiteral(red: 0.1333333333, green: 0.1333333333, blue: 0.1333333333, alpha: 1) 424 | keyOnColor = #colorLiteral(red: 0.368627451, green: 0.368627451, blue: 0.3882352941, alpha: 1) 425 | } else { 426 | whiteKeyOff = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) 427 | keyOnColor = keyOnUserColor 428 | } 429 | return onKeys.contains( 430 | MIDINoteNumber((firstOctave + octaveNumber) * 12 + whiteKeyNotes[n] + baseMIDINote )) ? keyOnColor : whiteKeyOff 431 | } 432 | 433 | func topKeyColor(_ n: Int, octaveNumber: Int) -> UIColor { 434 | if darkMode { 435 | blackKeyOff = #colorLiteral(red: 0.2370533347, green: 0.237835288, blue: 0.2605919242, alpha: 1) 436 | keyOnColor = #colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.4509803922, alpha: 1) 437 | } else { 438 | blackKeyOff = #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 1) 439 | keyOnColor = #colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.4509803922, alpha: 1) 440 | } 441 | if notesWithSharps[topKeyNotes[n]].range(of: "#") != nil { 442 | return onKeys.contains( 443 | MIDINoteNumber((firstOctave + octaveNumber) * 12 + topKeyNotes[n] + baseMIDINote) 444 | ) ? keyOnColor : blackKeyOff 445 | } 446 | return #colorLiteral(red: 1.000, green: 1.000, blue: 1.000, alpha: 0.000) 447 | 448 | } 449 | 450 | } 451 | -------------------------------------------------------------------------------- /RomPlayer/Extensions/Double+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+Extensions.swift 3 | // Swift Synth 4 | // 5 | // Created by Matthew Fecher on 1/5/16. 6 | // Copyright © 2016 AudioKit. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // ********************************************************* 12 | // MARK: - UI Helper Extensions 13 | // ********************************************************* 14 | 15 | extension Double { 16 | 17 | // Return string formatted to 2 decimal places 18 | var decimalString: String { 19 | return String(format: "%.02f", self) 20 | } 21 | 22 | // Return string shifted 3 decimal places to left 23 | var decimal1000String: String { 24 | let newValue = 1000 * self 25 | return String(format: "%.02f", newValue) 26 | } 27 | 28 | // Return ms 3 decimal places to left 29 | var msFormattedString: String { 30 | let newValue = 1000 * self 31 | return String(format: "%.00f ms", newValue) 32 | } 33 | 34 | // Formatted percentage string e.g. 0.55 -> 55% 35 | var percentageString: String { 36 | return "\(Int(100 * self))%" 37 | } 38 | 39 | // ********************************************************* 40 | // MARK: - Random Generators 41 | // ********************************************************* 42 | 43 | // return random number between 0.0 and 1.0 44 | public static func random() -> Double { 45 | return Double(arc4random()) / 0xFFFFFFFF 46 | } 47 | 48 | // return random number in range 49 | public static func random(min: Double, max: Double) -> Double { 50 | return Double.random() * (max - min) + min 51 | } 52 | 53 | // return either -1 or 1 randomly 54 | public static func randomSign() -> Double { 55 | return (arc4random_uniform(2) == 0) ? 1.0 : -1.0 56 | } 57 | 58 | // ********************************************************* 59 | // MARK: - Scale Range 60 | // ********************************************************* 61 | 62 | // Linear scale 0.0 to 1.0 to any range 63 | public static func scaleRange(_ value: Double, rangeMin: Double, rangeMax: Double) -> Double { 64 | return ((rangeMax - rangeMin) * (value - 0.0) / (1.0 - 0.0)) + rangeMin 65 | } 66 | 67 | // Linear scale entire range to another range 68 | public static func scaleEntireRange(_ value: Double, fromRangeMin: Double, fromRangeMax: Double, toRangeMin: Double, toRangeMax: Double) -> Double { 69 | return ((toRangeMax - toRangeMin) * (value - fromRangeMin) / (fromRangeMax - fromRangeMin)) + toRangeMin 70 | } 71 | 72 | 73 | // Linear Scale any range to: 0.0 to 1.0 74 | public static func scaleRangeZeroToOne(_ value: Double, rangeMin: Double, rangeMax: Double) -> Double { 75 | return Swift.abs((value - rangeMin) / (rangeMin - rangeMax)) 76 | } 77 | 78 | // Logarithmically scale 0.0 to 1.0 to any range 79 | public static func scaleRangeLog(_ value: Double, rangeMin: Double, rangeMax: Double) -> Double { 80 | let scale = (log(rangeMax) - log(rangeMin)) 81 | return exp(log(rangeMin) + (scale * value)) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /RomPlayer/Extensions/UILabel+Rotate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel+Rotate.swift 3 | // FMPlayer 4 | // 5 | // Created by Matthew Fecher on 6/12/18. 6 | // Copyright © 2018 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | class RotatableLabel: UILabel { 13 | } 14 | 15 | extension UILabel { 16 | @IBInspectable 17 | var rotation: Int { 18 | get { 19 | return 0 20 | } set { 21 | let radians = ((CGFloat.pi) * CGFloat(newValue) / CGFloat(180.0)) 22 | self.transform = CGAffineTransform(rotationAngle: radians) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /RomPlayer/Extensions/ViewController+DisplayAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+DisplayAlertController.swift 3 | // RadioInformer 4 | // 5 | // Created by Matthew Fecher on 5/1/16. 6 | // Copyright © 2016 AudioKit. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // ********************************************************* 12 | // MARK: - Display AlertViewController (Pop-up message) 13 | // ********************************************************* 14 | 15 | extension UIViewController { 16 | 17 | func displayAlertController(_ title: String, message: String) { 18 | // Create and display alert box 19 | let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert) 20 | let action = UIAlertAction(title: "Ok", style: .default, handler: nil) 21 | alert.addAction(action) 22 | present(alert, animated: true, completion: nil) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /RomPlayer/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "ipad", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "1x" 9 | }, 10 | { 11 | "orientation" : "landscape", 12 | "idiom" : "ipad", 13 | "filename" : "launchscreen@2x.png", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /RomPlayer/Images.xcassets/LaunchImage.launchimage/launchscreen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Images.xcassets/LaunchImage.launchimage/launchscreen@2x.png -------------------------------------------------------------------------------- /RomPlayer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ROM Player 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.1 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIBackgroundModes 26 | 27 | audio 28 | 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UIRequiresFullScreen 36 | 37 | UIStatusBarHidden 38 | 39 | UIStatusBarHidden~ipad 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationLandscapeRight 44 | UIInterfaceOrientationLandscapeLeft 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | UIViewControllerBasedStatusBarAppearance 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /RomPlayer/PopUpControllers/AboutViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutViewController.swift 3 | // ROM Player 4 | // 5 | // Created by Matthew Fecher on 7/24/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AboutViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | } 17 | 18 | @IBAction func closePressed(_ sender: UIButton) { 19 | dismiss(animated: true, completion: nil) 20 | } 21 | 22 | @IBAction func audioKitPressed(_ sender: UIButton) { 23 | if let url = URL(string: "https://github.com/AudioKit/AudioKit/graphs/contributors") { 24 | UIApplication.shared.open(url) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /RomPlayer/PopUpControllers/PopUpKeySettingsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopUpViewController.swift 3 | // ROM Player 4 | // 5 | // Created by Matthew Fecher on 11/27/16. 6 | // Copyright © 2016 audiokit. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol KeySettingsPopOverDelegate { 12 | func didFinishSelecting(octaveRange: Int, labelMode: Int, darkMode: Bool) 13 | } 14 | 15 | class PopUpKeySettingsController: UIViewController { 16 | 17 | @IBOutlet weak var octaveRangeSegment: UISegmentedControl! 18 | @IBOutlet weak var labelModeSegment: UISegmentedControl! 19 | @IBOutlet weak var keyboardModeSegment: UISegmentedControl! 20 | @IBOutlet weak var keyboardImage: UIImageView! 21 | 22 | var delegate: KeySettingsPopOverDelegate? 23 | 24 | var labelMode: Int = 1 25 | var octaveRange: Int = 2 26 | var darkMode: Bool = false 27 | 28 | enum KeyImage: String { 29 | case lightMode = "mockup_whitekeys" 30 | case darkMode = "mockup_blackkeys" 31 | } 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | 36 | view.layer.borderColor = #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 1) 37 | view.layer.borderWidth = 2 38 | 39 | // set currently selected options 40 | octaveRangeSegment.selectedSegmentIndex = octaveRange - 1 41 | labelModeSegment.selectedSegmentIndex = labelMode 42 | 43 | keyboardModeSegment.selectedSegmentIndex = darkMode ? 1 : 0 44 | if !darkMode { 45 | keyboardImage.image = UIImage(named: KeyImage.lightMode.rawValue) 46 | } 47 | } 48 | 49 | // Set fonts for UISegmentedControls 50 | override func viewDidLayoutSubviews() { 51 | let attr = NSDictionary(object: UIFont(name: "Avenir Next Condensed", size: 16.0)!, forKey: NSAttributedString.Key.font as NSCopying) 52 | labelModeSegment.setTitleTextAttributes(attr as [NSObject : AnyObject] as [NSObject : AnyObject] as? [NSAttributedString.Key : Any], for: .normal) 53 | keyboardModeSegment.setTitleTextAttributes(attr as [NSObject : AnyObject] as [NSObject : AnyObject] as? [NSAttributedString.Key : Any], for: .normal) 54 | octaveRangeSegment.setTitleTextAttributes(attr as [NSObject : AnyObject] as [NSObject : AnyObject] as? [NSAttributedString.Key : Any], for: .normal) 55 | } 56 | 57 | // ********************************************************** 58 | // MARK: - Actions 59 | // ********************************************************** 60 | 61 | @IBAction func octaveRangeDidChange(_ sender: UISegmentedControl) { 62 | 63 | octaveRange = sender.selectedSegmentIndex + 1 64 | delegate?.didFinishSelecting(octaveRange: octaveRange, labelMode: labelMode, darkMode: darkMode) 65 | } 66 | 67 | @IBAction func keyLabelDidChange(_ sender: UISegmentedControl) { 68 | 69 | labelMode = sender.selectedSegmentIndex 70 | delegate?.didFinishSelecting(octaveRange: octaveRange, labelMode: labelMode, darkMode: darkMode) 71 | } 72 | 73 | 74 | @IBAction func keyboardModeDidChange(_ sender: UISegmentedControl) { 75 | 76 | if sender.selectedSegmentIndex == 1 { 77 | darkMode = true 78 | keyboardImage.image = UIImage(named: KeyImage.darkMode.rawValue) 79 | } else { 80 | darkMode = false 81 | keyboardImage.image = UIImage(named: KeyImage.lightMode.rawValue) 82 | } 83 | 84 | delegate?.didFinishSelecting(octaveRange: octaveRange, labelMode: labelMode, darkMode: darkMode) 85 | } 86 | 87 | 88 | @IBAction func closeButton(_ sender: UIButton) { 89 | dismiss(animated: true, completion: nil) 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /RomPlayer/PopUpControllers/PopUpMIDIController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopUpMIDIController.swift 3 | // RomPlayer 4 | // 5 | // Created by Matthew Fecher on 10/22/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol MIDISettingsPopOverDelegate { 12 | func resetMIDILearn() 13 | func didSelectMIDIChannel(newChannel: Int) 14 | } 15 | 16 | class PopUpMIDIController: UIViewController { 17 | 18 | @IBOutlet weak var channelStepper: Stepper! 19 | @IBOutlet weak var channelLabel: UILabel! 20 | @IBOutlet weak var resetButton: SynthUIButton! 21 | 22 | var delegate: MIDISettingsPopOverDelegate? 23 | 24 | var userChannelIn: Int = 1 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | view.layer.borderColor = #colorLiteral(red: 0.06666666667, green: 0.06666666667, blue: 0.06666666667, alpha: 1) 30 | view.layer.borderWidth = 2 31 | 32 | // setup channel stepper 33 | userChannelIn += 1 // Internal MIDI Channels start at 0...15, Users see 1...16 34 | channelStepper.maxValue = 16 35 | channelStepper.value = userChannelIn 36 | updateChannelLabel() 37 | 38 | // Setup Callbacks 39 | setupCallbacks() 40 | } 41 | 42 | 43 | // ********************************************************** 44 | // MARK: - Callbacks 45 | // ********************************************************** 46 | 47 | func setupCallbacks() { 48 | // Setup Callback 49 | channelStepper.callback = { value in 50 | self.userChannelIn = Int(value) 51 | self.updateChannelLabel() 52 | self.delegate?.didSelectMIDIChannel(newChannel: self.userChannelIn - 1) 53 | } 54 | 55 | resetButton.callback = { value in 56 | self.delegate?.resetMIDILearn() 57 | self.resetButton.value = 0 58 | self.displayAlertController("MIDI Learn Reset", message: "All MIDI learn knob assignments have been removed.") 59 | } 60 | } 61 | 62 | func updateChannelLabel() { 63 | if userChannelIn == 0 { 64 | self.channelLabel.text = "MIDI Channel In: Omni" 65 | } else { 66 | self.channelLabel.text = "MIDI Channel In: \(userChannelIn)" 67 | } 68 | } 69 | 70 | // ********************************************************** 71 | // MARK: - Actions 72 | // ********************************************************** 73 | 74 | @IBAction func closeButton(_ sender: UIButton) { 75 | dismiss(animated: true, completion: nil) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /RomPlayer/PopUpControllers/PopUpPresetController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopUpPresetController 3 | // ROM Player 4 | // 5 | // Created by Matthew Fecher on 9/5/17. 6 | // Copyright © 2017 AudioKit Pro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol PresetPopOverDelegate { 12 | func didSelectNewPreset(presetIndex: Int) 13 | } 14 | 15 | class PopUpPresetController: UIViewController { 16 | @IBOutlet weak var presetsTableView: UITableView! 17 | @IBOutlet weak var popupView: UIView! 18 | 19 | var delegate: PresetPopOverDelegate? 20 | 21 | var presets: [String] = [] 22 | var presetIndex = 0 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | // presetsTableView.reloadData() 28 | presetsTableView.separatorColor = #colorLiteral(red: 0.3333333333, green: 0.3333333333, blue: 0.3333333333, alpha: 1) 29 | popupView.layer.borderColor = #colorLiteral(red: 0.1333333333, green: 0.1333333333, blue: 0.1333333333, alpha: 1) 30 | popupView.layer.borderWidth = 2 31 | popupView.layer.cornerRadius = 3 32 | } 33 | 34 | override func viewDidAppear(_ animated: Bool) { 35 | 36 | // Populate Preset current values 37 | let indexPath = IndexPath(row: presetIndex, section: 0) 38 | presetsTableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) 39 | } 40 | 41 | } 42 | 43 | // ***************************************************************** 44 | // MARK: - TableViewDataSource 45 | // ***************************************************************** 46 | 47 | extension PopUpPresetController: UITableViewDataSource { 48 | 49 | func numberOfSections(in categoryTableView: UITableView) -> Int { 50 | return 1 51 | } 52 | 53 | @objc(tableView:heightForRowAtIndexPath:) func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 54 | return 44 55 | } 56 | 57 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 58 | if presets.isEmpty { 59 | return 1 60 | } else { 61 | return presets.count 62 | } 63 | } 64 | 65 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 66 | 67 | if let cell = tableView.dequeueReusableCell(withIdentifier: "PresetCell") as? PresetCell { 68 | 69 | // Cell updated in PresetCell.swift 70 | cell.configureCell(presetName: presets[indexPath.row]) 71 | return cell 72 | 73 | } else { 74 | return PresetCell() 75 | } 76 | } 77 | } 78 | 79 | //***************************************************************** 80 | // MARK: - TableViewDelegate 81 | //***************************************************************** 82 | 83 | extension PopUpPresetController: UITableViewDelegate { 84 | 85 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 86 | presetIndex = (indexPath as NSIndexPath).row 87 | delegate?.didSelectNewPreset(presetIndex: presetIndex) 88 | } 89 | 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /RomPlayer/RomPlayer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | inter-app-audio 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RomPlayer/Sounds/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/.DS_Store -------------------------------------------------------------------------------- /RomPlayer/Sounds/midi/rom_bass.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/midi/rom_bass.mid -------------------------------------------------------------------------------- /RomPlayer/Sounds/midi/rom_lead.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/midi/rom_lead.mid -------------------------------------------------------------------------------- /RomPlayer/Sounds/midi/rom_poly.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/midi/rom_poly.mid -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/.DS_Store -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/TX Brass.sfz: -------------------------------------------------------------------------------- 1 | // this file was generated from TX Brass.exs using vonred's exs2sfz.py. Trickster goddess incoming. 2 | // modifications from generated file: in filenames, replace spaces with underscores, .aif -> .wv 3 | 4 | lokey=0 hikey=51 pitch_keycenter=48 pitch_keytrack=100 5 | lovel=000 hivel=127 amp_velcurve_127=1 loop_mode=loop_sustain loop_start=105304 loop_end=271114 sample=samples/TX_Chorus_Bras-000-048-c2.wv 6 | lokey=52 hikey=57 pitch_keycenter=54 pitch_keytrack=100 7 | lovel=000 hivel=127 amp_velcurve_127=1 loop_mode=loop_sustain loop_start=105125 loop_end=246095 sample=samples/TX_Chorus_Bras-000-054-f#2.wv 8 | lokey=58 hikey=63 pitch_keycenter=60 pitch_keytrack=100 9 | lovel=000 hivel=127 amp_velcurve_127=1 loop_mode=loop_sustain loop_start=105123 loop_end=309491 sample=samples/TX_Chorus_Bras-000-060-c3.wv 10 | lokey=64 hikey=69 pitch_keycenter=66 pitch_keytrack=100 11 | lovel=000 hivel=127 amp_velcurve_127=1 loop_mode=loop_sustain loop_start=105139 loop_end=193695 sample=samples/TX_Chorus_Bras-000-066-f#3.wv 12 | lokey=70 hikey=75 pitch_keycenter=72 pitch_keytrack=100 13 | lovel=000 hivel=127 amp_velcurve_127=1 loop_mode=loop_sustain loop_start=105312 loop_end=331137 sample=samples/TX_Chorus_Bras-000-072-c4.wv 14 | lokey=76 hikey=81 pitch_keycenter=78 pitch_keytrack=100 15 | lovel=000 hivel=127 amp_velcurve_127=1 loop_mode=loop_sustain loop_start=105276 loop_end=331319 sample=samples/TX_Chorus_Bras-000-078-f#4.wv 16 | lokey=82 hikey=127 pitch_keycenter=84 pitch_keytrack=100 17 | lovel=000 hivel=127 amp_velcurve_127=1 loop_mode=loop_sustain loop_start=105247 loop_end=243815 sample=samples/TX_Chorus_Bras-000-084-c5.wv 18 | -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/TX LoTine81z.sfz: -------------------------------------------------------------------------------- 1 | // this file was generated from TX_LoTine81z.exs using vonred's exs2sfz.py. Trickster goddess incoming. 2 | // modifications from generated file: in filenames, replace spaces with underscores, .aif -> .wv 3 | 4 | lokey=0 hikey=51 pitch_keycenter=48 pitch_keytrack=100 5 | lovel=087 hivel=127 sample=samples/TX_LoTine81z_ms0_048_c2.wv 6 | lovel=044 hivel=086 sample=samples/TX_LoTine81z_ms1_048_c2.wv 7 | lovel=000 hivel=043 sample=samples/TX_LoTine81z_ms2_048_c2.wv 8 | lokey=52 hikey=57 pitch_keycenter=54 pitch_keytrack=100 9 | lovel=087 hivel=127 sample=samples/TX_LoTine81z_ms0_054_f#2.wv 10 | lovel=044 hivel=086 sample=samples/TX_LoTine81z_ms1_054_f#2.wv 11 | lovel=000 hivel=043 sample=samples/TX_LoTine81z_ms2_054_f#2.wv 12 | lokey=58 hikey=63 pitch_keycenter=60 pitch_keytrack=100 13 | lovel=087 hivel=127 sample=samples/TX_LoTine81z_ms0_060_c3.wv 14 | lovel=044 hivel=086 sample=samples/TX_LoTine81z_ms1_060_c3.wv 15 | lovel=000 hivel=043 sample=samples/TX_LoTine81z_ms2_060_c3.wv 16 | lokey=64 hikey=69 pitch_keycenter=66 pitch_keytrack=100 17 | lovel=087 hivel=127 sample=samples/TX_LoTine81z_ms0_066_f#3.wv 18 | lovel=044 hivel=086 sample=samples/TX_LoTine81z_ms1_066_f#3.wv 19 | lovel=000 hivel=043 sample=samples/TX_LoTine81z_ms2_066_f#3.wv 20 | lokey=70 hikey=75 pitch_keycenter=72 pitch_keytrack=100 21 | lovel=087 hivel=127 sample=samples/TX_LoTine81z_ms0_072_c4.wv 22 | lovel=044 hivel=086 sample=samples/TX_LoTine81z_ms1_072_c4.wv 23 | lovel=000 hivel=043 sample=samples/TX_LoTine81z_ms2_072_c4.wv 24 | lokey=76 hikey=81 pitch_keycenter=78 pitch_keytrack=100 25 | lovel=087 hivel=127 sample=samples/TX_LoTine81z_ms0_078_f#4.wv 26 | lovel=044 hivel=086 sample=samples/TX_LoTine81z_ms1_078_f#4.wv 27 | lovel=000 hivel=043 sample=samples/TX_LoTine81z_ms2_078_f#4.wv 28 | lokey=82 hikey=127 pitch_keycenter=84 pitch_keytrack=100 29 | lovel=087 hivel=127 sample=samples/TX_LoTine81z_ms0_084_c5.wv 30 | lovel=044 hivel=086 sample=samples/TX_LoTine81z_ms1_084_c5.wv 31 | lovel=000 hivel=043 sample=samples/TX_LoTine81z_ms2_084_c5.wv 32 | -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/TX Metalimba.sfz: -------------------------------------------------------------------------------- 1 | // this file was generated from TX_Metalimba.exs using vonred's exs2sfz.py. Trickster goddess incoming. 2 | // modifications from generated file: in filenames, replace spaces with underscores, .aif -> .wv 3 | 4 | lokey=0 hikey=51 pitch_keycenter=48 pitch_keytrack=100 5 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Metalimba-000-048-c2.wv 6 | lokey=52 hikey=57 pitch_keycenter=54 pitch_keytrack=100 7 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Metalimba-000-054-f#2.wv 8 | lokey=58 hikey=63 pitch_keycenter=60 pitch_keytrack=100 9 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Metalimba-000-060-c3.wv 10 | lokey=64 hikey=69 pitch_keycenter=66 pitch_keytrack=100 11 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Metalimba-000-066-f#3.wv 12 | lokey=70 hikey=75 pitch_keycenter=72 pitch_keytrack=100 13 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Metalimba-000-072-c4.wv 14 | lokey=76 hikey=81 pitch_keycenter=78 pitch_keytrack=100 15 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Metalimba-000-078-f#4.wv 16 | lokey=82 hikey=127 pitch_keycenter=84 pitch_keytrack=100 17 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Metalimba-000-084-c5.wv 18 | -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/TX Pluck Bass.sfz: -------------------------------------------------------------------------------- 1 | // this file was generated from TX_Pluck_Bass.exs using vonred's exs2sfz.py. Trickster goddess incoming. 2 | // modifications from generated file: in filenames, replace spaces with underscores, .aif -> .wv 3 | 4 | lokey=0 hikey=51 pitch_keycenter=48 pitch_keytrack=100 5 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Pluck_Bass-000-048-c2.wv 6 | lokey=52 hikey=57 pitch_keycenter=54 pitch_keytrack=100 7 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Pluck_Bass-000-054-f#2.wv 8 | lokey=58 hikey=63 pitch_keycenter=60 pitch_keytrack=100 9 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Pluck_Bass-000-060-c3.wv 10 | lokey=64 hikey=69 pitch_keycenter=66 pitch_keytrack=100 11 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Pluck_Bass-000-066-f#3.wv 12 | lokey=70 hikey=75 pitch_keycenter=72 pitch_keytrack=100 13 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Pluck_Bass-000-072-c4.wv 14 | lokey=76 hikey=81 pitch_keycenter=78 pitch_keytrack=100 15 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Pluck_Bass-000-078-f#4.wv 16 | lokey=82 hikey=127 pitch_keycenter=84 pitch_keytrack=100 17 | lovel=000 hivel=127 amp_velcurve_127=1 sample=samples/TX_Pluck_Bass-000-084-c5.wv 18 | -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-048-c2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-048-c2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-054-f#2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-054-f#2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-060-c3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-060-c3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-066-f#3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-066-f#3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-072-c4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-072-c4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-078-f#4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-078-f#4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-084-c5.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Chorus_Bras-000-084-c5.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_048_c2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_048_c2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_054_f#2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_054_f#2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_060_c3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_060_c3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_066_f#3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_066_f#3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_072_c4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_072_c4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_078_f#4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_078_f#4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_084_c5.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms0_084_c5.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_048_c2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_048_c2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_054_f#2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_054_f#2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_060_c3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_060_c3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_066_f#3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_066_f#3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_072_c4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_072_c4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_078_f#4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_078_f#4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_084_c5.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms1_084_c5.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_048_c2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_048_c2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_054_f#2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_054_f#2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_060_c3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_060_c3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_066_f#3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_066_f#3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_072_c4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_072_c4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_078_f#4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_078_f#4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_084_c5.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_LoTine81z_ms2_084_c5.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-048-c2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-048-c2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-054-f#2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-054-f#2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-060-c3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-060-c3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-066-f#3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-066-f#3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-072-c4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-072-c4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-078-f#4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-078-f#4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-084-c5.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Metalimba-000-084-c5.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-048-c2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-048-c2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-054-f#2.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-054-f#2.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-060-c3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-060-c3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-066-f#3.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-066-f#3.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-072-c4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-072-c4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-078-f#4.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-078-f#4.wv -------------------------------------------------------------------------------- /RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-084-c5.wv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AudioKit/ROMPlayer/252d2c06e2ddd78968d9cbfa55b2f114b169a858/RomPlayer/Sounds/sfz/samples/TX_Pluck_Bass-000-084-c5.wv --------------------------------------------------------------------------------