AUPublic/AUInstrumentBase/AUInstrumentBase.cpp

/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information
 
Abstract:
Part of Core Audio AUInstrument Base Classes
*/
 
#include "AUInstrumentBase.h"
#include "AUMIDIDefs.h"
 
#if DEBUG
    #define DEBUG_PRINT 0
    #define DEBUG_PRINT_NOTE 0
    #define DEBUG_PRINT_RENDER 0
#endif
 
////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
const UInt32 kEventQueueSize = 1024;
 
AUInstrumentBase::AUInstrumentBase(
                            AudioComponentInstance          inInstance, 
                            UInt32                          numInputs,
                            UInt32                          numOutputs,
                            UInt32                          numGroups,
                            UInt32                          numParts)
    : MusicDeviceBase(inInstance, numInputs, numOutputs, numGroups), 
    mAbsoluteSampleFrame(0),
    mEventQueue(kEventQueueSize),
    mNumNotes(0),
    mNumActiveNotes(0),
    mMaxActiveNotes(0),
    mNotes(0),
    mNoteSize(0),
    mInitNumPartEls(numParts)
{
#if DEBUG_PRINT
    printf("new AUInstrumentBase\n");
#endif
    mFreeNotes.mState = kNoteState_Free;
    SetWantsRenderThreadID(true);
}
    
 
AUInstrumentBase::~AUInstrumentBase()
{
#if DEBUG_PRINT
    printf("delete AUInstrumentBase\n");
#endif
}
 
AUElement * AUInstrumentBase::CreateElement(AudioUnitScope inScope, AudioUnitElement element)
{
    switch (inScope)
    {
        case kAudioUnitScope_Group:
            return new SynthGroupElement(this, element, new MidiControls);
        case kAudioUnitScope_Part:
            return new SynthPartElement (this, element);
    }
    return MusicDeviceBase::CreateElement(inScope, element);
}
 
void        AUInstrumentBase::CreateExtendedElements() 
{
    Parts().Initialize(this, kAudioUnitScope_Part, mInitNumPartEls);
}
 
AUScope *   AUInstrumentBase::GetScopeExtended (AudioUnitScope inScope) 
{
    if (inScope == kAudioUnitScope_Part)
        return &mPartScope;
    return NULL;
}
 
 
void        AUInstrumentBase::SetNotes(UInt32 inNumNotes, UInt32 inMaxActiveNotes, SynthNote* inNotes, UInt32 inNoteDataSize)
{
#if DEBUG_PRINT_NOTE
    printf("AUInstrumentBase::SetNotes %d %d %p %d\n", inNumNotes, inMaxActiveNotes, inNotes, inNoteDataSize);
#endif
    mNumNotes = inNumNotes;
    mMaxActiveNotes = inMaxActiveNotes;
    mNoteSize = inNoteDataSize;
    mNotes = inNotes;
    
    for (UInt32 i=0; i<mNumNotes; ++i)
    {
            SynthNote *note = GetNote(i);
            note->Reset();
            mFreeNotes.AddNote(note);
    }
}
 
UInt32      AUInstrumentBase::CountActiveNotes()
{
    // debugging tool.
    UInt32 sum = 0;
    for (UInt32 i=0; i<mNumNotes; ++i)
    {
        SynthNote *note = GetNote(i);
        if (note->GetState() <= kNoteState_Released) 
            sum++;
    }
    return sum;
}
 
void        AUInstrumentBase::AddFreeNote(SynthNote* inNote)
{
    // Fast-released notes are already considered inactive and have already decr'd the active count
    if (inNote->GetState() < kNoteState_FastReleased) {
        DecNumActiveNotes();
    }
#if DEBUG_PRINT_NOTE
    else {
            printf("AUInstrumentBase::AddFreeNote: adding fast-released note %p\n", inNote);
    }
    printf("AUInstrumentBase::AddFreeNote (%p)  mNumActiveNotes %lu\n", inNote, mNumActiveNotes);
#endif
    mFreeNotes.AddNote(inNote);
}
 
