Files
itgmania212121/stepmania/src/RageSound.cpp
T

844 lines
22 KiB
C++
Raw Normal View History

2002-12-13 07:44:34 +00:00
/*
* Handle loading and decoding of sounds through SDL_sound. This file
* is portable; actual playing is handled in RageSoundManager.
* 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"
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
const int channels = 2;
2004-02-28 00:14:16 +00:00
const int framesize = 2 * channels; /* 16-bit */
2003-04-24 23:51:05 +00:00
#define samplerate() Sample->GetSampleRate()
2002-12-13 07:44:34 +00:00
/* The most data to buffer when streaming. This should generally be at least as large
* as the largest hardware buffer. */
const int internal_buffer_size = 1024*16;
/* The amount of data to read from SDL_sound at once. */
const unsigned read_block_size = 1024;
2002-12-13 07:44:34 +00:00
2004-02-28 00:14:16 +00:00
RageSoundParams::RageSoundParams():
2004-01-12 22:17:43 +00:00
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;
m_Volume = -1.0f; // use SOUNDMAN->GetMixVolume()
m_Balance = 0; // center
speed_input_samples = speed_output_samples = 1;
AccurateSync = false;
StopMode = M_AUTO;
2004-02-28 00:14:16 +00:00
}
RageSound::RageSound():
m_Mutex( "RageSound" )
2002-12-13 07:44:34 +00:00
{
ASSERT(SOUNDMAN);
2002-12-22 08:13:33 +00:00
original = this;
Sample = NULL;
2004-04-11 04:01:07 +00:00
decode_position = 0;
stopped_position = 0;
2002-12-13 07:44:34 +00:00
playing = false;
playing_thread = 0;
databuf.reserve(internal_buffer_size);
2004-02-28 00:14:16 +00:00
2004-04-20 03:55:36 +00:00
/* Register ourself. */
2004-03-18 01:31:34 +00:00
ID = SOUNDMAN->RegisterSound( this );
2002-12-13 07:44:34 +00:00
}
RageSound::~RageSound()
{
2002-12-22 08:13:33 +00:00
/* If we're a "master" sound (not a copy), tell RageSoundManager to
* stop mixing us and everything that's copied from us. */
if(original == this)
SOUNDMAN->StopPlayingAllCopiesOfSound(*this);
2002-12-22 08:13:33 +00:00
2002-12-13 07:44:34 +00:00
Unload();
2004-03-18 01:31:34 +00:00
/* Unregister ourself. */
SOUNDMAN->UnregisterSound( this );
2002-12-13 07:44:34 +00:00
}
2004-01-15 02:28:58 +00:00
RageSound::RageSound(const RageSound &cpy):
RageSoundBase( cpy ),
m_Mutex( "RageSound" )
2002-12-13 07:44:34 +00:00
{
ASSERT(SOUNDMAN);
Sample = NULL;
2002-12-13 07:44:34 +00:00
*this = cpy;
2004-04-20 03:55:36 +00:00
/* Register ourself. We have a different ID than our parent. */
ID = SOUNDMAN->RegisterSound( this );
}
RageSound &RageSound::operator=( const RageSound &cpy )
{
LockMut(cpy.m_Mutex);
2002-12-22 08:13:33 +00:00
original = cpy.original;
2004-02-28 01:02:06 +00:00
m_Param = cpy.m_Param;
2004-04-11 04:01:07 +00:00
decode_position = cpy.decode_position;
stopped_position = cpy.stopped_position;
2002-12-18 07:48:54 +00:00
playing = false;
playing_thread = 0;
2002-12-27 23:15:39 +00:00
databuf.reserve(internal_buffer_size);
delete Sample;
Sample = cpy.Sample->Copy();
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
delete Sample;
Sample = NULL;
2002-12-27 23:15:39 +00:00
2002-12-13 07:44:34 +00:00
m_sFilePath = "";
databuf.clear();
2002-12-13 07:44:34 +00:00
}
2002-12-27 23:15:39 +00:00
void RageSound::Fail(CString reason)
{
2003-08-12 18:29:35 +00:00
LOG->Warn("Decoding %s failed: %s", GetLoadedFilePath().c_str(), reason.c_str() );
2002-12-27 23:15:39 +00:00
error = reason;
}
2003-09-10 21:33:46 +00:00
2003-01-21 02:44:35 +00:00
bool RageSound::Load(CString sSoundFilePath, int precache)
2002-12-13 07:44:34 +00:00
{
2003-04-25 00:01:35 +00:00
LOG->Trace( "RageSound::LoadSound( '%s' )", sSoundFilePath.c_str() );
2002-12-13 07:44:34 +00:00
2003-01-21 02:44:35 +00:00
if(precache == 2)
precache = false;
2002-12-22 08:13:33 +00:00
/* Don't load over copies. */
2002-12-22 08:28:21 +00:00
ASSERT(original == this || m_sFilePath == "");
2002-12-22 08:13:33 +00:00
2002-12-13 07:44:34 +00:00
Unload();
m_sFilePath = sSoundFilePath;
2004-04-11 04:01:07 +00:00
decode_position = stopped_position = 0;
2002-12-13 07:44:34 +00:00
2003-09-10 21:33:46 +00:00
CString error;
2003-09-23 00:07:11 +00:00
Sample = SoundReader_FileReader::OpenFile( m_sFilePath, error );
2003-09-10 21:33:46 +00:00
if( Sample == NULL )
2003-05-13 13:35:32 +00:00
RageException::Throw( "RageSoundManager::RageSoundManager: error opening sound '%s': '%s'",
2003-09-23 00:07:11 +00:00
m_sFilePath.c_str(), error.c_str());
2002-12-13 07:44:34 +00:00
2004-01-03 02:36:14 +00:00
const int NeededRate = SOUNDMAN->GetDriverSampleRate( Sample->GetSampleRate() );
if( NeededRate != Sample->GetSampleRate() )
2003-04-23 19:11:59 +00:00
{
2003-09-25 01:48:09 +00:00
RageSoundReader_Resample *Resample = RageSoundReader_Resample::MakeResampler( (RageSoundReader_Resample::ResampleQuality) PREFSMAN->m_iSoundResampleQuality );
2003-04-23 19:11:59 +00:00
Resample->Open(Sample);
2004-01-03 02:36:14 +00:00
Resample->SetSampleRate( NeededRate );
2003-04-23 19:11:59 +00:00
Sample = Resample;
}
/* Try to precache. */
2003-03-15 23:34:45 +00:00
if(precache)
2002-12-13 07:44:34 +00:00
{
2003-03-15 23:34:45 +00:00
SoundReader_Preload *Preload = new SoundReader_Preload;
2003-04-23 18:33:59 +00:00
if(Preload->Open(Sample)) {
Sample = Preload;
2003-04-23 18:33:59 +00:00
} else {
/* Preload failed. It read some data, so we need to rewind the
* reader. */
Sample->SetPosition_Fast(0);
delete Preload;
2002-12-13 07:44:34 +00:00
}
}
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
}
2002-12-27 23:15:39 +00:00
/* Return the number of bytes available in the input buffer. */
int RageSound::Bytes_Available() const
{
return databuf.num_readable();
2002-12-13 07:44:34 +00:00
}
2003-01-02 06:52:39 +00:00
void RageSound::RateChange(char *buf, int &cnt,
int speed_input_samples, int speed_output_samples, int channels)
{
if(speed_input_samples == speed_output_samples)
return;
/* Rate change. Change speed_input_samples into speed_output_samples.
* Do this per-channel. */
static char *inbuf_tmp = NULL;
static int maxcnt = 0;
if(cnt > maxcnt)
{
maxcnt = cnt;
delete [] inbuf_tmp;
inbuf_tmp = new char[cnt];
}
memcpy(inbuf_tmp, buf, cnt);
for(int c = 0; c < channels; ++c)
{
const Sint16 *in = (const Sint16 *) inbuf_tmp;
Sint16 *out = (Sint16 *) buf;
in += c;
out += c;
for(unsigned n = 0; n < cnt/(channels * sizeof(Sint16)); n += speed_input_samples)
{
/* 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
2003-08-07 06:08:29 +00:00
Sint16 samps[20]; // max 2x rate
ASSERT(size_t(speed_input_samples) <= sizeof(samps)/sizeof(*samps));
2003-01-02 06:52:39 +00:00
int s;
for(s = 0; s < speed_input_samples; ++s) {
samps[s] = *in; in += channels;
}
float pos = 0;
float incr = float(speed_input_samples) / speed_output_samples;
for(s = 0; s < speed_output_samples; ++s) {
float frac = pos - floorf(pos);
int p = int(pos);
int val = int(samps[p] * (1-frac));
if(s+1 < speed_output_samples)
val += int(samps[p+1] * frac);
*out = Sint16(val);
pos += incr;
out += channels;
}
}
}
cnt = (cnt * speed_output_samples) / speed_input_samples;
}
2002-12-13 07:44:34 +00:00
/* Fill the buffer by about "bytes" worth of data. (We might go a little
2002-12-27 23:15:39 +00:00
* over, and we won't overflow our buffer.) Return the number of bytes
* actually read; 0 = EOF. */
2004-03-26 03:19:55 +00:00
int RageSound::FillBuf( int frames )
2002-12-13 07:44:34 +00:00
{
ASSERT(Sample);
2002-12-13 07:44:34 +00:00
2002-12-14 00:22:18 +00:00
bool got_something = false;
2002-12-27 23:15:39 +00:00
2004-03-26 03:19:55 +00:00
while( frames > 0 )
2002-12-13 07:44:34 +00:00
{
if(read_block_size > databuf.num_writable())
2002-12-13 07:44:34 +00:00
break; /* full */
2003-01-02 06:52:39 +00:00
char inbuf[10240];
unsigned read_size = read_block_size;
2002-12-27 23:15:39 +00:00
int cnt = 0;
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. */
2004-02-28 01:02:06 +00:00
read_size = read_size * 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. */
2004-02-28 01:02:06 +00:00
int block_size = sizeof(Sint16) * channels * m_Param.speed_input_samples;
2003-01-02 06:52:39 +00:00
read_size = (read_size / block_size) * block_size;
ASSERT(read_size < sizeof(inbuf));
2002-12-27 23:15:39 +00:00
}
2003-01-02 06:52:39 +00:00
ASSERT(read_size < sizeof(inbuf));
cnt = Sample->Read(inbuf, read_size);
2002-12-27 23:15:39 +00:00
if(cnt == 0)
2002-12-14 00:22:18 +00:00
return got_something; /* EOF */
2002-12-27 23:15:39 +00:00
if(cnt == -1)
2002-12-13 07:44:34 +00:00
{
Fail(Sample->GetError());
2002-12-27 23:15:39 +00:00
2003-08-12 18:29:35 +00:00
/* Pretend we got EOF. */
return 0;
2002-12-13 07:44:34 +00:00
}
2004-02-28 01:02:06 +00:00
RateChange( inbuf, cnt, 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. */
databuf.write((const char *) inbuf, cnt);
2004-03-26 03:19:55 +00:00
frames -= cnt/framesize;
2002-12-14 00:22:18 +00:00
got_something = true;
2002-12-13 07:44:34 +00:00
}
2002-12-14 00:22:18 +00:00
return got_something;
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. */
2004-03-26 03:26:20 +00:00
int RageSound::GetData( char *buffer, int frames )
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. */
2004-02-28 01:02:06 +00:00
const float LastSecond = m_Param.m_StartSecond + m_Param.m_LengthSeconds;
2004-04-11 04:01:07 +00:00
int FramesToRead = int(LastSecond*samplerate()) - decode_position;
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. */
2004-03-26 03:26:20 +00:00
frames = clamp( FramesToRead, 0, frames );
2002-12-27 23:15:39 +00:00
}
int got;
2004-04-11 04:01:07 +00:00
if( decode_position < 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? */
2004-04-11 04:01:07 +00:00
got = -decode_position;
2004-03-26 03:26:20 +00:00
got = min( got, frames );
if( buffer )
memset( buffer, 0, got*framesize );
} else {
2002-12-27 23:15:39 +00:00
/* Feed data out of our streaming buffer. */
ASSERT(Sample);
2004-03-26 03:26:20 +00:00
got = min( int(databuf.num_readable()/framesize), frames );
if( buffer )
databuf.read( buffer, got*framesize );
2002-12-27 23:15:39 +00:00
}
return got;
}
2004-04-11 03:31:14 +00:00
/* Adjust the balance of buffer; fBalance is -1...+1. */
void AdjustBalance( Sint16 *buffer, int frames, float fBalance )
{
if( fBalance == 0 )
return; /* no change */
bool bSwap = fBalance < 0;
if( bSwap )
fBalance = -fBalance;
float fLeftFactors[2] ={ 1-fBalance, 0 };
float fRightFactors[2] =
{
SCALE( fBalance, 0, 1, 0.5f, 0 ),
SCALE( fBalance, 0, 1, 0.5f, 1 )
};
if( bSwap )
{
swap( fLeftFactors[0], fRightFactors[0] );
swap( fLeftFactors[1], fRightFactors[1] );
}
RAGE_ASSERT_M( channels == 2, ssprintf("%i", channels) );
for( int samp = 0; samp < frames; ++samp )
{
Sint16 l = Sint16(buffer[0]*fLeftFactors[0] + buffer[1]*fLeftFactors[1]);
Sint16 r = Sint16(buffer[0]*fRightFactors[0] + buffer[1]*fRightFactors[1]);
buffer[0] = l;
buffer[1] = r;
buffer += 2;
}
}
void FadeSound( Sint16 *buffer, int frames, float fStartVolume, float fEndVolume )
{
for( int samp = 0; samp < frames; ++samp )
{
float fVolPercent = SCALE( samp, 0, frames, fStartVolume, fEndVolume );
fVolPercent = clamp( fVolPercent, 0.f, 1.f );
for(int i = 0; i < channels; ++i)
{
*buffer = short(*buffer * fVolPercent);
buffer++;
}
}
}
/* 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.
Sample (r), databuf (r)
decode_position (r), databuf (r)
*
*/
2004-03-18 03:30:36 +00:00
/* 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. size is in bytes.
*
* If the data returned is at the end of the stream, return false.
*
* size is in frames
2004-03-18 03:30:36 +00:00
* sound_frame is in frames (abstract)
*/
bool RageSound::GetDataToPlay( int16_t *buffer, int size, int &sound_frame, int &frames_stored )
2002-12-13 07:44:34 +00:00
{
2003-01-12 04:19:45 +00:00
int NumRewindsThisCall = 0;
/* We only update decode_position; only take a shared lock, so we don't block the main thread. */
// LockMut(m_Mutex);
2002-12-13 07:44:34 +00:00
ASSERT_M( playing, ssprintf("%p", this) );
2004-01-19 20:37:14 +00:00
2004-03-18 03:30:36 +00:00
frames_stored = 0;
2004-04-11 04:01:07 +00:00
sound_frame = decode_position;
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() )
2004-03-26 03:19:55 +00:00
FillBuf( size );
2002-12-13 07:44:34 +00:00
2004-03-18 03:30:36 +00:00
/* Get a block of data. */
2004-03-26 03:29:44 +00:00
int got_frames = GetData( (char *) buffer, size );
if( !got_frames )
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. */
2003-01-12 04:19:45 +00:00
NumRewindsThisCall++;
if(NumRewindsThisCall > 3)
{
/* 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() )
2004-03-26 03:19:55 +00:00
FillBuf( size );
2004-03-26 03:26:20 +00:00
if( GetData(NULL, size) == 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( buffer, 0, size*framesize );
got_frames = size;
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
}
2004-04-11 04:01:07 +00:00
/* This block goes from decode_position to decode_position+got_frames. */
2002-12-27 23:15:39 +00:00
2003-02-10 21:43:50 +00:00
/* We want to fade when there's FADE_TIME 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. */
2004-02-28 01:02:06 +00:00
if( m_Param.m_FadeLength != 0 && m_Param.m_LengthSeconds != -1 )
2004-02-28 00:14:16 +00:00
{
2004-04-11 03:31:14 +00:00
const float fLastSecond = m_Param.m_StartSecond+m_Param.m_LengthSeconds;
2004-04-11 04:01:07 +00:00
const float fStartVolume = fLastSecond - float(decode_position) / samplerate();
const float fEndVolume = fLastSecond - float(decode_position+got_frames) / samplerate();
2004-04-11 03:31:14 +00:00
FadeSound( (Sint16 *) buffer, got_frames, fStartVolume, fEndVolume );
2002-12-13 07:44:34 +00:00
}
2004-04-11 03:31:14 +00:00
AdjustBalance( (Sint16 *) buffer, got_frames, m_Param.m_Balance );
2004-02-27 23:26:55 +00:00
2004-04-11 04:01:07 +00:00
sound_frame = decode_position;
2004-03-18 03:30:36 +00:00
frames_stored = got_frames;
2004-04-11 04:01:07 +00:00
decode_position += got_frames;
2004-03-18 03:30:36 +00:00
return true;
}
}
/* Indicate that a block of audio data has been written to the device. */
void RageSound::CommitPlayingPosition( int64_t frameno, int pos, int got_frames )
{
2004-04-16 22:28:01 +00:00
m_Mutex.Lock();
pos_map.Insert( frameno, pos, got_frames );
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. */
int RageSound::GetPCM( char *buffer, int size, int64_t frameno )
{
ASSERT(playing);
/*
* "frameno" is the audio driver's conception of time. "position"
* is ours. Keep track of frameno->position mappings.
*
* This way, when we query the time later on, we can derive position
* values from the frameno values returned from GetPosition.
*/
/* Now actually put data from the correct buffer into the output. */
int bytes_stored = 0;
while( bytes_stored < size )
{
int pos, got_frames;
bool eof = !GetDataToPlay( (int16_t *)(buffer+bytes_stored), (size-bytes_stored)/framesize, pos, got_frames );
2004-03-18 03:30:36 +00:00
/* Save this frameno/position map. */
SOUNDMAN->CommitPlayingPosition( GetID(), frameno, pos, got_frames );
bytes_stored += got_frames * framesize;
frameno += got_frames;
2004-03-18 03:30:36 +00:00
if( eof )
break;
2002-12-13 07:44:34 +00:00
}
return bytes_stored;
}
2002-12-22 08:13:33 +00:00
/* Start playing from the current position. If the sound is already
* playing, Stop is called. */
void RageSound::StartPlaying()
2002-12-13 07:44:34 +00:00
{
2004-01-22 06:52:44 +00:00
// If no volume is set, use the default.
if( m_Param.m_Volume == -1 )
m_Param.m_Volume = SOUNDMAN->GetMixVolume();
2004-01-22 06:52:44 +00:00
2002-12-22 08:13:33 +00:00
ASSERT(!playing);
2002-12-13 07:44:34 +00:00
2004-01-12 01:02:18 +00:00
/* If 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. */
2004-02-28 01:02:06 +00:00
if( !m_Param.StartTime.IsZero() && m_Param.StartTime.Ago() > 0 )
2004-01-12 01:02:18 +00:00
LOG->Trace("Sound \"%s\" has a start time %f seconds in the past",
2004-02-28 01:02:06 +00:00
GetLoadedFilePath().c_str(), m_Param.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());
2002-12-22 08:13:33 +00:00
playing = true;
playing_thread = RageThread::GetCurrentThreadID();
2002-12-22 08:13:33 +00:00
SOUNDMAN->StartMixing(this);
SOUNDMAN->RegisterPlayingSound( 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
{
2003-02-10 21:43:50 +00:00
if(!playing)
return;
stopped_position = (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
SOUNDMAN->UnregisterPlayingSound( 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());
2002-12-13 07:44:34 +00:00
playing = false;
playing_thread = 0;
2002-12-22 08:13:33 +00:00
pos_map.Clear();
// 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()
{
if(!playing)
return;
m_Mutex.Lock();
stopped_position = (int) GetPositionSecondsInternal();
SOUNDMAN->UnregisterPlayingSound( this );
// LOG->Trace("set playing false for %p (SoundIsFinishedPlaying) (%s)", this, this->GetLoadedFilePath().c_str());
playing = false;
playing_thread = 0;
pos_map.Clear();
// LOG->Trace("SoundIsFinishedPlaying %p finished (%s)", this, this->GetLoadedFilePath().c_str());
m_Mutex.Unlock();
}
RageSound *RageSound::Play( const RageSoundParams *params )
2002-12-22 08:13:33 +00:00
{
return SOUNDMAN->PlaySound( *this, params );
2002-12-22 08:13:33 +00:00
}
void RageSound::Stop()
{
SOUNDMAN->StopPlayingAllCopiesOfSound(*this);
2002-12-22 08:13:33 +00:00
}
2002-12-13 07:44:34 +00:00
float RageSound::GetLengthSeconds()
{
ASSERT(Sample);
int len = Sample->GetLength();
2002-12-13 07:44:34 +00:00
if(len < 0)
{
LOG->Warn("GetLengthSeconds failed on %s: %s",
2003-04-25 00:01:35 +00:00
GetLoadedFilePath().c_str(), Sample->GetError().c_str() );
return -1;
2002-12-13 07:44:34 +00:00
}
return len / 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. */
int64_t RageSound::GetPositionSecondsInternal( bool *approximate ) const
2004-01-18 02:07:58 +00:00
{
LockMut(m_Mutex);
2004-01-18 02:07:58 +00:00
if( approximate )
*approximate = false;
/* If we're not playing, just report the static position. */
if( !IsPlaying() )
return stopped_position;
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. */
if( pos_map.IsEmpty() )
2004-01-18 02:07:58 +00:00
{
2004-04-11 04:01:07 +00:00
LOG->Trace("no data yet; %i", stopped_position);
2004-01-18 02:07:58 +00:00
if( approximate )
*approximate = true;
return stopped_position;
2004-01-18 02:07:58 +00:00
}
/* Get our current hardware position. */
2004-02-28 00:14:16 +00:00
int64_t cur_frame = SOUNDMAN->GetPosition(this);
2004-01-18 02:07:58 +00:00
return pos_map.Search( cur_frame, approximate );
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.
*/
2004-01-18 04:01:52 +00:00
float RageSound::GetPositionSeconds( bool *approximate, RageTimer *Timestamp ) const
{
LockMut(m_Mutex);
2004-01-18 04:01:52 +00:00
if( Timestamp )
{
HOOKS->EnterTimeCriticalSection();
Timestamp->Touch();
}
const float pos = GetPositionSecondsInternal( approximate ) / float(samplerate());
2004-01-18 04:01:52 +00:00
if( Timestamp )
HOOKS->ExitTimeCriticalSection();
2004-02-20 04:16:12 +00:00
return GetPlaybackRate() * pos;
}
2002-12-27 23:15:39 +00:00
bool RageSound::SetPositionSeconds( float fSeconds )
2002-12-13 07:44:34 +00:00
{
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
{
return Sample->GetSampleRate();
}
2004-02-28 00:14:16 +00:00
bool RageSound::SetPositionFrames( int frames )
2002-12-22 08:13:33 +00:00
{
LockMut(m_Mutex);
2002-12-27 23:15:39 +00:00
2003-03-15 21:02:45 +00:00
{
2004-04-11 04:01:07 +00:00
/* "decode_position" 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
2004-04-11 04:01:07 +00:00
* "decode_position" represents. */
2004-02-28 00:14:16 +00:00
const int scaled_frames = int( frames / GetPlaybackRate() );
2003-03-15 21:02:45 +00:00
/* If we're already there, don't do anything. */
2004-04-11 04:01:07 +00:00
if( decode_position == scaled_frames )
2003-03-15 21:02:45 +00:00
return true;
2004-04-11 04:01:07 +00:00
stopped_position = decode_position = scaled_frames;
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. */
2004-02-28 00:14:16 +00:00
int ms = int( float(frames) * 1000.f / samplerate() );
2003-03-15 21:02:45 +00:00
ms = max(ms, 0);
2002-12-22 08:13:33 +00:00
databuf.clear();
2002-12-27 23:15:39 +00:00
ASSERT(Sample);
2002-12-27 23:15:39 +00:00
int ret;
2004-02-28 01:02:06 +00:00
if( m_Param.AccurateSync )
ret = Sample->SetPosition_Accurate(ms);
2002-12-27 23:15:39 +00:00
else
ret = Sample->SetPosition_Fast(ms);
2002-12-27 23:15:39 +00:00
if(ret == -1)
{
/* XXX untested */
Fail(Sample->GetError());
2002-12-27 23:15:39 +00:00
return false; /* failed */
}
if(ret == 0 && ms != 0)
{
/* 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. */
2004-02-28 00:14:16 +00:00
LOG->Warn("SetPositionFrames: %i ms is beyond EOF in %s",
2003-04-25 00:01:35 +00:00
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
}
void RageSoundParams::SetPlaybackRate( float NewSpeed )
2004-02-28 00:14:16 +00:00
{
if( NewSpeed == 1.00f )
{
speed_input_samples = 1; speed_output_samples = 1;
2003-01-02 06:52:39 +00:00
} else {
/* Approximate it to the nearest tenth. */
speed_input_samples = int( roundf(NewSpeed * 10) );
speed_output_samples = 10;
2003-01-02 06:52:39 +00:00
}
2002-12-13 07:44:34 +00:00
}
2004-02-28 01:02:06 +00:00
float RageSound::GetVolume() const
2004-02-28 00:14:16 +00:00
{
2004-02-28 01:02:06 +00:00
return m_Param.m_Volume;
2004-02-28 00:14:16 +00:00
}
void RageSound::LockSound()
{
m_Mutex.Lock();
}
void RageSound::UnlockSound()
{
m_Mutex.Unlock();
}
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
{
return m_Param.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;
if( m_sFilePath.Find("loop") != -1 )
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-02-28 00:14:16 +00:00
Copyright (c) 2002-2004 by the person(s) listed below. All rights reserved.
2003-03-15 23:34:45 +00:00
Glenn Maynard
-----------------------------------------------------------------------------
*/