Tuesday, May 1, 2012

Hot Swapping Code on a Running Erlang Node

Today's random and extremely short post covers the topic of hot swapping code on an Erlang node. I'm writing this because I've chosen to use Erlang to write a server for a soon-to-be-released mobile game (www.zombyapp.com), and had trouble figuring out how to go about connecting to a detached Erlang shell (not surprisingly, this is an app that was started from the command line using the -detached flag). The code swapping is actually the easy part. Just issue the following command at the shell, and the new code is compiled and the next time any functions from that module are called, the new code will be executed:

c("/path/to/module.erl").

The tricky part is getting connected to that detached shell. I first encountered detached shells while tinkering with Chicago Boss (which I highly recommend even though it's a bit experimental), but I suppose I should first show you how to start a detached shell though, in case you are wondering. Of course, you would probably want to start an app or something from the command line as well (a detached shell is otherwise rather useless), but here is a barebones command:

erl -sname servernode -setcookie myR4nd0m_c00k!3 -detached

Great, so we've got this detached node. Now how do we connect to it? In the following command, you can put pretty much whatever you want for the sname argument as long as it is unique. The remsh command is the interesting part; the first chunk of the remote shell info contains the registered name of the node (this is why we use the sname argument), and the hostname is somewhat dependent on your setup. Your box may be configured to accept Erlang connections via a fully-qualified hostname. However, the default behavior seems to be looking for just the short hostname, as given by hostname -s.

erl -sname node1 -remsh servernode@hostname -setcookie myR4nd0m_c00k!3

Monday, April 30, 2012

Of Audio Units and Xcode 4.3

<GladosVoice>If you've dabbled much in Computer Music, you've probably heard of Steinburg's Virtual Studio Technology (aka VST). However, after the end of this paragraph, I will not be talking about that anymore, so you can just forget I mentioned it. It's really not every important at all except that it does pretty much the same thing as Audio Units and almost everybody has heard of it.</GladosVoice>

So back in the day, Apple came up with their own software synthesizer/effect plugin system called Audio Units. An Audio Unit is a plugin (specifically a bundle with a .component extension), written in C++, which can be used with the vast majority of pro audio software written for Mac OS X (GarageBand, Logic Pro, Ableton Live, etc.). Audio Units (part of Core Audio) are also available on iOS devices, but they are created via a different process which is quite different from making a Mac Audio Unit, so we won't be discussing that either (you can EASILY find documentation on how to write an iOS Audio Unit; this blog is for arcane stuff, remember?).

Enough of the (very interesting) background; why are you reading this article? Well, you probably fit into one of two categories. Either 1) You're curious about writing your own software synth (or effect), or 2) you're more than curious about writing your own software synth (or effect). I suspect most of you are in category 2, and furthermore that most of you have scoured the internet looking for a good tutorial on how to make an Audio Unit (there are surprisingly few) Instrument (most tutorials cover effects... why?) in Xcode 4.3+ (Apple broke a BUNCH of crap over the last few years for Audio Units developers and it's nearly impossible to find a tutorial that covers all the stuff they changed and broke). So, what I'm here to do is help you get an Audio Unit instrument project together that will actually compile, link, and make sound! By the way, this article was very helpful in pointing me in the right direction, and I think they deserve some credit: http://sample-hold.com/2011/11/23/getting-started-with-audio-units-on-os-x-lion-and-xcode-4-2-1/

So, let's get started by opening Xcode. Novel idea, eh? Since version 4.3, they've made Xcode a legit "App Bundle" like every other app in the App Store, and in the process they moved some things and left out some extraneous packages. Apparently Core Audio didn't make the cut so we've gotta grab the first. From the Xcode menu, select Open Developer Tool > More Developer Tools. Sign in to ADC with your Apple ID and download the "Audio Tools for Xcode" package. You can dump all that stuff wherever you want, but I've chosen to put it in /DeveloperExtras. I didn't want to use the "/Developer/Extras" location since that's where Xcode used to keep it's stuff, so I went with a new "safe" folder name (/DeveloperExtras). Just copy the whole shebang over to there.

Ok, so now we've got the whole Core Audio SDK. Awesome. Now let's create a project. Back in the day, there was this handy template to create an Audio Unit, but it's not there anymore (which is why you're reading this). Instead, we're gonna select a Mac OS X > Framework & Library > Bundle template (select Cocoa instead of CoreFoundation when prompted) and then add all the junk we need. Call it "My First Synth" or something (that's the name I'll use for the rest of this post). Let's start off with the fun stuff: the obligatory plist. Open it up and add an array called AudioComponents (it is an array, not a dictionary; I've seen several tutorials get this wrong. See the Apple Technical Note here: http://developer.apple.com/library/mac/#technotes/tn2276/_index.html#//apple_ref/doc/uid/DTS40011031). Inside this array, make the first entry a dictionary with the following keys:

  • description: My First Synth
  • factoryFunction: MyFirstSynthFactory
  • manufacturer: OSUM (for now, any 4-letter code will do; if you want to distribute Audio Units, you will need to eventually get an official one from Apple, but I thought that OSUM looked awesome)
  • name: My First Synth
  • subtype: TEST (again, there are rules regarding this field when distributing Audio Units, but for development, we will use TEST)
  • type: aumu (this field DOES matter; aumu is the type code for an Audio Unit MUsical instrument)
  • version: 0xFFFFFFFF (set the type of this field to number; the Apple Tech Note actually gets this wrong in the screenshot! If you want to know how this version number field works, you can find information elsewhere since it's not important now)
Next, head on over to the target's Build Settings and change the following fields:
  • Architectures: Standard (32/64-bit Intel)
  • Build Active Architecture Only: No
  • Other Linker Flags: -bundle
  • Wrapper Extension: component
  • Rez Search Paths (yes; Audio Units is an old SDK since we're referencing CarbonCore)
    • /DeveloperExtras/CoreAudio/AudioUnits/AUPublic/AUBase
    • /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Versions/A/Headers
  • Precompile Prefix Header: Yes
  • Exported Symbols File: $(SRCROOT)/$(PROJECT_NAME)/AU.exp
Next, we're going to create 4 files. First, add a C++ file and a header file. You can call them whatever (MyFirstSynth.h/cpp)? Then, add a similarly named file that ends in .exp (I used AU.exp above; the template is under Mac OS X > Other > Exports File in the templates browser). Finally, create another file (using the empty file template) that ends in .r (a Carbon Resource file; this is necessary for the plugin to work in 32-bit hosts).

Now we're gonna head back over to the project settings area and check out the build phases. Make sure the C++ file you created got added to the Compile Sources phase, and that the Carbon Resource file got added to the Build Carbon Resources phase. If the previously mentioned phase doesn't exist, not to worry; we can add it via the Add Build Phase button (put this phase after the Compile Sources phase). One more build phase to edit: delete all of the stuff that is in the Link with Binary Libraries phase (including Cocoa.framework) and add the following:
  • AudioUnit.framework
  • AudioToolbox.framework
  • CoreServices.framework
We are almost done with the project setting changes; just one more thing. Now that we've added a carbon resource, we are able to set the Other Rez Flags property in the Build Settings:
  •  -d i386_$i386 -I /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Versions/A/Headers -I /DeveloperExtras/CoreAudio/AudioUnits/AUPublic/AUBase
One more final bit of boring-ness related to the project setup. Go to the folder where you put the CoreAudio files (/DeveloperExtras/CoreAudio?) and drag the following files into the project. Make sure to uncheck the box to copy the files into the project directory and check the box to add the files to the target.

  • AudioUnits/
    • AUPublic/
      • AUBase/
        • AUBase.cpp
        • AUBase.h
        • AUDispatch.cpp
        • AUDispatch.h
        • AUInputElement.cpp
        • AUInputElement.h
        • AUOutputElement.cpp
        • AUOutputElement.h
        • AUPlugInDispatch.cpp
        • AUPlugInDispatch.h
        • AUScopeElement.cpp
        • AUScopeElement.h
        • ComponentBase.cpp
        • ComponentBase.h
      • AUInstrumentBase/
        • AUInstrumentBase.cpp
        • AUInstrumentBase.h
        • LockFreeFIFO.h
        • MIDIControlHandler.h
        • SynthElement.cpp
        • SynthElement.h
        • SynthEvent.h
        • SynthNote.cpp
        • SynthNote.h
        • SynthNoteList.cpp
        • SynthNoteList.h
      • OtherBases/
        • AUMIDIBase.cpp
        • AUMIDIBase.h
        • MusicDeviceBase.cpp
        • MusicDeviceBase.h
      • Utility/
        • AUBaseHelper.cpp
        • AUBaseHelper.h
        • AUBuffer.cpp
        • AUBuffer.h
        • AUMIDIDefs.h
  • PublicUtility/
    • CAAudioChannelLayout.cpp
    • CAAudioChannelLayout.h
    • CAAUMIDIMap.cpp
    • CAAUMIDIMap.h
    • CAAUMIDIMapManager.cpp
    • CAAUMIDIMapManager.h
    • CAAutoDisposer.h
    • CABufferList.cpp
    • CABufferList.h
    • CADebugMacros.cpp
    • CADebugMacros.h
    • CAHostTimeBase.cpp
    • CAHostTimeBase.h
    • CAMath.h
    • CAStreamBasicDescription.cpp
    • CAStreamBasicDescription.h
    • CAThreadSafeList.h
    • CAVectorUnit.cpp
    • CAVectorUnit.h
    • CAVectorUnitTypes.h
    • CAXException.cpp
    • CAXException.h

<Yawn>... You still awake? <Grabs another cup of coffee>... Now we're done with a good chunk of the boring stuff and we can actually edit some files. Remember that Carbon resource file we made? Well, we're pretty much gonna drop the same info that we put in the plist file in here (just in a slightly different format). Quick note on this part. My resource file is significantly longer than the one found in most tutorials online. This is because I pasted Apple's AUResources.r at the end of the "interesting" stuff. For the life of me, I'm unable to get past the rez error I encountered while trying to include Apple's file normally, so I just pasted it in. Also, I have no idea what the crap RES_ID is. If anyone knows what this is, let me know :)

#include <AudioUnit/AudioUnit.r>

#define RES_ID    1000
#define COMP_TYPE 'aumu'
#define COMP_SUBTYPE 'TEST'
#define COMP_MANUF 'OSUM'
#define VERSION 0xFFFFFFFF
#define NAME "My First Synth"
#define DESCRIPTION "My First Synth"
#define ENTRY_POINT "MyFirstSynthEntry"

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // AUResources.r
 //
 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

/* sample macro definitions -- all of these symbols must be defined
 #define RES_ID kHALOutputResID
 #define COMP_TYPE kAudioUnitComponentType
 #define COMP_SUBTYPE kAudioUnitOutputSubType
 #define COMP_MANUF kAudioUnitAudioHardwareOutputSubSubType
 #define VERSION 0x00010000
 #define NAME "AudioHALOutput"
 #define DESCRIPTION "Audio hardware output AudioUnit"
 #define ENTRY_POINT "AUHALEntry"
 */
#define UseExtendedThingResource 1

#include <CoreServices/CoreServices.r>

// this is a define used to indicate that a component has no static data that would mean 
// that no more than one instance could be open at a time - never been true for AUs
#ifndef cmpThreadSafeOnMac
#define cmpThreadSafeOnMac 0x10000000
#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

resource 'STR ' (RES_ID, purgeable) {
NAME
};

resource 'STR ' (RES_ID + 1, purgeable) {
DESCRIPTION
};

resource 'dlle' (RES_ID) {
ENTRY_POINT
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

resource 'thng' (RES_ID, NAME) {
COMP_TYPE,
COMP_SUBTYPE,
COMP_MANUF,
0, 0, 0, 0, // no 68K
'STR ', RES_ID,
'STR ', RES_ID + 1,
0, 0, /* icon */
VERSION,
componentHasMultiplePlatforms | componentDoAutoVersion,
0,
{
        #if defined(ppc_YES)
        cmpThreadSafeOnMac,
        'dlle', RES_ID, platformPowerPCNativeEntryPoint
        #define NeedLeadingComma 1
        #endif
        #if defined(ppc64_YES)
#if defined(NeedLeadingComma)
        ,
#endif
        cmpThreadSafeOnMac,
        'dlle', RES_ID, platformPowerPC64NativeEntryPoint
        #define NeedLeadingComma 1
        #endif
        #if defined(i386_YES)
#if defined(NeedLeadingComma)
        ,
#endif
        cmpThreadSafeOnMac,
        'dlle', RES_ID, platformIA32NativeEntryPoint
        #define NeedLeadingComma 1
        #endif
        #if defined(x86_64_YES)
#if defined(NeedLeadingComma)
        ,
#endif
        cmpThreadSafeOnMac,
        'dlle', RES_ID, 8
        #define NeedLeadingComma 1
        #endif
}
};

#undef RES_ID
#undef COMP_TYPE
#undef COMP_SUBTYPE
#undef COMP_MANUF
#undef VERSION
#undef NAME
#undef DESCRIPTION
#undef ENTRY_POINT
#undef NeedLeadingComma


Before we get to a "normal" source file, we've got one more weird file–the exported symbols file. Open it up and add the following lines:

_MyFirstSynthFactory
_MyFirstSynthEntry


Next up is the prefix header file.

#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
#include <CoreAudio/CoreAudioTypes.h>
#include <CoreFoundation/CoreFoundation.h>
#else
#include <CoreAudioTypes.h>
#include <CoreFoundation.h>
#endif

#include <CoreServices/CoreServices.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioUnit/AudioUnit.h>


#include <AudioToolbox/AudioToolbox.h>

Ok, we're pretty much done with the "boilerplate" junk now. Here's our first real source file, the Audio Unit header (this goes in MyFirstSynth.h or AU.h or whatever you called it). Most of this is straight from Apple's SinSynth project. You're probably wondering at this point what's wrong with Apple's project and why you can't change a few things like the Tech Note says. As it turns out, I spent about 10 hours trying to figure out why Apple's code wouldn't compile. If I remember correctly, the only major thing that needed fixing in this file (which is reflected in my code below) is that the SynthNote struct's Render method prototype has changed, and SinSynth was never updated. Sorry in advance for the lack of indentation (bad blogger, bad). Xcode will fix it when you paste it in :)

#include "AUInstrumentBase.h"

#ifndef MyFirstSynth_AU_h
#define MyFirstSynth_AU_h

static const UInt32 kNumNotes = 12;

struct TestNote : public SynthNote
{
virtual ~TestNote() {}
    
virtual bool Attack(const MusicDeviceNoteParams &inParams);
virtual void Kill(UInt32 inFrame); // voice is being stolen.
virtual void Release(UInt32 inFrame);
virtual void FastRelease(UInt32 inFrame);
virtual Float32 Amplitude() { return amp; } // used for finding quietest note for voice stealing.
virtual OSStatus Render(UInt64 inAbsoluteSampleFrame, UInt32 inNumFrames, AudioBufferList** inBufferList, UInt32 inOutBusCount);
    
double phase, amp, maxamp, susamp;
double up_slope, decay_slope, dn_slope, fast_dn_slope;
};

class MyFirstSynth : public AUMonotimbralInstrumentBase
{
public:
    MyFirstSynth(ComponentInstance inComponentInstance);
virtual ~MyFirstSynth();
    
virtual OSStatus Initialize();
virtual void Cleanup();
virtual OSStatus Version() { return 0xFFFFFF; }
    
virtual OSStatus GetParameterInfo(AudioUnitScope inScope,
                                                 AudioUnitParameterID inParameterID,
                                                 AudioUnitParameterInfo & outParameterInfo);
private:
TestNote mTestNotes[kNumNotes];
};

#endif

Finally, we get to the C++ source file! The primary thing that I had to change here to get it to compile was again the prototype of the Render method. Because of some parameter changes, there were some slight modifications to the render file's code as well.

I also made a few "improvements" to the basic sine wave synth. Well, the first improvement isn't really an enhancement; I just changed it from a sine wave generator to a square wave generator. As you might imagine, the Render method is going to be the starting point for any customizations. Go read some books on sound design to make some more interesting sounds :)

The other change actually is an improvement. If you've done much with synthesizers, you've probably seen the standard ADSR (Attack, Decay, Sustain, Release) envelope controls. These allow some basic control over the sound since a synth that immediately plays a sound at full volume when the key is pressed, and stops immediately when the key is released is quite boring in most cases. Do a search through the file for kGlobal and you'll get the hang of how to define and use parameters, which will show up in an auto-generated GUI in AU hosts like GarageBand.

#include "AU.h"

AUDIOCOMPONENT_ENTRY(AUMusicDeviceFactory, MyFirstSynth)

static const UInt32 kMaxActiveNotes = 8;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const double twopi = 2.0 * 3.14159265358979;

inline double pow5(double x) { double x2 = x*x; return x2*x2*x; }

#pragma mark MyFirstSynth Methods

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static const AudioUnitParameterID kGlobalVolumeParam = 0;
static const CFStringRef kGlobalVolumeName = CFSTR("Volume");

static const AudioUnitParameterID kGlobalAttackParam = 1;
static const CFStringRef kGlobalAttackName = CFSTR("Attack");

static const AudioUnitParameterID kGlobalDecayParam = 2;
static const CFStringRef kGlobalDecayName = CFSTR("Decay");

static const AudioUnitParameterID kGlobalSustainParam = 3;
static const CFStringRef kGlobalSustainName = CFSTR("Sustain");

static const AudioUnitParameterID kGlobalReleaseParam = 4;
static const CFStringRef kGlobalReleaseName = CFSTR("Release");

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MyFirstSynth::MyFirstSynth
//
// This synth has No inputs, One output
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MyFirstSynth::MyFirstSynth(ComponentInstance inComponentInstance)
: AUMonotimbralInstrumentBase(inComponentInstance, 0, 1)
{
CreateElements();
Globals()->UseIndexedParameters(5); // we're only defining one param
Globals()->SetParameter(kGlobalVolumeParam, 1.0);
Globals()->SetParameter(kGlobalAttackParam, 0.01);
    Globals()->SetParameter(kGlobalDecayParam, 0.75);
    Globals()->SetParameter(kGlobalSustainParam, 0.5);
    Globals()->SetParameter(kGlobalReleaseParam, 0.75);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MyFirstSynth::~MyFirstSynth
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MyFirstSynth::~MyFirstSynth()
{
}


void MyFirstSynth::Cleanup()
{
#if DEBUG_PRINT
printf("MyFirstSynth::Cleanup\n");
#endif
}

OSStatus MyFirstSynth::Initialize()
{
#if DEBUG_PRINT
printf("->MyFirstSynth::Initialize\n");
#endif
AUMonotimbralInstrumentBase::Initialize();
    
SetNotes(kNumNotes, kMaxActiveNotes, mTestNotes, sizeof(TestNote));
#if DEBUG_PRINT
printf("<-MyFirstSynth::Initialize\n");
#endif
return noErr;
}

OSStatus MyFirstSynth::GetParameterInfo(AudioUnitScope inScope,
                                                   AudioUnitParameterID inParameterID,
                                                   AudioUnitParameterInfo & outParameterInfo)
{
if (inParameterID == kGlobalVolumeParam) {
        if (inScope != kAudioUnitScope_Global) return kAudioUnitErr_InvalidScope;
        
        outParameterInfo.flags = SetAudioUnitParameterDisplayType (0, kAudioUnitParameterFlag_DisplaySquareRoot);
        outParameterInfo.flags += kAudioUnitParameterFlag_IsWritable;
        outParameterInfo.flags += kAudioUnitParameterFlag_IsReadable;
        
        AUBase::FillInParameterName (outParameterInfo, kGlobalVolumeName, false);
        outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain;
        outParameterInfo.minValue = 0;
        outParameterInfo.maxValue = 1.0;
        outParameterInfo.defaultValue = 1.0;
        return noErr;
    }
    else if (inParameterID == kGlobalAttackParam) {
        if (inScope != kAudioUnitScope_Global) return kAudioUnitErr_InvalidScope;
        
        outParameterInfo.flags = SetAudioUnitParameterDisplayType (0, kAudioUnitParameterFlag_DisplaySquareRoot);
        outParameterInfo.flags += kAudioUnitParameterFlag_IsWritable;
        outParameterInfo.flags += kAudioUnitParameterFlag_IsReadable;
        
        AUBase::FillInParameterName (outParameterInfo, kGlobalAttackName, false);
        outParameterInfo.unit = kAudioUnitParameterUnit_Seconds;
        outParameterInfo.minValue = 0.001;
        outParameterInfo.maxValue = 3.0;
        outParameterInfo.defaultValue = 0.01;
        return noErr;
    }
    else if (inParameterID == kGlobalDecayParam) {
        if (inScope != kAudioUnitScope_Global) return kAudioUnitErr_InvalidScope;
        
        outParameterInfo.flags = SetAudioUnitParameterDisplayType (0, kAudioUnitParameterFlag_DisplaySquareRoot);
        outParameterInfo.flags += kAudioUnitParameterFlag_IsWritable;
        outParameterInfo.flags += kAudioUnitParameterFlag_IsReadable;
        
        AUBase::FillInParameterName (outParameterInfo, kGlobalDecayName, false);
        outParameterInfo.unit = kAudioUnitParameterUnit_Seconds;
        outParameterInfo.minValue = 0;
        outParameterInfo.maxValue = 3.0;
        outParameterInfo.defaultValue = 0.75;
        return noErr;
    }
    else if (inParameterID == kGlobalSustainParam) {
        if (inScope != kAudioUnitScope_Global) return kAudioUnitErr_InvalidScope;
        
        outParameterInfo.flags = SetAudioUnitParameterDisplayType (0, kAudioUnitParameterFlag_DisplaySquareRoot);
        outParameterInfo.flags += kAudioUnitParameterFlag_IsWritable;
        outParameterInfo.flags += kAudioUnitParameterFlag_IsReadable;
        
        AUBase::FillInParameterName (outParameterInfo, kGlobalSustainName, false);
        outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain;
        outParameterInfo.minValue = 0;
        outParameterInfo.maxValue = 1.0;
        outParameterInfo.defaultValue = 0.5;
        return noErr;
    }
    else if (inParameterID == kGlobalReleaseParam) {
        if (inScope != kAudioUnitScope_Global) return kAudioUnitErr_InvalidScope;
        
        outParameterInfo.flags = SetAudioUnitParameterDisplayType (0, kAudioUnitParameterFlag_DisplaySquareRoot);
        outParameterInfo.flags += kAudioUnitParameterFlag_IsWritable;
        outParameterInfo.flags += kAudioUnitParameterFlag_IsReadable;
        
        AUBase::FillInParameterName (outParameterInfo, kGlobalReleaseName, false);
        outParameterInfo.unit = kAudioUnitParameterUnit_Seconds;
        outParameterInfo.minValue = 0.001;
        outParameterInfo.maxValue = 3.0;
        outParameterInfo.defaultValue = 0.75;
        return noErr;
    }
    else {
        return kAudioUnitErr_InvalidParameter;
    }
}


#pragma mark TestNote Methods

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

bool TestNote::Attack(const MusicDeviceNoteParams &inParams)
#if DEBUG_PRINT
    printf("TestNote::Attack %08X %d\n", this, GetState());
#endif
    double sampleRate = SampleRate();
    phase = 0.;
    amp = 0.;
    maxamp = 0.4 * pow(inParams.mVelocity/127., 3.);
    susamp = GetGlobalParameter(kGlobalSustainParam) * maxamp;
    up_slope = maxamp / (GetGlobalParameter(kGlobalAttackParam) * sampleRate);
    decay_slope = (susamp-maxamp) / (GetGlobalParameter(kGlobalDecayParam) * sampleRate);
    dn_slope = -maxamp / (GetGlobalParameter(kGlobalReleaseParam) * sampleRate);
    fast_dn_slope = -maxamp / (0.005 * sampleRate);
    
    return true;
}

void TestNote::Release(UInt32 inFrame)
{
SynthNote::Release(inFrame);
#if DEBUG_PRINT
printf("TestNote::Release %08X %d\n", this, GetState());
#endif
}

void TestNote::FastRelease(UInt32 inFrame) // voice is being stolen.
{
SynthNote::Release(inFrame);
#if DEBUG_PRINT
printf("TestNote::Release %08X %d\n", this, GetState());
#endif
}

void TestNote::Kill(UInt32 inFrame) // voice is being stolen.
{
SynthNote::Kill(inFrame);
#if DEBUG_PRINT
printf("TestNote::Kill %08X %d\n", this, GetState());
#endif
}

OSStatus TestNote::Render(UInt64 inAbsoluteSampleFrame, UInt32 inNumFrames, AudioBufferList** inBufferList, UInt32 inOutBusCount)
{
float *left, *right;
    /* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
     Changes to this parameter (kGlobalVolumeParam) are not being de-zippered; 
     Left as an exercise for the reader
     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ */
float globalVol = GetGlobalParameter(kGlobalVolumeParam);
int numChans = inBufferList[0]->mNumberBuffers;
if (numChans > 2) return -1;
left = (float*)inBufferList[0]->mBuffers[0].mData;
right = numChans == 2 ? (float*)inBufferList[0]->mBuffers[1].mData : 0;
    
double sampleRate = SampleRate();
double freq = Frequency() * (twopi/sampleRate);
    
#if DEBUG_PRINT_RENDER
printf("TestNote::Render %08X %d %g %g\n", this, GetState(), phase, amp);
#endif
    SynthNoteState state = GetState();
    
switch (state)
{
case kNoteState_Attacked :
case kNoteState_Sostenutoed :
case kNoteState_ReleasedButSostenutoed :
case kNoteState_ReleasedButSustained :
        {
            for (UInt32 frame=0; frame<inNumFrames; ++frame)
            {
                if (amp < maxamp && state == kNoteState_Attacked) {
                    amp += up_slope;
                    
                    if (amp >= maxamp) {
                        SetState(kNoteState_Sostenutoed);
                    }
                }
                else if (amp >= susamp && state == kNoteState_Sostenutoed) {
                    amp += decay_slope;
                    dn_slope = -amp / (GetGlobalParameter(kGlobalReleaseParam) * sampleRate);
                }
                
                //float out = pow5(sin(phase)) * amp * globalVol;
                float out = (sin(phase) > 0 ? 1 : -1) * amp * globalVol;
                phase += freq;
                if (phase > twopi) phase -= twopi;
                left[frame] += out;
                if (right) right[frame] += out;
            }
        }
break;
case kNoteState_Released :
        {
            UInt32 endFrame = 0xFFFFFFFF;
            for (UInt32 frame=0; frame<inNumFrames; ++frame)
            {
                if (amp > 0.0) amp += dn_slope;
                else if (endFrame == 0xFFFFFFFF) endFrame = frame;
                //float out = pow5(sin(phase)) * amp * globalVol;
                float out = (sin(phase) > 0 ? 1 : -1) * amp * globalVol;
                phase += freq;
                left[frame] += out;
                if (right) right[frame] += out;
            }
            if (endFrame != 0xFFFFFFFF) {
#if DEBUG_PRINT
                printf("TestNote::NoteEnded  %08X %d %g %g\n", this, GetState(), phase, amp);
#endif
                NoteEnded(endFrame);
            }
        }
break;
case kNoteState_FastReleased :
        {
            UInt32 endFrame = 0xFFFFFFFF;
            for (UInt32 frame=0; frame<inNumFrames; ++frame)
            {
                if (amp > 0.0) amp += fast_dn_slope;
                else if (endFrame == 0xFFFFFFFF) endFrame = frame;
                float out = pow5(sin(phase)) * amp * globalVol;
                phase += freq;
                left[frame] += out;
                if (right) right[frame] += out;
            }
            if (endFrame != 0xFFFFFFFF) {
#if DEBUG_PRINT
                printf("TestNote::NoteEnded  %08X %d %g %g\n", this, GetState(), phase, amp);
#endif
                NoteEnded(endFrame);
            }
        }
break;
default :
break;
}
return noErr;
}

Now, if all goes well, you should be able to build the Audio Unit. If not, please let me know and I'll figure out what I missed in the tutorial. Once you've got a working component, copy it from the build folder into ~/Library/Audio/Plug-Ins/Components. You can validate your new plug-in by issuing the following command from the terminal:
  • auval -v aumu TEST OSUM
If all goes well, you've got a valid Audio Unit that you can test out in AU Lab or GarageBand!