/* * file w32_sndsrv.c - sound library for xblast * * $Id: w32_sndsrv.c,v 1.3 2004/05/14 10:00:36 alfie Exp $ * * Program XBLAST * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net) * * 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; or (at your option) * any later version * * This program is distributed in the hope that it will be entertaining, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILTY 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 the Free Software Foundation, Inc. * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "w32_sndsrv.h" #include "snd.h" //#include "w32_mm.h" #include "util.h" /* * local macros */ #define WAVE_BUFFER_SIZE 2210 #define NUM_WAVE_HEADERS 4 /* * local types */ typedef struct { SND_Id id; /* the sound's id to refer to it */ const char *name; /* raw samples data file name */ char *samples; /* pointer to samples memory */ size_t length; /* length in samples of the sound */ XBBool repeat; /* repeat flag to play sound endlessly */ XBBool mono; /* mono flag indicating mono sounds */ } SoundInfo; typedef struct _sound_play { struct _sound_play *next; SND_Id id; /* reference to effect */ short left; /* left speaker volume */ short right; /* right speaker volume */ size_t pos; /* current data position */ } SoundItem; typedef struct { HGLOBAL hHdr; WAVEHDR *pHdr; HGLOBAL hBuf; char *pBuf; } HeaderInfo; /* * local variables */ static HWAVEOUT hWaveOut; static SoundItem *playList = NULL; static unsigned idThread; static XBBool finished = XBFalse; static HeaderInfo header[NUM_WAVE_HEADERS]; static short mixBuffer[WAVE_BUFFER_SIZE]; /* wave formats for stereo or mono */ static WAVEFORMATEX waveFormatMono = { WAVE_FORMAT_PCM, 1, 22050, 22050, 1 , 8, 0 }; static WAVEFORMATEX waveFormatStereo = { WAVE_FORMAT_PCM, 2, 22050, 44100, 2 , 8, 0 }; /* * sound sample table */ static SoundInfo soundInfo[SND_MAX] = { {SND_BAD, "xb_bad", NULL, 0, XBFalse, XBFalse}, /* got a skull */ {SND_DROP, "xb_drop", NULL, 0, XBFalse, XBTrue}, /* dropped a bomb */ {SND_NEWBOMB, "xbnbmb", NULL, 0, XBFalse, XBTrue}, /* got an extra bomb */ {SND_NEWKICK, "xbnkick", NULL, 0, XBFalse, XBTrue}, /* got kick extra */ {SND_NEWPUMP, "xbnpmp", NULL, 0, XBFalse, XBTrue}, /* got pump extra */ {SND_NEWRC, "xbnrc", NULL, 0, XBFalse, XBTrue}, /* got rem. control */ {SND_MOREFIRE, "xbfire", NULL, 0, XBFalse, XBTrue}, /* got more range */ {SND_DEAD, "xb_dead", NULL, 0, XBFalse, XBFalse}, /* player died */ {SND_EXPL, "xb_expl", NULL, 0, XBFalse, XBTrue}, /* normal explosion */ {SND_KICK, "xb_kick", NULL, 0, XBFalse, XBTrue}, /* kick a bomb */ {SND_PUMP, "xb_pump", NULL, 0, XBFalse, XBTrue}, /* pump a bomb */ {SND_OUCH, "xb_ouch", NULL, 0, XBFalse, XBFalse}, /* player lost life */ {SND_INTRO, "xb_intro", NULL, 0, XBFalse, XBFalse}, /* intro fanfare */ {SND_APPL, "xb_appl", NULL, 0, XBFalse, XBFalse}, /* applause */ {SND_BUTT, "xb_butt", NULL, 0, XBFalse, XBTrue}, /* triggered button */ {SND_SHOOT, "xb_shoot", NULL, 0, XBFalse, XBFalse}, /* using rem. ctrl. */ {SND_INVIS, "xb_nvis", NULL, 0, XBFalse, XBFalse}, /* player invisible */ {SND_INVINC, "xb_nvnc", NULL, 0, XBFalse, XBFalse}, /* player invincible */ {SND_NEWTELE, "xbntel", NULL, 0, XBFalse, XBTrue}, /* player got telep. */ {SND_TELE, "xbtele", NULL, 0, XBFalse, XBTrue}, /* player uses tele. */ {SND_INJ, "xbinj", NULL, 0, XBFalse, XBFalse}, /* player got junkie */ {SND_MINIBOMB, "xbmbmb", NULL, 0, XBFalse, XBTrue}, /* small bomb expl. */ {SND_WON, "xb_won", NULL, 0, XBFalse, XBFalse}, /* player won */ {SND_HAUNT, "xb_haunt", NULL, 0, XBFalse, XBFalse}, /* haunting bomb */ {SND_SPIRAL, "xb_spir", NULL, 0, XBFalse, XBTrue}, /* spiral shrinking */ {SND_SPBOMB, "xb_spbmb", NULL, 0, XBFalse, XBTrue}, /* got special bomb */ {SND_SLIDE, "xbslide", NULL, 0, XBFalse, XBTrue}, /* bomb slide sound */ {SND_FINALE, "xbfin", NULL, 0, XBFalse, XBFalse}, /* final fanfare */ {SND_WARN, "xb_warn", NULL, 0, XBFalse, XBFalse}, /* shrink warn sound */ {SND_STUN, "xb_stun", NULL, 0, XBFalse, XBFalse}, /* player stun sound */ {SND_WHIRL, "xb_whrl", NULL, 0, XBTrue, XBFalse}, /* intro whirl */ {SND_COMPOUND, "xb_cmpnd", NULL, 0, XBFalse, XBFalse}, /* compound shrink */ {SND_TELE1, "xbtele1", NULL, 0, XBFalse, XBTrue}, /* teleport start */ {SND_TELE2, "xbtele2", NULL, 0, XBFalse, XBTrue}, /* teleport end */ {SND_HOLY, "xbholy", NULL, 0, XBFalse, XBFalse}, /* holy grail extra */ {SND_ENCLOAK, "xbcloak", NULL, 0, XBFalse, XBTrue}, /* encloak sound */ {SND_DECLOAK, "xbdcloak", NULL, 0, XBFalse, XBTrue}, /* decloak sound */ {SND_FAST, "xbfast", NULL, 0, XBFalse, XBTrue}, /* speed up extra */ {SND_SLOW, "xbslow", NULL, 0, XBFalse, XBTrue}, /* slow down extra */ {SND_SLAY, "xbslay", NULL, 0, XBFalse, XBTrue}, /* slay extra */ {SND_LIFE, "xblife", NULL, 0, XBFalse, XBTrue}, /* extra life */ {SND_NEWCLOAK, "xbcloakx", NULL, 0, XBFalse, XBTrue}, /* new cloak extra */ {SND_BOMBMORPH,"xb_bombmorph", NULL, 0, XBFalse, XBTrue}, /* bomb morph */ }; /* * check waveout capabilities */ XBBool SoundCheckWaveOut (XBBool stereo) { WAVEFORMATEX *pFormat = stereo ? &waveFormatStereo : &waveFormatMono; if (0 == waveOutOpen (NULL, WAVE_MAPPER, pFormat, 0, 0, WAVE_FORMAT_QUERY) ) { return XBTrue; } else { return XBFalse; } } /* CheckWaveOut */ /* * Callback for audio device */ void PASCAL _export WaveProc (HWAVE hWave, UINT msg, unsigned inst, unsigned par1, unsigned par2) { /* assert (hWave == hWaveOut); */ switch (msg) { case MM_WOM_DONE: PostThreadMessage (idThread, MSG_XBLAST_SOUND_DONE, 0, par1); break; case MM_WOM_CLOSE: PostThreadMessage (idThread, MSG_XBLAST_SOUND_CLOSE, 0, 0); break; } } /* WaveProc */ /* * initialize waveform headers */ static XBBool InitHeader (HeaderInfo *header) { static XBBool first = XBTrue; assert (NULL != header); /* header */ header->hHdr = GlobalAlloc (GMEM_MOVEABLE, sizeof (WAVEHDR)); assert (header->hHdr != NULL); header->pHdr = GlobalLock (header->hHdr); assert (header->pHdr != NULL); memset (header->pHdr, 0, sizeof (WAVEHDR)); /* data */ header->hBuf = GlobalAlloc (GMEM_MOVEABLE, WAVE_BUFFER_SIZE); assert (header->hBuf != NULL); header->pBuf = GlobalLock (header->hBuf); assert (header->pBuf != NULL); memset (header->pBuf, 0x80, WAVE_BUFFER_SIZE); /* set header data */ header->pHdr->lpData = header->pBuf; header->pHdr->dwBufferLength = WAVE_BUFFER_SIZE; /* Prepare and Write header */ if (0 != waveOutPrepareHeader (hWaveOut, header->pHdr, sizeof (WAVEHDR) ) ) { printf ("prepare header failed\n"); return XBFalse; } if (0 != waveOutWrite (hWaveOut, header->pHdr, sizeof (WAVEHDR) ) ) { printf ("write failed\n"); return XBFalse; } first = ! first; return XBTrue; } /* InitHeader */ /* * */ static void FinishHeader (HeaderInfo *header) { assert (header != NULL); if (NULL != header->hBuf) { GlobalUnlock (header->hBuf); GlobalFree (header->hBuf); header->hBuf = NULL; header->pBuf = NULL; } if (NULL != header->hHdr) { GlobalUnlock (header->hHdr); GlobalFree (header->hHdr); header->hHdr = NULL; header->pHdr = NULL; } } /* FinishHeader */ /* * Add single sound to buffer */ XBBool AddSound (short *buf, SoundItem *ptr) { size_t i; size_t nBytes; size_t totalBytes = 0; char *src; assert (NULL != buf); assert (NULL != ptr); assert (SND_MAX >= ptr->id); assert (NULL != soundInfo[ptr->id].samples); if (soundInfo[ptr->id].mono) { /* * 1 channel 8 bits source */ /* number of bytes to add */ if (soundInfo[ptr->id].length - ptr->pos > WAVE_BUFFER_SIZE/2) { nBytes = WAVE_BUFFER_SIZE/2; } else { nBytes = soundInfo[ptr->id].length - ptr->pos; } /* add on both channels */ src = soundInfo[ptr->id].samples + ptr->pos; for (i = 0; i < nBytes; i ++) { buf[2*i] += ptr->left * src[i]; buf[2*i+1] += ptr->right * src[i]; } ptr->pos += nBytes; } else { /* * 2 channel 8 bits source */ do { /* number of bytes to add */ if (soundInfo[ptr->id].length - ptr->pos > WAVE_BUFFER_SIZE - totalBytes) { nBytes = WAVE_BUFFER_SIZE - totalBytes; } else { nBytes = soundInfo[ptr->id].length - ptr->pos; } /* add on both channels */ src = soundInfo[ptr->id].samples + ptr->pos; for (i = 0; i < nBytes; i ++) { buf[i + totalBytes] += ptr->left * src[i]; } ptr->pos += nBytes; if (soundInfo[ptr->id].repeat) { totalBytes += nBytes; if (ptr->pos == soundInfo[ptr->id].length) { ptr->pos = 0; } } else { totalBytes = WAVE_BUFFER_SIZE; } } while (totalBytes < WAVE_BUFFER_SIZE); } return (ptr->pos < soundInfo[ptr->id].length); } /* AddSound */ /* * one buffer is finished do it again */ static void SoundDone (const WAVEHDR *hdr) { int i, j; SoundItem *list = NULL; SoundItem *play, *next; char *dst; short *src; short val; /* find header */ for (i = 0; i < NUM_WAVE_HEADERS; i ++) { if (header[i].pHdr == hdr) { break; } } if (i >= NUM_WAVE_HEADERS) { fprintf (stderr, "failed to identify header\n"); return; } /* clean up */ if (0 != waveOutUnprepareHeader (hWaveOut, header[i].pHdr, sizeof (WAVEHDR) ) ) { return; } /* add sound effects */ if (NULL == playList) { memset (header[i].pBuf, 0x80, WAVE_BUFFER_SIZE); } else { memset (mixBuffer, 0x00, 2*WAVE_BUFFER_SIZE); for (play = playList; play != NULL; play = next) { next = play->next; if (AddSound (mixBuffer, play) ) { /* continue with this sound */ play->next = list; list = play; } else { /* no longer play this sound */ free (play); } } playList = list; /* copy to waveout buffer */ src = mixBuffer; dst = header[i].pBuf; for (j = 0; j < WAVE_BUFFER_SIZE; j ++) { val = src[j] / 16 + 128; if (val < 0) { dst[j] = 0; } else if (val > 255) { dst[j] = (unsigned char) 255; } else { dst[j] = (unsigned char) val; } } } /* write it again */ if (! finished) { if (0 != waveOutPrepareHeader (hWaveOut, header[i].pHdr, sizeof (WAVEHDR) ) ) { printf ("prepare header failed\n"); return; } if (0 != waveOutWrite (hWaveOut, header[i].pHdr, sizeof (WAVEHDR) ) ) { printf ("write failed\n"); return; } } } /* SoundDone */ /* * add this sound to play list */ static void SoundPlay (SND_Id id, short left, short right) { SoundItem *ptr; /* 1. search for same entry with offset 0 */ for (ptr = playList; ptr != NULL; ptr = ptr->next) { if (ptr->id == id && ptr->pos == 0) { break; } } /* 2. if no one was found create new one */ if (NULL == ptr) { ptr = calloc (1, sizeof (SoundItem)); assert (NULL != ptr); ptr->id = id; ptr->next = playList; playList = ptr; } /* 3. add own amplitudes */ ptr->left += left; ptr->right += right; } /* SoundPlay */ /* * remove this sound to play list */ static void SoundStop (SND_Id id) { SoundItem *list = NULL; SoundItem *ptr, *next; /* delete all sounds of given type */ for (ptr = playList; ptr != NULL; ptr = next) { next = ptr->next; if (ptr->id == id) { free (ptr); } else { ptr->next = list; list = ptr; } } playList = list; } /* SoundStop */ /* * unload sound */ static void SoundLoad (SND_Id id, char *samples) { assert (soundInfo[id].id == id); if (NULL != soundInfo[id].samples) { free (soundInfo[id].samples); } soundInfo[id].samples = samples; } /* SoundUnload */ /* * shutdown wave output */ static void SoundShutdown () { int i; finished = XBTrue; waveOutReset (hWaveOut); waveOutClose (hWaveOut); hWaveOut = NULL; for (i = 0; i < NUM_WAVE_HEADERS; i ++) { FinishHeader (header + i); } } /* SoundShutdown */ /* * thread start function */ DWORD PASCAL _export SoundThreadStereo (void *par) { int i; MSG msg; /* get own id */ idThread = GetCurrentThreadId (); /* open device */ if (0 != waveOutOpen (&hWaveOut, WAVE_MAPPER, &waveFormatStereo, (DWORD) WaveProc, 0, CALLBACK_FUNCTION) ) { fprintf (stderr, "failed to open waveout device\n"); return 0; } /* initialize two wave headers */ for (i = 0; i < NUM_WAVE_HEADERS; i ++) { if (! InitHeader (header + i) ) { fprintf (stderr, "failed to init waveout headers\n"); return 0; } } /* wait for messages */ while (GetMessage (&msg, NULL, MSG_XBLAST_SOUND_FIRST, MSG_XBLAST_SOUND_LAST)) { switch (msg.message) { case MSG_XBLAST_SOUND_DONE: SoundDone ((WAVEHDR *) msg.lParam); break; case MSG_XBLAST_SOUND_PLAY: SoundPlay ((SND_Id) msg.wParam, (short) LOWORD (msg.lParam), (short) HIWORD (msg.lParam) ); break; case MSG_XBLAST_SOUND_STOP: SoundStop ((SND_Id) msg.wParam); break; case MSG_XBLAST_SOUND_LOAD: SoundLoad ((SND_Id) msg.wParam, (char *) msg.lParam); break; case MSG_XBLAST_SOUND_SHUTDOWN: SoundShutdown (); break; case MSG_XBLAST_SOUND_CLOSE: /* TODO clean up */ hWaveOut = NULL; return 1; } } return 1; } /* SoundThreadStereo */ /* * load a sound file from disk */ char * SoundLoadFile (SND_Id id) { assert (soundInfo[id].id == id); return ReadRawFile ("sounds", soundInfo[id].name, &soundInfo[id].length); } /* SoundLoad */ /* * end of file w32_sndsrv.c */