OSStatus            AUInstrumentBase::Initialize()
{
/*
TO DO:
    Currently ValidFormat will check and validate that the num channels is not being
    changed if the AU doesn't support the SupportedNumChannels property - which is correct
    
    What needs to happen here is that IFF the AU does support this property, (ie, the AU
    can be configured to have different num channels than its original configuration) then
    the state of the AU at Initialization needs to be validated.
    
    This is work still to be done - see AUEffectBase for the kind of logic that needs to be applied here
*/
 
    // override to call SetNotes
    
    mNoteIDCounter = 128; // reset this every time we initialise
    mAbsoluteSampleFrame = 0;
    return noErr;
}
 
void                AUInstrumentBase::Cleanup()
{
    mFreeNotes.Empty();
}
 
 
OSStatus            AUInstrumentBase::Reset(            AudioUnitScope                  inScope,
                                                        AudioUnitElement                inElement)
{
#if DEBUG_PRINT
    printf("AUInstrumentBase::Reset\n");
#endif
    if (inScope == kAudioUnitScope_Global)
    {
        // kill all notes..
        mFreeNotes.Empty();
        for (UInt32 i=0; i<mNumNotes; ++i)
        {
            SynthNote *note = GetNote(i);
            if (note->IsSounding()) 
                note->Kill(0);
            note->ListRemove();
            mFreeNotes.AddNote(note);
        }
        mNumActiveNotes = 0;
        mAbsoluteSampleFrame = 0;
 
        // empty lists.
        UInt32 numGroups = Groups().GetNumberOfElements();
        for (UInt32 j = 0; j < numGroups; ++j)
        {
            SynthGroupElement *group = (SynthGroupElement*)Groups().GetElement(j);
            group->Reset();
        }
    }
    return MusicDeviceBase::Reset(inScope, inElement);
}
 
