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