raze-gles/source/sw/src/sounds.cpp
Christoph Oelckers 628bd9efc6 Filled in the rest of the menus
Sound is only partially functional, video mode completely nonfunctional, but it makes no sense adjusting them to the current backend code when it's due for replacement.
2019-12-05 00:47:05 +01:00

1987 lines
48 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment
This file is part of Shadow Warrior version 1.2
Shadow Warrior 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 the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "ns.h"
#include "compat.h"
#include "build.h"
#include "cache1d.h"
#include "keys.h"
#include "names2.h"
#include "mytypes.h"
#include "fx_man.h"
#include "music.h"
#include "al_midi.h"
#include "gamedefs.h"
#include "config.h"
#include "panel.h"
#include "game.h"
#include "sounds.h"
#include "ai.h"
#include "network.h"
#include "cache.h"
#include "text.h"
#include "rts.h"
#include "menus.h"
#include "config.h"
#include "menu/menu.h"
#ifdef _WIN32
#include "sdlayer.h"
#endif
BEGIN_SW_NS
extern USERp User[MAXSPRITES];
void DumpSounds(void);
// Parentally locked sounds list
int PLocked_Sounds[] =
{
483,328,334,335,338,478,450,454,452,453,456,457,458,455,460,462,
461,464,466,465,467,463,342,371,254,347,350,432,488,489,490,76,339,
499,500,506,479,480,481,482,78,600,467,548,547,544,546,545,542,541,540,
539,536,529,525,522,521,515,516,612,611,589,625,570,569,567,565,
558,557
};
uint8_t RedBookSong[40] =
{
2,4,9,12,10, // Title and ShareWare levels
5,6,8,11,12,5,10,4,6,9,7,10,8,7,9,10,11,5, // Registered levels
11,8,7,13,5,6, // Deathmatch levels
13 // Fight boss
};
// Global vars used by ambient sounds to set spritenum of ambient sounds for later lookups in
// the sprite array so FAFcansee can know the sound sprite's current sector location
SWBOOL Use_SoundSpriteNum = FALSE;
int16_t SoundSpriteNum = -1; // Always set this back to -1 for proper validity checking!
SWBOOL MusicInitialized = FALSE;
SWBOOL FxInitialized = FALSE;
void SoundCallBack(unsigned int num);
SWBOOL LoadSong(const char *track);
#define MUSIC_ID -65536
#define NUM_SAMPLES 10
const char *BitNames[2] =
{
"8-bit", "16-bit"
};
const char *ChannelNames[2] =
{
"Mono", "Stereo"
};
const char *VoiceNames[8] =
{
"1", "2", "3", "4", "5", "6", "7", "8"
};
int music;
int soundfx;
int num_voices;
int NumSounds = 0;
int angle;
int distance;
int voice;
int loopflag;
typedef enum
{
SongTypeNone,
SongTypeMIDI,
SongTypeWave,
} SongType_t;
char *SongPtr = NULL;
int SongLength = 0;
char *SongName = NULL;
int SongTrack = 0;
SongType_t SongType = SongTypeNone;
int SongVoice = -1;
extern SWBOOL DemoMode;
//
// Includes digi.h to build the table
//
#define DIGI_TABLE
VOC_INFO voc[] =
{
#include "digi.h"
};
#undef DIGI_TABLE
//
// Includes ambient.h to build the table of ambient sounds for game
//
#define AMBIENT_TABLE
AMB_INFO ambarray[] =
{
#include "ambient.h"
};
#undef AMBIENT_TABLE
#define MAX_AMBIENT_SOUNDS 82
#define MAXSONGS 10 // This is the max songs per episode
SWBOOL OpenSound(VOC_INFOp vp, FileReader &handle, int *length);
int ReadSound(FileReader & handle, VOC_INFOp vp, int length);
// 3d sound engine function prototype
VOC3D_INFOp Insert3DSound(void);
#if 0
// DEBUG
char *globsndata[DIGI_MAX], *globvpdata[DIGI_MAX];
int glength[DIGI_MAX];
#endif
/*
===================
=
= My stuff
=
===================
*/
int PlayerPainVocs[] =
{
DIGI_PLAYERPAIN1,
DIGI_PLAYERPAIN2,
DIGI_PLAYERPAIN3,
DIGI_PLAYERPAIN4,
DIGI_PLAYERPAIN5
};
// Don't have these sounds yet
int PlayerLowHealthPainVocs[] =
{
DIGI_HURTBAD1,
DIGI_HURTBAD2,
DIGI_HURTBAD3,
DIGI_HURTBAD4,
DIGI_HURTBAD5
};
int TauntAIVocs[] =
{
DIGI_TAUNTAI1,
DIGI_TAUNTAI2,
DIGI_TAUNTAI3,
DIGI_TAUNTAI4,
DIGI_TAUNTAI5,
DIGI_TAUNTAI6,
DIGI_TAUNTAI7,
DIGI_TAUNTAI8,
DIGI_TAUNTAI9,
DIGI_TAUNTAI10,
DIGI_COWABUNGA,
DIGI_NOCHARADE,
DIGI_TIMETODIE,
DIGI_EATTHIS,
DIGI_FIRECRACKERUPASS,
DIGI_HOLYCOW,
DIGI_HAHA2,
DIGI_HOLYPEICESOFCOW,
DIGI_HOLYSHIT,
DIGI_HOLYPEICESOFSHIT,
DIGI_PAYINGATTENTION,
DIGI_EVERYBODYDEAD,
DIGI_KUNGFU,
DIGI_HOWYOULIKEMOVE,
DIGI_HAHA3,
DIGI_NOMESSWITHWANG,
DIGI_RAWREVENGE,
DIGI_YOULOOKSTUPID,
DIGI_TINYDICK,
DIGI_NOTOURNAMENT,
DIGI_WHOWANTSWANG,
DIGI_MOVELIKEYAK,
DIGI_ALLINREFLEXES
};
int PlayerGetItemVocs[] =
{
DIGI_GOTITEM1,
DIGI_HAHA1,
DIGI_BANZAI,
DIGI_COWABUNGA,
DIGI_TIMETODIE
};
int PlayerYellVocs[] =
{
DIGI_PLAYERYELL1,
DIGI_PLAYERYELL2,
DIGI_PLAYERYELL3
};
#if 0
// DEBUG
void CheckSndData(char *file, int line)
{
short i;
//return;
for (i = 0; i<DIGI_MAX; i++)
{
if (!globsndata[i] && !globvpdata[i]) continue;
if (memcmp(globsndata[i], globvpdata[i], glength[i]) != 0)
{
printf("%s %d\n",file,line);
printf("CheckSndData: Data is not the same! num = %d",i);
exit(0);
}
}
}
#endif
//
// Routine called when a sound is finished playing
//
void
SoundCallBack(intptr_t num)
{
VOC_INFOp vp;
if ((int) num == MUSIC_ID)
{
return;
}
// RTS sounds are negative
if ((int)num < 0)
{
return;
}
vp = &voc[num];
// Update counter
//vp->playing--;
vp->lock--;
}
//
void
ClearSoundLocks(void)
{
unsigned i;
for (i = 0; i < SIZ(voc); i++)
{
if (voc[i].lock >= 200)
voc[i].lock = 199;
}
}
void
UnInitSound(void)
{
SoundShutdown();
MusicShutdown();
}
void
InitFX(void)
{
VOC_INFOp vp;
short i;
#if 0
// DEBUG
for (i=0; i<DIGI_MAX; i++)
{
globsndata[i] = globvpdata[i] = NULL;
glength[i] = 0;
}
#endif
//ExternalSoundMod();
// Select which cards to use
SoundStartup();
for (vp = voc; vp < &voc[SIZ(voc)]; vp++)
{
vp->playing = 0;
}
// Set up our fx callback so we can display the sounds that are playing
FX_SetCallBack(SoundCallBack);
}
void
InitMusic(void)
{
// Select which cards to use
MusicStartup();
//SendGeneralMidiSysX();
}
void
ExternalSoundMod(void)
{
FILE *fin;
VOC_INFOp vp;
char name[40];
char new_name[40];
int pri;
int pitch_lo, pitch_hi;
int ret;
fin = fopen("swextern.snd", "r");
if (!fin)
return;
while (TRUE)
{
ret = fscanf(fin, "%s %s %d %d", name, new_name, &pitch_lo, &pitch_hi);
if (ret == EOF)
break;
for (vp = voc; vp < &voc[SIZ(voc)]; vp++)
{
#if 0
if (!vp->name)
continue;
#endif
if (!Bstrcasecmp(name, vp->name))
{
// vp->priority = pri;
strcpy(vp->name, new_name);
vp->pitch_lo = pitch_lo;
vp->pitch_hi = pitch_hi;
}
}
}
fclose(fin);
}
extern short Level;
SWBOOL
PlaySong(char *song_file_name, int cdaudio_track, SWBOOL loop, SWBOOL restart)
{
if (!MusicEnabled())
{
return FALSE;
}
if (DemoMode)
return FALSE;
if (!restart)
{
if (SongType == SongTypeWave)
{
if (SongTrack > 0 && SongTrack == cdaudio_track)
{
// ogg replacement for a CD track
return TRUE;
}
else if (SongName && song_file_name && !strcmp(SongName, song_file_name))
{
return TRUE;
}
}
else if (SongType == SongTypeMIDI)
{
if (SongName && song_file_name && !strcmp(SongName, song_file_name))
{
return TRUE;
}
}
}
StopSong();
if (!SW_SHAREWARE)
{
if (cdaudio_track >= 0 && mus_redbook)
{
char waveformtrack[MAXWAVEFORMTRACKLENGTH];
Bstrncpy(waveformtrack, gs.WaveformTrackName, MAXWAVEFORMTRACKLENGTH - 1);
char *numPos = Bstrstr(waveformtrack, "??");
if (numPos && (numPos-waveformtrack) < MAXWAVEFORMTRACKLENGTH - 2)
{
static const char *tracktypes[] = { ".flac", ".ogg" };
const size_t tracknamebaselen = Bstrlen(waveformtrack);
size_t i;
numPos[0] = '0' + (cdaudio_track / 10) % 10;
numPos[1] = '0' + cdaudio_track % 10;
for (i = 0; i < ARRAY_SIZE(tracktypes); ++i)
{
waveformtrack[tracknamebaselen] = '\0';
Bstrncat(waveformtrack, tracktypes[i], MAXWAVEFORMTRACKLENGTH - 1);
if (LoadSong(waveformtrack))
{
SongVoice = FX_Play(SongPtr, SongLength, 0, 0, 0,
255, 255, 255, FX_MUSIC_PRIORITY, 1.f, MUSIC_ID);
if (SongVoice > FX_Ok)
{
SongType = SongTypeWave;
SongTrack = cdaudio_track;
SongName = Bstrdup(waveformtrack);
return TRUE;
}
}
}
buildprintf("Can't find CD track %i!\n", cdaudio_track);
}
else
{
buildprintf("Make sure to have \"??\" as a placeholder for the track number in your WaveformTrackName!\n");
buildprintf(" e.g. WaveformTrackName = \"MUSIC/Track??\"\n");
}
}
}
if (!song_file_name || !LoadSong(song_file_name))
{
return FALSE;
}
if (!memcmp(SongPtr, "MThd", 4))
{
MUSIC_PlaySong(SongPtr, SongLength, MUSIC_LoopSong);
SongType = SongTypeMIDI;
SongName = strdup(song_file_name);
return TRUE;
}
else
{
SongVoice = FX_Play(SongPtr, SongLength, 0, 0, 0,
255, 255, 255, FX_MUSIC_PRIORITY, 1.f, MUSIC_ID);
if (SongVoice > FX_Ok)
{
SongType = SongTypeWave;
SongName = strdup(song_file_name);
return TRUE;
}
}
return FALSE;
}
void
StopFX(void)
{
FX_StopAllSounds();
}
void
StopSong(void)
{
if (DemoMode)
return;
if (SongType == SongTypeWave && SongVoice > 0)
{
FX_StopSound(SongVoice);
SongVoice = 0;
}
else if (SongType == SongTypeMIDI)
{
MUSIC_StopSong();
}
SongType = SongTypeNone;
DO_FREE_AND_NULL(SongName);
SongTrack = 0;
if (SongPtr)
{
FreeMem(SongPtr);
SongPtr = 0;
SongLength = 0;
}
}
void
PauseSong(SWBOOL pauseon)
{
if (!MusicEnabled()) return;
if (SongType == SongTypeWave && SongVoice > 0)
{
FX_PauseVoice(SongVoice, pauseon);
}
}
SWBOOL
SongIsPlaying(void)
{
return FALSE;
}
void
StopSound(void)
{
StopFX();
StopSong();
}
//
// Sound Distance Calculation
//
#define MAXLEVLDIST 19000 // The higher the number, the further away you can hear sound
short SoundDist(int x, int y, int z, int basedist)
{
double tx, ty, tz;
double sqrdist,retval;
double decay,decayshift;
extern short screenpeek;
#define DECAY_CONST 4000
tx = fabs(Player[screenpeek].posx - x);
ty = fabs(Player[screenpeek].posy - y);
tz = fabs((Player[screenpeek].posz - z) >> 4);
// Use the Pythagreon Theorem to compute the magnitude of a 3D vector
sqrdist = fabs(tx*tx + ty*ty + tz*tz);
retval = sqrt(sqrdist);
if (basedist < 0) // if basedist is negative
{
short i;
decayshift=2;
decay = labs(basedist) / DECAY_CONST;
for (i=0; i<decay; i++)
decayshift *= 2;
if (fabs(double(basedist)/decayshift) >= retval)
retval = 0;
else
retval *= decay;
}
else
{
if (basedist > retval)
retval = 0;
else
retval -= basedist;
}
retval = retval * 256 / MAXLEVLDIST;
if (retval < 0) retval = 0;
if (retval > 255) retval = 255;
return retval;
}
//
// Angle calcuations - may need to be checked to make sure they are right
//
short SoundAngle(int x, int y)
{
extern short screenpeek;
short angle, delta_angle;
angle = getangle(x - Player[screenpeek].posx, y - Player[screenpeek].posy);
delta_angle = GetDeltaAngle(angle, Player[screenpeek].pang);
// convert a delta_angle to a real angle if negative
if (delta_angle < 0)
delta_angle = NORM_ANGLE((1024 + delta_angle) + 1024);
// convert 2048 degree angle to 128 degree angle
return delta_angle >> 4;
}
int _PlayerSound(const char *file, int line, int num, int *x, int *y, int *z, Voc3D_Flags flags, PLAYERp pp)
//PlayerSound(int num, int *x, int *y, int *z, Voc3D_Flags flags, PLAYERp pp)
{
int handle;
VOC_INFOp vp;
if (Prediction)
return 0;
if (pp < Player || pp >= Player + MAX_SW_PLAYERS)
{
TerminateGame();
printf("Player Sound invalid player: file %s, line %d\n",file,line);
exit(0);
}
PRODUCTION_ASSERT(pp >= Player && pp < Player+MAX_SW_PLAYERS);
PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);
if (TEST(pp->Flags, PF_DEAD)) return 0; // You're dead, no talking!
// If this is a player voice and he's already yacking, forget it.
vp = &voc[num];
if (vp == NULL)
{
TerminateGame();
printf("vp == NULL in PlayerSound, num = %d\n",num);
exit(0);
}
// Not a player voice, bail.
if (vp->priority != PRI_PLAYERVOICE && vp->priority != PRI_PLAYERDEATH)
return 0;
// He wasn't talking, but he will be now.
if (!pp->PlayerTalking)
{
pp->PlayerTalking = TRUE;
pp->TalkVocnum = num; // Set the voc number
pp->TalkVocHandle = PlaySound(num, x, y, z, flags); // Play the sound
if (pp->TalkVocHandle < 0)
{
pp->PlayerTalking = FALSE;
pp->TalkVocnum = -1;
pp->TalkVocHandle = -1;
}
}
return 0;
}
void LockSound(int num)
{
VOC_INFOp vp = &voc[num];
// if data is not locked
if (vp->lock <= CACHE_UNLOCK_MAX)
{
vp->lock = CACHE_LOCK_START;
}
else
// if data is already locked
{
vp->lock++;
if (vp->lock >= CACHE_LOCK_MAX || vp->lock == 0)
{
DumpSounds();
TerminateGame();
printf("lock > MAX, num = %d",num);
exit(0);
}
//ASSERT(vp->lock < CACHE_LOCK_MAX);
//ASSERT(vp->lock != 0);
}
}
SWBOOL CacheSound(int num, int type)
{
VOC_INFOp vp = &voc[num];
PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);
// if no data we need to cache it in
if (!vp->data)
{
FileReader handle;
int length;
if (!OpenSound(vp, handle, &length))
{
sprintf(ds,"Could not open sound %s, num %d, priority %d\n",vp->name,num,vp->priority);
OSD_Printf("%s", ds);
return FALSE;
}
if (vp != NULL)
{
//FILE *fp;
/*
if (type == CACHE_SOUND_PLAY)
// start it out locked at the min
vp->lock = CACHE_LOCK_START;
else
if (type == CACHE_SOUND_PRECACHE)
// start it out unlocked at the max
*/
vp->lock = CACHE_UNLOCK_MAX;
cacheAllocateBlock((intptr_t*)&vp->data, length, &vp->lock);
///////
ASSERT(vp->data);
ReadSound(handle, vp, length);
}
}
return TRUE;
}
////////////////////////////////////////////////////////////////////////////
// Play a sound
////////////////////////////////////////////////////////////////////////////
#define SOUND_UNIT MAXLEVLDIST/255
// NOTE: If v3df_follow == 1, x,y,z are considered literal coordinates
int PlaySound(int num, int *x, int *y, int *z, Voc3D_Flags flags)
{
VOC_INFOp vp;
VOC3D_INFOp v3p;
int pitch = 0;
short angle, sound_dist;
int tx, ty, tz;
uint8_t priority;
SPRITEp sp=NULL;
// DEBUG
//extern SWBOOL Pachinko_Win_Cheat;
// Don't play game sounds when in menus
//if (M_Active() && (*x!=0 || *y!=0 || *z!=0)) return(-1);
// Weed out parental lock sounds if PLock is active
if (adult_lockout || Global_PLock)
{
unsigned i;
for (i=0; i<sizeof(PLocked_Sounds); i++)
{
if (num == PLocked_Sounds[i])
return -1;
}
}
if (Prediction)
return -1;
if (!SoundEnabled())
return -1;
PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);
// Reset voice
voice = -1;
// This is used for updating looping sounds in Update3DSounds
if (Use_SoundSpriteNum && SoundSpriteNum >= 0)
{
ASSERT(SoundSpriteNum >= 0 && SoundSpriteNum < MAXSPRITES);
sp = &sprite[SoundSpriteNum];
}
if (snd_ambience && TEST(flags,v3df_ambient) && !TEST(flags,v3df_nolookup)) // Look for invalid ambient numbers
{
if (num < 0 || num > MAX_AMBIENT_SOUNDS)
{
sprintf(ds,"Invalid or out of range ambient sound number %d\n",num);
PutStringInfo(Player+screenpeek, ds);
return -1;
}
}
// Call queue management to add sound to play list.
// 3D sound manager will update playing sound 10x per second until
// the sound ends, at which time it is removed from both the 3D
// sound list as well as the actual cache.
v3p = Insert3DSound();
// If the ambient flag is set, do a name conversion to point to actual
// digital sound entry.
v3p->num = num;
v3p->priority = 0;
v3p->FX_Ok = FALSE; // Hasn't played yet
if (snd_ambience && TEST(flags,v3df_ambient) && !TEST(flags,v3df_nolookup))
{
v3p->maxtics = STD_RANDOM_RANGE(ambarray[num].maxtics);
flags |= ambarray[num].ambient_flags; // Add to flags if any
num = ambarray[num].diginame;
}
PRODUCTION_ASSERT(num >= 0 && num < DIGI_MAX);
// Assign voc to voc pointer
vp = &voc[num];
if (M_Active() && *x==0 && *y==0 && *z==0) // Menus sound outdo everything
priority = 100;
else
priority = vp->priority;
v3p->vp = vp;
// Assign voc info to 3d struct for future reference
v3p->x = x;
v3p->y = y;
v3p->z = z;
v3p->fx = *x;
v3p->fy = *y;
v3p->fz = *z;
v3p->flags = flags;
if (flags & v3df_follow)
{
tx = *x;
ty = *y;
if (!z)
tz = 0; // Some sound calls don't have a z
// value
else
tz = *z;
}
else
{
// Don't use pointers to coordinate values.
tx = v3p->fx;
ty = v3p->fy;
tz = v3p->fz;
}
// Special case stuff for sounds being played in a level
if (*x==0 && *y==0 && *z==0)
tx = ty = tz = 0;
if ((vp->voc_flags & vf_loop) && Use_SoundSpriteNum && SoundSpriteNum >= 0 && sp)
{
tx=sp->x;
ty=sp->y;
tz=sp->z;
//CON_Message("Using sp to set tx=%ld,ty=%ld,tz=%ld",tx,ty,tz);
}
// Calculate sound angle
if (flags & v3df_dontpan) // If true, don't do panning
angle = 0;
else
angle = SoundAngle(tx, ty);
// Calculate sound distance
if (tx == 0 && ty == 0 && tz == 0)
sound_dist = 255; // Special case for menus sounds,etc.
else
sound_dist = SoundDist(tx, ty, tz, vp->voc_distance);
v3p->doplr_delta = sound_dist; // Save of distance for doppler
// effect
// //DSPRINTF(ds,"sound dist = %d\n",sound_dist);
// MONO_PRINT(ds);
// Can the ambient sound see the player? If not, tone it down some.
if ((vp->voc_flags & vf_loop) && Use_SoundSpriteNum && SoundSpriteNum >= 0)
{
PLAYERp pp = Player+screenpeek;
//MONO_PRINT("PlaySound:Checking sound cansee");
if (!FAFcansee(tx, ty, tz, sp->sectnum,pp->posx, pp->posy, pp->posz, pp->cursectnum))
{
//MONO_PRINT("PlaySound:Reducing sound distance");
sound_dist += ((sound_dist/2)+(sound_dist/4)); // Play more quietly
if (sound_dist > 255) sound_dist = 255;
// Special Cases
if (num == DIGI_WHIPME) sound_dist = 255;
}
}
// Assign ambient priorities based on distance
if (snd_ambience && TEST(flags, v3df_ambient))
{
v3p->priority = v3p->vp->priority - (sound_dist / 26);
priority = v3p->priority;
}
if (!CacheSound(num, CACHE_SOUND_PLAY))
{
v3p->flags = v3df_kill;
v3p->handle = -1;
v3p->dist = 0;
v3p->deleted = TRUE; // Sound init failed, remove it!
return -1;
}
LockSound(num);
if (sound_dist < 5)
angle = 0;
// Check for pitch bending
if (vp->pitch_lo > vp->pitch_hi)
ASSERT(vp->pitch_lo <= vp->pitch_hi);
if (vp->pitch_hi == vp->pitch_lo)
pitch = vp->pitch_lo;
else if (vp->pitch_hi != vp->pitch_lo)
pitch = vp->pitch_lo + (STD_RANDOM_RANGE(vp->pitch_hi - vp->pitch_lo));
#if 0
// DEBUG
if (Pachinko_Win_Cheat)
{
CheckSndData(__FILE__, __LINE__);
Pachinko_Win_Cheat = FALSE;
CON_Message("S O U N D S C H E C K E D");
}
#endif
// Request playback and play it as a looping sound if flag is set.
if (vp->voc_flags & vf_loop)
{
short loopvol=0;
if ((loopvol = 255-sound_dist) <= 0)
loopvol = 0;
if (sound_dist < 255 || (flags & v3df_init))
{
voice = FX_Play((char *)vp->data, vp->datalen, 0, 0,
pitch, loopvol, loopvol, loopvol, priority, 1.f, num); // [JM] Should probably utilize floating point volume. !CHECKME!
}
else
voice = -1;
}
else
//if(!flags & v3df_init) // If not initing sound, play it
if (tx==0 && ty==0 && tz==0) // It's a non-inlevel sound
{
voice = FX_Play((char *)vp->data, vp->datalen, -1, -1, pitch, 255, 255, 255, priority, 1.f, num); // [JM] And here !CHECKME!
}
else // It's a 3d sound
{
if (sound_dist < 255)
{
voice = FX_Play3D((char *)vp->data, vp->datalen, FX_ONESHOT, pitch, angle, sound_dist, priority, 1.f, num); // [JM] And here !CHECKME!
}
else
voice = -1;
}
// If sound played, update our counter
if (voice > FX_Ok)
{
//vp->playing++;
v3p->FX_Ok = TRUE;
}
else
{
vp->lock--;
}
// Assign voc info to 3d struct for future reference
v3p->handle = voice; // Save the current voc handle in struct
v3p->dist = sound_dist;
v3p->tics = 0; // Reset tics
if (flags & v3df_init)
v3p->flags ^= v3df_init; // Turn init off now
return voice;
}
void PlaySoundRTS(int rts_num)
{
char *rtsptr;
int voice=-1;
if (!RTS_IsInitialized() || !SoundEnabled())
return;
rtsptr = (char *)RTS_GetSound(rts_num - 1);
ASSERT(rtsptr);
voice = FX_Play3D(rtsptr, RTS_SoundLength(rts_num - 1), FX_ONESHOT, 0, 0, 0, 255, 1.f, -rts_num); // [JM] Float volume here too I bet. !CHECKME!
}
///////////////////////////////////////////////
SWBOOL
OpenSound(VOC_INFOp vp, FileReader &handle, int *length)
{
handle = kopenFileReader(vp->name, 0);
if (!handle.isOpen())
{
return FALSE;
}
*length = handle.GetLength();
return TRUE;
}
int
ReadSound(FileReader &handle, VOC_INFOp vp, int length)
{
if (handle.Read(vp->data, length) != length)
{
TerminateGame();
printf("Error reading file '%s'.\n", vp->name);
exit(0);
}
vp->datalen = length;
return 0;
}
SWBOOL
LoadSong(const char *filename)
{
auto fr = kopenFileReader(filename, 0);
if (!fr.isOpen())
{
return FALSE;
}
auto size = fr.GetLength();
auto ptr = (char *) AllocMem(size);
if (ptr == NULL)
{
return FALSE;
}
if (fr.Read(ptr, size) != size)
{
FreeMem(ptr);
return FALSE;
}
SongPtr = ptr;
SongLength = size;
return TRUE;
}
void
SoundStartup(void)
{
void *initdata = 0;
#ifdef MIXERTYPEWIN
initdata = (void *) win_gethwnd();
#endif
//snd_enabled = TRUE;
int status = FX_Init(snd_numvoices, snd_numchannels, snd_mixrate, initdata);
if (status != FX_Ok)
{
buildprintf("Sound error: %s\n", FX_ErrorString(status));
return;
}
FxInitialized = TRUE;
snd_fxvolume.Callback();
snd_reversestereo.Callback();
FX_SetCallBack(SoundCallBack);
}
/*
===================
=
= SoundShutdown
=
===================
*/
void
SoundShutdown(void)
{
if (!FxInitialized)
return;
int status = FX_Shutdown();
if (status != FX_Ok)
{
buildprintf("Sound error: %s\n", FX_ErrorString(status));
}
}
/*
===================
=
= MusicStartup
=
===================
*/
void MusicStartup(void)
{
// if they chose None lets return
buildprintf("Initializing MIDI driver... ");
int status;
if ((status = MUSIC_Init(MusicDevice)) == MUSIC_Ok)
{
if (MusicDevice == ASS_AutoDetect)
MusicDevice = MIDI_GetDevice();
}
else if ((status = MUSIC_Init(ASS_AutoDetect)) == MUSIC_Ok)
{
MusicDevice = MIDI_GetDevice();
}
else
{
buildprintf("Music error: %s\n", MUSIC_ErrorString(status));
return;
}
MusicInitialized = TRUE;
MUSIC_SetVolume(mus_volume);
auto fil = kopenFileReader("swtimbr.tmb", 0);
if (fil.isOpen())
{
auto tmb = fil.Read();
if (tmb.Size())
AL_RegisterTimbreBank(tmb.Data());
}
}
void COVER_SetReverb(int amt)
{
FX_SetReverb(amt);
}
/*
===================
=
= MusicShutdown
=
===================
*/
void MusicShutdown(void)
{
StopSong();
int status = MUSIC_Shutdown();
if (status != MUSIC_Ok)
{
buildprintf("Music error: %s\n", MUSIC_ErrorString(status));
}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////
//
// 3D sound engine
// Sound management routines that keep a list of
// all sounds being played in a level.
// Doppler and Panning effects are achieved here.
//
///////////////////////////////////////////////
// Declare and initialize linked list of vocs.
VOC3D_INFOp voc3dstart = NULL;
VOC3D_INFOp voc3dend = NULL;
//////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////
// Initialize new vocs in the 3D sound queue
///////////////////////////////////////////////
VOC3D_INFOp
InitNew3DSound(VOC3D_INFOp v3p)
{
v3p->handle = -1; // Initialize handle to new sound
// value
v3p->owner = -1;
v3p->deleted = FALSE; // Used for when sound gets deleted
return v3p;
}
///////////////////////////////////////////////
// Inserts new vocs in the 3D sound queue
///////////////////////////////////////////////
VOC3D_INFOp
Insert3DSound(void)
{
VOC3D_INFOp vp, old;
// Allocate memory for new sound
// You can allocate new sounds as long as memory holds out.
// If you run out of memory for sounds, you got problems anyway.
vp = (VOC3D_INFOp) AllocMem(sizeof(VOC3D_INFO));
ASSERT(vp != NULL);
memset(vp,0xCC,sizeof(VOC3D_INFO)); // Zero out the memory
if (!voc3dend) // First item in list
{
vp->next = vp->prev = NULL;
voc3dend = vp;
voc3dstart = vp;
InitNew3DSound(vp);
return vp;
}
old = voc3dend; // Put it on the end
old->next = vp;
vp->next = NULL;
vp->prev = old;
voc3dend = vp;
InitNew3DSound(vp);
return vp;
}
/////////////////////////////////////////////////////
// Deletes vocs in the 3D sound queue with no owners
/////////////////////////////////////////////////////
void
DeleteNoSoundOwner(short spritenum)
{
VOC3D_INFOp vp, dp;
vp = voc3dstart;
while (vp)
{
dp = NULL;
if (vp->owner == spritenum && vp->owner >= 0 && (vp->vp->voc_flags & vf_loop))
{
//DSPRINTF(ds,"Deleting owner %d\n",vp->owner);
//MONO_PRINT(ds);
// Make sure to stop active
// sounds
if (FX_SoundValidAndActive(vp->handle))
{
FX_StopSound(vp->handle);
vp->handle = 0;
}
#if 0
// Clean up the sound active counter for cacheing locks
if (vp->FX_Ok) // Only decrement if sound ever played
vp->vp->playing--; // Decrement instance of sound playing
if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
vp->vp->lock = CACHE_UNLOCK_MAX;
#endif
dp = vp; // Point to sound to be deleted
if (vp->prev)
{
vp->prev->next = vp->next;
}
else
{
voc3dstart = vp->next; // New first item
if (voc3dstart)
voc3dstart->prev = NULL;
}
if (vp->next)
{
vp->next->prev = vp->prev; // Middle element
}
else
{
voc3dend = vp->prev; // Delete last element
}
}
vp = vp->next;
if (dp != NULL)
FreeMem(dp); // Return memory to heap
}
}
// This is called from KillSprite to kill a follow sound with no valid sprite owner
// Stops and active sound with the follow bit set, even play once sounds.
void DeleteNoFollowSoundOwner(short spritenum)
{
VOC3D_INFOp vp, dp;
SPRITEp sp = &sprite[spritenum];
vp = voc3dstart;
while (vp)
{
dp = NULL;
// If the follow flag is set, compare the x and y addresses.
if ((vp->flags & v3df_follow) && vp->x == &sp->x && vp->y == &sp->y)
{
if (FX_SoundValidAndActive(vp->handle))
{
FX_StopSound(vp->handle);
vp->handle = 0;
}
#if 0
if (vp->FX_Ok) // Only decrement if sound ever played
vp->vp->playing--; // Decrement instance of sound playing
if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
vp->vp->lock = CACHE_UNLOCK_MAX;
#endif
dp = vp; // Point to sound to be deleted
if (vp->prev)
{
vp->prev->next = vp->next;
}
else
{
voc3dstart = vp->next; // New first item
if (voc3dstart)
voc3dstart->prev = NULL;
}
if (vp->next)
{
vp->next->prev = vp->prev; // Middle element
}
else
{
voc3dend = vp->prev; // Delete last element
}
}
vp = vp->next;
if (dp != NULL)
FreeMem(dp); // Return memory to heap
}
}
///////////////////////////////////////////////
// Deletes vocs in the 3D sound queue
///////////////////////////////////////////////
void
Delete3DSounds(void)
{
VOC3D_INFOp vp, dp;
PLAYERp pp;
int cnt=0;
vp = voc3dstart;
while (vp)
{
dp = NULL;
if (vp->deleted)
{
#if 0
if (vp->FX_Ok) // Only decrement if sound ever played
vp->vp->playing--; // Decrement instance of sound playing
if (vp->vp->playing == 0 && vp->vp->lock > CACHE_UNLOCK_MAX)
vp->vp->lock = CACHE_UNLOCK_MAX;
#endif
//DSPRINTF(ds,"Delete3DSounds: deleting owner %d digi %d\n",vp->owner,vp->num);
//MONO_PRINT(ds);
// Reset Player talking flag if a voice was deleted
//if(vp->num > DIGI_FIRSTPLAYERVOICE && vp->num < DIGI_LASTPLAYERVOICE)
if (!vp->vp)
{
printf("Delete3DSounds(): NULL vp->vp\n");
}
else // JBF: added null check
if (vp->vp->priority == PRI_PLAYERVOICE || vp->vp->priority == PRI_PLAYERDEATH)
{
int16_t pnum;
TRAVERSE_CONNECT(pnum)
{
pp = &Player[pnum];
if (vp->num == pp->TalkVocnum)
{
pp->PlayerTalking = FALSE;
pp->TalkVocnum = -1;
pp->TalkVocHandle = -1;
//DSPRINTF(ds,"DELETED PLAYER VOICE VOC! NUM=%d\n",vp->num);
//MONO_PRINT(ds);
}
}
}
dp = vp; // Point to sound to be deleted
if (vp->prev)
{
vp->prev->next = vp->next;
}
else
{
voc3dstart = vp->next; // New first item
if (voc3dstart)
voc3dstart->prev = NULL;
}
if (vp->next)
{
vp->next->prev = vp->prev; // Middle element
}
else
{
voc3dend = vp->prev; // Delete last element
}
}
vp = vp->next;
if (dp != NULL)
{
FreeMem(dp); // Return memory to heap
}
}
}
////////////////////////////////////////////////////////////////////////////
// Play a sound
////////////////////////////////////////////////////////////////////////////
int
RandomizeAmbientSpecials(int handle)
{
#define MAXRNDAMB 12
int ambrand[] =
{
56,57,58,59,60,61,62,63,64,65,66,67
};
short i;
// If ambient sound is found in the array, randomly pick a new sound
for (i=0; i<MAXRNDAMB; i++)
{
if (handle == ambrand[i])
return ambrand[STD_RANDOM_RANGE(MAXRNDAMB-1)];
}
return handle; // Give back the sound, no new one was found
}
void
DoTimedSound(VOC3D_INFOp p)
{
p->tics += synctics;
if (p->tics >= p->maxtics)
{
if (!FX_SoundValidAndActive(p->handle))
{
// Check for special case ambient sounds
p->num = RandomizeAmbientSpecials(p->num);
// Sound was bumped from active sounds list, try to play again.
// Don't bother if voices are already maxed out.
if (FX_SoundsPlaying() < snd_numvoices)
{
if (p->flags & v3df_follow)
{
PlaySound(p->num, p->x, p->y, p->z, p->flags);
p->deleted = TRUE; // Mark old sound for deletion
}
else
{
PlaySound(p->num, &p->fx, &p->fy, &p->fz, p->flags);
p->deleted = TRUE; // Mark old sound for deletion
}
}
}
p->tics = 0;
//while (p->tics >= p->maxtics) // Really stupid thing to do!
// {
// p->tics -= p->maxtics;
// }
}
}
void
StopAmbientSound(void)
{
VOC3D_INFOp p;
extern SWBOOL InMenuLevel;
if (InMenuLevel) return;
p = voc3dstart;
while (p)
{
// kill ambient sounds if Ambient is off
if (TEST(p->flags,v3df_ambient))
SET(p->flags, v3df_kill);
if (p->flags & v3df_kill)
{
if (FX_SoundValidAndActive(p->handle))
{
FX_StopSound(p->handle); // Make sure to stop active sounds
p->handle = 0;
}
p->deleted = TRUE;
}
p = p->next;
}
Delete3DSounds();
}
void
StartAmbientSound(void)
{
VOC3D_INFOp p;
short i,nexti;
extern SWBOOL InMenuLevel;
if (InMenuLevel) return; // Don't restart ambience if no level is active! Will crash game.
TRAVERSE_SPRITE_STAT(headspritestat[STAT_AMBIENT], i, nexti)
{
SPRITEp sp = &sprite[i];
PlaySound(sp->lotag, &sp->x, &sp->y, &sp->z, v3df_ambient | v3df_init
| v3df_doppler | v3df_follow);
Set3DSoundOwner(i); // Ambient sounds need this to get sectnum for later processing
}
}
///////////////////////////////////////////////
// Main function to update 3D sound array
///////////////////////////////////////////////
typedef struct
{
VOC3D_INFOp p;
short dist;
uint8_t priority;
} TVOC_INFO, *TVOC_INFOp;
void
DoUpdateSounds3D(void)
{
VOC3D_INFOp p;
SWBOOL looping;
int pitch = 0, pitchmax;
int delta;
short dist, angle;
SWBOOL deletesound = FALSE;
TVOC_INFO TmpVocArray[32];
int i;
static SWBOOL MoveSkip8 = 0;
if (M_Active()) return;
// This function is already only call 10x per sec, this widdles it down even more!
MoveSkip8 = (MoveSkip8 + 1) & 15;
//CON_Message("Sounds Playing = %d",FX_SoundsPlaying());
// Zero out the temporary array
//memset(&TmpVocArray[0],0,sizeof(TmpVocArray));
for (i=0; i<32; i++)
{
TmpVocArray[i].p = NULL;
TmpVocArray[i].dist = 0;
TmpVocArray[i].priority = 0;
}
p = voc3dstart;
while (p)
{
ASSERT(p->num >= 0 && p->num < DIGI_MAX);
looping = p->vp->voc_flags & vf_loop;
// //DSPRINTF(ds,"sound %d FX_SoundActive = %d\n,",p->num,FX_SoundActive(p->handle));
// MONO_PRINT(ds);
// If sprite owner is dead, kill this sound as long as it isn't ambient
if (looping && p->owner == -1 && !TEST(p->flags,v3df_ambient))
{
SET(p->flags, v3df_kill);
}
// Is the sound slated for death? Kill it, otherwise play it.
if (p->flags & v3df_kill)
{
if (FX_SoundValidAndActive(p->handle))
{
FX_StopSound(p->handle); // Make sure to stop active sounds
p->handle = 0;
}
//DSPRINTF(ds,"%d had v3df_kill.\n",p->num);
//MONO_PRINT(ds);
p->deleted = TRUE;
}
else
{
if (!FX_SoundValidAndActive(p->handle) && !looping)
{
if (p->flags & v3df_intermit)
{
DoTimedSound(p);
}
else
//if(p->owner == -1 && !TEST(p->flags,v3df_ambient))
{
//DSPRINTF(ds,"%d is now inactive.\n",p->num);
//MONO_PRINT(ds);
p->deleted = TRUE;
}
}
else if (FX_SoundValidAndActive(p->handle))
{
if (p->flags & v3df_follow)
{
dist = SoundDist(*p->x, *p->y, *p->z, p->vp->voc_distance);
angle = SoundAngle(*p->x, *p->y);
}
else
{
if (p->fx == 0 && p->fy == 0 && p->fz == 0)
dist = 0;
else
dist = SoundDist(p->fx, p->fy, p->fz, p->vp->voc_distance);
angle = SoundAngle(p->fx, p->fy);
}
// Can the ambient sound see the player? If not, tone it down some.
if ((p->vp->voc_flags & vf_loop) && p->owner != -1)
{
PLAYERp pp = Player+screenpeek;
SPRITEp sp = &sprite[p->owner];
//MONO_PRINT("Checking sound cansee");
if (!FAFcansee(sp->x, sp->y, sp->z, sp->sectnum,pp->posx, pp->posy, pp->posz, pp->cursectnum))
{
//MONO_PRINT("Reducing sound distance");
dist += ((dist/2)+(dist/4)); // Play more quietly
if (dist > 255) dist = 255;
// Special cases
if (p->num == 76 && TEST(p->flags,v3df_ambient))
{
dist = 255; // Cut off whipping sound, it's secret
}
}
}
if (dist >= 255 && p->vp->voc_distance == DIST_NORMAL)
{
FX_StopSound(p->handle); // Make sure to stop active
p->handle = 0;
// sounds
}
else
{
// Handle Panning Left and Right
if (!(p->flags & v3df_dontpan))
FX_Pan3D(p->handle, angle, dist);
else
FX_Pan3D(p->handle, 0, dist);
// Handle Doppler Effects
#define DOPPLERMAX 400
if (!(p->flags & v3df_doppler) && FX_SoundActive(p->handle))
{
pitch -= (dist - p->doplr_delta);
if (p->vp->pitch_lo != 0 && p->vp->pitch_hi != 0)
{
if (abs(p->vp->pitch_lo) > abs(p->vp->pitch_hi))
pitchmax = abs(p->vp->pitch_lo);
else
pitchmax = abs(p->vp->pitch_hi);
}
else
pitchmax = DOPPLERMAX;
if (pitch > pitchmax)
pitch = pitchmax;
if (pitch < -pitchmax)
pitch = -pitchmax;
p->doplr_delta = dist; // Save new distance to
// struct
FX_SetPitch(p->handle, pitch);
}
}
}
else if (!FX_SoundValidAndActive(p->handle) && looping)
{
if (p->flags & v3df_follow)
{
dist = SoundDist(*p->x, *p->y, *p->z, p->vp->voc_distance);
angle = SoundAngle(*p->x, *p->y);
}
else
{
dist = SoundDist(p->fx, p->fy, p->fz, p->vp->voc_distance);
angle = SoundAngle(p->fx, p->fy);
}
// Sound was bumped from active sounds list, try to play
// again.
// Don't bother if voices are already maxed out.
// Sort looping vocs in order of priority and distance
//if (FX_SoundsPlaying() < snd_numvoices && dist <= 255)
if (dist <= 255)
{
for (i=0; i<min((int)SIZ(TmpVocArray), *snd_numvoices); i++)
{
if (p->priority >= TmpVocArray[i].priority)
{
if (!TmpVocArray[i].p || dist < TmpVocArray[i].dist)
{
ASSERT(p->num >= 0 && p->num < DIGI_MAX);
TmpVocArray[i].p = p;
TmpVocArray[i].dist = dist;
TmpVocArray[i].priority = p->priority;
break;
}
}
}
}
} // !FX_SoundActive
} // if(p->flags & v3df_kill)
p = p->next;
} // while(p)
// //DSPRINTF(ds,"Num vocs in list: %d, Sounds playing: %d\n",numelems,FX_SoundsPlaying());
// MONO_PRINT(ds);
// Process all the looping sounds that said they wanted to get back in
// Only update these sounds 5x per second! Woo hoo!, aren't we optimized now?
//if(MoveSkip8==0)
// {
for (i=0; i<min((int)SIZ(TmpVocArray), *snd_numvoices); i++)
{
int handle;
p = TmpVocArray[i].p;
//if (FX_SoundsPlaying() >= snd_numvoices || !p) break;
if (!p) break;
ASSERT(p->num >= 0 && p->num < DIGI_MAX);
if (p->flags & v3df_follow)
{
if (p->owner == -1)
{
int enumber;
enumber = p->num;
TerminateGame();
printf("Owner == -1 on looping sound with follow flag set!\n");
printf("p->num = %d\n",enumber);
exit(0);
}
Use_SoundSpriteNum = TRUE;
SoundSpriteNum = p->owner;
handle = PlaySound(p->num, p->x, p->y, p->z, p->flags);
//if(handle >= 0 || TEST(p->flags,v3df_ambient)) // After a valid PlaySound, it's ok to use voc3dend
voc3dend->owner = p->owner; // Transfer the owner
p->deleted = TRUE;
Use_SoundSpriteNum = FALSE;
SoundSpriteNum = -1;
//MONO_PRINT("TmpVocArray playing a follow sound");
}
else
{
if (p->owner == -1)
{
int enumber;
enumber = p->num;
TerminateGame();
printf("Owner == -1 on looping sound, no follow flag.\n");
printf("p->num = %d\n",enumber);
exit(0);
}
Use_SoundSpriteNum = TRUE;
SoundSpriteNum = p->owner;
handle = PlaySound(p->num, &p->fx, &p->fy, &p->fz, p->flags);
//if(handle >= 0 || TEST(p->flags,v3df_ambient))
voc3dend->owner = p->owner; // Transfer the owner
p->deleted = TRUE;
Use_SoundSpriteNum = FALSE;
SoundSpriteNum = -1;
//MONO_PRINT("TmpVocArray playing a non-follow sound");
}
}
// } // MoveSkip8
// Clean out any deleted sounds now
Delete3DSounds();
}
//////////////////////////////////////////////////
// Terminate the sounds list
//////////////////////////////////////////////////
void
Terminate3DSounds(void)
{
VOC3D_INFOp vp;
vp = voc3dstart;
while (vp)
{
if (vp->handle > 0)
FX_StopSound(vp->handle); // Make sure to stop active sounds
vp->handle = 0;
vp->deleted = TRUE;
vp = vp->next;
}
Delete3DSounds(); // Now delete all remaining sounds
ClearSoundLocks();
}
void
DumpSounds(void)
{
VOC3D_INFOp vp;
vp = voc3dstart;
while (vp)
{
if (TEST(vp->flags,v3df_ambient))
sprintf(ds,"vp->num=%d, vp->owner=%d, vp->vp->lock=%d",ambarray[vp->num].diginame,vp->owner,vp->vp->lock);
else
sprintf(ds,"vp->num=%d, vp->owner=%d, vp->vp->lock=%d",vp->num,vp->owner,vp->vp->lock);
DebugWriteString(ds);
if (vp->owner >= 0)
{
SPRITEp sp = &sprite[vp->owner];
sprintf(ds,"sp->picnum=%d, sp->hitag=%d, sp->lotag=%d, sp->owner=%d\n",TrackerCast(sp->picnum), TrackerCast(sp->hitag), TrackerCast(sp->lotag), TrackerCast(sp->owner));
DebugWriteString(ds);
}
vp = vp->next;
}
}
//////////////////////////////////////////////////
// Set owner to check when to kill looping sounds
// Must be called immediately after PlaySound call
// since this only assigns value to last sound
// on voc list
//////////////////////////////////////////////////
void
Set3DSoundOwner(short spritenum)
{
VOC3D_INFOp p;
// ASSERT(p->handle != -1); // Check for bogus sounds
p = voc3dend;
if (!p) return;
// Queue up sounds with ambient flag even if they didn't play right away!
if (p->handle != -1 || TEST(p->flags,v3df_ambient))
{
p->owner = spritenum;
}
else
{
p->deleted = TRUE;
p->flags = v3df_kill;
}
}
//////////////////////////////////////////////////
// Play a sound using special sprite setup
//////////////////////////////////////////////////
void
PlaySpriteSound(short spritenum, int attrib_ndx, Voc3D_Flags flags)
{
SPRITEp sp = &sprite[spritenum];
USERp u = User[spritenum];
ASSERT(u);
// //DSPRINTF(ds,"index = %d, digi num = %d\n",attrib_ndx,u->Attrib->Sounds[attrib_ndx]);
// MONO_PRINT(ds);
PlaySound(u->Attrib->Sounds[attrib_ndx], &sp->x, &sp->y, &sp->z, flags);
}
END_SW_NS