void        AUInstrumentBase::PerformEvents(const AudioTimeStamp& inTimeStamp)
{
#if DEBUG_PRINT_RENDER
    printf("AUInstrumentBase::PerformEvents\n");
#endif
    SynthEvent *event;
    SynthGroupElement *group;
    
    while ((event = mEventQueue.ReadItem()) != NULL)
    {
#if DEBUG_PRINT_RENDER
        printf("event %08X %d\n", event, event->GetEventType());
#endif
        switch(event->GetEventType())
        {
            case SynthEvent::kEventType_NoteOn :
                RealTimeStartNote(GetElForGroupID (event->GetGroupID()), event->GetNoteID(),
                                    event->GetOffsetSampleFrame(), *event->GetParams());
                break;
            case SynthEvent::kEventType_NoteOff :
                RealTimeStopNote(event->GetGroupID(), event->GetNoteID(),
                    event->GetOffsetSampleFrame());
                break;
            case SynthEvent::kEventType_SustainOn :
                group = GetElForGroupID (event->GetGroupID());
                group->SustainOn(event->GetOffsetSampleFrame());
                break;
            case SynthEvent::kEventType_SustainOff :
                group = GetElForGroupID (event->GetGroupID());
                group->SustainOff(event->GetOffsetSampleFrame());
                break;
            case SynthEvent::kEventType_SostenutoOn :
                group = GetElForGroupID (event->GetGroupID());
                group->SostenutoOn(event->GetOffsetSampleFrame());
                break;
            case SynthEvent::kEventType_SostenutoOff :
                group = GetElForGroupID (event->GetGroupID());
                group->SostenutoOff(event->GetOffsetSampleFrame());
                break;
            case SynthEvent::kEventType_AllNotesOff :
                group = GetElForGroupID (event->GetGroupID());
                group->AllNotesOff(event->GetOffsetSampleFrame());
                break;
            case SynthEvent::kEventType_AllSoundOff :
                group = GetElForGroupID (event->GetGroupID());
                group->AllSoundOff(event->GetOffsetSampleFrame());
                break;
            case SynthEvent::kEventType_ResetAllControllers :
                group = GetElForGroupID (event->GetGroupID());
                group->ResetAllControllers(event->GetOffsetSampleFrame());
                break;
        }
        
        mEventQueue.AdvanceReadPtr();
    }
}
 
                                                        
OSStatus            AUInstrumentBase::Render(   AudioUnitRenderActionFlags &    ioActionFlags,
                                                const AudioTimeStamp &          inTimeStamp,
                                                UInt32                          inNumberFrames)
{
    PerformEvents(inTimeStamp);
 
    AUScope &outputs = Outputs();
    UInt32 numOutputs = outputs.GetNumberOfElements();
    for (UInt32 j = 0; j < numOutputs; ++j)
    {
        GetOutput(j)->PrepareBuffer(inNumberFrames);    // AUBase::DoRenderBus() only does this for the first output element
        AudioBufferList& bufferList = GetOutput(j)->GetBufferList();
        for (UInt32 k = 0; k < bufferList.mNumberBuffers; ++k)
        {
            memset(bufferList.mBuffers[k].mData, 0, bufferList.mBuffers[k].mDataByteSize);
        }
    }
    UInt32 numGroups = Groups().GetNumberOfElements();
    for (UInt32 j = 0; j < numGroups; ++j)
    {
        SynthGroupElement *group = (SynthGroupElement*)Groups().GetElement(j);
        OSStatus err = group->Render((SInt64)inTimeStamp.mSampleTime, inNumberFrames, outputs);
        if (err) return err;
    }
    mAbsoluteSampleFrame += inNumberFrames;
    return noErr;
}
 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  AUInstrumentBase::ValidFormat
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool                AUInstrumentBase::ValidFormat(  AudioUnitScope                  inScope,
                                                    AudioUnitElement                inElement,
                                                    const CAStreamBasicDescription  & inNewFormat)
{   
        // if the AU supports this, then we should just let this go through to the Init call
    if (SupportedNumChannels (NULL)) 
        return MusicDeviceBase::ValidFormat(inScope, inElement, inNewFormat);
 
    bool isGood = MusicDeviceBase::ValidFormat (inScope, inElement, inNewFormat);
    if (!isGood) return false;
    
        // if we get to here, then the basic criteria is that the
        // num channels cannot change on an existing bus
    AUIOElement *el = GetIOElement (inScope, inElement);
    return (el->GetStreamFormat().NumberChannels() == inNewFormat.NumberChannels()); 
}
 
 
bool                AUInstrumentBase::StreamFormatWritable( AudioUnitScope                  scope,
                                                            AudioUnitElement                element)
{
    return IsInitialized() ? false : true;
}
 
OSStatus            AUInstrumentBase::RealTimeStartNote(    SynthGroupElement           *inGroup,
                                                            NoteInstanceID              inNoteInstanceID, 
                                                            UInt32                      inOffsetSampleFrame, 
                                                            const MusicDeviceNoteParams &inParams)
{
    return noErr;
}
 
SynthPartElement *  AUInstrumentBase::GetPartElement (AudioUnitElement inPartElement)
{
    AUScope & parts = Parts();
    unsigned int numEls = parts.GetNumberOfElements();
    for (unsigned int i = 0; i < numEls; ++i) {
        SynthPartElement* el = reinterpret_cast<SynthPartElement*>(parts.GetElement(i));
        if (el->GetIndex() == inPartElement) {
            return el;
        }
    }
    return NULL;
}
 
SynthGroupElement * AUInstrumentBase::GetElForGroupID (MusicDeviceGroupID   inGroupID)
{
    AUScope & groups = Groups();
    unsigned int numEls = groups.GetNumberOfElements();
    SynthGroupElement* unassignedEl = NULL;
    
    for (unsigned int i = 0; i < numEls; ++i) {
        SynthGroupElement* el = reinterpret_cast<SynthGroupElement*>(groups.GetElement(i));
        if (el->GroupID() == inGroupID) 
            return el;
        if (el->GroupID() == SynthGroupElement::kUnassignedGroup) {
            unassignedEl = el;
            break; // we fill this up from the start of the group scope vector
        }
    }
    if (unassignedEl) {
        unassignedEl->SetGroupID(inGroupID);
        return unassignedEl;
    }
    throw static_cast<OSStatus>(kAudioUnitErr_InvalidElement);
}
 
