├── .gitignore ├── Demo ├── Assets │ ├── eq-slider-border@2x.png │ ├── slider-knob@2x.png │ ├── toleqdemo.gif │ ├── twitter-music-frame.psd │ └── uislider-thumb-image.psd ├── LARSBar.xcodeproj │ └── project.pbxproj ├── LARSBarDemo │ ├── Default-568h@2x.png │ ├── Default.png │ ├── Default@2x.png │ ├── LARSBarDemo-Info.plist │ ├── LARSBarDemo-Prefix.pch │ ├── TOLAppDelegate.h │ ├── TOLAppDelegate.m │ ├── TOLViewController.h │ ├── TOLViewController.m │ ├── en.lproj │ │ ├── InfoPlist.strings │ │ └── TOLViewController.xib │ └── main.m └── Vendor │ └── Novocaine │ ├── Novocaine.h │ └── Novocaine.m ├── LARSBar.podspec ├── LARSBar ├── LARSBar.h └── LARSBar.m ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /Demo/Assets/eq-slider-border@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsacus/LARSBar/dc24cc385be7ee8b82f7dd6c534b3ebc02c52839/Demo/Assets/eq-slider-border@2x.png -------------------------------------------------------------------------------- /Demo/Assets/slider-knob@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsacus/LARSBar/dc24cc385be7ee8b82f7dd6c534b3ebc02c52839/Demo/Assets/slider-knob@2x.png -------------------------------------------------------------------------------- /Demo/Assets/toleqdemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsacus/LARSBar/dc24cc385be7ee8b82f7dd6c534b3ebc02c52839/Demo/Assets/toleqdemo.gif -------------------------------------------------------------------------------- /Demo/Assets/twitter-music-frame.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsacus/LARSBar/dc24cc385be7ee8b82f7dd6c534b3ebc02c52839/Demo/Assets/twitter-music-frame.psd -------------------------------------------------------------------------------- /Demo/Assets/uislider-thumb-image.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsacus/LARSBar/dc24cc385be7ee8b82f7dd6c534b3ebc02c52839/Demo/Assets/uislider-thumb-image.psd -------------------------------------------------------------------------------- /Demo/LARSBar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 46031ABB17278ECB007524A2 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46031ABA17278ECB007524A2 /* QuartzCore.framework */; }; 11 | 461037361730B87200251028 /* Novocaine.m in Sources */ = {isa = PBXBuildFile; fileRef = 461037351730B87200251028 /* Novocaine.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 12 | 461037381730B8B300251028 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 461037371730B8B300251028 /* AudioToolbox.framework */; }; 13 | 4610373A1730B8BD00251028 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 461037391730B8BD00251028 /* Accelerate.framework */; }; 14 | 467BAB45173D3CCB0050B3B7 /* LARSBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 467BAB44173D3CCB0050B3B7 /* LARSBar.m */; }; 15 | 4695089F17277F4300118E00 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4695089E17277F4300118E00 /* UIKit.framework */; }; 16 | 469508A117277F4300118E00 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 469508A017277F4300118E00 /* Foundation.framework */; }; 17 | 469508A317277F4300118E00 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 469508A217277F4300118E00 /* CoreGraphics.framework */; }; 18 | 469508A917277F4300118E00 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 469508A717277F4300118E00 /* InfoPlist.strings */; }; 19 | 469508AB17277F4300118E00 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 469508AA17277F4300118E00 /* main.m */; }; 20 | 469508AF17277F4300118E00 /* TOLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 469508AE17277F4300118E00 /* TOLAppDelegate.m */; }; 21 | 469508B117277F4300118E00 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 469508B017277F4300118E00 /* Default.png */; }; 22 | 469508B317277F4300118E00 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 469508B217277F4300118E00 /* Default@2x.png */; }; 23 | 469508B517277F4300118E00 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 469508B417277F4300118E00 /* Default-568h@2x.png */; }; 24 | 469508B817277F4300118E00 /* TOLViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 469508B717277F4300118E00 /* TOLViewController.m */; }; 25 | 469508BB17277F4300118E00 /* TOLViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 469508B917277F4300118E00 /* TOLViewController.xib */; }; 26 | 46DA8A671734978900D4D292 /* eq-slider-border@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 46DA8A661734978900D4D292 /* eq-slider-border@2x.png */; }; 27 | 46EC87371734A4D800ABF923 /* slider-knob@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 46EC87361734A4D800ABF923 /* slider-knob@2x.png */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 46031ABA17278ECB007524A2 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 32 | 461037341730B87200251028 /* Novocaine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Novocaine.h; sourceTree = ""; }; 33 | 461037351730B87200251028 /* Novocaine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Novocaine.m; sourceTree = ""; }; 34 | 461037371730B8B300251028 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 35 | 461037391730B8BD00251028 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 36 | 467BAB43173D3CCB0050B3B7 /* LARSBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LARSBar.h; sourceTree = ""; }; 37 | 467BAB44173D3CCB0050B3B7 /* LARSBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LARSBar.m; sourceTree = ""; }; 38 | 4695089B17277F4300118E00 /* LARSBarDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LARSBarDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 4695089E17277F4300118E00 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 40 | 469508A017277F4300118E00 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 41 | 469508A217277F4300118E00 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 42 | 469508A617277F4300118E00 /* LARSBarDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "LARSBarDemo-Info.plist"; sourceTree = ""; }; 43 | 469508A817277F4300118E00 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 44 | 469508AA17277F4300118E00 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | 469508AC17277F4300118E00 /* LARSBarDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LARSBarDemo-Prefix.pch"; sourceTree = ""; }; 46 | 469508AD17277F4300118E00 /* TOLAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOLAppDelegate.h; sourceTree = ""; }; 47 | 469508AE17277F4300118E00 /* TOLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOLAppDelegate.m; sourceTree = ""; }; 48 | 469508B017277F4300118E00 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 49 | 469508B217277F4300118E00 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 50 | 469508B417277F4300118E00 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 51 | 469508B617277F4300118E00 /* TOLViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOLViewController.h; sourceTree = ""; }; 52 | 469508B717277F4300118E00 /* TOLViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOLViewController.m; sourceTree = ""; }; 53 | 469508BA17277F4300118E00 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/TOLViewController.xib; sourceTree = ""; }; 54 | 46DA8A661734978900D4D292 /* eq-slider-border@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "eq-slider-border@2x.png"; sourceTree = ""; }; 55 | 46EC87361734A4D800ABF923 /* slider-knob@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "slider-knob@2x.png"; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | 4695089817277F4300118E00 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | 4610373A1730B8BD00251028 /* Accelerate.framework in Frameworks */, 64 | 461037381730B8B300251028 /* AudioToolbox.framework in Frameworks */, 65 | 46031ABB17278ECB007524A2 /* QuartzCore.framework in Frameworks */, 66 | 4695089F17277F4300118E00 /* UIKit.framework in Frameworks */, 67 | 469508A117277F4300118E00 /* Foundation.framework in Frameworks */, 68 | 469508A317277F4300118E00 /* CoreGraphics.framework in Frameworks */, 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | /* End PBXFrameworksBuildPhase section */ 73 | 74 | /* Begin PBXGroup section */ 75 | 461037331730B87200251028 /* Novocaine */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 461037341730B87200251028 /* Novocaine.h */, 79 | 461037351730B87200251028 /* Novocaine.m */, 80 | ); 81 | name = Novocaine; 82 | path = Vendor/Novocaine; 83 | sourceTree = ""; 84 | }; 85 | 467BAB42173D3CCB0050B3B7 /* LARSBar */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 467BAB43173D3CCB0050B3B7 /* LARSBar.h */, 89 | 467BAB44173D3CCB0050B3B7 /* LARSBar.m */, 90 | ); 91 | name = LARSBar; 92 | path = ../LARSBar; 93 | sourceTree = ""; 94 | }; 95 | 4695089217277F4300118E00 = { 96 | isa = PBXGroup; 97 | children = ( 98 | 467BAB42173D3CCB0050B3B7 /* LARSBar */, 99 | 46DA8A651734978900D4D292 /* Assets */, 100 | 461037331730B87200251028 /* Novocaine */, 101 | 469508A417277F4300118E00 /* LARSBarDemo */, 102 | 4695089D17277F4300118E00 /* Frameworks */, 103 | 4695089C17277F4300118E00 /* Products */, 104 | ); 105 | sourceTree = ""; 106 | }; 107 | 4695089C17277F4300118E00 /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 4695089B17277F4300118E00 /* LARSBarDemo.app */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 4695089D17277F4300118E00 /* Frameworks */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 461037391730B8BD00251028 /* Accelerate.framework */, 119 | 461037371730B8B300251028 /* AudioToolbox.framework */, 120 | 46031ABA17278ECB007524A2 /* QuartzCore.framework */, 121 | 4695089E17277F4300118E00 /* UIKit.framework */, 122 | 469508A017277F4300118E00 /* Foundation.framework */, 123 | 469508A217277F4300118E00 /* CoreGraphics.framework */, 124 | ); 125 | name = Frameworks; 126 | sourceTree = ""; 127 | }; 128 | 469508A417277F4300118E00 /* LARSBarDemo */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 469508AD17277F4300118E00 /* TOLAppDelegate.h */, 132 | 469508AE17277F4300118E00 /* TOLAppDelegate.m */, 133 | 469508B617277F4300118E00 /* TOLViewController.h */, 134 | 469508B717277F4300118E00 /* TOLViewController.m */, 135 | 469508B917277F4300118E00 /* TOLViewController.xib */, 136 | 469508A517277F4300118E00 /* Supporting Files */, 137 | ); 138 | path = LARSBarDemo; 139 | sourceTree = ""; 140 | }; 141 | 469508A517277F4300118E00 /* Supporting Files */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 469508A617277F4300118E00 /* LARSBarDemo-Info.plist */, 145 | 469508A717277F4300118E00 /* InfoPlist.strings */, 146 | 469508AA17277F4300118E00 /* main.m */, 147 | 469508AC17277F4300118E00 /* LARSBarDemo-Prefix.pch */, 148 | 469508B017277F4300118E00 /* Default.png */, 149 | 469508B217277F4300118E00 /* Default@2x.png */, 150 | 469508B417277F4300118E00 /* Default-568h@2x.png */, 151 | ); 152 | name = "Supporting Files"; 153 | sourceTree = ""; 154 | }; 155 | 46DA8A651734978900D4D292 /* Assets */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 46EC87361734A4D800ABF923 /* slider-knob@2x.png */, 159 | 46DA8A661734978900D4D292 /* eq-slider-border@2x.png */, 160 | ); 161 | path = Assets; 162 | sourceTree = ""; 163 | }; 164 | /* End PBXGroup section */ 165 | 166 | /* Begin PBXNativeTarget section */ 167 | 4695089A17277F4300118E00 /* LARSBarDemo */ = { 168 | isa = PBXNativeTarget; 169 | buildConfigurationList = 469508BE17277F4400118E00 /* Build configuration list for PBXNativeTarget "LARSBarDemo" */; 170 | buildPhases = ( 171 | 4695089717277F4300118E00 /* Sources */, 172 | 4695089817277F4300118E00 /* Frameworks */, 173 | 4695089917277F4300118E00 /* Resources */, 174 | ); 175 | buildRules = ( 176 | ); 177 | dependencies = ( 178 | ); 179 | name = LARSBarDemo; 180 | productName = LARSBarDemo; 181 | productReference = 4695089B17277F4300118E00 /* LARSBarDemo.app */; 182 | productType = "com.apple.product-type.application"; 183 | }; 184 | /* End PBXNativeTarget section */ 185 | 186 | /* Begin PBXProject section */ 187 | 4695089317277F4300118E00 /* Project object */ = { 188 | isa = PBXProject; 189 | attributes = { 190 | CLASSPREFIX = TOL; 191 | LastUpgradeCheck = 0460; 192 | ORGANIZATIONNAME = theonlylars; 193 | }; 194 | buildConfigurationList = 4695089617277F4300118E00 /* Build configuration list for PBXProject "LARSBar" */; 195 | compatibilityVersion = "Xcode 3.2"; 196 | developmentRegion = English; 197 | hasScannedForEncodings = 0; 198 | knownRegions = ( 199 | en, 200 | ); 201 | mainGroup = 4695089217277F4300118E00; 202 | productRefGroup = 4695089C17277F4300118E00 /* Products */; 203 | projectDirPath = ""; 204 | projectRoot = ""; 205 | targets = ( 206 | 4695089A17277F4300118E00 /* LARSBarDemo */, 207 | ); 208 | }; 209 | /* End PBXProject section */ 210 | 211 | /* Begin PBXResourcesBuildPhase section */ 212 | 4695089917277F4300118E00 /* Resources */ = { 213 | isa = PBXResourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | 469508A917277F4300118E00 /* InfoPlist.strings in Resources */, 217 | 469508B117277F4300118E00 /* Default.png in Resources */, 218 | 469508B317277F4300118E00 /* Default@2x.png in Resources */, 219 | 469508B517277F4300118E00 /* Default-568h@2x.png in Resources */, 220 | 469508BB17277F4300118E00 /* TOLViewController.xib in Resources */, 221 | 46DA8A671734978900D4D292 /* eq-slider-border@2x.png in Resources */, 222 | 46EC87371734A4D800ABF923 /* slider-knob@2x.png in Resources */, 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | }; 226 | /* End PBXResourcesBuildPhase section */ 227 | 228 | /* Begin PBXSourcesBuildPhase section */ 229 | 4695089717277F4300118E00 /* Sources */ = { 230 | isa = PBXSourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | 469508AB17277F4300118E00 /* main.m in Sources */, 234 | 469508AF17277F4300118E00 /* TOLAppDelegate.m in Sources */, 235 | 469508B817277F4300118E00 /* TOLViewController.m in Sources */, 236 | 461037361730B87200251028 /* Novocaine.m in Sources */, 237 | 467BAB45173D3CCB0050B3B7 /* LARSBar.m in Sources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXSourcesBuildPhase section */ 242 | 243 | /* Begin PBXVariantGroup section */ 244 | 469508A717277F4300118E00 /* InfoPlist.strings */ = { 245 | isa = PBXVariantGroup; 246 | children = ( 247 | 469508A817277F4300118E00 /* en */, 248 | ); 249 | name = InfoPlist.strings; 250 | sourceTree = ""; 251 | }; 252 | 469508B917277F4300118E00 /* TOLViewController.xib */ = { 253 | isa = PBXVariantGroup; 254 | children = ( 255 | 469508BA17277F4300118E00 /* en */, 256 | ); 257 | name = TOLViewController.xib; 258 | sourceTree = ""; 259 | }; 260 | /* End PBXVariantGroup section */ 261 | 262 | /* Begin XCBuildConfiguration section */ 263 | 469508BC17277F4400118E00 /* Debug */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 275 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 276 | COPY_PHASE_STRIP = NO; 277 | GCC_C_LANGUAGE_STANDARD = gnu99; 278 | GCC_DYNAMIC_NO_PIC = NO; 279 | GCC_OPTIMIZATION_LEVEL = 0; 280 | GCC_PREPROCESSOR_DEFINITIONS = ( 281 | "DEBUG=1", 282 | "$(inherited)", 283 | ); 284 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 285 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 289 | ONLY_ACTIVE_ARCH = YES; 290 | SDKROOT = iphoneos; 291 | }; 292 | name = Debug; 293 | }; 294 | 469508BD17277F4400118E00 /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 299 | CLANG_CXX_LIBRARY = "libc++"; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_WARN_CONSTANT_CONVERSION = YES; 302 | CLANG_WARN_EMPTY_BODY = YES; 303 | CLANG_WARN_ENUM_CONVERSION = YES; 304 | CLANG_WARN_INT_CONVERSION = YES; 305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 306 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 307 | COPY_PHASE_STRIP = YES; 308 | GCC_C_LANGUAGE_STANDARD = gnu99; 309 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 310 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 311 | GCC_WARN_UNUSED_VARIABLE = YES; 312 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 313 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 314 | SDKROOT = iphoneos; 315 | VALIDATE_PRODUCT = YES; 316 | }; 317 | name = Release; 318 | }; 319 | 469508BF17277F4400118E00 /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | CODE_SIGN_IDENTITY = "iPhone Developer"; 323 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 324 | GCC_PREFIX_HEADER = "LARSBarDemo/LARSBarDemo-Prefix.pch"; 325 | INFOPLIST_FILE = "LARSBarDemo/LARSBarDemo-Info.plist"; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | PROVISIONING_PROFILE = ""; 328 | WARNING_CFLAGS = ( 329 | "-Wextra", 330 | "-Wall", 331 | "-Wno-unused-parameter", 332 | ); 333 | WRAPPER_EXTENSION = app; 334 | }; 335 | name = Debug; 336 | }; 337 | 469508C017277F4400118E00 /* Release */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | CODE_SIGN_IDENTITY = "iPhone Developer"; 341 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 342 | GCC_PREFIX_HEADER = "LARSBarDemo/LARSBarDemo-Prefix.pch"; 343 | INFOPLIST_FILE = "LARSBarDemo/LARSBarDemo-Info.plist"; 344 | PRODUCT_NAME = "$(TARGET_NAME)"; 345 | PROVISIONING_PROFILE = ""; 346 | WARNING_CFLAGS = ( 347 | "-Wextra", 348 | "-Wall", 349 | "-Wno-unused-parameter", 350 | ); 351 | WRAPPER_EXTENSION = app; 352 | }; 353 | name = Release; 354 | }; 355 | /* End XCBuildConfiguration section */ 356 | 357 | /* Begin XCConfigurationList section */ 358 | 4695089617277F4300118E00 /* Build configuration list for PBXProject "LARSBar" */ = { 359 | isa = XCConfigurationList; 360 | buildConfigurations = ( 361 | 469508BC17277F4400118E00 /* Debug */, 362 | 469508BD17277F4400118E00 /* Release */, 363 | ); 364 | defaultConfigurationIsVisible = 0; 365 | defaultConfigurationName = Release; 366 | }; 367 | 469508BE17277F4400118E00 /* Build configuration list for PBXNativeTarget "LARSBarDemo" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | 469508BF17277F4400118E00 /* Debug */, 371 | 469508C017277F4400118E00 /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | /* End XCConfigurationList section */ 377 | }; 378 | rootObject = 4695089317277F4300118E00 /* Project object */; 379 | } 380 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsacus/LARSBar/dc24cc385be7ee8b82f7dd6c534b3ebc02c52839/Demo/LARSBarDemo/Default-568h@2x.png -------------------------------------------------------------------------------- /Demo/LARSBarDemo/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsacus/LARSBar/dc24cc385be7ee8b82f7dd6c534b3ebc02c52839/Demo/LARSBarDemo/Default.png -------------------------------------------------------------------------------- /Demo/LARSBarDemo/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsacus/LARSBar/dc24cc385be7ee8b82f7dd6c534b3ebc02c52839/Demo/LARSBarDemo/Default@2x.png -------------------------------------------------------------------------------- /Demo/LARSBarDemo/LARSBarDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | LARSBar 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.theonlylars.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | LARSBar 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/LARSBarDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'LARSBarDemo' target in the 'LARSBarDemo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/TOLAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOLAppDelegate.h 3 | // LARSBarDemo 4 | // 5 | // Created by Lars Anderson on 4/23/13. 6 | // Copyright (c) 2013 theonlylars. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TOLViewController; 12 | 13 | @interface TOLAppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @property (strong, nonatomic) TOLViewController *viewController; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/TOLAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOLAppDelegate.m 3 | // LARSBarDemo 4 | // 5 | // Created by Lars Anderson on 4/23/13. 6 | // Copyright (c) 2013 theonlylars. All rights reserved. 7 | // 8 | 9 | #import "TOLAppDelegate.h" 10 | 11 | #import "TOLViewController.h" 12 | #import "Novocaine.h" 13 | 14 | @implementation TOLAppDelegate 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 19 | // Override point for customization after application launch. 20 | self.viewController = [[TOLViewController alloc] initWithNibName:@"TOLViewController" bundle:nil]; 21 | self.window.rootViewController = self.viewController; 22 | [self.window makeKeyAndVisible]; 23 | return YES; 24 | } 25 | 26 | - (void)applicationWillResignActive:(UIApplication *)application 27 | { 28 | // 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. 29 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 30 | } 31 | 32 | - (void)applicationDidEnterBackground:(UIApplication *)application 33 | { 34 | // 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. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | - (void)applicationWillEnterForeground:(UIApplication *)application 39 | { 40 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 41 | } 42 | 43 | - (void)applicationDidBecomeActive:(UIApplication *)application 44 | { 45 | // 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. 46 | } 47 | 48 | - (void)applicationWillTerminate:(UIApplication *)application 49 | { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/TOLViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOLViewController.h 3 | // LARSBarDemo 4 | // 5 | // Created by Lars Anderson on 4/23/13. 6 | // Copyright (c) 2013 theonlylars. All rights reserved. 7 | // 8 | 9 | #import 10 | @class LARSBar; 11 | 12 | @interface TOLViewController : UIViewController 13 | @property (weak, nonatomic) IBOutlet LARSBar *eqSlider; 14 | @property (weak, nonatomic) IBOutlet UIImageView *sliderFrame; 15 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *frameHeightConstraint; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/TOLViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOLViewController.m 3 | // LARSBarDemo 4 | // 5 | // Created by Lars Anderson on 4/23/13. 6 | // Copyright (c) 2013 theonlylars. All rights reserved. 7 | // 8 | 9 | #import "TOLViewController.h" 10 | #import "LARSBar.h" 11 | #import "Novocaine.h" 12 | 13 | @interface TOLViewController () 14 | 15 | @property (nonatomic, retain) Novocaine *audioManager; 16 | 17 | @end 18 | 19 | @implementation TOLViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | // Do any additional setup after loading the view, typically from a nib. 25 | 26 | UIImage *backgroundImage = [UIImage imageNamed:@"eq-slider-border"]; 27 | 28 | self.frameHeightConstraint.constant = backgroundImage.size.height; 29 | 30 | backgroundImage = [backgroundImage resizableImageWithCapInsets:UIEdgeInsetsMake(0.f, 12.f, 0.f, 12.f)]; 31 | self.sliderFrame.image = backgroundImage; 32 | 33 | UIImage *sliderKnob = [UIImage imageNamed:@"slider-knob"]; 34 | [self.eqSlider setThumbImage:sliderKnob forState:UIControlStateNormal]; 35 | 36 | self.audioManager = [Novocaine audioManager]; 37 | [self.audioManager setSamplingRate:1/60.f]; 38 | 39 | self.view.backgroundColor = [UIColor colorWithRed:0.14 green:0.15 blue:0.16 alpha:1.0]; 40 | 41 | // MEASURE SOME DECIBELS! 42 | // ================================================== 43 | __block CGFloat dbVal = 0.0f; 44 | typeof(self) __weak weakSelf = self; 45 | [self.audioManager setInputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels) { 46 | vDSP_vsq(data, 1, data, 1, numFrames*numChannels); 47 | float meanVal = 0.0f; 48 | vDSP_meanv(data, 1, &meanVal, numFrames*numChannels); 49 | float one = 1.0; 50 | vDSP_vdbcon(&meanVal, 1, &one, &meanVal, 1, 1, 0); 51 | dbVal = dbVal + 0.2f*(meanVal - dbVal); 52 | if (isnan(dbVal)) { 53 | dbVal = 0.f; 54 | } 55 | 56 | CGFloat max = 0.f; 57 | CGFloat min = -60.f; 58 | CGFloat percentage = 1.f-dbVal/(min-max); 59 | dispatch_async(dispatch_get_main_queue(), ^{ 60 | weakSelf.eqSlider.leftChannelLevel = percentage; 61 | weakSelf.eqSlider.rightChannelLevel = percentage; 62 | }); 63 | 64 | // NSLog(@"Decibel level: %f (%f)\n", dbVal, percentage); 65 | }]; 66 | } 67 | 68 | - (void)didReceiveMemoryWarning 69 | { 70 | [super didReceiveMemoryWarning]; 71 | // Dispose of any resources that can be recreated. 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/en.lproj/TOLViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1552 5 | 12D78 6 | 3084 7 | 1187.37 8 | 626.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 2083 12 | 13 | 14 | IBNSLayoutConstraint 15 | IBProxyObject 16 | IBUIImageView 17 | IBUISlider 18 | IBUIView 19 | 20 | 21 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 22 | 23 | 24 | PluginDependencyRecalculationVersion 25 | 26 | 27 | 28 | 29 | IBFilesOwner 30 | IBCocoaTouchFramework 31 | 32 | 33 | IBFirstResponder 34 | IBCocoaTouchFramework 35 | 36 | 37 | 38 | 274 39 | 40 | 41 | 42 | 292 43 | {{10, 260}, {300, 28}} 44 | 45 | 46 | 47 | _NS:9 48 | NO 49 | IBCocoaTouchFramework 50 | 51 | 52 | 53 | 292 54 | {{18, 263}, {284, 23}} 55 | 56 | 57 | 58 | _NS:9 59 | NO 60 | IBCocoaTouchFramework 61 | 0 62 | 0 63 | 0.5 64 | 65 | 66 | {{0, 20}, {320, 548}} 67 | 68 | 69 | 70 | 71 | 3 72 | MC43NQA 73 | 74 | 2 75 | 76 | 77 | NO 78 | 79 | 80 | IBUIScreenMetrics 81 | 82 | YES 83 | 84 | 85 | 86 | 87 | 88 | {320, 568} 89 | {568, 320} 90 | 91 | 92 | IBCocoaTouchFramework 93 | Retina 4 Full Screen 94 | 2 95 | 96 | IBCocoaTouchFramework 97 | 98 | 99 | 100 | 101 | 102 | 103 | view 104 | 105 | 106 | 107 | 7 108 | 109 | 110 | 111 | eqSlider 112 | 113 | 114 | 115 | 13 116 | 117 | 118 | 119 | sliderFrame 120 | 121 | 122 | 123 | 28 124 | 125 | 126 | 127 | frameHeightConstraint 128 | 129 | 130 | 131 | 8 132 | 0 133 | 134 | 0 135 | 1 136 | 137 | 28 138 | 139 | 1000 140 | 141 | 9 142 | 40 143 | 1 144 | 145 | 146 | 57 147 | 148 | 149 | 150 | 151 | 152 | 0 153 | 154 | 155 | 156 | 157 | 158 | -1 159 | 160 | 161 | File's Owner 162 | 163 | 164 | -2 165 | 166 | 167 | 168 | 169 | 6 170 | 171 | 172 | 173 | 174 | 175 | 6 176 | 0 177 | 178 | 6 179 | 1 180 | 181 | 20 182 | 183 | 1000 184 | 185 | 8 186 | 29 187 | 3 188 | 189 | 190 | 191 | 5 192 | 0 193 | 194 | 5 195 | 1 196 | 197 | 20 198 | 199 | 1000 200 | 201 | 8 202 | 29 203 | 3 204 | 205 | 206 | 207 | 10 208 | 0 209 | 210 | 10 211 | 1 212 | 213 | 0.0 214 | 215 | 1000 216 | 217 | 5 218 | 22 219 | 2 220 | 221 | 222 | 223 | 10 224 | 0 225 | 226 | 10 227 | 1 228 | 229 | 0.0 230 | 231 | 1000 232 | 233 | 6 234 | 24 235 | 2 236 | 237 | 238 | 239 | 6 240 | 0 241 | 242 | 6 243 | 1 244 | 245 | 10 246 | 247 | 1000 248 | 249 | 9 250 | 40 251 | 3 252 | 253 | 254 | 255 | 5 256 | 0 257 | 258 | 5 259 | 1 260 | 261 | 10 262 | 263 | 1000 264 | 265 | 9 266 | 40 267 | 3 268 | 269 | 270 | 271 | 272 | 273 | 274 | 8 275 | 276 | 277 | 278 | 279 | 280 | 9 281 | 282 | 283 | 284 | 285 | 12 286 | 287 | 288 | 289 | 290 | 14 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 17 299 | 300 | 301 | 302 | 303 | 29 304 | 305 | 306 | 307 | 308 | 38 309 | 310 | 311 | 312 | 313 | 21 314 | 315 | 316 | 317 | 318 | 42 319 | 320 | 321 | 322 | 323 | 324 | 325 | TOLViewController 326 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 327 | UIResponder 328 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 329 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 330 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 331 | 332 | 333 | 334 | 335 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 336 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 337 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 338 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 339 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 340 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | LARSBar 350 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 351 | 352 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 353 | 354 | 355 | 356 | 357 | 358 | 57 359 | 360 | 361 | 362 | 363 | LARSBar 364 | UISlider 365 | 366 | IBProjectSource 367 | ./Classes/LARSBar.h 368 | 369 | 370 | 371 | NSLayoutConstraint 372 | NSObject 373 | 374 | IBProjectSource 375 | ./Classes/NSLayoutConstraint.h 376 | 377 | 378 | 379 | TOLViewController 380 | UIViewController 381 | 382 | LARSBar 383 | NSLayoutConstraint 384 | UIImageView 385 | 386 | 387 | 388 | eqSlider 389 | LARSBar 390 | 391 | 392 | frameHeightConstraint 393 | NSLayoutConstraint 394 | 395 | 396 | sliderFrame 397 | UIImageView 398 | 399 | 400 | 401 | IBProjectSource 402 | ./Classes/TOLViewController.h 403 | 404 | 405 | 406 | 407 | 0 408 | IBCocoaTouchFramework 409 | YES 410 | 3 411 | YES 412 | 2083 413 | 414 | 415 | -------------------------------------------------------------------------------- /Demo/LARSBarDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LARSBarDemo 4 | // 5 | // Created by Lars Anderson on 4/23/13. 6 | // Copyright (c) 2013 theonlylars. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TOLAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([TOLAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Demo/Vendor/Novocaine/Novocaine.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Alex Wiltschko 2 | // 3 | // Permission is hereby granted, free of charge, to any person 4 | // obtaining a copy of this software and associated documentation 5 | // files (the "Software"), to deal in the Software without 6 | // restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following 10 | // conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | // OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | #import 25 | #import 26 | #import 27 | 28 | #if defined __MAC_OS_X_VERSION_MAX_ALLOWED 29 | #define USING_OSX 30 | #include 31 | #else 32 | #define USING_IOS 33 | #endif 34 | 35 | #include 36 | 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | static void CheckError(OSStatus error, const char *operation) 43 | { 44 | if (error == noErr) return; 45 | 46 | char str[20]; 47 | // see if it appears to be a 4-char-code 48 | *(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error); 49 | if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) { 50 | str[0] = str[5] = '\''; 51 | str[6] = '\0'; 52 | } else 53 | // no, format it as an integer 54 | sprintf(str, "%d", (int)error); 55 | 56 | fprintf(stderr, "Error: %s (%s)\n", operation, str); 57 | 58 | exit(1); 59 | } 60 | 61 | 62 | OSStatus inputCallback (void *inRefCon, 63 | AudioUnitRenderActionFlags * ioActionFlags, 64 | const AudioTimeStamp * inTimeStamp, 65 | UInt32 inOutputBusNumber, 66 | UInt32 inNumberFrames, 67 | AudioBufferList * ioData); 68 | 69 | OSStatus renderCallback (void *inRefCon, 70 | AudioUnitRenderActionFlags * ioActionFlags, 71 | const AudioTimeStamp * inTimeStamp, 72 | UInt32 inOutputBusNumber, 73 | UInt32 inNumberFrames, 74 | AudioBufferList * ioData); 75 | 76 | 77 | #if defined (USING_IOS) 78 | void sessionPropertyListener(void * inClientData, 79 | AudioSessionPropertyID inID, 80 | UInt32 inDataSize, 81 | const void * inData); 82 | 83 | #endif 84 | 85 | 86 | void sessionInterruptionListener(void *inClientData, UInt32 inInterruption); 87 | 88 | #ifdef __cplusplus 89 | } 90 | #endif 91 | 92 | typedef void (^OutputBlock)(float *data, UInt32 numFrames, UInt32 numChannels); 93 | typedef void (^InputBlock)(float *data, UInt32 numFrames, UInt32 numChannels); 94 | 95 | #if defined (USING_IOS) 96 | @interface Novocaine : NSObject 97 | #elif defined (USING_OSX) 98 | @interface Novocaine : NSObject 99 | #endif 100 | { 101 | // Audio Handling 102 | AudioUnit inputUnit; 103 | AudioUnit outputUnit; 104 | AudioBufferList *inputBuffer; 105 | 106 | // Session Properties 107 | BOOL inputAvailable; 108 | NSString *inputRoute; 109 | UInt32 numInputChannels; 110 | UInt32 numOutputChannels; 111 | Float64 samplingRate; 112 | BOOL isInterleaved; 113 | UInt32 numBytesPerSample; 114 | AudioStreamBasicDescription inputFormat; 115 | AudioStreamBasicDescription outputFormat; 116 | 117 | // Audio Processing 118 | OutputBlock outputBlock; 119 | InputBlock inputBlock; 120 | 121 | float *inData; 122 | float *outData; 123 | 124 | BOOL playing; 125 | // BOOL playThroughEnabled; 126 | 127 | 128 | #if defined (USING_OSX) 129 | AudioDeviceID *deviceIDs; 130 | NSMutableArray *deviceNames; 131 | AudioDeviceID defaultInputDeviceID; 132 | NSString *defaultDeviceName; 133 | #endif 134 | 135 | } 136 | 137 | @property AudioUnit inputUnit; 138 | @property AudioUnit outputUnit; 139 | @property AudioBufferList *inputBuffer; 140 | @property (nonatomic, copy) OutputBlock outputBlock; 141 | @property (nonatomic, copy) InputBlock inputBlock; 142 | @property BOOL inputAvailable; 143 | @property (nonatomic, retain) NSString *inputRoute; 144 | @property UInt32 numInputChannels; 145 | @property UInt32 numOutputChannels; 146 | @property Float64 samplingRate; 147 | @property BOOL isInterleaved; 148 | @property UInt32 numBytesPerSample; 149 | @property AudioStreamBasicDescription inputFormat; 150 | @property AudioStreamBasicDescription outputFormat; 151 | 152 | // @property BOOL playThroughEnabled; 153 | @property BOOL playing; 154 | @property float *inData; 155 | @property float *outData; 156 | 157 | #if defined (USING_OSX) 158 | @property AudioDeviceID *deviceIDs; 159 | @property (nonatomic, retain) NSMutableArray *deviceNames; 160 | @property AudioDeviceID defaultInputDeviceID; 161 | @property (nonatomic, retain) NSString *defaultInputDeviceName; 162 | @property AudioDeviceID defaultOutputDeviceID; 163 | @property (nonatomic, retain) NSString *defaultOutputDeviceName; 164 | - (void)enumerateAudioDevices; 165 | #endif 166 | 167 | 168 | // Singleton methods 169 | + (Novocaine *) audioManager; 170 | 171 | 172 | // Audio Unit methods 173 | - (void)play; 174 | - (void)pause; 175 | - (void)setupAudio; 176 | - (void)ifAudioInputIsAvailableThenSetupAudioSession; 177 | 178 | #if defined ( USING_IOS ) 179 | - (void)checkSessionProperties; 180 | - (void)checkAudioSource; 181 | #endif 182 | 183 | 184 | @end 185 | -------------------------------------------------------------------------------- /Demo/Vendor/Novocaine/Novocaine.m: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Alex Wiltschko 2 | // 3 | // Permission is hereby granted, free of charge, to any person 4 | // obtaining a copy of this software and associated documentation 5 | // files (the "Software"), to deal in the Software without 6 | // restriction, including without limitation the rights to use, 7 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the 9 | // Software is furnished to do so, subject to the following 10 | // conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be 13 | // included in all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | // OTHER DEALINGS IN THE SOFTWARE. 23 | // 24 | // TODO: 25 | // Switching mic and speaker on/off 26 | // 27 | // HOUSEKEEPING AND NICE FEATURES: 28 | // Disambiguate outputFormat (the AUHAL's stream format) 29 | // More nuanced input detection on the Mac 30 | // Route switching should work, check with iPhone 31 | // Device switching should work, check with laptop. Read that damn book. 32 | // Wrap logging with debug macros. 33 | // Think about what should be public, what private. 34 | // Ability to select non-default devices. 35 | 36 | 37 | #import "Novocaine.h" 38 | #define kInputBus 1 39 | #define kOutputBus 0 40 | #define kDefaultDevice 999999 41 | 42 | #import "TargetConditionals.h" 43 | 44 | static Novocaine *audioManager = nil; 45 | 46 | @interface Novocaine() 47 | - (void)setupAudio; 48 | 49 | - (NSString *)applicationDocumentsDirectory; 50 | 51 | @end 52 | 53 | 54 | @implementation Novocaine 55 | @synthesize inputUnit; 56 | @synthesize outputUnit; 57 | @synthesize inputBuffer; 58 | @synthesize inputRoute, inputAvailable; 59 | @synthesize numInputChannels, numOutputChannels; 60 | @synthesize inputBlock, outputBlock; 61 | @synthesize samplingRate; 62 | @synthesize isInterleaved; 63 | @synthesize numBytesPerSample; 64 | @synthesize inData; 65 | @synthesize outData; 66 | @synthesize playing; 67 | 68 | @synthesize outputFormat; 69 | @synthesize inputFormat; 70 | // @synthesize playThroughEnabled; 71 | 72 | #if defined( USING_OSX ) 73 | @synthesize deviceIDs; 74 | @synthesize deviceNames; 75 | @synthesize defaultInputDeviceID; 76 | @synthesize defaultInputDeviceName; 77 | @synthesize defaultOutputDeviceID; 78 | @synthesize defaultOutputDeviceName; 79 | #endif 80 | 81 | #pragma mark - Singleton Methods 82 | + (Novocaine *) audioManager 83 | { 84 | @synchronized(self) 85 | { 86 | if (audioManager == nil) { 87 | audioManager = [[Novocaine alloc] init]; 88 | } 89 | } 90 | return audioManager; 91 | } 92 | 93 | + (id)allocWithZone:(NSZone *)zone { 94 | @synchronized(self) { 95 | if (audioManager == nil) { 96 | audioManager = [super allocWithZone:zone]; 97 | return audioManager; // assignment and return on first allocation 98 | } 99 | } 100 | return nil; // on subsequent allocation attempts return nil 101 | } 102 | 103 | - (id)copyWithZone:(NSZone *)zone 104 | { 105 | return self; 106 | } 107 | 108 | - (id)retain { 109 | return self; 110 | } 111 | 112 | - (unsigned)retainCount { 113 | return UINT_MAX; // denotes an object that cannot be released 114 | } 115 | 116 | - (oneway void)release { 117 | //do nothing 118 | } 119 | 120 | - (id)init 121 | { 122 | if (self = [super init]) 123 | { 124 | 125 | // Initialize some stuff k? 126 | outputBlock = nil; 127 | inputBlock = nil; 128 | 129 | // Initialize a float buffer to hold audio 130 | self.inData = (float *)calloc(8192, sizeof(float)); // probably more than we'll need 131 | self.outData = (float *)calloc(8192, sizeof(float)); 132 | 133 | self.inputBlock = nil; 134 | self.outputBlock = nil; 135 | 136 | #if defined ( USING_OSX ) 137 | self.deviceNames = [[NSMutableArray alloc] initWithCapacity:100]; // more than we'll need 138 | #endif 139 | 140 | self.playing = NO; 141 | // self.playThroughEnabled = NO; 142 | 143 | // Fire up the audio session ( with steady error checking ... ) 144 | [self ifAudioInputIsAvailableThenSetupAudioSession]; 145 | 146 | return self; 147 | 148 | } 149 | 150 | return nil; 151 | } 152 | 153 | 154 | #pragma mark - Block Handling 155 | - (void)setInputBlock:(InputBlock)newInputBlock 156 | { 157 | InputBlock tmpBlock = inputBlock; 158 | inputBlock = Block_copy(newInputBlock); 159 | Block_release(tmpBlock); 160 | } 161 | 162 | - (void)setOutputBlock:(OutputBlock)newOutputBlock 163 | { 164 | OutputBlock tmpBlock = outputBlock; 165 | outputBlock = Block_copy(newOutputBlock); 166 | Block_release(tmpBlock); 167 | } 168 | 169 | 170 | 171 | #pragma mark - Audio Methods 172 | 173 | 174 | - (void)ifAudioInputIsAvailableThenSetupAudioSession { 175 | // Initialize and configure the audio session, and add an interuption listener 176 | 177 | #if defined ( USING_IOS ) 178 | CheckError( AudioSessionInitialize(NULL, NULL, sessionInterruptionListener, self), "Couldn't initialize audio session"); 179 | [self checkAudioSource]; 180 | #elif defined ( USING_OSX ) 181 | // TODO: grab the audio device 182 | [self enumerateAudioDevices]; 183 | self.inputAvailable = YES; 184 | #endif 185 | 186 | // Check the session properties (available input routes, number of channels, etc) 187 | 188 | 189 | 190 | // If we do have input, then let's rock 'n roll. 191 | if (self.inputAvailable) { 192 | [self setupAudio]; 193 | [self play]; 194 | } 195 | 196 | // If we don't have input, then ask the user to provide some 197 | else 198 | { 199 | #if defined ( USING_IOS ) 200 | UIAlertView *noInputAlert = 201 | [[UIAlertView alloc] initWithTitle:@"No Audio Input" 202 | message:@"Couldn't find any audio input. Plug in your Apple headphones or another microphone." 203 | delegate:self 204 | cancelButtonTitle:@"OK" 205 | otherButtonTitles:nil]; 206 | 207 | [noInputAlert show]; 208 | [noInputAlert release]; 209 | #endif 210 | 211 | } 212 | } 213 | 214 | 215 | - (void)setupAudio 216 | { 217 | 218 | 219 | // --- Audio Session Setup --- 220 | // --------------------------- 221 | 222 | #if defined ( USING_IOS ) 223 | 224 | UInt32 sessionCategory = kAudioSessionCategory_PlayAndRecord; 225 | CheckError( AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, 226 | sizeof (sessionCategory), 227 | &sessionCategory), "Couldn't set audio category"); 228 | 229 | 230 | // Add a property listener, to listen to changes to the session 231 | CheckError( AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, sessionPropertyListener, self), "Couldn't add audio session property listener"); 232 | 233 | // Set the buffer size, this will affect the number of samples that get rendered every time the audio callback is fired 234 | // A small number will get you lower latency audio, but will make your processor work harder 235 | #if !TARGET_IPHONE_SIMULATOR 236 | Float32 preferredBufferSize = 0.0232; 237 | CheckError( AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(preferredBufferSize), &preferredBufferSize), "Couldn't set the preferred buffer duration"); 238 | #endif 239 | 240 | 241 | // Set the audio session active 242 | CheckError( AudioSessionSetActive(YES), "Couldn't activate the audio session"); 243 | 244 | [self checkSessionProperties]; 245 | 246 | #elif defined ( USING_OSX ) 247 | 248 | 249 | 250 | #endif 251 | 252 | 253 | 254 | // ----- Audio Unit Setup ----- 255 | // ---------------------------- 256 | 257 | 258 | // Describe the output unit. 259 | 260 | #if defined ( USING_OSX ) 261 | AudioComponentDescription inputDescription = {0}; 262 | inputDescription.componentType = kAudioUnitType_Output; 263 | inputDescription.componentSubType = kAudioUnitSubType_HALOutput; 264 | inputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; 265 | 266 | AudioComponentDescription outputDescription = {0}; 267 | outputDescription.componentType = kAudioUnitType_Output; 268 | outputDescription.componentSubType = kAudioUnitSubType_HALOutput; 269 | outputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; 270 | 271 | #elif defined (USING_IOS) 272 | AudioComponentDescription inputDescription = {0}; 273 | inputDescription.componentType = kAudioUnitType_Output; 274 | inputDescription.componentSubType = kAudioUnitSubType_RemoteIO; 275 | inputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; 276 | 277 | #endif 278 | 279 | 280 | 281 | // Get component 282 | AudioComponent inputComponent = AudioComponentFindNext(NULL, &inputDescription); 283 | CheckError( AudioComponentInstanceNew(inputComponent, &inputUnit), "Couldn't create the output audio unit"); 284 | 285 | #if defined ( USING_OSX ) 286 | AudioComponent outputComponent = AudioComponentFindNext(NULL, &outputDescription); 287 | CheckError( AudioComponentInstanceNew(outputComponent, &outputUnit), "Couldn't create the output audio unit"); 288 | #endif 289 | 290 | 291 | // Enable input 292 | UInt32 one = 1; 293 | CheckError( AudioUnitSetProperty(inputUnit, 294 | kAudioOutputUnitProperty_EnableIO, 295 | kAudioUnitScope_Input, 296 | kInputBus, 297 | &one, 298 | sizeof(one)), "Couldn't enable IO on the input scope of output unit"); 299 | 300 | #if defined ( USING_OSX ) 301 | // Disable output on the input unit 302 | // (only on Mac, since on the iPhone, the input unit is also the output unit) 303 | UInt32 zero = 0; 304 | CheckError( AudioUnitSetProperty(inputUnit, 305 | kAudioOutputUnitProperty_EnableIO, 306 | kAudioUnitScope_Output, 307 | kOutputBus, 308 | &zero, 309 | sizeof(UInt32)), "Couldn't disable output on the audio unit"); 310 | 311 | // Enable output 312 | CheckError( AudioUnitSetProperty(outputUnit, 313 | kAudioOutputUnitProperty_EnableIO, 314 | kAudioUnitScope_Output, 315 | kOutputBus, 316 | &one, 317 | sizeof(one)), "Couldn't enable IO on the input scope of output unit"); 318 | 319 | // Disable input 320 | CheckError( AudioUnitSetProperty(outputUnit, 321 | kAudioOutputUnitProperty_EnableIO, 322 | kAudioUnitScope_Input, 323 | kInputBus, 324 | &zero, 325 | sizeof(UInt32)), "Couldn't disable output on the audio unit"); 326 | 327 | #endif 328 | 329 | // TODO: first query the hardware for desired stream descriptions 330 | // Check the input stream format 331 | 332 | # if defined ( USING_IOS ) 333 | UInt32 size; 334 | size = sizeof( AudioStreamBasicDescription ); 335 | CheckError( AudioUnitGetProperty( inputUnit, 336 | kAudioUnitProperty_StreamFormat, 337 | kAudioUnitScope_Input, 338 | 1, 339 | &inputFormat, 340 | &size ), 341 | "Couldn't get the hardware input stream format"); 342 | 343 | // Check the output stream format 344 | size = sizeof( AudioStreamBasicDescription ); 345 | CheckError( AudioUnitGetProperty( inputUnit, 346 | kAudioUnitProperty_StreamFormat, 347 | kAudioUnitScope_Output, 348 | 1, 349 | &outputFormat, 350 | &size ), 351 | "Couldn't get the hardware output stream format"); 352 | 353 | // TODO: check this works on iOS! 354 | inputFormat.mSampleRate = 44100.0; 355 | outputFormat.mSampleRate = 44100.0; 356 | self.samplingRate = inputFormat.mSampleRate; 357 | self.numBytesPerSample = inputFormat.mBitsPerChannel / 8; 358 | 359 | size = sizeof(AudioStreamBasicDescription); 360 | CheckError(AudioUnitSetProperty(inputUnit, 361 | kAudioUnitProperty_StreamFormat, 362 | kAudioUnitScope_Output, 363 | kInputBus, 364 | &outputFormat, 365 | size), 366 | "Couldn't set the ASBD on the audio unit (after setting its sampling rate)"); 367 | 368 | 369 | # elif defined ( USING_OSX ) 370 | 371 | UInt32 size = sizeof(AudioDeviceID); 372 | if(self.defaultInputDeviceID == kAudioDeviceUnknown) 373 | { 374 | AudioDeviceID thisDeviceID; 375 | UInt32 propsize = sizeof(AudioDeviceID); 376 | CheckError(AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propsize, &thisDeviceID), "Could not get the default device"); 377 | self.defaultInputDeviceID = thisDeviceID; 378 | } 379 | 380 | if (self.defaultOutputDeviceID == kAudioDeviceUnknown) 381 | { 382 | AudioDeviceID thisDeviceID; 383 | UInt32 propsize = sizeof(AudioDeviceID); 384 | CheckError(AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propsize, &thisDeviceID), "Could not get the default device"); 385 | self.defaultOutputDeviceID = thisDeviceID; 386 | 387 | } 388 | 389 | 390 | // Set the current device to the default input unit. 391 | CheckError( AudioUnitSetProperty( inputUnit, 392 | kAudioOutputUnitProperty_CurrentDevice, 393 | kAudioUnitScope_Global, 394 | kOutputBus, 395 | &defaultInputDeviceID, 396 | sizeof(AudioDeviceID) ), "Couldn't set the current input audio device"); 397 | 398 | CheckError( AudioUnitSetProperty( outputUnit, 399 | kAudioOutputUnitProperty_CurrentDevice, 400 | kAudioUnitScope_Global, 401 | kOutputBus, 402 | &defaultOutputDeviceID, 403 | sizeof(AudioDeviceID) ), "Couldn't set the current output audio device"); 404 | 405 | 406 | UInt32 propertySize = sizeof(AudioStreamBasicDescription); 407 | CheckError(AudioUnitGetProperty(inputUnit, 408 | kAudioUnitProperty_StreamFormat, 409 | kAudioUnitScope_Output, 410 | kInputBus, 411 | &outputFormat, 412 | &propertySize), 413 | "Couldn't get ASBD from input unit"); 414 | 415 | 416 | // 9/6/10 - check the input device's stream format 417 | CheckError(AudioUnitGetProperty(inputUnit, 418 | kAudioUnitProperty_StreamFormat, 419 | kAudioUnitScope_Input, 420 | kInputBus, 421 | &inputFormat, 422 | &propertySize), 423 | "Couldn't get ASBD from input unit"); 424 | 425 | 426 | outputFormat.mSampleRate = inputFormat.mSampleRate; 427 | // outputFormat.mFormatFlags = kAudioFormatFlagsCanonical; 428 | self.samplingRate = inputFormat.mSampleRate; 429 | self.numBytesPerSample = inputFormat.mBitsPerChannel / 8; 430 | 431 | self.numInputChannels = inputFormat.mChannelsPerFrame; 432 | self.numOutputChannels = outputFormat.mChannelsPerFrame; 433 | 434 | propertySize = sizeof(AudioStreamBasicDescription); 435 | CheckError(AudioUnitSetProperty(inputUnit, 436 | kAudioUnitProperty_StreamFormat, 437 | kAudioUnitScope_Output, 438 | kInputBus, 439 | &outputFormat, 440 | propertySize), 441 | "Couldn't set the ASBD on the audio unit (after setting its sampling rate)"); 442 | 443 | 444 | #endif 445 | 446 | 447 | 448 | #if defined ( USING_IOS ) 449 | UInt32 numFramesPerBuffer; 450 | size = sizeof(UInt32); 451 | CheckError(AudioUnitGetProperty(inputUnit, 452 | kAudioUnitProperty_MaximumFramesPerSlice, 453 | kAudioUnitScope_Global, 454 | kOutputBus, 455 | &numFramesPerBuffer, 456 | &size), 457 | "Couldn't get the number of frames per callback"); 458 | 459 | UInt32 bufferSizeBytes = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket * numFramesPerBuffer; 460 | 461 | #elif defined ( USING_OSX ) 462 | // Get the size of the IO buffer(s) 463 | UInt32 bufferSizeFrames = 0; 464 | size = sizeof(UInt32); 465 | CheckError (AudioUnitGetProperty(self.inputUnit, 466 | kAudioDevicePropertyBufferFrameSize, 467 | kAudioUnitScope_Global, 468 | 0, 469 | &bufferSizeFrames, 470 | &size), 471 | "Couldn't get buffer frame size from input unit"); 472 | UInt32 bufferSizeBytes = bufferSizeFrames * sizeof(Float32); 473 | #endif 474 | 475 | 476 | 477 | if (outputFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) { 478 | // The audio is non-interleaved 479 | printf("Not interleaved!\n"); 480 | self.isInterleaved = NO; 481 | 482 | // allocate an AudioBufferList plus enough space for array of AudioBuffers 483 | UInt32 propsize = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * outputFormat.mChannelsPerFrame); 484 | 485 | //malloc buffer lists 486 | self.inputBuffer = (AudioBufferList *)malloc(propsize); 487 | self.inputBuffer->mNumberBuffers = outputFormat.mChannelsPerFrame; 488 | 489 | //pre-malloc buffers for AudioBufferLists 490 | for(UInt32 i =0; i< self.inputBuffer->mNumberBuffers ; i++) { 491 | self.inputBuffer->mBuffers[i].mNumberChannels = 1; 492 | self.inputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes; 493 | self.inputBuffer->mBuffers[i].mData = malloc(bufferSizeBytes); 494 | memset(self.inputBuffer->mBuffers[i].mData, 0, bufferSizeBytes); 495 | } 496 | 497 | } else { 498 | printf ("Format is interleaved\n"); 499 | self.isInterleaved = YES; 500 | 501 | // allocate an AudioBufferList plus enough space for array of AudioBuffers 502 | UInt32 propsize = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * 1); 503 | 504 | //malloc buffer lists 505 | self.inputBuffer = (AudioBufferList *)malloc(propsize); 506 | self.inputBuffer->mNumberBuffers = 1; 507 | 508 | //pre-malloc buffers for AudioBufferLists 509 | self.inputBuffer->mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame; 510 | self.inputBuffer->mBuffers[0].mDataByteSize = bufferSizeBytes; 511 | self.inputBuffer->mBuffers[0].mData = malloc(bufferSizeBytes); 512 | memset(self.inputBuffer->mBuffers[0].mData, 0, bufferSizeBytes); 513 | 514 | } 515 | 516 | 517 | // Slap a render callback on the unit 518 | AURenderCallbackStruct callbackStruct; 519 | callbackStruct.inputProc = inputCallback; 520 | callbackStruct.inputProcRefCon = self; 521 | 522 | CheckError( AudioUnitSetProperty(inputUnit, 523 | kAudioOutputUnitProperty_SetInputCallback, 524 | kAudioUnitScope_Global, 525 | 0, 526 | &callbackStruct, 527 | sizeof(callbackStruct)), "Couldn't set the callback on the input unit"); 528 | 529 | 530 | callbackStruct.inputProc = renderCallback; 531 | callbackStruct.inputProcRefCon = self; 532 | # if defined ( USING_OSX ) 533 | CheckError( AudioUnitSetProperty(outputUnit, 534 | kAudioUnitProperty_SetRenderCallback, 535 | kAudioUnitScope_Input, 536 | 0, 537 | &callbackStruct, 538 | sizeof(callbackStruct)), 539 | "Couldn't set the render callback on the input unit"); 540 | 541 | #elif defined ( USING_IOS ) 542 | CheckError( AudioUnitSetProperty(inputUnit, 543 | kAudioUnitProperty_SetRenderCallback, 544 | kAudioUnitScope_Input, 545 | 0, 546 | &callbackStruct, 547 | sizeof(callbackStruct)), 548 | "Couldn't set the render callback on the input unit"); 549 | #endif 550 | 551 | 552 | 553 | 554 | CheckError(AudioUnitInitialize(inputUnit), "Couldn't initialize the output unit"); 555 | #if defined ( USING_OSX ) 556 | CheckError(AudioUnitInitialize(outputUnit), "Couldn't initialize the output unit"); 557 | #endif 558 | 559 | 560 | 561 | } 562 | 563 | #if defined (USING_OSX) 564 | - (void)enumerateAudioDevices 565 | { 566 | UInt32 propSize; 567 | 568 | UInt32 propsize = sizeof(AudioDeviceID); 569 | CheckError(AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propsize, &defaultInputDeviceID), "Could not get the default device"); 570 | 571 | AudioHardwareGetPropertyInfo( kAudioHardwarePropertyDevices, &propSize, NULL ); 572 | uint32_t deviceCount = ( propSize / sizeof(AudioDeviceID) ); 573 | 574 | // Allocate the device IDs 575 | self.deviceIDs = (AudioDeviceID *)calloc(deviceCount, sizeof(AudioDeviceID)); 576 | [deviceNames removeAllObjects]; 577 | 578 | // Get all the device IDs 579 | CheckError( AudioHardwareGetProperty( kAudioHardwarePropertyDevices, &propSize, self.deviceIDs ), "Could not get device IDs"); 580 | 581 | // Get the names of all the device IDs 582 | for( int i = 0; i < deviceCount; i++ ) 583 | { 584 | UInt32 size = sizeof(AudioDeviceID); 585 | CheckError( AudioDeviceGetPropertyInfo( self.deviceIDs[i], 0, true, kAudioDevicePropertyDeviceName, &size, NULL ), "Could not get device name length"); 586 | 587 | char cStringOfDeviceName[size]; 588 | CheckError( AudioDeviceGetProperty( self.deviceIDs[i], 0, true, kAudioDevicePropertyDeviceName, &size, cStringOfDeviceName ), "Could not get device name"); 589 | NSString *thisDeviceName = [NSString stringWithCString:cStringOfDeviceName encoding:NSUTF8StringEncoding]; 590 | 591 | NSLog(@"Device: %@, ID: %d", thisDeviceName, self.deviceIDs[i]); 592 | [deviceNames addObject:thisDeviceName]; 593 | } 594 | 595 | } 596 | 597 | #endif 598 | 599 | 600 | 601 | - (void)pause { 602 | 603 | if (playing) { 604 | CheckError( AudioOutputUnitStop(inputUnit), "Couldn't stop the output unit"); 605 | #if defined ( USING_OSX ) 606 | CheckError( AudioOutputUnitStop(outputUnit), "Couldn't stop the output unit"); 607 | #endif 608 | playing = NO; 609 | } 610 | 611 | } 612 | 613 | - (void)play { 614 | 615 | UInt32 isInputAvailable=0; 616 | UInt32 size = sizeof(isInputAvailable); 617 | 618 | #if defined ( USING_IOS ) 619 | CheckError( AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, 620 | &size, 621 | &isInputAvailable), "Couldn't check if input was available"); 622 | 623 | #elif defined ( USING_OSX ) 624 | isInputAvailable = 1; 625 | 626 | #endif 627 | 628 | 629 | self.inputAvailable = isInputAvailable; 630 | 631 | if ( self.inputAvailable ) { 632 | // Set the audio session category for simultaneous play and record 633 | if (!playing) { 634 | CheckError( AudioOutputUnitStart(inputUnit), "Couldn't start the output unit"); 635 | #if defined ( USING_OSX ) 636 | CheckError( AudioOutputUnitStart(outputUnit), "Couldn't start the output unit"); 637 | #endif 638 | 639 | self.playing = YES; 640 | 641 | } 642 | } 643 | 644 | } 645 | 646 | 647 | #pragma mark - Render Methods 648 | OSStatus inputCallback (void *inRefCon, 649 | AudioUnitRenderActionFlags * ioActionFlags, 650 | const AudioTimeStamp * inTimeStamp, 651 | UInt32 inOutputBusNumber, 652 | UInt32 inNumberFrames, 653 | AudioBufferList * ioData) 654 | { 655 | 656 | 657 | Novocaine *sm = (Novocaine *)inRefCon; 658 | 659 | if (!sm.playing) 660 | return noErr; 661 | if (sm.inputBlock == nil) 662 | return noErr; 663 | 664 | 665 | // Check the current number of channels 666 | // Let's actually grab the audio 667 | #if TARGET_IPHONE_SIMULATOR 668 | // this is a workaround for an issue with core audio on the simulator, // 669 | // likely due to 44100 vs 48000 difference in OSX // 670 | if( inNumberFrames == 471 ) 671 | inNumberFrames = 470; 672 | #endif 673 | CheckError( AudioUnitRender(sm.inputUnit, ioActionFlags, inTimeStamp, inOutputBusNumber, inNumberFrames, sm.inputBuffer), "Couldn't render the output unit"); 674 | 675 | 676 | // Convert the audio in something manageable 677 | // For Float32s ... 678 | if ( sm.numBytesPerSample == 4 ) // then we've already got flaots 679 | { 680 | 681 | float zero = 0.0f; 682 | if ( ! sm.isInterleaved ) { // if the data is in separate buffers, make it interleaved 683 | for (int i=0; i < sm.numInputChannels; ++i) { 684 | vDSP_vsadd((float *)sm.inputBuffer->mBuffers[i].mData, 1, &zero, sm.inData+i, 685 | sm.numInputChannels, inNumberFrames); 686 | } 687 | } 688 | else { // if the data is already interleaved, copy it all in one happy block. 689 | // TODO: check mDataByteSize is proper 690 | memcpy(sm.inData, (float *)sm.inputBuffer->mBuffers[0].mData, sm.inputBuffer->mBuffers[0].mDataByteSize); 691 | } 692 | } 693 | 694 | // For SInt16s ... 695 | else if ( sm.numBytesPerSample == 2 ) // then we're dealing with SInt16's 696 | { 697 | if ( ! sm.isInterleaved ) { 698 | for (int i=0; i < sm.numInputChannels; ++i) { 699 | vDSP_vflt16((SInt16 *)sm.inputBuffer->mBuffers[i].mData, 1, sm.inData+i, sm.numInputChannels, inNumberFrames); 700 | } 701 | } 702 | else { 703 | vDSP_vflt16((SInt16 *)sm.inputBuffer->mBuffers[0].mData, 1, sm.inData, 1, inNumberFrames*sm.numInputChannels); 704 | } 705 | 706 | float scale = 1.0 / (float)INT16_MAX; 707 | vDSP_vsmul(sm.inData, 1, &scale, sm.inData, 1, inNumberFrames*sm.numInputChannels); 708 | } 709 | 710 | // Now do the processing! 711 | sm.inputBlock(sm.inData, inNumberFrames, sm.numInputChannels); 712 | 713 | return noErr; 714 | 715 | 716 | } 717 | 718 | OSStatus renderCallback (void *inRefCon, 719 | AudioUnitRenderActionFlags * ioActionFlags, 720 | const AudioTimeStamp * inTimeStamp, 721 | UInt32 inOutputBusNumber, 722 | UInt32 inNumberFrames, 723 | AudioBufferList * ioData) 724 | { 725 | 726 | 727 | Novocaine *sm = (Novocaine *)inRefCon; 728 | float zero = 0.0; 729 | 730 | 731 | for (int iBuffer=0; iBuffer < ioData->mNumberBuffers; ++iBuffer) { 732 | memset(ioData->mBuffers[iBuffer].mData, 0, ioData->mBuffers[iBuffer].mDataByteSize); 733 | } 734 | 735 | if (!sm.playing) 736 | return noErr; 737 | if (!sm.outputBlock) 738 | return noErr; 739 | 740 | 741 | // Collect data to render from the callbacks 742 | sm.outputBlock(sm.outData, inNumberFrames, sm.numOutputChannels); 743 | 744 | 745 | // Put the rendered data into the output buffer 746 | // TODO: convert SInt16 ranges to float ranges. 747 | if ( sm.numBytesPerSample == 4 ) // then we've already got floats 748 | { 749 | 750 | for (int iBuffer=0; iBuffer < ioData->mNumberBuffers; ++iBuffer) { 751 | 752 | int thisNumChannels = ioData->mBuffers[iBuffer].mNumberChannels; 753 | 754 | for (int iChannel = 0; iChannel < thisNumChannels; ++iChannel) { 755 | vDSP_vsadd(sm.outData+iChannel, sm.numOutputChannels, &zero, (float *)ioData->mBuffers[iBuffer].mData, thisNumChannels, inNumberFrames); 756 | } 757 | } 758 | } 759 | else if ( sm.numBytesPerSample == 2 ) // then we need to convert SInt16 -> Float (and also scale) 760 | { 761 | float scale = (float)INT16_MAX; 762 | vDSP_vsmul(sm.outData, 1, &scale, sm.outData, 1, inNumberFrames*sm.numOutputChannels); 763 | 764 | for (int iBuffer=0; iBuffer < ioData->mNumberBuffers; ++iBuffer) { 765 | 766 | int thisNumChannels = ioData->mBuffers[iBuffer].mNumberChannels; 767 | 768 | for (int iChannel = 0; iChannel < thisNumChannels; ++iChannel) { 769 | vDSP_vfix16(sm.outData+iChannel, sm.numOutputChannels, (SInt16 *)ioData->mBuffers[iBuffer].mData+iChannel, thisNumChannels, inNumberFrames); 770 | } 771 | } 772 | 773 | } 774 | 775 | return noErr; 776 | 777 | } 778 | 779 | #pragma mark - Audio Session Listeners 780 | #if defined (USING_IOS) 781 | void sessionPropertyListener(void * inClientData, 782 | AudioSessionPropertyID inID, 783 | UInt32 inDataSize, 784 | const void * inData){ 785 | 786 | // Determines the reason for the route change, to ensure that it is not 787 | // because of a category change. 788 | CFNumberRef routeChangeReasonRef = (CFNumberRef)CFDictionaryGetValue ((CFDictionaryRef)inData, CFSTR (kAudioSession_AudioRouteChangeKey_Reason) ); 789 | SInt32 routeChangeReason; 790 | CFNumberGetValue (routeChangeReasonRef, kCFNumberSInt32Type, &routeChangeReason); 791 | 792 | if (inID == kAudioSessionProperty_AudioRouteChange && routeChangeReason != kAudioSessionRouteChangeReason_CategoryChange) 793 | { 794 | Novocaine *sm = (Novocaine *)inClientData; 795 | [sm checkSessionProperties]; 796 | } 797 | 798 | } 799 | 800 | - (void)checkAudioSource { 801 | // Check what the incoming audio route is. 802 | UInt32 propertySize = sizeof(CFStringRef); 803 | CFStringRef route; 804 | CheckError( AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route), "Couldn't check the audio route"); 805 | self.inputRoute = (NSString *)route; 806 | CFRelease(route); 807 | NSLog(@"AudioRoute: %@", self.inputRoute); 808 | 809 | 810 | // Check if there's input available. 811 | // TODO: check if checking for available input is redundant. 812 | // Possibly there's a different property ID change? 813 | UInt32 isInputAvailable = 0; 814 | UInt32 size = sizeof(isInputAvailable); 815 | CheckError( AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, 816 | &size, 817 | &isInputAvailable), "Couldn't check if input is available"); 818 | self.inputAvailable = (BOOL)isInputAvailable; 819 | NSLog(@"Input available? %d", self.inputAvailable); 820 | 821 | } 822 | 823 | 824 | // To be run ONCE per session property change and once on initialization. 825 | - (void)checkSessionProperties 826 | { 827 | 828 | // Check if there is input, and from where 829 | [self checkAudioSource]; 830 | 831 | // Check the number of input channels. 832 | // Find the number of channels 833 | UInt32 size = sizeof(self.numInputChannels); 834 | UInt32 newNumChannels; 835 | CheckError( AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareInputNumberChannels, &size, &newNumChannels), "Checking number of input channels"); 836 | self.numInputChannels = newNumChannels; 837 | // self.numInputChannels = 1; 838 | NSLog(@"We've got %lu input channels", self.numInputChannels); 839 | 840 | 841 | // Check the number of input channels. 842 | // Find the number of channels 843 | CheckError( AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputNumberChannels, &size, &newNumChannels), "Checking number of output channels"); 844 | self.numOutputChannels = newNumChannels; 845 | // self.numOutputChannels = 1; 846 | NSLog(@"We've got %lu output channels", self.numOutputChannels); 847 | 848 | 849 | // Get the hardware sampling rate. This is settable, but here we're only reading. 850 | Float64 currentSamplingRate; 851 | size = sizeof(currentSamplingRate); 852 | CheckError( AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate, &size, ¤tSamplingRate), "Checking hardware sampling rate"); 853 | self.samplingRate = currentSamplingRate; 854 | NSLog(@"Current sampling rate: %f", self.samplingRate); 855 | 856 | } 857 | 858 | void sessionInterruptionListener(void *inClientData, UInt32 inInterruption) { 859 | 860 | Novocaine *sm = (Novocaine *)inClientData; 861 | 862 | if (inInterruption == kAudioSessionBeginInterruption) { 863 | NSLog(@"Begin interuption"); 864 | sm.inputAvailable = NO; 865 | } 866 | else if (inInterruption == kAudioSessionEndInterruption) { 867 | NSLog(@"End interuption"); 868 | sm.inputAvailable = YES; 869 | [sm play]; 870 | } 871 | 872 | } 873 | 874 | #endif 875 | 876 | 877 | 878 | 879 | #if defined ( USING_OSX ) 880 | 881 | // Checks the number of channels and sampling rate of the connected device. 882 | - (void)checkDeviceProperties 883 | { 884 | 885 | } 886 | 887 | - (void)selectAudioDevice:(AudioDeviceID)deviceID 888 | { 889 | 890 | } 891 | 892 | #endif 893 | 894 | 895 | #pragma mark - Convenience Methods 896 | - (NSString *)applicationDocumentsDirectory { 897 | return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 898 | } 899 | 900 | 901 | @end 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | -------------------------------------------------------------------------------- /LARSBar.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'LARSBar' 3 | s.version = '1.1.0' 4 | s.summary = 'An oddly-named UISlider subclass which mimics the EQ slider found on Twitter\'s #music app.' 5 | s.homepage = 'https://github.com/larsacus/LARSBar' 6 | s.author = { 7 | 'Lars Anderson' => 'iAm@theonlylars.com' 8 | } 9 | s.license = {:type => 'MIT', :file => 'LICENSE'} 10 | s.platform = :ios, '5.0' 11 | s.source = { 12 | :git => 'https://github.com/larsacus/LARSBar.git', 13 | :tag => s.version.to_s 14 | } 15 | s.requires_arc = true 16 | s.source_files = 'LARSBar/*.{h,m}' 17 | s.frameworks = 'CoreGraphics', 'QuartzCore' 18 | end -------------------------------------------------------------------------------- /LARSBar/LARSBar.h: -------------------------------------------------------------------------------- 1 | // 2 | // LARSBar.h 3 | // LARSBarDemo 4 | // 5 | // Created by Lars Anderson on 4/23/13. 6 | // Copyright (c) 2013 theonlylars. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LARSBar : UISlider 12 | 13 | /** The value for the left channel eq in the range of 0 to 1. 14 | 15 | @warning This value is clipped at 1. 16 | */ 17 | @property (nonatomic, assign) CGFloat leftChannelLevel; 18 | 19 | /** The value for the right channel eq in the range of 0 to 1. 20 | 21 | @warning This value is clipped at 1. 22 | */ 23 | @property (nonatomic, assign) CGFloat rightChannelLevel; 24 | 25 | /** The inactive color for the eq. This is the color that the eq 26 | light takes on past the right side of the slider thumb knob. 27 | */ 28 | @property (nonatomic, strong) UIColor *inactiveColor; 29 | 30 | /** The active color for the eq when it is available to be lit up. 31 | This is the color that the eq light takes on before the slider 32 | thumb knob. 33 | */ 34 | @property (nonatomic, strong) UIColor *activeColor; 35 | 36 | /** The EQ light glow colors. The number of light sections the 37 | light takes on depends on how many colors you pass in here. 38 | */ 39 | @property (nonatomic, copy) NSArray *glowColors; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /LARSBar/LARSBar.m: -------------------------------------------------------------------------------- 1 | // 2 | // LARSBar.m 3 | // LARSBarDemo 4 | // 5 | // Created by Lars Anderson on 4/23/13. 6 | // Copyright (c) 2013 theonlylars. All rights reserved. 7 | // 8 | 9 | #import "LARSBar.h" 10 | #import 11 | 12 | const CGSize TOLLightLayerSize = {10.f, 12.f}; 13 | const CGFloat TOLTargetLightPadding = -3.f; 14 | 15 | @interface TOLEQLight : CALayer 16 | 17 | @property (nonatomic, assign, getter = isActive) BOOL active; 18 | @property (nonatomic, assign) CGFloat lightState; 19 | @property (nonatomic, strong) UIColor *glowColor; 20 | @property (nonatomic, strong) UIColor *inactiveColor; 21 | @property (nonatomic, strong) UIColor *activeColor; 22 | 23 | @end 24 | 25 | @interface LARSBar () 26 | @property (nonatomic, strong) NSMutableArray *leftChannelLightLayers; 27 | @property (nonatomic, strong) NSMutableArray *rightChannelLightLayers; 28 | @end 29 | 30 | @implementation LARSBar 31 | 32 | - (instancetype)awakeAfterUsingCoder:(NSCoder *)aDecoder{ 33 | 34 | [self setup]; 35 | 36 | return [super awakeAfterUsingCoder:aDecoder]; 37 | } 38 | 39 | - (instancetype)initWithFrame:(CGRect)frame{ 40 | self = [super initWithFrame:frame]; 41 | if (self) { 42 | [self setup]; 43 | } 44 | return self; 45 | } 46 | 47 | - (void)setup{ 48 | UIImage *emptyImage = [[UIImage alloc] init]; 49 | 50 | [self setMinimumTrackImage:emptyImage 51 | forState:UIControlStateNormal]; 52 | [self setMaximumTrackImage:emptyImage 53 | forState:UIControlStateNormal]; 54 | 55 | self.leftChannelLightLayers = [NSMutableArray array]; 56 | self.rightChannelLightLayers = [NSMutableArray array]; 57 | self.glowColors = @[[UIColor colorWithRed:0.00 green:0.90 blue:0.29 alpha:1.0], 58 | [UIColor colorWithRed:1.00 green:0.82 blue:0.27 alpha:1.0], 59 | [UIColor colorWithRed:1.00 green:0.38 blue:0.14 alpha:1.0], 60 | [UIColor colorWithRed:1.00 green:0.00 blue:0.08 alpha:1.0]]; 61 | 62 | self.leftChannelLevel = 0.5f; 63 | self.rightChannelLevel = 0.5f; 64 | } 65 | 66 | - (void)layoutSubviews{ 67 | [super layoutSubviews]; 68 | 69 | NSInteger numberOfLights = floorf((CGRectGetWidth(self.bounds)-TOLTargetLightPadding)/(TOLLightLayerSize.width + TOLTargetLightPadding)); 70 | CGFloat totalWidth = CGRectGetWidth(self.bounds); 71 | CGFloat lightWidth = TOLLightLayerSize.width; 72 | 73 | CGFloat actualPadding = roundf((totalWidth - numberOfLights*lightWidth)/(numberOfLights+1)); 74 | 75 | [self updateLightArraysForNumberOfLights:numberOfLights 76 | lightWidth:lightWidth 77 | actualPadding:actualPadding]; 78 | 79 | [self setLightPercentage:self.leftChannelLevel 80 | forLightArray:self.leftChannelLightLayers]; 81 | [self setLightPercentage:self.rightChannelLevel 82 | forLightArray:self.rightChannelLightLayers]; 83 | } 84 | 85 | #pragma mark - Updating Lights 86 | - (void)setLeftChannelLevel:(CGFloat)percentage{ 87 | _leftChannelLevel = MIN(MAX(percentage, 0.f), 1.f); 88 | 89 | [self setLightPercentage:_leftChannelLevel 90 | forLightArray:self.leftChannelLightLayers]; 91 | } 92 | 93 | - (void)setRightChannelLevel:(CGFloat)percentage{ 94 | _rightChannelLevel = MIN(MAX(percentage, 0.f), 1.f); 95 | 96 | [self setLightPercentage:_rightChannelLevel 97 | forLightArray:self.rightChannelLightLayers]; 98 | } 99 | 100 | - (void)setLightPercentage:(CGFloat)percentage forLightArray:(NSArray *)lightArray{ 101 | CGFloat valuePercentage = self.value/self.maximumValue; 102 | CGFloat normalizedPercentage = percentage*valuePercentage; 103 | 104 | NSUInteger maxLight = floor(lightArray.count*normalizedPercentage); 105 | BOOL newLightState; 106 | 107 | for (NSUInteger lightNum = 0; lightNum < lightArray.count; lightNum++) { 108 | TOLEQLight *currentLight = lightArray[lightNum]; 109 | 110 | if(lightNum < maxLight){ 111 | newLightState = YES; 112 | } 113 | else{ 114 | newLightState = NO; 115 | } 116 | 117 | if (currentLight.lightState != newLightState) { 118 | [currentLight setNeedsDisplay]; 119 | } 120 | 121 | [currentLight setLightState:newLightState]; 122 | } 123 | } 124 | 125 | #pragma mark - Light Layout 126 | 127 | - (void)layoutLightNumber:(NSUInteger)lightNum 128 | center:(CGPoint)center 129 | storageArray:(NSMutableArray *)storageArray 130 | totalLights:(NSInteger)totalLights{ 131 | TOLEQLight *currentLight = nil; 132 | if (lightNum < storageArray.count) { 133 | currentLight = storageArray[lightNum]; 134 | } 135 | else{ 136 | currentLight = [[TOLEQLight alloc] init]; 137 | currentLight.lightState = NO; 138 | 139 | if (self.activeColor != nil) { 140 | currentLight.activeColor = self.activeColor; 141 | } 142 | 143 | if (self.inactiveColor != nil) { 144 | currentLight.inactiveColor = self.inactiveColor; 145 | } 146 | 147 | currentLight.bounds = (CGRect){CGPointZero, TOLLightLayerSize}; 148 | 149 | [storageArray addObject:currentLight]; 150 | } 151 | 152 | if (self.activeColor != nil && 153 | ([currentLight.activeColor isEqual:self.activeColor] == NO)) { 154 | currentLight.activeColor = self.activeColor; 155 | [currentLight setNeedsDisplay]; 156 | } 157 | 158 | if (self.inactiveColor != nil && 159 | ([currentLight.inactiveColor isEqual:self.inactiveColor] == NO)) { 160 | currentLight.inactiveColor = self.inactiveColor; 161 | [currentLight setNeedsDisplay]; 162 | } 163 | 164 | CGFloat lightPercentage = [self lightPercentageForSliderValue]; 165 | UIColor *colorForLight = [self colorForLightNum:lightNum 166 | totalLights:totalLights]; 167 | BOOL lightState = [self lightStateForLightNumber:lightNum 168 | totalLights:totalLights 169 | percentage:lightPercentage]; 170 | 171 | currentLight.position = center; 172 | if ([colorForLight isEqual:currentLight.glowColor] == NO) { 173 | currentLight.glowColor = colorForLight; 174 | [currentLight setNeedsDisplay]; 175 | } 176 | 177 | if (lightState != currentLight.active) { 178 | currentLight.active = lightState; 179 | [currentLight setNeedsDisplay]; 180 | } 181 | 182 | if ([currentLight.superlayer isEqual:self.layer] == NO) { 183 | [self.layer insertSublayer:currentLight atIndex:0]; 184 | } 185 | } 186 | 187 | - (CGFloat)lightPercentageForSliderValue{ 188 | CGFloat calculatedValue = (self.value-self.minimumValue)/(self.maximumValue-self.minimumValue); 189 | return MIN(MAX(calculatedValue, 0.f), 1.f); 190 | } 191 | 192 | - (BOOL)lightStateForLightNumber:(NSInteger)lightNumber 193 | totalLights:(NSInteger)totalLights 194 | percentage:(CGFloat)sliderPercentage{ 195 | CGFloat lightPercentage = lightNumber/(CGFloat)totalLights; 196 | 197 | return (lightPercentage < sliderPercentage); 198 | } 199 | 200 | - (UIColor *)colorForLightNum:(NSInteger)lightNum totalLights:(NSInteger)totalLights{ 201 | CGFloat lightPercentage = lightNum/(CGFloat)totalLights; 202 | CGFloat totalColors = self.glowColors.count; 203 | 204 | for (NSInteger colorNum = 1; colorNum <= totalColors; colorNum++) { 205 | CGFloat upperPercentage = (colorNum)/totalColors; 206 | CGFloat lowerPercentage = (colorNum-1)/totalColors; 207 | 208 | if ((lightPercentage < upperPercentage) && 209 | (lightPercentage >= lowerPercentage)) { 210 | return self.glowColors[colorNum-1]; 211 | } 212 | } 213 | 214 | return nil; 215 | } 216 | 217 | - (void)updateLightArraysForNumberOfLights:(NSUInteger)numberOfLights 218 | lightWidth:(CGFloat)lightWidth 219 | actualPadding:(CGFloat)actualPadding { 220 | if (self.leftChannelLightLayers.count > numberOfLights) { 221 | [self cleanUpLightLayers:self.leftChannelLightLayers 222 | forNumberOfLights:numberOfLights]; 223 | } 224 | 225 | if (self.rightChannelLightLayers.count > numberOfLights) { 226 | [self cleanUpLightLayers:self.rightChannelLightLayers 227 | forNumberOfLights:numberOfLights]; 228 | } 229 | 230 | for (NSUInteger lightNum = 0; lightNum < numberOfLights; lightNum++) { 231 | CGFloat x = actualPadding*(lightNum+1) + lightWidth*lightNum + (lightWidth/2); 232 | CGFloat centerLineHeight = CGRectGetHeight(self.bounds)/2; 233 | CGFloat yOffset = 2.f + TOLLightLayerSize.height/2; 234 | CGPoint topCenter = CGPointMake(x, centerLineHeight - yOffset); 235 | CGPoint bottomCenter = CGPointMake(x, centerLineHeight + yOffset); 236 | 237 | [self layoutLightNumber:lightNum 238 | center:topCenter 239 | storageArray:self.leftChannelLightLayers 240 | totalLights:numberOfLights]; 241 | 242 | [self layoutLightNumber:lightNum 243 | center:bottomCenter 244 | storageArray:self.rightChannelLightLayers 245 | totalLights:numberOfLights]; 246 | } 247 | } 248 | 249 | - (void)cleanUpLightLayers:(NSMutableArray *)lights forNumberOfLights:(NSInteger)numberOfLights{ 250 | NSRange removeRange = NSMakeRange(numberOfLights, lights.count-numberOfLights); 251 | NSArray *cleanUpLayers = [lights subarrayWithRange:removeRange]; 252 | [self cleanUpLightLayers:cleanUpLayers]; 253 | [lights removeObjectsInRange:removeRange]; 254 | } 255 | 256 | - (void)cleanUpLightLayers:(NSArray *)lights{ 257 | for (CALayer *lightLayer in lights) { 258 | [lightLayer removeFromSuperlayer]; 259 | } 260 | } 261 | 262 | @end 263 | 264 | @implementation TOLEQLight 265 | 266 | @dynamic lightState; 267 | 268 | - (instancetype)init{ 269 | self = [super init]; 270 | if (self) { 271 | self.activeColor = [UIColor colorWithRed: 0.376 green: 0.4 blue: 0.416 alpha: 1]; 272 | self.inactiveColor = [UIColor colorWithRed:0.19 green:0.19 blue:0.19 alpha:0.8]; 273 | self.glowColor = [UIColor yellowColor]; 274 | self.lightState = 1.f; 275 | self.active = YES; 276 | self.contentsScale = [[UIScreen mainScreen] scale]; 277 | // self.drawsAsynchronously = YES; // Unsure how this might affect app performance, disabling 278 | } 279 | return self; 280 | } 281 | 282 | - (instancetype)initWithLayer:(id)layer { 283 | self = [super initWithLayer:layer]; 284 | if (self) { 285 | if ([layer isKindOfClass:[TOLEQLight class]]) { 286 | TOLEQLight *lightLayer = (TOLEQLight *)layer; 287 | 288 | self.activeColor = lightLayer.activeColor; 289 | self.inactiveColor = lightLayer.inactiveColor; 290 | self.glowColor = lightLayer.glowColor; 291 | self.lightState = lightLayer.lightState; 292 | self.active = lightLayer.isActive; 293 | self.contentsScale = lightLayer.contentsScale; 294 | } 295 | } 296 | return self; 297 | } 298 | 299 | + (BOOL)needsDisplayForKey:(NSString *)key { 300 | if ([key isEqualToString:NSStringFromSelector(@selector(lightState))]) { 301 | return YES; 302 | } 303 | 304 | return [super needsDisplayForKey:key]; 305 | } 306 | 307 | - (id)actionForKey:(NSString *)event { 308 | 309 | if ([event isEqualToString:NSStringFromSelector(@selector(lightState))]) { 310 | CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:event]; 311 | NSValue *fromValue = [self.presentationLayer valueForKey:event]; 312 | 313 | anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; 314 | anim.duration = 0.10; 315 | anim.fromValue = fromValue; 316 | 317 | return anim; 318 | } 319 | 320 | return [super actionForKey:event]; 321 | } 322 | 323 | - (void)drawInContext:(CGContextRef)ctx{ 324 | 325 | UIGraphicsPushContext(ctx); 326 | 327 | CGFloat width = CGRectGetWidth(self.bounds); 328 | CGFloat height = CGRectGetHeight(self.bounds); 329 | CGFloat lightRectRatio = floorf(width/2.f)/width; 330 | 331 | CGFloat sideDimension = MIN(lightRectRatio*width, lightRectRatio*height); 332 | 333 | CGRect lightRect = CGRectMake((width - lightRectRatio*width)/2.f, 334 | (height - lightRectRatio*height)/2.f, 335 | sideDimension, 336 | sideDimension); 337 | 338 | CGFloat scale = [[UIScreen mainScreen] scale]; 339 | 340 | CGContextRef context = ctx; 341 | 342 | //// Color Declarations 343 | UIColor* baseFillColor = nil; 344 | 345 | if(self.isActive){ 346 | baseFillColor = self.activeColor; 347 | } 348 | else{ 349 | baseFillColor = self.inactiveColor; 350 | } 351 | 352 | // Base Frame Drawing 353 | UIBezierPath* lightFramePath = [UIBezierPath bezierPathWithOvalInRect:lightRect]; 354 | CGContextSaveGState(context); 355 | [baseFillColor setFill]; 356 | [lightFramePath fill]; 357 | CGContextRestoreGState(context); 358 | 359 | UIColor* strokeColor = [UIColor colorWithRed: 0.094 green: 0.102 blue: 0.102 alpha: 0.8]; 360 | UIColor* underStrokeColor = [UIColor colorWithRed: 0.224 green: 0.227 blue: 0.231 alpha: 1]; 361 | UIColor* clearColor = [UIColor clearColor]; 362 | 363 | // Active Color Drawing 364 | if (self.lightState > 0.f) { 365 | UIColor *activeFillColor = [self.glowColor colorWithAlphaComponent:self.lightState]; 366 | 367 | //// Shadow Declarations 368 | UIColor* underStroke = underStrokeColor; 369 | CGSize underStrokeOffset = CGSizeMake(0.f, 1.f/scale); 370 | CGFloat underStrokeBlurRadius = 0; 371 | 372 | CGContextSaveGState(context); 373 | CGContextSetShadowWithColor(context, underStrokeOffset, underStrokeBlurRadius, underStroke.CGColor); 374 | [activeFillColor setFill]; 375 | [lightFramePath fill]; 376 | 377 | CGContextRestoreGState(context); 378 | } 379 | 380 | [strokeColor setStroke]; 381 | lightFramePath.lineWidth = 1.f/scale; 382 | [lightFramePath stroke]; 383 | 384 | //// Glow Drawing 385 | CGFloat endRadius = self.lightState * MAX(floorf(width/2.f), floorf(height/2.f)); 386 | 387 | if (self.lightState > lightRectRatio) { 388 | //// Gradient Declarations 389 | UIColor* lightGlowColor = [self.glowColor colorWithAlphaComponent:0.2*self.lightState]; 390 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 391 | NSArray* lightGlowGradientColors = [NSArray arrayWithObjects: 392 | (id)lightGlowColor.CGColor, 393 | (id)clearColor.CGColor, nil]; 394 | CGFloat lightGlowGradientLocations[] = {lightRectRatio, 1.f}; 395 | CGGradientRef lightGlowGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)lightGlowGradientColors, lightGlowGradientLocations); 396 | CGContextDrawRadialGradient(context, lightGlowGradient, 397 | CGPointMake(width/2.f, height/2.f - (1 - 1/scale)), 0.f, 398 | CGPointMake(width/2.f, height/2.f - (1 - 1/scale)), endRadius, 399 | kCGGradientDrawsBeforeStartLocation); 400 | 401 | //// Cleanup 402 | CGGradientRelease(lightGlowGradient); 403 | CGColorSpaceRelease(colorSpace); 404 | } 405 | } 406 | 407 | @end 408 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Lars Anderson, theonlylars 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LARSBar 2 | =========== 3 | 4 | A `UISlider` subclass mimicking the awesome EQ slider found on Twitter's #music app. Named like this because my friends kept calling it this - and I really can't come up with anything better. 5 | 6 | You'll need to bring your own assets for the frame, track and slider thumb, or just use the ones I've created in the sample project - I don't care. I've included the PSD files. 7 | 8 | Excuse the crummy gif screen capture quality, the performance is quite good and there are no gaps in the animation: 9 | ![LARSBar in action](Demo/Assets/toleqdemo.gif) 10 | 11 | ## Interface 12 | Pretty simple interface: 13 | 14 | ``` lang:objective-c 15 | /** The value for the left channel eq in the range of 0 to 1. 16 | 17 | @warning This value is clipped at 1. 18 | */ 19 | @property (nonatomic, assign) CGFloat leftChannelLevel; 20 | 21 | /** The value for the right channel eq in the range of 0 to 1. 22 | 23 | @warning This value is clipped at 1. 24 | */ 25 | @property (nonatomic, assign) CGFloat rightChannelLevel; 26 | 27 | /** The inactive color for the eq. This is the color that the eq light takes on past the right side of the slider thumb knob. 28 | */ 29 | @property (nonatomic, strong) UIColor *inactiveColor; 30 | 31 | /** The active color for the eq when it is available to be lit up. This is the color that the eq light takes on before the slider thumb knob. 32 | */ 33 | @property (nonatomic, strong) UIColor *activeColor; 34 | 35 | /** The EQ light glow colors. The number of light sections the light takes on depends on how many colors you pass in here. 36 | */ 37 | @property (nonatomic, copy) NSArray *glowColors; 38 | ``` 39 | 40 | In addition to all of the above, since this is just a `UISlider` subclass, you can treat it just like you would a normal `UISlider` (Using `UIAppearance`, etc). 41 | 42 | ## License 43 | MIT License. Go nuts. 44 | 45 | ## Homepage 46 | If you're not on github right now reading this, you can find the page here: https://github.com/larsacus/LARSBar 47 | 48 | ## Other 49 | Demo project uses [Novocaine](https://github.com/alexbw/novocaine) for audio input. Other than that - have fun. 50 | 51 | by _Lars_ ([@theonlylars](http://twitter.com/theonlylars)) 52 | 53 | --------------------------------------------------------------------------------