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