Files
fs2open.github.com/code/gamesnd/gamesnd.cpp
T
asarium 0ff8a2c159 Add support for multiple sounds in one game sound entry
This adds support for specifying multiple sounds for a single game sound
entry. The engine then selects which sound will be played based on the
strategy provided by the game data. This required a few changes to some
of the game code since previously every game sound had exactly one sound
file attached to it. The most notable change is that it is no longer
possible to determine the exact duration a sound can have so instead the
new code computes the maximum duration a sound can have and uses that
where appropriate.

This also adds support for randomizing the pitch and volume of the sound
to provide a more varied experience even if only a single sound is used.

One possible issue that exists is that the "sequential" strategy only
works globally at the moment so if two sources use the same game sound
concurrently then the two sources will share the sequence of sounds
since there is no sound instance data at the moment. That can be added
in a future change but should not present a huge issue for most
use-cases.

Visual Studio on Appveyor somehow causes deprecation error on _our_
definition of vsprintf. I have no idea why these changes introduced this
warning but I won't debug Visual Studio compiler bugs that I can't
reproduce with my Visual Studio version so I just disabled those
warnings.
2017-10-17 10:57:22 +02:00

1357 lines
51 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include <sstream>
#include <limits.h>
#include "gamesnd/gamesnd.h"
#include "localization/localize.h"
#include "parse/parselo.h"
#include "sound/ds.h"
#include "species_defs/species_defs.h"
#include "tracing/tracing.h"
SCP_vector<game_snd> Snds;
SCP_vector<game_snd> Snds_iface;
SCP_vector<int> Snds_iface_handle;
// jg18 - default priorities and limits for retail gameplay sounds
static const int NUM_RETAIL_GAMEPLAY_SOUNDS = 192; // indices 0-191, from retail sounds.tbl
static EnhancedSoundData Default_sound_priorities[NUM_RETAIL_GAMEPLAY_SOUNDS] =
{
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_MISSILE_TRACKING = 0, //!< Missle tracking to acquire a lock (looped)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_MISSILE_LOCK = 1, //!< Missle lock (non-looping)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_PRIMARY_CYCLE = 2, //!< cycle primary weapon
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_SECONDARY_CYCLE = 3, //!< cycle secondary weapon
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_ENGINE = 4, //!< engine sound (as heard in cockpit)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_CARGO_REVEAL = 5, //!< cargo revealed
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 3), // SND_DEATH_ROLL = 6, //!< ship death roll
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_SHIP_EXPLODE_1 = 7, //!< ship explosion 1
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_TARGET_ACQUIRE = 8, //!< target acquried
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_ENERGY_ADJUST = 9, //!< energy level change success
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_ENERGY_ADJUST_FAIL = 10, //!< energy level change fail
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_ENERGY_TRANS = 11, //!< energy transfer success
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_ENERGY_TRANS_FAIL = 12, //!< energy transfer fail
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_FULL_THROTTLE = 13, //!< set full throttle
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_ZERO_THROTTLE = 14, //!< set zero throttle
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_THROTTLE_UP = 15, //!< set 1/3 or 2/3 throttle (up)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_THROTTLE_DOWN = 16, //!< set 1/3 or 2/3 throttle (down)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_DOCK_APPROACH = 17, //!< dock approach retros
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_DOCK_ATTACH = 18, //!< dock attach
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_DOCK_DETACH = 19, //!< dock detach
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_DOCK_DEPART = 20, //!< dock depart retros
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_ABURN_ENGAGE = 21, //!< afterburner engage
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_ABURN_LOOP = 22, //!< afterburner burn sound (looped)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 3), // SND_VAPORIZED = 23, //!< Destroyed by a beam (vaporized)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 2), // SND_ABURN_FAIL = 24, //!< afterburner fail (no fuel when aburn pressed)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_HEATLOCK_WARN = 25, //!< heat-seeker launch warning
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_OUT_OF_MISSLES = 26, //!< tried to fire a missle when none are left
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_OUT_OF_WEAPON_ENERGY = 27, //!< tried to fire lasers when not enough energy left
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_TARGET_FAIL = 28, //!< target fail sound (i.e. press targeting key, but nothing happens)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_SQUADMSGING_ON = 29, //!< squadmate message menu appears
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_SQUADMSGING_OFF = 30, //!< squadmate message menu disappears
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 5), // SND_DEBRIS = 31, //!< debris sound (persistant, looping)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_SUBSYS_DIE_1 = 32, //!< subsystem gets destroyed on player ship
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_MISSILE_START_LOAD = 33, //!< missle start load (during rearm/repair)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_MISSILE_LOAD = 34, //!< missle load (during rearm/repair)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_SHIP_REPAIR = 35, //!< ship is being repaired (during rearm/repair)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 3), // SND_PLAYER_HIT_LASER = 36, //!< player ship is hit by laser fire
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 3), // SND_PLAYER_HIT_MISSILE = 37, //!< player ship is hit by missile
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_CMEASURE_CYCLE = 38, //!< countermeasure cycle
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 2), // SND_SHIELD_HIT = 39, //!< shield hit
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_SHIELD_HIT_YOU = 40, //!< player shield is hit
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_GAME_MOUSE_CLICK = 41, //!< mouse click
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_ASPECTLOCK_WARN = 42, //!< aspect launch warning
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_SHIELD_XFER_OK = 43, //!< shield quadrant transfer successful
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 5), // SND_ENGINE_WASH = 44, //!< Engine wash (looped)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 2), // SND_WARP_IN = 45, //!< warp hole opening up for arriving
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 2), // SND_WARP_OUT = 46, //!< warp hole opening up for departing (Same as warp in for now)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_PLAYER_WARP_FAIL = 47, //!< player warp has failed
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_STATIC = 48, //!< hud gauge static
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 3), // SND_SHIP_EXPLODE_2 = 49, //!< ship explosion 2
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_PLAYER_WARP_OUT = 50, //!< ship is warping out in 3rd person
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 5), // SND_SHIP_SHIP_HEAVY = 51, //!< heavy ship-ship collide sound
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 5), // SND_SHIP_SHIP_LIGHT = 52, //!< light ship-ship collide sound
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 5), // SND_SHIP_SHIP_SHIELD = 53, //!< shield ship-ship collide overlay sound
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_THREAT_FLASH = 54, //!< missile threat indicator flashes
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 3), // SND_PROXIMITY_WARNING = 55, //!< proximity warning (heat seeker)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 3), // SND_PROXIMITY_ASPECT_WARNING = 56, //!< proximity warning (aspect)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 2), // SND_DIRECTIVE_COMPLETE = 57, //!< directive complete
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 3), // SND_SUBSYS_EXPLODE = 58, //!< other ship subsystem destroyed
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_CAPSHIP_EXPLODE = 59, //!< captial ship explosion
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_CAPSHIP_SUBSYS_EXPLODE = 60, //!< captial ship subsystem destroyed
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_LARGESHIP_WARPOUT = 61, //!< large ship warps out
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 5), // SND_ASTEROID_EXPLODE_LARGE = 62, //!< large asteroid blows up
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 5), // SND_ASTEROID_EXPLODE_SMALL = 63, //!< small asteroid blows up
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_CUE_VOICE = 64, //!< sound to indicate voice is about to start
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_END_VOICE = 65, //!< sound to indicate voice has ended
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 2), // SND_CARGO_SCAN = 66, //!< cargo scanning (looped)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 5), // SND_WEAPON_FLYBY = 67, //!< weapon flyby sound
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 5), // SND_ASTEROID = 68, //!< asteroid sound (persistant, looped)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 2), // SND_CAPITAL_WARP_IN = 69, //!< capital warp hole opening
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 2), // SND_CAPITAL_WARP_OUT = 70, //!< capital warp hole closing
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 5), // SND_ENGINE_LOOP_LARGE = 71, //!< LARGE engine ambient
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_SUBSPACE_LEFT_CHANNEL = 72, //!< subspace ambient sound (left channel) (looped)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_SUBSPACE_RIGHT_CHANNEL = 73, //!< subspace ambient sound (right channel) (looped)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 4), // SND_MISSILE_EVADED_POPUP = 74, //!< "evaded" HUD popup
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_ENGINE_LOOP_HUGE = 75, //!< HUGE engine ambient
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_LIGHT_LASER_FIRE = 76, //!< SD-4 Sidearm laser fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_LIGHT_LASER_IMPACT = 77, //!< DR-2 Scalpel fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_HVY_LASER_FIRE = 78, //!< Flail II fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_HVY_LASER_IMPACT = 79, //!< Prometheus R laser fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_MASSDRV_FIRED = 80, //!< Prometheus S laser fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_MASSDRV_IMPACT = 81, //!< GTW-66 Newton Cannon fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_FLAIL_FIRED = 82, //!< UD-8 Kayser Laser fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_FLAIL_IMPACT = 83, //!< GTW-19 Circe laser fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_NEUTRON_FLUX_FIRED = 84, //!< GTW-83 Lich laser fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_NEUTRON_FLUX_IMPACT = 85, //!< Laser impact
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_DEBUG_LASER_FIRED = 86, //!< Subach-HLV Vasudan laser
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_ROCKEYE_FIRED = 87, //!< rockeye missile launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_MISSILE_IMPACT1 = 88, //!< missile impact 1
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_MAG_MISSILE_LAUNCH = 89, //!< mag pulse missile launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_FURY_MISSILE_LAUNCH = 90, //!< fury missile launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_SHRIKE_MISSILE_LAUNCH = 91, //!< shrike missile launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_ANGEL_MISSILE_LAUNCH = 92, //!< angel fire missile launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_CLUSTER_MISSILE_LAUNCH = 93, //!< cluster bomb launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_CLUSTERB_MISSILE_LAUNCH = 94, //!< cluster baby bomb launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_STILETTO_MISSILE_LAUNCH = 95, //!< stiletto bomb launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_TSUNAMI_MISSILE_LAUNCH = 96, //!< tsunami bomb launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_HARBINGER_MISSILE_LAUNCH = 97, //!< harbinger bomb launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_MEGAWOKKA_MISSILE_LAUNCH = 98, //!< mega wokka launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_CMEASURE1_LAUNCH = 99, //!< countermeasure 1 launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_SHIVAN_LIGHT_LASER_FIRE = 100,//!< Shivan light laser
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_SHOCKWAVE_EXPLODE = 101,//!< shockwave ignition
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_SWARM_MISSILE_LAUNCH = 102,//!< swarm missile sound
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_UNDEFINED_103 = 103,//!< Shivan heavy laser
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_UNDEFINED_104 = 104,//!< Vasudan SuperCap engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 5), // SND_UNDEFINED_105 = 105,//!< Shivan SuperCap engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 5), // SND_UNDEFINED_106 = 106,//!< Terran SuperCap engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_UNDEFINED_107 = 107,//!< Vasudan light laser fired
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_UNDEFINED_108 = 108,//!< Shivan heavy laser
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 2), // SND_SHOCKWAVE_IMPACT = 109,//!< shockwave impact
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_UNDEFINED_110 = 110,//!< TERRAN TURRET 1
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_UNDEFINED_111 = 111,//!< TERRAN TURRET 2
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_UNDEFINED_112 = 112,//!< VASUDAN TURRET 1
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_UNDEFINED_113 = 113,//!< VASUDAN TURRET 2
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_UNDEFINED_114 = 114,//!< SHIVAN TURRET 1
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_TARG_LASER_LOOP = 115,//!< targeting laser loop sound
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 8), // SND_FLAK_FIRE = 116,//!< Flak Gun Launch
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_SHIELD_BREAKER = 117,//!< Flak Gun Impact
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM_LOW, 8), // SND_EMP_MISSILE = 118,//!< EMP Missle
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 5), // SND_AUTOCANNON_LOOP = 119,//!< Escape Pod Drone
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 5), // SND_AUTOCANNON_SHOT = 120,//!< Beam Hit 1
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_BEAM_LOOP = 121,//!< beam loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_BEAM_UP = 122,//!< beam power up
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_BEAM_DOWN = 123,//!< beam power down
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_BEAM_SHOT = 124,//!< Beam shot 1
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_BEAM_VAPORIZE = 125,//!< Beam shot 2
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_TERRAN_FIGHTER_ENG = 126,//!< Terran fighter engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_TERRAN_BOMBER_ENG = 127,//!< Terran bomber engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_TERRAN_CAPITAL_ENG = 128,//!< Terran cruiser engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_SPECIESB_FIGHTER_ENG = 129,//!< Vasudan fighter engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_SPECIESB_BOMBER_ENG = 130,//!< Vasudan bomber engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_SPECIESB_CAPITAL_ENG = 131,//!< Vasudan cruiser engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_SHIVAN_FIGHTER_ENG = 132,//!< Shivan fighter engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_SHIVAN_BOMBER_ENG = 133,//!< Shivan bomber engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_SHIVAN_CAPITAL_ENG = 134,//!< Shivan cruiser engine
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 3), // SND_REPAIR_SHIP_ENG = 135,//!< Repair ship beacon/engine sound
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_UNDEFINED_136 = 136,//!< Terran capital engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_UNDEFINED_137 = 137,//!< Vasudan capital engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 4), // SND_UNDEFINED_138 = 138,//!< Shivan capital engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_DEBRIS_ARC_01 = 139,//!< 0.10 second spark sound effect
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_DEBRIS_ARC_02 = 140,//!< 0.25 second spark sound effect
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_DEBRIS_ARC_03 = 141,//!< 0.50 second spark sound effect
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_DEBRIS_ARC_04 = 142,//!< 0.75 second spark sound effect
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_DEBRIS_ARC_05 = 143,//!< 1.00 second spark sound effect
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_144 = 144,//!< LTerSlash beam loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_145 = 145,//!< TerSlash beam loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_146 = 146,//!< SGreen beam loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_147 = 147,//!< BGreen beem loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_148 = 148,//!< BFGreen been loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_149 = 149,//!< Antifighter beam loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_150 = 150,//!< 1 sec warm up
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_151 = 151,//!< 1.5 sec warm up
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_152 = 152,//!< 2.5 sec warm up
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_153 = 153,//!< 3 sec warm up
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_154 = 154,//!< 3.5 sec warm up
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_155 = 155,//!< 5 sec warm up
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_156 = 156,//!< LTerSlash warm down
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_157 = 157,//!< TerSlash warm down
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_158 = 158,//!< SGreen warm down
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_159 = 159,//!< BGreen warm down
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_160 = 160,//!< BFGreen warm down
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_161 = 161,//!< T_AntiFtr warm down
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_COPILOT = 162,//!< copilot (SCP)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_163 = 163,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_164 = 164,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_165 = 165,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_166 = 166,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_167 = 167,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_168 = 168,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_169 = 169,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_170 = 170,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_171 = 171,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_172 = 172,//!< (Empty in Retail)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_SUPERNOVA_1 = 173,//!< SuperNova (distant)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MUST_PLAY, 1), // SND_SUPERNOVA_2 = 174,//!< SuperNova (shockwave)
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_UNDEFINED_175 = 175,//!< Shivan large engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_MEDIUM, 3), // SND_UNDEFINED_176 = 176,//!< Shivan large engine
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_177 = 177,//!< SRed beam loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_178 = 178,//!< LRed beam loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 3), // SND_UNDEFINED_179 = 179,//!< Antifighter beam loop
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_LIGHTNING_1 = 180,//!< Thunder 1 sound in neblua
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 4), // SND_LIGHTNING_2 = 181,//!< Thunder 2 sound in neblua
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 3), // SND_UNDEFINED_182 = 182,//!< 1 sec warm up
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 3), // SND_UNDEFINED_183 = 183,//!< 1.5 sec warm up
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 3), // SND_UNDEFINED_184 = 184,//!< 3 sec warm up
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 2), // SND_UNDEFINED_185 = 185,//!< Shivan Commnode
EnhancedSoundData( SND_ENHANCED_PRIORITY_HIGH, 1), // SND_UNDEFINED_186 = 186,//!< Volition PirateShip
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 3), // SND_UNDEFINED_187 = 187,//!< SRed warm down
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 3), // SND_UNDEFINED_188 = 188,//!< LRed warm down
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 3), // SND_UNDEFINED_189 = 189,//!< AntiFtr warm down
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 2), // SND_UNDEFINED_190 = 190,//!< Instellation 1
EnhancedSoundData(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 2) // SND_UNDEFINED_191 = 191,//!< Instellation 2
};
static const EnhancedSoundData default_enhanced_sound_data(SND_ENHANCED_PRIORITY_MEDIUM_HIGH, 1);
/*
* Update any uninitialized EnhancedSoundData in Snds
* with hardcoded defaults for retail.
*/
void gamesnd_add_retail_default_enhanced_sound_data()
{
int i = 0;
for (SCP_vector<game_snd>::iterator it = Snds.begin(), end = Snds.end(); it != end; ++it, ++i)
{
if (it->enhanced_sound_data.priority== SND_ENHANCED_PRIORITY_INVALID)
{
if (i < NUM_RETAIL_GAMEPLAY_SOUNDS)
{
it->enhanced_sound_data.priority= Default_sound_priorities[i].priority;
}
else
{
it->enhanced_sound_data.priority= default_enhanced_sound_data.priority;
}
}
if (it->enhanced_sound_data.limit < 1)
{
if (i < NUM_RETAIL_GAMEPLAY_SOUNDS)
{
it->enhanced_sound_data.limit = Default_sound_priorities[i].limit;
}
else
{
it->enhanced_sound_data.limit= default_enhanced_sound_data.limit;
}
}
}
}
void gamesnd_play_iface(int n)
{
if (Snds_iface_handle[n] >= 0)
snd_stop(Snds_iface_handle[n]);
Snds_iface_handle[n] = snd_play(gamesnd_get_interface_sound(n));
}
/**
* Function to search for a given game_snd with the specified name
* in the passed vector
*
* @param name Name to search for
* @param sounds Vector to seach in
*
*/
int gamesnd_lookup_name(const char* name, const SCP_vector<game_snd>& sounds)
{
// if we get passed -1, don't bother trying to look it up.
if (name == NULL || *name == 0 || !strcmp(name, "-1"))
{
return -1;
}
Assert( sounds.size() <= INT_MAX );
int i = 0;
for(SCP_vector<game_snd>::const_iterator snd = sounds.begin(); snd != sounds.end(); ++snd)
{
if (!snd->name.compare(name))
{
return i;
}
i++;
}
return -1;
}
// WMC - now ignores file extension.
int gamesnd_get_by_name(const char* name)
{
Assert( Snds.size() <= INT_MAX );
int index = gamesnd_lookup_name(name, Snds);
if (index < 0)
{
int i = 0;
for(SCP_vector<game_snd>::iterator snd = Snds.begin(); snd != Snds.end(); ++snd)
{
if (snd->sound_entries.size() != 1) {
// Ignore game sounds with more than one sound entry
continue;
}
auto& entry = snd->sound_entries.front();
char *p = strrchr( entry.filename, '.' );
if(p == NULL)
{
if(!stricmp(entry.filename, name))
{
index = i;
break;
}
}
else if(!strnicmp(entry.filename, name, p - entry.filename))
{
index = i;
break;
}
i++;
}
}
return index;
}
int gamesnd_get_by_iface_name(const char* name)
{
Assert( Snds_iface.size() <= INT_MAX );
Assert( Snds_iface.size() == Snds_iface_handle.size() );
int index = gamesnd_lookup_name(name, Snds_iface);
if (index < 0)
{
int i = 0;
for(SCP_vector<game_snd>::iterator snd = Snds_iface.begin(); snd != Snds_iface.end(); ++snd)
{
if (snd->sound_entries.size() != 1) {
// Ignore game sounds with more than one sound entry
continue;
}
auto& entry = snd->sound_entries.front();
char *p = strrchr( entry.filename, '.' );
if(p == NULL)
{
if(!stricmp(entry.filename, name))
{
index = i;
break;
}
}
else if(!strnicmp(entry.filename, name, p - entry.filename))
{
index = i;
break;
}
i++;
}
}
return index;
}
int gamesnd_get_by_tbl_index(int index)
{
char temp[11];
sprintf(temp, "%i", index);
return gamesnd_lookup_name(temp, Snds);
}
int gamesnd_get_by_iface_tbl_index(int index)
{
Assert( Snds_iface.size() == Snds_iface_handle.size() );
char temp[11];
sprintf(temp, "%i", index);
return gamesnd_lookup_name(temp, Snds_iface);
}
/**
* Helper function for parse_sound and parse_sound_list. Do not use directly.
*
* @param tag Tag
* @param idx_dest Sound index destination
* @param object_name Object name being parsed
* @param buf Buffer holding string to be parsed
* @param flags See the parse_sound_flags enum
*
*/
void parse_sound_core(const char* tag, int *idx_dest, const char* object_name, const char* buf, parse_sound_flags flags)
{
int idx;
if(flags & PARSE_SOUND_INTERFACE_SOUND)
idx = gamesnd_get_by_iface_name(buf);
else
idx = gamesnd_get_by_name(buf);
if(idx != -1)
{
(*idx_dest) = idx;
}
size_t size_to_check = 0;
if(flags & PARSE_SOUND_INTERFACE_SOUND)
{
size_to_check = Snds_iface.size();
Assert( Snds_iface.size() == Snds_iface_handle.size() );
}
else
{
size_to_check = Snds.size();
}
Assert( size_to_check <= INT_MAX );
//Ensure sound is in range
if((*idx_dest) < -1 || (*idx_dest) >= (int)size_to_check)
{
(*idx_dest) = -1;
Warning(LOCATION, "%s sound index out of range on '%s'. Must be between 0 and " SIZE_T_ARG ". Forcing to -1 (Nonexistent sound).\n",
tag, object_name, size_to_check);
}
}
/**
* Parse a sound. When using this function for a table entry,
* required_string and optional_string aren't needed, as this function deals with
* that as its tag parameter, just make sure that the destination sound index can
* handle -1 if things don't work out.
*
* @param tag Tag
* @param idx_dest Sound index destination
* @param object_name Object name being parsed
* @param flags See the parse_sound_flags enum
*
*/
void parse_sound(const char* tag, int *idx_dest, const char* object_name, parse_sound_flags flags)
{
if(optional_string(tag))
{
char buf[MAX_FILENAME_LEN];
stuff_string(buf, F_NAME, MAX_FILENAME_LEN);
parse_sound_core(tag, idx_dest, object_name, buf, flags);
}
}
/**
* CommanderDJ - Parse a list of sounds. When using this function for a table entry,
* required_string and optional_string aren't needed, as this function deals with
* that as its tag parameter, just make sure that the destination sound index(es) can
* handle -1 if things don't work out.
*
* @param destination Vector where sound indexes are to be stored
* @param tag Tag
* @param object_name Name of object being parsed
* @param flags See the parse_sound_flags enum
*
*/
void parse_sound_list(const char* tag, SCP_vector<int>& destination, const char* object_name, parse_sound_flags flags)
{
if(optional_string(tag))
{
int check=0;
//if we're using the old format, parse the first entry separately
if(!(flags & PARSE_SOUND_SCP_SOUND_LIST))
{
stuff_int(&check);
}
//now read the rest of the entries on the line
for(size_t i=0; !check_for_eoln(); i++)
{
char buf[MAX_FILENAME_LEN];
stuff_string_white(buf, MAX_FILENAME_LEN);
//we do this conditionally to avoid adding needless entries when reparsing
if(destination.size() <= i)
{
destination.push_back(-1);
}
parse_sound_core(tag, &destination.at(i), object_name, buf, flags);
}
//if we're using the old format, double check the size)
if(!(flags & PARSE_SOUND_SCP_SOUND_LIST) && (destination.size() != (unsigned)check))
{
mprintf(("%s in '%s' has " SIZE_T_ARG " entries. This does not match entered size of %i.", tag, object_name, destination.size(), check));
}
}
}
/**
* Load in sounds that we expect will get played
*
* The method currently used is to load all those sounds that have the hardware flag
* set. This works well since we don't want to try and load hardware sounds in on the
* fly (too slow).
*/
void gamesnd_preload_common_sounds()
{
if ( !Sound_enabled )
return;
Assert( Snds.size() <= INT_MAX );
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
if ( gs->preload ) {
for (auto& entry : gs->sound_entries) {
if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) ) {
game_busy( NOX("** preloading common game sounds **") ); // Animate loading cursor... does nothing if loading screen not active.
entry.id = snd_load(&entry, gs->flags);
}
}
}
}
}
/**
* Load the ingame sounds into memory
*/
void gamesnd_load_gameplay_sounds()
{
if ( !Sound_enabled )
return;
Assert( Snds.size() <= INT_MAX );
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
if ( !gs->preload ) { // don't try to load anything that's already preloaded
for (auto& entry : gs->sound_entries) {
if (entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4)) {
game_busy(NOX("** preloading gameplay sounds **")); // Animate loading cursor... does nothing if loading screen not active.
entry.id = snd_load(&entry, gs->flags);
}
}
}
}
}
/**
* Unload the ingame sounds from memory
*/
void gamesnd_unload_gameplay_sounds()
{
Assert( Snds.size() <= INT_MAX );
for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
for (auto& entry : gs->sound_entries) {
if (entry.id != -1) {
snd_unload(entry.id);
entry.id = -1;
}
}
}
}
/**
* Load the interface sounds into memory
*/
void gamesnd_load_interface_sounds()
{
if ( !Sound_enabled )
return;
Assert( Snds_iface.size() < INT_MAX );
for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
for (auto& entry : si->sound_entries) {
if ( entry.filename[0] != 0 && strnicmp(entry.filename, NOX("none.wav"), 4) ) {
entry.id = snd_load(&entry, si->flags);
}
}
}
}
/**
* Unload the interface sounds from memory
*/
void gamesnd_unload_interface_sounds()
{
Assert( Snds_iface.size() < INT_MAX );
for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
for (auto& entry : si->sound_entries) {
if ( entry.id != -1 ) {
snd_unload( entry.id );
entry.id = -1;
entry.id_sig = -1;
}
}
}
}
void parse_gamesnd_old(game_snd* gs)
{
int is_3d;
int temp;
// An old sound is just a single entry sound set
gs->sound_entries.resize(1);
auto& entry = gs->sound_entries.back();
stuff_string(entry.filename, F_NAME, MAX_FILENAME_LEN, ",");
if (!stricmp(entry.filename, NOX("empty")))
{
entry.filename[0] = 0;
advance_to_eoln(NULL);
return;
}
Mp++;
stuff_int(&temp);
if (temp > 0)
{
gs->preload = true;
}
float default_volume;
stuff_float(&default_volume);
gs->volume_range = util::UniformFloatRange(default_volume);
stuff_int(&is_3d);
if (is_3d)
{
gs->flags |= GAME_SND_USE_DS3D;
stuff_int(&gs->min);
stuff_int(&gs->max);
}
else
{
gs->min = 0;
gs->max = 0;
// silly retail, not abiding by its own format...
if (!stricmp(entry.filename, "l_hit.wav") || !stricmp(entry.filename, "m_hit.wav"))
{
int temp_min, temp_max;
ignore_gray_space();
if (stuff_int_optional(&temp_min, true) == 2)
{
ignore_gray_space();
if (stuff_int_optional(&temp_max, true) == 2)
{
mprintf(("Dutifully converting retail sound %s, '%s' to a 3D sound...\n", gs->name.c_str(), entry.filename));
is_3d = 1;
gs->flags |= GAME_SND_USE_DS3D;
gs->min = temp_min;
gs->max = temp_max;
}
}
}
}
// Default pitch is 1.0
gs->pitch_range = util::UniformFloatRange(1.0f);
// check for extra values per Mantis #2408
ignore_gray_space();
if (stuff_int_optional(&temp, true) == 2)
{
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);
}
advance_to_eoln(NULL);
}
EnhancedSoundPriority convert_to_enhanced_priority(const char * priority_str)
{
Assertion(priority_str != NULL, "convert_to_enhanced_priority given null priority_str!");
if (!stricmp(priority_str, "Must Play"))
{
return SND_ENHANCED_PRIORITY_MUST_PLAY;
}
else if (!stricmp(priority_str, "High"))
{
return SND_ENHANCED_PRIORITY_HIGH;
}
else if (!stricmp(priority_str, "Medium-High"))
{
return SND_ENHANCED_PRIORITY_MEDIUM_HIGH;
}
else if (!stricmp(priority_str, "Medium"))
{
return SND_ENHANCED_PRIORITY_MEDIUM;
}
else if (!stricmp(priority_str, "Medium-Low"))
{
return SND_ENHANCED_PRIORITY_MEDIUM_LOW;
}
else if (!stricmp(priority_str, "Low"))
{
return SND_ENHANCED_PRIORITY_LOW;
}
else
{
error_display(1, "Unknown enhanced sound priority: %s\n", priority_str);
return SND_ENHANCED_PRIORITY_INVALID;
}
}
bool required_string_no_create(const char* token, bool no_create)
{
if (no_create)
{
return optional_string(token) == 1;
}
required_string(token);
return true;
}
static GameSoundCycleType parse_cycle_type() {
if (optional_string("Sequential")) {
return GameSoundCycleType::Sequential;
} else if (optional_string("Random")) {
return GameSoundCycleType::Random;
} else {
error_display(0, "Failed to parse sound cycle type. Expected 'sequential' or 'random'. Got [%.32s]", next_tokens());
// Ignore everything until the end of the line. That should hopefully skip the bad token.
advance_to_eoln(nullptr);
return GameSoundCycleType::Sequential;
}
}
void parse_gamesnd_soundset(game_snd* gs, bool no_create) {
// If this gets called then we just saw "+Entry:" so we can begin processing the first entry immediately
do {
// For now there is no way to change an existing entry so ever +Entry statement creates a new sound entry
gs->sound_entries.resize(gs->sound_entries.size() + 1);
auto& entry = gs->sound_entries.back();
stuff_string(entry.filename, F_NAME, MAX_FILENAME_LEN);
} while (optional_string("+Entry:"));
if (required_string_no_create("+Cycle type:", no_create)) {
gs->cycle_type = parse_cycle_type();
}
if (required_string_no_create("+Preload:", no_create))
{
stuff_boolean(&gs->preload);
}
if (required_string_no_create("+Volume:", no_create))
{
gs->volume_range = util::parseUniformRange(0.0f, 1.0f);
}
if (optional_string("+3D Sound:"))
{
gs->flags |= GAME_SND_USE_DS3D;
required_string("+Attenuation start:");
stuff_int(&gs->min);
required_string("+Attenuation end:");
stuff_int(&gs->max);
}
else
{
gs->min = 0;
gs->max = 0;
}
// jg18 - enhanced sound parameters
if (optional_string("+Priority:"))
{
SCP_string priority_string;
stuff_string(priority_string, F_NAME);
EnhancedSoundPriority priority = convert_to_enhanced_priority(priority_string.c_str());
if (priority != SND_ENHANCED_PRIORITY_INVALID)
{
gs->enhanced_sound_data.priority= priority;
}
// else case not needed since conversion function displays message on error
}
if (optional_string("+Limit:"))
{
int temp_limit;
stuff_int(&temp_limit);
if ((temp_limit > 0) && (static_cast<uint>(temp_limit) <= SND_ENHANCED_MAX_LIMIT))
{
gs->enhanced_sound_data.limit = (unsigned int)temp_limit;
}
else
{
error_display(1, "Invalid enhanced sound limit: %d\n", temp_limit);
}
}
if (optional_string("+Pitch:")) {
gs->pitch_range = util::parseUniformRange(0.0001f);
} else if (!no_create) {
// Default pitch is 1.0
gs->pitch_range = util::UniformFloatRange(1.0f);
}
}
void parse_gamesnd_new(game_snd* gs, bool no_create)
{
game_snd_entry* entry = nullptr;
if (!no_create) {
gs->sound_entries.resize(1);
entry = &gs->sound_entries.back();
} else {
if (gs->sound_entries.size() != 1) {
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.");
}
entry = &gs->sound_entries.back();
}
char name[MAX_FILENAME_LEN];
// New extended format found
stuff_string(name, F_NAME, MAX_FILENAME_LEN);
if (!stricmp(name, NOX("empty")))
{
entry->filename[0] = 0;
return;
}
// If the name _doesn't_ match <same> put it into gs->filename;
if (stricmp(name, "<same>"))
{
strcpy_s(entry->filename, name);
}
else if (!no_create)
{
// Throw an error if <same> was specified but we are creating a new entry
error_display(1, "'<same>' is only allowed if +nocreate was specified!");
return;
}
if (required_string_no_create("+Preload:", no_create))
{
stuff_boolean(&gs->preload);
}
if (required_string_no_create("+Volume:", no_create))
{
float default_volume;
stuff_float(&default_volume);
gs->volume_range = util::UniformFloatRange(default_volume);
}
if (optional_string("+3D Sound:"))
{
gs->flags |= GAME_SND_USE_DS3D;
required_string("+Attenuation start:");
stuff_int(&gs->min);
required_string("+Attenuation end:");
stuff_int(&gs->max);
}
else
{
gs->min = 0;
gs->max = 0;
}
// jg18 - enhanced sound parameters
if (optional_string("+Priority:"))
{
SCP_string priority_string;
stuff_string(priority_string, F_NAME);
EnhancedSoundPriority priority = convert_to_enhanced_priority(priority_string.c_str());
if (priority != SND_ENHANCED_PRIORITY_INVALID)
{
gs->enhanced_sound_data.priority= priority;
}
// else case not needed since conversion function displays message on error
}
if (optional_string("+Limit:"))
{
int temp_limit;
stuff_int(&temp_limit);
if ((temp_limit > 0) && (static_cast<uint>(temp_limit) <= SND_ENHANCED_MAX_LIMIT))
{
gs->enhanced_sound_data.limit = (unsigned int)temp_limit;
}
else
{
error_display(1, "Invalid enhanced sound limit: %d\n", temp_limit);
}
}
// Default pitch is 1.0
gs->pitch_range = util::UniformFloatRange(1.0f);
}
void gamesnd_parse_entry(game_snd *gs, bool no_create, SCP_vector<game_snd> *lookupVector)
{
SCP_string name;
stuff_string(name, F_NAME, "\t \n");
if (!no_create)
{
if (lookupVector != NULL)
{
if (gamesnd_lookup_name(name.c_str(), *lookupVector) >= 0)
{
Warning(LOCATION, "Duplicate sound name \"%s\" found!", name.c_str());
}
}
gs->name = name;
}
else
{
int vectorIndex = gamesnd_lookup_name(name.c_str(), *lookupVector);
if (vectorIndex < 0)
{
Warning(LOCATION, "No existing sound entry with name \"%s\" found!", name.c_str());
no_create = false;
gs->name = name;
}
else
{
gs = &lookupVector->at(vectorIndex);
}
}
if (optional_string("+Filename:"))
{
parse_gamesnd_new(gs, no_create);
} else if (optional_string("+Entry:")) {
parse_gamesnd_soundset(gs, no_create);
}
else
{
parse_gamesnd_old(gs);
}
}
/**
* Parse a sound effect entry by requiring the given tag at the beginning.
*
* @param gs The game_snd instance to fill in
* @param tag The tag that's required before an entry
* @param lookupVector If non-NULL used to look up @c +nocreate entries
*
* @return @c true when a new entry has been parsed and should be added to the list of known
* entries. @c false otherwise, for example in case of @c +nocreate
*/
bool gamesnd_parse_line(game_snd *gs, const char *tag, SCP_vector<game_snd> *lookupVector = NULL)
{
Assertion(gs != NULL, "Invalid game_snd pointer passed to gamesnd_parse_line!");
required_string(tag);
bool no_create = false;
if (lookupVector != NULL)
{
if(optional_string("+nocreate"))
{
no_create = true;
}
}
gamesnd_parse_entry(gs, no_create, lookupVector);
return !no_create;
}
void parse_sound_environments()
{
char name[65] = { '\0' };
char template_name[65] = { '\0' };
EFXREVERBPROPERTIES *props;
while (required_string_either("#Sound Environments End", "$Name:"))
{
required_string("$Name:");
stuff_string(name, F_NAME, sizeof(name)-1);
if (optional_string("$Template:"))
{
stuff_string(template_name, F_NAME, sizeof(template_name)-1);
}
else
{
template_name[0] = '\0';
}
ds_eax_get_prop(&props, name, template_name);
if (optional_string("+Density:"))
{
stuff_float(&props->flDensity);
CLAMP(props->flDensity, 0.0f, 1.0f);
}
if (optional_string("+Diffusion:"))
{
stuff_float(&props->flDiffusion);
CLAMP(props->flDiffusion, 0.0f, 1.0f);
}
if (optional_string("+Gain:"))
{
stuff_float(&props->flGain);
CLAMP(props->flGain, 0.0f, 1.0f);
}
if (optional_string("+Gain HF:"))
{
stuff_float(&props->flGainHF);
CLAMP(props->flGainHF, 0.0f, 1.0f);
}
if (optional_string("+Gain LF:"))
{
stuff_float(&props->flGainLF);
CLAMP(props->flGainLF, 0.0f, 1.0f);
}
if (optional_string("+Decay Time:"))
{
stuff_float(&props->flDecayTime);
CLAMP(props->flDecayTime, 0.01f, 20.0f);
}
if (optional_string("+Decay HF Ratio:"))
{
stuff_float(&props->flDecayHFRatio);
CLAMP(props->flDecayHFRatio, 0.1f, 20.0f);
}
if (optional_string("+Decay LF Ratio:"))
{
stuff_float(&props->flDecayLFRatio);
CLAMP(props->flDecayLFRatio, 0.1f, 20.0f);
}
if (optional_string("+Reflections Gain:"))
{
stuff_float(&props->flReflectionsGain);
CLAMP(props->flReflectionsGain, 0.0f, 3.16f);
}
if (optional_string("+Reflections Delay:"))
{
stuff_float(&props->flReflectionsDelay);
CLAMP(props->flReflectionsDelay, 0.0f, 0.3f);
}
if (optional_string("+Reflections Pan:"))
{
stuff_float_list(props->flReflectionsPan, 3);
CLAMP(props->flReflectionsPan[0], 0.0f, 1.0f);
CLAMP(props->flReflectionsPan[1], 0.0f, 1.0f);
CLAMP(props->flReflectionsPan[2], 0.0f, 1.0f);
}
if (optional_string("+Late Reverb Gain:"))
{
stuff_float(&props->flLateReverbGain);
CLAMP(props->flLateReverbGain, 0.0f, 10.0f);
}
if (optional_string("+Late Reverb Delay:"))
{
stuff_float(&props->flLateReverbDelay);
CLAMP(props->flLateReverbDelay, 0.0f, 0.1f);
}
if (optional_string("+Late Reverb Pan:"))
{
stuff_float_list(props->flLateReverbPan, 3);
CLAMP(props->flLateReverbPan[0], 0.0f, 1.0f);
CLAMP(props->flLateReverbPan[1], 0.0f, 1.0f);
CLAMP(props->flLateReverbPan[2], 0.0f, 1.0f);
}
if (optional_string("+Echo Time:"))
{
stuff_float(&props->flEchoTime);
CLAMP(props->flEchoTime, 0.075f, 0.25f);
}
if (optional_string("+Echo Depth:"))
{
stuff_float(&props->flEchoDepth);
CLAMP(props->flEchoDepth, 0.0f, 1.0f);
}
if (optional_string("+Modulation Time:"))
{
stuff_float(&props->flModulationTime);
CLAMP(props->flModulationTime, 0.004f, 4.0f);
}
if (optional_string("+Modulation Depth:"))
{
stuff_float(&props->flModulationDepth);
CLAMP(props->flModulationDepth, 0.0f, 1.0f);
}
if (optional_string("+HF Reference:"))
{
stuff_float(&props->flHFReference);
CLAMP(props->flHFReference, 1000.0f, 20000.0f);
}
if (optional_string("+LF Reference:"))
{
stuff_float(&props->flLFReference);
CLAMP(props->flLFReference, 20.0f, 1000.0f);
}
if (optional_string("+Room Rolloff Factor:"))
{
stuff_float(&props->flRoomRolloffFactor);
CLAMP(props->flRoomRolloffFactor, 0.0f, 10.0f);
}
if (optional_string("+Air Absorption Gain HF:"))
{
stuff_float(&props->flAirAbsorptionGainHF);
CLAMP(props->flAirAbsorptionGainHF, 0.892f, 1.0f);
}
if (optional_string("+Decay HF Limit:"))
{
stuff_int(&props->iDecayHFLimit);
CLAMP(props->iDecayHFLimit, 0, 1);
}
}
required_string("#Sound Environments End");
}
static SCP_vector<species_info> missingFlybySounds;
void parse_sound_table(const char* filename)
{
try
{
read_file_text(filename, CF_TYPE_TABLES);
reset_parse();
// Parse the gameplay sounds section
if (optional_string("#Game Sounds Start"))
{
while (!check_for_string("#Game Sounds End"))
{
game_snd tempSound;
if (gamesnd_parse_line(&tempSound, "$Name:", &Snds))
{
Snds.push_back(game_snd(tempSound));
}
}
required_string("#Game Sounds End");
}
// Parse the interface sounds section
if (optional_string("#Interface Sounds Start"))
{
while (!check_for_string("#Interface Sounds End"))
{
game_snd tempSound;
if (gamesnd_parse_line(&tempSound, "$Name:", &Snds_iface))
{
Snds_iface.push_back(game_snd(tempSound));
Snds_iface_handle.push_back(-1);
}
}
required_string("#Interface Sounds End");
}
// parse flyby sound section
if (optional_string("#Flyby Sounds Start"))
{
char species_name_tag[NAME_LENGTH + 2];
int sanity_check = 0;
size_t i;
while (!check_for_string("#Flyby Sounds End") && (sanity_check <= (int)Species_info.size()))
{
for (i = 0; i < Species_info.size(); i++)
{
species_info *species = &Species_info[i];
sprintf(species_name_tag, "$%s:", species->species_name);
if (check_for_string(species_name_tag))
{
gamesnd_parse_line(&species->snd_flyby_fighter, species_name_tag);
gamesnd_parse_line(&species->snd_flyby_bomber, species_name_tag);
sanity_check--;
}
else
{
sanity_check++;
}
}
}
required_string("#Flyby Sounds End");
}
if (optional_string("#Sound Environments Start"))
{
parse_sound_environments();
}
}
catch (const parse::ParseException& e)
{
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
return;
}
gamesnd_add_retail_default_enhanced_sound_data();
}
/**
* Parse the sounds.tbl file, and load the specified sounds.
*/
void gamesnd_parse_soundstbl()
{
parse_sound_table("sounds.tbl");
parse_modular_table("*-snd.tbm", parse_sound_table);
// if we are missing any species then report
if (!missingFlybySounds.empty())
{
SCP_string errorString;
for (size_t i = 0; i < missingFlybySounds.size(); i++)
{
errorString.append(missingFlybySounds[i].species_name);
errorString.append("\n");
}
Error(LOCATION, "The following species are missing flyby sounds in sounds.tbl:\n%s", errorString.c_str());
}
missingFlybySounds.clear();
}
/**
* Close out gamesnd, ONLY CALL FROM game_shutdown()!!!!
*/
void gamesnd_close()
{
Snds.clear();
Snds_iface.clear();
Snds_iface_handle.clear();
}
/**
* Callback function for the UI code to call when the mouse first goes over a button.
*/
void common_play_highlight_sound()
{
gamesnd_play_iface(SND_USER_OVER);
}
/**
* Callback function for the UI code to call when an error beep needed.
*/
void gamesnd_play_error_beep()
{
gamesnd_play_iface(SND_GENERAL_FAIL);
}
game_snd* gamesnd_get_game_sound(int handle) {
Assertion(handle >= 0 && handle < (int) Snds.size(), "Invalid game sound handle %d detected!", handle);
return &Snds[handle];
}
game_snd* gamesnd_get_interface_sound(int handle) {
Assertion(handle >= 0 && handle < (int) Snds_iface.size(), "Invalid interface sound handle %d detected!", handle);
return &Snds_iface[handle];
}
bool gamesnd_game_sound_valid(int sound) {
return sound >= 0 && sound < (int) Snds.size();
}
bool gamesnd_interface_sound_valid(int sound) {
return sound >= 0 && sound < (int) Snds_iface.size();
}
float gamesnd_get_max_duration(game_snd* gs) {
int max_length = 0;
for (auto& entry : gs->sound_entries) {
Assertion(entry.id != -1, "Game sound must be loaded to determine maximum duration!");
max_length = std::max(max_length, snd_get_duration(entry.id));
}
return max_length / gs->pitch_range.max();
}
game_snd_entry* gamesnd_choose_entry(game_snd* gs) {
Assertion(!gs->sound_entries.empty(), "No sound entries found in game sound! This may not happen!");
size_t index = 0;
switch(gs->cycle_type) {
case GameSoundCycleType::Random:
if (gs->sound_entries.size() == 1) {
index = 0;
} else {
index = util::UniformRange<size_t>(0, gs->sound_entries.size() - 1).next();
}
break;
case GameSoundCycleType::Sequential:
if (gs->last_entry_index == std::numeric_limits<size_t>::max()) {
// If this is the first time we must return the first sound to keep everything consistent
index = 0;
} else {
index = (gs->last_entry_index + 1) % gs->sound_entries.size();
}
break;
}
gs->last_entry_index = index;
return &gs->sound_entries[index];
}