Files
itgmania212121/stepmania/src/RageSound.cpp
T

929 lines
26 KiB
C++
Raw Normal View History

2002-12-13 07:44:34 +00:00
/*
2005-01-20 01:49:04 +00:00
* Handle loading and decoding of sounds.
*
2002-12-13 07:44:34 +00:00
* 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.
*
2002-12-27 23:15:39 +00:00
* 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.
2002-12-13 07:44:34 +00:00
*/
2003-02-16 04:01:45 +00:00
#include "global.h"
2002-12-13 07:44:34 +00:00
#include "RageSound.h"
#include "RageSoundManager.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "RageException.h"
#include "PrefsManager.h"
2004-01-18 04:01:52 +00:00
#include "arch/ArchHooks/ArchHooks.h"
2004-10-25 01:53:19 +00:00
#include "RageSoundUtil.h"
2003-03-15 23:34:45 +00:00
#include "RageSoundReader_Preload.h"
2003-04-23 19:11:59 +00:00
#include "RageSoundReader_Resample.h"
2003-09-13 06:08:51 +00:00
#include "RageSoundReader_FileReader.h"
2002-12-13 07:44:34 +00:00
2005-12-23 05:31:27 +00:00
static const int channels = 2;
static const int framesize = 2 * channels; /* 16-bit */
2005-06-08 04:47:03 +00:00
#define samplerate() m_pSource->GetSampleRate()
2002-12-13 07:44:34 +00:00
/* The most data to buffer when streaming. */
2005-12-23 05:31:27 +00:00
static const int internal_buffer_size = 1024*1;
2002-12-13 07:44:34 +00:00
2004-06-13 20:03:18 +00:00
/* The amount of data to read at once. */
2005-12-23 05:31:27 +00:00
static const unsigned read_block_size = 1024;
2002-12-13 07:44:34 +00:00
2004-02-28 00:14:16 +00:00
RageSoundParams::RageSoundParams():
2005-09-23 00:12:32 +00:00
m_StartTime( RageZeroTimer )
2004-02-28 00:14:16 +00:00
{
m_StartSecond = 0;
m_LengthSeconds = -1;
2004-02-28 00:14:16 +00:00
m_FadeLength = 0;
2005-10-02 08:35:46 +00:00
m_Volume = 1.0f;
2004-02-28 00:14:16 +00:00
m_Balance = 0; // center
speed_input_samples = speed_output_samples = 1;
2005-09-23 00:12:32 +00:00
m_bAccurateSync = false;
StopMode = M_AUTO;
m_bIsCriticalSound = false;
2004-02-28 00:14:16 +00:00
}
RageSound::RageSound():
m_Mutex( "RageSound" )
2002-12-13 07:44:34 +00:00
{
2005-06-08 04:47:03 +00:00
ASSERT( SOUNDMAN );
2002-12-13 07:44:34 +00:00
2005-06-08 04:47:03 +00:00
m_pSource = NULL;
m_iDecodePosition = 0;
m_iStoppedPosition = 0;
m_iMaxDriverFrame = 0;
m_bPlaying = false;
m_DataBuffer.reserve( internal_buffer_size );
2004-02-28 00:14:16 +00:00
2005-06-08 04:47:03 +00:00
m_iID = SOUNDMAN->GetUniqueID();
/* Register ourself last, once everything is initialized. */
SOUNDMAN->RegisterSound( this );
2002-12-13 07:44:34 +00:00
}
RageSound::~RageSound()
{
Unload();
2004-03-18 01:31:34 +00:00
/* Unregister ourself. */
SOUNDMAN->UnregisterSound( this );
2002-12-13 07:44:34 +00:00
}
2005-06-08 04:47:03 +00:00
RageSound::RageSound( const RageSound &cpy ):
RageSoundBase( cpy ),
m_Mutex( "RageSound" )
2002-12-13 07:44:34 +00:00
{
ASSERT(SOUNDMAN);
2005-06-08 04:47:03 +00:00
m_pSource = NULL;
2002-12-13 07:44:34 +00:00
*this = cpy;
/* We have a different ID than our parent. */
2005-06-08 04:47:03 +00:00
m_iID = SOUNDMAN->GetUniqueID();
/* Register ourself. */
SOUNDMAN->RegisterSound( this );
}
RageSound &RageSound::operator=( const RageSound &cpy )
{
LockMut(cpy.m_Mutex);
2004-02-28 01:02:06 +00:00
m_Param = cpy.m_Param;
2005-06-08 04:47:03 +00:00
m_iDecodePosition = cpy.m_iDecodePosition;
m_iStoppedPosition = cpy.m_iStoppedPosition;
m_iMaxDriverFrame = 0;
m_bPlaying = false;
m_DataBuffer.reserve( internal_buffer_size );
delete m_pSource;
if( cpy.m_pSource )
m_pSource = cpy.m_pSource->Copy();
2004-10-23 17:43:49 +00:00
else
2005-06-08 04:47:03 +00:00
m_pSource = NULL;
2002-12-13 07:44:34 +00:00
2002-12-22 08:28:21 +00:00
m_sFilePath = cpy.m_sFilePath;
return *this;
2002-12-13 07:44:34 +00:00
}
void RageSound::Unload()
{
LockMut(m_Mutex);
2002-12-13 07:44:34 +00:00
if(IsPlaying())
2002-12-22 08:13:33 +00:00
StopPlaying();
2002-12-13 07:44:34 +00:00
2005-06-08 04:47:03 +00:00
delete m_pSource;
m_pSource = NULL;
2002-12-27 23:15:39 +00:00
2002-12-13 07:44:34 +00:00
m_sFilePath = "";
2005-06-08 04:47:03 +00:00
m_DataBuffer.clear();
2002-12-13 07:44:34 +00:00
}
2004-12-04 21:20:02 +00:00
bool RageSound::IsLoaded() const
2004-12-04 21:13:29 +00:00
{
2005-06-08 04:47:03 +00:00
return m_pSource != NULL;
2004-12-04 21:13:29 +00:00
}
2005-06-08 04:47:03 +00:00
void RageSound::Fail( CString sReason )
2002-12-27 23:15:39 +00:00
{
2005-06-08 04:47:03 +00:00
LOG->Warn( "Decoding %s failed: %s", GetLoadedFilePath().c_str(), sReason.c_str() );
2002-12-27 23:15:39 +00:00
2005-06-08 04:47:03 +00:00
m_sError = sReason;
2002-12-27 23:15:39 +00:00
}
2004-06-17 01:22:47 +00:00
class RageSoundReader_Silence: public SoundReader
{
public:
int GetLength() const { return 0; }
int GetLength_Fast() const { return 0; }
int SetPosition_Accurate(int ms) { return 0; }
int SetPosition_Fast(int ms) { return 0; }
int Read(char *buf, unsigned len) { return 0; }
SoundReader *Copy() const { return new RageSoundReader_Silence; }
2004-08-21 07:01:54 +00:00
int GetSampleRate() const { return 44100; }
bool IsStreamingFromDisk() const { return false; }
2004-06-17 01:22:47 +00:00
};
2005-03-16 04:10:10 +00:00
bool RageSound::Load( CString sSoundFilePath )
2002-12-13 07:44:34 +00:00
{
2005-03-16 04:10:10 +00:00
/* Automatically determine whether to precache */
/* TODO: Hook this up to a pref? */
return Load( sSoundFilePath, false );
}
2002-12-13 07:44:34 +00:00
2005-06-08 04:47:03 +00:00
bool RageSound::Load( CString sSoundFilePath, bool bPrecache )
2005-03-16 04:10:10 +00:00
{
2005-06-08 04:47:03 +00:00
LOG->Trace( "RageSound::LoadSound( '%s', %d )", sSoundFilePath.c_str(), bPrecache );
2003-01-21 02:44:35 +00:00
2005-11-23 18:51:00 +00:00
/* If this sound is already preloaded and held by SOUNDMAN, just make a copy
* of that. Since RageSoundReader_Preload is refcounted, this is cheap. */
2005-07-18 19:09:31 +00:00
SoundReader *pSound = SOUNDMAN->GetLoadedSound( sSoundFilePath );
if( pSound == NULL )
2004-06-17 01:22:47 +00:00
{
2005-07-18 19:09:31 +00:00
CString error;
pSound = SoundReader_FileReader::OpenFile( sSoundFilePath, error );
if( pSound == NULL )
{
LOG->Warn( "RageSound::Load: error opening sound \"%s\": %s",
sSoundFilePath.c_str(), error.c_str() );
pSound = new RageSoundReader_Silence;
}
}
else
{
/* The sound we were given from SOUNDMAN is already preloaded. */
bPrecache = false;
2003-04-23 19:11:59 +00:00
}
LoadSoundReader( pSound );
2004-11-14 04:29:33 +00:00
/* Try to precache. Do this after calling LoadSoundReader() to put the
2005-06-08 04:47:03 +00:00
* sound in this->m_pSource, so we preload after resampling. */
if( bPrecache )
2005-07-18 19:09:31 +00:00
{
if( RageSoundReader_Preload::PreloadSound(m_pSource) )
{
/* We've preloaded the sound. Pass it to SOUNDMAN, for reuse. */
SOUNDMAN->AddLoadedSound( sSoundFilePath, (RageSoundReader_Preload *) m_pSource );
}
}
2004-11-14 04:29:33 +00:00
m_sFilePath = sSoundFilePath;
2004-04-20 01:55:23 +00:00
m_Mutex.SetName( ssprintf("RageSound (%s)", Basename(sSoundFilePath).c_str() ) );
2002-12-27 23:15:39 +00:00
return true;
2002-12-13 07:44:34 +00:00
}
void RageSound::LoadSoundReader( SoundReader *pSound )
{
Unload();
2005-06-08 04:47:03 +00:00
m_iDecodePosition = m_iStoppedPosition = 0;
2005-06-08 04:47:03 +00:00
const int iNeededRate = SOUNDMAN->GetDriverSampleRate( pSound->GetSampleRate() );
if( iNeededRate != pSound->GetSampleRate() )
{
RageSoundReader_Resample *Resample = RageSoundReader_Resample::MakeResampler();
Resample->Open( pSound );
2005-06-08 04:47:03 +00:00
Resample->SetSampleRate( iNeededRate );
pSound = Resample;
}
2005-06-08 04:47:03 +00:00
m_pSource = pSound;
}
2002-12-27 23:15:39 +00:00
/* Return the number of bytes available in the input buffer. */
int RageSound::Bytes_Available() const
{
2005-06-08 04:47:03 +00:00
return m_DataBuffer.num_readable();
2002-12-13 07:44:34 +00:00
}
2003-01-02 06:52:39 +00:00
2005-06-08 04:47:03 +00:00
void RageSound::RateChange( char *pBuffer, int &iCount, int iInputSpeed, int iOutputSpeed, int iChannels )
2003-01-02 06:52:39 +00:00
{
2005-06-08 04:47:03 +00:00
if( iInputSpeed == iOutputSpeed )
2003-01-02 06:52:39 +00:00
return;
2005-06-08 04:47:03 +00:00
/* Rate change. Change iInputSpeed into iOutputSpeed per-channel. */
static char *pInbufTmp = NULL;
static int iInbufTmpSize = 0;
if( iCount > iInbufTmpSize )
2003-01-02 06:52:39 +00:00
{
2005-06-08 04:47:03 +00:00
iInbufTmpSize = iCount;
delete [] pInbufTmp;
pInbufTmp = new char[iCount];
2003-01-02 06:52:39 +00:00
}
2005-06-08 04:47:03 +00:00
memcpy( pInbufTmp, pBuffer, iCount );
2003-01-02 06:52:39 +00:00
2005-06-08 04:47:03 +00:00
for( int c = 0; c < iChannels; ++c )
2003-01-02 06:52:39 +00:00
{
2005-06-08 04:47:03 +00:00
const int16_t *pInput = (const int16_t *) pInbufTmp;
int16_t *pOutput = (int16_t *) pBuffer;
pInput += c;
pOutput += c;
for( unsigned n = 0; n < iCount/(iChannels * sizeof(int16_t)); n += iInputSpeed )
2003-01-02 06:52:39 +00:00
{
/* Input 4 samples, output 5; 25% slowdown with no
2003-03-15 21:02:45 +00:00
* rounding error. */
2003-01-02 06:52:39 +00:00
2004-05-11 21:08:13 +00:00
int16_t samps[20]; // max 2x rate
2005-06-08 04:47:03 +00:00
ASSERT( size_t(iInputSpeed) <= sizeof(samps)/sizeof(*samps) );
2003-01-02 06:52:39 +00:00
int s;
2005-06-08 04:47:03 +00:00
for( s = 0; s < iInputSpeed; ++s )
{
samps[s] = *pInput;
pInput += iChannels;
2003-01-02 06:52:39 +00:00
}
2005-06-08 04:47:03 +00:00
float fPosition = 0;
float fIncrement = float(iInputSpeed) / iOutputSpeed;
2003-01-02 06:52:39 +00:00
2005-06-08 04:47:03 +00:00
for( s = 0; s < iOutputSpeed; ++s )
{
float frac = fPosition - floorf( fPosition );
int iPosition = int(fPosition);
int iValue = int(samps[iPosition] * (1-frac));
if( s+1 < iOutputSpeed )
iValue += int(samps[iPosition+1] * frac);
*pOutput = int16_t(iValue);
fPosition += fIncrement;
pOutput += iChannels;
2003-01-02 06:52:39 +00:00
}
}
}
2005-06-08 04:47:03 +00:00
iCount = (iCount * iOutputSpeed) / iInputSpeed;
2003-01-02 06:52:39 +00:00
}
2005-11-23 18:52:49 +00:00
/* Fill the buffer by about iFrames worth of data. (We might go a little
2005-11-23 18:53:31 +00:00
* over, and we won't overflow our buffer.) Return true if data was read;
* false on EOF. Convert mono input to stereo. */
2005-11-23 18:52:49 +00:00
bool RageSound::FillBuf( int iFrames )
2002-12-13 07:44:34 +00:00
{
2005-06-08 04:47:03 +00:00
ASSERT( m_pSource );
2002-12-13 07:44:34 +00:00
2005-06-08 04:47:03 +00:00
bool bGotSomething = false;
2002-12-27 23:15:39 +00:00
2005-06-08 04:47:03 +00:00
while( iFrames > 0 )
2002-12-13 07:44:34 +00:00
{
2005-06-08 04:47:03 +00:00
if( read_block_size > m_DataBuffer.num_writable() )
2002-12-13 07:44:34 +00:00
break; /* full */
2003-01-02 06:52:39 +00:00
char inbuf[10240];
2005-11-23 18:51:00 +00:00
unsigned iReadSize = read_block_size;
2002-12-27 23:15:39 +00:00
2004-02-28 01:02:06 +00:00
if( m_Param.speed_input_samples != m_Param.speed_output_samples )
2003-01-02 06:52:39 +00:00
{
/* Read enough data to produce read_block_size. */
2005-11-23 18:51:00 +00:00
iReadSize = iReadSize * m_Param.speed_input_samples / m_Param.speed_output_samples;
2003-01-02 06:52:39 +00:00
/* Read in blocks that are a multiple of a sample, the number of
* channels and the number of input samples. */
2005-11-23 18:58:25 +00:00
int iBlockSize = sizeof(int16_t) * channels * m_Param.speed_input_samples;
iReadSize = (iReadSize / iBlockSize) * iBlockSize;
2005-11-23 18:51:00 +00:00
ASSERT(iReadSize < sizeof(inbuf));
2002-12-27 23:15:39 +00:00
}
2004-07-24 02:01:12 +00:00
/* channels == 2; we want stereo. If the input data is mono, read half as many
* samples. */
2005-06-08 04:47:03 +00:00
if( m_pSource->GetNumChannels() == 1 )
2005-11-23 18:51:00 +00:00
iReadSize /= 2;
2004-07-24 02:01:12 +00:00
2005-11-23 18:51:00 +00:00
ASSERT( iReadSize < sizeof(inbuf) );
2003-01-02 06:52:39 +00:00
2005-11-23 18:51:00 +00:00
int iCount = m_pSource->Read( inbuf, iReadSize );
2005-06-08 04:47:03 +00:00
if( iCount == 0 )
return bGotSomething; /* EOF */
2002-12-14 00:22:18 +00:00
2005-06-08 04:47:03 +00:00
if( iCount == -1 )
2002-12-13 07:44:34 +00:00
{
2005-06-08 04:47:03 +00:00
Fail( m_pSource->GetError() );
2002-12-27 23:15:39 +00:00
2003-08-12 18:29:35 +00:00
/* Pretend we got EOF. */
2005-11-23 18:52:49 +00:00
return false;
2002-12-13 07:44:34 +00:00
}
2005-06-08 04:47:03 +00:00
if( m_pSource->GetNumChannels() == 1 )
2004-11-14 20:21:04 +00:00
{
2005-06-08 04:47:03 +00:00
RageSoundUtil::ConvertMonoToStereoInPlace( (int16_t *) inbuf, iCount / sizeof(int16_t) );
iCount *= 2;
2004-11-14 20:21:04 +00:00
}
2005-06-08 04:47:03 +00:00
RateChange( inbuf, iCount, m_Param.speed_input_samples, m_Param.speed_output_samples, channels );
2003-08-12 18:29:35 +00:00
2002-12-27 23:15:39 +00:00
/* Add the data to the buffer. */
2005-06-08 04:47:03 +00:00
m_DataBuffer.write( (const char *) inbuf, iCount );
iFrames -= iCount / framesize;
bGotSomething = true;
2002-12-13 07:44:34 +00:00
}
2005-06-08 04:47:03 +00:00
return bGotSomething;
2002-12-13 07:44:34 +00:00
}
2002-12-27 23:15:39 +00:00
/* Get a block of data from the input. If buffer is NULL, just return the amount
* that would be read. */
2005-06-08 04:47:03 +00:00
int RageSound::GetData( char *pBuffer, int iFrames )
2002-12-27 23:15:39 +00:00
{
2004-02-28 01:02:06 +00:00
if( m_Param.m_LengthSeconds != -1 )
2002-12-27 23:15:39 +00:00
{
/* We have a length; only read up to the end. */
2005-11-23 18:51:00 +00:00
const float fLastSecond = m_Param.m_StartSecond + m_Param.m_LengthSeconds;
int iFramesToRead = int(fLastSecond*samplerate()) - m_iDecodePosition;
2002-12-27 23:15:39 +00:00
/* If it's negative, we're past the end, so cap it at 0. Don't read
* more than size. */
2005-11-23 18:51:00 +00:00
iFrames = clamp( iFramesToRead, 0, iFrames );
2002-12-27 23:15:39 +00:00
}
2005-06-08 04:47:03 +00:00
int iGot;
if( m_iDecodePosition < 0 )
2004-03-26 03:26:20 +00:00
{
2002-12-27 23:15:39 +00:00
/* We havn't *really* started playing yet, so just feed silence. How
* many more bytes of silence do we need? */
2005-06-08 04:47:03 +00:00
iGot = -m_iDecodePosition;
iGot = min( iGot, iFrames );
if( pBuffer )
memset( pBuffer, 0, iGot*framesize );
} else {
2002-12-27 23:15:39 +00:00
/* Feed data out of our streaming buffer. */
2005-06-08 04:47:03 +00:00
ASSERT( m_pSource );
iGot = min( int(m_DataBuffer.num_readable()/framesize), iFrames );
if( pBuffer )
m_DataBuffer.read( pBuffer, iGot*framesize );
2002-12-27 23:15:39 +00:00
}
2005-06-08 04:47:03 +00:00
return iGot;
2002-12-27 23:15:39 +00:00
}
/*
* Retrieve audio data, for mixing. At the time of this call, the frameno at which the
2004-03-18 03:30:36 +00:00
* sound will be played doesn't have to be known. Once committed, and the frameno
* is known, call CommitPCMData.
2004-03-18 03:30:36 +00:00
*
* 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.
2004-03-18 03:30:36 +00:00
*
* If no data is returned (we're at the end of the stream), return false.
2004-03-18 03:30:36 +00:00
*/
bool RageSound::GetDataToPlay( int16_t *pBuffer, int iFrames, int &iSoundFrame, int &iFramesStored )
2002-12-13 07:44:34 +00:00
{
2005-06-08 04:47:03 +00:00
int iNumRewindsThisCall = 0;
2003-01-12 04:19:45 +00:00
2005-06-08 04:47:03 +00:00
/* We only update m_iDecodePosition; only take a shared lock, so we don't block the main thread. */
// LockMut(m_Mutex);
2002-12-13 07:44:34 +00:00
2005-06-08 04:47:03 +00:00
ASSERT_M( m_bPlaying, ssprintf("%p", this) );
2004-01-19 20:37:14 +00:00
2005-06-08 04:47:03 +00:00
iFramesStored = 0;
iSoundFrame = m_iDecodePosition;
2002-12-13 07:44:34 +00:00
2004-03-18 03:30:36 +00:00
while( 1 )
2002-12-13 07:44:34 +00:00
{
2004-03-18 03:30:36 +00:00
/* If we don't have any data left buffered, fill the buffer by
* up to as much as we need. */
if( !Bytes_Available() )
FillBuf( iFrames );
2002-12-13 07:44:34 +00:00
2004-03-18 03:30:36 +00:00
/* Get a block of data. */
int iGotFrames = GetData( (char *) pBuffer, iFrames );
/* If we didn't get any data, see if we need to pad the end of the file with
* silence for m_LengthSeconds. */
2005-06-08 04:47:03 +00:00
if( !iGotFrames && m_Param.m_LengthSeconds != -1 )
{
2005-11-23 18:58:25 +00:00
const float fLastSecond = m_Param.m_StartSecond + m_Param.m_LengthSeconds;
int iLastFrame = int(fLastSecond*samplerate());
int iFramesOfSilence = iLastFrame - m_iDecodePosition;
iFramesOfSilence = clamp( iFramesOfSilence, 0, iFrames );
2005-11-23 18:58:25 +00:00
if( iFramesOfSilence > 0 )
{
2005-11-23 18:58:25 +00:00
memset( pBuffer, 0, iFramesOfSilence * framesize );
iGotFrames = iFramesOfSilence;
}
}
2005-06-08 04:47:03 +00:00
if( !iGotFrames )
2002-12-18 06:44:56 +00:00
{
2004-03-26 03:38:27 +00:00
/* EOF. */
switch( GetStopMode() )
2002-12-18 06:44:56 +00:00
{
2004-03-26 03:38:27 +00:00
case RageSoundParams::M_STOP:
/* Not looping. Normally, we'll just stop here. */
return false;
case RageSoundParams::M_LOOP:
/* Rewind and restart. */
2005-06-08 04:47:03 +00:00
iNumRewindsThisCall++;
if( iNumRewindsThisCall > 3 )
2003-01-12 04:19:45 +00:00
{
/* We're rewinding a bunch of times in one call. This probably means
* that the length is too short. It might also mean that the start
* position is very close to the end of the file, so we're looping
* over the remainder. If we keep doing this, we'll chew CPU rewinding,
* so stop. */
LOG->Warn( "Sound %s is busy looping. Sound stopped (start = %f, length = %f)",
2004-02-28 01:02:06 +00:00
GetLoadedFilePath().c_str(), m_Param.m_StartSecond, m_Param.m_LengthSeconds );
2003-01-12 04:19:45 +00:00
2004-03-18 03:30:36 +00:00
return false;
2003-01-12 04:19:45 +00:00
}
/* Rewind and start over. XXX: this will take an exclusive lock */
2004-02-28 01:02:06 +00:00
SetPositionSeconds( m_Param.m_StartSecond );
2002-12-27 23:15:39 +00:00
/* Make sure we can get some data. If we can't, then we'll have
* nothing to send and we'll just end up coming back here. */
2004-03-18 03:30:36 +00:00
if( !Bytes_Available() )
FillBuf( iFrames );
if( GetData(NULL, iFrames) == 0 )
2002-12-27 23:15:39 +00:00
{
LOG->Warn( "Can't loop data in %s; no data available at start point %f",
2004-02-28 01:02:06 +00:00
GetLoadedFilePath().c_str(), m_Param.m_StartSecond );
2002-12-27 23:15:39 +00:00
/* Stop here. */
2004-03-18 03:30:36 +00:00
return false;
2002-12-27 23:15:39 +00:00
}
2002-12-18 06:44:56 +00:00
continue;
2002-12-27 23:15:39 +00:00
2004-03-26 03:38:27 +00:00
case RageSoundParams::M_CONTINUE:
/* Keep playing silence. */
memset( pBuffer, 0, iFrames*framesize );
iGotFrames = iFrames;
2004-03-26 03:38:27 +00:00
break;
2002-12-13 07:44:34 +00:00
2004-03-26 03:38:27 +00:00
default:
ASSERT(0);
}
2002-12-13 07:44:34 +00:00
}
2005-06-08 04:47:03 +00:00
/* This block goes from m_iDecodePosition to m_iDecodePosition+iGotFrames. */
2002-12-27 23:15:39 +00:00
/* We want to fade when there's m_FadeLength seconds left, but if
2004-02-28 00:14:16 +00:00
* m_LengthFrames is -1, we don't know the length we're playing.
* (m_LengthFrames is the length to play, not the length of the
2002-12-20 02:00:43 +00:00
* source.) If we don't know the length, don't fade. */
if( m_Param.m_FadeLength != 0 && m_Param.m_LengthSeconds != -1 )
2004-02-28 00:14:16 +00:00
{
const float fFinishFadingOutAt = m_Param.m_StartSecond + m_Param.m_LengthSeconds;
const float fStartFadingOutAt = fFinishFadingOutAt - m_Param.m_FadeLength;
2005-06-08 04:47:03 +00:00
const float fStartSecond = float(m_iDecodePosition) / samplerate();
const float fEndSecond = float(m_iDecodePosition+iGotFrames) / samplerate();
const float fStartVolume = SCALE( fStartSecond, fStartFadingOutAt, fFinishFadingOutAt, 1.0f, 0.0f );
const float fEndVolume = SCALE( fEndSecond, fStartFadingOutAt, fFinishFadingOutAt, 1.0f, 0.0f );
2005-06-08 04:47:03 +00:00
RageSoundUtil::Fade( pBuffer, iGotFrames, fStartVolume, fEndVolume );
2002-12-13 07:44:34 +00:00
}
2005-06-08 04:47:03 +00:00
RageSoundUtil::Pan( pBuffer, iGotFrames, m_Param.m_Balance );
2004-02-27 23:26:55 +00:00
2005-06-08 04:47:03 +00:00
iSoundFrame = m_iDecodePosition;
2004-03-18 03:30:36 +00:00
2005-06-08 04:47:03 +00:00
iFramesStored = iGotFrames;
m_iDecodePosition += iGotFrames;
2004-03-18 03:30:36 +00:00
return true;
}
}
/* Indicate that a block of audio data has been written to the device. */
2005-06-08 04:47:03 +00:00
void RageSound::CommitPlayingPosition( int64_t frameno, int pos, int iGotFrames )
2004-03-18 03:30:36 +00:00
{
2004-04-16 22:28:01 +00:00
m_Mutex.Lock();
2005-06-08 04:47:03 +00:00
m_PositionMapping.Insert( frameno, pos, iGotFrames );
2004-04-16 22:28:01 +00:00
m_Mutex.Unlock();
2004-03-18 03:30:36 +00:00
}
/* Called by the mixer: return a block of sound data.
* Be careful; this is called in a separate thread. */
2005-06-08 04:47:03 +00:00
int RageSound::GetPCM( char *pBuffer, int iSize, int64_t iFrameno )
2004-03-18 03:30:36 +00:00
{
2005-06-08 04:47:03 +00:00
ASSERT( m_bPlaying );
2004-03-18 03:30:36 +00:00
/* Now actually put data from the correct buffer into the output. */
2005-06-08 04:47:03 +00:00
int iBytesStored = 0;
while( iBytesStored < iSize )
2004-03-18 03:30:36 +00:00
{
2005-06-08 04:47:03 +00:00
int iPosition, iGotFrames;
bool bEof = !GetDataToPlay( (int16_t *)(pBuffer+iBytesStored), (iSize-iBytesStored)/framesize, iPosition, iGotFrames );
2004-03-18 03:30:36 +00:00
/* Save this frameno/position map. */
2005-06-08 04:47:03 +00:00
SOUNDMAN->CommitPlayingPosition( GetID(), iFrameno, iPosition, iGotFrames );
2004-03-18 03:30:36 +00:00
2005-06-08 04:47:03 +00:00
iBytesStored += iGotFrames * framesize;
iFrameno += iGotFrames;
2004-03-18 03:30:36 +00:00
2005-06-08 04:47:03 +00:00
if( bEof )
2004-03-18 03:30:36 +00:00
break;
2002-12-13 07:44:34 +00:00
}
2005-06-08 04:47:03 +00:00
return iBytesStored;
2002-12-13 07:44:34 +00:00
}
2005-11-23 18:31:29 +00:00
/* Start playing from the current position. */
2002-12-22 08:13:33 +00:00
void RageSound::StartPlaying()
2002-12-13 07:44:34 +00:00
{
2005-06-08 04:47:03 +00:00
ASSERT( !m_bPlaying );
2002-12-13 07:44:34 +00:00
2005-09-23 00:12:32 +00:00
/* If m_StartTime is in the past, then we probably set a start time but took too
2004-01-12 01:02:18 +00:00
* long loading. We don't want that; log it, since it can be unobvious. */
2005-09-23 00:12:32 +00:00
if( !m_Param.m_StartTime.IsZero() && m_Param.m_StartTime.Ago() > 0 )
2004-01-12 01:02:18 +00:00
LOG->Trace("Sound \"%s\" has a start time %f seconds in the past",
2005-09-23 00:12:32 +00:00
GetLoadedFilePath().c_str(), m_Param.m_StartTime.Ago() );
2004-01-12 01:02:18 +00:00
2002-12-27 23:15:39 +00:00
/* Tell the sound manager to start mixing us. */
// LOG->Trace("set playing true for %p (StartPlaying) (%s)", this, this->GetLoadedFilePath().c_str());
2005-06-08 04:47:03 +00:00
m_bPlaying = true;
if( !m_Param.m_bIsCriticalSound && SOUNDMAN->GetPlayOnlyCriticalSounds() )
m_Param.m_Volume = 0;
SOUNDMAN->StartMixing( this );
// LOG->Trace("StartPlaying %p finished (%s)", this, this->GetLoadedFilePath().c_str());
2002-12-13 07:44:34 +00:00
}
2002-12-22 08:13:33 +00:00
void RageSound::StopPlaying()
2002-12-13 07:44:34 +00:00
{
2005-06-08 04:47:03 +00:00
if( !m_bPlaying )
2003-02-10 21:43:50 +00:00
return;
2005-06-08 04:47:03 +00:00
m_iStoppedPosition = (int) GetPositionSecondsInternal();
2003-01-25 09:01:42 +00:00
/* Tell the sound driver to stop mixing this sound. */
2002-12-13 07:44:34 +00:00
SOUNDMAN->StopMixing(this);
2004-01-15 01:17:47 +00:00
/* Lock the mutex after calling UnregisterPlayingSound. We must not make driver
* calls with our mutex locked (driver mutex < sound mutex). Nobody else will
* see our sound as not playing until we set playing = false. */
m_Mutex.Lock();
// LOG->Trace("set playing false for %p (StopPlaying) (%s)", this, this->GetLoadedFilePath().c_str());
2005-06-08 04:47:03 +00:00
m_bPlaying = false;
2005-06-08 04:47:03 +00:00
m_iMaxDriverFrame = 0;
m_PositionMapping.Clear();
/* We may still have positions queued up in RageSoundManager. We need to make sure
* that we don't accept those; otherwise, if we start playing again quickly, they'll
* confuse GetPositionSeconds(). Do this by changing our ID. */
2004-11-15 19:11:57 +00:00
SOUNDMAN->UnregisterSound( this );
2005-06-08 04:47:03 +00:00
m_iID = SOUNDMAN->GetUniqueID();
2004-11-15 19:11:57 +00:00
SOUNDMAN->RegisterSound( this );
// LOG->Trace("StopPlaying %p finished (%s)", this, this->GetLoadedFilePath().c_str());
m_Mutex.Unlock();
2002-12-13 07:44:34 +00:00
}
/* This is similar to StopPlaying, except it's called by sound drivers when we're done
* playing, rather than by users to as us to stop. (The only difference is that this
* doesn't call SOUNDMAN->StopMixing; there's no reason to tell the sound driver to
* stop mixing, since they're the one telling us we're done.)
*
* This is only called from the main thread. */
void RageSound::SoundIsFinishedPlaying()
{
2005-06-08 04:47:03 +00:00
if( !m_bPlaying )
return;
m_Mutex.Lock();
2005-06-08 04:47:03 +00:00
m_iStoppedPosition = (int) GetPositionSecondsInternal();
// LOG->Trace("set playing false for %p (SoundIsFinishedPlaying) (%s)", this, this->GetLoadedFilePath().c_str());
2005-06-08 04:47:03 +00:00
m_bPlaying = false;
2005-06-08 04:47:03 +00:00
m_PositionMapping.Clear();
// LOG->Trace("SoundIsFinishedPlaying %p finished (%s)", this, this->GetLoadedFilePath().c_str());
m_Mutex.Unlock();
}
2005-06-08 04:47:03 +00:00
RageSound *RageSound::Play( const RageSoundParams *pParams )
2002-12-22 08:13:33 +00:00
{
2005-06-08 04:47:03 +00:00
if( m_pSource == NULL )
2005-06-08 04:16:03 +00:00
{
LOG->Warn( "RageSound::Play: sound not loaded" );
return NULL;
}
2005-06-08 04:47:03 +00:00
return SOUNDMAN->PlaySound( *this, pParams );
2002-12-22 08:13:33 +00:00
}
void RageSound::Stop()
{
StopPlaying();
2002-12-22 08:13:33 +00:00
}
bool RageSound::Pause( bool bPause )
{
2005-06-08 04:47:03 +00:00
if( m_pSource == NULL )
2005-06-08 04:16:03 +00:00
{
LOG->Warn( "RageSound::Pause: sound not loaded" );
return false;
}
return SOUNDMAN->Pause( this, bPause );
}
2002-12-22 08:13:33 +00:00
2002-12-13 07:44:34 +00:00
float RageSound::GetLengthSeconds()
{
2005-06-08 04:47:03 +00:00
if( m_pSource == NULL )
2005-06-08 04:16:03 +00:00
{
LOG->Warn( "RageSound::GetLengthSeconds: sound not loaded" );
return -1;
}
2005-06-08 04:47:03 +00:00
int iLength = m_pSource->GetLength();
2002-12-13 07:44:34 +00:00
2005-06-08 04:47:03 +00:00
if( iLength < 0 )
{
2005-11-23 18:51:00 +00:00
LOG->Warn( "GetLengthSeconds failed on %s: %s", GetLoadedFilePath().c_str(), m_pSource->GetError().c_str() );
return -1;
2002-12-13 07:44:34 +00:00
}
2005-06-08 04:47:03 +00:00
return iLength / 1000.f; /* ms -> secs */
2002-12-13 07:44:34 +00:00
}
2004-02-20 04:16:12 +00:00
/* Get the position in frames. */
2005-06-08 04:47:03 +00:00
int64_t RageSound::GetPositionSecondsInternal( bool *bApproximate ) const
2004-01-18 02:07:58 +00:00
{
2005-06-08 04:47:03 +00:00
LockMut( m_Mutex );
2004-01-18 02:07:58 +00:00
2005-06-08 04:47:03 +00:00
if( bApproximate )
*bApproximate = false;
2004-01-18 02:07:58 +00:00
/* If we're not playing, just report the static position. */
if( !IsPlaying() )
2005-06-08 04:47:03 +00:00
return m_iStoppedPosition;
2004-01-18 02:07:58 +00:00
/* If we don't yet have any position data, GetPCM hasn't yet been called at all,
* so guess what we think the real time is. */
2005-06-08 04:47:03 +00:00
if( m_PositionMapping.IsEmpty() )
2004-01-18 02:07:58 +00:00
{
2005-06-08 04:47:03 +00:00
LOG->Trace( "no data yet; %i", m_iStoppedPosition );
if( bApproximate )
*bApproximate = true;
return m_iStoppedPosition;
2004-01-18 02:07:58 +00:00
}
/* Get our current hardware position. */
2005-06-08 04:47:03 +00:00
int64_t iCurrentFrame = SOUNDMAN->GetPosition(this);
2004-01-18 02:07:58 +00:00
/* It's sometimes possible for the hardware position to move backwards, usually
* on underrun. We can try to prevent this in each driver, but it's an obscure
* error, so let's clamp the result here instead. Be sure to reset this on stop,
* since the position may reset. */
2005-06-08 04:47:03 +00:00
if( iCurrentFrame < m_iMaxDriverFrame )
2004-08-24 23:51:43 +00:00
{
/* Clamp the output to one per second, so one underruns don't cascade due to
* output spam. */
static RageTimer last(RageZeroTimer);
if( last.IsZero() || last.Ago() > 1.0f )
{
LOG->Trace( "Sound %s: driver returned a lesser position (%i < %i)",
2005-06-08 04:47:03 +00:00
this->GetLoadedFilePath().c_str(), (int) iCurrentFrame, (int) m_iMaxDriverFrame );
2004-08-24 23:51:43 +00:00
last.Touch();
}
}
2005-06-08 04:47:03 +00:00
m_iMaxDriverFrame = iCurrentFrame = max( iCurrentFrame, m_iMaxDriverFrame );
2005-06-08 04:47:03 +00:00
return m_PositionMapping.Search( iCurrentFrame, bApproximate );
2004-01-18 02:07:58 +00:00
}
2004-01-18 04:04:37 +00:00
/*
* 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.
*/
2005-06-08 04:47:03 +00:00
float RageSound::GetPositionSeconds( bool *bApproximate, RageTimer *pTimestamp ) const
{
2005-06-08 04:47:03 +00:00
LockMut( m_Mutex );
2004-01-18 04:01:52 +00:00
2005-06-08 04:47:03 +00:00
if( pTimestamp )
2004-01-18 04:01:52 +00:00
{
HOOKS->EnterTimeCriticalSection();
2005-06-08 04:47:03 +00:00
pTimestamp->Touch();
2004-01-18 04:01:52 +00:00
}
2005-06-08 04:47:03 +00:00
const float fPosition = GetPositionSecondsInternal( bApproximate ) / float(samplerate());
2004-01-18 04:01:52 +00:00
2005-06-08 04:47:03 +00:00
if( pTimestamp )
2004-01-18 04:01:52 +00:00
HOOKS->ExitTimeCriticalSection();
2005-06-08 04:47:03 +00:00
return GetPlaybackRate() * fPosition;
}
2002-12-27 23:15:39 +00:00
bool RageSound::SetPositionSeconds( float fSeconds )
2002-12-13 07:44:34 +00:00
{
2005-06-08 04:47:03 +00:00
if( m_pSource == NULL )
2005-06-08 04:16:03 +00:00
{
LOG->Warn( "RageSound::SetPositionSeconds(%f): sound not loaded", fSeconds );
return false;
}
return SetPositionFrames( int(fSeconds * samplerate()) );
2002-12-22 08:13:33 +00:00
}
2003-04-24 23:51:05 +00:00
/* This is always the desired sample rate of the current driver. */
int RageSound::GetSampleRate() const
{
2005-06-08 04:47:03 +00:00
if( m_pSource == NULL )
2005-06-08 04:16:03 +00:00
{
LOG->Warn( "RageSound::GetSampleRate(): sound not loaded" );
return 44100;
}
2005-06-08 04:47:03 +00:00
return m_pSource->GetSampleRate();
2003-04-24 23:51:05 +00:00
}
2004-08-21 07:01:54 +00:00
bool RageSound::IsStreamingFromDisk() const
{
2005-06-08 04:47:03 +00:00
if( m_pSource == NULL )
2005-06-08 04:16:03 +00:00
{
LOG->Warn( "RageSound::IsStreamingFromDisk: sound not loaded" );
return false;
}
2005-06-08 04:47:03 +00:00
return m_pSource->IsStreamingFromDisk();
2004-08-21 07:01:54 +00:00
}
2003-04-24 23:51:05 +00:00
2005-06-08 04:47:03 +00:00
bool RageSound::SetPositionFrames( int iFrames )
2002-12-22 08:13:33 +00:00
{
2005-06-08 04:47:03 +00:00
LockMut( m_Mutex );
2002-12-27 23:15:39 +00:00
2005-06-08 04:47:03 +00:00
if( m_pSource == NULL )
2005-06-08 04:16:03 +00:00
{
2005-06-19 00:21:48 +00:00
LOG->Warn( "RageSound::SetPositionFrames(%d): sound not loaded", iFrames );
2005-06-08 04:16:03 +00:00
return false;
}
2003-03-15 21:02:45 +00:00
{
2005-06-08 04:47:03 +00:00
/* "m_iDecodePosition" records the number of frames we've output to the
2003-03-15 21:02:45 +00:00
* speaker. If the rate isn't 1.0, this will be different from the
* position in the sound data itself. For example, if we're playing
2004-02-28 00:14:16 +00:00
* at 0.5x, and we're seeking to the 10th frame, we would have actually
* played 20 frames, and it's the number of real speaker frames that
2005-06-08 04:47:03 +00:00
* "m_iDecodePosition" represents. */
const int iScaledFrames = int( iFrames / GetPlaybackRate() );
2003-03-15 21:02:45 +00:00
/* If we're already there, don't do anything. */
2005-06-08 04:47:03 +00:00
if( m_iDecodePosition == iScaledFrames )
2003-03-15 21:02:45 +00:00
return true;
2005-06-08 04:47:03 +00:00
m_iStoppedPosition = m_iDecodePosition = iScaledFrames;
2003-03-15 21:02:45 +00:00
}
/* The position we're going to seek the input stream to. We have
* to do this in floating point to avoid overflow. */
2005-06-08 04:47:03 +00:00
int ms = int( float(iFrames) * 1000.f / samplerate() );
2005-10-23 07:04:55 +00:00
ms = max( ms, 0 );
2002-12-22 08:13:33 +00:00
2005-06-08 04:47:03 +00:00
m_DataBuffer.clear();
2002-12-27 23:15:39 +00:00
2005-10-23 07:04:55 +00:00
int iRet;
2005-09-23 00:12:32 +00:00
if( m_Param.m_bAccurateSync )
2005-10-23 07:04:55 +00:00
iRet = m_pSource->SetPosition_Accurate(ms);
2002-12-27 23:15:39 +00:00
else
2005-10-23 07:04:55 +00:00
iRet = m_pSource->SetPosition_Fast(ms);
2002-12-27 23:15:39 +00:00
2005-10-23 07:04:55 +00:00
if( iRet == -1 )
2002-12-27 23:15:39 +00:00
{
2005-06-08 04:47:03 +00:00
Fail( m_pSource->GetError() );
2002-12-27 23:15:39 +00:00
return false; /* failed */
}
2005-10-23 07:04:55 +00:00
if( iRet == 0 && ms != 0 )
2002-12-27 23:15:39 +00:00
{
/* We were told to seek somewhere, and we got 0 instead, which means
* we passed EOF. This could be a truncated file or invalid data. */
2005-10-23 07:04:55 +00:00
LOG->Warn( "SetPositionFrames: %i ms is beyond EOF in %s",
ms, GetLoadedFilePath().c_str() );
2002-12-27 23:15:39 +00:00
return false; /* failed */
2002-12-27 23:15:39 +00:00
}
return true;
2002-12-13 07:44:34 +00:00
}
2005-06-08 04:47:03 +00:00
void RageSoundParams::SetPlaybackRate( float fSpeed )
2004-02-28 00:14:16 +00:00
{
2005-06-08 04:47:03 +00:00
if( fSpeed == 1.00f )
2004-02-28 00:14:16 +00:00
{
2005-06-08 04:47:03 +00:00
speed_input_samples = 1;
speed_output_samples = 1;
}
else
{
2003-01-02 06:52:39 +00:00
/* Approximate it to the nearest tenth. */
2005-06-08 04:47:03 +00:00
speed_input_samples = int( roundf(fSpeed * 10) );
speed_output_samples = 10;
2003-01-02 06:52:39 +00:00
}
2002-12-13 07:44:34 +00:00
}
2005-10-02 08:35:46 +00:00
float RageSound::GetAbsoluteVolume() const
{
float f = m_Param.m_Volume;
f *= SOUNDMAN->GetMixVolume();
2005-10-02 08:35:46 +00:00
return f;
}
2004-02-28 01:02:06 +00:00
float RageSound::GetPlaybackRate() const
2004-02-28 00:14:16 +00:00
{
2004-02-28 01:02:06 +00:00
return float(m_Param.speed_input_samples) / m_Param.speed_output_samples;
2004-02-28 00:14:16 +00:00
}
RageTimer RageSound::GetStartTime() const
2004-02-28 00:14:16 +00:00
{
2005-09-23 00:12:32 +00:00
return m_Param.m_StartTime;
2004-02-28 00:14:16 +00:00
}
void RageSound::SetParams( const RageSoundParams &p )
2004-02-28 00:14:16 +00:00
{
m_Param = p;
2004-02-28 00:14:16 +00:00
}
RageSoundParams::StopMode_t RageSound::GetStopMode() const
2004-02-28 00:14:16 +00:00
{
if( m_Param.StopMode != RageSoundParams::M_AUTO )
return m_Param.StopMode;
2005-12-21 08:27:00 +00:00
if( m_sFilePath.find("loop") != string::npos )
return RageSoundParams::M_LOOP;
else
return RageSoundParams::M_STOP;
2003-02-10 21:43:50 +00:00
}
2003-03-15 23:34:45 +00:00
/*
2004-05-06 02:40:33 +00:00
* 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.
*/