Last active
May 14, 2020 13:22
-
-
Save brianmichel/0affe74620d80cd6487a9b713ebed2f9 to your computer and use it in GitHub Desktop.
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
extension String { | |
subscript (bounds: CountableClosedRange<Int>) -> String { | |
let start = index(startIndex, offsetBy: bounds.lowerBound) | |
let end = index(startIndex, offsetBy: bounds.upperBound) | |
return String(self[start...end]) | |
} | |
subscript (bounds: CountableRange<Int>) -> String { | |
let start = index(startIndex, offsetBy: bounds.lowerBound) | |
let end = index(startIndex, offsetBy: bounds.upperBound) | |
return String(self[start..<end]) | |
} | |
} | |
// https://gist.github.com/patrickjuchli/d1b07f97e0ea1da5db09 | |
extension FourCharCode { | |
public init(stringLiteral value: StringLiteralType) { | |
precondition(value.count == 4) | |
var code: FourCharCode = 0 | |
// Value has to consist of 4 printable ASCII characters, e.g. '420v'. | |
// Note: This implementation does not enforce printable range (32-126) | |
if value.count == 4 && value.utf8.count == 4 { | |
for byte in value.utf8 { | |
code = code << 8 + FourCharCode(byte) | |
} | |
} | |
else { | |
print("FourCharCode: Can't initialize with '\(value)', only printable ASCII allowed. Setting to '????'.") | |
code = 0x3F3F3F3F // = '????' | |
} | |
self = code | |
} | |
} | |
let applChunkID = FourCharCode(stringLiteral: "APPL") | |
final class AudioFile { | |
private let fileId: AudioFileID | |
init?(url: URL) { | |
var audioFile: AudioFileID? | |
let status = AudioFileOpenURL(url as CFURL, .readPermission, kAudioFileAIFFType, &audioFile) | |
print("status: \(status)") | |
if let file = audioFile { | |
fileId = file | |
} else { | |
return nil | |
} | |
} | |
func readAPPLChunk() { | |
var itemCount: UInt32 = 0 | |
let status = AudioFileCountUserData(fileId, applChunkID, &itemCount) | |
// Make sure there is at least 1 'APPL' chunk | |
if status == noErr && itemCount > 0 { | |
var size: UInt32 = 0 | |
let sizeStatus = AudioFileGetUserDataSize(fileId, applChunkID, 0, &size) | |
// Make sure you can get the size of the chunk and that there are no issues. | |
if sizeStatus == noErr && size > 0 { | |
let regularData = unsafeBitCast(calloc(1, Int(size)), to: UnsafeMutablePointer<UInt8>.self) | |
defer { | |
regularData.deallocate() | |
} | |
let dataStatus = AudioFileGetUserData(fileId, applChunkID, 0, &size, regularData) | |
// Make sure you could fetch the user data for the chunk and that there was data returned. | |
if dataStatus == noErr && size > 0 { | |
// Explicitly do not free when done here since we're not copying the raw pointer bytes (gotta go fast) | |
let string = String(bytesNoCopy: regularData, length: Int(size), encoding: .utf8, freeWhenDone: false) | |
// Determine if we've reached the OP-1 JSON, and if so advance 4 bytes and strip control characters. | |
if let almostJSON = string, almostJSON.starts(with: "op-1") { | |
let strippedString = almostJSON[4..<almostJSON.count].trimmingCharacters(in: .controlCharacters) | |
do { | |
let parsed = try JSONSerialization.jsonObject(with: strippedString.data(using: .utf8)!) | |
print("Parsed: \(parsed)") | |
} catch(let error) { | |
print("Error Parsing String - \(error)") | |
} | |
} | |
} | |
} else { | |
print("error size: \(sizeStatus) - \(itemCount)") | |
} | |
} else { | |
print("Count Data Status: \(status) - \(itemCount)") | |
} | |
} | |
deinit { | |
AudioFileClose(fileId) | |
} | |
} | |
let url = Bundle.main.url(forResource: "sample-adjusted", withExtension: "aif")! | |
let file = AudioFile(url: url) | |
file?.readAPPLChunk() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment