fixups and some not-yet-working rate code
This commit is contained in:
+451
-181
@@ -11,14 +11,18 @@
|
|||||||
* in large chunks, we're forced to decode in larger chunks as well, which can
|
* in large chunks, we're forced to decode in larger chunks as well, which can
|
||||||
* cause framerate problems.
|
* cause framerate problems.
|
||||||
*
|
*
|
||||||
* Known problems:
|
|
||||||
* I hear a click in one speaker at the beginning of some MP3s. This is probably
|
|
||||||
* something wrong with my SDL_sound MAD wrapper ...
|
|
||||||
*
|
|
||||||
* TODO:
|
* TODO:
|
||||||
* Rate (speed)
|
* Rate (speed)
|
||||||
* Configurable buffer sizes (stored in SoundManager) and so on
|
* 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
|
* We need (yet) another layer of abstraction: RageSoundSource. It'll just
|
||||||
* implement the SDL_sound interface (in a class). Two implementations;
|
* implement the SDL_sound interface (in a class). Two implementations;
|
||||||
* one, used normally, that just wraps SDL_sound; and another that is given
|
* one, used normally, that just wraps SDL_sound; and another that is given
|
||||||
@@ -36,19 +40,14 @@
|
|||||||
#include "RageException.h"
|
#include "RageException.h"
|
||||||
#include "RageTimer.h"
|
#include "RageTimer.h"
|
||||||
|
|
||||||
#include "SDL_sound-1.0.0/SDL_sound.h"
|
#include "RageSoundReader_SDL_Sound.h"
|
||||||
#ifdef _DEBUG
|
|
||||||
#pragma comment(lib, "SDL_sound-1.0.0/lib/sdl_sound_static_d.lib")
|
|
||||||
#else
|
|
||||||
#pragma comment(lib, "SDL_sound-1.0.0/lib/sdl_sound_static.lib")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const int channels = 2;
|
const int channels = 2;
|
||||||
const int samplesize = 2 * channels; /* 16-bit */
|
const int samplesize = 2 * channels; /* 16-bit */
|
||||||
const int samplerate = 44100;
|
const int samplerate = 44100;
|
||||||
|
|
||||||
/* If a sound is smaller than this, we'll load it entirely into memory. */
|
/* If a sound is smaller than this, we'll load it entirely into memory. */
|
||||||
const int max_prebuf_size = 1024*256;
|
const int max_prebuf_size = 0; // 1024*256;
|
||||||
|
|
||||||
/* The most data to buffer when streaming. This should generally be at least as large
|
/* The most data to buffer when streaming. This should generally be at least as large
|
||||||
* as the largest hardware buffer. */
|
* as the largest hardware buffer. */
|
||||||
@@ -62,6 +61,155 @@ const int read_block_size = 1024;
|
|||||||
* driver will have that much latency. */
|
* driver will have that much latency. */
|
||||||
const int pos_map_backlog_samples = samplerate;
|
const int pos_map_backlog_samples = samplerate;
|
||||||
|
|
||||||
|
class SpeedChanger
|
||||||
|
{
|
||||||
|
/* Number of samples for each input value: */
|
||||||
|
int compress;
|
||||||
|
|
||||||
|
/* Amount to compress the sample: */
|
||||||
|
float rate;
|
||||||
|
|
||||||
|
/* Current sample: */
|
||||||
|
Uint32 cur_sample;
|
||||||
|
|
||||||
|
/* Number of inputs for this sample (once it reaches compress, it's complete). */
|
||||||
|
int sample_inputs;
|
||||||
|
|
||||||
|
float frac;
|
||||||
|
|
||||||
|
basic_string<Uint16> samples;
|
||||||
|
|
||||||
|
basic_string<Uint16> output;
|
||||||
|
|
||||||
|
static float cub(float fm1, float f0, float f1, float f2, float x);
|
||||||
|
void move_data();
|
||||||
|
|
||||||
|
public:
|
||||||
|
SpeedChanger();
|
||||||
|
~SpeedChanger();
|
||||||
|
void Set(float speed);
|
||||||
|
void write(const Uint16 *buf, int size);
|
||||||
|
void eof(); /* no more data will be written */
|
||||||
|
unsigned read(Uint16 *buf, unsigned size);
|
||||||
|
};
|
||||||
|
|
||||||
|
SpeedChanger::SpeedChanger()
|
||||||
|
{
|
||||||
|
compress = 1;
|
||||||
|
rate = 1;
|
||||||
|
cur_sample = sample_inputs = 0;
|
||||||
|
frac = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpeedChanger::~SpeedChanger()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute f(x) with a cubic interpolation */
|
||||||
|
float SpeedChanger::cub(
|
||||||
|
float fm1, /* f(-1) */
|
||||||
|
float f0, /* f(0) */
|
||||||
|
float f1, /* f(1) */
|
||||||
|
float f2, /* f(2) */
|
||||||
|
float x) /* 0.0 <= x < 1.0 */
|
||||||
|
{
|
||||||
|
/* a x^3 + b x^2 + c x + d */
|
||||||
|
float a, b, c, d;
|
||||||
|
|
||||||
|
d = f0;
|
||||||
|
b = .5f * (f1+fm1) - f0;
|
||||||
|
a = 1/6.f * (f2-f1+fm1-f0-4.0f*b);
|
||||||
|
c = f1 - a - b - d;
|
||||||
|
|
||||||
|
return ((a * x + b) * x + c) * x + d;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
void SpeedChanger::Set(float speed)
|
||||||
|
{
|
||||||
|
if(speed >= 1.0f)
|
||||||
|
compress = int(speed);
|
||||||
|
else
|
||||||
|
compress = 1;
|
||||||
|
|
||||||
|
rate = speed / compress;
|
||||||
|
|
||||||
|
cur_sample = sample_inputs = 0;
|
||||||
|
|
||||||
|
/* previous value for interp: */
|
||||||
|
samples.insert(samples.end(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpeedChanger::write(const Uint16 *buf, int size)
|
||||||
|
{
|
||||||
|
while(size)
|
||||||
|
{
|
||||||
|
cur_sample += buf[0];
|
||||||
|
buf++;
|
||||||
|
size--;
|
||||||
|
sample_inputs++;
|
||||||
|
|
||||||
|
if(sample_inputs == compress)
|
||||||
|
move_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpeedChanger::move_data()
|
||||||
|
{
|
||||||
|
/* We have a complete sample. Reduce it to the average: */
|
||||||
|
cur_sample /= compress;
|
||||||
|
|
||||||
|
/* Add it to the sample buffer: */
|
||||||
|
samples.insert(samples.end(), Uint16(cur_sample));
|
||||||
|
sample_inputs = cur_sample = 0;
|
||||||
|
|
||||||
|
if(samples.size() != 4)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* We have enough samples to interpolate. */
|
||||||
|
while(frac < 1)
|
||||||
|
{
|
||||||
|
float interp = cub(samples[0], samples[1],
|
||||||
|
samples[2], samples[3], frac);
|
||||||
|
|
||||||
|
Uint16 clipped;
|
||||||
|
if(interp > 65535) clipped = 65535u;
|
||||||
|
else if(interp < 0) clipped = 0;
|
||||||
|
else clipped = Uint16(interp);
|
||||||
|
output.insert(output.end(), clipped);
|
||||||
|
frac += rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
frac -= 1;
|
||||||
|
samples.erase(samples.begin(), samples.begin()+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpeedChanger::eof()
|
||||||
|
{
|
||||||
|
/* Feed null until all samples are empty. */
|
||||||
|
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
Uint16 nothing = 0;
|
||||||
|
write(¬hing, 0);
|
||||||
|
|
||||||
|
int empty = true;
|
||||||
|
for(unsigned i = 0; empty && i < samples.size(); ++i)
|
||||||
|
if(samples[i]) empty = false;
|
||||||
|
if(empty) return;
|
||||||
|
move_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned SpeedChanger::read(Uint16 *buf, unsigned size)
|
||||||
|
{
|
||||||
|
size = min(size, output.size());
|
||||||
|
memcpy(buf, output.data(), size*sizeof(Uint16));
|
||||||
|
output.erase(output.begin(), output.begin() + size);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
RageSound::RageSound()
|
RageSound::RageSound()
|
||||||
{
|
{
|
||||||
ASSERT(SOUNDMAN);
|
ASSERT(SOUNDMAN);
|
||||||
@@ -79,9 +227,9 @@ RageSound::RageSound()
|
|||||||
stream.Sample = NULL;
|
stream.Sample = NULL;
|
||||||
position = 0;
|
position = 0;
|
||||||
playing = false;
|
playing = false;
|
||||||
Loop = false;
|
StopMode = M_STOP;
|
||||||
AutoStop = true;
|
|
||||||
speed = 1.0f;
|
speed = 1.0f;
|
||||||
|
speedchanger = NULL;
|
||||||
stream.buf.reserve(internal_buffer_size);
|
stream.buf.reserve(internal_buffer_size);
|
||||||
m_StartSample = 0;
|
m_StartSample = 0;
|
||||||
m_LengthSamples = -1;
|
m_LengthSamples = -1;
|
||||||
@@ -115,12 +263,12 @@ RageSound::RageSound(const RageSound &cpy)
|
|||||||
big = cpy.big;
|
big = cpy.big;
|
||||||
m_StartSample = cpy.m_StartSample;
|
m_StartSample = cpy.m_StartSample;
|
||||||
m_LengthSamples = cpy.m_LengthSamples;
|
m_LengthSamples = cpy.m_LengthSamples;
|
||||||
Loop = cpy.Loop;
|
StopMode = cpy.StopMode;
|
||||||
position = cpy.position;
|
position = cpy.position;
|
||||||
playing = false;
|
playing = false;
|
||||||
speed = cpy.speed;
|
speed = 1;
|
||||||
|
speedchanger = NULL;
|
||||||
AccurateSync = cpy.AccurateSync;
|
AccurateSync = cpy.AccurateSync;
|
||||||
AutoStop = cpy.AutoStop;
|
|
||||||
|
|
||||||
if(big)
|
if(big)
|
||||||
{
|
{
|
||||||
@@ -130,6 +278,9 @@ RageSound::RageSound(const RageSound &cpy)
|
|||||||
Load(cpy.GetLoadedFilePath(), false);
|
Load(cpy.GetLoadedFilePath(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(cpy.speed != 1)
|
||||||
|
SetPlaybackRate(cpy.speed);
|
||||||
|
|
||||||
/* Load() won't work on a copy if m_sFilePath is already set, so
|
/* Load() won't work on a copy if m_sFilePath is already set, so
|
||||||
* copy this down here. */
|
* copy this down here. */
|
||||||
m_sFilePath = cpy.m_sFilePath;
|
m_sFilePath = cpy.m_sFilePath;
|
||||||
@@ -143,16 +294,40 @@ void RageSound::Unload()
|
|||||||
if(IsPlaying())
|
if(IsPlaying())
|
||||||
StopPlaying();
|
StopPlaying();
|
||||||
|
|
||||||
Sound_FreeSample(stream.Sample);
|
delete stream.Sample;
|
||||||
stream.Sample = NULL;
|
stream.Sample = NULL;
|
||||||
|
|
||||||
|
delete speedchanger;
|
||||||
|
speedchanger = NULL;
|
||||||
|
|
||||||
m_sFilePath = "";
|
m_sFilePath = "";
|
||||||
stream.buf.clear();
|
stream.buf.clear();
|
||||||
|
|
||||||
full_buf.erase();
|
full_buf.erase();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RageSound::Load(CString sSoundFilePath, bool cache)
|
/* This is called upon fatal failure. Replace the sound with silence. */
|
||||||
|
void RageSound::Fail(CString reason)
|
||||||
|
{
|
||||||
|
delete stream.Sample;
|
||||||
|
stream.Sample = NULL;
|
||||||
|
|
||||||
|
big = false;
|
||||||
|
|
||||||
|
full_buf.erase();
|
||||||
|
/* XXX untested
|
||||||
|
* full_buf.append(0, 1024); should be OK, but VC6 is broken ... */
|
||||||
|
basic_string<char> empty(1024, 0);
|
||||||
|
full_buf.insert(full_buf.end(), empty.begin(), empty.end());
|
||||||
|
position = 0;
|
||||||
|
|
||||||
|
LOG->Warn("Decoding %s failed: %s",
|
||||||
|
GetLoadedFilePath().GetString(), reason.GetString() );
|
||||||
|
|
||||||
|
error = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RageSound::Load(CString sSoundFilePath, bool cache)
|
||||||
{
|
{
|
||||||
LOG->Trace( "RageSound::LoadSound( '%s' )", sSoundFilePath.GetString() );
|
LOG->Trace( "RageSound::LoadSound( '%s' )", sSoundFilePath.GetString() );
|
||||||
|
|
||||||
@@ -168,12 +343,8 @@ void RageSound::Load(CString sSoundFilePath, bool cache)
|
|||||||
sound_desired.format = AUDIO_S16SYS;
|
sound_desired.format = AUDIO_S16SYS;
|
||||||
sound_desired.rate = samplerate;
|
sound_desired.rate = samplerate;
|
||||||
|
|
||||||
Sound_Sample *NewSample = Sound_NewSampleFromFile(sSoundFilePath.GetString(),
|
SoundReader *NewSample = new SoundReader_SDL_Sound;
|
||||||
&sound_desired, read_block_size);
|
NewSample->Open(sSoundFilePath.GetString());
|
||||||
|
|
||||||
if( NewSample == NULL )
|
|
||||||
RageException::Throw( "RageSound::LoadSound: error loading %s: %s",
|
|
||||||
sSoundFilePath.GetString(), Sound_GetError() );
|
|
||||||
|
|
||||||
/* Try to decode into full_buf. */
|
/* Try to decode into full_buf. */
|
||||||
big = false;
|
big = false;
|
||||||
@@ -182,10 +353,9 @@ void RageSound::Load(CString sSoundFilePath, bool cache)
|
|||||||
|
|
||||||
/* Check the length, and see if we think it'll fit in the buffer. */
|
/* Check the length, and see if we think it'll fit in the buffer. */
|
||||||
{
|
{
|
||||||
int len = Sound_Length(NewSample);
|
int len = NewSample->GetLength_Fast();
|
||||||
/* This will fail with EAGAIN if it'll take a while. We only want
|
if(len != -1)
|
||||||
* to do this if it's fast. */
|
{
|
||||||
if(len != -1) {
|
|
||||||
float secs = len / 1000.f;
|
float secs = len / 1000.f;
|
||||||
|
|
||||||
int pcmsize = int(secs * samplerate * samplesize); /* seconds -> bytes */
|
int pcmsize = int(secs * samplerate * samplesize); /* seconds -> bytes */
|
||||||
@@ -194,40 +364,41 @@ void RageSound::Load(CString sSoundFilePath, bool cache)
|
|||||||
else
|
else
|
||||||
full_buf.reserve(pcmsize);
|
full_buf.reserve(pcmsize);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sound_Rewind(NewSample);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while(!big) {
|
while(!big) {
|
||||||
int cnt = Sound_Decode(NewSample);
|
char buf[1024];
|
||||||
|
int cnt = NewSample->Read(buf, sizeof(buf));
|
||||||
|
|
||||||
if(cnt < 0)
|
if(cnt < 0) {
|
||||||
RageException::Throw("Read error on %s: %s",
|
/* XXX untested */
|
||||||
sSoundFilePath.GetString(), Sound_GetError() ); /* XXX (see other error-handling XXX) */
|
Fail(Sound_GetError());
|
||||||
|
delete NewSample;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!cnt) break; /* eof */
|
||||||
|
|
||||||
/* Add the buffer. */
|
/* Add the buffer. */
|
||||||
full_buf.append((const char *)NewSample->buffer,
|
full_buf.append(buf, buf+cnt);
|
||||||
(const char *)NewSample->buffer+cnt);
|
|
||||||
|
|
||||||
if(full_buf.size() > max_prebuf_size) {
|
if(full_buf.size() > max_prebuf_size) {
|
||||||
full_buf.erase();
|
full_buf.erase();
|
||||||
big = true; /* too big */
|
big = true; /* too big */
|
||||||
}
|
}
|
||||||
|
|
||||||
if(NewSample->flags & SOUND_SAMPLEFLAG_EOF)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(big) {
|
if(big) {
|
||||||
/* Oops; we need to stream it. */
|
/* Oops; we need to stream it. */
|
||||||
stream.Sample = NewSample;
|
stream.Sample = NewSample;
|
||||||
Sound_Rewind(stream.Sample);
|
stream.Sample->SetPosition_Accurate(0);
|
||||||
} else {
|
} else {
|
||||||
/* We're done with the stream. */
|
/* We're done with the stream. */
|
||||||
Sound_FreeSample(NewSample);
|
delete NewSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
position = 0;
|
position = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RageSound::SetStartSeconds( float secs )
|
void RageSound::SetStartSeconds( float secs )
|
||||||
@@ -247,45 +418,109 @@ void RageSound::SetLengthSeconds(float secs)
|
|||||||
void RageSound::Update(float delta)
|
void RageSound::Update(float delta)
|
||||||
{
|
{
|
||||||
if(playing && big && delta)
|
if(playing && big && delta)
|
||||||
stream.FillBuf(int(delta * samplerate * samplesize));
|
FillBuf(int(delta * samplerate * samplesize));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the number of bytes available in the input buffer. */
|
||||||
|
int RageSound::Bytes_Available() const
|
||||||
|
{
|
||||||
|
if(big)
|
||||||
|
return stream.buf.size();
|
||||||
|
|
||||||
|
unsigned byte_pos = position * samplesize; /* samples -> bytes */
|
||||||
|
if(byte_pos > full_buf.size())
|
||||||
|
return 0; /* eof */
|
||||||
|
|
||||||
|
return full_buf.size() - byte_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fill the buffer by about "bytes" worth of data. (We might go a little
|
/* Fill the buffer by about "bytes" worth of data. (We might go a little
|
||||||
* over, and we won't overflow our buffer.) */
|
* over, and we won't overflow our buffer.) Return the number of bytes
|
||||||
int RageSound::stream_t::FillBuf(int bytes)
|
* actually read; 0 = EOF. */
|
||||||
|
int RageSound::FillBuf(int bytes)
|
||||||
{
|
{
|
||||||
LockMut(SOUNDMAN->lock);
|
LockMut(SOUNDMAN->lock);
|
||||||
|
|
||||||
ASSERT(Sample);
|
if(!big)
|
||||||
|
return 0; /* prebuffer is already fully loaded */
|
||||||
|
|
||||||
|
ASSERT(stream.Sample);
|
||||||
|
|
||||||
bool got_something = false;
|
bool got_something = false;
|
||||||
if(Sample->flags & SOUND_SAMPLEFLAG_EOF)
|
|
||||||
return got_something; /* EOF */
|
bool at_eof = false;
|
||||||
|
|
||||||
while(bytes > 0)
|
while(bytes > 0)
|
||||||
{
|
{
|
||||||
if(buf.size()+read_block_size > buf.capacity())
|
if(read_block_size > stream.buf.capacity() - stream.buf.size())
|
||||||
break; /* full */
|
break; /* full */
|
||||||
|
|
||||||
int cnt = Sound_Decode(Sample);
|
char inbuf[1024];
|
||||||
if(Sample->flags & SOUND_SAMPLEFLAG_EOF)
|
int cnt = 0;
|
||||||
return got_something; /* EOF */
|
if(speedchanger)
|
||||||
|
|
||||||
if(Sample->flags & SOUND_SAMPLEFLAG_ERROR)
|
|
||||||
{
|
{
|
||||||
/* There was a fatal error; get it with Sound_GetError().
|
unsigned input_size = stream.buf.capacity() - stream.buf.size();
|
||||||
* XXX: How should we handle sound errors? We can't
|
input_size = min(input_size, sizeof(inbuf));
|
||||||
* just return error, since we're in a separate thread.
|
input_size /= sizeof(Uint16);
|
||||||
* Most of the time we should probably just warn and move
|
|
||||||
* on (no big deal), but the gameplay screen should query
|
cnt = speedchanger->read((Uint16 *) inbuf, input_size);
|
||||||
* periodically and do something more intelligent when we
|
cnt *= sizeof(Uint16);
|
||||||
* fail (so we don't play out the rest of the song in
|
|
||||||
* silence) ... */
|
stream.buf.write((const char *) inbuf, cnt);
|
||||||
RageException::Throw("Read error: %s",
|
bytes -= cnt;
|
||||||
Sound_GetError() );
|
|
||||||
|
if(cnt)
|
||||||
|
got_something = true;
|
||||||
|
|
||||||
|
if(!cnt)
|
||||||
|
{
|
||||||
|
/* Read input data. */
|
||||||
|
cnt = stream.Sample->Read(inbuf, sizeof(inbuf));
|
||||||
|
if(cnt == -1)
|
||||||
|
{
|
||||||
|
/* XXX untested */
|
||||||
|
Fail(Sound_GetError());
|
||||||
|
|
||||||
|
/* Pretend we got data; we actually just switched to a non-streaming
|
||||||
|
* buffer. */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cnt == 0)
|
||||||
|
{
|
||||||
|
if(!at_eof)
|
||||||
|
{
|
||||||
|
at_eof = true;
|
||||||
|
speedchanger->eof();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Really EOF. */
|
||||||
|
speedchanger->eof();
|
||||||
|
return got_something; /* EOF */
|
||||||
|
}
|
||||||
|
|
||||||
|
speedchanger->write((const Uint16 *) inbuf, cnt/2);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.write((const char *)Sample->buffer, cnt);
|
cnt = stream.Sample->Read(inbuf, sizeof(inbuf));
|
||||||
|
if(cnt == 0)
|
||||||
|
return got_something; /* EOF */
|
||||||
|
|
||||||
|
if(cnt == -1)
|
||||||
|
{
|
||||||
|
/* XXX untested */
|
||||||
|
Fail(Sound_GetError());
|
||||||
|
|
||||||
|
/* Pretend we got data; we actually just switched to a non-streaming
|
||||||
|
* buffer. */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add the data to the buffer. */
|
||||||
|
stream.buf.write((const char *) inbuf, cnt);
|
||||||
bytes -= cnt;
|
bytes -= cnt;
|
||||||
got_something = true;
|
got_something = true;
|
||||||
}
|
}
|
||||||
@@ -293,6 +528,47 @@ int RageSound::stream_t::FillBuf(int bytes)
|
|||||||
return got_something;
|
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 if(big) {
|
||||||
|
/* Feed data out of our streaming buffer. */
|
||||||
|
ASSERT(stream.Sample);
|
||||||
|
got = min(int(stream.buf.size()), size);
|
||||||
|
if(buffer)
|
||||||
|
stream.buf.read(buffer, got);
|
||||||
|
} else {
|
||||||
|
/* Feed data out of our full buffer. */
|
||||||
|
int byte_pos = position * samplesize;
|
||||||
|
got = min(int(full_buf.size())-byte_pos, size);
|
||||||
|
got = max(got, 0);
|
||||||
|
if(buffer)
|
||||||
|
memcpy(buffer, full_buf.data()+byte_pos, got);
|
||||||
|
}
|
||||||
|
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
/* Called by the mixer: return a block of sound data.
|
/* Called by the mixer: return a block of sound data.
|
||||||
* Be careful; this is called in a separate thread. */
|
* Be careful; this is called in a separate thread. */
|
||||||
int RageSound::GetPCM(char *buffer, int size, int sampleno)
|
int RageSound::GetPCM(char *buffer, int size, int sampleno)
|
||||||
@@ -301,96 +577,59 @@ int RageSound::GetPCM(char *buffer, int size, int sampleno)
|
|||||||
|
|
||||||
ASSERT(playing);
|
ASSERT(playing);
|
||||||
|
|
||||||
int bytes_stored = 0;
|
|
||||||
|
|
||||||
/* Erase old pos_map data. */
|
/* Erase old pos_map data. */
|
||||||
while(pos_map.size() > 1 && pos_map.back().sampleno - pos_map.front().sampleno > pos_map_backlog_samples)
|
while(pos_map.size() > 1 && pos_map.back().sampleno - pos_map.front().sampleno > pos_map_backlog_samples)
|
||||||
pos_map.pop_front();
|
pos_map.pop_front();
|
||||||
|
|
||||||
/* "sampleno" is the audio driver's conception of time. "position"
|
/*
|
||||||
* is ours. Keep track of sampleno->position mappings for two GetPCM calls.
|
* "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
|
* This way, when we query the time later on, we can derive position
|
||||||
* values from the sampleno values returned from GetPosition.
|
* values from the sampleno values returned from GetPosition.
|
||||||
*
|
*/
|
||||||
* We need to keep two buffers worth of values, since we might loop at
|
|
||||||
* the end of a buffer. */
|
|
||||||
|
|
||||||
/* Now actually put data from the correct buffer into the output. */
|
/* Now actually put data from the correct buffer into the output. */
|
||||||
|
int bytes_stored = 0;
|
||||||
while(size)
|
while(size)
|
||||||
{
|
{
|
||||||
int got;
|
/* Get a block of data. */
|
||||||
int MaxBytes = size;
|
int got = GetData(buffer, 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. */
|
|
||||||
MaxBytes = clamp(SamplesToRead * samplesize, 0, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, MaxBytes);
|
|
||||||
memset(buffer, 0, got);
|
|
||||||
} else if(big) {
|
|
||||||
/* Feed data out of our streaming buffer. */
|
|
||||||
ASSERT(stream.Sample);
|
|
||||||
got = min(int(stream.buf.size()), MaxBytes);
|
|
||||||
stream.buf.read(buffer, got);
|
|
||||||
} else {
|
|
||||||
/* Feed data out of our full buffer. */
|
|
||||||
int byte_pos = position * samplesize;
|
|
||||||
got = min(int(full_buf.size())-byte_pos, MaxBytes);
|
|
||||||
got = max(got, 0);
|
|
||||||
memcpy(buffer, full_buf.data()+byte_pos, got);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!got)
|
if(!got)
|
||||||
{
|
{
|
||||||
/* We need more data. Find out if we've hit EOF. */
|
/* If we don't have any data left buffered, fill the buffer by
|
||||||
bool HitEOF = true;
|
* up to as much as we need. */
|
||||||
if(big) {
|
if(!Bytes_Available())
|
||||||
/* If we don't have any data left buffered, fill the buffer by up to
|
FillBuf(size);
|
||||||
* as much as we need. */
|
|
||||||
if(stream.buf.size() || stream.FillBuf(size))
|
|
||||||
HitEOF = false; /* we have more */
|
|
||||||
} else {
|
|
||||||
unsigned byte_pos = position * samplesize; /* samples -> bytes */
|
|
||||||
if(byte_pos < full_buf.size())
|
|
||||||
HitEOF = false; /* we have more */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we've passed the stop point (m_StartSample+m_LengthSamples), pretend
|
/* If we got some data, we're OK. */
|
||||||
* we've hit EOF. */
|
if(GetData(NULL, size) != 0)
|
||||||
if(m_LengthSamples != -1 &&
|
continue; /* we have more */
|
||||||
position >= m_StartSample+m_LengthSamples)
|
|
||||||
HitEOF = true;
|
|
||||||
|
|
||||||
if(!HitEOF)
|
/* We're at the end of the data. If we're looping, rewind and restart. */
|
||||||
continue;
|
if(StopMode == M_LOOP)
|
||||||
|
|
||||||
if(Loop && m_LengthSamples == 0)
|
|
||||||
{
|
|
||||||
/* Oops. Looping with seconds == 0 doesn't make much sense.
|
|
||||||
* It might happen if we're given an empty sound file as input,
|
|
||||||
* though. Let's just stop. */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We're at EOF. If we're not looping, just stop. */
|
|
||||||
if(Loop && m_LengthSamples != 0)
|
|
||||||
{
|
{
|
||||||
/* Rewind and start over. */
|
/* Rewind and start over. */
|
||||||
SetPositionSamples(m_StartSample);
|
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().GetString(), m_StartSample);
|
||||||
|
|
||||||
|
/* Stop here. */
|
||||||
|
return bytes_stored;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(AutoStop)
|
|
||||||
|
/* Not looping. Normally, we'll just stop here. */
|
||||||
|
if(StopMode == M_STOP)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* We're out of data, but we're not going to stop, so fill in the
|
/* We're out of data, but we're not going to stop, so fill in the
|
||||||
@@ -399,12 +638,12 @@ int RageSound::GetPCM(char *buffer, int size, int sampleno)
|
|||||||
got = size;
|
got = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Save this sampleno/position map. */
|
/* This block goes from position to position+got_samples. */
|
||||||
pos_map.push_back(pos_map_t(sampleno, position, got/samplesize));
|
|
||||||
|
|
||||||
int got_samples = got / samplesize; /* bytes -> samples */
|
int got_samples = got / samplesize; /* bytes -> samples */
|
||||||
|
|
||||||
/* This block goes from position to position+got_samples. */
|
/* Save this sampleno/position map. */
|
||||||
|
pos_map.push_back(pos_map_t(sampleno, position, got_samples));
|
||||||
|
|
||||||
const float FADE_TIME = 1.5f;
|
const float FADE_TIME = 1.5f;
|
||||||
|
|
||||||
/* XXX: Loop shouldn't set fading; add a Fade_Time member?
|
/* XXX: Loop shouldn't set fading; add a Fade_Time member?
|
||||||
@@ -413,7 +652,7 @@ int RageSound::GetPCM(char *buffer, int size, int sampleno)
|
|||||||
* m_LengthSamples is -1, we don't know the length we're playing.
|
* 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
|
* (m_LengthSamples is the length to play, not the length of the
|
||||||
* source.) If we don't know the length, don't fade. */
|
* source.) If we don't know the length, don't fade. */
|
||||||
if(Loop && m_LengthSamples != -1) {
|
if(StopMode == M_LOOP && m_LengthSamples != -1) {
|
||||||
Sint16 *p = (Sint16 *) buffer;
|
Sint16 *p = (Sint16 *) buffer;
|
||||||
int this_position = position;
|
int this_position = position;
|
||||||
|
|
||||||
@@ -431,7 +670,6 @@ int RageSound::GetPCM(char *buffer, int size, int sampleno)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bytes_stored += got;
|
bytes_stored += got;
|
||||||
position += got_samples;
|
position += got_samples;
|
||||||
size -= got;
|
size -= got;
|
||||||
@@ -450,7 +688,7 @@ void RageSound::StartPlaying()
|
|||||||
|
|
||||||
ASSERT(!playing);
|
ASSERT(!playing);
|
||||||
|
|
||||||
// Tell the sound manager to start mixing us
|
/* Tell the sound manager to start mixing us. */
|
||||||
playing = true;
|
playing = true;
|
||||||
SOUNDMAN->StartMixing(this);
|
SOUNDMAN->StartMixing(this);
|
||||||
}
|
}
|
||||||
@@ -479,16 +717,14 @@ float RageSound::GetLengthSeconds()
|
|||||||
{
|
{
|
||||||
if(big) {
|
if(big) {
|
||||||
ASSERT(stream.Sample);
|
ASSERT(stream.Sample);
|
||||||
|
int len = stream.Sample->GetLength();
|
||||||
int len = Sound_Length(stream.Sample);
|
|
||||||
if(len == -1 && stream.Sample->flags & SOUND_SAMPLEFLAG_EAGAIN) {
|
|
||||||
/* This indicates the length check will take a little while; call
|
|
||||||
* it again to confirm. */
|
|
||||||
len = Sound_Length(stream.Sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(len < 0)
|
if(len < 0)
|
||||||
return -1; /* XXX: put a Sound_GetError() error message somewhere */
|
{
|
||||||
|
LOG->Warn("GetLengthSeconds failed on %s: %s",
|
||||||
|
GetLoadedFilePath().GetString(), stream.Sample->GetError().c_str() );
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return len / 1000.f; /* ms -> secs */
|
return len / 1000.f; /* ms -> secs */
|
||||||
} else {
|
} else {
|
||||||
@@ -503,14 +739,14 @@ float RageSound::GetPositionSeconds() const
|
|||||||
|
|
||||||
/* If we're not playing, just report the static position. */
|
/* If we're not playing, just report the static position. */
|
||||||
if( !IsPlaying() )
|
if( !IsPlaying() )
|
||||||
return position / float(samplerate);
|
return speed * position / float(samplerate);
|
||||||
|
|
||||||
/* If we don't yet have any position data, GetPCM hasn't yet been called at all,
|
/* If we don't yet have any position data, GetPCM hasn't yet been called at all,
|
||||||
* so report the static position. */
|
* so report the static position. */
|
||||||
{
|
{
|
||||||
if(pos_map.empty()) {
|
if(pos_map.empty()) {
|
||||||
LOG->Trace("no data yet; %i", position);
|
LOG->Trace("no data yet; %i", position);
|
||||||
return position / float(samplerate);
|
return speed * position / float(samplerate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +764,7 @@ float RageSound::GetPositionSeconds() const
|
|||||||
/* cur_sample lies in this block; it's an exact match. Figure
|
/* cur_sample lies in this block; it's an exact match. Figure
|
||||||
* out the exact position. */
|
* out the exact position. */
|
||||||
int diff = pos_map[i].position - pos_map[i].sampleno;
|
int diff = pos_map[i].position - pos_map[i].sampleno;
|
||||||
return float(cur_sample + diff) / samplerate;
|
return speed * float(cur_sample + diff) / samplerate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* See if the current position is close to the beginning of this block. */
|
/* See if the current position is close to the beginning of this block. */
|
||||||
@@ -558,60 +794,94 @@ float RageSound::GetPositionSeconds() const
|
|||||||
* 3. Underflow; we'll be given a larger sample number than we know about.
|
* 3. Underflow; we'll be given a larger sample number than we know about.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return closest_position / float(samplerate);
|
return speed * closest_position / float(samplerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RageSound::SetPositionSeconds( float fSeconds )
|
bool RageSound::SetPositionSeconds( float fSeconds )
|
||||||
{
|
{
|
||||||
SetPositionSamples( fSeconds == -1? -1: int(fSeconds * samplerate) );
|
return SetPositionSamples( fSeconds == -1? -1: int(fSeconds * samplerate) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void RageSound::SetPositionSamples( int samples )
|
bool RageSound::SetPositionSamples( int samples )
|
||||||
{
|
{
|
||||||
if(samples == -1)
|
if(samples == -1)
|
||||||
samples = m_StartSample;
|
samples = m_StartSample;
|
||||||
|
|
||||||
/* This can take a while. Only lock the sound buffer if we're actually playing. */
|
/* This can take a while. Only lock the sound buffer if we're actually playing. */
|
||||||
SOUNDMAN->lock.Lock();
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
|
||||||
|
/* This can take a while. Only hold the sound buffer if we're actually playing. */
|
||||||
if(!playing)
|
if(!playing)
|
||||||
{
|
L.Unlock();
|
||||||
SOUNDMAN->lock.Unlock();
|
|
||||||
/* If we're already there, don't do anything. */
|
/* If we're already there, don't do anything. */
|
||||||
if(position == samples)
|
if(position == samples)
|
||||||
return;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
position = samples;
|
position = samples;
|
||||||
if( samples < 0 )
|
if( samples < 0 )
|
||||||
samples = 0;
|
samples = 0;
|
||||||
|
|
||||||
if(big) {
|
int ms = int(float(samples) * 1000.f / samplerate);
|
||||||
ASSERT(stream.Sample);
|
|
||||||
int ms = int(float(samples) * 1000.f / samplerate);
|
|
||||||
|
|
||||||
if(ms == 0)
|
if(!big) {
|
||||||
Sound_Rewind(stream.Sample);
|
/* Just make sure the position is in range. */
|
||||||
else if(AccurateSync)
|
if(position*samplesize < int(full_buf.size()))
|
||||||
Sound_AccurateSeek(stream.Sample, ms);
|
return true;
|
||||||
else
|
|
||||||
{
|
/* We were told to seek beyond EOF. This could be a truncated file
|
||||||
RageTimer tm;
|
* or invalid data. Warn about it and jump back to the beginning. */
|
||||||
Sound_FastSeek(stream.Sample, ms);
|
LOG->Warn("SetPositionSamples: %i ms is beyond EOF in %s",
|
||||||
LOG->Trace("%f", tm.GetDeltaTime());
|
ms, GetLoadedFilePath().GetString());
|
||||||
}
|
position = 0;
|
||||||
stream.buf.clear();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(playing)
|
stream.buf.clear();
|
||||||
SOUNDMAN->lock.Unlock();
|
|
||||||
|
ASSERT(stream.Sample);
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
if(AccurateSync)
|
||||||
|
ret = stream.Sample->SetPosition_Accurate(ms);
|
||||||
|
else
|
||||||
|
ret = stream.Sample->SetPosition_Fast(ms);
|
||||||
|
|
||||||
|
if(ret == -1)
|
||||||
|
{
|
||||||
|
/* XXX untested */
|
||||||
|
Fail(Sound_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. Warn
|
||||||
|
* about it and jump back to the beginning. */
|
||||||
|
LOG->Warn("SetPositionSamples: %i ms is beyond EOF in %s",
|
||||||
|
ms, GetLoadedFilePath().GetString());
|
||||||
|
|
||||||
|
position = 0;
|
||||||
|
return false; /* failed (but recoverable) */
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RageSound::SetPlaybackRate( float fScale )
|
void RageSound::SetPlaybackRate( float NewSpeed )
|
||||||
{
|
{
|
||||||
LockMut(SOUNDMAN->lock);
|
LockMut(SOUNDMAN->lock);
|
||||||
|
|
||||||
speed = fScale;
|
/* Scale the position to the new scale. XXX untested */
|
||||||
|
position *= int(speed / NewSpeed);
|
||||||
|
|
||||||
|
speed = NewSpeed;
|
||||||
|
|
||||||
|
if(!speedchanger)
|
||||||
|
speedchanger = new SpeedChanger;
|
||||||
|
|
||||||
|
speedchanger->Set(speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is used to start music. It probably belongs in RageSoundManager. */
|
/* This is used to start music. It probably belongs in RageSoundManager. */
|
||||||
@@ -629,7 +899,7 @@ void RageSound::LoadAndPlayIfNotAlready( CString sSoundFilePath )
|
|||||||
SetStartSeconds();
|
SetStartSeconds();
|
||||||
SetLengthSeconds();
|
SetLengthSeconds();
|
||||||
SetPositionSamples();
|
SetPositionSamples();
|
||||||
SetLooping();
|
SetStopMode(M_LOOP);
|
||||||
StartPlaying();
|
StartPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+65
-56
@@ -1,8 +1,6 @@
|
|||||||
#ifndef RAGE_SOUND_OBJ_H
|
#ifndef RAGE_SOUND_OBJ_H
|
||||||
#define RAGE_SOUND_OBJ_H
|
#define RAGE_SOUND_OBJ_H
|
||||||
|
|
||||||
#include "SDL_sound-1.0.0/SDL_sound.h"
|
|
||||||
|
|
||||||
#include "RageThreads.h"
|
#include "RageThreads.h"
|
||||||
#include "RageSoundManager.h"
|
#include "RageSoundManager.h"
|
||||||
|
|
||||||
@@ -24,18 +22,75 @@ public:
|
|||||||
void read(char *buf, unsigned size);
|
void read(char *buf, unsigned size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SoundReader;
|
||||||
|
class SpeedChanger;
|
||||||
|
|
||||||
class RageSound
|
class RageSound
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
/* M_STOP (default) stops the sound after m_LengthSamples have been played.
|
||||||
|
* M_LOOP restarts from m_StartSample.
|
||||||
|
* M_CONTINUE feeds silence, which is useful to continue timing longer than the
|
||||||
|
* actual sound. */
|
||||||
|
enum StopMode_t {
|
||||||
|
M_STOP, M_LOOP, M_CONTINUE
|
||||||
|
} StopMode;
|
||||||
|
|
||||||
|
RageSound();
|
||||||
|
~RageSound();
|
||||||
|
RageSound(const RageSound &cpy);
|
||||||
|
|
||||||
|
/* If cache is true, we'll preload the entire file into memory if it's
|
||||||
|
* small enough. False is only generally used when we're going to do
|
||||||
|
* operations on a file but not actually play it (eg. to find out
|
||||||
|
* its length).
|
||||||
|
*
|
||||||
|
* If the file failed to load, false is returned, Error() is set
|
||||||
|
* and a null sample will be loaded. (This makes failed loads nonfatal;
|
||||||
|
* they can be ignored most of the time, so we continue to work if a file
|
||||||
|
* is broken or missing.) */
|
||||||
|
bool Load(CString fn, bool cache = true);
|
||||||
|
|
||||||
|
void LoadAndPlayIfNotAlready( CString sSoundFilePath );
|
||||||
|
void Unload();
|
||||||
|
|
||||||
|
void SetStopMode(StopMode_t m) { StopMode = m; }
|
||||||
|
StopMode_t GetStopMode() const { return StopMode; }
|
||||||
|
|
||||||
|
void SetStartSeconds(float secs = 0); /* default = beginning */
|
||||||
|
void SetLengthSeconds(float secs = -1); /* default = no length limit */
|
||||||
|
void StartPlaying();
|
||||||
|
void StopPlaying();
|
||||||
|
|
||||||
|
CString GetError() const { return error; }
|
||||||
|
bool Error() const { return !error.empty(); }
|
||||||
|
|
||||||
|
RageSound *Play();
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
float GetLengthSeconds();
|
||||||
|
float GetPositionSeconds() const;
|
||||||
|
bool SetPositionSeconds( float fSeconds = -1);
|
||||||
|
void SetAccurateSync(bool yes=true) { AccurateSync = yes; }
|
||||||
|
void SetPlaybackRate( float fScale );
|
||||||
|
float GetPlaybackRate() const { return speed; }
|
||||||
|
bool IsPlaying() const { return playing; }
|
||||||
|
CString GetLoadedFilePath() const { return m_sFilePath; }
|
||||||
|
|
||||||
|
/* Query only: */
|
||||||
|
bool IsStreaming() const { return big; }
|
||||||
|
|
||||||
|
private:
|
||||||
/* If we were copied from another RageSound, this will point to it; otherwise
|
/* If we were copied from another RageSound, this will point to it; otherwise
|
||||||
* this is ourself. */
|
* this is ourself. */
|
||||||
RageSound *original;
|
RageSound *original;
|
||||||
|
|
||||||
/* These are only used when big == true: */
|
/* These are only used when big == true: */
|
||||||
struct stream_t {
|
struct stream_t {
|
||||||
Sound_Sample *Sample;
|
SoundReader *Sample;
|
||||||
int FillBuf(int bytes);
|
|
||||||
CircBuf buf;
|
CircBuf buf;
|
||||||
} stream;
|
} stream;
|
||||||
|
int FillBuf(int bytes);
|
||||||
|
|
||||||
/* These are only used when big == false: */
|
/* These are only used when big == false: */
|
||||||
basic_string<char> full_buf;
|
basic_string<char> full_buf;
|
||||||
@@ -61,11 +116,9 @@ class RageSound
|
|||||||
deque<pos_map_t> pos_map;
|
deque<pos_map_t> pos_map;
|
||||||
|
|
||||||
CString m_sFilePath;
|
CString m_sFilePath;
|
||||||
// float m_Rate;
|
|
||||||
|
|
||||||
/* The amount of data to play (or loop): */
|
/* The amount of data to play (or loop): */
|
||||||
int m_StartSample, m_LengthSamples;
|
int m_StartSample, m_LengthSamples;
|
||||||
bool Loop;
|
|
||||||
|
|
||||||
/* Current position of the output sound; if < 0, nothing will play until it
|
/* Current position of the output sound; if < 0, nothing will play until it
|
||||||
* becomes positive. This is recorded in samples, to avoid rounding error. */
|
* becomes positive. This is recorded in samples, to avoid rounding error. */
|
||||||
@@ -73,23 +126,18 @@ class RageSound
|
|||||||
bool playing;
|
bool playing;
|
||||||
|
|
||||||
float speed;
|
float speed;
|
||||||
|
SpeedChanger *speedchanger; /* only if speed != 1 */
|
||||||
|
|
||||||
bool AccurateSync;
|
bool AccurateSync;
|
||||||
|
|
||||||
/* If true, the sound will stop when it reaches the end; otherwise it'll
|
CString error;
|
||||||
* continue to move forward, feeding silence, which is useful to continue
|
|
||||||
* timing longer than the actual sound. (However, if this is false, the
|
|
||||||
* sound will never stop on its own unless destructed; it must be explitly
|
|
||||||
* stopped, and PlayCopy can't be used.) Default is true. Ignored when looping. */
|
|
||||||
bool AutoStop;
|
|
||||||
|
|
||||||
void SetPositionSamples( int samples = -1 );
|
bool SetPositionSamples( int samples = -1 );
|
||||||
|
int GetData(char *buffer, int size);
|
||||||
|
void Fail(CString reason);
|
||||||
|
int Bytes_Available() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RageSound();
|
|
||||||
~RageSound();
|
|
||||||
RageSound(const RageSound &cpy);
|
|
||||||
|
|
||||||
/* Used by RageSoundManager: */
|
/* Used by RageSoundManager: */
|
||||||
RageSound *GetOriginal() { return original; }
|
RageSound *GetOriginal() { return original; }
|
||||||
|
|
||||||
@@ -101,45 +149,6 @@ public:
|
|||||||
int GetPCM(char *buffer, int size, int sampleno);
|
int GetPCM(char *buffer, int size, int sampleno);
|
||||||
|
|
||||||
void Update(float delta);
|
void Update(float delta);
|
||||||
|
|
||||||
/* User API from here on: */
|
|
||||||
|
|
||||||
/* If cache is true, we'll preload the entire file into memory if it's
|
|
||||||
* small enough. False is only generally used when we're going to do
|
|
||||||
* operations on a file but not actually play it (eg. to find out
|
|
||||||
* its length). */
|
|
||||||
void Load(CString fn, bool cache = true);
|
|
||||||
|
|
||||||
void LoadAndPlayIfNotAlready( CString sSoundFilePath );
|
|
||||||
void Unload();
|
|
||||||
|
|
||||||
/* If enabled, then the sound will automatically stop when it reaches
|
|
||||||
* the end; otherwise it'll feed silence until stopped manually. */
|
|
||||||
void SetAutoStop(bool yes=true) { AutoStop=yes; }
|
|
||||||
bool GetAutoStop() const { return AutoStop; }
|
|
||||||
|
|
||||||
void SetStartSeconds(float secs = 0); /* default = beginning */
|
|
||||||
void SetLengthSeconds(float secs = -1); /* default = no length limit */
|
|
||||||
void StartPlaying();
|
|
||||||
// void Pause();
|
|
||||||
void StopPlaying();
|
|
||||||
|
|
||||||
RageSound *Play();
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
float GetLengthSeconds();
|
|
||||||
float GetPositionSeconds() const;
|
|
||||||
void SetPositionSeconds( float fSeconds = -1);
|
|
||||||
void SetAccurateSync(bool yes=true) { AccurateSync = yes; }
|
|
||||||
void SetPlaybackRate( float fScale );
|
|
||||||
float GetPlaybackRate() const { return speed; }
|
|
||||||
bool IsPlaying() const { return playing; }
|
|
||||||
CString GetLoadedFilePath() const { return m_sFilePath; }
|
|
||||||
void SetLooping(bool yes=true) { Loop=yes; }
|
|
||||||
bool GetLooping() const { return Loop; }
|
|
||||||
|
|
||||||
/* Query only: */
|
|
||||||
bool IsStreaming() const { return big; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "arch/arch.h"
|
#include "arch/arch.h"
|
||||||
#include "arch/Sound/RageSoundDriver.h"
|
#include "arch/Sound/RageSoundDriver.h"
|
||||||
|
#include "SDL_audio.h"
|
||||||
|
|
||||||
RageSoundManager::RageSoundManager(CString drivers)
|
RageSoundManager::RageSoundManager(CString drivers)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user