OSStatus            AUInstrumentBase::RealTimeStopNote(
                                                MusicDeviceGroupID          inGroupID, 
                                                NoteInstanceID              inNoteInstanceID, 
                                                UInt32                      inOffsetSampleFrame)
{
#if DEBUG_PRINT
    printf("AUInstrumentBase::RealTimeStopNote ch %d id %d\n", inGroupID, inNoteInstanceID);
#endif
    
    SynthGroupElement *gp = (inGroupID == kMusicNoteEvent_Unused
                                ? GetElForNoteID (inNoteInstanceID)
                                : GetElForGroupID(inGroupID));
    if (gp)
    {
        gp->NoteOff (inNoteInstanceID, inOffsetSampleFrame);
    }
    
    return noErr;
}
 
SynthGroupElement * AUInstrumentBase::GetElForNoteID (NoteInstanceID inNoteID)
{
#if DEBUG_PRINT
    printf("GetElForNoteID id %u\n", inNoteID);
#endif
    AUScope & groups = Groups();
    unsigned int numEls = groups.GetNumberOfElements();
    
    for (unsigned int i = 0; i < numEls; ++i) {
        SynthGroupElement* el = reinterpret_cast<SynthGroupElement*>(groups.GetElement(i));
        if (el->GetNote(inNoteID) != NULL)  // searches for any note state
            return el;
    }
    throw static_cast<OSStatus>(kAudioUnitErr_InvalidElement);
}
 
OSStatus            AUInstrumentBase::StartNote(    MusicDeviceInstrumentID     inInstrument, 
                                                    MusicDeviceGroupID          inGroupID, 
                                                    NoteInstanceID *            outNoteInstanceID, 
                                                    UInt32                      inOffsetSampleFrame, 
                                                    const MusicDeviceNoteParams &inParams)
{
    OSStatus err = noErr;
    
    NoteInstanceID noteID; 
    if (outNoteInstanceID) {
        noteID = NextNoteID();
        *outNoteInstanceID = noteID;
    } else
        noteID = (UInt32)inParams.mPitch;
    
#if DEBUG_PRINT
    printf("AUInstrumentBase::StartNote ch %u, key %u, offset %u\n", inGroupID, (unsigned) inParams.mPitch, inOffsetSampleFrame);
#endif
 
    if (InRenderThread ())
    {       
        err = RealTimeStartNote(
                    GetElForGroupID(inGroupID),
                    noteID,
                    inOffsetSampleFrame,
                    inParams);
    }
    else
    {
        SynthEvent *event = mEventQueue.WriteItem();
        if (!event) return -1; // queue full
 
        event->Set(
            SynthEvent::kEventType_NoteOn,
            inGroupID,
            noteID,
            inOffsetSampleFrame,
            &inParams
        );
        
        mEventQueue.AdvanceWritePtr();
    }
    return err;
}
 
OSStatus            AUInstrumentBase::StopNote( MusicDeviceGroupID          inGroupID, 
                                                NoteInstanceID              inNoteInstanceID, 
                                                UInt32                      inOffsetSampleFrame)
{
#if DEBUG_PRINT
    printf("AUInstrumentBase::StopNote ch %u, id %u, offset %u\n", (unsigned)inGroupID, (unsigned)inNoteInstanceID, inOffsetSampleFrame);
#endif
    OSStatus err = noErr;
 
    if (InRenderThread ())
    {       
        err = RealTimeStopNote(
            inGroupID,
            inNoteInstanceID,
            inOffsetSampleFrame);
    }
    else
    {
        SynthEvent *event = mEventQueue.WriteItem();
        if (!event) return -1; // queue full
 
        event->Set(
            SynthEvent::kEventType_NoteOff,
            inGroupID,
            inNoteInstanceID,
            inOffsetSampleFrame,
            NULL
        );
        
        mEventQueue.AdvanceWritePtr();
    }
    return err;
}
 
