Before, even though we did match all possible extensions, we only took the first match and tried opening it. If we were looking for a .png image, but there was an unrelated .txt file with the same name (as it actually happens in RTPs), we would potentially see the .txt first, try opening it, and fail alltogether, even though the image file existed. Now we try opening all matching files until we find one that we can parse. This fixes #101.
278 lines
6 KiB
C++
278 lines
6 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.f;
|
|
float _pitch = clamp<int>(pitch, 50, 150) / 100.f;
|
|
|
|
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;
|
|
}
|
|
}
|