TSA

The Strange Agency

Building a Granular Synth in Swift, Part 2: Loading Audio

Now let’s add a little bit of structure and create an AudioSource object that can wrap the file loading and act as a buffer source for our audio engine. Per Choosing Between Structures and Classes we’ll go with struct over class for now, although it might turn out that passing our source around by reference might be useful later.

GrainSwift/AudioSource.swift at 7d0c817b0d8fe42d63a8f86848ef935a9ff42fd3 · thestrangeagency/GrainSwift · GitHub

Per Managing Model Data in Your App it looks like we’ll want an ObservableObject in order to display audio state in the UI. As we will be adding other items to the audio state, we’ll want to wrap the AudioSource struct in another class that deals with other aspects of our audio situation. We are building an audio app that does nothing else, so it feels natural to make this state part of the global environment. We can quickly add a loaded variable to the source struct and display its value in the UI.

wrap AudioSource in an Observable and display loaded state · thestrangeagency/GrainSwift@2b26939 · GitHub

Now we have a buffer of audio and our granulation can begin, but we have no way to hear it. So, first let’s set up an audio graph and use our source node to simply play audio from our buffer. Here we quickly step into the weeds of audio formats. We’ll begin by using a Float format for reading the audio file, as this will match our output and let us dump audio from the file buffer to the AVAudioSourceNode render callback more easily.

GrainSwift/AudioSource.swift at c0de958f0f6aea673e866dd756124dd1186d0e0f · thestrangeagency/GrainSwift · GitHub

GrainSwift/Audio.swift at c0de958f0f6aea673e866dd756124dd1186d0e0f · thestrangeagency/GrainSwift · GitHub

One bit of mayhem is the way we get at the buffers in Swift. One runs into some low-lever gyrations like this on Stack Overflow, but there are some convenience methods in the UnsafeMutableAudioBufferListPointer wrappers that let us get directly at the buffer data via regular array subscripts.

One can for example:

let bufferListPointer = UnsafeMutableAudioBufferListPointer(audioBufferList)

Which then allows for more convenient access:

let outBuffer:UnsafeMutableBufferPointer<Float> = UnsafeMutableBufferPointer(bufferListPointer[channel])

Note that we’re assuming stereo file data, so a mono file will crash when channel is greater than 0 at channelData[channel]. Curiously, this happens only occasionally when running on my simulator. The output buffer list count varies between 1 and 2. Anyway, let’s handle mono files more cleanly and maybe improve our naming: handle mono files · thestrangeagency/GrainSwift@9315241 · GitHub.

Edit: min() should have been max()! use max instead of min · thestrangeagency/GrainSwift@84407f8 · GitHub