/*==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 . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "HeadSpin.h" #include "plComponentProcBase.h" #include "resource.h" #include "plComponent.h" #include "plComponentReg.h" #include #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; }