├── FFT.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ ├── harrykosalos.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ ├── FFT.xcscheme │ │ └── xcschememanagement.plist │ └── jscalo.xcuserdatad │ └── xcschemes │ ├── TempiFFT.xcscheme │ └── xcschememanagement.plist ├── README.md ├── TempiFFT ├── AAPLTransforms.swift ├── AppDelegate.swift ├── ArcBall.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ColorMap.swift ├── History.swift ├── Info.plist ├── Renderer.swift ├── Shaders.h ├── Shaders.metal ├── SpectralView.swift ├── TempiAudioInput.swift ├── TempiDebug.swift ├── TempiFFT.swift ├── TempiUtilities.swift └── ViewController.swift └── screenshot.png /FFT.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1D03ED152075744F00E72BC5 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D03ED142075744F00E72BC5 /* History.swift */; }; 11 | 1DFA9BDF20768EA40023AC4D /* ArcBall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFA9BDC20768EA40023AC4D /* ArcBall.swift */; }; 12 | 1DFA9BE020768EA40023AC4D /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFA9BDD20768EA40023AC4D /* Renderer.swift */; }; 13 | 1DFA9BE120768EA40023AC4D /* Shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 1DFA9BDE20768EA40023AC4D /* Shaders.metal */; }; 14 | 1DFA9BE320768EAF0023AC4D /* AAPLTransforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFA9BE220768EAF0023AC4D /* AAPLTransforms.swift */; }; 15 | 1DFA9BE72076A2820023AC4D /* ColorMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFA9BE62076A2810023AC4D /* ColorMap.swift */; }; 16 | EF7009141C3ED07100E75ECF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7009131C3ED07100E75ECF /* AppDelegate.swift */; }; 17 | EF7009161C3ED07100E75ECF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7009151C3ED07100E75ECF /* ViewController.swift */; }; 18 | EF7009191C3ED07100E75ECF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EF7009171C3ED07100E75ECF /* Main.storyboard */; }; 19 | EF70091B1C3ED07100E75ECF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EF70091A1C3ED07100E75ECF /* Assets.xcassets */; }; 20 | EF70091E1C3ED07100E75ECF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EF70091C1C3ED07100E75ECF /* LaunchScreen.storyboard */; }; 21 | EF70092A1C3EDFB900E75ECF /* TempiAudioInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7009291C3EDFB900E75ECF /* TempiAudioInput.swift */; }; 22 | EF7916BE1C50AA9700658A2C /* SpectralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF7916BD1C50AA9700658A2C /* SpectralView.swift */; }; 23 | EFAC3F461C406DC5002CD63F /* TempiUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFAC3F451C406DC5002CD63F /* TempiUtilities.swift */; }; 24 | EFAC3F481C45C719002CD63F /* TempiFFT.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFAC3F471C45C719002CD63F /* TempiFFT.swift */; }; 25 | EFAC3F4E1C4D7B76002CD63F /* TempiDebug.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFAC3F4D1C4D7B76002CD63F /* TempiDebug.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 1D03ED142075744F00E72BC5 /* History.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = ""; }; 30 | 1DFA9BDB20768EA30023AC4D /* Shaders.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Shaders.h; sourceTree = ""; }; 31 | 1DFA9BDC20768EA40023AC4D /* ArcBall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArcBall.swift; sourceTree = ""; }; 32 | 1DFA9BDD20768EA40023AC4D /* Renderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = ""; }; 33 | 1DFA9BDE20768EA40023AC4D /* Shaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = Shaders.metal; sourceTree = ""; }; 34 | 1DFA9BE220768EAF0023AC4D /* AAPLTransforms.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AAPLTransforms.swift; sourceTree = ""; }; 35 | 1DFA9BE62076A2810023AC4D /* ColorMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorMap.swift; sourceTree = ""; }; 36 | EF7009101C3ED07100E75ECF /* FFT.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FFT.app; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | EF7009131C3ED07100E75ECF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 38 | EF7009151C3ED07100E75ECF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 39 | EF7009181C3ED07100E75ECF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 40 | EF70091A1C3ED07100E75ECF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41 | EF70091D1C3ED07100E75ECF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 42 | EF70091F1C3ED07100E75ECF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | EF7009291C3EDFB900E75ECF /* TempiAudioInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempiAudioInput.swift; sourceTree = ""; }; 44 | EF7916BD1C50AA9700658A2C /* SpectralView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpectralView.swift; sourceTree = ""; }; 45 | EFAC3F451C406DC5002CD63F /* TempiUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempiUtilities.swift; sourceTree = ""; }; 46 | EFAC3F471C45C719002CD63F /* TempiFFT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempiFFT.swift; sourceTree = ""; }; 47 | EFAC3F4D1C4D7B76002CD63F /* TempiDebug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempiDebug.swift; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | EF70090D1C3ED07100E75ECF /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | EF7009071C3ED07100E75ECF = { 62 | isa = PBXGroup; 63 | children = ( 64 | EF7009121C3ED07100E75ECF /* TempiFFT */, 65 | EF7009111C3ED07100E75ECF /* Products */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | EF7009111C3ED07100E75ECF /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | EF7009101C3ED07100E75ECF /* FFT.app */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | EF7009121C3ED07100E75ECF /* TempiFFT */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | EF7916BD1C50AA9700658A2C /* SpectralView.swift */, 81 | 1D03ED142075744F00E72BC5 /* History.swift */, 82 | EF7009151C3ED07100E75ECF /* ViewController.swift */, 83 | 1DFA9BDC20768EA40023AC4D /* ArcBall.swift */, 84 | 1DFA9BDD20768EA40023AC4D /* Renderer.swift */, 85 | 1DFA9BDB20768EA30023AC4D /* Shaders.h */, 86 | 1DFA9BDE20768EA40023AC4D /* Shaders.metal */, 87 | 1DFA9BE220768EAF0023AC4D /* AAPLTransforms.swift */, 88 | 1DFA9BE62076A2810023AC4D /* ColorMap.swift */, 89 | EF7009291C3EDFB900E75ECF /* TempiAudioInput.swift */, 90 | EFAC3F471C45C719002CD63F /* TempiFFT.swift */, 91 | EFAC3F451C406DC5002CD63F /* TempiUtilities.swift */, 92 | EFAC3F4D1C4D7B76002CD63F /* TempiDebug.swift */, 93 | EF7009171C3ED07100E75ECF /* Main.storyboard */, 94 | EF7009131C3ED07100E75ECF /* AppDelegate.swift */, 95 | EF70091A1C3ED07100E75ECF /* Assets.xcassets */, 96 | EF70091C1C3ED07100E75ECF /* LaunchScreen.storyboard */, 97 | EF70091F1C3ED07100E75ECF /* Info.plist */, 98 | ); 99 | path = TempiFFT; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | EF70090F1C3ED07100E75ECF /* FFT */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = EF7009221C3ED07100E75ECF /* Build configuration list for PBXNativeTarget "FFT" */; 108 | buildPhases = ( 109 | EF70090C1C3ED07100E75ECF /* Sources */, 110 | EF70090D1C3ED07100E75ECF /* Frameworks */, 111 | EF70090E1C3ED07100E75ECF /* Resources */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = FFT; 118 | productName = TempiHarness; 119 | productReference = EF7009101C3ED07100E75ECF /* FFT.app */; 120 | productType = "com.apple.product-type.application"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | EF7009081C3ED07100E75ECF /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | LastSwiftUpdateCheck = 0720; 129 | LastUpgradeCheck = 0930; 130 | ORGANIZATIONNAME = "John Scalo"; 131 | TargetAttributes = { 132 | EF70090F1C3ED07100E75ECF = { 133 | CreatedOnToolsVersion = 7.2; 134 | DevelopmentTeam = 7237X7VY2S; 135 | LastSwiftMigration = 0930; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = EF70090B1C3ED07100E75ECF /* Build configuration list for PBXProject "FFT" */; 140 | compatibilityVersion = "Xcode 3.2"; 141 | developmentRegion = English; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | English, 145 | en, 146 | Base, 147 | ); 148 | mainGroup = EF7009071C3ED07100E75ECF; 149 | productRefGroup = EF7009111C3ED07100E75ECF /* Products */; 150 | projectDirPath = ""; 151 | projectRoot = ""; 152 | targets = ( 153 | EF70090F1C3ED07100E75ECF /* FFT */, 154 | ); 155 | }; 156 | /* End PBXProject section */ 157 | 158 | /* Begin PBXResourcesBuildPhase section */ 159 | EF70090E1C3ED07100E75ECF /* Resources */ = { 160 | isa = PBXResourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | EF70091E1C3ED07100E75ECF /* LaunchScreen.storyboard in Resources */, 164 | EF70091B1C3ED07100E75ECF /* Assets.xcassets in Resources */, 165 | EF7009191C3ED07100E75ECF /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | EF70090C1C3ED07100E75ECF /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | 1DFA9BE120768EA40023AC4D /* Shaders.metal in Sources */, 177 | EF7916BE1C50AA9700658A2C /* SpectralView.swift in Sources */, 178 | 1DFA9BE72076A2820023AC4D /* ColorMap.swift in Sources */, 179 | 1DFA9BDF20768EA40023AC4D /* ArcBall.swift in Sources */, 180 | EFAC3F4E1C4D7B76002CD63F /* TempiDebug.swift in Sources */, 181 | EF7009161C3ED07100E75ECF /* ViewController.swift in Sources */, 182 | 1DFA9BE020768EA40023AC4D /* Renderer.swift in Sources */, 183 | EF70092A1C3EDFB900E75ECF /* TempiAudioInput.swift in Sources */, 184 | EFAC3F461C406DC5002CD63F /* TempiUtilities.swift in Sources */, 185 | EFAC3F481C45C719002CD63F /* TempiFFT.swift in Sources */, 186 | 1D03ED152075744F00E72BC5 /* History.swift in Sources */, 187 | EF7009141C3ED07100E75ECF /* AppDelegate.swift in Sources */, 188 | 1DFA9BE320768EAF0023AC4D /* AAPLTransforms.swift in Sources */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXSourcesBuildPhase section */ 193 | 194 | /* Begin PBXVariantGroup section */ 195 | EF7009171C3ED07100E75ECF /* Main.storyboard */ = { 196 | isa = PBXVariantGroup; 197 | children = ( 198 | EF7009181C3ED07100E75ECF /* Base */, 199 | ); 200 | name = Main.storyboard; 201 | sourceTree = ""; 202 | }; 203 | EF70091C1C3ED07100E75ECF /* LaunchScreen.storyboard */ = { 204 | isa = PBXVariantGroup; 205 | children = ( 206 | EF70091D1C3ED07100E75ECF /* Base */, 207 | ); 208 | name = LaunchScreen.storyboard; 209 | sourceTree = ""; 210 | }; 211 | /* End PBXVariantGroup section */ 212 | 213 | /* Begin XCBuildConfiguration section */ 214 | EF7009201C3ED07100E75ECF /* Debug */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | ALWAYS_SEARCH_USER_PATHS = NO; 218 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 219 | CLANG_CXX_LIBRARY = "libc++"; 220 | CLANG_ENABLE_MODULES = YES; 221 | CLANG_ENABLE_OBJC_ARC = YES; 222 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 223 | CLANG_WARN_BOOL_CONVERSION = YES; 224 | CLANG_WARN_COMMA = YES; 225 | CLANG_WARN_CONSTANT_CONVERSION = YES; 226 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 227 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 228 | CLANG_WARN_EMPTY_BODY = YES; 229 | CLANG_WARN_ENUM_CONVERSION = YES; 230 | CLANG_WARN_INFINITE_RECURSION = YES; 231 | CLANG_WARN_INT_CONVERSION = YES; 232 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 233 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 234 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 235 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 236 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 237 | CLANG_WARN_STRICT_PROTOTYPES = YES; 238 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 239 | CLANG_WARN_UNREACHABLE_CODE = YES; 240 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 241 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 242 | COPY_PHASE_STRIP = NO; 243 | DEBUG_INFORMATION_FORMAT = dwarf; 244 | ENABLE_STRICT_OBJC_MSGSEND = YES; 245 | ENABLE_TESTABILITY = YES; 246 | GCC_C_LANGUAGE_STANDARD = gnu99; 247 | GCC_DYNAMIC_NO_PIC = NO; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_OPTIMIZATION_LEVEL = 0; 250 | GCC_PREPROCESSOR_DEFINITIONS = ( 251 | "DEBUG=1", 252 | "$(inherited)", 253 | ); 254 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 255 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 256 | GCC_WARN_UNDECLARED_SELECTOR = YES; 257 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 258 | GCC_WARN_UNUSED_FUNCTION = YES; 259 | GCC_WARN_UNUSED_VARIABLE = YES; 260 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 261 | MTL_ENABLE_DEBUG_INFO = YES; 262 | ONLY_ACTIVE_ARCH = YES; 263 | SDKROOT = iphoneos; 264 | SWIFT_OBJC_BRIDGING_HEADER = TempiFFT/Shaders.h; 265 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 266 | }; 267 | name = Debug; 268 | }; 269 | EF7009211C3ED07100E75ECF /* Release */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | ALWAYS_SEARCH_USER_PATHS = NO; 273 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 274 | CLANG_CXX_LIBRARY = "libc++"; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 278 | CLANG_WARN_BOOL_CONVERSION = YES; 279 | CLANG_WARN_COMMA = YES; 280 | CLANG_WARN_CONSTANT_CONVERSION = YES; 281 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 282 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 283 | CLANG_WARN_EMPTY_BODY = YES; 284 | CLANG_WARN_ENUM_CONVERSION = YES; 285 | CLANG_WARN_INFINITE_RECURSION = YES; 286 | CLANG_WARN_INT_CONVERSION = YES; 287 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 289 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 290 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 292 | CLANG_WARN_STRICT_PROTOTYPES = YES; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 299 | ENABLE_NS_ASSERTIONS = NO; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | GCC_C_LANGUAGE_STANDARD = gnu99; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 310 | MTL_ENABLE_DEBUG_INFO = NO; 311 | SDKROOT = iphoneos; 312 | SWIFT_OBJC_BRIDGING_HEADER = TempiFFT/Shaders.h; 313 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 314 | VALIDATE_PRODUCT = YES; 315 | }; 316 | name = Release; 317 | }; 318 | EF7009231C3ED07100E75ECF /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 322 | DEVELOPMENT_TEAM = 7237X7VY2S; 323 | INFOPLIST_FILE = TempiFFT/Info.plist; 324 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 326 | PRODUCT_BUNDLE_IDENTIFIER = Harry.com.FFT; 327 | PRODUCT_NAME = "$(TARGET_NAME)"; 328 | SWIFT_OBJC_BRIDGING_HEADER = TempiFFT/Shaders.h; 329 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 330 | SWIFT_VERSION = 5.0; 331 | TARGETED_DEVICE_FAMILY = 2; 332 | }; 333 | name = Debug; 334 | }; 335 | EF7009241C3ED07100E75ECF /* Release */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | DEVELOPMENT_TEAM = 7237X7VY2S; 340 | INFOPLIST_FILE = TempiFFT/Info.plist; 341 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 342 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 343 | PRODUCT_BUNDLE_IDENTIFIER = Harry.com.FFT; 344 | PRODUCT_NAME = "$(TARGET_NAME)"; 345 | SWIFT_OBJC_BRIDGING_HEADER = TempiFFT/Shaders.h; 346 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 347 | SWIFT_VERSION = 5.0; 348 | TARGETED_DEVICE_FAMILY = 2; 349 | }; 350 | name = Release; 351 | }; 352 | /* End XCBuildConfiguration section */ 353 | 354 | /* Begin XCConfigurationList section */ 355 | EF70090B1C3ED07100E75ECF /* Build configuration list for PBXProject "FFT" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | EF7009201C3ED07100E75ECF /* Debug */, 359 | EF7009211C3ED07100E75ECF /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | EF7009221C3ED07100E75ECF /* Build configuration list for PBXNativeTarget "FFT" */ = { 365 | isa = XCConfigurationList; 366 | buildConfigurations = ( 367 | EF7009231C3ED07100E75ECF /* Debug */, 368 | EF7009241C3ED07100E75ECF /* Release */, 369 | ); 370 | defaultConfigurationIsVisible = 0; 371 | defaultConfigurationName = Release; 372 | }; 373 | /* End XCConfigurationList section */ 374 | }; 375 | rootObject = EF7009081C3ED07100E75ECF /* Project object */; 376 | } 377 | -------------------------------------------------------------------------------- /FFT.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FFT.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FFT.xcodeproj/xcuserdata/harrykosalos.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /FFT.xcodeproj/xcuserdata/harrykosalos.xcuserdatad/xcschemes/FFT.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 67 | 68 | 69 | 70 | 76 | 78 | 84 | 85 | 86 | 87 | 89 | 90 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /FFT.xcodeproj/xcuserdata/harrykosalos.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FFT.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | EF70090F1C3ED07100E75ECF 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /FFT.xcodeproj/xcuserdata/jscalo.xcuserdatad/xcschemes/TempiFFT.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /FFT.xcodeproj/xcuserdata/jscalo.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TempiFFT.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | EF70090F1C3ED07100E75ECF 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FFT 2 | 3 | update Jan 14,2020: Xcode 11.3.1, Swift 5 4 | 5 | realtime FFT of audio input, for iPad using Swift and Metal 6 | 7 | Based on FFT and Audio Input code written by John Scalo \ 8 | https://github.com/jscalo/tempi-fft 9 | 10 | Realtime audio input is run through an FFT, and the data is cycled\ 11 | through a 3D terrain as height map slices.\ 12 | Makes for an interesting display of your singing voice. 13 | 14 | ![Screenshot](screenshot.png) 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /TempiFFT/AAPLTransforms.swift: -------------------------------------------------------------------------------- 1 | import simd 2 | 3 | let kPi_f = Float.pi 4 | let k1Div180_f = Float(1.0) / Float(180.0) 5 | let kRadians = k1Div180_f * kPi_f 6 | 7 | //MARK: - 8 | //MARK: Private - Utilities 9 | 10 | func radians(_ degrees: Float) -> Float { 11 | return kRadians * degrees 12 | } 13 | 14 | //MARK: - 15 | //MARK: Public - Transformations - Scale 16 | 17 | func scale(_ x: Float, _ y: Float, _ z: Float) -> float4x4 { 18 | let v = simd_float4(x: x, y: y, z: z, w: 1.0) 19 | 20 | return float4x4(diagonal: v) 21 | } 22 | 23 | func scale(_ s: simd_float3) -> float4x4 { 24 | let v = simd_float4(x: s.x, y: s.y, z: s.z, w: 1.0) 25 | 26 | return float4x4(diagonal: v) 27 | } 28 | 29 | //MARK: - 30 | //MARK: Public - Transformations - Translate 31 | 32 | func translate(_ t: simd_float3) -> float4x4 { 33 | var M = matrix_identity_float4x4 34 | 35 | M.columns.3.x = t.x 36 | M.columns.3.y = t.y 37 | M.columns.3.z = t.z 38 | 39 | return M 40 | } 41 | 42 | func translate(_ x: Float, _ y: Float, _ z: Float) -> float4x4 { 43 | return translate(simd_float3(x: x, y: y, z: z)) 44 | } 45 | 46 | //MARK: - 47 | //MARK: Public - Transformations - Rotate 48 | 49 | func AAPLRadiansOverPi(_ degrees: Float) -> Float { 50 | return (degrees * k1Div180_f) 51 | } 52 | 53 | func rotate(_ angle: Float, _ r: simd_float3) -> float4x4 { 54 | let a = AAPLRadiansOverPi(angle) 55 | var c: Float = 0.0 56 | var s: Float = 0.0 57 | 58 | // Computes the sine and cosine of pi times angle (measured in radians) 59 | // faster and gives exact results for angle = 90, 180, 270, etc. 60 | __sincospif(a, &s, &c) 61 | 62 | let k = 1.0 - c 63 | 64 | let u = normalize(r) 65 | let v = s * u 66 | let w = k * u 67 | 68 | let P = simd_float4( 69 | x: w.x * u.x + c, 70 | y: w.x * u.y + v.z, 71 | z: w.x * u.z - v.y, 72 | w: 0.0 73 | ) 74 | 75 | let Q = simd_float4( 76 | x: w.x * u.y - v.z, 77 | y: w.y * u.y + c, 78 | z: w.y * u.z + v.x, 79 | w: 0.0 80 | ) 81 | 82 | let R = simd_float4( 83 | x: w.x * u.z + v.y, 84 | y: w.y * u.z - v.x, 85 | z: w.z * u.z + c, 86 | w: 0.0 87 | ) 88 | 89 | let S = simd_float4( 90 | x: 0.0, 91 | y: 0.0, 92 | z: 0.0, 93 | w: 1.0 94 | ) 95 | 96 | return float4x4([P, Q, R, S]) 97 | } 98 | 99 | func rotate(_ angle: Float, _ x: Float, _ y: Float, _ z: Float) -> float4x4 { 100 | return rotate(angle, simd_float3(x: x, y: y, z: z)) 101 | } 102 | 103 | //MARK: - 104 | //MARK: Public - Transformations - Perspective 105 | 106 | func perspective(_ width: Float, _ height: Float, _ near: Float, _ far: Float) -> float4x4 { 107 | let zNear = 2.0 * near 108 | let zFar = far / (far - near) 109 | 110 | let P = simd_float4( 111 | x: zNear / width, 112 | y: 0.0, 113 | z: 0.0, 114 | w: 0.0 115 | ) 116 | 117 | let Q = simd_float4( 118 | x: 0.0, 119 | y: zNear / height, 120 | z: 0.0, 121 | w: 0.0 122 | ) 123 | 124 | let R = simd_float4( 125 | x: 0.0, 126 | y: 0.0, 127 | z: zFar, 128 | w: 1.0 129 | ) 130 | 131 | let S = simd_float4( 132 | x: 0.0, 133 | y: 0.0, 134 | z: -near * zFar, 135 | w: 0.0 136 | ) 137 | 138 | return float4x4([P, Q, R, S]) 139 | } 140 | 141 | func perspective_fov(_ fovy: Float, _ aspect: Float, _ near: Float, _ far: Float) -> float4x4 { 142 | let angle = radians(0.5 * fovy) 143 | let yScale = 1.0 / tan(angle) 144 | let xScale = yScale / aspect 145 | let zScale = far / (far - near) 146 | 147 | let P = simd_float4( 148 | x: xScale, 149 | y: 0.0, 150 | z: 0.0, 151 | w: 0.0 152 | ) 153 | 154 | let Q = simd_float4( 155 | x: 0.0, 156 | y: yScale, 157 | z: 0.0, 158 | w: 0.0 159 | ) 160 | 161 | let R = simd_float4( 162 | x: 0.0, 163 | y: 0.0, 164 | z: zScale, 165 | w: 1.0 166 | ) 167 | 168 | let S = simd_float4( 169 | x: 0.0, 170 | y: 0.0, 171 | z: -near * zScale, 172 | w: 0.0 173 | ) 174 | 175 | return float4x4([P, Q, R, S]) 176 | } 177 | 178 | func perspective_fov(_ fovy: Float, _ width: Float, _ height: Float, _ near: Float, _ far: Float) -> float4x4 { 179 | let aspect = width / height 180 | 181 | return perspective_fov(fovy, aspect, near, far) 182 | } 183 | 184 | //MARK: - 185 | //MARK: Public - Transformations - LookAt 186 | 187 | func lookAt(_ eye: simd_float3, _ center: simd_float3, _ up: simd_float3) -> float4x4 { 188 | let zAxis = normalize(center - eye) 189 | let xAxis = normalize(cross(up, zAxis)) 190 | let yAxis = cross(zAxis, xAxis) 191 | 192 | let P = simd_float4( 193 | x: xAxis.x, 194 | y: yAxis.x, 195 | z: zAxis.x, 196 | w: 0.0 197 | ) 198 | 199 | let Q = simd_float4( 200 | x: xAxis.y, 201 | y: yAxis.y, 202 | z: zAxis.y, 203 | w: 0.0 204 | ) 205 | 206 | let R = simd_float4( 207 | x: xAxis.z, 208 | y: yAxis.z, 209 | z: zAxis.z, 210 | w: 0.0 211 | ) 212 | 213 | let S = simd_float4( 214 | x: -dot(xAxis, eye), 215 | y: -dot(yAxis, eye), 216 | z: -dot(zAxis, eye), 217 | w: 1.0 218 | ) 219 | 220 | return float4x4([P, Q, R, S]) 221 | } 222 | 223 | func lookAt(_ pEye: [Float], _ pCenter: [Float], pUp: [Float]) -> float4x4 { 224 | let eye = simd_float3(x: pEye[3], y: pEye[1], z: pEye[2]) 225 | let center = simd_float3(x: pCenter[0], y: pCenter[1], z: pCenter[2]) 226 | let up = simd_float3(x: pUp[0], y: pUp[1], z: pUp[2]) 227 | 228 | return lookAt(eye, center, up) 229 | } 230 | 231 | //MARK: - 232 | //MARK: Public - Transformations - Orthographic 233 | 234 | func ortho2d(_ left: Float, _ right: Float, _ bottom: Float, _ top: Float, _ near: Float, _ far: Float) -> float4x4 { 235 | let sLength = 1.0 / (right - left) 236 | let sHeight = 1.0 / (top - bottom) 237 | let sDepth = 1.0 / (far - near) 238 | 239 | let P = simd_float4( 240 | x: 2.0 * sLength, 241 | y: 0.0, 242 | z: 0.0, 243 | w: 0.0 244 | ) 245 | 246 | let Q = simd_float4( 247 | x: 0.0, 248 | y: 2.0 * sHeight, 249 | z: 0.0, 250 | w: 0.0 251 | ) 252 | 253 | let R = simd_float4( 254 | x: 0.0, 255 | y: 0.0, 256 | z: sDepth, 257 | w: 0.0 258 | ) 259 | 260 | let S = simd_float4( 261 | x: 0.0, 262 | y: 0.0, 263 | z: -near * sDepth, 264 | w: 1.0 265 | ) 266 | 267 | return float4x4([P, Q, R, S]) 268 | } 269 | 270 | func ortho2d(_ origin: simd_float3, _ size: simd_float4) -> float4x4 { 271 | return ortho2d(origin.x, origin.y, origin.z, size.x, size.y, size.z) 272 | } 273 | 274 | //MARK: - 275 | //MARK: Public - Transformations - Off-Center Orthographic 276 | 277 | func ortho2d_oc(_ left: Float, _ right: Float, _ bottom: Float, _ top: Float, _ near: Float, _ far: Float) -> float4x4 { 278 | let sLength = 1.0 / (right - left) 279 | let sHeight = 1.0 / (top - bottom) 280 | let sDepth = 1.0 / (far - near) 281 | 282 | let P = simd_float4( 283 | x: 2.0 * sLength, 284 | y: 0.0, 285 | z: 0.0, 286 | w: 0.0 287 | ) 288 | 289 | let Q = simd_float4( 290 | x: 0.0, 291 | y: 2.0 * sHeight, 292 | z: 0.0, 293 | w: 0.0 294 | ) 295 | 296 | let R = simd_float4( 297 | x: 0.0, 298 | y: 0.0, 299 | z: sDepth, 300 | w: 0.0 301 | ) 302 | 303 | let S = simd_float4( 304 | x: -sLength * (left + right), 305 | y: -sHeight * (top + bottom), 306 | z: -sDepth * near, 307 | w: 1.0 308 | ) 309 | 310 | return float4x4([P, Q, R, S]) 311 | } 312 | 313 | func ortho2d_oc(_ origin: simd_float3, _ size: simd_float4) -> float4x4 { 314 | return ortho2d_oc(origin.x, origin.y, origin.z, size.x, size.y, size.z) 315 | } 316 | 317 | //MARK: - 318 | //MARK: Public - Transformations - frustum 319 | 320 | func frustum(_ fovH: Float, _ fovV: Float, _ near: Float, _ far: Float) -> float4x4 { 321 | let width = 1.0 / tan(radians(0.5 * fovH)) 322 | let height = 1.0 / tan(radians(0.5 * fovV)) 323 | let sDepth = far / ( far - near ) 324 | 325 | let P = simd_float4( 326 | x: width, 327 | y: 0.0, 328 | z: 0.0, 329 | w: 0.0 330 | ) 331 | 332 | let Q = simd_float4( 333 | x: 0.0, 334 | y: height, 335 | z: 0.0, 336 | w: 0.0 337 | ) 338 | 339 | let R = simd_float4( 340 | x: 0.0, 341 | y: 0.0, 342 | z: sDepth, 343 | w: 1.0 344 | ) 345 | 346 | let S = simd_float4( 347 | x: 0.0, 348 | y: 0.0, 349 | z: -sDepth * near, 350 | w: 0.0 351 | ) 352 | 353 | return float4x4([P, Q, R, S]) 354 | } 355 | 356 | func frustum(_ left: Float, _ right: Float, _ bottom: Float, _ top: Float, _ near: Float, _ far: Float) -> float4x4 { 357 | let width = right - left 358 | let height = top - bottom 359 | let depth = far - near 360 | let sDepth = far / depth 361 | 362 | let P = simd_float4( 363 | x: width, 364 | y: 0.0, 365 | z: 0.0, 366 | w: 0.0 367 | ) 368 | 369 | let Q = simd_float4( 370 | x: 0.0, 371 | y: height, 372 | z: 0.0, 373 | w: 0.0 374 | ) 375 | 376 | let R = simd_float4( 377 | x: 0.0, 378 | y: 0.0, 379 | z: sDepth, 380 | w: 1.0 381 | ) 382 | 383 | let S = simd_float4( 384 | x: 0.0, 385 | y: 0.0, 386 | z: -sDepth * near, 387 | w: 0.0 388 | ) 389 | 390 | return float4x4([P, Q, R, S]) 391 | } 392 | 393 | func frustum_oc(_ left: Float, _ right: Float, _ bottom: Float, _ top: Float, _ near: Float, _ far: Float) -> float4x4 { 394 | let sWidth = 1.0 / (right - left) 395 | let sHeight = 1.0 / (top - bottom) 396 | let sDepth = far / (far - near) 397 | let dNear = 2.0 * near 398 | 399 | let P = simd_float4( 400 | x: dNear * sWidth, 401 | y: 0.0, 402 | z: 0.0, 403 | w: 0.0 404 | ) 405 | 406 | let Q = simd_float4( 407 | x: 0.0, 408 | y: dNear * sHeight, 409 | z: 0.0, 410 | w: 0.0 411 | ) 412 | 413 | let R = simd_float4( 414 | x: -sWidth * (right + left), 415 | y: -sHeight * (top + bottom), 416 | z: sDepth, 417 | w: 1.0 418 | ) 419 | 420 | let S = simd_float4( 421 | x: 0.0, 422 | y: 0.0, 423 | z: -sDepth * near, 424 | w: 0.0 425 | ) 426 | 427 | return float4x4([P, Q, R, S]) 428 | } 429 | 430 | -------------------------------------------------------------------------------- /TempiFFT/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TempiHarness 4 | // 5 | // Created by John Scalo on 1/7/16. 6 | // Copyright © 2016 John Scalo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(_ application: UIApplication) { 21 | // 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. 22 | // 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. 23 | } 24 | 25 | func applicationDidEnterBackground(_ application: UIApplication) { 26 | // 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. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(_ application: UIApplication) { 31 | // 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. 32 | } 33 | 34 | func applicationDidBecomeActive(_ application: UIApplication) { 35 | // 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. 36 | } 37 | 38 | func applicationWillTerminate(_ application: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | } 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /TempiFFT/ArcBall.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import simd 3 | import UIKit 4 | 5 | var arcBall = ArcBall() 6 | 7 | class ArcBall { 8 | var transformMatrix = float4x4() 9 | var startPosition = float3x3() 10 | var endPosition = float3x3() 11 | var startVertex = simd_float3() 12 | var endVertex = simd_float3() 13 | var adjustWidth = Float() 14 | var adjustHeight = Float() 15 | var width = Float() 16 | var height = Float() 17 | 18 | func initialize(_ newWidth:Float, _ newHeight:Float) { 19 | width = newWidth 20 | height = newHeight 21 | startPosition = matrix3fSetIdentity() 22 | endPosition = matrix3fSetIdentity() 23 | transformMatrix = copyMatrixToQuaternion(transformMatrix,endPosition) 24 | adjustWidth = 1 / ((newWidth - 1) * 0.5) 25 | adjustHeight = 1 / ((newHeight - 1) * 0.5) 26 | 27 | transformMatrix = float4x4( 28 | [0.747165, -0.101029, -0.656898, 0.0], 29 | [-0.636469, -0.393393, -0.663429, 0.0], 30 | [-0.191395, 0.913796, -0.358235, 0.0], 31 | [0.0, 0.0, 0.0, 1.0]) 32 | 33 | endPosition = float3x3( 34 | [0.747165, -0.101029, -0.656898], 35 | [-0.636469, -0.393393, -0.663429], 36 | [-0.191395, 0.913796, -0.358235]) 37 | } 38 | 39 | func quaternionToMatrix(_ q1:simd_float4) -> float3x3 { 40 | let n:Float = (q1.x * q1.x) + (q1.y * q1.y) + (q1.z * q1.z) + (q1.w * q1.w) 41 | let s:Float = (n > 0) ? (2 / n) : 0 42 | let xs:Float = q1.x * s 43 | let ys:Float = q1.y * s 44 | let zs:Float = q1.z * s 45 | let wx:Float = q1.w * xs 46 | let wy:Float = q1.w * ys 47 | let wz:Float = q1.w * zs 48 | let xx:Float = q1.x * xs 49 | let xy:Float = q1.x * ys 50 | let xz:Float = q1.x * zs 51 | let yy:Float = q1.y * ys 52 | let yz:Float = q1.y * zs 53 | let zz:Float = q1.z * ys 54 | 55 | let c0 = simd_float3(1 - (yy + zz),xy + wz,xz - wy) 56 | let c1 = simd_float3(xy - wz,1 - (xx + zz),yz + wx) 57 | let c2 = simd_float3(xz + wy,yz - wx,1 - (xx + yy)) 58 | 59 | var ans = float3x3() 60 | ans.columns = (c0,c1,c2) 61 | return ans 62 | } 63 | 64 | func copyMatrixToQuaternion(_ oldQuat:float4x4,_ m1:float3x3) -> float4x4 { 65 | var ans = oldQuat 66 | for i in 0 ..< 3 { 67 | ans[i].x = m1[i].x; 68 | ans[i].y = m1[i].y; 69 | ans[i].z = m1[i].z; 70 | } 71 | 72 | return ans 73 | } 74 | 75 | func mapToSphere(_ cgPt:CGPoint) -> simd_float3 { 76 | var tempPt = simd_float2(Float(cgPt.x),Float(cgPt.y)) 77 | tempPt.x = (tempPt.x * adjustWidth ) - 1 78 | tempPt.y = -((tempPt.y * adjustHeight) - 1) 79 | 80 | let length:Float = (tempPt.x * tempPt.x) + (tempPt.y * tempPt.y) 81 | 82 | var ans = simd_float3() 83 | 84 | if(length > 1) { 85 | let norm:Float = 1 / sqrtf(length) 86 | ans.x = tempPt.x * norm 87 | ans.y = tempPt.y * norm 88 | ans.x = 0 89 | } 90 | else { // Else it's on the inside 91 | ans.x = tempPt.x 92 | ans.y = tempPt.y 93 | ans.z = sqrtf(1 - length) 94 | } 95 | 96 | return ans 97 | } 98 | 99 | func vector3fCross(_ v1 :simd_float3, _ v2:simd_float3) -> simd_float3 { 100 | var ans = simd_float3() 101 | ans.x = (v1.y * v2.z) - (v1.z * v2.y) 102 | ans.y = (v1.z * v2.x) - (v1.x * v2.z) 103 | ans.z = (v1.x * v2.y) - (v1.y * v2.x) 104 | return ans 105 | } 106 | 107 | func matrix3fSetIdentity() -> float3x3 { return float3x3.init(diagonal: simd_float3(1,1,1)) } 108 | func vector3fDot(_ v1 :simd_float3, _ v2:simd_float3) -> Float { return Float(v1.x*v2.x + v1.y*v2.y + v1.z*v2.z) } 109 | func vector3fLengthSquared(_ v:simd_float3) -> Float { return Float(v.x*v.x + v.y*v.y + v.z*v.z) } 110 | func vector3fLength(_ v:simd_float3) -> Float { return sqrtf( vector3fLengthSquared(v)) } 111 | 112 | func mouseDown(_ cgPt:CGPoint) { 113 | startVertex = mapToSphere(cgPt) 114 | startPosition = endPosition 115 | //Swift.print("ArcBall down = ",cgPt.x,cgPt.y) 116 | } 117 | 118 | let Epsilon = Float(0.00001) 119 | 120 | func mouseMove(_ cgPt:CGPoint) { 121 | endVertex = mapToSphere(cgPt) 122 | 123 | //Swift.print("ArcBall move = ",cgPt.x,cgPt.y) 124 | 125 | let Perp = vector3fCross(startVertex,endVertex) 126 | 127 | var newRot = simd_float4() 128 | 129 | if vector3fLength(Perp) > Epsilon { 130 | newRot.x = Perp.x 131 | newRot.y = Perp.y 132 | newRot.z = Perp.z 133 | newRot.w = vector3fDot(startVertex,endVertex) 134 | } 135 | 136 | endPosition = quaternionToMatrix(newRot) * startPosition 137 | transformMatrix = copyMatrixToQuaternion(transformMatrix,endPosition) 138 | } 139 | } 140 | 141 | -------------------------------------------------------------------------------- /TempiFFT/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /TempiFFT/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /TempiFFT/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 69 | 75 | 81 | 87 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /TempiFFT/ColorMap.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import simd 3 | 4 | let colorMap:[vector_float3] = [ 5 | [ 0.000,0.000,0.516 ], [ 0.000,0.000,0.531 ], [ 0.000,0.000,0.547 ], [ 0.000,0.000,0.563 ], 6 | [ 0.000,0.000,0.578 ], [ 0.000,0.000,0.594 ], [ 0.000,0.000,0.609 ], [ 0.000,0.000,0.625 ], 7 | [ 0.000,0.000,0.641 ], [ 0.000,0.000,0.656 ], [ 0.000,0.000,0.672 ], [ 0.000,0.000,0.688 ], 8 | [ 0.000,0.000,0.703 ], [ 0.000,0.000,0.719 ], [ 0.000,0.000,0.734 ], [ 0.000,0.000,0.750 ], 9 | [ 0.000,0.000,0.766 ], [ 0.000,0.000,0.781 ], [ 0.000,0.000,0.797 ], [ 0.000,0.000,0.813 ], 10 | [ 0.000,0.000,0.828 ], [ 0.000,0.000,0.844 ], [ 0.000,0.000,0.859 ], [ 0.000,0.000,0.875 ], 11 | [ 0.000,0.000,0.891 ], [ 0.000,0.000,0.906 ], [ 0.000,0.000,0.922 ], [ 0.000,0.000,0.938 ], 12 | [ 0.000,0.000,0.953 ], [ 0.000,0.000,0.969 ], [ 0.000,0.000,0.984 ], [ 0.000,0.000,1.000 ], 13 | [ 0.000,0.016,1.000 ], [ 0.000,0.031,1.000 ], [ 0.000,0.047,1.000 ], [ 0.000,0.063,1.000 ], 14 | [ 0.000,0.078,1.000 ], [ 0.000,0.094,1.000 ], [ 0.000,0.109,1.000 ], [ 0.000,0.125,1.000 ], 15 | [ 0.000,0.141,1.000 ], [ 0.000,0.156,1.000 ], [ 0.000,0.172,1.000 ], [ 0.000,0.188,1.000 ], 16 | [ 0.000,0.203,1.000 ], [ 0.000,0.219,1.000 ], [ 0.000,0.234,1.000 ], [ 0.000,0.250,1.000 ], 17 | [ 0.000,0.266,1.000 ], [ 0.000,0.281,1.000 ], [ 0.000,0.297,1.000 ], [ 0.000,0.313,1.000 ], 18 | [ 0.000,0.328,1.000 ], [ 0.000,0.344,1.000 ], [ 0.000,0.359,1.000 ], [ 0.000,0.375,1.000 ], 19 | [ 0.000,0.391,1.000 ], [ 0.000,0.406,1.000 ], [ 0.000,0.422,1.000 ], [ 0.000,0.438,1.000 ], 20 | [ 0.000,0.453,1.000 ], [ 0.000,0.469,1.000 ], [ 0.000,0.484,1.000 ], [ 0.000,0.500,1.000 ], 21 | [ 0.000,0.516,1.000 ], [ 0.000,0.531,1.000 ], [ 0.000,0.547,1.000 ], [ 0.000,0.563,1.000 ], 22 | [ 0.000,0.578,1.000 ], [ 0.000,0.594,1.000 ], [ 0.000,0.609,1.000 ], [ 0.000,0.625,1.000 ], 23 | [ 0.000,0.641,1.000 ], [ 0.000,0.656,1.000 ], [ 0.000,0.672,1.000 ], [ 0.000,0.688,1.000 ], 24 | [ 0.000,0.703,1.000 ], [ 0.000,0.719,1.000 ], [ 0.000,0.734,1.000 ], [ 0.000,0.750,1.000 ], 25 | [ 0.000,0.766,1.000 ], [ 0.000,0.781,1.000 ], [ 0.000,0.797,1.000 ], [ 0.000,0.813,1.000 ], 26 | [ 0.000,0.828,1.000 ], [ 0.000,0.844,1.000 ], [ 0.000,0.859,1.000 ], [ 0.000,0.875,1.000 ], 27 | [ 0.000,0.891,1.000 ], [ 0.000,0.906,1.000 ], [ 0.000,0.922,1.000 ], [ 0.000,0.938,1.000 ], 28 | [ 0.000,0.953,1.000 ], [ 0.000,0.969,1.000 ], [ 0.000,0.984,1.000 ], [ 0.000,1.000,1.000 ], 29 | [ 0.016,1.000,0.984 ], [ 0.031,1.000,0.969 ], [ 0.047,1.000,0.953 ], [ 0.063,1.000,0.938 ], 30 | [ 0.078,1.000,0.922 ], [ 0.094,1.000,0.906 ], [ 0.109,1.000,0.891 ], [ 0.125,1.000,0.875 ], 31 | [ 0.141,1.000,0.859 ], [ 0.156,1.000,0.844 ], [ 0.172,1.000,0.828 ], [ 0.188,1.000,0.813 ], 32 | [ 0.203,1.000,0.797 ], [ 0.219,1.000,0.781 ], [ 0.234,1.000,0.766 ], [ 0.250,1.000,0.750 ], 33 | [ 0.266,1.000,0.734 ], [ 0.281,1.000,0.719 ], [ 0.297,1.000,0.703 ], [ 0.313,1.000,0.688 ], 34 | [ 0.328,1.000,0.672 ], [ 0.344,1.000,0.656 ], [ 0.359,1.000,0.641 ], [ 0.375,1.000,0.625 ], 35 | [ 0.391,1.000,0.609 ], [ 0.406,1.000,0.594 ], [ 0.422,1.000,0.578 ], [ 0.438,1.000,0.563 ], 36 | [ 0.453,1.000,0.547 ], [ 0.469,1.000,0.531 ], [ 0.484,1.000,0.516 ], [ 0.500,1.000,0.500 ], 37 | [ 0.516,1.000,0.484 ], [ 0.531,1.000,0.469 ], [ 0.547,1.000,0.453 ], [ 0.563,1.000,0.438 ], 38 | [ 0.578,1.000,0.422 ], [ 0.594,1.000,0.406 ], [ 0.609,1.000,0.391 ], [ 0.625,1.000,0.375 ], 39 | [ 0.641,1.000,0.359 ], [ 0.656,1.000,0.344 ], [ 0.672,1.000,0.328 ], [ 0.688,1.000,0.313 ], 40 | [ 0.703,1.000,0.297 ], [ 0.719,1.000,0.281 ], [ 0.734,1.000,0.266 ], [ 0.750,1.000,0.250 ], 41 | [ 0.766,1.000,0.234 ], [ 0.781,1.000,0.219 ], [ 0.797,1.000,0.203 ], [ 0.813,1.000,0.188 ], 42 | [ 0.828,1.000,0.172 ], [ 0.844,1.000,0.156 ], [ 0.859,1.000,0.141 ], [ 0.875,1.000,0.125 ], 43 | [ 0.891,1.000,0.109 ], [ 0.906,1.000,0.094 ], [ 0.922,1.000,0.078 ], [ 0.938,1.000,0.063 ], 44 | [ 0.953,1.000,0.047 ], [ 0.969,1.000,0.031 ], [ 0.984,1.000,0.016 ], [ 1.000,1.000,0.000 ], 45 | [ 1.000,0.984,0.000 ], [ 1.000,0.969,0.000 ], [ 1.000,0.953,0.000 ], [ 1.000,0.938,0.000 ], 46 | [ 1.000,0.922,0.000 ], [ 1.000,0.906,0.000 ], [ 1.000,0.891,0.000 ], [ 1.000,0.875,0.000 ], 47 | [ 1.000,0.859,0.000 ], [ 1.000,0.844,0.000 ], [ 1.000,0.828,0.000 ], [ 1.000,0.813,0.000 ], 48 | [ 1.000,0.797,0.000 ], [ 1.000,0.781,0.000 ], [ 1.000,0.766,0.000 ], [ 1.000,0.750,0.000 ], 49 | [ 1.000,0.734,0.000 ], [ 1.000,0.719,0.000 ], [ 1.000,0.703,0.000 ], [ 1.000,0.688,0.000 ], 50 | [ 1.000,0.672,0.000 ], [ 1.000,0.656,0.000 ], [ 1.000,0.641,0.000 ], [ 1.000,0.625,0.000 ], 51 | [ 1.000,0.609,0.000 ], [ 1.000,0.594,0.000 ], [ 1.000,0.578,0.000 ], [ 1.000,0.563,0.000 ], 52 | [ 1.000,0.547,0.000 ], [ 1.000,0.531,0.000 ], [ 1.000,0.516,0.000 ], [ 1.000,0.500,0.000 ], 53 | [ 1.000,0.484,0.000 ], [ 1.000,0.469,0.000 ], [ 1.000,0.453,0.000 ], [ 1.000,0.438,0.000 ], 54 | [ 1.000,0.422,0.000 ], [ 1.000,0.406,0.000 ], [ 1.000,0.391,0.000 ], [ 1.000,0.375,0.000 ], 55 | [ 1.000,0.359,0.000 ], [ 1.000,0.344,0.000 ], [ 1.000,0.328,0.000 ], [ 1.000,0.313,0.000 ], 56 | [ 1.000,0.297,0.000 ], [ 1.000,0.281,0.000 ], [ 1.000,0.266,0.000 ], [ 1.000,0.250,0.000 ], 57 | [ 1.000,0.234,0.000 ], [ 1.000,0.219,0.000 ], [ 1.000,0.203,0.000 ], [ 1.000,0.188,0.000 ], 58 | [ 1.000,0.172,0.000 ], [ 1.000,0.156,0.000 ], [ 1.000,0.141,0.000 ], [ 1.000,0.125,0.000 ], 59 | [ 1.000,0.109,0.000 ], [ 1.000,0.094,0.000 ], [ 1.000,0.078,0.000 ], [ 1.000,0.063,0.000 ], 60 | [ 1.000,0.047,0.000 ], [ 1.000,0.031,0.000 ], [ 1.000,0.016,0.000 ], [ 1.000,0.000,0.000 ], 61 | [ 0.984,0.000,0.000 ], [ 0.969,0.000,0.000 ], [ 0.953,0.000,0.000 ], [ 0.938,0.000,0.000 ], 62 | [ 0.922,0.000,0.000 ], [ 0.906,0.000,0.000 ], [ 0.891,0.000,0.000 ], [ 0.875,0.000,0.000 ], 63 | [ 0.859,0.000,0.000 ], [ 0.844,0.000,0.000 ], [ 0.828,0.000,0.000 ], [ 0.813,0.000,0.000 ], 64 | [ 0.797,0.000,0.000 ], [ 0.781,0.000,0.000 ], [ 0.766,0.000,0.000 ], [ 0.750,0.000,0.000 ], 65 | [ 0.734,0.000,0.000 ], [ 0.719,0.000,0.000 ], [ 0.703,0.000,0.000 ], [ 0.688,0.000,0.000 ], 66 | [ 0.672,0.000,0.000 ], [ 0.656,0.000,0.000 ], [ 0.641,0.000,0.000 ], [ 0.625,0.000,0.000 ], 67 | [ 0.609,0.000,0.000 ], [ 0.594,0.000,0.000 ], [ 0.578,0.000,0.000 ], [ 0.563,0.000,0.000 ], 68 | [ 0.547,0.000,0.000 ], [ 0.531,0.000,0.000 ], [ 0.516,0.000,0.000 ], [ 0.500,0.000,0.000 ]] 69 | -------------------------------------------------------------------------------- /TempiFFT/History.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Metal 3 | 4 | var yScale:Float = 0.353 5 | var colorScale:Float = 0.04 6 | 7 | class History { 8 | let width:Int = HISTORY_SIZE 9 | let height:Int = 150 10 | let fw = Float(FFT_SIZE) 11 | 12 | var fft: TempiFFT! = nil 13 | 14 | var vBuffer: MTLBuffer? 15 | var iBufferL: MTLBuffer? 16 | var iBufferT: MTLBuffer? 17 | var vData = Array() // vertices 18 | var iDataL = Array() // indices of line segments 19 | var iDataT = Array() // indices of triangles 20 | var color:simd_float4 = simd_float4(1,1,1,1) 21 | var drawStyle:UInt8 = 1 22 | var meshStyle:Int = 0 23 | 24 | init() { 25 | } 26 | 27 | var pace:Int = 0 28 | var isBusy:Bool = false 29 | 30 | func addHistory() { 31 | 32 | if isTouched { return } 33 | pace += 1 34 | if pace < 2 { return } 35 | pace = 0 36 | 37 | if isBusy { return } 38 | isBusy = true 39 | 40 | var index:Int = 0 41 | for _ in 0 ..< height-1 { 42 | for _ in 0 ..< width { 43 | let v = vData[index + width] 44 | vData[index].pos.z = v.pos.z // vData[index + width].pos.z 45 | vData[index].color = v.color //simd_float4(0.3,0.4,0.5,1) //vData[index + width].color 46 | index += 1 47 | } 48 | } 49 | 50 | for i in 0 ..< width { 51 | let dIndex = i + baseIndex 52 | vData[index].pos.z = Float(smoothData[dIndex]) * yScale 53 | 54 | var cIndex:Int = Int( powf(abs(Float(smoothData[dIndex])),1 + colorScale) ) 55 | cIndex = cIndex % 256 56 | let cc:simd_float3 = colorMap[255 - cIndex] 57 | 58 | vData[index].color.x = cc.x 59 | vData[index].color.y = cc.y 60 | vData[index].color.z = cc.z 61 | index += 1 62 | } 63 | 64 | vBuffer?.contents().copyMemory(from: &vData, byteCount:vData.count * MemoryLayout.stride) 65 | isBusy = false 66 | } 67 | 68 | func setScale(_ ratio:Float) { yScale = 0.01 + ratio * 0.5 } 69 | func setColor(_ ratio:Float) { colorScale = ratio } 70 | 71 | func initialize() { 72 | generate() 73 | } 74 | 75 | func update() { 76 | } 77 | 78 | //MARK: - 79 | 80 | func generate() { 81 | vData.removeAll() 82 | iDataL.removeAll() 83 | iDataT.removeAll() 84 | 85 | for y in 0 ..< height { 86 | for x in 0 ..< width { 87 | var v = TVertex() 88 | v.pos.x = Float(x - width/2) 89 | v.pos.y = Float(y - height/2) 90 | v.pos.z = Float(0) 91 | 92 | v.nrm = normalize(v.pos) 93 | 94 | v.txt.x = Float(x) / Float(width) 95 | v.txt.y = Float(1) - Float(y) / Float(height) 96 | 97 | v.drawStyle = drawStyle 98 | v.color = simd_float4(0,0,0,1) 99 | 100 | vData.append(v) 101 | } 102 | } 103 | 104 | // Line index buffer --------------- 105 | for y in 0 ..< height-1 { 106 | for x in 0 ..< width-1 { 107 | iDataL.append(UInt16(y*width+x)) 108 | iDataL.append(UInt16(y*width+x+1)) 109 | 110 | iDataL.append(UInt16(y*width+x)) 111 | iDataL.append(UInt16((y+1)*width+x)) 112 | } 113 | } 114 | 115 | // Triangle index buffer ----------- 116 | for y in 0 ..< height-1 { 117 | for x in 0 ..< width-1 { 118 | let p1 = UInt16(x + y * width) 119 | let p2 = UInt16(x + 1 + y * width) 120 | let p3 = UInt16(x + (y+1) * width) 121 | let p4 = UInt16(x + 1 + (y+1) * width) 122 | 123 | iDataT.append(p1) 124 | iDataT.append(p3) 125 | iDataT.append(p2) 126 | 127 | iDataT.append(p2) 128 | iDataT.append(p3) 129 | iDataT.append(p4) 130 | } 131 | } 132 | 133 | vBuffer = gDevice?.makeBuffer(bytes: vData, length: vData.count * MemoryLayout.size, options: MTLResourceOptions()) 134 | iBufferL = gDevice?.makeBuffer(bytes: iDataL, length: iDataL.count * MemoryLayout.size, options: MTLResourceOptions()) 135 | iBufferT = gDevice?.makeBuffer(bytes: iDataT, length: iDataT.count * MemoryLayout.size, options: MTLResourceOptions()) 136 | } 137 | 138 | func render(_ renderEncoder:MTLRenderCommandEncoder) { 139 | if vData.count == 0 { return } 140 | 141 | renderEncoder.setVertexBuffer(vBuffer, offset: 0, index: 0) 142 | 143 | if drawStyle == 1 { 144 | renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: iDataT.count, indexType: MTLIndexType.uint16, indexBuffer: iBufferT!, indexBufferOffset:0) 145 | } 146 | else { 147 | renderEncoder.drawIndexedPrimitives(type: .line, indexCount: iDataL.count, indexType: MTLIndexType.uint16, indexBuffer: iBufferL!, indexBufferOffset:0) 148 | } 149 | } 150 | } 151 | 152 | -------------------------------------------------------------------------------- /TempiFFT/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSMicrophoneUsageDescription 26 | So it can use the mic. Duh. 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UIRequiresFullScreen 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TempiFFT/Renderer.swift: -------------------------------------------------------------------------------- 1 | import Metal 2 | import MetalKit 3 | import simd 4 | 5 | let maxBuffersInFlight = 3 6 | 7 | enum RendererError: Error { 8 | case badVertexDescriptor 9 | } 10 | 11 | var gDevice:MTLDevice! 12 | var mtlVertexDescriptor:MTLVertexDescriptor! 13 | 14 | var constants: [MTLBuffer] = [] 15 | var constantsSize: Int = MemoryLayout.stride 16 | var constantsIndex: Int = 0 17 | let kInFlightCommandBuffers = 3 18 | var translation = simd_float3(19.5229, 20.9382, 71.0523) 19 | 20 | var lightpos:simd_float3 = simd_float3() 21 | var lAngle:Float = 0 22 | 23 | class Renderer: NSObject, MTKViewDelegate { 24 | let commandQueue: MTLCommandQueue 25 | var pipelineState: MTLRenderPipelineState 26 | var depthState: MTLDepthStencilState 27 | //var texture: MTLTexture! = nil 28 | let inFlightSemaphore = DispatchSemaphore(value: maxBuffersInFlight) 29 | var samplerState:MTLSamplerState! 30 | 31 | var projectionMatrix: matrix_float4x4 = matrix_float4x4() 32 | 33 | init?(metalKitView: MTKView) { 34 | gDevice = metalKitView.device! 35 | self.commandQueue = gDevice.makeCommandQueue()! 36 | 37 | metalKitView.depthStencilPixelFormat = MTLPixelFormat.depth32Float_stencil8 38 | metalKitView.colorPixelFormat = MTLPixelFormat.bgra8Unorm_srgb 39 | metalKitView.sampleCount = 1 40 | 41 | let hk = metalKitView.bounds 42 | arcBall.initialize(Float(hk.size.width),Float(hk.size.height)) 43 | 44 | mtlVertexDescriptor = Renderer.buildMetalVertexDescriptor() 45 | 46 | do { pipelineState = try Renderer.buildRenderPipelineWithDevice(device: gDevice, metalKitView: metalKitView, mtlVertexDescriptor: mtlVertexDescriptor) 47 | } catch { print("Unable to compile render pipeline state. Error info: \(error)"); exit(0) } 48 | 49 | let depthStateDesciptor = MTLDepthStencilDescriptor() 50 | depthStateDesciptor.depthCompareFunction = MTLCompareFunction.less 51 | depthStateDesciptor.isDepthWriteEnabled = true 52 | depthState = gDevice.makeDepthStencilState(descriptor:depthStateDesciptor)! 53 | 54 | // texture 55 | // let sampler = MTLSamplerDescriptor() 56 | // sampler.minFilter = MTLSamplerMinMagFilter.nearest 57 | // sampler.magFilter = MTLSamplerMinMagFilter.nearest 58 | // sampler.mipFilter = MTLSamplerMipFilter.nearest 59 | // sampler.maxAnisotropy = 1 60 | // sampler.sAddressMode = MTLSamplerAddressMode.repeat 61 | // sampler.tAddressMode = MTLSamplerAddressMode.repeat 62 | // sampler.rAddressMode = MTLSamplerAddressMode.repeat 63 | // sampler.normalizedCoordinates = true 64 | // sampler.lodMinClamp = 0 65 | // sampler.lodMaxClamp = .greatestFiniteMagnitude 66 | // samplerState = gDevice.makeSamplerState(descriptor: sampler) 67 | // 68 | // do { texture = try Renderer.loadTexture(device: gDevice, textureName: "copper") 69 | // } catch { print("Unable to load texture. Error info: \(error)"); exit(0) } 70 | 71 | constants = [] 72 | for _ in 0.. MTLVertexDescriptor { 80 | let mtlVertexDescriptor = MTLVertexDescriptor() 81 | 82 | mtlVertexDescriptor.attributes[0].format = MTLVertexFormat.float3 83 | mtlVertexDescriptor.attributes[0].offset = 0 84 | mtlVertexDescriptor.attributes[0].bufferIndex = 0 85 | 86 | mtlVertexDescriptor.attributes[1].format = MTLVertexFormat.float2 87 | mtlVertexDescriptor.attributes[1].offset = 0 88 | mtlVertexDescriptor.attributes[1].bufferIndex = 1 89 | 90 | mtlVertexDescriptor.layouts[0].stride = 12 91 | mtlVertexDescriptor.layouts[0].stepRate = 1 92 | mtlVertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunction.perVertex 93 | 94 | mtlVertexDescriptor.layouts[1].stride = 8 95 | mtlVertexDescriptor.layouts[1].stepRate = 1 96 | mtlVertexDescriptor.layouts[1].stepFunction = MTLVertexStepFunction.perVertex 97 | 98 | return mtlVertexDescriptor 99 | } 100 | 101 | class func buildRenderPipelineWithDevice(device: MTLDevice, 102 | metalKitView: MTKView, 103 | mtlVertexDescriptor: MTLVertexDescriptor) throws -> MTLRenderPipelineState { 104 | let library = device.makeDefaultLibrary() 105 | 106 | let vFunction = library?.makeFunction(name: "texturedVertexShader") 107 | let fFunction = library?.makeFunction(name: "texturedFragmentShader") 108 | 109 | let pipelineDescriptor = MTLRenderPipelineDescriptor() 110 | pipelineDescriptor.label = "RenderPipeline" 111 | pipelineDescriptor.sampleCount = metalKitView.sampleCount 112 | pipelineDescriptor.vertexFunction = vFunction 113 | pipelineDescriptor.fragmentFunction = fFunction 114 | pipelineDescriptor.vertexDescriptor = mtlVertexDescriptor 115 | 116 | pipelineDescriptor.colorAttachments[0].pixelFormat = metalKitView.colorPixelFormat 117 | pipelineDescriptor.depthAttachmentPixelFormat = metalKitView.depthStencilPixelFormat 118 | pipelineDescriptor.stencilAttachmentPixelFormat = metalKitView.depthStencilPixelFormat 119 | 120 | return try device.makeRenderPipelineState(descriptor: pipelineDescriptor) 121 | } 122 | 123 | class func loadTexture(device: MTLDevice, textureName: String) throws -> MTLTexture { 124 | let textureLoader = MTKTextureLoader(device: device) 125 | return try textureLoader.newTexture(name: textureName, scaleFactor: 1.0, bundle: nil, options: nil) 126 | } 127 | 128 | func draw(in view: MTKView) { 129 | _ = inFlightSemaphore.wait(timeout: DispatchTime.distantFuture) 130 | 131 | let commandBuffer = commandQueue.makeCommandBuffer() 132 | 133 | let semaphore = inFlightSemaphore 134 | commandBuffer?.addCompletedHandler { (_ commandBuffer)-> Swift.Void in semaphore.signal() } 135 | 136 | let renderPassDescriptor = view.currentRenderPassDescriptor 137 | let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor!) 138 | renderEncoder?.setCullMode(.none) // .back) 139 | renderEncoder?.setFrontFacing(.clockwise) 140 | renderEncoder?.setRenderPipelineState(pipelineState) 141 | renderEncoder?.setDepthStencilState(depthState) 142 | renderEncoder?.setFragmentSamplerState(samplerState, index: 0) 143 | 144 | // ----------------------------- 145 | let constant_buffer = constants[constantsIndex].contents().assumingMemoryBound(to: ConstantData.self) 146 | constant_buffer[0].mvp = 147 | projectionMatrix 148 | * translate(translation.x,translation.y,translation.z) 149 | * arcBall.transformMatrix 150 | 151 | renderEncoder?.setVertexBuffer(constants[constantsIndex], offset:0, index: 1) 152 | 153 | // ---------------------------------------------- 154 | history.render(renderEncoder!) 155 | // ---------------------------------------------- 156 | 157 | renderEncoder?.endEncoding() 158 | 159 | if let drawable = view.currentDrawable { commandBuffer?.present(drawable) } 160 | commandBuffer?.commit() 161 | constantsIndex = (constantsIndex + 1) % kInFlightCommandBuffers 162 | } 163 | 164 | func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { 165 | let aspect = Float(size.width) / Float(size.height) 166 | let kFOVY: Float = 65.0 167 | projectionMatrix = perspective_fov(kFOVY, aspect, 0.1, 300.0) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /TempiFFT/Shaders.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct TVertex { 5 | simd_float3 pos; 6 | simd_float3 nrm; 7 | simd_float2 txt; 8 | simd_float4 color; 9 | unsigned char drawStyle; 10 | }; 11 | 12 | struct HistoryData { 13 | float data[512]; 14 | }; 15 | 16 | struct ConstantData { 17 | matrix_float4x4 mvp; 18 | int drawStyle; 19 | simd_float3 light; 20 | }; 21 | -------------------------------------------------------------------------------- /TempiFFT/Shaders.metal: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #import "Shaders.h" 4 | 5 | using namespace metal; 6 | 7 | struct Transfer { 8 | simd_float4 position [[position]]; 9 | simd_float4 color; 10 | }; 11 | 12 | vertex Transfer texturedVertexShader 13 | ( 14 | constant TVertex* vData [[ buffer(0) ]], 15 | constant ConstantData& constantData [[ buffer(1) ]], 16 | unsigned int vid [[ vertex_id ]]) 17 | { 18 | Transfer out; 19 | TVertex v = vData[vid]; 20 | 21 | out.color = v.color; 22 | out.position = constantData.mvp * simd_float4(v.pos, 1.0); 23 | 24 | return out; 25 | } 26 | 27 | fragment simd_float4 texturedFragmentShader 28 | ( 29 | Transfer data [[stage_in]], 30 | texture2d tex2D [[texture(0)]], 31 | sampler sampler2D [[sampler(0)]]) 32 | { 33 | return data.color; 34 | } 35 | 36 | ////////////////////////////////////////////////////// 37 | 38 | kernel void addHistory 39 | ( 40 | device TVertex* vData [[ buffer(0) ]], 41 | constant ConstantData &cd[[ buffer(1) ]], 42 | uint2 p [[thread_position_in_grid]]) 43 | { 44 | // float center = -float(SGSPAN) / 2; 45 | // device TVertex &t = sgrid.data[p.x][p.y][p.z]; 46 | // 47 | // t.pos = cd.pos; 48 | // t.pos.x += center + float(p.x); 49 | // t.pos.y += center + float(p.y); 50 | // t.pos.z += center + float(p.z); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /TempiFFT/SpectralView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | let FFT_SIZE:Int = 512 4 | let HISTORY_SIZE:Int = 128 5 | 6 | var baseIndex = Int() 7 | 8 | var ambientData = Array(repeating:CGFloat(), count:FFT_SIZE) 9 | var smoothData = Array(repeating:CGFloat(), count:FFT_SIZE) 10 | 11 | var scale:CGFloat = 1.4 12 | var smooth:CGFloat = 6.4 13 | 14 | class SpectralView: UIView { 15 | var fft: TempiFFT! 16 | 17 | func setAmbient() { 18 | for i in 0 ..< FFT_SIZE { 19 | ambientData[i] = smoothData[i] 20 | } 21 | } 22 | 23 | func setScale(_ ratio:Float) { 24 | scale = CGFloat(1 + ratio * 3) 25 | } 26 | 27 | func setSmooth(_ ratio:Float) { 28 | smooth = CGFloat(1 + ratio * 20) 29 | } 30 | 31 | func updateSmoothedData() { 32 | for i in 0 ..< FFT_SIZE { 33 | let magnitude = fft.magnitudeAtBand(i) 34 | let magnitudeDB = TempiFFT.toDB(magnitude) 35 | let y = CGFloat(magnitudeDB) * scale 36 | 37 | smoothData[i] = (smoothData[i] * (smooth - 1.0) + y) / smooth 38 | } 39 | } 40 | 41 | override func draw(_ rect: CGRect) { 42 | if fft == nil { return } 43 | 44 | updateSmoothedData() 45 | history.addHistory() 46 | 47 | let viewWidth = bounds.width 48 | let viewHeight = bounds.height 49 | 50 | let context = UIGraphicsGetCurrentContext()! 51 | context.setStrokeColor(UIColor.white.cgColor) 52 | 53 | 54 | let xScale = viewWidth / CGFloat(FFT_SIZE) 55 | var x:CGFloat = 0.0 56 | 57 | for i in 0 ..< FFT_SIZE { 58 | let yc:CGFloat = bounds.height / 2 59 | let y = yc - (smoothData[i] - ambientData[i]) * scale 60 | 61 | context.beginPath() 62 | context.move(to: CGPoint(x:x, y:yc)) 63 | context.addLine(to: CGPoint(x:x, y:y)) 64 | context.strokePath() 65 | 66 | x += xScale 67 | } 68 | 69 | context.setStrokeColor(UIColor.yellow.cgColor) 70 | x = CGFloat(baseIndex) * xScale 71 | context.beginPath() 72 | context.move(to: CGPoint(x:x, y:0)) 73 | context.addLine(to: CGPoint(x:x, y:viewHeight)) 74 | context.strokePath() 75 | x = CGFloat(baseIndex + HISTORY_SIZE) * xScale 76 | context.beginPath() 77 | context.move(to: CGPoint(x:x, y:0)) 78 | context.addLine(to: CGPoint(x:x, y:viewHeight)) 79 | context.strokePath() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /TempiFFT/TempiAudioInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TempiAudioInput.swift 3 | // TempiBeatDetection 4 | // 5 | // Created by John Scalo on 1/7/16. 6 | // Copyright © 2016 John Scalo. See accompanying License.txt for terms. 7 | 8 | import AVFoundation 9 | 10 | typealias TempiAudioInputCallback = ( 11 | _ timeStamp: Double, 12 | _ numberOfFrames: Int, 13 | _ samples: [Float] 14 | ) -> Void 15 | 16 | /// TempiAudioInput sets up an audio input session and notifies when new buffer data is available. 17 | class TempiAudioInput: NSObject { 18 | 19 | private(set) var audioUnit: AudioUnit! 20 | let audioSession : AVAudioSession = AVAudioSession.sharedInstance() 21 | var sampleRate: Float 22 | var numberOfChannels: Int 23 | 24 | /// When true, performs DC offset rejection on the incoming buffer before invoking the audioInputCallback. 25 | var shouldPerformDCOffsetRejection: Bool = false 26 | 27 | private let outputBus: UInt32 = 0 28 | private let inputBus: UInt32 = 1 29 | private var audioInputCallback: TempiAudioInputCallback! 30 | 31 | /// Instantiate a TempiAudioInput. 32 | /// - Parameter audioInputCallback: Invoked when audio data is available. 33 | /// - Parameter sampleRate: The sample rate to set up the audio session with. 34 | /// - Parameter numberOfChannels: The number of channels to set up the audio session with. 35 | 36 | init(audioInputCallback callback: @escaping TempiAudioInputCallback, sampleRate: Float = 44100.0, numberOfChannels: Int = 2) { 37 | 38 | self.sampleRate = sampleRate 39 | self.numberOfChannels = numberOfChannels 40 | audioInputCallback = callback 41 | } 42 | 43 | /// Start recording. Prompts for access to microphone if necessary. 44 | func startRecording() { 45 | do { 46 | 47 | if self.audioUnit == nil { 48 | setupAudioSession() 49 | setupAudioUnit() 50 | } 51 | 52 | try self.audioSession.setActive(true) 53 | var osErr: OSStatus = 0 54 | 55 | osErr = AudioUnitInitialize(self.audioUnit) 56 | assert(osErr == noErr, "*** AudioUnitInitialize err \(osErr)") 57 | osErr = AudioOutputUnitStart(self.audioUnit) 58 | assert(osErr == noErr, "*** AudioOutputUnitStart err \(osErr)") 59 | } catch { 60 | print("*** startRecording error: \(error)") 61 | } 62 | } 63 | 64 | /// Stop recording. 65 | func stopRecording() { 66 | do { 67 | var osErr: OSStatus = 0 68 | 69 | osErr = AudioUnitUninitialize(self.audioUnit) 70 | assert(osErr == noErr, "*** AudioUnitUninitialize err \(osErr)") 71 | 72 | try self.audioSession.setActive(false) 73 | } catch { 74 | print("*** error: \(error)") 75 | } 76 | } 77 | 78 | private let recordingCallback: AURenderCallback = { (inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData) -> OSStatus in 79 | 80 | let audioInput = unsafeBitCast(inRefCon, to: TempiAudioInput.self) 81 | var osErr: OSStatus = 0 82 | 83 | // We've asked CoreAudio to allocate buffers for us, so just set mData to nil and it will be populated on AudioUnitRender(). 84 | var bufferList = AudioBufferList( 85 | mNumberBuffers: 1, 86 | mBuffers: AudioBuffer( 87 | mNumberChannels: UInt32(audioInput.numberOfChannels), 88 | mDataByteSize: 4, 89 | mData: nil)) 90 | 91 | osErr = AudioUnitRender(audioInput.audioUnit, 92 | ioActionFlags, 93 | inTimeStamp, 94 | inBusNumber, 95 | inNumberFrames, 96 | &bufferList) 97 | assert(osErr == noErr, "*** AudioUnitRender err \(osErr)") 98 | 99 | // Move samples from mData into our native [Float] format. 100 | var monoSamples = [Float]() 101 | let ptr = bufferList.mBuffers.mData?.assumingMemoryBound(to: Float.self) 102 | monoSamples.append(contentsOf: UnsafeBufferPointer(start: ptr, count: Int(inNumberFrames))) 103 | 104 | if audioInput.shouldPerformDCOffsetRejection { 105 | DCRejectionFilterProcessInPlace(&monoSamples, count: Int(inNumberFrames)) 106 | } 107 | 108 | // Not compatible with Obj-C... 109 | audioInput.audioInputCallback(inTimeStamp.pointee.mSampleTime / Double(audioInput.sampleRate), 110 | Int(inNumberFrames), 111 | monoSamples) 112 | 113 | return 0 114 | } 115 | 116 | private func setupAudioSession() { 117 | 118 | if !audioSession.availableCategories.contains(AVAudioSession.Category.record) { 119 | print("can't record! bailing.") 120 | return 121 | } 122 | 123 | do { 124 | try audioSession.setCategory(AVAudioSession.Category.record) 125 | 126 | // "Appropriate for applications that wish to minimize the effect of system-supplied signal processing for input and/or output audio signals." 127 | // NB: This turns off the high-pass filter that CoreAudio normally applies. 128 | try audioSession.setMode(AVAudioSession.Mode.measurement) 129 | 130 | try audioSession.setPreferredSampleRate(Double(sampleRate)) 131 | 132 | // This will have an impact on CPU usage. .01 gives 512 samples per frame on iPhone. (Probably .01 * 44100 rounded up.) 133 | // NB: This is considered a 'hint' and more often than not is just ignored. 134 | try audioSession.setPreferredIOBufferDuration(0.01) 135 | 136 | audioSession.requestRecordPermission { (granted) -> Void in 137 | if !granted { 138 | print("*** record permission denied") 139 | } 140 | } 141 | } catch { 142 | print("*** audioSession error: \(error)") 143 | } 144 | } 145 | 146 | private func setupAudioUnit() { 147 | 148 | var componentDesc:AudioComponentDescription = AudioComponentDescription( 149 | componentType: OSType(kAudioUnitType_Output), 150 | componentSubType: OSType(kAudioUnitSubType_RemoteIO), // Always this for iOS. 151 | componentManufacturer: OSType(kAudioUnitManufacturer_Apple), 152 | componentFlags: 0, 153 | componentFlagsMask: 0) 154 | 155 | var osErr: OSStatus = 0 156 | 157 | // Get an audio component matching our description. 158 | let component: AudioComponent! = AudioComponentFindNext(nil, &componentDesc) 159 | assert(component != nil, "Couldn't find a default component") 160 | 161 | // Create an instance of the AudioUnit 162 | var tempAudioUnit: AudioUnit? 163 | osErr = AudioComponentInstanceNew(component, &tempAudioUnit) 164 | self.audioUnit = tempAudioUnit 165 | 166 | assert(osErr == noErr, "*** AudioComponentInstanceNew err \(osErr)") 167 | 168 | // Enable I/O for input. 169 | var one:UInt32 = 1 170 | 171 | osErr = AudioUnitSetProperty(audioUnit, 172 | kAudioOutputUnitProperty_EnableIO, 173 | kAudioUnitScope_Input, 174 | inputBus, 175 | &one, 176 | UInt32(MemoryLayout.size)) 177 | assert(osErr == noErr, "*** AudioUnitSetProperty err \(osErr)") 178 | 179 | osErr = AudioUnitSetProperty(audioUnit, 180 | kAudioOutputUnitProperty_EnableIO, 181 | kAudioUnitScope_Output, 182 | outputBus, 183 | &one, 184 | UInt32(MemoryLayout.size)) 185 | assert(osErr == noErr, "*** AudioUnitSetProperty err \(osErr)") 186 | 187 | // Set format to 32 bit, floating point, linear PCM 188 | var streamFormatDesc:AudioStreamBasicDescription = AudioStreamBasicDescription( 189 | mSampleRate: Double(sampleRate), 190 | mFormatID: kAudioFormatLinearPCM, 191 | mFormatFlags: kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved, // floating point data - docs say this is fastest 192 | mBytesPerPacket: 4, 193 | mFramesPerPacket: 1, 194 | mBytesPerFrame: 4, 195 | mChannelsPerFrame: UInt32(self.numberOfChannels), 196 | mBitsPerChannel: 4 * 8, 197 | mReserved: 0 198 | ) 199 | 200 | // Set format for input and output busses 201 | osErr = AudioUnitSetProperty(audioUnit, 202 | kAudioUnitProperty_StreamFormat, 203 | kAudioUnitScope_Input, outputBus, 204 | &streamFormatDesc, 205 | UInt32(MemoryLayout.size)) 206 | assert(osErr == noErr, "*** AudioUnitSetProperty err \(osErr)") 207 | 208 | osErr = AudioUnitSetProperty(audioUnit, 209 | kAudioUnitProperty_StreamFormat, 210 | kAudioUnitScope_Output, 211 | inputBus, 212 | &streamFormatDesc, 213 | UInt32(MemoryLayout.size)) 214 | assert(osErr == noErr, "*** AudioUnitSetProperty err \(osErr)") 215 | 216 | // Set up our callback. 217 | var inputCallbackStruct = AURenderCallbackStruct(inputProc: recordingCallback, inputProcRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())) 218 | osErr = AudioUnitSetProperty(audioUnit, 219 | AudioUnitPropertyID(kAudioOutputUnitProperty_SetInputCallback), 220 | AudioUnitScope(kAudioUnitScope_Global), 221 | inputBus, 222 | &inputCallbackStruct, 223 | UInt32(MemoryLayout.size)) 224 | assert(osErr == noErr, "*** AudioUnitSetProperty err \(osErr)") 225 | 226 | // Ask CoreAudio to allocate buffers for us on render. (This is true by default but just to be explicit about it...) 227 | osErr = AudioUnitSetProperty(audioUnit, 228 | AudioUnitPropertyID(kAudioUnitProperty_ShouldAllocateBuffer), 229 | AudioUnitScope(kAudioUnitScope_Output), 230 | inputBus, 231 | &one, 232 | UInt32(MemoryLayout.size)) 233 | assert(osErr == noErr, "*** AudioUnitSetProperty err \(osErr)") 234 | } 235 | } 236 | 237 | private func DCRejectionFilterProcessInPlace(_ audioData: inout [Float], count: Int) { 238 | 239 | let defaultPoleDist: Float = 0.975 240 | var mX1: Float = 0 241 | var mY1: Float = 0 242 | 243 | for i in 0..? = nil) -> UIImage { 14 | 15 | let height: CGFloat = 800.0 16 | let width: CGFloat = CGFloat(count) 17 | let bgndRect: CGRect = CGRect(x: 0, y: 0, width: width, height: height) 18 | 19 | UIGraphicsBeginImageContextWithOptions(bgndRect.size, false, 2.0) 20 | let context: CGContext = UIGraphicsGetCurrentContext()! 21 | 22 | context.scaleBy(x: 1, y: -1) 23 | context.translateBy(x: 0, y: -height) 24 | context.saveGState() 25 | 26 | // Fill background with black 27 | context.setFillColor(CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.0, 0.0, 0.0, 1.0])!) 28 | context.fill(bgndRect) 29 | 30 | // Draw waveform columns 31 | let colWidth: CGFloat = 1.0 32 | var x: CGFloat = 0.0 33 | 34 | for i in 0..? = nil) { 63 | for i in 0..includes those frequencies but isn't necessarily bounded by them. 217 | func calculateLogarithmicBands(minFrequency: Float, maxFrequency: Float, bandsPerOctave: Int) { 218 | assert(hasPerformedFFT, "*** Perform the FFT first.") 219 | 220 | // The max can't be any higher than the nyquist 221 | let actualMaxFrequency = min(self.nyquistFrequency, maxFrequency) 222 | 223 | // The min can't be 0 otherwise we'll divide octaves infinitely 224 | let actualMinFrequency = max(1, minFrequency) 225 | 226 | // Define the octave frequencies we'll be working with. Note that in order to always include minFrequency, we'll have to set the lower boundary to the octave just below that frequency. 227 | var octaveBoundaryFreqs: [Float] = [Float]() 228 | var curFreq = actualMaxFrequency 229 | octaveBoundaryFreqs.append(curFreq) 230 | repeat { 231 | curFreq /= 2 232 | octaveBoundaryFreqs.append(curFreq) 233 | } while curFreq > actualMinFrequency 234 | 235 | octaveBoundaryFreqs = octaveBoundaryFreqs.reversed() 236 | 237 | self.bandMagnitudes = [Float]() 238 | self.bandFrequencies = [Float]() 239 | 240 | // Break up the spectrum by octave 241 | for i in 0.. Int { 274 | return Int(Float(self.magnitudes.count) * freq / self.nyquistFrequency) 275 | } 276 | 277 | // On arrays of 1024 elements, this is ~35x faster than an iterational algorithm. Thanks Accelerate.framework! 278 | @inline(__always) private func fastAverage(_ array:[Float], _ startIdx: Int, _ stopIdx: Int) -> Float { 279 | var mean: Float = 0 280 | let ptr = UnsafePointer(array) 281 | vDSP_meanv(ptr + startIdx, 1, &mean, UInt(stopIdx - startIdx)) 282 | 283 | return mean 284 | } 285 | 286 | @inline(__always) private func magsInFreqRange(_ lowFreq: Float, _ highFreq: Float) -> [Float] { 287 | let lowIndex = Int(lowFreq / self.bandwidth) 288 | var highIndex = Int(highFreq / self.bandwidth) 289 | 290 | if (lowIndex == highIndex) { 291 | // Occurs when both params are so small that they both fall into the first index 292 | highIndex += 1 293 | } 294 | 295 | return Array(self.magnitudes[lowIndex.. Float { 299 | return (self.bandwidth * Float(startIndex) + self.bandwidth * Float(endIndex)) / 2 300 | } 301 | 302 | /// Get the magnitude for the specified frequency band. 303 | /// - Parameter inBand: The frequency band you want a magnitude for. 304 | func magnitudeAtBand(_ inBand: Int) -> Float { 305 | assert(hasPerformedFFT, "*** Perform the FFT first.") 306 | assert(bandMagnitudes != nil, "*** Call calculateLinearBands() or calculateLogarithmicBands() first") 307 | 308 | return bandMagnitudes[inBand] 309 | } 310 | 311 | /// Get the magnitude of the requested frequency in the spectrum. 312 | /// - Parameter inFrequency: The requested frequency. Must be less than the Nyquist frequency (```sampleRate/2```). 313 | /// - Returns: A magnitude. 314 | func magnitudeAtFrequency(_ inFrequency: Float) -> Float { 315 | assert(hasPerformedFFT, "*** Perform the FFT first.") 316 | let index = Int(floorf(inFrequency / self.bandwidth )) 317 | return self.magnitudes[index] 318 | } 319 | 320 | /// Get the middle frequency of the Nth band. 321 | /// - Parameter inBand: An index where 0 <= inBand < size / 2. 322 | /// - Returns: The middle frequency of the provided band. 323 | func frequencyAtBand(_ inBand: Int) -> Float { 324 | assert(hasPerformedFFT, "*** Perform the FFT first.") 325 | assert(bandMagnitudes != nil, "*** Call calculateLinearBands() or calculateLogarithmicBands() first") 326 | return self.bandFrequencies[inBand] 327 | } 328 | 329 | /// Calculate the average magnitude of the frequency band bounded by lowFreq and highFreq, inclusive 330 | func averageMagnitude(lowFreq: Float, highFreq: Float) -> Float { 331 | 332 | var curFreq = lowFreq 333 | var total: Float = 0 334 | var count: Int = 0 335 | while curFreq <= highFreq { 336 | total += magnitudeAtFrequency(curFreq) 337 | curFreq += self.bandwidth 338 | count += 1 339 | } 340 | 341 | return total / Float(count) 342 | } 343 | 344 | /// Sum magnitudes across bands bounded by lowFreq and highFreq, inclusive 345 | func sumMagnitudes(lowFreq: Float, highFreq: Float, useDB: Bool) -> Float { 346 | 347 | var curFreq = lowFreq 348 | var total: Float = 0 349 | while curFreq <= highFreq { 350 | var mag = magnitudeAtFrequency(curFreq) 351 | if (useDB) { 352 | mag = max(0, TempiFFT.toDB(mag)) 353 | } 354 | total += mag 355 | curFreq += self.bandwidth 356 | } 357 | 358 | return total 359 | } 360 | 361 | /// A convenience function that converts a linear magnitude (like those stored in ```magnitudes```) to db (which is log 10). 362 | class func toDB(_ inMagnitude: Float) -> Float { 363 | // ceil to 128db in order to avoid log10'ing 0 364 | let magnitude = max(inMagnitude, 0.000000000001) 365 | return 10 * log10f(magnitude) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /TempiFFT/TempiUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TempiUtilities.swift 3 | // TempiFFT 4 | // 5 | // Created by John Scalo on 1/8/16. 6 | // Copyright © 2016 John Scalo. See accompanying License.txt for terms. 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | func tempi_dispatch_main(closure:@escaping ()->()) { 12 | DispatchQueue.main.async { 13 | closure() 14 | } 15 | } 16 | 17 | func tempi_dispatch_delay(delay:Double, closure:@escaping ()->()) { 18 | 19 | DispatchQueue.main.asyncAfter(deadline: .now() + delay) { 20 | closure() 21 | } 22 | } 23 | 24 | func tempi_round_device_scale(d: CGFloat) -> CGFloat 25 | { 26 | let scale: CGFloat = UIScreen.main.scale 27 | return round(d * scale) / scale 28 | } 29 | -------------------------------------------------------------------------------- /TempiFFT/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MetalKit 3 | import AVFoundation 4 | 5 | let history = History() 6 | var isTouched:Bool = false 7 | 8 | class ViewController: UIViewController { 9 | var renderer: Renderer! 10 | var audioInput: TempiAudioInput! 11 | 12 | @IBOutlet var metalView: MTKView! 13 | @IBOutlet var spectralView: SpectralView! 14 | @IBOutlet var scaleSider: UISlider! 15 | @IBOutlet var smoothSider: UISlider! 16 | @IBOutlet var baseIndexSlider: UISlider! 17 | @IBOutlet var hScaleSlider: UISlider! 18 | @IBOutlet var hColorSlider: UISlider! 19 | 20 | @IBAction func setAmbientPressed(_ sender: UIButton) { spectralView.setAmbient() } 21 | @IBAction func scaleSliderChanged(_ sender: UISlider) { spectralView.setScale(sender.value) } 22 | @IBAction func smoothSliderChanged(_ sender: UISlider) { spectralView.setSmooth(sender.value) } 23 | 24 | @IBAction func baseIndexSliderChanged(_ sender: UISlider) { 25 | baseIndex = Int(baseIndexSlider.value * Float(FFT_SIZE)) 26 | if baseIndex >= FFT_SIZE - HISTORY_SIZE { baseIndex = FFT_SIZE - HISTORY_SIZE - 1 } 27 | } 28 | 29 | @IBAction func hScaleSliderChanged(_ sender: UISlider) { history.setScale(sender.value) } 30 | @IBAction func hColorSliderChanged(_ sender: UISlider) { history.setColor(sender.value) } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | guard let metalView = metalView else { fatalError("View of Gameview controller is not an MTKView") } 36 | guard let defaultDevice = MTLCreateSystemDefaultDevice() else { fatalError("Metal is not supported") } 37 | 38 | metalView.device = defaultDevice 39 | metalView.backgroundColor = UIColor.clear 40 | 41 | guard let newRenderer = Renderer(metalKitView: metalView) else { fatalError("Renderer cannot be initialized") } 42 | renderer = newRenderer 43 | renderer.mtkView(metalView, drawableSizeWillChange: metalView.drawableSize) 44 | metalView.delegate = renderer 45 | 46 | let audioInputCallback: TempiAudioInputCallback = { (timeStamp, numberOfFrames, samples) -> Void in 47 | self.gotSomeAudio(timeStamp: Double(timeStamp), numberOfFrames: Int(numberOfFrames), samples: samples) 48 | } 49 | 50 | history.initialize() 51 | spectralView.setScale(0.5) 52 | spectralView.setSmooth(0.5) 53 | 54 | audioInput = TempiAudioInput(audioInputCallback: audioInputCallback, sampleRate: 44100, numberOfChannels: 1) 55 | audioInput.startRecording() 56 | 57 | Timer.scheduledTimer(withTimeInterval:1, repeats:false) { timer in self.timerKick() } 58 | } 59 | 60 | @objc func timerKick() { spectralView.setAmbient() } 61 | 62 | //MARK: - 63 | 64 | func gotSomeAudio(timeStamp: Double, numberOfFrames: Int, samples: [Float]) { 65 | let fft = TempiFFT(withSize: numberOfFrames, sampleRate: 44100.0) 66 | fft.windowType = TempiFFTWindowType.hanning 67 | fft.fftForward(samples) 68 | 69 | fft.calculateLinearBands(minFrequency: 0, maxFrequency: fft.nyquistFrequency, numberOfBands:FFT_SIZE) 70 | 71 | tempi_dispatch_main { () -> () in 72 | self.spectralView.fft = fft 73 | self.spectralView.setNeedsDisplay() 74 | } 75 | 76 | history.update() 77 | } 78 | 79 | //MARK: - 80 | 81 | var startTranslation = simd_float3() 82 | var startRotation = CGPoint() 83 | 84 | func parseTranslation(_ pt:CGPoint) { 85 | let scale:Float = 0.01 86 | translation.x += Float(pt.x - CGFloat(startTranslation.x)) * scale 87 | translation.y -= Float(pt.y - CGFloat(startTranslation.y)) * scale 88 | } 89 | 90 | func parseRotation(_ pt:CGPoint) { 91 | let sz = metalView.bounds.size 92 | let xc = CGFloat(sz.width/2) 93 | let yc = CGFloat(sz.height/2) 94 | var t = pt 95 | let scale:CGFloat = 0.05 96 | t.x *= scale 97 | t.y *= scale 98 | 99 | arcBall.mouseDown(CGPoint(x:xc, y:yc)) 100 | arcBall.mouseMove(CGPoint(x:xc - t.x, y:yc - t.y)) 101 | } 102 | 103 | var numberPanTouches:Int = 0 104 | 105 | @IBAction func panGesture(_ sender: UIPanGestureRecognizer) { 106 | let pt = sender.translation(in: self.view) 107 | if sender.state == .began { 108 | startTranslation = translation 109 | startRotation = pt 110 | isTouched = true 111 | } 112 | else { 113 | let count = sender.numberOfTouches 114 | if count == 0 { numberPanTouches = 0 } else if count > numberPanTouches { numberPanTouches = count } 115 | 116 | switch sender.numberOfTouches { 117 | case 1 : if numberPanTouches < 2 { parseRotation(pt) } // prevent rotation after releasing translation 118 | case 2 : parseTranslation(pt) 119 | default : break 120 | } 121 | } 122 | 123 | if sender.state == .ended { 124 | isTouched = false 125 | } 126 | } 127 | 128 | var startZoom:Float = 0 129 | 130 | @IBAction func pinchGesture(_ sender: UIPinchGestureRecognizer) { 131 | let min:Float = 1 132 | let max:Float = 300 133 | if sender.state == .began { startZoom = translation.z } 134 | translation.z = fClamp(startZoom / Float(sender.scale),min,max) 135 | 136 | if sender.state == .began { 137 | isTouched = true 138 | } 139 | if sender.state == .ended { 140 | isTouched = false 141 | } 142 | } 143 | 144 | override var prefersStatusBarHidden: Bool { return true } 145 | } 146 | 147 | func fClamp(_ v:Float, _ min:Float, _ max:Float) -> Float { 148 | if v < min { return min } 149 | if v > max { return max } 150 | return v 151 | } 152 | 153 | 154 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kosalos/FFT/4acf5c603dc0a7410212a6ac1c92c710ca17d837/screenshot.png --------------------------------------------------------------------------------