/* midassound.c * * A Sound module for DOOM using MIDAS Digital Audio System. Based on * original i_sound.c from the source distribution. * * Petteri Kangaslampi, pekangas@sci.fi * * [RH] Changed most of this with features from Hexen and Quake. * Note to self: copy this to the djgpp dir when I make changes. */ /* Original file header: */ //----------------------------------------------------------------------------- // // i_sound.c // // Copyright (C) 1993-1996 by id Software, Inc. // // This source is available for distribution and/or modification // only under the terms of the DOOM Source Code License as // published by id Software. All rights reserved. // // The source is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License // for more details. // // DESCRIPTION: // System interface for sound. // //----------------------------------------------------------------------------- #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #include "resource.h" #endif #ifdef DJGPP #define TRUE 1 // Make the MIDAS headers happy (and avoid redefining BOOL) #define FALSE 0 typedef unsigned long DWORD; #include "doomtype.h" #endif #include #include #include #include "m_alloc.h" #include /* We'll need to go below the MIDAS API level a bit */ #include "../midas/src/midas/midas.h" #include "../midas/include/midasdll.h" #include "wave.h" #include "m_swap.h" #include "z_zone.h" #include "c_cvars.h" #include "i_system.h" #include "i_sound.h" #include "i_music.h" #include "m_argv.h" #include "m_misc.h" #include "w_wad.h" #include "i_video.h" #include "s_sound.h" #include "doomdef.h" #ifdef _WIN32 extern HINSTANCE hInstance; #endif BOOL MidasInited; // [RH] The mixing rate for the sounds. cvar_t *snd_samplerate; // killough 2/21/98: optionally use varying pitched sounds cvar_t *snd_pitched; // [RH] Use a cvar to control it! #define PITCH(f,x) (snd_pitched->value ? ((f)*(x))/128 : (f)) // Maps sfx channels onto MIDAS channels static struct { MIDASsamplePlayHandle playHandle; // MIDAS sample play handle int soundID; // sfx playing on this channel DWORD midasChannel; // MIDAS channel this one maps to } *ChannelMap; #ifdef _WIN32 static int wavonly = 0; static int primarysound = 0; #endif static int nosound = 0; static int numChannels; static float volmul; void MIDASerror(void) { I_FatalError ("MIDAS Error: %s", MIDASgetErrorMessage(MIDASgetLastError())); } /* Loads a sound and adds it to MIDAS * Really returns a MIDAS sample handle */ static void getsfx (sfxinfo_t *sfx) { char sndtemp[128]; byte *sfxdata; byte *sfxcopy; int size; int i; int error; static sdSample smp; // Get the sound data from the WAD and register it with MIDAS // If the sound doesn't exist, try a generic male sound (if // this is a player sound) or the empty sound. if (sfx->lumpnum == -1) { char *basename; int sfx_id; if (!strnicmp (sfx->name, "player/", 7) && (basename = strchr (sfx->name + 7, '/'))) { sprintf (sndtemp, "player/male/%s", basename+1); sfx_id = S_FindSound (sndtemp); if (sfx_id != -1) sfx->lumpnum = S_sfx[sfx_id].lumpnum; } if (sfx->lumpnum == -1) badwave: sfx->lumpnum = W_GetNumForName ("dsempty"); } // See if there is another sound already initialized with this lump. If so, // then set this one up as a link, and don't load the sound again. for (i = 0; i < numsfx; i++) if (S_sfx[i].data && !S_sfx[i].link && S_sfx[i].lumpnum == sfx->lumpnum) { DPrintf ("Linked to %s (%d)\n", S_sfx[i].name, i); sfx->link = S_sfx + i; sfx->ms = S_sfx[i].ms; sfx->data = S_sfx[i].data; sfx->loopdata = S_sfx[i].loopdata; return; } size = W_LumpLength (sfx->lumpnum); if (size == 0) goto badwave; sfxdata = W_CacheLumpNum (sfx->lumpnum, PU_CACHE); /* Add sound to MIDAS: */ /* A hack below API level - yeahyeah, we should add support for preparing samples from memory to the API */ if (LONG(*((unsigned int *)sfxdata)) == ID_RIFF) { // RIFF WAVE sound byte *sfx_p, *sfxend, *wavedata; fmt_t fmtchunk; unsigned datalen = 0; if (LONG(((int *)sfxdata)[1]) > size - 8) { Printf (PRINT_HIGH, "%s: lump is too short\n", sfx->name); goto badwave; } sfxend = sfxdata + LONG(((int *)sfxdata)[1]) + 8; if (LONG(((unsigned int *)sfxdata)[2]) != ID_WAVE) { Printf (PRINT_HIGH, "%s: not a WAVE file\n", sfx->name); goto badwave; } sfx_p = sfxdata + 4*3; fmtchunk.FormatTag = ~0; wavedata = NULL; while (sfx_p < sfxend) { unsigned int chunkid = LONG(((unsigned int *)sfx_p)[0]); unsigned int chunklen = LONG(((unsigned int *)sfx_p)[1]); sfx_p += 4*2; if (chunkid == ID_fmt) { if (chunklen < sizeof(fmtchunk)) { fmtchunk.FormatTag = ~1; // Signal that the chunk was too short, continue; // but continue in case there is another good one } memcpy (&fmtchunk, sfx_p, sizeof(fmtchunk)); fmtchunk.FormatTag = SHORT(fmtchunk.FormatTag); fmtchunk.Channels = SHORT(fmtchunk.Channels); fmtchunk.SamplesPerSec = LONG(fmtchunk.SamplesPerSec); fmtchunk.AvgBytesPerSec = LONG(fmtchunk.AvgBytesPerSec); fmtchunk.BlockAlign = SHORT(fmtchunk.BlockAlign); } else if (chunkid == ID_data) { if (fmtchunk.FormatTag == WAVE_FMT_PCM) { wavedata = sfx_p; datalen = chunklen; break; } else if (fmtchunk.FormatTag == ~0) { Printf (PRINT_HIGH, "%s: no fmt chunk\n", sfx->name); } else if (fmtchunk.FormatTag == ~1) { Printf (PRINT_HIGH, "%s: fmt chunk too short\n", sfx->name); } else { Printf (PRINT_HIGH, "%s: unknown format %u\n", sfx->name, fmtchunk.FormatTag); } goto badwave; } sfx_p += chunklen; } if (!wavedata || datalen == 0) { Printf (PRINT_HIGH, "%s: no data chunk\n", sfx->name); goto badwave; } if (fmtchunk.Channels > 2) { Printf (PRINT_HIGH, "%s: too many channels\n", sfx->name); goto badwave; } if (fmtchunk.Channels < 1) { Printf (PRINT_HIGH, "%s: no channels\n", sfx->name); goto badwave; } if (fmtchunk.BlockAlign != 1 && fmtchunk.BlockAlign != 2 && fmtchunk.BlockAlign != 4) { Printf (PRINT_HIGH, "%s: bad blockalign (%d)\n", fmtchunk.BlockAlign); goto badwave; } /* Build Sound Device sample structure for the sample: */ smp.sample = wavedata; smp.samplePos = sdSmpConv; if (fmtchunk.Channels == 1) smp.sampleType = fmtchunk.BlockAlign == 1 ? smp8bitMono : smp16bitMono; else smp.sampleType = fmtchunk.BlockAlign == 2 ? smp8bitStereo : smp16bitStereo; smp.sampleLength = datalen; sfx->frequency = fmtchunk.SamplesPerSec; sfx->length = datalen; sfx->ms = datalen / fmtchunk.BlockAlign; } else { // DMX sound /* Build Sound Device sample structure for the sample: */ smp.sample = sfxdata+8; smp.samplePos = sdSmpConv; smp.sampleType = MIDAS_SAMPLE_8BIT_MONO; smp.sampleLength = size-8; sfx->frequency = ((unsigned short *)sfxdata)[1]; // Extract sample rate from the sound header sfx->ms = sfx->length = ((unsigned int *)sfxdata)[1]; if ((signed)sfx->length > size - 8) { Printf (PRINT_HIGH, "%s is missing %d bytes\n", sfx->name, sfx->length - size + 8); sfx->ms = sfx->length = size - 8; } } /* No loop 2: */ smp.loop2Start = smp.loop2End = 0; smp.loop2Type = loopNone; sfxcopy = Malloc (smp.sampleLength); memcpy (sfxcopy, smp.sample, smp.sampleLength); Z_Free (sfxdata); smp.sample = sfxcopy; /* Add the sample to the Sound Device: */ { // Avoid using __fastcall for this function typedef int (STACK_ARGS *blargh_t)(sdSample*, int, unsigned *); blargh_t blargh = (blargh_t)midasSD->AddSample; /* No loop: */ smp.loopMode = sdLoopNone; smp.loop1Start = smp.loop1End = 0; smp.loop1Type = loopNone; if ( (error = blargh (&smp, 0, (unsigned *)&sfx->data)) != OK) I_FatalError ("getsfx: AddSample failed: %s", MIDASgetErrorMessage(error)); /* With loop: */ smp.loopMode = sdLoop1; smp.loop1Start = 0; smp.loop1End = smp.sampleLength; smp.loop1Type = loopUnidir; if ( (error = blargh (&smp, 0, (unsigned *)&sfx->loopdata)) != OK) I_FatalError ("getsfx: AddSample failed: %s", MIDASgetErrorMessage(error)); } // Remove the cached lump. if (sfx->frequency == 0) sfx->frequency = 11025; sfx->ms = (sfx->ms * 1000) / (sfx->frequency); } // // SFX API // void I_SetChannels (int numchannels) { int i; if (!MIDASopenChannels (numchannels + (M_CheckParm ("-nomusic") ? 0 : 64))) MIDASerror(); ChannelMap = Z_Malloc (numchannels * sizeof(*ChannelMap), PU_STATIC, 0); for (i = 0; i < numchannels; i++) { if (MIDAS_ILLEGAL_CHANNEL == (ChannelMap[i].midasChannel = MIDASallocateChannel ())) { MIDASerror (); } ChannelMap[i].playHandle = 0; ChannelMap[i].soundID = -1; } // Don't play sfx really quiet. // Use less amplification for each new channel added. MIDASsetAmplification ((int)(log((double)numchannels) * 144)); numChannels = numchannels; } void I_SetSfxVolume (int volume) { // volume range is 0-15 // volmul range is 0-1.whatever volmul = (float)((volume / 15.0) * (256.0 / 255.0)); } // // Starting a sound means adding it // to the current list of active sounds // in the internal channels. // As the SFX info struct contains // e.g. a pointer to the raw data, // it is ignored. // int I_StartSound (sfxinfo_t *sfx, int vol, int sep, int pitch, int channel, BOOL looping) { int id = sfx - S_sfx; int volume; int pan; /* Calculate MIDAS volume and panning from volume and separation: */ volume = ((int)(vol*volmul))/4; /* original range 0-255, MIDAS range 0-64 */ if ( volume > 64 ) volume = 64; else if ( volume < 0 ) volume = 0; if (sep < 0) { pan = MIDAS_PAN_SURROUND; } else { pan = (sep-128) / 2; /* original 0-255, MIDAS -64-64 */ /* Clamp: */ if ( pan < MIDAS_PAN_LEFT) pan = MIDAS_PAN_LEFT; else if ( pan > MIDAS_PAN_RIGHT) pan = MIDAS_PAN_RIGHT; } ChannelMap[channel].playHandle = MIDASplaySample ( looping ? (MIDASsample)sfx->loopdata : (MIDASsample)sfx->data, ChannelMap[channel].midasChannel, 0, PITCH(sfx->frequency,pitch), volume, pan); ChannelMap[channel].soundID = id; return channel+1; } void I_StopSound (int handle) { handle--; if (ChannelMap[handle].playHandle) { if (!MIDASstopSample (ChannelMap[handle].playHandle)) MIDASerror(); ChannelMap[handle].playHandle = 0; } } int I_SoundIsPlaying (int handle) { int is; handle--; if (!ChannelMap[handle].playHandle) return 0; else if (!(is = (int)MIDASgetSamplePlayStatus (ChannelMap[handle].playHandle))) ChannelMap[handle].playHandle = 0; return is; } void I_UpdateSoundParams (int handle, int vol, int sep, int pitch) { int mvol, mpan; handle--; if (!ChannelMap[handle].playHandle) return; /* Calculate MIDAS volume and panning from volume and separation: */ mvol = ((int)(vol*volmul))/4; /* original range 0-255, MIDAS range 0-64 */ mpan = (sep-128) / 2; /* original 0-255, MIDAS -64-64 */ /* Clamp: */ if ( mvol > 64 ) mvol = 64; if ( mvol < 0 ) mvol = 0; if ( mpan < -64) mpan = -64; if ( mpan > 64) mpan = 64; /* Set: */ { MIDASsamplePlayHandle sample = ChannelMap[handle].playHandle; if ( !MIDASsetSampleVolume(sample, mvol) || !MIDASsetSamplePanning(sample, mpan) || !MIDASsetSampleRate(sample, PITCH(S_sfx[ChannelMap[handle].soundID].frequency,pitch)) ) MIDASerror(); } } void I_LoadSound (struct sfxinfo_struct *sfx) { if (!sfx->data) { int i = sfx - S_sfx; DPrintf ("loading sound \"%s\" (%d)\n", sfx->name, i); getsfx (sfx); } } #ifdef _WIN32 extern DWORD Window; /* window handle from i_main.c (actually HWND) */ // [RH] Dialog procedure for the error dialog that appears if MIDAS // could not be initialized for some reason. BOOL CALLBACK InitBoxCallback (HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) { static const char *messyTemplate = "MIDAS could not be initialized.\r\n" "(Reason: %s.)\r\n\r\n" "If another program is using the sound card, you " "can try stopping it and clicking \"Retry.\"\r\n\r\n" "Otherwise, you can either click \"No Sound\" to use " "ZDoom without sound or click \"Quit\" if you don't " "really want to play ZDoom."; switch (message) { case WM_INITDIALOG: { char *midaserr = MIDASgetErrorMessage (MIDASgetLastError ()); char messyText[2048]; sprintf (messyText, messyTemplate, midaserr); SetDlgItemText (hwndDlg, IDC_ERRORMESSAGE, messyText); } return TRUE; case WM_COMMAND: if (wParam == IDOK || wParam == IDC_NOSOUND || wParam == IDQUIT) { EndDialog (hwndDlg, wParam); return TRUE; } break; } return FALSE; } #endif void I_InitSound (void) { I_InitMusic(); /* Get command line options: */ #ifdef _WIN32 wavonly = !!M_CheckParm ("-wavonly"); primarysound = !!M_CheckParm ("-primarysound"); #endif nosound = !!M_CheckParm ("-nosfx") || !!M_CheckParm ("-nosound"); Printf (PRINT_HIGH, "I_InitSound: Initializing MIDAS\n"); MIDASstartup(); MIDASsetOption(MIDAS_OPTION_MIXRATE, (int)snd_samplerate->value); MIDASsetOption(MIDAS_OPTION_MIXBUFLEN, 200); #ifdef _WIN32 if ( !wavonly ) { MIDASsetOption(MIDAS_OPTION_DSOUND_HWND, Window); if ( primarysound ) MIDASsetOption(MIDAS_OPTION_DSOUND_MODE, MIDAS_DSOUND_PRIMARY); else MIDASsetOption(MIDAS_OPTION_DSOUND_MODE, MIDAS_DSOUND_STREAM); } #endif if ( nosound ) MIDASsetOption(MIDAS_OPTION_FORCE_NO_SOUND, TRUE); #ifdef DJGPP if (M_CheckParm ("-m")) MIDASconfig (); #endif while (!MIDASinit()) { #ifdef _WIN32 // If MIDAS can't be initialized, give the user some // choices other than quit. switch (DialogBox (hInstance, MAKEINTRESOURCE(IDD_MIDASINITERROR), (HWND)Window, (DLGPROC)InitBoxCallback)) { case IDC_NOSOUND: MIDASsetOption (MIDAS_OPTION_FORCE_NO_SOUND, TRUE); break; case IDQUIT: exit (0); break; } #else static int errorcount = 0; if (!errorcount) { errorcount++; Printf (PRINT_HIGH, "Sound init error: %s\nUsing no sound\n", MIDASgetErrorMessage (MIDASgetLastError ())); MIDASsetOption (MIDAS_OPTION_FORCE_NO_SOUND, TRUE); if (M_CheckParm ("-m") == 0) Printf (PRINT_HIGH, "(Try running with the -m parameter.)\n"); } else { exit (0); } #endif } MidasInited = true; atexit (I_ShutdownSound); if ( !MIDASstartBackgroundPlay(100) ) MIDASerror(); // Finished initialization. Printf (PRINT_HIGH, "I_InitSound: sound module ready\n"); } void STACK_ARGS I_ShutdownSound (void) { int i, c = 0; size_t len = 0; if (MidasInited) { Printf (PRINT_HIGH, "I_ShutdownSound: Stopping sounds\n"); if (ChannelMap) { for (i = 0; i < numChannels; i++ ) { if (ChannelMap[i].playHandle) { if (!MIDASstopSample (ChannelMap[i].playHandle)) MIDASerror (); } } Z_Free (ChannelMap); ChannelMap = NULL; } I_ShutdownMusic(); Printf (PRINT_HIGH, "I_ShutdownSound: Uninitializing MIDAS\n"); // [RH] Free all loaded samples for (i = 0; i < numsfx; i++) { if (!S_sfx[i].link) { if (S_sfx[i].data) { MIDASfreeSample ((MIDASsample)S_sfx[i].data); len += S_sfx[i].length; c++; } if (S_sfx[i].loopdata) { MIDASfreeSample ((MIDASsample)S_sfx[i].loopdata); } } S_sfx[i].data = S_sfx[i].link = NULL; } Printf (PRINT_HIGH, "%d sounds expunged (%d bytes)\n", c, len); if ( !MIDASstopBackgroundPlay() ) MIDASerror(); // [RH] This seems to cause problems with MIDASclose(). // if ( !MIDAScloseChannels() ) // MIDASerror(); } if ( !MIDASclose() ) MIDASerror(); }