Thomas Royal

Composer | Pianist | Technologist

Writings


Using AVAudioUnitSampler in Swift

June 22, 2014

The AVAudioUnitSampler class of Apple's AVFoundation greatly simplifies the process of playing audio files. As it is an AudioUnit, you can theoretically process sounds using native AudioUnits that include abilities to do mixing, eq, and spatialization of sounds. (I haven't tested this yet.) This post deatils how I used AVAudioUnitSampler in a simple drum machine project I am working on.

Setting up AVAudioUnitSampler

Very little code is needed to instantiate an AVAudioUnitSampler object:

// Instatiate audio engine
let audioEngine = AVAudioEngine()

// get the reference to the mixer to
// connect the output of the AVAudio
let mixer = ae.mainMixerNode

// instantiate sampler. Initialization arguments
// are currently unneccesary
let sampler = AVAudioUnitSampler()

// add sampler to audio engine graph 
// and connect it to the mixer node
audioEngine.attachNode(sampler)
audioEngine.connect(sampler, to: mixer, format: sampler.audioFormatForBus(0) )

After this you write some code that loads your samples, and then start the audio engine.

// start audio engine
// ignoring errors, which is not recommended
audioEngine.startAndReturnError(nil)

After this, you play and stop notes using the startNote and stopNote functions, providing the pitch in using MIDI note numbers and the volume in the range of 1-127. (The documentation for this function exists in the AVAudioUnitMIDIInstrument documentation, which is the superclass of AVAudioUnitSampler.

Loading Samples into AVAudioUnitSampler

According to the AVAudioUnitSampler documentation, objects of AVAudioUnitSampler class are able to load Soundfonts, DLS banks, and Garageband and Logic exs files. (Note that at this time, the Soundfont implementation doesn't seem to be quite complete, as evidenced by this post.)

AVAudioUnitSampler can also load individual wave, caf, aiff and mp3 files. With these kinds of files, if there is metadata about root key, and key range, AVAudioUnitSampler will use this data. For example, if your audio file says the root note is 60, and the key range is between 60 and 60, if you do this:

sampler.startNote(61, withVelocity: 127,onChannel: 0)

. . . you will not hear this sound.

I had some difficulty finding a way to easily edit wave metadata. The easiest solution seemed to be [Sample Manager][], which is currently about sixty US dollars.

One thing that I found, quite by accident, is that if you provide a name and octave number in the name of the sample, the note will be assigned to the MIDI number associated with the note. For example, if you name your sample violinC4.wav, your sample will be assigned to note number 60.

Drum Machine Class in Swift

I used the following code to make a drum machine class in Swift:

import AVFoundation

class DrumMachine{
    var ae:AVAudioEngine
    var sampler:AVAudioUnitSampler
    var mixer:AVAudioMixerNode

    var midiNoteNumberFor:Dictionary<String,UInt8> = [
        "BD":48,
        "Snr":50,
        "Hat":52,
        "Hit":53,
        "VI":68,
        "V":67,
        "i":60,
        "III": 63
    ]

    init(){

        var urls = NSBundle.mainBundle().URLsForResourcesWithExtension("wav", subdirectory: "wavs")

        ae = AVAudioEngine()
        mixer = ae.mainMixerNode

        sampler = AVAudioUnitSampler()

        ae.attachNode(sampler)
        ae.connect(sampler, to: mixer, format: sampler.outputFormatForBus(0))

        sampler.loadAudioFilesAtURLs(urls, error: nil)
        ae.startAndReturnError(nil)

    }

    func play(snd:String){
        // shouldn't I care if snd exists in the
        // midiNoteNumberFor Dictionary????????? 
        sampler.startNote(midiNoteNumberFor[snd]!, withVelocity: 127,onChannel: 0)

    }
}

To use this, I had a directory called wavs that had the following filenames:

I imported this directory into my XCode project.

To use it, I call the DrumMachine play function with a string that I want to play as a parameter. This instrument is the used to look up the note number as defined in the midiNoteNumberFor dictionary. For this project, I wanted to use UIButton labels as the argument to the drum machine, so I used a dictionary containing these names and mapped them to the note numbers that correspond to the note and and octave in the sample name.

This code is a bit janky. I really aught to make sure that the string passed in as a parameter to the play function exists in my midi note dictionary.

Oh well, fun times.