1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
5  * file distributed with this source distribution.
6  *
7  * Additional copyright for this file:
8  * Copyright (C) 1999-2000 Revolution Software Ltd.
9  * This code is based on source code created by Revolution Software,
10  * used with permission.
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25  *
26  */
27 
28 #include "engines/icb/p4.h"
29 #include "engines/icb/common/px_common.h"
30 #include "engines/icb/common/px_sfx_description.h"
31 #include "engines/icb/common/px_linkeddatafile.h"
32 #include "engines/icb/common/px_clu_api.h"
33 #include "engines/icb/icb.h"
34 #include "engines/icb/debug.h"
35 #include "engines/icb/sound.h"
36 #include "engines/icb/sound_lowlevel.h"
37 #include "engines/icb/global_objects.h"
38 #include "engines/icb/res_man.h"
39 #include "engines/icb/session.h"
40 #include "engines/icb/mission.h"
41 #include "engines/icb/common/px_sound_constants.h"
42 #include "engines/icb/sound_logic.h"
43 #include "engines/icb/remora.h"
44 
45 #include "common/textconsole.h"
46 
47 namespace ICB {
48 
49 const char *menuSoundID = "__MENU__";
50 uint32 menuSoundIDHash = NULL_HASH;
51 
52 // max is 127, min is zero
53 int32 speechVolume = 64;
54 int32 sfxVolume = 64;
55 int32 musicVolume = 64;
56 
57 int32 currentSoundLevel;
58 
GetCurrentSoundLevel()59 int32 GetCurrentSoundLevel() { return currentSoundLevel; }
60 
GetSpeechVolume()61 int32 GetSpeechVolume() { return speechVolume; }
62 
GetSfxVolume()63 int32 GetSfxVolume() { return sfxVolume; }
64 
GetMusicVolume()65 int32 GetMusicVolume() { return musicVolume; }
66 
SetSpeechVolume(int32 v)67 void SetSpeechVolume(int32 v) {
68 	if ((v < 0) || (v > 128))
69 		Fatal_error("Speech volume must be 0-128 not %d", v);
70 	speechVolume = v;
71 }
72 
SetSfxVolume(int32 v)73 void SetSfxVolume(int32 v) {
74 	if ((v < 0) || (v > 128))
75 		Fatal_error("Sfx volume must be 0-128 not %d", v);
76 	sfxVolume = v;
77 	// sfx volume will change next update...
78 }
79 
SetMusicVolume(int32 v)80 void SetMusicVolume(int32 v) {
81 	if ((v < 0) || (v > 128))
82 		Fatal_error("Music volume must be 0-128 not %d", v);
83 	musicVolume = v;
84 }
85 
86 // max volume
87 #define MAX_VOLUME 127
88 #define SPEECH_ON_VOLUME 48
89 #define REMORA_ACTIVE_VOLUME 24
90 
91 #define RESERVED_CHANNELS ((1 << SPEECH_CHANNEL) | (1 << MUSIC_CHANNEL))
92 
93 #define NUMBER_CHANNELS 24
94 
95 #define MAX_Y_DISTANCE_SQR (200 * 200)
96 
97 bool8 soundOn = TRUE8;
98 
99 bool8 pauseSound = TRUE8;
100 
101 int32 speechOnSliderValue = MAX_VOLUME;
102 
103 const uint8 pitchMakerMults[12 * 16] = PITCH_MULT;
104 const uint8 pitchMakerDivs[12 * 16] = PITCH_DIV;
105 
106 class PitchMaker {
107 
108 #define STANDARD_PITCH 4096
109 
110 public:
GetPitch(int32 initialPitch,int32 envValue)111 	int32 GetPitch(int32 initialPitch, int32 envValue) {
112 		int32 v;
113 		int32 e;
114 
115 		e = envValue;
116 		v = STANDARD_PITCH;
117 
118 		// shifting up an octave
119 		while (e >= (128 * 12)) {
120 			v <<= 1; // *2
121 			e -= (128 * 12);
122 		}
123 
124 		// shifting down an octave
125 		while (e <= (-128 * 12)) {
126 			v >>= 1; // /2
127 			e += (128 * 12);
128 		}
129 
130 		// divide by 8 so there are 16 per semi-tone
131 		e /= 8;
132 
133 		// use exp tables for - and + envValue
134 		if (e < 0) {
135 			v = (v * pitchMakerDivs[-e]) / 128;
136 		} else if (e > 0) {
137 			v = (v * pitchMakerMults[e]) / 128;
138 		}
139 
140 		return (initialPitch * v) / STANDARD_PITCH;
141 	}
142 };
143 
144 PitchMaker pitchMaker;
145 
146 const uint8 volMakerFunction[MAX_VOLUME + 1] = VOL_FUNCTION;
147 
148 class VolMaker {
149 
150 	  public:
GetVol(int32 i)151 	int32 GetVol(int32 i) {
152 		if (i < 0)
153 			return 0;
154 		else if (i > 127)
155 			return 127;
156 		else
157 			return (sfxVolume * volMakerFunction[i]) >> 7; // sfxVolume is 0 (none) to full (128)
158 	}
159 
GetVolMusic(int32 i)160 	int32 GetVolMusic(int32 i) {
161 		if (i < 0)
162 			return 0;
163 		else if (i > 127)
164 			return 127;
165 		else
166 			return (musicVolume * volMakerFunction[i]) >> 7; // sfxVolume is 0 (none) to full (128)
167 	}
168 };
169 
170 VolMaker volMaker;
171 
172 const char *gunSfxVar = "gun_sfx";
173 const char *defaultGunSfx = "gunshot";
174 const char *gunDesc = "gunshot";
175 
176 const char *ricochetSfxVar = "ricochet_sfx";
177 const char *defaultRicochetSfx = "ricochet";
178 const char *ricochetDesc = "ricochet";
179 
180 const char *openSfxVar = "open_sfx";
181 const char *defaultOpenSfx = "door_test";
182 const char *openDesc = "open";
183 
184 const char *closeSfxVar = "close_sfx";
185 const char *defaultCloseSfx = "door_test";
186 const char *closeDesc = "close";
187 
188 const char *addingMediSfxVar = "add_medi_sfx";
189 const char *defaultAddingMediSfx = "add_medi";
190 const char *defaultUsingMediSfx = "use_medi";
191 const char *addingMediDesc = "adding medipack";
192 
193 const char *addingClipSfxVar = "add_clip_sfx";
194 const char *defaultAddingClipSfx = "add_clip";
195 const char *addingClipDesc = "adding clip";
196 
197 const char *activateRemoraSfxVar = "remora_enter_sfx";
198 const char *activateRemoraSfx = "remora_enter";
199 const char *activateRemoraDesc = "entering Remora";
200 
201 const char *emailSfxVar = "remora_enter_sfx";
202 const char *defaultEmailSfx = "remora_enter";
203 const char *emailDesc = "email arriving";
204 
205 const char *deactivateRemoraSfxVar = "remora_exit_sfx";
206 const char *deactivateRemoraSfx = "remora_exit";
207 const char *deactivateRemoraDesc = "exiting Remora";
208 
209 const char *tinkleSfxVar = "tinkle_sfx";
210 const char *defaultTinkleSfx = "cartridge_tinkle";
211 const char *tinkleDesc = "tinkle";
212 
213 const char *menuUpDownSfx = "menu\\menu_up_down";
214 const char *menuSelectSfx = "menu\\menu_select";
215 const char *menuCancelSfx = "menu\\menu_cancel";
216 
217 #define INT_1 128
218 
219 // returns the value given by env at x (using INT maths)
220 // env is in format where 128=1
221 // x is in same units...
EvalEnv(const CEnvelope & env,int32 x)222 int32 EvalEnv(const CEnvelope &env, int32 x) {
223 
224 	int32 pa = env.a;
225 	int32 pb = env.b;
226 	int32 pc = env.c;
227 	int32 pd = env.d;
228 	int32 xxx, xx;
229 
230 	if (pa == 0)
231 		xxx = 0;
232 	else if (abs(pa) < INT_1)
233 		xxx = (pa * x * x * x) / (INT_1 * INT_1 * INT_1);
234 	else if (abs(pa) < (INT_1 * INT_1))
235 		xxx = (pa * (x * x * x / INT_1)) / (INT_1 * INT_1);
236 	// xxx=((pd/INT_1)*x*x*x)/(INT_1*INT_1);
237 	else if (abs(pa) < (INT_1 * INT_1 * INT_1))
238 		xxx = ((pa / INT_1) * (x * x * x / (INT_1))) / (INT_1);
239 	else
240 		xxx = ((pa / (INT_1 * INT_1)) * (x * x * x / INT_1));
241 	// xxx=((pd/(INT_1*INT_1))*x*x*x)/(INT_1);
242 
243 	if (pb == 0)
244 		xx = 0;
245 	else if (abs(pb) < (INT_1 * INT_1))
246 		xx = (pb * x * x) / (INT_1 * INT_1);
247 	else if (abs(pb) < (INT_1 * INT_1 * INT_1))
248 		xx = (pb / INT_1) * (x * x / INT_1);
249 	else
250 		xx = (pb / (INT_1 * INT_1)) * (x * x);
251 
252 	return xxx + xx + ((x * pc) / (INT_1)) + (pd);
253 }
254 
255 // get the sfxlist file!
GetMissionSfxFile()256 _linked_data_file *GetMissionSfxFile() {
257 	uint32 fileHash;
258 	uint32 clusterHash;
259 	_linked_data_file *f = NULL;
260 
261 	// if no mission return NULL
262 	if (!g_mission) {
263 		// PC NEEDS THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
264 		Fatal_error("No global mission sound so no special sfx!");
265 
266 	}
267 	// get the file...
268 	else {
269 
270 		fileHash = NULL_HASH;
271 		clusterHash = MS->Fetch_session_cluster_hash();
272 		f = (_linked_data_file *)private_session_resman->Res_open("m_sfxlist", fileHash, MS->Fetch_session_cluster(), clusterHash);
273 
274 	}
275 
276 	if ((f->GetHeaderVersion() != SFX_VERSION) || (f->header.type != FT_COMPILED_SFX))
277 		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);
278 
279 	return f;
280 }
281 
GetSessionSfxFile()282 _linked_data_file *GetSessionSfxFile() {
283 
284 	// if no session return NULL
285 	if ((!g_mission) || (!(g_mission->session))) {
286 		warning("no session so no sfx file!");
287 		return NULL;
288 	}
289 
290 	uint32 fileHash = NULL_HASH;
291 	uint32 clusterHash = MS->Fetch_session_cluster_hash();
292 	_linked_data_file *f;
293 
294 	// For the PC clustering the sfx file does not have the path, just the name
295 
296 	f = (_linked_data_file *)private_session_resman->Res_open(
297 
298 	    "s_sfxlist",
299 
300 	    fileHash, MS->Fetch_session_cluster(), clusterHash);
301 
302 	if ((f->GetHeaderVersion() != SFX_VERSION) || (f->header.type != FT_COMPILED_SFX))
303 		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);
304 	return f;
305 }
306 
307 // get a pointer to sfx (number) in the mission or session sfx file
GetMissionSfx(int32 number)308 CSfx *GetMissionSfx(int32 number) {
309 	_linked_data_file *linkedSfx;
310 
311 	linkedSfx = GetMissionSfxFile();
312 
313 	return (CSfx *)linkedSfx->Fetch_item_by_number(number);
314 }
315 
GetSessionSfx(int32 number)316 CSfx *GetSessionSfx(int32 number) {
317 	_linked_data_file *linkedSfx;
318 
319 	linkedSfx = GetSessionSfxFile();
320 
321 	return (CSfx *)linkedSfx->Fetch_item_by_number(number);
322 }
323 
324 // 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)
WhichMissionSfx(uint32 sfx)325 int32 WhichMissionSfx(uint32 sfx) {
326 	_linked_data_file *linkedSfx;
327 	uint32 n;
328 
329 	linkedSfx = GetMissionSfxFile();
330 	if (linkedSfx == NULL)
331 		return -1;
332 
333 	n = linkedSfx->Fetch_item_number_by_hash(sfx);
334 
335 	if (n == PX_LINKED_DATA_FILE_ERROR)
336 		return -1;
337 	else
338 		return (int32)n;
339 }
340 
WhichSessionSfx(uint32 sfx)341 int32 WhichSessionSfx(uint32 sfx) {
342 	_linked_data_file *linkedSfx;
343 	uint32 n;
344 
345 	linkedSfx = GetSessionSfxFile();
346 	if (linkedSfx == NULL)
347 		return -1;
348 
349 	n = linkedSfx->Fetch_item_number_by_hash(sfx);
350 
351 	if (n == PX_LINKED_DATA_FILE_ERROR)
352 		return -1;
353 	else
354 		return (int32)n;
355 }
356 
SfxExists(uint32 sfxHash)357 bool8 SfxExists(uint32 sfxHash) {
358 	int32 sfxNumber;
359 
360 	sfxNumber = WhichMissionSfx(sfxHash);
361 
362 	if (sfxNumber == -1) {
363 		sfxNumber = WhichSessionSfx(sfxHash);
364 	}
365 
366 	if (sfxNumber == -1)
367 		return FALSE8;
368 	else
369 		return TRUE8;
370 }
371 
SfxExists(const char * sfx)372 bool8 SfxExists(const char *sfx) { return SfxExists(HashString(sfx)); }
373 
374 int32 channelUsage = 0;
375 
376 #define IS_CHANNEL_USED(CH) ((channelUsage | RESERVED_CHANNELS) & (1 << CH))
377 #define IS_CHANNEL_FREE(CH) ((~(channelUsage | RESERVED_CHANNELS)) & (1 << CH))
378 #define SET_CHANNEL_USED(CH)                                                                                                                                                       \
379 	{ channelUsage |= (1 << CH); }
380 #define SET_CHANNEL_FREE(CH)                                                                                                                                                       \
381 	{ channelUsage &= (~(1 << CH)); }
382 
GetFreeChannel()383 int32 GetFreeChannel() {
384 	int32 i;
385 	for (i = 0; i < NUMBER_CHANNELS; i++)
386 		if (IS_CHANNEL_FREE(i)) {
387 			return i;
388 		}
389 	return -1;
390 }
391 
392 #define UPDATES_PER_SECOND 10
393 #define MAX_ENV_POSITION (128 * 128)
394 
395 #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)
396 
397 // Get sfx for this registered sound
GetSfx()398 CSfx *CRegisteredSound::GetSfx() {
399 	CSfx *the_sfx = 0;
400 
401 	if (m_sfxNumber == -1)
402 		Fatal_error("sfx is not found in session or mission");
403 
404 	if (m_inSession)
405 		the_sfx = GetSessionSfx(m_sfxNumber);
406 	else
407 		the_sfx = GetMissionSfx(m_sfxNumber);
408 
409 	if (!the_sfx)
410 		Fatal_error("Can't find registered SFX??? (number %d m_inSession=%d)", m_sfxNumber, m_inSession);
411 
412 	return the_sfx;
413 }
414 
415 // Zero out all the values of a sound
Wipe()416 void CRegisteredSound::Wipe() {
417 	m_objID = NO_REGISTERED_SOUND;
418 	m_sndHash = NULL_HASH;
419 	m_channel = -1;
420 
421 	m_x = REAL_ZERO;
422 	m_y = REAL_ZERO;
423 	m_z = REAL_ZERO;
424 
425 	m_restart_time = -1;
426 
427 	m_sfxNumber = NULL_HASH;
428 
429 	m_velocity = 0;
430 	m_position = 0; // position*128
431 
432 	m_volume = 0;
433 	m_current_pitch = 0;
434 	m_sample_pitch = 0;
435 	m_rand_pitch_value = 0;
436 	m_next_random_pos = 0;
437 	m_pan = 0;
438 
439 	m_xoffset = REAL_ZERO;
440 	m_yoffset = REAL_ZERO;
441 	m_zoffset = REAL_ZERO;
442 
443 	m_objMoving = 0;
444 	m_volume_offset = 0;
445 	m_inSession = FALSE8;
446 	m_turnOff = FALSE8;
447 	m_remove = FALSE8;
448 }
449 
450 // update a sound if it is being turned off (reduce volume until 0 then destroy)
TurnOff()451 void CRegisteredSound::TurnOff() {
452 	if (m_turnOff) {
453 		if (m_channel == -1) {
454 			m_turnOff = FALSE8;
455 			if (m_remove)
456 				m_objID = NO_REGISTERED_SOUND; // remove sound
457 			return;
458 		}
459 
460 		if (m_volume == 0) {
461 			Tdebug("sounds.txt", "Finally turning off %d!", m_channel);
462 
463 			if (soundOn)
464 				StopSample(m_channel);
465 
466 			SET_CHANNEL_FREE(m_channel);
467 			m_channel = -1;
468 			m_turnOff = FALSE8;
469 			if (m_remove)
470 				m_objID = NO_REGISTERED_SOUND; // remove sound
471 		} else {
472 			m_volume -= VOLUME_SLIDE;
473 			if (m_volume < 0)
474 				m_volume = 0;
475 
476 			if (soundOn)
477 				SetChannelVolumeAndPan(m_channel, volMaker.GetVol(m_volume), m_pan);
478 		}
479 	}
480 }
481 
482 // update every game cycle...
UpdateGameCycle(int32 newVol,int32 newPan)483 void CRegisteredSound::UpdateGameCycle(int32 newVol, int32 newPan) {
484 	CSfx *sfx;
485 
486 	if (m_objID == NO_REGISTERED_SOUND)
487 		return;
488 
489 	// if we're beyond next random update point (this must be done before wrap around...
490 	sfx = GetSfx();
491 
492 	// new random pitch value
493 	if ((sfx->m_rand_mode) && (m_position > m_next_random_pos)) {
494 		GetRandom(sfx);
495 		m_next_random_pos += (128 * 128 - 1) / (sfx->m_rand_mode);
496 	}
497 
498 	// stop sound if nesesary or loop
499 	if (m_position > MAX_ENV_POSITION) {
500 		if (!((sfx->m_looping) & SFX_LOOPING_FLAG)) {
501 			m_position = MAX_ENV_POSITION - 1; // hold here while turning off...
502 
503 			Tdebug("sounds.txt", "sound ending");
504 
505 			// turn off next cycle..
506 			if (m_channel != -1) {
507 				m_turnOff = TRUE8; // this will keep updating until m_channel is set to -1 by turn off at which point the sound will end
508 				m_remove = TRUE8;  // remove when finished turning off
509 			} else
510 				m_objID = NO_REGISTERED_SOUND; // don't need to update anymore...
511 		} else {
512 			// Tdebug("sounds.txt","sound looping...");
513 
514 			m_next_random_pos = 0; // reset random pos thingy
515 
516 			// if sample is not looping then replay...
517 			if ((m_channel != -1) && (!((sfx->m_looping) & WAV_LOOPING_FLAG))) {
518 				m_position = 0;
519 			} else {                                // sample is looping or sound isn't on so just reset wave
520 				m_position -= MAX_ENV_POSITION; // reset wave
521 
522 				if (m_position <= 0) // definately don't restart accidently
523 					m_position = 1;
524 			}
525 		}
526 	}
527 
528 	if (m_position < 0)
529 		m_position++;
530 
531 	// if we're currently playing then update the low level if m_position=0 then time to start up sound
532 	if ((m_channel != -1) && (m_position >= 0)) {
533 		int32 v, p;
534 		int32 myPos;
535 
536 		myPos = (m_position * (sfx->m_pitch.div)) & (128 * 128 - 1);                                        // work out where we are on the pitch graph...
537 		p = pitchMaker.GetPitch(m_sample_pitch, m_rand_pitch_value + EvalEnv(sfx->m_pitch, myPos / INT_1)); // get envelope pitch
538 
539 		// if paused sounds then set pitch to zero
540 
541 		if ((pauseSound) && ((m_sndHash != menuSoundIDHash) || (m_objID != SPECIAL_SOUND))) {
542 			// printf("channel %d pitch is now 0\n",m_channel);
543 			p = 0;
544 		}
545 
546 		// has changed so update pitch
547 
548 		if (p != m_current_pitch) {
549 			m_current_pitch = p;
550 
551 			if (soundOn)
552 				SetChannelPitch(m_channel, m_current_pitch);
553 		}
554 
555 		// VOLUME
556 
557 		if (m_turnOff) {
558 			v = m_volume; // if we're turning off then ignore volume envelope
559 		} else {
560 			myPos = (m_position * (sfx->m_volume.div)) & (128 * 128 - 1); // volume pos
561 			v = (newVol * EvalEnv(sfx->m_volume, myPos / INT_1)) / 128;   // get new volume from envelope and input volume
562 
563 			// decrease volume if speech is playing...
564 			v = (v * speechOnSliderValue) / 128;
565 
566 			// limit to 0-127 for volume
567 			if (v > MAX_VOLUME)
568 				v = MAX_VOLUME;
569 			else if (v < 0)
570 				v = 0;
571 		}
572 
573 		// current sound level is measure of how much sound is in scene
574 		// here it is both affected by distance from player and
575 		// volume on the curve...
576 		currentSoundLevel += (v * v);
577 
578 		// if either volume or pan has changed
579 
580 		if ((v != m_volume) || (newPan != m_pan)) {
581 			m_volume = v;
582 			m_pan = newPan;
583 
584 			// only set if sound on
585 			if (soundOn)
586 				SetChannelVolumeAndPan(m_channel, volMaker.GetVol((m_volume * m_volume_offset) / 128), m_pan);
587 		}
588 
589 		// if we need to start the sound effect
590 
591 		if (m_position == 0) {
592 			Tdebug("sounds.txt", "Starting sound");
593 
594 			m_position = 1; // so we don't try and start it twice in the unlikely event
595 			// that no 10hz signal goes between calls...
596 
597 			// only start sample if sound is on
598 			if (soundOn) {
599 				StartSample(m_channel, sfx->GetSampleName(), m_inSession, (sfx->m_looping) & WAV_LOOPING_FLAG);
600 			}
601 		}
602 	}
603 }
604 
605 // update at 10hz EXACTLY
Update10Hz()606 void CRegisteredSound::Update10Hz() {
607 	if (m_objID == NO_REGISTERED_SOUND)
608 		return;
609 
610 	// only update special menu sounds during 10hz signal...
611 	// if sounds are paused and sound is not a special menu sound (either sndHash doesn't match or sound object is not special)
612 	if ((pauseSound) && ((m_sndHash != menuSoundIDHash) || (m_objID != SPECIAL_SOUND)))
613 		return;
614 
615 	if (m_position >= 0)
616 		m_position += m_velocity;
617 }
618 
619 int32 GetSoundCloser(int32 objID, PXreal x, PXreal y, PXreal z);
620 
621 // make a sound hearable, get a channel and set to it...
SetHearable()622 bool8 CRegisteredSound::SetHearable() {
623 	int32 ch;
624 
625 	// sound is already hearable so ignore this call
626 	if (m_channel != -1)
627 		return TRUE8;
628 
629 	Tdebug("sounds.txt", "Sound is now hearable");
630 
631 	// find a free channel
632 	ch = GetFreeChannel();
633 	Tdebug("sounds.txt", "Channel %d", ch);
634 
635 	// if we couldn't get a channel then we might want to knock out another sound
636 	if (ch == -1) {
637 
638 		ch = GetSoundCloser(m_objID, m_x, m_y, m_z);
639 		if (ch == -1)
640 			return TRUE8; // still don't return true just dont set a channel
641 	}
642 
643 	SET_CHANNEL_USED(ch);
644 
645 	// reset volume and pitch ready to be set (keep existing pan)
646 	m_volume = 0xffff;
647 	m_current_pitch = 0xffff;
648 
649 	// set channel
650 	m_channel = ch;
651 
652 	m_position = m_restart_time; // start sample at next update...
653 	m_restart_time = -1;
654 
655 	return TRUE8;
656 }
657 
658 // make a sound unhearable, remove from playing sounds list...
SetUnhearable()659 void CRegisteredSound::SetUnhearable() {
660 	// already unhearable or turning off so do nothing...
661 	if ((m_channel == -1) || (m_turnOff))
662 		return;
663 
664 	Tdebug("sounds.txt", "Sound is now unhearable");
665 
666 	// start turning off sound... (we are definately playing it...)
667 	m_turnOff = TRUE8;
668 	// don't remove though
669 }
670 
671 // remove the sound
Remove()672 void CRegisteredSound::Remove() {
673 	// stop sample etc
674 	if (m_channel == -1) {
675 		m_objID = NO_REGISTERED_SOUND; // just remove it
676 	} else {
677 		m_turnOff = TRUE8; // fade down
678 		m_remove = TRUE8;  // then remove
679 	}
680 }
681 
GetRandom(CSfx * sfx)682 void CRegisteredSound::GetRandom(CSfx *sfx) {
683 	if (sfx->m_rand_pitch)
684 		m_rand_pitch_value = (g_icb->getRandomSource()->getRandomNumber(2 * 128 * sfx->m_rand_pitch - 1)) - (128 * sfx->m_rand_pitch);
685 	else
686 		m_rand_pitch_value = 0;
687 }
688 
689 // register a new sound setting internal params
Register(const char * sndName,const char * sfxName,uint32 sfxHash,int8 volume)690 void CRegisteredSound::Register(const char *sndName, const char *sfxName, uint32 sfxHash, int8 volume) {
691 	CSfx *sfx;
692 
693 	// simple params
694 	m_sndHash = HashString(sndName);
695 
696 	if (sfxHash == NULL_HASH)
697 		sfxHash = HashString(sfxName);
698 
699 	m_sfxNumber = WhichMissionSfx(sfxHash);
700 	m_inSession = FALSE8;
701 
702 	if (m_sfxNumber == -1) {
703 		m_sfxNumber = WhichSessionSfx(sfxHash);
704 		m_inSession = TRUE8;
705 	}
706 
707 	if (m_sfxNumber == -1)
708 		Fatal_error("sfx %s(%08x) is not found in session or mission", sfxName, sfxHash);
709 
710 	// check it's okay...
711 	sfx = GetSfx();
712 
713 	// set velocity = MAX_POSITION/((duration/128)*UPDATES_PER_SECOND)= 128*128*128/(duration*UPDATES_PER_SECOND)
714 	m_velocity = (MAX_ENV_POSITION * 128) / (sfx->m_duration * UPDATES_PER_SECOND);
715 	Tdebug("sounds.txt", "length=%d secs vel %d\n", (sfx->m_duration) / 128, m_velocity);
716 
717 	// start next frame..
718 	m_restart_time = -1; // when hearable start at -1
719 	m_position = -1;
720 
721 	m_channel = -1;
722 
723 	m_volume = 0; // default, gets changed if sound is heard
724 	m_pan = 0;    // default (centre) will get changed before playing if necesary
725 
726 	m_sample_pitch = GetSamplePitch(sfx->GetSampleName(), m_inSession);
727 
728 	m_current_pitch = 0; // always alter pitch before playing
729 
730 	GetRandom(sfx);
731 	m_next_random_pos = 0;
732 
733 	m_remove = FALSE8;
734 	m_turnOff = FALSE8;
735 
736 	m_xoffset = m_yoffset = m_zoffset = (PXreal)0;
737 
738 	m_volume_offset = volume;
739 
740 	padding1 = 0;
741 }
742 
743 // register a sound and take position from the position of the object registering the sound
RegisterFromObject(const uint32 objID,const char * sndName,const char * sfxName,uint32 sfxHash,PXreal xo,PXreal yo,PXreal zo,int8 volume)744 void CRegisteredSound::RegisterFromObject(const uint32 objID, const char *sndName, const char *sfxName, uint32 sfxHash, PXreal xo, PXreal yo, PXreal zo, int8 volume) {
745 	Register(sndName, sfxName, sfxHash, volume);
746 	m_xoffset = xo;
747 	m_yoffset = yo;
748 	m_zoffset = zo;
749 
750 	// set this last so any updates don't know it's turned on yet...
751 	m_objID = objID;
752 
753 	if ((MS->logic_structs[m_objID]->image_type) == VOXEL) {
754 		Tdebug("sounds.txt", "sound creator is an actor");
755 		m_objMoving = 1;
756 	} else
757 		m_objMoving = 0;
758 
759 	MS->logic_structs[m_objID]->GetPosition(m_x, m_y, m_z);
760 
761 	// This tells the sound logic about the new sound, to drive events etc.
762 	g_oSoundLogicEngine->NewSound(objID, (int32)m_x, (int32)m_y, (int32)m_z, GetSfx(), m_sndHash);
763 }
764 
765 // register a sound as an absolute position
RegisterFromAbsolute(const uint32 objID,const char * sndName,const char * sfxName,uint32 sfxHash,PXreal x,PXreal y,PXreal z,int8 volume)766 void CRegisteredSound::RegisterFromAbsolute(const uint32 objID, const char *sndName, const char *sfxName, uint32 sfxHash, PXreal x, PXreal y, PXreal z, int8 volume) {
767 
768 	Register(sndName, sfxName, sfxHash, volume);
769 
770 	// set this last so any updates don't know it's turned on yet...
771 	m_objID = objID;
772 
773 	m_x = x;
774 	m_y = y;
775 	m_z = z;
776 	m_objMoving = 0;
777 }
778 
779 #define MAX_SCREEN (float)(SCREEN_WIDTH / 2)
780 
781 // if the sound if coming froma moving souce, this updates the position, otherwise it does nothing
GetPosition()782 void CRegisteredSound::GetPosition() {
783 	if (m_objMoving) {
784 		MS->logic_structs[m_objID]->GetPosition(m_x, m_y, m_z);
785 		m_x += m_xoffset;
786 		m_y += m_yoffset;
787 		m_z += m_zoffset;
788 	}
789 }
790 
791 // gets the volume and pan values for a sound, based on the position of the sound relative to the actor / camera
GetVolumeAndPan(int32 & vol,int32 & pan)792 void CRegisteredSound::GetVolumeAndPan(int32 &vol, int32 &pan) {
793 	// special sound, always central, etc
794 	if (m_objID == SPECIAL_SOUND) {
795 		vol = (int32)m_z; // get sound from z position...!
796 		pan = (int32)m_x; // pan is x position!
797 		return;
798 	}
799 
800 	GetPosition();
801 	vol = g_oSoundLogicEngine->ProcessSound((int32)m_x, (int32)m_y, (int32)m_z, GetSfx());
802 
803 	PXvector v = {m_x, m_y, m_z};
804 	PXvector screenPos;
805 	bool8 dontUse;
806 
807 	if (!MS->SetOK()) {
808 		pan = 0;
809 		return;
810 	}
811 
812 	PXcamera &cam = MS->GetCamera();
813 	PXWorldToFilm(v, cam, dontUse, screenPos);
814 
815 	if (screenPos.x < (-MAX_SCREEN))
816 		screenPos.x = (-MAX_SCREEN);
817 	if (screenPos.x > MAX_SCREEN)
818 		screenPos.x = MAX_SCREEN;
819 
820 	// /2 for centralisation
821 	pan = (int32)(128 * screenPos.x) / (SCREEN_WIDTH);
822 
823 	return;
824 }
825 
826 CRegisteredSound *g_registeredSounds[MAX_REGISTERED_SOUNDS];
827 
828 int32 assignedSounds = 0;
829 
830 // must be called at 10hz time...
UpdateSounds10Hz()831 void UpdateSounds10Hz() {
832 	int32 i;
833 
834 	for (i = 0; i < MAX_REGISTERED_SOUNDS; i++)
835 		g_registeredSounds[i]->Update10Hz();
836 }
837 
838 // called every game cycle sets hearable and unhearable sounds...
UpdateHearableSounds()839 void UpdateHearableSounds() {
840 	int32 i;
841 	int32 assigned;
842 	int32 volume;
843 	int32 pan;
844 
845 	assigned = 0;
846 
847 	// no sound...
848 	currentSoundLevel = 0;
849 
850 	for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
851 		// we might need to do this even if sound is not currently used
852 		g_registeredSounds[i]->TurnOff();
853 
854 		if (g_registeredSounds[i]->IsUsed()) {
855 			// work out if is current hearable by the camera
856 
857 			// if speical sound or sound is not paused then we get the volume and pan from manager
858 			if (((g_registeredSounds[i]->m_objID == SPECIAL_SOUND) && (g_registeredSounds[i]->m_sndHash == menuSoundIDHash)) || (!pauseSound)) {
859 				g_registeredSounds[i]->GetVolumeAndPan(volume, pan);
860 
861 				// if volume is greater than zero
862 				if (volume > 0) {
863 					if (!g_registeredSounds[i]->SetHearable())
864 						Fatal_error("Can't find a free channel to play sound");
865 				} else
866 					g_registeredSounds[i]->SetUnhearable(); // no Update required we are turning off or are off screen....
867 			} else {
868 				volume = 0;
869 				pan = 0;
870 			}
871 
872 			// update the wave etc
873 			g_registeredSounds[i]->UpdateGameCycle(volume, pan);
874 		}
875 	}
876 
877 	currentSoundLevel = (currentSoundLevel * 100) / (128 * 128);
878 	if (currentSoundLevel > 100)
879 		currentSoundLevel = 100;
880 
881 	assignedSounds = assigned;
882 
883 	// speech volume slider
884 
885 	int32 speechOnSliderTarget;
886 
887 	// if somebody speaking...
888 	if ((g_mission) && (g_mission->session) && (MS->speech_info[CONV_ID].total_subscribers > 0) && (GetSpeechVolume() > 0))
889 		speechOnSliderTarget = SPEECH_ON_VOLUME;
890 	// if remora active even lower volume
891 	else if (g_oRemora->IsActive())
892 		speechOnSliderTarget = REMORA_ACTIVE_VOLUME;
893 	// otherwise our target is full volume
894 	else
895 		speechOnSliderTarget = MAX_VOLUME;
896 
897 	// update
898 	if (speechOnSliderValue > speechOnSliderTarget) {
899 		speechOnSliderValue -= VOLUME_SLIDE;
900 		if (speechOnSliderValue < speechOnSliderTarget)
901 			speechOnSliderValue = speechOnSliderTarget;
902 	} else if (speechOnSliderValue < speechOnSliderTarget) {
903 		speechOnSliderValue += VOLUME_SLIDE;
904 		if (speechOnSliderValue > speechOnSliderTarget)
905 			speechOnSliderValue = speechOnSliderTarget;
906 	}
907 }
908 
GetSoundCloser(int32 objID,PXreal x,PXreal y,PXreal z)909 int32 GetSoundCloser(int32 objID, PXreal x, PXreal y, PXreal z) {
910 	PXreal px = REAL_ZERO;
911 	PXreal py = REAL_ZERO;
912 	PXreal pz = REAL_ZERO; // play pos
913 
914 	PXreal dist;     // distance of this object
915 	PXreal thisDist; // distance of comparing object
916 
917 	int32 i;
918 	int32 c;
919 
920 	// if
921 	if (objID != SPECIAL_SOUND) {
922 		MS->player.log->GetPosition(px, py, pz);
923 
924 		x -= px;
925 		y -= py;
926 		z -= pz;
927 		dist = x * x + y * y + z * z;
928 	} else // special sound so distance is by definition 0
929 		dist = (PXreal)0;
930 
931 	PXreal maxDist = dist;
932 	int32 sfxReplace = -1;
933 	int32 channelReplace = -1;
934 
935 	for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
936 		c = g_registeredSounds[i]->m_channel;
937 
938 		// can only replace a sound that has a channel and is not a special sound
939 		if ((c != -1) && (g_registeredSounds[i]->m_objID != SPECIAL_SOUND)) {
940 			g_registeredSounds[i]->GetPosition();
941 			x = g_registeredSounds[i]->m_x - px;
942 			y = g_registeredSounds[i]->m_y - py;
943 			z = g_registeredSounds[i]->m_z - pz;
944 			thisDist = x * x + y * y + z * z;
945 
946 			// if this distance from player is more than the maximum so far then this is the furthest sound
947 			// yet found, and should therefore be replaced...
948 			if (thisDist > maxDist) {
949 				// this is the new max distance
950 				maxDist = thisDist;
951 
952 				// this is the sfx and channcel to take
953 				sfxReplace = i;
954 				channelReplace = c;
955 			}
956 		}
957 	}
958 
959 	// we found a sound further away than us therefore we can have it's channcel
960 	// the sfx is sfxReplace, the channel we are stealing is channelReplace
961 	if (channelReplace != -1) {
962 		Tdebug("sounds.txt", "replacing sound %d (channel %d) because it's too far away (dist: %g, our dist: %g)", sfxReplace, channelReplace, maxDist, dist);
963 
964 		// the sfx now has no channel
965 		g_registeredSounds[sfxReplace]->m_channel = -1;
966 
967 		// we return the channel we have just pinched
968 
969 		return channelReplace;
970 	}
971 
972 	// otherwise we found no channel so return -1
973 	return -1;
974 }
975 
TurnOffSound()976 void TurnOffSound() { soundOn = FALSE8; }
977 
TurnOnSound()978 void TurnOnSound() { soundOn = TRUE8; }
979 
GetFreeSound(const char * sfxName)980 int32 GetFreeSound(const char *sfxName) {
981 	int32 i;
982 
983 	for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
984 		if (g_registeredSounds[i]->IsFree()) {
985 			Tdebug("sounds.txt", "sfx: %s  registered sound: %d", sfxName, i);
986 			return i;
987 		}
988 	}
989 
990 	Fatal_error("No free sounds! %s", sfxName);
991 	return -1;
992 }
993 
FindSound(uint32 obj,uint32 sndHash,int32 start=0)994 int32 FindSound(uint32 obj, uint32 sndHash, int32 start = 0) {
995 	int32 i;
996 
997 	for (i = start; i < MAX_REGISTERED_SOUNDS; i++) {
998 		if (g_registeredSounds[i]->IsThisSound(obj, sndHash))
999 			return i;
1000 	}
1001 
1002 	// should this be a fatal error..??
1003 	return -1;
1004 }
1005 
1006 // fn_play_sfx_offset(sfxName,sndID,x,y,z,isNico)
1007 // this is the generic function
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)1008 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,
1009 						 int8 volume_offset) {
1010 	// check we have the hash before we register any sounds...!
1011 	if (menuSoundIDHash == NULL_HASH)
1012 		menuSoundIDHash = HashString(menuSoundID);
1013 
1014 	int32 i;
1015 	i = GetFreeSound(sfxName);
1016 
1017 	// for getting nico position
1018 	PXreal x, y, z;
1019 
1020 	if ((obj != SPECIAL_SOUND) && (pauseSound))
1021 		warning("Registering sound whilst sound paused!");
1022 
1023 	// special sound
1024 	if (obj == SPECIAL_SOUND) {
1025 		g_registeredSounds[i]->RegisterFromAbsolute(obj, sndID, sfxName, sfxHash, xo, yo, zo, volume_offset);
1026 	}
1027 	// absolute sound (no name)
1028 	else if ((offsetName == NULL) || (strcmp(offsetName, "") == 0)) {
1029 		// absolute address
1030 		g_registeredSounds[i]->RegisterFromAbsolute(obj, sndID, sfxName, sfxHash, xo, yo, zo, volume_offset);
1031 	}
1032 	// object is a nico marker
1033 	else if (isNico) {
1034 		// is nico so get position of it...
1035 		// x=, y=, z=
1036 		_feature_info *fi = (_feature_info *)(MS->features->Fetch_item_by_name(offsetName));
1037 		x = fi->x;
1038 		y = fi->y;
1039 		z = fi->z;
1040 		g_registeredSounds[i]->RegisterFromAbsolute(obj, sndID, sfxName, sfxHash, x + xo, y + yo, z + zo, volume_offset);
1041 	}
1042 	// object is a mega
1043 	else {
1044 		// is mega object so attach sound to it
1045 		// obj=
1046 		obj = MS->objects->Fetch_item_number_by_name(offsetName);
1047 		g_registeredSounds[i]->RegisterFromObject(obj, sndID, sfxName, sfxHash, xo, yo, zo, volume_offset);
1048 	}
1049 
1050 	Tdebug("sounds.txt", "restart time is %d %d\n", (-1) - (time), time);
1051 	g_registeredSounds[i]->m_restart_time = (-1) - (time);
1052 }
1053 
1054 // register a sound from an object
RegisterSound(uint32 obj,const char * sfxName,uint32 sfxHash,const char * sndID,int8 volume_offset)1055 void RegisterSound(uint32 obj, const char *sfxName, uint32 sfxHash, const char *sndID, int8 volume_offset) {
1056 	const char *name;
1057 
1058 	if (obj == SPECIAL_SOUND)
1059 		name = NULL;
1060 	else
1061 		name = (const char *)(MS->objects->Fetch_items_name_by_number(obj));
1062 
1063 	RegisterSoundOffset(obj, name, sfxName, sfxHash, sndID, (PXreal)0, (PXreal)0, (PXreal)0, 0, 0, volume_offset);
1064 }
1065 
1066 // register a sound from an absolute position
RegisterSoundAbsolute(uint32 obj,const char * sfxName,uint32 sfxHash,const char * sndID,PXreal x,PXreal y,PXreal z,int8 volume_offset)1067 void RegisterSoundAbsolute(uint32 obj, const char *sfxName, uint32 sfxHash, const char *sndID, PXreal x, PXreal y, PXreal z, int8 volume_offset) {
1068 	RegisterSoundOffset(obj, NULL, sfxName, sfxHash, sndID, x, y, z, 0, 0, volume_offset);
1069 }
1070 
RegisterSoundTime(uint32 obj,const char * sfxName,uint32 sfxHash,const char * sndID,int32 time,int8 volume_offset)1071 void RegisterSoundTime(uint32 obj, const char *sfxName, uint32 sfxHash, const char *sndID, int32 time, int8 volume_offset) {
1072 	const char *name;
1073 
1074 	if (obj == SPECIAL_SOUND)
1075 		name = NULL;
1076 	else
1077 		name = (const char *)(MS->objects->Fetch_items_name_by_number(obj));
1078 
1079 	RegisterSoundOffset(obj, name, sfxName, sfxHash, sndID, (PXreal)0, (PXreal)0, (PXreal)0, 0, time, volume_offset);
1080 }
1081 
1082 // special sound
1083 // for menus
RegisterMenuSound(const char * sfxName,uint32 sfxHash,int32 volume,int32 pan,int8 volume_offset)1084 void RegisterMenuSound(const char *sfxName, uint32 sfxHash, int32 volume, int32 pan, int8 volume_offset) {
1085 	// volume is z of position
1086 	RegisterSoundOffset(SPECIAL_SOUND, NULL, sfxName, sfxHash, menuSoundID, (PXreal)pan, (PXreal)0, (PXreal)volume, 0, 0, volume_offset);
1087 }
1088 
1089 // special sound
1090 // for in game (these are paused just like any other...
RegisterSoundSpecial(const char * sfxName,uint32 sfxHash,const char * sndID,int32 volume,int32 pan,int8 volume_offset)1091 void RegisterSoundSpecial(const char *sfxName, uint32 sfxHash, const char *sndID, int32 volume, int32 pan, int8 volume_offset) {
1092 	// volume is z of position
1093 	RegisterSoundOffset(SPECIAL_SOUND, NULL, sfxName, sfxHash, sndID, (PXreal)pan, (PXreal)0, (PXreal)volume, 0, 0, volume_offset);
1094 }
1095 
RemoveRegisteredSound(uint32 obj,const char * sndID)1096 void RemoveRegisteredSound(uint32 obj, const char *sndID) {
1097 	int32 i;
1098 	uint32 sndIDHash;
1099 
1100 	sndIDHash = HashString(sndID);
1101 
1102 	int32 start;
1103 
1104 	// do object sounds with id
1105 
1106 	// start at beginning
1107 	start = 0;
1108 
1109 	do {
1110 		i = FindSound(obj, sndIDHash, start);
1111 		// okay we have one (either object or special)
1112 		if (i != -1)
1113 			g_registeredSounds[i]->Remove();
1114 
1115 		// next start of search is one above this...
1116 		start = i + 1;
1117 
1118 	} while (i != -1);
1119 
1120 	// now do special sounds with ID
1121 
1122 	// restart
1123 	start = 0;
1124 
1125 	do {
1126 		i = FindSound(SPECIAL_SOUND, sndIDHash, start);
1127 
1128 		// okay we have one (either object or special)
1129 		if (i != -1)
1130 			g_registeredSounds[i]->Remove();
1131 
1132 		// next start of search is one above this...
1133 		start = i + 1;
1134 
1135 	} while (i != -1);
1136 
1137 	// okay all done
1138 }
1139 
RemoveAllSoundsWithID(uint32 obj)1140 void RemoveAllSoundsWithID(uint32 obj) {
1141 	int32 i;
1142 
1143 	for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
1144 		if (g_registeredSounds[i]->GetObjectID() == obj)
1145 			g_registeredSounds[i]->Remove();
1146 	}
1147 }
1148 
StopAllSoundsNow()1149 void StopAllSoundsNow() {
1150 	Tdebug("sounds.txt", "stopping");
1151 	uint32 i;
1152 
1153 	for (i = 0; i < MAX_REGISTERED_SOUNDS; i++) {
1154 		g_registeredSounds[i]->Wipe();
1155 	}
1156 
1157 	for (i = 0; i < NUMBER_CHANNELS; i++) {
1158 		if (soundOn) {
1159 			StopSample(i);
1160 		}
1161 
1162 		SET_CHANNEL_FREE(i);
1163 	}
1164 
1165 	Tdebug("sounds.txt", "stopped");
1166 }
1167 
1168 // pause all sounds except special sounds...
PauseSounds()1169 void PauseSounds() {
1170 	pauseSound = TRUE8;
1171 	UpdateHearableSounds();
1172 }
1173 
UnpauseSounds()1174 void UnpauseSounds() {
1175 	pauseSound = FALSE8;
1176 }
1177 
1178 } // End of namespace ICB
1179