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.
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.
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.
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