1 (*
2 * Hedgewars, a free turn based strategy game
3 * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 2 of the License
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *)
18
19 {$INCLUDE "options.inc"}
20
21 unit uSound;
22 (*
23 * This unit controls the sounds and music of the game.
24 * Doesn't really do anything if isSoundEnabled = false and isMusicEnabled = false
25 *
26 * There are three basic types of sound controls:
27 * Music - The background music of the game:
28 * * will only be played if isMusicEnabled = true
29 * * can be started, changed, paused and resumed
30 * Sound - Can be started and stopped
31 * Looped Sound - Subtype of sound: plays in a loop using a
32 * "channel", of which the id is returned on start.
33 * The channel id can be used to stop a specific sound loop.
34 *)
35 interface
36 uses SDLh, uConsts, uTypes;
37
38 procedure preInitModule;
39 procedure initModule;
40 procedure freeModule;
41
42 procedure InitSound; // Initiates sound-system if isSoundEnabled.
43 procedure ReleaseSound(complete: boolean); // Releases sound-system and used resources.
44 procedure ResetSound; // Reset sound state to the previous state.
45 procedure SetSound(enabled: boolean); // Enable/disable sound-system and backup status.
46 procedure SetAudioDampen(enabled: boolean); // Enable/disable automatic dampening if losing window focus.
47
48 // MUSIC
49
50 // Obvious music commands for music track
51 procedure SetMusic(enabled: boolean); // Enable/disable music.
52 procedure SetMusicName(musicname: shortstring); // Set name of the file to play.
53 procedure PlayMusic; // Play music from the start.
54 procedure PauseMusic; // Pause music.
55 procedure ResumeMusic; // Resume music from pause point.
56 procedure ChangeMusic(musicname: shortstring); // Replaces music track with musicname and plays it.
57 procedure StopMusic; // Stops and releases the current track.
58
59
60 // SOUNDS
61
62 // Plays the sound snd [from a given voicepack],
63 // if keepPlaying is given and true,
64 // then the sound's playback won't be interrupted if asked to play again.
65 // Returns true if sound was found and is played, false otherwise.
PlaySoundnull66 function PlaySound(snd: TSound): boolean;
PlaySoundnull67 function PlaySound(snd: TSound; keepPlaying: boolean): boolean;
PlaySoundnull68 function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean): boolean;
PlaySoundnull69 function PlaySound(snd: TSound; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
PlaySoundVnull70 function PlaySoundV(snd: TSound; voicepack: PVoicepack): boolean;
PlaySoundVnull71 function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean): boolean;
PlaySoundVnull72 function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask: boolean): boolean;
PlaySoundVnull73 function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
74
75 // Plays/stops a sound to replace the main background music temporarily.
76 procedure PlayMusicSound(snd: TSound);
77 procedure StopMusicSound(snd: TSound);
78
79 // Plays sound snd [of voicepack] in a loop, but starts with fadems milliseconds of fade-in.
80 // Returns sound channel of the looped sound.
LoopSoundnull81 function LoopSound(snd: TSound): LongInt;
LoopSoundnull82 function LoopSound(snd: TSound; fadems: LongInt): LongInt;
LoopSoundVnull83 function LoopSoundV(snd: TSound; voicepack: PVoicepack): LongInt;
LoopSoundVnull84 function LoopSoundV(snd: TSound; voicepack: PVoicepack; fadems: LongInt): LongInt;
85
86 // Stops the normal/looped sound of the given type/in the given channel
87 // [with a fade-out effect for fadems milliseconds].
88 procedure StopSound(snd: TSound);
89 procedure StopSound(snd: TSound; soundAsMusic: boolean);
90 procedure StopSoundChan(chn: LongInt);
91 procedure StopSoundChan(chn, fadems: LongInt);
92
93 // Add voice to the voice queue
94 procedure AddVoice(snd: TSound; voicepack: PVoicepack);
95 procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask, isFallback: boolean);
96
97 // Actually play next voice
98 procedure PlayNextVoice;
99
100
101 // GLOBAL FUNCTIONS
102
103 // Drastically lower the volume when we lose focus (and restore the previous value).
104 procedure DampenAudio;
105 procedure UndampenAudio;
106
107 // Mute/Unmute audio
108 procedure MuteAudio;
109
110
111 // MISC
112
113 // Set the initial volume
114 procedure SetVolume(vol: LongInt);
115
116 // Modifies the sound volume of the game by voldelta and returns the new volume level.
ChangeVolumenull117 function ChangeVolume(voldelta: LongInt): LongInt;
118
119 // Returns the current volume in percent. Intended for display on UI.
GetVolumePercentnull120 function GetVolumePercent(): LongInt;
121
122 // Returns a pointer to the voicepack with the given name.
AskForVoicepacknull123 function AskForVoicepack(name: shortstring): Pointer;
124
125 var MusicFN: shortstring; // music file name
126 SDMusicFN: shortstring; // SD music file name
127 FallbackMusicFN: shortstring; // fallback music file name
128 FallbackSDMusicFN: shortstring; // fallback SD music fille name
129
130 var Volume: LongInt;
131 SoundTimerTicks: Longword;
132 LastVoiceFailed: boolean;
133 implementation
134 uses uVariables, uConsole, uCommands, uDebug, uPhysFSLayer;
135
136 const chanTPU = 32;
137 var cInitVolume: LongInt;
138 previousVolume: LongInt; // cached volume value
139 lastChan: array [TSound] of LongInt;
140 voicepacks: array[0..cMaxTeams] of TVoicepack;
141 defVoicepack: PVoicepack;
142 Mus: PMixMusic; // music pointer
143 isMusicEnabled: boolean;
144 isSoundEnabled: boolean;
145 isAutoDampening: boolean;
146 isSEBackup: boolean;
147 VoiceList : array[0..7] of TVoice = (
148 ( snd: sndNone; voicepack: nil; isFallback: false),
149 ( snd: sndNone; voicepack: nil; isFallback: false),
150 ( snd: sndNone; voicepack: nil; isFallback: false),
151 ( snd: sndNone; voicepack: nil; isFallback: false),
152 ( snd: sndNone; voicepack: nil; isFallback: false),
153 ( snd: sndNone; voicepack: nil; isFallback: false),
154 ( snd: sndNone; voicepack: nil; isFallback: false),
155 ( snd: sndNone; voicepack: nil; isFallback: false));
156 Soundz: array[TSound] of record
157 FileName: string[31];
158 Path, AltPath : TPathType;
159 end = (
160 (FileName: ''; Path: ptNone; AltPath: ptNone),// sndNone
161 (FileName: 'grenadeimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndGrenadeImpact
162 (FileName: 'explosion.ogg'; Path: ptSounds; AltPath: ptNone),// sndExplosion
163 (FileName: 'throwpowerup.ogg'; Path: ptSounds; AltPath: ptNone),// sndThrowPowerUp
164 (FileName: 'throwrelease.ogg'; Path: ptSounds; AltPath: ptNone),// sndThrowRelease
165 (FileName: 'splash.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndSplash
166 (FileName: 'shotgunreload.ogg'; Path: ptSounds; AltPath: ptNone),// sndShotgunReload
167 (FileName: 'shotgunfire.ogg'; Path: ptSounds; AltPath: ptNone),// sndShotgunFire
168 (FileName: 'graveimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndGraveImpact
169 (FileName: 'mineimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndMineImpact
170 (FileName: 'minetick.ogg'; Path: ptSounds; AltPath: ptNone),// sndMineTicks
171 // TODO: New mudball sound?
172 (FileName: 'Droplet1.ogg'; Path: ptSounds; AltPath: ptNone),// sndMudballImpact
173 (FileName: 'pickhammer.ogg'; Path: ptSounds; AltPath: ptNone),// sndPickhammer
174 (FileName: 'gun.ogg'; Path: ptSounds; AltPath: ptNone),// sndGun
175 (FileName: 'bee.ogg'; Path: ptSounds; AltPath: ptNone),// sndBee
176 (FileName: 'Jump1.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump1
177 (FileName: 'Jump2.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump2
178 (FileName: 'Jump3.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump3
179 (FileName: 'Yessir.ogg'; Path: ptVoices; AltPath: ptNone),// sndYesSir
180 (FileName: 'Laugh.ogg'; Path: ptVoices; AltPath: ptNone),// sndLaugh
181 (FileName: 'Illgetyou.ogg'; Path: ptVoices; AltPath: ptNone),// sndIllGetYou
182 (FileName: 'Justyouwait.ogg'; Path: ptVoices; AltPath: ptNone),// sndJustyouwait
183 (FileName: 'Incoming.ogg'; Path: ptVoices; AltPath: ptNone),// sndIncoming
184 (FileName: 'Missed.ogg'; Path: ptVoices; AltPath: ptNone),// sndMissed
185 (FileName: 'Stupid.ogg'; Path: ptVoices; AltPath: ptNone),// sndStupid
186 (FileName: 'Firstblood.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirstBlood
187 (FileName: 'Boring.ogg'; Path: ptVoices; AltPath: ptNone),// sndBoring
188 (FileName: 'Byebye.ogg'; Path: ptVoices; AltPath: ptNone),// sndByeBye
189 (FileName: 'Sameteam.ogg'; Path: ptVoices; AltPath: ptNone),// sndSameTeam
190 (FileName: 'Nutter.ogg'; Path: ptVoices; AltPath: ptNone),// sndNutter
191 (FileName: 'Reinforcements.ogg'; Path: ptVoices; AltPath: ptNone),// sndReinforce
192 (FileName: 'Traitor.ogg'; Path: ptVoices; AltPath: ptNone),// sndTraitor
193 (FileName: 'Youllregretthat.ogg'; Path: ptVoices; AltPath: ptNone),// sndRegret
194 (FileName: 'Enemydown.ogg'; Path: ptVoices; AltPath: ptNone),// sndEnemyDown
195 (FileName: 'Coward.ogg'; Path: ptVoices; AltPath: ptNone),// sndCoward
196 (FileName: 'Hurry.ogg'; Path: ptVoices; AltPath: ptNone),// sndHurry
197 (FileName: 'Watchit.ogg'; Path: ptVoices; AltPath: ptNone),// sndWatchIt
198 (FileName: 'Kamikaze.ogg'; Path: ptVoices; AltPath: ptNone),// sndKamikaze
199 (FileName: 'cake2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCake
200 (FileName: 'Ow1.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw1
201 (FileName: 'Ow2.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw2
202 (FileName: 'Ow3.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw3
203 (FileName: 'Ow4.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw4
204 (FileName: 'Firepunch1.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch1
205 (FileName: 'Firepunch2.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch2
206 (FileName: 'Firepunch3.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch3
207 (FileName: 'Firepunch4.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch4
208 (FileName: 'Firepunch5.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch5
209 (FileName: 'Firepunch6.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch6
210 (FileName: 'Melon.ogg'; Path: ptVoices; AltPath: ptNone),// sndMelon
211 (FileName: 'Hellish.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellish
212 (FileName: 'Yoohoo.ogg'; Path: ptSounds; AltPath: ptNone),// sndYoohoo
213 (FileName: 'rcplane.ogg'; Path: ptSounds; AltPath: ptNone),// sndRCPlane
214 (FileName: 'whipcrack.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhipCrack
215 (FileName:'ride_of_the_valkyries.ogg'; Path: ptSounds; AltPath: ptNone),// sndRideOfTheValkyries
216 (FileName: 'denied.ogg'; Path: ptSounds; AltPath: ptNone),// sndDenied
217 (FileName: 'placed.ogg'; Path: ptSounds; AltPath: ptNone),// sndPlaced
218 (FileName: 'baseballbat.ogg'; Path: ptSounds; AltPath: ptNone),// sndBaseballBat
219 (FileName: 'steam.ogg'; Path: ptSounds; AltPath: ptNone),// sndVaporize
220 (FileName: 'warp.ogg'; Path: ptSounds; AltPath: ptNone),// sndWarp
221 (FileName: 'suddendeath.ogg'; Path: ptSounds; AltPath: ptNone),// sndSuddenDeath
222 (FileName: 'mortar.ogg'; Path: ptSounds; AltPath: ptNone),// sndMortar
223 (FileName: 'shutterclick.ogg'; Path: ptSounds; AltPath: ptNone),// sndShutter
224 (FileName: 'homerun.ogg'; Path: ptSounds; AltPath: ptNone),// sndHomerun
225 (FileName: 'molotov.ogg'; Path: ptSounds; AltPath: ptNone),// sndMolotov
226 (FileName: 'Takecover.ogg'; Path: ptVoices; AltPath: ptNone),// sndCover
227 (FileName: 'Uh-oh.ogg'; Path: ptVoices; AltPath: ptNone),// sndUhOh
228 (FileName: 'Oops.ogg'; Path: ptVoices; AltPath: ptNone),// sndOops
229 (FileName: 'Nooo.ogg'; Path: ptVoices; AltPath: ptNone),// sndNooo
230 (FileName: 'Hello.ogg'; Path: ptVoices; AltPath: ptNone),// sndHello
231 (FileName: 'ropeshot.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeShot
232 (FileName: 'ropeattach.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeAttach
233 (FileName: 'roperelease.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeRelease
234 (FileName: 'switchhog.ogg'; Path: ptSounds; AltPath: ptNone),// sndSwitchHog
235 (FileName: 'Victory.ogg'; Path: ptVoices; AltPath: ptNone),// sndVictory
236 (FileName: 'Flawless.ogg'; Path: ptVoices; AltPath: ptNone),// sndFlawless
237 (FileName: 'sniperreload.ogg'; Path: ptSounds; AltPath: ptNone),// sndSniperReload
238 (FileName: 'steps.ogg'; Path: ptSounds; AltPath: ptNone),// sndSteps
239 (FileName: 'lowgravity.ogg'; Path: ptSounds; AltPath: ptNone),// sndLowGravity
240 (FileName: 'hell_growl.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact1
241 (FileName: 'hell_ooff.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact2
242 (FileName: 'hell_ow.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact3
243 (FileName: 'hell_ugh.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact4
244 (FileName: 'melonimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndMelonImpact
245 (FileName: 'Droplet1.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet1
246 (FileName: 'Droplet2.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet2
247 (FileName: 'Droplet3.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet3
248 (FileName: 'egg.ogg'; Path: ptSounds; AltPath: ptNone),// sndEggBreak
249 (FileName: 'drillgun.ogg'; Path: ptSounds; AltPath: ptNone),// sndDrillRocket
250 (FileName: 'PoisonCough.ogg'; Path: ptVoices; AltPath: ptDefaultVoice),// sndPoisonCough
251 (FileName: 'PoisonMoan.ogg'; Path: ptVoices; AltPath: ptDefaultVoice),// sndPoisonMoan
252 (FileName: 'BirdyLay.ogg'; Path: ptSounds; AltPath: ptNone),// sndBirdyLay
253 (FileName: 'Whistle.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhistle
254 (FileName: 'beewater.ogg'; Path: ptSounds; AltPath: ptNone),// sndBeeWater
255 (FileName: '1C.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano0
256 (FileName: '2D.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano1
257 (FileName: '3E.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano2
258 (FileName: '4F.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano3
259 (FileName: '5G.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano4
260 (FileName: '6A.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano5
261 (FileName: '7B.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano6
262 (FileName: '8C.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano7
263 (FileName: '9D.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano8
264 (FileName: 'skip.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndSkip
265 (FileName: 'sinegun.ogg'; Path: ptSounds; AltPath: ptNone),// sndSineGun
266 (FileName: 'Ooff1.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff1
267 (FileName: 'Ooff2.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff2
268 (FileName: 'Ooff3.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff3
269 (FileName: 'hammer.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhack
270 (FileName: 'Comeonthen.ogg'; Path: ptVoices; AltPath: ptNone),// sndComeonthen
271 (FileName: 'parachute.ogg'; Path: ptSounds; AltPath: ptNone),// sndParachute
272 (FileName: 'bump.ogg'; Path: ptSounds; AltPath: ptNone),// sndBump
273 (FileName: 'hogchant3.ogg'; Path: ptSounds; AltPath: ptNone),// sndResurrector
274 (FileName: 'plane.ogg'; Path: ptSounds; AltPath: ptNone),// sndPlane
275 (FileName: 'TARDIS.ogg'; Path: ptSounds; AltPath: ptNone),// sndTardis
276 (FileName: 'frozen_hog_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndFrozenHogImpact
277 (FileName: 'ice_beam.ogg'; Path: ptSounds; AltPath: ptNone),// sndIceBeam
278 (FileName: 'hog_freeze.ogg'; Path: ptSounds; AltPath: ptNone), // sndHogFreeze
279 (FileName: 'airmine_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndAirMineImpact
280 (FileName: 'knife_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndKnifeImpact
281 (FileName: 'extratime.ogg'; Path: ptSounds; AltPath: ptNone),// sndExtraTime
282 (FileName: 'lasersight.ogg'; Path: ptSounds; AltPath: ptNone),// sndLaserSight
283 (FileName: 'invulnerable.ogg'; Path: ptSounds; AltPath: ptNone),// sndInvulnerable
284 (FileName: 'ufo.ogg'; Path: ptSounds; AltPath: ptNone),// sndJetpackLaunch
285 (FileName: 'jetpackboost.ogg'; Path: ptSounds; AltPath: ptNone),// sndJetpackBoost
286 (FileName: 'portalshot.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalShot
287 (FileName: 'portalswitch.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalSwitch
288 (FileName: 'portalopen.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalOpen
289 (FileName: 'blowtorch.ogg'; Path: ptSounds; AltPath: ptNone),// sndBlowTorch
290 (FileName: 'countdown1.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown1
291 (FileName: 'countdown2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown2
292 (FileName: 'countdown3.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown3
293 (FileName: 'countdown4.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown4
294 // TODO: Check which creeper (formerly rubberduck) sounds are needed, maybe rename them
295 (FileName: 'rubberduck_drop.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperDrop
296 (FileName: 'rubberduck_water.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperWater
297 (FileName: 'rubberduck_die.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperDie
298 (FileName: 'custom1.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom1
299 (FileName: 'custom2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom2
300 (FileName: 'custom3.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom3
301 (FileName: 'custom4.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom4
302 (FileName: 'custom5.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom5
303 (FileName: 'custom6.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom6
304 (FileName: 'custom7.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom7
305 (FileName: 'custom8.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom8
306 (FileName: 'minigun.ogg'; Path: ptSounds; AltPath: ptNone),// sndMinigun
307 (FileName: 'flamethrower.ogg'; Path: ptSounds; AltPath: ptNone),// sndFlamethrower
308 (FileName: 'ice_beam_idle.ogg'; Path: ptSounds; AltPath: ptNone),// sndIceBeamIdle
309 (FileName: 'landgun.ogg'; Path: ptSounds; AltPath: ptNone),// sndLandGun
310 (FileName: 'graveimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndCaseImpact
311 // TODO: New Extra Damage sound
312 (FileName: 'hell_ugh.ogg'; Path: ptSounds; AltPath: ptNone),// sndExtraDamage
313 (FileName: 'firepunch_hit.ogg'; Path: ptSounds; AltPath: ptNone),// sndFirePunchHit
314 (FileName: 'Grenade.ogg'; Path: ptVoices; AltPath: ptNone),// sndGrenade
315 (FileName: 'Thisoneismine.ogg'; Path: ptVoices; AltPath: ptNone),// sndThisOneIsMine
316 (FileName: 'Whatthe.ogg'; Path: ptVoices; AltPath: ptNone),// sndWhatThe
317 (FileName: 'Solong.ogg'; Path: ptVoices; AltPath: ptNone),// sndSoLong
318 (FileName: 'Ohdear.ogg'; Path: ptVoices; AltPath: ptNone),// sndOhDear
319 (FileName: 'Gonnagetyou.ogg'; Path: ptVoices; AltPath: ptNone),// sndGonnaGetYou
320 (FileName: 'Drat.ogg'; Path: ptVoices; AltPath: ptNone),// sndDrat
321 (FileName: 'Bugger.ogg'; Path: ptVoices; AltPath: ptNone),// sndBugger
322 (FileName: 'Amazing.ogg'; Path: ptVoices; AltPath: ptNone),// sndAmazing
323 (FileName: 'Brilliant.ogg'; Path: ptVoices; AltPath: ptNone),// sndBrilliant
324 (FileName: 'Excellent.ogg'; Path: ptVoices; AltPath: ptNone),// sndExcellent
325 (FileName: 'Fire.ogg'; Path: ptVoices; AltPath: ptNone),// sndFire
326 (FileName: 'Watchthis.ogg'; Path: ptVoices; AltPath: ptNone),// sndWatchThis
327 (FileName: 'Runaway.ogg'; Path: ptVoices; AltPath: ptNone),// sndRunAway
328 (FileName: 'Revenge.ogg'; Path: ptVoices; AltPath: ptNone),// sndRevenge
329 (FileName: 'Cutitout.ogg'; Path: ptVoices; AltPath: ptNone),// sndCutItOut
330 (FileName: 'Leavemealone.ogg'; Path: ptVoices; AltPath: ptNone),// sndLeaveMeAlone
331 (FileName: 'Ouch.ogg'; Path: ptVoices; AltPath: ptNone),// sndOuch
332 (FileName: 'Hmm.ogg'; Path: ptVoices; AltPath: ptNone),// sndHmm
333 (FileName: 'Kiss.ogg'; Path: ptSounds; AltPath: ptNone),// sndKiss
334 (FileName: 'Flyaway.ogg'; Path: ptVoices; AltPath: ptNone),// sndFlyAway
335 (FileName: 'planewater.ogg'; Path: ptSounds; AltPath: ptNone) // sndPlaneWater
336 );
337
338
AskForVoicepacknull339 function AskForVoicepack(name: shortstring): Pointer;
340 var i: Longword;
341 tmp, nameStart, langName, path: shortstring;
342 begin
343 nameStart:= name;
344 i:= 0;
345
346 { Adjust for language suffix: Voicepacks can have an optional language suffix.
347 It's an underscore followed by an ISO 639-1 or ISO 639-2 language code.
348 The suffix “_qau” is special, it will enable automatic language selection
349 of this voicepack. For example, if team has set Default_qau as voicepack,
350 and the player language is Russian, the actual voicepack will be Default_ru,
351 provided it can be found on the disk.
352 “qau” is a valid ISO 639-2 language code reserved for local use. }
353 tmp:= Copy(name, Length(name) - 3, 4);
354 if (tmp = '_qau') then
355 name:= Copy(name, 1, Length(name) - 4);
356 if (cLanguage <> 'en') and (tmp = '_qau') then
357 begin
358 langName:= name+'_'+cLanguage;
359 path:= cPathz[ptVoices] + '/' + langName;
360 if pfsExists(path) then
361 name:= langName
362 else
363 if Length(cLanguage) > 3 then
364 begin
365 langName:= name+'_'+Copy(cLanguage,1,2);
366 path:= cPathz[ptVoices] + '/' + langName;
367 if pfsExists(path) then
368 name:= langName
369 end
370 end;
371
372 path:= cPathz[ptVoices] + '/' + name;
373
374 // Fallback to localized Default if voicepack can't be found at all
375 if (nameStart <> 'Default_qau') and (not pfsExists(path)) then
376 exit(AskForVoicepack('Default_qau'));
377
378 while (voicepacks[i].name <> name) and (voicepacks[i].name <> '') and (i < cMaxTeams) do
379 begin
380 inc(i);
381 //TryDo(i <= cMaxTeams, 'Engine bug: AskForVoicepack i > cMaxTeams', true)
382 end;
383
384 voicepacks[i].name:= name;
385 AskForVoicepack:= @voicepacks[i]
386 end;
387
388 procedure InitSound;
389 const channels: LongInt = 2;
390 var success: boolean;
391 s: shortstring;
392 begin
393 if not (isSoundEnabled or isMusicEnabled) then
394 begin
395 isAudioMuted:= true;
396 cInitVolume:= 0;
397 exit;
398 end;
399 WriteToConsole('Init sound...');
400 success:= SDL_InitSubSystem(SDL_INIT_AUDIO) = 0;
401
402 if success then
403 begin
404 WriteLnToConsole(msgOK);
405 WriteToConsole('Open audio...');
406 success:= Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, channels, 1024) = 0;
407 end;
408
409 if success then
410 WriteLnToConsole(msgOK)
411 else
412 begin
413 WriteLnToConsole(msgFailed);
414 isSoundEnabled:= false;
415 isMusicEnabled:= false;
416 isAudioMuted:= true;
417 cInitVolume:= 0;
418 end;
419
420 WriteToConsole('Init SDL_mixer... ');
421
422 if (Mix_Init(MIX_INIT_OGG or MIX_INIT_OPUS) and MIX_INIT_OPUS) = 0 then
423 begin
424 s:= SDL_GetError();
425 WriteToConsole('Cannot init OPUS: ' + s);
426
427 if SDLCheck(Mix_Init(MIX_INIT_OGG) <> 0, 'Mix_Init', true) then exit;
428 end;
429
430 WriteLnToConsole(msgOK);
431
432 // from uVariables to be used by other modules
433 cIsSoundEnabled:= true;
434
435 Mix_AllocateChannels(Succ(chanTPU));
436 previousVolume:= cInitVolume;
437 ChangeVolume(cInitVolume);
438 end;
439
440 procedure ResetSound;
441 begin
442 isSoundEnabled:= isSEBackup;
443 end;
444
445 procedure SetSound(enabled: boolean);
446 begin
447 isSEBackup:= isSoundEnabled;
448 isSoundEnabled:= enabled;
449 end;
450
451 procedure SetAudioDampen(enabled: boolean);
452 begin
453 isAutoDampening:= enabled;
454 end;
455
456 // when complete is false, this procedure just releases some of the chucks on inactive channels
457 // in this way music is not stopped, nor are chucks currently being played
458 procedure ReleaseSound(complete: boolean);
459 var i: TSound;
460 t: Longword;
461 begin
462 // release and nil all sounds
463 for t:= 0 to cMaxTeams do
464 if voicepacks[t].name <> '' then
465 for i:= Low(TSound) to High(TSound) do
466 if voicepacks[t].chunks[i] <> nil then
467 if complete or (Mix_Playing(lastChan[i]) = 0) then
468 begin
469 Mix_HaltChannel(lastChan[i]);
470 lastChan[i]:= -1;
471 Mix_FreeChunk(voicepacks[t].chunks[i]);
472 voicepacks[t].chunks[i]:= nil;
473 end;
474
475 // stop music
476 if complete then
477 begin
478 if Mus <> nil then
479 begin
480 Mix_HaltMusic();
481 Mix_FreeMusic(Mus);
482 Mus:= nil;
483 end;
484
485 // make sure all instances of sdl_mixer are unloaded before continuing
486 while Mix_Init(0) <> 0 do
487 Mix_Quit();
488
489 Mix_CloseAudio();
490 end;
491 end;
492
493 // Get a fallback voice, assuming that snd is not available. Returns sndNone if none is found.
494 function GetFallbackV(snd: TSound): TSound;
495 begin
496 // Fallback to sndFirePunch1 / sndOw1 / sndOoff1 if a "higher-numbered" sound is missing
497 if (snd in [sndFirePunch2, sndFirePunch3, sndFirePunch4, sndFirePunch5, sndFirePunch6]) then
498 GetFallbackV := sndFirePunch1
499 else if (snd in [sndOw2, sndOw3, sndOw4, sndOuch]) then
500 GetFallbackV := sndOw1
501 else if (snd in [sndOoff2, sndOoff3]) then
502 GetFallbackV := sndOoff1
503 // Other fallback sounds
504 else if (snd = sndGrenade) then
505 if random(2) = 0 then
506 GetFallbackV := sndNooo
507 else
508 GetFallbackV := sndUhOh
509 else if (snd in [sndDrat, sndBugger]) then
510 GetFallbackV := sndStupid
511 else if (snd in [sndGonnaGetYou, sndIllGetYou, sndJustYouWait, sndCutItOut, sndLeaveMeAlone]) then
512 GetFallbackV := sndRegret
513 else if (snd in [sndOhDear, sndSoLong]) then
514 GetFallbackV := sndByeBye
515 else if (snd in [sndWhatThe, sndUhOh]) then
516 GetFallbackV := sndNooo
517 else if (snd = sndRunAway) then
518 GetFallbackV := sndOops
519 else if (snd = sndThisOneIsMine) then
520 GetFallbackV := sndReinforce
521 else if (snd in [sndAmazing, sndBrilliant, sndExcellent]) then
522 GetFallbackV := sndEnemyDown
523 else if (snd = sndPoisonCough) then
524 GetFallbackV := sndPoisonMoan
525 else if (snd = sndPoisonMoan) then
526 GetFallbackV := sndPoisonCough
527 else if (snd = sndFlawless) then
528 GetFallbackV := sndVictory
529 else if (snd = sndSameTeam) then
530 GetFallbackV := sndTraitor
531 else if (snd = sndMelon) then
532 GetFallbackV := sndCover
533 // sndHmm is used for enemy turn start, so sndHello is an "okay" replacement
534 else if (snd = sndHmm) then
535 GetFallbackV := sndHello
536 else
537 GetFallbackV := sndNone;
538 end;
539
540 function PlaySound(snd: TSound): boolean;
541 begin
542 PlaySound:= PlaySoundV(snd, nil, false, false, false);
543 end;
544
545 function PlaySound(snd: TSound; keepPlaying: boolean): boolean;
546 begin
547 PlaySound:= PlaySoundV(snd, nil, keepPlaying, false, false);
548 end;
549
550 function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean): boolean;
551 begin
552 PlaySound:= PlaySoundV(snd, nil, keepPlaying, ignoreMask, false);
553 end;
554
555 function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask, soundAsMusic: boolean): boolean;
556 begin
557 PlaySound:= PlaySoundV(snd, nil, keepPlaying, ignoreMask, soundAsMusic);
558 end;
559
560 function PlaySoundV(snd: TSound; voicepack: PVoicepack): boolean;
561 begin
562 PlaySoundV:= PlaySoundV(snd, voicepack, false, false, false);
563 end;
564
565 function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean): boolean;
566 begin
567 PlaySoundV:= PlaySoundV(snd, voicepack, keepPlaying, false, false);
568 end;
569
570 function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask: boolean): boolean;
571 begin
572 PlaySoundV:= PlaySoundV(snd, voicepack, keepPlaying, ignoreMask, false);
573 end;
574
575 function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
576 var s: shortstring;
577 tempSnd, loadSnd: TSound;
578 rwops: PSDL_RWops;
579 begin
580 PlaySoundV:= false;
581 if ((not isSoundEnabled) and (not (soundAsMusic and isMusicEnabled))) or fastUntilLag then
582 exit;
583
584 if keepPlaying and (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then
585 exit;
586
587 if (ignoreMask = false) and (MaskedSounds[snd] = true) then
588 exit;
589
590 if (voicepack <> nil) then
591 begin
592 if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then
593 begin
594 loadSnd:= snd;
595 s:= cPathz[Soundz[loadSnd].Path] + '/' + voicepack^.name + '/' + Soundz[loadSnd].FileName;
596
597 // Fallback taunts
598 if (not pfsExists(s)) then
599 begin
600 tempSnd := GetFallbackV(snd);
601 if tempSnd <> sndNone then
602 begin
603 loadSnd := tempSnd;
604 //LastVoice.snd := tempSnd;
605 end;
606 s:= cPathz[Soundz[loadSnd].Path] + '/' + voicepack^.name + '/' + Soundz[loadSnd].FileName;
607 end;
608 WriteToConsole(msgLoading + s + ' ... ');
609 rwops := rwopsOpenRead(s);
610
611 if rwops = nil then
612 begin
613 s:= cPathz[Soundz[loadSnd].AltPath] + '/' + Soundz[loadSnd].FileName;
614 WriteToConsole(msgLoading + s + ' ... ');
615 rwops := rwopsOpenRead(s);
616 end;
617 voicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 1);
618
619 if voicepack^.chunks[snd] = nil then
620 WriteLnToConsole(msgFailed)
621 else
622 WriteLnToConsole(msgOK)
623 end;
624 lastChan[snd]:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], 0, -1);
625 PlaySoundV:= true;
626 end
627 else
628 begin
629 if (defVoicepack^.chunks[snd] = nil) and (Soundz[snd].Path <> ptVoices) and (Soundz[snd].FileName <> '') then
630 begin
631 s:= cPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
632 WriteToConsole(msgLoading + s + ' ... ');
633 rwops := rwopsOpenRead(s);
634
635 if rwops = nil then
636 begin
637 s:= cPathz[Soundz[snd].AltPath] + '/' + Soundz[snd].FileName;
638 WriteToConsole(msgLoading + s + ' ... ');
639 rwops := rwopsOpenRead(s);
640 end;
641
642 defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 1);
643 if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit;
644 WriteLnToConsole(msgOK);
645 end;
646 lastChan[snd]:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], 0, -1);
647 PlaySoundV:= true;
648 end;
649 end;
650
651 procedure PlayMusicSound(snd: TSound);
652 begin
653 PauseMusic;
654 PlaySound(snd, false, false, true);
655 end;
656
657 procedure StopMusicSound(snd: TSound);
658 begin
659 StopSound(snd, true);
660 ResumeMusic;
661 end;
662
663 procedure AddVoice(snd: TSound; voicepack: PVoicepack);
664 begin
665 AddVoice(snd, voicepack, false, false);
666 end;
667
668 {
669 AddVoice: Add a voice to the voice queue.
670 * snd: Sound ID
671 * voicepack: Hedgehog voicepack
672 * ignoreMask: If true, the sound will be played anyway if masked by Lua
673 * isFallback: If true, this sound is added as fallback if the sound previously added to the
674 queue was not found. Useful to make sure a voice is always played, even if
675 a voicepack is incomplete.
676 Example:
677 AddVoice(sndRevenge, voiceAttacker);
678 AddVoice(sndRegret, voiceVictim, false, true);
679 --> plays sndRegret if sndRevenge could not be played.
680 }
681 procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask, isFallback: boolean);
682 var i : LongInt;
683 begin
684
685 if (not isSoundEnabled) or fastUntilLag or ((LastVoice.snd = snd) and (LastVoice.voicepack = voicepack)) then
686 exit;
687 if (ignoreMask = false) and (MaskedSounds[snd] = true) then
688 exit;
689
690 if (snd = sndVictory) or (snd = sndFlawless) then
691 begin
692 Mix_FadeOutChannel(-1, 800);
693 for i:= 0 to High(VoiceList) do
694 VoiceList[i].snd:= sndNone;
695 LastVoice.snd:= sndNone;
696 end;
697
698 i:= 0;
699 while (i <= High(VoiceList)) and (VoiceList[i].snd <> sndNone) do
700 inc(i);
701
702 // skip playing same sound for same hog twice
703 if (i>0) and (VoiceList[i-1].snd = snd) and (VoiceList[i-1].voicepack = voicepack) then
704 exit;
705 if(i <= High(VoiceList)) then
706 begin
707 VoiceList[i].snd:= snd;
708 VoiceList[i].voicepack:= voicepack;
709 VoiceList[i].isFallback:= isFallback;
710 end
711 end;
712
713 procedure PlayNextVoice;
714 var i : LongInt;
715 played : boolean;
716 begin
717 if (not isSoundEnabled) or fastUntilLag or ((LastVoice.snd <> sndNone) and (lastChan[LastVoice.snd] <> -1) and (Mix_Playing(lastChan[LastVoice.snd]) <> 0)) then
718 exit;
719 i:= 0;
720 while (i<High(VoiceList)) and (VoiceList[i].snd = sndNone) do
721 inc(i);
722
723 played:= false;
724 if (VoiceList[i].snd <> sndNone) and ((not VoiceList[i].isFallback) or LastVoiceFailed) then
725 begin
726 LastVoice.snd:= VoiceList[i].snd;
727 LastVoice.voicepack:= VoiceList[i].voicepack;
728 LastVoice.isFallback:= VoiceList[i].isFallback;
729 VoiceList[i].snd:= sndNone;
730 played:= PlaySoundV(LastVoice.snd, LastVoice.voicepack);
731 // Remember if sound was not played.
732 LastVoiceFailed:= (not played);
733 end
734 else
735 LastVoice.snd:= sndNone;
736 end;
737
738 function LoopSound(snd: TSound): LongInt;
739 begin
740 LoopSound:= LoopSoundV(snd, nil)
741 end;
742
743 function LoopSound(snd: TSound; fadems: LongInt): LongInt;
744 begin
745 LoopSound:= LoopSoundV(snd, nil, fadems)
746 end;
747
748 function LoopSoundV(snd: TSound; voicepack: PVoicepack): LongInt;
749 begin
750 voicepack:= voicepack; // avoid compiler hint
751 LoopSoundV:= LoopSoundV(snd, nil, 0)
752 end;
753
754 function LoopSoundV(snd: TSound; voicepack: PVoicepack; fadems: LongInt): LongInt;
755 var s: shortstring;
756 rwops: PSDL_RWops;
757 begin
758 if (not isSoundEnabled) or fastUntilLag then
759 begin
760 LoopSoundV:= -1;
761 exit
762 end;
763
764 if (voicepack <> nil) then
765 begin
766 if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then
767 begin
768 s:= cPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName;
769 WriteToConsole(msgLoading + s + ' ');
770 rwops:=rwopsOpenRead(s);
771
772 if rwops = nil then
773 begin
774 s:= cPathz[Soundz[snd].AltPath] + '/' + Soundz[snd].FileName;
775 WriteToConsole(msgLoading + s + ' ... ');
776 rwops:=rwopsOpenRead(s);
777 end;
778
779 voicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 1);
780 if voicepack^.chunks[snd] = nil then
781 WriteLnToConsole(msgFailed)
782 else
783 WriteLnToConsole(msgOK)
784 end;
785 LoopSoundV:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], -1, -1)
786 end
787 else
788 begin
789 if (defVoicepack^.chunks[snd] = nil) and (Soundz[snd].Path <> ptVoices) and (Soundz[snd].FileName <> '') then
790 begin
791 s:= cPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName;
792 WriteToConsole(msgLoading + s + ' ');
793 defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwopsOpenRead(s), 1);
794 if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit(-1);
795 WriteLnToConsole(msgOK);
796 end;
797 if fadems > 0 then
798 LoopSoundV:= Mix_FadeInChannelTimed(-1, defVoicepack^.chunks[snd], -1, fadems, -1)
799 else
800 LoopSoundV:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], -1, -1);
801 end;
802 end;
803
804 procedure StopSound(snd: TSound);
805 begin
806 StopSound(snd, false);
807 end;
808
809 procedure StopSound(snd: TSound; soundAsMusic: boolean);
810 begin
811 if ((not isSoundEnabled) and (not (soundAsMusic and isMusicEnabled))) then
812 exit;
813
814 if (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then
815 begin
816 Mix_HaltChannel(lastChan[snd]);
817 lastChan[snd]:= -1;
818 end;
819 end;
820
821 procedure StopSoundChan(chn: LongInt);
822 begin
823 if not isSoundEnabled then
824 exit;
825
826 if (chn <> -1) and (Mix_Playing(chn) <> 0) then
827 Mix_HaltChannel(chn);
828 end;
829
830 procedure StopSoundChan(chn, fadems: LongInt);
831 begin
832 if not isSoundEnabled then
833 exit;
834
835 if (chn <> -1) and (Mix_Playing(chn) <> 0) then
836 if isAudioMuted then
837 Mix_HaltChannel(chn)
838 else
839 Mix_FadeOutChannel(chn, fadems);
840 end;
841
842 procedure PlayMusic;
843 var s: shortstring;
844 begin
845 if (MusicFN = '') or (not isMusicEnabled) then
846 exit;
847 if SuddenDeath and (SDMusicFN <> '') then
848 s:= '/Music/' + SDMusicFN
849 else s:= '/Music/' + MusicFN;
850 WriteToConsole(msgLoading + s + ' ');
851
852 // Load normal music
853 Mus:= Mix_LoadMUS_RW(rwopsOpenRead(s));
854 SDLCheck(Mus <> nil, 'Mix_LoadMUS_RW', false);
855 if Mus <> nil then
856 WriteLnToConsole(msgOK);
857
858 // If normal music failed, try to get fallback music
859 if Mus = nil then
860 begin
861 WriteLnToConsole('Music not found. Trying fallback music.');
862 if SuddenDeath and (FallbackSDMusicFN <> '') then
863 s:= '/Music/' + FallbackSDMusicFN
864 else if (not SuddenDeath) and (FallbackMusicFN <> '') then
865 s:= '/Music/' + FallbackMusicFN
866 else
867 begin
868 WriteLnToConsole('No fallback music configured!');
869 s:= ''
870 end;
871
872 if (s <> '') then
873 begin
874 WriteLnToConsole(msgLoading + s + ' ');
875 Mus:= Mix_LoadMUS_RW(rwopsOpenRead(s));
876 SDLCheck(Mus <> nil, 'Mix_LoadMUS_RW', false);
877 if Mus <> nil then
878 WriteLnToConsole(msgOK)
879 end;
880 end;
881
882 SDLCheck(Mix_FadeInMusic(Mus, -1, 3000) <> -1, 'Mix_FadeInMusic', false)
883 end;
884
885 procedure SetVolume(vol: LongInt);
886 begin
887 cInitVolume:= vol;
888 end;
889
890 function GetVolumePercent(): LongInt;
891 begin
892 GetVolumePercent:= Volume * 100 div MIX_MAX_VOLUME;
893 // 0 and 100 will only be displayed when at min/max values
894 // to avoid confusion.
895 if ((GetVolumePercent = 0) and (Volume > 0)) then
896 GetVolumePercent:= 1
897 else if ((GetVolumePercent = 100) and (Volume < MIX_MAX_VOLUME)) then
898 GetVolumePercent:= 99;
899 end;
900
901 function ChangeVolume(voldelta: LongInt): LongInt;
902 begin
903 ChangeVolume:= 0;
904 if not (isSoundEnabled or isMusicEnabled) or ((voldelta = 0) and (not (cInitVolume = 0))) then
905 exit;
906
907 inc(Volume, voldelta);
908 if Volume < 0 then
909 Volume:= 0;
910 // apply Volume to all channels
911 Mix_Volume(-1, Volume);
912 // get assigned Volume
913 Volume:= Mix_Volume(-1, -1);
914 if isMusicEnabled then
915 Mix_VolumeMusic(Volume * 4 div 8);
916 ChangeVolume:= GetVolumePercent();
917
918 if (isMusicEnabled) then
919 if (Volume = 0) then
920 PauseMusic
921 else
922 ResumeMusic;
923
924 isAudioMuted:= (Volume = 0);
925 end;
926
927 procedure DampenAudio;
928 begin
929 if (isAudioMuted or (not isAutoDampening)) then
930 exit;
931 previousVolume:= Volume;
932 ChangeVolume(-Volume * 7 div 9);
933 end;
934
935 procedure UndampenAudio;
936 begin
937 if (isAudioMuted or (not isAutoDampening)) then
938 exit;
939 ChangeVolume(previousVolume - Volume);
940 end;
941
942 procedure MuteAudio;
943 begin
944 if not (isSoundEnabled or isMusicEnabled) then
945 exit;
946
947 if (isAudioMuted) then
948 begin
949 ResumeMusic;
950 ChangeVolume(previousVolume);
951 end
952 else
953 begin
954 PauseMusic;
955 previousVolume:= Volume;
956 ChangeVolume(-Volume);
957 end;
958
959 // isAudioMuted is updated in ChangeVolume
960 end;
961
962 procedure SetMusic(enabled: boolean);
963 begin
964 isMusicEnabled:= enabled;
965 end;
966
967 procedure SetMusicName(musicname: shortstring);
968 begin
969 MusicFN:= musicname;
970 end;
971
972 procedure PauseMusic;
973 begin
974 if (MusicFN = '') or (not isMusicEnabled) then
975 exit;
976
977 if Mus <> nil then
978 Mix_PauseMusic(Mus);
979 end;
980
981 procedure ResumeMusic;
982 begin
983 if (MusicFN = '') or (not isMusicEnabled) then
984 exit;
985
986 if Mus <> nil then
987 Mix_ResumeMusic(Mus);
988 end;
989
990 procedure ChangeMusic(musicname: shortstring);
991 begin
992 MusicFN:= musicname;
993 if (MusicFN = '') or (not isMusicEnabled) then
994 exit;
995
996 StopMusic;
997 PlayMusic;
998 end;
999
1000 procedure StopMusic;
1001 begin
1002 if (MusicFN = '') or (not isMusicEnabled) then
1003 exit;
1004
1005 if Mus <> nil then
1006 begin
1007 Mix_FreeMusic(Mus);
1008 Mus:= nil;
1009 end
1010 end;
1011
1012 procedure chVoicepack(var s: shortstring);
1013 begin
1014 if CurrentTeam = nil then
1015 OutError(errmsgIncorrectUse + ' "/voicepack"', true);
1016 if s[1]='"' then Delete(s, 1, 1);
1017 if s[byte(s[0])]='"' then
1018 Delete(s, byte(s[0]), 1);
1019 CurrentTeam^.voicepack:= AskForVoicepack(s)
1020 end;
1021
1022 procedure preInitModule;
1023 begin
1024 isMusicEnabled:= true;
1025 isSoundEnabled:= true;
1026 isAutoDampening:= true;
1027 cInitVolume:= 100;
1028 end;
1029
1030 procedure initModule;
1031 var t: LongInt;
1032 i: TSound;
1033 begin
1034 RegisterVariable('voicepack', @chVoicepack, false);
1035
1036 MusicFN:='';
1037 SDMusicFN:= 'sdmusic.ogg';
1038 FallbackMusicFN:='';
1039 FallbackSDMusicFN:= 'sdmusic.ogg';
1040 Mus:= nil;
1041 isAudioMuted:= false;
1042 isSEBackup:= isSoundEnabled;
1043 Volume:= 0;
1044 SoundTimerTicks:= 0;
1045 defVoicepack:= AskForVoicepack('Default_qau');
1046 LastVoiceFailed:= false;
1047
1048 for i:= Low(TSound) to High(TSound) do
1049 lastChan[i]:= -1;
1050
1051 // initialize all voices to nil so that they can be loaded lazily
1052 for t:= 0 to cMaxTeams do
1053 if voicepacks[t].name <> '' then
1054 for i:= Low(TSound) to High(TSound) do
1055 voicepacks[t].chunks[i]:= nil;
1056
1057 (* on MOBILE SDL_mixer has to be compiled against Tremor (USE_OGG_TREMOR)
1058 or sound files bigger than 32k will lockup the game on slow cpu *)
1059 for i:= Low(TSound) to High(TSound) do
1060 defVoicepack^.chunks[i]:= nil;
1061
1062 end;
1063
1064 procedure freeModule;
1065 begin
1066 if isSoundEnabled or isMusicEnabled then
1067 ReleaseSound(true);
1068 end;
1069
1070 end.
1071
1072