Multi-timbral AVAudioUnitMIDIInstrument

Swift Language

Multi-timbral AVAudioUnitMIDIInstrument in Swift

Table of Contents

Introduction

Table of Contents

There is one sublcass of AVAudioUnitMIDIInstrument provided by Apple – the AVAudioUnitSampler. The only problem is that it is mono-timbral; it cannot play more than one timbre at a time.

To create a new AVAudioUnit, we need to use a bit of Core Audio.
So, I’ll give you two examples, one using Core Audio and an AUGraph and then one using AVFoundation using AVAudioEngine.

Core Audio Unit

Table of Contents

We need to create an AUGraph and attach nodes to it.

Here’s the first step. Create your class, define instance variables, and create the graph using Core Audio’s C API.

Here is the item we’re interested in. Create a node that’s an Audio Unit Music Device with a subtype MIDISynth and add it to the graph.

And also create the usual io node, kAudioUnitSubType_RemoteIO on iOS, in the same way. I’m not going to bother with a mixer in this example..

Get the audio units from the nodes using AUGraphNodeInfo in order to get/set properties on them later. Then connect them using AUGraphConnectNodeInput.

To load the Sound Font, set the kMusicDeviceProperty_SoundBankURL property on your unit. I’m using a SoundFont from MuseScore here.

The typical Sound Font contains dozens of patches. You don’t really want to load every single one of them. You should pre-load the patches you will actually use. The way to do that is a bit strange. You set the property kAUMIDISynthProperty_EnablePreload to true (1), send MIDI program change messages via MusicDeviceMIDIEvent for the patches you want to load, and then turn off kAUMIDISynthProperty_EnablePreload by setting it to 0. You need to have the AUGraph initialized via AUGraphInitialize before calling this.

Where is this documented? Damned if I know. Do you know? Tell me.

Now when you want to play a note, you send a MIDI program change to tell the synth unit which patch to use.

If you want to play a sequence, the traditional way to do that with an AUGraph is with the Audio Toolbox entities. MusicPlayer will play a MusicSequence. When you create your MusicSequence, you attach it to the AUGraph.

There are examples in my Github project for sending note on/note off messages as well as playing a MusicSequence through the AUGraph.

AVFoundation Unit

Table of Contents

So we know the how to do this in Core Audio. How do you do it in AVFoundation?

The class hierarchy for AVAudioUnitSampler is:
AVAudioNode -> AVAudioUnit -> AVAudioUnitMIDIInstrument -> AVAudioUnitSampler

So, our AVAudioUnit will be:
AVAudioNode -> AVAudioUnit -> AVAudioUnitMIDIInstrument -> AVAudioUnitMIDISynth

That part was obvious. What you need to do though is not especially clear. As usual, Apple doesn’t give you a clue. So, this is how I got it to work. I don’t know if this is the “official” method. If you know, tell me.

I’ve noticed that the provided AVAudionUnits work with no-arg inits. So, I decided to create the AudioUnit’s AudioComponentDescription here and pass it up through the hierarchy to have one of those classes (probably AVAudioUnit) initialize it.

AVAudioUnit defines the audioUnit property. We can use that to set the kMusicDeviceProperty_SoundBankURL property for a Sound Font.

Remember that kAUMIDISynthProperty_EnablePreload chacha we did to pre-load patches? We can do that here too.

That’s it.

To use it, attach it to your audio engine.

You can play a sequence via the AVAudioSequencer which is attached to your engine. If you don’t preload your patches, the sequencer will do that for you.

This is how to load a standard MIDI file into the sequencer.

The sequencer can also be created with NSData. This is quite convenient – everyone loves creating an NSMutableData instance and then shove bytes into it. Right?
Have a MusicSequence? Your only option it to turn it into NSData.

This works. If you have a better way, let me know.

Summary

Table of Contents

All this to create an AVAudioUnit subclass.

You should preload the patches you are going to use. If you’re going to use an AVAudioSequencer, you don’t have to; it will do it for you.

Create an AVAudioUnit subclass and pass a Core Audio AudioComponentDescription to a superclass in your init function.

You can access the audioUnit in your AVAudioUnit subclass and set properties on it using Core Audio.

Resources

Table of Contents

