/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "HeadSpin.h"
#include "plComponentProcBase.h"

#include "resource.h"
#include "plComponent.h"
#include "plComponentReg.h"
#include <map>
#include "plAudioComponents.h"
#include "plMiscComponents.h"
#include "plAnimComponent.h"
#include "plInterp/plAnimEaseTypes.h"
#include "plAvatar/plAGAnim.h"

#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"

#include "MaxConvert/plConvert.h"
#include "MaxMain/plPluginResManager.h"
#include "MaxMain/plPlasmaRefMsgs.h"


#include "plgDispatch.h"
#include "pnMessage/plObjRefMsg.h"
#include "pnMessage/plIntRefMsg.h"
#include "pnMessage/plNodeRefMsg.h"


#include "plScene/plSceneNode.h"
#include "MaxConvert/hsConverterUtils.h"
#include "MaxConvert/hsControlConverter.h"
#include "plInterp/plController.h"
#include "MaxMain/plMaxNode.h"
#include "pnKeyedObject/plKey.h"

//Physics Related
//#include "../plHavok1/plHKPhysical.h"         //Physics Comp
#include "pnSceneObject/plSimulationInterface.h"
#include "MaxMain/plPhysicalProps.h"
#include "plPhysX/plPXPhysical.h"

// Sound Related
#include "plPhysical/plEnvEffectDetector.h"
#include "pnMessage/plEnvEffectMsg.h"
#include "plAudible/plWinAudible.h"
#include "pnSceneObject/plAudioInterface.h"

// Anim Related
#include "plMaxAnimUtils.h"
#include "plMaxWaveUtils.h"
#include "pfAudio/plRandomSoundMod.h"
#include "plAudio/plWin32StaticSound.h"
#include "plAudio/plWin32StreamingSound.h"
#include "plAudio/plWin32GroupedSound.h"
#include "plAudioCore/plSoundBuffer.h"
#include "plFile/plFileUtils.h"

// Valdez Asset Manager Related
#ifdef MAXASS_AVAILABLE
#include "../../AssetMan/PublicInterface/MaxAssInterface.h"
#endif
#include <shlwapi.h>

// Fun soft volume stuff
#include "plSoftVolumeComponent.h"
#include "../plIntersect/plSoftVolume.h"

// Misc
#include "MaxMain/plMaxCFGFile.h"
#include "plPickNode.h"

// EAX stuff
#include "plAudio/plEAXListenerMod.h"
#ifdef EAX_SDK_AVAILABLE
#include <eax-util.h>
#include <eaxlegacy.h>
#endif

#include "plResMgr/plLocalization.h"
#include "plPhysical/plPhysicalSndGroup.h"

// EAX3 values which eax4 no longer defines, but we still need.
// Single window material preset
#define EAX_MATERIAL_SINGLEWINDOW          (-2800)
#define EAX_MATERIAL_SINGLEWINDOWLF        0.71f
#define EAX_MATERIAL_SINGLEWINDOWROOMRATIO 0.43f

// Double window material preset
#define EAX_MATERIAL_DOUBLEWINDOW          (-5000)
#define EAX_MATERIAL_DOUBLEWINDOWLF        0.40f
#define EAX_MATERIAL_DOUBLEWINDOWROOMRATIO 0.24f

// Thin door material preset
#define EAX_MATERIAL_THINDOOR              (-1800)
#define EAX_MATERIAL_THINDOORLF            0.66f
#define EAX_MATERIAL_THINDOORROOMRATIO     0.66f

// Thick door material preset
#define EAX_MATERIAL_THICKDOOR             (-4400)
#define EAX_MATERIAL_THICKDOORLF           0.64f
#define EAX_MATERIAL_THICKDOORROOMRATIO    0.27f

// Wood wall material preset
#define EAX_MATERIAL_WOODWALL              (-4000)
#define EAX_MATERIAL_WOODWALLLF            0.50f
#define EAX_MATERIAL_WOODWALLROOMRATIO     0.30f

// Brick wall material preset
#define EAX_MATERIAL_BRICKWALL             (-5000)
#define EAX_MATERIAL_BRICKWALLLF           0.60f
#define EAX_MATERIAL_BRICKWALLROOMRATIO    0.24f

// Stone wall material preset
#define EAX_MATERIAL_STONEWALL             (-6000)
#define EAX_MATERIAL_STONEWALLLF           0.68f
#define EAX_MATERIAL_STONEWALLROOMRATIO    0.20f

// Curtain material preset
#define EAX_MATERIAL_CURTAIN               (-1200)
#define EAX_MATERIAL_CURTAINLF             0.15f
#define EAX_MATERIAL_CURTAINROOMRATIO      1.00f

void DummyCodeIncludeFuncAudio() {}


/////////////////////////////////////////////////////////////////////////////////////////////////
/// Base Sound Emitter Component ////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////

enum
{
    kSoundFileName, 
    kLoopingChekBx_DEAD,            //Removed in v3
    kLoopBegin_DEAD,                //Removed in v2
    kLoopEnd_DEAD,                  //Removed in v2
    kMinFallOffRad_DEAD,            // removed in v6
    kMaxFallOffRad_DEAD,            // removed in v6
    kSoundAutoStartCkBx, 
    kSoundLoopCkBx,
    kSoundLoopSegCkBx_DEAD,         //Removed in v3
    kSoundLoopSegBeg_DEAD,
    kSoundLoopSegEnd_DEAD,
    kSoundLoopSegBegDDList_DEAD,    //Inserted in v3
    kSoundLoopSegEndDDList_DEAD,    //Inserted in v3
    kSoundLoopSegBeg2_DEAD,         //Inserted in v3
    kSoundLoopSegEnd2_DEAD,         //Inserted in v3
    kSFileNameTextField,
    kOldSoundVolumeSlider,          //Inserted in v4 OBLITERATE
    kSoundIConeAngle,               //Inserted in v5
    kSoundOConeAngle,               //Inserted in v5
    kSoundOConeVolumeSlider,        //Inserted in v5
    kMinFallOffRad,
    kMaxFallOffRad,
    kSoundLoopName,
    kSoundConeBool,                 //Inserted in v6,
    kNotSoOldSoundVolumeSlider,
    kSndFadeInEnable,
    kSndFadeInType,
    kSndFadeInLength,
    kSndFadeOutEnable,
    kSndFadeOutType,
    kSndFadeOutLength,
    kSndFadedVolume,            // Currently unsupported
    kSndSoftRegion,
    kSndSoftRegionEnable,
    kSndVersionCount,           // So we can do version upgrading (DAMN YOU MAX!!!)
    kSoundVolumeSlider,
    kSndDisableLOD,
    kSndChannelSelect,
    kSndAllowChannelSelect,
    kSndIsWMAFile_DEAD,
    kSndWMAStartClip_DEAD,
    kSndWMAEndClip_DEAD,
    kSndEnableCrossfadeCover_DEAD,
    kSndCrossfadeCoverFilename_DEAD,
    kSndCoverIsWMAFile_DEAD,
    kSndCoverWMAStartClip_DEAD,
    kSndCoverWMAEndClip_DEAD,
    kSndIsLocalOnly,
    kSndCategory,
    kSndPriority,
    kSndIncidental,
    kSndStreamCompressed,
};

enum
{
    kSndFadeTypeLinear,
    kSndFadeTypeLogarithmic,
    kSndFadeTypeExponential
};

UInt32  plBaseSoundEmitterComponent::fWarningFlags = 0;
//bool  plBaseSoundEmitterComponent::fAllowUnhide = false;

void    plBaseSoundEmitterComponent::IShowError( UInt32 type, const char *errMsg, const char *nodeName, plErrorMsg *pErrMsg )
{
    if( !( fWarningFlags & (1 << type) ) )
    {
        if( pErrMsg->Set( true, "Sound Component Error", errMsg, nodeName ).CheckAskOrCancel() )
            fWarningFlags |= (1 << type);
        pErrMsg->Set( false );
    }
}

plBaseSoundEmitterComponent::plBaseSoundEmitterComponent()
{
    fAllowUnhide = false;
    fAssetsUpdated = false;
    fCreateGrouped = false;
    fIndices.clear();
    fValidNodes.clear();
}

plBaseSoundEmitterComponent::~plBaseSoundEmitterComponent()
{
}


RefTargetHandle plBaseSoundEmitterComponent::Clone( RemapDir &remap )
{
    // Do the base clone
    plBaseSoundEmitterComponent *obj = (plBaseSoundEmitterComponent *)plComponentBase::Clone( remap );

#ifdef MAXASS_AVAILABLE
    obj->fSoundAssetId = fSoundAssetId;
    obj->fCoverSoundAssetID = fCoverSoundAssetID;
#endif

    return obj;
}

void    plBaseSoundEmitterComponent::IConvertOldVolume( void )
{
    int oldVol = fCompPB->GetInt( (ParamID)kOldSoundVolumeSlider, 0 );
    if( oldVol != 4999 )
    {
        float v = (float)( oldVol - 5000 ) / 5000.f;
        fCompPB->SetValue( (ParamID)kNotSoOldSoundVolumeSlider, 0, v );
        fCompPB->SetValue( (ParamID)kOldSoundVolumeSlider, 0, 4999 );
    }

    // Shut up.
    float notSoOldV = fCompPB->GetFloat( (ParamID)kNotSoOldSoundVolumeSlider, 0 );
    if( notSoOldV != -1.f )
    {
        float d3dValueReally = -5000.f + ( 5000.f * notSoOldV );

        float ourNewValue = (float)d3dValueReally / 100.f;
        
        fCompPB->SetValue( (ParamID)kSoundVolumeSlider, 0, ourNewValue );
        fCompPB->SetValue( (ParamID)kNotSoOldSoundVolumeSlider, 0, -1.f );
    }
}

float   plBaseSoundEmitterComponent::IGetDigitalVolume( void ) const
{
    return (float)pow( 10.f, fCompPB->GetFloat( (ParamID)kSoundVolumeSlider, 0 ) / 20.f );
}

#define OLD_MAX_ASS_CHUNK 0x5500
#define MAX_ASS_CHUNK 0x5501

IOResult plBaseSoundEmitterComponent::Save(ISave *isave) 
{
    IOResult res = plComponentBase::Save(isave);
    if (res != IO_OK)
        return res;

#ifdef MAXASS_AVAILABLE
    isave->BeginChunk(MAX_ASS_CHUNK);
    ULONG nwrite;

    UInt64 id = fSoundAssetId;
    res = isave->Write(&id, sizeof(id), &nwrite);
    if (res != IO_OK)
        return res;

    id = fCoverSoundAssetID;
    res = isave->Write(&id, sizeof(id), &nwrite);
    if (res != IO_OK)
        return res;

    isave->EndChunk();
#endif

    return IO_OK;
}   

IOResult plBaseSoundEmitterComponent::Load(ILoad *iload) 
{
    IOResult res = plComponentBase::Load(iload);
    if (res != IO_OK)
        return res;

#ifdef MAXASS_AVAILABLE
    while (IO_OK == (res = iload->OpenChunk()))
    {
        if (iload->CurChunkID() == OLD_MAX_ASS_CHUNK)
        {
            VARIANT tempVar;
            ULONG nread;
            res = iload->Read(&tempVar, sizeof(VARIANT), &nread);
            fSoundAssetId = tempVar.decVal.Lo64;
        }
        // Secret AssMan value used for no good....
        else if (iload->CurChunkID() == MAX_ASS_CHUNK)
        {
            ULONG nread;
            UInt64 id;
            res = iload->Read(&id, sizeof(id), &nread);
            fSoundAssetId = id;
            res = iload->Read(&id, sizeof(id), &nread);
            fCoverSoundAssetID = id;
        }
        iload->CloseChunk();
        if (res != IO_OK) 
            return res;
    }
#endif

    return IO_OK;
}

#ifdef MAXASS_AVAILABLE
void plBaseSoundEmitterComponent::SetSoundAssetId( plBaseSoundEmitterComponent::WhichSound which, jvUniqueId assetId, const TCHAR *fileName )
{
    if( which == kBaseSound )
    {
        fSoundAssetId = assetId;

        fCompPB->SetValue( (ParamID)kSoundFileName, 0, (TCHAR *)fileName );
        if( fCompPB->GetMap() )
            fCompPB->GetMap()->Invalidate( (ParamID)kSoundFileName );
    }
    else
    {
        hsAssert( false, "Setting a sound that isn't supported on this component" );
    }
}

jvUniqueId plBaseSoundEmitterComponent::GetSoundAssetID( plBaseSoundEmitterComponent::WhichSound which )
{
    if( which == kCoverSound )
        return fCoverSoundAssetID;
    else if( which == kBaseSound )
        return fSoundAssetId;

    hsAssert( false, "Getting a sound that isn't supported on this component" );
    return fSoundAssetId;
}
#endif

void    plBaseSoundEmitterComponent::IUpdateAssets( void )
{
#ifdef MAXASS_AVAILABLE
    if( fAssetsUpdated )
        return;

    if( !fSoundAssetId.IsEmpty() || !fCoverSoundAssetID.IsEmpty() )
    {
        MaxAssInterface *maxAssInterface = GetMaxAssInterface();
        if( !maxAssInterface ) 
            return;
        
        // Download the latest version and retrieve the filename
        char newfilename[ MAX_PATH ];
        if(maxAssInterface->GetLatestVersionFile( fSoundAssetId, newfilename, MAX_PATH ) )
        {
            // AssetID overrides filename
            fCompPB->SetValue( (ParamID)kSoundFileName, 0, newfilename );
        }

        fAssetsUpdated = true;
    }
    else
        fAssetsUpdated = true;
#endif
}

const char* plBaseSoundEmitterComponent::GetSoundFileName( plBaseSoundEmitterComponent::WhichSound which )
{
    IUpdateAssets();

    if( which == kBaseSound )
        return fCompPB->GetStr( (ParamID)kSoundFileName );

    hsAssert( false, "Getting a sound that isn't supported on this component" );
    return nil;
}

hsBool plBaseSoundEmitterComponent::DeInit( plMaxNode *node, plErrorMsg *pErrMsg )
{
    fCreateGrouped = false;
    fIndices.clear();
    fValidNodes.clear();
    return true;
}

// Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plBaseSoundEmitterComponent::SetupProperties( plMaxNode *pNode, plErrorMsg *pErrMsg )
{
    IConvertOldVolume();
/*
    for (int i = 0; i < fIndices.Count(); i++)
        delete(fIndices[i]);
    fIndices.SetCountAndZero(0);
*/

    return true;
}

void    plBaseSoundEmitterComponent::SetCreateGrouped( plMaxNode *baseNode, int commonSoundIdx )
{
    fIndices[ baseNode ] = commonSoundIdx;
    fCreateGrouped = true;
}

bool plBaseSoundEmitterComponent::IValidate(plMaxNode *node, plErrorMsg *pErrMsg)
{
    if( GetSoundFileName( kBaseSound ) == nil )
    {
        pErrMsg->Set(true, "Sound 3D FileName Error", "The Sound 3D component %s is missing a filename.", node->GetName()).Show();
        pErrMsg->Set(false);
        return false;
    }

    return true;
}

hsBool plBaseSoundEmitterComponent::PreConvert( plMaxNode *node, plErrorMsg *pErrMsg, Class_ID classToConvert )
{
    const char* dbgNodeName = node->GetName();
    fValidNodes[node] = IValidate(node, pErrMsg);
    if (!fValidNodes[node])
        return false;

    node->SetForceLocal(true);

    const plAudioInterface *ai = node->GetSceneObject()->GetAudioInterface();
    if (!ai)
    {
        ai = TRACKED_NEW plAudioInterface;
        plKey pAiKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), (hsKeyedObject*)ai,node->GetKey()->GetUoid().GetLocation(), node->GetLoadMask());
        hsgResMgr::ResMgr()->AddViaNotify(pAiKey, TRACKED_NEW plObjRefMsg(node->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
    }
    if (!ai->GetAudible())
    {
        plAudible *pAudible = TRACKED_NEW plWinAudible;
        // Add a key for it
        plKey key = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), pAudible, node->GetKey()->GetUoid().GetLocation(), node->GetLoadMask());
        
        plIntRefMsg* pMsg = TRACKED_NEW plIntRefMsg(node->GetKey(), plRefMsg::kOnCreate, 0, plIntRefMsg::kAudible);
        hsgResMgr::ResMgr()->AddViaNotify(pAudible->GetKey(), pMsg, plRefFlags::kActiveRef );

        pAudible->SetSceneNode(node->GetRoomKey());
    }

    if( !fCreateGrouped )
        fIndices[ node ] = node->GetNextSoundIdx();

    return true;
}

void    plBaseSoundEmitterComponent::IGrabFadeValues( plSound *sound )
{
    if( fCompPB->GetInt( (ParamID)kSndFadeInEnable, 0 ) != 0 )
    {
        // Fade in is enabled; set the params
        plSound::plFadeParams::Type     type;

        hsScalar len = (hsScalar)fCompPB->GetFloat( (ParamID)kSndFadeInLength, 0 );

        switch( fCompPB->GetInt( (ParamID)kSndFadeInType, 0 ) )
        {
            case kSndFadeTypeLinear:        type = plSound::plFadeParams::kLinear; break;
            case kSndFadeTypeLogarithmic:   type = plSound::plFadeParams::kLogarithmic; break;
            case kSndFadeTypeExponential:   type = plSound::plFadeParams::kExponential; break;
        }

        sound->SetFadeInEffect( type, len );
    }

    if( fCompPB->GetInt( (ParamID)kSndFadeOutEnable, 0 ) != 0 )
    {
        // Fade out is enabled; set the params
        plSound::plFadeParams::Type     type;

        hsScalar len = (hsScalar)fCompPB->GetFloat( (ParamID)kSndFadeOutLength, 0 );

        switch( fCompPB->GetInt( (ParamID)kSndFadeOutType, 0 ) )
        {
            case kSndFadeTypeLinear:        type = plSound::plFadeParams::kLinear; break;
            case kSndFadeTypeLogarithmic:   type = plSound::plFadeParams::kLogarithmic; break;
            case kSndFadeTypeExponential:   type = plSound::plFadeParams::kExponential; break;
        }

        sound->SetFadeOutEffect( type, len );
    }

//  sound->SetFadedVolume( (hsScalar)fCompPB->GetFloat( kSndFadedVolume, 0 ) );
}

void    plBaseSoundEmitterComponent::IGrabSoftRegion( plSound *sound, plErrorMsg *pErrMsg )
{
    // Do the soft volume, if there is one
    if( fCompPB->GetInt( (ParamID)kSndSoftRegionEnable, 0 ) != 0 )
    {
        plSoftVolBaseComponent* softComp = plSoftVolBaseComponent::GetSoftComponent( fCompPB->GetINode( (ParamID)kSndSoftRegion ) );
        if( softComp != nil )
        {
            plKey softKey = softComp->GetSoftVolume();
            if( softKey != nil )
            {
                // Make sure we set checkListener on the sucker
                plSoftVolume *vol = plSoftVolume::ConvertNoRef( softKey->GetObjectPtr() );
                if( vol != nil )
                {
                    vol->SetCheckListener();
                    hsgResMgr::ResMgr()->AddViaNotify( softKey, TRACKED_NEW plGenRefMsg( sound->GetKey(), plRefMsg::kOnCreate, 0, plSound::kSoftRegion ), plRefFlags::kActiveRef );
                }
            }
        }
        else
        {
            pErrMsg->Set(true, "Sound Emitter Error", "The Sound emitter component %s is checked to use a soft region, but no soft region is specified. Ignoring setting.", GetINode()->GetName() ).Show();
            pErrMsg->Set(false);
        }
    }
}

UInt32  plBaseSoundEmitterComponent::ICalcSourceBufferFlags( void ) const
{
    UInt32 bufferFlags = 0;

    if( IHasWaveformProps() )
    {
        if( fCompPB->GetInt( (ParamID)kSndAllowChannelSelect ) )
        {
            if( fCompPB->GetInt( (ParamID)kSndChannelSelect ) )
                bufferFlags = plSoundBuffer::kOnlyRightChannel;
            else
                bufferFlags = plSoundBuffer::kOnlyLeftChannel;
        }
    }

    return bufferFlags;
}

