What I actually try to do is to play back a couple of samples, through the speaker. I assumed that this would be the most trivial part of the project, but so far it has been the most time consuming.
Most APIs expect an audio file, which I do not have. I found one API that looks like it can take some samples as input and play through the speaker: AudioQueue.
Below is all the relevant code. It is my very first time working with any of these things, so this is probably very ugly code.
This is part of ViewController.swift, which acts as my main class.
Important to note here is that ret.0 is a UnsafeMutablePointer<Double> that points to the samples.
...
let ret = syntSine(dataAsDouble, Int(pixelsWide), Int(pixelsHigh))
samplecount = ret.1
// Attempt using AudioQueue
var aq: AudioQueue = AudioQueue(input: ret.0, nSamples: samplecount)
aq.initialize()
var prepped: UInt32 = 0
var primeResult: OSStatus = AudioQueuePrime(aq.queue, 22100, &prepped)
println("Status: \(primeResult) Prepped: \(prepped)")
var AQError = AudioQueueStart(aq.queue, nil)
println("AQError: \(AQError)")
...
The AudioQueue class is intended to prepare the queue.
import Foundation
class AudioQueue {
var desc : AudioStreamBasicDescription
var queue : AudioQueueRef
var nSamples: Int // number of samples in input
var bufferIndex: Int
let spb: Int // samples per buffer
var upperBounds: Int // current upper boundary in input
var input: UnsafeMutablePointer<Double>
init(input: UnsafeMutablePointer<Double>, nSamples: Int) {
println("init called")
// Does not work when the constant type is omitted. This just cost me a couple of hours. Thanks Apple. Fuck you.
self.desc = AudioStreamBasicDescription(
mSampleRate: 44100,
mFormatID: AudioFormatID(kAudioFormatLinearPCM),
mFormatFlags: AudioFormatFlags(kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved | kLinearPCMFormatFlagIsFloat | kLinearPCMFormatFlagIsPacked),
mBytesPerPacket: 4,
mFramesPerPacket: 1,
mBytesPerFrame: 4,
mChannelsPerFrame: 1,
mBitsPerChannel: 32,
mReserved: 0
)
self.queue = nil
self.nSamples = nSamples
self.bufferIndex = 0
self.spb = 22100
self.upperBounds = 0
self.input = input
}
func audioQueueHandleBuffer(inAQ : AudioQueueRef, inBuffer : AudioQueueBufferRef) {
println("AudioQueueHandleBuffer called")
var i = 0 // ouput index
var outBuffer: UnsafeMutablePointer<Double> = UnsafeMutablePointer<Double>(inBuffer.memory.mAudioData) // TODO: check whether this is correct
//fill buffer
if(nSamples - bufferIndex < spb) {
upperBounds = nSamples - bufferIndex
} else {
upperBounds = bufferIndex + spb
}
for(; bufferIndex < upperBounds; bufferIndex++) {
outBuffer[i] = input[bufferIndex]
}
inBuffer.memory.mAudioData = UnsafeMutablePointer<Void>(outBuffer)
}
func initialize() {
println("initialize called")
var err = CFIAudioQueueNewOutput(&desc, audioQueueHandleBuffer, &queue)
println("Initialize Error: \(err)")
// ...
}
}
This Objective-C code is supposed to hand the function pointer to AudioQueueNewOutput.
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
// from: http://stackoverflow.com/questions/25341632/pass-c-function-callback-in-swift
void audioQueueHandleBuffer(void *ctx, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
NSCAssert(ctx != NULL, @"Cannot execute NULL context block.");
void(^block)(AudioQueueRef, AudioQueueBufferRef) = (__bridge void(^)(AudioQueueRef, AudioQueueBufferRef))ctx;
return block(inAQ, inBuffer);
}
OSStatus CFIAudioQueueNewOutput(AudioStreamBasicDescription *desc, void(^callback)(AudioQueueRef, AudioQueueBufferRef), AudioQueueRef *queue) {
return AudioQueueNewOutput(desc, audioQueueHandleBuffer, (__bridge_retained void *)([callback copy]), nil, nil, 0, queue);
}
That should be all the necessary code. The callback function is never called and hence cannot fill the buffer. I think the problem might be with the Ojective-C wrapper, but I really don't know and don't understand the black art that is Objective-C.