scummvm/engines/icb/sound.cpp
Orgad Shaneh 75bfc1290b ICB: Use nullptr
Using clang-tidy modernize-use-nullptr
2021-11-14 15:51:59 +02:00

1179 lines
31 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the AUTHORS
* file distributed with this source distribution.
*
* Additional copyright for this file:
* Copyright (C) 1999-2000 Revolution Software Ltd.
* This code is based on source code created by Revolution Software,
* used with permission.
*
* This program 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.
*
*/
#include "engines/icb/p4.h"
#include "engines/icb/common/px_common.h"
#include "engines/icb/common/px_sfx_description.h"
#include "engines/icb/common/px_linkeddatafile.h"
#include "engines/icb/common/px_clu_api.h"
#include "engines/icb/icb.h"
#include "engines/icb/debug.h"
#include "engines/icb/sound.h"
#include "engines/icb/sound_lowlevel.h"
#include "engines/icb/global_objects.h"
#include "engines/icb/res_man.h"
#include "engines/icb/session.h"
#include "engines/icb/mission.h"
#include "engines/icb/common/px_sound_constants.h"
#include "engines/icb/sound_logic.h"
#include "engines/icb/remora.h"
#include "common/textconsole.h"
namespace ICB {
const char *menuSoundID = "__MENU__";
uint32 menuSoundIDHash = NULL_HASH;
// max is 127, min is zero
int32 speechVolume = 64;
int32 sfxVolume = 64;
int32 musicVolume = 64;
int32 currentSoundLevel;
int32 GetCurrentSoundLevel() { return currentSoundLevel; }
int32 GetSpeechVolume() { return speechVolume; }
int32 GetSfxVolume() { return sfxVolume; }
int32 GetMusicVolume() { return musicVolume; }
void SetSpeechVolume(int32 v) {
if ((v < 0) || (v > 128))
Fatal_error("Speech volume must be 0-128 not %d", v);
speechVolume = v;
}
void SetSfxVolume(int32 v) {
if ((v < 0) || (v > 128))
Fatal_error("Sfx volume must be 0-128 not %d", v);
sfxVolume = v;
// sfx volume will change next update...
}
void SetMusicVolume(int32 v) {
if ((v < 0) || (v > 128))
Fatal_error("Music volume must be 0-128 not %d", v);
musicVolume = v;
}
// max volume
#define MAX_VOLUME 127
#define SPEECH_ON_VOLUME 48
#define REMORA_ACTIVE_VOLUME 24
#define RESERVED_CHANNELS ((1 << SPEECH_CHANNEL) | (1 << MUSIC_CHANNEL))
#define NUMBER_CHANNELS 24
#define MAX_Y_DISTANCE_SQR (200 * 200)
bool8 soundOn = TRUE8;
bool8 pauseSound = TRUE8;
int32 speechOnSliderValue = MAX_VOLUME;
const uint8 pitchMakerMults[12 * 16] = PITCH_MULT;
const uint8 pitchMakerDivs[12 * 16] = PITCH_DIV;
class PitchMaker {
#define STANDARD_PITCH 4096
public:
int32 GetPitch(int32 initialPitch, int32 envValue) {
int32 v;
int32 e;
e = envValue;
v = STANDARD_PITCH;
// shifting up an octave
while (e >= (128 * 12)) {
v <<= 1; // *2
e -= (128 * 12);
}
// shifting down an octave
while (e <= (-128 * 12)) {
v >>= 1; // /2
e += (128 * 12);
}
// divide by 8 so there are 16 per semi-tone
e /= 8;
// use exp tables for - and + envValue
if (e < 0) {
v = (v * pitchMakerDivs[-e]) / 128;
} else if (e > 0) {
v = (v * pitchMakerMults[e]) / 128;
}
return (initialPitch * v) / STANDARD_PITCH;
}
};
PitchMaker pitchMaker;
const uint8 volMakerFunction[MAX_VOLUME + 1] = VOL_FUNCTION;
class VolMaker {
public:
int32 GetVol(int32 i) {
if (i < 0)
return 0;
else if (i > 127)
return 127;
else
return (sfxVolume * volMakerFunction[i]) >> 7; // sfxVolume is 0 (none) to full (128)
}
int32 GetVolMusic(int32 i) {
if (i < 0)
return 0;
else if (i > 127)
return 127;
else
return (musicVolume * volMakerFunction[i]) >> 7; // sfxVolume is 0 (none) to full (128)
}
};
VolMaker volMaker;
const char *gunSfxVar = "gun_sfx";
const char *defaultGunSfx = "gunshot";
const char *gunDesc = "gunshot";
const char *ricochetSfxVar = "ricochet_sfx";
const char *defaultRicochetSfx = "ricochet";
const char *ricochetDesc = "ricochet";
const char *openSfxVar = "open_sfx";
const char *defaultOpenSfx = "door_test";
const char *openDesc = "open";
const char *closeSfxVar = "close_sfx";
const char *defaultCloseSfx = "door_test";
const char *closeDesc = "close";
const char *addingMediSfxVar = "add_medi_sfx";
const char *defaultAddingMediSfx = "add_medi";
const char *defaultUsingMediSfx = "use_medi";
const char *addingMediDesc = "adding medipack";
const char *addingClipSfxVar = "add_clip_sfx";
const char *defaultAddingClipSfx = "add_clip";
const char *addingClipDesc = "adding clip";
const char *activateRemoraSfxVar = "remora_enter_sfx";
const char *activateRemoraSfx = "remora_enter";
const char *activateRemoraDesc = "entering Remora";
const char *emailSfxVar = "remora_enter_sfx";
const char *defaultEmailSfx = "remora_enter";
const char *emailDesc = "email arriving";
const char *deactivateRemoraSfxVar = "remora_exit_sfx";
const char *deactivateRemoraSfx = "remora_exit";
const char *deactivateRemoraDesc = "exiting Remora";
const char *tinkleSfxVar = "tinkle_sfx";
const char *defaultTinkleSfx = "cartridge_tinkle";
const char *tinkleDesc = "tinkle";
const char *menuUpDownSfx = "menu\\menu_up_down";
const char *menuSelectSfx = "menu\\menu_select";
const char *menuCancelSfx = "menu\\menu_cancel";
#define INT_1 128
// returns the value given by env at x (using INT maths)
// env is in format where 128=1
// x is in same units...
int32 EvalEnv(const CEnvelope &env, int32 x) {
int32 pa = env.a;
int32 pb = env.b;
int32 pc = env.c;
int32 pd = env.d;
int32 xxx, xx;
if (pa == 0)
xxx = 0;
else if (abs(pa) < INT_1)
xxx = (pa * x * x * x) / (INT_1 * INT_1 * INT_1);
else if (abs(pa) < (INT_1 * INT_1))
xxx = (pa * (x * x * x / INT_1)) / (INT_1 * INT_1);
// xxx=((pd/INT_1)*x*x*x)/(INT_1*INT_1);
else if (abs(pa) < (INT_1 * INT_1 * INT_1))
xxx = ((pa / INT_1) * (x * x * x / (INT_1))) / (INT_1);
else
xxx = ((pa / (INT_1 * INT_1)) * (x * x * x / INT_1));
// xxx=((pd/(INT_1*INT_1))*x*x*x)/(INT_1);
if (pb == 0)
xx = 0;
else if (abs(pb) < (INT_1 * INT_1))
xx = (pb * x * x) / (INT_1 * INT_1);
else if (abs(pb) < (INT_1 * INT_1 * INT_1))
xx = (pb / INT_1) * (x * x / INT_1);
else
xx = (pb / (INT_1 * INT_1)) * (x * x);
return xxx + xx + ((x * pc) / (INT_1)) + (pd);
}
// get the sfxlist file!
_linked_data_file *GetMissionSfxFile() {
uint32 fileHash;
uint32 clusterHash;
_linked_data_file *f = nullptr;
// if no mission return NULL
if (!g_mission) {
// PC NEEDS THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Fatal_error("No global mission sound so no special sfx!");
}
// get the file...
else {
fileHash = NULL_HASH;
clusterHash = MS->Fetch_session_cluster_hash();
f = (_linked_data_file *)private_session_resman->Res_open("m_sfxlist", fileHash, MS->Fetch_session_cluster(), clusterHash);
}
if ((f->GetHeaderVersion() != SFX_VERSION) || (f->header.type != FT_COMPILED_SFX))
Fatal_error("Sound: mission::the.cmpsfxlist, Header wrong, engine:%d,%08x file:%d,%08x\n", SFX_VERSION, FT_COMPILED_SFX, f->GetHeaderVersion(), f->header.type);
return f;
}
_linked_data_file *GetSessionSfxFile() {
// if no session return NULL
if ((!g_mission) || (!(g_mission->session))) {
warning("no session so no sfx file!");
return nullptr;
}
uint32 fileHash = NULL_HASH;
uint32 clusterHash = MS->Fetch_session_cluster_hash();
_linked_data_file *f;
// For the PC clustering the sfx file does not have the path, just the name
f = (_linked_data_file *)private_session_resman->Res_open(
"s_sfxlist",
fileHash, MS->Fetch_session_cluster(), clusterHash);
if ((f->GetHeaderVersion() != SFX_VERSION) || (f->header.type != FT_COMPILED_SFX))
Fatal_error("Sound: session::the.cmpsfxlist, Header wrong, engine:%d,%08x file:%d,%08x\n", SFX_VERSION, FT_COMPILED_SFX, f->GetHeaderVersion(), f->header.type);
return f;
}
// get a pointer to sfx (number) in the mission or session sfx file
CSfx *GetMissionSfx(int32 number) {
_linked_data_file *linkedSfx;
linkedSfx = GetMissionSfxFile();
return (CSfx *)linkedSfx->Fetch_item_by_number(number);
}
CSfx *GetSessionSfx(int32 number) {
_linked_data_file *linkedSfx;
linkedSfx = GetSessionSfxFile();
return (CSfx *)linkedSfx->Fetch_item_by_number(number);
}
// return a number for the sfx (in either mision or session) -1 means the sfx is not in the sound file (probabily in the other one)
int32 WhichMissionSfx(uint32 sfx) {
_linked_data_file *linkedSfx;
uint32 n;
linkedSfx = GetMissionSfxFile();
if (linkedSfx == nullptr)
return -1;
n = linkedSfx->Fetch_item_number_by_hash(sfx);
if (n == PX_LINKED_DATA_FILE_ERROR)
return -1;
else
return (int32)n;
}
int32 WhichSessionSfx(uint32 sfx) {
_linked_data_file *linkedSfx;
uint32 n;
linkedSfx = GetSessionSfxFile();
if (linkedSfx == nullptr)
return -1;
n = linkedSfx->Fetch_item_number_by_hash(sfx);
if (n == PX_LINKED_DATA_FILE_ERROR)
return -1;
else
return (int32)n;
}
bool8 SfxExists(uint32 sfxHash) {
int32 sfxNumber;
sfxNumber = WhichMissionSfx(sfxHash);
if (sfxNumber == -1) {
sfxNumber = WhichSessionSfx(sfxHash);
}
if (sfxNumber == -1)
return FALSE8;
else
return TRUE8;
}
bool8 SfxExists(const char *sfx) { return SfxExists(HashString(sfx)); }
int32 channelUsage = 0;
#define IS_CHANNEL_USED(CH) ((channelUsage | RESERVED_CHANNELS) & (1 << CH))
#define IS_CHANNEL_FREE(CH) ((~(channelUsage | RESERVED_CHANNELS)) & (1 << CH))
#define SET_CHANNEL_USED(CH) \
{ channelUsage |= (1 << CH); }
#define SET_CHANNEL_FREE(CH) \
{ channelUsage &= (~(1 << CH)); }
int32 GetFreeChannel() {
int32 i;
for (i = 0; i < NUMBER_CHANNELS; i++)
if (IS_CHANNEL_FREE(i)) {
return i;
}
return -1;
}
#define UPDATES_PER_SECOND 10
#define MAX_ENV_POSITION (128 * 128)
#define VOLUME_SLIDE 48 // how quickly we fade out a sound when it's time to cut off (this happens rarely but we don't want a pop)
// Get sfx for this registered sound
CSfx *CRegisteredSound::GetSfx() {
CSfx *the_sfx = nullptr;
if (m_sfxNumber == -1)
Fatal_error("sfx is not found in session or mission");
if (m_inSession)
the_sfx = GetSessionSfx(m_sfxNumber);
else
the_sfx = GetMissionSfx(m_sfxNumber);
if (!the_sfx)
Fatal_error("Can't find registered SFX??? (number %d m_inSession=%d)", m_sfxNumber, m_inSession);
return the_sfx;
}
// Zero out all the values of a sound
void CRegisteredSound::Wipe() {
m_objID = NO_REGISTERED_SOUND;
m_sndHash = NULL_HASH;
m_channel = -1;
m_x = REAL_ZERO;
m_y = REAL_ZERO;
m_z = REAL_ZERO;
m_restart_time = -1;
m_sfxNumber = NULL_HASH;
m_velocity = 0;
m_position = 0; // position*128
m_volume = 0;
m_current_pitch = 0;
m_sample_pitch = 0;
m_rand_pitch_value = 0;
m_next_random_pos = 0;
m_pan = 0;
m_xoffset = REAL_ZERO;
m_yoffset = REAL_ZERO;
m_zoffset = REAL_ZERO;
m_objMoving = 0;
m_volume_offset = 0;
m_inSession = FALSE8;
m_turnOff = FALSE8;
m_remove = FALSE8;
}
// update a sound if it is being turned off (reduce volume until 0 then destroy)
void CRegisteredSound::TurnOff() {
if (m_turnOff) {
if (m_channel == -1) {
m_turnOff = FALSE8;
if (m_remove)
m_objID = NO_REGISTERED_SOUND; // remove sound
return;
}
if (m_volume == 0) {
Tdebug("sounds.txt", "Finally turning off %d!", m_channel);
if (soundOn)
StopSample(m_channel);
SET_CHANNEL_FREE(m_channel);
m_channel = -1;
m_turnOff = FALSE8;
if (m_remove)
m_objID = NO_REGISTERED_SOUND; // remove sound
} else {
m_volume -= VOLUME_SLIDE;
if (m_volume < 0)
m_volume = 0;
if (soundOn)
SetChannelVolumeAndPan(m_channel, volMaker.GetVol(m_volume), m_pan);
}
}
}
// update every game cycle...
void CRegisteredSound::UpdateGameCycle(int32 newVol, int32 newPan) {
CSfx *sfx;
if (m_objID == NO_REGISTERED_SOUND)
return;
// if we're beyond next random update point (this must be done before wrap around...
sfx = GetSfx();
// new random pitch value
if ((sfx->m_rand_mode) && (m_position > m_next_random_pos)) {
GetRandom(sfx);
m_next_random_pos += (128 * 128 - 1) / (sfx->m_rand_mode);
}
// stop sound if nesesary or loop
if (m_position > MAX_ENV_POSITION) {
if (!((sfx->m_looping) & SFX_LOOPING_FLAG)) {
m_position = MAX_ENV_POSITION - 1; // hold here while turning off...
Tdebug("sounds.txt", "sound ending");
// turn off next cycle..
if (m_channel != -1) {
m_turnOff = TRUE8; // this will keep updating until m_channel is set to -1 by turn off at which point the sound will end
m_remove = TRUE8; // remove when finished turning off
} else
m_objID = NO_REGISTERED_SOUND; // don't need to update anymore...
} else {
// Tdebug("sounds.txt","sound looping...");
m_next_random_pos = 0; // reset random pos thingy
// if sample is not looping then replay...
if ((m_channel != -1) && (!((sfx->m_looping) & WAV_LOOPING_FLAG))) {
m_position = 0;
} else { // sample is looping or sound isn't on so just reset wave
m_position -= MAX_ENV_POSITION; // reset wave
if (m_position <= 0) // definately don't restart accidently
m_position = 1;
}
}
}
if (m_position < 0)
m_position++;
// if we're currently playing then update the low level if m_position=0 then time to start up sound
if ((m_channel != -1) && (m_position >= 0)) {
int32 v, p;
int32 myPos;
myPos = (m_position * (sfx->m_pitch.div)) & (128 * 128 - 1); // work out where we are on the pitch graph...
p = pitchMaker.GetPitch(m_sample_pitch, m_rand_pitch_value + EvalEnv(sfx->m_pitch, myPos / INT_1)); // get envelope pitch
// if paused sounds then set pitch to zero
if ((pauseSound) && ((m_sndHash != menuSoundIDHash) || (m_objID != SPECIAL_SOUND))) {
// printf("channel %d pitch is now 0\n",m_channel);
p = 0;
}
// has changed so update pitch
if (p != m_current_pitch) {
m_current_pitch = p;
if (soundOn)
SetChannelPitch(m_channel, m_current_pitch);
}
// VOLUME
if (m_turnOff) {
v = m_volume; // if we're turning off then ignore volume envelope
} else {
myPos = (m_position * (sfx->m_volume.div)) & (128 * 128 - 1); // volume pos
v = (newVol * EvalEnv(sfx->m_volume, myPos / INT_1)) / 128; // get new volume from envelope and input volume
// decrease volume if speech is playing...
v = (v * speechOnSliderValue) / 128;
// limit to 0-127 for volume
if (v > MAX_VOLUME)
v = MAX_VOLUME;
else if (v < 0)
v = 0;
}
// current sound level is measure of how much sound is in scene
// here it is both affected by distance from player and
// volume on the curve...
currentSoundLevel += (v * v);
// if either volume or pan has changed
if ((v != m_volume) || (newPan != m_pan)) {
m_volume = v;
m_pan = newPan;
// only set if sound on
if (soundOn)
SetChannelVolumeAndPan(m_channel, volMaker.GetVol((m_volume * m_volume_offset) / 128), m_pan);
}
// if we need to start the sound effect
if (m_position == 0) {
Tdebug("sounds.txt", "Starting sound");
m_position = 1; // so we don't try and start it twice in the unlikely event
// that no 10hz signal goes between calls...
// only start sample if sound is on
if (soundOn) {
StartSample(m_channel, sfx->GetSampleName(), m_inSession, (sfx->m_looping) & WAV_LOOPING_FLAG);
}
}
}
}
// update at 10hz EXACTLY
void CRegisteredSound::Update10Hz() {
if (m_objID == NO_REGISTERED_SOUND)
return;
// only update special menu sounds during 10hz signal...
// if sounds are paused and sound is not a special menu sound (either sndHash doesn't match or sound object is not special)
if ((pauseSound) && ((m_sndHash != menuSoundIDHash) || (m_objID != SPECIAL_SOUND)))
return;
if (m_position >= 0)
m_position += m_velocity;
}
int32 GetSoundCloser(int32 objID, PXreal x, PXreal y, PXreal z);
// make a sound hearable, get a channel and set to it...
bool8 CRegisteredSound::SetHearable() {
int32 ch;
// sound is already hearable so ignore this call
if (m_channel != -1)
return TRUE8;
Tdebug("sounds.txt", "Sound is now hearable");
// find a free channel
ch = GetFreeChannel();
Tdebug("sounds.txt", "Channel %d", ch);
// if we couldn't get a channel then we might want to knock out another sound
if (ch == -1) {
ch = GetSoundCloser(m_objID, m_x, m_y, m_z);
if (ch == -1)
return TRUE8; // still don't return true just dont set a channel
}
SET_CHANNEL_USED(ch);
// reset volume and pitch ready to be set (keep existing pan)
m_volume = 0xffff;
m_current_pitch = 0xffff;
// set channel
m_channel = ch;
m_position = m_restart_time; // start sample at next update...
m_restart_time = -1;
return TRUE8;
}
// make a sound unhearable, remove from playing sounds list...
void CRegisteredSound::SetUnhearable() {
// already unhearable or turning off so do nothing...
if ((m_channel == -1) || (m_turnOff))
return;
Tdebug("sounds.txt", "Sound is now unhearable");
// start turning off sound... (we are definately playing it...)
m_turnOff = TRUE8;
// don't remove though
}
// remove the sound
void CRegisteredSound::Remove() {
// stop sample etc
if (m_channel == -1) {
m_objID = NO_REGISTERED_SOUND; // just remove it
} else {
m_turnOff = TRUE8; // fade down
m_remove = TRUE8; // then remove
}
}
void CRegisteredSound::GetRandom(CSfx *sfx) {
if (sfx->m_rand_pitch)
m_rand_pitch_value = (g_icb->getRandomSource()->getRandomNumber(2 * 128 * sfx->m_rand_pitch - 1)) - (128 * sfx->m_rand_pitch);
else
m_rand_pitch_value = 0;
}
// register a new sound setting internal params
void CRegisteredSound::Register(const char *sndName, const char *sfxName, uint32 sfxHash, int8 volume) {
CSfx *sfx;
// simple params
m_sndHash = HashString(sndName);
if (sfxHash == NULL_HASH)
sfxHash = HashString(sfxName);
m_sfxNumber = WhichMissionSfx(sfxHash);
m_inSession = FALSE8;
if (m_sfxNumber == -1) {
m_sfxNumber = WhichSessionSfx(sfxHash);
m_inSession = TRUE8;
}
if (m_sfxNumber == -1)
Fatal_error("sfx %s(%08x) is not found in session or mission", sfxName, sfxHash);
// check it's okay...
sfx = GetSfx();
// set velocity = MAX_POSITION/((duration/128)*UPDATES_PER_SECOND)= 128*128*128/(duration*UPDATES_PER_SECOND)
m_velocity = (MAX_ENV_POSITION * 128) / (sfx->m_duration * UPDATES_PER_SECOND);
Tdebug("sounds.txt", "length=%d secs vel %d\n", (sfx->m_duration) / 128, m_velocity);
// start next frame..
m_restart_time = -1; // when hearable start at -1
m_position = -1;
m_channel = -1;
m_volume = 0; // default, gets changed if sound is heard
m_pan = 0; // default (centre) will get changed before playing if necesary
m_sample_pitch = GetSamplePitch(sfx->GetSampleName(), m_inSession);
m_current_pitch = 0; // always alter pitch before playing
GetRandom(sfx);
m_next_random_pos = 0;
m_remove = FALSE8;
m_turnOff = FALSE8;
m_xoffset = m_yoffset = m_zoffset = (PXreal)0;
m_volume_offset = volume;
padding1 = 0;
}
// register a sound and take position from the position of the object registering the sound
void CRegisteredSound::RegisterFromObject(const uint32 objID, const char *sndName, const char *sfxName, uint32 sfxHash, PXreal xo, PXreal yo, PXreal zo, int8 volume) {
Register(sndName, sfxName, sfxHash, volume);
m_xoffset = xo;
m_yoffset = yo;
m_zoffset = zo;
// set this last so any updates don't know it's turned on yet...
m_objID = objID;
if ((MS->logic_structs[m_objID]->image_type) == VOXEL) {
Tdebug("sounds.txt", "sound creator is an actor");
m_objMoving = 1;
} else
m_objMoving = 0;
MS->logic_structs[m_objID]->GetPosition(m_x, m_y, m_z);
// This tells the sound logic about the new sound, to drive events etc.
g_oSoundLogicEngine->NewSound(objID, (int32)m_x, (int32)m_y, (int32)m_z, GetSfx(), m_sndHash);
}
// register a sound as an absolute position
void CRegisteredSound::RegisterFromAbsolute(const uint32 objID, const char *sndName, const char *sfxName, uint32 sfxHash, PXreal x, PXreal y, PXreal z, int8 volume) {
Register(sndName, sfxName, sfxHash, volume);
// set this last so any updates don't know it's turned on yet...
m_objID = objID;
m_x = x;
m_y = y;
m_z = z;
m_objMoving = 0;
}
#define MAX_SCREEN (float)(SCREEN_WIDTH / 2)
// if the sound if coming froma moving souce, this updates the position, otherwise it does nothing
void CRegisteredSound::GetPosition() {
if (m_objMoving) {
MS->logic_structs[m_objID]->GetPosition(m_x, m_y, m_z);
m_x += m_xoffset;
m_y += m_yoffset;
m_z += m_zoffset;
}
}
// gets the volume and pan values for a sound, based on the position of the sound relative to the actor / camera
void CRegisteredSound::GetVolumeAndPan(int32 &vol, int32 &pan) {
// special sound, always central, etc
if (m_objID == SPECIAL_SOUND) {
vol = (int32)m_z; // get sound from z position...!
pan = (int32)m_x; // pan is x position!
return;
}
GetPosition();
vol = g_oSoundLogicEngine->ProcessSound((int32)m_x, (int32)m_y, (int32)m_z, GetSfx());
PXvector v = {m_x, m_y, m_z};
PXvector screenPos;
bool8 dontUse;
if (!MS->SetOK()) {
pan = 0;
return;
}
PXcamera &cam = MS->GetCamera();
PXWorldToFilm(v, cam, dontUse, screenPos);
if (screenPos.x < (-MAX_SCREEN))
screenPos.x = (-MAX_SCREEN);
if (screenPos.x > MAX_SCREEN)
screenPos.x = MAX_SCREEN;
// /2 for centralisation
pan = (int32)(128 * screenPos.x) / (SCREEN_WIDTH);
return;
}
CRegisteredSound *g_registeredSounds[MAX_REGISTERED_SOUNDS];
int32 assignedSounds = 0;
// must be called at 10hz time...
void UpdateSounds10Hz() {
int32 i;
for (i = 0; i < MAX_REGISTERED_SOUNDS; i++)
g_registeredSounds[i]->Update10Hz();
}
// called every game cycle sets hearable and unhearable sounds...
void UpdateHearableSounds() {
int32 i;
int32 assigned;
int32 volume;
int32 pan;
assigned = 0;
// no sound...
currentSoundLevel = 0;
for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
// we might need to do this even if sound is not currently used
g_registeredSounds[i]->TurnOff();
if (g_registeredSounds[i]->IsUsed()) {
// work out if is current hearable by the camera
// if speical sound or sound is not paused then we get the volume and pan from manager
if (((g_registeredSounds[i]->m_objID == SPECIAL_SOUND) && (g_registeredSounds[i]->m_sndHash == menuSoundIDHash)) || (!pauseSound)) {
g_registeredSounds[i]->GetVolumeAndPan(volume, pan);
// if volume is greater than zero
if (volume > 0) {
if (!g_registeredSounds[i]->SetHearable())
Fatal_error("Can't find a free channel to play sound");
} else
g_registeredSounds[i]->SetUnhearable(); // no Update required we are turning off or are off screen....
} else {
volume = 0;
pan = 0;
}
// update the wave etc
g_registeredSounds[i]->UpdateGameCycle(volume, pan);
}
}
currentSoundLevel = (currentSoundLevel * 100) / (128 * 128);
if (currentSoundLevel > 100)
currentSoundLevel = 100;
assignedSounds = assigned;
// speech volume slider
int32 speechOnSliderTarget;
// if somebody speaking...
if ((g_mission) && (g_mission->session) && (MS->speech_info[CONV_ID].total_subscribers > 0) && (GetSpeechVolume() > 0))
speechOnSliderTarget = SPEECH_ON_VOLUME;
// if remora active even lower volume
else if (g_oRemora->IsActive())
speechOnSliderTarget = REMORA_ACTIVE_VOLUME;
// otherwise our target is full volume
else
speechOnSliderTarget = MAX_VOLUME;
// update
if (speechOnSliderValue > speechOnSliderTarget) {
speechOnSliderValue -= VOLUME_SLIDE;
if (speechOnSliderValue < speechOnSliderTarget)
speechOnSliderValue = speechOnSliderTarget;
} else if (speechOnSliderValue < speechOnSliderTarget) {
speechOnSliderValue += VOLUME_SLIDE;
if (speechOnSliderValue > speechOnSliderTarget)
speechOnSliderValue = speechOnSliderTarget;
}
}
int32 GetSoundCloser(int32 objID, PXreal x, PXreal y, PXreal z) {
PXreal px = REAL_ZERO;
PXreal py = REAL_ZERO;
PXreal pz = REAL_ZERO; // play pos
PXreal dist; // distance of this object
PXreal thisDist; // distance of comparing object
int32 i;
int32 c;
// if
if (objID != SPECIAL_SOUND) {
MS->player.log->GetPosition(px, py, pz);
x -= px;
y -= py;
z -= pz;
dist = x * x + y * y + z * z;
} else // special sound so distance is by definition 0
dist = (PXreal)0;
PXreal maxDist = dist;
int32 sfxReplace = -1;
int32 channelReplace = -1;
for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
c = g_registeredSounds[i]->m_channel;
// can only replace a sound that has a channel and is not a special sound
if ((c != -1) && (g_registeredSounds[i]->m_objID != SPECIAL_SOUND)) {
g_registeredSounds[i]->GetPosition();
x = g_registeredSounds[i]->m_x - px;
y = g_registeredSounds[i]->m_y - py;
z = g_registeredSounds[i]->m_z - pz;
thisDist = x * x + y * y + z * z;
// if this distance from player is more than the maximum so far then this is the furthest sound
// yet found, and should therefore be replaced...
if (thisDist > maxDist) {
// this is the new max distance
maxDist = thisDist;
// this is the sfx and channcel to take
sfxReplace = i;
channelReplace = c;
}
}
}
// we found a sound further away than us therefore we can have it's channcel
// the sfx is sfxReplace, the channel we are stealing is channelReplace
if (channelReplace != -1) {
Tdebug("sounds.txt", "replacing sound %d (channel %d) because it's too far away (dist: %g, our dist: %g)", sfxReplace, channelReplace, maxDist, dist);
// the sfx now has no channel
g_registeredSounds[sfxReplace]->m_channel = -1;
// we return the channel we have just pinched
return channelReplace;
}
// otherwise we found no channel so return -1
return -1;
}
void TurnOffSound() { soundOn = FALSE8; }
void TurnOnSound() { soundOn = TRUE8; }
int32 GetFreeSound(const char *sfxName) {
int32 i;
for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
if (g_registeredSounds[i]->IsFree()) {
Tdebug("sounds.txt", "sfx: %s registered sound: %d", sfxName, i);
return i;
}
}
Fatal_error("No free sounds! %s", sfxName);
return -1;
}
int32 FindSound(uint32 obj, uint32 sndHash, int32 start = 0) {
int32 i;
for (i = start; i < MAX_REGISTERED_SOUNDS; i++) {
if (g_registeredSounds[i]->IsThisSound(obj, sndHash))
return i;
}
// should this be a fatal error..??
return -1;
}
// fn_play_sfx_offset(sfxName,sndID,x,y,z,isNico)
// this is the generic function
void RegisterSoundOffset(uint32 obj, const char *offsetName, const char *sfxName, uint32 sfxHash, const char *sndID, PXreal xo, PXreal yo, PXreal zo, int32 isNico, int32 time,
int8 volume_offset) {
// check we have the hash before we register any sounds...!
if (menuSoundIDHash == NULL_HASH)
menuSoundIDHash = HashString(menuSoundID);
int32 i;
i = GetFreeSound(sfxName);
// for getting nico position
PXreal x, y, z;
if ((obj != SPECIAL_SOUND) && (pauseSound))
warning("Registering sound whilst sound paused!");
// special sound
if (obj == SPECIAL_SOUND) {
g_registeredSounds[i]->RegisterFromAbsolute(obj, sndID, sfxName, sfxHash, xo, yo, zo, volume_offset);
}
// absolute sound (no name)
else if ((offsetName == nullptr) || (strcmp(offsetName, "") == 0)) {
// absolute address
g_registeredSounds[i]->RegisterFromAbsolute(obj, sndID, sfxName, sfxHash, xo, yo, zo, volume_offset);
}
// object is a nico marker
else if (isNico) {
// is nico so get position of it...
// x=, y=, z=
_feature_info *fi = (_feature_info *)(MS->features->Fetch_item_by_name(offsetName));
x = fi->x;
y = fi->y;
z = fi->z;
g_registeredSounds[i]->RegisterFromAbsolute(obj, sndID, sfxName, sfxHash, x + xo, y + yo, z + zo, volume_offset);
}
// object is a mega
else {
// is mega object so attach sound to it
// obj=
obj = MS->objects->Fetch_item_number_by_name(offsetName);
g_registeredSounds[i]->RegisterFromObject(obj, sndID, sfxName, sfxHash, xo, yo, zo, volume_offset);
}
Tdebug("sounds.txt", "restart time is %d %d\n", (-1) - (time), time);
g_registeredSounds[i]->m_restart_time = (-1) - (time);
}
// register a sound from an object
void RegisterSound(uint32 obj, const char *sfxName, uint32 sfxHash, const char *sndID, int8 volume_offset) {
const char *name;
if (obj == SPECIAL_SOUND)
name = nullptr;
else
name = (const char *)(MS->objects->Fetch_items_name_by_number(obj));
RegisterSoundOffset(obj, name, sfxName, sfxHash, sndID, (PXreal)0, (PXreal)0, (PXreal)0, 0, 0, volume_offset);
}
// register a sound from an absolute position
void RegisterSoundAbsolute(uint32 obj, const char *sfxName, uint32 sfxHash, const char *sndID, PXreal x, PXreal y, PXreal z, int8 volume_offset) {
RegisterSoundOffset(obj, nullptr, sfxName, sfxHash, sndID, x, y, z, 0, 0, volume_offset);
}
void RegisterSoundTime(uint32 obj, const char *sfxName, uint32 sfxHash, const char *sndID, int32 time, int8 volume_offset) {
const char *name;
if (obj == SPECIAL_SOUND)
name = nullptr;
else
name = (const char *)(MS->objects->Fetch_items_name_by_number(obj));
RegisterSoundOffset(obj, name, sfxName, sfxHash, sndID, (PXreal)0, (PXreal)0, (PXreal)0, 0, time, volume_offset);
}
// special sound
// for menus
void RegisterMenuSound(const char *sfxName, uint32 sfxHash, int32 volume, int32 pan, int8 volume_offset) {
// volume is z of position
RegisterSoundOffset(SPECIAL_SOUND, nullptr, sfxName, sfxHash, menuSoundID, (PXreal)pan, (PXreal)0, (PXreal)volume, 0, 0, volume_offset);
}
// special sound
// for in game (these are paused just like any other...
void RegisterSoundSpecial(const char *sfxName, uint32 sfxHash, const char *sndID, int32 volume, int32 pan, int8 volume_offset) {
// volume is z of position
RegisterSoundOffset(SPECIAL_SOUND, nullptr, sfxName, sfxHash, sndID, (PXreal)pan, (PXreal)0, (PXreal)volume, 0, 0, volume_offset);
}
void RemoveRegisteredSound(uint32 obj, const char *sndID) {
int32 i;
uint32 sndIDHash;
sndIDHash = HashString(sndID);
int32 start;
// do object sounds with id
// start at beginning
start = 0;
do {
i = FindSound(obj, sndIDHash, start);
// okay we have one (either object or special)
if (i != -1)
g_registeredSounds[i]->Remove();
// next start of search is one above this...
start = i + 1;
} while (i != -1);
// now do special sounds with ID
// restart
start = 0;
do {
i = FindSound(SPECIAL_SOUND, sndIDHash, start);
// okay we have one (either object or special)
if (i != -1)
g_registeredSounds[i]->Remove();
// next start of search is one above this...
start = i + 1;
} while (i != -1);
// okay all done
}
void RemoveAllSoundsWithID(uint32 obj) {
int32 i;
for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
if (g_registeredSounds[i]->GetObjectID() == obj)
g_registeredSounds[i]->Remove();
}
}
void StopAllSoundsNow() {
Tdebug("sounds.txt", "stopping");
uint32 i;
for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
g_registeredSounds[i]->Wipe();
}
for (i = 0; i < NUMBER_CHANNELS; i++) {
if (soundOn) {
StopSample(i);
}
SET_CHANNEL_FREE(i);
}
Tdebug("sounds.txt", "stopped");
}
// pause all sounds except special sounds...
void PauseSounds() {
pauseSound = TRUE8;
UpdateHearableSounds();
}
void UnpauseSounds() {
pauseSound = FALSE8;
}
} // End of namespace ICB