plSoundBuffer   *plBaseSoundEmitterComponent::GetSourceBuffer( const char *fileName, plMaxNode *srcNode, UInt32 srcBufferFlags )
{
    plSoundBuffer* sb = IGetSourceBuffer(fileName, srcNode, srcBufferFlags);

    const char* plasmaDir = plMaxConfig::GetClientPath();
    if (plasmaDir)
    {
        char sfxPath[MAX_PATH];
        sprintf(sfxPath, "%ssfx\\%s", plasmaDir, plFileUtils::GetFileName(fileName));
    
        // Export any localized versions as well
        for (int i = 0; i < plLocalization::GetNumLocales(); i++)
        {
            char localName[MAX_PATH];
            if (plLocalization::ExportGetLocalized(sfxPath, i, localName))
            {
                IGetSourceBuffer(localName, srcNode, srcBufferFlags);
            }
        }
    }

    return sb;
}

plSoundBuffer   *plBaseSoundEmitterComponent::IGetSourceBuffer( const char *fileName, plMaxNode *srcNode, UInt32 srcBufferFlags )
{
    plKey       key;
    char        keyName[ MAX_PATH ];
    char        fullPath[ MAX_PATH ];


    strcpy( keyName, fileName );
    ::PathStripPath( keyName );
    
    // TEMP HACK until we get packed sounds: 
    // Given the source filename, we check to see if it's in our plasma game directory. If not, or if
    // it's out of date, we copy it over. We'll truncate the filename inside plSoundBuffer when we're ready.

    const char *plasmaDir = plMaxConfig::GetClientPath();
    if( plasmaDir != nil )
    {
        strcpy( fullPath, plasmaDir );
        strcat( fullPath, "sfx\\" );

        // Before we finish our path, make sure that directory EXISTS
        plFileUtils::CreateDir( fullPath );

        // Now finish the path...
        strcat( fullPath, keyName );

        // Check filestamp
        WIN32_FILE_ATTRIBUTE_DATA   oldFileAttrib, newFileAttrib;
        BOOL                        oldOK, newOK;

        oldOK = GetFileAttributesEx( fileName, GetFileExInfoStandard, &oldFileAttrib );
        newOK = GetFileAttributesEx( fullPath, GetFileExInfoStandard, &newFileAttrib );

        if( oldOK && newOK )
        {
            // Only copy if the file is newer
            if( ::CompareFileTime( &oldFileAttrib.ftLastWriteTime, &newFileAttrib.ftLastWriteTime ) > 0 )
            {
                ::CopyFile( fileName, fullPath, FALSE );
            }
        }
        else
        {
            // Can't compare, so either there was an error or the target file doesn't exist. Copy no matter what.
            ::CopyFile( fileName, fullPath, FALSE );
        }

        // Point to our new sound file
        fileName = fullPath;
    }

    // Additional info for the keyName--need some flag mangling, specifically for the left/right channel mangling
    if( srcBufferFlags & plSoundBuffer::kOnlyLeftChannel )
        strcat( keyName, ":L" );
    else if( srcBufferFlags & plSoundBuffer::kOnlyRightChannel )
        strcat( keyName, ":R" );

    key = srcNode->FindPageKey( plSoundBuffer::Index(), keyName );  
    if( key != nil )
        return plSoundBuffer::ConvertNoRef( key->GetObjectPtr() );

    // Not yet created, so make a new one
    plSoundBuffer   *buffer = TRACKED_NEW plSoundBuffer( fileName, srcBufferFlags );
    if( !buffer->IsValid() )
    {
        // Invalid, so delete and return nil
        delete buffer;
        return nil;
    }

    // We'll put it in a location parallel to the age, say, (age,district,"sounds")
    plLocation  loc = srcNode->GetLocation();
//  plKey roomKey = hsgResMgr::ResMgr()->NameToLoc( loc.GetAge(), loc.GetChapter(), "sounds" );
    // TEMP HACK FOR NOW, until we actually finish implementing this--just hide them in the same file
//  plKey roomKey = hsgResMgr::ResMgr()->FindLocation( loc.GetAge(), loc.GetChapter(), loc.GetPage() );

    // The buffer may be shared across multiple sources. We could or together the LoadMasks of all
    // the nodes that use it, or we can just go with the default loadmask of Always load, and
    // count on it never getting dragged into memory if nothing that references it does.
    hsgResMgr::ResMgr()->NewKey( keyName, buffer, srcNode->GetLocation());

    return buffer;
}

//// LookupLatestAsset ///////////////////////////////////////////////////////
//  Does a find in AssetMan for the given sound file and makes sure it's
//  copied over to the AssetMan directory, returning the path to said asset.
//  Returns true if found, false if not.

#ifdef MAXASS_AVAILABLE
hsBool  plBaseSoundEmitterComponent::LookupLatestAsset( const char *waveName, char *retPath, plErrorMsg *errMsg )
{
    MaxAssInterface* assetMan = GetMaxAssInterface();
    if( assetMan == nil )
        return false;       // No AssetMan available

    // Try to find it in assetMan
    jvUniqueId assetId;
    if (assetMan->FindAssetByFilename(waveName, assetId))
    {
        // Get the latest version
        char assetPath[MAX_PATH];
        if (!assetMan->GetLatestVersionFile(assetId, assetPath, sizeof(assetPath)))
        {
            errMsg->Set( true, "SoundEmitter Convert Error",
                        "Unable to update wave file '%s' because AssetMan was unable to get the latest version. Using local copy instead.", waveName ).Show();
            errMsg->Set( false );
            return false;
        }

        // Copy the string over and go
        hsStrcpy( retPath, assetPath );
        return true;
    }

    return false;
}
#endif

plSoundBuffer   *plBaseSoundEmitterComponent::IProcessSourceBuffer( plMaxNode *maxNode, plErrorMsg *errMsg )
{
    const char    *fileName = GetSoundFileName( kBaseSound );
    if( fileName == nil )
        return nil;

    plSoundBuffer *srcBuffer = GetSourceBuffer( fileName, maxNode, ICalcSourceBufferFlags() );
    if( srcBuffer == nil )
    {
        IShowError( kSrcBufferInvalid, "The file specified for the sound 3D component %s is invalid. "
                                        "This emitter will not be exported.", GetINode()->GetName(), errMsg );
        return nil;
    }

    return srcBuffer;
}

void    plBaseSoundEmitterComponent::UpdateSoundFileSelection( void )
{
    plSoundBuffer   *baseBuffer = nil;


    // Attempt to load the sound in
    if( GetSoundFileName( kBaseSound ) == nil )
    {
        // Disable this feature by default
        fCompPB->SetValue( (ParamID)kSndAllowChannelSelect, 0, 0 );
    }
    else
    {
        if( IAllowStereoFiles() )
        {
            // We allow stereo files, so we don't want to allow stereo->mono select
            fCompPB->SetValue( (ParamID)kSndAllowChannelSelect, 0, 0 );
        }
        else
        {
            baseBuffer = TRACKED_NEW plSoundBuffer( GetSoundFileName( kBaseSound ) );
            if( baseBuffer != nil && baseBuffer->IsValid() )
            {
                // Update our stereo channel selection if necessary
                if( baseBuffer->GetHeader().fNumChannels == 1 )
                {
                    fCompPB->SetValue( (ParamID)kSndAllowChannelSelect, 0, 0 );
                }
                else
                {
                    fCompPB->SetValue( (ParamID)kSndAllowChannelSelect, 0, 1 );
                }
            }
            else
                // Disable this feature by default
                fCompPB->SetValue( (ParamID)kSndAllowChannelSelect, 0, 0 );
        }
    }

    if( baseBuffer != nil )
        delete baseBuffer;
}

hsScalar    plBaseSoundEmitterComponent::GetSoundVolume( void ) const
{
    return IGetDigitalVolume();
}

//// UpdateCategories ///////////////////////////////////////////////////////////////////////////
// Loads the given combo box with category selections and sets the ParamID for the category parameter.
// Returns false if there are no categories to choose for this component

hsBool  plBaseSoundEmitterComponent::UpdateCategories( HWND dialogBox, int &categoryID, ParamID &paramID )
{
    HWND    comboBox = GetDlgItem( dialogBox, IDC_SND_CATEGORY );
    char    **cats;
    int     *catEnums;
    int     i, currCat, idx, currIdx;


    // Get our list of cats
    if( !IGetCategoryList( cats, catEnums ) )
        return false;

    // We get two categories for this one: Background Music (default) and Ambience
    ComboBox_ResetContent( comboBox );

    currCat = fCompPB->GetInt( (ParamID)kSndCategory );
    currIdx = -1;

    for( i = 0; cats[ i ][ 0 ] != 0; i++ )
    {
        idx = ComboBox_AddString( comboBox, cats[ i ] );
        ComboBox_SetItemData( comboBox, idx, catEnums[ i ] );
        if( catEnums[ i ] == currCat )
            currIdx = idx;
    }

    if( currIdx != -1 )
        ComboBox_SetCurSel( comboBox, currIdx );
    else
    {
        // Option not found in our list, reset to a valid option
        ComboBox_SetCurSel( comboBox, 0 );
        fCompPB->SetValue( (ParamID)kSndCategory, 0, catEnums[ 0 ] );
    }

    // Return info
    paramID = (ParamID)kSndCategory;
    categoryID = IDC_SND_CATEGORY;

    return true;
}

SegmentMap *GetCompWaveSegmentMap(const char *file)
{
    if( file == nil )
        return nil;

    return GetWaveSegmentMap( file, nil );

/*
    const char *path = plMaxConfig::GetClientPath();
    if (file && path)
    {
        char fullpath[MAX_PATH];
        sprintf(fullpath, "%sSfx\\%s", path, file);
        return GetWaveSegmentMap(fullpath, nil);
    }

    return nil;
*/
}

//// ISetBaseParameters /////////////////////////////////////////////////////////////////////////
//  Sets up parameters on the given sound based on the common paramblock values

void    plBaseSoundEmitterComponent::ISetBaseParameters( plSound *destSound, plErrorMsg *pErrMsg )
{
    // Make sure our category is valid before we set it
    int     i, cat = fCompPB->GetInt( (ParamID)kSndCategory );
    char    **cats;
    int     *catEnums;

    if( IGetCategoryList( cats, catEnums ) )
    {
        for( i = 0; cats[ i ][ 0 ] != 0; i++ )
        {
            if( catEnums[ i ] == cat )
                break;
        }
        if( cats[ i ][ 0 ] == 0 )
            cat = catEnums[ 0 ];
    }
    destSound->SetType( cat );

    destSound->SetVolume( IGetDigitalVolume() );
    destSound->SetProperty( plSound::kPropAutoStart, fCompPB->GetInt( (ParamID)kSoundAutoStartCkBx ) );
    IGrabFadeValues( destSound );
    if( fCompPB->GetInt( (ParamID)kSoundLoopCkBx ) )
    {
        destSound->SetProperty( plSound::kPropLooping, true );

        const char *loop = fCompPB->GetStr((ParamID)kSoundLoopName);
        if (loop && loop[0] != '\0')
        {
            SegmentMap *segMap = GetCompWaveSegmentMap( GetSoundFileName( kBaseSound ) );
            if (segMap && segMap->find(loop) != segMap->end())
            {
                SegmentSpec *spec = (*segMap)[loop];
//                  sound->SetLoopPoints(spec->fStart, spec->fEnd);
            }
        }
    }
    else
        destSound->SetProperty( plSound::kPropLooping, false );
}

//// AddToAnim //////////////////////////////////////////////////////////////////////////////////
//  Support for animated volumes

hsBool  plBaseSoundEmitterComponent::AddToAnim( plAGAnim *anim, plMaxNode *node )
{
    hsBool result = false;
    plController *ctl;
    hsControlConverter& cc = hsControlConverter::Instance();

    hsScalar start, end;
    if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
    {
        start = end = -1;
    }
    else
    {
        start = anim->GetStart();
        end = anim->GetEnd();
    }

    ctl = cc.MakeScalarController( fCompPB->GetController( (ParamID)kSoundVolumeSlider ), node, start, end );
    if( ctl != nil )
    {
        // Better only do this when the sound component is applied to only one object...
        if( fIndices.size() != 1 )
        {
            delete ctl;
            return false;
        }

        std::map<plMaxNode*, int>::iterator i = fIndices.begin();
        plSoundVolumeApplicator *app = TRACKED_NEW plSoundVolumeApplicator( (*i).second );
        app->SetChannelName(node->GetName());
        plAnimComponentBase::SetupCtl( anim, ctl, app, node );
        result = true;      
    }

    return result;
}

//  Class that accesses the paramblock

struct indexinfo
{
    indexinfo::indexinfo() { pNode = nil; fIndex = -1; }
    plMaxNode*  pNode;
    int         fIndex; 
};

int GetSoundNameAndIdx(plComponentBase *comp, plMaxNodeBase *node, const char*& name)
{
    int idx = -1;
    if( ( comp->ClassID() == SOUND_3D_COMPONENT_ID ||
        comp->ClassID() == BGND_MUSIC_COMPONENT_ID ||
        comp->ClassID() == GUI_SOUND_COMPONENT_ID ) && node->CanConvert())
    {
        idx = ((plBaseSoundEmitterComponent *)comp)->GetSoundIdx((plMaxNode*)node);
    }
    if(node->CanConvert())
        name = idx < 0 ? nil : comp->GetINode()->GetName();
    else
        name = nil;
    return idx;
}

int plAudioComp::GetSoundModIdx(plComponentBase *comp, plMaxNode *node)
{
    if( comp->ClassID() == SOUND_3D_COMPONENT_ID ||
        comp->ClassID() == BGND_MUSIC_COMPONENT_ID ||
        comp->ClassID() == GUI_SOUND_COMPONENT_ID )
        return ((plBaseSoundEmitterComponent *)comp)->GetSoundIdx(node);
    return -1;
}

bool    plAudioComp::IsLocalOnly( plComponentBase *comp )
{
    if( comp->ClassID() == SOUND_3D_COMPONENT_ID ||
        comp->ClassID() == BGND_MUSIC_COMPONENT_ID ||
        comp->ClassID() == GUI_SOUND_COMPONENT_ID )
        return ((plBaseSoundEmitterComponent *)comp)->IsLocalOnly() ? true : false;
    return false;
}

bool plAudioComp::IsSoundComponent(plComponentBase *comp)
{
    Class_ID id = comp->ClassID();

    if( id == SOUND_3D_COMPONENT_ID ||
        id == BGND_MUSIC_COMPONENT_ID ||
        id == GUI_SOUND_COMPONENT_ID )
        return true;

    return false;
}

class plAudioBaseComponentProc : public plLCBoxComponentProc
{

protected:

    void    IConvertOldVolume( IParamBlock2 *pb )
    {
        int oldVol = pb->GetInt( (ParamID)kOldSoundVolumeSlider, 0 );
        if( oldVol != 4999 )
        {
            float v = (float)( oldVol - 5000 ) / 5000.f;
            pb->SetValue( (ParamID)kNotSoOldSoundVolumeSlider, 0, v );
            pb->SetValue( (ParamID)kOldSoundVolumeSlider, 0, 4999 );
        }

        // Shut up.
        float notSoOldV = pb->GetFloat( (ParamID)kNotSoOldSoundVolumeSlider, 0 );
        if( notSoOldV != -1.f )
        {
            float d3dValueReally = -5000.f + ( 5000.f * notSoOldV );

            float ourNewValue = (float)d3dValueReally / 100.f;

            pb->SetValue( (ParamID)kSoundVolumeSlider, 0, ourNewValue );
            pb->SetValue( (ParamID)kNotSoOldSoundVolumeSlider, 0, -1.f );
        }
    }

    void    IGetNewLocalFileName( plBaseSoundEmitterComponent *soundComponent, plBaseSoundEmitterComponent::WhichSound which )
    {
        char fileName[ MAX_PATH ], dirName[ MAX_PATH ];
        const char* name = soundComponent->GetSoundFileName( which );

        if( name != nil )
            strcpy( fileName, name );
        else
            strcpy( fileName, _T( "" ) );

        strcpy( dirName, fileName );
        ::PathRemoveFileSpec( dirName );

        OPENFILENAME ofn = {0};
        ofn.lStructSize = sizeof( OPENFILENAME );
        ofn.hwndOwner = GetCOREInterface()->GetMAXHWnd();
        ofn.lpstrFilter = "WAV Files (*.wav)\0*.wav\0Windows Media Audio Files (*.wma)\0*.wma\0OGG Vorbis Files (*.ogg)\0*.ogg\0";
        ofn.lpstrFile = (LPSTR)fileName;
        ofn.nMaxFile = sizeof( fileName );
        ofn.lpstrInitialDir = dirName;
        ofn.lpstrTitle = "Choose a sound file";
        ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
        ofn.lpstrDefExt = "wav";

#ifdef MAXASS_AVAILABLE
        if( GetOpenFileName( &ofn ) )
        {
            jvUniqueId emptyId;
            soundComponent->SetSoundAssetId( which, emptyId, fileName );
        }
#endif
    }

    void    IUpdateSoundButton( plBaseSoundEmitterComponent *soundComponent, HWND hDlg, int dlgBtnItemToSet, plBaseSoundEmitterComponent::WhichSound which )
    {
        ICustButton         *custButton;
        TCHAR               fileName[ MAX_PATH ];


        custButton = GetICustButton( GetDlgItem( hDlg, dlgBtnItemToSet ) );
        if( custButton != nil )
        {
            const char* origName = soundComponent->GetSoundFileName( which );

            if( origName != nil && strlen( origName ) > 0 )
            {
                strcpy( fileName, origName );
                ::PathStripPath( fileName );
                custButton->SetText( fileName );
            }
            else
                custButton->SetText( _T( "<None>" ) );

            ReleaseICustButton( custButton );
        }

        soundComponent->UpdateSoundFileSelection();
    }

    void    ISelectSoundFile( plBaseSoundEmitterComponent *soundComponent, HWND hDlg, int dlgBtnItemToSet, plBaseSoundEmitterComponent::WhichSound which )
    {
#ifdef MAXASS_AVAILABLE
        MaxAssInterface* maxAssInterface = GetMaxAssInterface();

        // if we have the assetman plug-in, then try to use it, unless shift is held down
        if( maxAssInterface && !( GetKeyState( VK_SHIFT ) & 0x8000 ) )
        {
            jvUniqueId assetId = soundComponent->GetSoundAssetID(which);

            char fileName[MAX_PATH];
            if (maxAssInterface->OpenSoundDlg(assetId, fileName, MAX_PATH))
            {
                // Set asset ID and filename
                soundComponent->SetSoundAssetId(which, assetId, fileName);
            }
        }
        else
        {
            IGetNewLocalFileName( soundComponent, which );
        }
#else
        IGetNewLocalFileName( soundComponent, which );
#endif

        // Update the button now
        if( hDlg != nil )
            IUpdateSoundButton( soundComponent, hDlg, dlgBtnItemToSet, which );
    }

