mkxp/src/audio.cpp

1783 lines
32 KiB
C++
Raw Normal View History

2013-09-01 14:27:21 +00:00
/*
** audio.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
**
** mkxp is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 2 of the License, or
** (at your option) any later version.
**
** mkxp is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
*/
#include "audio.h"
#include "sharedstate.h"
2013-09-01 14:27:21 +00:00
#include "util.h"
#include "intrulist.h"
#include "filesystem.h"
#include "exception.h"
#include "al-util.h"
#include "boost-hash.h"
#include "debugwriter.h"
2013-09-01 14:27:21 +00:00
#include <vector>
#include <string>
#include <assert.h>
2013-09-01 14:27:21 +00:00
#include <SDL_audio.h>
#include <SDL_thread.h>
#include <SDL_endian.h>
#include <SDL_timer.h>
#include <SDL_sound.h>
#ifdef RGSS2
#define OV_EXCLUDE_STATIC_CALLBACKS
#include <vorbis/vorbisfile.h>
#endif
#include <alc.h>
2013-09-01 14:27:21 +00:00
#define AUDIO_SLEEP 10
#define SE_SOURCES 6
#define SE_CACHE_MEM (10*1024*1024) // 10 MB
2013-09-01 14:27:21 +00:00
static uint8_t formatSampleSize(int sdlFormat)
{
switch (sdlFormat)
{
case AUDIO_U8 :
case AUDIO_S8 :
return 1;
case AUDIO_U16LSB :
case AUDIO_U16MSB :
case AUDIO_S16LSB :
case AUDIO_S16MSB :
return 2;
default:
Debug() << "Unhandled sample format";
abort();
}
return 0;
}
2013-09-01 14:27:21 +00:00
static ALenum chooseALFormat(int sampleSize, int channelCount)
{
switch (sampleSize)
{
case 1 :
switch (channelCount)
2013-09-01 14:27:21 +00:00
{
case 1 : return AL_FORMAT_MONO8;
case 2 : return AL_FORMAT_STEREO8;
default: abort();
}
case 2 :
switch (channelCount)
{
case 1 : return AL_FORMAT_MONO16;
case 2 : return AL_FORMAT_STEREO16;
default : abort();
}
default : abort();
}
2013-09-01 14:27:21 +00:00
return 0;
}
2013-09-01 14:27:21 +00:00
static const int streamBufSize = 32768;
2013-09-01 14:27:21 +00:00
struct SoundBuffer
{
/* Uniquely identifies this or equal buffer */
std::string key;
2013-09-01 14:27:21 +00:00
AL::Buffer::ID alBuffer;
2013-09-01 14:27:21 +00:00
/* Link into the buffer cache priority list */
IntruListLink<SoundBuffer> link;
2013-09-01 14:27:21 +00:00
/* Buffer byte count */
uint32_t bytes;
2013-09-01 14:27:21 +00:00
/* Reference count */
uint8_t refCount;
2013-09-01 14:27:21 +00:00
SoundBuffer()
: link(this),
refCount(1)
2013-09-01 14:27:21 +00:00
{
alBuffer = AL::Buffer::gen();
2013-09-01 14:27:21 +00:00
}
static SoundBuffer *ref(SoundBuffer *buffer)
{
++buffer->refCount;
return buffer;
2013-09-01 14:27:21 +00:00
}
static void deref(SoundBuffer *buffer)
2013-09-01 14:27:21 +00:00
{
if (--buffer->refCount == 0)
delete buffer;
}
private:
~SoundBuffer()
{
AL::Buffer::del(alBuffer);
}
};
2013-09-01 14:27:21 +00:00
struct SoundEmitter
{
typedef BoostHash<std::string, SoundBuffer*> BufferHash;
IntruList<SoundBuffer> buffers;
BufferHash bufferHash;
/* Byte count sum of all cached / playing buffers */
uint32_t bufferBytes;
2013-09-01 14:27:21 +00:00
AL::Source::ID alSrcs[SE_SOURCES];
SoundBuffer *atchBufs[SE_SOURCES];
/* Index of next source to be used */
int srcIndex;
2013-09-01 14:27:21 +00:00
SoundEmitter()
: bufferBytes(0),
srcIndex(0)
{
for (int i = 0; i < SE_SOURCES; ++i)
{
alSrcs[i] = AL::Source::gen();
atchBufs[i] = 0;
}
2013-09-01 14:27:21 +00:00
}
~SoundEmitter()
2013-09-01 14:27:21 +00:00
{
for (int i = 0; i < SE_SOURCES; ++i)
{
AL::Source::stop(alSrcs[i]);
AL::Source::del(alSrcs[i]);
if (atchBufs[i])
SoundBuffer::deref(atchBufs[i]);
}
BufferHash::const_iterator iter;
for (iter = bufferHash.cbegin(); iter != bufferHash.cend(); ++iter)
SoundBuffer::deref(iter->second);
2013-09-01 14:27:21 +00:00
}
void play(const std::string &filename,
int volume,
int pitch)
2013-09-01 14:27:21 +00:00
{
float _volume = clamp<int>(volume, 0, 100) / 100.f;
float _pitch = clamp<int>(pitch, 50, 150) / 100.f;
SoundBuffer *buffer = allocateBuffer(filename);
int soundIndex = srcIndex++;
if (srcIndex > SE_SOURCES-1)
srcIndex = 0;
AL::Source::ID src = alSrcs[soundIndex];
AL::Source::stop(src);
AL::Source::detachBuffer(src);
SoundBuffer *old = atchBufs[soundIndex];
if (old)
SoundBuffer::deref(old);
atchBufs[soundIndex] = SoundBuffer::ref(buffer);
AL::Source::attachBuffer(src, buffer->alBuffer);
AL::Source::setVolume(src, _volume);
AL::Source::setPitch(src, _pitch);
AL::Source::play(src);
}
void stop()
{
for (int i = 0; i < SE_SOURCES; i++)
AL::Source::stop(alSrcs[i]);
2013-09-01 14:27:21 +00:00
}
private:
SoundBuffer *allocateBuffer(const std::string &filename)
{
SoundBuffer *buffer = bufferHash.value(filename, 0);
if (buffer)
{
/* Buffer still in cashe.
* Move to front of priority list */
buffers.remove(buffer->link);
buffers.append(buffer->link);
return buffer;
2013-09-04 15:53:12 +00:00
}
else
{
/* Buffer not in cashe, needs to be loaded */
SDL_RWops dataSource;
const char *extension;
shState->fileSystem().openRead(dataSource, filename.c_str(),
FileSystem::Audio, false, &extension);
Sound_Sample *sampleHandle = Sound_NewSample(&dataSource, extension, 0, streamBufSize);
if (!sampleHandle)
{
SDL_RWclose(&dataSource);
throw Exception(Exception::SDLError, "SDL_sound: %s", Sound_GetError());
}
uint32_t decBytes = Sound_DecodeAll(sampleHandle);
uint8_t sampleSize = formatSampleSize(sampleHandle->actual.format);
uint32_t sampleCount = decBytes / sampleSize;
buffer = new SoundBuffer;
buffer->key = filename;
buffer->bytes = sampleSize * sampleCount;
ALenum alFormat = chooseALFormat(sampleSize, sampleHandle->actual.channels);
AL::Buffer::uploadData(buffer->alBuffer, alFormat, sampleHandle->buffer,
buffer->bytes, sampleHandle->actual.rate);
Sound_FreeSample(sampleHandle);
2013-09-01 14:27:21 +00:00
uint32_t wouldBeBytes = bufferBytes + buffer->bytes;
2013-09-01 14:27:21 +00:00
/* If memory limit is reached, delete lowest priority buffer
* until there is room or no buffers left */
while (wouldBeBytes > SE_CACHE_MEM && !buffers.isEmpty())
{
SoundBuffer *last = buffers.tail();
bufferHash.erase(last->key);
buffers.remove(last->link);
wouldBeBytes -= last->bytes;
SoundBuffer::deref(last);
}
bufferHash.insert(filename, buffer);
buffers.prepend(buffer->link);
2013-09-04 15:53:12 +00:00
bufferBytes = wouldBeBytes;
return buffer;
}
2013-09-01 14:27:21 +00:00
}
};
static const int streamBufs = 3;
2013-09-01 14:27:21 +00:00
struct ALDataSource
{
enum Status
2013-09-01 14:27:21 +00:00
{
NoError,
EndOfStream,
WrapAround,
Error
};
virtual ~ALDataSource() {}
/* Read/process next chunk of data, and attach it
* to provided AL buffer */
virtual Status fillBuffer(AL::Buffer::ID alBuffer) = 0;
virtual int sampleRate() = 0;
2013-09-01 14:27:21 +00:00
virtual void seekToOffset(float seconds) = 0;
/* Seek back to start */
virtual void reset() = 0;
/* The frame count right after wrap around */
virtual uint32_t loopStartFrames() = 0;
};
struct SDLSoundSource : ALDataSource
{
Sound_Sample *sample;
SDL_RWops &srcOps;
uint8_t sampleSize;
bool looped;
ALenum alFormat;
ALsizei alFreq;
SDLSoundSource(SDL_RWops &ops,
const char *extension,
uint32_t maxBufSize,
bool looped)
: srcOps(ops),
looped(looped)
{
sample = Sound_NewSample(&srcOps, extension, 0, maxBufSize);
if (!sample)
{
SDL_RWclose(&ops);
throw Exception(Exception::SDLError, "SDL_sound: %s", Sound_GetError());
}
sampleSize = formatSampleSize(sample->actual.format);
alFormat = chooseALFormat(sampleSize, sample->actual.channels);
alFreq = sample->actual.rate;
2013-09-01 14:27:21 +00:00
}
~SDLSoundSource()
2013-09-01 14:27:21 +00:00
{
2014-01-25 08:30:48 +00:00
/* This also closes 'srcOps' */
Sound_FreeSample(sample);
}
2013-09-01 14:27:21 +00:00
Status fillBuffer(AL::Buffer::ID alBuffer)
{
uint32_t decoded = Sound_Decode(sample);
if (sample->flags & SOUND_SAMPLEFLAG_EAGAIN)
2013-09-01 14:27:21 +00:00
{
/* Try to decode one more time on EAGAIN */
decoded = Sound_Decode(sample);
2013-09-01 14:27:21 +00:00
/* Give up */
if (sample->flags & SOUND_SAMPLEFLAG_EAGAIN)
return ALDataSource::Error;
}
2013-09-01 14:27:21 +00:00
if (sample->flags & SOUND_SAMPLEFLAG_ERROR)
return ALDataSource::Error;
2013-09-01 14:27:21 +00:00
AL::Buffer::uploadData(alBuffer, alFormat, sample->buffer, decoded, alFreq);
2013-09-01 14:27:21 +00:00
if (sample->flags & SOUND_SAMPLEFLAG_EOF)
{
if (looped)
{
Sound_Rewind(sample);
return ALDataSource::WrapAround;
}
else
{
return ALDataSource::EndOfStream;
}
2013-09-01 14:27:21 +00:00
}
return ALDataSource::NoError;
2013-09-01 14:27:21 +00:00
}
int sampleRate()
{
return sample->actual.rate;
}
2013-09-01 14:27:21 +00:00
void seekToOffset(float seconds)
{
Sound_Seek(sample, static_cast<uint32_t>(seconds * 1000));
}
void reset()
{
Sound_Rewind(sample);
}
uint32_t loopStartFrames()
{
/* Loops from the beginning of the file */
return 0;
}
2013-09-01 14:27:21 +00:00
};
#ifdef RGSS2
static size_t vfRead(void *ptr, size_t size, size_t nmemb, void *ops)
{
return SDL_RWread(static_cast<SDL_RWops*>(ops), ptr, size, nmemb);
}
static int vfSeek(void *ops, ogg_int64_t offset, int whence)
{
return SDL_RWseek(static_cast<SDL_RWops*>(ops), offset, whence);
}
static long vfTell(void *ops)
{
return SDL_RWtell(static_cast<SDL_RWops*>(ops));
}
static ov_callbacks OvCallbacks =
{
vfRead,
vfSeek,
0,
vfTell
};
struct VorbisSource : ALDataSource
{
SDL_RWops &src;
OggVorbis_File vf;
uint32_t currentFrame;
struct
{
uint32_t start;
uint32_t length;
uint32_t end;
bool valid;
bool requested;
} loop;
struct
{
int channels;
int rate;
int frameSize;
ALenum alFormat;
} info;
std::vector<int16_t> sampleBuf;
VorbisSource(SDL_RWops &ops,
bool looped)
: src(ops),
currentFrame(0)
{
int error = ov_open_callbacks(&src, &vf, 0, 0, OvCallbacks);
if (error)
{
SDL_RWclose(&src);
throw Exception(Exception::MKXPError,
"Vorbisfile: Cannot read ogg file");
}
/* Extract bitstream info */
info.channels = vf.vi->channels;
info.rate = vf.vi->rate;
if (info.channels > 2)
{
ov_clear(&vf);
SDL_RWclose(&src);
throw Exception(Exception::MKXPError,
"Cannot handle audio with more than 2 channels");
}
info.alFormat = chooseALFormat(sizeof(int16_t), info.channels);
info.frameSize = sizeof(int16_t) * info.channels;
sampleBuf.resize(streamBufSize);
loop.requested = looped;
loop.valid = false;
loop.start = loop.length = 0;
if (!loop.requested)
return;
/* Try to extract loop info */
for (int i = 0; i < vf.vc->comments; ++i)
{
char *comment = vf.vc->user_comments[i];
char *sep = strstr(comment, "=");
/* No '=' found */
if (!sep)
continue;
/* Empty value */
if (!*(sep+1))
continue;
*sep = '\0';
if (!strcmp(comment, "LOOPSTART"))
loop.start = strtol(sep+1, 0, 10);
if (!strcmp(comment, "LOOPLENGTH"))
loop.length = strtol(sep+1, 0, 10);
*sep = '=';
}
loop.end = loop.start + loop.length;
loop.valid = (loop.start && loop.length);
}
~VorbisSource()
{
ov_clear(&vf);
SDL_RWclose(&src);
}
int sampleRate()
{
return info.rate;
}
void seekToOffset(float seconds)
{
currentFrame = seconds * info.rate;
if (loop.valid && currentFrame > loop.end)
currentFrame = loop.start;
/* If seeking fails, just seek back to start */
if (ov_pcm_seek(&vf, currentFrame) != 0)
ov_raw_seek(&vf, 0);
}
Status fillBuffer(AL::Buffer::ID alBuffer)
{
void *bufPtr = sampleBuf.data();
int availBuf = sampleBuf.size();
int bufUsed = 0;
int canRead = availBuf;
Status retStatus = ALDataSource::NoError;
bool readAgain = false;
if (loop.valid)
{
int tilLoopEnd = loop.end * info.frameSize;
canRead = std::min(availBuf, tilLoopEnd);
}
while (canRead > 16)
{
long res = ov_read(&vf, static_cast<char*>(bufPtr),
canRead, 0, sizeof(int16_t), 1, 0);
if (res < 0)
{
/* Read error */
retStatus = ALDataSource::Error;
break;
}
if (res == 0)
{
/* EOF */
if (loop.requested)
{
retStatus = ALDataSource::WrapAround;
reset();
}
else
{
retStatus = ALDataSource::EndOfStream;
}
/* If we sought right to the end of the file,
* we might be EOF without actually having read
* any data at all yet (which mustn't happen),
* so we try to continue reading some data. */
if (bufUsed > 0)
break;
if (readAgain)
{
/* We're still not getting data though.
* Just error out to prevent an endless loop */
retStatus = ALDataSource::Error;
break;
}
readAgain = true;
}
bufUsed += (res / sizeof(int16_t));
bufPtr = &sampleBuf[bufUsed];
currentFrame += (res / info.frameSize);
if (loop.valid && currentFrame >= loop.end)
{
/* Determine how many frames we're
* over the loop end */
int discardFrames = currentFrame - loop.end;
bufUsed -= discardFrames * info.channels;
retStatus = ALDataSource::WrapAround;
/* Seek to loop start */
currentFrame = loop.start;
if (ov_pcm_seek(&vf, currentFrame) != 0)
retStatus = ALDataSource::Error;
break;
}
canRead -= res;
}
if (retStatus != ALDataSource::Error)
AL::Buffer::uploadData(alBuffer, info.alFormat, sampleBuf.data(),
bufUsed*sizeof(int16_t), info.rate);
return retStatus;
}
void reset()
{
ov_raw_seek(&vf, 0);
currentFrame = 0;
}
uint32_t loopStartFrames()
{
if (loop.valid)
return loop.start;
else
return 0;
}
};
#endif
/* State-machine like audio playback stream.
* This class is NOT thread safe */
struct ALStream
2013-09-01 14:27:21 +00:00
{
enum State
{
Closed,
Stopped,
Playing,
Paused
};
bool looped;
State state;
ALDataSource *source;
SDL_Thread *thread;
std::string threadName;
SDL_mutex *pauseMut;
bool preemptPause;
/* When this flag isn't set and alSrc is
* in 'STOPPED' state, stream isn't over
* (it just hasn't started yet) */
bool streamInited;
bool sourceExhausted;
bool threadTermReq;
bool needsRewind;
float startOffset;
2013-09-01 14:27:21 +00:00
AL::Source::ID alSrc;
AL::Buffer::ID alBuf[streamBufs];
2013-09-01 14:27:21 +00:00
uint64_t procFrames;
AL::Buffer::ID lastBuf;
2013-09-01 14:27:21 +00:00
SDL_RWops srcOps;
struct
2013-09-01 14:27:21 +00:00
{
ALenum format;
ALsizei freq;
} stream;
enum LoopMode
{
Looped,
NotLooped
};
ALStream(LoopMode loopMode,
const std::string &threadId)
: looped(loopMode == Looped),
state(Closed),
source(0),
thread(0),
preemptPause(false),
streamInited(false),
needsRewind(false)
{
alSrc = AL::Source::gen();
AL::Source::setVolume(alSrc, 1.0);
AL::Source::setPitch(alSrc, 1.0);
AL::Source::detachBuffer(alSrc);
for (int i = 0; i < streamBufs; ++i)
alBuf[i] = AL::Buffer::gen();
pauseMut = SDL_CreateMutex();
threadName = std::string("al_stream (") + threadId + ")";
2013-09-01 14:27:21 +00:00
}
~ALStream()
2013-09-01 14:27:21 +00:00
{
close();
2013-09-01 14:27:21 +00:00
clearALQueue();
2013-09-01 14:27:21 +00:00
AL::Source::del(alSrc);
2013-09-01 14:27:21 +00:00
for (int i = 0; i < streamBufs; ++i)
AL::Buffer::del(alBuf[i]);
2013-09-01 14:27:21 +00:00
SDL_DestroyMutex(pauseMut);
}
void close()
{
checkStopped();
2013-09-01 14:27:21 +00:00
switch (state)
{
case Playing:
case Paused:
stopStream();
case Stopped:
closeSource();
state = Closed;
case Closed:
return;
}
}
void open(const std::string &filename)
{
checkStopped();
switch (state)
{
case Playing:
case Paused:
stopStream();
case Stopped:
closeSource();
case Closed:
openSource(filename);
}
state = Stopped;
2013-09-01 14:27:21 +00:00
}
void stop()
{
checkStopped();
switch (state)
{
case Closed:
case Stopped:
return;
case Playing:
case Paused:
stopStream();
}
state = Stopped;
2013-09-01 14:27:21 +00:00
}
void play(float offset = 0)
2013-09-01 14:27:21 +00:00
{
checkStopped();
2013-09-01 14:27:21 +00:00
switch (state)
2013-09-01 14:27:21 +00:00
{
case Closed:
case Playing:
return;
case Stopped:
startStream(offset);
break;
case Paused :
resumeStream();
}
2013-09-01 14:27:21 +00:00
state = Playing;
}
2013-09-01 14:27:21 +00:00
void pause()
{
checkStopped();
switch (state)
{
case Closed:
case Stopped:
case Paused:
return;
case Playing:
pauseStream();
2013-09-01 14:27:21 +00:00
}
state = Paused;
}
void setVolume(float value)
{
AL::Source::setVolume(alSrc, value);
}
void setPitch(float value)
{
AL::Source::setPitch(alSrc, value);
}
State queryState()
{
checkStopped();
return state;
}
float queryOffset()
{
if (state == Closed)
return 0;
float procOffset = static_cast<float>(procFrames) / source->sampleRate();
return procOffset + AL::Source::getSecOffset(alSrc);
}
private:
void closeSource()
{
delete source;
}
void openSource(const std::string &filename)
{
const char *ext;
shState->fileSystem().openRead(srcOps, filename.c_str(), FileSystem::Audio, false, &ext);
#ifdef RGSS2
/* Try to read ogg file signature */
char sig[5];
memset(sig, '\0', sizeof(sig));
SDL_RWread(&srcOps, sig, 1, 4);
SDL_RWseek(&srcOps, 0, RW_SEEK_SET);
if (!strcmp(sig, "OggS"))
source = new VorbisSource(srcOps, looped);
else
source = new SDLSoundSource(srcOps, ext, streamBufSize, looped);
#else
source = new SDLSoundSource(srcOps, ext, streamBufSize, looped);
#endif
needsRewind = false;
}
void stopStream()
{
threadTermReq = true;
AL::Source::stop(alSrc);
if (thread)
{
SDL_WaitThread(thread, 0);
thread = 0;
needsRewind = true;
}
procFrames = 0;
}
void startStream(float offset)
{
clearALQueue();
preemptPause = false;
streamInited = false;
sourceExhausted = false;
threadTermReq = false;
startOffset = offset;
procFrames = offset * source->sampleRate();
thread = SDL_CreateThread(streamDataFun, threadName.c_str(), this);
}
void pauseStream()
{
SDL_LockMutex(pauseMut);
if (AL::Source::getState(alSrc) != AL_PLAYING)
preemptPause = true;
2013-09-01 14:27:21 +00:00
else
AL::Source::pause(alSrc);
SDL_UnlockMutex(pauseMut);
}
void resumeStream()
{
SDL_LockMutex(pauseMut);
if (preemptPause)
preemptPause = false;
else
AL::Source::play(alSrc);
SDL_UnlockMutex(pauseMut);
}
void checkStopped()
{
/* This only concerns the scenario where
* state is still 'Playing', but the stream
* has already ended on its own (EOF, Error) */
if (state != Playing)
return;
/* If streaming thread hasn't queued up
* buffers yet there's not point in querying
* the AL source */
if (!streamInited)
return;
/* If alSrc isn't playing, but we haven't
* exhausted the data source yet, we're just
* having a buffer underrun */
if (!sourceExhausted)
return;
if (AL::Source::getState(alSrc) == AL_PLAYING)
return;
stopStream();
state = Stopped;
}
void clearALQueue()
{
/* Unqueue all buffers */
ALint queuedBufs = AL::Source::getProcBufferCount(alSrc);
while (queuedBufs--)
AL::Source::unqueueBuffer(alSrc);
}
/* thread func */
void streamData()
{
/* Fill up queue */
bool firstBuffer = true;
ALDataSource::Status status;
if (needsRewind)
{
if (startOffset > 0)
source->seekToOffset(startOffset);
else
source->reset();
}
for (int i = 0; i < streamBufs; ++i)
2013-09-01 14:27:21 +00:00
{
AL::Buffer::ID buf = alBuf[i];
2013-09-01 14:27:21 +00:00
status = source->fillBuffer(buf);
2013-09-01 14:27:21 +00:00
if (status == ALDataSource::Error)
return;
2013-09-01 14:27:21 +00:00
AL::Source::queueBuffer(alSrc, buf);
2013-09-01 14:27:21 +00:00
if (firstBuffer)
{
resumeStream();
2013-09-01 14:27:21 +00:00
firstBuffer = false;
streamInited = true;
}
2013-09-01 14:27:21 +00:00
if (threadTermReq)
return;
2013-09-01 14:27:21 +00:00
if (status == ALDataSource::EndOfStream)
2013-09-01 14:27:21 +00:00
{
sourceExhausted = true;
break;
}
}
2013-09-01 14:27:21 +00:00
/* Wait for buffers to be consumed, then
* refill and queue them up again */
while (true)
{
ALint procBufs = AL::Source::getProcBufferCount(alSrc);
2013-09-01 14:27:21 +00:00
while (procBufs--)
{
if (threadTermReq)
break;
AL::Buffer::ID buf = AL::Source::unqueueBuffer(alSrc);
/* If something went wrong, try again later */
if (buf == AL::Buffer::ID(0))
break;
if (buf == lastBuf)
{
/* Reset the processed sample count so
* querying the playback offset returns 0.0 again */
procFrames = source->loopStartFrames();
lastBuf = AL::Buffer::ID(0);
}
else
{
/* Add the frame count contained in this
* buffer to the total count */
ALint bits = AL::Buffer::getBits(buf);
ALint size = AL::Buffer::getSize(buf);
ALint chan = AL::Buffer::getChannels(buf);
2013-12-15 09:12:44 +00:00
if (bits != 0 && chan != 0)
procFrames += ((size / (bits / 8)) / chan);
}
if (sourceExhausted)
continue;
status = source->fillBuffer(buf);
if (status == ALDataSource::Error)
{
sourceExhausted = true;
return;
}
AL::Source::queueBuffer(alSrc, buf);
/* In case of buffer underrun,
* start playing again */
if (AL::Source::getState(alSrc) == AL_STOPPED)
AL::Source::play(alSrc);
/* If this was the last buffer before the data
* source loop wrapped around again, mark it as
* such so we can catch it and reset the processed
* sample count once it gets unqueued */
if (status == ALDataSource::WrapAround)
lastBuf = buf;
if (status == ALDataSource::EndOfStream)
sourceExhausted = true;
2013-09-01 14:27:21 +00:00
}
if (threadTermReq)
break;
SDL_Delay(AUDIO_SLEEP);
2013-09-01 14:27:21 +00:00
}
}
static int streamDataFun(void *_self)
{
ALStream &self = *static_cast<ALStream*>(_self);
self.streamData();
return 0;
}
2013-09-01 14:27:21 +00:00
};
struct AudioStream
2013-09-01 14:27:21 +00:00
{
struct
{
std::string filename;
float volume;
float pitch;
} current;
/* Volume set with 'play()' */
float baseVolume;
/* Volume set by external threads,
* such as for fade-in/out.
* Multiplied with intVolume for final
* playback volume.
* fadeVolume: used by fade-out thread.
* extVolume: used by MeWatch. */
float fadeVolume;
float extVolume;
2013-09-01 14:27:21 +00:00
/* Note that 'extPaused' and 'noResumeStop' are
* effectively only used with the AudioStream
* instance representing the BGM */
/* Flag indicating that the MeWatch paused this
* (BGM) stream because a ME started playing.
* While this flag is set, calls to 'play()'
* might open another file, but will not start
* the playback stream (the MeWatch will start
* it as soon as the ME finished playing). */
bool extPaused;
2013-09-01 14:27:21 +00:00
/* Flag indicating that this stream shouldn't be
* started by the MeWatch when it is in stopped
* state (eg. because the BGM stream was explicitly
* stopped by the user script while the ME was playing.
* When a new BGM is started (via 'play()') while an ME
* is playing, the file will be loaded without starting
* the stream, but we want the MeWatch to start it as
* soon as the ME ends, so we unset this flag. */
bool noResumeStop;
ALStream stream;
SDL_mutex *streamMut;
2013-09-01 14:27:21 +00:00
struct
2013-09-01 14:27:21 +00:00
{
/* Fade is in progress */
bool active;
/* Request fade thread to finish and
* cleanup (like it normally would) */
bool reqFini;
/* Request fade thread to terminate
* immediately */
bool reqTerm;
SDL_Thread *thread;
std::string threadName;
/* Amount of reduced absolute volume
* per ms of fade time */
float msStep;
/* Ticks at start of fade */
uint32_t startTicks;
} fade;
AudioStream(ALStream::LoopMode loopMode,
const std::string &threadId)
: baseVolume(1.0),
fadeVolume(1.0),
extVolume(1.0),
extPaused(false),
noResumeStop(false),
stream(loopMode, threadId)
{
current.volume = 1.0;
current.pitch = 1.0;
fade.active = false;
fade.thread = 0;
fade.threadName = std::string("audio_fade (") + threadId + ")";
streamMut = SDL_CreateMutex();
2013-09-01 14:27:21 +00:00
}
~AudioStream()
2013-09-01 14:27:21 +00:00
{
if (fade.thread)
2013-09-01 14:27:21 +00:00
{
fade.reqTerm = true;
SDL_WaitThread(fade.thread, 0);
2013-09-01 14:27:21 +00:00
}
lockStream();
stream.stop();
stream.close();
unlockStream();
SDL_DestroyMutex(streamMut);
2013-09-01 14:27:21 +00:00
}
void play(const std::string &filename,
int volume,
int pitch,
float offset = 0)
2013-09-01 14:27:21 +00:00
{
finiFadeInt();
lockStream();
float _volume = clamp<int>(volume, 0, 100) / 100.f;
float _pitch = clamp<int>(pitch, 50, 150) / 100.f;
ALStream::State sState = stream.queryState();
/* If all parameters match the current ones and we're
* still playing, there's nothing to do */
if (filename == current.filename
&& _volume == current.volume
&& _pitch == current.pitch
&& (sState == ALStream::Playing || sState == ALStream::Paused))
{
unlockStream();
2013-09-01 14:27:21 +00:00
return;
}
/* If all parameters except volume match the current ones,
* we update the volume and continue streaming */
if (filename == current.filename
&& _pitch == current.pitch
&& (sState == ALStream::Playing || sState == ALStream::Paused))
{
setBaseVolume(_volume);
current.volume = _volume;
unlockStream();
return;
}
/* Requested audio file is different from current one */
bool diffFile = (filename != current.filename);
switch (sState)
{
case ALStream::Paused :
case ALStream::Playing :
stream.stop();
case ALStream::Stopped :
if (diffFile)
stream.close();
case ALStream::Closed :
if (diffFile)
{
try
{
/* This will throw on errors while
* opening the data source */
stream.open(filename);
}
catch (const Exception &e)
{
unlockStream();
throw e;
}
}
2014-01-01 11:56:45 +00:00
break;
}
2013-09-01 14:27:21 +00:00
setBaseVolume(_volume);
stream.setPitch(_pitch);
2013-09-01 14:27:21 +00:00
current.filename = filename;
current.volume = _volume;
current.pitch = _pitch;
2013-09-01 14:27:21 +00:00
if (!extPaused)
stream.play(offset);
else
noResumeStop = false;
unlockStream();
}
void stop()
{
finiFadeInt();
lockStream();
noResumeStop = true;
stream.stop();
2013-09-01 14:27:21 +00:00
unlockStream();
2013-09-01 14:27:21 +00:00
}
void fadeOut(int duration)
2013-09-01 14:27:21 +00:00
{
lockStream();
2013-09-01 14:27:21 +00:00
ALStream::State sState = stream.queryState();
if (fade.active)
2013-09-01 14:27:21 +00:00
{
unlockStream();
return;
}
if (sState == ALStream::Paused)
{
stream.stop();
unlockStream();
2013-09-01 14:27:21 +00:00
return;
}
if (sState != ALStream::Playing)
{
unlockStream();
return;
}
if (fade.thread)
{
fade.reqFini = true;
SDL_WaitThread(fade.thread, 0);
fade.thread = 0;
}
fade.active = true;
fade.msStep = (1.0) / duration;
fade.reqFini = false;
fade.reqTerm = false;
fade.startTicks = SDL_GetTicks();
fade.thread = SDL_CreateThread(fadeThreadFun, fade.threadName.c_str(), this);
unlockStream();
}
/* Any access to this classes 'stream' member,
* whether state query or modification, must be
* protected by a 'lock'/'unlock' pair */
void lockStream()
{
SDL_LockMutex(streamMut);
}
void unlockStream()
{
SDL_UnlockMutex(streamMut);
}
void setFadeVolume(float value)
{
fadeVolume = value;
updateVolume();
}
void setExtVolume1(float value)
{
extVolume = value;
updateVolume();
}
float playingOffset()
{
return stream.queryOffset();
}
private:
void finiFadeInt()
{
if (!fade.thread)
return;
fade.reqFini = true;
SDL_WaitThread(fade.thread, 0);
fade.thread = 0;
}
void updateVolume()
{
stream.setVolume(baseVolume * fadeVolume * extVolume);
}
void setBaseVolume(float value)
{
baseVolume = value;
updateVolume();
}
void fadeThread()
{
while (true)
{
/* Just immediately terminate on request */
if (fade.reqTerm)
break;
lockStream();
uint32_t curDur = SDL_GetTicks() - fade.startTicks;
float resVol = 1.0 - (curDur*fade.msStep);
ALStream::State state = stream.queryState();
if (state != ALStream::Playing
|| resVol < 0
|| fade.reqFini)
2013-09-01 14:27:21 +00:00
{
if (state != ALStream::Paused)
stream.stop();
setFadeVolume(1.0);
unlockStream();
2013-09-01 14:27:21 +00:00
break;
}
setFadeVolume(resVol);
unlockStream();
SDL_Delay(AUDIO_SLEEP);
2013-09-01 14:27:21 +00:00
}
fade.active = false;
}
2013-09-01 14:27:21 +00:00
static int fadeThreadFun(void *self)
{
static_cast<AudioStream*>(self)->fadeThread();
2013-09-01 14:27:21 +00:00
return 0;
}
};
struct AudioPrivate
{
AudioStream bgm;
AudioStream bgs;
AudioStream me;
SoundEmitter se;
/* The 'MeWatch' is responsible for detecting
* a playing ME, quickly fading out the BGM and
* keeping it paused/stopped while the ME plays,
* and unpausing/fading the BGM back in again
* afterwards */
enum MeWatchState
{
MeNotPlaying,
BgmFadingOut,
MePlaying,
BgmFadingIn
};
struct
{
SDL_Thread *thread;
bool active;
bool termReq;
MeWatchState state;
} meWatch;
2013-09-01 14:27:21 +00:00
AudioPrivate()
: bgm(ALStream::Looped, "bgm"),
bgs(ALStream::Looped, "bgs"),
me(ALStream::NotLooped, "me")
{
meWatch.active = false;
meWatch.termReq = false;
meWatch.state = MeNotPlaying;
meWatch.thread = SDL_CreateThread(meWatchFun, "audio_mewatch", this);
}
~AudioPrivate()
{
meWatch.termReq = true;
SDL_WaitThread(meWatch.thread, 0);
}
2013-09-01 14:27:21 +00:00
void meWatchFunInt()
{
const float fadeOutStep = 1.f / (200 / AUDIO_SLEEP);
const float fadeInStep = 1.f / (1000 / AUDIO_SLEEP);
2013-09-01 14:27:21 +00:00
while (true)
2013-09-01 14:27:21 +00:00
{
if (meWatch.termReq)
2013-09-01 14:27:21 +00:00
return;
switch (meWatch.state)
{
case MeNotPlaying:
{
me.lockStream();
if (me.stream.queryState() == ALStream::Playing)
{
/* ME playing detected. -> FadeOutBGM */
bgm.extPaused = true;
meWatch.state = BgmFadingOut;
}
me.unlockStream();
break;
}
2013-09-01 14:27:21 +00:00
case BgmFadingOut :
2013-09-01 14:27:21 +00:00
{
me.lockStream();
if (me.stream.queryState() != ALStream::Playing)
{
/* ME has ended while fading OUT BGM. -> FadeInBGM */
me.unlockStream();
meWatch.state = BgmFadingIn;
break;
}
bgm.lockStream();
float vol = bgm.extVolume;
vol -= fadeOutStep;
if (vol < 0 || bgm.stream.queryState() != ALStream::Playing)
{
/* Either BGM has fully faded out, or stopped midway. -> MePlaying */
bgm.setExtVolume1(0);
bgm.stream.pause();
meWatch.state = MePlaying;
bgm.unlockStream();
me.unlockStream();
break;
}
bgm.setExtVolume1(vol);
bgm.unlockStream();
me.unlockStream();
2013-09-01 14:27:21 +00:00
break;
}
case MePlaying :
{
me.lockStream();
if (me.stream.queryState() != ALStream::Playing)
{
/* ME has ended */
bgm.lockStream();
bgm.extPaused = false;
ALStream::State sState = bgm.stream.queryState();
if (sState == ALStream::Paused)
{
/* BGM is paused. -> FadeInBGM */
bgm.stream.play();
meWatch.state = BgmFadingIn;
}
else
{
/* BGM is stopped. -> MeNotPlaying */
bgm.setExtVolume1(1.0);
if (!bgm.noResumeStop)
bgm.stream.play();
meWatch.state = MeNotPlaying;
}
bgm.unlockStream();
}
me.unlockStream();
break;
}
case BgmFadingIn :
{
bgm.lockStream();
if (bgm.stream.queryState() == ALStream::Stopped)
{
/* BGM stopped midway fade in. -> MeNotPlaying */
bgm.setExtVolume1(1.0);
meWatch.state = MeNotPlaying;
bgm.unlockStream();
break;
}
2013-09-01 14:27:21 +00:00
me.lockStream();
2013-09-01 14:27:21 +00:00
if (me.stream.queryState() == ALStream::Playing)
{
/* ME started playing midway BGM fade in. -> FadeOutBGM */
bgm.extPaused = true;
meWatch.state = BgmFadingOut;
me.unlockStream();
bgm.unlockStream();
break;
}
float vol = bgm.extVolume;
vol += fadeInStep;
if (vol >= 1)
{
/* BGM fully faded in. -> MeNotPlaying */
vol = 1.0;
meWatch.state = MeNotPlaying;
}
bgm.setExtVolume1(vol);
me.unlockStream();
bgm.unlockStream();
break;
}
}
SDL_Delay(AUDIO_SLEEP);
2013-09-01 14:27:21 +00:00
}
}
2013-09-01 14:27:21 +00:00
static int meWatchFun(void *self)
{
static_cast<AudioPrivate*>(self)->meWatchFunInt();
return 0;
2013-09-01 14:27:21 +00:00
}
};
Audio::Audio()
: p(new AudioPrivate)
{}
2013-09-01 14:27:21 +00:00
void Audio::bgmPlay(const char *filename,
int volume,
int pitch
#ifdef RGSS3
,float pos
#endif
)
2013-09-01 14:27:21 +00:00
{
#ifdef RGSS3
p->bgm.play(filename, volume, pitch, pos);
#else
2013-09-01 14:27:21 +00:00
p->bgm.play(filename, volume, pitch);
#endif
2013-09-01 14:27:21 +00:00
}
void Audio::bgmStop()
{
p->bgm.stop();
}
void Audio::bgmFade(int time)
{
p->bgm.fadeOut(time);
2013-09-01 14:27:21 +00:00
}
void Audio::bgsPlay(const char *filename,
int volume,
int pitch
#ifdef RGSS3
,float pos
#endif
)
2013-09-01 14:27:21 +00:00
{
#ifdef RGSS3
p->bgs.play(filename, volume, pitch, pos);
#else
2013-09-01 14:27:21 +00:00
p->bgs.play(filename, volume, pitch);
#endif
2013-09-01 14:27:21 +00:00
}
void Audio::bgsStop()
{
p->bgs.stop();
}
void Audio::bgsFade(int time)
{
p->bgs.fadeOut(time);
2013-09-01 14:27:21 +00:00
}
void Audio::mePlay(const char *filename,
int volume,
int pitch)
{
p->me.play(filename, volume, pitch);
}
void Audio::meStop()
{
p->me.stop();
}
void Audio::meFade(int time)
{
p->me.fadeOut(time);
2013-09-01 14:27:21 +00:00
}
void Audio::sePlay(const char *filename,
int volume,
int pitch)
{
p->se.play(filename, volume, pitch);
}
void Audio::seStop()
{
p->se.stop();
}
#ifdef RGSS3
void Audio::setupMidi()
{
}
float Audio::bgmPos()
{
return p->bgm.playingOffset();
}
float Audio::bgsPos()
{
return p->bgs.playingOffset();
}
#endif
2013-09-01 14:27:21 +00:00
Audio::~Audio() { delete p; }