AVAudioPCMBuffer built programmatically, not playing back in stereo

3.2k views Asked by At

I'm trying to fill an AVAudioPCMBuffer programmatically in Swift to build a metronome. This is the first real app I'm trying to build, so it's also my first audio app. Right now I'm experimenting with different frameworks and methods of getting the metronome looping accurately.

I'm trying to build an AVAudioPCMBuffer with the length of a measure/bar so that I can use the .Loops option of the AVAudioPlayerNode's scheduleBuffer method. I start by loading my file(2 ch, 44100 Hz, Float32, non-inter, *.wav and *.m4a both have same issue) into a buffer, then copying that buffer frame by frame separated by empty frames into the barBuffer. The loop below is how I'm accomplishing this.

If I schedule the original buffer to play, it will play back in stereo, but when I schedule the barBuffer, I only get the left channel. As I said I'm a beginner at programming, and have no experience with audio programming, so this might be my lack of knowledge on 32 bit float channels, or on this data type UnsafePointer<UnsafeMutablePointer<float>>. When I look at the floatChannelData property in swift, the description makes it sound like this should be copying two channels.

var j = 0
for i in 0..<Int(capacity) {
    barBuffer.floatChannelData.memory[j] = buffer.floatChannelData.memory[i]
    j += 1
}
j += Int(silenceLengthInSamples)
// loop runs 4 times for 4 beats per bar. 

edit: I removed the glaring mistake i += 1, thanks to hotpaw2. The right channel is still missing when barBuffer is played back though.

2

There are 2 answers

5
hola On BEST ANSWER

Unsafe pointers in swift are pretty weird to get used to.

floatChannelData.memory[j] only accesses the first channel of data. To access the other channel(s), you have a couple choices:

Using advancedBy

// Where current channel is at 0

// Get a channel pointer aka UnsafePointer<UnsafeMutablePointer<Float>> 
let channelN = floatChannelData.advancedBy( channelNumber )

// Get channel data aka UnsafeMutablePointer<Float>
let channelNData = channelN.memory

// Get first two floats of channel channelNumber
let floatOne = channelNData.memory
let floatTwo = channelNData.advancedBy(1).memory

Using Subscript

// Get channel data aka UnsafeMutablePointer<Float>
let channelNData = floatChannelData[ channelNumber ]

// Get first two floats of channel channelNumber
let floatOne = channelNData[0]
let floatTwo = channelNData[1]

Using subscript is much clearer and the step of advancing and then manually accessing memory is implicit.


For your loop, try accessing all channels of the buffer by doing something like this:

for i in 0..<Int(capacity) {
    for n in 0..<Int(buffer.format.channelCount) {
         barBuffer.floatChannelData[n][j] = buffer.floatChannelData[n][i]
    }
}

Hope this helps!

1
hotpaw2 On

This looks like a misunderstanding of Swift "for" loops. The Swift "for" loop automatically increments the "i" array index. But you are incrementing it again in the loop body, which means that you end up skipping every other sample (the Right channel) in your initial buffer.