You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

4001 lines
125 KiB

/*==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/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
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 "../PubUtilLib/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
#include "../../AssetMan/PublicInterface/MaxAssInterface.h"
#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"
#include <eax-util.h>
#include <eaxlegacy.h>
#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 );
obj->fSoundAssetId = fSoundAssetId;
obj->fCoverSoundAssetID = fCoverSoundAssetID;
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;
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();
return IO_OK;
}
IOResult plBaseSoundEmitterComponent::Load(ILoad *iload)
{
IOResult res = plComponentBase::Load(iload);
if (res != IO_OK)
return res;
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;
}
return IO_OK;
}
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;
}
void plBaseSoundEmitterComponent::IUpdateAssets( void )
{
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;
}
TCHAR *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.
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;
}
plSoundBuffer *plBaseSoundEmitterComponent::IProcessSourceBuffer( plMaxNode *maxNode, plErrorMsg *errMsg )
{
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 )
{
TCHAR fileName[ MAX_PATH ], dirName[ MAX_PATH ], *name;
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 = 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";
if( GetOpenFileName( &ofn ) )
{
jvUniqueId emptyId;
soundComponent->SetSoundAssetId( which, emptyId, fileName );
}
}
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 )
{
TCHAR *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 )
{
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 );
}
// 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;
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
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 ], *c;
strcpy( newPath, plasmaDir );
strcat( newPath, "sfx\\" );
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;
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;
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 );
for( i = 0; i < /*sizeof( EAX30_ORIGINAL_PRESETS )
/ sizeof( EAXLISTENERPROPERTIES )*/26 ; i++ )
ComboBox_AddString( comboBox, EAX30_ORIGINAL_PRESET_NAMES[ i ] );
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 );
// 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 );
}
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 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;
}