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