1 /* FluidSynth - A Software Synthesizer
2  *
3  * Copyright (C) 2003  Peter Hanappe and others.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public License
7  * as published by the Free Software Foundation; either version 2 of
8  * the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301, USA
19  */
20 
21 
22 #define INITGUID
23 
24 #include "fluidsynth_priv.h"
25 #include "fluid_synth.h"
26 #include "fluid_sys.h"
27 #include "fluid_adriver.h"
28 #include "fluid_settings.h"
29 #include <mmsystem.h>
30 #include <dsound.h>
31 
32 fluid_audio_driver_t*
33 new_fluid_dsound_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth);
34 
35 int delete_fluid_dsound_audio_driver(fluid_audio_driver_t* data);
36 DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter);
37 
38 HWND fluid_win32_get_window(void);
39 char* fluid_win32_error(HRESULT hr);
40 
41 
42 #define FLUID_HINSTANCE  ((HINSTANCE)fluid_get_hinstance())
43 
44 typedef struct {
45   fluid_audio_driver_t driver;
46   LPDIRECTSOUND direct_sound;
47   LPDIRECTSOUNDBUFFER prim_buffer;
48   LPDIRECTSOUNDBUFFER sec_buffer;
49   WAVEFORMATEX* format;
50   HANDLE thread;
51   DWORD threadID;
52   fluid_synth_t* synth;
53   fluid_audio_callback_t write;
54   int cont;
55   DWORD buffer_byte_size;
56   DWORD queue_byte_size;
57   DWORD frame_size;
58 } fluid_dsound_audio_driver_t;
59 
60 typedef struct {
61   LPGUID devGUID;
62   char* devname;
63 } fluid_dsound_devsel_t;
64 
65 BOOL CALLBACK
fluid_dsound_enum_callback(LPGUID guid,LPCTSTR description,LPCTSTR module,LPVOID context)66 fluid_dsound_enum_callback(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context)
67 {
68   fluid_settings_t* settings = (fluid_settings_t*) context;
69   fluid_settings_add_option(settings, "audio.dsound.device", (const char *)description);
70 
71   return TRUE;
72 }
73 
74 BOOL CALLBACK
fluid_dsound_enum_callback2(LPGUID guid,LPCTSTR description,LPCTSTR module,LPVOID context)75 fluid_dsound_enum_callback2(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context)
76 {
77   fluid_dsound_devsel_t* devsel = (fluid_dsound_devsel_t*) context;
78   FLUID_LOG(FLUID_DBG, "Testing audio device: %s", description);
79   if (strcasecmp(devsel->devname, description) == 0) {
80     devsel->devGUID = FLUID_NEW(GUID);
81     if(devsel->devGUID) {
82       memcpy(devsel->devGUID, guid, sizeof(GUID));
83       FLUID_LOG(FLUID_DBG, "Selected audio device GUID: %p", devsel->devGUID);
84     }
85   }
86   return TRUE;
87 }
88 
fluid_dsound_audio_driver_settings(fluid_settings_t * settings)89 void fluid_dsound_audio_driver_settings(fluid_settings_t* settings)
90 {
91   fluid_settings_register_str(settings, "audio.dsound.device", "default", 0, NULL, NULL);
92   fluid_settings_add_option(settings, "audio.dsound.device", "default");
93   DirectSoundEnumerate((LPDSENUMCALLBACK) fluid_dsound_enum_callback, settings);
94 }
95 
96 
97 /*
98  * new_fluid_dsound_audio_driver
99  */
100 fluid_audio_driver_t*
new_fluid_dsound_audio_driver(fluid_settings_t * settings,fluid_synth_t * synth)101 new_fluid_dsound_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth)
102 {
103   HRESULT hr;
104   DSBUFFERDESC desc;
105   fluid_dsound_audio_driver_t* dev = NULL;
106   DSCAPS caps;
107   char *buf1;
108   DWORD bytes1;
109   double sample_rate;
110   int periods, period_size;
111   fluid_dsound_devsel_t devsel;
112 
113   /* check if the globals are initialized */
114   if (FLUID_HINSTANCE == NULL) {
115     FLUID_LOG(FLUID_ERR, "FluidSynth hinstance not set, which is needed for DirectSound");
116     return NULL;
117   }
118 
119 /*
120   if (fluid_wnd == NULL) {
121     if (fluid_win32_create_window() != 0) {
122       FLUID_LOG(FLUID_ERR, "Couldn't create window needed for DirectSound");
123       return NULL;
124     }
125   }
126 */
127   /* create and clear the driver data */
128   dev = FLUID_NEW(fluid_dsound_audio_driver_t);
129   if (dev == NULL) {
130     FLUID_LOG(FLUID_ERR, "Out of memory");
131     return NULL;
132   }
133   FLUID_MEMSET(dev, 0, sizeof(fluid_dsound_audio_driver_t));
134 
135   dev->synth = synth;
136   dev->cont = 1;
137 
138   fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate);
139   fluid_settings_getint(settings, "audio.periods", &periods);
140   fluid_settings_getint(settings, "audio.period-size", &period_size);
141 
142   /* check the format */
143   if (!fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) {
144     FLUID_LOG(FLUID_ERR, "Unhandled sample format");
145     goto error_recovery;
146   }
147 
148   dev->frame_size = 2 * sizeof(short);
149   dev->buffer_byte_size = period_size * dev->frame_size;
150   dev->queue_byte_size = periods * dev->buffer_byte_size;
151   dev->write = fluid_synth_write_s16;
152 
153   /* create and initialize the buffer format */
154   dev->format = (WAVEFORMATEX*) FLUID_MALLOC(sizeof(WAVEFORMATEX));
155   if (dev->format == NULL) {
156     FLUID_LOG(FLUID_ERR, "Out of memory");
157     goto error_recovery;
158   }
159   ZeroMemory(dev->format, sizeof(WAVEFORMATEX));
160 
161   dev->format->wFormatTag = WAVE_FORMAT_PCM;
162   dev->format->nChannels = 2;
163   dev->format->wBitsPerSample = 16;
164   dev->format->nSamplesPerSec = (DWORD) sample_rate;
165   dev->format->nBlockAlign = (WORD) dev->frame_size;
166   dev->format->nAvgBytesPerSec = dev->format->nSamplesPerSec * dev->frame_size;
167   dev->format->cbSize = 0;
168 
169   devsel.devGUID = NULL;
170   /* get the selected device name. if none is specified, use NULL for the default device. */
171   if(fluid_settings_dupstr(settings, "audio.dsound.device", &devsel.devname)    /* ++ alloc device name */
172      && devsel.devname && strlen (devsel.devname) > 0) {
173     /* look for the GUID of the selected device */
174     DirectSoundEnumerate((LPDSENUMCALLBACK) fluid_dsound_enum_callback2, (void *)&devsel);
175   }
176 
177   if (devsel.devname) FLUID_FREE (devsel.devname);      /* -- free device name */
178 
179   /* open DirectSound */
180   hr = DirectSoundCreate(devsel.devGUID, &dev->direct_sound, NULL);
181   if (hr != DS_OK) {
182     FLUID_LOG(FLUID_ERR, "Failed to create the DirectSound object");
183     goto error_recovery;
184   }
185 
186   hr = IDirectSound_SetCooperativeLevel(dev->direct_sound, fluid_win32_get_window(), DSSCL_PRIORITY);
187   if (hr != DS_OK) {
188     FLUID_LOG(FLUID_ERR, "Failed to set the cooperative level");
189     goto error_recovery;
190   }
191 
192   caps.dwSize = sizeof(caps);
193   hr = IDirectSound_GetCaps(dev->direct_sound, &caps);
194   if (hr != DS_OK)  {
195     FLUID_LOG(FLUID_ERR, "Failed to query the device capacities");
196     goto error_recovery;
197   }
198 
199   /* create primary buffer */
200 
201   ZeroMemory(&desc, sizeof(DSBUFFERDESC));
202   desc.dwSize = sizeof(DSBUFFERDESC);
203   desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
204 
205   if (caps.dwFreeHwMixingStreamingBuffers > 0) {
206     desc.dwFlags |= DSBCAPS_LOCHARDWARE;
207   }
208 
209   hr = IDirectSound_CreateSoundBuffer(dev->direct_sound, &desc, &dev->prim_buffer, NULL);
210   if (hr != DS_OK) {
211     FLUID_LOG(FLUID_ERR, "Failed to allocate the primary buffer");
212     goto error_recovery;
213   }
214 
215   /* set the primary sound buffer to this format. if it fails, just
216      print a warning. */
217   hr = IDirectSoundBuffer_SetFormat(dev->prim_buffer, dev->format);
218   if (hr != DS_OK) {
219     FLUID_LOG(FLUID_WARN, "Can't set format of primary sound buffer", fluid_win32_error(hr));
220   }
221 
222   /* initialize the buffer description */
223 
224   ZeroMemory(&desc, sizeof(DSBUFFERDESC));
225   desc.dwSize = sizeof(DSBUFFERDESC);
226   desc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
227   desc.lpwfxFormat = dev->format;
228   desc.dwBufferBytes = dev->queue_byte_size;
229   desc.dwReserved = 0;
230 
231   if (caps.dwFreeHwMixingStreamingBuffers > 0) {
232     desc.dwFlags |= DSBCAPS_LOCHARDWARE;
233   }
234 
235   /* create the secondary sound buffer */
236 
237   hr = IDirectSound_CreateSoundBuffer(dev->direct_sound, &desc, &dev->sec_buffer, NULL);
238   if (hr != DS_OK) {
239     FLUID_LOG(FLUID_ERR, "dsound: Can't create sound buffer: %s", fluid_win32_error(hr));
240     goto error_recovery;
241   }
242 
243 
244   /* Lock */
245   hr = IDirectSoundBuffer_Lock(dev->sec_buffer, 0, 0, (void*) &buf1, &bytes1, 0, 0, DSBLOCK_ENTIREBUFFER);
246 
247   if ((hr != DS_OK) || (buf1 == NULL)) {
248     FLUID_LOG(FLUID_PANIC, "Failed to lock the audio buffer. Exiting.");
249     goto error_recovery;
250   }
251 
252   /* fill the buffer with silence */
253   memset(buf1, 0, bytes1);
254 
255   /* Unlock */
256   IDirectSoundBuffer_Unlock(dev->sec_buffer, buf1, bytes1, 0, 0);
257 
258 
259   /* start the audio thread */
260   dev->thread = CreateThread(NULL, 0, &fluid_dsound_audio_run, (LPVOID) dev, 0, &dev->threadID);
261   if (dev->thread == NULL) {
262     goto error_recovery;
263   }
264 
265   return (fluid_audio_driver_t*) dev;
266 
267  error_recovery:
268   delete_fluid_dsound_audio_driver((fluid_audio_driver_t*) dev);
269   return NULL;
270 }
271 
272 
delete_fluid_dsound_audio_driver(fluid_audio_driver_t * d)273 int delete_fluid_dsound_audio_driver(fluid_audio_driver_t* d)
274 {
275   fluid_dsound_audio_driver_t* dev = (fluid_dsound_audio_driver_t*) d;
276 
277   if (dev == NULL) {
278     return FLUID_OK;
279   }
280 
281   /* tell the audio thread to stop its loop */
282   dev->cont = 0;
283 
284   /* wait till the audio thread exits */
285   if (dev->thread != 0) {
286     if (WaitForSingleObject(dev->thread, 2000) != WAIT_OBJECT_0) {
287       /* on error kill the thread mercilessly */
288       FLUID_LOG(FLUID_DBG, "Couldn't join the audio thread. killing it.");
289       TerminateThread(dev->thread, 0);
290     }
291   }
292 
293   /* release all the allocated ressources */
294 
295   if (dev->format != NULL) {
296     FLUID_FREE(dev->format);
297   }
298 
299   if (dev->sec_buffer != NULL) {
300     IDirectSoundBuffer_Stop(dev->sec_buffer);
301     IDirectSoundBuffer_Release(dev->sec_buffer);
302   }
303   if (dev->prim_buffer != NULL) {
304     IDirectSoundBuffer_Release(dev->prim_buffer);
305   }
306   if (dev->direct_sound != NULL) {
307     IDirectSound_Release(dev->direct_sound);
308   }
309 
310   FLUID_FREE(dev);
311 
312 //  fluid_win32_destroy_window();
313 
314   return 0;
315 }
316 
fluid_dsound_audio_run(LPVOID lpParameter)317 DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter)
318 {
319   fluid_dsound_audio_driver_t* dev = (fluid_dsound_audio_driver_t*) lpParameter;
320   short *buf1, *buf2;
321   DWORD bytes1, bytes2;
322   DWORD cur_position, frames, play_position, write_position, bytes;
323   HRESULT res;
324 
325   cur_position = 0;
326 
327   /* boost the priority of the audio thread */
328   SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
329 
330   IDirectSoundBuffer_Play(dev->sec_buffer, 0, 0, DSBPLAY_LOOPING);
331 
332   while (dev->cont) {
333 
334     IDirectSoundBuffer_GetCurrentPosition(dev->sec_buffer, &play_position, &write_position);
335 
336     if (cur_position <= play_position) {
337       bytes = play_position - cur_position;
338     } else if ((play_position < cur_position) && (write_position <= cur_position)) {
339       bytes = dev->queue_byte_size + play_position - cur_position;
340     } else {
341       bytes = 0;
342     }
343 
344     if (bytes >= dev->buffer_byte_size) {
345 
346       /* Lock */
347       res = IDirectSoundBuffer_Lock(dev->sec_buffer, cur_position, bytes, (void*) &buf1, &bytes1, (void*) &buf2, &bytes2, 0);
348 
349       if ((res != DS_OK) || (buf1 == NULL)) {
350 	FLUID_LOG(FLUID_PANIC, "Failed to lock the audio buffer. System lockup might follow. Exiting.");
351 	ExitProcess(0);
352       }
353 
354       /* fill the first part of the buffer */
355       if (bytes1 > 0) {
356 	frames = bytes1 / dev->frame_size;
357 	dev->write(dev->synth, frames, buf1, 0, 2, buf1, 1, 2);
358 	cur_position += frames * dev->frame_size;
359       }
360 
361       /* fill the second part of the buffer */
362       if ((buf2 != NULL) && (bytes2 > 0)) {
363 	frames = bytes2 / dev->frame_size;
364 	dev->write(dev->synth, frames, buf2, 0, 2, buf2, 1, 2);
365 	cur_position += frames * dev->frame_size;
366       }
367 
368       /* Unlock */
369       IDirectSoundBuffer_Unlock(dev->sec_buffer, buf1, bytes1, buf2, bytes2);
370 
371       if (cur_position >= dev->queue_byte_size) {
372 	cur_position -= dev->queue_byte_size;
373       }
374 
375     } else {
376       Sleep(1);
377     }
378   }
379 
380   ExitThread(0);
381   return 0; /* never reached */
382 }
383 
384 
fluid_win32_error(HRESULT hr)385 char* fluid_win32_error(HRESULT hr) {
386   char *s = "Don't know why";
387   switch (hr) {
388   case E_NOINTERFACE: s = "No such interface"; break;
389   case DSERR_GENERIC: s = "Generic error"; break;
390   case DSERR_ALLOCATED: s = "Required resources already allocated"; break;
391   case DSERR_BADFORMAT: s = "The format is not supported"; break;
392   case DSERR_INVALIDPARAM: s = "Invalid parameter"; break;
393   case DSERR_NOAGGREGATION: s = "No aggregation"; break;
394   case DSERR_OUTOFMEMORY: s = "Out of memory"; break;
395   case DSERR_UNINITIALIZED: s = "Uninitialized"; break;
396   case DSERR_UNSUPPORTED: s = "Function not supported"; break;
397   }
398   return s;
399 }
400