3

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.

JGHof
  • 85
  • 8

2 Answers2

2

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!

hola
  • 2,180
  • 9
  • 21
  • Do you know how the readIntoBuffer method works? file.length returns 3008 (samples, frames?) When I start looking at the pointer channels things get weird. Channel 0 has data from buffer.floatChannelData[0][0] - [0][6015]. Channel 1 shows data from buffer.floatChannelData[1][0] - [0][3007]. The numbers line up at this point, I just don't know what they're representing, or why channel 0 has twice as many. buffer.floatChannelData[2] however, seems completely inconsistent. sometimes returning huge numbers in e notation, and sometimes "bad access" errors. Thanks for your answer! – JGHof Mar 09 '16 at 19:26
  • @JGHof It reads the audio file into a given buffer. What you want to know? – hola Mar 11 '16 at 03:02
  • The gibberish values are from accessing memory that's been allocated, but may not be initialized. The bad access is from accessing memory that has not been allocated. That's why it's unsafe. Following the length and channel count is important so you don't access memory that's not yours or allocated. – hola Mar 11 '16 at 03:03
  • I'm confused on how exactly the AVAudioBuffer translates a file into the buffer. Do I only need channels [0] and [1], or is there data in [2] that I need to copy? Do you know why [1] has half the amount of data compared to [0]? is there a way to get the channel count programmatically, or is that on my to know how my file translates into the buffer? Thanks for any help you can provide. – JGHof Mar 11 '16 at 03:26
  • Here's a [paste](http://pastebin.com/zCriSY63) showing how to read an audio file into a buffer. Yes, you'll want all channels. Only access channels and samples that are in valid range defined by the file format and the given sample length. It has to do with the way memory is allocated and how pointers point to memory. Out of scope here, but it's easy to find out, google about pointers. The channel count is retrieved from the buffer or the audio file's file format property. For audio file: `processingFormat.channelCount` or `fileFormat.channelCount`. For buffer: `format.channelCount`. – hola Mar 11 '16 at 09:42
1

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.

hotpaw2
  • 68,014
  • 12
  • 81
  • 143
  • This actually didn't fix the issue with the right channel being silent. The sample sounds a lot better without every other sample being skipped, but it''s still only in the left channel. – JGHof Feb 20 '16 at 23:41