diff --git a/mkxp.pro b/mkxp.pro index 17475ca..6a1bad0 100644 --- a/mkxp.pro +++ b/mkxp.pro @@ -1,7 +1,7 @@ TEMPLATE = app -QT = core +QT = TARGET = mkxp DEPENDPATH += src shader assets INCLUDEPATH += . src @@ -14,9 +14,9 @@ isEmpty(BINDING) { CONFIG += $$BINDING unix { - CONFIG += link_pkgconfig - PKGCONFIG += sigc++-2.0 glew pixman-1 zlib sdl2 SDL2_image SDL2_ttf sfml-audio - LIBS += -lphysfs + CONFIG += link_pkgconfig + PKGCONFIG += QtCore sigc++-2.0 glew pixman-1 zlib \ + physfs sdl2 SDL2_image SDL2_ttf SDL_sound openal } # 'slots' keyword fucks with libsigc++ @@ -62,7 +62,8 @@ HEADERS += \ src/config.h \ src/tileatlas.h \ src/perftimer.h \ - src/sharedstate.h + src/sharedstate.h \ + src/al-util.h SOURCES += \ src/main.cpp \ diff --git a/src/al-util.h b/src/al-util.h new file mode 100644 index 0000000..fe82bf4 --- /dev/null +++ b/src/al-util.h @@ -0,0 +1,181 @@ +/* +** al-util.h +** +** This file is part of mkxp. +** +** Copyright (C) 2013 Jonas Kulla +** +** 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 . +*/ + +#ifndef ALUTIL_H +#define ALUTIL_H + +#include +#include + +namespace AL +{ + +#define DEF_AL_ID \ +struct ID \ +{ \ + ALuint al; \ + explicit ID(ALuint al = 0) \ + : al(al) \ + {} \ + ID &operator=(const ID &o) \ + { \ + al = o.al; \ + return *this; \ + } \ + bool operator==(const ID &o) const \ + { \ + return al == o.al; \ + } \ +}; + +namespace Buffer +{ + DEF_AL_ID + + inline Buffer::ID gen() + { + Buffer::ID id; + alGenBuffers(1, &id.al); + + return id; + } + + inline void del(Buffer::ID id) + { + alDeleteBuffers(1, &id.al); + } + + inline void uploadData(Buffer::ID id, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) + { + alBufferData(id.al, format, data, size, freq); + } + + inline ALint getInteger(Buffer::ID id, ALenum prop) + { + ALint value; + alGetBufferi(id.al, prop, &value); + + return value; + } + + inline ALint getSize(Buffer::ID id) + { + return getInteger(id, AL_SIZE); + } + + inline ALint getBits(Buffer::ID id) + { + return getInteger(id, AL_BITS); + } +} + +namespace Source +{ + DEF_AL_ID + + inline Source::ID gen() + { + Source::ID id; + alGenSources(1, &id.al); + + return id; + } + + inline void del(Source::ID id) + { + alDeleteSources(1, &id.al); + } + + inline void attachBuffer(Source::ID id, Buffer::ID buffer) + { + alSourcei(id.al, AL_BUFFER, buffer.al); + } + + inline void detachBuffer(Source::ID id) + { + attachBuffer(id, Buffer::ID(0)); + } + + inline void queueBuffer(Source::ID id, Buffer::ID buffer) + { + alSourceQueueBuffers(id.al, 1, &buffer.al); + } + + inline Buffer::ID unqueueBuffer(Source::ID id) + { + Buffer::ID buffer; + alSourceUnqueueBuffers(id.al, 1, &buffer.al); + + return buffer; + } + + inline ALint getInteger(Source::ID id, ALenum prop) + { + ALint value; + alGetSourcei(id.al, prop, &value); + + return value; + } + + inline ALint getProcBufferCount(Source::ID id) + { + return getInteger(id, AL_BUFFERS_PROCESSED); + } + + inline ALenum getState(Source::ID id) + { + return getInteger(id, AL_SOURCE_STATE); + } + + inline ALint getSampleOffset(Source::ID id) + { + return getInteger(id, AL_SAMPLE_OFFSET); + } + + inline void setVolume(Source::ID id, float value) + { + alSourcef(id.al, AL_GAIN, value); + } + + inline void setPitch(Source::ID id, float value) + { + alSourcef(id.al, AL_PITCH, value); + } + + inline void play(Source::ID id) + { + alSourcePlay(id.al); + } + + inline void stop(Source::ID id) + { + alSourceStop(id.al); + } + + inline void pause(Source::ID id) + { + alSourcePause(id.al); + } +} + +} + +#endif // ALUTIL_H diff --git a/src/audio.cpp b/src/audio.cpp index da9a23b..9e26275 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -26,583 +26,1445 @@ #include "intrulist.h" #include "filesystem.h" #include "exception.h" +#include "al-util.h" -#include -#include -#include -#include -#include -#include #include #include -#include "SDL_thread.h" +#include +#include -//#include "SDL_mixer.h" +#include +#include +#include +#include +#include + +#include #include -#define FADE_SLEEP 10 -#define SOUND_MAG_SIZE 10 -#define SOUND_MAX_MEM (50*1000*1000) // 5 MB +#define AUDIO_SLEEP 10 +#define SE_SOURCES 6 +#define SE_CACHE_MEM (10*1024*1024) // 10 MB -struct MusicEntity +static void genFloatSamples(int sampleCount, int sdlFormat, const void *in, float *out) { - sf::Music music; - QByteArray filename; - FileStream currentData; - SDL_mutex *mutex; + (void) genFloatSamples; - int pitch; + int i = sampleCount; - volatile bool fading; - struct + /* Convert from the possible fixed point + * formats to [-1;1] floats */ + switch (sdlFormat) { - unsigned int duration; - float msStep; - volatile bool terminate; - sf::Thread *thread; - } fadeData; - - /* normalized */ - float intVolume; - float extVolume; - - bool extPaused; - bool noFadeInFlag; - - MusicEntity() - : currentData(0), - pitch(100), - fading(false), - intVolume(1), - extVolume(1), - extPaused(false), - noFadeInFlag(false) + case AUDIO_U8 : { - music.setLoop(true); - fadeData.thread = 0; - mutex = SDL_CreateMutex(); + const uint8_t *_in = static_cast(in); + + while (i--) + out[i] = (((float) _in[i] / 255.f) - 0.5) * 2; + + break; + } + case AUDIO_S8 : + { + const int8_t *_in = static_cast(in); + + while (i--) + out[i] = (float) _in[i] / 128.f; // ??? + + break; + } + case AUDIO_U16LSB : + { + const uint16_t *_in = static_cast(in); + + while (i--) + out[i] = (((float) _in[i] / 65536.f) - 0.5) * 2; + + break; + } + case AUDIO_U16MSB : + { + const int16_t *_in = static_cast(in); + + while (i--) + { + int16_t swp = SDL_Swap16(_in[i]); + out[i] = (((float) swp / 65536.f) - 0.5) * 2; + } + + break; + } + case AUDIO_S16LSB : + { + const int16_t *_in = static_cast(in); + + while (i--) + out[i] = (float) _in[i] / 32768.f; // ??? + + break; + } + case AUDIO_S16MSB : + { + const int16_t *_in = static_cast(in); + + while (i--) + { + int16_t swp = SDL_Swap16(_in[i]); + out[i] = (float) swp / 32768.f; + } + + break; + } + qDebug() << "Unhandled sample format"; + default: Q_ASSERT(0); // XXX + } +} + +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; + + case AUDIO_F32 : + return 4; + + default: + qDebug() << "Unhandled sample format"; + Q_ASSERT(0); } - ~MusicEntity() + return 0; +} + +static ALenum chooseALFormat(int sampleSize, int channelCount) +{ + switch (sampleSize) { - SDL_DestroyMutex(mutex); + case 1 : + switch (channelCount) + { + case 1 : return AL_FORMAT_MONO8; + case 2 : return AL_FORMAT_STEREO8; + default: Q_ASSERT(0); + } + case 2 : + switch (channelCount) + { + case 1 : return AL_FORMAT_MONO16; + case 2 : return AL_FORMAT_STEREO16; + default : Q_ASSERT(0); + } + case 4 : + switch (channelCount) + { + case 1 : return AL_FORMAT_MONO_FLOAT32; + case 2 : return AL_FORMAT_STEREO_FLOAT32; + default : Q_ASSERT(0); + } + default : Q_ASSERT(0); + } + + return 0; +} + +static const int streamBufSize = 32768; + +struct SoundBuffer +{ + /* Filename, pitch. + * Uniquely identifies this or equal buffer */ + typedef QPair Key; + Key key; + + AL::Buffer::ID alBuffer; + + /* Link into the buffer cache priority list */ + IntruListLink link; + + /* Buffer byte count */ + uint32_t bytes; + + /* Reference count */ + uint8_t refCount; + + SoundBuffer() + : link(this), + refCount(1) + + { + alBuffer = AL::Buffer::gen(); + } + + ~SoundBuffer() + { + AL::Buffer::del(alBuffer); + } + + static SoundBuffer *ref(SoundBuffer *buffer) + { + ++buffer->refCount; + + return buffer; + } + + static void deref(SoundBuffer *buffer) + { + if (--buffer->refCount == 0) + delete buffer; + } +}; + +struct SoundEmitter +{ + IntruList buffers; + QHash bufferHash; + + /* Byte count sum of all cached / playing buffers */ + uint32_t bufferBytes; + + AL::Source::ID alSrcs[SE_SOURCES]; + SoundBuffer *atchBufs[SE_SOURCES]; + /* Index of next source to be used */ + int srcIndex; + +#ifdef RUBBERBAND + std::vector floatBuf; +#endif + + SoundEmitter() + : bufferBytes(0), + srcIndex(0) + { + for (int i = 0; i < SE_SOURCES; ++i) + { + alSrcs[i] = AL::Source::gen(); + atchBufs[i] = 0; + } +#ifdef RUBBERBAND + floatBuf.resize(streamBufSize/4); +#endif + } + + ~SoundEmitter() + { + 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]); + } + + QHash::iterator iter; + for (iter = bufferHash.begin(); iter != bufferHash.end(); ++iter) + delete iter.value(); } void play(const QByteArray &filename, int volume, int pitch) { - terminateFade(); + float _volume = clamp(volume, 0, 100) / 100.f; - volume = clamp(volume, 0, 100); - pitch = clamp(pitch, 50, 150); + SoundBuffer *buffer = allocateBuffer(filename, pitch); - if (filename == this->filename - && volume == (int)music.getVolume() -// && pitch == music.getPitch() - && music.getStatus() == sf::Music::Playing) - return; + int soundIndex = srcIndex++; + if (srcIndex > SE_SOURCES-1) + srcIndex = 0; - if (filename == this->filename - && music.getStatus() == sf::Music::Playing) -// && pitch == music.getPitch()) - { - intVolume = (float) volume / 100; - updateVolumeSync(); - return; - } + AL::Source::ID src = alSrcs[soundIndex]; + AL::Source::stop(src); + AL::Source::detachBuffer(src); - SDL_LockMutex(mutex); + SoundBuffer *old = atchBufs[soundIndex]; - this->music.stop(); + if (old) + SoundBuffer::deref(old); - this->filename = filename; + atchBufs[soundIndex] = SoundBuffer::ref(buffer); - currentData.close(); + AL::Source::attachBuffer(src, buffer->alBuffer); + AL::Source::setVolume(src, _volume); - currentData = - shState->fileSystem().openRead(filename.constData(), FileSystem::Audio); +#ifndef RUBBERBAND + AL::Source::setPitch(src, clamp(pitch, 50, 150) / 100.f); +#endif - if (!this->music.openFromStream(currentData)) - return; - - intVolume = (float) volume / 100; - updateVolume(); -// this->music.setPitch((float) pitch / 100); - - if (!extPaused) - this->music.play(); - else - noFadeInFlag = true; - - SDL_UnlockMutex(mutex); + AL::Source::play(src); } void stop() { - terminateFade(); - - stopPriv(); - } - - void fade(unsigned int ms) - { - if (music.getStatus() != sf::Music::Playing) - return; - - if (fading) - return; - - delete fadeData.thread; - fadeData.thread = new sf::Thread(&MusicEntity::processFade, this); - fadeData.duration = ms; - fadeData.terminate = false; - - fading = true; - fadeData.thread->launch(); - } - - void updateVolume() - { - music.setVolume(intVolume * extVolume * 100); - } - - void updateVolumeSync() - { - SDL_LockMutex(mutex); - updateVolume(); - SDL_UnlockMutex(mutex); + for (int i = 0; i < SE_SOURCES; i++) + AL::Source::stop(alSrcs[i]); } private: - void terminateFade() +#ifdef RUBBERBAND + void ensureFloatBufSize(uint32_t size) { - if (!fading) - { - delete fadeData.thread; - fadeData.thread = 0; + if (size <= floatBuf.size()) return; - } - /* Tell our thread to wrap up and wait for it */ - fadeData.terminate = true; - fadeData.thread->wait(); - - fading = false; - - delete fadeData.thread; - fadeData.thread = 0; + floatBuf.resize(size); } +#endif - void stopPriv() + SoundBuffer *allocateBuffer(const QByteArray &filename, + int pitch) { - SDL_LockMutex(mutex); - music.stop(); - SDL_UnlockMutex(mutex); +#ifndef RUBBERBAND + pitch = 100; +#endif - filename = QByteArray(); - } + SoundBuffer::Key soundKey(filename, pitch); - void processFade() - { - float msStep = music.getVolume() / fadeData.duration; - sf::Clock timer; - sf::Time sleepTime = sf::milliseconds(FADE_SLEEP); - unsigned int currentDur = 0; - - while (true) - { - int elapsed = timer.getElapsedTime().asMilliseconds(); - timer.restart(); - currentDur += elapsed; - - if (music.getStatus() != sf::Music::Playing) - break; - - if (fadeData.terminate) - break; - - if (currentDur >= fadeData.duration) - break; - - intVolume = (float) (music.getVolume() - (elapsed * msStep)) / 100; - updateVolumeSync(); - - sf::sleep(sleepTime); - } - - stopPriv(); - fading = false; - } -}; - -//struct SoundBuffer -//{ -// Mix_Chunk *sdlBuffer; -// QByteArray filename; -// IntruListLink link; - -// SoundBuffer() -// : link(this) -// {} -//}; - -//struct SoundEntity -//{ -// IntruList buffers; -// QHash bufferHash; -// uint cacheSize; // in chunks, for now -// int channelIndex; - -// SoundEntity() -// : cacheSize(0), -// channelIndex(0) -// { -// Mix_AllocateChannels(SOUND_MAG_SIZE); -// } - -// ~SoundEntity() -// { -// IntruListLink *iter = buffers.iterStart(); -// iter = iter->next; - -// for (int i = 0; i < buffers.getSize(); ++i) -// { -// SoundBuffer *buffer = iter->data; -// iter = iter->next; -// delete buffer; -// } -// } - -// void play(const char *filename, -// int volume, -// int pitch) -// { -// (void) pitch; - -// volume = bound(volume, 0, 100); - -// Mix_Chunk *buffer = requestBuffer(filename); - -// int nextChIdx = channelIndex++; -// if (channelIndex > SOUND_MAG_SIZE-1) -// channelIndex = 0; - -// Mix_HaltChannel(nextChIdx); -// Mix_Volume(nextChIdx, ((float) MIX_MAX_VOLUME / 100) * volume); -// Mix_PlayChannelTimed(nextChIdx, buffer, 0, -1); -// } - -// void stop() -// { -// /* Stop all channels */ -// Mix_HaltChannel(-1); -// } - -//private: -// Mix_Chunk *requestBuffer(const char *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->sdlBuffer; -// } -// else -// { -// /* Buffer not in cashe, needs to be loaded */ -// SDL_RWops ops; -// shState->fileSystem().openRead(ops, filename, FileSystem::Audio); - -// Mix_Chunk *sdlBuffer = Mix_LoadWAV_RW(&ops, 1); - -// if (!sdlBuffer) -// { -// SDL_RWclose(&ops); -// throw Exception(Exception::RGSSError, "Unable to read sound file"); -// } - -// buffer = new SoundBuffer; -// buffer->sdlBuffer = sdlBuffer; -// buffer->filename = filename; - -// ++cacheSize; - -// bufferHash.insert(filename, buffer); -// buffers.prepend(buffer->link); - -// if (cacheSize > 20) -// { -// SoundBuffer *last = buffers.tail(); -// bufferHash.remove(last->filename); -// buffers.remove(last->link); -// --cacheSize; - -// qDebug() << "Deleted buffer" << last->filename; - -// delete last; -// } - -// return buffer->sdlBuffer; -// } -// } -//}; - -struct SoundBuffer -{ - sf::SoundBuffer sfBuffer; - QByteArray filename; - IntruListLink link; - - SoundBuffer() - : link(this) - {} -}; - -struct SoundEntity -{ - IntruList buffers; - QHash bufferHash; - unsigned int bufferSamples; - - sf::Sound soundMag[SOUND_MAG_SIZE]; - int magIndex; - - SoundEntity() - : bufferSamples(0), - magIndex(0) - {} - - ~SoundEntity() - { - QHash::iterator iter; - for (iter = bufferHash.begin(); iter != bufferHash.end(); ++iter) - delete iter.value(); - } - - void play(const char *filename, - int volume, - int pitch) - { - (void) pitch; - - volume = clamp(volume, 0, 100); - - sf::SoundBuffer &buffer = allocateBuffer(filename); - - int soundIndex = magIndex++; - if (magIndex > SOUND_MAG_SIZE-1) - magIndex = 0; - - sf::Sound &sound = soundMag[soundIndex]; - sound.stop(); - sound.setBuffer(buffer); - sound.setVolume(volume); - - sound.play(); - } - - void stop() - { - for (int i = 0; i < SOUND_MAG_SIZE; i++) - soundMag[i].stop(); - } - -private: - sf::SoundBuffer &allocateBuffer(const char *filename) - { - SoundBuffer *buffer = bufferHash.value(filename, 0); + SoundBuffer *buffer = bufferHash.value(soundKey, 0); if (buffer) { /* Buffer still in cashe. * Move to front of priority list */ - buffers.remove(buffer->link); buffers.append(buffer->link); - return buffer->sfBuffer; + return buffer; } else { /* Buffer not in cashe, needs to be loaded */ + SDL_RWops dataSource; + const char *extension; - FileStream data = - shState->fileSystem().openRead(filename, FileSystem::Audio); + shState->fileSystem().openRead(dataSource, filename.constData(), + FileSystem::Audio, false, &extension); - buffer = new SoundBuffer; - buffer->sfBuffer.loadFromStream(data); - bufferSamples += buffer->sfBuffer.getSampleCount(); + Sound_Sample *sampleHandle = Sound_NewSample(&dataSource, extension, 0, streamBufSize); - data.close(); - -// qDebug() << "SoundCache: Current memory consumption:" << bufferSamples/2; - - buffer->filename = filename; - - bufferHash.insert(filename, buffer); - buffers.prepend(buffer->link); - - // FIXME this part would look better if it actually looped until enough memory is freed - /* If memory limit is reached, delete lowest priority buffer. - * Samples are 2 bytes big */ - if ((bufferSamples/2) > SOUND_MAX_MEM && !buffers.isEmpty()) + if (!sampleHandle) { - SoundBuffer *last = buffers.tail(); - bufferHash.remove(last->filename); - buffers.remove(last->link); - bufferSamples -= last->sfBuffer.getSampleCount(); - - qDebug() << "Deleted buffer" << last->filename; - - delete last; + SDL_RWclose(&dataSource); + throw Exception(Exception::SDLError, "SDL_sound: %s", Sound_GetError()); } - return buffer->sfBuffer; + uint32_t decBytes = Sound_DecodeAll(sampleHandle); + uint8_t sampleSize = formatSampleSize(sampleHandle->actual.format); + uint32_t sampleCount = decBytes / sampleSize; + + uint32_t actSampleSize; +#ifdef RUBBERBAND + actSampleSize = sizeof(float); +#else + actSampleSize = sampleSize; +#endif + + buffer = new SoundBuffer; + buffer->key = soundKey; + buffer->bytes = actSampleSize * sampleCount; + + ALenum alFormat = chooseALFormat(actSampleSize, sampleHandle->actual.channels); + +#ifdef RUBBERBAND + /* Fill float buffer */ + ensureFloatBufSize(sampleCount); + genFloatSamples(sampleCount, sampleHandle->actual.format, + sampleHandle->buffer, floatBuf.data()); + + // XXX apply stretcher + + /* Upload data to AL */ + AL::Buffer::uploadData(buffer->alBuffer, alFormat, floatBuf.data(), + buffer->bytes, sampleHandle->actual.rate); +#else + AL::Buffer::uploadData(buffer->alBuffer, alFormat, sampleHandle->buffer, + buffer->bytes, sampleHandle->actual.rate); +#endif + Sound_FreeSample(sampleHandle); + + 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(soundKey, buffer); + buffers.prepend(buffer->link); + + bufferBytes = wouldBeBytes; + + return buffer; } } }; +static const int streamBufs = 3; + +struct ALDataSource +{ + enum Status + { + 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 float samplesToOffset(uint32_t samples) = 0; + + virtual void seekToOffset(float offset) = 0; + + /* Seek back to start */ + virtual void reset() = 0; +}; + +struct SDLSoundSource : ALDataSource +{ + Sound_Sample *sample; + SDL_RWops ops; + uint8_t sampleSize; + bool looped; + + ALenum alFormat; + ALsizei alFreq; + + SDLSoundSource(const std::string &filename, + uint32_t maxBufSize, + bool looped, + float pitch) + : looped(looped) + { + (void) pitch; + + const char *extension; + shState->fileSystem().openRead(ops, + filename.c_str(), + FileSystem::Audio, + false, &extension); + + sample = Sound_NewSample(&ops, 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; + } + + ~SDLSoundSource() + { + Sound_FreeSample(sample); + } + + Status fillBuffer(AL::Buffer::ID alBuffer) + { + uint32_t decoded = Sound_Decode(sample); + + if (sample->flags & SOUND_SAMPLEFLAG_EAGAIN) + { + /* Try to decode one more time on EAGAIN */ + decoded = Sound_Decode(sample); + + /* Give up */ + if (sample->flags & SOUND_SAMPLEFLAG_EAGAIN) + return ALDataSource::Error; + } + + if (sample->flags & SOUND_SAMPLEFLAG_ERROR) + return ALDataSource::Error; + + // XXX apply stretcher here + + AL::Buffer::uploadData(alBuffer, alFormat, sample->buffer, decoded, alFreq); + + if (sample->flags & SOUND_SAMPLEFLAG_EOF) + { + if (looped) + { + Sound_Rewind(sample); + return ALDataSource::WrapAround; + } + else + { + return ALDataSource::EndOfStream; + } + } + + return ALDataSource::NoError; + } + + float samplesToOffset(uint32_t samples) + { + uint32_t frames = samples / sample->actual.channels; + + return static_cast(frames) / sample->actual.rate; + } + + void seekToOffset(float offset) + { + Sound_Seek(sample, static_cast(offset * 1000)); + } + + void reset() + { + Sound_Rewind(sample); + } +}; + +/* State-machine like audio playback stream. + * This class is NOT thread safe */ +struct ALStream +{ + enum State + { + Closed, + Stopped, + Playing, + Paused + }; + + bool looped; + State state; + + ALDataSource *source; + SDL_Thread *thread; + + 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; + + AL::Source::ID alSrc; + AL::Buffer::ID alBuf[streamBufs]; + + uint64_t procSamples; + AL::Buffer::ID lastBuf; + + struct + { + ALenum format; + ALsizei freq; + } stream; + + enum LoopMode + { + Looped, + NotLooped + }; + + ALStream(LoopMode loopMode) + : 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(); + } + + ~ALStream() + { + close(); + + clearALQueue(); + + AL::Source::del(alSrc); + + for (int i = 0; i < streamBufs; ++i) + AL::Buffer::del(alBuf[i]); + + SDL_DestroyMutex(pauseMut); + } + + void close() + { + checkStopped(); + + switch (state) + { + case Playing: + case Paused: + stopStream(); + case Stopped: + closeSource(); + state = Closed; + case Closed: + return; + } + } + + void open(const std::string &filename, + float pitch) + { + checkStopped(); + + switch (state) + { + case Playing: + case Paused: + stopStream(); + case Stopped: + closeSource(); + case Closed: + openSource(filename, pitch); + } + + state = Stopped; + } + + void stop() + { + checkStopped(); + + switch (state) + { + case Closed: + case Stopped: + return; + case Playing: + case Paused: + stopStream(); + } + + state = Stopped; + } + + void play() + { + checkStopped(); + + switch (state) + { + case Closed: + case Playing: + return; + case Stopped: + startStream(); + break; + case Paused : + resumeStream(); + } + + state = Playing; + } + + void pause() + { + checkStopped(); + + switch (state) + { + case Closed: + case Stopped: + case Paused: + return; + case Playing: + pauseStream(); + } + + state = Paused; + } + + void setOffset(float value) + { + if (state == Closed) + return; + // XXX needs more work. protect source with mutex + source->seekToOffset(value); + needsRewind = false; + } + + void setVolume(float value) + { + AL::Source::setVolume(alSrc, value); + } + + State queryState() + { + checkStopped(); + + return state; + } + + float queryOffset() + { + if (state == Closed) + return 0; + + uint32_t sampleSum = + procSamples + AL::Source::getSampleOffset(alSrc); + + return source->samplesToOffset(sampleSum); + } + +private: + void closeSource() + { + delete source; + } + + void openSource(const std::string &filename, + float pitch) + { + source = new SDLSoundSource(filename, streamBufSize, looped, pitch); + needsRewind = false; + +#ifndef RUBBERBAND + AL::Source::setPitch(alSrc, pitch); +#endif + } + + void stopStream() + { + threadTermReq = true; + + AL::Source::stop(alSrc); + + if (thread) + { + SDL_WaitThread(thread, 0); + thread = 0; + needsRewind = true; + } + + procSamples = 0; + } + + void startStream() + { + clearALQueue(); + + procSamples = 0; + + preemptPause = false; + streamInited = false; + sourceExhausted = false; + threadTermReq = false; + thread = SDL_CreateThread(streamDataFun, "al_stream", this); + } + + void pauseStream() + { + SDL_LockMutex(pauseMut); + + if (AL::Source::getState(alSrc) != AL_PLAYING) + preemptPause = true; + 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) + source->reset(); + + for (int i = 0; i < streamBufs; ++i) + { + AL::Buffer::ID buf = alBuf[i]; + + status = source->fillBuffer(buf); + + if (status == ALDataSource::Error) + return; + + AL::Source::queueBuffer(alSrc, buf); + + if (firstBuffer) + { + resumeStream(); + + firstBuffer = false; + streamInited = true; + } + + if (threadTermReq) + return; + + if (status == ALDataSource::EndOfStream) + { + sourceExhausted = true; + break; + } + } + + /* Wait for buffers to be consumed, then + * refill and queue them up again */ + while (true) + { + ALint procBufs = AL::Source::getProcBufferCount(alSrc); + + while (procBufs--) + { + if (threadTermReq) + break; + + AL::Buffer::ID buf = AL::Source::unqueueBuffer(alSrc); + + if (buf == lastBuf) + { + /* Reset the processed sample count so + * querying the playback offset returns 0.0 again */ + procSamples = 0; + lastBuf = AL::Buffer::ID(0); + } + else + { + /* Add the sample count contained in this + * buffer to the total count */ + ALint bits = AL::Buffer::getBits(buf); + ALint size = AL::Buffer::getSize(buf); + + procSamples += (size / (bits / 8)); + } + + 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; + } + + if (threadTermReq) + break; + + SDL_Delay(AUDIO_SLEEP); + } + } + + static int streamDataFun(void *_self) + { + ALStream &self = *static_cast(_self); + self.streamData(); + return 0; + } +}; + +struct AudioStream +{ + 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 */ + float fadeVolume; + float extVolume; + + bool extPaused; + + ALStream stream; + SDL_mutex *streamMut; + + struct + { + /* 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; + + /* 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) + : baseVolume(1.0), + fadeVolume(1.0), + extVolume(1.0), + extPaused(false), + stream(loopMode) + { + current.volume = 1.0; + current.pitch = 1.0; + + fade.active = false; + fade.thread = 0; + + streamMut = SDL_CreateMutex(); + } + + ~AudioStream() + { + if (fade.thread) + { + fade.reqTerm = true; + SDL_WaitThread(fade.thread, 0); + } + + lockStream(); + + stream.stop(); + stream.close(); + + unlockStream(); + + SDL_DestroyMutex(streamMut); + } + + void play(const std::string &filename, + int volume, + int pitch) + { + finiFadeInt(); + + lockStream(); + + float _volume = clamp(volume, 0, 100) / 100.f; + float _pitch = clamp(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(); + 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; + } + + switch (sState) + { + case ALStream::Paused : + case ALStream::Playing : + stream.stop(); + case ALStream::Stopped : + stream.close(); + case ALStream::Closed : + break; + } + + stream.open(filename, _pitch); + setBaseVolume(_volume); + + current.filename = filename; + current.volume = _volume; + current.pitch = _pitch; + + if (!extPaused) + stream.play(); + + unlockStream(); + } + + void stop() + { + finiFadeInt(); + + lockStream(); + + stream.stop(); + + unlockStream(); + } + + void fadeOut(int duration) + { + lockStream(); + + ALStream::State sState = stream.queryState(); + + if (fade.active) + { + unlockStream(); + + return; + } + + if (sState == ALStream::Paused) + { + stream.stop(); + unlockStream(); + + 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, "audio_fade", 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(); + } + +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) + { + if (state != ALStream::Paused) + stream.stop(); + + setFadeVolume(1.0); + unlockStream(); + + break; + } + + setFadeVolume(resVol); + + unlockStream(); + + SDL_Delay(AUDIO_SLEEP); + } + + fade.active = false; + } + + static int fadeThreadFun(void *self) + { + static_cast(self)->fadeThread(); + + return 0; + } +}; + struct AudioPrivate { - MusicEntity bgm; - MusicEntity bgs; - MusicEntity me; + AudioStream bgm; + AudioStream bgs; + AudioStream me; - SoundEntity se; + SoundEmitter se; - sf::Thread *meWatchThread; - bool meWatchRunning; - bool meWatchThreadTerm; + /* 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; AudioPrivate() - : meWatchThread(0), - meWatchRunning(false), - meWatchThreadTerm(false) + : bgm(ALStream::Looped), + bgs(ALStream::Looped), + me(ALStream::NotLooped) { - me.music.setLoop(false); + meWatch.active = false; + meWatch.termReq = false; + meWatch.state = MeNotPlaying; + meWatch.thread = SDL_CreateThread(meWatchFun, "audio_mewatch", this); } ~AudioPrivate() { - if (meWatchThread) - { - meWatchThreadTerm = true; - meWatchThread->wait(); - delete meWatchThread; - } - - bgm.stop(); - bgs.stop(); - me.stop(); - se.stop(); + meWatch.termReq = true; + SDL_WaitThread(meWatch.thread, 0); } - void scheduleMeWatch() + void meWatchFunInt() { - if (meWatchRunning) - return; + const float fadeOutStep = 1.f / (200 / AUDIO_SLEEP); + const float fadeInStep = 1.f / (1000 / AUDIO_SLEEP); - meWatchRunning = true; - - if (meWatchThread) - meWatchThread->wait(); - - bgm.extPaused = true; - - delete meWatchThread; - meWatchThread = new sf::Thread(&AudioPrivate::meWatchFunc, this); - meWatchThread->launch(); - } - - void meWatchFunc() - { - // FIXME Need to catch the case where an ME is started while - // the BGM is still being faded in from this function - sf::Time sleepTime = sf::milliseconds(FADE_SLEEP); - const int bgmFadeOutSteps = 20; - const int bgmFadeInSteps = 100; - - /* Fade out BGM */ - for (int i = bgmFadeOutSteps; i > 0; --i) + while (true) { - if (meWatchThreadTerm) + if (meWatch.termReq) return; - if (bgm.music.getStatus() != sf::Music::Playing) + switch (meWatch.state) { - bgm.extVolume = 0; - bgm.updateVolumeSync(); + case MeNotPlaying: + { + me.lockStream(); + + if (me.stream.queryState() == ALStream::Playing) + { + /* ME playing detected. -> FadeOutBGM */ + bgm.extPaused = true; + meWatch.state = BgmFadingOut; + } + + me.unlockStream(); + break; } - bgm.extVolume = (1.0 / bgmFadeOutSteps) * (i-1); - bgm.updateVolumeSync(); - sf::sleep(sleepTime); - } - - SDL_LockMutex(bgm.mutex); - if (bgm.music.getStatus() == sf::Music::Playing) - bgm.music.pause(); - SDL_UnlockMutex(bgm.mutex); - - /* Linger while ME plays */ - while (me.music.getStatus() == sf::Music::Playing) - { - if (meWatchThreadTerm) - return; - - sf::sleep(sleepTime); - } - - SDL_LockMutex(bgm.mutex); - bgm.extPaused = false; - - if (bgm.music.getStatus() == sf::Music::Paused) - bgm.music.play(); - SDL_UnlockMutex(bgm.mutex); - - /* Fade in BGM again */ - for (int i = 0; i < bgmFadeInSteps; ++i) - { - if (meWatchThreadTerm) - return; - - SDL_LockMutex(bgm.mutex); - - if (bgm.music.getStatus() != sf::Music::Playing || bgm.noFadeInFlag) + case BgmFadingOut : { - bgm.noFadeInFlag = false; - bgm.extVolume = 1; - bgm.updateVolume(); - bgm.music.play(); - SDL_UnlockMutex(bgm.mutex); + 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(); + break; } - bgm.extVolume = (1.0 / bgmFadeInSteps) * (i+1); - bgm.updateVolume(); + case MePlaying : + { + me.lockStream(); - SDL_UnlockMutex(bgm.mutex); + if (me.stream.queryState() != ALStream::Playing) + { + /* ME has ended */ + bgm.lockStream(); - sf::sleep(sleepTime); + 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); + 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; + } + + me.lockStream(); + + 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); } + } - meWatchRunning = false; + static int meWatchFun(void *self) + { + static_cast(self)->meWatchFunInt(); + + return 0; } }; Audio::Audio() : p(new AudioPrivate) -{ -} +{} void Audio::bgmPlay(const char *filename, @@ -619,7 +1481,7 @@ void Audio::bgmStop() void Audio::bgmFade(int time) { - p->bgm.fade(time); + p->bgm.fadeOut(time); } @@ -637,7 +1499,7 @@ void Audio::bgsStop() void Audio::bgsFade(int time) { - p->bgs.fade(time); + p->bgs.fadeOut(time); } @@ -646,7 +1508,6 @@ void Audio::mePlay(const char *filename, int pitch) { p->me.play(filename, volume, pitch); - p->scheduleMeWatch(); } void Audio::meStop() @@ -656,7 +1517,7 @@ void Audio::meStop() void Audio::meFade(int time) { - p->me.fade(time); + p->me.fadeOut(time); } diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 6f3ea5d..f1a4890 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -26,6 +26,8 @@ #include "physfs.h" +#include + #include #include #include @@ -536,112 +538,35 @@ static const PHYSFS_Archiver RGSS_Archiver = RGSS_closeArchive }; -FileStream::FileStream(PHYSFS_File *file) -{ - p = file; -} - -FileStream::~FileStream() -{ -// if (p) -// PHYSFS_close(p); -} - -void FileStream::operator=(const FileStream &o) -{ - p = o.p; -} - -sf::Int64 FileStream::read(void *data, sf::Int64 size) -{ - if (!p) - return -1; - - return PHYSFS_readBytes(p, data, size); -} - -sf::Int64 FileStream::seek(sf::Int64 position) -{ - if (!p) - return -1; - - int success = PHYSFS_seek(p, (PHYSFS_uint64) position); - - return success ? position : -1; -} - -sf::Int64 FileStream::tell() -{ - if (!p) - return -1; - - return PHYSFS_tell(p); -} - -sf::Int64 FileStream::getSize() -{ - if (!p) - return -1; - - return PHYSFS_fileLength(p); -} - -sf::Int64 FileStream::write(const void *data, sf::Int64 size) -{ - if (!p) - return -1; - - return PHYSFS_writeBytes(p, data, size); -} - -void FileStream::close() -{ - if (p) - { - PHYSFS_close(p); - p = 0; - } -} - -static const char *imgExt[] = -{ - "jpg", - "png" -}; - -static const char *audExt[] = -{ -// "mid", -// "midi", -// "mp3", -// "wma", - "ogg", - "wav" -}; - -static const char *fonExt[] = -{ - "ttf" -}; - -struct FileExtensions -{ - const char **ext; - int count; -} fileExtensions[] = -{ - { imgExt, ARRAY_SIZE(imgExt) }, - { audExt, ARRAY_SIZE(audExt) }, - { fonExt, ARRAY_SIZE(fonExt) } -}; - struct FileSystemPrivate { /* All keys are lower case */ QHash pathCache; + QList extensions[FileSystem::Undefined]; + + /* Attempt to locate an extension string in a filename. + * Either a pointer into the input string pointing at the + * extension, or null is returned */ + const char *findExt(const char *filename) + { + int len; + + for (len = strlen(filename); len > 0; --len) + { + if (filename[len] == '/') + return 0; + + if (filename[len] == '.') + return &filename[len+1]; + } + + return 0; + } + const char *completeFileName(const char *filename, - FileSystem::FileType type) + FileSystem::FileType type, + const char **foundExt) { char buff[512]; size_t i; @@ -654,30 +579,48 @@ struct FileSystemPrivate QByteArray key(buff); if (pathCache.contains(key)) + { + /* The extension might already be included here, + * so try to find it */ + if (foundExt) + *foundExt = findExt(filename); + return pathCache[key].constData(); + } char buff2[512]; if (type != FileSystem::Undefined) { - FileExtensions *extTest = &fileExtensions[type]; - for (int i = 0; i < extTest->count; ++i) + QList &extList = extensions[type]; + for (int i = 0; i < extList.count(); ++i) { - snprintf(buff2, sizeof(buff2), "%s.%s", buff, extTest->ext[i]); + const char *ext = extList[i].constData(); + snprintf(buff2, sizeof(buff2), "%s.%s", buff, ext); key = buff2; + if (pathCache.contains(key)) + { + if (foundExt) + *foundExt = ext; + return pathCache[key].constData(); + } } } + if (foundExt) + *foundExt = 0; + return 0; } PHYSFS_File *openReadInt(const char *filename, - FileSystem::FileType type) + FileSystem::FileType type, + const char **foundExt) { - const char *foundName = completeFileName(filename, type); + const char *foundName = completeFileName(filename, type, foundExt); if (!foundName) throw Exception(Exception::NoFileError, @@ -696,6 +639,34 @@ FileSystem::FileSystem(const char *argv0, { p = new FileSystemPrivate; + /* Image extensions */ + p->extensions[Image] << "jpg" << "png"; + + /* Audio extensions */ + const Sound_DecoderInfo **di; + for (di = Sound_AvailableDecoders(); *di; ++di) + { + const char **ext; + for (ext = (*di)->extensions; *ext; ++ext) + { + /* All reported extension are uppercase, + * so we need to hammer them down first */ + char buf[16]; + for (size_t i = 0; i < sizeof(buf); ++i) + { + buf[i] = tolower((*ext)[i]); + + if (!buf[i]) + break; + } + + p->extensions[Audio] << buf; + } + } + + /* Font extensions */ + p->extensions[Font] << "ttf"; + PHYSFS_init(argv0); PHYSFS_registerArchiver(&RGSS_Archiver); @@ -745,14 +716,6 @@ void FileSystem::createPathCache() PHYSFS_enumerateFilesCallback(".", cacheEnumCB, p); } -FileStream FileSystem::openRead(const char *filename, - FileType type) -{ - PHYSFS_File *handle = p->openReadInt(filename, type); - - return FileStream(handle); -} - static inline PHYSFS_File *sdlPHYS(SDL_RWops *ops) { return static_cast(ops->hidden.unknown.data1); @@ -847,9 +810,10 @@ const Uint32 SDL_RWOPS_PHYSFS = SDL_RWOPS_UNKNOWN+10; void FileSystem::openRead(SDL_RWops &ops, const char *filename, FileType type, - bool freeOnClose) + bool freeOnClose, + const char **foundExt) { - PHYSFS_File *handle = p->openReadInt(filename, type); + PHYSFS_File *handle = p->openReadInt(filename, type, foundExt); ops.size = SDL_RWopsSize; ops.seek = SDL_RWopsSeek; @@ -867,7 +831,7 @@ void FileSystem::openRead(SDL_RWops &ops, bool FileSystem::exists(const char *filename, FileType type) { - const char *foundName = p->completeFileName(filename, type); + const char *foundName = p->completeFileName(filename, type, 0); return (foundName != 0); } diff --git a/src/filesystem.h b/src/filesystem.h index 8a18a23..8e97b78 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -22,33 +22,8 @@ #ifndef FILESYSTEM_H #define FILESYSTEM_H -#include "SFML/System/InputStream.hpp" - #include "SDL_rwops.h" -struct PHYSFS_File; - -class FileStream : public sf::InputStream -{ -public: - FileStream(PHYSFS_File *); - ~FileStream(); - - void operator=(const FileStream &o); - - sf::Int64 read(void *data, sf::Int64 size); - sf::Int64 seek(sf::Int64 position); - sf::Int64 tell(); - sf::Int64 getSize(); - - sf::Int64 write(const void *data, sf::Int64 size); - - void close(); - -private: - PHYSFS_File *p; /* NULL denotes invalid stream */ -}; - struct FileSystemPrivate; class FileSystem @@ -72,13 +47,11 @@ public: Undefined }; - FileStream openRead(const char *filename, - FileType type = Undefined); - void openRead(SDL_RWops &ops, const char *filename, FileType type = Undefined, - bool freeOnClose = false); + bool freeOnClose = false, + const char **foundExt = 0); bool exists(const char *filename, FileType type = Undefined); diff --git a/src/main.cpp b/src/main.cpp index 5425e77..aa182cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,10 +20,12 @@ */ #include "glew.h" +#include #include "SDL.h" #include "SDL_image.h" #include "SDL_ttf.h" +#include #include "sharedstate.h" #include "eventthread.h" @@ -77,7 +79,7 @@ int rgssThreadFun(void *userdata) { RGSSThreadData *threadData = static_cast(userdata); SDL_Window *win = threadData->window; - SDL_GLContext ctx; + SDL_GLContext glCtx; /* Setup GL context */ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); @@ -85,9 +87,9 @@ int rgssThreadFun(void *userdata) if (threadData->config.debugMode) SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); - ctx = SDL_GL_CreateContext(win); + glCtx = SDL_GL_CreateContext(win); - if (!ctx) + if (!glCtx) { rgssThreadError(threadData, QByteArray("Error creating context: ") + SDL_GetError()); return 0; @@ -96,7 +98,7 @@ int rgssThreadFun(void *userdata) if (glewInit() != GLEW_OK) { rgssThreadError(threadData, "Error initializing glew"); - SDL_GL_DeleteContext(ctx); + SDL_GL_DeleteContext(glCtx); return 0; } @@ -110,7 +112,7 @@ int rgssThreadFun(void *userdata) if (!GLEW_VERSION_2_0) { rgssThreadError(threadData, "At least OpenGL 2.0 is required"); - SDL_GL_DeleteContext(ctx); + SDL_GL_DeleteContext(glCtx); return 0; } @@ -121,7 +123,7 @@ int rgssThreadFun(void *userdata) { rgssThreadError(threadData, QByteArray("Required GL extension \"") + reqExt[i] + "\" not present"); - SDL_GL_DeleteContext(ctx); + SDL_GL_DeleteContext(glCtx); return 0; } } @@ -130,6 +132,28 @@ int rgssThreadFun(void *userdata) DebugLogger dLogger; + /* Setup AL context */ + ALCdevice *alcDev = alcOpenDevice(0); + + if (!alcDev) + { + rgssThreadError(threadData, "Error opening OpenAL device"); + SDL_GL_DeleteContext(glCtx); + return 0; + } + + ALCcontext *alcCtx = alcCreateContext(alcDev, 0); + + if (!alcCtx) + { + rgssThreadError(threadData, "Error creating OpenAL context"); + alcCloseDevice(alcDev); + SDL_GL_DeleteContext(glCtx); + return 0; + } + + alcMakeContextCurrent(alcCtx); + SharedState::initInstance(threadData); /* Start script execution */ @@ -140,7 +164,10 @@ int rgssThreadFun(void *userdata) SharedState::finiInstance(); - SDL_GL_DeleteContext(ctx); + alcDestroyContext(alcCtx); + alcCloseDevice(alcDev); + + SDL_GL_DeleteContext(glCtx); return 0; } @@ -155,6 +182,7 @@ int main(int, char *argv[]) if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { qDebug() << "Error initializing SDL:" << SDL_GetError(); + return 0; } @@ -163,6 +191,7 @@ int main(int, char *argv[]) { qDebug() << "Error initializing SDL_image:" << SDL_GetError(); SDL_Quit(); + return 0; } @@ -171,6 +200,17 @@ int main(int, char *argv[]) qDebug() << "Error initializing SDL_ttf:" << SDL_GetError(); IMG_Quit(); SDL_Quit(); + + return 0; + } + + if (Sound_Init() == 0) + { + qDebug() << "Error initializing SDL_sound:" << Sound_GetError(); + TTF_Quit(); + IMG_Quit(); + SDL_Quit(); + return 0; } @@ -241,6 +281,7 @@ int main(int, char *argv[]) SDL_DestroyWindow(win); + Sound_Quit(); TTF_Quit(); IMG_Quit(); SDL_Quit();