1 /** @file audiodriver.cpp  Audio driver loading and interface management.
2  *
3  * @authors Copyright © 2012-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2013-2015 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "audio/audiodriver.h"
22 
23 #include "dd_main.h"
24 #include "audio/sys_audiod_dummy.h"
25 #ifndef DENG_DISABLE_SDLMIXER
26 #  include "audio/sys_audiod_sdlmixer.h"
27 #endif
28 
29 #include <de/Library>
30 #include <de/LibraryFile>
31 #include <de/NativeFile>
32 
33 using namespace de;
34 
DENG2_PIMPL(AudioDriver)35 DENG2_PIMPL(AudioDriver)
36 {
37     bool initialized   = false;
38     ::Library *library = nullptr;
39 
40     audiodriver_t          iBase;
41     audiointerface_sfx_t   iSfx;
42     audiointerface_music_t iMusic;
43     audiointerface_cd_t    iCd;
44 
45     Impl(Public *i) : Base(i)
46     {
47         zap(iBase);
48         zap(iSfx);
49         zap(iMusic);
50         zap(iCd);
51     }
52 
53     static LibraryFile *tryFindAudioPlugin(String const &name)
54     {
55         if (!name.isEmpty())
56         {
57             LibraryFile *found = nullptr;
58             Library_ForAll([&name, &found] (LibraryFile &lib)
59             {
60                 if (lib.hasUnderscoreName(name))
61                 {
62                     found = &lib;
63                     return LoopAbort;
64                 }
65                 return LoopContinue;
66             });
67             return found;
68         }
69         return nullptr;
70     }
71 
72     void getDummyInterfaces()
73     {
74         DENG2_ASSERT(!initialized);
75 
76         library = nullptr;
77         std::memcpy(&iBase,  &audiod_dummy,       sizeof(iBase));
78         std::memcpy(&iSfx,   &audiod_dummy_sfx,   sizeof(iSfx));
79         std::memcpy(&iMusic, &audiod_dummy_music, sizeof(iMusic));
80         std::memcpy(&iCd,    &audiod_dummy_cd,    sizeof(iCd));
81     }
82 
83 #ifndef DENG_DISABLE_SDLMIXER
84     void getSdlMixerInterfaces()
85     {
86         DENG2_ASSERT(!initialized);
87 
88         library = nullptr;
89         std::memcpy(&iBase,  &audiod_sdlmixer,       sizeof(iBase));
90         std::memcpy(&iSfx,   &audiod_sdlmixer_sfx,   sizeof(iSfx));
91         std::memcpy(&iMusic, &audiod_sdlmixer_music, sizeof(iMusic));
92         std::memcpy(&iCd,    &audiod_dummy_cd,       sizeof(iCd));
93     }
94 #endif
95 
96     void importInterfaces(LibraryFile &libFile)
97     {
98         DENG2_ASSERT(!initialized);
99 
100         zap(iBase);
101         zap(iSfx);
102         zap(iMusic);
103         zap(iCd);
104 
105         library = Library_New(libFile.path().toUtf8().constData());
106         if(!library)
107         {
108             throw LoadError("AudioDriver::importInterfaces",
109                             "Failed to load " + libFile.description());
110         }
111 
112         de::Library &lib = libFile.library();
113 
114         lib.setSymbolPtr( iBase.Init,        "DS_Init");
115         lib.setSymbolPtr( iBase.Shutdown,    "DS_Shutdown");
116         lib.setSymbolPtr( iBase.Event,       "DS_Event");
117         lib.setSymbolPtr( iBase.Set,         "DS_Set", de::Library::OptionalSymbol);
118 
119         if(lib.hasSymbol("DS_SFX_Init"))
120         {
121             lib.setSymbolPtr( iSfx.gen.Init,      "DS_SFX_Init");
122             lib.setSymbolPtr( iSfx.gen.Create,    "DS_SFX_CreateBuffer");
123             lib.setSymbolPtr( iSfx.gen.Destroy,   "DS_SFX_DestroyBuffer");
124             lib.setSymbolPtr( iSfx.gen.Load,      "DS_SFX_Load");
125             lib.setSymbolPtr( iSfx.gen.Reset,     "DS_SFX_Reset");
126             lib.setSymbolPtr( iSfx.gen.Play,      "DS_SFX_Play");
127             lib.setSymbolPtr( iSfx.gen.Stop,      "DS_SFX_Stop");
128             lib.setSymbolPtr( iSfx.gen.Refresh,   "DS_SFX_Refresh");
129             lib.setSymbolPtr( iSfx.gen.Set,       "DS_SFX_Set");
130             lib.setSymbolPtr( iSfx.gen.Setv,      "DS_SFX_Setv");
131             lib.setSymbolPtr( iSfx.gen.Listener,  "DS_SFX_Listener");
132             lib.setSymbolPtr( iSfx.gen.Listenerv, "DS_SFX_Listenerv");
133             lib.setSymbolPtr( iSfx.gen.Getv,      "DS_SFX_Getv", de::Library::OptionalSymbol);
134         }
135 
136         if(lib.hasSymbol("DM_Music_Init"))
137         {
138             lib.setSymbolPtr( iMusic.gen.Init,    "DM_Music_Init");
139             lib.setSymbolPtr( iMusic.gen.Update,  "DM_Music_Update");
140             lib.setSymbolPtr( iMusic.gen.Get,     "DM_Music_Get");
141             lib.setSymbolPtr( iMusic.gen.Set,     "DM_Music_Set");
142             lib.setSymbolPtr( iMusic.gen.Pause,   "DM_Music_Pause");
143             lib.setSymbolPtr( iMusic.gen.Stop,    "DM_Music_Stop");
144             lib.setSymbolPtr( iMusic.SongBuffer,  "DM_Music_SongBuffer", de::Library::OptionalSymbol);
145             lib.setSymbolPtr( iMusic.Play,        "DM_Music_Play",       de::Library::OptionalSymbol);
146             lib.setSymbolPtr( iMusic.PlayFile,    "DM_Music_PlayFile",   de::Library::OptionalSymbol);
147         }
148 
149         if(lib.hasSymbol("DM_CDAudio_Init"))
150         {
151             lib.setSymbolPtr( iCd.gen.Init,       "DM_CDAudio_Init");
152             lib.setSymbolPtr( iCd.gen.Update,     "DM_CDAudio_Update");
153             lib.setSymbolPtr( iCd.gen.Set,        "DM_CDAudio_Set");
154             lib.setSymbolPtr( iCd.gen.Get,        "DM_CDAudio_Get");
155             lib.setSymbolPtr( iCd.gen.Pause,      "DM_CDAudio_Pause");
156             lib.setSymbolPtr( iCd.gen.Stop,       "DM_CDAudio_Stop");
157             lib.setSymbolPtr( iCd.Play,           "DM_CDAudio_Play");
158         }
159     }
160 };
161 
AudioDriver()162 AudioDriver::AudioDriver() : d(new Impl(this))
163 {}
164 
name() const165 String AudioDriver::name() const
166 {
167     if(!isLoaded()) return "(invalid)";
168     return AudioDriver_GetName(App_AudioSystem().toDriverId(this));
169 }
170 
status() const171 AudioDriver::Status AudioDriver::status() const
172 {
173     if(d->initialized) return Initialized;
174     if(d->iBase.Init != nullptr) return Loaded;
175     return Invalid;
176 }
177 
statusAsText() const178 String AudioDriver::statusAsText() const
179 {
180     switch(status())
181     {
182     case Invalid:     return "Invalid";
183     case Loaded:      return "Loaded";
184     case Initialized: return "Initialized";
185 
186     default:
187         DENG2_ASSERT(!"AudioDriver::statusAsText: Unknown status");
188         return "Unknown";
189     }
190 }
191 
load(String const & identifier)192 void AudioDriver::load(String const &identifier)
193 {
194     LOG_AS("AudioDriver");
195 
196     if(isLoaded())
197     {
198         /// @throw LoadError  Attempted to load on top of an already loaded driver.
199         throw LoadError("AudioDriver::load", "Already initialized. Cannot load '" + identifier + "'");
200     }
201 
202     // Perhaps a built-in audio driver?
203     if(!identifier.compareWithoutCase("dummy"))
204     {
205         d->getDummyInterfaces();
206         return;
207     }
208 #ifndef DENG_DISABLE_SDLMIXER
209     if(!identifier.compareWithoutCase("sdlmixer"))
210     {
211         d->getSdlMixerInterfaces();
212         return;
213     }
214 #endif
215 
216     // Perhaps a plugin audio driver?
217     LibraryFile *plugin = Impl::tryFindAudioPlugin(identifier);
218     if(!plugin)
219     {
220         /// @throw LoadError  Unknown driver specified.
221         throw LoadError("AudioDriver::load", "Unknown driver \"" + identifier + "\"");
222     }
223 
224     try
225     {
226         // Exchange entrypoints.
227         d->importInterfaces(*plugin);
228     }
229     catch(de::Library::SymbolMissingError const &er)
230     {
231         /// @throw LoadError  One or more missing symbol.
232         throw LoadError("AudioDriver::load", "Failed exchanging entrypoints:\n" + er.asText());
233     }
234 }
235 
unload()236 void AudioDriver::unload()
237 {
238     LOG_AS("AudioDriver");
239 
240     if(isInitialized())
241     {
242         /// @throw LoadError  Cannot unload while initialized.
243         throw LoadError("AudioDriver::unload", "'" + name() + "' is still initialized, cannot unload");
244     }
245 
246     if(isLoaded())
247     {
248         Library_Delete(d->library); d->library = nullptr;
249         zap(d->iBase);
250         zap(d->iSfx);
251         zap(d->iMusic);
252         zap(d->iCd);
253     }
254 }
255 
initialize()256 void AudioDriver::initialize()
257 {
258     LOG_AS("AudioDriver");
259 
260     // Already been here?
261     if(d->initialized) return;
262 
263     DENG2_ASSERT(d->iBase.Init != nullptr);
264     d->initialized = d->iBase.Init();
265 }
266 
deinitialize()267 void AudioDriver::deinitialize()
268 {
269     LOG_AS("AudioDriver");
270 
271     // Already been here?
272     if(!d->initialized) return;
273 
274     if(d->iBase.Shutdown)
275     {
276         d->iBase.Shutdown();
277     }
278     d->initialized = false;
279 }
280 
library() const281 ::Library *AudioDriver::library() const
282 {
283     return d->library;
284 }
285 
isAvailable(String const & identifier)286 bool AudioDriver::isAvailable(String const &identifier)
287 {
288     if (identifier == "dummy") return true;
289 #ifndef DENG_DISABLE_SDLMIXER
290     if (identifier == "sdlmixer") return true;
291 #else
292     if (identifier == "sdlmixer") return false;
293 #endif
294     return Impl::tryFindAudioPlugin(identifier);
295 }
296 
iBase() const297 audiodriver_t /*const*/ &AudioDriver::iBase() const
298 {
299     return d->iBase;
300 }
301 
hasSfx() const302 bool AudioDriver::hasSfx() const
303 {
304     return iSfx().gen.Init != nullptr;
305 }
306 
hasMusic() const307 bool AudioDriver::hasMusic() const
308 {
309     return iMusic().gen.Init != nullptr;
310 }
311 
hasCd() const312 bool AudioDriver::hasCd() const
313 {
314     return iCd().gen.Init != nullptr;
315 }
316 
iSfx() const317 audiointerface_sfx_t /*const*/ &AudioDriver::iSfx() const
318 {
319     return d->iSfx;
320 }
321 
iMusic() const322 audiointerface_music_t /*const*/ &AudioDriver::iMusic() const
323 {
324     return d->iMusic;
325 }
326 
iCd() const327 audiointerface_cd_t /*const*/ &AudioDriver::iCd() const
328 {
329     return d->iCd;
330 }
331 
interfaceName(void * anyAudioInterface) const332 String AudioDriver::interfaceName(void *anyAudioInterface) const
333 {
334     if((void *)&d->iSfx == anyAudioInterface)
335     {
336         /// @todo  SFX interfaces can't be named yet.
337         return name();
338     }
339 
340     if((void *)&d->iMusic == anyAudioInterface || (void *)&d->iCd == anyAudioInterface)
341     {
342         char buf[256];  /// @todo  This could easily overflow...
343         auto *gen = (audiointerface_music_generic_t *) anyAudioInterface;
344         if(gen->Get(MUSIP_ID, buf))
345         {
346             return buf;
347         }
348         else
349         {
350             return "[MUSIP_ID not defined]";
351         }
352     }
353 
354     return "";  // Not recognized.
355 }
356 
AudioDriver_GetName(audiodriverid_t id)357 String AudioDriver_GetName(audiodriverid_t id)
358 {
359     static String const audioDriverNames[AUDIODRIVER_COUNT] = {
360         /* AUDIOD_DUMMY */      "Dummy",
361         /* AUDIOD_SDL_MIXER */  "SDLMixer",
362         /* AUDIOD_OPENAL */     "OpenAL",
363         /* AUDIOD_FMOD */       "FMOD",
364         /* AUDIOD_FLUIDSYNTH */ "FluidSynth",
365         /* AUDIOD_DSOUND */     "DirectSound",        // Win32 only
366         /* AUDIOD_WINMM */      "Windows Multimedia"  // Win32 only
367     };
368     if(VALID_AUDIODRIVER_IDENTIFIER(id))
369         return audioDriverNames[id];
370 
371     DENG2_ASSERT(!"S_GetDriverName: Unknown driver id");
372     return "";
373 }
374