From c4525059ce7330bba637af5918bcc1248cdb3491 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Fri, 13 Apr 2012 16:54:08 -0300 Subject: [PATCH] implement basic sound engine using OpenAL --- src/framework/CMakeLists.txt | 15 +- src/framework/application.cpp | 79 ++++--- src/framework/application.h | 3 +- src/framework/cmake/FindVorbisFile.cmake | 10 + src/framework/const.h | 7 - src/framework/core/clock.cpp | 11 +- src/framework/core/clock.h | 2 + src/framework/core/filestream.cpp | 38 +++- src/framework/core/filestream.h | 11 +- src/framework/pch.h | 3 + src/framework/platform/win32window.cpp | 15 +- src/framework/sound/declarations.h | 44 ++++ src/framework/sound/oggsoundfile.cpp | 116 ++++++++++ src/framework/sound/oggsoundfile.h | 50 +++++ src/framework/sound/soundbuffer.cpp | 64 ++++++ src/framework/sound/soundbuffer.h | 42 ++++ src/framework/sound/soundfile.cpp | 69 ++++++ src/framework/sound/soundfile.h | 55 +++++ src/framework/sound/soundmanager.cpp | 245 ++++++++++++++++++++++ src/framework/sound/soundmanager.h | 75 +++++++ src/framework/sound/soundsource.cpp | 95 +++++++++ src/framework/sound/soundsource.h | 56 +++++ src/framework/sound/streamsoundsource.cpp | 144 +++++++++++++ src/framework/sound/streamsoundsource.h | 60 ++++++ src/otclient/otclient.cpp | 2 +- 25 files changed, 1236 insertions(+), 75 deletions(-) create mode 100644 src/framework/cmake/FindVorbisFile.cmake create mode 100644 src/framework/sound/declarations.h create mode 100644 src/framework/sound/oggsoundfile.cpp create mode 100644 src/framework/sound/oggsoundfile.h create mode 100644 src/framework/sound/soundbuffer.cpp create mode 100644 src/framework/sound/soundbuffer.h create mode 100644 src/framework/sound/soundfile.cpp create mode 100644 src/framework/sound/soundfile.h create mode 100644 src/framework/sound/soundmanager.cpp create mode 100644 src/framework/sound/soundmanager.h create mode 100644 src/framework/sound/soundsource.cpp create mode 100644 src/framework/sound/soundsource.h create mode 100644 src/framework/sound/streamsoundsource.cpp create mode 100644 src/framework/sound/streamsoundsource.h diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index 0cf5b562..705640a4 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -35,6 +35,8 @@ FIND_PACKAGE(Lua REQUIRED) FIND_PACKAGE(PhysFS REQUIRED) FIND_PACKAGE(GMP REQUIRED) FIND_PACKAGE(ZLIB REQUIRED) +FIND_PACKAGE(OpenAL REQUIRED) +FIND_PACKAGE(VorbisFile REQUIRED) # setup compiler options SET(CXX_WARNS "-Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-but-set-variable") @@ -114,24 +116,27 @@ ENDIF() INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIR} + ${OPENAL_INCLUDE_DIR} ${LUA_INCLUDE_DIR} ${PHYSFS_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} + ${VORBISFILE_INCLUDE_DIR} "${CMAKE_CURRENT_LIST_DIR}/.." ) SET(framework_LIBRARIES ${Boost_LIBRARIES} ${OPENGL_LIBRARIES} + ${OPENAL_LIBRARY} ${LUA_LIBRARIES} ${PHYSFS_LIBRARY} ${GMP_LIBRARY} ${ZLIB_LIBRARY} + ${VORBISFILE_LIBRARY} ${ADDITIONAL_LIBRARIES} ) - SET(framework_SOURCES ${framework_SOURCES} # framework ${CMAKE_CURRENT_LIST_DIR}/application.cpp @@ -182,6 +187,14 @@ SET(framework_SOURCES ${framework_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/graphics/paintershaderprogram.cpp ${CMAKE_CURRENT_LIST_DIR}/graphics/coordsbuffer.cpp + # framework sound + ${CMAKE_CURRENT_LIST_DIR}/sound/soundsource.cpp + ${CMAKE_CURRENT_LIST_DIR}/sound/soundbuffer.cpp + ${CMAKE_CURRENT_LIST_DIR}/sound/soundfile.cpp + ${CMAKE_CURRENT_LIST_DIR}/sound/soundmanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/sound/oggsoundfile.cpp + ${CMAKE_CURRENT_LIST_DIR}/sound/streamsoundsource.cpp + # framework otml ${CMAKE_CURRENT_LIST_DIR}/otml/otmldocument.cpp ${CMAKE_CURRENT_LIST_DIR}/otml/otmlemitter.cpp diff --git a/src/framework/application.cpp b/src/framework/application.cpp index 45a61047..4decd4b3 100644 --- a/src/framework/application.cpp +++ b/src/framework/application.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -64,10 +65,8 @@ Application::~Application() g_app = nullptr; } -void Application::init(const std::vector& args, int appFlags) +void Application::init(const std::vector& args) { - m_appFlags = appFlags; - // capture exit signals signal(SIGTERM, exitSignalHandler); signal(SIGINT, exitSignalHandler); @@ -83,38 +82,36 @@ void Application::init(const std::vector& args, int appFlags) // initialize resources g_resources.init(args[0].c_str()); - if(m_appFlags & Fw::AppEnableConfigs) { - // setup configs write directory - if(!g_resources.setupWriteDir(m_appName)) - logError("Could not setup write directory"); + // setup configs write directory + if(!g_resources.setupWriteDir(m_appName)) + logError("Could not setup write directory"); - // load configs - if(!g_configs.load("/config.otml")) - logInfo("Using default configurations."); - } + // load configs + if(!g_configs.load("/config.otml")) + logInfo("Using default configurations."); // setup platform window - if(m_appFlags & Fw::AppEnableGraphics) { - g_ui.init(); + g_ui.init(); - g_window.init(); - g_window.hide(); - g_window.setOnResize(std::bind(&Application::resize, this, _1)); - g_window.setOnInputEvent(std::bind(&Application::inputEvent, this, _1)); - g_window.setOnClose(std::bind(&Application::close, this)); + g_window.init(); + g_window.hide(); + g_window.setOnResize(std::bind(&Application::resize, this, _1)); + g_window.setOnInputEvent(std::bind(&Application::inputEvent, this, _1)); + g_window.setOnClose(std::bind(&Application::close, this)); - // initialize graphics - g_graphics.init(); + // initialize graphics + g_graphics.init(); - // fire first resize - resize(g_window.getSize()); + // initialize sound + g_sounds.init(); - // display window when the application starts running - //g_eventDispatcher.addEvent([]{ g_window.show(); }); - } + // fire first resize + resize(g_window.getSize()); - if(m_appFlags & Fw::AppEnableModules) - g_modules.discoverModulesPath(); + // display window when the application starts running + //g_eventDispatcher.addEvent([]{ g_window.show(); }); + + g_modules.discoverModulesPath(); m_initialized = true; } @@ -127,12 +124,10 @@ void Application::terminate() g_lua.callGlobalField("g_app", "onTerminate"); // hide the window because there is no render anymore - if(m_appFlags & Fw::AppEnableGraphics) - g_window.hide(); + g_window.hide(); // run modules unload events - if(m_appFlags & Fw::AppEnableModules) - g_modules.unloadModules(); + g_modules.unloadModules(); // release remaining lua object references g_lua.collectGarbage(); @@ -144,17 +139,17 @@ void Application::terminate() Connection::terminate(); // terminate graphics - if(m_appFlags & Fw::AppEnableGraphics) { - g_ui.terminate(); - g_window.terminate(); - } + g_ui.terminate(); + g_window.terminate(); + + // terminate sound + g_sounds.terminate(); // flush remaining dispatcher events g_eventDispatcher.flush(); // save configurations - if(m_appFlags & Fw::AppEnableConfigs) - g_configs.save(); + g_configs.save(); // release resources g_resources.terminate(); @@ -186,7 +181,7 @@ void Application::run() // poll all events before rendering poll(); - if(m_appFlags & Fw::AppEnableGraphics && g_window.isVisible()) { + if(g_window.isVisible()) { g_graphics.beginRender(); render(); g_graphics.endRender(); @@ -214,11 +209,9 @@ void Application::exit() void Application::poll() { - if(m_appFlags & Fw::AppEnableGraphics) { - // poll input events - g_window.poll(); - //g_particleManager.update(); - } + // poll input events + g_window.poll(); + //g_particleManager.update(); Connection::poll(); g_eventDispatcher.poll(); diff --git a/src/framework/application.h b/src/framework/application.h index e30d4d29..7870a423 100644 --- a/src/framework/application.h +++ b/src/framework/application.h @@ -35,7 +35,7 @@ public: Application(const std::string& appName); ~Application(); - virtual void init(const std::vector& args, int appFlags); + virtual void init(const std::vector& args); virtual void registerLuaFunctions(); virtual void terminate(); virtual void run(); @@ -64,7 +64,6 @@ protected: std::string m_appName; std::string m_appVersion; std::string m_appBuildDate; - int m_appFlags; int m_frameSleep; Boolean m_initialized; Boolean m_running; diff --git a/src/framework/cmake/FindVorbisFile.cmake b/src/framework/cmake/FindVorbisFile.cmake new file mode 100644 index 00000000..bab2a682 --- /dev/null +++ b/src/framework/cmake/FindVorbisFile.cmake @@ -0,0 +1,10 @@ +# Try to find the VORBISFILE library +# VORBISFILE_FOUND - system has VORBISFILE +# VORBISFILE_INCLUDE_DIR - the VORBISFILE include directory +# VORBISFILE_LIBRARY - the VORBISFILE library + +FIND_PATH(VORBISFILE_INCLUDE_DIR NAMES vorbis/vorbisfile.h) +FIND_LIBRARY(VORBISFILE_LIBRARY NAMES vorbisfile) +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(VORBISFILE DEFAULT_MSG VORBISFILE_LIBRARY VORBISFILE_INCLUDE_DIR) +MARK_AS_ADVANCED(VORBISFILE_LIBRARY VORBISFILE_INCLUDE_DIR) \ No newline at end of file diff --git a/src/framework/const.h b/src/framework/const.h index c75ad042..99c0ce21 100644 --- a/src/framework/const.h +++ b/src/framework/const.h @@ -264,13 +264,6 @@ namespace Fw DraggingState = 2048, LastWidgetState = 4096 }; - - enum AppicationFlags { - AppEnableModules = 1, - AppEnableGraphics = 2, - AppEnableConfigs = 4, - AppEnableAll = AppEnableModules | AppEnableGraphics | AppEnableConfigs - }; } #endif diff --git a/src/framework/core/clock.cpp b/src/framework/core/clock.cpp index 180a1aaa..f1595b8d 100644 --- a/src/framework/core/clock.cpp +++ b/src/framework/core/clock.cpp @@ -32,15 +32,20 @@ Clock::Clock() m_startupTime = std::chrono::high_resolution_clock::now(); m_currentTicks = 0; } - ticks_t Clock::updateTicks() { - auto timeNow = std::chrono::high_resolution_clock::now(); - m_currentTicks = std::chrono::duration_cast(timeNow - m_startupTime).count(); + m_currentTicks = asyncTicks(); m_currentTime = m_currentTicks/1000.0f; return m_currentTicks; } +ticks_t Clock::asyncTicks() +{ + auto timeNow = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(timeNow - m_startupTime).count(); +} + + void Clock::sleep(int ms) { usleep(ms * 1000); diff --git a/src/framework/core/clock.h b/src/framework/core/clock.h index 7057a15a..b303351e 100644 --- a/src/framework/core/clock.h +++ b/src/framework/core/clock.h @@ -33,10 +33,12 @@ public: ticks_t updateTicks(); void sleep(int ms); + ticks_t asyncTicks(); ticks_t ticks() { return m_currentTicks; } ticks_t ticksElapsed(long prevTicks) { return m_currentTicks - prevTicks; } ticks_t ticksFor(int delay) { return m_currentTicks + delay; } + float asyncTime() { return asyncTicks()/1000.0f; } float time() { return m_currentTime; } float timeElapsed(float prevTime) { return m_currentTime - prevTime; } float timeFor(float delay) { return m_currentTime + delay; } diff --git a/src/framework/core/filestream.cpp b/src/framework/core/filestream.cpp index 89558a0d..de92dc9a 100644 --- a/src/framework/core/filestream.cpp +++ b/src/framework/core/filestream.cpp @@ -35,38 +35,53 @@ FileStream::~FileStream() close(); } -void FileStream::close() +bool FileStream::close() { if(m_fileHandle) { if(PHYSFS_isInit() && PHYSFS_close(m_fileHandle) == 0) logTraceError("operation failed on '", m_name, "': ", PHYSFS_getLastError()); m_fileHandle = nullptr; + return true; } + return false; } -void FileStream::flush() +bool FileStream::flush() { - if(PHYSFS_flush(m_fileHandle) == 0) + if(PHYSFS_flush(m_fileHandle) == 0) { logTraceError("operation failed on '", m_name, "': ", PHYSFS_getLastError()); + return false; + } + return true; } -void FileStream::read(void *buffer, uint count) +int FileStream::read(void *buffer, int size, int nmemb) { - if(PHYSFS_read(m_fileHandle, buffer, 1, count) != count) + int res = PHYSFS_read(m_fileHandle, buffer, size, nmemb); + if(res == -1) { logTraceError("operation failed on '", m_name, "': ", PHYSFS_getLastError()); + return 0; + } + return res; } -void FileStream::write(void *buffer, uint count) +bool FileStream::write(void *buffer, int count) { - if(PHYSFS_write(m_fileHandle, buffer, 1, count) != count) + if(PHYSFS_write(m_fileHandle, buffer, 1, count) != count) { logTraceError("operation failed on '", m_name, "': ", PHYSFS_getLastError()); + return false; + } + return true; } -void FileStream::seek(uint pos) +bool FileStream::seek(int pos) { - if(PHYSFS_seek(m_fileHandle, pos) == 0) + if(PHYSFS_seek(m_fileHandle, pos) == 0) { logTraceError("operation failed on '", m_name, "': ", PHYSFS_getLastError()); + return false; + } + return true; } int FileStream::size() @@ -74,6 +89,11 @@ int FileStream::size() return PHYSFS_fileLength(m_fileHandle); } +int FileStream::tell() +{ + return PHYSFS_tell(m_fileHandle); +} + uint8 FileStream::getU8() { uint8 v = 0; diff --git a/src/framework/core/filestream.h b/src/framework/core/filestream.h index 388c8453..93bb6b6e 100644 --- a/src/framework/core/filestream.h +++ b/src/framework/core/filestream.h @@ -37,12 +37,13 @@ protected: public: ~FileStream(); - void close(); - void flush(); - void write(void *buffer, uint count); - void read(void *buffer, uint count); - void seek(uint pos); + bool close(); + bool flush(); + bool write(void *buffer, int count); + int read(void *buffer, int size, int nmemb = 1); + bool seek(int pos); int size(); + int tell(); std::string name() { return m_name; } std::string readAll(); diff --git a/src/framework/pch.h b/src/framework/pch.h index c5fcc0b4..3946f1b6 100644 --- a/src/framework/pch.h +++ b/src/framework/pch.h @@ -55,6 +55,9 @@ #include #include #include +#include +#include +#include // boost utilities #include diff --git a/src/framework/platform/win32window.cpp b/src/framework/platform/win32window.cpp index e03576bd..551f0a88 100644 --- a/src/framework/platform/win32window.cpp +++ b/src/framework/platform/win32window.cpp @@ -600,19 +600,26 @@ LRESULT WIN32Window::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar switch(wParam) { case SIZE_MAXIMIZED: m_maximized = true; + m_visible = true; break; case SIZE_RESTORED: m_maximized = false; + m_visible = true; + break; + case SIZE_MINIMIZED: + m_visible = false; break; } - m_visible = !(wParam == SIZE_MINIMIZED); - if(m_visible) { - m_size.setWidth(LOWORD(lParam)); - m_size.setHeight(HIWORD(lParam)); + Size size; + size.setWidth(std::max(std::min((int)LOWORD(lParam), 7680), m_minimumSize.width())); + size.setHeight(std::max(std::min((int)HIWORD(lParam), 4320), m_minimumSize.height())); + if(m_visible && m_size != size) { + m_size = size; m_onResize(m_size); } + break; } default: diff --git a/src/framework/sound/declarations.h b/src/framework/sound/declarations.h new file mode 100644 index 00000000..6ebbc685 --- /dev/null +++ b/src/framework/sound/declarations.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FRAMEWORK_SOUND_DECLARATIONS_H +#define FRAMEWORK_SOUND_DECLARATIONS_H + +#include + +#include +#include + +class SoundManager; +class SoundSource; +class SoundBuffer; +class SoundFile; +class StreamSoundSource; +class OggSoundFile; + +typedef std::shared_ptr SoundSourcePtr; +typedef std::shared_ptr SoundFilePtr; +typedef std::shared_ptr SoundBufferPtr; +typedef std::shared_ptr StreamSoundSourcePtr; +typedef std::shared_ptr OggSoundFilePtr; + +#endif diff --git a/src/framework/sound/oggsoundfile.cpp b/src/framework/sound/oggsoundfile.cpp new file mode 100644 index 00000000..a7010dc6 --- /dev/null +++ b/src/framework/sound/oggsoundfile.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "oggsoundfile.h" + +OggSoundFile::OggSoundFile(const FileStreamPtr& fileStream) : SoundFile(fileStream) +{ + memset(&m_vorbisFile, 0, sizeof(m_vorbisFile)); +} + +OggSoundFile::~OggSoundFile() +{ + ov_clear(&m_vorbisFile); +} + +bool OggSoundFile::prepareOgg() +{ + ov_callbacks callbacks = { cb_read, cb_seek, cb_close, cb_tell }; + ov_open_callbacks(m_file.get(), &m_vorbisFile, 0, 0, callbacks); + + vorbis_info* vi = ov_info(&m_vorbisFile, -1); + if(!vi) { + logError("ogg file not supported: ", m_file->name()); + return false; + } + + m_channels = vi->channels; + m_rate = vi->rate; + m_bps = 16; + m_size = ov_pcm_total(&m_vorbisFile, -1) * 2; + + return true; +} + +int OggSoundFile::read(void *buffer, int bufferSize) +{ + char* bytesBuffer = reinterpret_cast(buffer); + int section = 0; + size_t totalBytesRead = 0; + + while(bufferSize > 0) { + size_t bytesToRead = bufferSize; + long bytesRead = ov_read(&m_vorbisFile, bytesBuffer, bytesToRead, 0, 2, 1, §ion); + if(bytesRead == 0) + break; + + bufferSize -= bytesRead; + bytesBuffer += bytesRead; + totalBytesRead += bytesRead; + } + + return totalBytesRead; +} + +void OggSoundFile::reset() +{ + ov_pcm_seek(&m_vorbisFile, 0); +} + +size_t OggSoundFile::cb_read(void* ptr, size_t size, size_t nmemb, void* source) +{ + FileStream *file = static_cast(source); + return file->read(ptr, size, nmemb); +} + +int OggSoundFile::cb_seek(void* source, ogg_int64_t offset, int whence) +{ + FileStream *file = static_cast(source); + switch(whence) { + case SEEK_SET: + if(file->seek(offset)) + return 0; + break; + case SEEK_CUR: + if(file->seek(file->tell() + offset)) + return 0; + break; + case SEEK_END: + if(file->seek(file->size() + offset)) + return 0; + break; + } + return -1; +} + +int OggSoundFile::cb_close(void* source) +{ + FileStream *file = static_cast(source); + file->close(); + return 0; +} + +long OggSoundFile::cb_tell(void* source) +{ + FileStream *file = static_cast(source); + return file->tell(); +} diff --git a/src/framework/sound/oggsoundfile.h b/src/framework/sound/oggsoundfile.h new file mode 100644 index 00000000..64e37488 --- /dev/null +++ b/src/framework/sound/oggsoundfile.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef OGGSOUNDFILE_H +#define OGGSOUNDFILE_H + +#include "soundfile.h" + +#include + +class OggSoundFile : public SoundFile +{ +public: + OggSoundFile(const FileStreamPtr& fileStream); + virtual ~OggSoundFile(); + + bool prepareOgg(); + + int read(void *buffer, int bufferSize); + void reset(); + +private: + static size_t cb_read(void* ptr, size_t size, size_t nmemb, void* source); + static int cb_seek(void* source, ogg_int64_t offset, int whence); + static int cb_close(void* source); + static long cb_tell(void* source); + + OggVorbis_File m_vorbisFile; +}; + +#endif diff --git a/src/framework/sound/soundbuffer.cpp b/src/framework/sound/soundbuffer.cpp new file mode 100644 index 00000000..2f8e0b54 --- /dev/null +++ b/src/framework/sound/soundbuffer.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "soundbuffer.h" +#include "soundfile.h" + +#include + +SoundBuffer::SoundBuffer() +{ + m_bufferId = 0; + alGenBuffers(1, &m_bufferId); + assert(alGetError() == AL_NO_ERROR); +} + +SoundBuffer::~SoundBuffer() +{ + alDeleteBuffers(1, &m_bufferId); + assert(alGetError() == AL_NO_ERROR); +} + +bool SoundBuffer::loadSoundFile(const SoundFilePtr& soundFile) +{ + ALenum format = soundFile->getSampleFormat(); + if(format == AL_UNDETERMINED) { + logError("unable to determine sample format for '", soundFile->getName(), "'"); + return false; + } + + DataBuffer samples(soundFile->getSize()); + int read = soundFile->read(&samples[0], soundFile->getSize()); + if(read <= 0) { + logError("unable to fill audio buffer data for '", soundFile->getName(), "'"); + return false; + } + + alBufferData(m_bufferId, format, &samples[0], soundFile->getSize(), soundFile->getRate()); + ALenum err = alGetError(); + if(err != AL_NO_ERROR) { + logError("unable to fill audio buffer data for '", soundFile->getName(), "': ", alGetString(err)); + return false; + } + + return true; +} diff --git a/src/framework/sound/soundbuffer.h b/src/framework/sound/soundbuffer.h new file mode 100644 index 00000000..294a7459 --- /dev/null +++ b/src/framework/sound/soundbuffer.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SOUNDBUFFER_H +#define SOUNDBUFFER_H + +#include "declarations.h" + +class SoundBuffer +{ +public: + SoundBuffer(); + ~SoundBuffer(); + + bool loadSoundFile(const SoundFilePtr& soundFile); + + int getBufferId() { return m_bufferId; } + +private: + ALuint m_bufferId; +}; + +#endif diff --git a/src/framework/sound/soundfile.cpp b/src/framework/sound/soundfile.cpp new file mode 100644 index 00000000..36aab82b --- /dev/null +++ b/src/framework/sound/soundfile.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "soundfile.h" +#include "oggsoundfile.h" +#include + +SoundFile::SoundFile(const FileStreamPtr& fileStream) +{ + m_file = fileStream; +} + +SoundFilePtr SoundFile::loadSoundFile(const std::string& filename) +{ + FileStreamPtr file = g_resources.openFile(filename); + if(!file) { + logTraceError("unable to open ", filename); + return nullptr; + } + + char magic[4]; + file->read(magic, 4); + file->seek(0); + + SoundFilePtr soundFile; + if(strncmp(magic, "OggS", 4) == 0) { + OggSoundFilePtr oggSoundFile = OggSoundFilePtr(new OggSoundFile(file)); + if(oggSoundFile->prepareOgg()) + soundFile = oggSoundFile; + } else + logError("unknown sound file format ", filename); + + return soundFile; +} + +ALenum SoundFile::getSampleFormat() +{ + if(m_channels == 2) { + if(m_bps == 16) + return AL_FORMAT_STEREO16; + else if(m_bps == 8) + return AL_FORMAT_STEREO8; + } else if(m_channels == 1) { + if(m_bps == 16) + return AL_FORMAT_MONO16; + else if(m_bps == 8) + return AL_FORMAT_MONO8; + } + return AL_UNDETERMINED; +} diff --git a/src/framework/sound/soundfile.h b/src/framework/sound/soundfile.h new file mode 100644 index 00000000..d5002675 --- /dev/null +++ b/src/framework/sound/soundfile.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SOUNDFILE_H +#define SOUNDFILE_H + +#include "declarations.h" +#include + +class SoundFile +{ +public: + SoundFile(const FileStreamPtr& fileStream); + virtual ~SoundFile() { } + static SoundFilePtr loadSoundFile(const std::string& filename); + + virtual int read(void *buffer, int bufferSize) = 0; + virtual void reset() = 0; + + ALenum getSampleFormat(); + + int getChannels() { return m_channels; } + int getRate() { return m_rate; } + int getBps() { return m_bps; } + int getSize() { return m_size; } + std::string getName() { return m_file ? m_file->name() : std::string(); } + +protected: + FileStreamPtr m_file; + int m_channels; + int m_rate; + int m_bps; + int m_size; +}; + +#endif diff --git a/src/framework/sound/soundmanager.cpp b/src/framework/sound/soundmanager.cpp new file mode 100644 index 00000000..b0bbdb34 --- /dev/null +++ b/src/framework/sound/soundmanager.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "soundmanager.h" +#include "soundsource.h" +#include "soundbuffer.h" +#include "soundfile.h" +#include "streamsoundsource.h" + +#include +#include + +SoundManager g_sounds; + +void SoundManager::init() +{ + m_run = false; + + m_device = alcOpenDevice(NULL); + if(!m_device) { + logError("unable to open audio device"); + return; + } + + m_context = alcCreateContext(m_device, NULL); + if(!m_context) { + logError("unable to create audio context: ", alcGetString(m_device, alcGetError(m_device))); + return; + } + + alcMakeContextCurrent(m_context); + m_thread = std::thread(std::bind(&SoundManager::audioThread, &g_sounds)); + while(!m_run) + g_clock.sleep(1); + + m_musicEnabled = true; + m_soundEnabled = true; + + /* + g_eventDispatcher.scheduleEvent([this] { + play("/test.ogg"); + }, 10); + */ +} + +void SoundManager::terminate() +{ + m_run = false; + m_thread.join(); + + m_sources.clear(); + m_buffers.clear(); + m_musicSource = nullptr; + m_currentMusic = ""; + + m_musicEnabled = false; + m_soundEnabled = false; + + alcMakeContextCurrent(NULL); + + if(m_context) { + alcDestroyContext(m_context); + m_context = nullptr; + } + + if(m_device) { + alcCloseDevice(m_device); + m_device = nullptr; + } +} + +void SoundManager::audioThread() +{ + m_run = true; + while(m_run) { + //TODO: use condition variable + g_clock.sleep(30); + update(); + } +} + +void SoundManager::update() +{ + std::lock_guard lock(m_mutex); + + static ticks_t lastUpdate = 0; + ticks_t now = g_clock.asyncTicks(); + + if(now - lastUpdate < 300) + return; + lastUpdate = now; + + for(auto it = m_sources.begin(); it != m_sources.end();) { + SoundSourcePtr source = *it; + + source->update(); + + if(!source->isPlaying()) + it = m_sources.erase(it); + else + ++it; + } + + if(m_musicSource) { + m_musicSource->update(); + if(!m_musicSource->isPlaying()) + m_musicSource = nullptr; + } + + if(m_context) { + alcProcessContext(m_context); + } +} + +void SoundManager::preload(const std::string& filename) +{ + std::lock_guard lock(m_mutex); + + auto it = m_buffers.find(filename); + if(it != m_buffers.end()) + return; + + SoundFilePtr soundFile = SoundFile::loadSoundFile(filename); + + // only keep small files + if(soundFile->getSize() > MAX_CACHE_SIZE) + return; + + SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer); + if(buffer->loadSoundFile(soundFile)) + m_buffers[filename] = buffer; +} + +void SoundManager::enableSound(bool enable) +{ + std::lock_guard lock(m_mutex); + + if(!isAudioEnabled()) + return; +} + +void SoundManager::play(const std::string& filename) +{ + std::lock_guard lock(m_mutex); + + if(!m_soundEnabled) + return; + + SoundSourcePtr soundSource = createSoundSource(filename); + if(!soundSource) { + logError("unable to play '", filename, "'"); + return; + } + + soundSource->setRelative(true); + soundSource->play(); + + m_sources.push_back(soundSource); +} + +void SoundManager::enableMusic(bool enable) +{ + std::lock_guard lock(m_mutex); + + if(!isAudioEnabled()) + return; + + m_musicEnabled = enable; + + if(enable) + playMusic(m_currentMusic); + else + m_musicSource = nullptr; +} + +void SoundManager::playMusic(const std::string& filename, bool fade) +{ + std::lock_guard lock(m_mutex); + + if(m_currentMusic == filename && m_musicSource) + return; + m_currentMusic = filename; + + if(!m_musicEnabled) + return; + + if(filename.empty()) { + m_musicSource = nullptr; + return; + } +} + +void SoundManager::stopMusic(float fadetime) +{ + std::lock_guard lock(m_mutex); +} + +SoundSourcePtr SoundManager::createSoundSource(const std::string& filename) +{ + SoundSourcePtr soundSource; + + auto it = m_buffers.find(filename); + if(it != m_buffers.end()) { + soundSource = SoundSourcePtr(new SoundSource); + soundSource->setBuffer(it->second); + } else { + SoundFilePtr soundFile = SoundFile::loadSoundFile(filename); + if(!soundFile) + return nullptr; + + if(soundFile->getSize() <= MAX_CACHE_SIZE) { + soundSource = SoundSourcePtr(new SoundSource); + SoundBufferPtr buffer = SoundBufferPtr(new SoundBuffer); + buffer->loadSoundFile(soundFile); + soundSource->setBuffer(buffer); + m_buffers[filename] = buffer; + logWarning("uncached sound '", filename, "' requested to be played"); + } else { + StreamSoundSourcePtr streamSoundSource(new StreamSoundSource); + streamSoundSource->setSoundFile(soundFile); + soundSource = streamSoundSource; + } + } + + return soundSource; +} diff --git a/src/framework/sound/soundmanager.h b/src/framework/sound/soundmanager.h new file mode 100644 index 00000000..3d47f838 --- /dev/null +++ b/src/framework/sound/soundmanager.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SOUNDMANAGER_H +#define SOUNDMANAGER_H + +#include "declarations.h" + +class SoundManager +{ + enum { + MAX_CACHE_SIZE = 10000000 + }; + +public: + void init(); + void terminate(); + + void audioThread(); + + void update(); + + void preload(const std::string& filename); + + void enableSound(bool enable); + void play(const std::string& filename); + + void enableMusic(bool enable); + void playMusic(const std::string& filename, bool fade = false); + void stopMusic(float fadetime = 0); + + bool isMusicEnabled() { return m_musicEnabled; } + bool isSoundEnabled() { return m_soundEnabled; } + bool isAudioEnabled() { return m_device && m_context; } + std::string getCurrentMusic() { return m_currentMusic; } + +private: + SoundSourcePtr createSoundSource(const std::string& filename); + ALuint loadFileIntoBuffer(const SoundFilePtr& soundFile); + + std::map m_buffers; + std::vector m_sources; + StreamSoundSourcePtr m_musicSource; + ALCdevice *m_device; + ALCcontext *m_context; + std::thread m_thread; + std::atomic m_run; + std::recursive_mutex m_mutex; + Boolean m_musicEnabled; + Boolean m_soundEnabled; + std::string m_currentMusic; +}; + +extern SoundManager g_sounds; + +#endif diff --git a/src/framework/sound/soundsource.cpp b/src/framework/sound/soundsource.cpp new file mode 100644 index 00000000..6833fdac --- /dev/null +++ b/src/framework/sound/soundsource.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "soundsource.h" +#include "soundbuffer.h" + +SoundSource::SoundSource() +{ + m_sourceId = 0; + alGenSources(1, &m_sourceId); + assert(alGetError() == AL_NO_ERROR); + setReferenceDistance(128); +} + +SoundSource::~SoundSource() +{ + stop(); + alDeleteSources(1, &m_sourceId); + assert(alGetError() == AL_NO_ERROR); +} + +void SoundSource::play() +{ + alSourcePlay(m_sourceId); + assert(alGetError() == AL_NO_ERROR); +} + +void SoundSource::stop() +{ + alSourceStop(m_sourceId); + assert(alGetError() == AL_NO_ERROR); + if(m_buffer) { + alSourcei(m_sourceId, AL_BUFFER, AL_NONE); + assert(alGetError() == AL_NO_ERROR); + m_buffer = nullptr; + } +} + +bool SoundSource::isPlaying() +{ + ALint state = AL_PLAYING; + alGetSourcei(m_sourceId, AL_SOURCE_STATE, &state); + return state != AL_STOPPED; +} + +void SoundSource::setBuffer(const SoundBufferPtr& buffer) +{ + alSourcei(m_sourceId, AL_BUFFER, buffer->getBufferId()); + assert(alGetError() == AL_NO_ERROR); + m_buffer = buffer; +} + +void SoundSource::setLooping(bool looping) +{ + alSourcei(m_sourceId, AL_LOOPING, looping ? AL_TRUE : AL_FALSE); +} + +void SoundSource::setRelative(bool relative) +{ + alSourcei(m_sourceId, AL_SOURCE_RELATIVE, relative ? AL_TRUE : AL_FALSE); +} + +void SoundSource::setReferenceDistance(float distance) +{ + alSourcef(m_sourceId, AL_REFERENCE_DISTANCE, distance); +} + +void SoundSource::setGain(float gain) +{ + alSourcef(m_sourceId, AL_GAIN, gain); +} + +void SoundSource::setPitch(float pitch) +{ + alSourcef(m_sourceId, AL_PITCH, pitch); +} diff --git a/src/framework/sound/soundsource.h b/src/framework/sound/soundsource.h new file mode 100644 index 00000000..f00c9407 --- /dev/null +++ b/src/framework/sound/soundsource.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef SOUNDSOURCE_H +#define SOUNDSOURCE_H + +#include "declarations.h" + +class SoundSource +{ +public: + SoundSource(); + virtual ~SoundSource(); + + void play(); + void stop(); + + bool isPlaying(); + + void setBuffer(const SoundBufferPtr& buffer); + virtual void setLooping(bool looping); + void setRelative(bool relative); + void setReferenceDistance(float distance); + void setGain(float gain); + void setPitch(float pitch); + + // TODO: velocity, position + +protected: + virtual void update() { } + friend class SoundManager; + + ALuint m_sourceId; + SoundBufferPtr m_buffer; +}; + +#endif diff --git a/src/framework/sound/streamsoundsource.cpp b/src/framework/sound/streamsoundsource.cpp new file mode 100644 index 00000000..5a07f794 --- /dev/null +++ b/src/framework/sound/streamsoundsource.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "streamsoundsource.h" +#include "soundbuffer.h" +#include "soundfile.h" + +#include +#include + +StreamSoundSource::StreamSoundSource() +{ + for(auto& buffer : m_buffers) + buffer = SoundBufferPtr(new SoundBuffer); + m_fadeState = NoFading; +} + +StreamSoundSource::~StreamSoundSource() +{ + stop(); + + ALint queued; + alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued); + for(int i = 0; i < queued; ++i) { + ALuint buffer; + alSourceUnqueueBuffers(m_sourceId, 1, &buffer); + } + m_buffers.fill(nullptr); +} + +void StreamSoundSource::setSoundFile(const SoundFilePtr& soundFile) +{ + m_soundFile = soundFile; + + ALint queued; + alGetSourcei(m_sourceId, AL_BUFFERS_QUEUED, &queued); + for(int i = 0; i < STREAM_FRAGMENTS - queued; ++i) { + if(!fillBufferAndQueue(m_buffers[i]->getBufferId())) + break; + } +} + +void StreamSoundSource::update() +{ + ALint processed = 0; + alGetSourcei(m_sourceId, AL_BUFFERS_PROCESSED, &processed); + for(ALint i = 0; i < processed; ++i) { + ALuint buffer; + alSourceUnqueueBuffers(m_sourceId, 1, &buffer); + //SoundManager::check_al_error("Couldn't unqueue audio buffer: "); + + if(!fillBufferAndQueue(buffer)) + break; + } + + if(!isPlaying()) { + if(processed == 0 || !m_looping) + return; + + // we might have to restart the source if we had a buffer underrun + //log_info << "Restarting audio source because of buffer underrun" << std::endl; + //play(); + } + + float realTime = g_clock.asyncTime(); + if(m_fadeState == FadingOn) { + float time = realTime - m_fadeStartTime; + if(time >= m_fadeTime) { + setGain(1.0); + m_fadeState = NoFading; + } else { + setGain(time / m_fadeTime); + } + } else if(m_fadeState == FadingOff) { + float time = realTime - m_fadeStartTime; + if(time >= m_fadeTime) { + stop(); + m_fadeState = NoFading; + } else { + setGain((m_fadeTime - time) / m_fadeTime); + } + } +} + +bool StreamSoundSource::fillBufferAndQueue(ALuint buffer) +{ + // fill buffer + DataBuffer bufferData(STREAM_FRAGMENT_SIZE); + + int bytesRead = 0; + do { + bytesRead += m_soundFile->read(&bufferData[bytesRead], STREAM_FRAGMENT_SIZE - bytesRead); + + // end of sound file + if(bytesRead < STREAM_FRAGMENT_SIZE) { + if(m_looping) + m_soundFile->reset(); + else + break; + } + } while(bytesRead < STREAM_FRAGMENT_SIZE); + + if(bytesRead > 0) { + ALenum format = m_soundFile->getSampleFormat(); + alBufferData(buffer, format, &bufferData[0], bytesRead, m_soundFile->getRate()); + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + logError("unable to refill audio buffer for '", m_soundFile->getName(), "': ", alGetString(err)); + + alSourceQueueBuffers(m_sourceId, 1, &buffer); + err = alGetError(); + if(err != AL_NO_ERROR) + logError("unable to queue audio buffer for '", m_soundFile->getName(), "': ", alGetString(err)); + } + + // return false if there aren't more buffers to fill + return bytesRead >= STREAM_FRAGMENT_SIZE; +} + +void StreamSoundSource::setFading(FadeState state, float fadeTime) +{ + m_fadeState = state; + m_fadeTime = fadeTime; + m_fadeStartTime = g_clock.asyncTime(); +} diff --git a/src/framework/sound/streamsoundsource.h b/src/framework/sound/streamsoundsource.h new file mode 100644 index 00000000..8511830a --- /dev/null +++ b/src/framework/sound/streamsoundsource.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef STREAMSOUNDSOURCE_H +#define STREAMSOUNDSOURCE_H + +#include "soundsource.h" + +class StreamSoundSource : public SoundSource +{ + enum { + STREAM_BUFFER_SIZE = 1024 * 500, + STREAM_FRAGMENTS = 5, + STREAM_FRAGMENT_SIZE = STREAM_BUFFER_SIZE / STREAM_FRAGMENTS + }; + +public: + enum FadeState { NoFading, FadingOn, FadingOff }; + + StreamSoundSource(); + virtual ~StreamSoundSource(); + + void setSoundFile(const SoundFilePtr& soundFile); + + void setFading(FadeState state, float fadetime); + FadeState getFadeState() { return m_fadeState; } + + void update(); + +private: + bool fillBufferAndQueue(ALuint buffer); + + SoundFilePtr m_soundFile; + std::array m_buffers; + FadeState m_fadeState; + float m_fadeStartTime; + float m_fadeTime; + Boolean m_looping; +}; + +#endif diff --git a/src/otclient/otclient.cpp b/src/otclient/otclient.cpp index 72ebc5c8..a084a525 100644 --- a/src/otclient/otclient.cpp +++ b/src/otclient/otclient.cpp @@ -79,7 +79,7 @@ void OTClient::init(const std::vector& args) logInfo("Startup options:", startupOptions); g_logger.setLogFile(Fw::formatString("%s.txt", Otc::AppCompactName)); - Application::init(args, Fw::AppEnableAll); + Application::init(args); g_modules.discoverModules();