(* * Hedgewars, a free turn based strategy game * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA *){$INCLUDE "options.inc"}unit uSound;(* * This unit controls the sounds and music of the game. * Doesn't really do anything if isSoundEnabled = false and isMusicEnabled = false * * There are three basic types of sound controls: * Music - The background music of the game: * * will only be played if isMusicEnabled = true * * can be started, changed, paused and resumed * Sound - Can be started and stopped * Looped Sound - Subtype of sound: plays in a loop using a * "channel", of which the id is returned on start. * The channel id can be used to stop a specific sound loop. *)interfaceuses SDLh, uConsts, uTypes, SysUtils;procedure preInitModule;procedure initModule;procedure freeModule;procedure InitSound; // Initiates sound-system if isSoundEnabled.procedure ReleaseSound(complete: boolean); // Releases sound-system and used resources.procedure ResetSound; // Reset sound state to the previous state.procedure SetSound(enabled: boolean); // Enable/disable sound-system and backup status.// MUSIC// Obvious music commands for music trackprocedure SetMusic(enabled: boolean); // Enable/disable music.procedure SetMusicName(musicname: shortstring); // Set name of the file to play.procedure PlayMusic; // Play music from the start.procedure PauseMusic; // Pause music.procedure ResumeMusic; // Resume music from pause point.procedure ChangeMusic(musicname: shortstring); // Replaces music track with musicname and plays it.procedure StopMusic; // Stops and releases the current track.// SOUNDS// Plays the sound snd [from a given voicepack],// if keepPlaying is given and true,// then the sound's playback won't be interrupted if asked to play again.procedure PlaySound(snd: TSound);procedure PlaySound(snd: TSound; keepPlaying: boolean);procedure PlaySoundV(snd: TSound; voicepack: PVoicepack);procedure PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean);// Plays sound snd [of voicepack] in a loop, but starts with fadems milliseconds of fade-in.// Returns sound channel of the looped sound.function LoopSound(snd: TSound): LongInt;function LoopSound(snd: TSound; fadems: LongInt): LongInt;function LoopSoundV(snd: TSound; voicepack: PVoicepack): LongInt; // WTF?function LoopSoundV(snd: TSound; voicepack: PVoicepack; fadems: LongInt): LongInt;// Stops the normal/looped sound of the given type/in the given channel// [with a fade-out effect for fadems milliseconds].procedure StopSound(snd: TSound);procedure StopSoundChan(chn: LongInt);procedure StopSoundChan(chn, fadems: LongInt);procedure AddVoice(snd: TSound; voicepack: PVoicepack);procedure PlayNextVoice;// GLOBAL FUNCTIONS// Drastically lower the volume when we lose focus (and restore the previous value).procedure DampenAudio;procedure UndampenAudio;// Mute/Unmute audioprocedure MuteAudio;// MISC// Set the initial volumeprocedure SetVolume(vol: LongInt);// Modifies the sound volume of the game by voldelta and returns the new volume level.function ChangeVolume(voldelta: LongInt): LongInt;// Returns a pointer to the voicepack with the given name.function AskForVoicepack(name: shortstring): Pointer;var Volume: LongInt; SoundTimerTicks: Longword;implementationuses uVariables, uConsole, uCommands, uDebug, uPhysFSLayer;const chanTPU = 32;var cInitVolume: LongInt; previousVolume: LongInt; // cached volume value lastChan: array [TSound] of LongInt; voicepacks: array[0..cMaxTeams] of TVoicepack; defVoicepack: PVoicepack; Mus: PMixMusic; // music pointer MusicFN: shortstring; // music file name isMusicEnabled: boolean; isSoundEnabled: boolean; isSEBackup: boolean; VoiceList : array[0..7] of TVoice = ( ( snd: sndNone; voicepack: nil), ( snd: sndNone; voicepack: nil), ( snd: sndNone; voicepack: nil), ( snd: sndNone; voicepack: nil), ( snd: sndNone; voicepack: nil), ( snd: sndNone; voicepack: nil), ( snd: sndNone; voicepack: nil), ( snd: sndNone; voicepack: nil)); Soundz: array[TSound] of record FileName: string[31]; Path : TPathType; end = ( (FileName: ''; Path: ptNone ),// sndNone (FileName: 'grenadeimpact.ogg'; Path: ptSounds),// sndGrenadeImpact (FileName: 'explosion.ogg'; Path: ptSounds),// sndExplosion (FileName: 'throwpowerup.ogg'; Path: ptSounds),// sndThrowPowerUp (FileName: 'throwrelease.ogg'; Path: ptSounds),// sndThrowRelease (FileName: 'splash.ogg'; Path: ptSounds),// sndSplash (FileName: 'shotgunreload.ogg'; Path: ptSounds),// sndShotgunReload (FileName: 'shotgunfire.ogg'; Path: ptSounds),// sndShotgunFire (FileName: 'graveimpact.ogg'; Path: ptSounds),// sndGraveImpact (FileName: 'mineimpact.ogg'; Path: ptSounds),// sndMineImpact (FileName: 'minetick.ogg'; Path: ptSounds),// sndMineTicks (FileName: 'Droplet1.ogg'; Path: ptSounds),// sndMudballImpact (FileName: 'pickhammer.ogg'; Path: ptSounds),// sndPickhammer (FileName: 'gun.ogg'; Path: ptSounds),// sndGun (FileName: 'bee.ogg'; Path: ptSounds),// sndBee (FileName: 'Jump1.ogg'; Path: ptVoices),// sndJump1 (FileName: 'Jump2.ogg'; Path: ptVoices),// sndJump2 (FileName: 'Jump3.ogg'; Path: ptVoices),// sndJump3 (FileName: 'Yessir.ogg'; Path: ptVoices),// sndYesSir (FileName: 'Laugh.ogg'; Path: ptVoices),// sndLaugh (FileName: 'Illgetyou.ogg'; Path: ptVoices),// sndIllGetYou (FileName: 'Justyouwait.ogg'; Path: ptVoices),// sndJustyouwait (FileName: 'Incoming.ogg'; Path: ptVoices),// sndIncoming (FileName: 'Missed.ogg'; Path: ptVoices),// sndMissed (FileName: 'Stupid.ogg'; Path: ptVoices),// sndStupid (FileName: 'Firstblood.ogg'; Path: ptVoices),// sndFirstBlood (FileName: 'Boring.ogg'; Path: ptVoices),// sndBoring (FileName: 'Byebye.ogg'; Path: ptVoices),// sndByeBye (FileName: 'Sameteam.ogg'; Path: ptVoices),// sndSameTeam (FileName: 'Nutter.ogg'; Path: ptVoices),// sndNutter (FileName: 'Reinforcements.ogg'; Path: ptVoices),// sndReinforce (FileName: 'Traitor.ogg'; Path: ptVoices),// sndTraitor (FileName: 'Youllregretthat.ogg'; Path: ptVoices),// sndRegret (FileName: 'Enemydown.ogg'; Path: ptVoices),// sndEnemyDown (FileName: 'Coward.ogg'; Path: ptVoices),// sndCoward (FileName: 'Hurry.ogg'; Path: ptVoices),// sndHurry (FileName: 'Watchit.ogg'; Path: ptVoices),// sndWatchIt (FileName: 'Kamikaze.ogg'; Path: ptVoices),// sndKamikaze (FileName: 'cake2.ogg'; Path: ptSounds),// sndCake (FileName: 'Ow1.ogg'; Path: ptVoices),// sndOw1 (FileName: 'Ow2.ogg'; Path: ptVoices),// sndOw2 (FileName: 'Ow3.ogg'; Path: ptVoices),// sndOw3 (FileName: 'Ow4.ogg'; Path: ptVoices),// sndOw4 (FileName: 'Firepunch1.ogg'; Path: ptVoices),// sndFirepunch1 (FileName: 'Firepunch2.ogg'; Path: ptVoices),// sndFirepunch2 (FileName: 'Firepunch3.ogg'; Path: ptVoices),// sndFirepunch3 (FileName: 'Firepunch4.ogg'; Path: ptVoices),// sndFirepunch4 (FileName: 'Firepunch5.ogg'; Path: ptVoices),// sndFirepunch5 (FileName: 'Firepunch6.ogg'; Path: ptVoices),// sndFirepunch6 (FileName: 'Melon.ogg'; Path: ptVoices),// sndMelon (FileName: 'Hellish.ogg'; Path: ptSounds),// sndHellish (FileName: 'Yoohoo.ogg'; Path: ptSounds),// sndYoohoo (FileName: 'rcplane.ogg'; Path: ptSounds),// sndRCPlane (FileName: 'whipcrack.ogg'; Path: ptSounds),// sndWhipCrack (FileName:'ride_of_the_valkyries.ogg'; Path: ptSounds),// sndRideOfTheValkyries (FileName: 'denied.ogg'; Path: ptSounds),// sndDenied (FileName: 'placed.ogg'; Path: ptSounds),// sndPlaced (FileName: 'baseballbat.ogg'; Path: ptSounds),// sndBaseballBat (FileName: 'steam.ogg'; Path: ptSounds),// sndVaporize (FileName: 'warp.ogg'; Path: ptSounds),// sndWarp (FileName: 'suddendeath.ogg'; Path: ptSounds),// sndSuddenDeath (FileName: 'mortar.ogg'; Path: ptSounds),// sndMortar (FileName: 'shutterclick.ogg'; Path: ptSounds),// sndShutter (FileName: 'homerun.ogg'; Path: ptSounds),// sndHomerun (FileName: 'molotov.ogg'; Path: ptSounds),// sndMolotov (FileName: 'Takecover.ogg'; Path: ptVoices),// sndCover (FileName: 'Uh-oh.ogg'; Path: ptVoices),// sndUhOh (FileName: 'Oops.ogg'; Path: ptVoices),// sndOops (FileName: 'Nooo.ogg'; Path: ptVoices),// sndNooo (FileName: 'Hello.ogg'; Path: ptVoices),// sndHello (FileName: 'ropeshot.ogg'; Path: ptSounds),// sndRopeShot (FileName: 'ropeattach.ogg'; Path: ptSounds),// sndRopeAttach (FileName: 'roperelease.ogg'; Path: ptSounds),// sndRopeRelease (FileName: 'switchhog.ogg'; Path: ptSounds),// sndSwitchHog (FileName: 'Victory.ogg'; Path: ptVoices),// sndVictory (FileName: 'Flawless.ogg'; Path: ptVoices),// sndFlawless (FileName: 'sniperreload.ogg'; Path: ptSounds),// sndSniperReload (FileName: 'steps.ogg'; Path: ptSounds),// sndSteps (FileName: 'lowgravity.ogg'; Path: ptSounds),// sndLowGravity (FileName: 'hell_growl.ogg'; Path: ptSounds),// sndHellishImpact1 (FileName: 'hell_ooff.ogg'; Path: ptSounds),// sndHellishImpact2 (FileName: 'hell_ow.ogg'; Path: ptSounds),// sndHellishImpact3 (FileName: 'hell_ugh.ogg'; Path: ptSounds),// sndHellishImpact4 (FileName: 'melonimpact.ogg'; Path: ptSounds),// sndMelonImpact (FileName: 'Droplet1.ogg'; Path: ptSounds),// sndDroplet1 (FileName: 'Droplet2.ogg'; Path: ptSounds),// sndDroplet2 (FileName: 'Droplet3.ogg'; Path: ptSounds),// sndDroplet3 (FileName: 'egg.ogg'; Path: ptSounds),// sndEggBreak (FileName: 'drillgun.ogg'; Path: ptSounds),// sndDrillRocket (FileName: 'PoisonCough.ogg'; Path: ptVoices),// sndPoisonCough (FileName: 'PoisonMoan.ogg'; Path: ptVoices),// sndPoisonMoan (FileName: 'BirdyLay.ogg'; Path: ptSounds),// sndBirdyLay (FileName: 'Whistle.ogg'; Path: ptSounds),// sndWhistle (FileName: 'beewater.ogg'; Path: ptSounds),// sndBeeWater (FileName: '1C.ogg'; Path: ptSounds),// sndPiano0 (FileName: '2D.ogg'; Path: ptSounds),// sndPiano1 (FileName: '3E.ogg'; Path: ptSounds),// sndPiano2 (FileName: '4F.ogg'; Path: ptSounds),// sndPiano3 (FileName: '5G.ogg'; Path: ptSounds),// sndPiano4 (FileName: '6A.ogg'; Path: ptSounds),// sndPiano5 (FileName: '7B.ogg'; Path: ptSounds),// sndPiano6 (FileName: '8C.ogg'; Path: ptSounds),// sndPiano7 (FileName: '9D.ogg'; Path: ptSounds),// sndPiano8 (FileName: 'skip.ogg'; Path: ptSounds),// sndSkip (FileName: 'sinegun.ogg'; Path: ptSounds),// sndSineGun (FileName: 'Ooff1.ogg'; Path: ptVoices),// sndOoff1 (FileName: 'Ooff2.ogg'; Path: ptVoices),// sndOoff2 (FileName: 'Ooff3.ogg'; Path: ptVoices),// sndOoff3 (FileName: 'hammer.ogg'; Path: ptSounds),// sndWhack (FileName: 'Comeonthen.ogg'; Path: ptVoices),// sndComeonthen (FileName: 'parachute.ogg'; Path: ptSounds),// sndParachute (FileName: 'bump.ogg'; Path: ptSounds),// sndBump (FileName: 'hogchant3.ogg'; Path: ptSounds),// sndResurrector (FileName: 'plane.ogg'; Path: ptSounds),// sndPlane (FileName: 'TARDIS.ogg'; Path: ptSounds) // sndTardis );function AskForVoicepack(name: shortstring): Pointer;var i: Longword; locName, path: shortstring;begini:= 0; // First, attempt to locate a localised version of the voice if cLocale <> 'en' then begin locName:= name+'_'+cLocale; path:= cPathz[ptVoices] + '/' + locName; if pfsExists(path) then name:= locName else if Length(cLocale) > 3 then begin locName:= name+'_'+Copy(cLocale,1,2); path:= cPathz[ptVoices] + '/' + locName; if pfsExists(path) then name:= locName end end; // If that fails, use the unmodified one while (voicepacks[i].name <> name) and (voicepacks[i].name <> '') do begin inc(i); TryDo(i <= cMaxTeams, 'Engine bug: AskForVoicepack i > cMaxTeams', true) end; voicepacks[i].name:= name; AskForVoicepack:= @voicepacks[i]end;procedure InitSound;const channels: LongInt = {$IFDEF MOBILE}1{$ELSE}2{$ENDIF};var success: boolean;begin if not (isSoundEnabled or isMusicEnabled) then exit; WriteToConsole('Init sound...'); success:= SDL_InitSubSystem(SDL_INIT_AUDIO) >= 0; if success then success:= Mix_OpenAudio(44100, $8010, channels, 1024) = 0; if success then WriteLnToConsole(msgOK) else begin WriteLnToConsole(msgFailed); isSoundEnabled:= false; isMusicEnabled:= false; end; WriteToConsole('Init SDL_mixer... '); SDLTry(Mix_Init(MIX_INIT_OGG) <> 0, true); WriteLnToConsole(msgOK); Mix_AllocateChannels(Succ(chanTPU)); ChangeVolume(cInitVolume); end;procedure ResetSound;begin isSoundEnabled:= isSEBackup;end;procedure SetSound(enabled: boolean);begin isSEBackup:= isSoundEnabled; isSoundEnabled:= enabled;end;// when complete is false, this procedure just releases some of the chucks on inactive channels// in this way music is not stopped, nor are chucks currently being playedprocedure ReleaseSound(complete: boolean);var i: TSound; t: Longword;begin // release and nil all sounds for t:= 0 to cMaxTeams do if voicepacks[t].name <> '' then for i:= Low(TSound) to High(TSound) do if voicepacks[t].chunks[i] <> nil then if complete or (Mix_Playing(lastChan[i]) = 0) then begin Mix_HaltChannel(lastChan[i]); lastChan[i]:= -1; Mix_FreeChunk(voicepacks[t].chunks[i]); voicepacks[t].chunks[i]:= nil; end; // stop music if complete then begin if Mus <> nil then begin Mix_HaltMusic(); Mix_FreeMusic(Mus); Mus:= nil; end; // make sure all instances of sdl_mixer are unloaded before continuing while Mix_Init(0) <> 0 do Mix_Quit(); Mix_CloseAudio(); end;end;procedure PlaySound(snd: TSound);begin PlaySoundV(snd, nil, false);end;procedure PlaySound(snd: TSound; keepPlaying: boolean);begin PlaySoundV(snd, nil, keepPlaying);end;procedure PlaySoundV(snd: TSound; voicepack: PVoicepack);begin PlaySoundV(snd, voicepack, false);end;procedure PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean);var s:shortstring;begin if (not isSoundEnabled) or fastUntilLag then exit; if keepPlaying and (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then exit; if (voicepack <> nil) then begin if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then begin s:= cPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName; if (not pfsExists(s)) and (snd in [sndFirePunch2, sndFirePunch3, sndFirePunch4, sndFirePunch5, sndFirePunch6]) then s:= cPathz[Soundz[sndFirePunch1].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName; WriteToConsole(msgLoading + s + ' '); voicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwopsOpenRead(s), 1); if voicepack^.chunks[snd] = nil then WriteLnToConsole(msgFailed) else WriteLnToConsole(msgOK) end; lastChan[snd]:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], 0, -1) end else begin if (defVoicepack^.chunks[snd] = nil) and (Soundz[snd].Path <> ptVoices) and (Soundz[snd].FileName <> '') then begin s:= cPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName; WriteToConsole(msgLoading + s + ' '); defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwopsOpenRead(s), 1); SDLTry(defVoicepack^.chunks[snd] <> nil, true); WriteLnToConsole(msgOK); end; lastChan[snd]:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], 0, -1) end;end;procedure AddVoice(snd: TSound; voicepack: PVoicepack);var i : LongInt;begin if (not isSoundEnabled) or fastUntilLag or ((LastVoice.snd = snd) and (LastVoice.voicepack = voicepack)) then exit; if (snd = sndVictory) or (snd = sndFlawless) then begin Mix_FadeOutChannel(-1, 800); for i:= 0 to High(VoiceList) do VoiceList[i].snd:= sndNone; LastVoice.snd:= sndNone; end; i:= 0; while (i<8) and (VoiceList[i].snd <> sndNone) do inc(i); // skip playing same sound for same hog twice if (i>0) and (VoiceList[i-1].snd = snd) and (VoiceList[i-1].voicepack = voicepack) then exit; VoiceList[i].snd:= snd; VoiceList[i].voicepack:= voicepack;end;procedure PlayNextVoice;var i : LongInt;begin if (not isSoundEnabled) or fastUntilLag or ((LastVoice.snd <> sndNone) and (lastChan[LastVoice.snd] <> -1) and (Mix_Playing(lastChan[LastVoice.snd]) <> 0)) then exit; i:= 0; while (i<High(VoiceList)) and (VoiceList[i].snd = sndNone) do inc(i); if (VoiceList[i].snd <> sndNone) then begin LastVoice.snd:= VoiceList[i].snd; LastVoice.voicepack:= VoiceList[i].voicepack; VoiceList[i].snd:= sndNone; PlaySoundV(LastVoice.snd, LastVoice.voicepack) end else LastVoice.snd:= sndNone;end;function LoopSound(snd: TSound): LongInt;begin LoopSound:= LoopSoundV(snd, nil)end;function LoopSound(snd: TSound; fadems: LongInt): LongInt;begin LoopSound:= LoopSoundV(snd, nil, fadems)end;function LoopSoundV(snd: TSound; voicepack: PVoicepack): LongInt;begin voicepack:= voicepack; // avoid compiler hint LoopSoundV:= LoopSoundV(snd, nil, 0)end;function LoopSoundV(snd: TSound; voicepack: PVoicepack; fadems: LongInt): LongInt;var s: shortstring;begin if (not isSoundEnabled) or fastUntilLag then begin LoopSoundV:= -1; exit end; if (voicepack <> nil) then begin if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then begin s:= cPathz[Soundz[snd].Path] + '/' + voicepack^.name + '/' + Soundz[snd].FileName; WriteToConsole(msgLoading + s + ' '); voicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwopsOpenRead(s), 1); if voicepack^.chunks[snd] = nil then WriteLnToConsole(msgFailed) else WriteLnToConsole(msgOK) end; LoopSoundV:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], -1, -1) end else begin if (defVoicepack^.chunks[snd] = nil) and (Soundz[snd].Path <> ptVoices) and (Soundz[snd].FileName <> '') then begin s:= cPathz[Soundz[snd].Path] + '/' + Soundz[snd].FileName; WriteToConsole(msgLoading + s + ' '); defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwopsOpenRead(s), 1); SDLTry(defVoicepack^.chunks[snd] <> nil, true); WriteLnToConsole(msgOK); end; if fadems > 0 then LoopSoundV:= Mix_FadeInChannelTimed(-1, defVoicepack^.chunks[snd], -1, fadems, -1) else LoopSoundV:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], -1, -1); end;end;procedure StopSound(snd: TSound);begin if not isSoundEnabled then exit; if (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then begin Mix_HaltChannel(lastChan[snd]); lastChan[snd]:= -1; end;end;procedure StopSoundChan(chn: LongInt);begin if not isSoundEnabled then exit; if (chn <> -1) and (Mix_Playing(chn) <> 0) then Mix_HaltChannel(chn);end;procedure StopSoundChan(chn, fadems: LongInt);begin if not isSoundEnabled then exit; if (chn <> -1) and (Mix_Playing(chn) <> 0) then Mix_FadeOutChannel(chn, fadems);end;procedure PlayMusic;var s: shortstring;begin if (MusicFN = '') or (not isMusicEnabled) then exit; s:= '/Music/' + MusicFN; WriteToConsole(msgLoading + s + ' '); Mus:= Mix_LoadMUS_RW(rwopsOpenRead(s)); SDLTry(Mus <> nil, false); WriteLnToConsole(msgOK); SDLTry(Mix_FadeInMusic(Mus, -1, 3000) <> -1, false)end;procedure SetVolume(vol: LongInt);begin cInitVolume:= vol;end;function ChangeVolume(voldelta: LongInt): LongInt;begin ChangeVolume:= 0; if not (isSoundEnabled or isMusicEnabled) or ((voldelta = 0) and (not (cInitVolume = 0))) then exit; inc(Volume, voldelta); if Volume < 0 then Volume:= 0; // apply Volume to all channels Mix_Volume(-1, Volume); // get assigned Volume Volume:= Mix_Volume(-1, -1); if isMusicEnabled then Mix_VolumeMusic(Volume * 4 div 8); ChangeVolume:= Volume * 100 div MIX_MAX_VOLUME; if (isMusicEnabled) then if (Volume = 0) then PauseMusic else ResumeMusic; isAudioMuted:= (Volume = 0);end;procedure DampenAudio;begin if (isAudioMuted) then exit; previousVolume:= Volume; ChangeVolume(-Volume * 7 div 9);end;procedure UndampenAudio;begin if (isAudioMuted) then exit; ChangeVolume(previousVolume - Volume);end;procedure MuteAudio;begin if not (isSoundEnabled or isMusicEnabled) then exit; if (isAudioMuted) then begin ResumeMusic; ChangeVolume(previousVolume); end else begin PauseMusic; previousVolume:= Volume; ChangeVolume(-Volume); end; // isAudioMuted is updated in ChangeVolumeend;procedure SetMusic(enabled: boolean);begin isMusicEnabled:= enabled;end;procedure SetMusicName(musicname: shortstring);begin MusicFN:= musicname;end;procedure PauseMusic;begin if (MusicFN = '') or (not isMusicEnabled) then exit; if Mus <> nil then Mix_PauseMusic(Mus);end;procedure ResumeMusic;begin if (MusicFN = '') or (not isMusicEnabled) then exit; if Mus <> nil then Mix_ResumeMusic(Mus);end;procedure ChangeMusic(musicname: shortstring);begin MusicFN:= musicname; if (MusicFN = '') or (not isMusicEnabled) then exit; StopMusic; PlayMusic;end;procedure StopMusic;begin if (MusicFN = '') or (not isMusicEnabled) then exit; if Mus <> nil then begin Mix_FreeMusic(Mus); Mus:= nil; endend;procedure chVoicepack(var s: shortstring);begin if CurrentTeam = nil then OutError(errmsgIncorrectUse + ' "/voicepack"', true); if s[1]='"' then Delete(s, 1, 1); if s[byte(s[0])]='"' then Delete(s, byte(s[0]), 1); CurrentTeam^.voicepack:= AskForVoicepack(s)end;procedure chMute(var s: shortstring);begin s:= s; // avoid compiler hint MuteAudio;end;procedure preInitModule;begin isMusicEnabled:= true; isSoundEnabled:= true; cInitVolume:= 100;end;procedure initModule;var t: LongInt; i: TSound;begin RegisterVariable('voicepack', @chVoicepack, false); RegisterVariable('mute' , @chMute , true ); MusicFN:=''; Mus:= nil; isAudioMuted:= false; isSEBackup:= isSoundEnabled; Volume:= 0; SoundTimerTicks:= 0; defVoicepack:= AskForVoicepack('Default'); for i:= Low(TSound) to High(TSound) do lastChan[i]:= -1; // initialize all voices to nil so that they can be loaded lazily for t:= 0 to cMaxTeams do if voicepacks[t].name <> '' then for i:= Low(TSound) to High(TSound) do voicepacks[t].chunks[i]:= nil; (* on MOBILE SDL_mixer has to be compiled against Tremor (USE_OGG_TREMOR) or sound files bigger than 32k will lockup the game on slow cpu *) for i:= Low(TSound) to High(TSound) do defVoicepack^.chunks[i]:= nil;end;procedure freeModule;begin if isSoundEnabled or isMusicEnabled then ReleaseSound(true);end;end.