/* Handle and provide an interface to the sound driver. Delete sounds that * have been detached from their owner when they're finished playing. */ #include "global.h" #include "RageSoundManager.h" #include "RageException.h" #include "RageUtil.h" #include "RageSound.h" #include "RageLog.h" #include "RageTimer.h" #include "arch/Sound/RageSoundDriver.h" #include "arch/arch.h" /* * The lock ordering requirements are: * RageSound::Lock before g_SoundManMutex * RageSound::Lock must not be locked when calling driver calls (since the driver * may lock a mutex and then make RageSound calls back) * * Do not make RageSound calls that might lock while holding g_SoundManMutex. */ static RageMutex g_SoundManMutex("SoundMan"); RageSoundManager *SOUNDMAN = NULL; RageSoundManager::RageSoundManager() { pos_map_queue.reserve( 1024 ); MixVolume = 1.0f; } void RageSoundManager::Init( CString drivers ) { driver = MakeRageSoundDriver(drivers); if( driver == NULL ) RageException::Throw( "Couldn't find a sound driver that works" ); } RageSoundManager::~RageSoundManager() { g_SoundManMutex.Lock(); /* lock for access to owned_sounds */ set sounds = owned_sounds; g_SoundManMutex.Unlock(); /* finished with owned_sounds */ /* Clear any sounds that we own and havn't freed yet. */ set::iterator j = sounds.begin(); while(j != sounds.end()) delete *(j++); /* Don't lock while deleting the driver (the decoder thread might deadlock). */ delete driver; } /* * Previously, we went to some lengths to shut down sounds before exiting threads. * The only other thread that actually starts sounds is SOUND. Doing this was ugly; * instead, let's shut down the driver early, stopping all sounds. We don't want * to delete SOUNDMAN early, since those threads are still using it; just shut down * the driver. */ void RageSoundManager::Shutdown() { if( driver != NULL ) { delete driver; driver = NULL; } } void RageSoundManager::StartMixing( RageSoundBase *snd ) { if( driver != NULL ) driver->StartMixing(snd); } void RageSoundManager::StopMixing( RageSoundBase *snd ) { if( driver != NULL ) driver->StopMixing(snd); } bool RageSoundManager::Pause( RageSoundBase *snd, bool bPause ) { if( driver == NULL ) return false; else return driver->PauseMixing( snd, bPause ); } int64_t RageSoundManager::GetPosition( const RageSoundBase *snd ) const { if( driver == NULL ) return 0; return driver->GetPosition(snd); } void RageSoundManager::Update(float delta) { FlushPosMapQueue(); /* Scan the owned_sounds list for sounds that are no longer playing, and delete them. */ g_SoundManMutex.Lock(); /* lock for access to owned_sounds */ set ToDelete; for( set::iterator it = owned_sounds.begin(); it != owned_sounds.end(); ++it ) { if( (*it)->IsPlaying() ) continue; LOG->Trace("XXX: deleting '%s'", (*it)->GetLoadedFilePath().c_str()); ToDelete.insert( (*it) ); } for( set::iterator it = ToDelete.begin(); it != ToDelete.end(); ++it ) owned_sounds.erase( *it ); g_SoundManMutex.Unlock(); /* finished with owned_sounds */ /* Be sure to release g_SoundManMutex before deleting sounds. */ for( set::iterator it = ToDelete.begin(); it != ToDelete.end(); ++it ) delete *it; if( driver != NULL ) driver->Update(delta); } /* Register the given sound, and return a unique ID. */ void RageSoundManager::RegisterSound( RageSound *p ) { g_SoundManMutex.Lock(); /* lock for access to all_sounds */ all_sounds[p->GetID()] = p; g_SoundManMutex.Unlock(); /* finished with all_sounds */ } void RageSoundManager::UnregisterSound( RageSound *p ) { g_SoundManMutex.Lock(); /* lock for access to all_sounds */ all_sounds.erase( p->GetID() ); g_SoundManMutex.Unlock(); /* finished with all_sounds */ } /* Return a unique ID. */ int RageSoundManager::GetUniqueID() { LockMut(g_SoundManMutex); /* serialize iID */ static int iID = 0; return ++iID; } void RageSoundManager::CommitPlayingPosition( int ID, int64_t frameno, int pos, int got_frames ) { /* This can be called from realtime threads; don't lock any mutexes. */ queued_pos_map_t p; p.ID = ID; p.frameno = frameno; p.pos = pos; p.got_frames = got_frames; pos_map_queue.write( &p, 1 ); } RageSound *RageSoundManager::GetSoundByID( int ID ) { LockMut( g_SoundManMutex ); /* Find the sound with p.ID. */ map::iterator it; it = all_sounds.find( ID ); if( it == all_sounds.end() ) return NULL; return it->second; } /* This is only called by RageSoundManager::Update. */ void RageSoundManager::FlushPosMapQueue() { queued_pos_map_t p; /* We don't need to lock to access pos_map_queue. */ while( pos_map_queue.read( &p, 1 ) ) { RageSound *pSound = GetSoundByID( p.ID ); /* If we can't find the ID, the sound was probably deleted before we got here. */ if( pSound == NULL ) { // LOG->Trace("ignored unknown (stale?) commit ID %i", p.ID); continue; } pSound->CommitPlayingPosition( p.frameno, p.pos, p.got_frames ); } } float RageSoundManager::GetPlayLatency() const { if( driver == NULL ) return 0; return driver->GetPlayLatency(); } int RageSoundManager::GetDriverSampleRate( int rate ) const { if( driver == NULL ) return 44100; return driver->GetSampleRate( rate ); } /* The return value of PlaySound and PlayCopyOfSound is only valid in the main * thread, until the next call to Update(). After that, it may be deleted. */ RageSound *RageSoundManager::PlaySound( RageSound &snd, const RageSoundParams *params ) { if( snd.IsPlaying() ) return PlayCopyOfSound( snd, params ); if( params ) snd.SetParams( *params ); // Move to the start position. snd.SetPositionSeconds( snd.GetParams().m_StartSecond ); snd.StartPlaying(); return &snd; } RageSound *RageSoundManager::PlayCopyOfSound( RageSound &snd, const RageSoundParams *params ) { RageSound *pSound = new RageSound(snd); DeleteSoundWhenFinished( pSound ); if( params ) pSound->SetParams( *params ); // Move to the start position. pSound->SetPositionSeconds( pSound->GetParams().m_StartSecond ); pSound->StartPlaying(); return pSound; } void RageSoundManager::DeleteSound( RageSound *p ) { /* Stop playing the sound. */ p->StopPlaying(); /* We might be in a thread, so don't delete it here. */ DeleteSoundWhenFinished( p ); } void RageSoundManager::DeleteSoundWhenFinished( RageSound *pSound ) { g_SoundManMutex.Lock(); /* lock for access to owned_sounds */ owned_sounds.insert( pSound ); g_SoundManMutex.Unlock(); /* finished with owned_sounds */ } /* Don't hold the lock when we don't have to. We call this function from other * threads, to avoid stalling the gameplay thread. */ void RageSoundManager::PlayOnce( CString sPath ) { /* We want this to start quickly, so don't try to prebuffer it. */ RageSound *snd = new RageSound; snd->Load(sPath, false); snd->Play(); /* We're responsible for freeing it. Add it to owned_sounds *after* we start * playing, so RageSoundManager::Update doesn't free it before we actually start * it. */ DeleteSoundWhenFinished( snd ); } void RageSoundManager::SetPrefs(float MixVol) { g_SoundManMutex.Lock(); /* lock for access to MixVolume */ MixVolume = MixVol; g_SoundManMutex.Unlock(); /* finished with MixVolume */ } /* Standalone helpers: */ void RageSoundManager::AttenuateBuf( int16_t *buf, int samples, float vol ) { while( samples-- ) { *buf = int16_t( (*buf) * vol ); ++buf; } } /* * Copyright (c) 2002-2004 Glenn Maynard * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */