Category

Swift AVFoundation to play audio or MIDI

Swift Language

Swift AVFoundation

There are many ways to play sound in iOS. Core Audio has been around for a while and it is very powerful. It is a C API, so using it from Objective-C and Swift is possible, but awkward. Apple has been moving towards a higher level API with AVFoundation. Here I will summarize how to use AVFoundation for several common audio tasks.

N.B. Some of these examples use new capabilities of iOS 8.

There is a newer version of this post. It has been updated to Swift 2.

Playing an Audio file

Let’s start by loading an audio file with an AVAudioPlayer instance. There are several audio formats that the player will grok. I had trouble with a few MP3 files that played in iTunes or VLC, but caused a cryptic exception in the player. So, check your source audio files first.

If you want other formats, your Mac has a converter named afconvert. See the man page.

Let’s go step by step.

Get the file URL.

Create the player. You will need to make the player an instance variable, because if you just use a local variable, it will be popped off the stack before you hear anything.

You can provide the player a hint for how to parse the audio data. There are several constants you can use.

Now configure the player. prepareToPlay() “pre-rolls” the audio file to reduce start up delays when you finally call play().
You can set the player’s delegate to track status.

To set the delegate you have to make a class implement the player delegate protocol. My class has the clever name “Sound”.

Finally, the transport controls that can be called from an action.

The complete gist for the AVAudioPlayer:

Audio Session

The Audio Session singleton is an intermediary between your app and the media daemon. Your app and all other apps (should) make requests to the shared session. Since we are playing an audio file, we should tell the session that is our intention by requesting that its category be AVAudioSessionCategoryPlayback, and then make the session active. You should do this in the code above right before you call play() on the player.

Setting a session for playback.

Go to Table of Contents

Playing a MIDI file

You use AVMIDIPlayer to play standard MIDI files. Loading the player is similar to loading the AVAudioPlayer. You need to load a soundbank from a Soundfont or DLS file. The player also has a pre-roll prepareToPlay() function.

I’m not interested in copyright infringement, so I have not included either a DLS or SF2 file. So do a web search for a GM SoundFont2 file. They are loaded in the same manner. I’ve tried the MuseCore SoundFont and it sounds ok. There is probably a General MIDI DLS on your OSX system already: /System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls. Copy this to the project bundle if you want to try it.

You can also load the MIDI player with an NSData instance like this:

Cool, so besides getting the data from a file, how about creating a sequence on the fly? There are the Core Audio MusicSequence and MusicTrack classes to do that. But damned if I can find a way to turn the sequence into NSData. Do you? FWIW, the AVAudioEngine q.v. has a barely documented musicSequence variable. Maybe we can use that in the future.

In your action, call the play() function on the player. There is only one play function, and that requires a completion handler.

Complete AVMIDIPlayer example gist.

Go to Table of Contents

Audio Engine

iOS 8 introduces a new audio engine which seems to be the successor to Core Audio’s AUGraph and friends. See my article on using these classes in Swift.

The new AVAudioEngine class is the analog to AUGraph. You create AudioNode instances and attach them to the engine. Then you start the engine to initiate data flow.

Here is an engine that has a player node attached to it. The player node is attached to the engine’s mixer. These are instance variables.

Then you need to start the engine.

Cool. Silence.

Let’s give it something to play. It can be an audio file, or as we’ll see, a MIDI file or a computed buffer.
In this example we create an AVAudioFile instance from an MP3 file, and tell the playerNode to play it.

First, load an audio file. If you know the format of the file you can provide hints.

Now hand the audio file to the player node by “scheduling” it, then playing it.

Go to Table of Contents

Playing MIDI Notes

How about triggering MIDI notes/events based on UI events? You need an instance of AVAudioUnitMIDIInstrument among your nodes. There is one concrete subclass named AVAudioUnitSampler. Create a sampler and attach it to the engine.

At init time, create a URL to your SoundFont or DLS file as we did previously.

Then in your UI’s action function, load the appropriate instrument into the sampler. The program parameter is a General MIDI instrument number. You might want to set up constants. Soundbanks have banks of sound. You need to specify which bank to use with the bankMSB and bankLSB. I use a Core Audio constant here to choose the “melodic” bank and not the “percussion” bank.

Then send a MIDI program change to the sampler. After that, you can send startNote and stopNote messages to the sampler. You need to match the parameters for each start and stop message.

Go to Table of Contents

Summary

This is a good start I hope. There are other things I’ll cover soon, such as generating and processing the audio buffer data.

Resources

Go to Table of Contents

