/*==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 . 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 #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 // 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 #include #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 ¶mID ) { 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::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( "" ) ); 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 &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 &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 startPoses; hsTArray volumes; hsLargeArray 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 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 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 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() : "" ); INode *slide = IGet( comp->GetParamBlock( 0 ), plPhysicsSndGroupComp::kRefSlideSounds, idx ); ::SetWindowText( GetDlgItem( hWnd, IDC_SND_SLIDE ), ( slide != nil ) ? slide->GetName() : "" ); } 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 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 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; }