gzdoom/src/s_sound.cpp
Randy Heit 84d125cf21 - Fixed: Actor-less sounds that aren't played on CHAN_AUTO should still be
subject to channel overriding.
- Re-added priority selection based on sound usage.
- Reduced the number of virtual channels to match the number of real
  channels.
- Added customizable rolloff, including Doom's standard linear gain rolloff.
  SNDINFO commands are:
    $rolloff <sound> <min distance> <max distance>          -- linear gain (like Doom)
    $rolloff <sound> linear <min distance> <max distance>   -- linear volume
    $rolloff <sound> log <min distance> <rolloff factor>    -- logarithmic
    $rolloff <sound> custom <min distance> <max distance>   -- use SNDCURVE lump
  Anything closer than min distance is full volume and anything further than
  max distance is inaudible. Logarithmic rolloff does not have a maximum
  distance; it has a scalar that controls how quickly the volume drops off
  instead.


SVN r834 (trunk)
2008-03-22 03:33:41 +00:00

1769 lines
41 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id: s_sound.c,v 1.3 1998/01/05 16:26:08 pekangas Exp $
//
// 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: none
//
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <io.h>
#endif
#include <fcntl.h>
#include "m_alloc.h"
#include "i_system.h"
#include "i_sound.h"
#include "i_music.h"
#include "i_cd.h"
#include "s_sound.h"
#include "s_sndseq.h"
#include "s_playlist.h"
#include "c_dispatch.h"
#include "m_random.h"
#include "w_wad.h"
#include "doomdef.h"
#include "p_local.h"
#include "doomstat.h"
#include "cmdlib.h"
#include "v_video.h"
#include "v_text.h"
#include "a_sharedglobal.h"
#include "gstrings.h"
#include "vectors.h"
#include "gi.h"
#include "templates.h"
#include "zstring.h"
// MACROS ------------------------------------------------------------------
#ifdef NeXT
// NeXT doesn't need a binary flag in open call
#define O_BINARY 0
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
#define SELECT_ATTEN(a) ((a)==ATTN_NONE ? 0 : (a)==ATTN_STATIC ? 3 : 1)
#ifndef FIXED2FLOAT
#define FIXED2FLOAT(f) (((float)(f))/(float)65536)
#endif
#define NORM_PITCH 128
#define NORM_PRIORITY 64
#define NORM_SEP 0
#define S_PITCH_PERTURB 1
#define S_STEREO_SWING 0.75
// TYPES -------------------------------------------------------------------
struct MusPlayingInfo
{
FString name;
void *handle;
int baseorder;
bool loop;
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
extern float S_GetMusicVolume (const char *music);
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
static fixed_t P_AproxDistance2 (fixed_t *listener, fixed_t x, fixed_t y);
static void S_StartSound (fixed_t *pt, AActor *mover, int channel,
int sound_id, float volume, float attenuation, bool looping);
static void S_ActivatePlayList (bool goBack);
static void CalcPosVel (fixed_t *pt, AActor *mover, int constz, float pos[3],
float vel[3]);
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static bool SoundPaused; // whether sound effects are paused
static bool MusicPaused; // whether music is paused
static MusPlayingInfo mus_playing; // music currently being played
static FString LastSong; // last music that was played
static FPlayList *PlayList;
// PUBLIC DATA DEFINITIONS -------------------------------------------------
int sfx_empty;
FSoundChan *Channels;
FSoundChan *FreeChannels;
int S_RolloffType;
float S_MinDistance;
float S_MaxDistanceOrRolloffFactor;
BYTE *S_SoundCurve;
int S_SoundCurveSize;
CVAR (Bool, snd_surround, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // [RH] Use surround sounds?
FBoolCVar noisedebug ("noise", false, 0); // [RH] Print sound debugging info?
CVAR (Int, snd_channels, 32, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // number of channels available
CVAR (Bool, snd_flipstereo, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
// CODE --------------------------------------------------------------------
//==========================================================================
//
// P_AproxDistance2
//
//==========================================================================
static fixed_t P_AproxDistance2 (fixed_t *listener, fixed_t x, fixed_t y)
{
// calculate the distance to sound origin
// and clip it if necessary
if (listener)
{
fixed_t adx = abs (listener[0] - x);
fixed_t ady = abs (listener[1] - y);
// From _GG1_ p.428. Appox. eucledian distance fast.
return adx + ady - ((adx < ady ? adx : ady)>>1);
}
else
return 0;
}
static fixed_t P_AproxDistance2 (AActor *listener, fixed_t x, fixed_t y)
{
return listener ? P_AproxDistance2 (&listener->x, x, y) : 0;
}
//==========================================================================
//
// S_NoiseDebug
//
// [RH] Print sound debug info. Called by status bar.
//==========================================================================
void S_NoiseDebug (void)
{
FSoundChan *chan;
fixed_t ox, oy;
int y, color;
y = 32 * CleanYfac;
screen->DrawText (CR_YELLOW, 0, y, "*** SOUND DEBUG INFO ***", TAG_DONE);
y += 8;
screen->DrawText (CR_GOLD, 0, y, "name", TAG_DONE);
screen->DrawText (CR_GOLD, 70, y, "x", TAG_DONE);
screen->DrawText (CR_GOLD, 120, y, "y", TAG_DONE);
screen->DrawText (CR_GOLD, 170, y, "vol", TAG_DONE);
screen->DrawText (CR_GOLD, 200, y, "dist", TAG_DONE);
screen->DrawText (CR_GOLD, 240, y, "chan", TAG_DONE);
y += 8;
if (Channels == NULL)
{
return;
}
// Display the last channel first.
for (chan = Channels; chan->NextChan != NULL; chan = chan->NextChan)
{ }
while (y < SCREENHEIGHT - 16)
{
char temp[16];
fixed_t *origin = chan->Pt;
if (!chan->Is3D)
{
ox = players[consoleplayer].camera->x;
oy = players[consoleplayer].camera->y;
}
else if (origin)
{
ox = origin[0];
oy = origin[1];
}
else
{
ox = chan->X;
oy = chan->Y;
}
color = chan->Loop ? CR_BROWN : CR_GREY;
Wads.GetLumpName (temp, chan->SfxInfo->lumpnum);
temp[8] = 0;
screen->DrawText (color, 0, y, temp, TAG_DONE);
sprintf (temp, "%d", ox >> FRACBITS);
screen->DrawText (color, 70, y, temp, TAG_DONE);
sprintf (temp, "%d", oy >> FRACBITS);
screen->DrawText (color, 120, y, temp, TAG_DONE);
sprintf (temp, "%g", chan->Volume);
screen->DrawText (color, 170, y, temp, TAG_DONE);
sprintf (temp, "%d", P_AproxDistance2 (players[consoleplayer].camera, ox, oy) / FRACUNIT);
screen->DrawText (color, 200, y, temp, TAG_DONE);
sprintf (temp, "%d", chan->EntChannel);
screen->DrawText (color, 240, y, temp, TAG_DONE);
y += 8;
if (chan->PrevChan == &Channels)
{
break;
}
chan = (FSoundChan *)((size_t)chan->PrevChan - myoffsetof(FSoundChan, NextChan));
}
BorderNeedRefresh = screen->GetPageCount ();
}
static FString LastLocalSndInfo;
static FString LastLocalSndSeq;
void S_AddLocalSndInfo(int lump);
//==========================================================================
//
// S_Init
//
// Initializes sound stuff, including volume. Sets channels, SFX and
// music volume, allocates channel buffer, and sets S_sfx lookup.
//==========================================================================
void S_Init ()
{
int curvelump;
atterm (S_Shutdown);
// remove old data (S_Init can be called multiple times!)
if (S_SoundCurve != NULL)
{
delete[] S_SoundCurve;
}
// Heretic and Hexen have sound curve lookup tables. Doom does not.
curvelump = Wads.CheckNumForName ("SNDCURVE");
if (curvelump >= 0)
{
S_SoundCurveSize = Wads.LumpLength (curvelump);
S_SoundCurve = new BYTE[S_SoundCurveSize];
Wads.ReadLump(curvelump, S_SoundCurve);
}
// Free all channels for use.
while (Channels != NULL)
{
S_ReturnChannel(Channels);
}
// no sounds are playing, and they are not paused
MusicPaused = false;
SoundPaused = false;
}
//==========================================================================
//
// S_InitData
//
//==========================================================================
void S_InitData ()
{
LastLocalSndInfo = LastLocalSndSeq = "";
S_ParseSndInfo ();
S_ParseSndSeq (-1);
S_ParseSndEax ();
}
//==========================================================================
//
// S_Shutdown
//
//==========================================================================
void S_Shutdown ()
{
FSoundChan *chan, *next;
if (GSnd != NULL)
{
while (Channels != NULL)
{
GSnd->StopSound(Channels);
}
}
else
{
for (chan = Channels; chan != NULL; chan = next)
{
next = chan->NextChan;
delete chan;
}
Channels = NULL;
}
for (chan = FreeChannels; chan != NULL; chan = next)
{
next = chan->NextChan;
delete chan;
}
FreeChannels = NULL;
if (S_SoundCurve != NULL)
{
delete[] S_SoundCurve;
S_SoundCurve = NULL;
}
if (PlayList != NULL)
{
delete PlayList;
PlayList = NULL;
}
}
//==========================================================================
//
// S_Start
//
// Per level startup code. Kills playing sounds at start of level
// and starts new music.
//==========================================================================
void S_Start ()
{
if (GSnd)
{
// kill all playing sounds at start of level (trust me - a good idea)
S_StopAllChannels();
// Check for local sound definitions. Only reload if they differ
// from the previous ones.
// To be certain better check whether level is valid!
char *LocalSndInfo = level.info ? level.info->soundinfo : (char*)"";
char *LocalSndSeq = level.info ? level.info->sndseq : (char*)"";
bool parse_ss = false;
// This level uses a different local SNDINFO
if (LastLocalSndInfo.CompareNoCase(LocalSndInfo) != 0 || !level.info)
{
// First delete the old sound list
for(unsigned i = 1; i < S_sfx.Size(); i++)
{
GSnd->UnloadSound(&S_sfx[i]);
}
// Parse the global SNDINFO
S_ParseSndInfo();
if (*LocalSndInfo)
{
// Now parse the local SNDINFO
int j = Wads.CheckNumForName(LocalSndInfo);
if (j>=0) S_AddLocalSndInfo(j);
}
// Also reload the SNDSEQ if the SNDINFO was replaced!
parse_ss=true;
}
else if (LastLocalSndSeq.CompareNoCase(LocalSndSeq) != 0)
{
parse_ss=true;
}
if (parse_ss)
{
S_ParseSndSeq(*LocalSndSeq? Wads.CheckNumForName(LocalSndSeq) : -1);
}
else
LastLocalSndInfo = LocalSndInfo;
LastLocalSndSeq = LocalSndSeq;
}
SoundPaused = false;
// stop the old music if it has been paused.
// This ensures that the new music is started from the beginning
// if it's the same as the last one and it has been paused.
if (MusicPaused) S_StopMusic(true);
// start new music for the level
MusicPaused = false;
// [RH] This is a lot simpler now.
if (!savegamerestore)
{
if (level.cdtrack == 0 || !S_ChangeCDMusic (level.cdtrack, level.cdid))
S_ChangeMusic (level.music, level.musicorder);
}
}
//==========================================================================
//
// S_PrecacheLevel
//
// Like R_PrecacheLevel, but for sounds.
//
//==========================================================================
void S_PrecacheLevel ()
{
unsigned int i;
if (GSnd)
{
for (i = 0; i < S_sfx.Size(); ++i)
{
S_sfx[i].bUsed = false;
}
AActor *actor;
TThinkerIterator<AActor> iterator;
while ( (actor = iterator.Next ()) != NULL )
{
S_sfx[actor->SeeSound].bUsed = true;
S_sfx[actor->AttackSound].bUsed = true;
S_sfx[actor->PainSound].bUsed = true;
S_sfx[actor->DeathSound].bUsed = true;
S_sfx[actor->ActiveSound].bUsed = true;
S_sfx[actor->UseSound].bUsed = true;
}
for (i = 1; i < S_sfx.Size(); ++i)
{
if (S_sfx[i].bUsed)
{
S_CacheSound (&S_sfx[i]);
}
}
for (i = 1; i < S_sfx.Size(); ++i)
{
if (!S_sfx[i].bUsed && S_sfx[i].link == sfxinfo_t::NO_LINK)
{
GSnd->UnloadSound (&S_sfx[i]);
}
}
}
}
//==========================================================================
//
// S_CacheSound
//
//==========================================================================
void S_CacheSound (sfxinfo_t *sfx)
{
if (GSnd)
{
if (sfx->bPlayerReserve)
{
return;
}
else if (sfx->bRandomHeader)
{
S_CacheRandomSound (sfx);
}
else
{
while (sfx->link != sfxinfo_t::NO_LINK)
{
sfx = &S_sfx[sfx->link];
}
sfx->bUsed = true;
GSnd->LoadSound (sfx);
}
}
}
//==========================================================================
//
// S_GetChannel
//
// Returns a free channel for the system sound interface.
//
//==========================================================================
FSoundChan *S_GetChannel(void *syschan)
{
FSoundChan *chan;
if (FreeChannels != NULL)
{
chan = FreeChannels;
S_UnlinkChannel(chan);
}
else
{
chan = new FSoundChan;
memset(chan, 0, sizeof(*chan));
}
S_LinkChannel(chan, &Channels);
chan->SysChannel = syschan;
return chan;
}
//==========================================================================
//
// S_ReturnChannel
//
// Returns a channel to the free pool.
//
//==========================================================================
void S_ReturnChannel(FSoundChan *chan)
{
if (chan->Mover != NULL)
{
chan->Mover->SoundChans &= ~(1 << chan->EntChannel);
}
S_UnlinkChannel(chan);
memset(chan, 0, sizeof(*chan));
S_LinkChannel(chan, &FreeChannels);
}
//==========================================================================
//
// S_UnlinkChannel
//
//==========================================================================
void S_UnlinkChannel(FSoundChan *chan)
{
*(chan->PrevChan) = chan->NextChan;
if (chan->NextChan != NULL)
{
chan->NextChan->PrevChan = chan->PrevChan;
}
}
//==========================================================================
//
// S_LinkChannel
//
//==========================================================================
void S_LinkChannel(FSoundChan *chan, FSoundChan **head)
{
chan->NextChan = *head;
if (chan->NextChan != NULL)
{
chan->NextChan->PrevChan = &chan->NextChan;
}
*head = chan;
chan->PrevChan = head;
}
// [RH] Split S_StartSoundAtVolume into multiple parts so that sounds can
// be specified both by id and by name. Also borrowed some stuff from
// Hexen and parameters from Quake.
//==========================================================================
//
// CalcPosVel
//
// Calculates a sound's position and velocity for 3D sounds.
//=========================================================================
void CalcPosVel (fixed_t *pt, AActor *mover, int constz,
float pos[3], float vel[3])
{
if (mover != NULL && 0)
{
vel[0] = FIXED2FLOAT(mover->momx) * TICRATE;
vel[1] = FIXED2FLOAT(mover->momz) * TICRATE;
vel[2] = FIXED2FLOAT(mover->momy) * TICRATE;
}
else
{
vel[0] = vel[1] = vel[2] = 0.f;
}
pos[0] = FIXED2FLOAT (pt[0]);
pos[2] = FIXED2FLOAT (pt[1]);
if (constz)
{
pos[1] = FIXED2FLOAT(players[consoleplayer].camera->z);
vel[1] = 0.f;
}
else
{
pos[1] = FIXED2FLOAT(pt[2]);
}
}
//==========================================================================
//
// S_StartSound
//
// 0 attenuation means full volume over whole level
// 0<attenuation<=1 means to scale the distance by that amount when
// calculating volume
//==========================================================================
static void S_StartSound (fixed_t *pt, AActor *mover, int channel,
int sound_id, float volume, float attenuation, bool looping)
{
sfxinfo_t *sfx;
int chanflags;
int basepriority;
int org_id;
int pitch;
fixed_t x, y, z;
FSoundChan *chan;
static int sndcount = 0;
if (sound_id <= 0 || volume <= 0 || GSnd == NULL)
return;
org_id = sound_id;
if (pt == NULL)
{
attenuation = 0;
// Give these variables values, although they don't really matter
x = y = z = 0;
}
else
{
x = pt[0];
y = pt[1];
z = pt[2];
}
chanflags = channel & ~7;
if (chanflags & CHAN_IMMOBILE)
{
pt = NULL;
}
if (i_compatflags & COMPATF_MAGICSILENCE)
{ // For people who just can't play without a silent BFG.
channel = CHAN_WEAPON;
}
else
{
if ((channel & CHAN_MAYBE_LOCAL) && (i_compatflags & COMPATF_SILENTPICKUP))
{
if (mover != NULL && mover != players[consoleplayer].camera)
{
return;
}
}
channel &= 7;
}
volume = clamp(volume, 0.f, 1.f);
sfx = &S_sfx[sound_id];
// If this is a singular sound, don't play it if it's already playing.
if (pt != NULL && sfx->bSingular && S_CheckSingular(sound_id))
return;
// Resolve player sounds, random sounds, and aliases
while (sfx->link != sfxinfo_t::NO_LINK)
{
if (sfx->bPlayerReserve)
{
sound_id = S_FindSkinnedSound (mover, sound_id);
}
else if (sfx->bRandomHeader)
{
sound_id = S_PickReplacement (sound_id);
}
else
{
sound_id = sfx->link;
}
sfx = &S_sfx[sound_id];
}
// Make sure the sound is loaded.
if (sfx->data == NULL)
{
GSnd->LoadSound (sfx);
if (sfx->link != sfxinfo_t::NO_LINK)
{
sfx = &S_sfx[sfx->link];
}
}
// The empty sound never plays.
if (sfx->lumpnum == sfx_empty)
{
return;
}
// Select priority.
if (attenuation <= 0 || mover == players[consoleplayer].camera)
{
basepriority = 40;
}
else
{
switch (channel)
{
case CHAN_WEAPON:
basepriority = 20;
break;
case CHAN_VOICE:
basepriority = 10;
break;
default:
case CHAN_BODY:
basepriority = 0;
break;
case CHAN_ITEM:
basepriority = -10;
break;
}
basepriority = int(basepriority * attenuation);
}
if (mover != NULL && channel == CHAN_AUTO)
{ // Select a channel that isn't already playing something.
BYTE mask = mover->SoundChans;
// Try channel 0 first, then travel from channel 7 down.
if ((mask & 1) == 0)
{
channel = 0;
}
else
{
for (channel = 7; channel > 0; --channel, mask <<= 1)
{
if ((mask & 0x80) == 0)
{
break;
}
}
if (channel == 0)
{ // Crap. No free channels.
return;
}
}
}
// If this actor is already playing something on the selected channel, stop it.
if ((mover == NULL && channel != CHAN_AUTO) || (mover != NULL && mover->SoundChans & (1 << channel)))
{
for (chan = Channels; chan != NULL; chan = chan->NextChan)
{
if (chan->Mover == mover && chan->EntChannel == channel)
{
GSnd->StopSound(chan);
break;
}
}
}
// Vary the sfx pitches.
if (sfx->PitchMask != 0)
{
pitch = NORM_PITCH - (M_Random()&sfx->PitchMask) + (M_Random()&sfx->PitchMask);
}
else
{
pitch = NORM_PITCH;
}
if (attenuation > 0)
{
float pos[3];
float vel[3];
if (pt)
{
CalcPosVel (pt, mover, chanflags & CHAN_LISTENERZ, pos, vel);
}
else
{
fixed_t pt2[3];
pt2[0] = x;
pt2[1] = y;
pt2[2] = z;
CalcPosVel (pt2, mover, chanflags & CHAN_LISTENERZ, pos, vel);
}
chan = GSnd->StartSound3D (sfx, volume, attenuation, pitch, basepriority, looping, pos, vel, !(chanflags & CHAN_NOPAUSE));
if (chan != NULL)
{
chan->ConstZ = !!(chanflags & CHAN_LISTENERZ);
}
}
else
{
chan = GSnd->StartSound (sfx, volume, pitch, looping, !(chanflags & CHAN_NOPAUSE));
if (chan != NULL)
{
chan->ConstZ = true;
}
}
if (chan != NULL)
{
chan->SoundID = sound_id;
chan->OrgID = org_id;
chan->Mover = mover;
chan->Pt = pt != NULL ? pt : &chan->X;
chan->SfxInfo = sfx;
chan->EntChannel = channel;
chan->Volume = volume;
chan->X = x;
chan->Y = y;
chan->Z = z;
chan->Loop = looping;
if (mover != NULL)
{
mover->SoundChans |= 1 << channel;
}
}
}
//==========================================================================
//
// S_SoundID
//
//==========================================================================
void S_SoundID (int channel, int sound_id, float volume, int attenuation)
{
S_StartSound ((fixed_t *)NULL, NULL, channel, sound_id, volume, SELECT_ATTEN(attenuation), false);
}
void S_SoundID (AActor *ent, int channel, int sound_id, float volume, int attenuation)
{
if (ent->Sector->MoreFlags & SECF_SILENT)
return;
S_StartSound (&ent->x, ent, channel, sound_id, volume, SELECT_ATTEN(attenuation), false);
}
void S_SoundID (fixed_t *pt, int channel, int sound_id, float volume, int attenuation)
{
S_StartSound (pt, NULL, channel, sound_id, volume, SELECT_ATTEN(attenuation), false);
}
void S_SoundID (fixed_t x, fixed_t y, fixed_t z, int channel, int sound_id, float volume, int attenuation)
{
fixed_t pt[3];
pt[0] = x;
pt[1] = y;
pt[2] = z;
S_StartSound (pt, NULL, channel|CHAN_IMMOBILE, sound_id, volume, SELECT_ATTEN(attenuation), false);
}
//==========================================================================
//
// S_LoopedSoundID
//
//==========================================================================
void S_LoopedSoundID (AActor *ent, int channel, int sound_id, float volume, int attenuation)
{
if (ent->Sector->MoreFlags & SECF_SILENT)
return;
S_StartSound (&ent->x, ent, channel, sound_id, volume, SELECT_ATTEN(attenuation), true);
}
void S_LoopedSoundID (fixed_t *pt, int channel, int sound_id, float volume, int attenuation)
{
S_StartSound (pt, NULL, channel, sound_id, volume, SELECT_ATTEN(attenuation), true);
}
//==========================================================================
//
// S_StartNamedSound
//
//==========================================================================
void S_StartNamedSound (AActor *ent, fixed_t *pt, int channel,
const char *name, float volume, float attenuation, bool looping)
{
int sfx_id;
if (name == NULL ||
(ent != NULL && ent->Sector->MoreFlags & SECF_SILENT))
{
return;
}
sfx_id = S_FindSound (name);
if (sfx_id == 0)
DPrintf ("Unknown sound %s\n", name);
if (ent)
S_StartSound (&ent->x, ent, channel, sfx_id, volume, attenuation, looping);
else
S_StartSound (pt, NULL, channel, sfx_id, volume, attenuation, looping);
}
//==========================================================================
//
// S_Sound
//
//==========================================================================
void S_Sound (int channel, const char *name, float volume, int attenuation)
{
S_StartNamedSound ((AActor *)NULL, NULL, channel, name, volume, SELECT_ATTEN(attenuation), false);
}
void S_Sound (AActor *ent, int channel, const char *name, float volume, int attenuation)
{
S_StartNamedSound (ent, NULL, channel, name, volume, SELECT_ATTEN(attenuation), false);
}
void S_Sound (fixed_t *pt, int channel, const char *name, float volume, int attenuation)
{
S_StartNamedSound (NULL, pt, channel, name, volume, SELECT_ATTEN(attenuation), false);
}
void S_Sound (fixed_t x, fixed_t y, int channel, const char *name, float volume, int attenuation)
{
fixed_t pt[3];
pt[0] = x;
pt[1] = y;
S_StartNamedSound (NULL, pt, channel|CHAN_LISTENERZ|CHAN_IMMOBILE,
name, volume, SELECT_ATTEN(attenuation), false);
}
//==========================================================================
//
// S_LoopedSound
//
//==========================================================================
void S_LoopedSound (AActor *ent, int channel, const char *name, float volume, int attenuation)
{
S_StartNamedSound (ent, NULL, channel, name, volume, SELECT_ATTEN(attenuation), true);
}
//==========================================================================
//
// S_CheckSingular
//
// Returns true if a copy of this sound is already playing.
//
//==========================================================================
bool S_CheckSingular(int sound_id)
{
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
{
if (chan->OrgID == sound_id)
{
return true;
}
}
return false;
}
//==========================================================================
//
// S_StopSound
//
// Stops a sound from a single source playing on a specific channel.
//==========================================================================
void S_StopSound (fixed_t *pt, int channel)
{
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
{
if (((pt == NULL && chan->Pt == &chan->X) || chan->Pt == pt) &&
((i_compatflags & COMPATF_MAGICSILENCE) || chan->EntChannel == channel))
{
GSnd->StopSound(chan);
}
}
}
void S_StopSound (AActor *ent, int channel)
{
// No need to search every channel if we know it's not playing anything.
if (ent != NULL && ent->SoundChans & (1 << channel))
{
S_StopSound (&ent->x, channel);
}
}
//==========================================================================
//
// S_StopAllChannels
//
//==========================================================================
void S_StopAllChannels ()
{
if (GSnd == NULL)
{
return;
}
SN_StopAllSequences();
while (Channels != NULL)
{
GSnd->StopSound(Channels);
}
GSnd->UpdateSounds();
}
//==========================================================================
//
// S_RelinkSound
//
// Moves all the sounds from one thing to another. If the destination is
// NULL, then the sound becomes a positioned sound.
//==========================================================================
void S_RelinkSound (AActor *from, AActor *to)
{
if (from == NULL || GSnd == NULL)
return;
fixed_t *frompt = &from->x;
fixed_t *topt = to ? &to->x : NULL;
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
{
if (chan->Pt == frompt)
{
if (to != NULL || !chan->Loop)
{
chan->Pt = topt ? topt : &chan->X;
chan->X = frompt[0];
chan->Y = frompt[1];
chan->Z = frompt[2];
chan->Mover = to;
}
else
{
GSnd->StopSound(chan);
}
}
}
}
//==========================================================================
//
// S_GetSoundPlayingInfo
//
// Is a sound being played by a specific actor/point?
//==========================================================================
bool S_GetSoundPlayingInfo (fixed_t *pt, int sound_id)
{
if (sound_id > 0)
{
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
{
if (chan->Pt == pt && chan->OrgID == sound_id)
return true;
}
}
return false;
}
bool S_GetSoundPlayingInfo (AActor *ent, int sound_id)
{
return S_GetSoundPlayingInfo (ent ? &ent->x : NULL, sound_id);
}
//==========================================================================
//
// S_IsActorPlayingSomething
//
//==========================================================================
bool S_IsActorPlayingSomething (AActor *actor, int channel, int sound_id)
{
if (actor->SoundChans == 0)
{
return false;
}
if (i_compatflags & COMPATF_MAGICSILENCE)
{
channel = 0;
}
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
{
if (chan->Pt == &actor->x)
{
if (channel == 0 || chan->EntChannel == channel)
{
return sound_id <= 0 || chan->OrgID == sound_id;
}
}
}
return false;
}
//==========================================================================
//
// S_PauseSound
//
// Stop music and sound effects, during game PAUSE.
//==========================================================================
void S_PauseSound (bool notmusic)
{
if (!notmusic && mus_playing.handle && !MusicPaused)
{
I_PauseSong (mus_playing.handle);
MusicPaused = true;
}
if (GSnd != NULL && !SoundPaused)
{
GSnd->SetSfxPaused (true);
SoundPaused = true;
}
}
//==========================================================================
//
// S_ResumeSound
//
// Resume music and sound effects, after game PAUSE.
//==========================================================================
void S_ResumeSound ()
{
if (mus_playing.handle && MusicPaused)
{
I_ResumeSong (mus_playing.handle);
MusicPaused = false;
}
if (GSnd != NULL && SoundPaused)
{
GSnd->SetSfxPaused (false);
SoundPaused = false;
}
}
//==========================================================================
//
// S_UpdateSounds
//
// Updates music & sounds
//==========================================================================
void S_UpdateSounds (void *listener_p)
{
float pos[3], vel[3];
I_UpdateMusic();
if (GSnd == NULL)
return;
// [RH] Update playlist
if (PlayList &&
mus_playing.handle &&
!I_QrySongPlaying(mus_playing.handle))
{
PlayList->Advance();
S_ActivatePlayList(false);
}
for (FSoundChan *chan = Channels; chan != NULL; chan = chan->NextChan)
{
if (chan->Is3D)
{
CalcPosVel(chan->Pt, chan->Mover, chan->ConstZ, pos, vel);
GSnd->UpdateSoundParams3D(chan, pos, vel);
}
}
SN_UpdateActiveSequences();
GSnd->UpdateListener();
GSnd->UpdateSounds();
}
//==========================================================================
//
// S_ActivatePlayList
//
// Plays the next song in the playlist. If no songs in the playlist can be
// played, then it is deleted.
//==========================================================================
void S_ActivatePlayList (bool goBack)
{
int startpos, pos;
startpos = pos = PlayList->GetPosition ();
S_StopMusic (true);
while (!S_ChangeMusic (PlayList->GetSong (pos), 0, false, true))
{
pos = goBack ? PlayList->Backup () : PlayList->Advance ();
if (pos == startpos)
{
delete PlayList;
PlayList = NULL;
Printf ("Cannot play anything in the playlist.\n");
return;
}
}
}
//==========================================================================
//
// S_ChangeCDMusic
//
// Starts a CD track as music.
//==========================================================================
bool S_ChangeCDMusic (int track, unsigned int id, bool looping)
{
char temp[32];
if (id)
{
sprintf (temp, ",CD,%d,%x", track, id);
}
else
{
sprintf (temp, ",CD,%d", track);
}
return S_ChangeMusic (temp, 0, looping);
}
//==========================================================================
//
// S_StartMusic
//
// Starts some music with the given name.
//==========================================================================
bool S_StartMusic (const char *m_id)
{
return S_ChangeMusic (m_id, 0, false);
}
//==========================================================================
//
// S_ChangeMusic
//
// Starts playing a music, possibly looping.
//
// [RH] If music is a MOD, starts it at position order. If name is of the
// format ",CD,<track>,[cd id]" song is a CD track, and if [cd id] is
// specified, it will only be played if the specified CD is in a drive.
//==========================================================================
TArray<char> musiccache;
bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force)
{
if (!force && PlayList)
{ // Don't change if a playlist is active
return false;
}
// allow specifying "*" as a placeholder to play the level's default music.
if (musicname != NULL && !strcmp(musicname, "*"))
{
if (gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL)
{
musicname = level.music;
order = level.musicorder;
}
else
{
musicname = NULL;
}
}
if (musicname == NULL || musicname[0] == 0)
{
// Don't choke if the map doesn't have a song attached
S_StopMusic (true);
return false;
}
if (!mus_playing.name.IsEmpty() && stricmp (mus_playing.name, musicname) == 0)
{
if (order != mus_playing.baseorder)
{
mus_playing.baseorder =
(I_SetSongPosition (mus_playing.handle, order) ? order : 0);
}
return true;
}
if (strnicmp (musicname, ",CD,", 4) == 0)
{
int track = strtoul (musicname+4, NULL, 0);
const char *more = strchr (musicname+4, ',');
unsigned int id = 0;
if (more != NULL)
{
id = strtoul (more+1, NULL, 16);
}
S_StopMusic (true);
mus_playing.handle = I_RegisterCDSong (track, id);
}
else
{
int lumpnum = -1;
int offset, length;
int device = -1;
int * devp = MidiDevices.CheckKey(FName(musicname));
if (devp != NULL) device = *devp;
if (!FileExists (musicname))
{
if ((lumpnum = Wads.CheckNumForName (musicname, ns_music)) == -1)
{
if ((lumpnum = Wads.CheckNumForFullName (musicname)) == -1)
{
Printf ("Music \"%s\" not found\n", musicname);
return false;
}
}
if (!Wads.IsUncompressedFile(lumpnum))
{
// We must cache the music data and use it from memory.
// shut down old music before reallocating and overwriting the cache!
S_StopMusic (true);
offset = -1; // this tells the low level code that the music
// is being used from memory
length = Wads.LumpLength (lumpnum);
if (length == 0)
{
offset = 0;
return false;
}
musiccache.Resize(length);
Wads.ReadLump(lumpnum, &musiccache[0]);
}
else
{
offset = Wads.GetLumpOffset (lumpnum);
length = Wads.LumpLength (lumpnum);
}
}
else
{
offset = 0;
length = 0;
}
// shutdown old music
S_StopMusic (true);
// load & register it
if (offset!=-1)
{
mus_playing.handle = I_RegisterSong (lumpnum != -1 ?
Wads.GetWadFullName (Wads.GetLumpFile (lumpnum)) :
musicname, NULL, offset, length, device);
}
else
{
mus_playing.handle = I_RegisterSong (NULL, &musiccache[0], -1, length, device);
}
}
mus_playing.loop = looping;
if (mus_playing.handle != 0)
{ // play it
mus_playing.name = musicname;
I_PlaySong (mus_playing.handle, looping, S_GetMusicVolume (musicname));
mus_playing.baseorder =
(I_SetSongPosition (mus_playing.handle, order) ? order : 0);
return true;
}
return false;
}
//==========================================================================
//
// S_RestartMusic
//
// Must only be called from snd_reset in i_sound.cpp!
//==========================================================================
void S_RestartMusic ()
{
if (!LastSong.IsEmpty())
{
S_ChangeMusic (LastSong, mus_playing.baseorder, mus_playing.loop, true);
LastSong = "";
}
}
//==========================================================================
//
// S_GetMusic
//
//==========================================================================
int S_GetMusic (char **name)
{
int order;
if (mus_playing.name)
{
*name = copystring (mus_playing.name);
order = mus_playing.baseorder;
}
else
{
*name = NULL;
order = 0;
}
return order;
}
//==========================================================================
//
// S_StopMusic
//
//==========================================================================
void S_StopMusic (bool force)
{
// [RH] Don't stop if a playlist is active.
if ((force || PlayList == NULL) && !mus_playing.name.IsEmpty())
{
if (MusicPaused)
I_ResumeSong(mus_playing.handle);
I_StopSong(mus_playing.handle);
I_UnRegisterSong(mus_playing.handle);
LastSong = mus_playing.name;
mus_playing.name = "";
mus_playing.handle = 0;
}
}
//==========================================================================
//
// CCMD playsound
//
//==========================================================================
CCMD (playsound)
{
if (argv.argc() > 1)
{
S_Sound (CHAN_AUTO, argv[1], 1.f, ATTN_NONE);
}
}
//==========================================================================
//
// CCMD idmus
//
//==========================================================================
CCMD (idmus)
{
level_info_t *info;
char *map;
int l;
if (argv.argc() > 1)
{
if (gameinfo.flags & GI_MAPxx)
{
l = atoi (argv[1]);
if (l <= 99)
map = CalcMapName (0, l);
else
{
Printf ("%s\n", GStrings("STSTR_NOMUS"));
return;
}
}
else
{
map = CalcMapName (argv[1][0] - '0', argv[1][1] - '0');
}
if ( (info = FindLevelInfo (map)) )
{
if (info->music)
{
S_ChangeMusic (info->music, info->musicorder);
Printf ("%s\n", GStrings("STSTR_MUS"));
}
}
else
{
Printf ("%s\n", GStrings("STSTR_NOMUS"));
}
}
}
//==========================================================================
//
// CCMD changemus
//
//==========================================================================
CCMD (changemus)
{
if (argv.argc() > 1)
{
if (PlayList)
{
delete PlayList;
PlayList = NULL;
}
S_ChangeMusic (argv[1], argv.argc() > 2 ? atoi (argv[2]) : 0);
}
}
//==========================================================================
//
// CCMD stopmus
//
//==========================================================================
CCMD (stopmus)
{
if (PlayList)
{
delete PlayList;
PlayList = NULL;
}
S_StopMusic (false);
}
//==========================================================================
//
// CCMD cd_play
//
// Plays a specified track, or the entire CD if no track is specified.
//==========================================================================
CCMD (cd_play)
{
char musname[16];
if (argv.argc() == 1)
strcpy (musname, ",CD,");
else
sprintf (musname, ",CD,%d", atoi(argv[1]));
S_ChangeMusic (musname, 0, true);
}
//==========================================================================
//
// CCMD cd_stop
//
//==========================================================================
CCMD (cd_stop)
{
CD_Stop ();
}
//==========================================================================
//
// CCMD cd_eject
//
//==========================================================================
CCMD (cd_eject)
{
CD_Eject ();
}
//==========================================================================
//
// CCMD cd_close
//
//==========================================================================
CCMD (cd_close)
{
CD_UnEject ();
}
//==========================================================================
//
// CCMD cd_pause
//
//==========================================================================
CCMD (cd_pause)
{
CD_Pause ();
}
//==========================================================================
//
// CCMD cd_resume
//
//==========================================================================
CCMD (cd_resume)
{
CD_Resume ();
}
//==========================================================================
//
// CCMD playlist
//
//==========================================================================
CCMD (playlist)
{
int argc = argv.argc();
if (argc < 2 || argc > 3)
{
Printf ("playlist <playlist.m3u> [<position>|shuffle]\n");
}
else
{
if (PlayList != NULL)
{
PlayList->ChangeList (argv[1]);
}
else
{
PlayList = new FPlayList (argv[1]);
}
if (PlayList->GetNumSongs () == 0)
{
delete PlayList;
PlayList = NULL;
}
else
{
if (argc == 3)
{
if (stricmp (argv[2], "shuffle") == 0)
{
PlayList->Shuffle ();
}
else
{
PlayList->SetPosition (atoi (argv[2]));
}
}
S_ActivatePlayList (false);
}
}
}
//==========================================================================
//
// CCMD playlistpos
//
//==========================================================================
static bool CheckForPlaylist ()
{
if (PlayList == NULL)
{
Printf ("No playlist is playing.\n");
return false;
}
return true;
}
CCMD (playlistpos)
{
if (CheckForPlaylist() && argv.argc() > 1)
{
PlayList->SetPosition (atoi (argv[1]) - 1);
S_ActivatePlayList (false);
}
}
//==========================================================================
//
// CCMD playlistnext
//
//==========================================================================
CCMD (playlistnext)
{
if (CheckForPlaylist())
{
PlayList->Advance ();
S_ActivatePlayList (false);
}
}
//==========================================================================
//
// CCMD playlistprev
//
//==========================================================================
CCMD (playlistprev)
{
if (CheckForPlaylist())
{
PlayList->Backup ();
S_ActivatePlayList (true);
}
}
//==========================================================================
//
// CCMD playliststatus
//
//==========================================================================
CCMD (playliststatus)
{
if (CheckForPlaylist ())
{
Printf ("Song %d of %d:\n%s\n",
PlayList->GetPosition () + 1,
PlayList->GetNumSongs (),
PlayList->GetSong (PlayList->GetPosition ()));
}
}
//==========================================================================
//
// CCMD cachesound <sound name>
//
//==========================================================================
CCMD (cachesound)
{
if (argv.argc() < 2)
{
Printf ("Usage: cachesound <sound> ...\n");
return;
}
for (int i = 1; i < argv.argc(); ++i)
{
int sfxnum = S_FindSound (argv[i]);
if (sfxnum > 0)
{
S_CacheSound (&S_sfx[sfxnum]);
}
}
}