mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-17 15:18:11 +00:00
75bfc1290b
Using clang-tidy modernize-use-nullptr
1179 lines
31 KiB
C++
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
|