    BOOL DlgProc( TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
    {
        plBaseSoundEmitterComponent *soundComp = (plBaseSoundEmitterComponent *)map->GetParamBlock()->GetOwner();


        switch( msg )
        {
            case WM_INITDIALOG:
                CheckDlgButton(hWnd, IDC_SND_TRACKVIEW, soundComp->fAllowUnhide ? BST_CHECKED : BST_UNCHECKED );
                return true;
            
            case WM_COMMAND:
                if( LOWORD( wParam ) == IDC_SND_TRACKVIEW )
                {
                    soundComp->fAllowUnhide = ( IsDlgButtonChecked( hWnd, IDC_SND_TRACKVIEW ) == BST_CHECKED );
                    plComponentShow::Update();
                    return true;
                }
                break;
        }
        
        return false;
    }
};


//// Helper accessors and dialog procs ////

static plSingleCompSelProc gSoundSoftVolumeSelProc( kSndSoftRegion, IDC_COMP_SOUNDREGION_CHOOSE_VOLUME, "Select a soft region to use for the sound" );

// When one of our parameters that is a ref changes, send out the component ref
// changed message.  Normally, messages from component refs are ignored since
// they pass along all the messages of the ref, which generates a lot of false
// converts.
class plSoundSoftVolAccessor : public PBAccessor
{
public:
    void Set( PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t )
    {
        if( id == kSndSoftRegion )
        {
            plBaseSoundEmitterComponent *comp = (plBaseSoundEmitterComponent*)owner;
            comp->NotifyDependents( FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED );
        }
    }
};

static plSoundSoftVolAccessor gSoundSoftVolAccessor;


enum
{
    kSndSharedParams,
    kS3DBaseParams,
    kS3DSoftVolumeParams,
    kSoundFadeParams,
    kSndWaveformParams,
    kSndEAXParams
};

//// Shared ParamBlock for All Sounds ///////////////////////////////////////////////////////////

#define sSoundSharedPBHeader(s)     kSndSharedParams,   IDD_COMP_SOUNDBASE,         s##,            0, 0, &gSoundCompProc,  \
                                    kSoundFadeParams,   IDD_COMP_SOUND_FADEPARAMS,  IDS_COMP_SOUNDFADEPARAMS,   0, 0, &gSoundFadeParamsProc

static ParamBlockDesc2 sSoundSharedPB
(
    plComponent::kBlkComp + 2, _T("Sound Shared Params"), 0, nil, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP, plComponent::kRefComp,

    2,  // Number of rollouts
    kSndSharedParams,           IDD_COMP_SOUNDBASE,         IDS_COMP_SOUNDBASE,         0, 0, nil,
    kSoundFadeParams,               IDD_COMP_SOUND_FADEPARAMS,  IDS_COMP_SOUNDFADEPARAMS,   0, 0, nil,

    // params

    /// Version # (currently 0, won't use until we know everyone has paramblocks with this in it)
    kSndVersionCount, _T(""), TYPE_INT, 0, 0,
        p_range, 0, 10000,
        p_default, 0,
        end,
    
    kSoundFileName,     _T("fileName"),     TYPE_STRING,        0, 0,
        end,
    
    kSoundAutoStartCkBx, _T("autoStart"),       TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   kSndSharedParams, TYPE_SINGLECHEKBOX, IDC_COMP_SOUND3D_AUTOSTART_CKBX,
        end,
    
    kSoundLoopCkBx, _T("loop"),     TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   kSndSharedParams, TYPE_SINGLECHEKBOX, IDC_COMP_SOUND3D_LOOPCHKBOX,
        end,
    kSoundLoopName, _T("loopName"), TYPE_STRING,    0, 0,
        end,

    kSndDisableLOD, _T("disableLOD"),       TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   kSndSharedParams, TYPE_SINGLECHEKBOX, IDC_COMP_SOUND_DISABLELOD,
        end,

    kSoundVolumeSlider, _T("volume"),   TYPE_FLOAT,  P_ANIMATABLE,  IDS_SND_VOLUME,
        p_ui,   kSndSharedParams, TYPE_SLIDER,  EDITTYPE_FLOAT, IDC_COMP_SOUND3D_SLIDERVIEWER, IDC_COMP_SOUND3D_VOLSLIDER, 4,
        p_range, -48.0f, 0.f,
        p_default, 0.f,
        end,

    kNotSoOldSoundVolumeSlider, _T(""), TYPE_FLOAT,     0,  0,
        end,

    kOldSoundVolumeSlider, _T(""),  TYPE_INT,       0,  0,
        end,

    kSndCategory, _T("category"),   TYPE_INT,   0, 0,   
        p_range, plSound::kStartType, plSound::kNumTypes - 1,
        p_default, plSound::kSoundFX,
        end,

    /// Fade Parameters rollout
    kSndFadeInEnable, _T("fadeInEnable"),       TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   kSoundFadeParams, TYPE_SINGLECHEKBOX, IDC_SOUND3D_INENABLE,
        end,

    kSndFadeInType, _T("fadeInType"),       TYPE_INT,       0, 0,
        p_default,  0,
        end,

    kSndFadeInLength, _T("fadeInLength"),   TYPE_FLOAT, 0,0,
        p_ui,   kSoundFadeParams, TYPE_SPINNER, EDITTYPE_FLOAT, IDC_SOUND3D_INLENGTH, IDC_SOUND3D_INLENGTHSPIN, 1.0f,
        p_default,  1.f,
        end,

    kSndFadeOutEnable, _T("fadeOutEnable"),     TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   kSoundFadeParams, TYPE_SINGLECHEKBOX, IDC_SOUND3D_OUTENABLE,
        end,

    kSndFadeOutType, _T("fadeOutType"),     TYPE_INT,       0, 0,
        p_default,  0,
        end,

    kSndFadeOutLength, _T("fadeOutLength"), TYPE_FLOAT, 0,0,
        p_ui,   kSoundFadeParams, TYPE_SPINNER, EDITTYPE_FLOAT, IDC_SOUND3D_OUTLENGTH, IDC_SOUND3D_OUTLENGTHSPIN, 1.0f,
        p_default,  1.f,
        end,

    end
);

//// ParamBlock Macros for Waveform Properties Rollout ///////////////////////////////////////////

#define sSndWaveformPropsHeader     kSndWaveformParams, IDD_COMP_SOUNDSRC, IDS_COMP_SOUNDSRC, 0, 0, NULL

#define sSndWaveformPropsParamTemplate \
                                                                                                            \
    kSndAllowChannelSelect, _T( "allowChannelSelect" ), TYPE_BOOL, 0, 0,                                    \
        p_default, 0,                                                                                       \
        p_ui, kSndWaveformParams, TYPE_SINGLECHEKBOX, IDC_SND_ISSTEREO_HIDDEN,                              \
        p_enable_ctrls, 1, kSndChannelSelect,                                                               \
        end,                                                                                                \
                                                                                                            \
    /* Channel select for stereo sources */                                                                 \
    kSndChannelSelect, _T( "sourceChannel" ), TYPE_INT, 0, 0,                                               \
        p_ui,   kSndWaveformParams, TYPE_RADIO, 2, IDC_SND_CHANSRC1, IDC_SND_CHANSRC2,                      \
        p_default, 0,                                                                                       \
        end,                                                                                                \
                                                                                                            \
    kSndPriority,           _T("sndPriority"), TYPE_INT, 0, 0,                                              \
        p_range, 0, 10,                                                                                     \
        p_ui,   kSndWaveformParams, TYPE_SPINNER, EDITTYPE_INT, IDC_SND_PRIORITY, IDC_SND_PRIORITY_SPIN, 1.f,   \
        p_default, 0,                                                                                       \
        end                                                                                             

//// Enums Source EAX Properties Rollout ////////////////////////////////////////////////////////

enum EAXRefs
{
    kEAXEnabled = 128,
    kEAXRoom,
    kEAXRoomHF,
    kEAXRoomAuto,
    kEAXRoomHFAuto,
    kEAXOutsideVolHF,
    kEAXAirAbsorptionFact,
    kEAXRoomRolloffFact,
    kEAXDopplerFact,
    kEAXRolloffFact,

    kEAXEnableOcclusion,
    kEAXOcclusionRegion,
    kEAXStartOcclusion,
    kEAXStartOcclusionLFRatio,
    kEAXStartOcclusionRoomRatio,
    kEAXStartOcclusionDirectRatio,
    kEAXEndOcclusion,
    kEAXEndOcclusionLFRatio,
    kEAXEndOcclusionRoomRatio,
    kEAXEndOcclusionDirectRatio,
    kEAXWhichOccSwapped,
    kEAXTempOcclusion,
    kEAXTempOcclusionLFRatio,
    kEAXTempOcclusionRoomRatio,
    kEAXTempOcclusionDirectRatio,
    kEAXTempOccSwapper
};

//// DialogProc for Source EAX Properties Rollout ///////////////////////////////////////////////

class plEAXPropsDlgProc : public plSingleCompSelProc
{
    IParamBlock2    *fLastBlockSwapped;

    void    ISwapOutOcclusion( IParamBlock2 *pb )
    {
        if( pb == nil )
            return;

        if( pb->GetInt( (ParamID)kEAXWhichOccSwapped ) == 0 )
        {
            // Swap out to start values
            pb->SetValue( (ParamID)kEAXStartOcclusion,              0, pb->GetInt( (ParamID)kEAXTempOcclusion ) );
            pb->SetValue( (ParamID)kEAXStartOcclusionLFRatio,       0, pb->GetFloat( (ParamID)kEAXTempOcclusionLFRatio ) );
            pb->SetValue( (ParamID)kEAXStartOcclusionRoomRatio,     0, pb->GetFloat( (ParamID)kEAXTempOcclusionRoomRatio ) );
            pb->SetValue( (ParamID)kEAXStartOcclusionDirectRatio,   0, pb->GetFloat( (ParamID)kEAXTempOcclusionDirectRatio ) );
        }
        else if( pb->GetInt( (ParamID)kEAXWhichOccSwapped ) == 1 )
        {
            // Swap out to end values
            pb->SetValue( (ParamID)kEAXEndOcclusion,                0, pb->GetInt( (ParamID)kEAXTempOcclusion ) );
            pb->SetValue( (ParamID)kEAXEndOcclusionLFRatio,     0, pb->GetFloat( (ParamID)kEAXTempOcclusionLFRatio ) );
            pb->SetValue( (ParamID)kEAXEndOcclusionRoomRatio,   0, pb->GetFloat( (ParamID)kEAXTempOcclusionRoomRatio ) );
            pb->SetValue( (ParamID)kEAXEndOcclusionDirectRatio, 0, pb->GetFloat( (ParamID)kEAXTempOcclusionDirectRatio ) );
        }

        // Set to "none swapped"
        pb->SetValue( (ParamID)kEAXWhichOccSwapped, 0, (int)-1 );

        fLastBlockSwapped = nil;
    }

    void    ISwapInOcclusion( IParamBlock2 *pb, int which )
    {
        if( pb == nil )
            return;

        if( which == 0 )
        {
            // Swap in from start values
            pb->SetValue( (ParamID)kEAXTempOcclusion,               0, pb->GetInt( (ParamID)kEAXStartOcclusion ) );
            pb->SetValue( (ParamID)kEAXTempOcclusionLFRatio,        0, pb->GetFloat( (ParamID)kEAXStartOcclusionLFRatio ) );
            pb->SetValue( (ParamID)kEAXTempOcclusionRoomRatio,      0, pb->GetFloat( (ParamID)kEAXStartOcclusionRoomRatio ) );
            pb->SetValue( (ParamID)kEAXTempOcclusionDirectRatio,    0, pb->GetFloat( (ParamID)kEAXStartOcclusionDirectRatio ) );
        }
        else
        {
            // Swap in from end values
            pb->SetValue( (ParamID)kEAXTempOcclusion,               0, pb->GetInt( (ParamID)kEAXEndOcclusion ) );
            pb->SetValue( (ParamID)kEAXTempOcclusionLFRatio,        0, pb->GetFloat( (ParamID)kEAXEndOcclusionLFRatio ) );
            pb->SetValue( (ParamID)kEAXTempOcclusionRoomRatio,  0, pb->GetFloat( (ParamID)kEAXEndOcclusionRoomRatio ) );
            pb->SetValue( (ParamID)kEAXTempOcclusionDirectRatio,    0, pb->GetFloat( (ParamID)kEAXEndOcclusionDirectRatio ) );
        }

        pb->SetValue( (ParamID)kEAXWhichOccSwapped, 0, (int)which );
        if( pb->GetMap() != nil )
            pb->GetMap()->UpdateUI( 0 );

        fLastBlockSwapped = pb;
    }

    class plOccPreset
    {
        public:
            char    *fName;
            Int16   fOcc;
            float   fLFRatio;
            float   fRoomRatio;
    };

    plOccPreset fPresets[ 9 ];

    void    ILoadOccPresets( HWND hDlg )
    {
        HWND    combo = GetDlgItem( hDlg, IDC_EAX_OCCPRESET );
        int     i;

        
        ComboBox_ResetContent( combo );
        for( i = 0; i < 9; i++ )
            ComboBox_AddString( combo, fPresets[ i ].fName );

        ComboBox_SetCurSel( combo, 0 );
    }

public:

    void    FlushSwappedPBs( void )
    {
        if( fLastBlockSwapped != nil )
            ISwapOutOcclusion( fLastBlockSwapped );
    }

    plEAXPropsDlgProc() : plSingleCompSelProc( kEAXOcclusionRegion, IDC_EAX_OCCREGION, "Select the soft region to blend these EAX occlusion properties" )
    {
        int i;

        // Annoyingly, the EAX headers don't have a convenient array, just some #defines
        static char occNames[][ 64 ] = { "Single window", "Double window", "Thin door", "Thick door",
                                    "Wood wall", "Brick wall", "Stone wall", "Curtain" };
        Int16   occValues[] = { EAX_MATERIAL_SINGLEWINDOW, EAX_MATERIAL_DOUBLEWINDOW, EAX_MATERIAL_THINDOOR,
                                EAX_MATERIAL_THICKDOOR, EAX_MATERIAL_WOODWALL, EAX_MATERIAL_BRICKWALL,
                                EAX_MATERIAL_STONEWALL, EAX_MATERIAL_CURTAIN };
        float   occLFValues[] = { EAX_MATERIAL_SINGLEWINDOWLF, EAX_MATERIAL_DOUBLEWINDOWLF, EAX_MATERIAL_THINDOORLF,
                                EAX_MATERIAL_THICKDOORLF, EAX_MATERIAL_WOODWALLLF, EAX_MATERIAL_BRICKWALLLF,
                                EAX_MATERIAL_STONEWALLLF, EAX_MATERIAL_CURTAINLF };
        Int16   occRoomValues[] = { EAX_MATERIAL_SINGLEWINDOWROOMRATIO, EAX_MATERIAL_DOUBLEWINDOWROOMRATIO, EAX_MATERIAL_THINDOORROOMRATIO,
                                EAX_MATERIAL_THICKDOORROOMRATIO, EAX_MATERIAL_WOODWALLROOMRATIO, EAX_MATERIAL_BRICKWALLROOMRATIO,
                                EAX_MATERIAL_STONEWALLROOMRATIO, EAX_MATERIAL_CURTAINROOMRATIO };

        for( i = 1; i < 9; i++ )
        {
            fPresets[ i ].fName = occNames[ i - 1 ];
            fPresets[ i ].fOcc = occValues[ i - 1 ];
            fPresets[ i ].fLFRatio = occLFValues[ i - 1 ];
            fPresets[ i ].fRoomRatio = occRoomValues[ i - 1 ];
        }
        fPresets[ 0 ].fName = "None";
        fPresets[ 0 ].fOcc = 0;
        fPresets[ 0 ].fLFRatio = 0.25f;
        fPresets[ 0 ].fRoomRatio = 1.5f;
    }

    void DeleteThis() {}

    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        IParamBlock2 *pblock = map->GetParamBlock();

        switch( msg )
        {
            case WM_INITDIALOG:
                pblock->SetValue( (ParamID)kEAXTempOccSwapper, 0, (int)0 );
                ISwapInOcclusion( pblock, 0 );
                ILoadOccPresets( hWnd );
                break;
            
            case WM_DESTROY:
                ISwapOutOcclusion( pblock );
                return 0;

            case WM_SHOWWINDOW:
                if( wParam )
                {
                    pblock->SetValue( (ParamID)kEAXTempOccSwapper, 0, (int)0 );
                    ISwapInOcclusion( pblock, 0 );
                }
                else
                    ISwapOutOcclusion( pblock );
                return 0;

            case WM_COMMAND:
                if( LOWORD( wParam ) == IDC_EAX_STARTOCC || LOWORD( wParam ) == IDC_EAX_ENDOCC )
                {
                    // Our radio button to switch between start and end values was hit. So swap out the values
                    // from the temp ones
                    ISwapOutOcclusion( pblock );
                    ISwapInOcclusion( pblock, ( LOWORD( wParam ) == IDC_EAX_STARTOCC ) ? 0 : 1 );
                    return true;
                }
                else if( LOWORD( wParam ) == IDC_EAX_OCCPRESET && HIWORD( wParam ) == CBN_SELCHANGE )
                {
                    HWND combo = GetDlgItem( hWnd, IDC_EAX_OCCPRESET );
                    int idx = ComboBox_GetCurSel( combo );
                    if( idx != CB_ERR )
                    {
                        // Load from presets
                        pblock->SetValue( (ParamID)kEAXTempOcclusion, 0, (int)fPresets[ idx ].fOcc );
                        pblock->SetValue( (ParamID)kEAXTempOcclusionLFRatio, 0, fPresets[ idx ].fLFRatio );
                        pblock->SetValue( (ParamID)kEAXTempOcclusionRoomRatio, 0, fPresets[ idx ].fRoomRatio );
                    }
                    return true;
                }
                break;
        }   
        return plSingleCompSelProc::DlgProc( t, map, hWnd, msg, wParam, lParam );
    }

};  

static plEAXPropsDlgProc    sEAXPropsDlgProc;

//// ParamBlock for Source EAX Properties Rollout ///////////////////////////////////////////////
//  Note: we can't make this a real ParamBlock and do P_INCLUDE_PARAMS because, in Discreet's 
//  amazing method of doing things, we can't INCLUDE more than one ParamBlock in any other PB.
//  So either we chain them together here (and thus make them dependent on one another, which
//  is lame) or we just make the whole damned thing a #define, which is all P_INCLUDE_PARAMS
//  really does anyway.

#define sSndEAXPropsParamHeader     kSndEAXParams, IDD_COMP_EAXBUFFER, IDS_COMP_EAXBUFFER, 0, APPENDROLL_CLOSED, &sEAXPropsDlgProc
//static ParamBlockDesc2    sSndEAXPropsParamTemplate
//(
    /// Main def
//  plComponent::kBlkComp + 1, _T("sndEAXProps"), 0, nil, P_AUTO_UI + P_MULTIMAP + P_AUTO_CONSTRUCT, plComponent::kRefComp, 

//  1, 
//  kSndEAXParams, IDD_COMP_EAXBUFFER, IDS_COMP_EAXBUFFER, 0, 0, nil,   

