//------------------------------------------------------------------------- /* 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" #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; iplaying--; 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; iplaying = 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); } 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); } } void SetSongVolume(int volume) { } 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= 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 32 degree angle return delta_angle >> 6; } 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); CON_ConMessage("%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 (UsingMenus && (*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= 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 (UsingMenus && *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)); mus_enabled = FALSE; 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_SoundActive(vp->handle)) { FX_StopSound(vp->handle); } #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_SoundActive(vp->handle)) { FX_StopSound(vp->handle); } #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; itics += synctics; if (p->tics >= p->maxtics) { if (!FX_SoundActive(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 } } } // !FX_SoundActive 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_SoundActive(p->handle)) FX_StopSound(p->handle); // Make sure to stop active sounds 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 (UsingMenus) 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_SoundActive(p->handle)) FX_StopSound(p->handle); // Make sure to stop active sounds //DSPRINTF(ds,"%d had v3df_kill.\n",p->num); //MONO_PRINT(ds); p->deleted = TRUE; } else { if (!FX_SoundActive(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_SoundActive(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 // 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_SoundActive(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; ipriority >= 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= 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) { FX_StopSound(vp->handle); // Make sure to stop active sounds 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