OSStatus    AUInstrumentBase::SendPedalEvent(MusicDeviceGroupID inGroupID, UInt32 inEventType, UInt32 inOffsetSampleFrame)
{
    
    if (InRenderThread ())
    {
        SynthGroupElement *group = GetElForGroupID(inGroupID);
        if (!group)
            return kAudioUnitErr_InvalidElement;
        
        switch (inEventType)
        {
            case SynthEvent::kEventType_SustainOn :
                group->SustainOn(inOffsetSampleFrame);
                break;
            case SynthEvent::kEventType_SustainOff :
                group->SustainOff(inOffsetSampleFrame);
                break;
            case SynthEvent::kEventType_SostenutoOn :
                group->SostenutoOn(inOffsetSampleFrame);
                break;
            case SynthEvent::kEventType_SostenutoOff :
                group->SostenutoOff(inOffsetSampleFrame);
                break;
            case SynthEvent::kEventType_AllNotesOff :
                group->AllNotesOff(inOffsetSampleFrame);
                mNumActiveNotes = CountActiveNotes();
                break;
            case SynthEvent::kEventType_AllSoundOff :
                group->AllSoundOff(inOffsetSampleFrame);
                mNumActiveNotes = CountActiveNotes();
                break;
            case SynthEvent::kEventType_ResetAllControllers :
                group->ResetAllControllers(inOffsetSampleFrame);
                break;
        }
    }
    else
    {
        SynthEvent *event = mEventQueue.WriteItem();
        if (!event) return -1; // queue full
 
        event->Set(inEventType, inGroupID, 0, 0, NULL);
        
        mEventQueue.AdvanceWritePtr();
    }
    return noErr;
}
 
OSStatus    AUInstrumentBase::HandleControlChange(  UInt8   inChannel,
                                                    UInt8   inController,
                                                    UInt8   inValue,
                                                    UInt32  inStartFrame)
{
#if DEBUG_PRINT
    printf("AUInstrumentBase::HandleControlChange ch %u ctlr: %u val: %u frm: %u\n", inChannel, inController, inValue, inStartFrame);
#endif
    SynthGroupElement *gp = GetElForGroupID(inChannel);
    if (gp)
    {
        gp->ChannelMessage(inController, inValue);
    }
    else
        return kAudioUnitErr_InvalidElement;
    switch (inController)
    {
        case kMidiController_Sustain :
            if (inValue >= 64)
                SendPedalEvent(inChannel, SynthEvent::kEventType_SustainOn, inStartFrame);
            else
                SendPedalEvent(inChannel, SynthEvent::kEventType_SustainOff, inStartFrame);
            break;
        case kMidiController_Sostenuto :
            if (inValue >= 64)
                SendPedalEvent(inChannel, SynthEvent::kEventType_SostenutoOn, inStartFrame);
            else
                SendPedalEvent(inChannel, SynthEvent::kEventType_SostenutoOff, inStartFrame);
            break;
        case kMidiController_OmniModeOff:
        case kMidiController_OmniModeOn:
        case kMidiController_MonoModeOn:
        case kMidiController_MonoModeOff:
            HandleAllSoundOff(inChannel);
            break;
    }
    return noErr;
}
                                                
