Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

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