833 lines
22 KiB
C++
833 lines
22 KiB
C++
/*
|
|
* 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.
|
|
*
|
|
* TODO:
|
|
* Configurable buffer sizes (stored in SoundManager) and so on
|
|
*
|
|
* 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.
|
|
*
|
|
* We need (yet) another layer of abstraction: RageSoundSource. It'll just
|
|
* implement the SDL_sound interface (in a class). Two implementations;
|
|
* one, used normally, that just wraps SDL_sound; and another that is given
|
|
* a list of sounds and time offsets and transparently mixes them together
|
|
* at the given times, filling the gaps with silence. This should be an easy
|
|
* way to handle autoplay tracks in keyed games. Normal background music can
|
|
* be passed to it with an offset of 0 (or the gap, however it works out).
|
|
*/
|
|
|
|
#include "global.h"
|
|
#include "RageSound.h"
|
|
#include "RageSoundManager.h"
|
|
#include "RageUtil.h"
|
|
#include "RageLog.h"
|
|
#include "RageException.h"
|
|
#include "PrefsManager.h"
|
|
#include "arch/ArchHooks/ArchHooks.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "RageSoundReader_Preload.h"
|
|
#include "RageSoundReader_Resample.h"
|
|
#include "RageSoundReader_FileReader.h"
|
|
|
|
const int channels = 2;
|
|
const int samplesize = 2 * channels; /* 16-bit */
|
|
#define samplerate() Sample->GetSampleRate()
|
|
|
|
/* 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;
|
|
|
|
/* The number of samples we should keep pos_map data for. This being too high
|
|
* is mostly harmless; the data is small. */
|
|
const int pos_map_backlog_samples = 100000;
|
|
|
|
RageSound::RageSound():
|
|
StartTime( RageZeroTimer )
|
|
{
|
|
ASSERT(SOUNDMAN);
|
|
LockMut(SOUNDMAN->lock);
|
|
|
|
original = this;
|
|
Sample = NULL;
|
|
position = 0;
|
|
stopped_position = -1;
|
|
playing = false;
|
|
StopMode = M_STOP;
|
|
speed_input_samples = speed_output_samples = 1;
|
|
databuf.reserve(internal_buffer_size);
|
|
m_StartSample = 0;
|
|
m_LengthSamples = -1;
|
|
m_Volume = -1.0f; // use SOUNDMAN->GetMixVolume()
|
|
m_Balance = 0; // center
|
|
AccurateSync = false;
|
|
fade_length = 0;
|
|
/* Register ourselves, so we receive Update()s. */
|
|
SOUNDMAN->all_sounds.insert(this);
|
|
}
|
|
|
|
RageSound::~RageSound()
|
|
{
|
|
/* 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->StopPlayingSound(*this);
|
|
|
|
Unload();
|
|
|
|
/* Unregister ourselves. */
|
|
SOUNDMAN->lock.Lock();
|
|
SOUNDMAN->all_sounds.erase(this);
|
|
SOUNDMAN->lock.Unlock();
|
|
}
|
|
|
|
RageSound::RageSound(const RageSound &cpy):
|
|
RageSoundBase( cpy )
|
|
{
|
|
ASSERT(SOUNDMAN);
|
|
LockMut(SOUNDMAN->lock);
|
|
|
|
Sample = NULL;
|
|
|
|
original = cpy.original;
|
|
m_StartSample = cpy.m_StartSample;
|
|
m_LengthSamples = cpy.m_LengthSamples;
|
|
m_Volume = cpy.m_Volume;
|
|
StopMode = cpy.StopMode;
|
|
position = cpy.position;
|
|
playing = false;
|
|
AccurateSync = cpy.AccurateSync;
|
|
StartTime = cpy.StartTime;
|
|
fade_length = cpy.fade_length;
|
|
speed_input_samples = cpy.speed_input_samples;
|
|
speed_output_samples = cpy.speed_output_samples;
|
|
|
|
databuf.reserve(internal_buffer_size);
|
|
Sample = cpy.Sample->Copy();
|
|
|
|
/* Load() won't work on a copy if m_sFilePath is already set, so
|
|
* copy this down here. */
|
|
m_sFilePath = cpy.m_sFilePath;
|
|
|
|
/* Register ourselves, so we receive Update()s. */
|
|
SOUNDMAN->all_sounds.insert(this);
|
|
}
|
|
|
|
void RageSound::Unload()
|
|
{
|
|
if(IsPlaying())
|
|
StopPlaying();
|
|
|
|
delete Sample;
|
|
Sample = NULL;
|
|
|
|
m_sFilePath = "";
|
|
databuf.clear();
|
|
}
|
|
|
|
void RageSound::Fail(CString reason)
|
|
{
|
|
LOG->Warn("Decoding %s failed: %s", GetLoadedFilePath().c_str(), reason.c_str() );
|
|
|
|
error = reason;
|
|
}
|
|
|
|
|
|
bool RageSound::Load(CString sSoundFilePath, int precache)
|
|
{
|
|
LOG->Trace( "RageSound::LoadSound( '%s' )", sSoundFilePath.c_str() );
|
|
|
|
if(precache == 2)
|
|
precache = false;
|
|
|
|
/* Don't load over copies. */
|
|
ASSERT(original == this || m_sFilePath == "");
|
|
|
|
Unload();
|
|
|
|
m_sFilePath = sSoundFilePath;
|
|
position = 0;
|
|
|
|
|
|
// Check for "loop" hint
|
|
if( m_sFilePath.Find("loop") != -1 )
|
|
SetStopMode( M_LOOP );
|
|
else
|
|
SetStopMode( M_STOP );
|
|
|
|
CString error;
|
|
Sample = SoundReader_FileReader::OpenFile( m_sFilePath, error );
|
|
if( Sample == NULL )
|
|
RageException::Throw( "RageSoundManager::RageSoundManager: error opening sound '%s': '%s'",
|
|
m_sFilePath.c_str(), error.c_str());
|
|
|
|
const int NeededRate = SOUNDMAN->GetDriverSampleRate( Sample->GetSampleRate() );
|
|
if( NeededRate != Sample->GetSampleRate() )
|
|
{
|
|
RageSoundReader_Resample *Resample = RageSoundReader_Resample::MakeResampler( (RageSoundReader_Resample::ResampleQuality) PREFSMAN->m_iSoundResampleQuality );
|
|
Resample->Open(Sample);
|
|
Resample->SetSampleRate( NeededRate );
|
|
Sample = Resample;
|
|
}
|
|
|
|
/* Try to precache. */
|
|
if(precache)
|
|
{
|
|
SoundReader_Preload *Preload = new SoundReader_Preload;
|
|
if(Preload->Open(Sample)) {
|
|
Sample = Preload;
|
|
} else {
|
|
/* Preload failed. It read some data, so we need to rewind the
|
|
* reader. */
|
|
Sample->SetPosition_Fast(0);
|
|
delete Preload;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RageSound::SetStartSeconds( float secs )
|
|
{
|
|
ASSERT(!playing);
|
|
m_StartSample = int(secs*samplerate());
|
|
}
|
|
|
|
void RageSound::SetLengthSeconds(float secs)
|
|
{
|
|
RAGE_ASSERT_M( secs == -1 || secs >= 0, ssprintf("%f",secs) );
|
|
ASSERT(!playing);
|
|
|
|
if(secs == -1)
|
|
m_LengthSamples = -1;
|
|
else
|
|
m_LengthSamples = int(secs*samplerate());
|
|
}
|
|
|
|
/* Read data at the rate we're playing it. We only do this to smooth out the rate
|
|
* we read data; the sound thread will always read more if it's needed.
|
|
*
|
|
* Actually, this isn't a good idea. The sound driver will read in small chunks,
|
|
* interleaving between files. For example, if four files are playing, and each
|
|
* is two chunks behind, it'll read a chunk from each file twice, instead of reading
|
|
* two chunks for each file at a time, which reduces the chance of underrun. */
|
|
void RageSound::Update(float delta)
|
|
{
|
|
// LockMut(SOUNDMAN->lock);
|
|
|
|
// if( playing && delta )
|
|
// FillBuf(int(delta * GetSampleRate() * samplesize));
|
|
}
|
|
|
|
/* Return the number of bytes available in the input buffer. */
|
|
int RageSound::Bytes_Available() const
|
|
{
|
|
return databuf.size();
|
|
}
|
|
|
|
|
|
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
|
|
* rounding error. */
|
|
|
|
Sint16 samps[20]; // max 2x rate
|
|
ASSERT(size_t(speed_input_samples) <= sizeof(samps)/sizeof(*samps));
|
|
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;
|
|
}
|
|
|
|
/* Fill the buffer by about "bytes" worth of data. (We might go a little
|
|
* over, and we won't overflow our buffer.) Return the number of bytes
|
|
* actually read; 0 = EOF. */
|
|
int RageSound::FillBuf(int bytes)
|
|
{
|
|
LockMut(SOUNDMAN->lock);
|
|
|
|
ASSERT(Sample);
|
|
|
|
bool got_something = false;
|
|
|
|
while(bytes > 0)
|
|
{
|
|
if(read_block_size > databuf.capacity() - databuf.size())
|
|
break; /* full */
|
|
|
|
char inbuf[10240];
|
|
unsigned read_size = read_block_size;
|
|
int cnt = 0;
|
|
|
|
if(speed_input_samples != speed_output_samples)
|
|
{
|
|
/* Read enough data to produce read_block_size. */
|
|
read_size = read_size * speed_input_samples / speed_output_samples;
|
|
|
|
/* Read in blocks that are a multiple of a sample, the number of
|
|
* channels and the number of input samples. */
|
|
int block_size = sizeof(Sint16) * channels * speed_input_samples;
|
|
read_size = (read_size / block_size) * block_size;
|
|
ASSERT(read_size < sizeof(inbuf));
|
|
}
|
|
|
|
ASSERT(read_size < sizeof(inbuf));
|
|
|
|
cnt = Sample->Read(inbuf, read_size);
|
|
if(cnt == 0)
|
|
return got_something; /* EOF */
|
|
|
|
if(cnt == -1)
|
|
{
|
|
Fail(Sample->GetError());
|
|
|
|
/* Pretend we got EOF. */
|
|
return 0;
|
|
}
|
|
|
|
RateChange(inbuf, cnt, speed_input_samples, speed_output_samples, channels);
|
|
|
|
/* Add the data to the buffer. */
|
|
databuf.write((const char *) inbuf, cnt);
|
|
bytes -= cnt;
|
|
got_something = true;
|
|
}
|
|
|
|
return got_something;
|
|
}
|
|
|
|
/* Get a block of data from the input. If buffer is NULL, just return the amount
|
|
* that would be read. */
|
|
int RageSound::GetData(char *buffer, int size)
|
|
{
|
|
if(m_LengthSamples != -1)
|
|
{
|
|
/* We have a length; only read up to the end. MaxPosition is the
|
|
* sample position of the end. */
|
|
int SamplesToRead = m_StartSample + m_LengthSamples - position;
|
|
|
|
/* If it's negative, we're past the end, so cap it at 0. Don't read
|
|
* more than size. */
|
|
size = clamp(SamplesToRead * samplesize, 0, size);
|
|
}
|
|
|
|
int got;
|
|
if(position < 0) {
|
|
/* We havn't *really* started playing yet, so just feed silence. How
|
|
* many more bytes of silence do we need? */
|
|
got = -position * samplesize;
|
|
got = min(got, size);
|
|
if(buffer)
|
|
memset(buffer, 0, got);
|
|
} else {
|
|
/* Feed data out of our streaming buffer. */
|
|
ASSERT(Sample);
|
|
got = min(int(databuf.size()), size);
|
|
if(buffer)
|
|
databuf.read(buffer, got);
|
|
}
|
|
|
|
return got;
|
|
}
|
|
|
|
/* 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 )
|
|
{
|
|
int NumRewindsThisCall = 0;
|
|
|
|
LockMut(SOUNDMAN->lock);
|
|
|
|
ASSERT(playing);
|
|
|
|
/* Erase old pos_map data. */
|
|
CleanPosMap( pos_map );
|
|
|
|
/*
|
|
* "sampleno" is the audio driver's conception of time. "position"
|
|
* is ours. Keep track of sampleno->position mappings.
|
|
*
|
|
* This way, when we query the time later on, we can derive position
|
|
* values from the sampleno values returned from GetPosition.
|
|
*/
|
|
|
|
/* Now actually put data from the correct buffer into the output. */
|
|
int bytes_stored = 0;
|
|
while(size)
|
|
{
|
|
/* Get a block of data. */
|
|
int got = GetData(buffer, size);
|
|
|
|
if(!got)
|
|
{
|
|
/* If we don't have any data left buffered, fill the buffer by
|
|
* up to as much as we need. */
|
|
if(!Bytes_Available())
|
|
FillBuf(size);
|
|
|
|
/* If we got some data, we're OK. */
|
|
if(GetData(NULL, size) != 0)
|
|
continue; /* we have more */
|
|
|
|
/* We're at the end of the data. If we're looping, rewind and restart. */
|
|
if(StopMode == M_LOOP)
|
|
{
|
|
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 = %i, length = %i)",
|
|
GetLoadedFilePath().c_str(), m_StartSample, m_LengthSamples);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Rewind and start over. */
|
|
SetPositionSamples(m_StartSample);
|
|
|
|
/* 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. */
|
|
if(!Bytes_Available()) FillBuf(size);
|
|
if(GetData(NULL, size) == 0)
|
|
{
|
|
LOG->Warn("Can't loop data in %s; no data available at start point %i",
|
|
GetLoadedFilePath().c_str(), m_StartSample);
|
|
|
|
/* Stop here. */
|
|
return bytes_stored;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Not looping. Normally, we'll just stop here. */
|
|
if(StopMode == M_STOP)
|
|
break;
|
|
|
|
/* We're out of data, but we're not going to stop, so fill in the
|
|
* rest with silence. */
|
|
memset(buffer, 0, size);
|
|
got = size;
|
|
}
|
|
|
|
/* This block goes from position to position+got_frames. */
|
|
int got_frames = got / samplesize; /* bytes -> frames */
|
|
|
|
/* Save this sampleno/position map. */
|
|
pos_map.push_back( pos_map_t(frameno, position, got_frames) );
|
|
|
|
/* We want to fade when there's FADE_TIME seconds left, but if
|
|
* m_LengthSamples is -1, we don't know the length we're playing.
|
|
* (m_LengthSamples is the length to play, not the length of the
|
|
* source.) If we don't know the length, don't fade. */
|
|
if(fade_length != 0 && m_LengthSamples != -1) {
|
|
Sint16 *p = (Sint16 *) buffer;
|
|
int this_position = position;
|
|
|
|
for(int samp = 0; samp < got_frames; ++samp)
|
|
{
|
|
float fSecsUntilSilent = float(m_StartSample + m_LengthSamples - this_position) / samplerate();
|
|
float fVolPercent = fSecsUntilSilent / fade_length;
|
|
|
|
fVolPercent = clamp(fVolPercent, 0.f, 1.f);
|
|
for(int i = 0; i < channels; ++i) {
|
|
*p = short(*p * fVolPercent);
|
|
p++;
|
|
}
|
|
this_position++;
|
|
}
|
|
}
|
|
|
|
if( m_Balance != 0 )
|
|
{
|
|
Sint16 *p = (Sint16 *) buffer;
|
|
const float fLeft = SCALE( m_Balance, -1, 1, 1, 0 );
|
|
const float fRight = SCALE( m_Balance, -1, 1, 0, 1 );
|
|
const int iLeft = int(fLeft*256);
|
|
const int iRight = int(fRight*256);
|
|
RAGE_ASSERT_M( channels == 2, ssprintf("%i", channels) );
|
|
for( int samp = 0; samp < got_frames; ++samp )
|
|
{
|
|
*(p++) = short( (*p * iLeft) >> 8 );
|
|
*(p++) = short( (*p * iRight) >> 8 );
|
|
}
|
|
}
|
|
|
|
bytes_stored += got;
|
|
position += got_frames;
|
|
size -= got;
|
|
buffer += got;
|
|
frameno += got_frames;
|
|
}
|
|
|
|
return bytes_stored;
|
|
}
|
|
|
|
/* Start playing from the current position. If the sound is already
|
|
* playing, Stop is called. */
|
|
void RageSound::StartPlaying()
|
|
{
|
|
LockMut(SOUNDMAN->lock);
|
|
|
|
// If no volume is set, use the default.
|
|
if( GetVolume() == -1 )
|
|
SetVolume( SOUNDMAN->GetMixVolume() );
|
|
|
|
stopped_position = -1;
|
|
|
|
ASSERT(!playing);
|
|
|
|
/* 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. */
|
|
if( !StartTime.IsZero() && StartTime.Ago() > 0 )
|
|
LOG->Trace("Sound \"%s\" has a start time %f seconds in the past",
|
|
GetLoadedFilePath().c_str(), StartTime.Ago() );
|
|
|
|
/* Tell the sound manager to start mixing us. */
|
|
playing = true;
|
|
SOUNDMAN->StartMixing(this);
|
|
SOUNDMAN->playing_sounds.insert( this );
|
|
}
|
|
|
|
void RageSound::StopPlaying()
|
|
{
|
|
if(!playing)
|
|
return;
|
|
|
|
stopped_position = GetPositionSecondsInternal();
|
|
|
|
/* Tell the sound manager to stop mixing this sound. */
|
|
SOUNDMAN->StopMixing(this);
|
|
|
|
SOUNDMAN->lock.Lock();
|
|
SOUNDMAN->playing_sounds.erase( this );
|
|
SOUNDMAN->lock.Unlock();
|
|
|
|
playing = false;
|
|
|
|
pos_map.clear();
|
|
}
|
|
|
|
RageSound *RageSound::Play()
|
|
{
|
|
return SOUNDMAN->PlaySound(*this);
|
|
}
|
|
|
|
void RageSound::Stop()
|
|
{
|
|
SOUNDMAN->StopPlayingSound(*this);
|
|
}
|
|
|
|
|
|
float RageSound::GetLengthSeconds()
|
|
{
|
|
ASSERT(Sample);
|
|
int len = Sample->GetLength();
|
|
|
|
if(len < 0)
|
|
{
|
|
LOG->Warn("GetLengthSeconds failed on %s: %s",
|
|
GetLoadedFilePath().c_str(), Sample->GetError().c_str() );
|
|
return -1;
|
|
}
|
|
|
|
return len / 1000.f; /* ms -> secs */
|
|
}
|
|
|
|
int64_t RageSound::SearchPosMap( const deque<pos_map_t> &pos_map, int64_t cur_frame, bool *approximate )
|
|
{
|
|
/* sampleno is probably in pos_map. Search to figure out what position
|
|
* this frameno maps to. */
|
|
int64_t closest_position = 0, closest_position_dist = INT_MAX;
|
|
int closest_block = 0; /* print only */
|
|
for( unsigned i = 0; i < pos_map.size(); ++i )
|
|
{
|
|
if( cur_frame >= pos_map[i].frameno &&
|
|
cur_frame < pos_map[i].frameno+pos_map[i].frames )
|
|
{
|
|
/* cur_frame lies in this block; it's an exact match. Figure
|
|
* out the exact position. */
|
|
int64_t diff = pos_map[i].position - pos_map[i].frameno;
|
|
return cur_frame + diff;
|
|
}
|
|
|
|
/* See if the current position is close to the beginning of this block. */
|
|
int64_t dist = llabs( pos_map[i].frameno - cur_frame );
|
|
if( dist < closest_position_dist )
|
|
{
|
|
closest_position_dist = dist;
|
|
closest_block = i;
|
|
closest_position = pos_map[i].position - dist;
|
|
}
|
|
|
|
/* See if the current position is close to the end of this block. */
|
|
dist = llabs( pos_map[i].frameno + pos_map[i].frames - cur_frame );
|
|
if( dist < closest_position_dist )
|
|
{
|
|
closest_position_dist = dist;
|
|
closest_position = pos_map[i].position + pos_map[i].frames + dist;
|
|
}
|
|
}
|
|
|
|
/* The sample is out of the range of data we've actually sent.
|
|
* Return the closest position.
|
|
*
|
|
* There are three cases when this happens:
|
|
* 1. After the first GetPCM call, but before it actually gets heard.
|
|
* 2. After GetPCM returns EOF and the sound has flushed, but before
|
|
* SoundStopped has been called.
|
|
* 3. Underflow; we'll be given a larger sample number than we know about.
|
|
*/
|
|
/* XXX: %lli normally, %I64i in Windows */
|
|
LOG->Trace( "Approximate sound time: driver sample %lli, pos_map sample %lli (dist %lli), closest position is %lli",
|
|
cur_frame, pos_map[closest_block].frameno, closest_position_dist, closest_position );
|
|
|
|
if( approximate )
|
|
*approximate = true;
|
|
return closest_position;
|
|
}
|
|
|
|
void RageSound::CleanPosMap( deque<pos_map_t> &pos_map )
|
|
{
|
|
/* Determine the number of frames of data we have. */
|
|
int64_t total_frames = 0;
|
|
for( unsigned i = 0; i < pos_map.size(); ++i )
|
|
total_frames += pos_map[i].frames;
|
|
|
|
/* Remove the oldest entry so long we'll stil have enough data. Don't delete every
|
|
* sample, so we'll always have some data to extrapolate from. */
|
|
while( pos_map.size() > 1 && total_frames - pos_map.front().frames > pos_map_backlog_samples )
|
|
{
|
|
total_frames -= pos_map.front().frames;
|
|
pos_map.pop_front();
|
|
}
|
|
}
|
|
|
|
/* Get the position in frames. */
|
|
int64_t RageSound::GetPositionSecondsInternal( bool *approximate ) const
|
|
{
|
|
LockMut(SOUNDMAN->lock);
|
|
|
|
if( approximate )
|
|
*approximate = false;
|
|
|
|
/* If we're not playing, just report the static position. */
|
|
if( !IsPlaying() )
|
|
{
|
|
if(stopped_position != -1)
|
|
return stopped_position;
|
|
return position;
|
|
}
|
|
|
|
/* 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.empty())
|
|
{
|
|
LOG->Trace("no data yet; %i", position);
|
|
if( approximate )
|
|
*approximate = true;
|
|
return position - int(samplerate()*SOUNDMAN->GetPlayLatency());
|
|
}
|
|
|
|
/* Get our current hardware position. */
|
|
int64_t cur_sample = SOUNDMAN->GetPosition(this);
|
|
|
|
return SearchPosMap( pos_map, cur_sample, approximate );
|
|
}
|
|
|
|
/*
|
|
* 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 *approximate, RageTimer *Timestamp ) const
|
|
{
|
|
LockMut(SOUNDMAN->lock);
|
|
|
|
if( Timestamp )
|
|
{
|
|
HOOKS->EnterTimeCriticalSection();
|
|
Timestamp->Touch();
|
|
}
|
|
|
|
const float pos = GetPositionSecondsInternal( approximate ) / float(samplerate());
|
|
|
|
if( Timestamp )
|
|
HOOKS->ExitTimeCriticalSection();
|
|
|
|
return GetPlaybackRate() * pos;
|
|
}
|
|
|
|
|
|
bool RageSound::SetPositionSeconds( float fSeconds )
|
|
{
|
|
return SetPositionSamples( fSeconds == -1? -1: int(fSeconds * samplerate()) );
|
|
}
|
|
|
|
/* This is always the desired sample rate of the current driver. */
|
|
int RageSound::GetSampleRate() const
|
|
{
|
|
return Sample->GetSampleRate();
|
|
}
|
|
|
|
|
|
bool RageSound::SetPositionSamples( int samples )
|
|
{
|
|
if(samples == -1)
|
|
samples = m_StartSample;
|
|
|
|
/* This can take a while. Only lock the sound buffer if we're actually playing. */
|
|
LockMutex L(SOUNDMAN->lock);
|
|
if(!playing)
|
|
L.Unlock();
|
|
|
|
{
|
|
/* "position" records the number of samples we've output to the
|
|
* 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
|
|
* at 0.5x, and we're seeking to the 10th sample, we would have actually
|
|
* played 20 samples, and it's the number of real speaker samples that
|
|
* "position" represents. */
|
|
const int scaled_samples = int(samples / GetPlaybackRate());
|
|
|
|
/* If we're already there, don't do anything. */
|
|
if(position == scaled_samples)
|
|
return true;
|
|
|
|
position = scaled_samples;
|
|
}
|
|
|
|
/* The position we're going to seek the input stream to. We have
|
|
* to do this in floating point to avoid overflow. */
|
|
int ms = int(float(samples) * 1000.f / samplerate());
|
|
ms = max(ms, 0);
|
|
|
|
databuf.clear();
|
|
|
|
ASSERT(Sample);
|
|
|
|
int ret;
|
|
if(AccurateSync)
|
|
ret = Sample->SetPosition_Accurate(ms);
|
|
else
|
|
ret = Sample->SetPosition_Fast(ms);
|
|
|
|
if(ret == -1)
|
|
{
|
|
/* XXX untested */
|
|
Fail(Sample->GetError());
|
|
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. */
|
|
LOG->Warn("SetPositionSamples: %i ms is beyond EOF in %s",
|
|
ms, GetLoadedFilePath().c_str());
|
|
|
|
return false; /* failed */
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RageSound::SetPlaybackRate( float NewSpeed )
|
|
{
|
|
LockMut(SOUNDMAN->lock);
|
|
|
|
if(GetPlaybackRate() == NewSpeed)
|
|
return;
|
|
|
|
if(NewSpeed == 1.00f) {
|
|
speed_input_samples = 1; speed_output_samples = 1;
|
|
} else {
|
|
/* Approximate it to the nearest tenth. */
|
|
speed_input_samples = int(roundf(NewSpeed * 10));
|
|
speed_output_samples = 10;
|
|
}
|
|
}
|
|
|
|
void RageSound::SetFadeLength( float fSeconds )
|
|
{
|
|
fade_length = fSeconds;
|
|
}
|
|
|
|
/*
|
|
-----------------------------------------------------------------------------
|
|
Copyright (c) 2002-2003 by the person(s) listed below. All rights reserved.
|
|
Glenn Maynard
|
|
-----------------------------------------------------------------------------
|
|
*/
|