Share These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Facebook
  • Twitter
  • LinkedIn
  • email
  • DZone
  • Slashdot
  • Reddit
  • Google Bookmarks
  • Digg
  • StumbleUpon
  • del.icio.us
This entry was posted in AVFoundation, Core Audio, Swift and tagged , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

18 Comments

  1. Zelda
    Posted April 2, 2016 at 12:35 am | Permalink

    I am looking for some way to tell if AVAudioSequencer has finished playing, so I can update the play/stop button. Some sort of notification or completion handler I imagine, have any of you guys had experience with
    this situation.
    Thanks
    Z

    • Gene De Lisa
      Posted April 3, 2016 at 8:58 am | Permalink

      No, Apple wasn’t gracious enough to do that.
      I’d love to be wrong on that, so if anyone else knows a way, let me know.

      Take a look at my example for how to accomplish “rewinding”.

      Specifically look at the play() function in this controller.

  2. SNIP3R
    Posted April 29, 2016 at 10:35 pm | Permalink

    Similar question to Zelda’s, is there a way to setup callbacks for any events on AVAudioSequencer? I’d love a callback when the sequencer is looping, or to be able to get callbacks on certain beats. Can this be added or extended? Thanks for the blog Gene, very helpful!

  3. Sven
    Posted May 2, 2016 at 8:25 pm | Permalink

    Hello,
    Your blog is gold. 🙂
    It looks like you are sending midi events only on channel 0. Isn’t it possible to preload patches in MIDISynth on various channels, and later to send midi notes on those channels? Sending a program change for every note seems to be overkill.

    • Gene De Lisa
      Posted May 3, 2016 at 6:21 pm | Permalink

      As with everything I write, I show an answer that works but not necessarily the answer 🙂

      Did you try it to see if it works? If I weren’t swamped with work projects, I’d do it now.

  4. Posted August 4, 2016 at 8:35 am | Permalink

    Gene, Thanks alot for all your post! They are extremely valuable to the community… .

    I am wondering whether you’ve experienced Memory Leak problems when disposing AudioGraph with SoundFonts and if you’ve managed to overcome it?

    We have a simple program that uses the same procedure as your loadMIDISynthSoundFont() and loadSynthPatches() and everytime AudioGraph is disposed, we experience a 13Mb overhead! (We don’t use AV stuff..). We use the good old “FuildR3 GM2-2.SF2” for this test… .

    Any hints?

    • Gene De Lisa
      Posted August 5, 2016 at 7:38 am | Permalink

      If you’d like to send me an excerpt of your code, maybe just having an extra set of eyeballs on the problem would help.

  5. Korin WH
    Posted August 19, 2016 at 7:44 pm | Permalink

    Thank you Gene! I’ve been reading your blog posts and one you wrote about a year ago or so stated that the AVAudioSequencer technology is unstable so I was wondering what was the status of that? Is it more stable now? Does it have the ability to add or remove events from the Music Sequence?

    What is the difference between the AVAudioEngine.musicSequence property and the one that we attach via AVAudioSequencer?

    Is it possible to use the old Core Audio/Core MIDI APIs (MusicTrack, MusicSequence, MusicPlayer) for play back with AVAudioUnitNodes? We already have an existing architecture in AVAudioEngine, but need add/remove functionality with a sequencer. Would MusicSequence work for that?

    • Gene De Lisa
      Posted August 22, 2016 at 8:19 am | Permalink

      As of Swift 3 beta 6, nothing has been added to AVAudioSequencer and AVMusicTrack. They are for playback only right now. Doug says file a request on Radar – if there are “enough” then they might think about it. I’m sure they are tired of hearing from me about it.

      In the past I tested setting the musicSequence property on AVAudioEngine and it crashed. I suppose they want you to not use it directly. The documentation on that property is awful as usual.

      Yes you can use the Audiotoolbox classes with AVAudioEngine. I have a 6.022 x 10^gazillion github repos with examples.

      Here’s a blog post

      Here’s a a repo

  6. Jens
    Posted June 17, 2017 at 12:04 am | Permalink

    Hello.

    If anyone can hint me to a useful replacement for the awful *AVAudioSequencer*,
    written in Swift 3/4, I would be really very happy!

    Everything seems to be possible with AVFoundation yet, loading/creating/editing/saving MIDI sequences, creating multiple sampler instances and sophisticated effect processing chains and so on…

    except an *useful sequencer*, which is really missing.

    *AVAudioSequencer* is still completely useless for creative coding of that kind. Its totally limited in possibilities. It’s even totally superfluous, because it does nothing that cannot be done with other methods.

    Unfortunately, currently I have no clue how to build an own sequencer class, that is for instance able to drive multiple instances of *AVAudioUnitSampler* based on multi-channel *MusicSequence*s. Using simple multi-timbre DLS and Soundfont MidiSynths is just not that exciting, as one could rather just use a simple AVMidiPlayer for playback then.

    Any advices would be highly appreciated. 😉

    • Gene De Lisa
      Posted June 17, 2017 at 9:53 am | Permalink

      There’s the C Audiotoolbox MusicPlayer and MusicSequence which is usable from Swift but rather clunky.

      The AVFoundation classes are half-baked right now. They are geared towards simply playing back a pre-existing MIDI file. The AVTrack has nothing in it to build it from scratch. Apple says “file a Radar”. I did. It told me that it was a “duplicate request”, but in Apple’s wisdom, Radars are private and not searchable.

      Swift 4 and iOS 11 betas do not improve this even a little. Interesting, because they’re going to mark AUGraph as deprecated. I wonder if the Audiotoolbox will be deprecated and replaced with AVFoundation classes. I’m not holding my breath.

    • Jens
      Posted June 17, 2017 at 10:05 am | Permalink

      Update:

      I am busy now with trying a ‘fake’ sequencer via CoreMIDI by using a virtual MIDI port and a *MusicPlayer* instance -> in connection with calling *MIDIDestinationCreateWithBlock()* to instantiate a callback for the MIDI events inside a *MusicSequence* in memory. So I am able to redirect these events to multiple (multi timbre) *AVAudioUnitSampler*s with success – finally achieving a multi channel sampler with independent effect chains. This way, I will have the desired control over midi/audio channels, which is not possible with a standard dls/sf2 MIDISynth. It basically works so far, but have not tested in depth yet. Requirement is that it will play Standard MIDI files.

      Generally I still consider this to be a somehow “fast & dirty” solution, especially because this virtual MIDI Port may be visible to other apps, which is not always, what I want. I do not know, whether there is a possibility to hide this virtual port to others.

      My initial idea was to code a sequencer from the scratch, based on a high resolution timer and a sorted event list from the MusicSequence in memory or such. I guess this would be much more work and time for testing .. and I am quite new to the world of that nifty swifty…

      • Jens
        Posted June 23, 2017 at 4:52 am | Permalink

        Update 2:

        Hello again.
        I am working on this for days now, and the BUGS in this entire framework (AudioToolbox) are so annoying!
        At first, the SWIFT frameworks (especially the interesting parts) are badly (if even at all) documented.
        By trial and error I discovered loads of bugs, i.e. with all kind of MIDI RAW data, i.e MIDIMetaEvent, MusicEventUserData, MIDIRawData with *MusicSequence*. So either I am totally stupid, or they did the SWIFT porting wrong…
        However, excessively tweaking my code, I found a possibility to work around this. I would rather say, it is a kind of “Hack” to get this working. The latest “pointer access” stuff of SWIFT also play a big part to this. Probably SWIFT is the complete WRONG way to do this all with satisfaction …

        At first, I wanted to build a complete MIDI Monitor with the possibility to edit, save and reload multichannel recorded MIDI data this way. But without the support of Meta and SysEx, which all require correct handling of MIDI ‘raw data’ of arbitrary variable length this would be completely senseless, as there is no chance to re-open a saved MusicTrack as a MIDI file and read back these data correctly. ^^
        It seems, that ist is not even possible to simply save and load song and track names with the provided SWIFT AudioToolbox methods in a correct manner.

        *If someone actually knows, how this correctly works, so I would be very happy to see some code, because mine is a HACK.*

        The sequencer Part of my project based on the MusicPlayer with MIDIEndpoint basically works, but there are major design flaws of the entire AudioToolbox framework in my opinion. That is: There is no possibility to tag music events uniquely inside a MusicSequence, so that you cannot access events in any useful manner. Instead, you always and endless have to iterate all those events and you have to “guess”, whether the fond event is the one you wanted or possibly not. ^^ So that is a pain. And it is actually a totally unprofessional and quite useless implementation. Sorry, Apple, But that’s a fact!

        I guess, if Apples Garageband would rely oh these frameworks alone with would be written completely in SWIFT, so the program actually won’t work at all. ^^

  7. Lew Wang
    Posted June 23, 2017 at 2:47 am | Permalink

    Hi Gene, Thanks for your post, It’s great and helped me a lot.

    I use Core Audio Unit to implement multiple midi channel play with different instrument. I work it out by learn from this blog. But standard midi file always have some channel haven’t instrument audio output.

    I use FluidR3 GM2-2 as my soundfont bank. It must have 128 kind of instrument tone.

    I checked the midi command Cx (x is midi channel index , this command is set the instrument tone of current channel) send to apple core audio unit by the method of MusicDeviceMIDIEvent

    Of course ,I checked the volume of every midi channel ,It’s not zero.

    It’s strange, Do you met this situation? Any advices would be highly appreciated.

    • Jens
      Posted June 23, 2017 at 9:43 am | Permalink

      Do you use the default multi timbre/channel MidiSynth for that or an AuSampler (mono timbre, only channel omni – receives on all channels) with different audio busses ?

      Try to check your sound bank and whether the patches (program changes) use MIDI Bank Select 0. Possibly there are no other sound banks (1 to 126) defined inside the sound font, which may cause this behaviour, if it is switched inside the MIDI file.

      There may be other reasons too: I.e. usage of explression and sustain controllers, invalid samples inside the font or a corrupted mapping inside the sound font… Also the MIDI files may have some internals, that cause such behaviour.

      I discovered similar things inside the iOS simulator. Did not find any exact reason, except, that it may have to do with internal memory allocation of the AudioToolbox framework. Very large sounfonts for instance do produce complete dropouts in sound at certain positions and situations…

    • Gene De Lisa
      Posted June 23, 2017 at 12:23 pm | Permalink

      Send me a link to your github repo or email me your code and I’ll see.

  8. Jens
    Posted June 23, 2017 at 9:23 am | Permalink

    The big MIDI Raw Data problem in SWIFT traces down to a certain problem.
    The data structs are obviously imported/converted wrong in the SWIFT headers. A Meta Message, for instance, is defined there as:

    public struct MIDIMetaEvent {
    public var metaEventType: UInt8
    public var unused1: UInt8
    public var unused2: UInt8
    public var unused3: UInt8
    public var dataLength: UInt32
    public var data: (UInt8) <<<<<<<<<
    public init()
    public init(metaEventType: UInt8, unused1: UInt8, unused2: UInt8, unused3: UInt8, dataLength: UInt32, data: (UInt8))
    }

    That, (please note that ominous (UInt8) tuple), is the reason, why it won't work! SWIFT obviously internally copies this data when allocating events – we do not get direct access to the data for some reason. And this(re) allocates the space for exactly 1 element regarding this declaration… So only the first byte of the message is accessible for us – which is exactly what many of us have experienced – all other data is not there. Although, the data still validly exists anywhere in memory, as CAShow() shows…

    So the trick for a solution is, to provide an own version of the MIDIMetaEvent struct definition, with a corrected data field and pass that to the functions which require it. But note, that MIDI raw data can have massively varying size…

    I have seen many source code samples, where the users just simply access the pointers of the default data struct with exactly 1 byte allocation and actually write junk into it per UnsaveMutablePointer actions! This is a quite dangerous way, because we do not know anything about how AudioToolbox internally works…

    Even if it worked this way for many tests, there is no chance to get the correct count of bytes inside the MIDI callbacks and MusicSequence iterations, if one uses the default (wrong) SWIFT struct definitions. My approach therefore is, to cast a user defined struct definition of MIDIMetaEvent & co. with a fixed buffer of properly aligned UInt8 data taking a sufficient size.

    Tested it. It worked. Now I am able to receive Meta, Sysex and User Events with the correct count of bytes and data.
    But it took an entire day to feature this out…

    • Gene De Lisa
      Posted June 23, 2017 at 12:22 pm | Permalink

      This comes up so often that I created a blog post with the answer I usually give.

      But I’d be curious to see your “corrected struct”. Can you email me a bit of the code?

Post a Comment

Your email is never published nor shared.

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">