[
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non:\n  workflow_dispatch:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    name: Build ${{ matrix.scheme }} (Xcode ${{ matrix.xcode_version }})\n    # NOTE: macos-latest is NOT equivalent to macos-12 as of September 2022.\n    # Source: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources\n    runs-on: macos-12\n    strategy:\n      # Setting 'fail-fast' to 'true' implies the build will fail the first time it encounters an error.\n      fail-fast: false\n      matrix:\n        xcode_version:\n          - '13.2' # swift 5.5\n          - '13.4' # swift 5.6\n    steps:\n      - uses: actions/checkout@v3\n      - name: build \n        run: swift build\n\n  # Send notification to Discord on failure.\n  send_notification:\n    name: Send Notification\n    uses: AudioKit/ci/.github/workflows/send_notification.yml@main\n    needs: [build]\n    if: ${{ failure() && github.ref == 'refs/heads/main' }}\n    secrets: inherit\n"
  },
  {
    "path": ".gitignore",
    "content": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n## User settings\nxcuserdata/\n\n## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)\n*.xcscmblueprint\n*.xccheckout\n\n## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)\nbuild/\nDerivedData/\n*.moved-aside\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\n\n## Obj-C/Swift specific\n*.hmap\n\n## App packaging\n*.ipa\n*.dSYM.zip\n*.dSYM\n\n## Playgrounds\ntimeline.xctimeline\nplayground.xcworkspace\n\n# Swift Package Manager\n#\n# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.\n# Packages/\n# Package.pins\n# Package.resolved\n# *.xcodeproj\n#\n# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata\n# hence it is not needed unless you have added a package configuration file to your project\n# .swiftpm\n\n.build/\n\n# CocoaPods\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# Pods/\n#\n# Add this line if you want to avoid checking in source code from the Xcode workspace\n# *.xcworkspace\n\n# Carthage\n#\n# Add this line if you want to avoid checking in source code from Carthage dependencies.\n# Carthage/Checkouts\n\nCarthage/Build/\n\n# Accio dependency management\nDependencies/\n.accio/\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo.\n# Instead, use fastlane to re-generate the screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/#source-control\n\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots/**/*.png\nfastlane/test_output\n\n# Code Injection\n#\n# After new code Injection tools there's a generated folder /iOSInjectionProject\n# https://github.com/johnno1962/injectionforxcode\n\niOSInjectionProject/\n.DS_Store "
  },
  {
    "path": ".spi.yml",
    "content": "version: 1\nbuilder:\n  configs:\n    - documentation_targets: [Waveform]"
  },
  {
    "path": ".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": ".swiftpm/xcode/xcshareddata/xcschemes/Waveform.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1400\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"Waveform_Waveform\"\n               BuildableName = \"Waveform_Waveform\"\n               BlueprintName = \"Waveform_Waveform\"\n               ReferencedContainer = \"container:\">\n            </BuildableReference>\n         </BuildActionEntry>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"Waveform_WaveformTests\"\n               BuildableName = \"Waveform_WaveformTests\"\n               BlueprintName = \"Waveform_WaveformTests\"\n               ReferencedContainer = \"container:\">\n            </BuildableReference>\n         </BuildActionEntry>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"Waveform\"\n               BuildableName = \"Waveform\"\n               BlueprintName = \"Waveform\"\n               ReferencedContainer = \"container:\">\n            </BuildableReference>\n         </BuildActionEntry>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"NO\"\n            buildForArchiving = \"NO\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"WaveformTests\"\n               BuildableName = \"WaveformTests\"\n               BlueprintName = \"WaveformTests\"\n               ReferencedContainer = \"container:\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"NO\">\n      <EnvironmentVariables>\n         <EnvironmentVariable\n            key = \"METAL_DEVICE_WRAPPER_TYPE\"\n            value = \"1\"\n            isEnabled = \"YES\">\n         </EnvironmentVariable>\n      </EnvironmentVariables>\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"WaveformTests\"\n               BuildableName = \"WaveformTests\"\n               BlueprintName = \"WaveformTests\"\n               ReferencedContainer = \"container:\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"Waveform_Waveform\"\n            BuildableName = \"Waveform_Waveform\"\n            BlueprintName = \"Waveform_Waveform\"\n            ReferencedContainer = \"container:\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Demo/WaveformDemo/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Demo/WaveformDemo/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Demo/WaveformDemo/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Demo/WaveformDemo/ContentView.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport AVFoundation\nimport SwiftUI\nimport Waveform\n\nclass WaveformDemoModel: ObservableObject {\n    var samples: SampleBuffer\n\n    init(file: AVAudioFile) {\n        let stereo = file.floatChannelData()!\n        samples = SampleBuffer(samples: stereo[0])\n    }\n}\n\nfunc getFile() -> AVAudioFile {\n    let url = Bundle.main.url(forResource: \"Piano\", withExtension: \"mp3\")!\n    return try! AVAudioFile(forReading: url)\n}\n\nfunc clamp(_ x: Double, _ inf: Double, _ sup: Double) -> Double {\n    max(min(x, sup), inf)\n}\n\nstruct ContentView: View {\n    @StateObject var model = WaveformDemoModel(file: getFile())\n\n    @State var start = 0.0\n    @State var length = 1.0\n\n    let formatter = NumberFormatter()\n    var body: some View {\n        VStack {\n            ZStack(alignment: .leading) {\n                Waveform(samples: model.samples).foregroundColor(.cyan)\n                    .padding(.vertical, 5)\n                MinimapView(start: $start, length: $length)\n            }\n            .frame(height: 100)\n            .padding()\n            Waveform(samples: model.samples,\n                     start: Int(start * Double(model.samples.count - 1)),\n                     length: Int(length * Double(model.samples.count)))\n            .foregroundColor(.blue)\n        }\n        .padding()\n    }\n}\n"
  },
  {
    "path": "Demo/WaveformDemo/MinimapView.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport AVFoundation\nimport SwiftUI\nimport Waveform\n\nstruct MinimapView: View {\n    @Binding var start: Double\n    @Binding var length: Double\n\n    @GestureState var initialStart: Double?\n    @GestureState var initialLength: Double?\n\n    let indicatorSize = 10.0\n\n    var body: some View {\n        GeometryReader { gp in\n            RoundedRectangle(cornerRadius: indicatorSize)\n                .frame(width: length * gp.size.width)\n                .offset(x: start * gp.size.width)\n                .opacity(0.3)\n                .gesture(DragGesture()\n                    .updating($initialStart) { _, state, _ in\n                        if state == nil {\n                            state = start\n                        }\n                    }\n                    .onChanged { drag in\n                        if let initialStart = initialStart {\n                            start = clamp(initialStart + drag.translation.width / gp.size.width, 0, 1 - length)\n                        }\n                    }\n                )\n\n            RoundedRectangle(cornerRadius: indicatorSize)\n                .foregroundColor(.white)\n                .frame(width: indicatorSize).opacity(0.3)\n                .offset(x: (start + length) * gp.size.width)\n                .padding(indicatorSize)\n                .gesture(DragGesture()\n                    .updating($initialLength) { _, state, _ in\n                        if state == nil {\n                            state = length\n                        }\n                    }\n                    .onChanged { drag in\n                        if let initialLength = initialLength {\n                            length = clamp(initialLength + drag.translation.width / gp.size.width, 0, 1 - start)\n                        }\n                    }\n                )\n        }\n    }\n}\n"
  },
  {
    "path": "Demo/WaveformDemo/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Demo/WaveformDemo/WaveformDemo.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>com.apple.security.app-sandbox</key>\n    <true/>\n    <key>com.apple.security.files.user-selected.read-only</key>\n    <true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Demo/WaveformDemo/WaveformDemoApp.swift",
    "content": "import SwiftUI\n\n@main\nstruct WaveformDemoApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n    }\n}\n"
  },
  {
    "path": "Demo/WaveformDemo.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 56;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t29D479382A13DBED0033DF90 /* Piano.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 29D479372A13DBEC0033DF90 /* Piano.mp3 */; };\n\t\tF130C47828EBF1390017B2AF /* MinimapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F130C47728EBF1390017B2AF /* MinimapView.swift */; };\n\t\tF1A202B428DEA41E007CD919 /* WaveformDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A202B328DEA41E007CD919 /* WaveformDemoApp.swift */; };\n\t\tF1A202B628DEA41E007CD919 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A202B528DEA41E007CD919 /* ContentView.swift */; };\n\t\tF1A202B828DEA420007CD919 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F1A202B728DEA420007CD919 /* Assets.xcassets */; };\n\t\tF1A202BC28DEA420007CD919 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F1A202BB28DEA420007CD919 /* Preview Assets.xcassets */; };\n\t\tF1A202C628DEA96C007CD919 /* Waveform in Frameworks */ = {isa = PBXBuildFile; productRef = F1A202C528DEA96C007CD919 /* Waveform */; };\n\t\tF1FB409728E6660E00AFA732 /* beat.aiff in Resources */ = {isa = PBXBuildFile; fileRef = F1FB409628E6660E00AFA732 /* beat.aiff */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXFileReference section */\n\t\t29D479372A13DBEC0033DF90 /* Piano.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = Piano.mp3; path = ../../Tests/WaveformTests/Piano.mp3; sourceTree = \"<group>\"; };\n\t\tF130C47728EBF1390017B2AF /* MinimapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimapView.swift; sourceTree = \"<group>\"; };\n\t\tF1A202B028DEA41E007CD919 /* WaveformDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WaveformDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tF1A202B328DEA41E007CD919 /* WaveformDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformDemoApp.swift; sourceTree = \"<group>\"; };\n\t\tF1A202B528DEA41E007CD919 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = \"<group>\"; };\n\t\tF1A202B728DEA420007CD919 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tF1A202B928DEA420007CD919 /* WaveformDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WaveformDemo.entitlements; sourceTree = \"<group>\"; };\n\t\tF1A202BB28DEA420007CD919 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = \"Preview Assets.xcassets\"; sourceTree = \"<group>\"; };\n\t\tF1A202C328DEA7BC007CD919 /* Waveform */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Waveform; path = ..; sourceTree = \"<group>\"; };\n\t\tF1FB409628E6660E00AFA732 /* beat.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = beat.aiff; path = ../../Tests/WaveformTests/beat.aiff; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tF1A202AD28DEA41E007CD919 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tF1A202C628DEA96C007CD919 /* Waveform in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tF1A202A728DEA41E007CD919 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tF1A202C228DEA7BC007CD919 /* Packages */,\n\t\t\t\tF1A202B228DEA41E007CD919 /* WaveformDemo */,\n\t\t\t\tF1A202B128DEA41E007CD919 /* Products */,\n\t\t\t\tF1A202C428DEA96C007CD919 /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tF1A202B128DEA41E007CD919 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tF1A202B028DEA41E007CD919 /* WaveformDemo.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tF1A202B228DEA41E007CD919 /* WaveformDemo */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t29D479372A13DBEC0033DF90 /* Piano.mp3 */,\n\t\t\t\tF1A202B328DEA41E007CD919 /* WaveformDemoApp.swift */,\n\t\t\t\tF1FB409628E6660E00AFA732 /* beat.aiff */,\n\t\t\t\tF1A202B528DEA41E007CD919 /* ContentView.swift */,\n\t\t\t\tF130C47728EBF1390017B2AF /* MinimapView.swift */,\n\t\t\t\tF1A202B728DEA420007CD919 /* Assets.xcassets */,\n\t\t\t\tF1A202B928DEA420007CD919 /* WaveformDemo.entitlements */,\n\t\t\t\tF1A202BA28DEA420007CD919 /* Preview Content */,\n\t\t\t);\n\t\t\tpath = WaveformDemo;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tF1A202BA28DEA420007CD919 /* Preview Content */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tF1A202BB28DEA420007CD919 /* Preview Assets.xcassets */,\n\t\t\t);\n\t\t\tpath = \"Preview Content\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tF1A202C228DEA7BC007CD919 /* Packages */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tF1A202C328DEA7BC007CD919 /* Waveform */,\n\t\t\t);\n\t\t\tname = Packages;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tF1A202C428DEA96C007CD919 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\tF1A202AF28DEA41E007CD919 /* WaveformDemo */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = F1A202BF28DEA420007CD919 /* Build configuration list for PBXNativeTarget \"WaveformDemo\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tF1A202AC28DEA41E007CD919 /* Sources */,\n\t\t\t\tF1A202AD28DEA41E007CD919 /* Frameworks */,\n\t\t\t\tF1A202AE28DEA41E007CD919 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = WaveformDemo;\n\t\t\tpackageProductDependencies = (\n\t\t\t\tF1A202C528DEA96C007CD919 /* Waveform */,\n\t\t\t);\n\t\t\tproductName = WaveformDemo;\n\t\t\tproductReference = F1A202B028DEA41E007CD919 /* WaveformDemo.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tF1A202A828DEA41E007CD919 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1400;\n\t\t\t\tLastUpgradeCheck = 1400;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tF1A202AF28DEA41E007CD919 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.0;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = F1A202AB28DEA41E007CD919 /* Build configuration list for PBXProject \"WaveformDemo\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = F1A202A728DEA41E007CD919;\n\t\t\tproductRefGroup = F1A202B128DEA41E007CD919 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tF1A202AF28DEA41E007CD919 /* WaveformDemo */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tF1A202AE28DEA41E007CD919 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tF1FB409728E6660E00AFA732 /* beat.aiff in Resources */,\n\t\t\t\tF1A202BC28DEA420007CD919 /* Preview Assets.xcassets in Resources */,\n\t\t\t\t29D479382A13DBED0033DF90 /* Piano.mp3 in Resources */,\n\t\t\t\tF1A202B828DEA420007CD919 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tF1A202AC28DEA41E007CD919 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tF1A202B628DEA41E007CD919 /* ContentView.swift in Sources */,\n\t\t\t\tF1A202B428DEA41E007CD919 /* WaveformDemoApp.swift in Sources */,\n\t\t\t\tF130C47828EBF1390017B2AF /* MinimapView.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin XCBuildConfiguration section */\n\t\tF1A202BD28DEA420007CD919 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tF1A202BE28DEA420007CD919 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tF1A202C028DEA420007CD919 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = WaveformDemo/WaveformDemo.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"WaveformDemo/Preview Content\\\"\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]\" = UIStatusBarStyleDefault;\n\t\t\t\t\"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]\" = UIStatusBarStyleDefault;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"@executable_path/Frameworks\";\n\t\t\t\t\"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]\" = \"@executable_path/../Frameworks\";\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 12.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = io.audiokit.WaveformDemo;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = auto;\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator macosx\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tF1A202C128DEA420007CD919 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = WaveformDemo/WaveformDemo.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"WaveformDemo/Preview Content\\\"\";\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]\" = YES;\n\t\t\t\t\"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]\" = UIStatusBarStyleDefault;\n\t\t\t\t\"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]\" = UIStatusBarStyleDefault;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 15.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"@executable_path/Frameworks\";\n\t\t\t\t\"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]\" = \"@executable_path/../Frameworks\";\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 12.0;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = io.audiokit.WaveformDemo;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = auto;\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator macosx\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tF1A202AB28DEA41E007CD919 /* Build configuration list for PBXProject \"WaveformDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tF1A202BD28DEA420007CD919 /* Debug */,\n\t\t\t\tF1A202BE28DEA420007CD919 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tF1A202BF28DEA420007CD919 /* Build configuration list for PBXNativeTarget \"WaveformDemo\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tF1A202C028DEA420007CD919 /* Debug */,\n\t\t\t\tF1A202C128DEA420007CD919 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\tF1A202C528DEA96C007CD919 /* Waveform */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = Waveform;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = F1A202A828DEA41E007CD919 /* Project object */;\n}\n"
  },
  {
    "path": "Demo/WaveformDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "Demo/WaveformDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 AudioKit\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Package.swift",
    "content": "// swift-tools-version:5.3\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"Waveform\",\n    platforms: [.macOS(.v11), .iOS(.v14)],\n    products: [.library(name: \"Waveform\", targets: [\"Waveform\"])],\n    targets: [\n        .target(name: \"Waveform\", resources: [.process(\"Waveform.docc\")]),\n        .testTarget(name: \"WaveformTests\", dependencies: [\"Waveform\"], resources: [.copy(\"beat.aiff\")]),\n    ]\n)\n"
  },
  {
    "path": "README.md",
    "content": "# Waveform\n\nGPU accelerated SwiftUI waveform view\n\n![waveform-demo](Sources/Waveform/Waveform.docc/Resources/demo.png)\n\n## Documentation\n\nThe API Reference can be found on [the AudioKit Website](https://www.audiokit.io/Waveform). \nPackage contains a demo project and a playground to help you get started quickly.\n"
  },
  {
    "path": "Sources/Waveform/AVAudio+FloatData.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport Accelerate\nimport AVFoundation\n\nextension AVAudioPCMBuffer {\n    /// Returns audio data as an `Array` of `Float` Arrays.\n    ///\n    /// If stereo:\n    /// - `floatChannelData?[0]` will contain an Array of left channel samples as `Float`\n    /// - `floatChannelData?[1]` will contains an Array of right channel samples as `Float`\n    func toFloatChannelData() -> [[Float]]? {\n        // Do we have PCM channel data?\n        guard let pcmFloatChannelData = floatChannelData else {\n            return nil\n        }\n\n        let channelCount = Int(format.channelCount)\n        let frameLength = Int(self.frameLength)\n        let stride = self.stride\n\n        // Preallocate our Array so we're not constantly thrashing while resizing as we append.\n        let zeroes: [Float] = Array(repeating: 0, count: frameLength)\n        var result = Array(repeating: zeroes, count: channelCount)\n\n        // Loop across our channels...\n        for channel in 0 ..< channelCount {\n            // Make sure we go through all of the frames...\n            for sampleIndex in 0 ..< frameLength {\n                result[channel][sampleIndex] = pcmFloatChannelData[channel][sampleIndex * stride]\n            }\n        }\n\n        return result\n    }\n}\n\nextension AVAudioFile {\n    /// converts to a 32 bit PCM buffer\n    func toAVAudioPCMBuffer() -> AVAudioPCMBuffer? {\n        guard let buffer = AVAudioPCMBuffer(pcmFormat: processingFormat,\n                                            frameCapacity: AVAudioFrameCount(length)) else { return nil }\n\n        do {\n            framePosition = 0\n            try read(into: buffer)\n            print(\"Created buffer with format\")\n\n        } catch let error as NSError {\n            print(\"Cannot read into buffer \" + error.localizedDescription)\n        }\n\n        return buffer\n    }\n\n    /// converts to Swift friendly Float array\n    public func floatChannelData() -> [[Float]]? {\n        guard let pcmBuffer = toAVAudioPCMBuffer(),\n              let data = pcmBuffer.toFloatChannelData() else { return nil }\n        return data\n    }\n}\n"
  },
  {
    "path": "Sources/Waveform/Helpers.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport Foundation\nimport Metal\nimport SwiftUI\n\n#if canImport(UIKit)\nimport UIKit\n#elseif canImport(AppKit)\nimport AppKit\n#endif\n\n/// Returns the minimums of chunks of binSize.\nfunc binMin(samples: [Float], binSize: Int) -> [Float] {\n    var out: [Float] = .init(repeating: 0.0, count: samples.count / binSize)\n\n    // Note: we have to use a dumb while loop to avoid swift's Range and have\n    //       decent perf in debug.\n    var bin = 0\n    while bin < out.count {\n\n        // Note: we could do the following but it's too slow in debug\n        // out[bin] = samples[(bin * binSize) ..< ((bin + 1) * binSize)].min()!\n\n        var v = Float.greatestFiniteMagnitude\n        let start: Int = bin * binSize\n        let end: Int = (bin + 1) * binSize\n        var i = start\n        while i < end {\n            v = min(samples[i], v)\n            i += 1\n        }\n        out[bin] = v\n        bin += 1\n    }\n    return out\n}\n\n/// Returns the maximums of chunks of binSize.\nfunc binMax(samples: [Float], binSize: Int) -> [Float] {\n    var out: [Float] = .init(repeating: 0.0, count: samples.count / binSize)\n\n    // Note: we have to use a dumb while loop to avoid swift's Range and have\n    //       decent perf in debug.\n    var bin = 0\n    while bin < out.count {\n\n        // Note: we could do the following but it's too slow in debug\n        // out[bin] = samples[(bin * binSize) ..< ((bin + 1) * binSize)].max()!\n\n        var v = -Float.greatestFiniteMagnitude\n        let start: Int = bin * binSize\n        let end: Int = (bin + 1) * binSize\n        var i = start\n        while i < end {\n            v = max(samples[i], v)\n            i += 1\n        }\n        out[bin] = v\n        bin += 1\n    }\n    return out\n}\n\nextension MTLDevice {\n    func makeBuffer(_ values: [Float]) -> MTLBuffer? {\n        makeBuffer(bytes: values, length: MemoryLayout<Float>.size * values.count)\n    }\n}\n\npublic extension MTLRenderCommandEncoder {\n    func setFragmentBytes<T>(_ value: T, index: Int) {\n        var copy = value\n        setFragmentBytes(&copy, length: MemoryLayout<T>.size, index: index)\n    }\n\n    func setFragmentBytes<T>(_ value: T, index: Int32) {\n        var copy = value\n        setFragmentBytes(&copy, length: MemoryLayout<T>.size, index: Int(index))\n    }\n}\n\nextension Color {\n    var components: SIMD4<Float> {\n\n        var r: CGFloat = 0\n        var g: CGFloat = 0\n        var b: CGFloat = 0\n        var a: CGFloat = 0\n\n        #if canImport(UIKit)\n        UIColor(self).getRed(&r, green: &g, blue: &b, alpha: &a)\n        #elseif canImport(AppKit)\n        NSColor(self).usingColorSpace(.deviceRGB)!.getRed(&r, green: &g, blue: &b, alpha: &a)\n        #endif\n\n        return .init(Float(r), Float(g), Float(b), Float(a))\n    }\n}\n"
  },
  {
    "path": "Sources/Waveform/Renderer.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport Foundation\nimport Metal\nimport MetalKit\nimport SwiftUI\n\nlet MaxBuffers = 3\n\n/// Parameters defining the look and feel of the waveform\nstruct Constants {\n\n    /// Foreground color\n    var color = SIMD4<Float>(1,1,1,1)\n\n    /// Initialize the Constants structure\n    /// - Parameter color: Foreground color\n    init(color: Color = .white) {\n        self.color = color.components\n    }\n}\n\nclass Renderer: NSObject, MTKViewDelegate {\n    var device: MTLDevice!\n    var queue: MTLCommandQueue!\n    var pipeline: MTLRenderPipelineState!\n    var source = \"\"\n    public var constants = Constants()\n\n    private let inflightSemaphore = DispatchSemaphore(value: MaxBuffers)\n\n    var minBuffers: [MTLBuffer] = []\n    var maxBuffers: [MTLBuffer] = []\n\n    var samples = SampleBuffer(samples: [0])\n    var start = 0\n    var length = 0\n\n    init(device: MTLDevice) {\n        self.device = device\n        queue = device.makeCommandQueue()\n\n        let library = try! device.makeDefaultLibrary(bundle: Bundle.module)\n\n        let rpd = MTLRenderPipelineDescriptor()\n        rpd.vertexFunction = library.makeFunction(name: \"waveform_vert\")\n        rpd.fragmentFunction = library.makeFunction(name: \"waveform_frag\")\n\n        let colorAttachment = rpd.colorAttachments[0]!\n        colorAttachment.pixelFormat = .bgra8Unorm\n        colorAttachment.isBlendingEnabled = true\n        colorAttachment.sourceRGBBlendFactor = .sourceAlpha\n        colorAttachment.sourceAlphaBlendFactor = .sourceAlpha\n        colorAttachment.destinationRGBBlendFactor = .oneMinusSourceAlpha\n        colorAttachment.destinationAlphaBlendFactor = .oneMinusSourceAlpha\n\n        pipeline = try! device.makeRenderPipelineState(descriptor: rpd)\n\n        minBuffers = [device.makeBuffer([0])!]\n        maxBuffers = [device.makeBuffer([0])!]\n\n        super.init()\n    }\n\n    func mtkView(_: MTKView, drawableSizeWillChange _: CGSize) {}\n\n    func selectBuffers(width: CGFloat) -> (MTLBuffer?, MTLBuffer?) {\n        var level = 0\n        for (minBuffer, maxBuffer) in zip(minBuffers, maxBuffers) {\n            if CGFloat(minBuffer.length / MemoryLayout<Float>.size) < width {\n                return (minBuffer, maxBuffer)\n            }\n            level += 1\n        }\n\n        // Use optional binding to safely access last element of each array\n        if let minBufferLast = minBuffers.last, let maxBufferLast = maxBuffers.last {\n            return (minBufferLast, maxBufferLast)\n        } else {\n            // If either array is empty, return nil\n            return (nil, nil)\n        }\n    }\n    \n    func encode(to commandBuffer: MTLCommandBuffer,\n                pass: MTLRenderPassDescriptor,\n                width: CGFloat)\n    {\n        pass.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0)\n\n        let highestResolutionCount = Float(samples.samples.count)\n        let startFactor = Float(start) / highestResolutionCount\n        let lengthFactor = Float(length) / highestResolutionCount\n\n        let (minBufferOpt, maxBufferOpt) = selectBuffers(width: width / CGFloat(lengthFactor))\n        guard let minBuffer = minBufferOpt, let maxBuffer = maxBufferOpt else {\n            //early return to gracefully fail.\n            return\n        }\n\n        let enc = commandBuffer.makeRenderCommandEncoder(descriptor: pass)!\n        enc.setRenderPipelineState(pipeline)\n\n        let bufferLength = Float(minBuffer.length / MemoryLayout<Float>.size)\n        let bufferStart = Int(bufferLength * startFactor)\n        var bufferCount = Int(bufferLength * lengthFactor)\n\n        enc.setFragmentBuffer(minBuffer, offset: bufferStart * MemoryLayout<Float>.size, index: 0)\n        enc.setFragmentBuffer(maxBuffer, offset: bufferStart * MemoryLayout<Float>.size, index: 1)\n        assert(minBuffer.length == maxBuffer.length)\n        enc.setFragmentBytes(&bufferCount, length: MemoryLayout<Int32>.size, index: 2)\n        let c = [constants]\n        enc.setFragmentBytes(c, length: MemoryLayout<Constants>.size, index: 3)\n        enc.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)\n        enc.endEncoding()\n    }\n\n    func draw(in view: MTKView) {\n        let size = view.frame.size\n        let w = Float(size.width)\n        let h = Float(size.height)\n        // let scale = Float(view.contentScaleFactor)\n\n        if w == 0 || h == 0 {\n            return\n        }\n\n        // use semaphore to encode 3 frames ahead\n        _ = inflightSemaphore.wait(timeout: DispatchTime.distantFuture)\n\n        let commandBuffer = queue.makeCommandBuffer()!\n\n        let semaphore = inflightSemaphore\n        commandBuffer.addCompletedHandler { _ in\n            semaphore.signal()\n        }\n\n        if let renderPassDescriptor = view.currentRenderPassDescriptor, let currentDrawable = view.currentDrawable {\n            encode(to: commandBuffer, pass: renderPassDescriptor, width: size.width)\n\n            commandBuffer.present(currentDrawable)\n        }\n        commandBuffer.commit()\n    }\n\n    func set(samples: SampleBuffer, start: Int, length: Int) {\n        self.start = start\n        self.length = length\n        if samples === self.samples {\n            return\n        }\n        self.samples = samples\n\n        let buffers = makeBuffers(device: device, samples: samples)\n        self.minBuffers = buffers.0\n        self.maxBuffers = buffers.1\n    }\n}\n\nfunc makeBuffers(device: MTLDevice, samples: SampleBuffer) -> ([MTLBuffer], [MTLBuffer]) {\n    var minSamples = samples.samples\n    var maxSamples = samples.samples\n\n    var s = samples.samples.count\n    var minBuffers: [MTLBuffer] = []\n    var maxBuffers: [MTLBuffer] = []\n    while s > 2 {\n        minBuffers.append(device.makeBuffer(minSamples)!)\n        maxBuffers.append(device.makeBuffer(maxSamples)!)\n\n        minSamples = binMin(samples: minSamples, binSize: 2)\n        maxSamples = binMax(samples: maxSamples, binSize: 2)\n        s /= 2\n    }\n    return (minBuffers, maxBuffers)\n}\n"
  },
  {
    "path": "Sources/Waveform/SampleBuffer.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport Foundation\n\n/// Immutable data for samples so we can quickly compare to see if we should recompute.\npublic final class SampleBuffer: Sendable {\n    let samples: [Float]\n\n    /// Initialize the buffer with samples\n    public init(samples: [Float]) {\n        self.samples = samples\n    }\n\n    /// Number of samples\n    public var count: Int {\n        samples.count\n    }\n}\n"
  },
  {
    "path": "Sources/Waveform/Waveform.docc/Waveform.md",
    "content": "# ``Waveform``\n\nGPU accelerated SwiftUI waveform view\n\n## Overview\n\nCode is hosted on Github: [](https://github.com/AudioKit/Waveform/)\n\n![Demo Screenshot](demo)\n\n## Topics\n\n### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->\n\n- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->\n"
  },
  {
    "path": "Sources/Waveform/Waveform.metal",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\n#include <metal_stdlib>\nusing namespace metal;\n\nkernel void waveform_bin(device const float* in,\n                         device float* out_min,\n                         device float* out_max,\n                         constant uint& count,\n                         uint tid [[ thread_position_in_grid]]) {\n    \n    if (tid >= count) {\n        return;\n    }\n    \n    auto a = in[tid*2];\n    auto b = in[tid*2+1];\n    out_min[tid] = min(a, b);\n    out_max[tid] = max(a, b);\n}\n\nstruct FragIn {\n    float4 position [[ position ]];\n    float2 uv; // (0, 1) x (-1, 1)\n};\n\n\nconstant float2 pos[4] = { {-1,-1}, {1,-1}, {-1,1}, {1,1 } };\nconstant float2 uv[4] = { {0, -1}, {1, -1}, {0,1}, {1,1 } };\n\nvertex FragIn waveform_vert(uint id [[ vertex_id ]]) {\n    FragIn out;\n    out.position = float4(pos[id], 0, 1);\n    out.uv = uv[id];\n    return out;\n}\n\nstruct Constants {\n    float4 color;\n};\n\nfloat sample_waveform(device const float* min_waveform,\n                      device const float* max_waveform,\n                      uint count,\n                      float2 uv) {\n\n    int x = clamp(int(count * uv.x), 0, int(count));\n\n    auto min_value = min_waveform[x];\n    auto max_value = max_waveform[x];\n    \n    auto falloff = 4 * length(fwidth(uv));\n    \n    // Feather the top and bottom.\n    auto s0 = smoothstep(min_value - falloff, min_value, uv.y);\n    auto s1 = 1.0 - smoothstep(max_value, max_value + falloff, uv.y);\n\n    return s0 * s1;\n}\n\nfragment half4 waveform_frag(FragIn in   [[ stage_in ]],\n                             device const float* min_waveform,\n                             device const float* max_waveform,\n                             constant uint& count,\n                             constant Constants& constants) {\n    \n    half s = sample_waveform(min_waveform, max_waveform, count, in.uv);\n\n    half4 color = half4(constants.color);\n    color.a *= s;\n    return color;\n\n}\n"
  },
  {
    "path": "Sources/Waveform/Waveform.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport AVFoundation\nimport MetalKit\nimport SwiftUI\n\n#if os(macOS)\n/// Waveform SwiftUI View\npublic struct Waveform: NSViewRepresentable {\n    var samples: SampleBuffer\n    var start: Int\n    var length: Int\n    var constants: Constants = Constants()\n\n\n    /// Initialize the waveform\n    /// - Parameters:\n    ///   - samples: All samples able to be displayed\n    ///   - start: Which sample on which to start displaying samples\n    ///   - length: The width of the entire waveform in samples\n    ///   - constants: Look and feel parameters for the waveform\n    public init(samples: SampleBuffer, start: Int = 0, length: Int = 0) {\n        self.samples = samples\n        self.start = start\n        if length > 0 {\n            self.length = min(length, samples.samples.count - start)\n        } else {\n            self.length = samples.samples.count - start\n        }\n    }\n\n    /// Class required by NSViewRepresentable\n    public class Coordinator {\n        var renderer: Renderer\n\n        init(constants: Constants) {\n            renderer = Renderer(device: MTLCreateSystemDefaultDevice()!)\n            renderer.constants = constants\n        }\n    }\n\n    /// Required by NSViewRepresentable\n    public func makeCoordinator() -> Coordinator {\n        return Coordinator(constants: constants)\n    }\n\n    /// Required by NSViewRepresentable\n    public func makeNSView(context: Context) -> some NSView {\n        let metalView = MTKView(frame: CGRect(x: 0, y: 0, width: 1024, height: 768),\n                                device: MTLCreateSystemDefaultDevice()!)\n        metalView.enableSetNeedsDisplay = true\n        metalView.isPaused = true\n        metalView.delegate = context.coordinator.renderer\n        metalView.layer?.isOpaque = false\n        return metalView\n    }\n\n    /// Required by NSViewRepresentable\n    public func updateNSView(_ nsView: NSViewType, context: Context) {\n        let renderer = context.coordinator.renderer\n        renderer.constants = constants\n        renderer.set(samples: samples, start: start, length: length)\n        nsView.setNeedsDisplay(nsView.bounds)\n    }\n}\n#else\n/// Waveform SwiftUI View\npublic struct Waveform: UIViewRepresentable {\n    var samples: SampleBuffer\n    var start: Int\n    var length: Int\n    var constants: Constants = Constants()\n\n    /// Initialize the waveform\n    /// - Parameters:\n    ///   - samples: All samples able to be displayed\n    ///   - start: Which sample on which to start displaying samples\n    ///   - length: The width of the entire waveform in samples\n    ///   - constants: Look and feel parameters for the waveform\n    public init(samples: SampleBuffer, start: Int = 0, length: Int = 0) {\n        self.samples = samples\n        self.start = start\n        if length > 0 {\n            self.length = length\n        } else {\n            self.length = samples.samples.count\n        }\n    }\n\n    /// Required by UIViewRepresentable\n    public class Coordinator {\n        var renderer: Renderer\n\n        init(constants: Constants) {\n            renderer = Renderer(device: MTLCreateSystemDefaultDevice()!)\n        }\n    }\n\n    /// Required by UIViewRepresentable\n    public func makeCoordinator() -> Coordinator {\n        return Coordinator(constants: constants)\n    }\n\n    /// Required by UIViewRepresentable\n    public func makeUIView(context: Context) -> some UIView {\n        let metalView = MTKView(frame: CGRect(x: 0, y: 0, width: 1024, height: 768),\n                                device: MTLCreateSystemDefaultDevice()!)\n        metalView.enableSetNeedsDisplay = true\n        metalView.isPaused = true\n        metalView.delegate = context.coordinator.renderer\n        metalView.layer.isOpaque = false\n        return metalView\n    }\n\n    /// Required by UIViewRepresentable\n    public func updateUIView(_ uiView: UIViewType, context: Context) {\n        let renderer = context.coordinator.renderer\n        renderer.constants = constants\n        renderer.set(samples: samples, start: start, length: length)\n        uiView.setNeedsDisplay()\n    }\n}\n#endif\n\nextension Waveform {\n    /// Modifer to change the foreground color of the wheel\n    /// - Parameter foregroundColor: foreground color\n    public func foregroundColor(_ foregroundColor: Color) -> Waveform {\n        var copy = self\n        copy.constants = Constants(color: foregroundColor)\n        return copy\n    }\n}\n"
  },
  {
    "path": "Tests/WaveformTests/MTLTexture+Image.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport CoreGraphics\nimport Foundation\nimport Metal\n\nfunc createImage(data: UnsafeMutablePointer<UInt8>, w: Int, h: Int) -> CGImage {\n    let dataSize = 4 * w * h\n\n    let provider = CGDataProvider(dataInfo: nil, data: data, size: dataSize, releaseData: { _, _, _ in })!\n\n    let colorSpace = CGColorSpaceCreateDeviceRGB()\n\n    let image = CGImage(width: w,\n                        height: h,\n                        bitsPerComponent: 8,\n                        bitsPerPixel: 32,\n                        bytesPerRow: w * 4,\n                        space: colorSpace,\n                        bitmapInfo: .init(rawValue: CGImageAlphaInfo.noneSkipLast.rawValue),\n                        provider: provider,\n                        decode: nil,\n                        shouldInterpolate: true,\n                        intent: .defaultIntent)!\n\n    return image\n}\n\nextension MTLTexture {\n    var cgImage: CGImage {\n        let dataSize = width * height * 4\n        let ptr = UnsafeMutablePointer<UInt8>.allocate(capacity: dataSize)\n\n        switch pixelFormat {\n        case .bgra8Unorm:\n            getBytes(ptr, bytesPerRow: width * 4, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)\n            for i in 0 ..< (width * height) {\n                swap(&ptr[4 * i], &ptr[4 * i + 2])\n            }\n        default:\n            fatalError()\n        }\n\n        return createImage(data: ptr, w: width, h: height)\n    }\n\n    var isBlack: Bool {\n        let dataSize = width * height * 4\n        let ptr = UnsafeMutablePointer<UInt8>.allocate(capacity: dataSize)\n        defer {\n            ptr.deallocate()\n        }\n\n        switch pixelFormat {\n        case .bgra8Unorm:\n            getBytes(ptr, bytesPerRow: width * 4, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0)\n        default:\n            fatalError()\n        }\n\n        for x in 0 ..< dataSize {\n            if ptr[x] != 0 {\n                return false\n            }\n        }\n\n        return true\n    }\n}\n"
  },
  {
    "path": "Tests/WaveformTests/WaveformTests.swift",
    "content": "// Copyright AudioKit. All Rights Reserved. Revision History at http://github.com/AudioKit/Waveform/\n\nimport AVFoundation\nimport CoreFoundation\nimport CoreGraphics\nimport Metal\nimport MetalKit\n@testable import Waveform\nimport XCTest\n\nfinal class WaveformTests: XCTestCase {\n    let device = MTLCreateSystemDefaultDevice()\n    var queue: MTLCommandQueue!\n    var texture: MTLTexture!\n    var pass: MTLRenderPassDescriptor!\n\n    override func setUp() {\n        guard let device = device else { return }\n        queue = device.makeCommandQueue()!\n\n        let w = 512\n        let h = 512\n\n        let textureDesc = MTLTextureDescriptor()\n        textureDesc.pixelFormat = .bgra8Unorm\n        textureDesc.width = w\n        textureDesc.height = h\n\n        textureDesc.usage = [.renderTarget, .shaderRead, .shaderWrite]\n\n        texture = device.makeTexture(descriptor: textureDesc)\n        XCTAssertNotNil(texture)\n\n        pass = MTLRenderPassDescriptor()\n        pass.colorAttachments[0].texture = texture\n        pass.colorAttachments[0].storeAction = .store\n        pass.colorAttachments[0].loadAction = .clear\n    }\n\n    func writeCGImage(image: CGImage, url: CFURL) {\n        #if os(macOS)\n        let dest = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, nil)!\n        CGImageDestinationAddImage(dest, image, nil)\n        assert(CGImageDestinationFinalize(dest))\n        #endif\n    }\n\n    func showTexture(texture: MTLTexture, name: String) {\n        let tmpURL = FileManager.default.temporaryDirectory.appendingPathComponent(name)\n        print(\"saving to \\(tmpURL)\")\n        writeCGImage(image: texture.cgImage, url: tmpURL as CFURL)\n    }\n\n    func render(samples: [Float]) async {\n        guard let device = device else { return }\n        let renderer = Renderer(device: device)\n\n        await renderer.set(samples: SampleBuffer(samples: samples), start: 0, length: samples.count)\n\n        let commandBuffer = queue.makeCommandBuffer()!\n        renderer.encode(to: commandBuffer, pass: pass, width: 512)\n\n        #if os(macOS)\n        let blit = commandBuffer.makeBlitCommandEncoder()!\n        blit.synchronize(resource: texture)\n        blit.endEncoding()\n        #endif\n\n        commandBuffer.commit()\n        commandBuffer.waitUntilCompleted()\n\n        XCTAssertFalse(texture.isBlack)\n\n        showTexture(texture: texture, name: \"Waveform.png\")\n    }\n\n    func testRenderBeat() async throws {\n        guard let url = Bundle.module.url(forResource: \"beat\", withExtension: \"aiff\") else {\n            XCTFail()\n            return\n        }\n\n        let file = try! AVAudioFile(forReading: url)\n\n        let stereo = file.floatChannelData()!\n\n        await render(samples: stereo[0])\n    }\n}\n"
  },
  {
    "path": "Waveform.playground/Contents.swift",
    "content": "import PlaygroundSupport\nimport SwiftUI\nimport Waveform\n\nstruct WaveformDemoView: View {\n    var samples: [Float] {\n        var s: [Float] = []\n        let size = 1000\n        for i in 0 ..< size {\n            let sine = sin(Float(i * 2) * .pi / Float(size)) * 0.9\n            s.append(sine + 0.1 * Float.random(in: -1 ... 1))\n        }\n        return s\n    }\n\n    @State var start = 0.0\n    @State var length = 1.0\n\n    let formatter = NumberFormatter()\n    var body: some View {\n        Waveform(samples: SampleBuffer(samples: samples),\n                 start: 0,\n                 length: 1000)\n            .padding()\n    }\n}\n\nPlaygroundPage.current.setLiveView(WaveformDemoView().frame(width: 1100, height: 500))\nPlaygroundPage.current.needsIndefiniteExecution = true\n"
  },
  {
    "path": "Waveform.playground/contents.xcplayground",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<playground version='5.0' target-platform='macos' display-mode='rendered' buildActiveScheme='true' executeOnSourceChanges='true' importAppTypes='true'>\n    <timeline fileName='timeline.xctimeline'/>\n</playground>"
  }
]