1 /**
2  * @file fmod_sfx.cpp
3  * Sound effects interface. @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 Ex)
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 Ex (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 Ex Programmer's
29  * API" under the "FMOD Ex 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 "dd_share.h"
44 #include <de/LogBuffer>
45 #include <de/Lockable>
46 #include <stdlib.h>
47 #include <cmath>
48 #include <vector>
49 #include <map>
50 
51 typedef std::map<FMOD::Sound*, sfxbuffer_t*> Streams;
52 typedef std::vector<char> RawSamplePCM8;
53 
54 struct BufferInfo
55 {
56     FMOD::Channel* channel;
57     FMOD::Sound* sound;
58     FMOD_MODE mode;
59     float pan;
60     float volume;
61     float minDistanceMeters;
62     float maxDistanceMeters;
63     FMODVector position;
64     FMODVector velocity;
65 
BufferInfoBufferInfo66     BufferInfo()
67         : channel(0), sound(0), mode(0),
68           pan(0.f), volume(1.f),
69           minDistanceMeters(10), maxDistanceMeters(100) {}
70 
71     /**
72      * Changes the channel's 3D position mode (head-relative or world coordinates).
73      *
74      * @param newMode  @c true, if the channel should be head-relative.
75      */
setRelativeModeBufferInfo76     void setRelativeMode(bool newMode) {
77         if (newMode) {
78             mode &= ~FMOD_3D_WORLDRELATIVE;
79             mode |=  FMOD_3D_HEADRELATIVE;
80         }
81         else {
82             mode |=  FMOD_3D_WORLDRELATIVE;
83             mode &= ~FMOD_3D_HEADRELATIVE;
84         }
85         if (channel) channel->setMode(mode);
86     }
87 };
88 
89 struct Listener
90 {
91     FMODVector position;
92     FMODVector velocity;
93     FMODVector front;
94     FMODVector up;
95 
96     /**
97      * Parameters are in radians.
98      * Example front vectors:
99      *   Yaw 0:(0,0,1), pi/2:(-1,0,0)
100      */
setOrientationListener101     void setOrientation(float yaw, float pitch)
102     {
103         using std::sin;
104         using std::cos;
105 
106         front.x = cos(yaw) * cos(pitch);
107         front.y = sin(yaw) * cos(pitch);
108         front.z = sin(pitch);
109 
110         up.x = -cos(yaw) * sin(pitch);
111         up.y = -sin(yaw) * sin(pitch);
112         up.z = cos(pitch);
113 
114         /*DSFMOD_TRACE("Front:" << front.x << "," << front.y << "," << front.z << " Up:"
115                      << up.x << "," << up.y << "," << up.z);*/
116     }
117 };
118 
119 static float unitsPerMeter = 1.f;
120 static float dopplerScale = 1.f;
121 static Listener listener;
122 static de::LockableT<Streams> streams;
123 
sfxPropToString(int prop)124 const char* sfxPropToString(int prop)
125 {
126     switch (prop)
127     {
128     case SFXBP_VOLUME:        return "SFXBP_VOLUME";
129     case SFXBP_FREQUENCY:     return "SFXBP_FREQUENCY";
130     case SFXBP_PAN:           return "SFXBP_PAN";
131     case SFXBP_MIN_DISTANCE:  return "SFXBP_MIN_DISTANCE";
132     case SFXBP_MAX_DISTANCE:  return "SFXBP_MAX_DISTANCE";
133     case SFXBP_POSITION:      return "SFXBP_POSITION";
134     case SFXBP_VELOCITY:      return "SFXBP_VELOCITY";
135     case SFXBP_RELATIVE_MODE: return "SFXBP_RELATIVE_MODE";
136     default:                  return "?";
137     }
138 }
139 
bufferInfo(sfxbuffer_t * buf)140 static BufferInfo& bufferInfo(sfxbuffer_t* buf)
141 {
142     assert(buf->ptr != 0);
143     return *reinterpret_cast<BufferInfo*>(buf->ptr);
144 }
145 
channelCallback(FMOD_CHANNELCONTROL * channelcontrol,FMOD_CHANNELCONTROL_TYPE controltype,FMOD_CHANNELCONTROL_CALLBACK_TYPE callbacktype,void *,void *)146 static FMOD_RESULT F_CALLBACK channelCallback(FMOD_CHANNELCONTROL *channelcontrol,
147                                               FMOD_CHANNELCONTROL_TYPE controltype,
148                                               FMOD_CHANNELCONTROL_CALLBACK_TYPE callbacktype,
149                                               void *,
150                                               void *)
151 {
152     if (controltype != FMOD_CHANNELCONTROL_CHANNEL)
153     {
154         return FMOD_OK;
155     }
156 
157     FMOD::Channel *channel = reinterpret_cast<FMOD::Channel *>(channelcontrol);
158     sfxbuffer_t *buf = 0;
159 
160     switch (callbacktype)
161     {
162     case FMOD_CHANNELCONTROL_CALLBACK_END:
163         // The sound has ended, mark the channel.
164         channel->getUserData(reinterpret_cast<void **>(&buf));
165         if (buf)
166         {
167             LOGDEV_AUDIO_XVERBOSE("[FMOD] channelCallback: sfxbuffer %p stops", buf);
168             buf->flags &= ~SFXBF_PLAYING;
169             // The channel becomes invalid after the sound stops.
170             bufferInfo(buf).channel = 0;
171         }
172         channel->setCallback(0);
173         channel->setUserData(0);
174         break;
175 
176     default:
177         break;
178     }
179     return FMOD_OK;
180 }
181 
DS_SFX_Init(void)182 int DS_SFX_Init(void)
183 {
184     return fmodSystem != 0;
185 }
186 
DS_SFX_CreateBuffer(int flags,int bits,int rate)187 sfxbuffer_t* DS_SFX_CreateBuffer(int flags, int bits, int rate)
188 {
189     DSFMOD_TRACE("SFX_CreateBuffer: flags=" << flags << ", bits=" << bits << ", rate=" << rate);
190 
191     sfxbuffer_t* buf;
192 
193     // Clear the buffer.
194     buf = reinterpret_cast<sfxbuffer_t*>(calloc(sizeof(*buf), 1));
195 
196     // Initialize with format info.
197     buf->bytes = bits / 8;
198     buf->rate = rate;
199     buf->flags = flags;
200     buf->freq = rate; // Modified by calls to Set(SFXBP_FREQUENCY).
201 
202     // Allocate extra state information.
203     buf->ptr = new BufferInfo;
204 
205     LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_CreateBuffer: Created sfxbuffer %p", buf);
206 
207     return buf;
208 }
209 
DS_SFX_DestroyBuffer(sfxbuffer_t * buf)210 void DS_SFX_DestroyBuffer(sfxbuffer_t* buf)
211 {
212     if (!buf) return;
213 
214     LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_DestroyBuffer: Destroying sfxbuffer %p", buf);
215 
216     BufferInfo& info = bufferInfo(buf);
217     if (info.sound)
218     {
219         DENG2_GUARD(streams);
220         info.sound->release();
221         streams.value.erase(info.sound);
222     }
223 
224     // Free the memory allocated for the buffer.
225     delete reinterpret_cast<BufferInfo*>(buf->ptr);
226     free(buf);
227 }
228 
229 #if 0
230 static void toSigned8bit(const unsigned char* source, int size, RawSamplePCM8& output)
231 {
232     output.clear();
233     output.resize(size);
234     for (int i = 0; i < size; ++i)
235     {
236         output[i] = char(source[i]) - 128;
237     }
238 }
239 #endif
240 
pcmReadCallback(FMOD_SOUND * soundPtr,void * data,unsigned int datalen)241 static FMOD_RESULT F_CALLBACK pcmReadCallback(FMOD_SOUND* soundPtr, void* data, unsigned int datalen)
242 {
243     FMOD::Sound *sound = reinterpret_cast<FMOD::Sound *>(soundPtr);
244 
245     sfxbuffer_t *buf = nullptr;
246     {
247         DENG2_GUARD(streams);
248         Streams::iterator found = streams.value.find(sound);
249         if (found == streams.value.end())
250         {
251             return FMOD_ERR_NOTREADY;
252         }
253         buf = found->second;
254         DENG_ASSERT(buf != NULL);
255         DENG_ASSERT(buf->flags & SFXBF_STREAM);
256     }
257 
258     // Call the stream callback.
259     sfxstreamfunc_t func = reinterpret_cast<sfxstreamfunc_t>(buf->sample->data);
260     if (func(buf, data, datalen))
261     {
262         return FMOD_OK;
263     }
264     else
265     {
266         // The stream function failed to produce data.
267         return FMOD_ERR_NOTREADY;
268     }
269 }
270 
271 /**
272  * Prepare the buffer for playing a sample by filling the buffer with as
273  * much sample data as fits. The pointer to sample is saved, so the caller
274  * mustn't free it while the sample is loaded.
275  */
DS_SFX_Load(sfxbuffer_t * buf,struct sfxsample_s * sample)276 void DS_SFX_Load(sfxbuffer_t* buf, struct sfxsample_s* sample)
277 {
278     if (!fmodSystem || !buf || !sample) return;
279 
280     bool streaming = (buf->flags & SFXBF_STREAM) != 0;
281 
282     // Tell the engine we have used up the entire sample already.
283     buf->sample = sample;
284     buf->written = sample->size;
285     buf->flags &= ~SFXBF_RELOAD;
286 
287     BufferInfo& info = bufferInfo(buf);
288 
289     FMOD_CREATESOUNDEXINFO params;
290     zeroStruct(params);
291     params.length = sample->size;
292     params.defaultfrequency = sample->rate;
293     params.numchannels = 1; // Doomsday only uses mono samples currently.
294     params.format = (sample->bytesPer == 1? FMOD_SOUND_FORMAT_PCM8 : FMOD_SOUND_FORMAT_PCM16);
295 
296     LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_Load: sfxbuffer %p sample (size:%i, freq:%i, bps:%i)",
297                           buf << sample->size << sample->rate << sample->bytesPer);
298 
299     // If it has a sample, release it later.
300     if (info.sound)
301     {
302         DENG2_GUARD(streams);
303         LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_Load: Releasing buffer's old Sound %p", info.sound);
304         info.sound->release();
305         streams.value.erase(info.sound);
306     }
307 
308     //RawSamplePCM8 signConverted;
309     const char* sampleData = reinterpret_cast<const char*>(sample->data);
310     if (!streaming)
311     {
312         /*if (sample->bytesPer == 1)
313         {
314             // Doomsday gives us unsigned 8-bit audio samples.
315             toSigned8bit(reinterpret_cast<const unsigned char*>(sample->data), sample->size, signConverted);
316             sampleData = &signConverted[0];
317         }*/
318         info.mode = FMOD_OPENMEMORY |
319                     FMOD_OPENRAW |
320                     (buf->flags & SFXBF_3D? FMOD_3D : FMOD_2D) |
321                     (buf->flags & SFXBF_REPEAT? FMOD_LOOP_NORMAL : 0);
322     }
323     else // Set up for streaming.
324     {
325         info.mode = FMOD_OPENUSER |
326                     FMOD_CREATESTREAM |
327                     FMOD_LOOP_NORMAL;
328 
329         params.numchannels = 2; /// @todo  Make this configurable.
330         params.length = sample->numSamples;
331         params.decodebuffersize = sample->rate / 4;
332         params.pcmreadcallback = pcmReadCallback;
333         sampleData = 0; // will be streamed
334     }
335     if (buf->flags & SFXBF_3D)
336     {
337         info.mode |= FMOD_3D_WORLDRELATIVE;
338     }
339 
340     // Pass the sample to FMOD.
341     FMOD_RESULT result;
342     result = fmodSystem->createSound(sampleData, info.mode, &params, &info.sound);
343     DSFMOD_ERRCHECK(result);
344     LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_Load: created Sound %p%s",
345                           info.sound << (streaming? " as streaming" : ""));
346 
347     if (streaming)
348     {
349         DENG2_GUARD(streams);
350         // Keep a record of the playing stream for the PCM read callback.
351         streams.value[info.sound] = buf;
352         LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_Load: noting %p belongs to streaming buffer %p",
353                               info.sound << buf);
354     }
355 
356     // Not started yet.
357     info.channel = 0;
358 
359 #ifdef _DEBUG
360     // Check memory.
361     int currentAlloced = 0;
362     int maxAlloced = 0;
363     FMOD::Memory_GetStats(&currentAlloced, &maxAlloced, false);
364     DSFMOD_TRACE("SFX_Load: FMOD memory alloced:" << currentAlloced << ", max:" << maxAlloced);
365 #endif
366 
367     // Now the buffer is ready for playing.
368 }
369 
370 /**
371  * Stops the buffer and makes it forget about its sample.
372  */
DS_SFX_Reset(sfxbuffer_t * buf)373 void DS_SFX_Reset(sfxbuffer_t* buf)
374 {
375     if (!buf)
376         return;
377 
378     LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_Reset: sfxbuffer %p", buf);
379 
380     DS_SFX_Stop(buf);
381     buf->sample = 0;
382     buf->flags &= ~SFXBF_RELOAD;
383 
384     BufferInfo& info = bufferInfo(buf);
385     if (info.sound)
386     {
387         DENG2_GUARD(streams);
388         LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_Reset: releasing Sound %p", info.sound);
389         info.sound->release();
390         streams.value.erase(info.sound);
391     }
392     if (info.channel)
393     {
394         info.channel->setCallback(0);
395         info.channel->setUserData(0);
396         info.channel->setMute(true);
397     }
398     info = BufferInfo();
399 }
400 
DS_SFX_Play(sfxbuffer_t * buf)401 void DS_SFX_Play(sfxbuffer_t* buf)
402 {
403     // Playing is quite impossible without a sample.
404     if (!buf || !buf->sample)
405         return;
406 
407     BufferInfo& info = bufferInfo(buf);
408     assert(info.sound != 0);
409 
410     FMOD_RESULT result;
411     result = fmodSystem->playSound(info.sound, nullptr, true, &info.channel);
412     DSFMOD_ERRCHECK(result);
413 
414     if (!info.channel) return;
415 
416     // Set the properties of the sound.
417     info.channel->setPan(info.pan);
418     info.channel->setFrequency(float(buf->freq));
419     info.channel->setVolume(info.volume);
420     info.channel->setUserData(buf);
421     info.channel->setCallback(channelCallback);
422     if (buf->flags & SFXBF_3D)
423     {
424         // 3D properties.
425         info.channel->set3DMinMaxDistance(info.minDistanceMeters,
426                                           info.maxDistanceMeters);
427         info.channel->set3DAttributes(&info.position, &info.velocity);
428         info.channel->setMode(info.mode);
429     }
430 
431     LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_Play: sfxbuffer %p, pan:%f, freq:%i, vol:%f, loop:%b",
432             buf << info.pan << buf->freq << info.volume << ((buf->flags & SFXBF_REPEAT) != 0));
433 
434     // Start playing it.
435     info.channel->setPaused(false);
436 
437     // The buffer is now playing.
438     buf->flags |= SFXBF_PLAYING;
439 }
440 
DS_SFX_Stop(sfxbuffer_t * buf)441 void DS_SFX_Stop(sfxbuffer_t* buf)
442 {
443     if (!buf) return;
444 
445     LOGDEV_AUDIO_XVERBOSE("[FMOD] SFX_Stop: sfxbuffer %p", buf);
446 
447     BufferInfo& info = bufferInfo(buf);
448     {
449         DENG2_GUARD(streams);
450         Streams::iterator found = streams.value.find(info.sound);
451         if (found != streams.value.end() && info.channel)
452         {
453             info.channel->setPaused(true);
454         }
455     }
456     if (info.channel)
457     {
458         info.channel->setUserData(0);
459         info.channel->setCallback(0);
460         info.channel->setMute(true);
461         info.channel = 0;
462     }
463 
464     // Clear the flag that tells the Sfx module about playing buffers.
465     buf->flags &= ~SFXBF_PLAYING;
466 }
467 
468 /**
469  * Buffer streamer. Called by the Sfx refresh thread.
470  * FMOD handles this for us.
471  */
DS_SFX_Refresh(sfxbuffer_t *)472 void DS_SFX_Refresh(sfxbuffer_t*)
473 {}
474 
475 /**
476  * @param prop          SFXBP_VOLUME (0..1)
477  *                      SFXBP_FREQUENCY (Hz)
478  *                      SFXBP_PAN (-1..1)
479  *                      SFXBP_MIN_DISTANCE
480  *                      SFXBP_MAX_DISTANCE
481  *                      SFXBP_RELATIVE_MODE
482  */
DS_SFX_Set(sfxbuffer_t * buf,int prop,float value)483 void DS_SFX_Set(sfxbuffer_t* buf, int prop, float value)
484 {
485     if (!buf)
486         return;
487 
488     BufferInfo& info = bufferInfo(buf);
489 
490     switch (prop)
491     {
492     case SFXBP_VOLUME:
493         if (FEQUAL(info.volume, value)) return; // No change.
494         assert(value >= 0);
495         info.volume = value;
496         if (info.channel) info.channel->setVolume(info.volume);
497         break;
498 
499     case SFXBP_FREQUENCY: {
500         unsigned int newFreq = (unsigned int) (buf->rate * value);
501         if (buf->freq == newFreq) return; // No change.
502         buf->freq = newFreq;
503         if (info.channel) info.channel->setFrequency(float(buf->freq));
504         break; }
505 
506     case SFXBP_PAN:
507         if (FEQUAL(info.pan, value)) return; // No change.
508         info.pan = value;
509         if (info.channel) info.channel->setPan(info.pan);
510         break;
511 
512     case SFXBP_MIN_DISTANCE:
513         info.minDistanceMeters = value;
514         if (info.channel) info.channel->set3DMinMaxDistance(info.minDistanceMeters,
515                                                            info.maxDistanceMeters);
516         break;
517 
518     case SFXBP_MAX_DISTANCE:
519         info.maxDistanceMeters = value;
520         if (info.channel) info.channel->set3DMinMaxDistance(info.minDistanceMeters,
521                                                            info.maxDistanceMeters);
522         break;
523 
524     case SFXBP_RELATIVE_MODE:
525         info.setRelativeMode(value > 0);
526         break;
527 
528     default:
529         break;
530     }
531 
532     //DSFMOD_TRACE("SFX_Set: sfxbuffer " << buf << ", " << sfxPropToString(prop) << " = " << value);
533 }
534 
535 /**
536  * Coordinates specified in world coordinate system:
537  * +X to the right, +Y up and +Z away (Y and Z swapped, i.e.).
538  *
539  * @param property      SFXBP_POSITION
540  *                      SFXBP_VELOCITY
541  */
DS_SFX_Setv(sfxbuffer_t * buf,int prop,float * values)542 void DS_SFX_Setv(sfxbuffer_t* buf, int prop, float* values)
543 {
544     if (!fmodSystem || !buf) return;
545 
546     BufferInfo& info = bufferInfo(buf);
547 
548     switch (prop)
549     {
550     case SFXBP_POSITION:
551         info.position.set(values);
552         if (info.channel) info.channel->set3DAttributes(&info.position, &info.velocity);
553         break;
554 
555     case SFXBP_VELOCITY:
556         info.velocity.set(values);
557         if (info.channel) info.channel->set3DAttributes(&info.position, &info.velocity);
558         break;
559 
560     default:
561         break;
562     }
563 }
564 
565 /**
566  * @param property      SFXLP_UNITS_PER_METER
567  *                      SFXLP_DOPPLER
568  *                      SFXLP_UPDATE
569  */
DS_SFX_Listener(int prop,float value)570 void DS_SFX_Listener(int prop, float value)
571 {
572     switch (prop)
573     {
574     case SFXLP_UNITS_PER_METER:
575         unitsPerMeter = value;
576         fmodSystem->set3DSettings(dopplerScale, unitsPerMeter, 1.0f);
577         DSFMOD_TRACE("SFX_Listener: Units per meter = " << unitsPerMeter);
578         break;
579 
580     case SFXLP_DOPPLER:
581         dopplerScale = value;
582         fmodSystem->set3DSettings(dopplerScale, unitsPerMeter, 1.0f);
583         DSFMOD_TRACE("SFX_Listener: Doppler factor = " << value);
584         break;
585 
586     case SFXLP_UPDATE:
587         // Update the properties set with Listenerv.
588         fmodSystem->set3DListenerAttributes(0, &listener.position, &listener.velocity,
589                                             &listener.front, &listener.up);
590         break;
591 
592     default:
593         break;
594     }
595 }
596 
597 /**
598  * Convert linear volume 0..1 to a logarithmic range.
599  */
linearToLog(float linear)600 static float linearToLog(float linear)
601 {
602     return 10.f * std::log10(linear);
603 }
604 
605 /**
606  * Convert dB value to a linear 0..1 value.
607  */
logToLinear(float db)608 static float logToLinear(float db)
609 {
610     return std::pow(10.f, db/10.f);
611 }
612 
613 //static float scaleLogarithmic(float db, float scale, de::Rangef const &range)
614 //{
615 //    return range.clamp(linearToLog(scale * logToLinear(db)));
616 //}
617 
618 /**
619  * Update the ambient reverb properties.
620  *
621  * @param reverb  Array of NUM_REVERB_DATA parameters (see SRD_*).
622  */
updateListenerEnvironmentSettings(float * reverb)623 static void updateListenerEnvironmentSettings(float *reverb)
624 {
625     if (!fmodSystem || !reverb) return;
626 
627     DSFMOD_TRACE("updateListenerEnvironmentSettings: " <<
628                  reverb[0] << " " << reverb[1] << " " <<
629                  reverb[2] << " " << reverb[3]);
630 
631     // No reverb?
632     if (reverb[SFXLP_REVERB_VOLUME] == 0 && reverb[SFXLP_REVERB_SPACE]   == 0 &&
633         reverb[SFXLP_REVERB_DECAY]  == 0 && reverb[SFXLP_REVERB_DAMPING] == 0)
634     {
635         FMOD_REVERB_PROPERTIES noReverb = FMOD_PRESET_OFF;
636         fmodSystem->setReverbProperties(0, &noReverb);
637         return;
638     }
639 
640     static FMOD_REVERB_PROPERTIES const presetPlain       = FMOD_PRESET_PLAIN;
641     static FMOD_REVERB_PROPERTIES const presetConcertHall = FMOD_PRESET_CONCERTHALL;
642     static FMOD_REVERB_PROPERTIES const presetAuditorium  = FMOD_PRESET_AUDITORIUM;
643     static FMOD_REVERB_PROPERTIES const presetCave        = FMOD_PRESET_CAVE;
644     static FMOD_REVERB_PROPERTIES const presetGeneric     = FMOD_PRESET_GENERIC;
645     static FMOD_REVERB_PROPERTIES const presetRoom        = FMOD_PRESET_ROOM;
646 
647     float space = reverb[SFXLP_REVERB_SPACE];
648     if (reverb[SFXLP_REVERB_DECAY] > .5)
649     {
650         // This much decay needs at least the Generic environment.
651         if (space < .2)
652             space = .2f;
653     }
654 
655     // Choose a preset based on the size of the space.
656     FMOD_REVERB_PROPERTIES props;
657     if (space >= 1)
658         props = presetPlain;
659     else if (space >= .8)
660         props = presetConcertHall;
661     else if (space >= .6)
662         props = presetAuditorium;
663     else if (space >= .4)
664         props = presetCave;
665     else if (space >= .2)
666         props = presetGeneric;
667     else
668         props = presetRoom;
669 
670     // Overall reverb volume adjustment.
671     //props.WetLevel = scaleLogarithmic(props.WetLevel, reverb[SFXLP_REVERB_VOLUME], de::Rangef(-80.f, 0.f));
672     props.WetLevel = de::Rangef(-80.f, 0.f).clamp(linearToLog((logToLinear(props.WetLevel) + reverb[SFXLP_REVERB_VOLUME])/6.f));
673     //setEAXdw(DSPROPERTY_EAXLISTENER_ROOM, volLinearToLog(rev[SFXLP_REVERB_VOLUME]));
674 
675     // Reverb decay.
676     float const decayFactor = 1.f + (reverb[SFXLP_REVERB_DECAY] - .5f) * 1.5f;
677     props.DecayTime = std::min(std::max(100.f, props.DecayTime * decayFactor), 20000.f);
678     //mulEAXf(DSPROPERTY_EAXLISTENER_DECAYTIME, val, EAXLISTENER_MINDECAYTIME, EAXLISTENER_MAXDECAYTIME);
679 
680     // Damping.
681     //props.HighCut = de::Rangef(20, 20000).clamp(20000 * std::pow(1.f - reverb[SFXLP_REVERB_DAMPING], 2.f));
682     props.HighCut = de::Rangef(20, 20000).clamp(props.HighCut * std::pow(1.f - reverb[SFXLP_REVERB_DAMPING], 2.f));
683     //float const damping = std::max(.1f, 1.1f * (1.2f - reverb[SFXLP_REVERB_DAMPING]));
684     //props.RoomHF = linearToLog(std::pow(10.f, props.RoomHF / 2000.f) * damping);
685     //mulEAXdw(DSPROPERTY_EAXLISTENER_ROOMHF, val);
686 
687     qDebug() << "WetLevel:" << props.WetLevel << "dB" << "input:" << reverb[SFXLP_REVERB_VOLUME]
688              << "DecayTime:" << props.DecayTime << "ms"
689              << "HighCut:" << props.HighCut << "Hz";
690 
691     // A slightly increased roll-off. (Not in FMOD?)
692     //props.RoomRolloffFactor = 1.3f;
693 
694     fmodSystem->setReverbProperties(0, &props);
695 }
696 
697 /**
698  * @param prop  SFXLP_ORIENTATION  (yaw, pitch) in degrees.
699  */
DS_SFX_Listenerv(int prop,float * values)700 void DS_SFX_Listenerv(int prop, float* values)
701 {
702     switch (prop)
703     {
704     case SFXLP_POSITION:
705         listener.position.set(values);
706         //DSFMOD_TRACE("Pos:" << values[0] << "," << values[1] << "," << values[2]);
707         break;
708 
709     case SFXLP_ORIENTATION:
710         // Convert the angles to front and up vectors.
711         listener.setOrientation(float(values[0]/180*M_PI), float(values[1]/180*M_PI));
712         break;
713 
714     case SFXLP_VELOCITY:
715         listener.velocity.set(values);
716         break;
717 
718     case SFXLP_REVERB:
719         updateListenerEnvironmentSettings(values);
720         break;
721 
722     case SFXLP_PRIMARY_FORMAT:
723         DSFMOD_TRACE("SFX_Listenerv: Ignoring SFXLP_PRIMARY_FORMAT.");
724         return;
725 
726     default:
727         return;
728     }
729 }
730 
731 /**
732  * Gets a driver property.
733  *
734  * @param prop    Property (SFXP_*).
735  * @param values  Pointer to return value(s).
736  */
DS_SFX_Getv(int prop,void * values)737 int DS_SFX_Getv(int prop, void *values)
738 {
739     switch (prop)
740     {
741     case SFXIP_DISABLE_CHANNEL_REFRESH: {
742         /// The return value is a single 32-bit int.
743         int *wantDisable = reinterpret_cast<int *>(values);
744         if (wantDisable)
745         {
746             // Channel refresh is handled by FMOD, so we don't need to do anything.
747             *wantDisable = true;
748         }
749         break; }
750 
751     case SFXIP_ANY_SAMPLE_RATE_ACCEPTED: {
752         int *anySampleRate = reinterpret_cast<int *>(values);
753         if (anySampleRate)
754         {
755             // FMOD can resample on the fly as needed.
756             *anySampleRate = true;
757         }
758         break; }
759 
760     default:
761         return false;
762     }
763     return true;
764 }
765