diff --git a/Quake/snd_alsa.c b/Quake/snd_alsa.c new file mode 100644 index 00000000..465fe017 --- /dev/null +++ b/Quake/snd_alsa.c @@ -0,0 +1,332 @@ +/* + snd_alsa.c + + ALSA 1.0 sound driver for Linux Hexen II + + Copyright (C) 1999,2004 contributors of the QuakeForge project + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA +*/ + + +#include "quakedef.h" +#include + +#define NB_PERIODS 4 + +//static const char alsa_default[] = "hw:0,0"; +//static const char alsa_default[] = "plughw:0"; +static const char alsa_default[] = "default"; +static const char *pcmname = alsa_default; +static snd_pcm_t *pcm = NULL; +static snd_pcm_uframes_t buffer_size; + +static const int tryrates[] = { 11025, 22050, 44100, 48000, 16000, 24000, 8000 }; +static const int MAX_TRYRATES = sizeof(tryrates)/sizeof(tryrates[0]); + + +#if defined(__GNUC__) && \ + !(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +#define ALSA_CHECK_ERR(check, fmt, args...) \ + do { \ + if (check < 0) { \ + Con_Printf ("ALSA: " fmt, ##args); \ + goto error; \ + } \ + } while (0) +#else +#define ALSA_CHECK_ERR(check, ...) \ + do { \ + if (check < 0) { \ + Con_Printf ("ALSA: " __VA_ARGS__); \ + goto error; \ + } \ + } while (0) +#endif + +qboolean SNDDMA_Init (dma_t *dma) +{ + int i, err; + unsigned int rate; + int tmp_bits, tmp_chan; + snd_pcm_hw_params_t *hw = NULL; + snd_pcm_sw_params_t *sw = NULL; + snd_pcm_uframes_t frag_size; + + i = COM_CheckParm("-alsadev"); + if (i != 0 && i < com_argc - 1) + pcmname = com_argv[i + 1]; + + err = snd_pcm_open (&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (err < 0) + { + Con_Printf ("ALSA: error opening device \"%s\": %s\n", pcmname, snd_strerror(err)); + return false; + } + Con_Printf ("ALSA: Using device: %s\n", pcmname); + + err = snd_pcm_hw_params_malloc (&hw); + ALSA_CHECK_ERR(err, "unable to allocate hardware params. %s\n", snd_strerror(err)); + + err = snd_pcm_hw_params_any (pcm, hw); + ALSA_CHECK_ERR(err, "unable to init hardware params. %s\n", snd_strerror(err)); + + err = snd_pcm_hw_params_set_access (pcm, hw, SND_PCM_ACCESS_MMAP_INTERLEAVED); + ALSA_CHECK_ERR(err, "unable to set interleaved access. %s\n", snd_strerror(err)); + + i = (loadas8bit.value) ? 8 : 16; + tmp_bits = (i == 8) ? SND_PCM_FORMAT_U8 : SND_PCM_FORMAT_S16; + err = snd_pcm_hw_params_set_format (pcm, hw, (snd_pcm_format_t) tmp_bits); + if (err < 0) + { + Con_Printf ("Problems setting %d bit format, trying alternatives..\n", i); + tmp_bits = (i == 8) ? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U8; + err = snd_pcm_hw_params_set_format (pcm, hw, (snd_pcm_format_t) tmp_bits); + ALSA_CHECK_ERR(err, "Neither 8 nor 16 bit format supported. %s\n", snd_strerror(err)); + } + tmp_bits = (tmp_bits == SND_PCM_FORMAT_U8) ? 8 : 16; + + i = tmp_chan = (COM_CheckParm("-sndmono") == 0) ? 2 : 1; + err = snd_pcm_hw_params_set_channels (pcm, hw, tmp_chan); + if (err < 0) + { + Con_Printf ("Problems setting channels to %s, retrying for %s\n", + (i == 2) ? "stereo" : "mono", + (i == 2) ? "mono" : "stereo"); + tmp_chan = (i == 2) ? 1 : 2; + err = snd_pcm_hw_params_set_channels (pcm, hw, tmp_chan); + ALSA_CHECK_ERR(err, "unable to set desired channels. %s\n", snd_strerror(err)); + } + + rate = (int)sndspeed.value; + err = snd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0); + if (err < 0) + { + Con_Printf("Problems setting sample rate, trying alternatives..\n"); + for (i = 0; i < MAX_TRYRATES; i++) + { + rate = tryrates[i]; + err = snd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0); + if (err < 0) + { + Con_DPrintf ("Unable to set sample rate %d\n", tryrates[i]); + rate = 0; + } + else + { + if (rate != tryrates[i]) + { + Con_Printf ("Warning: Rate set (%u) didn't match requested rate (%d)!\n", rate, tryrates[i]); + // goto error; + } + break; + } + } + if (rate == 0) + { + Con_Printf ("Unable to set any sample rates.\n"); + goto error; + } + } + else + { + if (rate != (int)sndspeed.value) + { + Con_Printf ("Warning: Rate set (%u) didn't match requested rate (%d)!\n", rate, (int)sndspeed.value); + // goto error; + } + } + + /* pick a buffer size that is a power of 2 (by masking off low bits) */ + buffer_size = i = (int)(rate * 0.15f); + while (buffer_size & (buffer_size-1)) + buffer_size &= (buffer_size-1); + /* then check if it is the nearest power of 2 and bump it up if not */ + if (i - buffer_size >= buffer_size >> 1) + buffer_size *= 2; + + err = snd_pcm_hw_params_set_buffer_size_near (pcm, hw, &buffer_size); + ALSA_CHECK_ERR(err, "unable to set buffer size near %lu (%s)\n", + (unsigned long)buffer_size, snd_strerror(err)); + + err = snd_pcm_hw_params_get_buffer_size (hw, &buffer_size); + ALSA_CHECK_ERR(err, "unable to get buffer size. %s\n", snd_strerror(err)); + if (buffer_size & (buffer_size-1)) + { + Con_Printf ("ALSA: WARNING: non-power of 2 buffer size. sound may be\n"); + Con_Printf ("unsatisfactory. Recommend using either the plughw or hw\n"); + Con_Printf ("devices or adjusting dmix to have a power of 2 buf size\n"); + } + + /* pick a period size near the buffer_size we got from ALSA */ + frag_size = buffer_size / NB_PERIODS; + err = snd_pcm_hw_params_set_period_size_near (pcm, hw, &frag_size, 0); + ALSA_CHECK_ERR(err, "unable to set period size near %i. %s\n", + (int)frag_size, snd_strerror(err)); + + err = snd_pcm_hw_params (pcm, hw); + ALSA_CHECK_ERR(err, "unable to install hardware params. %s\n", snd_strerror(err)); + + err = snd_pcm_sw_params_malloc (&sw); + ALSA_CHECK_ERR(err, "unable to allocate software params. %s\n", snd_strerror(err)); + + err = snd_pcm_sw_params_current (pcm, sw); + ALSA_CHECK_ERR(err, "unable to determine current software params. %s\n", snd_strerror(err)); + + err = snd_pcm_sw_params_set_start_threshold (pcm, sw, ~0U); + ALSA_CHECK_ERR(err, "unable to set playback threshold. %s\n", snd_strerror(err)); + + err = snd_pcm_sw_params_set_stop_threshold (pcm, sw, ~0U); + ALSA_CHECK_ERR(err, "unable to set playback stop threshold. %s\n", snd_strerror(err)); + + err = snd_pcm_sw_params (pcm, sw); + ALSA_CHECK_ERR(err, "unable to install software params. %s\n", snd_strerror(err)); + + memset ((void *) dma, 0, sizeof(dma_t)); + shm = dma; + + shm->channels = tmp_chan; + + /* + // don't mix less than this in mono samples: + err = snd_pcm_hw_params_get_period_size (hw, + (snd_pcm_uframes_t *) (char *) (&shm->submission_chunk), 0); + ALSA_CHECK_ERR(err, "unable to get period size. %s\n", snd_strerror(err)); + */ + shm->submission_chunk = 1; + shm->samplepos = 0; + shm->samplebits = tmp_bits; + + Con_Printf ("ALSA: %lu bytes buffer with mmap interleaved access\n", (unsigned long)buffer_size); + + shm->samples = buffer_size * shm->channels; // mono samples in buffer + shm->speed = rate; + + SNDDMA_GetDMAPos (); // sets shm->buffer + + snd_pcm_hw_params_free(hw); + snd_pcm_sw_params_free(sw); + + return true; + +error: +// full clean-up + if (hw) + snd_pcm_hw_params_free(hw); + if (sw) + snd_pcm_sw_params_free(sw); + shm = NULL; + snd_pcm_close (pcm); + pcm = NULL; + return false; +} + +int SNDDMA_GetDMAPos (void) +{ + snd_pcm_uframes_t offset; + snd_pcm_uframes_t nframes; + const snd_pcm_channel_area_t *areas; + + if (!shm) + return 0; + + nframes = shm->samples/shm->channels; + snd_pcm_avail_update (pcm); + snd_pcm_mmap_begin (pcm, &areas, &offset, &nframes); + // The following commit was absent in QF, causing the + // very first sound to be corrupted + snd_pcm_mmap_commit (pcm, offset, nframes); + offset *= shm->channels; + nframes *= shm->channels; + shm->samplepos = offset; + shm->buffer = (unsigned char *) areas->addr; // FIXME! there's an area per channel + return shm->samplepos; +} + +void SNDDMA_Shutdown (void) +{ + if (shm) + { + // full clean-up + Con_Printf ("Shutting down ALSA sound\n"); + snd_pcm_drop (pcm); // do I need this? + snd_pcm_close (pcm); + pcm = NULL; + shm->buffer = NULL; + shm = NULL; + } +} + +/* +============== +SNDDMA_LockBuffer + +Makes sure dma buffer is valid +============== +*/ +void SNDDMA_LockBuffer (void) +{ + /* nothing to do here */ +} + +/* +============== +SNDDMA_Submit + +Unlock the dma buffer / +Send sound to the device +============== +*/ +void SNDDMA_Submit (void) +{ + snd_pcm_uframes_t offset; + snd_pcm_uframes_t nframes; + const snd_pcm_channel_area_t *areas; + int state; + int count = paintedtime - soundtime; + + nframes = count / shm->channels; + snd_pcm_avail_update (pcm); + snd_pcm_mmap_begin (pcm, &areas, &offset, &nframes); + state = snd_pcm_state (pcm); + + switch (state) + { + case SND_PCM_STATE_PREPARED: + snd_pcm_mmap_commit (pcm, offset, nframes); + snd_pcm_start (pcm); + break; + case SND_PCM_STATE_RUNNING: + snd_pcm_mmap_commit (pcm, offset, nframes); + break; + default: + break; + } +} + +void SNDDMA_BlockSound (void) +{ + snd_pcm_pause (pcm, 1); +} + +void SNDDMA_UnblockSound (void) +{ + snd_pcm_pause (pcm, 0); +} + diff --git a/Quake/snd_oss.c b/Quake/snd_oss.c new file mode 100644 index 00000000..68c39922 --- /dev/null +++ b/Quake/snd_oss.c @@ -0,0 +1,314 @@ +/* + snd_oss.c + + Copyright (C) 1996-1997 Id Software, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#include "quakedef.h" + +#include +#include +#include +#include +#include +#include +// FIXME: is "by the book" but we should take care of +// , and someday. +#include +#include + +static int FORMAT_S16; + +static int audio_fd = -1; +static const char oss_default[] = "/dev/dsp"; +static const char *ossdev = oss_default; +static unsigned long mmaplen; + +static const int tryrates[] = { 11025, 22050, 44100, 48000, 16000, 24000, 8000 }; +static const int MAX_TRYRATES = sizeof(tryrates)/sizeof(tryrates[0]); + + +qboolean SNDDMA_Init (dma_t *dma) +{ + int i, caps, tmp; + unsigned long sz; + struct audio_buf_info info; + + if (bigendien) FORMAT_S16 = AFMT_S16_BE; + else FORMAT_S16 = AFMT_S16_LE; + + tmp = COM_CheckParm("-ossdev"); + if (tmp != 0 && tmp < com_argc - 1) + ossdev = com_argv[tmp + 1]; + Con_Printf ("OSS: Using device: %s\n", ossdev); + +// open /dev/dsp, confirm capability to mmap, and get size of dma buffer + audio_fd = open(ossdev, O_RDWR|O_NONBLOCK); + if (audio_fd == -1) + { // Failed open, retry up to 3 times if it's busy + tmp = 3; + while ( (audio_fd == -1) && tmp-- && + ((errno == EAGAIN) || (errno == EBUSY)) ) + { + sleep (1); + audio_fd = open(ossdev, O_RDWR|O_NONBLOCK); + } + if (audio_fd == -1) + { + Con_Printf("Could not open %s. %s\n", ossdev, strerror(errno)); + return false; + } + } + + memset ((void *) dma, 0, sizeof(dma_t)); + shm = dma; + + if (ioctl(audio_fd, SNDCTL_DSP_RESET, 0) == -1) + { + Con_Printf("Could not reset %s. %s\n", ossdev, strerror(errno)); + goto error; + } + + if (ioctl(audio_fd, SNDCTL_DSP_GETCAPS, &caps) == -1) + { + Con_Printf("Couldn't retrieve soundcard capabilities. %s\n", strerror(errno)); + goto error; + } + + if (!(caps & DSP_CAP_TRIGGER) || !(caps & DSP_CAP_MMAP)) + { + Con_Printf("Audio driver doesn't support mmap or trigger\n"); + goto error; + } + +// set sample bits & speed + i = (loadas8bit.value) ? 8 : 16; + tmp = (i == 16) ? FORMAT_S16 : AFMT_U8; + if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &tmp) == -1) + { + Con_Printf("Problems setting %d bit format, trying alternatives..\n", i); + // try what the device gives us + if (ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &tmp) == -1) + { + Con_Printf("Unable to retrieve supported formats. %s\n", strerror(errno)); + goto error; + } + if (tmp & FORMAT_S16) + { + i = 16; + tmp = FORMAT_S16; + } + else if (tmp & AFMT_U8) + { + i = 8; + tmp = AFMT_U8; + } + else + { + Con_Printf("Neither 8 nor 16 bit format supported.\n"); + goto error; + } + if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &tmp) == -1) + { + Con_Printf("Unable to set sound format. %s\n", strerror(errno)); + goto error; + } + } + shm->samplebits = i; + + tmp = (int)sndspeed.value; + if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &tmp) == -1) + { + Con_Printf("Problems setting sample rate, trying alternatives..\n"); + shm->speed = 0; + for (i = 0; i < MAX_TRYRATES; i++) + { + tmp = tryrates[i]; + if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &tmp) == -1) + { + Con_DPrintf ("Unable to set sample rate %d\n", tryrates[i]); + } + else + { + if (tmp != tryrates[i]) + { + Con_Printf ("Warning: Rate set (%d) didn't match requested rate (%d)!\n", tmp, tryrates[i]); + // goto error; + } + shm->speed = tmp; + break; + } + } + if (shm->speed == 0) + { + Con_Printf("Unable to set any sample rates.\n"); + goto error; + } + } + else + { + if (tmp != (int)sndspeed.value) + { + Con_Printf ("Warning: Rate set (%d) didn't match requested rate (%d)!\n", tmp, (int)sndspeed.value); + // goto error; + } + shm->speed = tmp; + } + + i = (COM_CheckParm("-sndmono") == 0) ? 2 : 1; + tmp = (i == 2) ? 1 : 0; + if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &tmp) == -1) + { + Con_Printf ("Problems setting channels to %s, retrying for %s\n", + (i == 2) ? "stereo" : "mono", + (i == 2) ? "mono" : "stereo"); + tmp = (i == 2) ? 0 : 1; + if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &tmp) == -1) + { + Con_Printf("unable to set desired channels. %s\n", strerror(errno)); + goto error; + } + } + shm->channels = tmp +1; + + if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) + { + Con_Printf("Couldn't retrieve buffer status. %s\n", strerror(errno)); + goto error; + } + + shm->samples = info.fragstotal * info.fragsize / (shm->samplebits / 8); + shm->submission_chunk = 1; + +// memory map the dma buffer + sz = sysconf (_SC_PAGESIZE); + mmaplen = info.fragstotal * info.fragsize; + mmaplen = (mmaplen + sz - 1) & ~(sz - 1); + shm->buffer = (unsigned char *) mmap(NULL, mmaplen, PROT_READ|PROT_WRITE, + MAP_FILE|MAP_SHARED, audio_fd, 0); + if (!shm->buffer || shm->buffer == MAP_FAILED) + { + Con_Printf("Could not mmap %s. %s\n", ossdev, strerror(errno)); + goto error; + } + Con_Printf ("OSS: mmaped %lu bytes buffer\n", mmaplen); + +// toggle the trigger & start her up + tmp = 0; + if (ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp) == -1) + { + Con_Printf("Could not toggle %s. %s\n", ossdev, strerror(errno)); + munmap (shm->buffer, mmaplen); + goto error; + } + tmp = PCM_ENABLE_OUTPUT; + if (ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp) == -1) + { + Con_Printf("Could not toggle %s. %s\n", ossdev, strerror(errno)); + munmap (shm->buffer, mmaplen); + goto error; + } + + shm->samplepos = 0; + + return true; + +error: + close(audio_fd); + audio_fd = -1; + shm->buffer = NULL; + shm = NULL; + return false; +} + +int SNDDMA_GetDMAPos (void) +{ + struct count_info count; + + if (!shm) + return 0; + + if (ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &count) == -1) + { + Con_Printf("Uh, sound dead. %s\n", strerror(errno)); + munmap (shm->buffer, mmaplen); + shm->buffer = NULL; + shm = NULL; + close(audio_fd); + audio_fd = -1; + return 0; + } +// shm->samplepos = (count.bytes / (shm->samplebits / 8)) & (shm->samples-1); +// fprintf(stderr, "%d \r", count.ptr); + shm->samplepos = count.ptr / (shm->samplebits / 8); + + return shm->samplepos; +} + +void SNDDMA_Shutdown (void) +{ + int tmp = 0; + if (shm) + { + Con_Printf ("Shutting down OSS sound\n"); + munmap (shm->buffer, mmaplen); + shm->buffer = NULL; + shm = NULL; + ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp); + ioctl(audio_fd, SNDCTL_DSP_RESET, 0); + close(audio_fd); + audio_fd = -1; + } +} + +/* +============== +SNDDMA_LockBuffer + +Makes sure dma buffer is valid +============== +*/ +void SNDDMA_LockBuffer (void) +{ + /* nothing to do here */ +} + +/* +============== +SNDDMA_Submit + +Unlock the dma buffer / +Send sound to the device +=============== +*/ +void SNDDMA_Submit(void) +{ +} + +void SNDDMA_BlockSound (void) +{ +} + +void SNDDMA_UnblockSound (void) +{ +} + diff --git a/Quake/snd_win.c b/Quake/snd_win.c new file mode 100644 index 00000000..b94c2f1c --- /dev/null +++ b/Quake/snd_win.c @@ -0,0 +1,773 @@ +/* + snd_win.c + $Id: snd_win.c,v 1.37 2008-12-28 14:34:34 sezero Exp $ + + Copyright (C) 1996-1997 Id Software, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA +*/ + +#define DX_DLSYM /* dynamic loading of dsound symbols */ + +#include "quakedef.h" +#include "winquake.h" +#include +#include + +//#define SNDBUFSIZE 65536 +// 64K is > 1 second at 16-bit, 22050 Hz +//#define WAV_BUFFERS 64 +#define WAV_BUFFERS 128 +#define WAV_MASK (WAV_BUFFERS - 1) + +/* DirectSound : */ +#ifndef DSBSIZE_MIN +#define DSBSIZE_MIN 4 +#endif +#ifndef DSBSIZE_MAX +#define DSBSIZE_MAX 0x0FFFFFFF +#endif + +static LPDIRECTSOUND pDS; +static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; + +#if defined(DX_DLSYM) /* dynamic loading of dsound symbols */ +static HINSTANCE hInstDS; +static HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter); +#else /* ! DX_DLSYM : we're linked to dsound */ +#define pDirectSoundCreate DirectSoundCreate +#endif /* DX_DLSYM */ + +typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat; + +static qboolean wavonly; +static qboolean dsound_init; +static qboolean wav_init; +static qboolean snd_firsttime = true, snd_isdirect, snd_iswave; +static qboolean primary_format_set; + +static int sample16; +static int snd_sent, snd_completed; +static int ds_sbuf_size, wv_buf_size; + +static HANDLE hData; +static HGLOBAL hWaveHdr; + +static HPSTR lpData; +static LPWAVEHDR lpWaveHdr; +static HWAVEOUT hWaveOut; +//WAVEOUTCAPS wavecaps; + +static DWORD gSndBufSize; +static MMTIME mmstarttime; + + +/* +================== +FreeSound +================== +*/ +static void FreeSound (void) +{ + int i; + + if (pDSBuf) + { + IDirectSoundBuffer_Stop(pDSBuf); + IDirectSound_Release(pDSBuf); + } + +// only release primary buffer if it's not also the mixing buffer we just released + if (pDSPBuf && (pDSBuf != pDSPBuf)) + { + IDirectSound_Release(pDSPBuf); + } + + if (pDS) + { + IDirectSound_SetCooperativeLevel(pDS, mainwindow, DSSCL_NORMAL); + IDirectSound_Release(pDS); + } + + if (hWaveOut) + { + waveOutReset (hWaveOut); + + if (lpWaveHdr) + { + for (i = 0; i < WAV_BUFFERS; i++) + waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)); + } + + waveOutClose (hWaveOut); + + if (hWaveHdr) + { + GlobalUnlock(hWaveHdr); + GlobalFree(hWaveHdr); + } + if (hData) + { + GlobalUnlock(hData); + GlobalFree(hData); + } + } + + pDS = NULL; + pDSBuf = NULL; + pDSPBuf = NULL; + hWaveOut = 0; + hData = 0; + hWaveHdr = 0; + lpData = NULL; + lpWaveHdr = NULL; + dsound_init = false; + wav_init = false; +} + + +/* +================== +SNDDMA_InitDirect + +Direct-Sound support +================== +*/ +static sndinitstat SNDDMA_InitDirect (dma_t *dma) +{ + DSBUFFERDESC dsbuf; + DSBCAPS dsbcaps; + DWORD dwSize, dwWrite; + DSCAPS dscaps; + WAVEFORMATEX format, pformat; + HRESULT hresult; + int reps; + + memset((void *) dma, 0, sizeof(dma_t)); + shm = dma; + + shm->channels = 2; /* = desired_channels; */ + shm->samplebits = (loadas8bit.value) ? 8 : 16; + shm->speed = sndspeed.value; + + memset (&format, 0, sizeof(format)); + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = shm->channels; + format.wBitsPerSample = shm->samplebits; + format.nSamplesPerSec = shm->speed; + format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; + format.cbSize = 0; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + +#if defined(DX_DLSYM) + if (!hInstDS) + { + hInstDS = LoadLibrary("dsound.dll"); + + if (hInstDS == NULL) + { + Con_SafePrintf ("Couldn't load dsound.dll\n"); + return SIS_FAILURE; + } + + pDirectSoundCreate = (HRESULT (WINAPI *)(GUID FAR *, LPDIRECTSOUND FAR *, IUnknown FAR *)) + GetProcAddress(hInstDS,"DirectSoundCreate"); + + if (!pDirectSoundCreate) + { + Con_SafePrintf ("Couldn't get DS proc addr\n"); + return SIS_FAILURE; + } + } +#endif /* DX_DLSYM */ + + hresult = pDirectSoundCreate(NULL, &pDS, NULL); + if (hresult != DS_OK) + { + if (hresult != DSERR_ALLOCATED) + { + Con_SafePrintf ("DirectSound create failed\n"); + return SIS_FAILURE; + } + + Con_SafePrintf ("DirectSoundCreate failure, hardware already in use\n"); + return SIS_NOTAVAIL; + } + + dscaps.dwSize = sizeof(dscaps); + + if (DS_OK != IDirectSound_GetCaps(pDS, &dscaps)) + { + Con_SafePrintf ("Couldn't get DS caps\n"); + } + + if (dscaps.dwFlags & DSCAPS_EMULDRIVER) + { + Con_SafePrintf ("No DirectSound driver installed\n"); + FreeSound (); + return SIS_FAILURE; + } + + // if (DS_OK != IDirectSound_SetCooperativeLevel(pDS, mainwindow, DSSCL_EXCLUSIVE)) + /* Pa3PyX: Some MIDI synthesizers are software and require access to + waveOut; so if we set the coop level to exclusive, MIDI will fail + to init because the device is locked. We use priority level instead. + That way we don't lock out software synths and other apps, but can + still set the sound buffer format. */ + if (DS_OK != IDirectSound_SetCooperativeLevel(pDS, mainwindow, DSSCL_PRIORITY)) + { + Con_SafePrintf ("Set coop level failed\n"); + FreeSound (); + return SIS_FAILURE; + } + +// get access to the primary buffer, if possible, so we can set the +// sound hardware format + memset (&dsbuf, 0, sizeof(dsbuf)); + dsbuf.dwSize = sizeof(DSBUFFERDESC); + dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbuf.dwBufferBytes = 0; + dsbuf.lpwfxFormat = NULL; + + memset(&dsbcaps, 0, sizeof(dsbcaps)); + dsbcaps.dwSize = sizeof(dsbcaps); + primary_format_set = false; + + if (!COM_CheckParm ("-snoforceformat")) + { + if (DS_OK == IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL)) + { + pformat = format; + + if (DS_OK != IDirectSoundBuffer_SetFormat(pDSPBuf, &pformat)) + { + if (snd_firsttime) + Con_SafePrintf ("Set primary sound buffer format: no\n"); + } + else + { + if (snd_firsttime) + Con_SafePrintf ("Set primary sound buffer format: yes\n"); + + primary_format_set = true; + } + } + } + + if (!primary_format_set || !COM_CheckParm ("-primarysound")) + { + // create the secondary buffer we'll actually work with + memset (&dsbuf, 0, sizeof(dsbuf)); + dsbuf.dwSize = sizeof(DSBUFFERDESC); + dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE; + if (ds_sbuf_size < DSBSIZE_MIN) + ds_sbuf_size = 1 << (Q_log2(DSBSIZE_MIN) + 1); + if (ds_sbuf_size > DSBSIZE_MAX) + ds_sbuf_size = 1 << Q_log2(DSBSIZE_MAX); + dsbuf.dwBufferBytes = ds_sbuf_size; + dsbuf.lpwfxFormat = &format; + + memset(&dsbcaps, 0, sizeof(dsbcaps)); + dsbcaps.dwSize = sizeof(dsbcaps); + + if (DS_OK != IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL)) + { + Con_SafePrintf ("DS:CreateSoundBuffer Failed"); + FreeSound (); + return SIS_FAILURE; + } + + shm->channels = format.nChannels; + shm->samplebits = format.wBitsPerSample; + shm->speed = format.nSamplesPerSec; + + if (DS_OK != IDirectSound_GetCaps(pDSBuf, &dsbcaps)) + { + Con_SafePrintf ("DS:GetCaps failed\n"); + FreeSound (); + return SIS_FAILURE; + } + + if (snd_firsttime) + Con_SafePrintf ("Using secondary sound buffer\n"); + } + else + { + if (DS_OK != IDirectSound_SetCooperativeLevel(pDS, mainwindow, DSSCL_WRITEPRIMARY)) + { + Con_SafePrintf ("Set coop level failed\n"); + FreeSound (); + return SIS_FAILURE; + } + + if (DS_OK != IDirectSound_GetCaps(pDSPBuf, &dsbcaps)) + { + Con_Printf ("DS:GetCaps failed\n"); + return SIS_FAILURE; + } + + pDSBuf = pDSPBuf; + Con_SafePrintf ("Using primary sound buffer\n"); + } + + // Make sure mixer is active + IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); + + if (snd_firsttime) + Con_SafePrintf ("%lu bytes in sound buffer\n", (unsigned long)dsbcaps.dwBufferBytes); + + gSndBufSize = dsbcaps.dwBufferBytes; + +// initialize the buffer + reps = 0; + + while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID *) (HPSTR) &lpData, &dwSize, NULL, NULL, 0)) != DS_OK) + { + if (hresult != DSERR_BUFFERLOST) + { + Con_SafePrintf ("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n"); + FreeSound (); + return SIS_FAILURE; + } + + if (++reps > 10000) + { + Con_SafePrintf ("SNDDMA_InitDirect: DS: couldn't restore buffer\n"); + FreeSound (); + return SIS_FAILURE; + } + } + + memset(lpData, 0, dwSize); +// lpData[4] = lpData[5] = 0x7f; // force a pop for debugging + + IDirectSoundBuffer_Unlock(pDSBuf, lpData, dwSize, NULL, 0); + + /* we don't want anyone to access the buffer directly w/o locking it first. */ + lpData = NULL; + + IDirectSoundBuffer_Stop(pDSBuf); + IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite); + IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); + + shm->samples = gSndBufSize / (shm->samplebits / 8); + shm->samplepos = 0; + shm->submission_chunk = 1; + shm->buffer = (unsigned char *) lpData; + sample16 = (shm->samplebits / 8) - 1; + + dsound_init = true; + + return SIS_SUCCESS; +} + + +/* +================== +SNDDM_InitWav + +Crappy windows multimedia base +================== +*/ +static qboolean SNDDMA_InitWav (dma_t *dma) +{ + WAVEFORMATEX format; + int i; + HRESULT hr; + + snd_sent = 0; + snd_completed = 0; + + memset((void *) dma, 0, sizeof(dma_t)); + shm = dma; + + shm->channels = 2; /* = desired_channels; */ + shm->samplebits = (loadas8bit.value) ? 8 : 16; + shm->speed = sndspeed.value; + + memset (&format, 0, sizeof(format)); + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = shm->channels; + format.wBitsPerSample = shm->samplebits; + format.nSamplesPerSec = shm->speed; + format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; + format.cbSize = 0; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + + /* Open a waveform device for output using window callback. */ + hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, &format, 0, 0L, CALLBACK_NULL); + if (hr != MMSYSERR_NOERROR) + { + if (hr != MMSYSERR_ALLOCATED) + { + Con_SafePrintf ("waveOutOpen failed\n"); + return false; + } + + Con_SafePrintf ("waveOutOpen failure, hardware already in use\n"); + return false; + } + + /* + * Allocate and lock memory for the waveform data. The memory + * for waveform data must be globally allocated with + * GMEM_MOVEABLE and GMEM_SHARE flags. + */ + gSndBufSize = WAV_BUFFERS * wv_buf_size; + hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize); + if (!hData) + { + Con_SafePrintf ("Sound: Out of memory.\n"); + FreeSound (); + return false; + } + lpData = (HPSTR) GlobalLock(hData); + if (!lpData) + { + Con_SafePrintf ("Sound: Failed to lock.\n"); + FreeSound (); + return false; + } + memset (lpData, 0, gSndBufSize); + + /* + * Allocate and lock memory for the header. This memory must + * also be globally allocated with GMEM_MOVEABLE and + * GMEM_SHARE flags. + */ + hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS); + if (hWaveHdr == NULL) + { + Con_SafePrintf ("Sound: Failed to Alloc header.\n"); + FreeSound (); + return false; + } + lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr); + if (lpWaveHdr == NULL) + { + Con_SafePrintf ("Sound: Failed to lock header.\n"); + FreeSound (); + return false; + } + memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS); + + /* After allocation, set up and prepare headers. */ + for (i = 0; i < WAV_BUFFERS; i++) + { + lpWaveHdr[i].dwBufferLength = wv_buf_size; + lpWaveHdr[i].lpData = lpData + i * wv_buf_size; + + if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) + { + Con_SafePrintf ("Sound: failed to prepare wave headers\n"); + FreeSound (); + return false; + } + } + + shm->samples = gSndBufSize / (shm->samplebits / 8); + shm->samplepos = 0; + shm->submission_chunk = 1; + shm->buffer = (unsigned char *) lpData; + sample16 = (shm->samplebits / 8) - 1; + + wav_init = true; + + Con_SafePrintf ("%d sound buffers, %d bytes/sound buffer\n", WAV_BUFFERS, wv_buf_size); + + return true; +} + +/* +================== +SNDDMA_Init + +Try to find a sound device to mix for. +Returns false if nothing is found. +================== +*/ +qboolean SNDDMA_Init (dma_t *dma) +{ + sndinitstat stat; + int sndbits = (loadas8bit.value) ? 8 : 16; + + if (COM_CheckParm ("-wavonly")) + wavonly = true; + + dsound_init = wav_init = 0; + + stat = SIS_FAILURE; // assume DirectSound won't initialize + + /* Calculate Wave and DS buffer sizes to set, to store + 2 secs of data, round up to the next power of 2 */ + ds_sbuf_size = 1 << (Q_log2((sndbits >> 3) * ((int)sndspeed.value << 1)) + 1); + wv_buf_size = 1 << (Q_log2(((int)sndspeed.value << 3) / WAV_BUFFERS) + 1); + + /* Init DirectSound */ + if (!wavonly) + { + if (snd_firsttime || snd_isdirect) + { + stat = SNDDMA_InitDirect (dma); + + if (stat == SIS_SUCCESS) + { + snd_isdirect = true; + + if (snd_firsttime) + Con_SafePrintf ("DirectSound initialized\n"); + } + else + { + snd_isdirect = false; + Con_SafePrintf ("DirectSound failed to init\n"); + } + } + } + +// if DirectSound didn't succeed in initializing, try to initialize +// waveOut sound, unless DirectSound failed because the hardware is +// already allocated (in which case the user has already chosen not +// to have sound) + if (!dsound_init && (stat != SIS_NOTAVAIL)) + { + if (snd_firsttime || snd_iswave) + { + snd_iswave = SNDDMA_InitWav (dma); + + if (snd_iswave) + { + if (snd_firsttime) + Con_SafePrintf ("Wave sound initialized\n"); + } + else + { + Con_SafePrintf ("Wave sound failed to init\n"); + } + } + } + + if (!dsound_init && !wav_init) + { + if (snd_firsttime) + Con_SafePrintf ("No sound device initialized\n"); + + snd_firsttime = false; + + return false; + } + + snd_firsttime = false; + + return true; +} + +/* +============== +SNDDMA_GetDMAPos + +return the current sample position (in mono samples read) +inside the recirculating dma buffer, so the mixing code will know +how many sample are required to fill it up. +=============== +*/ +int SNDDMA_GetDMAPos (void) +{ + MMTIME mmtime; + int s; + DWORD dwWrite; + + if (dsound_init) + { + mmtime.wType = TIME_SAMPLES; + IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &mmtime.u.sample, &dwWrite); + s = mmtime.u.sample - mmstarttime.u.sample; + } + else if (wav_init) + { + s = snd_sent * wv_buf_size; + } + else + { // we should not reach here... + return 0; + } + + s >>= sample16; + + s &= (shm->samples-1); + + return s; +} + +/* +============== +SNDDMA_LockBuffer + +Makes sure dma buffer is valid +=============== +*/ +static DWORD locksize; +void SNDDMA_LockBuffer (void) +{ + if (pDSBuf) + { + void *pData; + int reps; + HRESULT hresult; + DWORD dwStatus; + + reps = 0; + shm->buffer = NULL; + + if (IDirectSoundBuffer_GetStatus(pDSBuf, &dwStatus) != DS_OK) + Con_Printf ("Couldn't get sound buffer status\n"); + + if (dwStatus & DSBSTATUS_BUFFERLOST) + IDirectSoundBuffer_Restore(pDSBuf); + + if (!(dwStatus & DSBSTATUS_PLAYING)) + IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); + + while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (void **) &pData, &locksize, NULL, NULL, 0)) != DS_OK) + { + if (hresult != DSERR_BUFFERLOST) + { + Con_Printf ("SNDDMA_LockBuffer: DS::Lock Sound Buffer Failed\n"); + S_Shutdown (); + return; + } + + if (++reps > 10000) + { + Con_Printf ("SNDDMA_LockBuffer: DS: couldn't restore buffer\n"); + S_Shutdown (); + return; + } + } + + shm->buffer = (unsigned char *) pData; + } +} + +/* +============== +SNDDMA_Submit + +Unlock the dma buffer / +Send sound to the device +=============== +*/ +void SNDDMA_Submit (void) +{ + LPWAVEHDR h; + int wResult; + + if (pDSBuf) + IDirectSoundBuffer_Unlock(pDSBuf, shm->buffer, locksize, NULL, 0); + + if (!wav_init) + return; + + // + // find which sound blocks have completed + // + while (1) + { + if ( snd_completed == snd_sent ) + { + Con_DPrintf ("Sound overrun\n"); + break; + } + + if ( ! (lpWaveHdr[snd_completed & WAV_MASK].dwFlags & WHDR_DONE) ) + { + break; + } + + snd_completed++; // this buffer has been played + } + + // + // submit two new sound blocks + // + while (((snd_sent - snd_completed) >> sample16) < 4) + { + h = lpWaveHdr + (snd_sent & WAV_MASK); + + snd_sent++; + /* + * Now the data block can be sent to the output device. The + * waveOutWrite function returns immediately and waveform + * data is sent to the output device in the background. + */ + wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR)); + + if (wResult != MMSYSERR_NOERROR) + { + Con_SafePrintf ("Failed to write block to device\n"); + FreeSound (); + return; + } + } +} + +/* +================== +SNDDMA_BlockSound +================== +*/ +void SNDDMA_BlockSound (void) +{ + // DirectSound takes care of blocking itself + if (snd_iswave) + { + waveOutReset (hWaveOut); + } +} + + +/* +================== +SNDDMA_UnblockSound +================== +*/ +void SNDDMA_UnblockSound (void) +{ +} + + +/* +============== +SNDDMA_Shutdown + +Reset the sound device for exiting +=============== +*/ +void SNDDMA_Shutdown (void) +{ + FreeSound (); +#if defined(DX_DLSYM) + if (hInstDS) + { + FreeLibrary(hInstDS); + hInstDS = NULL; + } +#endif /* DX_DLSYM */ +} +