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