├── AKSynthOneKnob.pcvd ├── UniversalKnob.xcodeproj ├── project.xcworkspace │ ├── xcuserdata │ │ └── wu.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── contents.xcworkspacedata ├── xcuserdata │ └── wu.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── SynthUISpike.xcscheme └── project.pbxproj ├── README.md ├── Knob+Touches.swift ├── SynthUISpike ├── ViewController.swift ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json └── AppDelegate.swift ├── Knob.swift └── KnobStyleKit.swift /AKSynthOneKnob.pcvd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogcode/CallbackKnob/HEAD/AKSynthOneKnob.pcvd -------------------------------------------------------------------------------- /UniversalKnob.xcodeproj/project.xcworkspace/xcuserdata/wu.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/analogcode/CallbackKnob/HEAD/UniversalKnob.xcodeproj/project.xcworkspace/xcuserdata/wu.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /UniversalKnob.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SynthKnobExample 2 | 3 | Simple Example of AudioKit Synth Knob. Using callback. Vector based. Resize in Interface Builder! 4 | 5 | ![knob](https://i.imgur.com/Wolesb6.png) 6 | 7 | PaintCode file is [here](https://github.com/AudioKit/AudioKitGraphics/blob/master/AKROMPlayerKnob.pcvd) 8 | 9 | For a delegate example, see [Delegate Knob Example](https://github.com/swiftcodex/SynthKnobExample) 10 | 11 | 12 | ![knob in ib](https://i.imgflip.com/1svkul.gif) 13 | -------------------------------------------------------------------------------- /UniversalKnob.xcodeproj/xcuserdata/wu.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SynthUISpike.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 944156D11F2121CC00C139B9 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /SynthUISpike/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Universal Knob 4 | // 5 | // Created by Matthew Fecher on 10/16/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var knobOne: Knob! 14 | @IBOutlet weak var knobTwo: Knob! 15 | @IBOutlet weak var displayLabel: UILabel! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | setupCallbacks() 20 | } 21 | 22 | //***************************************************************** 23 | // MARK: - Setup Knob 🎛 Callbacks 24 | //***************************************************************** 25 | 26 | func setupCallbacks() { 27 | 28 | knobOne.callback = { value in 29 | self.displayLabel.text = "Knob One: \(String(format: "%.02f", value))" 30 | } 31 | 32 | knobTwo.callback = { value in 33 | self.displayLabel.text = "Knob Two: \(String(format: "%.02f", value))" 34 | } 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /SynthUISpike/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SynthUISpike/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SynthUISpike/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 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /SynthUISpike/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SynthUISpike 4 | // 5 | // Created by Matthew Fecher on 7/20/17. 6 | // Copyright © 2017 Matthew Fecher. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // 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. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /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 | 11 | @IBDesignable 12 | public class Knob: UIView { 13 | 14 | var callback: (Double)->Void = { _ in } 15 | 16 | var minimum = 0.0 { 17 | didSet { 18 | self.knobValue = CGFloat((value - minimum) / (maximum - minimum)) 19 | } 20 | } 21 | var maximum = 1.0 { 22 | didSet { 23 | self.knobValue = CGFloat((value - minimum) / (maximum - minimum)) 24 | } 25 | } 26 | 27 | var value: Double = 0.5 { 28 | didSet { 29 | if value > maximum { 30 | value = maximum 31 | } 32 | if value < minimum { 33 | value = minimum 34 | } 35 | self.knobValue = CGFloat((value - minimum) / (maximum - minimum)) 36 | } 37 | } 38 | 39 | // Knob properties 40 | var knobValue: CGFloat = 0.5 { 41 | didSet { 42 | setNeedsDisplay() 43 | } 44 | } 45 | var knobFill: CGFloat = 0 46 | var knobSensitivity = 0.005 47 | var lastX: CGFloat = 0 48 | var lastY: CGFloat = 0 49 | 50 | // Init / Lifecycle 51 | override init(frame: CGRect) { 52 | super.init(frame: frame) 53 | contentMode = .redraw 54 | } 55 | 56 | required public init?(coder: NSCoder) { 57 | super.init(coder: coder) 58 | self.isUserInteractionEnabled = true 59 | contentMode = .redraw 60 | } 61 | 62 | override public func prepareForInterfaceBuilder() { 63 | super.prepareForInterfaceBuilder() 64 | 65 | contentMode = .scaleAspectFit 66 | clipsToBounds = true 67 | } 68 | 69 | public class override var requiresConstraintBasedLayout: Bool { 70 | return true 71 | } 72 | 73 | public override func draw(_ rect: CGRect) { 74 | KnobStyleKit.drawFMKnob(frame: CGRect(x:0,y:0, width: self.bounds.width, height: self.bounds.height), knobValue: knobValue) 75 | } 76 | 77 | // Helper 78 | func setPercentagesWithTouchPoint(_ touchPoint: CGPoint) { 79 | // Knobs assume up or right is increasing, and down or left is decreasing 80 | 81 | let horizontalChange = Double(touchPoint.x - lastX) * knobSensitivity 82 | value += horizontalChange * (maximum - minimum) 83 | 84 | let verticalChange = Double(touchPoint.y - lastY) * knobSensitivity 85 | value -= verticalChange * (maximum - minimum) 86 | 87 | callback(value) 88 | lastX = touchPoint.x 89 | lastY = touchPoint.y 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /UniversalKnob.xcodeproj/xcuserdata/wu.xcuserdatad/xcschemes/SynthUISpike.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /SynthUISpike/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | AvenirNextCondensed-Regular 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /KnobStyleKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KnobStyleKit.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 KnobStyleKit : 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 | -------------------------------------------------------------------------------- /UniversalKnob.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 942879671F216F94001C75FF /* KnobStyleKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942879661F216F94001C75FF /* KnobStyleKit.swift */; }; 11 | 944156D61F2121CC00C139B9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944156D51F2121CC00C139B9 /* AppDelegate.swift */; }; 12 | 944156D81F2121CC00C139B9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944156D71F2121CC00C139B9 /* ViewController.swift */; }; 13 | 944156DB1F2121CC00C139B9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 944156D91F2121CC00C139B9 /* Main.storyboard */; }; 14 | 944156DD1F2121CC00C139B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 944156DC1F2121CC00C139B9 /* Assets.xcassets */; }; 15 | 944156E01F2121CC00C139B9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 944156DE1F2121CC00C139B9 /* LaunchScreen.storyboard */; }; 16 | 944156F01F21254600C139B9 /* Knob+Touches.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944156ED1F21254600C139B9 /* Knob+Touches.swift */; }; 17 | 945DB4CF1F950CA300D2BEA1 /* Knob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945DB4CE1F950CA300D2BEA1 /* Knob.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 942879661F216F94001C75FF /* KnobStyleKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnobStyleKit.swift; sourceTree = ""; }; 22 | 944156D21F2121CC00C139B9 /* UniversalKnob.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UniversalKnob.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 944156D51F2121CC00C139B9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | 944156D71F2121CC00C139B9 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 25 | 944156DA1F2121CC00C139B9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 944156DC1F2121CC00C139B9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 944156DF1F2121CC00C139B9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 944156E11F2121CC00C139B9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 944156ED1F21254600C139B9 /* Knob+Touches.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Knob+Touches.swift"; sourceTree = ""; }; 30 | 945DB4CE1F950CA300D2BEA1 /* Knob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Knob.swift; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 944156CF1F2121CC00C139B9 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 944156C91F2121CC00C139B9 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 944156EB1F21253400C139B9 /* Knobs */, 48 | 944156D41F2121CC00C139B9 /* SynthUISpike */, 49 | 944156D31F2121CC00C139B9 /* Products */, 50 | ); 51 | sourceTree = ""; 52 | }; 53 | 944156D31F2121CC00C139B9 /* Products */ = { 54 | isa = PBXGroup; 55 | children = ( 56 | 944156D21F2121CC00C139B9 /* UniversalKnob.app */, 57 | ); 58 | name = Products; 59 | sourceTree = ""; 60 | }; 61 | 944156D41F2121CC00C139B9 /* SynthUISpike */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 944156D51F2121CC00C139B9 /* AppDelegate.swift */, 65 | 944156D71F2121CC00C139B9 /* ViewController.swift */, 66 | 944156D91F2121CC00C139B9 /* Main.storyboard */, 67 | 944156DC1F2121CC00C139B9 /* Assets.xcassets */, 68 | 944156DE1F2121CC00C139B9 /* LaunchScreen.storyboard */, 69 | 944156E11F2121CC00C139B9 /* Info.plist */, 70 | ); 71 | path = SynthUISpike; 72 | sourceTree = ""; 73 | }; 74 | 944156EB1F21253400C139B9 /* Knobs */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 942879661F216F94001C75FF /* KnobStyleKit.swift */, 78 | 944156ED1F21254600C139B9 /* Knob+Touches.swift */, 79 | 945DB4CE1F950CA300D2BEA1 /* Knob.swift */, 80 | ); 81 | name = Knobs; 82 | sourceTree = ""; 83 | }; 84 | /* End PBXGroup section */ 85 | 86 | /* Begin PBXNativeTarget section */ 87 | 944156D11F2121CC00C139B9 /* UniversalKnob */ = { 88 | isa = PBXNativeTarget; 89 | buildConfigurationList = 944156E41F2121CC00C139B9 /* Build configuration list for PBXNativeTarget "UniversalKnob" */; 90 | buildPhases = ( 91 | 944156CE1F2121CC00C139B9 /* Sources */, 92 | 944156CF1F2121CC00C139B9 /* Frameworks */, 93 | 944156D01F2121CC00C139B9 /* Resources */, 94 | ); 95 | buildRules = ( 96 | ); 97 | dependencies = ( 98 | ); 99 | name = UniversalKnob; 100 | productName = SynthUISpike; 101 | productReference = 944156D21F2121CC00C139B9 /* UniversalKnob.app */; 102 | productType = "com.apple.product-type.application"; 103 | }; 104 | /* End PBXNativeTarget section */ 105 | 106 | /* Begin PBXProject section */ 107 | 944156CA1F2121CC00C139B9 /* Project object */ = { 108 | isa = PBXProject; 109 | attributes = { 110 | LastSwiftUpdateCheck = 0830; 111 | LastUpgradeCheck = 0830; 112 | ORGANIZATIONNAME = "Matthew Fecher"; 113 | TargetAttributes = { 114 | 944156D11F2121CC00C139B9 = { 115 | CreatedOnToolsVersion = 8.3.3; 116 | DevelopmentTeam = G24WJ3XCZ3; 117 | LastSwiftMigration = 0830; 118 | ProvisioningStyle = Automatic; 119 | }; 120 | }; 121 | }; 122 | buildConfigurationList = 944156CD1F2121CC00C139B9 /* Build configuration list for PBXProject "UniversalKnob" */; 123 | compatibilityVersion = "Xcode 3.2"; 124 | developmentRegion = English; 125 | hasScannedForEncodings = 0; 126 | knownRegions = ( 127 | en, 128 | Base, 129 | ); 130 | mainGroup = 944156C91F2121CC00C139B9; 131 | productRefGroup = 944156D31F2121CC00C139B9 /* Products */; 132 | projectDirPath = ""; 133 | projectRoot = ""; 134 | targets = ( 135 | 944156D11F2121CC00C139B9 /* UniversalKnob */, 136 | ); 137 | }; 138 | /* End PBXProject section */ 139 | 140 | /* Begin PBXResourcesBuildPhase section */ 141 | 944156D01F2121CC00C139B9 /* Resources */ = { 142 | isa = PBXResourcesBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | 944156E01F2121CC00C139B9 /* LaunchScreen.storyboard in Resources */, 146 | 944156DD1F2121CC00C139B9 /* Assets.xcassets in Resources */, 147 | 944156DB1F2121CC00C139B9 /* Main.storyboard in Resources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXResourcesBuildPhase section */ 152 | 153 | /* Begin PBXSourcesBuildPhase section */ 154 | 944156CE1F2121CC00C139B9 /* Sources */ = { 155 | isa = PBXSourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | 944156D81F2121CC00C139B9 /* ViewController.swift in Sources */, 159 | 945DB4CF1F950CA300D2BEA1 /* Knob.swift in Sources */, 160 | 944156D61F2121CC00C139B9 /* AppDelegate.swift in Sources */, 161 | 942879671F216F94001C75FF /* KnobStyleKit.swift in Sources */, 162 | 944156F01F21254600C139B9 /* Knob+Touches.swift in Sources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXSourcesBuildPhase section */ 167 | 168 | /* Begin PBXVariantGroup section */ 169 | 944156D91F2121CC00C139B9 /* Main.storyboard */ = { 170 | isa = PBXVariantGroup; 171 | children = ( 172 | 944156DA1F2121CC00C139B9 /* Base */, 173 | ); 174 | name = Main.storyboard; 175 | sourceTree = ""; 176 | }; 177 | 944156DE1F2121CC00C139B9 /* LaunchScreen.storyboard */ = { 178 | isa = PBXVariantGroup; 179 | children = ( 180 | 944156DF1F2121CC00C139B9 /* Base */, 181 | ); 182 | name = LaunchScreen.storyboard; 183 | sourceTree = ""; 184 | }; 185 | /* End PBXVariantGroup section */ 186 | 187 | /* Begin XCBuildConfiguration section */ 188 | 944156E21F2121CC00C139B9 /* Debug */ = { 189 | isa = XCBuildConfiguration; 190 | buildSettings = { 191 | ALWAYS_SEARCH_USER_PATHS = NO; 192 | CLANG_ANALYZER_NONNULL = YES; 193 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 194 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 195 | CLANG_CXX_LIBRARY = "libc++"; 196 | CLANG_ENABLE_MODULES = YES; 197 | CLANG_ENABLE_OBJC_ARC = YES; 198 | CLANG_WARN_BOOL_CONVERSION = YES; 199 | CLANG_WARN_CONSTANT_CONVERSION = YES; 200 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 201 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 202 | CLANG_WARN_EMPTY_BODY = YES; 203 | CLANG_WARN_ENUM_CONVERSION = YES; 204 | CLANG_WARN_INFINITE_RECURSION = YES; 205 | CLANG_WARN_INT_CONVERSION = YES; 206 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 207 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 208 | CLANG_WARN_UNREACHABLE_CODE = YES; 209 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 210 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 211 | COPY_PHASE_STRIP = NO; 212 | DEBUG_INFORMATION_FORMAT = dwarf; 213 | ENABLE_STRICT_OBJC_MSGSEND = YES; 214 | ENABLE_TESTABILITY = YES; 215 | GCC_C_LANGUAGE_STANDARD = gnu99; 216 | GCC_DYNAMIC_NO_PIC = NO; 217 | GCC_NO_COMMON_BLOCKS = YES; 218 | GCC_OPTIMIZATION_LEVEL = 0; 219 | GCC_PREPROCESSOR_DEFINITIONS = ( 220 | "DEBUG=1", 221 | "$(inherited)", 222 | ); 223 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 224 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 225 | GCC_WARN_UNDECLARED_SELECTOR = YES; 226 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 227 | GCC_WARN_UNUSED_FUNCTION = YES; 228 | GCC_WARN_UNUSED_VARIABLE = YES; 229 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 230 | MTL_ENABLE_DEBUG_INFO = YES; 231 | ONLY_ACTIVE_ARCH = YES; 232 | SDKROOT = iphoneos; 233 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 234 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 235 | TARGETED_DEVICE_FAMILY = "1,2"; 236 | }; 237 | name = Debug; 238 | }; 239 | 944156E31F2121CC00C139B9 /* Release */ = { 240 | isa = XCBuildConfiguration; 241 | buildSettings = { 242 | ALWAYS_SEARCH_USER_PATHS = NO; 243 | CLANG_ANALYZER_NONNULL = YES; 244 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 245 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 246 | CLANG_CXX_LIBRARY = "libc++"; 247 | CLANG_ENABLE_MODULES = YES; 248 | CLANG_ENABLE_OBJC_ARC = YES; 249 | CLANG_WARN_BOOL_CONVERSION = YES; 250 | CLANG_WARN_CONSTANT_CONVERSION = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 253 | CLANG_WARN_EMPTY_BODY = YES; 254 | CLANG_WARN_ENUM_CONVERSION = YES; 255 | CLANG_WARN_INFINITE_RECURSION = YES; 256 | CLANG_WARN_INT_CONVERSION = YES; 257 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 258 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 259 | CLANG_WARN_UNREACHABLE_CODE = YES; 260 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 261 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 262 | COPY_PHASE_STRIP = NO; 263 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 264 | ENABLE_NS_ASSERTIONS = NO; 265 | ENABLE_STRICT_OBJC_MSGSEND = YES; 266 | GCC_C_LANGUAGE_STANDARD = gnu99; 267 | GCC_NO_COMMON_BLOCKS = YES; 268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 275 | MTL_ENABLE_DEBUG_INFO = NO; 276 | SDKROOT = iphoneos; 277 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 278 | TARGETED_DEVICE_FAMILY = "1,2"; 279 | VALIDATE_PRODUCT = YES; 280 | }; 281 | name = Release; 282 | }; 283 | 944156E51F2121CC00C139B9 /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 287 | DEVELOPMENT_TEAM = G24WJ3XCZ3; 288 | INFOPLIST_FILE = SynthUISpike/Info.plist; 289 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 290 | PRODUCT_BUNDLE_IDENTIFIER = com.audiokitpro.UniversalKnob; 291 | PRODUCT_NAME = "$(TARGET_NAME)"; 292 | SWIFT_VERSION = 3.0; 293 | }; 294 | name = Debug; 295 | }; 296 | 944156E61F2121CC00C139B9 /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 300 | DEVELOPMENT_TEAM = G24WJ3XCZ3; 301 | INFOPLIST_FILE = SynthUISpike/Info.plist; 302 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 303 | PRODUCT_BUNDLE_IDENTIFIER = com.audiokitpro.UniversalKnob; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | SWIFT_VERSION = 3.0; 306 | }; 307 | name = Release; 308 | }; 309 | /* End XCBuildConfiguration section */ 310 | 311 | /* Begin XCConfigurationList section */ 312 | 944156CD1F2121CC00C139B9 /* Build configuration list for PBXProject "UniversalKnob" */ = { 313 | isa = XCConfigurationList; 314 | buildConfigurations = ( 315 | 944156E21F2121CC00C139B9 /* Debug */, 316 | 944156E31F2121CC00C139B9 /* Release */, 317 | ); 318 | defaultConfigurationIsVisible = 0; 319 | defaultConfigurationName = Release; 320 | }; 321 | 944156E41F2121CC00C139B9 /* Build configuration list for PBXNativeTarget "UniversalKnob" */ = { 322 | isa = XCConfigurationList; 323 | buildConfigurations = ( 324 | 944156E51F2121CC00C139B9 /* Debug */, 325 | 944156E61F2121CC00C139B9 /* Release */, 326 | ); 327 | defaultConfigurationIsVisible = 0; 328 | defaultConfigurationName = Release; 329 | }; 330 | /* End XCConfigurationList section */ 331 | }; 332 | rootObject = 944156CA1F2121CC00C139B9 /* Project object */; 333 | } 334 | --------------------------------------------------------------------------------