1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 #include <sstream>
11 #include <climits>
12 
13 #include "gamesnd.h"
14 #include "gamesnd/gamesnd.h"
15 #include "localization/localize.h"
16 #include "parse/parselo.h"
17 #include "sound/ds.h"
18 #include "species_defs/species_defs.h"
19 #include "tracing/tracing.h"
20 
21 SCP_vector<game_snd>	Snds;
22 SCP_vector<game_snd>	Snds_iface;
23 SCP_vector<sound_handle> Snds_iface_handle;
24 
25 // jg18 - default priorities and limits for retail gameplay sounds
26 static const int NUM_RETAIL_GAMEPLAY_SOUNDS = 192; // indices 0-191, from retail sounds.tbl
27 static EnhancedSoundData Default_sound_priorities[NUM_RETAIL_GAMEPLAY_SOUNDS] =
28 {
29 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_MISSILE_TRACKING           = 0,  //!< Missle tracking to acquire a lock (looped)
30 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_MISSILE_LOCK               = 1,  //!< Missle lock (non-looping)
31 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_PRIMARY_CYCLE              = 2,  //!< cycle primary weapon
32 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_SECONDARY_CYCLE            = 3,  //!< cycle secondary weapon
33 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_ENGINE                     = 4,  //!< engine sound (as heard in cockpit)
34 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_CARGO_REVEAL               = 5,  //!< cargo revealed
35 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  3), // SND_DEATH_ROLL                 = 6,  //!< ship death roll
36 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_SHIP_EXPLODE_1             = 7,  //!< ship explosion 1
37 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_TARGET_ACQUIRE             = 8,  //!< target acquried
38 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_ENERGY_ADJUST              = 9,  //!< energy level change success
39 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_ENERGY_ADJUST_FAIL         = 10, //!< energy level change fail
40 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_ENERGY_TRANS               = 11, //!< energy transfer success
41 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_ENERGY_TRANS_FAIL         = 12, //!< energy transfer fail
42 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_FULL_THROTTLE              = 13, //!< set full throttle
43 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_ZERO_THROTTLE              = 14, //!< set zero throttle
44 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_THROTTLE_UP                = 15, //!< set 1/3 or 2/3 throttle (up)
45 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_THROTTLE_DOWN              = 16, //!< set 1/3 or 2/3 throttle (down)
46 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_DOCK_APPROACH              = 17, //!< dock approach retros
47 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_DOCK_ATTACH                = 18, //!< dock attach
48 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_DOCK_DETACH                = 19, //!< dock detach
49 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_DOCK_DEPART                = 20, //!< dock depart retros
50 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_ABURN_ENGAGE               = 21, //!< afterburner engage
51 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_ABURN_LOOP                 = 22, //!< afterburner burn sound (looped)
52 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  3), // SND_VAPORIZED                  = 23, //!< Destroyed by a beam (vaporized)
53 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  2), // SND_ABURN_FAIL                 = 24, //!< afterburner fail (no fuel when aburn pressed)
54 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_HEATLOCK_WARN              = 25, //!< heat-seeker launch warning
55 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_OUT_OF_MISSLES             = 26, //!< tried to fire a missle when none are left
56 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_OUT_OF_WEAPON_ENERGY       = 27, //!< tried to fire lasers when not enough energy left
57 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_TARGET_FAIL                = 28, //!< target fail sound (i.e. press targeting key, but nothing happens)
58 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_SQUADMSGING_ON             = 29, //!< squadmate message menu appears
59 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_SQUADMSGING_OFF            = 30, //!< squadmate message menu disappears
60 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  5), // SND_DEBRIS                     = 31, //!< debris sound (persistant, looping)
61 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_SUBSYS_DIE_1               = 32, //!< subsystem gets destroyed on player ship
62 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_MISSILE_START_LOAD         = 33, //!< missle start load (during rearm/repair)
63 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_MISSILE_LOAD               = 34, //!< missle load (during rearm/repair)
64 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_SHIP_REPAIR                = 35, //!< ship is being repaired (during rearm/repair)
65 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  3), // SND_PLAYER_HIT_LASER           = 36, //!< player ship is hit by laser fire
66 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  3), // SND_PLAYER_HIT_MISSILE         = 37, //!< player ship is hit by missile
67 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_CMEASURE_CYCLE             = 38, //!< countermeasure cycle
68 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  2), // SND_SHIELD_HIT                 = 39, //!< shield hit
69 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_SHIELD_HIT_YOU             = 40, //!< player shield is hit
70 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_GAME_MOUSE_CLICK           = 41, //!< mouse click
71 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_ASPECTLOCK_WARN            = 42, //!< aspect launch warning
72 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_SHIELD_XFER_OK             = 43, //!< shield quadrant transfer successful
73 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  5), // SND_ENGINE_WASH                = 44, //!< Engine wash (looped)
74 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  2), // SND_WARP_IN                    = 45, //!< warp hole opening up for arriving
75 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  2), // SND_WARP_OUT                   = 46, //!< warp hole opening up for departing (Same as warp in for now)
76 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_PLAYER_WARP_FAIL           = 47, //!< player warp has failed
77 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_STATIC                     = 48, //!< hud gauge static
78 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  3), // SND_SHIP_EXPLODE_2             = 49, //!< ship explosion 2
79 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_PLAYER_WARP_OUT            = 50, //!< ship is warping out in 3rd person
80 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  5), // SND_SHIP_SHIP_HEAVY            = 51, //!< heavy ship-ship collide sound
81 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  5), // SND_SHIP_SHIP_LIGHT            = 52, //!< light ship-ship collide sound
82 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  5), // SND_SHIP_SHIP_SHIELD           = 53, //!< shield ship-ship collide overlay sound
83 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_THREAT_FLASH               = 54, //!< missile threat indicator flashes
84 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  3), // SND_PROXIMITY_WARNING          = 55, //!< proximity warning (heat seeker)
85 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  3), // SND_PROXIMITY_ASPECT_WARNING   = 56, //!< proximity warning (aspect)
86 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  2), // SND_DIRECTIVE_COMPLETE         = 57, //!< directive complete
87 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  3), // SND_SUBSYS_EXPLODE             = 58, //!< other ship subsystem destroyed
88 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_CAPSHIP_EXPLODE            = 59, //!< captial ship explosion
89 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_CAPSHIP_SUBSYS_EXPLODE     = 60, //!< captial ship subsystem destroyed
90 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_LARGESHIP_WARPOUT          = 61, //!< large ship warps out
91 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  5), // SND_ASTEROID_EXPLODE_LARGE     = 62, //!< large asteroid blows up
92 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  5), // SND_ASTEROID_EXPLODE_SMALL     = 63, //!< small asteroid blows up
93 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_CUE_VOICE                  = 64, //!< sound to indicate voice is about to start
94 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_END_VOICE                  = 65, //!< sound to indicate voice has ended
95 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  2), // SND_CARGO_SCAN                 = 66, //!< cargo scanning (looped)
96 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  5), // SND_WEAPON_FLYBY               = 67, //!< weapon flyby sound
97 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  5), // SND_ASTEROID                   = 68, //!< asteroid sound (persistant, looped)
98 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  2), // SND_CAPITAL_WARP_IN            = 69, //!< capital warp hole opening
99 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  2), // SND_CAPITAL_WARP_OUT           = 70, //!< capital warp hole closing
100 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  5), // SND_ENGINE_LOOP_LARGE          = 71, //!< LARGE engine ambient
101 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_SUBSPACE_LEFT_CHANNEL      = 72, //!< subspace ambient sound (left channel) (looped)
102 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_SUBSPACE_RIGHT_CHANNEL     = 73, //!< subspace ambient sound (right channel) (looped)
103 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  4), // SND_MISSILE_EVADED_POPUP       = 74, //!< "evaded" HUD popup
104 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_ENGINE_LOOP_HUGE           = 75, //!< HUGE engine ambient
105 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_LIGHT_LASER_FIRE           = 76, //!< SD-4 Sidearm laser fired
106 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_LIGHT_LASER_IMPACT         = 77, //!< DR-2 Scalpel fired
107 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_HVY_LASER_FIRE             = 78, //!< Flail II fired
108 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_HVY_LASER_IMPACT           = 79, //!< Prometheus R laser fired
109 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_MASSDRV_FIRED              = 80, //!< Prometheus S laser fired
110 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_MASSDRV_IMPACT             = 81, //!< GTW-66 Newton Cannon fired
111 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_FLAIL_FIRED                = 82, //!< UD-8 Kayser Laser fired
112 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_FLAIL_IMPACT               = 83, //!< GTW-19 Circe laser fired
113 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_NEUTRON_FLUX_FIRED         = 84, //!< GTW-83 Lich laser fired
114 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_NEUTRON_FLUX_IMPACT        = 85, //!< Laser impact
115 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_DEBUG_LASER_FIRED          = 86, //!< Subach-HLV Vasudan laser
116 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_ROCKEYE_FIRED              = 87, //!< rockeye missile launch
117 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_MISSILE_IMPACT1            = 88, //!< missile impact 1
118 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_MAG_MISSILE_LAUNCH         = 89, //!< mag pulse missile launch
119 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_FURY_MISSILE_LAUNCH        = 90, //!< fury missile launch
120 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_SHRIKE_MISSILE_LAUNCH      = 91, //!< shrike missile launch
121 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_ANGEL_MISSILE_LAUNCH       = 92, //!< angel fire missile launch
122 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_CLUSTER_MISSILE_LAUNCH     = 93, //!< cluster bomb launch
123 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_CLUSTERB_MISSILE_LAUNCH    = 94, //!< cluster baby bomb launch
124 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_STILETTO_MISSILE_LAUNCH    = 95, //!< stiletto bomb launch
125 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_TSUNAMI_MISSILE_LAUNCH     = 96, //!< tsunami bomb launch
126 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_HARBINGER_MISSILE_LAUNCH   = 97, //!< harbinger bomb launch
127 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_MEGAWOKKA_MISSILE_LAUNCH   = 98, //!< mega wokka launch
128 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_CMEASURE1_LAUNCH           = 99, //!< countermeasure 1 launch
129 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_SHIVAN_LIGHT_LASER_FIRE    = 100,//!< Shivan light laser
130 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_SHOCKWAVE_EXPLODE          = 101,//!< shockwave ignition
131 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_SWARM_MISSILE_LAUNCH       = 102,//!< swarm missile sound
132 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_UNDEFINED_103              = 103,//!< Shivan heavy laser
133 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_UNDEFINED_104              = 104,//!< Vasudan SuperCap engine
134 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  5), // SND_UNDEFINED_105              = 105,//!< Shivan SuperCap engine
135 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  5), // SND_UNDEFINED_106              = 106,//!< Terran SuperCap engine
136 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_UNDEFINED_107              = 107,//!< Vasudan light laser fired
137 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_UNDEFINED_108              = 108,//!< Shivan heavy laser
138 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  2), // SND_SHOCKWAVE_IMPACT           = 109,//!< shockwave impact
139 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_UNDEFINED_110              = 110,//!< TERRAN TURRET 1
140 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_UNDEFINED_111              = 111,//!< TERRAN TURRET 2
141 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_UNDEFINED_112              = 112,//!< VASUDAN TURRET 1
142 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_UNDEFINED_113              = 113,//!< VASUDAN TURRET 2
143 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_UNDEFINED_114              = 114,//!< SHIVAN TURRET 1
144 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_TARG_LASER_LOOP            = 115,//!< targeting laser loop sound
145 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  8), // SND_FLAK_FIRE                  = 116,//!< Flak Gun Launch
146 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_SHIELD_BREAKER             = 117,//!< Flak Gun Impact
147 	EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW,  8), // SND_EMP_MISSILE                = 118,//!< EMP Missle
148 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  5), // SND_AUTOCANNON_LOOP            = 119,//!< Escape Pod Drone
149 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  5), // SND_AUTOCANNON_SHOT            = 120,//!< Beam Hit 1
150 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_BEAM_LOOP                  = 121,//!< beam loop
151 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_BEAM_UP                    = 122,//!< beam power up
152 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_BEAM_DOWN                  = 123,//!< beam power down
153 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_BEAM_SHOT                  = 124,//!< Beam shot 1
154 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_BEAM_VAPORIZE              = 125,//!< Beam shot 2
155 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_TERRAN_FIGHTER_ENG         = 126,//!< Terran fighter engine
156 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_TERRAN_BOMBER_ENG          = 127,//!< Terran bomber engine
157 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_TERRAN_CAPITAL_ENG         = 128,//!< Terran cruiser engine
158 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_SPECIESB_FIGHTER_ENG       = 129,//!< Vasudan fighter engine
159 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_SPECIESB_BOMBER_ENG        = 130,//!< Vasudan bomber engine
160 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_SPECIESB_CAPITAL_ENG       = 131,//!< Vasudan cruiser engine
161 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_SHIVAN_FIGHTER_ENG         = 132,//!< Shivan fighter engine
162 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_SHIVAN_BOMBER_ENG          = 133,//!< Shivan bomber engine
163 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_SHIVAN_CAPITAL_ENG         = 134,//!< Shivan cruiser engine
164 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  3), // SND_REPAIR_SHIP_ENG            = 135,//!< Repair ship beacon/engine sound
165 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_UNDEFINED_136              = 136,//!< Terran capital engine
166 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_UNDEFINED_137              = 137,//!< Vasudan capital engine
167 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  4), // SND_UNDEFINED_138              = 138,//!< Shivan capital engine
168 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_DEBRIS_ARC_01              = 139,//!< 0.10 second spark sound effect
169 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_DEBRIS_ARC_02              = 140,//!< 0.25 second spark sound effect
170 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_DEBRIS_ARC_03              = 141,//!< 0.50 second spark sound effect
171 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_DEBRIS_ARC_04              = 142,//!< 0.75 second spark sound effect
172 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_DEBRIS_ARC_05              = 143,//!< 1.00 second spark sound effect
173 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_144              = 144,//!< LTerSlash beam loop
174 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_145              = 145,//!< TerSlash	beam loop
175 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_146              = 146,//!< SGreen 	beam loop
176 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_147              = 147,//!< BGreen	beem loop
177 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_148              = 148,//!< BFGreen	been loop
178 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_149              = 149,//!< Antifighter 	beam loop
179 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_150              = 150,//!< 1 sec		warm up
180 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_151              = 151,//!< 1.5 sec 	warm up
181 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_152              = 152,//!< 2.5 sec 	warm up
182 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_153              = 153,//!< 3 sec 	warm up
183 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_154              = 154,//!< 3.5 sec 	warm up
184 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_155              = 155,//!< 5 sec 	warm up
185 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_156              = 156,//!< LTerSlash	warm down
186 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_157              = 157,//!< TerSlash	warm down
187 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_158              = 158,//!< SGreen	warm down
188 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_159              = 159,//!< BGreen	warm down
189 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_160              = 160,//!< BFGreen	warm down
190 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_161              = 161,//!< T_AntiFtr	warm down
191 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_COPILOT                    = 162,//!< copilot (SCP)
192 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_163              = 163,//!< (Empty in Retail)
193 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_164              = 164,//!< (Empty in Retail)
194 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_165              = 165,//!< (Empty in Retail)
195 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_166              = 166,//!< (Empty in Retail)
196 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_167              = 167,//!< (Empty in Retail)
197 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_168              = 168,//!< (Empty in Retail)
198 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_169              = 169,//!< (Empty in Retail)
199 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_170              = 170,//!< (Empty in Retail)
200 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_171              = 171,//!< (Empty in Retail)
201 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_172              = 172,//!< (Empty in Retail)
202 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_SUPERNOVA_1                = 173,//!< SuperNova (distant)
203 	EnhancedSoundData(  SND_ENHANCED_PRIORITY_MUST_PLAY,  1), // SND_SUPERNOVA_2                = 174,//!< SuperNova (shockwave)
204 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_UNDEFINED_175              = 175,//!< Shivan large engine
205 	EnhancedSoundData(     SND_ENHANCED_PRIORITY_MEDIUM,  3), // SND_UNDEFINED_176              = 176,//!< Shivan large engine
206 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_177              = 177,//!< SRed 		beam loop
207 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_178              = 178,//!< LRed		beam loop
208 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  3), // SND_UNDEFINED_179              = 179,//!< Antifighter	beam loop
209 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_LIGHTNING_1                = 180,//!< Thunder 1 sound in neblua
210 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  4), // SND_LIGHTNING_2                = 181,//!< Thunder 2 sound in neblua
211 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  3), // SND_UNDEFINED_182              = 182,//!< 1 sec 	warm up
212 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  3), // SND_UNDEFINED_183              = 183,//!< 1.5 sec 	warm up
213 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  3), // SND_UNDEFINED_184              = 184,//!< 3 sec 	warm up
214 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  2), // SND_UNDEFINED_185              = 185,//!< Shivan Commnode
215 	EnhancedSoundData(       SND_ENHANCED_PRIORITY_HIGH,  1), // SND_UNDEFINED_186              = 186,//!< Volition PirateShip
216 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  3), // SND_UNDEFINED_187              = 187,//!< SRed 		warm down
217 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  3), // SND_UNDEFINED_188              = 188,//!< LRed 		warm down
218 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  3), // SND_UNDEFINED_189              = 189,//!< AntiFtr	warm down
219 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  2), // SND_UNDEFINED_190              = 190,//!< Instellation 1
220 	EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH,  2)  // SND_UNDEFINED_191              = 191,//!< Instellation 2
221 };
222 
223 static const EnhancedSoundData default_enhanced_sound_data(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 1);
224 
225 
226 /*
227  * Update any uninitialized EnhancedSoundData in Snds
228   * with hardcoded defaults for retail.
229  */
gamesnd_add_retail_default_enhanced_sound_data()230 void gamesnd_add_retail_default_enhanced_sound_data()
231 {
232 	int i = 0;
233 
234 	for (SCP_vector<game_snd>::iterator it = Snds.begin(), end = Snds.end(); it != end; ++it, ++i)
235 	{
236 		if (it->enhanced_sound_data.priority== SND_ENHANCED_PRIORITY_INVALID)
237 		{
238 			if (i < NUM_RETAIL_GAMEPLAY_SOUNDS)
239 			{
240 				it->enhanced_sound_data.priority= Default_sound_priorities[i].priority;
241 			}
242 			else
243 			{
244 				it->enhanced_sound_data.priority= default_enhanced_sound_data.priority;
245 			}
246 		}
247 
248 		if (it->enhanced_sound_data.limit < 1)
249 		{
250 			if (i < NUM_RETAIL_GAMEPLAY_SOUNDS)
251 			{
252 				it->enhanced_sound_data.limit = Default_sound_priorities[i].limit;
253 			}
254 			else
255 			{
256 				it->enhanced_sound_data.limit= default_enhanced_sound_data.limit;
257 			}
258 		}
259 	}
260 }
261 
gamesnd_play_iface(interface_snd_id n)262 void gamesnd_play_iface(interface_snd_id n)
263 {
264 	if (Snds_iface_handle[n.value()].isValid())
265 		snd_stop(Snds_iface_handle[n.value()]);
266 
267 	Snds_iface_handle[n.value()] = snd_play(gamesnd_get_interface_sound(n));
268 }
269 
270 /**
271  * Function to search for a given game_snd with the specified name
272  * in the passed vector
273  *
274  * @param name Name to search for
275  * @param sounds Vector to seach in
276  *
277  */
gamesnd_lookup_name(const char * name,const SCP_vector<game_snd> & sounds)278 int gamesnd_lookup_name(const char* name, const SCP_vector<game_snd>& sounds)
279 {
280 	// if we get passed -1, don't bother trying to look it up.
281 	if (name == NULL || *name == 0 || !strcmp(name, "-1"))
282 	{
283 		return -1;
284 	}
285 
286 	Assert( sounds.size() <= INT_MAX );
287 
288 	int i = 0;
289 
290 	for(SCP_vector<game_snd>::const_iterator snd = sounds.begin(); snd != sounds.end(); ++snd)
291 	{
292 		if (!stricmp(snd->name.c_str(), name))
293 		{
294 			return i;
295 		}
296 		i++;
297 	}
298 
299 	return -1;
300 }
301 
302 // WMC - now ignores file extension.
gamesnd_get_by_name(const char * name)303 gamesnd_id gamesnd_get_by_name(const char* name)
304 {
305 	Assert( Snds.size() <= INT_MAX );
306 
307 	// empty name is not valid!
308 	if (name == nullptr || name[0] == '\0')
309 		return gamesnd_id(-1);
310 
311 	int index = gamesnd_lookup_name(name, Snds);
312 
313 	if (index < 0)
314 	{
315 		int i = 0;
316 		for (auto& Snd : Snds) {
317 			if (Snd.sound_entries.size() != 1) {
318 				// Ignore game sounds with more than one sound entry
319 				continue;
320 			}
321 
322 			auto& entry = Snd.sound_entries.front();
323 			char *p = strrchr( entry.filename, '.' );
324 			if(p == NULL)
325 			{
326 				if(!stricmp(entry.filename, name))
327 				{
328 					index = i;
329 					break;
330 				}
331 			}
332 			else if(!strnicmp(entry.filename, name, p - entry.filename))
333 			{
334 				index = i;
335 				break;
336 			}
337 
338 			i++;
339 		}
340 	}
341 
342 	return gamesnd_id(index);
343 }
344 
gamesnd_get_by_iface_name(const char * name)345 interface_snd_id gamesnd_get_by_iface_name(const char* name)
346 {
347 	Assert( Snds_iface.size() <= INT_MAX );
348 	Assert( Snds_iface.size() == Snds_iface_handle.size() );
349 
350 	auto index = gamesnd_lookup_name(name, Snds_iface);
351 
352 	if (index < 0)
353 	{
354 		int i = 0;
355 		for (auto& snd : Snds_iface) {
356 			if (snd.sound_entries.size() != 1) {
357 				// Ignore game sounds with more than one sound entry
358 				continue;
359 			}
360 
361 			auto& entry = snd.sound_entries.front();
362 			char *p = strrchr( entry.filename, '.' );
363 			if(p == NULL)
364 			{
365 				if(!stricmp(entry.filename, name))
366 				{
367 					index = i;
368 					break;
369 				}
370 			}
371 			else if(!strnicmp(entry.filename, name, p - entry.filename))
372 			{
373 				index = i;
374 				break;
375 			}
376 
377 			i++;
378 		}
379 	}
380 
381 	return interface_snd_id(index);
382 }
383 
gamesnd_get_by_tbl_index(int index)384 gamesnd_id gamesnd_get_by_tbl_index(int index)
385 {
386 	char temp[11];
387 	sprintf(temp, "%i", index);
388 
389 	auto idx = gamesnd_lookup_name(temp, Snds);
390 
391 	if (idx < 0) {
392 		return gamesnd_id();
393 	} else {
394 		return gamesnd_id(idx);
395 	}
396 }
397 
gamesnd_get_by_iface_tbl_index(int index)398 interface_snd_id gamesnd_get_by_iface_tbl_index(int index)
399 {
400 	Assert( Snds_iface.size() == Snds_iface_handle.size() );
401 
402 	char temp[11];
403 	sprintf(temp, "%i", index);
404 
405 	auto idx = gamesnd_lookup_name(temp, Snds_iface);
406 
407 	if (idx < 0) {
408 		return interface_snd_id();
409 	} else {
410 		return interface_snd_id(idx);
411 	}
412 }
413 
414 /**
415  * Parse a sound. When using this function for a table entry,
416  * required_string and optional_string aren't needed, as this function deals with
417  * that as its tag parameter, just make sure that the destination sound index can
418  * handle -1 if things don't work out.
419  *
420  * @param tag Tag
421  * @param idx_dest Sound index destination
422  * @param flags See the parse_sound_flags enum
423  *
424  */
parse_game_sound(const char * tag,gamesnd_id * idx_dest)425 bool parse_game_sound(const char* tag, gamesnd_id* idx_dest)
426 {
427 	if (optional_string(tag))
428 	{
429 		*idx_dest = parse_game_sound_inline();
430 		return true;
431 	}
432 
433 	return false;
434 }
435 
436 /**
437  * @brief Parses a game sound that should appear at the current parsing location
438  * @return The game sound id or invalid id when parsing fails
439  */
parse_game_sound_inline()440 gamesnd_id parse_game_sound_inline()
441 {
442 	SCP_string buf;
443 	stuff_string(buf, F_NAME);
444 
445 	auto id = gamesnd_get_by_name(buf.c_str());
446 
447 	// The special case "-1" is needed to silence warnings where sounds are intentionally removed
448 	if (!id.isValid() && buf != "-1") {
449 		error_display(0, "Could not find game sound with name '%s'!", buf.c_str());
450 	}
451 
452 	return id;
453 }
454 
455 /**
456  * Parse a sound. When using this function for a table entry,
457  * required_string and optional_string aren't needed, as this function deals with
458  * that as its tag parameter, just make sure that the destination sound index can
459  * handle -1 if things don't work out.
460  *
461  * @param tag Tag
462  * @param idx_dest Sound index destination
463  * @param flags See the parse_sound_flags enum
464  *
465  */
parse_iface_sound(const char * tag,interface_snd_id * idx_dest)466 void parse_iface_sound(const char* tag, interface_snd_id* idx_dest) {
467 	Assert( Snds_iface.size() == Snds_iface_handle.size() );
468 
469 	if(optional_string(tag))
470 	{
471 		SCP_string buf;
472 		stuff_string(buf, F_NAME);
473 
474 		*idx_dest = gamesnd_get_by_iface_name(buf.c_str());
475 
476 		// The special case "-1" is needed to silence warnings where sounds are intentionally removed
477 		if (!idx_dest->isValid() && buf != "-1") {
478 			error_display(0, "Could not find interface sound with name '%s'!", buf.c_str());
479 		}
480 	}
481 }
482 
483 /**
484  * CommanderDJ - Parse a list of sounds. When using this function for a table entry,
485  * required_string and optional_string aren't needed, as this function deals with
486  * that as its tag parameter, just make sure that the destination sound index(es) can
487  * handle -1 if things don't work out.
488  *
489  * @param destination Vector where sound indexes are to be stored
490  * @param tag Tag
491  * @param object_name Name of object being parsed
492  * @param flags See the parse_sound_flags enum
493  *
494  */
parse_iface_sound_list(const char * tag,SCP_vector<interface_snd_id> & destination,const char * object_name,bool scp_list)495 void parse_iface_sound_list(const char* tag, SCP_vector<interface_snd_id>& destination, const char* object_name, bool scp_list)
496 {
497 	if(optional_string(tag))
498 	{
499 		int check=0;
500 
501 		//if we're using the old format, parse the first entry separately
502 		if(!scp_list)
503 		{
504 			stuff_int(&check);
505 		}
506 
507 		//now read the rest of the entries on the line
508 		for(size_t i=0; !check_for_eoln(); i++)
509 		{
510 			SCP_string buf;
511 			stuff_string_white(buf);
512 
513 			//we do this conditionally to avoid adding needless entries when reparsing
514 			if(destination.size() <= i)
515 			{
516 				destination.push_back(interface_snd_id());
517 			}
518 
519 			destination[i] = gamesnd_get_by_iface_name(buf.c_str());
520 
521 			if (!destination[i].isValid()) {
522 				error_display(0, "Could not find interface sound with name '%s'!", buf.c_str());
523 			}
524 		}
525 
526 		//if we're using the old format, double check the size)
527 		if(!scp_list && (destination.size() != (unsigned)check))
528 		{
529 			mprintf(("%s in '%s' has " SIZE_T_ARG " entries. This does not match entered size of %i.\n", tag, object_name, destination.size(), check));
530 		}
531 	}
532 }
533 
534 /**
535  * Load in sounds that we expect will get played
536  *
537  * The method currently used is to load all those sounds that have the hardware flag
538  * set.  This works well since we don't want to try and load hardware sounds in on the
539  * fly (too slow).
540  */
gamesnd_preload_common_sounds()541 void gamesnd_preload_common_sounds()
542 {
543 	if ( !Sound_enabled )
544 		return;
545 
546 	Assert( Snds.size() <= INT_MAX );
547 	for (auto& gs: Snds) {
548 		if ( gs.preload ) {
549 			for (auto& entry : gs.sound_entries) {
550 				if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) {
551 					game_busy( NOX("** preloading common game sounds **") );	// Animate loading cursor... does nothing if loading screen not active.
552 					entry.id = snd_load(&entry, &gs.flags);
553 				}
554 			}
555 		}
556 	}
557 }
558 
559 /**
560  * Load the ingame sounds into memory
561  */
gamesnd_load_gameplay_sounds()562 void gamesnd_load_gameplay_sounds()
563 {
564 	if ( !Sound_enabled )
565 		return;
566 
567 	Assert( Snds.size() <= INT_MAX );
568 	for (auto& gs: Snds) {
569 		if ( !gs.preload ) { // don't try to load anything that's already preloaded
570 			for (auto& entry : gs.sound_entries) {
571 				if (entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0) {
572 					game_busy(NOX("** preloading gameplay sounds **"));        // Animate loading cursor... does nothing if loading screen not active.
573 					entry.id = snd_load(&entry, &gs.flags);
574 				}
575 			}
576 		}
577 	}
578 }
579 
580 /**
581  * Unload the ingame sounds from memory
582  */
gamesnd_unload_gameplay_sounds()583 void gamesnd_unload_gameplay_sounds()
584 {
585 	Assert( Snds.size() <= INT_MAX );
586 	for (auto& gs: Snds) {
587 		for (auto& entry : gs.sound_entries) {
588 			if (entry.id.isValid()) {
589 				snd_unload(entry.id);
590 				entry.id = sound_load_id::invalid();
591 			}
592 		}
593 	}
594 }
595 
596 /**
597  * Load the interface sounds into memory
598  */
gamesnd_load_interface_sounds()599 void gamesnd_load_interface_sounds()
600 {
601 	if ( !Sound_enabled )
602 		return;
603 
604 	Assert( Snds_iface.size() < INT_MAX );
605 	for (auto& gs: Snds) {
606 		for (auto& entry : gs.sound_entries) {
607 			if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) != 0 ) {
608 				entry.id = snd_load(&entry, &gs.flags);
609 			}
610 		}
611 	}
612 }
613 
614 /**
615  * Unload the interface sounds from memory
616  */
gamesnd_unload_interface_sounds()617 void gamesnd_unload_interface_sounds()
618 {
619 	Assert( Snds_iface.size() < INT_MAX );
620 	for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
621 		for (auto& entry : si->sound_entries) {
622 			if (entry.id.isValid()) {
623 				snd_unload( entry.id );
624 				entry.id     = sound_load_id::invalid();
625 				entry.id_sig = -1;
626 			}
627 		}
628 	}
629 }
630 
parse_gamesnd_old(game_snd * gs)631 void parse_gamesnd_old(game_snd* gs)
632 {
633 	int is_3d;
634 	int temp;
635 
636 	// An old sound is just a single entry sound set
637 	gs->sound_entries.resize(1);
638 	auto& entry = gs->sound_entries.back();
639 
640 	// Default pitch is 1.0. This is set here in case we don't have a valid file name
641 	gs->pitch_range = util::UniformFloatRange(1.0f);
642 
643 	stuff_string(entry.filename, F_NAME, MAX_FILENAME_LEN, ",");
644 
645 	if (!stricmp(entry.filename, NOX("empty")))
646 	{
647 		entry.filename[0] = 0;
648 		advance_to_eoln(NULL);
649 		return;
650 	}
651 	Mp++;
652 
653 	stuff_int(&temp);
654 
655 	if (temp > 0)
656 	{
657 		gs->preload = true;
658 	}
659 
660 	float default_volume;
661 	stuff_float(&default_volume);
662 	gs->volume_range = util::UniformFloatRange(default_volume);
663 
664 	stuff_int(&is_3d);
665 
666 	if (is_3d)
667 	{
668 		gs->flags |= GAME_SND_USE_DS3D;
669 		stuff_int(&gs->min);
670 		stuff_int(&gs->max);
671 	}
672 	else
673 	{
674 		gs->min = 0;
675 		gs->max = 0;
676 
677 		// silly retail, not abiding by its own format...
678 		if (!stricmp(entry.filename, "l_hit.wav") || !stricmp(entry.filename, "m_hit.wav"))
679 		{
680 			int temp_min, temp_max;
681 
682 			if (stuff_int_optional(&temp_min) == 2)
683 			{
684 				if (stuff_int_optional(&temp_max) == 2)
685 				{
686 					mprintf(("Dutifully converting retail sound %s, '%s' to a 3D sound...\n", gs->name.c_str(), entry.filename));
687 					is_3d = 1;
688 
689 					gs->flags |= GAME_SND_USE_DS3D;
690 					gs->min = temp_min;
691 					gs->max = temp_max;
692 				}
693 			}
694 		}
695 	}
696 
697 	// check for extra values per Mantis #2408
698 	if (stuff_int_optional(&temp) == 2)
699 	{
700 		Warning(LOCATION, "Unexpected extra value %d found for sound '%s' (filename '%s')!  Check the format of the sounds.tbl (or .tbm) entry.", temp, gs->name.c_str(), entry.filename);
701 	}
702 
703 	advance_to_eoln(NULL);
704 }
705 
convert_to_enhanced_priority(const char * priority_str)706 EnhancedSoundPriority convert_to_enhanced_priority(const char * priority_str)
707 {
708 	Assertion(priority_str != NULL, "convert_to_enhanced_priority given null priority_str!");
709 
710 	if (!stricmp(priority_str, "Must Play"))
711 	{
712 		return SND_ENHANCED_PRIORITY_MUST_PLAY;
713 	}
714 	else if (!stricmp(priority_str, "High"))
715 	{
716 		return SND_ENHANCED_PRIORITY_HIGH;
717 	}
718 	else if (!stricmp(priority_str, "Medium-High"))
719 	{
720 		return SND_ENHANCED_PRIORITY_MEDIUM_HIGH;
721 	}
722 	else if (!stricmp(priority_str, "Medium"))
723 	{
724 		return SND_ENHANCED_PRIORITY_MEDIUM;
725 	}
726 	else if (!stricmp(priority_str, "Medium-Low"))
727 	{
728 		return SND_ENHANCED_PRIORITY_MEDIUM_LOW;
729 	}
730 	else if (!stricmp(priority_str, "Low"))
731 	{
732 		return SND_ENHANCED_PRIORITY_LOW;
733 	}
734 	else
735 	{
736 		error_display(1, "Unknown enhanced sound priority: %s\n", priority_str);
737 		return SND_ENHANCED_PRIORITY_INVALID;
738 	}
739 }
740 
required_string_no_create(const char * token,bool no_create)741 bool required_string_no_create(const char* token, bool no_create)
742 {
743     if (no_create)
744     {
745         return optional_string(token) == 1;
746     }
747 
748     required_string(token);
749     return true;
750 }
751 
parse_cycle_type()752 static GameSoundCycleType parse_cycle_type() {
753 	if (optional_string("Sequential")) {
754 		return GameSoundCycleType::SequentialCycle;
755 	} else if (optional_string("Random")) {
756 		return GameSoundCycleType::RandomCycle;
757 	} else {
758 		error_display(0, "Failed to parse sound cycle type. Expected 'sequential' or 'random'. Got [%.32s]", next_tokens());
759 		// Ignore everything until the end of the line. That should hopefully skip the bad token.
760 		advance_to_eoln(nullptr);
761 		return GameSoundCycleType::SequentialCycle;
762 	}
763 }
764 
parse_gamesnd_soundset(game_snd * gs,bool no_create)765 void parse_gamesnd_soundset(game_snd* gs, bool no_create) {
766 	// If this gets called then we just saw "+Entry:" so we can begin processing the first entry immediately
767 
768 	do {
769 		// For now there is no way to change an existing entry so ever +Entry statement creates a new sound entry
770 		gs->sound_entries.resize(gs->sound_entries.size() + 1);
771 		auto& entry = gs->sound_entries.back();
772 
773 		stuff_string(entry.filename, F_NAME, MAX_FILENAME_LEN);
774 	} while (optional_string("+Entry:"));
775 
776 	if (required_string_no_create("+Cycle type:", no_create)) {
777 		gs->cycle_type = parse_cycle_type();
778 	}
779 
780 	if (required_string_no_create("+Preload:", no_create))
781 	{
782 		stuff_boolean(&gs->preload);
783 	}
784 
785 	if (required_string_no_create("+Volume:", no_create))
786 	{
787 		gs->volume_range = util::parseUniformRange(0.0f, 1.0f);
788 	}
789 
790 	if (optional_string("+3D Sound:"))
791 	{
792 		gs->flags |= GAME_SND_USE_DS3D;
793 		required_string("+Attenuation start:");
794 
795 		stuff_int(&gs->min);
796 
797 		required_string("+Attenuation end:");
798 
799 		stuff_int(&gs->max);
800 	}
801 	else
802 	{
803 		gs->min = 0;
804 		gs->max = 0;
805 	}
806 
807 	// jg18 - enhanced sound parameters
808 	if (optional_string("+Priority:"))
809 	{
810 		SCP_string priority_string;
811 		stuff_string(priority_string, F_NAME);
812 		EnhancedSoundPriority priority = convert_to_enhanced_priority(priority_string.c_str());
813 		if (priority != SND_ENHANCED_PRIORITY_INVALID)
814 		{
815 			gs->enhanced_sound_data.priority= priority;
816 		}
817 		// else case not needed since conversion function displays message on error
818 	}
819 
820 	if (optional_string("+Limit:"))
821 	{
822 		int temp_limit;
823 		stuff_int(&temp_limit);
824 
825 		if ((temp_limit > 0) && (static_cast<uint>(temp_limit) <= SND_ENHANCED_MAX_LIMIT))
826 		{
827 			gs->enhanced_sound_data.limit = (unsigned int)temp_limit;
828 		}
829 		else
830 		{
831 			error_display(1, "Invalid enhanced sound limit: %d\n", temp_limit);
832 		}
833 	}
834 
835 	if (optional_string("+Pitch:")) {
836 		gs->pitch_range = util::parseUniformRange(0.0001f);
837 	} else if (!no_create) {
838 		// Default pitch is 1.0
839 		gs->pitch_range = util::UniformFloatRange(1.0f);
840 	}
841 }
842 
parse_gamesnd_new(game_snd * gs,bool no_create)843 void parse_gamesnd_new(game_snd* gs, bool no_create)
844 {
845 	game_snd_entry* entry = nullptr;
846 	if (!no_create) {
847 		gs->sound_entries.resize(1);
848 		entry = &gs->sound_entries.back();
849 	} else {
850 		if (gs->sound_entries.size() != 1) {
851 			error_display(1, "The SCP syntax cannot be used to modify an existing sound that has more than one entry! Use the soundset syntax for adding new entries.");
852 		}
853 		entry = &gs->sound_entries.back();
854 	}
855 
856 	char name[MAX_FILENAME_LEN];
857 	// New extended format found
858 	stuff_string(name, F_NAME, MAX_FILENAME_LEN);
859 
860 	// Default pitch is 1.0. This is set here in case we don't have a valid file name
861 	gs->pitch_range = util::UniformFloatRange(1.0f);
862 
863 	if (!stricmp(name, NOX("empty")))
864 	{
865 		entry->filename[0] = 0;
866 		return;
867 	}
868 
869 	// If the name _doesn't_ match <same> put it into gs->filename;
870 	if (stricmp(name, "<same>") != 0)
871 	{
872 		strcpy_s(entry->filename, name);
873 	}
874 	else if (!no_create)
875 	{
876 		// Throw an error if <same> was specified but we are creating a new entry
877 		error_display(1, "'<same>' is only allowed if +nocreate was specified!");
878 		return;
879 	}
880 
881 	if (required_string_no_create("+Preload:", no_create))
882 	{
883 		stuff_boolean(&gs->preload);
884 	}
885 
886 	if (required_string_no_create("+Volume:", no_create))
887 	{
888 		float default_volume;
889 		stuff_float(&default_volume);
890 		gs->volume_range = util::UniformFloatRange(default_volume);
891 	}
892 
893 	if (optional_string("+3D Sound:"))
894 	{
895 		gs->flags |= GAME_SND_USE_DS3D;
896 		required_string("+Attenuation start:");
897 
898 		stuff_int(&gs->min);
899 
900 		required_string("+Attenuation end:");
901 
902 		stuff_int(&gs->max);
903 	}
904 	else
905 	{
906 		gs->min = 0;
907 		gs->max = 0;
908 	}
909 
910 	// jg18 - enhanced sound parameters
911 	if (optional_string("+Priority:"))
912 	{
913 		SCP_string priority_string;
914 		stuff_string(priority_string, F_NAME);
915 		EnhancedSoundPriority priority = convert_to_enhanced_priority(priority_string.c_str());
916 		if (priority != SND_ENHANCED_PRIORITY_INVALID)
917 		{
918 			gs->enhanced_sound_data.priority= priority;
919 		}
920 		// else case not needed since conversion function displays message on error
921 	}
922 
923 	if (optional_string("+Limit:"))
924 	{
925 		int temp_limit;
926 		stuff_int(&temp_limit);
927 
928 		if ((temp_limit > 0) && (static_cast<uint>(temp_limit) <= SND_ENHANCED_MAX_LIMIT))
929 		{
930 			gs->enhanced_sound_data.limit = (unsigned int)temp_limit;
931 		}
932 		else
933 		{
934 			error_display(1, "Invalid enhanced sound limit: %d\n", temp_limit);
935 		}
936 	}
937 }
938 
gamesnd_parse_entry(game_snd * gs,bool no_create,SCP_vector<game_snd> * lookupVector)939 void gamesnd_parse_entry(game_snd *gs, bool no_create, SCP_vector<game_snd> *lookupVector)
940 {
941 	SCP_string name;
942 
943 	stuff_string(name, F_NAME, "\t \n");
944 
945 	if (!no_create)
946 	{
947 		if (lookupVector != NULL)
948 		{
949 			if (gamesnd_lookup_name(name.c_str(), *lookupVector) >= 0)
950 			{
951 				Warning(LOCATION, "Duplicate sound name \"%s\" found!", name.c_str());
952 			}
953 		}
954 
955 		gs->name = name;
956 	}
957 	else
958 	{
959 		int vectorIndex = gamesnd_lookup_name(name.c_str(), *lookupVector);
960 
961 		if (vectorIndex < 0)
962 		{
963 			Warning(LOCATION, "No existing sound entry with name \"%s\" found!", name.c_str());
964 			no_create = false;
965 			gs->name = name;
966 		}
967 		else
968 		{
969 			gs = &lookupVector->at(vectorIndex);
970 		}
971 	}
972 
973 	if (optional_string("+Filename:"))
974 	{
975 		parse_gamesnd_new(gs, no_create);
976 	} else if (optional_string("+Entry:")) {
977 		parse_gamesnd_soundset(gs, no_create);
978 	}
979 	else
980 	{
981 		parse_gamesnd_old(gs);
982 	}
983 }
984 
985 /**
986  * Parse a sound effect entry by requiring the given tag at the beginning.
987  *
988  * @param gs The game_snd instance to fill in
989  * @param tag The tag that's required before an entry
990  * @param lookupVector If non-NULL used to look up @c +nocreate entries
991  *
992  * @return @c true when a new entry has been parsed and should be added to the list of known
993  *			entries. @c false otherwise, for example in case of @c +nocreate
994  */
gamesnd_parse_line(game_snd * gs,const char * tag,SCP_vector<game_snd> * lookupVector=NULL)995 bool gamesnd_parse_line(game_snd *gs, const char *tag, SCP_vector<game_snd> *lookupVector = NULL)
996 {
997 	Assertion(gs != NULL, "Invalid game_snd pointer passed to gamesnd_parse_line!");
998 
999 	required_string(tag);
1000 
1001 	bool no_create = false;
1002 
1003 	if (lookupVector != NULL)
1004 	{
1005 		if(optional_string("+nocreate"))
1006 		{
1007 			no_create = true;
1008 		}
1009 	}
1010 
1011 	gamesnd_parse_entry(gs, no_create, lookupVector);
1012 
1013 	return !no_create;
1014 }
1015 
parse_sound_environments()1016 void parse_sound_environments()
1017 {
1018 	char name[65] = { '\0' };
1019 	char template_name[65] = { '\0' };
1020 	EFXREVERBPROPERTIES *props;
1021 
1022 	while (required_string_either("#Sound Environments End", "$Name:"))
1023 	{
1024 		required_string("$Name:");
1025 		stuff_string(name, F_NAME, sizeof(name)-1);
1026 
1027 		if (optional_string("$Template:"))
1028 		{
1029 			stuff_string(template_name, F_NAME, sizeof(template_name)-1);
1030 		}
1031 		else
1032 		{
1033 			template_name[0] = '\0';
1034 		}
1035 
1036 		ds_eax_get_prop(&props, name, template_name);
1037 
1038 		if (optional_string("+Density:"))
1039 		{
1040 			stuff_float(&props->flDensity);
1041 			CLAMP(props->flDensity, 0.0f, 1.0f);
1042 		}
1043 
1044 		if (optional_string("+Diffusion:"))
1045 		{
1046 			stuff_float(&props->flDiffusion);
1047 			CLAMP(props->flDiffusion, 0.0f, 1.0f);
1048 		}
1049 
1050 		if (optional_string("+Gain:"))
1051 		{
1052 			stuff_float(&props->flGain);
1053 			CLAMP(props->flGain, 0.0f, 1.0f);
1054 		}
1055 
1056 		if (optional_string("+Gain HF:"))
1057 		{
1058 			stuff_float(&props->flGainHF);
1059 			CLAMP(props->flGainHF, 0.0f, 1.0f);
1060 		}
1061 
1062 		if (optional_string("+Gain LF:"))
1063 		{
1064 			stuff_float(&props->flGainLF);
1065 			CLAMP(props->flGainLF, 0.0f, 1.0f);
1066 		}
1067 
1068 		if (optional_string("+Decay Time:"))
1069 		{
1070 			stuff_float(&props->flDecayTime);
1071 			CLAMP(props->flDecayTime, 0.01f, 20.0f);
1072 		}
1073 
1074 		if (optional_string("+Decay HF Ratio:"))
1075 		{
1076 			stuff_float(&props->flDecayHFRatio);
1077 			CLAMP(props->flDecayHFRatio, 0.1f, 20.0f);
1078 		}
1079 
1080 		if (optional_string("+Decay LF Ratio:"))
1081 		{
1082 			stuff_float(&props->flDecayLFRatio);
1083 			CLAMP(props->flDecayLFRatio, 0.1f, 20.0f);
1084 		}
1085 
1086 		if (optional_string("+Reflections Gain:"))
1087 		{
1088 			stuff_float(&props->flReflectionsGain);
1089 			CLAMP(props->flReflectionsGain, 0.0f, 3.16f);
1090 		}
1091 
1092 		if (optional_string("+Reflections Delay:"))
1093 		{
1094 			stuff_float(&props->flReflectionsDelay);
1095 			CLAMP(props->flReflectionsDelay, 0.0f, 0.3f);
1096 		}
1097 
1098 		if (optional_string("+Reflections Pan:"))
1099 		{
1100 			stuff_float_list(props->flReflectionsPan, 3);
1101 			CLAMP(props->flReflectionsPan[0], 0.0f, 1.0f);
1102 			CLAMP(props->flReflectionsPan[1], 0.0f, 1.0f);
1103 			CLAMP(props->flReflectionsPan[2], 0.0f, 1.0f);
1104 		}
1105 
1106 		if (optional_string("+Late Reverb Gain:"))
1107 		{
1108 			stuff_float(&props->flLateReverbGain);
1109 			CLAMP(props->flLateReverbGain, 0.0f, 10.0f);
1110 		}
1111 
1112 		if (optional_string("+Late Reverb Delay:"))
1113 		{
1114 			stuff_float(&props->flLateReverbDelay);
1115 			CLAMP(props->flLateReverbDelay, 0.0f, 0.1f);
1116 		}
1117 
1118 		if (optional_string("+Late Reverb Pan:"))
1119 		{
1120 			stuff_float_list(props->flLateReverbPan, 3);
1121 			CLAMP(props->flLateReverbPan[0], 0.0f, 1.0f);
1122 			CLAMP(props->flLateReverbPan[1], 0.0f, 1.0f);
1123 			CLAMP(props->flLateReverbPan[2], 0.0f, 1.0f);
1124 		}
1125 
1126 		if (optional_string("+Echo Time:"))
1127 		{
1128 			stuff_float(&props->flEchoTime);
1129 			CLAMP(props->flEchoTime, 0.075f, 0.25f);
1130 		}
1131 
1132 		if (optional_string("+Echo Depth:"))
1133 		{
1134 			stuff_float(&props->flEchoDepth);
1135 			CLAMP(props->flEchoDepth, 0.0f, 1.0f);
1136 		}
1137 
1138 		if (optional_string("+Modulation Time:"))
1139 		{
1140 			stuff_float(&props->flModulationTime);
1141 			CLAMP(props->flModulationTime, 0.004f, 4.0f);
1142 		}
1143 
1144 		if (optional_string("+Modulation Depth:"))
1145 		{
1146 			stuff_float(&props->flModulationDepth);
1147 			CLAMP(props->flModulationDepth, 0.0f, 1.0f);
1148 		}
1149 
1150 		if (optional_string("+HF Reference:"))
1151 		{
1152 			stuff_float(&props->flHFReference);
1153 			CLAMP(props->flHFReference, 1000.0f, 20000.0f);
1154 		}
1155 
1156 		if (optional_string("+LF Reference:"))
1157 		{
1158 			stuff_float(&props->flLFReference);
1159 			CLAMP(props->flLFReference, 20.0f, 1000.0f);
1160 		}
1161 
1162 		if (optional_string("+Room Rolloff Factor:"))
1163 		{
1164 			stuff_float(&props->flRoomRolloffFactor);
1165 			CLAMP(props->flRoomRolloffFactor, 0.0f, 10.0f);
1166 		}
1167 
1168 		if (optional_string("+Air Absorption Gain HF:"))
1169 		{
1170 			stuff_float(&props->flAirAbsorptionGainHF);
1171 			CLAMP(props->flAirAbsorptionGainHF, 0.892f, 1.0f);
1172 		}
1173 
1174 		if (optional_string("+Decay HF Limit:"))
1175 		{
1176 			stuff_int(&props->iDecayHFLimit);
1177 			CLAMP(props->iDecayHFLimit, 0, 1);
1178 		}
1179 	}
1180 
1181 	required_string("#Sound Environments End");
1182 }
1183 
1184 static SCP_vector<species_info> missingFlybySounds;
1185 
parse_sound_table(const char * filename)1186 void parse_sound_table(const char* filename)
1187 {
1188 	try
1189 	{
1190 		read_file_text(filename, CF_TYPE_TABLES);
1191 		reset_parse();
1192 
1193 		// Parse the gameplay sounds section
1194 		if (optional_string("#Game Sounds Start"))
1195 		{
1196 			while (!check_for_string("#Game Sounds End"))
1197 			{
1198 				game_snd tempSound;
1199 				if (gamesnd_parse_line(&tempSound, "$Name:", &Snds))
1200 				{
1201 					Snds.push_back(game_snd(tempSound));
1202 				}
1203 			}
1204 
1205 			required_string("#Game Sounds End");
1206 		}
1207 
1208 		// Parse the interface sounds section
1209 		if (optional_string("#Interface Sounds Start"))
1210 		{
1211 			while (!check_for_string("#Interface Sounds End"))
1212 			{
1213 				game_snd tempSound;
1214 				if (gamesnd_parse_line(&tempSound, "$Name:", &Snds_iface))
1215 				{
1216 					Snds_iface.push_back(game_snd(tempSound));
1217 					Snds_iface_handle.push_back(sound_handle::invalid());
1218 				}
1219 			}
1220 
1221 			required_string("#Interface Sounds End");
1222 		}
1223 
1224 		// parse flyby sound section
1225 		if (optional_string("#Flyby Sounds Start"))
1226 		{
1227 			// try parsing sounds for each species
1228 			// Note 1: instead of going through Species_info and requiring a sound for each species,
1229 			// we now parse the token and extract the species name from it
1230 			// Note 2: if a species doesn't have a flyby sound, the flyby code will just not play anything
1231 			while (!check_for_string("#Flyby Sounds End"))
1232 			{
1233 				char species_name_tag[NAME_LENGTH + 2];
1234 				stuff_string_until(species_name_tag, ":", NAME_LENGTH + 2);
1235 
1236 				if (species_name_tag[0] != '$')
1237 				{
1238 					error_display(0, "Unexpected token tag %s", species_name_tag);
1239 					advance_to_eoln(nullptr);
1240 					continue;
1241 				}
1242 
1243 				int idx = species_info_lookup(&species_name_tag[1]);
1244 				if (idx < 0)
1245 				{
1246 					mprintf(("Skipping flyby sound for unknown species %s\n", &species_name_tag[1]));
1247 					advance_to_eoln(nullptr);
1248 					continue;
1249 				}
1250 
1251 				// now we have a known species
1252 				species_info *species = &Species_info[idx];
1253 				strcat_s(species_name_tag, ":");	// put back the terminator to restore the entire tag
1254 
1255 				// parse the two sounds for it
1256 				gamesnd_parse_line(&species->snd_flyby_fighter, ":");				// since we stuffed most of the tag, the : is all that remains on this line
1257 				gamesnd_parse_line(&species->snd_flyby_bomber, species_name_tag);	// for the subsequent line we use required_string on the whole tag
1258 			}
1259 
1260 			required_string("#Flyby Sounds End");
1261 		}
1262 
1263 		if (optional_string("#Sound Environments Start"))
1264 		{
1265 			parse_sound_environments();
1266 		}
1267 	}
1268 	catch (const parse::ParseException& e)
1269 	{
1270 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
1271 		return;
1272 	}
1273 
1274 	gamesnd_add_retail_default_enhanced_sound_data();
1275 }
1276 
1277 /**
1278  * Parse the sounds.tbl file, and load the specified sounds.
1279  */
gamesnd_parse_soundstbl()1280 void gamesnd_parse_soundstbl()
1281 {
1282 	parse_sound_table("sounds.tbl");
1283 
1284 	parse_modular_table("*-snd.tbm", parse_sound_table);
1285 
1286 	// if we are missing any species then report
1287 	if (!missingFlybySounds.empty())
1288 	{
1289 		SCP_string errorString;
1290 		for (size_t i = 0; i < missingFlybySounds.size(); i++)
1291 		{
1292 			errorString.append(missingFlybySounds[i].species_name);
1293 			errorString.append("\n");
1294 		}
1295 
1296 		Error(LOCATION, "The following species are missing flyby sounds in sounds.tbl:\n%s", errorString.c_str());
1297 	}
1298 
1299 	missingFlybySounds.clear();
1300 }
1301 
1302 /**
1303  * Close out gamesnd, ONLY CALL FROM game_shutdown()!!!!
1304  */
gamesnd_close()1305 void gamesnd_close()
1306 {
1307 	Snds.clear();
1308 	Snds_iface.clear();
1309 	Snds_iface_handle.clear();
1310 }
1311 
1312 /**
1313  * Callback function for the UI code to call when the mouse first goes over a button.
1314  */
common_play_highlight_sound()1315 void common_play_highlight_sound()
1316 {
1317 	gamesnd_play_iface(InterfaceSounds::USER_OVER);
1318 }
1319 
1320 /**
1321  * Callback function for the UI code to call when an error beep needed.
1322  */
gamesnd_play_error_beep()1323 void gamesnd_play_error_beep()
1324 {
1325 	gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
1326 }
1327 
gamesnd_get_game_sound(gamesnd_id handle)1328 game_snd* gamesnd_get_game_sound(gamesnd_id handle) {
1329 	Assertion(handle.isValid(), "Invalid game sound handle detected!");
1330 	Assertion(handle.value() < (int) Snds.size(), "Invalid game sound handle %d detected!", handle.value());
1331 	return &Snds[handle.value()];
1332 }
gamesnd_get_interface_sound(interface_snd_id handle)1333 game_snd* gamesnd_get_interface_sound(interface_snd_id handle) {
1334 	Assertion(handle.isValid(), "Invalid interface sound handle detected!");
1335 	Assertion(handle.value() < (int) Snds_iface.size(), "Invalid interface sound handle %d detected!", handle.value());
1336 	return &Snds_iface[handle.value()];
1337 }
1338 
gamesnd_game_sound_valid(gamesnd_id sound)1339 bool gamesnd_game_sound_valid(gamesnd_id sound) {
1340 	return sound.isValid() && sound.value() < (int) Snds.size();
1341 }
gamesnd_interface_sound_valid(interface_snd_id sound)1342 bool gamesnd_interface_sound_valid(interface_snd_id sound) {
1343 	return sound.isValid() && sound.value() < (int) Snds_iface.size();
1344 }
1345 
gamesnd_get_max_duration(game_snd * gs)1346 float gamesnd_get_max_duration(game_snd* gs) {
1347 	int max_length = 0;
1348 
1349 	for (auto& entry : gs->sound_entries) {
1350 		if (!entry.id.isValid()) {
1351 			// Lazily load unloaded sound entries when required
1352 			entry.id = snd_load(&entry, &gs->flags);
1353 		}
1354 
1355 		max_length = std::max(max_length, snd_get_duration(entry.id));
1356 	}
1357 
1358 	return max_length / gs->pitch_range.max();
1359 }
gamesnd_choose_entry(game_snd * gs)1360 game_snd_entry* gamesnd_choose_entry(game_snd* gs) {
1361 	Assertion(!gs->sound_entries.empty(), "No sound entries found in game sound! This may not happen!");
1362 
1363 	size_t index = 0;
1364 	switch(gs->cycle_type) {
1365 	case GameSoundCycleType::RandomCycle:
1366 		if (gs->sound_entries.size() == 1) {
1367 			index = 0;
1368 		} else {
1369 			index = util::UniformRange<size_t>(0, gs->sound_entries.size() - 1).next();
1370 		}
1371 		break;
1372 	case GameSoundCycleType::SequentialCycle:
1373 		if (gs->last_entry_index == std::numeric_limits<size_t>::max()) {
1374 			// If this is the first time we must return the first sound to keep everything consistent
1375 			index = 0;
1376 		} else {
1377 			index = (gs->last_entry_index + 1) % gs->sound_entries.size();
1378 		}
1379 		break;
1380 	}
1381 
1382 	gs->last_entry_index = index;
1383 	return &gs->sound_entries[index];
1384 }
1385