OSStatus    AUInstrumentBase::HandlePitchWheel(     UInt8   inChannel,
                                                    UInt8   inPitch1,   // LSB
                                                    UInt8   inPitch2,   // MSB
                                                    UInt32  inStartFrame)
{
    SynthGroupElement *gp = GetElForGroupID(inChannel);
    if (gp)
    {
        gp->ChannelMessage(kMidiMessage_PitchWheel, (inPitch2 << 7) | inPitch1);
        return noErr;
    }
    else
        return kAudioUnitErr_InvalidElement;
}
 
                                                
OSStatus    AUInstrumentBase::HandleChannelPressure(UInt8   inChannel,
                                                    UInt8   inValue,
                                                    UInt32  inStartFrame)
{
    SynthGroupElement *gp = GetElForGroupID(inChannel);
    if (gp)
    {
        gp->ChannelMessage(kMidiMessage_ChannelPressure, inValue);
        return noErr;
    }
    else
        return kAudioUnitErr_InvalidElement;
}
 
 
OSStatus    AUInstrumentBase::HandleProgramChange(  UInt8   inChannel,
                                                    UInt8   inValue)
{
#if DEBUG_PRINT
    printf("AUInstrumentBase::HandleProgramChange %u %u\n", inChannel, inValue);
#endif
    SynthGroupElement *gp = GetElForGroupID(inChannel);
    if (gp)
    {
        gp->ChannelMessage(kMidiMessage_ProgramChange, inValue);
        return noErr;
    }
    else
        return kAudioUnitErr_InvalidElement;
}
 
 
OSStatus    AUInstrumentBase::HandlePolyPressure(   UInt8   inChannel,
                                                    UInt8   inKey,
                                                    UInt8   inValue,
                                                    UInt32  inStartFrame)
{
    SynthGroupElement *gp = GetElForGroupID(inChannel);
    if (gp)
    {
        // Combine key and value into single argument.  UGLY!
        gp->ChannelMessage(kMidiMessage_PolyPressure, (inKey << 7) | inValue);
        return noErr;
    }
    else
        return kAudioUnitErr_InvalidElement;
}
 
 
OSStatus    AUInstrumentBase::HandleResetAllControllers(    UInt8   inChannel)
{
    return SendPedalEvent (inChannel, SynthEvent::kEventType_ResetAllControllers, 0);
}
 
    
OSStatus    AUInstrumentBase::HandleAllNotesOff(            UInt8   inChannel)
{
    return SendPedalEvent (inChannel, SynthEvent::kEventType_AllNotesOff, 0);
}
 
    
OSStatus    AUInstrumentBase::HandleAllSoundOff(            UInt8   inChannel)
{
    return SendPedalEvent (inChannel, SynthEvent::kEventType_AllSoundOff, 0);
}
 
SynthNote*  AUInstrumentBase::GetAFreeNote(UInt32 inFrame)
{
#if DEBUG_PRINT_NOTE
    printf("AUInstrumentBase::GetAFreeNote:  %lu available\n", mFreeNotes.Length());
#endif
    SynthNote *note = mFreeNotes.mHead;
    if (note)
    {
        mFreeNotes.RemoveNote(note);
        return note;
    }
    
    return VoiceStealing(inFrame, true);
}
 
SynthNote*  AUInstrumentBase::VoiceStealing(UInt32 inFrame, bool inKillIt)
{
 
#if DEBUG_PRINT_NOTE
    printf("AUInstrumentBase::VoiceStealing\n");
#endif
    // free list was empty so we need to kill a note.
    UInt32 startState = inKillIt ? kNoteState_FastReleased : kNoteState_Released;
    for (UInt32 i = startState; i <= startState; --i)
    {
#if DEBUG_PRINT_NOTE
        printf(" checking state %d...\n", i);
#endif
        UInt32 numGroups = Groups().GetNumberOfElements();
        for (UInt32 j = 0; j < numGroups; ++j)
        {
            SynthGroupElement *group = (SynthGroupElement*)Groups().GetElement(j);
#if DEBUG_PRINT_NOTE
            printf("\tsteal group %d   size %d\n", j, group->mNoteList[i].Length());
#endif
            if (group->mNoteList[i].NotEmpty()) {
#if DEBUG_PRINT_NOTE
                printf("\t-- not empty\n");
#endif
                SynthNote *note = group->mNoteList[i].FindMostQuietNote();
                if (inKillIt) {
#if DEBUG_PRINT_NOTE
                    printf("\t--=== KILL ===---\n");
#endif
                    note->Kill(inFrame);
                    group->mNoteList[i].RemoveNote(note);
                    if (i != kNoteState_FastReleased)
                        DecNumActiveNotes();
                    return note;
                } else {
#if DEBUG_PRINT_NOTE
                    printf("\t--=== FAST RELEASE ===---\n");
#endif
                    group->mNoteList[i].RemoveNote(note);
                    note->FastRelease(inFrame);
                    group->mNoteList[kNoteState_FastReleased].AddNote(note);
                    DecNumActiveNotes(); // kNoteState_FastReleased counts as inactive for voice stealing purposes.
                    return NULL;
                }
            }
        }
    }
#if DEBUG_PRINT_NOTE
    printf("no notes to steal????\n");
#endif
    return NULL; // It should be impossible to get here. It means there were no notes to kill in any state. 
}
 
