Skip to content

Instantly share code, notes, and snippets.

@brianmichel
Last active May 14, 2020 13:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brianmichel/0affe74620d80cd6487a9b713ebed2f9 to your computer and use it in GitHub Desktop.
Save brianmichel/0affe74620d80cd6487a9b713ebed2f9 to your computer and use it in GitHub Desktop.
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