29 thoughts on “Swift AVFoundation to play audio or MIDI”

  1. Dear Gene,

    Thank you for your precious tutorial, it was exactly what I was looking for!
    About the first example (playing a sound file): I had to make the Sound class inherit from NSObject, otherwise Xcode complains.
    Everything seems OK, the console tells me that the file is playing, the volume is up… but I still get no sound.
    I am running on the emulator, not on an iPhone. Could this be the issue?

    Thanks in advance,

    Best,

    Elvio

  2. Ah, the Gist doesn’t match the Github complete project. Thanks.

    Yes, the Swift Sound class needs to inherit from NSObject for the key value things.

    Do you get a sound if you try your sound file with the github project?

    I did have a problem with one badly encoded MP3 file.

  3. IT WORKS! 😀
    I will spend the next days studying your code, and trying to understand what I did wrong in mine.
    Thanks again,

    Best,

    Elvio

  4. I simply put
    var mySound = Sound()
    inside the viewDidLoad() method. When I got it out, everything worked.
    Starting MIDI, now! 🙂

  5. Dear Gene,
    I finally had time to dig into the MIDI part of your code.
    It looks like the sound bank is actually initialized twice, once in Sound and the other in the View Controller.
    To avoid redundancy, I would suggest to delete the latter, and to refer to it with self.sound.soundbank

    But apart from this detail, I have a weird problem: the harpsichord sound (or whatever midi sound) is audible only when I press the NoteOn/NoteOff button. When I try to play a midi file, everything is played with sinuses.
    My impression is that something is broken in the chain, and that the engine connects to a default synth instead of connecting to the sampler with the sf2 bank.
    Do you have any hint? Thanks in advance!

    Best,

    Elvio

    1. They are 2 separate examples.

      The ViewController has an example of using “low level” AVAudioEngine code.

      Sound.swift is a higher level example that uses the AVMIDIPlayer and AVAudioPlayer.

  6. Dear Gene,

    You mention the possibility to “feed” an audioNode with a sound file, with a MIDI file or with a buffer.
    I can’t find any way to relate a MIDI file to an AVAudioNode (surprisingly, even if AVMIDIPlayer produces audio, it inherits just from NSObject). Could you give me a hint?

    Thanks,

    Elvio

  7. Hi I’m wondering how to receive MIDI inputs directly into a playground. What I’m wanting to do is receive the note number whenever I hit a key on my keyboard what would be the most practical way of doing this?

  8. Why is it when I do a basic search for midi players I get a whole bunch of smart phone crap instead of windows?

  9. Hello,

    Thanks for yours tutorials on this subject.

    I have one question about the “sendProgramChange” function of the AVAudioUnitSampler object. It shouldn’t allow to change the preset of a soundfont ?

    I want to play some musical notes of a soundfont file with multiple presets, but I’m unable to change the presets without reloading the soundfont files (which is too long to load).

    My other solution is to split my soundfont file in multiple files with only one preset, and to create multiple AVAudioUnitSampler objects, but this solution consume more memory.

    1. What works for me is to use a separate sampler for each sound font preset that I want to use. You hook up each sampler to the mixer. This is what I did in Core Audio graphs also.

      Let me know if that helps.

      1. Yes, it works well.
        I allocates the samplers when it’s necessary and I release them after playing the sounds.

        Thanks for your help.

  10. Hi Gene, thanks for taking the time to share all of this work. I’m on the “learn swift” journey. I’d appreciate any thoughts you might be willing to share on the appropriate technique to de-activate the session. I’m assuming you inspect the event generated from the controller when the sound file is finished playing and then de-activate it but I’m not sure what best practices is here. Appreciate anything you can share. Regards, Doug

  11. Hi!
    Wondering if anyone has any advice on getting a timestamp of a song when the audio starts to output. Right now there is about a second delay between the an avplayer action and audio being outputted. I would like to track the time when audio is being outputted on the iOS device rather than just the command action.

    Thanks!

  12. Hello Gene De Lisa!

    Very thank you for examples – it’s awesome!

    Please tell, how to implement real time audio processing to audio from music player, for example – add reverb to audio played from Apple Music player. Another words – insert audio processing chain right before audio output.

    Best regards, Ivan.

  13. Hello Gene De Lisa!

    Thanks for the explanation and example
    It helped me a lot!
    I have a questions :
    How can I change channel volume For an object AVMIDIPlayer in real time?
    Thank you …
    Best regards, sapir.

      1. Thank you for the answer

        I try it ,
        I use this : var sampler:AVAudioUnitSampler
        my code:
        self.sampler.sendController(7, withValue: 0, onChannel: 1)
        self.sampler.sendController(7, withValue: 0, onChannel: 2)
        self.sampler.sendController(7, withValue: 0, onChannel: 3)
        self.sampler.sendController(7, withValue: 120, onChannel: 10)

        I didnt hear eny change in the midi playing file in AVMIDIPLAYER

        Thank you …
        Best regards, sapir.

  14. Hi,

    When you load the sound font and set those values:
    let gmMarimba:UInt8 = 12
    let gmHarpsichord:UInt8 = 6

    Where do you get them from? Is there a software that can load a dls file and show all the available instruments in there?

    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.