1 /**
2  * @file driver_fmod.cpp
3  * FMOD Studio low-level audio plugin. @ingroup dsfmod
4  *
5  * @authors Copyright © 2011-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html (with exception granted to allow
9  * linking against FMOD Studio)
10  *
11  * <small>This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by the
13  * Free Software Foundation; either version 2 of the License, or (at your
14  * option) any later version. This program is distributed in the hope that it
15  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
16  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
17  * Public License for more details. You should have received a copy of the GNU
18  * General Public License along with this program; if not, see:
19  * http://www.gnu.org/licenses
20  *
21  * <b>Special Exception to GPLv2:</b>
22  * Linking the Doomsday Audio Plugin for FMOD Studio (audio_fmod) statically or
23  * dynamically with other modules is making a combined work based on
24  * audio_fmod. Thus, the terms and conditions of the GNU General Public License
25  * cover the whole combination. In addition, <i>as a special exception</i>, the
26  * copyright holders of audio_fmod give you permission to combine audio_fmod
27  * with free software programs or libraries that are released under the GNU
28  * LGPL and with code included in the standard release of "FMOD Studio Programmer's
29  * API" under the "FMOD Studio Programmer's API" license (or modified versions of
30  * such code, with unchanged license). You may copy and distribute such a
31  * system following the terms of the GNU GPL for audio_fmod and the licenses of
32  * the other code concerned, provided that you include the source code of that
33  * other code when and as the GNU GPL requires distribution of source code.
34  * (Note that people who make modified versions of audio_fmod are not obligated
35  * to grant this special exception for their modified versions; it is their
36  * choice whether to do so. The GNU General Public License gives permission to
37  * release a modified version without this exception; this exception also makes
38  * it possible to release a modified version which carries forward this
39  * exception.) </small>
40  */
41 
42 #include "driver_fmod.h"
43 #include "api_audiod.h"
44 #include "api_audiod_sfx.h"
45 #include <stdio.h>
46 #include <fmod.h>
47 #include <fmod_errors.h>
48 #include <fmod.hpp>
49 
50 #include "doomsday.h"
51 #include <de/c_wrapper.h>
52 #include <de/ArrayValue>
53 #include <de/Config>
54 #include <de/DictionaryValue>
55 #include <de/LogBuffer>
56 #include <de/ScriptSystem>
57 #include <de/TextValue>
58 
59 FMOD::System *fmodSystem = 0;
60 
61 struct Driver
62 {
63     de::String name;
64     FMOD_GUID guid;
65     int systemRate;
66     FMOD_SPEAKERMODE speakerMode;
67     int speakerModeChannels;
68 };
69 static QVector<Driver> fmodDrivers;
70 
speakerModeText(FMOD_SPEAKERMODE mode)71 static const char *speakerModeText(FMOD_SPEAKERMODE mode)
72 {
73     switch (mode)
74     {
75     case FMOD_SPEAKERMODE_DEFAULT:  return "Default";
76     case FMOD_SPEAKERMODE_RAW:      return "Raw";
77     case FMOD_SPEAKERMODE_MONO:     return "Mono";
78     case FMOD_SPEAKERMODE_STEREO:   return "Stereo";
79     case FMOD_SPEAKERMODE_QUAD:     return "Quad";
80     case FMOD_SPEAKERMODE_SURROUND: return "Surround";
81     case FMOD_SPEAKERMODE_5POINT1:  return "5.1";
82     case FMOD_SPEAKERMODE_7POINT1:  return "7.1";
83     default: break;
84     }
85     return "";
86 }
87 
88 /**
89  * Initialize the FMOD Studio low-level sound driver.
90  */
DS_Init(void)91 int DS_Init(void)
92 {
93     using namespace de;
94 
95     if (fmodSystem)
96     {
97         return true; // Already initialized.
98     }
99 
100     // Create the FMOD audio system.
101     FMOD_RESULT result;
102     if ((result = FMOD::System_Create(&fmodSystem)) != FMOD_OK)
103     {
104         LOGDEV_AUDIO_ERROR("FMOD::System_Create failed (%d) %s") << result << FMOD_ErrorString(result);
105         fmodSystem = 0;
106         return false;
107     }
108 
109     // Print the credit required by FMOD license.
110     LOG_AUDIO_NOTE("FMOD by Firelight Technologies Pty Ltd");
111 
112     // Check what kind of drivers are available.
113     {
114         int numDrivers = 0;
115         fmodSystem->getNumDrivers(&numDrivers);
116         fmodDrivers.resize(numDrivers);
117         std::unique_ptr<de::ArrayValue> names(new de::ArrayValue);
118         for (int i = 0; i < numDrivers; ++i)
119         {
120             auto &drv = fmodDrivers[i];
121             char nameBuf[512];
122             zap(nameBuf);
123             fmodSystem->getDriverInfo(i, nameBuf, sizeof(nameBuf),
124                                       &drv.guid, &drv.systemRate,
125                                       &drv.speakerMode, &drv.speakerModeChannels);
126             drv.name = String::format("%s (%s)", nameBuf, speakerModeText(drv.speakerMode));
127             names->add(TextValue(drv.name));
128 
129             LOG_AUDIO_MSG("FMOD driver %i: \"%s\" Rate:%iHz Mode:%s Channels:%i")
130                     << i << drv.name
131                     << drv.systemRate
132                     << speakerModeText(drv.speakerMode)
133                     << drv.speakerModeChannels;
134         }
135         ScriptSystem::get()["Audio"]["outputs"].value<DictionaryValue>()
136                 .add(new TextValue("fmod"), names.release());
137     }
138 
139     // Select the configured driver.
140     {
141         int configuredDriverIndex = Config::get().geti("audio.output", 0);
142         if (configuredDriverIndex < fmodDrivers.size())
143         {
144             result = fmodSystem->setDriver(configuredDriverIndex);
145             if (result != FMOD_OK)
146             {
147                 LOG_AUDIO_ERROR("Failed to select FMOD audio driver: %s")
148                         << fmodDrivers[configuredDriverIndex].name;
149             }
150         }
151     }
152 
153 #if 0
154 #ifdef WIN32
155     {
156         // Figure out the system's configured default speaker mode.
157         FMOD_SPEAKERMODE speakerMode;
158         result = fmodSystem->getDriverCaps(0, 0, 0, &speakerMode);
159         if (result == FMOD_OK)
160         {
161             fmodSystem->setSpeakerMode(speakerMode);
162         }
163     }
164 #endif
165 
166     de::String const speakerMode = de::Config::get().gets("audio.fmod.speakerMode", "");
167     if (speakerMode == "5.1")
168     {
169         fmodSystem->setSpeakerMode(FMOD_SPEAKERMODE_5POINT1);
170     }
171     else if (speakerMode == "7.1")
172     {
173         fmodSystem->setSpeakerMode(FMOD_SPEAKERMODE_7POINT1);
174     }
175     else if (speakerMode == "prologic")
176     {
177         fmodSystem->setSpeakerMode(FMOD_SPEAKERMODE_SRS5_1_MATRIX);
178     }
179 
180     // Manual overrides.
181     if (CommandLine_Exists("-speaker51"))
182     {
183         fmodSystem->setSpeakerMode(FMOD_SPEAKERMODE_5POINT1);
184     }
185     if (CommandLine_Exists("-speaker71"))
186     {
187         fmodSystem->setSpeakerMode(FMOD_SPEAKERMODE_7POINT1);
188     }
189     if (CommandLine_Exists("-speakerprologic"))
190     {
191         fmodSystem->setSpeakerMode(FMOD_SPEAKERMODE_SRS5_1_MATRIX);
192     }
193 #endif
194 
195     // Initialize FMOD.
196     if ((result = fmodSystem->init(
197              50, FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_CHANNEL_LOWPASS, 0)) !=
198         FMOD_OK)
199     {
200         LOGDEV_AUDIO_ERROR("FMOD init failed: (%d) %s") << result << FMOD_ErrorString(result);
201         fmodSystem->release();
202         fmodSystem = 0;
203         return false;
204     }
205 
206     // Options.
207     FMOD_ADVANCEDSETTINGS settings;
208     de::zap(settings);
209     settings.cbSize = sizeof(settings);
210     settings.HRTFMaxAngle = 360;
211     settings.HRTFMinAngle = 180;
212     settings.HRTFFreq = 11000;
213     fmodSystem->setAdvancedSettings(&settings);
214 
215 #ifdef _DEBUG
216     int numPlugins = 0;
217     fmodSystem->getNumPlugins(FMOD_PLUGINTYPE_CODEC, &numPlugins);
218     DSFMOD_TRACE("Plugins loaded: " << numPlugins);
219     for (int i = 0; i < numPlugins; i++)
220     {
221         unsigned int handle;
222         fmodSystem->getPluginHandle(FMOD_PLUGINTYPE_CODEC, i, &handle);
223 
224         FMOD_PLUGINTYPE pType;
225         char pName[100];
226         unsigned int pVer = 0;
227         fmodSystem->getPluginInfo(handle, &pType, pName, sizeof(pName), &pVer);
228 
229         DSFMOD_TRACE("Plugin " << i << ", handle " << handle << ": type " << pType
230                      << ", name:'" << pName << "', ver:" << pVer);
231     }
232 #endif
233 
234     LOGDEV_AUDIO_VERBOSE("[FMOD] Initialized");
235     return true;
236 }
237 
238 /**
239  * Shut everything down.
240  */
DS_Shutdown(void)241 void DS_Shutdown(void)
242 {
243     DMFmod_Music_Shutdown();
244     //DMFmod_CDAudio_Shutdown();
245 
246     DSFMOD_TRACE("DS_Shutdown.");
247     fmodSystem->release();
248     fmodSystem = 0;
249 }
250 
251 /**
252  * The Event function is called to tell the driver about certain critical
253  * events like the beginning and end of an update cycle.
254  */
DS_Event(int type)255 void DS_Event(int type)
256 {
257     if (!fmodSystem) return;
258 
259     if (type == SFXEV_END)
260     {
261         // End of frame, do an update.
262         fmodSystem->update();
263     }
264 }
265 
DS_Set(int prop,const void * ptr)266 int DS_Set(int prop, const void* ptr)
267 {
268     if (!fmodSystem) return false;
269 
270     switch (prop)
271     {
272     case AUDIOP_SOUNDFONT_FILENAME: {
273         const char* path = reinterpret_cast<const char*>(ptr);
274         DSFMOD_TRACE("DS_Set: Soundfont = " << path);
275         if (!path || !strlen(path))
276         {
277             // Use the default.
278             path = 0;
279         }
280         DMFmod_Music_SetSoundFont(path);
281         return true; }
282 
283     default:
284         DSFMOD_TRACE("DS_Set: Unknown property " << prop);
285         return false;
286     }
287 }
288 
289 /**
290  * Declares the type of the plugin so the engine knows how to treat it. Called
291  * automatically when the plugin is loaded.
292  */
deng_LibraryType(void)293 DENG_ENTRYPOINT const char* deng_LibraryType(void)
294 {
295     return "deng-plugin/audio";
296 }
297 
298 #if defined (DENG_STATIC_LINK)
299 
staticlib_audio_fmod_symbol(char const * name)300 DENG_EXTERN_C void *staticlib_audio_fmod_symbol(char const *name)
301 {
302     DENG_SYMBOL_PTR(name, deng_LibraryType)
303     DENG_SYMBOL_PTR(name, DS_Init)
304     DENG_SYMBOL_PTR(name, DS_Shutdown)
305     DENG_SYMBOL_PTR(name, DS_Event)
306     DENG_SYMBOL_PTR(name, DS_Set)
307     DENG_SYMBOL_PTR(name, DS_SFX_Init)
308     DENG_SYMBOL_PTR(name, DS_SFX_CreateBuffer)
309     DENG_SYMBOL_PTR(name, DS_SFX_DestroyBuffer)
310     DENG_SYMBOL_PTR(name, DS_SFX_Load)
311     DENG_SYMBOL_PTR(name, DS_SFX_Reset)
312     DENG_SYMBOL_PTR(name, DS_SFX_Play)
313     DENG_SYMBOL_PTR(name, DS_SFX_Stop)
314     DENG_SYMBOL_PTR(name, DS_SFX_Refresh)
315     DENG_SYMBOL_PTR(name, DS_SFX_Set)
316     DENG_SYMBOL_PTR(name, DS_SFX_Setv)
317     DENG_SYMBOL_PTR(name, DS_SFX_Listener)
318     DENG_SYMBOL_PTR(name, DS_SFX_Listenerv)
319     DENG_SYMBOL_PTR(name, DS_SFX_Getv)
320     DENG_SYMBOL_PTR(name, DM_Music_Init)
321     DENG_SYMBOL_PTR(name, DM_Music_Update)
322     DENG_SYMBOL_PTR(name, DM_Music_Get)
323     DENG_SYMBOL_PTR(name, DM_Music_Set)
324     DENG_SYMBOL_PTR(name, DM_Music_Pause)
325     DENG_SYMBOL_PTR(name, DM_Music_Stop)
326     DENG_SYMBOL_PTR(name, DM_Music_SongBuffer)
327     DENG_SYMBOL_PTR(name, DM_Music_Play)
328     DENG_SYMBOL_PTR(name, DM_Music_PlayFile)
329     qWarning() << name << "not found in audio_fmod";
330     return nullptr;
331 }
332 
333 #else
334 
335 DENG_DECLARE_API(Con);
336 
337 DENG_API_EXCHANGE(
338     DENG_GET_API(DE_API_CONSOLE, Con);
339 )
340 
341 #endif
342