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