1 /**\file driver_directsound.cpp
2 *\section License
3 * License: GPL
4 * Online License Link: http://www.gnu.org/licenses/gpl.html
5 *
6 *\author Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
7 *\author Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
8 *
9 * 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
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor,
22 * Boston, MA 02110-1301 USA
23 */
24
25 /**
26 * Win32 SFX driver for DirectSound, with EAX 2.0.
27 *
28 * Uses DirectSound 8.0
29 * Buffers created on Load.
30 */
31
32 // HEADER FILES ------------------------------------------------------------
33
34 #define DIRECTSOUND_VERSION 0x0800
35
36 #define WIN32_LEAN_AND_MEAN
37
38 #include <math.h>
39 #include <stdlib.h>
40 #include <windows.h>
41 #include <mmsystem.h>
42 #include <dsound.h>
43 #ifdef DENG_HAVE_EAX2
44 # include <eax.h>
45 #endif
46
47 #pragma warning (disable: 4035 4244)
48
49 #include <de/c_wrapper.h>
50 #include <de/timer.h>
51
52 #include "doomsday.h"
53 #include "api_audiod.h"
54 #include "api_audiod_sfx.h"
55
56 DENG_DECLARE_API(Base);
57 DENG_DECLARE_API(Con);
58
59 // MACROS ------------------------------------------------------------------
60
61 // DirectSound(3D)Buffer Pointer
62 #define DSBUF(buf) ((LPDIRECTSOUNDBUFFER) buf->ptr)
63 #define DSBUF3D(buf) ((LPDIRECTSOUND3DBUFFER8) buf->ptr3D)
64
65 #define MAX_FAILED_PROPS (10)
66
67 // TYPES -------------------------------------------------------------------
68
69 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
70
71 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
72
73 extern "C" {
74 int DS_Init(void);
75 void DS_Shutdown(void);
76 void DS_Event(int type);
77
78 int DS_SFX_Init(void);
79 sfxbuffer_t* DS_SFX_CreateBuffer(int flags, int bits, int rate);
80 void DS_SFX_DestroyBuffer(sfxbuffer_t* buf);
81 void DS_SFX_Load(sfxbuffer_t* buf, struct sfxsample_s* sample);
82 void DS_SFX_Reset(sfxbuffer_t* buf);
83 void DS_SFX_Play(sfxbuffer_t* buf);
84 void DS_SFX_Stop(sfxbuffer_t* buf);
85 void DS_SFX_Refresh(sfxbuffer_t* buf);
86 void DS_SFX_Set(sfxbuffer_t* buf, int prop, float value);
87 void DS_SFX_Setv(sfxbuffer_t* buf, int prop, float* values);
88 void DS_SFX_Listener(int prop, float value);
89 void DS_SFX_Listenerv(int prop, float* values);
90 }
91
92 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
93
94 static void commitEAXDeferred(void);
95
96 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
97
98 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
99
100 // PUBLIC DATA DEFINITIONS -------------------------------------------------
101
102 static dd_bool initOk = false;
103
104 static LPDIRECTSOUND8 dsound = NULL;
105 static LPDIRECTSOUNDBUFFER primary = NULL;
106 static LPDIRECTSOUND3DLISTENER8 dsListener = NULL;
107 static LPKSPROPERTYSET propertySet = NULL;
108 static dd_bool ignoreEAXErrors = false;
109 static dd_bool canSetPSF = true;
110
111 static DWORD failedProps[MAX_FAILED_PROPS];
112
113 // PRIVATE DATA DEFINITIONS ------------------------------------------------
114
115 // CODE --------------------------------------------------------------------
116
createBuffer(DSBUFFERDESC * desc)117 static IDirectSoundBuffer8* createBuffer(DSBUFFERDESC* desc)
118 {
119 IDirectSoundBuffer* buf;
120 IDirectSoundBuffer8* buf8;
121 HRESULT hr;
122
123 if(!desc)
124 return NULL;
125
126 // Try to create a secondary buffer with the requested properties.
127 if(FAILED(hr = dsound->CreateSoundBuffer(desc, &buf, NULL)))
128 return NULL;
129
130 // Obtain the DirectSoundBuffer8 interface.
131 if(FAILED
132 (hr = buf->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*) &buf8)))
133 buf8 = NULL;
134
135 // Release the old interface, we don't need it.
136 buf->Release();
137 return buf8;
138 }
139
get3DBuffer(IDirectSoundBuffer8 * buf8)140 static IDirectSound3DBuffer8* get3DBuffer(IDirectSoundBuffer8* buf8)
141 {
142 IDirectSound3DBuffer8* buf3d;
143 HRESULT hr;
144
145 if(!buf8)
146 return NULL;
147
148 // Query the 3D interface.
149 if(FAILED(hr = buf8->QueryInterface(IID_IDirectSound3DBuffer8,
150 (LPVOID*) &buf3d)))
151 {
152 App_Log(DE2_DEV_AUDIO_WARNING, "[DirectSound] get3DBuffer: Failed to get 3D interface (0x%x)", hr);
153 buf3d = NULL;
154 }
155
156 return buf3d;
157 }
158
159 #ifdef DENG_HAVE_EAX2
160 /**
161 * Does the EAX implementation support getting/setting of a propertry.
162 *
163 * @param prop Property id (constant) to be checked.
164 * @return @c true, if supported.
165 */
queryEAXSupport(int prop)166 static dd_bool queryEAXSupport(int prop)
167 {
168 # define EAXSUP (KSPROPERTY_SUPPORT_GET | KSPROPERTY_SUPPORT_SET)
169
170 if(propertySet)
171 {
172 ULONG support = 0;
173
174 propertySet->QuerySupport(DSPROPSETID_EAX_ListenerProperties, prop,
175 &support);
176
177 return (support & EAXSUP) == EAXSUP? true : false;
178 }
179
180 return false;
181
182 # undef EAXSUP
183 }
184 #endif
185
186 /**
187 * Init DirectSound, start playing the primary buffer.
188 *
189 * @return @c true, iff successful.
190 */
DS_Init(void)191 int DS_Init(void)
192 {
193 #define NUMBUFFERS_HW_3D ((uint) dsoundCaps.dwFreeHw3DStreamingBuffers)
194 #define NUMBUFFERS_HW_2D ((uint) dsoundCaps.dwFreeHwMixingStreamingBuffers)
195
196 #ifdef DENG_HAVE_EAX2
197 typedef struct eaxproperty_s {
198 DSPROPERTY_EAX_LISTENERPROPERTY prop;
199 char* name;
200 } eaxproperty_t;
201
202 static const eaxproperty_t eaxProps[] = {
203 { DSPROPERTY_EAXLISTENER_ENVIRONMENT, "Environment" },
204 { DSPROPERTY_EAXLISTENER_ROOM, "Room" },
205 { DSPROPERTY_EAXLISTENER_ROOMHF, "Room HF" },
206 { DSPROPERTY_EAXLISTENER_DECAYTIME, "Decay time" },
207 { DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, "Room roll-off factor" },
208 { DSPROPERTY_EAXLISTENER_NONE, NULL } // terminate.
209 };
210 #endif
211
212 DSBUFFERDESC desc;
213 DSCAPS dsoundCaps;
214 HWND hWnd;
215 HRESULT hr;
216 //uint numHW3DBuffers = 0;
217 dd_bool useEAX, eaxAvailable = false,
218 primaryBuffer3D = false, primaryBufferHW = false;
219 dd_bool haveInstance = false;
220
221 if(dsound)
222 return true; // Already initialized?
223
224 App_Log(DE2_AUDIO_VERBOSE, "[DirectSound] Initializing Direct Sound...");
225
226 // Can we set the Primary Sound Format?
227 canSetPSF = !CommandLine_Exists("-nopsf");
228 useEAX = !CommandLine_Exists("-noeax");
229
230 if(!(hWnd = (HWND) DD_GetVariable(DD_WINDOW_HANDLE)))
231 {
232 App_Log(DE2_AUDIO_ERROR, "[DirectSound] Cannot initialize DirectSound: "
233 "main window unavailable");
234 return false;
235 }
236
237 // First try to create the DirectSound8 object with EAX support.
238 hr = DSERR_GENERIC;
239 #ifdef DENG_HAVE_EAX2
240 if(useEAX)
241 {
242 if((hr = EAXDirectSoundCreate8(NULL, &dsound, NULL)) == DS_OK)
243 {
244 haveInstance = true;
245 eaxAvailable = true;
246 }
247 else
248 {
249 App_Log(DE2_AUDIO_VERBOSE, "[DirectSound] EAX could not be initialized (0x%x)", hr);
250 }
251 }
252 #endif
253
254 // Try plain old DS, then.
255 if(!haveInstance)
256 {
257 if((hr = DirectSoundCreate8(NULL, &dsound, NULL)) == DS_OK)
258 {
259 haveInstance = true;
260 }
261 else
262 {
263 App_Log(DE2_AUDIO_ERROR, "[DirectSound] Failed to create the DS8 instance (0x%x)", hr);
264 }
265 }
266
267 if(!haveInstance)
268 { // Oh dear. Give up.
269 return false;
270 }
271
272 // Set cooperative level.
273 if((hr = dsound->SetCooperativeLevel(hWnd, DSSCL_PRIORITY)) != DS_OK)
274 {
275 App_Log(DE2_AUDIO_ERROR, "[DirectSound] Failed to set cooperative level (0x%x)", hr);
276 return false;
277 }
278
279 // Lets query the device caps.
280 dsoundCaps.dwSize = sizeof(dsoundCaps);
281 if((hr = dsound->GetCaps(&dsoundCaps)) != DS_OK)
282 {
283 App_Log(DE2_AUDIO_ERROR, "[DirectSound] Failed querying device caps (0x%x)", hr);
284 return false;
285 }
286
287 dsListener = NULL;
288 if(NUMBUFFERS_HW_3D < 4)
289 useEAX = false;
290
291 ZeroMemory(&desc, sizeof(DSBUFFERDESC));
292 desc.dwSize = sizeof(DSBUFFERDESC);
293
294 /**
295 * Create the primary buffer.
296 * We prioritize buffer creation as follows:
297 * 3D hardware > 3D software > 2D hardware > 2D software.
298 */
299
300 // First try for a 3D buffer, hardware or software.
301 desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRL3D | DSBCAPS_CTRLVOLUME;
302 desc.dwFlags |= (NUMBUFFERS_HW_3D > 0? DSBCAPS_LOCHARDWARE : DSBCAPS_LOCSOFTWARE);
303
304 hr = dsound->CreateSoundBuffer(&desc, &primary, NULL);
305 if(hr != DS_OK && hr != DS_NO_VIRTUALIZATION)
306 { // Not available.
307 // Try for a 2D buffer.
308 ZeroMemory(&desc, sizeof(DSBUFFERDESC));
309 desc.dwSize = sizeof(DSBUFFERDESC);
310
311 desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
312 desc.dwFlags |= (NUMBUFFERS_HW_2D > 0? DSBCAPS_LOCHARDWARE : DSBCAPS_LOCSOFTWARE);
313
314 if((hr = dsound->CreateSoundBuffer(&desc, &primary, NULL)) != DS_OK)
315 {
316 App_Log(DE2_AUDIO_ERROR, "[DirectSound] Failed creating primary (2D) buffer (0x%x)", hr);
317 return false;
318 }
319
320 primaryBufferHW = (NUMBUFFERS_HW_2D > 0? true : false);
321 }
322 else
323 { // 3D buffer available.
324 primaryBuffer3D = true;
325 primaryBufferHW = (NUMBUFFERS_HW_3D > 0? true : false);
326
327 // Get the listener.
328 if(FAILED(hr =
329 primary->QueryInterface(IID_IDirectSound3DListener,
330 (LPVOID*) &dsListener)))
331 {
332 App_Log(DE2_DEV_AUDIO_MSG, "[DirectSound] 3D listener not available (0x%x)", hr);
333 }
334 }
335
336 // Start playing the primary buffer.
337 if(primary)
338 {
339 // Supposedly can be a bit more efficient not to stop the
340 // primary buffer when there are no secondary buffers playing.
341 primary->Play(0, 0, DSBPLAY_LOOPING);
342 }
343
344 // Try to get the EAX listener property set.
345 // Create a temporary secondary buffer for it.
346 if(eaxAvailable && useEAX)
347 {
348 IDirectSoundBuffer8* dummy;
349 IDirectSound3DBuffer8* dummy3d;
350 DSBUFFERDESC desc;
351 WAVEFORMATEX wave;
352
353 // Clear the failed properties array.
354 memset(failedProps, ~0, sizeof(failedProps));
355
356 propertySet = NULL;
357 if(CommandLine_Exists("-eaxignore"))
358 ignoreEAXErrors = true;
359
360 // Configure the temporary buffer.
361 ZeroMemory(&desc, sizeof(DSBUFFERDESC));
362 desc.dwSize = sizeof(DSBUFFERDESC);
363 desc.dwBufferBytes = DSBSIZE_MIN;
364 desc.dwFlags = DSBCAPS_STATIC | DSBCAPS_CTRL3D;
365 desc.lpwfxFormat = &wave;
366
367 ZeroMemory(&wave, sizeof(WAVEFORMATEX));
368 wave.wFormatTag = WAVE_FORMAT_PCM;
369 wave.nChannels = 1;
370 wave.nSamplesPerSec = 44100;
371 wave.wBitsPerSample = 16;
372 wave.nBlockAlign = 2;
373 wave.nAvgBytesPerSec = 88200;
374
375 // Create the temporary buffer.
376 if(!(dummy = createBuffer(&desc)))
377 return false;
378
379 // Get the 3D interface.
380 if(!(dummy3d = get3DBuffer(dummy)))
381 return false;
382
383 #ifdef DENG_HAVE_EAX2
384 // Query the property set interface
385 dummy3d->QueryInterface(IID_IKsPropertySet, (LPVOID*) &propertySet);
386 if(propertySet)
387 {
388 size_t i = 0;
389 dd_bool ok = true;
390
391 while(ok && eaxProps[i].prop != DSPROPERTY_EAXLISTENER_NONE)
392 {
393 const eaxproperty_t* p = &eaxProps[i];
394
395 if(!queryEAXSupport(p->prop))
396 ok = false;
397 else
398 i++;
399 }
400
401 if(!ok)
402 {
403 useEAX = false;
404
405 propertySet->Release();
406 propertySet = NULL;
407 }
408 }
409 else
410 #endif
411 {
412 useEAX = false;
413
414 App_Log(DE2_LOG_DEV | DE2_LOG_AUDIO | DE2_LOG_WARNING,
415 "dsDirectSound::DS_Init: Failed retrieving property set.");
416 }
417 }
418
419 // Announce capabilites:
420 App_Log(DE2_LOG_AUDIO, "DirectSound configuration:");
421 App_Log(DE2_LOG_AUDIO, " Primary Buffer: %s (%s)", (primaryBuffer3D? "3D" : "2D"),
422 (primaryBufferHW? "hardware" : "software"));
423 App_Log(DE2_LOG_AUDIO, " Hardware Buffers: %i", (primaryBuffer3D? NUMBUFFERS_HW_3D : NUMBUFFERS_HW_2D));
424 LogBuffer_Printf(DE2_LOG_AUDIO, " DSP: %s", eaxAvailable? "EAX 2.0" : "None");
425 if(eaxAvailable)
426 LogBuffer_Printf(DE2_LOG_AUDIO, " (%s)", useEAX? "enabled" : "disabled");
427 LogBuffer_Printf(DE2_LOG_AUDIO, "\n");
428
429 #ifdef DENG_HAVE_EAX2
430 if(eaxAvailable)
431 {
432 App_Log(DE2_LOG_AUDIO, " EAX Listner Environment:");
433 for(size_t i = 0; eaxProps[i].prop != DSPROPERTY_EAXLISTENER_NONE; ++i)
434 {
435 const eaxproperty_t* p = &eaxProps[i];
436
437 App_Log(DE2_LOG_AUDIO, " %s: %s", p->name,
438 queryEAXSupport(p->prop)? "Present" : "Not available");
439 }
440 }
441 #endif
442
443 // Success!
444 App_Log(DE2_LOG_AUDIO | DE2_LOG_VERBOSE | DE2_LOG_DEV,
445 "dsDirectSound::DS_Init: Initialization complete, OK.");
446 return true;
447
448 #undef NUMBUFFERS_HW_3D
449 #undef NUMBUFFERS_HW_2D
450 }
451
452 /**
453 * Shut everything down.
454 */
DS_Shutdown(void)455 void DS_Shutdown(void)
456 {
457 if(propertySet)
458 propertySet->Release();
459 propertySet = NULL;
460
461 if(dsListener)
462 dsListener->Release();
463 dsListener = NULL;
464
465 if(primary)
466 primary->Release();
467 primary = NULL;
468
469 if(dsound)
470 dsound->Release();
471 dsound = NULL;
472 }
473
474 /**
475 * The Event function is called to tell the driver about certain critical
476 * events like the beginning and end of an update cycle.
477 */
DS_Event(int)478 void DS_Event(int /*type*/)
479 {
480 // Do nothing...
481 }
482
DS_SFX_Init(void)483 int DS_SFX_Init(void)
484 {
485 return true;
486 }
487
488 /**
489 * Called using Listener().
490 */
setPrimaryFormat(int bits,int rate)491 static void setPrimaryFormat(int bits, int rate)
492 {
493 WAVEFORMATEX wave;
494
495 memset(&wave, 0, sizeof(wave));
496 wave.wFormatTag = WAVE_FORMAT_PCM;
497 wave.nChannels = 2;
498 wave.nSamplesPerSec = rate;
499 wave.nBlockAlign = wave.nChannels * bits / 8;
500 wave.nAvgBytesPerSec = wave.nSamplesPerSec * wave.nBlockAlign;
501 wave.wBitsPerSample = bits;
502
503 primary->SetFormat(&wave);
504 }
505
DS_SFX_CreateBuffer(int flags,int bits,int rate)506 sfxbuffer_t* DS_SFX_CreateBuffer(int flags, int bits, int rate)
507 {
508 int i;
509 WAVEFORMATEX format;
510 DSBUFFERDESC desc;
511 IDirectSoundBuffer8* buf_object8;
512 IDirectSound3DBuffer8* buf_object3d = NULL;
513 sfxbuffer_t* buf;
514
515 // If we don't have the listener, the primary buffer doesn't have 3D
516 // capabilities; don't create 3D buffers. DSound should provide software
517 // emulation, though, so this is really only a contingency.
518 if(!dsListener && flags & SFXBF_3D)
519 return NULL;
520
521 // Setup the buffer descriptor.
522 memset(&desc, 0, sizeof(desc));
523 desc.dwSize = sizeof(desc);
524 desc.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLVOLUME | DSBCAPS_STATIC |
525 (flags & SFXBF_3D ? DSBCAPS_CTRL3D : DSBCAPS_CTRLPAN);
526
527 // Calculate buffer size. Increase it to hit an 8 byte boundary.
528 desc.dwBufferBytes = bits / 8 * rate / 2; // 500ms buffer.
529 i = desc.dwBufferBytes % 8;
530 if(i)
531 desc.dwBufferBytes += 8 - i;
532
533 desc.lpwfxFormat = &format;
534 if(flags & SFXBF_3D)
535 {
536 // Control the selection with a Property!
537 /*#pragma message(" DS_DSoundCreateBuffer: Reminder: Select sw-3D algo with a Property!") */
538 desc.guid3DAlgorithm = DS3DALG_HRTF_LIGHT;
539 }
540
541 // And the wave data format.
542 format.wFormatTag = WAVE_FORMAT_PCM;
543 format.nChannels = 1;
544 format.nSamplesPerSec = rate;
545 format.wBitsPerSample = bits;
546 format.nBlockAlign = bits / 8;
547 format.nAvgBytesPerSec = rate * bits / 8;
548 format.cbSize = 0;
549
550 buf_object8 = createBuffer(&desc);
551 if(!buf_object8)
552 {
553 App_Log(DE2_AUDIO_WARNING, "[DirectSound] Failed to create buffer (rate:%i bits:%i)",
554 rate, bits);
555 return NULL;
556 }
557
558 // How about a 3D interface?
559 if(flags & SFXBF_3D)
560 {
561 buf_object3d = get3DBuffer(buf_object8);
562 if(!buf_object3d)
563 {
564 App_Log(DE2_AUDIO_WARNING,"[DirectSound] Failed to get a 3D interface for audio buffer");
565 buf_object8->Release();
566 return NULL;
567 }
568 }
569
570 // Clear the buffer.
571 buf = (sfxbuffer_t*) Z_Calloc(sizeof(*buf), PU_APPSTATIC, 0);
572
573 buf->ptr = buf_object8;
574 buf->ptr3D = buf_object3d;
575 buf->bytes = bits / 8;
576 buf->rate = rate;
577 buf->flags = flags;
578 buf->length = desc.dwBufferBytes;
579 buf->freq = rate; // Modified by calls to Set(SFXBP_FREQUENCY).
580
581 return buf;
582 }
583
DS_SFX_DestroyBuffer(sfxbuffer_t * buf)584 void DS_SFX_DestroyBuffer(sfxbuffer_t* buf)
585 {
586 if(!buf)
587 return;
588
589 DSBUF(buf)->Release();
590 // Free the memory allocated for the buffer.
591 Z_Free(buf);
592 }
593
594 /**
595 * Prepare the buffer for playing a sample by filling the buffer with as
596 * much sample data as fits. The pointer to sample is saved, so the caller
597 * mustn't free it while the sample is loaded.
598 */
DS_SFX_Load(sfxbuffer_t * buf,struct sfxsample_s * sample)599 void DS_SFX_Load(sfxbuffer_t* buf, struct sfxsample_s* sample)
600 {
601 void* data;
602 DWORD lockedBytes, wroteBytes;
603 HRESULT hr;
604
605 if(!buf || !sample)
606 return;
607
608 // Try to lock the buffer.
609 if(FAILED
610 (hr = DSBUF(buf)->Lock(0, 0, &data, &lockedBytes, 0, 0,
611 DSBLOCK_ENTIREBUFFER)))
612 return; // Couldn't lock!
613
614 // Write as much data as we can.
615 wroteBytes = MIN_OF(lockedBytes, sample->size);
616 memcpy(data, sample->data, wroteBytes);
617
618 // Fill the rest with zeroes.
619 if(wroteBytes < lockedBytes)
620 {
621 // Set the end marker since we already know it.
622 buf->cursor = wroteBytes;
623 memset((char *) data + wroteBytes, buf->bytes == 1 ? 128 : 0,
624 lockedBytes - wroteBytes);
625 }
626 else
627 {
628 // The whole buffer was filled, thus leaving the cursor at
629 // the beginning.
630 buf->cursor = 0;
631 }
632
633 DSBUF(buf)->Unlock(data, lockedBytes, 0, 0);
634
635 // Now the buffer is ready for playing.
636 buf->sample = sample;
637 buf->written = wroteBytes;
638 buf->flags &= ~SFXBF_RELOAD;
639
640 // Zero the play cursor.
641 DSBUF(buf)->SetCurrentPosition(0);
642 }
643
644 /**
645 * Stops the buffer and makes it forget about its sample.
646 */
DS_SFX_Reset(sfxbuffer_t * buf)647 void DS_SFX_Reset(sfxbuffer_t* buf)
648 {
649 if(!buf)
650 return;
651
652 DS_SFX_Stop(buf);
653 buf->sample = NULL;
654 buf->flags &= ~SFXBF_RELOAD;
655 }
656
657 /**
658 * @return Length of the buffer in milliseconds.
659 */
getBufLength(sfxbuffer_t * buf)660 static unsigned int getBufLength(sfxbuffer_t* buf)
661 {
662 if(!buf)
663 return 0;
664
665 return 1000 * buf->sample->numSamples / buf->freq;
666 }
667
DS_SFX_Play(sfxbuffer_t * buf)668 void DS_SFX_Play(sfxbuffer_t* buf)
669 {
670 HRESULT hr;
671
672 // Playing is quite impossible without a sample.
673 if(!buf || !buf->sample)
674 return;
675
676 // Do we need to reload?
677 if(buf->flags & SFXBF_RELOAD)
678 DS_SFX_Load(buf, buf->sample);
679
680 // The sound starts playing now?
681 if(!(buf->flags & SFXBF_PLAYING))
682 {
683 // Calculate the end time (milliseconds).
684 buf->endTime = Timer_RealMilliseconds() + getBufLength(buf);
685 }
686
687 if(FAILED(hr = DSBUF(buf)->Play(0, 0, DSBPLAY_LOOPING)))
688 return;
689
690 // The buffer is now playing.
691 buf->flags |= SFXBF_PLAYING;
692 }
693
DS_SFX_Stop(sfxbuffer_t * buf)694 void DS_SFX_Stop(sfxbuffer_t* buf)
695 {
696 if(!buf)
697 return;
698
699 DSBUF(buf)->Stop();
700
701 // Clear the flag that tells the Sfx module about playing buffers.
702 buf->flags &= ~SFXBF_PLAYING;
703
704 // If the sound is started again, it needs to be reloaded.
705 buf->flags |= SFXBF_RELOAD;
706 }
707
708 /*
709 static dd_bool InRange(uint pos, uint start, uint end)
710 {
711 if(end > start)
712 {
713 // This is the "normal" scenario: the write cursor is running
714 // ahead of the play cursor.
715 return (pos >= start && pos <= end);
716 }
717
718 // This is the "wrapping" scenario: the write cursor has wrapped
719 // back to the beginning, with the play cursor left at the end
720 // of the buffer. (The range is split in two.)
721 return (pos >= start || pos <= end);
722 }
723 */
724
725 /**
726 * Buffer streamer. Called by the Sfx refresh thread.
727 * Copy sample data into the buffer, and if the sample has ended, stop
728 * playing the buffer. If the buffer has been lost for some reason, restore
729 * it. Don't do anything time-consuming...
730 */
DS_SFX_Refresh(sfxbuffer_t * buf)731 void DS_SFX_Refresh(sfxbuffer_t* buf)
732 {
733 DWORD play, bytes[2], dose, fill;
734 void* data[2];
735 int writeBytes, i;
736 float usedSec;
737 unsigned int usedTime, nowTime;
738 HRESULT hr;
739
740 // Can only be done if there is a sample and the buffer is playing.
741 if(!buf || !buf->sample || !(buf->flags & SFXBF_PLAYING))
742 return;
743
744 nowTime = Timer_RealMilliseconds();
745
746 /**
747 * Have we passed the predicted end of sample?
748 * \note This test fails if the game has been running for about 50 days,
749 * since the millisecond counter overflows. It only affects sounds that
750 * are playing while the overflow happens, though.
751 */
752 if(!(buf->flags & SFXBF_REPEAT) && nowTime >= buf->endTime)
753 {
754 // Time for the sound to stop.
755 DS_SFX_Stop(buf);
756 return;
757 }
758
759 // Slightly redundant... (used = now - start)
760 usedTime = nowTime - (buf->endTime - getBufLength(buf));
761
762 // Approximate the current playing position (-0.1 sec for safety; we
763 // don't want to overwrite stuff before it gets played).
764 usedSec = usedTime / 1000.0f - 0.1f;
765 if(usedSec <= 0)
766 { // The update is a bit early; let's wait for the next one.
767 return;
768 }
769
770 play = (int) (usedSec * buf->freq * buf->bytes) % buf->length;
771
772 // How many bytes we must write (from buffer cursor up to play cursor).
773 if(buf->cursor < play)
774 writeBytes = play - buf->cursor;
775 else // Play has looped back to the beginning.
776 writeBytes = buf->length - buf->cursor + play;
777
778 // Try to lock the region, restoring if failed.
779 for(i = 0; i < 2; ++i)
780 {
781 if(FAILED
782 (hr = DSBUF(buf)->Lock(buf->cursor, writeBytes, &data[0],
783 &bytes[0], &data[1], &bytes[1], 0)))
784 {
785 if(hr == DSERR_BUFFERLOST)
786 {
787 DSBUF(buf)->Restore();
788 continue;
789 }
790 }
791
792 break;
793 }
794
795 if(FAILED(hr))
796 return; // Give up.
797
798 // Copy in two parts: as much sample data as we've got, and then zeros.
799 for(i = 0; i < 2 && data[i]; ++i)
800 {
801 // The dose is limited to the number of bytes we can write to this
802 // pointer and the number of bytes we've got left.
803 dose = MIN_OF(bytes[i], buf->sample->size - buf->written);
804
805 if(dose)
806 {
807 // Copy from the sample data and advance cursor & written.
808 memcpy((byte *) data[i], (byte *) buf->sample->data + buf->written,
809 dose);
810 buf->written += dose;
811 buf->cursor += dose;
812 }
813
814 if(dose < bytes[i])
815 {
816 // Repeating samples just rewind the 'written' counter when the
817 // end is reached.
818 if(!(buf->flags & SFXBF_REPEAT))
819 {
820 // The whole block was not filled. Write zeros in the rest.
821 fill = bytes[i] - dose;
822 // Filling an 8-bit sample with zeroes produces a nasty click.
823 memset((byte *) data[i] + dose, buf->bytes == 1 ? 128 : 0,
824 fill);
825 buf->cursor += fill;
826 }
827 }
828
829 // Wrap the cursor back to the beginning if needed. The wrap can
830 // only happen after the first write, really (where the buffer
831 // "breaks").
832 if(buf->cursor >= buf->length)
833 buf->cursor -= buf->length;
834 }
835
836 // And we're done! Unlock and get out of here.
837 DSBUF(buf)->Unlock(data[0], bytes[0], data[1], bytes[1]);
838
839 // If the buffer is in repeat mode, go back to the beginning once the
840 // end has been reached.
841 if((buf->flags & SFXBF_REPEAT) && buf->written == buf->sample->size)
842 buf->written = 0;
843 }
844
845 /**
846 * Convert linear volume 0..1 to logarithmic -10000..0.
847 */
volLinearToLog(float vol)848 static int volLinearToLog(float vol)
849 {
850 if(vol <= 0)
851 return DSBVOLUME_MIN;
852
853 if(vol >= 1)
854 return DSBVOLUME_MAX;
855
856 // Straighten the volume curve.
857 return MINMAX_OF(DSBVOLUME_MIN, (int) (100 * 20 * log10(vol)),
858 DSBVOLUME_MAX);
859 }
860
861 /**
862 * Convert linear pan -1..1 to logarithmic -10000..10000.
863 */
panLinearToLog(float pan)864 static int panLinearToLog(float pan)
865 {
866 if(pan >= 1)
867 return DSBPAN_RIGHT;
868 if(pan <= -1)
869 return DSBPAN_LEFT;
870 if(pan == 0)
871 return 0;
872 if(pan > 0)
873 return (int) (-100 * 20 * log10(1 - pan));
874
875 return (int) (100 * 20 * log10(1 + pan));
876 }
877
878 /**
879 * SFXBP_VOLUME (if negative, interpreted as attenuation)
880 * SFXBP_FREQUENCY
881 * SFXBP_PAN (-1..1)
882 * SFXBP_MIN_DISTANCE
883 * SFXBP_MAX_DISTANCE
884 * SFXBP_RELATIVE_MODE
885 */
DS_SFX_Set(sfxbuffer_t * buf,int prop,float value)886 void DS_SFX_Set(sfxbuffer_t* buf, int prop, float value)
887 {
888 if(!buf)
889 return;
890
891 switch(prop)
892 {
893 default:
894 #if _DEBUG
895 Con_Error("dsDS9::DS_DSoundSet: Unknown prop %i.", prop);
896 #endif
897 break;
898
899 case SFXBP_VOLUME:
900 {
901 LONG volume;
902
903 if(value <= 0) // Use logarithmic attenuation.
904 volume = (LONG) ((-1 - value) * 10000);
905 else // Linear volume.
906 volume = (LONG) volLinearToLog(value);
907
908 DSBUF(buf)->SetVolume(volume);
909 break;
910 }
911 case SFXBP_FREQUENCY:
912 {
913 unsigned int freq = (unsigned int) (buf->rate * value);
914
915 // Don't set redundantly.
916 if(freq != buf->freq)
917 {
918 buf->freq = freq;
919 DSBUF(buf)->SetFrequency(freq);
920 }
921 break;
922 }
923 case SFXBP_PAN:
924 DSBUF(buf)->SetPan(panLinearToLog(value));
925 break;
926
927 case SFXBP_MIN_DISTANCE:
928 if(!DSBUF3D(buf))
929 return;
930 DSBUF3D(buf)->SetMinDistance(value, DS3D_DEFERRED);
931 break;
932
933 case SFXBP_MAX_DISTANCE:
934 if(!DSBUF3D(buf))
935 return;
936 DSBUF3D(buf)->SetMaxDistance(value, DS3D_DEFERRED);
937 break;
938
939 case SFXBP_RELATIVE_MODE:
940 if(!DSBUF3D(buf))
941 return;
942 DSBUF3D(buf)->SetMode(value? DS3DMODE_HEADRELATIVE :
943 DS3DMODE_NORMAL, DS3D_DEFERRED);
944 break;
945 }
946 }
947
948 /**
949 * Coordinates specified in world coordinate system, converted to DSound's:
950 * +X to the right, +Y up and +Z away (Y and Z swapped, i.e.).
951 *
952 * @param prop SFXBP_POSITION
953 * SFXBP_VELOCITY
954 */
DS_SFX_Setv(sfxbuffer_t * buf,int prop,float * values)955 void DS_SFX_Setv(sfxbuffer_t* buf, int prop, float* values)
956 {
957 if(!buf || !values)
958 return;
959
960 if(!DSBUF3D(buf))
961 return;
962
963 switch(prop)
964 {
965 default:
966 #if _DEBUG
967 Con_Error("dsDS9::DS_DSoundSetv: Unknown prop %i.", prop);
968 #endif
969 break;
970
971 case SFXBP_POSITION:
972 DSBUF3D(buf)->SetPosition(values[VX], values[VZ], values[VY],
973 DS3D_DEFERRED);
974 break;
975
976 case SFXBP_VELOCITY:
977 DSBUF3D(buf)->SetVelocity(values[VX], values[VZ], values[VY],
978 DS3D_DEFERRED);
979 break;
980 }
981 }
982
983 /**
984 * Parameters are in radians.
985 * Example front vectors:
986 * Yaw 0:(0,0,1), pi/2:(-1,0,0)
987 */
listenerOrientation(float yaw,float pitch)988 static void listenerOrientation(float yaw, float pitch)
989 {
990 float front[3], up[3];
991
992 front[VX] = cos(yaw) * cos(pitch);
993 front[VZ] = sin(yaw) * cos(pitch);
994 front[VY] = sin(pitch);
995
996 up[VX] = -cos(yaw) * sin(pitch);
997 up[VZ] = -sin(yaw) * sin(pitch);
998 up[VY] = cos(pitch);
999
1000 dsListener->SetOrientation(front[VX], front[VY], front[VZ],
1001 up[VX], up[VY], up[VZ], DS3D_DEFERRED);
1002 }
1003
1004 #if DENG_HAVE_EAX2
1005
1006 /**
1007 * Set the property as 'failed'. No more errors are reported for it.
1008 */
setEAXFailed(DWORD prop)1009 static void setEAXFailed(DWORD prop)
1010 {
1011 int i;
1012
1013 for(i = 0; i < MAX_FAILED_PROPS; ++i)
1014 if(failedProps[i] == ~0)
1015 {
1016 failedProps[i] = prop;
1017 break;
1018 }
1019 }
1020
1021 /**
1022 * @return @c true, if the specified property has failed.
1023 */
hasEAXFailed(DWORD prop)1024 static dd_bool hasEAXFailed(DWORD prop)
1025 {
1026 int i;
1027
1028 for(i = 0; i < MAX_FAILED_PROPS; ++i)
1029 if(failedProps[i] == prop)
1030 return true;
1031
1032 return false;
1033 }
1034
1035 /**
1036 * @return @c true, if an EAX error should be reported.
1037 */
reportEAXError(DWORD prop,HRESULT hr)1038 static dd_bool reportEAXError(DWORD prop, HRESULT hr)
1039 {
1040 if(ignoreEAXErrors)
1041 return false;
1042 if(hr != DSERR_UNSUPPORTED)
1043 return true;
1044 if(hasEAXFailed(prop))
1045 return false; // Don't report again.
1046
1047 setEAXFailed(prop);
1048
1049 return true; // First time, do report.
1050 }
1051
setEAXdw(DWORD prop,int value)1052 static void setEAXdw(DWORD prop, int value)
1053 {
1054 HRESULT hr;
1055
1056 if(FAILED
1057 (hr = propertySet->Set(DSPROPSETID_EAX_ListenerProperties,
1058 prop | DSPROPERTY_EAXLISTENER_DEFERRED, NULL,
1059 0, &value, sizeof(DWORD))))
1060 {
1061 if(reportEAXError(prop, hr))
1062 App_Log(DE2_DEV_AUDIO_WARNING, "setEAXdw (prop:%i value:%i) failed. Result: %x",
1063 prop, value, hr);
1064 }
1065 }
1066
setEAXf(DWORD prop,float value)1067 static void setEAXf(DWORD prop, float value)
1068 {
1069 HRESULT hr;
1070
1071 if(FAILED
1072 (hr = propertySet->Set(DSPROPSETID_EAX_ListenerProperties,
1073 prop | DSPROPERTY_EAXLISTENER_DEFERRED, NULL,
1074 0, &value, sizeof(float))))
1075 {
1076 if(reportEAXError(prop, hr))
1077 App_Log(DE2_DEV_AUDIO_WARNING,"setEAXf (prop:%i value:%f) failed. Result: %x",
1078 prop, value, hr);
1079 }
1080 }
1081
1082 /**
1083 * Linear multiplication for a logarithmic property.
1084 */
mulEAXdw(DWORD prop,float mul)1085 static void mulEAXdw(DWORD prop, float mul)
1086 {
1087 DWORD retBytes;
1088 LONG value;
1089 HRESULT hr;
1090
1091 if(FAILED
1092 (hr = propertySet->Get(DSPROPSETID_EAX_ListenerProperties,
1093 prop, NULL, 0, &value, sizeof(value),
1094 &retBytes)))
1095 {
1096 if(reportEAXError(prop, hr))
1097 App_Log(DE2_DEV_AUDIO_WARNING, "mulEAXdw (prop:%i) get failed. Result: %x",
1098 prop, hr & 0xffff);
1099 }
1100
1101 setEAXdw(prop, volLinearToLog(pow(10, value / 2000.0f) * mul));
1102 }
1103
1104 /**
1105 * Linear multiplication for a linear property.
1106 */
mulEAXf(DWORD prop,float mul,float min,float max)1107 static void mulEAXf(DWORD prop, float mul, float min, float max)
1108 {
1109 DWORD retBytes;
1110 HRESULT hr;
1111 float value;
1112
1113 if(FAILED
1114 (hr = propertySet->Get(DSPROPSETID_EAX_ListenerProperties,
1115 prop, NULL, 0, &value, sizeof(value),
1116 &retBytes)))
1117 {
1118 if(reportEAXError(prop, hr))
1119 App_Log(DE2_DEV_AUDIO_WARNING,
1120 "mulEAXf (prop:%i) get failed. Result: %x", prop, hr & 0xffff);
1121 }
1122
1123 value *= mul;
1124 if(value < min)
1125 value = min;
1126 if(value > max)
1127 value = max;
1128
1129 setEAXf(prop, value);
1130 }
1131
1132 #endif
1133
1134 /**
1135 * Set a property of a listener.
1136 *
1137 * @param prop SFXLP_UNITS_PER_METER
1138 * SFXLP_DOPPLER
1139 * SFXLP_UPDATE
1140 * @param value Value to be set.
1141 */
DS_SFX_Listener(int prop,float value)1142 void DS_SFX_Listener(int prop, float value)
1143 {
1144 if(!dsListener)
1145 return;
1146
1147 switch(prop)
1148 {
1149 default:
1150 #if _DEBUG
1151 Con_Error("dsDS9::DS_DSoundListener: Unknown prop %i.", prop);
1152 #endif
1153 break;
1154
1155 case SFXLP_UPDATE:
1156 // Commit any deferred settings.
1157 dsListener->CommitDeferredSettings();
1158 #ifdef DENG_HAVE_EAX2
1159 commitEAXDeferred();
1160 #endif
1161 break;
1162
1163 case SFXLP_UNITS_PER_METER:
1164 dsListener->SetDistanceFactor(1 / value, DS3D_IMMEDIATE);
1165 break;
1166
1167 case SFXLP_DOPPLER:
1168 dsListener->SetDopplerFactor(value, DS3D_IMMEDIATE);
1169 break;
1170 }
1171 }
1172
1173 #ifdef DENG_HAVE_EAX2
commitEAXDeferred(void)1174 static void commitEAXDeferred(void)
1175 {
1176 if(!propertySet)
1177 return;
1178
1179 propertySet->Set(DSPROPSETID_EAX_ListenerProperties,
1180 DSPROPERTY_EAXLISTENER_COMMITDEFERREDSETTINGS, NULL,
1181 0, NULL, 0);
1182 }
1183
1184 /**
1185 * If EAX is available, set the listening environmental properties.
1186 * Values use SRD_* for indices.
1187 */
listenerEnvironment(float * rev)1188 static void listenerEnvironment(float* rev)
1189 {
1190 float val;
1191 int eaxVal;
1192
1193 if(!rev)
1194 return;
1195
1196 // This can only be done if EAX is available.
1197 if(!propertySet)
1198 return;
1199
1200 val = rev[SFXLP_REVERB_SPACE];
1201 if(rev[SFXLP_REVERB_DECAY] > .5)
1202 {
1203 // This much decay needs at least the Generic environment.
1204 if(val < .2)
1205 val = .2f;
1206 }
1207
1208 // Set the environment. Other properties are updated automatically.
1209 if(val >= 1)
1210 eaxVal = EAX_ENVIRONMENT_PLAIN;
1211 else if(val >= .8)
1212 eaxVal = EAX_ENVIRONMENT_CONCERTHALL;
1213 else if(val >= .6)
1214 eaxVal = EAX_ENVIRONMENT_AUDITORIUM;
1215 else if(val >= .4)
1216 eaxVal = EAX_ENVIRONMENT_CAVE;
1217 else if(val >= .2)
1218 eaxVal = EAX_ENVIRONMENT_GENERIC;
1219 else
1220 eaxVal = EAX_ENVIRONMENT_ROOM;
1221 setEAXdw(DSPROPERTY_EAXLISTENER_ENVIRONMENT, eaxVal);
1222
1223 // General reverb volume adjustment.
1224 setEAXdw(DSPROPERTY_EAXLISTENER_ROOM, volLinearToLog(rev[SFXLP_REVERB_VOLUME]));
1225
1226 // Reverb decay.
1227 val = (rev[SFXLP_REVERB_DECAY] - .5f) * 1.5f + 1;
1228 mulEAXf(DSPROPERTY_EAXLISTENER_DECAYTIME, val, EAXLISTENER_MINDECAYTIME,
1229 EAXLISTENER_MAXDECAYTIME);
1230
1231 // Damping.
1232 val = 1.1f * (1.2f - rev[SFXLP_REVERB_DAMPING]);
1233 if(val < .1)
1234 val = .1f;
1235 mulEAXdw(DSPROPERTY_EAXLISTENER_ROOMHF, val);
1236
1237 // A slightly increased roll-off.
1238 setEAXf(DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, 1.3f);
1239 }
1240 #endif
1241
1242 /**
1243 * Call SFXLP_UPDATE at the end of every channel update.
1244 */
DS_SFX_Listenerv(int prop,float * values)1245 void DS_SFX_Listenerv(int prop, float* values)
1246 {
1247 if(!values)
1248 return;
1249
1250 switch(prop)
1251 {
1252 case SFXLP_PRIMARY_FORMAT:
1253 if(canSetPSF)
1254 setPrimaryFormat((int) values[0], (int) values[1]);
1255 break;
1256
1257 case SFXLP_POSITION:
1258 if(!dsListener)
1259 return;
1260 dsListener->SetPosition(values[VX], values[VZ], values[VY],
1261 DS3D_DEFERRED);
1262 break;
1263
1264 case SFXLP_VELOCITY:
1265 if(!dsListener)
1266 return;
1267 dsListener->SetVelocity(values[VX], values[VZ], values[VY],
1268 DS3D_DEFERRED);
1269 break;
1270
1271 case SFXLP_ORIENTATION:
1272 if(!dsListener)
1273 return;
1274 listenerOrientation(values[VX] / 180 * DD_PI, values[VY] / 180 * DD_PI);
1275 break;
1276
1277 case SFXLP_REVERB:
1278 #ifdef DENG_HAVE_EAX2
1279 if(!dsListener)
1280 return;
1281 listenerEnvironment(values);
1282 #endif
1283 break;
1284
1285 default:
1286 DS_SFX_Listener(prop, 0);
1287 break;
1288 }
1289 }
1290
1291 /**
1292 * Declares the type of the plugin so the engine knows how to treat it. Called
1293 * automatically when the plugin is loaded.
1294 */
deng_LibraryType(void)1295 DENG_EXTERN_C const char* deng_LibraryType(void)
1296 {
1297 return "deng-plugin/audio";
1298 }
1299
1300 DENG_API_EXCHANGE(
1301 DENG_GET_API(DE_API_BASE, Base);
1302 DENG_GET_API(DE_API_CONSOLE, Con);
1303 )
1304