mkxp/src/soundemitter.cpp

279 lines
6.0 KiB
C++

/*
** soundemitter.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2014 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 "soundemitter.h"
#include "sharedstate.h"
#include "filesystem.h"
#include "exception.h"
#include "config.h"
#include "util.h"
#include "debugwriter.h"
#include <SDL_sound.h>
#define SE_CACHE_MEM (10*1024*1024) // 10 MB
struct SoundBuffer
{
/* Uniquely identifies this or equal buffer */
std::string key;
AL::Buffer::ID alBuffer;
/* Link into the buffer cache priority list */
IntruListLink<SoundBuffer> link;
/* Buffer byte count */
uint32_t bytes;
/* Reference count */
uint8_t refCount;
SoundBuffer()
: link(this),
refCount(1)
{
alBuffer = AL::Buffer::gen();
}
static SoundBuffer *ref(SoundBuffer *buffer)
{
++buffer->refCount;
return buffer;
}
static void deref(SoundBuffer *buffer)
{
if (--buffer->refCount == 0)
delete buffer;
}
private:
~SoundBuffer()
{
AL::Buffer::del(alBuffer);
}
};
/* Before: [a][b][c][d], After (index=1): [a][c][d][b] */
static void
arrayPushBack(std::vector<size_t> &array, size_t size, size_t index)
{
size_t v = array[index];
for (size_t t = index; t < size-1; ++t)
array[t] = array[t+1];
array[size-1] = v;
}
SoundEmitter::SoundEmitter(const Config &conf)
: bufferBytes(0),
srcCount(conf.SE.sourceCount),
alSrcs(srcCount),
atchBufs(srcCount),
srcPrio(srcCount)
{
for (size_t i = 0; i < srcCount; ++i)
{
alSrcs[i] = AL::Source::gen();
atchBufs[i] = 0;
srcPrio[i] = i;
}
}
SoundEmitter::~SoundEmitter()
{
for (size_t i = 0; i < srcCount; ++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);
}
void SoundEmitter::play(const std::string &filename,
int volume,
int pitch)
{
float _volume = clamp<int>(volume, 0, 100) / 100.0f;
float _pitch = clamp<int>(pitch, 50, 150) / 100.0f;
SoundBuffer *buffer = allocateBuffer(filename);
if (!buffer)
return;
/* Try to find first free source */
size_t i;
for (i = 0; i < srcCount; ++i)
if (AL::Source::getState(alSrcs[srcPrio[i]]) != AL_PLAYING)
break;
/* If we didn't find any, try to find the lowest priority source
* with the same buffer to overtake */
if (i == srcCount)
for (size_t j = 0; j < srcCount; ++j)
if (atchBufs[srcPrio[j]] == buffer)
i = j;
/* If we didn't find any, overtake the one with lowest priority */
if (i == srcCount)
i = 0;
size_t srcIndex = srcPrio[i];
/* Only detach/reattach if it's actually a different buffer */
bool switchBuffer = (atchBufs[srcIndex] != buffer);
/* Push the used source to the back of the priority list */
arrayPushBack(srcPrio, srcCount, i);
AL::Source::ID src = alSrcs[srcIndex];
AL::Source::stop(src);
if (switchBuffer)
AL::Source::detachBuffer(src);
SoundBuffer *old = atchBufs[srcIndex];
if (old)
SoundBuffer::deref(old);
atchBufs[srcIndex] = SoundBuffer::ref(buffer);
if (switchBuffer)
AL::Source::attachBuffer(src, buffer->alBuffer);
AL::Source::setVolume(src, _volume * GLOBAL_VOLUME);
AL::Source::setPitch(src, _pitch);
AL::Source::play(src);
}
void SoundEmitter::stop()
{
for (size_t i = 0; i < srcCount; i++)
AL::Source::stop(alSrcs[i]);
}
struct SoundOpenHandler : FileSystem::OpenHandler
{
SoundBuffer *buffer;
SoundOpenHandler()
: buffer(0)
{}
bool tryRead(SDL_RWops &ops, const char *ext)
{
Sound_Sample *sample = Sound_NewSample(&ops, ext, 0, STREAM_BUF_SIZE);
if (!sample)
{
SDL_RWclose(&ops);
return false;
}
/* Do all of the decoding in the handler so we don't have
* to keep the source ops around */
uint32_t decBytes = Sound_DecodeAll(sample);
uint8_t sampleSize = formatSampleSize(sample->actual.format);
uint32_t sampleCount = decBytes / sampleSize;
buffer = new SoundBuffer;
buffer->bytes = sampleSize * sampleCount;
ALenum alFormat = chooseALFormat(sampleSize, sample->actual.channels);
AL::Buffer::uploadData(buffer->alBuffer, alFormat, sample->buffer,
buffer->bytes, sample->actual.rate);
Sound_FreeSample(sample);
return true;
}
};
SoundBuffer *SoundEmitter::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;
}
else
{
/* Buffer not in cache, needs to be loaded */
SoundOpenHandler handler;
shState->fileSystem().openRead(handler, filename.c_str());
buffer = handler.buffer;
if (!buffer)
{
char buf[512];
snprintf(buf, sizeof(buf), "Unable to decode sound: %s: %s",
filename.c_str(), Sound_GetError());
Debug() << buf;
return 0;
}
buffer->key = filename;
uint32_t wouldBeBytes = bufferBytes + buffer->bytes;
/* 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.remove(last->key);
buffers.remove(last->link);
wouldBeBytes -= last->bytes;
SoundBuffer::deref(last);
}
bufferHash.insert(filename, buffer);
buffers.prepend(buffer->link);
bufferBytes = wouldBeBytes;
return buffer;
}
}