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, ¶ms, &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(¤tAlloced, &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