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