/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * Allegro mixer to DirectSound driver. * * By Robin Burrows. * * Based on original src/win/wdsound.c by Stefan Schimanski * and src/unix/oss.c by Joshua Heyer. * * Bugfixes by Javier Gonzalez. * * See readme.txt for copyright information. */ #define DIRECTSOUND_VERSION 0x0300 #include "allegro.h" #include "allegro/internal/aintern.h" #include "allegro/platform/aintwin.h" #ifndef SCAN_DEPEND #ifdef ALLEGRO_MINGW32 #undef MAKEFOURCC #endif #include #include #include #ifdef ALLEGRO_MSVC #include #endif #endif #ifndef ALLEGRO_WINDOWS #error something is wrong with the makefile #endif #define PREFIX_I "al-dsndmix INFO: " #define PREFIX_W "al-dsndmix WARNING: " #define PREFIX_E "al-dsndmix ERROR: " static int digi_dsoundmix_detect(int input); static int digi_dsoundmix_init(int input, int voices); static void digi_dsoundmix_exit(int input); static int digi_dsoundmix_mixer_volume(int volume); static int digi_dsoundmix_buffer_size(void); /* template driver: will be cloned for each device */ static DIGI_DRIVER digi_dsoundmix = { 0, empty_string, empty_string, empty_string, 0, // available voices 0, // voice number offset MIXER_MAX_SFX, // maximum voices we can support MIXER_DEF_SFX, // default number of voices to use /* setup routines */ digi_dsoundmix_detect, digi_dsoundmix_init, digi_dsoundmix_exit, digi_dsoundmix_mixer_volume, /* audiostream locking functions */ NULL, // AL_METHOD(void *, lock_voice, (int voice, int start, int end)); NULL, // AL_METHOD(void, unlock_voice, (int voice)); digi_dsoundmix_buffer_size, /* voice control functions */ _mixer_init_voice, _mixer_release_voice, _mixer_start_voice, _mixer_stop_voice, _mixer_loop_voice, /* position control functions */ _mixer_get_position, _mixer_set_position, /* volume control functions */ _mixer_get_volume, _mixer_set_volume, _mixer_ramp_volume, _mixer_stop_volume_ramp, /* pitch control functions */ _mixer_get_frequency, _mixer_set_frequency, _mixer_sweep_frequency, _mixer_stop_frequency_sweep, /* pan control functions */ _mixer_get_pan, _mixer_set_pan, _mixer_sweep_pan, _mixer_stop_pan_sweep, /* effect control functions */ _mixer_set_echo, _mixer_set_tremolo, _mixer_set_vibrato, /* input functions */ 0, // int rec_cap_bits; 0, // int rec_cap_stereo; digi_directsound_rec_cap_rate, digi_directsound_rec_cap_param, digi_directsound_rec_source, digi_directsound_rec_start, digi_directsound_rec_stop, digi_directsound_rec_read }; #define MAX_DRIVERS 16 static char *driver_names[MAX_DRIVERS]; static LPGUID driver_guids[MAX_DRIVERS]; #define USE_NEW_CODE 0 /* sound driver globals */ static LPDIRECTSOUND directsound = NULL; static LPDIRECTSOUNDBUFFER prim_buf = NULL; static LPDIRECTSOUNDBUFFER alleg_buf = NULL; static long int initial_volume; static int _freq, _bits, _stereo; static int alleg_to_dsound_volume[256]; static unsigned int digidsbufsize; static unsigned char *digidsbufdata; #if !USE_NEW_CODE static unsigned int bufdivs = 16, digidsbufpos; #else static int digidsbufdirty; #endif static int alleg_buf_paused = FALSE; static int alleg_buf_vol; /* ds_err: * Returns a DirectSound error string. */ #ifdef DEBUGMODE static char *ds_err(long err) { static char err_str[64]; switch (err) { case DS_OK: _al_sane_strncpy(err_str, "DS_OK", sizeof(err_str)); break; case DSERR_ALLOCATED: _al_sane_strncpy(err_str, "DSERR_ALLOCATED", sizeof(err_str)); break; case DSERR_BADFORMAT: _al_sane_strncpy(err_str, "DSERR_BADFORMAT", sizeof(err_str)); break; case DSERR_INVALIDPARAM: _al_sane_strncpy(err_str, "DSERR_INVALIDPARAM", sizeof(err_str)); break; case DSERR_NOAGGREGATION: _al_sane_strncpy(err_str, "DSERR_NOAGGREGATION", sizeof(err_str)); break; case DSERR_OUTOFMEMORY: _al_sane_strncpy(err_str, "DSERR_OUTOFMEMORY", sizeof(err_str)); break; case DSERR_UNINITIALIZED: _al_sane_strncpy(err_str, "DSERR_UNINITIALIZED", sizeof(err_str)); break; case DSERR_UNSUPPORTED: _al_sane_strncpy(err_str, "DSERR_UNSUPPORTED", sizeof(err_str)); break; default: _al_sane_strncpy(err_str, "DSERR_UNKNOWN", sizeof(err_str)); break; } return err_str; } #else #define ds_err(hr) "\0" #endif /* _get_dsalmix_driver: * System driver hook for listing the available sound drivers. This * generates the device list at runtime, to match whatever DirectSound * devices are available. */ DIGI_DRIVER *_get_dsalmix_driver(char *name, LPGUID guid, int num) { DIGI_DRIVER *driver; driver = malloc(sizeof(DIGI_DRIVER)); if (!driver) return NULL; memcpy(driver, &digi_dsoundmix, sizeof(DIGI_DRIVER)); driver->id = DIGI_DIRECTAMX(num); driver_names[num] = malloc(strlen(name)+10); if (driver_names[num]) { _al_sane_strncpy(driver_names[num], "Allegmix ", strlen(name)+10); _al_sane_strncpy(driver_names[num]+9, name, strlen(name)+1); driver->ascii_name = driver_names[num]; } driver_guids[num] = guid; return driver; } /* create_dsound_buffer: * Worker function for creating a DirectSound buffer. */ static LPDIRECTSOUNDBUFFER create_dsound_buffer(int len, int freq, int bits, int stereo, int vol) { LPDIRECTSOUNDBUFFER snd_buf; WAVEFORMATEX wf; DSBUFFERDESC dsbdesc; HRESULT hr; /* setup wave format structure */ memset(&wf, 0, sizeof(WAVEFORMATEX)); wf.wFormatTag = WAVE_FORMAT_PCM; wf.nChannels = stereo ? 2 : 1; wf.nSamplesPerSec = freq; wf.wBitsPerSample = bits; wf.nBlockAlign = bits * (stereo ? 2 : 1) / 8; wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; /* setup DSBUFFERDESC structure */ memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); dsbdesc.dwSize = sizeof(DSBUFFERDESC); /* need volume control and global focus */ dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; dsbdesc.dwBufferBytes = len * (bits / 8) * (stereo ? 2 : 1); dsbdesc.lpwfxFormat = &wf; /* create buffer */ hr = IDirectSound_CreateSoundBuffer(directsound, &dsbdesc, &snd_buf, NULL); if (FAILED(hr)) { _TRACE(PREFIX_E "create_directsound_buffer() failed (%s).\n", ds_err(hr)); _TRACE(PREFIX_E " - %d Hz, %s, %d bits\n", freq, stereo ? "stereo" : "mono", bits); return NULL; } /* set volume */ IDirectSoundBuffer_SetVolume(snd_buf, alleg_to_dsound_volume[MID(0, vol, 255)]); return snd_buf; } /* digi_dsoundmix_mixer_callback: * Callback function to update sound in the DS buffer. */ static void digi_dsoundmix_mixer_callback(void) { LPVOID lpvPtr1, lpvPtr2; DWORD dwBytes1, dwBytes2; DWORD playcurs, writecurs; HRESULT hr; int switch_mode; /* handle display switchs */ switch_mode = get_display_switch_mode(); if (alleg_buf_paused) { if (_win_app_foreground || (switch_mode == SWITCH_BACKGROUND) || (switch_mode == SWITCH_BACKAMNESIA)) { /* get current state of the sound buffer */ hr = IDirectSoundBuffer_GetStatus(alleg_buf, &dwBytes1); if ((hr == DS_OK) && (dwBytes1 & DSBSTATUS_BUFFERLOST)) { if(IDirectSoundBuffer_Restore(alleg_buf) != DS_OK) return; IDirectSoundBuffer_SetVolume(alleg_buf, alleg_buf_vol); } alleg_buf_paused = FALSE; IDirectSoundBuffer_Play(alleg_buf, 0, 0, DSBPLAY_LOOPING); } else return; } else { if (!_win_app_foreground && ((switch_mode == SWITCH_PAUSE) || (switch_mode == SWITCH_AMNESIA))) { alleg_buf_paused = TRUE; IDirectSoundBuffer_Stop(alleg_buf); return; } } #if USE_NEW_CODE /* this should work, dammit */ /* write data into the buffer */ if (digidsbufdirty) { _mix_some_samples((uintptr_t)digidsbufdata, 0, TRUE); digidsbufdirty = FALSE; } hr = IDirectSoundBuffer_GetCurrentPosition(alleg_buf, &playcurs, &writecurs); if (FAILED(hr)) return; if (((playcurs-writecurs)%(digidsbufsize*2)) < digidsbufsize) return; /* Consider the buffer used. Even if the buffer was lost, mark the data as old so the mixer doesn't stall */ digidsbufdirty = TRUE; hr = IDirectSoundBuffer_Lock(alleg_buf, 0, digidsbufsize, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, DSBLOCK_FROMWRITECURSOR); /* only try to restore the buffer once. don't wait around forever */ if (hr == DSERR_BUFFERLOST) { if(IDirectSoundBuffer_Restore(alleg_buf) != DS_OK) return; IDirectSoundBuffer_Play(alleg_buf, 0, 0, DSBPLAY_LOOPING); IDirectSoundBuffer_SetVolume(alleg_buf, alleg_buf_vol); hr = IDirectSoundBuffer_Lock(alleg_buf, 0, digidsbufsize, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, DSBLOCK_FROMWRITECURSOR); } if (FAILED(hr)) return; /* if the first buffer has enough room, put it all there */ if(dwBytes1 >= digidsbufsize) { dwBytes1 = digidsbufsize; memcpy(lpvPtr1, digidsbufdata, dwBytes1); dwBytes2 = 0; } /* else, sput the first part into the first buffer, and the rest into the second */ else { memcpy(lpvPtr1, digidsbufdata, dwBytes1); dwBytes2 = digidsbufsize-dwBytes1; memcpy(lpvPtr2, digidsbufdata+dwBytes1, dwBytes2); } IDirectSoundBuffer_Unlock(alleg_buf, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); #else hr = IDirectSoundBuffer_GetCurrentPosition(alleg_buf, &playcurs, &writecurs); if (FAILED(hr)) return; writecurs /= (digidsbufsize/bufdivs); writecurs += 8; while (writecurs > (bufdivs-1)) writecurs -= bufdivs; /* avoid locking the buffer if no data to write */ if (writecurs == digidsbufpos) return; hr = IDirectSoundBuffer_Lock(alleg_buf, 0, 0, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, DSBLOCK_FROMWRITECURSOR | DSBLOCK_ENTIREBUFFER); if (FAILED(hr)) return; /* write data into the buffer */ while (writecurs != digidsbufpos) { if (lpvPtr2) { memcpy((char*)lpvPtr2 + (((dwBytes1+dwBytes2)/bufdivs)*digidsbufpos), digidsbufdata + (((dwBytes1+dwBytes2)/bufdivs)*digidsbufpos), (dwBytes1+dwBytes2)/bufdivs); } else { memcpy((char*)lpvPtr1 + (((dwBytes1)/bufdivs)*digidsbufpos), digidsbufdata + (((dwBytes1)/bufdivs)*digidsbufpos), (dwBytes1)/bufdivs); } if (++digidsbufpos > (bufdivs-1)) digidsbufpos = 0; _mix_some_samples((uintptr_t) (digidsbufdata+(((dwBytes1+dwBytes2)/bufdivs)*digidsbufpos)), 0, TRUE); } IDirectSoundBuffer_Unlock(alleg_buf, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); #endif } /* digi_dsoundmix_detect: */ static int digi_dsoundmix_detect(int input) { HRESULT hr; int id; /* deduce our device number from the driver ID code */ id = ((digi_driver->id >> 8) & 0xFF) - 'A'; if (input) return digi_directsound_capture_detect(driver_guids[id]); if (!directsound) { /* initialize DirectSound interface */ hr = DirectSoundCreate(driver_guids[id], &directsound, NULL); if (FAILED(hr)) { _TRACE(PREFIX_E "DirectSound interface creation failed during detect (%s)\n", ds_err(hr)); return 0; } _TRACE(PREFIX_I "DirectSound interface successfully created\n"); /* release DirectSound */ IDirectSound_Release(directsound); directsound = NULL; } return 1; } /* digi_dsoundmix_init: */ static int digi_dsoundmix_init(int input, int voices) { LPVOID lpvPtr1, lpvPtr2; DWORD dwBytes1, dwBytes2; HRESULT hr; DSCAPS dscaps; DSBUFFERDESC desc; WAVEFORMATEX format; HWND allegro_wnd = win_get_window(); char tmp1[128], tmp2[128]; int v, id; /* deduce our device number from the driver ID code */ id = ((digi_driver->id >> 8) & 0xFF) - 'A'; if (input) return digi_directsound_capture_init(driver_guids[id]); digi_driver->voices = voices; /* initialize DirectSound interface */ hr = DirectSoundCreate(driver_guids[id], &directsound, NULL); if (FAILED(hr)) { _TRACE(PREFIX_E "Can't create DirectSound interface (%s)\n", ds_err(hr)); goto Error; } /* set cooperative level */ hr = IDirectSound_SetCooperativeLevel(directsound, allegro_wnd, DSSCL_PRIORITY); if (FAILED(hr)) _TRACE(PREFIX_W "Can't set DirectSound cooperative level (%s)\n", ds_err(hr)); else _TRACE(PREFIX_I "DirectSound cooperation level set to DSSCL_PRIORITY\n"); /* get hardware capabilities */ dscaps.dwSize = sizeof(DSCAPS); hr = IDirectSound_GetCaps(directsound, &dscaps); if (FAILED(hr)) { _TRACE(PREFIX_E "Can't get DirectSound caps (%s)\n", ds_err(hr)); goto Error; } if ((dscaps.dwFlags & DSCAPS_PRIMARY16BIT) && ((_sound_bits >= 16) || (_sound_bits <= 0))) _bits = 16; else _bits = 8; if ((dscaps.dwFlags & DSCAPS_PRIMARYSTEREO) && _sound_stereo) _stereo = 1; else _stereo = 0; /* Try to set the requested frequency */ if ((dscaps.dwMaxSecondarySampleRate > (DWORD)_sound_freq) && (_sound_freq > 0)) _freq = _sound_freq; /* If no frequency is specified, make sure it's clamped to a reasonable value by default */ else if ((_sound_freq <= 0) && (dscaps.dwMaxSecondarySampleRate > 44100)) _freq = 44100; else _freq = dscaps.dwMaxSecondarySampleRate; _TRACE(PREFIX_I "DirectSound caps: %u bits, %s, %uHz\n", _bits, _stereo ? "stereo" : "mono", _freq); memset(&desc, 0, sizeof(DSBUFFERDESC)); desc.dwSize = sizeof(DSBUFFERDESC); desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GLOBALFOCUS;// | DSBCAPS_CTRLVOLUME | DSBCAPS_STICKYFOCUS; hr = IDirectSound_CreateSoundBuffer(directsound, &desc, &prim_buf, NULL); if (FAILED(hr)) _TRACE(PREFIX_W "Can't create primary buffer (%s)\nGlobal volume control won't be available\n", ds_err(hr)); if (prim_buf) { hr = IDirectSoundBuffer_GetFormat(prim_buf, &format, sizeof(format), NULL); if (FAILED(hr)) { _TRACE(PREFIX_W "Can't get primary buffer format (%s)\n", ds_err(hr)); } else { format.nChannels = (_stereo ? 2 : 1); format.nSamplesPerSec = _freq; format.wBitsPerSample = _bits; format.nBlockAlign = (format.wBitsPerSample * format.nChannels) >> 3; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; hr = IDirectSoundBuffer_SetFormat(prim_buf, &format); if (FAILED(hr)) _TRACE(PREFIX_W "Can't set primary buffer format (%s)\n", ds_err(hr)); hr = IDirectSoundBuffer_GetFormat(prim_buf, &format, sizeof(format), NULL); if (FAILED(hr)) { _TRACE(PREFIX_W "Can't get primary buffer format (%s)\n", ds_err(hr)); } else { _TRACE(PREFIX_I "primary format:\n"); _TRACE(PREFIX_I " %u channels\n %u Hz\n %u AvgBytesPerSec\n %u BlockAlign\n %u bits\n %u size\n", format.nChannels, format.nSamplesPerSec, format.nAvgBytesPerSec, format.nBlockAlign, format.wBitsPerSample, format.cbSize); } } } /* setup volume lookup table */ alleg_to_dsound_volume[0] = DSBVOLUME_MIN; for (v = 1; v < 256; v++) { int dB = DSBVOLUME_MAX + 2000.0*log10(v/255.0); alleg_to_dsound_volume[v] = MAX(DSBVOLUME_MIN, dB); } /* create the sound buffer to mix into */ alleg_buf = create_dsound_buffer(get_config_int(uconvert_ascii("sound", tmp1), uconvert_ascii("dsound_numfrags", tmp2), 6) * 1024, _freq, _bits, _stereo, 255); if(!alleg_buf) { _TRACE(PREFIX_E "Can't create mixing buffer\n"); return -1; } /* fill the sound buffer with zeros */ hr = IDirectSoundBuffer_Lock(alleg_buf, 0, 0, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, DSBLOCK_ENTIREBUFFER); if (FAILED(hr)) { _TRACE(PREFIX_E "Can't lock sound buffer (%s)\n", ds_err(hr)); return -1; } memset(lpvPtr1, 0, dwBytes1); digidsbufsize = dwBytes1; if (lpvPtr2) { _TRACE(PREFIX_E "Second buffer set when locking buffer. Error?\n"); memset(lpvPtr2, 0, dwBytes2); digidsbufsize += dwBytes2; } IDirectSoundBuffer_Unlock(alleg_buf, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); _TRACE(PREFIX_I "Sound buffer length is %u\n", digidsbufsize); /* shouldn't ever happen */ if (digidsbufsize&1023) { _TRACE(PREFIX_E "Sound buffer is not multiple of 1024, failed\n"); return -1; } #if USE_NEW_CODE digidsbufsize /= 2; #endif digidsbufdata = malloc(digidsbufsize); if (!digidsbufdata) { _TRACE(PREFIX_E "Can't create temp buffer\n"); return -1; } #if USE_NEW_CODE if (_mixer_init(digidsbufsize / (_bits /8), _freq, _stereo, ((_bits == 16) ? 1 : 0), &digi_driver->voices) != 0) { _TRACE(PREFIX_E "Can't init software mixer\n"); return -1; } #else bufdivs = digidsbufsize/1024; if (_mixer_init((digidsbufsize / (_bits /8)) / bufdivs, _freq, _stereo, ((_bits == 16) ? 1 : 0), &digi_driver->voices) != 0) { _TRACE(PREFIX_E "Can't init software mixer\n"); return -1; } _mix_some_samples((uintptr_t)digidsbufdata, 0, TRUE); #endif /* get primary buffer volume */ IDirectSoundBuffer_GetVolume(alleg_buf, &initial_volume); alleg_buf_vol = initial_volume; /* mark the buffer as paused, so the mixer callback will start it */ alleg_buf_paused = TRUE; #if USE_NEW_CODE digidsbufdirty = TRUE; #endif install_int(digi_dsoundmix_mixer_callback, 20); /* 50 Hz */ return 0; Error: _TRACE(PREFIX_E "digi_directsound_init() failed\n"); digi_dsoundmix_exit(0); return -1; } /* digi_dsoundmix_exit: */ static void digi_dsoundmix_exit(int input) { if (input) { digi_directsound_capture_exit(); return; } /* stop playing */ remove_int(digi_dsoundmix_mixer_callback); if (digidsbufdata) { free(digidsbufdata); digidsbufdata = NULL; } /* destroy mixer buffer */ if (alleg_buf) { IDirectSoundBuffer_Release(alleg_buf); alleg_buf = NULL; } /* destroy primary buffer */ if (prim_buf) { /* restore primary buffer initial volume */ IDirectSoundBuffer_SetVolume(prim_buf, initial_volume); IDirectSoundBuffer_Release(prim_buf); prim_buf = NULL; } /* shutdown DirectSound */ if (directsound) { IDirectSound_Release(directsound); directsound = NULL; } } /* digi_dsoundmix_mixer_volume: */ static int digi_dsoundmix_mixer_volume(int volume) { if (prim_buf) { alleg_buf_vol = alleg_to_dsound_volume[MID(0, volume, 255)]; IDirectSoundBuffer_SetVolume(alleg_buf, alleg_buf_vol); } return 0; } /* digi_dsoundmix_buffer_size: */ static int digi_dsoundmix_buffer_size(void) { return digidsbufsize / (_bits / 8) / (_stereo ? 2 : 1); }