1 /*
2  Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
3 
4  This program is free software; you can redistribute it and/or
5  modify it under the terms of the GNU General Public License
6  as published by the Free Software Foundation; either version 2
7  of the License, or (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13  See the GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19  */
20 
21 /**
22  * DirectSound output driver for MultiVoc
23  */
24 
25 #define WIN32_LEAN_AND_MEAN
26 #define DIRECTSOUND_VERSION  0x0700
27 #include <windows.h>
28 #include <mmsystem.h>
29 #include <dsound.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 
33 #include "driver_directsound.h"
34 
35 enum {
36    DSErr_Warning = -2,
37    DSErr_Error   = -1,
38    DSErr_Ok      = 0,
39 	DSErr_Uninitialised,
40 	DSErr_DirectSoundCreate,
41 	DSErr_SetCooperativeLevel,
42 	DSErr_CreateSoundBuffer,
43 	DSErr_CreateSoundBufferSecondary,
44 	DSErr_SetFormat,
45 	DSErr_SetFormatSecondary,
46 	DSErr_Notify,
47 	DSErr_NotifyEvents,
48 	DSErr_SetNotificationPositions,
49 	DSErr_Play,
50 	DSErr_PlaySecondary,
51 	DSErr_CreateThread,
52 	DSErr_CreateMutex
53 };
54 
55 static int ErrorCode = DSErr_Ok;
56 static int Initialised = 0;
57 static int Playing = 0;
58 
59 static char *MixBuffer = 0;
60 static int MixBufferSize = 0;
61 static int MixBufferCount = 0;
62 static int MixBufferCurrent = 0;
63 static int MixBufferUsed = 0;
64 static void ( *MixCallBack )( void ) = 0;
65 
66 static LPDIRECTSOUND lpds = 0;
67 static LPDIRECTSOUNDBUFFER lpdsbprimary = 0, lpdsbsec = 0;
68 static LPDIRECTSOUNDNOTIFY lpdsnotify = 0;
69 static DSBPOSITIONNOTIFY notifyPositions[3] = { { 0,0 }, { 0,0 }, { 0,0 } };
70 static HANDLE mixThread = 0;
71 static HANDLE mutex = 0;
72 
73 
FillBufferPortion(char * ptr,int remaining)74 static void FillBufferPortion(char * ptr, int remaining)
75 {
76     int len;
77 	char *sptr;
78 
79 	while (remaining > 0) {
80 		if (MixBufferUsed == MixBufferSize) {
81 			MixCallBack();
82 
83 			MixBufferUsed = 0;
84 			MixBufferCurrent++;
85 			if (MixBufferCurrent >= MixBufferCount) {
86 				MixBufferCurrent -= MixBufferCount;
87 			}
88 		}
89 
90 		while (remaining > 0 && MixBufferUsed < MixBufferSize) {
91 			sptr = MixBuffer + (MixBufferCurrent * MixBufferSize) + MixBufferUsed;
92 
93 			len = MixBufferSize - MixBufferUsed;
94 			if (remaining < len) {
95 				len = remaining;
96 			}
97 
98 			memcpy(ptr, sptr, len);
99 
100 			ptr += len;
101 			MixBufferUsed += len;
102 			remaining -= len;
103 		}
104 	}
105 }
106 
FillBuffer(int bufnum)107 static void FillBuffer(int bufnum)
108 {
109     HRESULT err;
110     LPVOID ptr, ptr2;
111     DWORD remaining, remaining2;
112     int retries = 1;
113 
114     //fprintf(stderr, "DirectSound FillBuffer: filling %d\n", bufnum);
115 
116     do {
117         err = IDirectSoundBuffer_Lock(lpdsbsec,
118                   notifyPositions[bufnum].dwOffset,
119                   notifyPositions[1].dwOffset,
120                   &ptr, &remaining,
121                   &ptr2, &remaining2,
122                   0);
123         if (FAILED(err)) {
124             if (err == DSERR_BUFFERLOST) {
125                 err = IDirectSoundBuffer_Restore(lpdsbsec);
126                 if (FAILED(err)) {
127                     return;
128                 }
129 
130                 if (retries-- > 0) {
131                     continue;
132                 }
133             }
134             fprintf(stderr, "DirectSound FillBuffer: err %x\n", (unsigned int) err);
135             return;
136         }
137         break;
138     } while (1);
139 
140     if (ptr) {
141         FillBufferPortion((char *) ptr, remaining);
142     }
143     if (ptr2) {
144         FillBufferPortion((char *) ptr2, remaining2);
145     }
146 
147     IDirectSoundBuffer_Unlock(lpdsbsec, ptr, remaining, ptr2, remaining2);
148 }
149 
fillDataThread(LPVOID lpParameter)150 static DWORD WINAPI fillDataThread(LPVOID lpParameter)
151 {
152     HANDLE handles[3];
153     DWORD waitret, waitret2;
154 
155     handles[0] = notifyPositions[0].hEventNotify;
156     handles[1] = notifyPositions[1].hEventNotify;
157     handles[2] = notifyPositions[2].hEventNotify;
158 
159 	do {
160         waitret = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
161         switch (waitret) {
162             case WAIT_OBJECT_0:
163             case WAIT_OBJECT_0+1:
164                 waitret2 = WaitForSingleObject(mutex, INFINITE);
165                 if (waitret2 == WAIT_OBJECT_0) {
166                     FillBuffer(WAIT_OBJECT_0 + 1 - waitret);
167                     ReleaseMutex(mutex);
168                 } else {
169                     fprintf(stderr, "DirectSound fillDataThread: wfso err %d\n", (int) waitret2);
170                 }
171                 break;
172             case WAIT_OBJECT_0+2:
173                 fprintf(stderr, "DirectSound fillDataThread: exiting\n");
174                 ExitThread(0);
175                 break;
176             default:
177                 fprintf(stderr, "DirectSound fillDataThread: wfmo err %d\n", (int) waitret);
178                 break;
179         }
180 	} while (1);
181 
182 	return 0;
183 }
184 
185 
DirectSoundDrv_GetError(void)186 int DirectSoundDrv_GetError(void)
187 {
188 	return ErrorCode;
189 }
190 
DirectSoundDrv_ErrorString(int ErrorNumber)191 const char *DirectSoundDrv_ErrorString( int ErrorNumber )
192 {
193 	const char *ErrorString;
194 
195    switch( ErrorNumber )
196 	{
197       case DSErr_Warning :
198       case DSErr_Error :
199          ErrorString = DirectSoundDrv_ErrorString( ErrorCode );
200          break;
201 
202       case DSErr_Ok :
203          ErrorString = "DirectSound ok.";
204          break;
205 
206 		case DSErr_Uninitialised:
207 			ErrorString = "DirectSound uninitialised.";
208 			break;
209 
210 		case DSErr_DirectSoundCreate:
211             ErrorString = "DirectSound error: DirectSoundCreate failed.";
212             break;
213 
214         case DSErr_SetCooperativeLevel:
215             ErrorString = "DirectSound error: SetCooperativeLevel failed.";
216             break;
217 
218         case DSErr_CreateSoundBuffer:
219             ErrorString = "DirectSound error: primary CreateSoundBuffer failed.";
220             break;
221 
222         case DSErr_CreateSoundBufferSecondary:
223             ErrorString = "DirectSound error: secondary CreateSoundBuffer failed.";
224             break;
225 
226         case DSErr_SetFormat:
227             ErrorString = "DirectSound error: primary buffer SetFormat failed.";
228             break;
229 
230         case DSErr_SetFormatSecondary:
231             ErrorString = "DirectSound error: secondary buffer SetFormat failed.";
232             break;
233 
234         case DSErr_Notify:
235             ErrorString = "DirectSound error: failed querying secondary buffer for notify interface.";
236             break;
237 
238         case DSErr_NotifyEvents:
239             ErrorString = "DirectSound error: failed creating notify events.";
240             break;
241 
242         case DSErr_SetNotificationPositions:
243             ErrorString = "DirectSound error: failed setting notification positions.";
244             break;
245 
246         case DSErr_Play:
247             ErrorString = "DirectSound error: primary buffer Play failed.";
248             break;
249 
250         case DSErr_PlaySecondary:
251             ErrorString = "DirectSound error: secondary buffer Play failed.";
252             break;
253 
254         case DSErr_CreateThread:
255             ErrorString = "DirectSound error: failed creating mix thread.";
256             break;
257 
258         case DSErr_CreateMutex:
259             ErrorString = "DirectSound error: failed creating mix mutex.";
260             break;
261 
262 		default:
263 			ErrorString = "Unknown DirectSound error code.";
264 			break;
265 	}
266 
267 	return ErrorString;
268 
269 }
270 
271 
TeardownDSound(HRESULT err)272 static void TeardownDSound(HRESULT err)
273 {
274     if (FAILED(err)) {
275         fprintf(stderr, "Dying error: %x\n", (unsigned int) err);
276     }
277 
278     if (lpdsnotify)   IDirectSoundNotify_Release(lpdsnotify);
279     if (notifyPositions[0].hEventNotify) CloseHandle(notifyPositions[0].hEventNotify);
280     if (notifyPositions[1].hEventNotify) CloseHandle(notifyPositions[1].hEventNotify);
281     if (notifyPositions[2].hEventNotify) CloseHandle(notifyPositions[2].hEventNotify);
282     if (mutex) CloseHandle(mutex);
283     if (lpdsbsec)     IDirectSoundBuffer_Release(lpdsbsec);
284     if (lpdsbprimary) IDirectSoundBuffer_Release(lpdsbprimary);
285     if (lpds)         IDirectSound_Release(lpds);
286     notifyPositions[0].hEventNotify =
287     notifyPositions[1].hEventNotify =
288     notifyPositions[2].hEventNotify = 0;
289     mutex = 0;
290     lpdsnotify = 0;
291     lpdsbsec = 0;
292     lpdsbprimary = 0;
293     lpds = 0;
294 }
295 
DirectSoundDrv_PCM_Init(int * mixrate,int * numchannels,int * samplebits,void * initdata)296 int DirectSoundDrv_PCM_Init(int * mixrate, int * numchannels, int * samplebits, void * initdata)
297 {
298     HRESULT err;
299     DSBUFFERDESC bufdesc;
300     WAVEFORMATEX wfex;
301 
302     if (Initialised) {
303         DirectSoundDrv_PCM_Shutdown();
304     }
305 
306     err = DirectSoundCreate(0, &lpds, 0);
307     if (FAILED( err )) {
308         ErrorCode = DSErr_DirectSoundCreate;
309         return DSErr_Error;
310     }
311 
312     err = IDirectSound_SetCooperativeLevel(lpds, (HWND) initdata, DSSCL_PRIORITY);
313     if (FAILED( err )) {
314         TeardownDSound(err);
315         ErrorCode = DSErr_SetCooperativeLevel;
316         return DSErr_Error;
317     }
318 
319     memset(&bufdesc, 0, sizeof(DSBUFFERDESC));
320     bufdesc.dwSize = sizeof(DSBUFFERDESC);
321     bufdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
322 
323     err = IDirectSound_CreateSoundBuffer(lpds, &bufdesc, &lpdsbprimary, 0);
324     if (FAILED( err )) {
325         TeardownDSound(err);
326         ErrorCode = DSErr_CreateSoundBuffer;
327         return DSErr_Error;
328     }
329 
330     memset(&wfex, 0, sizeof(WAVEFORMATEX));
331     wfex.wFormatTag = WAVE_FORMAT_PCM;
332     wfex.nChannels = *numchannels;
333     wfex.nSamplesPerSec = *mixrate;
334     wfex.wBitsPerSample = *samplebits;
335     wfex.nBlockAlign = wfex.nChannels * wfex.wBitsPerSample / 8;
336     wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;
337 
338     err = IDirectSoundBuffer_SetFormat(lpdsbprimary, &wfex);
339     if (FAILED( err )) {
340         TeardownDSound(err);
341         ErrorCode = DSErr_SetFormat;
342         return DSErr_Error;
343     }
344 
345     // Mix buffer to be a power of 2, min 512 samples, and 4096 samples at 48kHz.
346     bufdesc.dwBufferBytes = 512;
347     while (bufdesc.dwBufferBytes < (4096 * *mixrate / 48000))
348         bufdesc.dwBufferBytes += bufdesc.dwBufferBytes;
349     bufdesc.dwBufferBytes *= wfex.nBlockAlign * 2;
350 
351     bufdesc.dwFlags = DSBCAPS_LOCSOFTWARE |
352                       DSBCAPS_CTRLPOSITIONNOTIFY |
353                       DSBCAPS_GETCURRENTPOSITION2;
354     bufdesc.lpwfxFormat = &wfex;
355 
356     err = IDirectSound_CreateSoundBuffer(lpds, &bufdesc, &lpdsbsec, 0);
357     if (FAILED( err )) {
358         TeardownDSound(err);
359         ErrorCode = DSErr_SetFormatSecondary;
360         return DSErr_Error;
361     }
362 
363     err = IDirectSoundBuffer_QueryInterface(lpdsbsec, &IID_IDirectSoundNotify,
364             (LPVOID *) &lpdsnotify);
365     if (FAILED( err )) {
366         TeardownDSound(err);
367         ErrorCode = DSErr_Notify;
368         return DSErr_Error;
369     }
370 
371     notifyPositions[0].dwOffset = 0;
372     notifyPositions[0].hEventNotify = CreateEvent(NULL, FALSE, FALSE, NULL);
373     notifyPositions[1].dwOffset = bufdesc.dwBufferBytes / 2;
374     notifyPositions[1].hEventNotify = CreateEvent(NULL, FALSE, FALSE, NULL);
375     notifyPositions[2].dwOffset = DSBPN_OFFSETSTOP;
376     notifyPositions[2].hEventNotify = CreateEvent(NULL, FALSE, FALSE, NULL);
377     if (!notifyPositions[0].hEventNotify ||
378         !notifyPositions[1].hEventNotify ||
379         !notifyPositions[2].hEventNotify) {
380         TeardownDSound(DS_OK);
381         ErrorCode = DSErr_NotifyEvents;
382         return DSErr_Error;
383     }
384 
385     err = IDirectSoundNotify_SetNotificationPositions(lpdsnotify, 3, notifyPositions);
386     if (FAILED( err )) {
387         TeardownDSound(err);
388         ErrorCode = DSErr_SetNotificationPositions;
389         return DSErr_Error;
390     }
391 
392     err = IDirectSoundBuffer_Play(lpdsbprimary, 0, 0, DSBPLAY_LOOPING);
393     if (FAILED( err )) {
394         TeardownDSound(err);
395         ErrorCode = DSErr_Play;
396         return DSErr_Error;
397     }
398 
399     mutex = CreateMutex(0, FALSE, 0);
400     if (!mutex) {
401         TeardownDSound(DS_OK);
402         ErrorCode = DSErr_CreateMutex;
403         return DSErr_Error;
404     }
405 
406     Initialised = 1;
407 
408     fprintf(stderr, "DirectSound Init: yay\n");
409 
410 	return DSErr_Ok;
411 }
412 
DirectSoundDrv_PCM_Shutdown(void)413 void DirectSoundDrv_PCM_Shutdown(void)
414 {
415     if (!Initialised) {
416         return;
417     }
418 
419     DirectSoundDrv_PCM_StopPlayback();
420 
421     TeardownDSound(DS_OK);
422 
423     Initialised = 0;
424 }
425 
DirectSoundDrv_PCM_BeginPlayback(char * BufferStart,int BufferSize,int NumDivisions,void (* CallBackFunc)(void))426 int DirectSoundDrv_PCM_BeginPlayback(char *BufferStart, int BufferSize,
427 						int NumDivisions, void ( *CallBackFunc )( void ) )
428 {
429     HRESULT err;
430 
431     if (!Initialised) {
432         ErrorCode = DSErr_Uninitialised;
433         return DSErr_Error;
434     }
435 
436     DirectSoundDrv_PCM_StopPlayback();
437 
438 	MixBuffer = BufferStart;
439 	MixBufferSize = BufferSize;
440 	MixBufferCount = NumDivisions;
441 	MixBufferCurrent = 0;
442 	MixBufferUsed = 0;
443 	MixCallBack = CallBackFunc;
444 
445 	// prime the buffer
446 	FillBuffer(0);
447 
448 	mixThread = CreateThread(NULL, 0, fillDataThread, 0, 0, 0);
449 	if (!mixThread) {
450         ErrorCode = DSErr_CreateThread;
451         return DSErr_Error;
452     }
453 
454     SetThreadPriority(mixThread, THREAD_PRIORITY_HIGHEST);
455 
456     err = IDirectSoundBuffer_Play(lpdsbsec, 0, 0, DSBPLAY_LOOPING);
457     if (FAILED( err )) {
458         ErrorCode = DSErr_PlaySecondary;
459         return DSErr_Error;
460     }
461 
462     Playing = 1;
463 
464 	return DSErr_Ok;
465 }
466 
DirectSoundDrv_PCM_StopPlayback(void)467 void DirectSoundDrv_PCM_StopPlayback(void)
468 {
469     if (!Playing) {
470         return;
471     }
472 
473     IDirectSoundBuffer_Stop(lpdsbsec);
474     IDirectSoundBuffer_SetCurrentPosition(lpdsbsec, 0);
475 
476     Playing = 0;
477 }
478 
DirectSoundDrv_PCM_Lock(void)479 void DirectSoundDrv_PCM_Lock(void)
480 {
481     DWORD err;
482 
483     err = WaitForSingleObject(mutex, INFINITE);
484     if (err != WAIT_OBJECT_0) {
485         fprintf(stderr, "DirectSound lock: wfso %d\n", (int) err);
486     }
487 }
488 
DirectSoundDrv_PCM_Unlock(void)489 void DirectSoundDrv_PCM_Unlock(void)
490 {
491     ReleaseMutex(mutex);
492 }
493 
494 
495 // vim:ts=4:sw=4:expandtab:
496