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