├── .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 | [](https://travis-ci.org/AudioKit/ROMPlayer)
4 | [](https://github.com/AudioKit/ROMPlayer/blob/master/LICENSE)
5 | [](http://twitter.com/AudioKitPro)
6 |
7 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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
--------------------------------------------------------------------------------