#define sSndEAXPropsParamTemplate \
                                                                                                            \
    kEAXEnabled, _T("eaxEnabled"), TYPE_BOOL, 0, 0,                                                         \
        p_default, 0,                                                                                       \
        p_ui, kSndEAXParams, TYPE_SINGLECHEKBOX, IDC_EAX_ENABLE,                                            \
        p_enable_ctrls, 10, kEAXRoom, kEAXRoomHF, kEAXRoomAuto, kEAXRoomHFAuto,                             \
                kEAXOutsideVolHF, kEAXAirAbsorptionFact, kEAXRoomRolloffFact, kEAXDopplerFact, kEAXRolloffFact,         \
                kEAXEnableOcclusion,                                                                        \
        end,                                                                                                \
                                                                                                            \
    kEAXRoom, _T("eaxRoom"),    TYPE_INT,       0,  0,                                                      \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_INT, IDC_EAX_ROOM_EDIT, IDC_EAX_ROOM, 8,               \
        p_range, -10000, 1000,                                                                              \
        p_default, 0,                                                                                       \
        end,                                                                                                \
                                                                                                            \
    kEAXRoomHF, _T("eaxRoomHF"),    TYPE_INT,       0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_INT, IDC_EAX_ROOMHF_EDIT, IDC_EAX_ROOMHF, 8,           \
        p_range, -10000, 0,                                                                                 \
        p_default, 0,                                                                                       \
        end,                                                                                                \
                                                                                                            \
    kEAXRoomAuto,       _T( "eaxRoomAuto" ), TYPE_BOOL, 0, 0,                                               \
        p_default, 1,                                                                                       \
        p_ui, kSndEAXParams, TYPE_SINGLECHEKBOX, IDC_EAX_ROOMAUTO,                                          \
        end,                                                                                                \
                                                                                                            \
    kEAXRoomHFAuto,     _T( "eaxRoomHFAuto" ), TYPE_BOOL, 0, 0,                                             \
        p_default, 1,                                                                                       \
        p_ui, kSndEAXParams, TYPE_SINGLECHEKBOX, IDC_EAX_ROOMHFAUTO,                                        \
        end,                                                                                                    \
                                                                                                            \
    kEAXOutsideVolHF, _T("eaxOutsideVolHF"),    TYPE_INT,       0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_INT, IDC_EAX_OUTSIDEVOLHF_EDIT, IDC_EAX_OUTSIDEVOLHF, 8,           \
        p_range, -10000, 0,                                                                                 \
        p_default, 0,                                                                                       \
        end,                                                                                                \
                                                                                                            \
    kEAXAirAbsorptionFact, _T("eaxAirAbsorptionFact"),  TYPE_FLOAT,     0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_FLOAT, IDC_EAX_AIRABSORPTFACT_EDIT, IDC_EAX_AIRABSORPTFACT, 8,         \
        p_range, 0.f, 10.f,                                                                                 \
        p_default, 1.f,                                                                                     \
        end,                                                                                                \
                                                                                                            \
    kEAXRoomRolloffFact, _T("eaxRoomRolloffFact"),  TYPE_FLOAT,     0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_FLOAT, IDC_EAX_ROOMROLLOFFFACT_EDIT, IDC_EAX_ROOMROLLOFFFACT, 8,           \
        p_range, 0.f, 10.f,                                                                                 \
        p_default, 0.f,                                                                                     \
        end,                                                                                                \
                                                                                                            \
    kEAXDopplerFact, _T("eaxDopplerFact"),  TYPE_FLOAT,     0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_FLOAT, IDC_EAX_DOPPLERFACT_EDIT, IDC_EAX_DOPPLERFACT, 8,           \
        p_range, 0.f, 10.f,                                                                                 \
        p_default, 0.f,                                                                                     \
        end,                                                                                                \
                                                                                                            \
    kEAXRolloffFact, _T("eaxRolloffFact"),  TYPE_FLOAT,     0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_FLOAT, IDC_EAX_ROLLOFFFACT_EDIT, IDC_EAX_ROLLOFFFACT, 8,           \
        p_range, 0.f, 10.f,                                                                                 \
        p_default, 0.f,                                                                                     \
        end,                                                                                                \
                                                                                                            \
    kEAXEnableOcclusion, _T("eaxEnableOcclusion"), TYPE_BOOL, 0, 0,                                         \
        p_default, 0,                                                                                       \
        p_ui, kSndEAXParams, TYPE_SINGLECHEKBOX, IDC_EAX_ENABLEOCCLUSION,                                   \
        p_enable_ctrls, 6, kEAXOcclusionRegion, kEAXTempOcclusion, kEAXTempOcclusionLFRatio, kEAXTempOcclusionRoomRatio,    \
                kEAXTempOcclusionDirectRatio, kEAXTempOccSwapper,                                           \
        end,                                                                                                \
                                                                                                            \
    kEAXOcclusionRegion, _T("eaxOcclusionRegion"),  TYPE_INODE,     0,  0,                                      \
        end,                                                                                                \
                                                                                                            \
    kEAXStartOcclusion, _T("eaxStartOcclusion"),    TYPE_INT,       0,  0,                                                  \
        p_range, -10000, 0, p_default, 0,                                                                   \
        end,                                                                                                \
                                                                                                            \
    kEAXStartOcclusionLFRatio, _T("eaxStartOccLFRatio"),    TYPE_FLOAT,     0,  0,                                                  \
        p_range, 0.f, 1.f, p_default, 0.25f,                                                                \
        end,                                                                                                \
                                                                                                            \
    kEAXStartOcclusionRoomRatio, _T("eaxStartOccRoomRatio"),    TYPE_FLOAT,     0,  0,                                                  \
        p_range, 0.f, 10.f, p_default, 1.5f,                                                                \
        end,                                                                                                \
                                                                                                            \
    kEAXStartOcclusionDirectRatio, _T("eaxStartOccDirectRatio"),    TYPE_FLOAT,     0,  0,                                                  \
        p_range, 0.f, 10.f, p_default, 1.0f,                                                                \
        end,                                                                                                \
                                                                                                            \
    kEAXEndOcclusion, _T("eaxEndOcclusion"),    TYPE_INT,       0,  0,                                                  \
        p_range, -10000, 0, p_default, 0,                                                                   \
        end,                                                                                                \
                                                                                                            \
    kEAXEndOcclusionLFRatio, _T("eaxEndOccLFRatio"),    TYPE_FLOAT,     0,  0,                                                  \
        p_range, 0.f, 1.f, p_default, 0.25f,                                                                \
        end,                                                                                                \
                                                                                                            \
    kEAXEndOcclusionRoomRatio, _T("eaxEndOccRoomRatio"),    TYPE_FLOAT,     0,  0,                                                  \
        p_range, 0.f, 10.f, p_default, 1.5f,                                                                \
        end,                                                                                                \
                                                                                                            \
    kEAXEndOcclusionDirectRatio, _T("eaxEndOccDirectRatio"),    TYPE_FLOAT,     0,  0,                                                  \
        p_range, 0.f, 10.f, p_default, 1.0f,                                                                \
        end,                                                                                                \
                                                                                                            \
    kEAXWhichOccSwapped, _T("eaxWhichOccSwapped"),  TYPE_INT,       0,  0,                                                  \
        end,                                                                                                \
                                                                                                            \
    kEAXTempOccSwapper, _T("eaxOccSwapper"),    TYPE_INT,       0,  0,                                      \
        p_ui, kSndEAXParams, TYPE_RADIO, 2, IDC_EAX_STARTOCC, IDC_EAX_ENDOCC,                               \
        p_default, 0,                                                               \
        end,                                                                                                \
                                                                                                            \
    kEAXTempOcclusion, _T("eaxTempOcclusion"),  TYPE_INT,       0,  0,                                      \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_INT, IDC_EAX_OCCLUSION_EDIT, IDC_EAX_OCCLUSION, 8,     \
        p_range, -10000, 0, p_default, 0,                                                                   \
        end,                                                                                                \
                                                                                                            \
    kEAXTempOcclusionLFRatio, _T("eaxTempOccLFRatio"),  TYPE_FLOAT,     0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_FLOAT, IDC_EAX_OCCLFRATIO_EDIT, IDC_EAX_OCCLFRATIO, 8,         \
        p_range, 0.f, 1.f, p_default, 0.25f,                                                                \
        end,                                                                                                \
                                                                                                            \
    kEAXTempOcclusionRoomRatio, _T("eaxTempOccRoomRatio"),  TYPE_FLOAT,     0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_FLOAT, IDC_EAX_OCCROOMRATIO_EDIT, IDC_EAX_OCCROOMRATIO, 8,         \
        p_range, 0.f, 10.f, p_default, 1.5f,                                                                \
        end,                                                                                                \
                                                                                                            \
    kEAXTempOcclusionDirectRatio, _T("eaxTempOccDirectRatio"),  TYPE_FLOAT,     0,  0,                                                  \
        p_ui,   kSndEAXParams, TYPE_SLIDER, EDITTYPE_FLOAT, IDC_EAX_OCCDIRECTRATIO_EDIT, IDC_EAX_OCCDIRECTRATIO, 8,         \
        p_range, 0.f, 10.f, p_default, 1.0f,                                                                \
        end                                                                                             

//  , end
//);


void    plBaseSoundEmitterComponent::IGrabEAXParams( plSound *sound, plErrorMsg *pErrMsg )
{
    sEAXPropsDlgProc.FlushSwappedPBs();

    plEAXSourceSettings &settings = sound->GetEAXSettings();

    if( fCompPB->GetInt( (ParamID)kEAXEnabled ) )
    {
        settings.Enable( true );
        settings.SetRoomParams( fCompPB->GetInt( (ParamID)kEAXRoom ), fCompPB->GetInt( (ParamID)kEAXRoomHF ),
                                fCompPB->GetInt( (ParamID)kEAXRoomAuto ), fCompPB->GetInt( (ParamID)kEAXRoomHFAuto ) );
        settings.SetOutsideVolHF( fCompPB->GetInt( (ParamID)kEAXOutsideVolHF ) );
        settings.SetFactors( fCompPB->GetFloat( (ParamID)kEAXAirAbsorptionFact ), 
                            fCompPB->GetFloat( (ParamID)kEAXRoomRolloffFact ),
                            fCompPB->GetFloat( (ParamID)kEAXDopplerFact ),
                            fCompPB->GetFloat( (ParamID)kEAXRolloffFact ) );

        if( fCompPB->GetInt( (ParamID)kEAXEnableOcclusion ) )
        {
            settings.GetSoftStarts().SetOcclusion( fCompPB->GetInt( (ParamID)kEAXStartOcclusion ),
                                                    fCompPB->GetFloat( (ParamID)kEAXStartOcclusionLFRatio ),
                                                    fCompPB->GetFloat( (ParamID)kEAXStartOcclusionRoomRatio ),
                                                    fCompPB->GetFloat( (ParamID)kEAXStartOcclusionDirectRatio ) );
            settings.GetSoftEnds().SetOcclusion( fCompPB->GetInt( (ParamID)kEAXEndOcclusion ),
                                                    fCompPB->GetFloat( (ParamID)kEAXEndOcclusionLFRatio ),
                                                    fCompPB->GetFloat( (ParamID)kEAXEndOcclusionRoomRatio ),
                                                    fCompPB->GetFloat( (ParamID)kEAXEndOcclusionDirectRatio ) );

            plSoftVolBaseComponent* softComp = plSoftVolBaseComponent::GetSoftComponent( fCompPB->GetINode( (ParamID)kEAXOcclusionRegion ) );
            if( softComp != nil )
            {
                plKey softKey = softComp->GetSoftVolume();
                if( softKey != nil )
                {
                    // Make sure we set checkListener on the sucker
                    plSoftVolume *vol = plSoftVolume::ConvertNoRef( softKey->GetObjectPtr() );
                    if( vol != nil )
                    {
                        vol->SetCheckListener();
                        hsgResMgr::ResMgr()->AddViaNotify( softKey, TRACKED_NEW plGenRefMsg( sound->GetKey(), plRefMsg::kOnCreate, 0, plSound::kRefSoftOcclusionRegion ), plRefFlags::kActiveRef );
                    }
                }
            }
            else
            {
                pErrMsg->Set(true, "Sound Emitter Error", "The Sound emitter component %s is checked to use an occlusion soft region, but no soft region is specified. Ignoring setting.", GetINode()->GetName() ).Show();
                pErrMsg->Set(false);
                settings.GetSoftStarts().Reset();
                settings.GetSoftEnds().Reset();
            }
        }
        else
        {
            settings.GetSoftStarts().Reset();
            settings.GetSoftEnds().Reset();
        }
    }
    else
        settings.Enable( false );
}


/////////////////////////////////////////////////////////////////////////////////////////////////
//
//  SoundEmitter Component
//
//

/*
class plBaseComponentProc : public ParamMap2UserDlgProc
{
protected:
    void ILoadComboBox(HWND hComboBox, const char *names[])
    {
        SendMessage(hComboBox, CB_RESETCONTENT, 0, 0);
        for (int i = 0; names[i]; i++)
            SendMessage(hComboBox, CB_ADDSTRING, 0, (LPARAM)names[i]);
        SendMessage(hComboBox, CB_SETCURSEL, 0, 0);
    }

    void ILoadListBox(HWND hListBox, IParamBlock2 *pb, int param, const char *names[])
    {
        SendMessage(hListBox, LB_RESETCONTENT, 0, 0);
        for (int i = 0; i < pb->Count(param); i++)
        {
            int idx = pb->GetInt(param, 0, i);
            SendMessage(hListBox, LB_ADDSTRING, 0, (LPARAM)names[idx]);
        }
    }

    void    IConvertOldVolume( IParamBlock2 *pb )
    {
        int oldVol = pb->GetInt( kOldSoundVolumeSlider, 0 );
        if( oldVol != 4999 )
        {
            float v = (float)( oldVol - 5000 ) / 5000.f;
            pb->SetValue( kSoundVolumeSlider, 0, v );
            pb->SetValue( kOldSoundVolumeSlider, 0, 4999 );
        }
    }

};
*/
class plSound3DEmitterComponent : public plBaseSoundEmitterComponent
{
public:
    plSound3DEmitterComponent();
    virtual ~plSound3DEmitterComponent();

    // Internal setup and write-only set properties on the MaxNode. No reading
    // of properties on the MaxNode, as it's still indeterminant.
    hsBool SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg);

    hsBool PreConvert(plMaxNode *node, plErrorMsg *pErrMsg);
    hsBool Convert(plMaxNode *node, plErrorMsg *pErrMsg);

    virtual hsBool  IsLocalOnly( void ) const { if( fCompPB->GetInt( (ParamID)kSndIsLocalOnly ) ) return true; else return false; }


    virtual hsBool  ConvertGrouped( plMaxNode *baseNode, hsTArray<plBaseSoundEmitterComponent *> &groupArray, plErrorMsg *pErrMsg );

protected:

    bool IValidate(plMaxNode *node, plErrorMsg *pErrMsg);

    virtual hsBool  IAllowStereoFiles( void ) const { return false; }

    void    ISetParameters( plWin32Sound *destSound, plErrorMsg *pErrMsg );

    virtual hsBool  IGetCategoryList( char **&catList, int *&catKonstantList );
};

class plSoundComponentProc : public plAudioBaseComponentProc
{
    hsBool  fHandleCategory;
    int     fCategoryCtrlID;
    ParamID fCategoryParamID;

public:
    void DeleteThis() {}

    void ILoadLoops(HWND hLoop, IParamBlock2 *pb)
    {
        SendMessage(hLoop, CB_RESETCONTENT, 0, 0);
        SendMessage(hLoop, CB_ADDSTRING, 0, (LPARAM)"(Entire Sound)");

        const char *loop = pb->GetStr(kSoundLoopName);
        if (!loop)
            loop = "";

        SegmentMap *segMap = GetCompWaveSegmentMap(pb->GetStr(kSoundFileName));
        
        if (segMap)
        {
            for (SegmentMap::iterator it = segMap->begin(); it != segMap->end(); it++)
            {
                SegmentSpec *spec = it->second;
                int idx = SendMessage(hLoop, CB_ADDSTRING, 0, (LPARAM)spec->fName);
                SendMessage(hLoop, CB_SETITEMDATA, idx, 1);

                if (!strcmp(spec->fName, loop))
                    SendMessage(hLoop, CB_SETCURSEL, idx, 0);
            }

            
            DeleteSegmentMap(segMap);
        }

        if (SendMessage(hLoop, CB_GETCURSEL, 0, 0) == CB_ERR)
            SendMessage(hLoop, CB_SETCURSEL, 0, 0);
    }

    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch( msg )
        {
            case WM_INITDIALOG:
                {
                    IParamBlock2 *pblock = map->GetParamBlock();

                    plSound3DEmitterComponent *soundComp = (plSound3DEmitterComponent *)map->GetParamBlock()->GetOwner();
                    hsAssert( soundComp != nil, "Problem trying to select a sound file" );

                    IUpdateSoundButton( soundComp, hWnd, IDC_COMP_SOUND3D_FILENAME_BTN, plBaseSoundEmitterComponent::kBaseSound );
                    
                    IConvertOldVolume( pblock );

                    fHandleCategory = soundComp->UpdateCategories( hWnd, fCategoryCtrlID, fCategoryParamID );
                }
                
                {
                    ILoadLoops(GetDlgItem(hWnd, IDC_LOOP_COMBO), map->GetParamBlock());
    #if 0
                    map->SetTooltip(kSoundFileName,         true, _T("A sound file name."));
                    map->SetTooltip(kLoopBegin,             true, _T("The distance, in feet, at which the sound begins to be less audible."));
                    map->SetTooltip(kLoopEnd,               true, _T("The distance, in feet, at which the sound is no longer audible."));
                    map->SetTooltip(kSoundLoopSegBeg2,      true, _T("The distance, in feet, at which the sound begins to be less audible."));
                    map->SetTooltip(kSoundLoopSegEnd2,      true, _T("The distance, in feet, at which the sound is no longer audible."));
                    map->SetTooltip(kSoundAutoStartCkBx,    true, _T("Check to play the sound file upon game start."));
                    map->SetTooltip(kSoundLoopSegBegDDList, true, _T("The time, keyframe or percentage at which looping is to begin."));
                    map->SetTooltip(kSoundLoopSegEndDDList, true, _T("The time, keyframe or percentage at which looping is to end."));
    #endif
                    
                    
                    break;
                }
            
        case WM_COMMAND:
            if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_LOOP_COMBO)
            {
                int idx = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
                if (idx == CB_ERR || SendMessage((HWND)lParam, CB_GETITEMDATA, idx, 0) == 0)
                    map->GetParamBlock()->SetValue((ParamID)kSoundLoopName, 0, "");
                else
                {
                    char buf[256];
                    SendMessage((HWND)lParam, CB_GETLBTEXT, idx, (LPARAM)buf);
                    map->GetParamBlock()->SetValue((ParamID)kSoundLoopName, 0, buf);
                }
                return true;
            }
            else if( LOWORD( wParam ) == IDC_COMP_SOUND3D_FILENAME_BTN )
            {
                plSound3DEmitterComponent *soundComp = (plSound3DEmitterComponent *)map->GetParamBlock()->GetOwner();
                hsAssert( soundComp != nil, "Problem trying to select a sound file" );
                
                ISelectSoundFile( soundComp, hWnd, IDC_COMP_SOUND3D_FILENAME_BTN, plBaseSoundEmitterComponent::kBaseSound );
            }
            else if( fHandleCategory && LOWORD( wParam ) == fCategoryCtrlID )
            {
                HWND ctrl = GetDlgItem( hWnd, fCategoryCtrlID );
                int idx = ComboBox_GetCurSel( ctrl );
                if( idx != CB_ERR )
                {
                    int cat = ComboBox_GetItemData( ctrl, idx );
                    map->GetParamBlock()->SetValue( (ParamID)fCategoryParamID, 0, cat );
                }
                else
                    map->GetParamBlock()->SetValue( (ParamID)fCategoryParamID, 0, (int)0 );
            }
            break;
        }
        
        return plAudioBaseComponentProc::DlgProc( t, map, hWnd, msg, wParam, lParam );
    }
    
};  

class plSoundFadeParamsDlgProc : public plAudioBaseComponentProc
{
    public:
        void DeleteThis() {}

