1 /*
2 * Portions of this file are copyright Rebirth contributors and licensed as
3 * described in COPYING.txt.
4 * Portions of this file are copyright Parallax Software and licensed
5 * according to the Parallax license below.
6 * See COPYING.txt for license details.
7
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
18 */
19
20
21 #include <algorithm>
22 #include <stdexcept>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <fcntl.h>
26 #include <string.h>
27 #include <ctype.h>
28
29 #include "maths.h"
30 #include "object.h"
31 #include "timer.h"
32 #include "joy.h"
33 #include "digi.h"
34 #include "digi_audio.h"
35 #include "sounds.h"
36 #include "args.h"
37 #include "newdemo.h"
38 #include "gameseg.h"
39 #include "dxxerror.h"
40 #include "wall.h"
41 #include "piggy.h"
42 #include "kconfig.h"
43 #include "config.h"
44
45 #include "compiler-range_for.h"
46 #include "d_levelstate.h"
47 #include <iterator>
48 #include <utility>
49
50 using std::max;
51
52 namespace dcx {
53
54 namespace {
55
56 constexpr std::integral_constant<unsigned, 1> SOF_USED{}; // Set if this sample is used
57 constexpr std::integral_constant<unsigned, 4> SOF_LINK_TO_OBJ{}; // Sound is linked to a moving object. If object dies, then finishes play and quits.
58 constexpr std::integral_constant<unsigned, 8> SOF_LINK_TO_POS{}; // Sound is linked to segment, pos
59 constexpr std::integral_constant<unsigned, 16> SOF_PLAY_FOREVER{}; // Play forever (or until level is stopped), otherwise plays once
60 constexpr std::integral_constant<unsigned, 32> SOF_PERMANENT{}; // Part of the level, like a waterfall or fan
61
62 constexpr std::integral_constant<unsigned, 150> MAX_SOUND_OBJECTS{};
63
64 }
65
66 struct sound_object
67 {
68 short signature; // A unique signature to this sound
69 ubyte flags; // Used to tell if this slot is used and/or currently playing, and how long.
70 ubyte pad; // Keep alignment
71 fix max_volume; // Max volume that this sound is playing at
72 vm_distance max_distance; // The max distance that this sound can be heard at...
73 int volume; // Volume that this sound is playing at
74 sound_pan pan; // Pan value that this sound is playing at
75 int channel; // What channel this is playing on, -1 if not playing
76 short soundnum; // The sound number that is playing
77 int loop_start; // The start point of the loop. -1 means no loop
78 int loop_end; // The end point of the loop
79 union link {
link()80 constexpr link() :
81 pos{}
82 {
83 }
84 struct {
85 segnum_t segnum; // Used if SOF_LINK_TO_POS field is used
86 uint8_t sidenum;
87 vms_vector position;
88 } pos;
89 struct {
90 objnum_t objnum; // Used if SOF_LINK_TO_OBJ field is used
91 object_signature_t objsignature;
92 } obj;
93 } link_type;
94 };
95
96 using sound_objects_t = std::array<sound_object, MAX_SOUND_OBJECTS>;
97 static sound_objects_t SoundObjects;
98 static short next_signature=0;
99
100 static int N_active_sound_objects;
101
digi_kill_sound(sound_object & s)102 static void digi_kill_sound(sound_object &s)
103 {
104 s.flags = 0; // Mark as dead, so some other sound can use this sound
105 if (s.channel > -1) {
106 N_active_sound_objects--;
107 digi_stop_sound(std::exchange(s.channel, -1));
108 }
109 }
110
find_sound_object_flags0(sound_objects_t & SoundObjects)111 static std::pair<sound_objects_t::iterator, sound_objects_t::iterator> find_sound_object_flags0(sound_objects_t &SoundObjects)
112 {
113 const auto eso = SoundObjects.end();
114 const auto i = std::find_if(SoundObjects.begin(), eso, [](sound_object &so) { return so.flags == 0; });
115 return {i, eso};
116 }
117
find_sound_object(sound_objects_t & SoundObjects,const unsigned soundnum,const vcobjidx_t obj,const sound_stack once)118 static std::pair<sound_objects_t::iterator, sound_objects_t::iterator> find_sound_object(sound_objects_t &SoundObjects, const unsigned soundnum, const vcobjidx_t obj, const sound_stack once)
119 {
120 if (once == sound_stack::allow_stacking)
121 return find_sound_object_flags0(SoundObjects);
122 const auto eso = SoundObjects.end();
123 sound_objects_t::iterator free_sound_object = eso;
124 for (auto i = SoundObjects.begin(); i != eso; ++i)
125 {
126 constexpr uint8_t used_obj = SOF_USED | SOF_LINK_TO_OBJ;
127 auto &so = *i;
128 const auto flags = so.flags;
129 if (flags == 0)
130 free_sound_object = i;
131 else if (so.soundnum == soundnum && (flags & used_obj) == used_obj)
132 {
133 /* No check for a signature match here. If the signature
134 * matches, this sound will be reclaimed to prevent
135 * stacking. If the signature does not match, then the old
136 * sound is tied to a dead object and needs to be removed.
137 */
138 if (so.link_type.obj.objnum == obj)
139 {
140 digi_kill_sound(so);
141 return {i, eso};
142 }
143 }
144 }
145 return {free_sound_object, eso};
146 }
147
148 }
149
150 namespace dsx {
151
152 /* Find the sound which actually equates to a sound number */
digi_xlat_sound(int soundno)153 int digi_xlat_sound(int soundno)
154 {
155 if (soundno < 0)
156 return -1;
157
158 if (CGameArg.SysLowMem)
159 {
160 soundno = AltSounds[soundno];
161 if (soundno == 255)
162 return -1;
163 }
164
165 //Assert(Sounds[soundno] != 255); //if hit this, probably using undefined sound
166 if (Sounds[soundno] == 255)
167 return -1;
168
169 return Sounds[soundno];
170 }
171
digi_unxlat_sound(int soundno)172 static int digi_unxlat_sound(int soundno)
173 {
174 if ( soundno < 0 ) return -1;
175
176 auto &table = (CGameArg.SysLowMem ? AltSounds : Sounds);
177 auto b = std::begin(table);
178 auto e = std::end(table);
179 auto i = std::find(b, e, soundno);
180 if (i != e)
181 return std::distance(b, i);
182 throw std::invalid_argument("sound not loaded");
183 }
184
digi_get_sound_loc(const vms_matrix & listener,const vms_vector & listener_pos,const vcsegptridx_t listener_seg,const vms_vector & sound_pos,const vcsegptridx_t sound_seg,fix max_volume,vm_distance max_distance)185 static std::pair<int, sound_pan> digi_get_sound_loc(const vms_matrix &listener, const vms_vector &listener_pos, const vcsegptridx_t listener_seg, const vms_vector &sound_pos, const vcsegptridx_t sound_seg, fix max_volume, vm_distance max_distance)
186 {
187
188 vms_vector vector_to_sound;
189 fix angle_from_ear;
190
191 max_distance = (max_distance*5)/4; // Make all sounds travel 1.25 times as far.
192
193 // Warning: Made the vm_vec_normalized_dir be vm_vec_normalized_dir_quick and got illegal values to acos in the fang computation.
194 auto distance = vm_vec_normalized_dir_quick( vector_to_sound, sound_pos, listener_pos );
195
196 if (distance < max_distance ) {
197 int num_search_segs = f2i(max_distance/20);
198 if ( num_search_segs < 1 ) num_search_segs = 1;
199
200 auto path_distance = find_connected_distance(listener_pos, listener_seg, sound_pos, sound_seg, num_search_segs, WALL_IS_DOORWAY_FLAG::rendpast | WALL_IS_DOORWAY_FLAG::fly);
201 if ( path_distance > -1 ) {
202 const int volume = max_volume - fixdiv(path_distance,max_distance);
203 if (volume > 0)
204 {
205 angle_from_ear = vm_vec_delta_ang_norm(listener.rvec,vector_to_sound,listener.uvec);
206 auto cosang = fix_cos(angle_from_ear);
207 if (GameCfg.ReverseStereo) cosang *= -1;
208 return {volume, sound_pan{(cosang + F1_0) / 2}};
209 }
210 }
211 }
212 return {};
213 }
214
digi_update_sound_loc(const vms_matrix & listener,const vms_vector & listener_pos,const vcsegptridx_t listener_seg,const vms_vector & sound_pos,const vcsegptridx_t sound_seg,sound_object & so)215 static void digi_update_sound_loc(const vms_matrix &listener, const vms_vector &listener_pos, const vcsegptridx_t listener_seg, const vms_vector &sound_pos, const vcsegptridx_t sound_seg, sound_object &so)
216 {
217 auto &&[volume, pan] = digi_get_sound_loc(listener, listener_pos, listener_seg, sound_pos, sound_seg, so.max_volume, so.max_distance);
218 so.volume = volume;
219 so.pan = pan;
220 }
221
digi_play_sample_once(int soundno,fix max_volume)222 void digi_play_sample_once( int soundno, fix max_volume )
223 {
224 if ( Newdemo_state == ND_STATE_RECORDING )
225 newdemo_record_sound( soundno );
226
227 soundno = digi_xlat_sound(soundno);
228
229 if (soundno < 0 ) return;
230
231 // start the sample playing
232 digi_start_sound(soundno, max_volume, sound_pan{0x7fff}, 0, -1, -1, sound_object_none);
233 }
234
digi_play_sample(int soundno,fix max_volume)235 void digi_play_sample( int soundno, fix max_volume )
236 {
237 if ( Newdemo_state == ND_STATE_RECORDING )
238 newdemo_record_sound( soundno );
239
240 soundno = digi_xlat_sound(soundno);
241
242 if (soundno < 0 ) return;
243
244 // start the sample playing
245 digi_start_sound(soundno, max_volume, sound_pan{0x7fff}, 0, -1, -1, sound_object_none);
246 }
247
digi_play_sample_3d(int soundno,const sound_pan angle,int volume)248 void digi_play_sample_3d(int soundno, const sound_pan angle, int volume)
249 {
250 if ( Newdemo_state == ND_STATE_RECORDING ) {
251 newdemo_record_sound_3d_once( soundno, angle, volume );
252 }
253
254 soundno = digi_xlat_sound(soundno);
255
256 if (soundno < 0 ) return;
257
258 if (volume < 10 ) return;
259
260 // start the sample playing
261 digi_start_sound( soundno, volume, angle, 0, -1, -1, sound_object_none);
262 }
263
264 static void SoundQ_init();
265 static void SoundQ_process();
266 static void SoundQ_pause();
267
digi_init_sounds()268 void digi_init_sounds()
269 {
270 SoundQ_init();
271
272 digi_stop_all_channels();
273
274 digi_stop_looping_sound();
275 range_for (auto &i, SoundObjects)
276 {
277 i.channel = -1;
278 i.flags = 0; // Mark as dead, so some other sound can use this sound
279 }
280 N_active_sound_objects = 0;
281 }
282
283 // plays a sample that loops forever.
284 // Call digi_stop_channe(channel) to stop it.
285 // Call digi_set_channel_volume(channel, volume) to change volume.
286 // if loop_start is -1, entire sample loops
287 // Returns the channel that sound is playing on, or -1 if can't play.
288 // This could happen because of no sound drivers loaded or not enough channels.
289 static int digi_looping_sound = -1;
290 static int digi_looping_volume;
291 static int digi_looping_start = -1;
292 static int digi_looping_end = -1;
293 static int digi_looping_channel = -1;
294
digi_play_sample_looping_sub()295 static void digi_play_sample_looping_sub()
296 {
297 if ( digi_looping_sound > -1 )
298 digi_looping_channel = digi_start_sound( digi_looping_sound, digi_looping_volume, sound_pan{0x7fff}, 1, digi_looping_start, digi_looping_end, sound_object_none);
299 }
300
digi_play_sample_looping(int soundno,fix max_volume,int loop_start,int loop_end)301 void digi_play_sample_looping( int soundno, fix max_volume,int loop_start, int loop_end )
302 {
303 soundno = digi_xlat_sound(soundno);
304
305 if (soundno < 0 ) return;
306
307 if (digi_looping_channel>-1)
308 digi_stop_sound( digi_looping_channel );
309
310 digi_looping_sound = soundno;
311 digi_looping_volume = max_volume;
312 digi_looping_start = loop_start;
313 digi_looping_end = loop_end;
314 digi_play_sample_looping_sub();
315 }
316
digi_change_looping_volume(fix volume)317 void digi_change_looping_volume( fix volume )
318 {
319 digi_looping_volume = volume;
320 if ( digi_looping_channel > -1 )
321 digi_set_channel_volume( digi_looping_channel, volume );
322 }
323
digi_pause_looping_sound()324 static void digi_pause_looping_sound()
325 {
326 const auto c = digi_looping_channel;
327 if (c > -1)
328 {
329 digi_looping_channel = -1;
330 digi_stop_sound(c);
331 }
332 }
333
digi_stop_looping_sound()334 void digi_stop_looping_sound()
335 {
336 digi_looping_sound = -1;
337 digi_pause_looping_sound();
338 }
339
digi_unpause_looping_sound()340 static void digi_unpause_looping_sound()
341 {
342 digi_play_sample_looping_sub();
343 }
344
345 //hack to not start object when loading level
346 int Dont_start_sound_objects = 0;
347
digi_start_sound_object(sound_object & s)348 static void digi_start_sound_object(sound_object &s)
349 {
350 // start sample structures
351 s.channel = -1;
352
353 if ( s.volume <= 0 )
354 return;
355
356 if ( Dont_start_sound_objects )
357 return;
358
359 // only use up to half the sound channels for "permanent" sounts
360 if ((s.flags & SOF_PERMANENT) && (N_active_sound_objects >= max(1, digi_max_channels / 4)))
361 return;
362
363 // start the sample playing
364
365 s.channel = digi_start_sound( s.soundnum,
366 s.volume,
367 s.pan,
368 s.flags & SOF_PLAY_FOREVER,
369 s.loop_start,
370 s.loop_end, &s);
371
372 if (s.channel > -1 )
373 N_active_sound_objects++;
374 }
375
digi_link_sound_common(const object_base & viewer,sound_object & so,const vms_vector & pos,const int forever,const fix max_volume,const vm_distance max_distance,const int soundnum,const vcsegptridx_t segnum)376 static void digi_link_sound_common(const object_base &viewer, sound_object &so, const vms_vector &pos, const int forever, const fix max_volume, const vm_distance max_distance, const int soundnum, const vcsegptridx_t segnum)
377 {
378 so.signature=next_signature++;
379 if ( forever )
380 so.flags |= SOF_PLAY_FOREVER;
381 so.soundnum = soundnum;
382 so.max_volume = max_volume;
383 so.max_distance = max_distance;
384 so.volume = 0;
385 so.pan = {};
386 if (Dont_start_sound_objects) { //started at level start
387 so.flags |= SOF_PERMANENT;
388 so.channel = -1;
389 }
390 else
391 {
392 digi_update_sound_loc(viewer.orient, viewer.pos, segnum.absolute_sibling(viewer.segnum), pos, segnum, so);
393 digi_start_sound_object(so);
394 // If it's a one-shot sound effect, and it can't start right away, then
395 // just cancel it and be done with it.
396 if ( (so.channel < 0) && (!(so.flags & SOF_PLAY_FOREVER)) ) {
397 so.flags = 0;
398 return;
399 }
400 }
401 }
402
digi_link_sound_to_object3(const unsigned org_soundnum,const vcobjptridx_t objnum,const uint8_t forever,const fix max_volume,const sound_stack once,const vm_distance max_distance,const int loop_start,const int loop_end)403 void digi_link_sound_to_object3(const unsigned org_soundnum, const vcobjptridx_t objnum, const uint8_t forever, const fix max_volume, const sound_stack once, const vm_distance max_distance, const int loop_start, const int loop_end)
404 {
405 auto &Objects = LevelUniqueObjectState.Objects;
406 auto &vcobjptr = Objects.vcptr;
407 const auto &&viewer = vcobjptr(Viewer);
408 int soundnum;
409
410 soundnum = digi_xlat_sound(org_soundnum);
411
412 if (max_volume < 0)
413 return;
414 // if ( max_volume > F1_0 ) max_volume = F1_0;
415
416 if (soundnum < 0)
417 return;
418 if (GameSounds[soundnum].data==NULL) {
419 Int3();
420 return;
421 }
422
423 if ( Newdemo_state == ND_STATE_RECORDING ) {
424 if ( !forever ) { // forever flag is not recorded, use original limited sound objects hack for demo recording
425 auto segnum = vcsegptridx(objnum->segnum);
426 const auto &&[volume, pan] = digi_get_sound_loc(viewer->orient, viewer->pos, segnum.absolute_sibling(viewer->segnum),
427 objnum->pos, segnum, max_volume,
428 max_distance);
429 newdemo_record_sound_3d_once( org_soundnum, pan, volume );
430 } else
431 newdemo_record_link_sound_to_object3( org_soundnum, objnum, max_volume, max_distance, loop_start, loop_end );
432 }
433
434 const auto &&f = find_sound_object(SoundObjects, soundnum, objnum, once);
435 if (f.first == f.second)
436 return;
437 auto &so = *f.first;
438 so.flags = SOF_USED | SOF_LINK_TO_OBJ;
439 so.link_type.obj.objnum = objnum;
440 so.link_type.obj.objsignature = objnum->signature;
441 so.loop_start = loop_start;
442 so.loop_end = loop_end;
443 digi_link_sound_common(viewer, so, objnum->pos, forever, max_volume, max_distance, soundnum, vcsegptridx(objnum->segnum));
444 }
445
digi_link_sound_to_object2(const unsigned soundnum,const vcobjptridx_t objnum,const uint8_t forever,const fix max_volume,const sound_stack once,const vm_distance max_distance)446 void digi_link_sound_to_object2(const unsigned soundnum, const vcobjptridx_t objnum, const uint8_t forever, const fix max_volume, const sound_stack once, const vm_distance max_distance)
447 {
448 digi_link_sound_to_object3(soundnum, objnum, forever, max_volume, once, max_distance, -1, -1);
449 }
450
digi_link_sound_to_object(const unsigned soundnum,const vcobjptridx_t objnum,const uint8_t forever,const fix max_volume,const sound_stack once)451 void digi_link_sound_to_object(const unsigned soundnum, const vcobjptridx_t objnum, const uint8_t forever, const fix max_volume, const sound_stack once)
452 {
453 digi_link_sound_to_object2(soundnum, objnum, forever, max_volume, once, vm_distance{256*F1_0});
454 }
455
digi_link_sound_to_pos2(fvcobjptr & vcobjptr,const int org_soundnum,const vcsegptridx_t segnum,const unsigned sidenum,const vms_vector & pos,int forever,fix max_volume,const vm_distance max_distance)456 static void digi_link_sound_to_pos2(fvcobjptr &vcobjptr, const int org_soundnum, const vcsegptridx_t segnum, const unsigned sidenum, const vms_vector &pos, int forever, fix max_volume, const vm_distance max_distance)
457 {
458 const auto &&viewer = vcobjptr(Viewer);
459 int soundnum;
460
461 soundnum = digi_xlat_sound(org_soundnum);
462
463 if (max_volume < 0)
464 return;
465 // if ( max_volume > F1_0 ) max_volume = F1_0;
466
467 if (soundnum < 0)
468 return;
469 if (GameSounds[soundnum].data==NULL) {
470 Int3();
471 return;
472 }
473 if (!forever)
474 {
475 // Hack to keep sounds from building up...
476 const auto &&[volume, pan] = digi_get_sound_loc(viewer->orient, viewer->pos, segnum.absolute_sibling(viewer->segnum), pos, segnum, max_volume, max_distance);
477 digi_play_sample_3d(org_soundnum, pan, volume);
478 return;
479 }
480
481 auto f = find_sound_object_flags0(SoundObjects);
482 if (f.first == f.second)
483 return;
484 auto &so = *f.first;
485 so.flags = SOF_USED | SOF_LINK_TO_POS;
486 so.link_type.pos.segnum = segnum;
487 so.link_type.pos.sidenum = sidenum;
488 so.link_type.pos.position = pos;
489 so.loop_start = so.loop_end = -1;
490 digi_link_sound_common(viewer, so, pos, forever, max_volume, max_distance, soundnum, segnum);
491 }
492
digi_link_sound_to_pos(const unsigned soundnum,const vcsegptridx_t segnum,const unsigned sidenum,const vms_vector & pos,const int forever,const fix max_volume)493 void digi_link_sound_to_pos(const unsigned soundnum, const vcsegptridx_t segnum, const unsigned sidenum, const vms_vector &pos, const int forever, const fix max_volume)
494 {
495 auto &Objects = LevelUniqueObjectState.Objects;
496 auto &vcobjptr = Objects.vcptr;
497 digi_link_sound_to_pos2(vcobjptr, soundnum, segnum, sidenum, pos, forever, max_volume, vm_distance{F1_0 * 256});
498 }
499
500 //if soundnum==-1, kill any sound
digi_kill_sound_linked_to_segment(const vmsegidx_t segnum,const unsigned sidenum,int soundnum)501 void digi_kill_sound_linked_to_segment(const vmsegidx_t segnum, const unsigned sidenum, int soundnum)
502 {
503 if (soundnum != -1)
504 soundnum = digi_xlat_sound(soundnum);
505 range_for (auto &i, SoundObjects)
506 {
507 if ( (i.flags & SOF_USED) && (i.flags & SOF_LINK_TO_POS) ) {
508 if ((i.link_type.pos.segnum == segnum) && (i.link_type.pos.sidenum==sidenum) && (soundnum==-1 || i.soundnum==soundnum )) {
509 digi_kill_sound(i);
510 }
511 }
512 }
513 }
514
digi_kill_sound_linked_to_object(const vcobjptridx_t objnum)515 void digi_kill_sound_linked_to_object(const vcobjptridx_t objnum)
516 {
517 if ( Newdemo_state == ND_STATE_RECORDING ) {
518 newdemo_record_kill_sound_linked_to_object( objnum );
519 }
520 range_for (auto &i, SoundObjects)
521 {
522 if ( (i.flags & SOF_USED) && (i.flags & SOF_LINK_TO_OBJ ) ) {
523 if (i.link_type.obj.objnum == objnum) {
524 digi_kill_sound(i);
525 }
526 }
527 }
528 }
529
530 // John's new function, 2/22/96.
digi_record_sound_objects()531 static void digi_record_sound_objects()
532 {
533 range_for (auto &s, SoundObjects)
534 {
535 if ((s.flags & SOF_USED) && (s.flags & SOF_LINK_TO_OBJ) && (s.flags & SOF_PLAY_FOREVER))
536 {
537 newdemo_record_link_sound_to_object3( digi_unxlat_sound(s.soundnum), s.link_type.obj.objnum,
538 s.max_volume, s.max_distance, s.loop_start, s.loop_end );
539 }
540 }
541 }
542
543 static int was_recording = 0;
544
digi_sync_sounds()545 void digi_sync_sounds()
546 {
547 int oldvolume;
548
549 if ( Newdemo_state == ND_STATE_RECORDING) {
550 if ( !was_recording ) {
551 digi_record_sound_objects();
552 }
553 was_recording = 1;
554 } else {
555 was_recording = 0;
556 }
557
558 SoundQ_process();
559 if (!Viewer)
560 return;
561 auto &Objects = LevelUniqueObjectState.Objects;
562 auto &vcobjptr = Objects.vcptr;
563 const auto &&viewer = vcobjptr(Viewer);
564 range_for (auto &s, SoundObjects)
565 {
566 if (s.flags & SOF_USED)
567 {
568 oldvolume = s.volume;
569 const auto oldpan = s.pan;
570
571 if ( !(s.flags & SOF_PLAY_FOREVER) ) {
572 // Check if its done.
573 if (s.channel > -1 ) {
574 if ( !digi_is_channel_playing(s.channel) ) {
575 digi_end_sound( s.channel );
576 s.flags = 0; // Mark as dead, so some other sound can use this sound
577 N_active_sound_objects--;
578 continue; // Go on to next sound...
579 }
580 }
581 }
582
583 if ( s.flags & SOF_LINK_TO_POS ) {
584 digi_update_sound_loc(viewer->orient, viewer->pos, vcsegptridx(viewer->segnum), s.link_type.pos.position, vcsegptridx(s.link_type.pos.segnum), s);
585 } else if ( s.flags & SOF_LINK_TO_OBJ ) {
586 const object &objp = [&vcobjptr, &s]{
587 if (Newdemo_state != ND_STATE_PLAYBACK)
588 return vcobjptr(s.link_type.obj.objnum);
589 auto objnum = newdemo_find_object(s.link_type.obj.objsignature);
590 if (objnum != object_none)
591 return static_cast<vcobjptr_t>(objnum);
592 return vcobjptr(object_first);
593 }();
594
595 if ((objp.type==OBJ_NONE) || (objp.signature!=s.link_type.obj.objsignature)) {
596 // The object that this is linked to is dead, so just end this sound if it is looping.
597 if ( s.channel>-1 ) {
598 if (s.flags & SOF_PLAY_FOREVER)
599 digi_stop_sound( s.channel );
600 else
601 digi_end_sound( s.channel );
602 N_active_sound_objects--;
603 }
604 s.flags = 0; // Mark as dead, so some other sound can use this sound
605 continue; // Go on to next sound...
606 } else {
607 digi_update_sound_loc(viewer->orient, viewer->pos, vcsegptridx(viewer->segnum), objp.pos, vcsegptridx(objp.segnum), s);
608 }
609 }
610
611 if (oldvolume != s.volume) {
612 if ( s.volume < 1 ) {
613 // Sound is too far away, so stop it from playing.
614
615 const auto c = s.channel;
616 if (c > -1)
617 {
618 s.channel = -1;
619 if (s.flags & SOF_PLAY_FOREVER)
620 digi_stop_sound(c);
621 else
622 digi_end_sound(c);
623 N_active_sound_objects--;
624 }
625
626 if (! (s.flags & SOF_PLAY_FOREVER)) {
627 s.flags = 0; // Mark as dead, so some other sound can use this sound
628 continue;
629 }
630
631 } else {
632 if (s.channel<0) {
633 digi_start_sound_object(s);
634 } else {
635 digi_set_channel_volume( s.channel, s.volume );
636 }
637 }
638 }
639
640 if (oldpan != s.pan) {
641 if (s.channel>-1)
642 digi_set_channel_pan( s.channel, s.pan );
643 }
644
645 }
646 }
647 }
648
digi_pause_digi_sounds()649 void digi_pause_digi_sounds()
650 {
651 digi_pause_looping_sound();
652 range_for (auto &s, SoundObjects)
653 {
654 if (!(s.flags & SOF_USED))
655 continue;
656 const auto c = s.channel;
657 if (c > -1)
658 {
659 s.channel = -1;
660 if (! (s.flags & SOF_PLAY_FOREVER))
661 s.flags = 0; // Mark as dead, so some other sound can use this sound
662 N_active_sound_objects--;
663 digi_stop_sound(c);
664 }
665 }
666
667 digi_stop_all_channels();
668 SoundQ_pause();
669 }
670
digi_resume_digi_sounds()671 void digi_resume_digi_sounds()
672 {
673 digi_sync_sounds(); //don't think we really need to do this, but can't hurt
674 digi_unpause_looping_sound();
675 }
676
677 // Called by the code in digi.c when another sound takes this sound object's
678 // slot because the sound was done playing.
digi_end_soundobj(sound_object & s)679 void digi_end_soundobj(sound_object &s)
680 {
681 Assert(s.flags & SOF_USED);
682 Assert(s.channel > -1);
683
684 N_active_sound_objects--;
685 s.channel = -1;
686 }
687
digi_stop_digi_sounds()688 void digi_stop_digi_sounds()
689 {
690 digi_stop_looping_sound();
691 range_for (auto &s, SoundObjects)
692 {
693 if (s.flags & SOF_USED)
694 {
695 if ( s.channel > -1 ) {
696 digi_stop_sound( s.channel );
697 N_active_sound_objects--;
698 }
699 s.flags = 0; // Mark as dead, so some other sound can use this sound
700 }
701 }
702
703 digi_stop_all_channels();
704 SoundQ_init();
705 }
706
707 #ifndef NDEBUG
verify_sound_channel_free(int channel)708 int verify_sound_channel_free( int channel )
709 {
710 const auto predicate = [channel](const sound_object &s) {
711 return (s.flags & SOF_USED) && s.channel == channel;
712 };
713 if (std::any_of(SoundObjects.begin(), SoundObjects.end(), predicate))
714 throw std::runtime_error("sound busy");
715 return 0;
716 }
717 #endif
718
719 struct sound_q
720 {
721 fix64 time_added;
722 int soundnum;
723 };
724
725 #define MAX_LIFE F1_0*30 // After being queued for 30 seconds, don't play it
726 static int SoundQ_head, SoundQ_tail, SoundQ_num;
727 int SoundQ_channel;
728 static std::array<sound_q, 32> SoundQ;
729
SoundQ_init()730 void SoundQ_init()
731 {
732 SoundQ_head = SoundQ_tail = 0;
733 SoundQ_num = 0;
734 SoundQ_channel = -1;
735 }
736
SoundQ_pause()737 void SoundQ_pause()
738 {
739 SoundQ_channel = -1;
740 }
741
SoundQ_end()742 void SoundQ_end()
743 {
744 // Current playing sound is stopped, so take it off the Queue
745 SoundQ_head = (SoundQ_head+1);
746 if (SoundQ_head >= SoundQ.size())
747 SoundQ_head = 0;
748 SoundQ_num--;
749 SoundQ_channel = -1;
750 }
751
SoundQ_process()752 void SoundQ_process()
753 {
754 if ( SoundQ_channel > -1 ) {
755 if ( digi_is_channel_playing(SoundQ_channel) )
756 return;
757 SoundQ_end();
758 }
759
760 while ( SoundQ_head != SoundQ_tail ) {
761 sound_q * q = &SoundQ[SoundQ_head];
762
763 if ( q->time_added+MAX_LIFE > timer_query() ) {
764 SoundQ_channel = digi_start_sound(q->soundnum, F1_0+1, sound_pan{0x7fff}, 0, -1, -1, sound_object_none);
765 return;
766 } else {
767 // expired; remove from Queue
768 SoundQ_end();
769 }
770 }
771 }
772
digi_start_sound_queued(short soundnum,fix volume)773 void digi_start_sound_queued( short soundnum, fix volume )
774 {
775 int i;
776
777 soundnum = digi_xlat_sound(soundnum);
778
779 if (soundnum < 0 ) return;
780
781 i = SoundQ_tail+1;
782 if (i >= SoundQ.size())
783 i = 0;
784
785 // Make sure its loud so it doesn't get cancelled!
786 if ( volume < F1_0+1 )
787 volume = F1_0 + 1;
788
789 if ( i != SoundQ_head ) {
790 SoundQ[SoundQ_tail].time_added = timer_query();
791 SoundQ[SoundQ_tail].soundnum = soundnum;
792 SoundQ_num++;
793 SoundQ_tail = i;
794 }
795
796 // Try to start it!
797 SoundQ_process();
798 }
799
800 }
801