/** @file audiodriver.cpp Audio driver loading and interface management. * * @authors Copyright © 2012-2017 Jaakko Keränen * @authors Copyright © 2013-2015 Daniel Swanson * * @par License * GPL: http://www.gnu.org/licenses/gpl.html * * This program 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. This program 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 this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "audio/audiodriver.h" #include "dd_main.h" #include "audio/sys_audiod_dummy.h" #ifndef DENG_DISABLE_SDLMIXER # include "audio/sys_audiod_sdlmixer.h" #endif #include #include #include using namespace de; DENG2_PIMPL(AudioDriver) { bool initialized = false; ::Library *library = nullptr; audiodriver_t iBase; audiointerface_sfx_t iSfx; audiointerface_music_t iMusic; audiointerface_cd_t iCd; Impl(Public *i) : Base(i) { zap(iBase); zap(iSfx); zap(iMusic); zap(iCd); } static LibraryFile *tryFindAudioPlugin(String const &name) { if (!name.isEmpty()) { LibraryFile *found = nullptr; Library_ForAll([&name, &found] (LibraryFile &lib) { if (lib.hasUnderscoreName(name)) { found = &lib; return LoopAbort; } return LoopContinue; }); return found; } return nullptr; } void getDummyInterfaces() { DENG2_ASSERT(!initialized); library = nullptr; std::memcpy(&iBase, &audiod_dummy, sizeof(iBase)); std::memcpy(&iSfx, &audiod_dummy_sfx, sizeof(iSfx)); std::memcpy(&iMusic, &audiod_dummy_music, sizeof(iMusic)); std::memcpy(&iCd, &audiod_dummy_cd, sizeof(iCd)); } #ifndef DENG_DISABLE_SDLMIXER void getSdlMixerInterfaces() { DENG2_ASSERT(!initialized); library = nullptr; std::memcpy(&iBase, &audiod_sdlmixer, sizeof(iBase)); std::memcpy(&iSfx, &audiod_sdlmixer_sfx, sizeof(iSfx)); std::memcpy(&iMusic, &audiod_sdlmixer_music, sizeof(iMusic)); std::memcpy(&iCd, &audiod_dummy_cd, sizeof(iCd)); } #endif void importInterfaces(LibraryFile &libFile) { DENG2_ASSERT(!initialized); zap(iBase); zap(iSfx); zap(iMusic); zap(iCd); library = Library_New(libFile.path().toUtf8().constData()); if(!library) { throw LoadError("AudioDriver::importInterfaces", "Failed to load " + libFile.description()); } de::Library &lib = libFile.library(); lib.setSymbolPtr( iBase.Init, "DS_Init"); lib.setSymbolPtr( iBase.Shutdown, "DS_Shutdown"); lib.setSymbolPtr( iBase.Event, "DS_Event"); lib.setSymbolPtr( iBase.Set, "DS_Set", de::Library::OptionalSymbol); if(lib.hasSymbol("DS_SFX_Init")) { lib.setSymbolPtr( iSfx.gen.Init, "DS_SFX_Init"); lib.setSymbolPtr( iSfx.gen.Create, "DS_SFX_CreateBuffer"); lib.setSymbolPtr( iSfx.gen.Destroy, "DS_SFX_DestroyBuffer"); lib.setSymbolPtr( iSfx.gen.Load, "DS_SFX_Load"); lib.setSymbolPtr( iSfx.gen.Reset, "DS_SFX_Reset"); lib.setSymbolPtr( iSfx.gen.Play, "DS_SFX_Play"); lib.setSymbolPtr( iSfx.gen.Stop, "DS_SFX_Stop"); lib.setSymbolPtr( iSfx.gen.Refresh, "DS_SFX_Refresh"); lib.setSymbolPtr( iSfx.gen.Set, "DS_SFX_Set"); lib.setSymbolPtr( iSfx.gen.Setv, "DS_SFX_Setv"); lib.setSymbolPtr( iSfx.gen.Listener, "DS_SFX_Listener"); lib.setSymbolPtr( iSfx.gen.Listenerv, "DS_SFX_Listenerv"); lib.setSymbolPtr( iSfx.gen.Getv, "DS_SFX_Getv", de::Library::OptionalSymbol); } if(lib.hasSymbol("DM_Music_Init")) { lib.setSymbolPtr( iMusic.gen.Init, "DM_Music_Init"); lib.setSymbolPtr( iMusic.gen.Update, "DM_Music_Update"); lib.setSymbolPtr( iMusic.gen.Get, "DM_Music_Get"); lib.setSymbolPtr( iMusic.gen.Set, "DM_Music_Set"); lib.setSymbolPtr( iMusic.gen.Pause, "DM_Music_Pause"); lib.setSymbolPtr( iMusic.gen.Stop, "DM_Music_Stop"); lib.setSymbolPtr( iMusic.SongBuffer, "DM_Music_SongBuffer", de::Library::OptionalSymbol); lib.setSymbolPtr( iMusic.Play, "DM_Music_Play", de::Library::OptionalSymbol); lib.setSymbolPtr( iMusic.PlayFile, "DM_Music_PlayFile", de::Library::OptionalSymbol); } if(lib.hasSymbol("DM_CDAudio_Init")) { lib.setSymbolPtr( iCd.gen.Init, "DM_CDAudio_Init"); lib.setSymbolPtr( iCd.gen.Update, "DM_CDAudio_Update"); lib.setSymbolPtr( iCd.gen.Set, "DM_CDAudio_Set"); lib.setSymbolPtr( iCd.gen.Get, "DM_CDAudio_Get"); lib.setSymbolPtr( iCd.gen.Pause, "DM_CDAudio_Pause"); lib.setSymbolPtr( iCd.gen.Stop, "DM_CDAudio_Stop"); lib.setSymbolPtr( iCd.Play, "DM_CDAudio_Play"); } } }; AudioDriver::AudioDriver() : d(new Impl(this)) {} String AudioDriver::name() const { if(!isLoaded()) return "(invalid)"; return AudioDriver_GetName(App_AudioSystem().toDriverId(this)); } AudioDriver::Status AudioDriver::status() const { if(d->initialized) return Initialized; if(d->iBase.Init != nullptr) return Loaded; return Invalid; } String AudioDriver::statusAsText() const { switch(status()) { case Invalid: return "Invalid"; case Loaded: return "Loaded"; case Initialized: return "Initialized"; default: DENG2_ASSERT(!"AudioDriver::statusAsText: Unknown status"); return "Unknown"; } } void AudioDriver::load(String const &identifier) { LOG_AS("AudioDriver"); if(isLoaded()) { /// @throw LoadError Attempted to load on top of an already loaded driver. throw LoadError("AudioDriver::load", "Already initialized. Cannot load '" + identifier + "'"); } // Perhaps a built-in audio driver? if(!identifier.compareWithoutCase("dummy")) { d->getDummyInterfaces(); return; } #ifndef DENG_DISABLE_SDLMIXER if(!identifier.compareWithoutCase("sdlmixer")) { d->getSdlMixerInterfaces(); return; } #endif // Perhaps a plugin audio driver? LibraryFile *plugin = Impl::tryFindAudioPlugin(identifier); if(!plugin) { /// @throw LoadError Unknown driver specified. throw LoadError("AudioDriver::load", "Unknown driver \"" + identifier + "\""); } try { // Exchange entrypoints. d->importInterfaces(*plugin); } catch(de::Library::SymbolMissingError const &er) { /// @throw LoadError One or more missing symbol. throw LoadError("AudioDriver::load", "Failed exchanging entrypoints:\n" + er.asText()); } } void AudioDriver::unload() { LOG_AS("AudioDriver"); if(isInitialized()) { /// @throw LoadError Cannot unload while initialized. throw LoadError("AudioDriver::unload", "'" + name() + "' is still initialized, cannot unload"); } if(isLoaded()) { Library_Delete(d->library); d->library = nullptr; zap(d->iBase); zap(d->iSfx); zap(d->iMusic); zap(d->iCd); } } void AudioDriver::initialize() { LOG_AS("AudioDriver"); // Already been here? if(d->initialized) return; DENG2_ASSERT(d->iBase.Init != nullptr); d->initialized = d->iBase.Init(); } void AudioDriver::deinitialize() { LOG_AS("AudioDriver"); // Already been here? if(!d->initialized) return; if(d->iBase.Shutdown) { d->iBase.Shutdown(); } d->initialized = false; } ::Library *AudioDriver::library() const { return d->library; } bool AudioDriver::isAvailable(String const &identifier) { if (identifier == "dummy") return true; #ifndef DENG_DISABLE_SDLMIXER if (identifier == "sdlmixer") return true; #else if (identifier == "sdlmixer") return false; #endif return Impl::tryFindAudioPlugin(identifier); } audiodriver_t /*const*/ &AudioDriver::iBase() const { return d->iBase; } bool AudioDriver::hasSfx() const { return iSfx().gen.Init != nullptr; } bool AudioDriver::hasMusic() const { return iMusic().gen.Init != nullptr; } bool AudioDriver::hasCd() const { return iCd().gen.Init != nullptr; } audiointerface_sfx_t /*const*/ &AudioDriver::iSfx() const { return d->iSfx; } audiointerface_music_t /*const*/ &AudioDriver::iMusic() const { return d->iMusic; } audiointerface_cd_t /*const*/ &AudioDriver::iCd() const { return d->iCd; } String AudioDriver::interfaceName(void *anyAudioInterface) const { if((void *)&d->iSfx == anyAudioInterface) { /// @todo SFX interfaces can't be named yet. return name(); } if((void *)&d->iMusic == anyAudioInterface || (void *)&d->iCd == anyAudioInterface) { char buf[256]; /// @todo This could easily overflow... auto *gen = (audiointerface_music_generic_t *) anyAudioInterface; if(gen->Get(MUSIP_ID, buf)) { return buf; } else { return "[MUSIP_ID not defined]"; } } return ""; // Not recognized. } String AudioDriver_GetName(audiodriverid_t id) { static String const audioDriverNames[AUDIODRIVER_COUNT] = { /* AUDIOD_DUMMY */ "Dummy", /* AUDIOD_SDL_MIXER */ "SDLMixer", /* AUDIOD_OPENAL */ "OpenAL", /* AUDIOD_FMOD */ "FMOD", /* AUDIOD_FLUIDSYNTH */ "FluidSynth", /* AUDIOD_DSOUND */ "DirectSound", // Win32 only /* AUDIOD_WINMM */ "Windows Multimedia" // Win32 only }; if(VALID_AUDIODRIVER_IDENTIFIER(id)) return audioDriverNames[id]; DENG2_ASSERT(!"S_GetDriverName: Unknown driver id"); return ""; }