        BOOL DlgProc( TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
        {
            const char          *types[] = { "Linear", "Logarithmic", "Exponential", NULL };
            IParamBlock2    *pb = map->GetParamBlock();
            BOOL            enable;


            switch( msg )
            {
                case WM_INITDIALOG:
                    // Load fade types

                    ILoadComboBox( GetDlgItem( hWnd, IDC_SOUND3D_INTYPE ), types );
                    ILoadComboBox( GetDlgItem( hWnd, IDC_SOUND3D_OUTTYPE ), types );

                    SendDlgItemMessage( hWnd, IDC_SOUND3D_INTYPE, CB_SETCURSEL, (WPARAM)pb->GetInt( (ParamID)kSndFadeInType, 0 ), 0 );
                    SendDlgItemMessage( hWnd, IDC_SOUND3D_OUTTYPE, CB_SETCURSEL, (WPARAM)pb->GetInt( (ParamID)kSndFadeOutType, 0 ), 0 );

                    enable = pb->GetInt( (ParamID)kSndFadeInEnable, 0 ) ? TRUE : FALSE;
                    EnableWindow( GetDlgItem( hWnd, IDC_SOUND3D_INTYPE ), enable );
                    EnableWindow( GetDlgItem( hWnd, IDC_SOUND3D_INLENGTH ), enable );

                    enable = pb->GetInt( (ParamID)kSndFadeOutEnable, 0 ) ? TRUE : FALSE;
                    EnableWindow( GetDlgItem( hWnd, IDC_SOUND3D_OUTTYPE ), enable );
                    EnableWindow( GetDlgItem( hWnd, IDC_SOUND3D_OUTLENGTH ), enable );

                    break;

                case WM_COMMAND:
                    if( HIWORD( wParam ) == CBN_SELCHANGE )
                    { 
                        if( LOWORD( wParam ) == IDC_SOUND3D_INTYPE )
                            pb->SetValue( (ParamID)kSndFadeInType, 0, (int)SendDlgItemMessage( hWnd, IDC_SOUND3D_INTYPE, CB_GETCURSEL, 0, 0 ) );
                        else if( LOWORD( wParam ) == IDC_SOUND3D_OUTTYPE )
                            pb->SetValue( (ParamID)kSndFadeOutType, 0, (int)SendDlgItemMessage( hWnd, IDC_SOUND3D_OUTTYPE, CB_GETCURSEL, 0, 0 ) );
                    }

                    else if( LOWORD( wParam ) == IDC_SOUND3D_INENABLE )
                    {
                        // Enable/disable controls manually
                        enable = pb->GetInt( (ParamID)kSndFadeInEnable, 0 ) ? TRUE : FALSE;
                        EnableWindow( GetDlgItem( hWnd, IDC_SOUND3D_INTYPE ), enable );
                        EnableWindow( GetDlgItem( hWnd, IDC_SOUND3D_INLENGTH ), enable );
                    }
                    else if( LOWORD( wParam ) == IDC_SOUND3D_OUTENABLE )
                    {
                        // Enable/disable controls manually
                        enable = pb->GetInt( (ParamID)kSndFadeOutEnable, 0 ) ? TRUE : FALSE;
                        EnableWindow( GetDlgItem( hWnd, IDC_SOUND3D_OUTTYPE ), enable );
                        EnableWindow( GetDlgItem( hWnd, IDC_SOUND3D_OUTLENGTH ), enable );
                    }
                    break;

            }

            return false;
        }
};  

//  For the paramblock below.
static plSoundComponentProc     gSoundCompProc;
static plSoundFadeParamsDlgProc gSoundFadeParamsProc;

//Max desc stuff necessary below.
CLASS_DESC(plSound3DEmitterComponent, gSound3DEmitterDesc, "Sound 3D",  "Sound3D", COMP_TYPE_AUDIO, SOUND_3D_COMPONENT_ID)

ParamBlockDesc2 gSound3DEmitterBk
(
    plComponent::kBlkComp, _T("3D Sound"), 0, &gSound3DEmitterDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP + P_INCLUDE_PARAMS, plComponent::kRefComp,

    6,  // Number of rollouts
    sSoundSharedPBHeader(IDS_COMP_SOUNDBASE),
    kS3DBaseParams,         IDD_COMP_SOUND3D,           IDS_COMP_SOUND3DS,          0, 0, &gSoundCompProc,
    kS3DSoftVolumeParams,   IDD_COMP_SOUND_SOFTPARAMS,  IDS_COMP_SOUNDSOFTPARAMS,   0, 0, &gSoundSoftVolumeSelProc,
    sSndWaveformPropsHeader,
    sSndEAXPropsParamHeader,

    // Included paramblock
    &sSoundSharedPB,

    // Waveform props define
    sSndWaveformPropsParamTemplate,

    // params

    kSndIsLocalOnly, _T("noNetworkSynch"),      TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   kS3DBaseParams, TYPE_SINGLECHEKBOX, IDC_SND_LOCALONLY,
        end,
    

    kMinFallOffRad, _T("minFalloff"),   TYPE_INT,   0, 0,   
        p_range, 1, 1000000000,
        p_default, 1,
        p_ui,   kS3DBaseParams, TYPE_SPINNER,   EDITTYPE_POS_INT,
        IDC_COMP_SOUND3D_EDIT3, IDC_COMP_SOUND3D_SPIN3, SPIN_AUTOSCALE,
        end,

    kMaxFallOffRad, _T("maxFalloff"),   TYPE_INT,   0, 0,   
        p_range, 1, 1000000000,
        p_default, 1000000000,
        p_ui,   kS3DBaseParams, TYPE_SPINNER,   EDITTYPE_POS_INT,
        IDC_COMP_SOUND3D_EDIT4, IDC_COMP_SOUND3D_SPIN4, SPIN_AUTOSCALE,
        end,

    kSoundConeBool, _T("SoundCone"),        TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_enable_ctrls, 3, kSoundIConeAngle, kSoundOConeAngle, kSoundOConeVolumeSlider,
        p_ui,   kS3DBaseParams, TYPE_SINGLECHEKBOX, IDC_COMP_SOUND3D_CONEEFFECT_CKBX,
        end,


    kSoundIConeAngle,   _T("insideConeAngle"), TYPE_INT,    0,  0,
        p_range, 0, 360,
        p_default, 360,
        p_ui,   kS3DBaseParams, TYPE_SPINNER, EDITTYPE_INT,
        IDC_COMP_SOUND3D_ICONEANGLE_EDIT, IDC_COMP_SOUND3D_ICONEANGLE_SPIN, 1.0f,
        end,

    kSoundOConeAngle,   _T("outsideConeAngle"), TYPE_INT,   0,  0,
        p_range, 0, 360,
        p_default, 360,
        p_ui,   kS3DBaseParams, TYPE_SPINNER, EDITTYPE_INT,
        IDC_COMP_SOUND3D_OCONEANGLE_EDIT, IDC_COMP_SOUND3D_OCONEANGLE_SPIN, 1.0f,
        end,
        
    kSoundOConeVolumeSlider, _T("outsideConeVolSlider"),    TYPE_INT,       0,  0,
        p_ui,   kS3DBaseParams, TYPE_SLIDER,    EDITTYPE_INT, IDC_COMP_SOUND3D_SLIDERVIEWER2, IDC_COMP_SOUND3D_VOLSLIDER2, 4,
        p_range, 5000,  10000,
        p_default, 5000,
        end,

    /// Soft Region/Volume Parameters rollout
    kSndSoftRegionEnable,   _T( "enableSoftRegion" ), TYPE_BOOL, 0, 0,
        p_ui,   kS3DSoftVolumeParams, TYPE_SINGLECHEKBOX, IDC_SOUND_SOFTENABLE,
        p_default, FALSE,
        end,

    kSndSoftRegion, _T("softRegion"),   TYPE_INODE,     0, 0,
        p_prompt, IDS_COMP_SOUNDSOFTSELECT,
        p_accessor, &gSoundSoftVolAccessor,
        end,
                
    kSndIncidental, _T("isIncidental"), TYPE_INT, 0, 0,
        p_default,  FALSE,
        p_ui,   kS3DBaseParams, TYPE_SINGLECHEKBOX, IDC_SND_INCIDENTAL,
        end,
        
    sSndEAXPropsParamTemplate,  // it's a #define

    end
);


plSound3DEmitterComponent::plSound3DEmitterComponent()
{
    fClassDesc = &gSound3DEmitterDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

plSound3DEmitterComponent::~plSound3DEmitterComponent()
{
}

//// IGetCategoryList ///////////////////////////////////////////////////////////////////////////
//  Returns a list of the categories and konstants supported for this type of sound

hsBool  plSound3DEmitterComponent::IGetCategoryList( char **&catList, int *&catKonstantList )
{
    static char *cats[] = { "Background Music", "Ambience", "Sound FX", "GUI", "NPC Voice", "" };
    static int  catEnums[] = { plSound::kBackgroundMusic, plSound::kAmbience, plSound::kSoundFX, plSound::kGUISound, plSound::kNPCVoices };

    catList = cats;
    catKonstantList = catEnums;

    return true;
}

// Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plSound3DEmitterComponent::SetupProperties(plMaxNode *pNode, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::SetupProperties( pNode, pErrMsg );
}

bool plSound3DEmitterComponent::IValidate(plMaxNode *node, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::IValidate( node, pErrMsg );
}

hsBool plSound3DEmitterComponent::PreConvert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::PreConvert( node, pErrMsg, SOUND_3D_COMPONENT_ID );
}

void    plSound3DEmitterComponent::ISetParameters( plWin32Sound *destSound, plErrorMsg *pErrMsg )
{
    ISetBaseParameters( destSound, pErrMsg );

    int min = fCompPB->GetInt( (ParamID)kMinFallOffRad );
    int max = fCompPB->GetInt( (ParamID)kMaxFallOffRad );
    float Vol = IGetDigitalVolume();

    int OutVol, innerCone, outerCone;
    
    if( fCompPB->GetInt( (ParamID)kSoundConeBool ) )
    {
        OutVol = fCompPB->GetInt( (ParamID)kSoundOConeVolumeSlider );
        innerCone = fCompPB->GetInt( (ParamID)kSoundIConeAngle );
        outerCone = fCompPB->GetInt( (ParamID)kSoundOConeAngle );
    }
    else
    {
        OutVol = 0;
        innerCone = 360;
        outerCone = 360;
    }

    destSound->SetMax(max);
    destSound->SetMin(min);
    destSound->SetOuterVolume(OutVol - 10000);
    destSound->SetConeAngles(innerCone, outerCone);
    destSound->SetProperty( plSound::kPropLocalOnly, fCompPB->GetInt( (ParamID)kSndIsLocalOnly ) ? true : false );
    destSound->SetPriority( fCompPB->GetInt( (ParamID)kSndPriority ) );

    if( fCompPB->GetInt( (ParamID)kSndIncidental ) )
    {
        destSound->SetProperty( plSound::kPropIncidental, true );

        // Refactor the priority, since incidental priorities are a different range
        int pri = fCompPB->GetInt( (ParamID)kSndPriority );
        pri = pri < 1 ? 1 : pri;
        destSound->SetPriority( pri );
    }

    if( fCompPB->GetInt( (ParamID)kSndDisableLOD ) )
    {
        // Force LOD off on this sound
        destSound->SetProperty( plSound::kPropDisableLOD, true );
    }
    
    if( fCompPB->GetInt( (ParamID)kSndChannelSelect ) )
        destSound->SetChannelSelect( plWin32Sound::kRightChannel );
    else
        destSound->SetChannelSelect( plWin32Sound::kLeftChannel );

    IGrabSoftRegion( destSound, pErrMsg );
    IGrabEAXParams( destSound, pErrMsg );
}

hsBool plSound3DEmitterComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    if (!fValidNodes[node])
        return false;

    if( fCreateGrouped )
        return true;

    const char* fileName = GetSoundFileName( kBaseSound );

    int fIndex = -1;
    if (fIndices.find(node) != fIndices.end())
        fIndex = fIndices[node];

    plSoundBuffer *srcBuffer = IProcessSourceBuffer( node, pErrMsg );
    if( srcBuffer == nil )
        return false;

    const plAudioInterface* ai = node->GetSceneObject()->GetAudioInterface();
    plWinAudible* pAudible = (plWinAudible*)ai->GetAudible();


    char    keyName[ 256 ];
    sprintf( keyName, "%s", GetINode()->GetName());

    plWin32Sound *sound = nil;

    if (!strcmp(node->GetName(), "LinkSoundSource"))
        sound = TRACKED_NEW plWin32LinkSound;
    else
    {
#if 0
        sound = TRACKED_NEW plWin32StaticSound;
#else
        /// New method, still in testing: any sounds over 4 seconds get made into streaming sounds
        if( srcBuffer->GetDataLengthInSecs() > 4.f )
            sound = TRACKED_NEW plWin32StreamingSound;
        else
            sound = TRACKED_NEW plWin32StaticSound;
    }
#endif

    hsgResMgr::ResMgr()->NewKey(keyName, sound, node->GetLocation(), node->GetLoadMask());
    hsgResMgr::ResMgr()->AddViaNotify( srcBuffer->GetKey(), TRACKED_NEW plGenRefMsg( sound->GetKey(), plRefMsg::kOnCreate, -1, plSound::kRefDataBuffer ), plRefFlags::kActiveRef );
    
    if( pAudible->AddSound( sound, fIndex, true ) )
    {
        ISetParameters( sound, pErrMsg );
    }

    return true;
}

// Converts an array of components into a single grouped sound
hsBool  plSound3DEmitterComponent::ConvertGrouped( plMaxNode *baseNode, hsTArray<plBaseSoundEmitterComponent *> &groupArray, plErrorMsg *pErrMsg )
{
    char    keyName[ 256 ];


    if( !fValidNodes[ baseNode ] || !fCreateGrouped )
        return false;


    // First, we need to grab the sound buffers from ALL the components and merge them into one big buffer.
    // Also build up an array of positions to feed to our groupedSound later.
    // Also also assign all the components the audioInterface index (will be the same one, so we need to
    // allocate it here).
    // Also also also build up a volume array parallel to startPoses that represents the individual volume
    // setting for each sound in the group
    hsTArray<UInt32>        startPoses;
    hsTArray<hsScalar>      volumes;
    hsLargeArray<UInt8>     mergedData;
    int                     i;
    plWAVHeader             mergedHeader;

    for( i = 0; i < groupArray.GetCount(); i++ )
    {
        // Make sure they're all 3D sounds...
        if( groupArray[ i ]->ClassID() != SOUND_3D_COMPONENT_ID )
        {
            char msg[ 512 ];
            sprintf( msg, "The sound component %s isn't a 3D sound, which is necessary for making grouped sounds. "
                        "Make sure all the sounds in this group are 3D sounds.", groupArray[ i ]->GetINode()->GetName() );
            IShowError( kSrcBufferInvalid, msg, baseNode->GetName(), pErrMsg );

            // Attempt to recover
            startPoses.Append( mergedData.GetCount() );
            volumes.Append( 1.f );
            continue;
        }

        // Grab the buffer for this sound directly from the original source
        const char *fileName = groupArray[ i ]->GetSoundFileName( kBaseSound );

        plSoundBuffer   *buffer = TRACKED_NEW plSoundBuffer( fileName );
        if( !buffer->IsValid() || !buffer->EnsureInternal() )
        {
            // OK, because some *cough* machines are completely stupid and don't load AssetMan scenes with 
            // AssetMan plugins, we need to do a really stupid fallback search to the current exporting directory.
            const char *plasmaDir = plMaxConfig::GetClientPath();
            bool worked = false;
            if( plasmaDir != nil )
            {
                char newPath[ MAX_PATH ];
                strcpy( newPath, plasmaDir );
                strcat( newPath, "sfx\\" );
                
                const char* c = strrchr( fileName, '\\' );
                if( c == nil )
                    c = strrchr( fileName, '/' );
                if( c == nil )
                    c = fileName;
                else
                    c++;
                strcat( newPath, c );

                // Got a path to try, so try it!
                delete buffer;
                buffer = TRACKED_NEW plSoundBuffer( newPath );
                if( buffer->IsValid() && buffer->EnsureInternal() )
                    worked = true;
            }

            if( !worked )
            {
                char msg[ 512 ];
                sprintf( msg, "The sound file %s cannot be loaded for component %s.", fileName, groupArray[ i ]->GetINode()->GetName() );
                IShowError( kSrcBufferInvalid, msg, baseNode->GetName(), pErrMsg );
                delete buffer;

                // Attempt to recover
                startPoses.Append( mergedData.GetCount() );
                volumes.Append( 1.f );
                continue;
            }
        }
        
        // Get a header (they should all be the same)
        if( i == 0 )
            mergedHeader = buffer->GetHeader();
        else
        {
            if( memcmp( &mergedHeader, &buffer->GetHeader(), sizeof( mergedHeader ) ) != 0 )
            {
                char msg[ 512 ];
                sprintf( msg, "The format for sound file %s does not match the format for the other grouped sounds on node %s. "
                            "Make sure the sounds are all the same format.", fileName, baseNode->GetName() );
                IShowError( kMergeSourceFormatMismatch, msg, baseNode->GetName(), pErrMsg );
                delete buffer;

                // Attempt to recover
                startPoses.Append( mergedData.GetCount() );
                volumes.Append( 1.f );
                continue;
            }
        }

        // Grab the data from this buffer and merge it
        // HACK: SetCount() won't copy the old data over, Expand() won't up the use count, so do
        // an expand-and-setCount combo.
        UInt32 pos = mergedData.GetCount();
        startPoses.Append( pos );
        mergedData.Expand( pos + buffer->GetDataLength() );
        mergedData.SetCount( pos + buffer->GetDataLength() );
        memcpy( &mergedData[ pos ], buffer->GetData(), buffer->GetDataLength() );

        delete buffer;

        // Also keep track of what the volume should be for this particular sound
        volumes.Append( groupArray[ i ]->GetSoundVolume() );
    }

    /// We got a merged buffer, so make a plSoundBuffer from it
    int index = -1;
    if( fIndices.find( baseNode ) != fIndices.end() )
        index = fIndices[ baseNode ];

    sprintf( keyName, "%s_MergedSound", GetINode()->GetName() );

    plKey buffKey = baseNode->FindPageKey( plSoundBuffer::Index(), keyName );   
    if( buffKey != nil )
        plPluginResManager::ResMgr()->NukeKeyAndObject( buffKey );

    // Create a new one...
    plSoundBuffer   *mergedBuffer = TRACKED_NEW plSoundBuffer();
    mergedBuffer->SetInternalData( mergedHeader, mergedData.GetCount(), mergedData.AcquireArray() );
    mergedData.Reset();
    // The buffer may be shared across multiple sources. We could or together the LoadMasks of all
    // the nodes that use it, or we can just go with the default loadmask of Always load, and
    // count on it never getting dragged into memory if nothing that references it does.
    hsgResMgr::ResMgr()->NewKey( keyName, mergedBuffer, baseNode->GetLocation() );


    /// We got the sound buffer, now just create a groupedSound for it

    const plAudioInterface* ai = baseNode->GetSceneObject()->GetAudioInterface();
    plWinAudible* pAudible = (plWinAudible*)ai->GetAudible();

    sprintf( keyName, "%s", GetINode()->GetName());
    plWin32GroupedSound *sound = TRACKED_NEW plWin32GroupedSound;
    sound->SetPositionArray( startPoses.GetCount(), startPoses.AcquireArray(), volumes.AcquireArray() );
    sound->SetProperty( plSound::kPropLoadOnlyOnCall, true );

    hsgResMgr::ResMgr()->NewKey( keyName, sound, baseNode->GetLocation(), baseNode->GetLoadMask() );
    hsgResMgr::ResMgr()->AddViaNotify( mergedBuffer->GetKey(), TRACKED_NEW plGenRefMsg( sound->GetKey(), plRefMsg::kOnCreate, -1, plSound::kRefDataBuffer ), plRefFlags::kActiveRef );
    
    if( pAudible->AddSound( sound, index, true ) )
    {
        // Just use the first component
        plSound3DEmitterComponent *grpComp = (plSound3DEmitterComponent *)groupArray[ 0 ];
        grpComp->ISetParameters( sound, pErrMsg );
    }
    
    /// All done!
    return true;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
//
//  Background Music Component
//

class plBackgroundMusicComponent : public plBaseSoundEmitterComponent
{
public:
    plBackgroundMusicComponent();
    virtual ~plBackgroundMusicComponent();

    // Internal setup and write-only set properties on the MaxNode. No reading
    // of properties on the MaxNode, as it's still indeterminant.
    hsBool SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg);

    hsBool PreConvert(plMaxNode *node, plErrorMsg *pErrMsg);
    hsBool Convert(plMaxNode *node, plErrorMsg *pErrMsg);

    virtual hsBool  IsLocalOnly( void ) const { if( fCompPB->GetInt( (ParamID)kSndIsLocalOnly ) ) return true; else return false; }

protected:
    virtual UInt32 ICalcSourceBufferFlags() const;

    bool IValidate(plMaxNode *node, plErrorMsg *pErrMsg);
    virtual hsBool  IGetCategoryList( char **&catList, int *&catKonstantList );
};

//Max desc stuff necessary below.
CLASS_DESC(plBackgroundMusicComponent, gBgndMusicEmitterDesc, "Nonspatial Sound",  "NonspatSound", COMP_TYPE_AUDIO, BGND_MUSIC_COMPONENT_ID)


ParamBlockDesc2 gBgndMusicEmitterBk
(
    plComponent::kBlkComp, _T("Bgnd Music"), 0, &gBgndMusicEmitterDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP + P_INCLUDE_PARAMS, plComponent::kRefComp,

    5,  // Number of rollouts
    sSoundSharedPBHeader(IDS_COMP_SOUNDBASE),
    kS3DBaseParams,         IDD_COMP_SOUNDBGND,         IDS_COMP_SOUNDBGND,         0, 0, &gSoundCompProc,
    sSndWaveformPropsHeader,
    kS3DSoftVolumeParams,   IDD_COMP_SOUND_SOFTPARAMS,  IDS_COMP_SOUNDSOFTPARAMS,   0, 0, &gSoundSoftVolumeSelProc,

    // Included paramblock
    &sSoundSharedPB,

    // Waveform props define
    sSndWaveformPropsParamTemplate,

    // params

    kSndIsLocalOnly, _T("noNetworkSynch"),      TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   kS3DBaseParams, TYPE_SINGLECHEKBOX, IDC_SND_LOCALONLY,
        end,

    kSndStreamCompressed,   _T("stream"),       TYPE_BOOL,      0, 0,
        p_ui,   kS3DBaseParams, TYPE_SINGLECHEKBOX, IDC_CHECK_STREAM,
        end,

    /// Soft Region/Volume Parameters rollout
    kSndSoftRegionEnable,   _T( "enableSoftRegion" ), TYPE_BOOL, 0, 0,
        p_ui,   kS3DSoftVolumeParams, TYPE_SINGLECHEKBOX, IDC_SOUND_SOFTENABLE,
        p_default, FALSE,
        end,

    kSndSoftRegion, _T("softRegion"),   TYPE_INODE,     0, 0,
        p_prompt, IDS_COMP_SOUNDSOFTSELECT,
        p_accessor, &gSoundSoftVolAccessor,
        end,

    end
);


