gzdoom/code/djgpp/I_sound.c
1999-02-21 00:00:00 +00:00

637 lines
16 KiB
C

/* 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 <windows.h>
#include <mmsystem.h>
#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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "m_alloc.h"
#include <math.h>
/* 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();
}