/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307, USA */ #define INITGUID #include "fluidsynth_priv.h" #include "fluid_synth.h" #include "fluid_sys.h" #include "fluid_adriver.h" #include "fluid_settings.h" #include #include #include fluid_audio_driver_t* new_fluid_dsound_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth); int delete_fluid_dsound_audio_driver(fluid_audio_driver_t* data); DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter); HWND fluid_win32_get_window(void); char* fluid_win32_error(HRESULT hr); #define FLUID_HINSTANCE ((HINSTANCE)fluid_get_hinstance()) typedef struct { fluid_audio_driver_t driver; LPDIRECTSOUND direct_sound; LPDIRECTSOUNDBUFFER prim_buffer; LPDIRECTSOUNDBUFFER sec_buffer; WAVEFORMATEX* format; HANDLE thread; DWORD threadID; fluid_synth_t* synth; fluid_audio_callback_t write; int cont; DWORD buffer_byte_size; DWORD queue_byte_size; DWORD frame_size; } fluid_dsound_audio_driver_t; typedef struct { LPGUID devGUID; char* devname; } fluid_dsound_devsel_t; BOOL CALLBACK fluid_dsound_enum_callback(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context) { fluid_settings_t* settings = (fluid_settings_t*) context; fluid_settings_add_option(settings, "audio.dsound.device", (char *)description); return TRUE; } BOOL CALLBACK fluid_dsound_enum_callback2(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context) { fluid_dsound_devsel_t* devsel = (fluid_dsound_devsel_t*) context; FLUID_LOG(FLUID_DBG, "Testing audio device: %s", description); if (strcasecmp(devsel->devname, description) == 0) { devsel->devGUID = FLUID_NEW(GUID); if(devsel->devGUID) { memcpy(devsel->devGUID, guid, sizeof(GUID)); FLUID_LOG(FLUID_DBG, "Selected audio device GUID: %p", devsel->devGUID); } } return TRUE; } void fluid_dsound_audio_driver_settings(fluid_settings_t* settings) { fluid_settings_register_str(settings, "audio.dsound.device", "default", 0, NULL, NULL); fluid_settings_add_option(settings, "audio.dsound.device", "default"); DirectSoundEnumerate((LPDSENUMCALLBACK) fluid_dsound_enum_callback, settings); } /* * new_fluid_dsound_audio_driver */ fluid_audio_driver_t* new_fluid_dsound_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth) { HRESULT hr; DSBUFFERDESC desc; fluid_dsound_audio_driver_t* dev = NULL; DSCAPS caps; char *buf1; DWORD bytes1; double sample_rate; int periods, period_size; fluid_dsound_devsel_t devsel; /* check if the globals are initialized */ if (FLUID_HINSTANCE == NULL) { FLUID_LOG(FLUID_ERR, "FluidSynth hinstance not set, which is needed for DirectSound"); return NULL; } /* if (fluid_wnd == NULL) { if (fluid_win32_create_window() != 0) { FLUID_LOG(FLUID_ERR, "Couldn't create window needed for DirectSound"); return NULL; } } */ /* create and clear the driver data */ dev = FLUID_NEW(fluid_dsound_audio_driver_t); if (dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_dsound_audio_driver_t)); dev->synth = synth; dev->cont = 1; fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); /* check the format */ if (!fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) { FLUID_LOG(FLUID_ERR, "Unhandled sample format"); goto error_recovery; } dev->frame_size = 2 * sizeof(short); dev->buffer_byte_size = period_size * dev->frame_size; dev->queue_byte_size = periods * dev->buffer_byte_size; dev->write = fluid_synth_write_s16; /* create and initialize the buffer format */ dev->format = (WAVEFORMATEX*) FLUID_MALLOC(sizeof(WAVEFORMATEX)); if (dev->format == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } ZeroMemory(dev->format, sizeof(WAVEFORMATEX)); dev->format->wFormatTag = WAVE_FORMAT_PCM; dev->format->nChannels = 2; dev->format->wBitsPerSample = 16; dev->format->nSamplesPerSec = (DWORD) sample_rate; dev->format->nBlockAlign = (WORD) dev->frame_size; dev->format->nAvgBytesPerSec = dev->format->nSamplesPerSec * dev->frame_size; dev->format->cbSize = 0; devsel.devGUID = NULL; /* get the selected device name. if none is specified, use NULL for the default device. */ if(fluid_settings_getstr(settings, "audio.dsound.device", &devsel.devname)) { /* look for the GUID of the selected device */ DirectSoundEnumerate((LPDSENUMCALLBACK) fluid_dsound_enum_callback2, (void *)&devsel); } /* open DirectSound */ hr = DirectSoundCreate(devsel.devGUID, &dev->direct_sound, NULL); if (hr != DS_OK) { FLUID_LOG(FLUID_ERR, "Failed to create the DirectSound object"); goto error_recovery; } hr = IDirectSound_SetCooperativeLevel(dev->direct_sound, fluid_win32_get_window(), DSSCL_PRIORITY); if (hr != DS_OK) { FLUID_LOG(FLUID_ERR, "Failed to set the cooperative level"); goto error_recovery; } caps.dwSize = sizeof(caps); hr = IDirectSound_GetCaps(dev->direct_sound, &caps); if (hr != DS_OK) { FLUID_LOG(FLUID_ERR, "Failed to query the device capacities"); goto error_recovery; } /* create primary buffer */ ZeroMemory(&desc, sizeof(DSBUFFERDESC)); desc.dwSize = sizeof(DSBUFFERDESC); desc.dwFlags = DSBCAPS_PRIMARYBUFFER; if (caps.dwFreeHwMixingStreamingBuffers > 0) { desc.dwFlags |= DSBCAPS_LOCHARDWARE; } hr = IDirectSound_CreateSoundBuffer(dev->direct_sound, &desc, &dev->prim_buffer, NULL); if (hr != DS_OK) { FLUID_LOG(FLUID_ERR, "Failed to allocate the primary buffer"); goto error_recovery; } /* set the primary sound buffer to this format. if it fails, just print a warning. */ hr = IDirectSoundBuffer_SetFormat(dev->prim_buffer, dev->format); if (hr != DS_OK) { FLUID_LOG(FLUID_WARN, "Can't set format of primary sound buffer", fluid_win32_error(hr)); } /* initialize the buffer description */ ZeroMemory(&desc, sizeof(DSBUFFERDESC)); desc.dwSize = sizeof(DSBUFFERDESC); desc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; desc.lpwfxFormat = dev->format; desc.dwBufferBytes = dev->queue_byte_size; desc.dwReserved = 0; if (caps.dwFreeHwMixingStreamingBuffers > 0) { desc.dwFlags |= DSBCAPS_LOCHARDWARE; } /* create the secondary sound buffer */ hr = IDirectSound_CreateSoundBuffer(dev->direct_sound, &desc, &dev->sec_buffer, NULL); if (hr != DS_OK) { FLUID_LOG(FLUID_ERR, "dsound: Can't create sound buffer: %s", fluid_win32_error(hr)); goto error_recovery; } /* Lock */ hr = IDirectSoundBuffer_Lock(dev->sec_buffer, 0, 0, (void*) &buf1, &bytes1, 0, 0, DSBLOCK_ENTIREBUFFER); if ((hr != DS_OK) || (buf1 == NULL)) { FLUID_LOG(FLUID_PANIC, "Failed to lock the audio buffer. Exiting."); goto error_recovery; } /* fill the buffer with silence */ memset(buf1, 0, bytes1); /* Unlock */ IDirectSoundBuffer_Unlock(dev->sec_buffer, buf1, bytes1, 0, 0); /* start the audio thread */ dev->thread = CreateThread(NULL, 0, &fluid_dsound_audio_run, (LPVOID) dev, 0, &dev->threadID); if (dev->thread == NULL) { goto error_recovery; } return (fluid_audio_driver_t*) dev; error_recovery: delete_fluid_dsound_audio_driver((fluid_audio_driver_t*) dev); return NULL; } int delete_fluid_dsound_audio_driver(fluid_audio_driver_t* d) { fluid_dsound_audio_driver_t* dev = (fluid_dsound_audio_driver_t*) d; if (dev == NULL) { return FLUID_OK; } /* tell the audio thread to stop its loop */ dev->cont = 0; /* wait till the audio thread exits */ if (dev->thread != 0) { if (WaitForSingleObject(dev->thread, 2000) != WAIT_OBJECT_0) { /* on error kill the thread mercilessly */ FLUID_LOG(FLUID_DBG, "Couldn't join the audio thread. killing it."); TerminateThread(dev->thread, 0); } } /* release all the allocated ressources */ if (dev->format != NULL) { FLUID_FREE(dev->format); } if (dev->sec_buffer != NULL) { IDirectSoundBuffer_Stop(dev->sec_buffer); IDirectSoundBuffer_Release(dev->sec_buffer); } if (dev->prim_buffer != NULL) { IDirectSoundBuffer_Release(dev->prim_buffer); } if (dev->direct_sound != NULL) { IDirectSound_Release(dev->direct_sound); } FLUID_FREE(dev); // fluid_win32_destroy_window(); return 0; } DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter) { fluid_dsound_audio_driver_t* dev = (fluid_dsound_audio_driver_t*) lpParameter; short *buf1, *buf2; DWORD bytes1, bytes2; DWORD offset = 0; DWORD cur_position, frames, play_position, write_position, bytes; HRESULT res; cur_position = 0; /* boost the priority of the audio thread */ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); IDirectSoundBuffer_Play(dev->sec_buffer, 0, 0, DSBPLAY_LOOPING); while (dev->cont) { IDirectSoundBuffer_GetCurrentPosition(dev->sec_buffer, &play_position, &write_position); if (cur_position <= play_position) { bytes = play_position - cur_position; } else if ((play_position < cur_position) && (write_position <= cur_position)) { bytes = dev->queue_byte_size + play_position - cur_position; } else { bytes = 0; } if (bytes >= dev->buffer_byte_size) { /* Lock */ res = IDirectSoundBuffer_Lock(dev->sec_buffer, cur_position, bytes, (void*) &buf1, &bytes1, (void*) &buf2, &bytes2, 0); if ((res != DS_OK) || (buf1 == NULL)) { FLUID_LOG(FLUID_PANIC, "Failed to lock the audio buffer. System lockup might follow. Exiting."); ExitProcess(0); } /* fill the first part of the buffer */ if (bytes1 > 0) { frames = bytes1 / dev->frame_size; dev->write(dev->synth, frames, buf1, 0, 2, buf1, 1, 2); cur_position += frames * dev->frame_size; } /* fill the second part of the buffer */ if ((buf2 != NULL) && (bytes2 > 0)) { frames = bytes2 / dev->frame_size; dev->write(dev->synth, frames, buf2, 0, 2, buf2, 1, 2); cur_position += frames * dev->frame_size; } /* Unlock */ IDirectSoundBuffer_Unlock(dev->sec_buffer, buf1, bytes1, buf2, bytes2); if (cur_position >= dev->queue_byte_size) { cur_position -= dev->queue_byte_size; } } else { Sleep(1); } } ExitThread(0); return 0; /* never reached */ } char* fluid_win32_error(HRESULT hr) { char *s = "Don't know why"; switch (hr) { case E_NOINTERFACE: s = "No such interface"; break; case DSERR_GENERIC: s = "Generic error"; break; case DSERR_ALLOCATED: s = "Required resources already allocated"; break; case DSERR_BADFORMAT: s = "The format is not supported"; break; case DSERR_INVALIDPARAM: s = "Invalid parameter"; break; case DSERR_NOAGGREGATION: s = "No aggregation"; break; case DSERR_OUTOFMEMORY: s = "Out of memory"; break; case DSERR_UNINITIALIZED: s = "Uninitialized"; break; case DSERR_UNSUPPORTED: s = "Function not supported"; break; } return s; }