22b7b34897
Course playing still results in it playing fine.
715 lines
19 KiB
C++
715 lines
19 KiB
C++
/* Handle loading and decoding of sounds.
|
|
*
|
|
* For small files, pre-decode the entire file into a regular buffer. We
|
|
* might want to play many samples at once, and we don't want to have to decode
|
|
* 5-10 mp3s simultaneously during play.
|
|
*
|
|
* For larger files, decode them on the fly. These are usually music, and there's
|
|
* usually only one of those playing at a time. When we get updates, decode data
|
|
* at the same rate we're playing it. If we don't do this, and we're being read
|
|
* in large chunks, we're forced to decode in larger chunks as well, which can
|
|
* cause framerate problems.
|
|
*
|
|
* Error handling:
|
|
* Decoding errors (eg. CRC failures) will be recovered from when possible.
|
|
*
|
|
* When they can't be recovered, the sound will stop (unless loop or !autostop)
|
|
* and the error will be available in GetError().
|
|
*
|
|
* Seeking past the end of the file will throw a warning and rewind.
|
|
*/
|
|
|
|
#include "global.h"
|
|
#include "RageSound.h"
|
|
#include "RageSoundManager.h"
|
|
#include "RageUtil.h"
|
|
#include "RageLog.h"
|
|
#include "PrefsManager.h"
|
|
#include "RageSoundUtil.h"
|
|
|
|
#include "RageSoundReader_Extend.h"
|
|
#include "RageSoundReader_Pan.h"
|
|
#include "RageSoundReader_PitchChange.h"
|
|
#include "RageSoundReader_PostBuffering.h"
|
|
#include "RageSoundReader_Preload.h"
|
|
#include "RageSoundReader_Resample_Good.h"
|
|
#include "RageSoundReader_FileReader.h"
|
|
#include "RageSoundReader_ThreadedBuffer.h"
|
|
|
|
#define samplerate() m_pSource->GetSampleRate()
|
|
|
|
RageSoundParams::RageSoundParams():
|
|
m_StartSecond(0), m_LengthSeconds(-1), m_fFadeInSeconds(0),
|
|
m_fFadeOutSeconds(0), m_Volume(1.0f), m_fAttractVolume(1.0f),
|
|
m_fPitch(1.0f), m_fSpeed(1.0f), m_StartTime( RageZeroTimer ),
|
|
StopMode(M_AUTO), m_bIsCriticalSound(false) {}
|
|
|
|
RageSoundLoadParams::RageSoundLoadParams():
|
|
m_bSupportRateChanging(false), m_bSupportPan(false) {}
|
|
|
|
RageSound::RageSound():
|
|
m_Mutex( "RageSound" ), m_pSource(NULL),
|
|
m_sFilePath(""), m_Param(), m_iStreamFrame(0),
|
|
m_iStoppedSourceFrame(0), m_bPlaying(false),
|
|
m_sError(""), m_bDeleteWhenFinished(false)
|
|
{
|
|
ASSERT( SOUNDMAN );
|
|
}
|
|
|
|
RageSound::~RageSound()
|
|
{
|
|
Unload();
|
|
}
|
|
|
|
RageSound::RageSound( const RageSound &cpy ):
|
|
RageSoundBase( cpy ),
|
|
m_Mutex( "RageSound" )
|
|
{
|
|
ASSERT(SOUNDMAN);
|
|
|
|
m_pSource = NULL;
|
|
|
|
*this = cpy;
|
|
}
|
|
|
|
RageSound &RageSound::operator=( const RageSound &cpy )
|
|
{
|
|
LockMut(cpy.m_Mutex);
|
|
|
|
/* If m_bDeleteWhenFinished, then nobody that has a reference to the sound
|
|
* should be making copies. */
|
|
ASSERT( !cpy.m_bDeleteWhenFinished );
|
|
|
|
m_Param = cpy.m_Param;
|
|
m_iStreamFrame = cpy.m_iStreamFrame;
|
|
m_iStoppedSourceFrame = cpy.m_iStoppedSourceFrame;
|
|
m_bPlaying = false;
|
|
m_bDeleteWhenFinished = false;
|
|
|
|
delete m_pSource;
|
|
if( cpy.m_pSource )
|
|
m_pSource = cpy.m_pSource->Copy();
|
|
else
|
|
m_pSource = NULL;
|
|
|
|
m_sFilePath = cpy.m_sFilePath;
|
|
|
|
return *this;
|
|
}
|
|
|
|
void RageSound::Unload()
|
|
{
|
|
if( IsPlaying() )
|
|
StopPlaying();
|
|
|
|
LockMut(m_Mutex);
|
|
|
|
delete m_pSource;
|
|
m_pSource = NULL;
|
|
|
|
m_sFilePath = "";
|
|
}
|
|
|
|
/* The sound will self-delete itself when it stops playing. If the sound is not
|
|
* playing, the sound will be deleted immediately. The caller loses ownership
|
|
* of the sound. */
|
|
void RageSound::DeleteSelfWhenFinishedPlaying()
|
|
{
|
|
m_Mutex.Lock();
|
|
|
|
if( !m_bPlaying )
|
|
{
|
|
m_Mutex.Unlock();
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
m_bDeleteWhenFinished = true;
|
|
m_Mutex.Unlock();
|
|
}
|
|
|
|
bool RageSound::IsLoaded() const
|
|
{
|
|
return m_pSource != NULL;
|
|
}
|
|
|
|
class RageSoundReader_Silence: public RageSoundReader
|
|
{
|
|
public:
|
|
int GetLength() const { return 0; }
|
|
int GetLength_Fast() const { return 0; }
|
|
int SetPosition( int iFrame ) { return 1; }
|
|
int Read( float *pBuf, int iFrames ) { return RageSoundReader::END_OF_FILE; }
|
|
RageSoundReader *Copy() const { return new RageSoundReader_Silence; }
|
|
int GetSampleRate() const { return 44100; }
|
|
unsigned GetNumChannels() const { return 1; }
|
|
int GetNextSourceFrame() const { return 0; }
|
|
float GetStreamToSourceRatio() const { return 1.0f; }
|
|
RString GetError() const { return ""; }
|
|
};
|
|
|
|
|
|
bool RageSound::Load( RString sSoundFilePath )
|
|
{
|
|
/* Automatically determine whether to precache */
|
|
/* TODO: Hook this up to a pref? */
|
|
return Load( sSoundFilePath, false );
|
|
}
|
|
|
|
bool RageSound::Load( RString sSoundFilePath, bool bPrecache, const RageSoundLoadParams *pParams )
|
|
{
|
|
LOG->Trace( "RageSound: Load \"%s\" (precache: %i)", sSoundFilePath.c_str(), bPrecache );
|
|
|
|
if( pParams == NULL )
|
|
{
|
|
static const RageSoundLoadParams Defaults;
|
|
pParams = &Defaults;
|
|
}
|
|
|
|
/* If this sound is already preloaded and held by SOUNDMAN, just make a copy
|
|
* of that. Since RageSoundReader_Preload is refcounted, this is cheap. */
|
|
RageSoundReader *pSound = SOUNDMAN->GetLoadedSound( sSoundFilePath );
|
|
bool bNeedBuffer = true;
|
|
if( pSound == NULL )
|
|
{
|
|
RString error;
|
|
bool bPrebuffer;
|
|
pSound = RageSoundReader_FileReader::OpenFile( sSoundFilePath, error, &bPrebuffer );
|
|
if( pSound == NULL )
|
|
{
|
|
LOG->Warn( "RageSound::Load: error opening sound \"%s\": %s",
|
|
sSoundFilePath.c_str(), error.c_str() );
|
|
|
|
pSound = new RageSoundReader_Silence;
|
|
}
|
|
|
|
/* If the sound is prebuffered into memory, we don't need to buffer reads. */
|
|
if( bPrebuffer )
|
|
bNeedBuffer = false;
|
|
}
|
|
else
|
|
{
|
|
/* The sound we were given from SOUNDMAN is already preloaded. */
|
|
bPrecache = false;
|
|
bNeedBuffer = false;
|
|
}
|
|
|
|
LoadSoundReader( pSound );
|
|
|
|
/* Try to precache. Do this after calling LoadSoundReader() to put the
|
|
* sound in this->m_pSource, so we preload after resampling. */
|
|
if( bPrecache )
|
|
{
|
|
if( RageSoundReader_Preload::PreloadSound(m_pSource) )
|
|
{
|
|
/* We've preloaded the sound. Pass it to SOUNDMAN, for reuse. */
|
|
SOUNDMAN->AddLoadedSound( sSoundFilePath, (RageSoundReader_Preload *) m_pSource );
|
|
bNeedBuffer = false;
|
|
}
|
|
}
|
|
|
|
m_pSource = new RageSoundReader_Extend( m_pSource );
|
|
if( bNeedBuffer )
|
|
m_pSource = new RageSoundReader_ThreadedBuffer( m_pSource );
|
|
m_pSource = new RageSoundReader_PostBuffering( m_pSource );
|
|
|
|
if( pParams->m_bSupportRateChanging )
|
|
{
|
|
RageSoundReader_PitchChange *pRate = new RageSoundReader_PitchChange( m_pSource );
|
|
m_pSource = pRate;
|
|
}
|
|
|
|
if( pParams->m_bSupportPan )
|
|
m_pSource = new RageSoundReader_Pan( m_pSource );
|
|
|
|
m_sFilePath = sSoundFilePath;
|
|
|
|
m_Mutex.SetName( ssprintf("RageSound (%s)", Basename(sSoundFilePath).c_str() ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
void RageSound::LoadSoundReader( RageSoundReader *pSound )
|
|
{
|
|
Unload();
|
|
|
|
m_iStreamFrame = m_iStoppedSourceFrame = 0;
|
|
|
|
const int iNeededRate = SOUNDMAN->GetDriverSampleRate();
|
|
bool bSupportRateChange = false;
|
|
if( iNeededRate != pSound->GetSampleRate() || bSupportRateChange )
|
|
{
|
|
RageSoundReader_Resample_Good *Resample = new RageSoundReader_Resample_Good( pSound, iNeededRate );
|
|
pSound = Resample;
|
|
}
|
|
|
|
m_pSource = pSound;
|
|
}
|
|
|
|
/*
|
|
* Retrieve audio data, for mixing. At the time of this call, the frameno at which the
|
|
* sound will be played doesn't have to be known. Once committed, and the frameno
|
|
* is known, call CommitPCMData.
|
|
*
|
|
* RageSound::GetDataToPlay and RageSound::FillBuf are the main threaded API. These
|
|
* need to execute without blocking other threads from calling eg. GetPositionSeconds,
|
|
* since they may take some time to run.
|
|
*
|
|
* On underrun, if no data was read, returns WOULD_BLOCK. On end of file, if no
|
|
* data was read, returns END_OF_FILE. If any data is read, it is returned; these
|
|
* conditions are masked and will be seen on the next call. Otherwise, the requested
|
|
* number of frames will always be returned.
|
|
*/
|
|
int RageSound::GetDataToPlay( float *pBuffer, int iFrames, int64_t &iStreamFrame, int &iFramesStored )
|
|
{
|
|
/* We only update m_iStreamFrame; only take a shared lock, so we don't block the main thread. */
|
|
// LockMut(m_Mutex);
|
|
|
|
ASSERT_M( m_bPlaying, ssprintf("%p", this) );
|
|
ASSERT( m_pSource );
|
|
|
|
iFramesStored = 0;
|
|
iStreamFrame = m_iStreamFrame;
|
|
|
|
while( iFrames > 0 )
|
|
{
|
|
float fRate = 1.0f;
|
|
int iSourceFrame;
|
|
|
|
/* Read data from our source. */
|
|
int iGotFrames = m_pSource->RetriedRead( pBuffer + (iFramesStored * m_pSource->GetNumChannels()), iFrames, &iSourceFrame, &fRate );
|
|
|
|
if( iGotFrames == RageSoundReader::ERROR )
|
|
{
|
|
m_sError = m_pSource->GetError();
|
|
LOG->Warn( "Decoding %s failed: %s", GetLoadedFilePath().c_str(), m_sError.c_str() );
|
|
}
|
|
|
|
if( iGotFrames < 0 )
|
|
{
|
|
if( !iFramesStored )
|
|
return iGotFrames;
|
|
else
|
|
break;
|
|
}
|
|
|
|
m_Mutex.Lock();
|
|
m_StreamToSourceMap.Insert( m_iStreamFrame, iGotFrames, iSourceFrame, fRate );
|
|
m_Mutex.Unlock();
|
|
|
|
m_iStreamFrame += iGotFrames;
|
|
|
|
iFramesStored += iGotFrames;
|
|
iFrames -= iGotFrames;
|
|
}
|
|
if( m_pSource->GetNumChannels() == 1 )
|
|
RageSoundUtil::ConvertMonoToStereoInPlace( pBuffer, iFramesStored );
|
|
|
|
return iFramesStored;
|
|
}
|
|
|
|
/* Indicate that a block of audio data has been written to the device. */
|
|
void RageSound::CommitPlayingPosition( int64_t iHardwareFrame, int64_t iStreamFrame, int iGotFrames )
|
|
{
|
|
m_Mutex.Lock();
|
|
m_HardwareToStreamMap.Insert( iHardwareFrame, iGotFrames, iStreamFrame );
|
|
m_Mutex.Unlock();
|
|
}
|
|
|
|
/* Start playing from the current position. */
|
|
void RageSound::StartPlaying()
|
|
{
|
|
ASSERT( !m_bPlaying );
|
|
|
|
// Move to the start position.
|
|
SetPositionFrames( lrintf(m_Param.m_StartSecond * samplerate()) );
|
|
|
|
/* If m_StartTime is in the past, then we probably set a start time but took too
|
|
* long loading. We don't want that; log it, since it can be unobvious. */
|
|
if( !m_Param.m_StartTime.IsZero() && m_Param.m_StartTime.Ago() > 0 )
|
|
LOG->Trace("Sound \"%s\" has a start time %f seconds in the past",
|
|
GetLoadedFilePath().c_str(), m_Param.m_StartTime.Ago() );
|
|
|
|
/* Tell the sound manager to start mixing us. */
|
|
// LOG->Trace("set playing true for %p (StartPlaying) (%s)", this, this->GetLoadedFilePath().c_str());
|
|
|
|
m_bPlaying = true;
|
|
|
|
/* Save the attract volume, so changes don't affect previously played
|
|
* sounds. */
|
|
m_Param.m_fAttractVolume = SOUNDMAN->GetVolumeOfNonCriticalSounds();
|
|
ApplyParams();
|
|
|
|
/* Don't lock while calling SOUNDMAN driver calls. */
|
|
ASSERT( !m_Mutex.IsLockedByThisThread() );
|
|
|
|
SOUNDMAN->StartMixing( this );
|
|
|
|
// LOG->Trace("StartPlaying %p finished (%s)", this, this->GetLoadedFilePath().c_str());
|
|
}
|
|
|
|
void RageSound::StopPlaying()
|
|
{
|
|
/* Don't lock while calling SOUNDMAN driver calls. */
|
|
ASSERT( !m_Mutex.IsLockedByThisThread() );
|
|
|
|
/* Tell the sound driver to stop mixing this sound. */
|
|
SOUNDMAN->StopMixing(this);
|
|
}
|
|
|
|
/* This is called by sound drivers when we're done playing. */
|
|
void RageSound::SoundIsFinishedPlaying()
|
|
{
|
|
if( !m_bPlaying )
|
|
return;
|
|
|
|
/* Get our current hardware position. */
|
|
int64_t iCurrentHardwareFrame = SOUNDMAN->GetPosition( NULL );
|
|
|
|
m_Mutex.Lock();
|
|
|
|
if( m_bDeleteWhenFinished )
|
|
{
|
|
m_bDeleteWhenFinished = false;
|
|
m_Mutex.Unlock();
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
/* Lock the mutex after calling SOUNDMAN->GetPosition(). We must not make driver
|
|
* calls with our mutex locked (driver mutex < sound mutex). */
|
|
if( !m_HardwareToStreamMap.IsEmpty() && !m_StreamToSourceMap.IsEmpty() )
|
|
m_iStoppedSourceFrame = (int) GetSourceFrameFromHardwareFrame( iCurrentHardwareFrame );
|
|
|
|
// LOG->Trace("set playing false for %p (SoundIsFinishedPlaying) (%s)", this, this->GetLoadedFilePath().c_str());
|
|
m_bPlaying = false;
|
|
|
|
m_HardwareToStreamMap.Clear();
|
|
m_StreamToSourceMap.Clear();
|
|
|
|
// LOG->Trace("SoundIsFinishedPlaying %p finished (%s)", this, this->GetLoadedFilePath().c_str());
|
|
|
|
m_Mutex.Unlock();
|
|
}
|
|
|
|
void RageSound::Play( const RageSoundParams *pParams )
|
|
{
|
|
if( m_pSource == NULL )
|
|
{
|
|
LOG->Warn( "RageSound::Play: sound not loaded" );
|
|
return;
|
|
}
|
|
|
|
if( IsPlaying() )
|
|
{
|
|
PlayCopy( pParams );
|
|
return;
|
|
}
|
|
|
|
if( pParams )
|
|
SetParams( *pParams );
|
|
|
|
StartPlaying();
|
|
}
|
|
|
|
void RageSound::PlayCopy( const RageSoundParams *pParams ) const
|
|
{
|
|
RageSound *pSound = new RageSound( *this );
|
|
|
|
if( pParams )
|
|
pSound->SetParams( *pParams );
|
|
|
|
pSound->StartPlaying();
|
|
pSound->DeleteSelfWhenFinishedPlaying();
|
|
}
|
|
|
|
void RageSound::Stop()
|
|
{
|
|
StopPlaying();
|
|
}
|
|
|
|
bool RageSound::Pause( bool bPause )
|
|
{
|
|
if( m_pSource == NULL )
|
|
{
|
|
LOG->Warn( "RageSound::Pause: sound not loaded" );
|
|
return false;
|
|
}
|
|
|
|
return SOUNDMAN->Pause( this, bPause );
|
|
}
|
|
|
|
float RageSound::GetLengthSeconds()
|
|
{
|
|
if( m_pSource == NULL )
|
|
{
|
|
LOG->Warn( "RageSound::GetLengthSeconds: sound not loaded" );
|
|
return -1;
|
|
}
|
|
|
|
int iLength = m_pSource->GetLength();
|
|
|
|
if( iLength < 0 )
|
|
{
|
|
LOG->Warn( "GetLengthSeconds failed on %s: %s", GetLoadedFilePath().c_str(), m_pSource->GetError().c_str() );
|
|
return -1;
|
|
}
|
|
|
|
return iLength / 1000.f; // ms -> secs
|
|
}
|
|
|
|
int RageSound::GetSourceFrameFromHardwareFrame( int64_t iHardwareFrame, bool *bApproximate ) const
|
|
{
|
|
if( m_HardwareToStreamMap.IsEmpty() || m_StreamToSourceMap.IsEmpty() )
|
|
return 0;
|
|
|
|
bool bApprox;
|
|
int64_t iStreamFrame = m_HardwareToStreamMap.Search( iHardwareFrame, &bApprox );
|
|
if( bApproximate && bApprox )
|
|
*bApproximate = true;
|
|
int64_t iSourceFrame = m_StreamToSourceMap.Search( iStreamFrame, &bApprox );
|
|
if( bApproximate && bApprox )
|
|
*bApproximate = true;
|
|
return (int) iSourceFrame;
|
|
}
|
|
|
|
/* If non-NULL, approximate is set to true if the returned time is approximated because of
|
|
* underrun, the sound not having started (after Play()) or finished (after EOF) yet.
|
|
*
|
|
* If non-NULL, Timestamp is set to the real clock time associated with the returned sound
|
|
* position. We might take a variable amount of time before grabbing the timestamp (to
|
|
* lock SOUNDMAN); we might lose the scheduler after grabbing it, when releasing SOUNDMAN.
|
|
*/
|
|
float RageSound::GetPositionSeconds( bool *bApproximate, RageTimer *pTimestamp ) const
|
|
{
|
|
/* Get our current hardware position. */
|
|
int64_t iCurrentHardwareFrame = SOUNDMAN->GetPosition( pTimestamp );
|
|
|
|
/* Lock the mutex after calling SOUNDMAN->GetPosition(). We must not make driver
|
|
* calls with our mutex locked (driver mutex < sound mutex). */
|
|
LockMut( m_Mutex );
|
|
|
|
if( bApproximate )
|
|
*bApproximate = false;
|
|
|
|
/* If we're not playing, just report the static position. */
|
|
if( !IsPlaying() )
|
|
return m_iStoppedSourceFrame / float(samplerate());
|
|
|
|
/* If we don't yet have any position data, CommitPlayingPosition hasn't yet been called at all,
|
|
* so guess what we think the real time is. */
|
|
if( m_HardwareToStreamMap.IsEmpty() || m_StreamToSourceMap.IsEmpty() )
|
|
{
|
|
// LOG->Trace( "no data yet; %i", m_iStoppedSourceFrame );
|
|
if( bApproximate )
|
|
*bApproximate = true;
|
|
return m_iStoppedSourceFrame / float(samplerate());
|
|
}
|
|
|
|
int iSourceFrame = GetSourceFrameFromHardwareFrame( iCurrentHardwareFrame, bApproximate );
|
|
return iSourceFrame / float(samplerate());
|
|
}
|
|
|
|
|
|
bool RageSound::SetPositionFrames( int iFrames )
|
|
{
|
|
LockMut( m_Mutex );
|
|
|
|
if( m_pSource == NULL )
|
|
{
|
|
LOG->Warn( "RageSound::SetPositionFrames(%d): sound not loaded", iFrames );
|
|
return false;
|
|
}
|
|
|
|
int iRet = m_pSource->SetPosition( iFrames );
|
|
if( iRet == -1 )
|
|
{
|
|
m_sError = m_pSource->GetError();
|
|
LOG->Warn( "SetPositionFrames: seek %s failed: %s", GetLoadedFilePath().c_str(), m_sError.c_str() );
|
|
}
|
|
else if( iRet == 0 )
|
|
{
|
|
/* Seeked past EOF. */
|
|
LOG->Warn( "SetPositionFrames: %i samples is beyond EOF in %s",
|
|
iFrames, GetLoadedFilePath().c_str() );
|
|
}
|
|
else
|
|
{
|
|
m_iStoppedSourceFrame = iFrames;
|
|
}
|
|
|
|
return iRet == 1;
|
|
}
|
|
|
|
float RageSound::GetPlaybackRate() const
|
|
{
|
|
return m_Param.m_fSpeed;
|
|
}
|
|
|
|
RageTimer RageSound::GetStartTime() const
|
|
{
|
|
return m_Param.m_StartTime;
|
|
}
|
|
|
|
void RageSound::SetParams( const RageSoundParams &p )
|
|
{
|
|
m_Param = p;
|
|
ApplyParams();
|
|
}
|
|
|
|
void RageSound::ApplyParams()
|
|
{
|
|
if( m_pSource == NULL )
|
|
return;
|
|
|
|
m_pSource->SetProperty( "Pitch", m_Param.m_fPitch );
|
|
m_pSource->SetProperty( "Speed", m_Param.m_fSpeed );
|
|
m_pSource->SetProperty( "StartSecond", m_Param.m_StartSecond );
|
|
m_pSource->SetProperty( "LengthSeconds", m_Param.m_LengthSeconds );
|
|
m_pSource->SetProperty( "FadeInSeconds", m_Param.m_fFadeInSeconds );
|
|
m_pSource->SetProperty( "FadeSeconds", m_Param.m_fFadeOutSeconds );
|
|
|
|
float fVolume = m_Param.m_Volume * SOUNDMAN->GetMixVolume();
|
|
if( !m_Param.m_bIsCriticalSound )
|
|
fVolume *= m_Param.m_fAttractVolume;
|
|
m_pSource->SetProperty( "Volume", fVolume );
|
|
|
|
switch( GetStopMode() )
|
|
{
|
|
case RageSoundParams::M_LOOP:
|
|
m_pSource->SetProperty( "Loop", 1.0f );
|
|
break;
|
|
case RageSoundParams::M_STOP:
|
|
m_pSource->SetProperty( "Stop", 1.0f );
|
|
break;
|
|
case RageSoundParams::M_CONTINUE:
|
|
m_pSource->SetProperty( "Continue", 1.0f );
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool RageSound::SetProperty( const RString &sProperty, float fValue )
|
|
{
|
|
return m_pSource->SetProperty( sProperty, fValue );
|
|
}
|
|
|
|
RageSoundParams::StopMode_t RageSound::GetStopMode() const
|
|
{
|
|
if( m_Param.StopMode != RageSoundParams::M_AUTO )
|
|
return m_Param.StopMode;
|
|
|
|
if( m_sFilePath.find("loop") != string::npos )
|
|
return RageSoundParams::M_LOOP;
|
|
else
|
|
return RageSoundParams::M_STOP;
|
|
}
|
|
|
|
void RageSound::SetStopModeFromString( const RString &sStopMode )
|
|
{
|
|
if( sStopMode.find("stop") != string::npos )
|
|
{
|
|
m_Param.StopMode = RageSoundParams::M_STOP;
|
|
}
|
|
else if( sStopMode.find("loop") != string::npos )
|
|
{
|
|
m_Param.StopMode = RageSoundParams::M_LOOP;
|
|
}
|
|
else if( sStopMode.find("continue") != string::npos )
|
|
{
|
|
m_Param.StopMode = RageSoundParams::M_CONTINUE;
|
|
}
|
|
else if( sStopMode.find("auto") != string::npos )
|
|
{
|
|
m_Param.StopMode = RageSoundParams::M_AUTO;
|
|
}
|
|
else
|
|
{
|
|
// error
|
|
}
|
|
}
|
|
|
|
|
|
// lua start
|
|
#include "LuaBinding.h"
|
|
|
|
/** @brief Allow Lua to have access to the RageSound. */
|
|
class LunaRageSound: public Luna<RageSound>
|
|
{
|
|
public:
|
|
static int pitch( T* p, lua_State *L )
|
|
{
|
|
RageSoundParams params( p->GetParams() );
|
|
params.m_fPitch = FArg(1);
|
|
p->SetParams( params );
|
|
return 0;
|
|
}
|
|
|
|
static int speed( T* p, lua_State *L )
|
|
{
|
|
RageSoundParams params( p->GetParams() );
|
|
params.m_fSpeed = FArg(1);
|
|
p->SetParams( params );
|
|
return 0;
|
|
}
|
|
|
|
static int volume( T* p, lua_State *L )
|
|
{
|
|
RageSoundParams params( p->GetParams() );
|
|
params.m_Volume = FArg(1);
|
|
p->SetParams( params );
|
|
return 0;
|
|
}
|
|
|
|
static int SetProperty( T* p, lua_State *L )
|
|
{
|
|
LuaHelpers::Push( L, p->SetProperty(SArg(1), FArg(2)) );
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
static int SetStopMode( T* p, lua_State *L )
|
|
{
|
|
LuaHelpers::Push( L, p->SetStopModeFromString(SArg(1)) );
|
|
return 1;
|
|
}
|
|
*/
|
|
|
|
LunaRageSound()
|
|
{
|
|
ADD_METHOD( pitch );
|
|
ADD_METHOD( speed );
|
|
ADD_METHOD( volume );
|
|
ADD_METHOD( SetProperty );
|
|
//ADD_METHOD( SetStopMode );
|
|
}
|
|
};
|
|
|
|
LUA_REGISTER_CLASS( RageSound )
|
|
// lua end
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|