////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
AUMonotimbralInstrumentBase::AUMonotimbralInstrumentBase(
                            AudioComponentInstance          inInstance, 
                            UInt32                          numInputs,
                            UInt32                          numOutputs,
                            UInt32                          numGroups,
                            UInt32                          numParts)
    : AUInstrumentBase(inInstance, numInputs, numOutputs, numGroups, numParts)
{
}
 
OSStatus            AUMonotimbralInstrumentBase::RealTimeStartNote( 
                                                            SynthGroupElement           *inGroup, 
                                                            NoteInstanceID              inNoteInstanceID, 
                                                            UInt32                      inOffsetSampleFrame, 
                                                            const MusicDeviceNoteParams &inParams)
{
#if DEBUG_PRINT_RENDER
    printf("AUMonotimbralInstrumentBase::RealTimeStartNote %d\n", inNoteInstanceID);
#endif
 
    if (NumActiveNotes() + 1 > MaxActiveNotes()) 
    {
        VoiceStealing(inOffsetSampleFrame, false);
    }
    SynthNote *note = GetAFreeNote(inOffsetSampleFrame);
    if (!note) return -1;
    
    SynthPartElement *part = GetPartElement (0);    // Only one part for monotimbral
    
    IncNumActiveNotes();
    inGroup->NoteOn(note, part, inNoteInstanceID, inOffsetSampleFrame, inParams);
    
    return noErr;
}
 
////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
 
OSStatus            AUMultitimbralInstrumentBase::GetPropertyInfo(AudioUnitPropertyID   inID,
                                                AudioUnitScope              inScope,
                                                AudioUnitElement            inElement,
                                                UInt32 &                    outDataSize,
                                                Boolean &                   outWritable)
{
    OSStatus result = noErr;
    
    switch (inID) 
    {
#if !TARGET_OS_IPHONE
        case kMusicDeviceProperty_PartGroup:
            if (inScope != kAudioUnitScope_Part) return kAudioUnitErr_InvalidScope;
            outDataSize = sizeof(UInt32);
            outWritable = true;
            break;
#endif
        default:
            result = AUInstrumentBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable);
    }
    return result;
}
 
OSStatus            AUMultitimbralInstrumentBase::GetProperty(  AudioUnitPropertyID     inID,
                                                AudioUnitScope              inScope,
                                                AudioUnitElement            inElement,
                                                void *                      outData)
{
    OSStatus result = noErr;
 
    switch (inID) 
    {
#if !TARGET_OS_IPHONE
        case kMusicDeviceProperty_PartGroup:
            if (inScope != kAudioUnitScope_Group) return kAudioUnitErr_InvalidScope;
            // ??
            return -1; //unimpl
            break;
#endif
        default:
            result = AUInstrumentBase::GetProperty (inID, inScope, inElement, outData);
    }
    
    return result;
}
 
 
 
OSStatus            AUMultitimbralInstrumentBase::SetProperty(  AudioUnitPropertyID             inID,
                                                                AudioUnitScope                  inScope,
                                                                AudioUnitElement                inElement,
                                                                const void *                    inData,
                                                                UInt32                          inDataSize)
{
    OSStatus result = noErr;
 
    switch (inID) 
    {
#if !TARGET_OS_IPHONE
        case kMusicDeviceProperty_PartGroup:
            if (inScope != kAudioUnitScope_Group) return kAudioUnitErr_InvalidScope;
            // ??
            return -1; //unimpl
            break;
#endif
        default:
            result = MusicDeviceBase::SetProperty (inID, inScope, inElement, inData, inDataSize);
    }
    
    return result;
}
 
////////////////////////////////////////////////////////////////////////////////////////////////////////////