plBackgroundMusicComponent::plBackgroundMusicComponent()
{
    fClassDesc = &gBgndMusicEmitterDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

plBackgroundMusicComponent::~plBackgroundMusicComponent()
{
}

// Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plBackgroundMusicComponent::SetupProperties(plMaxNode *pNode, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::SetupProperties( pNode, pErrMsg );
}

UInt32 plBackgroundMusicComponent::ICalcSourceBufferFlags() const
{
    UInt32 ourFlags = 0;
    if (fCompPB->GetInt(kSndStreamCompressed))
        ourFlags |= plSoundBuffer::kStreamCompressed;

    return plBaseSoundEmitterComponent::ICalcSourceBufferFlags() | ourFlags;
}

bool plBackgroundMusicComponent::IValidate(plMaxNode *node, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::IValidate( node, pErrMsg );
}

hsBool plBackgroundMusicComponent::PreConvert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::PreConvert( node, pErrMsg, BGND_MUSIC_COMPONENT_ID );
}

hsBool plBackgroundMusicComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    if (!fValidNodes[node])
        return false;

    const char* fileName = GetSoundFileName( kBaseSound );

    int fIndex = -1;
    if (fIndices.find(node) != fIndices.end())
        fIndex = fIndices[node];

    const plAudioInterface* ai = node->GetSceneObject()->GetAudioInterface();
    plWinAudible* pAudible = (plWinAudible*)ai->GetAudible();

    plSoundBuffer *srcBuffer = IProcessSourceBuffer( node, pErrMsg );
    if( srcBuffer == nil )
        return false;

    char    keyName[ 256 ];
    sprintf( keyName, "%s_Win32BgndSnd", GetINode()->GetName() );
    plWin32Sound *sound = nil;

    if( srcBuffer->GetDataLengthInSecs() > 4.f )
        sound = TRACKED_NEW plWin32StreamingSound;
    else
        sound = TRACKED_NEW plWin32StaticSound;

    hsgResMgr::ResMgr()->NewKey(keyName, sound, node->GetLocation(), node->GetLoadMask());

    srcBuffer->SetFlag( plSoundBuffer::kAlwaysExternal );
    hsgResMgr::ResMgr()->AddViaNotify( srcBuffer->GetKey(), TRACKED_NEW plGenRefMsg( sound->GetKey(), plRefMsg::kOnCreate, -1, plSound::kRefDataBuffer ), plRefFlags::kActiveRef );
    
    if (pAudible->AddSound( sound, fIndex, false))
    {
        ISetBaseParameters( sound, pErrMsg );

        sound->SetProperty( plSound::kPropLocalOnly, fCompPB->GetInt( (ParamID)kSndIsLocalOnly ) ? true : false );
        sound->SetPriority( fCompPB->GetInt( (ParamID)kSndPriority ) );

        if( fCompPB->GetInt( (ParamID)kSndDisableLOD ) )
        {
            // Force LOD off on this sound
            sound->SetProperty( plSound::kPropDisableLOD, true );
        }

        IGrabSoftRegion( sound, pErrMsg );
        sound->GetEAXSettings().Enable( false );
    }

    return true;
}

//// IGetCategoryList ///////////////////////////////////////////////////////////////////////////
//  Returns a list of the categories and konstants supported for this type of sound

hsBool  plBackgroundMusicComponent::IGetCategoryList( char **&catList, int *&catKonstantList )
{
    static char *cats[] = { "Background Music", "Ambience", "Sound FX", "GUI", "NPC Voice", "" };
    static int  catEnums[] = { plSound::kBackgroundMusic, plSound::kAmbience, plSound::kSoundFX, plSound::kGUISound, plSound::kNPCVoices };

    catList = cats;
    catKonstantList = catEnums;

    return true;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
//
//  GUI Sound Component
//

class plGUISoundComponent : public plBaseSoundEmitterComponent
{
public:
    plGUISoundComponent();
    virtual ~plGUISoundComponent();

    // Internal setup and write-only set properties on the MaxNode. No reading
    // of properties on the MaxNode, as it's still indeterminant.
    hsBool SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg);

    hsBool PreConvert(plMaxNode *node, plErrorMsg *pErrMsg);
    hsBool Convert(plMaxNode *node, plErrorMsg *pErrMsg);

    virtual void    UpdateSoundFileSelection( void ) { ; }

protected:

    bool    IValidate(plMaxNode *node, plErrorMsg *pErrMsg);

    virtual hsBool  IGetCategoryList( char **&catList, int *&catKonstantList );

    virtual hsBool  IHasWaveformProps( void ) const { return false; }
};

//Max desc stuff necessary below.
CLASS_DESC(plGUISoundComponent, gGUISoundEmitterDesc, "GUI Sound",  "GUISound", COMP_TYPE_AUDIO, GUI_SOUND_COMPONENT_ID)


ParamBlockDesc2 gGUISoundEmitterBk
(
    plComponent::kBlkComp, _T("GUI Sound"), 0, &gGUISoundEmitterDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP + P_INCLUDE_PARAMS, plComponent::kRefComp,

    2,  // Number of rollouts
    sSoundSharedPBHeader(IDS_COMP_SOUNDGUI),

    // Included paramblock
    &sSoundSharedPB,

    end
);


plGUISoundComponent::plGUISoundComponent()
{
    fClassDesc = &gGUISoundEmitterDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

plGUISoundComponent::~plGUISoundComponent()
{
}

//// IGetCategoryList ///////////////////////////////////////////////////////////////////////////
//  Returns a list of the categories and konstants supported for this type of sound

hsBool  plGUISoundComponent::IGetCategoryList( char **&catList, int *&catKonstantList )
{
    static char *cats[] = { "GUI", "" };
    static int  catEnums[] = { plSound::kGUISound };

    catList = cats;
    catKonstantList = catEnums;

    return true;
}

// Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plGUISoundComponent::SetupProperties(plMaxNode *pNode, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::SetupProperties( pNode, pErrMsg );
}

bool plGUISoundComponent::IValidate(plMaxNode *node, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::IValidate( node, pErrMsg );
}

hsBool plGUISoundComponent::PreConvert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    return plBaseSoundEmitterComponent::PreConvert( node, pErrMsg, GUI_SOUND_COMPONENT_ID );
}

hsBool plGUISoundComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    if (!fValidNodes[node])
        return false;

    const char* fileName = GetSoundFileName( kBaseSound );

    int fIndex = -1;
    if (fIndices.find(node) != fIndices.end())
        fIndex = fIndices[node];

    const plAudioInterface* ai = node->GetSceneObject()->GetAudioInterface();
    plWinAudible* pAudible = (plWinAudible*)ai->GetAudible();

    plSoundBuffer *srcBuffer = GetSourceBuffer( fileName, node, ICalcSourceBufferFlags() );
    if( srcBuffer == nil )
    {
        pErrMsg->Set( true, node->GetName(), "The file specified for the sound 3D component %s is invalid. This emitter will not be exported.", GetINode()->GetName() ).Show();
        pErrMsg->Set( false );
        return false;
    }

    char    keyName[ 256 ];
    sprintf( keyName, "%s_Win32GUISound", GetINode()->GetName() );

    plWin32StaticSound *sound = TRACKED_NEW plWin32StaticSound;
    hsgResMgr::ResMgr()->NewKey(keyName, sound, node->GetLocation(), node->GetLoadMask());

    hsgResMgr::ResMgr()->AddViaNotify( srcBuffer->GetKey(), TRACKED_NEW plGenRefMsg( sound->GetKey(), plRefMsg::kOnCreate, -1, plSound::kRefDataBuffer ), plRefFlags::kActiveRef );
    
    if (pAudible->AddSound( sound, fIndex, false))
    {
        ISetBaseParameters( sound, pErrMsg );

        sound->SetProperty( plSound::kPropLocalOnly, true );    // GUI sounds are always local-only

        if( fCompPB->GetInt( (ParamID)kSndDisableLOD ) )
        {
            // Force LOD off on this sound
            sound->SetProperty( plSound::kPropDisableLOD, true );
        }
    }

    return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
//
//  EAX Listener Soft Region component
//

class plEAXListenerComponent : public plComponent
{
public:
    enum 
    {
        kRefSoftRegion,
        kRefWhichSettings,
        kRefPreset,
        kRefCustFile,

        // The following are the parameters for the listener as defined in eax.h, minus the panning
        kRefEnvironmentSize,        // float
        kRefEnvironmentDiffusion,   // float
        kRefRoom,                   // long
        kRefRoomHF,                 // long
        kRefRoomLF,                 // long
        kRefDecayTime,              // float
        kRefDecayHFRatio,           // float
        kRefDecayLFRatio,           // float
        kRefReflections,            // long
        kRefReflectionsDelay,       // float
        // panning goes here
        kRefReverb,                 // long
        kRefReverbDelay,            // float
        // Reverb pan
        kRefEchoTime,               // float
        kRefEchoDepth,
        kRefModulationTime,
        kRefModulationDepth,
        kRefAirAbsorptionHF,
        kRefHFReference,
        kRefLFReference,
        kRefRoomRolloffFactor,
        kRefFlags,              // unsigned long
    };

public:
    plEAXListenerComponent();
    void DeleteThis() { delete this; }

    // SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
    // of properties on the MaxNode, as it's still indeterminant.
    hsBool SetupProperties(plMaxNode *pNode, plErrorMsg *errMsg);

    hsBool PreConvert(plMaxNode *pNode, plErrorMsg *errMsg);
    hsBool Convert(plMaxNode *node, plErrorMsg *errMsg);

    const char *GetCustFileName( void ) const;
    void        SetCustFile( const char *path );
};

// When one of our parameters that is a ref changes, send out the component ref
// changed message.  Normally, messages from component refs are ignored since
// they pass along all the messages of the ref, which generates a lot of false
// converts.
class plEAXListenerAccessor : public PBAccessor
{
public:
    void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t)
    {
        if (id == plEAXListenerComponent::kRefSoftRegion )
        {
            plEAXListenerComponent *comp = (plEAXListenerComponent*)owner;
            comp->NotifyDependents(FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED);
        }
    }
};
static plEAXListenerAccessor gEAXListenerAccessor;

//// DialogProc for EAXListenerComponent ////////////////////////////////////////////////////////

class plEAXListenerDlgProc : public plSingleCompSelProc
{
protected:

    hsBool  IGetCustFileName( plEAXListenerComponent *listenerComp )
    {
        TCHAR   fileName[ MAX_PATH ], dirName[ MAX_PATH ];

    
        const char *name = listenerComp->GetCustFileName();
        if( name != nil )
            strcpy( fileName, name );
        else
            strcpy( fileName, _T( "" ) );

        strcpy( dirName, fileName );
        ::PathRemoveFileSpec( dirName );

        OPENFILENAME ofn = {0};
        ofn.lStructSize = sizeof( OPENFILENAME );
        ofn.hwndOwner = GetCOREInterface()->GetMAXHWnd();
        ofn.lpstrFilter = "EAX Preset Files (*.eax)\0*.eax\0All Files\0*.*\0";
        ofn.lpstrFile = fileName;
        ofn.nMaxFile = sizeof( fileName );
        ofn.lpstrInitialDir = dirName;
        ofn.lpstrTitle = "Choose a sound file";
        ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
        ofn.lpstrDefExt = "eax";

        if( GetOpenFileName( &ofn ) )
        {
            listenerComp->SetCustFile( fileName );
            return true;
        }
        else
            return false;
    }
public:

    plEAXListenerDlgProc() 
        : plSingleCompSelProc( plEAXListenerComponent::kRefSoftRegion, IDC_EAX_SOFTREGION, "Select the soft region to apply these EAX listener properties to" )
    {
    }

