Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  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. }
  795.