-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7fc132f
Showing
13 changed files
with
684 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# Xcode | ||
# | ||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore | ||
|
||
## User settings | ||
xcuserdata/ | ||
|
||
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) | ||
*.xcscmblueprint | ||
*.xccheckout | ||
|
||
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) | ||
build/ | ||
DerivedData/ | ||
*.moved-aside | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
|
||
## Obj-C/Swift specific | ||
*.hmap | ||
|
||
## App packaging | ||
*.ipa | ||
*.dSYM.zip | ||
*.dSYM | ||
|
||
## Playgrounds | ||
timeline.xctimeline | ||
playground.xcworkspace | ||
|
||
# Swift Package Manager | ||
# | ||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. | ||
# Packages/ | ||
# Package.pins | ||
# Package.resolved | ||
# *.xcodeproj | ||
# | ||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata | ||
# hence it is not needed unless you have added a package configuration file to your project | ||
# .swiftpm | ||
|
||
.build/ | ||
|
||
# CocoaPods | ||
# | ||
# We recommend against adding the Pods directory to your .gitignore. However | ||
# you should judge for yourself, the pros and cons are mentioned at: | ||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control | ||
# | ||
# Pods/ | ||
# | ||
# Add this line if you want to avoid checking in source code from the Xcode workspace | ||
# *.xcworkspace | ||
|
||
# Carthage | ||
# | ||
# Add this line if you want to avoid checking in source code from Carthage dependencies. | ||
# Carthage/Checkouts | ||
|
||
Carthage/Build/ | ||
|
||
# Accio dependency management | ||
Dependencies/ | ||
.accio/ | ||
|
||
# fastlane | ||
# | ||
# It is recommended to not store the screenshots in the git repo. | ||
# Instead, use fastlane to re-generate the screenshots whenever they are needed. | ||
# For more information about the recommended setup visit: | ||
# https://docs.fastlane.tools/best-practices/source-control/#source-control | ||
|
||
fastlane/report.xml | ||
fastlane/Preview.html | ||
fastlane/screenshots/**/*.png | ||
fastlane/test_output | ||
|
||
# Code Injection | ||
# | ||
# After new code Injection tools there's a generated folder /iOSInjectionProject | ||
# https://github.com/johnno1962/injectionforxcode | ||
|
||
iOSInjectionProject/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// swift-tools-version:5.1 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "WavReader", | ||
products: [ | ||
// Products define the executables and libraries produced by a package, and make them visible to other packages. | ||
.library( | ||
name: "WavReader", | ||
targets: ["WavReader"]), | ||
], | ||
dependencies: [ | ||
// Dependencies declare other packages that this package depends on. | ||
// .package(url: /* package url */, from: "1.0.0"), | ||
], | ||
targets: [ | ||
// Targets are the basic building blocks of a package. A target can define a module or a test suite. | ||
// Targets can depend on other targets in this package, and on products in packages which this package depends on. | ||
.target( | ||
name: "WavReader", | ||
dependencies: ["CWavHeader"]), | ||
.target(name: "CWavHeader"), | ||
.testTarget( | ||
name: "WavReaderTests", | ||
dependencies: ["WavReader"]), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# WavReader (SwiftPM) | ||
|
||
This example needs a wav file sitting next to the executable file to work. | ||
|
||
Open the project in Xcode, build the project, then right click on `WavReader` under Products and choose `Show In Finder`. Add a file called `example.wav` in that directory and then you can run from within Xcode. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module CWavHeader { | ||
header "wavHeader.h" | ||
export * | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// | ||
// wavHeader.h | ||
// WavReader | ||
// | ||
// Created by Geordie Jay on 03.03.20. | ||
// Copyright © 2020 flowkey. All rights reserved. | ||
// | ||
|
||
#ifndef wavHeader_h | ||
#define wavHeader_h | ||
|
||
struct FmtChunk { | ||
unsigned char fmtChunkString[4]; | ||
unsigned int lengthOfFmtSection; | ||
unsigned short formatType; // 1 == PCM is the only supported format | ||
unsigned short channelCount; | ||
unsigned int sampleRate; | ||
unsigned int byteRate; | ||
unsigned short blockAlignment; | ||
unsigned short bitsPerSample; | ||
}; | ||
|
||
struct DataChunk { | ||
unsigned char dataChunkString[4]; | ||
unsigned int dataSize; | ||
}; | ||
|
||
#endif /* wavHeader_h */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// | ||
// WavReader.swift | ||
// WavReader | ||
// | ||
// Created by Geordie Jay on 03.03.20. | ||
// Copyright © 2020 flowkey. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import CWavHeader | ||
|
||
public struct WavReader: Sequence { | ||
public let sampleRate: Int | ||
public let numFrames: Int | ||
public let numChannels: Int | ||
public let bitsPerSample: Int | ||
public let bytesPerSample: Int | ||
|
||
public let bytesPerFrame: Int | ||
public let numSamples: Int | ||
public let blockSize: Int // in frames | ||
|
||
fileprivate let bytes: Data | ||
fileprivate let offsetToWavData: Int | ||
|
||
public init(bytes: Data, blockSize: Int = 1024) throws { | ||
self.blockSize = blockSize | ||
|
||
guard | ||
let indexOfFormatSection = bytes.firstOccurence(of: "fmt", maxSearchBytes: 512), | ||
let fmtChunk = bytes.withUnsafeBytes({ buffer -> FmtChunk? in | ||
buffer.baseAddress?.advanced(by: indexOfFormatSection).bindMemory(to: FmtChunk.self, capacity: 1).pointee | ||
}) | ||
else { | ||
preconditionFailure("Invalid wav header: could not find fmt section") | ||
} | ||
|
||
print(fmtChunk) | ||
|
||
sampleRate = Int(fmtChunk.sampleRate) | ||
numChannels = Int(fmtChunk.channelCount) | ||
bitsPerSample = Int(fmtChunk.bitsPerSample) | ||
bytesPerSample = bitsPerSample / 8 | ||
bytesPerFrame = numChannels * bytesPerSample | ||
|
||
guard | ||
let indexOfDataSection = bytes.firstOccurence(of: "data", maxSearchBytes: 512), | ||
let dataChunk = bytes.withUnsafeBytes({ buffer -> DataChunk? in | ||
buffer.baseAddress?.advanced(by: indexOfDataSection).bindMemory(to: DataChunk.self, capacity: 1).pointee | ||
}) | ||
else { | ||
preconditionFailure("Invalid wav header: could not find data section") | ||
} | ||
|
||
numFrames = Int(dataChunk.dataSize) / bytesPerFrame | ||
numSamples = numFrames * numChannels | ||
offsetToWavData = indexOfDataSection + MemoryLayout<DataChunk>.size | ||
|
||
self.bytes = bytes | ||
precondition(bitsPerSample == 16, "The algo currently only supports 16bit") | ||
} | ||
|
||
public init(filename: String = "example.wav", blockSize: Int = 1024) throws { | ||
let wavData = try Data(contentsOf: URL(fileURLWithPath: filename), options: [.mappedIfSafe]) | ||
try self.init(bytes: wavData, blockSize: blockSize) | ||
} | ||
|
||
public func makeIterator() -> WavReader.Iterator { | ||
return Iterator(self) | ||
} | ||
} | ||
|
||
extension WavReader { | ||
public class Iterator: IteratorProtocol { | ||
private var floatBuffer: [Float] | ||
private var wavFile: WavReader | ||
|
||
private var frameIndex = 0 | ||
|
||
fileprivate init(_ wavFile: WavReader) { | ||
self.wavFile = wavFile | ||
floatBuffer = [Float](repeating: 0.0, count: wavFile.blockSize * wavFile.numChannels) | ||
} | ||
|
||
public func next() -> [Float]? { | ||
let offsetToWavData = wavFile.offsetToWavData | ||
|
||
// it's cheaper to do multiplication than division, so divide | ||
// here once and multiply each sample by this number: | ||
let floatFactor = Float(1.0) / Float(Int16.max) | ||
|
||
var framesRead = 0 | ||
|
||
return wavFile.bytes.withUnsafeBytes { bufferPointer in | ||
let int16buffer = UnsafeBufferPointer<Int16>( | ||
start: bufferPointer.baseAddress!.advanced(by: offsetToWavData).assumingMemoryBound(to: Int16.self), | ||
count: wavFile.numSamples | ||
) | ||
|
||
while frameIndex < wavFile.numFrames { | ||
for channel in 0 ..< wavFile.numChannels { | ||
let dataArrayIndex = frameIndex + channel | ||
let sample = int16buffer[dataArrayIndex] | ||
floatBuffer[framesRead] = Float(sample) * floatFactor | ||
} | ||
|
||
framesRead += 1 | ||
self.frameIndex += 1 | ||
|
||
if framesRead == wavFile.blockSize { | ||
return floatBuffer | ||
} | ||
} | ||
|
||
if framesRead > 0 { | ||
// We're at EOF | ||
|
||
// Empty the rest of the float buffer, otherwise it | ||
// will contain the previous frame's data: | ||
(framesRead ..< wavFile.blockSize).forEach { i in | ||
floatBuffer[i] = 0.0 | ||
} | ||
|
||
return floatBuffer | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
} | ||
} | ||
|
||
fileprivate extension Data { | ||
/// Search for the first occurence of the given string in our Data's buffer. | ||
/// Optionally you can provide `maxSearchBytes` which stops the search after reaching the given byte count | ||
/// This allows us to stop searching the entire Data buffer if the String was not found quickly. | ||
func firstOccurence(of string: String, maxSearchBytes: Int? = nil) -> Int? { | ||
let stringLength = string.utf8CString.count - 1 | ||
if stringLength == 0 { return nil } | ||
|
||
let maxSearchBytes = (maxSearchBytes ?? self.count) - stringLength | ||
if maxSearchBytes <= 0 { return nil } | ||
|
||
return self.withUnsafeBytes { rawBufferPointer -> Int? in | ||
let buffer = rawBufferPointer.bindMemory(to: CChar.self) | ||
|
||
return string.withCString { stringBuffer in | ||
buffer.indices.first { index in | ||
if index >= maxSearchBytes { return false } | ||
|
||
for stringIndex in 0 ..< stringLength { | ||
if buffer[index + stringIndex] != stringBuffer[stringIndex] { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import XCTest | ||
|
||
import WavFileTests | ||
|
||
var tests = [XCTestCaseEntry]() | ||
tests += WavFileTests.allTests() | ||
XCTMain(tests) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import XCTest | ||
@testable import WavFile | ||
|
||
final class WavFileTests: XCTestCase { | ||
func testExample() { | ||
// This is an example of a functional test case. | ||
// Use XCTAssert and related functions to verify your tests produce the correct | ||
// results. | ||
XCTAssertEqual(true, true) | ||
} | ||
|
||
static var allTests = [ | ||
("testExample", testExample), | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import XCTest | ||
|
||
#if !canImport(ObjectiveC) | ||
public func allTests() -> [XCTestCaseEntry] { | ||
return [ | ||
testCase(WavFileTests.allTests), | ||
] | ||
} | ||
#endif |
Oops, something went wrong.