    BOOL DlgProc( TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
    {
        int             i;
        IParamBlock2    *pb = map->GetParamBlock();


        switch ( msg )
        {
            case WM_INITDIALOG:
                {

                    // Load the preset combo with preset names
                    HWND comboBox = GetDlgItem( hWnd, IDC_EAX_PRESET_COMBO );
                    ComboBox_ResetContent( comboBox );

#ifdef EAX_SDK_AVAILABLE
                    for( i = 0; i < /*sizeof( EAX30_ORIGINAL_PRESETS ) 
                        / sizeof( EAXLISTENERPROPERTIES )*/26 ; i++ )
                        ComboBox_AddString( comboBox, EAX30_ORIGINAL_PRESET_NAMES[ i ] );
#endif

                    ComboBox_SetCurSel( comboBox, pb->GetInt( (ParamID)plEAXListenerComponent::kRefPreset ) );

                    ICustButton *custButton = GetICustButton( GetDlgItem( hWnd, IDC_EAX_CUSTFILE ) );
                    if( custButton != nil )
                    {
                        custButton->SetText( pb->GetStr( (ParamID)plEAXListenerComponent::kRefCustFile ) );
                        ReleaseICustButton( custButton );
                    }
                }
                break;

            case WM_COMMAND:    
                if( LOWORD( wParam ) == IDC_EAX_PRESET_COMBO )
                {
                    int sel = SendDlgItemMessage( hWnd, IDC_EAX_PRESET_COMBO, CB_GETCURSEL, 0, 0 );
                    if( sel != CB_ERR )
                        pb->SetValue( (ParamID)plEAXListenerComponent::kRefPreset, 0, sel );
                    return true;
                }
                if( ( HIWORD( wParam ) == BN_CLICKED ) && LOWORD( wParam ) == IDC_EAX_CUSTFILE )
                {
                    // Get the file to load
                    plEAXListenerComponent *comp = (plEAXListenerComponent *)map->GetParamBlock()->GetOwner();
                    if( IGetCustFileName( comp ) )
                    {
                        ICustButton *custButton = GetICustButton( GetDlgItem( hWnd, IDC_EAX_CUSTFILE ) );
                        if( custButton != nil )
                        {
                            custButton->SetText( pb->GetStr( (ParamID)plEAXListenerComponent::kRefCustFile ) );
                            ReleaseICustButton( custButton );
                        }
                    }
                }
                break;
        }

        return plSingleCompSelProc::DlgProc( t, map, hWnd, msg, wParam, lParam );
    }

    void DeleteThis() {}
};

static plEAXListenerDlgProc gEAXListenerDlgProc;

//Max desc stuff necessary below.
CLASS_DESC(plEAXListenerComponent, gEAXListenerDesc, "EAX Listener",  "EAXListener", COMP_TYPE_AUDIO, EAX_LISTENER_COMPONENT_ID)

ParamBlockDesc2 gEAXListenerBlk
(   // KLUDGE: not the defined block ID, but kept for backwards compat.
 plComponent::kBlkComp, _T("EAXListener"), 0, &gEAXListenerDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_EAXLISTENER, IDS_COMP_EAXLISTENER, 0, 0, &gEAXListenerDlgProc,

    plEAXListenerComponent::kRefSoftRegion, _T("SoftRegion"),   TYPE_INODE,     0, 0,
        p_accessor,     &gEAXListenerAccessor,
        end,
    
    plEAXListenerComponent::kRefWhichSettings, _T("whichSettings"), TYPE_INT,       0, 0,
        p_ui, TYPE_RADIO, 2, IDC_EAX_PRESET, IDC_EAX_CUSTOM,
        p_default, 0,
        end,

    plEAXListenerComponent::kRefPreset, _T("preset"),   TYPE_INT,       0, 0,
        p_default, 0,
        end,

    // This is just a label for now, so the users know what file the presets came from
    plEAXListenerComponent::kRefCustFile, _T("custFile"), TYPE_STRING, 0, 0,
        p_default, _T(""),
        end,

    // EAX listener params (should be private)
    plEAXListenerComponent::kRefEnvironmentSize,        _T(""), TYPE_FLOAT, 0, 0, end,      // float
    plEAXListenerComponent::kRefEnvironmentDiffusion,   _T(""), TYPE_FLOAT, 0, 0, end,// float
    plEAXListenerComponent::kRefRoom,                   _T(""), TYPE_INT, 0, 0, end,// long
    plEAXListenerComponent::kRefRoomHF,                 _T(""), TYPE_INT, 0, 0, end,// long
    plEAXListenerComponent::kRefRoomLF,                 _T(""), TYPE_INT, 0, 0, end,// long
    plEAXListenerComponent::kRefDecayTime,              _T(""), TYPE_FLOAT, 0, 0, end,// float
    plEAXListenerComponent::kRefDecayHFRatio,           _T(""), TYPE_FLOAT, 0, 0, end,// float
    plEAXListenerComponent::kRefDecayLFRatio,           _T(""), TYPE_FLOAT, 0, 0, end,// float
    plEAXListenerComponent::kRefReflections,            _T(""), TYPE_INT, 0, 0, end,// long
    plEAXListenerComponent::kRefReflectionsDelay,       _T(""), TYPE_FLOAT, 0, 0, end,// float
        // panning goes here
    plEAXListenerComponent::kRefReverb,                 _T(""), TYPE_INT, 0, 0, end,// long
    plEAXListenerComponent::kRefReverbDelay,            _T(""), TYPE_FLOAT, 0, 0, end,// float
        // Reverb pan
    plEAXListenerComponent::kRefEchoTime,               _T(""), TYPE_FLOAT, 0, 0, end,// float
    plEAXListenerComponent::kRefEchoDepth,              _T(""), TYPE_FLOAT, 0, 0, end,
    plEAXListenerComponent::kRefModulationTime,         _T(""), TYPE_FLOAT, 0, 0, end,
    plEAXListenerComponent::kRefModulationDepth,        _T(""), TYPE_FLOAT, 0, 0, end,
    plEAXListenerComponent::kRefAirAbsorptionHF,        _T(""), TYPE_FLOAT, 0, 0, end,
    plEAXListenerComponent::kRefHFReference,            _T(""), TYPE_FLOAT, 0, 0, end,
    plEAXListenerComponent::kRefLFReference,            _T(""), TYPE_FLOAT, 0, 0, end,
    plEAXListenerComponent::kRefRoomRolloffFactor,      _T(""), TYPE_FLOAT, 0, 0, end,
    plEAXListenerComponent::kRefFlags,                  _T(""), TYPE_INT, 0, 0, end,// unsigned long

    end
);

plEAXListenerComponent::plEAXListenerComponent()
{
    fClassDesc = &gEAXListenerDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plEAXListenerComponent::Convert(plMaxNode *node, plErrorMsg *errMsg)
{
    if( !fCompPB->GetINode((ParamID)kRefSoftRegion) )
        return true;

    plSceneObject* sceneObj = node->GetSceneObject();
    if( !sceneObj )
        return true;
/*
    plLightInfo* li = plLightInfo::ConvertNoRef(sceneObj->GetGenericInterface(plLightInfo::Index()));
    if( !li )
        return true;
*/
    plSoftVolBaseComponent* softComp = plSoftVolBaseComponent::GetSoftComponent( fCompPB->GetINode( (ParamID)kRefSoftRegion ) );
    if( !softComp )
        return true;

    plKey softKey = softComp->GetSoftVolume();
    if( !softKey )
        return true;

    // Create a listener mod to handle these things
    plEAXListenerMod *listener = TRACKED_NEW plEAXListenerMod();
    node->AddModifier( listener, IGetUniqueName(node) );

    // Add the soft region
    hsgResMgr::ResMgr()->AddViaNotify( softKey, TRACKED_NEW plGenRefMsg( listener->GetKey(), plRefMsg::kOnCreate, 0, plEAXListenerMod::kRefSoftRegion ), plRefFlags::kActiveRef );

#ifdef EAX_SDK_AVAILABLE
    // Set up the parameters of the listener mod
    EAXLISTENERPROPERTIES *listenerProps = listener->GetListenerProps();
    if( fCompPB->GetInt( (ParamID)kRefWhichSettings ) == 0 )
    {
        // Set params based on a preset
        listener->SetFromPreset( fCompPB->GetInt( (ParamID)kRefPreset ) );
    }
    else
    {
        // Get the raw params
        listenerProps->flEnvironmentSize = fCompPB->GetFloat( (ParamID)kRefEnvironmentSize );
        listenerProps->flEnvironmentDiffusion = fCompPB->GetFloat( (ParamID)kRefEnvironmentDiffusion );
        listenerProps->lRoom = fCompPB->GetInt( (ParamID)kRefRoom );
        listenerProps->lRoomHF = fCompPB->GetInt( (ParamID)kRefRoomHF );
        listenerProps->lRoomLF = fCompPB->GetInt( (ParamID)kRefRoomLF );
        listenerProps->flDecayTime = fCompPB->GetFloat( (ParamID)kRefDecayTime );
        listenerProps->flDecayHFRatio = fCompPB->GetFloat( (ParamID)kRefDecayHFRatio );
        listenerProps->flDecayLFRatio = fCompPB->GetFloat( (ParamID)kRefDecayLFRatio );
        listenerProps->lReflections = fCompPB->GetInt( (ParamID)kRefReflections );
        listenerProps->flReflectionsDelay = fCompPB->GetFloat( (ParamID)kRefReflectionsDelay );
        //listenerProps->vReflectionsPan;     // early reflections panning vector
        listenerProps->lReverb = fCompPB->GetInt( (ParamID)kRefReverb );                  // late reverberation level relative to room effect
        listenerProps->flReverbDelay = fCompPB->GetFloat( (ParamID)kRefReverbDelay );
        //listenerProps->vReverbPan;          // late reverberation panning vector
        listenerProps->flEchoTime = fCompPB->GetFloat( (ParamID)kRefEchoTime );
        listenerProps->flEchoDepth = fCompPB->GetFloat( (ParamID)kRefEchoDepth );
        listenerProps->flModulationTime = fCompPB->GetFloat( (ParamID)kRefModulationTime );
        listenerProps->flModulationDepth = fCompPB->GetFloat( (ParamID)kRefModulationDepth );
        listenerProps->flAirAbsorptionHF = fCompPB->GetFloat( (ParamID)kRefAirAbsorptionHF );
        listenerProps->flHFReference = fCompPB->GetFloat( (ParamID)kRefHFReference );
        listenerProps->flLFReference = fCompPB->GetFloat( (ParamID)kRefLFReference );
        listenerProps->flRoomRolloffFactor = fCompPB->GetFloat( (ParamID)kRefRoomRolloffFactor );
        listenerProps->ulFlags = fCompPB->GetInt( (ParamID)kRefFlags );
    }
#endif
    
    return true;
}

hsBool plEAXListenerComponent::PreConvert(plMaxNode *pNode,  plErrorMsg *errMsg)
{

    return true;
}

// SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plEAXListenerComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *errMsg)
{

    return true;
}

const char *plEAXListenerComponent::GetCustFileName( void ) const
{
    return (const char *)fCompPB->GetStr( (ParamID)kRefCustFile );
}

void    plEAXListenerComponent::SetCustFile( const char *path )
{
    char    file[ MAX_PATH ];
    int     i;
    hsUNIXStream    presetFile;

    // Map of PB values to file entries
    struct FilePBMap
    {
        char    *fKeyword;
        ParamID fParamID;
        UInt8   fType;  // 0 is int, 1 is float for now
    } myMap[] = { 
        { "flEnvironmentSize", kRefEnvironmentSize, 1 },
        { "flEnvironmentDiffusion", kRefEnvironmentDiffusion, 1 },
        { "lRoom", kRefRoom, 0 },
        { "lRoomHF", kRefRoomHF, 0 },
        { "lRoomLF", kRefRoomLF, 0 },
        { "flDecayTime", kRefDecayTime, 1 },
        { "flDecayHFRatio", kRefDecayHFRatio, 1 },
        { "flDecayLFRatio", kRefDecayLFRatio, 1 },
        { "lReflections", kRefReflections, 0 },
        { "flReflectionsDelay", kRefReflectionsDelay, 1 },
        { "lReverb", kRefReverb, 0 },
        { "flReverbDelay", kRefReverbDelay, 1 },
        { "flEchoTime", kRefEchoTime, 1 },
        { "flEchoDepth", kRefEchoDepth, 1 },
        { "flModulationTime", kRefModulationTime, 1 },
        { "flModulationDepth", kRefModulationDepth, 1 },
        { "flAirAbsorptionHF", kRefAirAbsorptionHF, 1 },
        { "flHFReference", kRefHFReference, 1 },
        { "flLFReference", kRefLFReference, 1 },
        { "flRoomRolloffFactor", kRefRoomRolloffFactor, 1 },
        { "dwFlags", kRefFlags, 0 },
        { nil, 0, 0 } };

    // Read the file and set settings from it
    if( !presetFile.Open( path, "rt" ) )
    {
        // Oops
        hsAssert( false, "can't open file" );
        return;
    }

    // Loop and find our keywords
    for( i = 0; myMap[ i ].fKeyword != nil && !presetFile.AtEnd(); )
    {
        char line[ 512 ];

        // Read a line from the file until we find our keyword
        presetFile.ReadLn( line, sizeof( line ) );
        if( strstr( line, myMap[ i ].fKeyword ) == nil )
            continue;

        // Read the next line, with our value
        presetFile.ReadLn( line, sizeof( line ) );
        float value = atof( line );
        if( myMap[ i ].fType == 0 )
            fCompPB->SetValue( myMap[ i ].fParamID, 0, (int)value );
        else
            fCompPB->SetValue( myMap[ i ].fParamID, 0, (float)value );

        i++;
    }

    if( myMap[ i ].fKeyword != nil )
    {
        hsAssert( false, "Couldn't find all of the keywords in the settings file. Oh well" );
    }

    // All done!
    presetFile.Close();

    // Update our helper reminder string
    _splitpath( path, nil, nil, file, nil );
    fCompPB->SetValue( (ParamID)kRefCustFile, 0, file );
}

/// Obsolete SFX components (made obsolete by the new EAX support)

OBSOLETE_CLASS(plSoundReverbComponent, gSoundReverbDesc, "Audio Region",  "AudioRegion", COMP_TYPE_AUDIO, Class_ID(0x50507200, 0x48651c4c))
OBSOLETE_CLASS(plSoundChorusModComponent,gSoundChorusModDesc , "Chorus Effect",  "ChorusEffect", COMP_TYPE_AUDIO, Class_ID(0x10f91101, 0x28cb21b9))
OBSOLETE_CLASS(plSoundCompressorModComponent,gSoundCompressorModDesc , "Compressor Effect",  "CompressEffect", COMP_TYPE_AUDIO, Class_ID(0x443d2167, 0x4ca42eb))
OBSOLETE_CLASS(plSoundDistortModComponent,gSoundDistortModDesc , "Distort Effect",  "DistortEffect", COMP_TYPE_AUDIO, Class_ID(0x7cb45868, 0x61220227))
OBSOLETE_CLASS(plSoundEchoModComponent,gSoundEchoModDesc , "Echo Effect",  "EchoEffect", COMP_TYPE_AUDIO,Class_ID(0x2948347e, 0x30ba0be3))
OBSOLETE_CLASS(plSoundFlangerModComponent,gSoundFlangerModDesc , "Flanger Effect",  "FlangerEffect", COMP_TYPE_AUDIO, Class_ID(0x25034090, 0x361a08d7) )
OBSOLETE_CLASS(plSoundGargleModComponent,gSoundGargleModDesc , "Gargle Effect",  "GargleEffect", COMP_TYPE_AUDIO, Class_ID(0x639b6a41, 0x24da2462))
OBSOLETE_CLASS(plSoundReverbModComponent,gSoundReverbModDesc , "Reverb Effect",  "ReverbEffect", COMP_TYPE_AUDIO, Class_ID(0x1bef33fc, 0x5c763858))



#if 1 // Waiting... mf
/////////////////////////////////////////////////////////////////////////////////////////////////
//
//  RandomSound Component
//
//

plKey plAudioComp::GetRandomSoundKey(plComponentBase *comp, plMaxNode *node)
{
    if (comp->ClassID() == RANDOM_SOUND_COMPONENT_ID)
    {
        plRandomSoundComponent *rndSnd = (plRandomSoundComponent*)comp;
        if (rndSnd->fSoundMods.find(node) != rndSnd->fSoundMods.end())
            return rndSnd->fSoundMods[node]->GetKey();
    }

    return nil;
}

/////////////////////////////////////////////////////////////////////////////////////////
enum
{
    kAutoStart,
    kSelectMode,
    kDelayMode,
    kMinDelay,
    kMaxDelay,
    kUseAll,
    kGroupIdx,
    kSoundList,
    kGroupTotals,
    kLastPick,
    kCombineSounds
};

enum
{
    kNormal = 0,
    kNoRepeats,
    kFullSetRepeat,
    kFullSetStop,
    kSequential
};

enum
{
    kDelayFromStart = 0,
    kDelayFromEnd,
    kDelayInfinite
};

enum
{
    kRandomSoundMain,
    kRandomSoundGroup,
};

static const int kMaxGroups = 10;

class plRandomSoundComponentProc : public ParamMap2UserDlgProc
{
public:
    plRandomSoundComponentProc() {}

    BOOL DlgProc(TimeValue t, IParamMap2 *pm, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

    void DeleteThis() {}
    void UpdateDisplay(IParamMap2 *pm);
    virtual void Update(TimeValue t, Interval& valid, IParamMap2* pmap) { UpdateDisplay(pmap); }
};

static plRandomSoundComponentProc gRandomSoundComponentProc;

void plRandomSoundComponentProc::UpdateDisplay(IParamMap2 *pm)
{
    HWND hWnd = pm->GetHWnd();
    HWND hList = GetDlgItem(hWnd, IDC_COMP_RS_GROUPLIST);
    IParamBlock2 *pb = pm->GetParamBlock();
    plRandomSoundComponent *comp = (plRandomSoundComponent *)pb->GetOwner();
    
    ListBox_ResetContent(hList);
    int group = comp->GetCurGroupIdx();
    int startIdx = comp->GetStartIndex(group);
    int endIdx = comp->GetEndIndex(group);

    while (startIdx < endIdx)
    {
        INode *curNode = pb->GetINode(ParamID(kSoundList), 0, startIdx);
        if (curNode == nil)
        {
            comp->RemoveSound(startIdx);
            endIdx--;
            continue;
        }
        ListBox_AddString(hList, curNode->GetName());
        startIdx++;
    }
}

BOOL plRandomSoundComponentProc::DlgProc(TimeValue t, IParamMap2 *pm, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    IParamBlock2 *pb = pm->GetParamBlock();
    HWND hList = GetDlgItem(hWnd, IDC_COMP_RS_GROUPLIST);
    plRandomSoundComponent *comp = (plRandomSoundComponent *)pb->GetOwner();

    switch (msg)
    {
    case WM_INITDIALOG:
        //UpdateDisplay(pm);
        return TRUE;

    case WM_COMMAND:
        if (HIWORD(wParam) == BN_CLICKED)
        {
            if (LOWORD(wParam) == IDC_COMP_RS_GROUP_ADD)
            {
                std::vector<Class_ID> cids;
                cids.push_back(SOUND_3D_COMPONENT_ID);
                if (plPick::NodeRefKludge(pb, kLastPick, &cids, true, false))           
                    comp->AddSelectedSound();

                return TRUE;
            }
            // Remove the currently selected material
            else if (LOWORD(wParam) == IDC_COMP_RS_GROUP_REMOVE)
            {
                int curSel = ListBox_GetCurSel(hList);
                if (curSel >= 0)
                    comp->RemoveSound(curSel);

                return TRUE;
            }
        }
    }

    return FALSE;
}

//Max desc stuff necessary below.
CLASS_DESC(plRandomSoundComponent, gRandomSoundDesc, "Random Sound",  "RandomSound", COMP_TYPE_AUDIO, RANDOM_SOUND_COMPONENT_ID)

//
// Block not necessary, kept for backwards compat.
//

ParamBlockDesc2 gRandomSoundBk
(
 plComponent::kBlkComp, _T("RandomSound"), 0, &gRandomSoundDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP, plComponent::kRefComp,

    2, 
    kRandomSoundMain, IDD_COMP_RANDOMSOUND, IDS_COMP_RANDOMSOUNDS, 0, 0, NULL,
    kRandomSoundGroup, IDD_COMP_RANDOMSOUND_GROUPS, IDS_COMP_RANDOMSOUNDS_GROUPS, 0, APPENDROLL_CLOSED, &gRandomSoundComponentProc,

    // Main rollout
    kAutoStart,  _T("AutoStart"), TYPE_BOOL,        0, 0,
        p_default,  TRUE,
        p_ui, kRandomSoundMain, TYPE_SINGLECHEKBOX, IDC_COMP_RS_AUTOSTART,
        end,

    kSelectMode,    _T("SelectMode"),       TYPE_INT,       0, 0,
        p_ui, kRandomSoundMain, TYPE_RADIO, 5,  IDC_RADIO_RS_NORMAL,    IDC_RADIO_RS_NOREP, IDC_RADIO_RS_FSREP, IDC_RADIO_RS_FSSTOP, IDC_RADIO_RS_SEQ,
        end,

    kDelayMode, _T("DelayMode"),        TYPE_INT,       0, 0,
        p_ui, kRandomSoundMain, TYPE_RADIO, 3,  IDC_RADIO_RS_DELAYSTART,    IDC_RADIO_RS_DELAYEND,  IDC_RADIO_RS_DELAYNEVER, 
        end,

    kMinDelay,      _T("MinDelay"),     TYPE_FLOAT,     0, 0,   
        p_default, 0.0,
        p_range, -500.0, 1000.0,
        p_ui, kRandomSoundMain, TYPE_SPINNER,   EDITTYPE_FLOAT, 
        IDC_COMP_RS_DELAYMIN, IDC_COMP_RS_DELAYMIN_SPIN, 1.0,
        end,

    kMaxDelay,      _T("MaxDelay"),     TYPE_FLOAT,     0, 0,   
        p_default, 0.0,
        p_range, -500.0, 1000.0,
        p_ui, kRandomSoundMain, TYPE_SPINNER,   EDITTYPE_FLOAT, 
        IDC_COMP_RS_DELAYMAX, IDC_COMP_RS_DELAYMAX_SPIN, 0.1,
        end,

    // Group rollout
    kUseAll,    _T("UseAll"),   TYPE_BOOL,  0, 0,
        p_default,  TRUE,
        p_ui, kRandomSoundGroup, TYPE_SINGLECHEKBOX, IDC_COMP_RS_USEALL,
        end,

    kGroupIdx,  _T("GroupIndex"),   TYPE_INT,   0, 0,
        p_default, 1,
        p_range, 1, kMaxGroups,
        p_ui, kRandomSoundGroup, TYPE_SPINNER,  EDITTYPE_INT,
        IDC_COMP_RS_GROUP, IDC_COMP_RS_GROUP_SPIN, 1.f,
        end,

    kSoundList, _T("Sounds"), TYPE_INODE_TAB, 0,    0, 0,
        end,

    kGroupTotals, _T("Totals"), TYPE_INT_TAB, kMaxGroups,   0, 0,
        p_default, 0,
        end,

    kLastPick, _T("LastPick"), TYPE_INODE,  0, 0, // Temp storage space for the comp picker
        end,

    kCombineSounds, _T("combineSounds"), TYPE_BOOL, 0, 0,
        p_default, FALSE,
        p_ui, kRandomSoundGroup, TYPE_SINGLECHEKBOX, IDC_RAND_COMBINESOUNDS,
        end,

    end
);

plRandomSoundComponent::plRandomSoundComponent()
{
    fClassDesc = &gRandomSoundDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

int plRandomSoundComponent::GetCurGroupIdx()
{
    return fCompPB->GetInt(ParamID(kGroupIdx)) - 1;
}

int plRandomSoundComponent::GetStartIndex(int group)
{
    int result = 0;
    int i;
    for (i = 0; i < group; i++)
        result += fCompPB->GetInt(ParamID(kGroupTotals), 0, i);

    return result;
}

int plRandomSoundComponent::GetEndIndex(int group)
{
    return GetStartIndex(group) + fCompPB->GetInt(ParamID(kGroupTotals), 0, group);
}

void plRandomSoundComponent::AddSelectedSound()
{
    int group = GetCurGroupIdx();
    int soundIdx = GetEndIndex(group);

    INode *node = fCompPB->GetINode(ParamID(kLastPick));
    fCompPB->Insert(ParamID(kSoundList), soundIdx, 1, &node);

    fCompPB->SetValue(ParamID(kGroupTotals), 0, fCompPB->GetInt(ParamID(kGroupTotals), 0, group) + 1, group);
}

void plRandomSoundComponent::RemoveSound(int index)
{
    int group = GetCurGroupIdx();
    int soundIdx = GetStartIndex(group) + index;
    
    fCompPB->Delete(ParamID(kSoundList), soundIdx, 1);
    fCompPB->SetValue(ParamID(kGroupTotals), 0, fCompPB->GetInt(ParamID(kGroupTotals), 0, group) - 1, group);
}

hsBool plRandomSoundComponent::ICheckForSounds(plMaxNode* node)
{
    if (!node->CanConvert())
        return false;

    int nSounds = 0;
    UInt32 numComp = node->NumAttachedComponents(false);
    for(int i = 0; i < numComp; i++)
    {
        plComponentBase* comp = node->GetAttachedComponent(i);
        if (plAudioComp::IsSoundComponent(comp))
            nSounds++;
    }

    return nSounds > 0;
}

hsBool plRandomSoundComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
    if( !ICheckForSounds(node) )
    {
        // Warning that there's no sounds to be played?
        return true;
    }

    plRandomSoundMod* mod = fSoundMods[node];
    plSound *pSound = nil;
    const plAudioInterface* ai = nil;
    plWinAudible* pAudible = nil;


    if( fCompPB->GetInt((ParamID)kAutoStart) )
        mod->SetState(0);
    else
        mod->SetState(plRandomSoundMod::kStopped);

    UInt8 mode = plRandomSoundMod::kNormal;
    switch( fCompPB->GetInt((ParamID)kSelectMode) )
    {
    // random, repeats okay, play until stopped             - Normal
    case kNormal:
        mode = plRandomSoundMod::kNormal;
        break;
    // random, no repeats, play until stopped               - NoRepeats
    case kNoRepeats:
        mode = plRandomSoundMod::kNoRepeats;
        break;
    // random, play full cycle before repeating             - FullSetRepeat
    case kFullSetRepeat:
        mode = plRandomSoundMod::kCoverall | plRandomSoundMod::kNoRepeats;
        break;
    // random, play full cycle, then stop                   - FullSetStop
    case kFullSetStop:
        mode = plRandomSoundMod::kCoverall | plRandomSoundMod::kOneCycle | plRandomSoundMod::kNoRepeats;
        break;
    case kSequential:
        mode = plRandomSoundMod::kSequential;
        break;
    }

    switch( fCompPB->GetInt((ParamID)kDelayMode) )
    {
    case kDelayFromStart:
        break;
    case kDelayFromEnd:
        mode |= plRandomSoundMod::kDelayFromEnd;
        break;
    case kDelayInfinite:
        mode |= plRandomSoundMod::kOneCmd;
        break;
    }

    mod->SetMode(mode);

    float minDel = fCompPB->GetFloat((ParamID)kMinDelay);
    float maxDel = fCompPB->GetFloat((ParamID)kMaxDelay);
    if( minDel > maxDel )
    {
        float t = maxDel;
        maxDel = minDel;
        minDel = t;
    }

    mod->SetMinDelay(minDel);
    mod->SetMaxDelay(maxDel);

    node->AddModifier(mod, IGetUniqueName(node));


    if (!fCompPB->GetInt(ParamID(kUseAll))) // Actually using separate groups
    {
        ai = node->GetSceneObject()->GetAudioInterface();
        pAudible = (plWinAudible*)ai->GetAudible();
        hsTArray<plBaseSoundEmitterComponent *> comps;

        plRandomSoundModGroup *groups = TRACKED_NEW plRandomSoundModGroup[kMaxGroups];
        int i;
        int numSoFar = 0;
        for (i = 0; i < kMaxGroups; i++)
        {
            int numSounds = fCompPB->GetInt(ParamID(kGroupTotals), 0, i);
            if( numSounds == 0 )
            {
                groups[i].fGroupedIdx = -1; 
                groups[i].fNumSounds = 0;
                groups[i].fIndices = nil;
                continue;
            }

            groups[i].fIndices = TRACKED_NEW UInt16[numSounds];

            hsTArray<UInt16> indices;
            int j;

            if( !fCompPB->GetInt( (ParamID)kCombineSounds ) )
            {
                for (j = 0; j < numSounds; j++)
                {
                    plMaxNode *compNode = (plMaxNode*)fCompPB->GetINode(ParamID(kSoundList), 0, numSoFar + j);
                    if (compNode)
                    {
                        plBaseSoundEmitterComponent *comp = (plBaseSoundEmitterComponent *)compNode->ConvertToComponent();
                        int idx = comp->GetSoundIdx((plMaxNode*)node);
                        if (idx >= 0)
                        {
                            indices.Append(idx);
                        }
                    }
                }
                groups[i].fNumSounds = indices.GetCount();
                for (j = 0; j < indices.GetCount(); j++)
                {   
                    groups[i].fIndices[j] = indices[j];
                }
            }
            else
            {
                // Build array of components to give to ConvertGrouped()
                for (j = 0; j < numSounds; j++)
                {
                    plMaxNode *compNode = (plMaxNode*)fCompPB->GetINode(ParamID(kSoundList), 0, numSoFar + j);
                    if (compNode)
                    {
                        plBaseSoundEmitterComponent *comp = (plBaseSoundEmitterComponent *)compNode->ConvertToComponent();
                        comps.Append( comp );
                        // Stupid, i know. Leave me alone, PG is playing.
                        indices.Append( comps.GetCount() - 1 );
                    }
                }

                // Get index from first (should be the same for all of 'em)
                groups[i].fGroupedIdx = comps[ 0 ]->GetSoundIdx( (plMaxNode *)node );
                groups[i].fNumSounds = indices.GetCount();
                for (j = 0; j < indices.GetCount(); j++)
                {
                    groups[i].fIndices[j] = indices[ j ];
                }
            }
                
            numSoFar += groups[i].fNumSounds;
        }
        mod->SetGroupInfo(kMaxGroups, groups);

        if( fCompPB->GetInt( (ParamID)kCombineSounds ) )
        {
            // Convert (use pointer to first comp so we get the virtual call)
            if( !comps[ 0 ]->ConvertGrouped( node, comps, pErrMsg ) )
            {
                return false;
            }
        }
    }

    // Non-grouped random sounds - give priority to each sound
    else
    {
        ai = node->GetSceneObject()->GetAudioInterface();
        pAudible = (plWinAudible*)ai->GetAudible();
        int numSounds = pAudible->GetNumSounds();
        
        if(numSounds == 0) return true;
        
        pSound = pAudible->GetSound(0); // Get sound ptr
        int highestPriority = pSound->GetPriority();

        // Distance to lowest priority
        int distToLowest = 9 - highestPriority;
        if( distToLowest <= 0) distToLowest = 1;    // just incase

        for( int i = 0; i < numSounds; i++)
        {
            pSound = pAudible->GetSound(i); // Get sound ptr

            // Give the first random sound highest priority
            if(i == 0)
                pSound->SetPriority(highestPriority);

            else
            {
                pSound->SetPriority(highestPriority+((i-1)%distToLowest)+1);
            }
        }
    }
    
    return true;
}

hsBool plRandomSoundComponent::PreConvert(plMaxNode *pNode,  plErrorMsg *pErrMsg)
{
    if (ICheckForSounds(pNode))
    {
        plRandomSoundMod* mod = TRACKED_NEW plRandomSoundMod;
        hsgResMgr::ResMgr()->NewKey(IGetUniqueName(pNode), mod, pNode->GetLocation());
        fSoundMods[pNode] = mod;
    }

    return true;
}

// SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plRandomSoundComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *pErrMsg)
{
    fSoundMods.clear();

    // Tell some (all?) of the sound components we point to that they're going to be 
    // grouped sounds instead
    if( fCompPB->GetInt( (ParamID)kCombineSounds ) )
    {
        if (!fCompPB->GetInt(ParamID(kUseAll))) // Actually using separate groups
        {
            // Get a sound index to assign to all the components, since they get the same one as a grouped sound
            int idx = pNode->GetNextSoundIdx();

            int i, numSoFar = 0;
            for (i = 0; i < kMaxGroups; i++)
            {
                int numSounds = fCompPB->GetInt(ParamID(kGroupTotals), 0, i);

                if( numSounds <= 0 )
                    continue;

                int j;
                for (j = 0; j < numSounds; j++)
                {
                    plMaxNode *compNode = (plMaxNode*)fCompPB->GetINode(ParamID(kSoundList), 0, numSoFar + j);
                    if (compNode)
                    {
                        plBaseSoundEmitterComponent *comp = (plBaseSoundEmitterComponent *)compNode->ConvertToComponent();
                        comp->SetCreateGrouped( pNode, idx );
                    }
                }
                numSoFar += numSounds;
            }
        }
    }
    
    return true;
}



#endif // Waiting... mf




/////////////////////////////////////////////////////////////////////////////////////////////////
/// Physics Sound Group Component ///////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////

class plPhysicsSndGroupCompProc;
class plPhysicsSndGroupComp : public plComponent
{
protected:

    friend class plPhysicsSndGroupCompProc;

public:
    plPhysicsSndGroupComp();
    void DeleteThis() { delete this; }

    hsBool SetupProperties(plMaxNode *pNode, plErrorMsg *pErrMsg);
    hsBool PreConvert(plMaxNode *pNode, plErrorMsg *pErrMsg);
    hsBool Convert(plMaxNode *node, plErrorMsg *pErrMsg);

    enum Refs
    {
        kRefGroup,
        kRefImpactSoundsOld,
        kRefSlideSoundsOld,
        kRefDummyPickNode,
        kRefImpactSounds,
        kRefSlideSounds,
    };
};

class plPhysicsSndGroupCompProc : public ParamMap2UserDlgProc
{
public:
    plPhysicsSndGroupCompProc() {}

    BOOL DlgProc(TimeValue t, IParamMap2 *pm, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

    void DeleteThis() {}
    virtual void Update(TimeValue t, Interval& valid, IParamMap2* pmap) {  }

protected:

    void    IInitList( HWND hList, int currSel, hsBool allowAll )
    {
        int     i, toSet = -1;
        struct plSndGrp
        {
            char name[ 64 ];
            int group;
        } groups[] = {  { "Metal",  plPhysicalSndGroup::kMetal },
                        { "Grass",  plPhysicalSndGroup::kGrass },
                        { "Wood",   plPhysicalSndGroup::kWood },
                        { "Stone",  plPhysicalSndGroup::kWood + 1 },
                        { "Water",  plPhysicalSndGroup::kWood + 2 },
                        { "Bone",   plPhysicalSndGroup::kWood + 3 },
                        { "Dirt",   plPhysicalSndGroup::kWood + 4 },
                        { "Rug",    plPhysicalSndGroup::kWood + 5 },
                        { "Cone",   plPhysicalSndGroup::kWood + 6 },
                        { "User 1", plPhysicalSndGroup::kWood + 7 },
                        { "User 2", plPhysicalSndGroup::kWood + 8 },
                        { "User 3", plPhysicalSndGroup::kWood + 9 },
                        { "", plPhysicalSndGroup::kNone } };

        SendMessage( hList, CB_RESETCONTENT, 0, 0 );
        
        if( allowAll )
        {
            int idx = SendMessage( hList, CB_ADDSTRING, 0, (LPARAM)"* All *" );
            SendMessage( hList, CB_SETITEMDATA, idx, (LPARAM)-1 );
            if( currSel == -1 )
                toSet = idx;
        }

        for( i = 0; groups[ i ].group != plPhysicalSndGroup::kNone; i++ )
        {
            int idx = SendMessage( hList, CB_ADDSTRING, 0, (LPARAM)groups[ i ].name );
            SendMessage( hList, CB_SETITEMDATA, idx, (LPARAM)groups[ i ].group );

            if( groups[ i ].group == currSel )
                toSet = idx;
        }

        if( toSet != -1 )
            SendMessage( hList, CB_SETCURSEL, toSet, 0 );
    }

    void    IUpdateBtns( HWND hWnd, int idx, plPhysicsSndGroupComp *comp )
    {
        // Update da buttons
        if( idx == -1 )
            idx = 0;

        INode *impact = IGet( comp->GetParamBlock( 0 ), plPhysicsSndGroupComp::kRefImpactSounds, idx );
        ::SetWindowText( GetDlgItem( hWnd, IDC_SND_IMPACT ), ( impact != nil ) ? impact->GetName() : "<none>" );
        
        INode *slide = IGet( comp->GetParamBlock( 0 ), plPhysicsSndGroupComp::kRefSlideSounds, idx );
        ::SetWindowText( GetDlgItem( hWnd, IDC_SND_SLIDE ), ( slide != nil ) ? slide->GetName() : "<none>" );
    }
    
    void    ISet( IParamBlock2 *pb, ParamID which, int idx, INode *node )
    {
        if( pb->Count( which ) <= idx )
        {
            pb->SetCount( (ParamID)which, idx + 1 );
            pb->Resize( (ParamID)which, idx + 1 );
        }

        if( idx == -1 )
        {
            pb->SetCount( (ParamID)which, plPhysicalSndGroup::kWood + 9 );
            pb->Resize( which, plPhysicalSndGroup::kWood + 9 );
            int i;
            for( i = 0; i < plPhysicalSndGroup::kWood + 9; i++ )
                pb->SetValue( which, 0, node, i );
        }
        else
            pb->SetValue( which, 0, node, idx );
    }

    INode   *IGet( IParamBlock2 *pb, ParamID which, int idx )
    {
        if( pb->Count( which ) <= idx )
            return nil;
            
        return pb->GetINode( which, 0, idx );
    }
};

static plPhysicsSndGroupCompProc gPhysicsSndGroupCompProc;


BOOL plPhysicsSndGroupCompProc::DlgProc(TimeValue t, IParamMap2 *pm, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    IParamBlock2 *pb = pm->GetParamBlock();
    HWND hList = GetDlgItem( hWnd, IDC_SND_GROUP );
    HWND hAgainst = GetDlgItem( hWnd, IDC_SND_AGAINST );
    plPhysicsSndGroupComp *comp = (plPhysicsSndGroupComp *)pb->GetOwner();

    switch( msg )
    {
        case WM_INITDIALOG:
            {
                IInitList( GetDlgItem( hWnd, IDC_SND_GROUP ), pb->GetInt( plPhysicsSndGroupComp::kRefGroup ), false );
                IInitList( GetDlgItem( hWnd, IDC_SND_AGAINST ), -1, true );

                int idx = SendMessage( hAgainst, CB_GETCURSEL, 0, 0 );
                if( idx != CB_ERR )
                {
                    idx = (int)SendMessage( hAgainst, CB_GETITEMDATA, idx, 0 );
                    IUpdateBtns( hWnd, idx, comp );
                }
            }

            return TRUE;

        case WM_COMMAND:
            if( HIWORD( wParam ) == CBN_SELCHANGE )
            {
                if( LOWORD( wParam ) == IDC_SND_GROUP )
                {
                    int idx = SendMessage( hList, CB_GETCURSEL, 0, 0 );
                    if( idx != CB_ERR )
                    {
                        pb->SetValue( (ParamID)plPhysicsSndGroupComp::kRefGroup, 0, (int)SendMessage( hList, CB_GETITEMDATA, idx, 0 ) );
                    }
                    return true;
                }
                else if( LOWORD( wParam ) == IDC_SND_AGAINST )
                {
                    int idx = SendMessage( hAgainst, CB_GETCURSEL, 0, 0 );
                    if( idx != CB_ERR )
                    {
                        idx = (int)SendMessage( hAgainst, CB_GETITEMDATA, idx, 0 );
                        IUpdateBtns( hWnd, idx, comp );
                    }
                }
            }
            else if( LOWORD( wParam ) == IDC_SND_CLEAR_IMPACT )
            {
                int idx = SendMessage( hAgainst, CB_GETCURSEL, 0, 0 );
                if( idx != CB_ERR )
                {
                    idx = (int)SendMessage( hAgainst, CB_GETITEMDATA, idx, 0 );
                    if( idx == -1 )
                    {
                        pb->Resize( (ParamID)plPhysicsSndGroupComp::kRefImpactSounds, 0 ); 
                    }
                    else
                        ISet( pb, plPhysicsSndGroupComp::kRefImpactSounds, idx, nil );
                    IUpdateBtns( hWnd, idx, comp );
                }
            }
            else if( LOWORD( wParam ) == IDC_SND_CLEAR_SLIDE )
            {
                int idx = SendMessage( hAgainst, CB_GETCURSEL, 0, 0 );
                if( idx != CB_ERR )
                {
                    idx = (int)SendMessage( hAgainst, CB_GETITEMDATA, idx, 0 );
                    if( idx == -1 )
                        pb->Resize( (ParamID)plPhysicsSndGroupComp::kRefSlideSounds, 0 ); 
                    else
                        ISet( pb, plPhysicsSndGroupComp::kRefSlideSounds, idx, nil );
                    IUpdateBtns( hWnd, idx, comp );
                }
            }
            else if( LOWORD( wParam ) == IDC_SND_IMPACT )
            {
                int idx = SendMessage( hAgainst, CB_GETCURSEL, 0, 0 );
                if( idx != CB_ERR )
                {
                    idx = (int)SendMessage( hAgainst, CB_GETITEMDATA, idx, 0 );

                    std::vector<Class_ID> cids;
                    cids.push_back( RANDOM_SOUND_COMPONENT_ID );
                    if( plPick::NodeRefKludge( pb, plPhysicsSndGroupComp::kRefDummyPickNode, &cids, true, false ) )
                        ISet( comp->GetParamBlock( 0 ), plPhysicsSndGroupComp::kRefImpactSounds, idx, pb->GetINode( plPhysicsSndGroupComp::kRefDummyPickNode ) );
                    
                    IUpdateBtns( hWnd, idx, comp );
                }
            }
            else if( LOWORD( wParam ) == IDC_SND_SLIDE )
            {
                int idx = SendMessage( hAgainst, CB_GETCURSEL, 0, 0 );
                if( idx != CB_ERR )
                {
                    idx = (int)SendMessage( hAgainst, CB_GETITEMDATA, idx, 0 );
                    
                    std::vector<Class_ID> cids;
                    cids.push_back( RANDOM_SOUND_COMPONENT_ID );
                    if( plPick::NodeRefKludge( pb, plPhysicsSndGroupComp::kRefDummyPickNode, &cids, true, false ) )             
                        ISet( pb, plPhysicsSndGroupComp::kRefSlideSounds, idx, pb->GetINode( plPhysicsSndGroupComp::kRefDummyPickNode ) );
                        
                    IUpdateBtns( hWnd, idx, comp );
                }
            }

    }

    return FALSE;
}

// Simple accessor
class plPhysicsSndGroupAccessor : public PBAccessor
{
public:
    void Set( PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t )
    {
        if( id == plPhysicsSndGroupComp::kRefImpactSounds || id == plPhysicsSndGroupComp::kRefSlideSounds )
        {
            plPhysicsSndGroupComp *comp = (plPhysicsSndGroupComp *)owner;
            comp->NotifyDependents( FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED );
        }
    }
};
static plPhysicsSndGroupAccessor    glPhysicsSndGroupAccessor;

//Max desc stuff necessary below.
CLASS_DESC(plPhysicsSndGroupComp, gPhysSndGrpDesc, "Physics Sound Group",  "PhysSndGroup", COMP_TYPE_AUDIO, SOUND_PHYS_COMP_ID)

ParamBlockDesc2 gPhysSndGrpBk
(
    plComponent::kBlkComp, _T("PhysSndGroup"), 0, &gPhysSndGrpDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_SOUNDPHYS, IDS_COMP_SOUNDPHYS, 0, 0, &gPhysicsSndGroupCompProc,

    plPhysicsSndGroupComp::kRefGroup,  _T("Group"), TYPE_INT,       0, 0,
        p_default,  (int)plPhysicalSndGroup::kNone,
        end,

    plPhysicsSndGroupComp::kRefDummyPickNode, _T( "Dummy" ), TYPE_INODE, 0, 0,
        end,
        
    plPhysicsSndGroupComp::kRefImpactSounds,  _T("Impacts"), TYPE_INODE_TAB,        0, 0, 0,
//      p_accessor, glPhysicsSndGroupAccessor,
        end,

    plPhysicsSndGroupComp::kRefSlideSounds,  _T("Slides"), TYPE_INODE_TAB,      0, 0, 0,
//      p_accessor, glPhysicsSndGroupAccessor,
        end,

    end
);

plPhysicsSndGroupComp::plPhysicsSndGroupComp() 
{
    fClassDesc = &gPhysSndGrpDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plPhysicsSndGroupComp::Convert( plMaxNode *node, plErrorMsg *pErrMsg )
{
    plMaxNode *pNode;
    plKey RandSoundKey;

    // Try to grab the SI from the current scene object. This'll have the pointer we want
    plSceneObject *obj = node->GetSceneObject();
    if( obj != nil )
    {
        const plSimulationInterface* si = obj->GetSimulationInterface();
        if (si)
        {
            // Create a new sound group
            plPhysicalSndGroup *grp = TRACKED_NEW plPhysicalSndGroup( fCompPB->GetInt( (ParamID)kRefGroup ) );
            hsgResMgr::ResMgr()->NewKey( IGetUniqueName( node ), grp, node->GetLocation(), node->GetLoadMask() );

            // Convert each sound into a plWin32StaticSound and store onto the sound group
            int i;
            for( i = 0; i < fCompPB->Count( (ParamID)kRefImpactSounds ); i++ )
            {
                plMaxNode *targNode = (plMaxNode *)fCompPB->GetINode( (ParamID)kRefImpactSounds, 0, i );
                if( targNode != nil )
                {
                    plComponentBase *comp = targNode->ConvertToComponent();
                    if( comp != nil )
                    {
                        // Check root node for random sound component
                        RandSoundKey = plAudioComp::GetRandomSoundKey( comp, node );
                        if(RandSoundKey)
                            grp->AddImpactSound( i, RandSoundKey );
                        
                        // If not in root node check children
                        else
                        {
                            for(int j = 0; j < node->NumChildren(); j++)
                            {
                                pNode = (plMaxNode *)node->GetChildNode(j);
                                RandSoundKey = plAudioComp::GetRandomSoundKey( comp, pNode );
                                if(!RandSoundKey) continue;

                                grp->AddImpactSound( i, RandSoundKey );
                                break;
                            }
                        }
                    }
                }
            }

            for( i = 0; i < fCompPB->Count( (ParamID)kRefSlideSounds ); i++ )
            {
                plMaxNode *targNode = (plMaxNode *)fCompPB->GetINode( (ParamID)kRefSlideSounds, 0, i );
                if( targNode != nil )
                {
                    plComponentBase *comp = targNode->ConvertToComponent();
                    if( comp != nil )
                    {
                        // Check root node for random sound component
                        RandSoundKey = plAudioComp::GetRandomSoundKey( comp, node );
                        if(RandSoundKey)
                            grp->AddSlideSound( i, RandSoundKey );
                        else
                        {
                            for(int j = 0; j < node->NumChildren(); j++)
                            {
                                pNode = (plMaxNode *)node->GetChildNode(j);
                                RandSoundKey = plAudioComp::GetRandomSoundKey( comp, pNode );
                                if(!RandSoundKey) continue;

                                grp->AddSlideSound( i, RandSoundKey );
                                break;
                            }
                        }
                    }
                }
            }

            // Attach the sound group to the physical
            hsgResMgr::ResMgr()->AddViaNotify( grp->GetKey(), TRACKED_NEW plGenRefMsg( si->GetPhysical()->GetKey(), plRefMsg::kOnCreate, 0, plPXPhysical::kPhysRefSndGroup ), plRefFlags::kActiveRef );
        }
    }

    return true;
}

hsBool plPhysicsSndGroupComp::PreConvert( plMaxNode *pNode,  plErrorMsg *pErrMsg )
{
    return true;
}

hsBool plPhysicsSndGroupComp::SetupProperties( plMaxNode *pNode,  plErrorMsg *pErrMsg )
{
    return true;
}