OS X CoreAudio Host Tutorial
First of all, let's make clear that I'm not an expert at either ObjC or CoreAudio programming. While trying to learn both, I read the CoreAudio Developer Documentation and several example applications.
The Apple documentation is very informative from a technical point of view, while not explaining how one would set up and interface the nessecary parts.
Some example applications show limited functionality (a good thing while learning!) but come with a nice gui, also obfuscating how things work.
I've compiled some very basic functionality in a little library and test "application", using AudioGraphs from the Audio Toolbox. This tutorial is thus only suitable for complete newbies, so if you are one, this tutorial is for you! To make things extra simple: this is a console application, not made with XCode. Use an Xterm (comes with the X11 package) to compile this package (typing 'make' in the AudioUnits directory of the package).
Introduction
AudioUnits are normal Apple components and are treated as such. AudioUnits come in several types:
* Output AudioUnits: these, well gee, take input from another node and output it to the audio device
* Mixer AudioUnits: take input from several AudioUnits and combine the input before outputting it to another AudioUnit
* Effects AudioUnits: these generally take input from other AudioUnits and perform some transformation on it, then output it to another AudioUnit
* Music device AudioUnits: these AudioUnits generate audio while not taking input from other AudioUnits
This is not a complete list, but I suppose you get the idea.
An AudioGraph connects several AudioUnits into a tree and manages the tree while playing, adding or deleting AudioUnits. An AudioGraph consists of one output-AudioUnit and one or more AudioUnits supplying the output with audio data.
Coding
Creating an AudioUnit instance is done by filling a ComponentDescription variable and passing it to AUGraphNewNode(). However, we first have to create an AudioGraph! Here we go:
AUGraph AudioGraph;
NewAUGraph(&AudioGraph);
Although all types of AudioUnits can be added in any order, it'd be a nice choice if we first create the output node. We'll have to fill a ComponentDescription, create an AUNode in the graph and capture the AudioUnit instance of the AudioUnit selected by the parameters in the ComponentDescription:
ComponentDescription cd;
AUNode OutputNode;
AudioUnit OutputUnit;
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
cd.componentType = kAudioUnitType_Output;
cd.componentSubType = kAudioUnitSubType_DefaultOutput;
AUGraphNewNode(AudioGraph, &cd, 0, NULL, &OutputNode);
AUGraphGetNodeInfo(AudioGraph, OutputNode, 0, 0, 0, &OutputUnit);
An AUNode is used by the AUGraph framework, while we can talk i.e. MIDI to the AudioUnit, which is probably what we want. Now this particular output AudioUnit can only handle a single input. So in case we would ever want to hear more than one "instrument", we'll have to connect a mixer AudioUnit to the output:
AUNode MixerNode;
AudioUnit MixerUnit;
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
cd.componentType = kAudioUnitType_Mixer;
cd.componentSubType = kAudioUnitSubType_StereoMixer;
AUGraphNewNode(AudioGraph, &cd, 0, NULL, &MixerNode);
AUGraphGetNodeInfo(AudioGraph, MixerNode, 0, 0, 0, &MixerUnit);
Hmm that's not right?! This was the code for creating an AudioUnit instance. But notice we created a mixer unit this time. Ok well now we're going to connect them:
AUGraphConnectNodeInput(AudioGraph, MixerNode, 0, OutputNode, 0);
That was simple enough. Told you it was a newbie tutorial! Well, there's more to tell you about the zeroes in the arguments to this function. AUGraphConnectNodeInput takes an AUGraph, then the source and the destination nodes. All nodes have a number of input and output buses. Both the input and output buses are number 0, of which the number itself doesn't have a significance, but you can't connect a bus twice. I'll explain more of this later.
Now's a good time to 'start' the graph, to get it to output sound as soon as it's available. These three functions are nessecary to start the AUGraph:
AUGraphOpen(AudioGraph);
AUGraphInitialize(AudioGraph);
AUGraphStart(AudioGraph);
But hey, there's no music device AudioUnit yet! There's no sound to be heard! Well you guessed it, another AudioUnit is going to be initialized and added:
AUNode SynthNode;
AudioUnit SynthUnit;
cd.componentManufacturer = kAudioUnitManufacturer_Apple;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
cd.componentType = kAudioUnitType_MusicDevice;
cd.componentSubType = kAudioUnitSubType_DLSSynth;
AUGraphNewNode(AudioGraph, &cd, 0, NULL, &SynthNode);
AUGraphGetNodeInfo(AudioGraph, SynthNode, 0, 0, 0, &SynthUnit);
AUGraphConnectNodeInput(AudioGraph, SynthNode, 0, MixerNode, 0);
Notice that the ComponentDescription now describes the stock provided DLSSynth unit. The synth unit is connected via output bus 0 to the mixer input bus 0. If we want to add another unit to the mixer, we'd have to connect to mixer bus 1, like this: AUGraphConnectNodeInput(AudioGraph, OtherSynthNode, 0, MixerNode, 1);. But for now, we're desperate for just some sound emitting from our program, so we'll continue without adding another synth.
Since we already started the AUGraph with the AUGraphOpen(), -Initialize() and -Start() functions, we'll have to update the graph. This can be very useful: when an AUGraph is playing, one can add and delete units as one pleases while not disturbing the played output until one calls AUGraphUpdate.
AUGraphUpdate(AudioGraph, NULL);
CAShow(AudioGraph);
AUGraphUpdate can also take a pointer to a Boolean variable instead of the NULL, which will try to update the graph without interrupting the audio playback. However, be advised that the function might fail to update the graph when supplying a variable instead of NULL.
The CAShow function shows the setup of the AUGraph we created. You'll understand the significance when you're fiddling around with AudioUnits a bit.
We're almost done now! You've probably noticed that the synth unit doesn't do anything until we command it, so we'll add a few MIDI events:
MusicDeviceMIDIEvent(SynthUnit, 0x90, 60, 127, 0);
sleep(1);
MusicDeviceMIDIEvent(SynthUnit, 0x90, 62, 127, 0);
sleep(1);
MusicDeviceMIDIEvent(SynthUnit, 0x90, 64, 127, 0);
sleep(1);
sleep(5);
Ok done! Well ok we're not.. There's still some compiling to do, and some header files need to be included. So, scroll back to the top of your code and add these lines, as well as the main() function holding all this code if you haven't already:
#include <CoreAudio/CoreAudio.h>
#include <AudioToolbox/AudioToolbox.h>
Time to compile and run! Here goes:
gcc -o AUTest AUTest.c -framework AudioUnit -framework CoreAudio -framework AudioToolbox
./AUTest
The -framework flags import the nessecary frameworks into the program. When running the code, you should've heard a few piano strokes.
In real life, most of the used functions should be checked for failures! But now that you know how to set up an AUGraph, it should be a good idea to fiddle around with the program and add the nessecary checks! See the CoreAudio documentation. The AUGraph documentation is in the Audio Toolbox chapter.
In another tutorial, I'll explain how to retrieve the ComponentDescriptions and names of all AudioUnits on the system, which is practical when you want to supply the user with a dropdown list of AudioUnits.
Download the sourcecode of the example: AUTest.c
The library I was writing about earlier implements a simple wrapper around the AUGraph functions and is written in ObjC. You might want to use and extend it, so it's also available for download. Be advised that this library is theoretically buggy, since i.e. the InputLastConnected and OutputLastConnected variables are not checked for overflows.
Download the sourcecode of the library: AudioUnits.tar.gz
See other tutorials: OS X Tutorials.
If you have any comments, please mail me at QdK@quickdekay.net