hedgewars/uSound.pas
author Wuzzy <Wuzzy2@mail.ru>
Sat, 02 Nov 2019 13:01:28 +0100
changeset 15506 5a30396f8fb2
parent 15478 20066da10268
permissions -rw-r--r--
ClimbHome: Change misleading Seed assignment to nil value This was "Seed = ClimbHome", but ClimbHome was a nil value. This code still worked as the engine interpreted the nil value as empty string. But it can be very misleading. This changeset makes the Seed assignment more explicit by assigning the empty string directly. The compability has been tested.

(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2015 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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.
 *)
interface
uses SDLh, uConsts, uTypes;

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.
procedure SetAudioDampen(enabled: boolean);     // Enable/disable automatic dampening if losing window focus.

// MUSIC

// Obvious music commands for music track
procedure 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.
// Returns true if sound was found and is played, false otherwise.
function PlaySound(snd: TSound): boolean;
function PlaySound(snd: TSound; keepPlaying: boolean): boolean;
function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean): boolean;
function PlaySound(snd: TSound; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
function PlaySoundV(snd: TSound; voicepack: PVoicepack): boolean;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean): boolean;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask: boolean): boolean;
function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;

// Plays/stops a sound to replace the main background music temporarily.
procedure PlayMusicSound(snd: TSound);
procedure StopMusicSound(snd: TSound);

// 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;
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 StopSound(snd: TSound; soundAsMusic: boolean);
procedure StopSoundChan(chn: LongInt);
procedure StopSoundChan(chn, fadems: LongInt);

// Add voice to the voice queue
procedure AddVoice(snd: TSound; voicepack: PVoicepack);
procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask, isFallback: boolean);

// Actually play next voice
procedure PlayNextVoice;


// GLOBAL FUNCTIONS

// Drastically lower the volume when we lose focus (and restore the previous value).
procedure DampenAudio;
procedure UndampenAudio;

// Mute/Unmute audio
procedure MuteAudio;


// MISC

// Set the initial volume
procedure SetVolume(vol: LongInt);

// Modifies the sound volume of the game by voldelta and returns the new volume level.
function  ChangeVolume(voldelta: LongInt): LongInt;

// Returns the current volume in percent. Intended for display on UI.
function  GetVolumePercent(): LongInt;

// Returns a pointer to the voicepack with the given name.
function  AskForVoicepack(name: shortstring): Pointer;

var MusicFN: shortstring; // music file name
    SDMusicFN: shortstring; // SD music file name
    FallbackMusicFN: shortstring; // fallback music file name
    FallbackSDMusicFN: shortstring; // fallback SD music fille name

var Volume: LongInt;
    SoundTimerTicks: Longword;
    LastVoiceFailed: boolean;
implementation
uses 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
    isMusicEnabled: boolean;
    isSoundEnabled: boolean;
    isAutoDampening: boolean;
    isSEBackup: boolean;
    VoiceList : array[0..7] of TVoice =  (
                    ( snd: sndNone; voicepack: nil; isFallback: false),
                    ( snd: sndNone; voicepack: nil; isFallback: false),
                    ( snd: sndNone; voicepack: nil; isFallback: false),
                    ( snd: sndNone; voicepack: nil; isFallback: false),
                    ( snd: sndNone; voicepack: nil; isFallback: false),
                    ( snd: sndNone; voicepack: nil; isFallback: false),
                    ( snd: sndNone; voicepack: nil; isFallback: false),
                    ( snd: sndNone; voicepack: nil; isFallback: false));
    Soundz: array[TSound] of record
            FileName: string[31];
            Path, AltPath    : TPathType;
            end = (
            (FileName:                         ''; Path: ptNone; AltPath: ptNone),// sndNone
            (FileName:        'grenadeimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndGrenadeImpact
            (FileName:            'explosion.ogg'; Path: ptSounds; AltPath: ptNone),// sndExplosion
            (FileName:         'throwpowerup.ogg'; Path: ptSounds; AltPath: ptNone),// sndThrowPowerUp
            (FileName:         'throwrelease.ogg'; Path: ptSounds; AltPath: ptNone),// sndThrowRelease
            (FileName:               'splash.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndSplash
            (FileName:        'shotgunreload.ogg'; Path: ptSounds; AltPath: ptNone),// sndShotgunReload
            (FileName:          'shotgunfire.ogg'; Path: ptSounds; AltPath: ptNone),// sndShotgunFire
            (FileName:          'graveimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndGraveImpact
            (FileName:           'mineimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndMineImpact
            (FileName:             'minetick.ogg'; Path: ptSounds; AltPath: ptNone),// sndMineTicks
            // TODO: New mudball sound?
            (FileName:             'Droplet1.ogg'; Path: ptSounds; AltPath: ptNone),// sndMudballImpact
            (FileName:           'pickhammer.ogg'; Path: ptSounds; AltPath: ptNone),// sndPickhammer
            (FileName:                  'gun.ogg'; Path: ptSounds; AltPath: ptNone),// sndGun
            (FileName:                  'bee.ogg'; Path: ptSounds; AltPath: ptNone),// sndBee
            (FileName:                'Jump1.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump1
            (FileName:                'Jump2.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump2
            (FileName:                'Jump3.ogg'; Path: ptVoices; AltPath: ptNone),// sndJump3
            (FileName:               'Yessir.ogg'; Path: ptVoices; AltPath: ptNone),// sndYesSir
            (FileName:                'Laugh.ogg'; Path: ptVoices; AltPath: ptNone),// sndLaugh
            (FileName:            'Illgetyou.ogg'; Path: ptVoices; AltPath: ptNone),// sndIllGetYou
            (FileName:          'Justyouwait.ogg'; Path: ptVoices; AltPath: ptNone),// sndJustyouwait
            (FileName:             'Incoming.ogg'; Path: ptVoices; AltPath: ptNone),// sndIncoming
            (FileName:               'Missed.ogg'; Path: ptVoices; AltPath: ptNone),// sndMissed
            (FileName:               'Stupid.ogg'; Path: ptVoices; AltPath: ptNone),// sndStupid
            (FileName:           'Firstblood.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirstBlood
            (FileName:               'Boring.ogg'; Path: ptVoices; AltPath: ptNone),// sndBoring
            (FileName:               'Byebye.ogg'; Path: ptVoices; AltPath: ptNone),// sndByeBye
            (FileName:             'Sameteam.ogg'; Path: ptVoices; AltPath: ptNone),// sndSameTeam
            (FileName:               'Nutter.ogg'; Path: ptVoices; AltPath: ptNone),// sndNutter
            (FileName:       'Reinforcements.ogg'; Path: ptVoices; AltPath: ptNone),// sndReinforce
            (FileName:              'Traitor.ogg'; Path: ptVoices; AltPath: ptNone),// sndTraitor
            (FileName:      'Youllregretthat.ogg'; Path: ptVoices; AltPath: ptNone),// sndRegret
            (FileName:            'Enemydown.ogg'; Path: ptVoices; AltPath: ptNone),// sndEnemyDown
            (FileName:               'Coward.ogg'; Path: ptVoices; AltPath: ptNone),// sndCoward
            (FileName:                'Hurry.ogg'; Path: ptVoices; AltPath: ptNone),// sndHurry
            (FileName:              'Watchit.ogg'; Path: ptVoices; AltPath: ptNone),// sndWatchIt
            (FileName:             'Kamikaze.ogg'; Path: ptVoices; AltPath: ptNone),// sndKamikaze
            (FileName:                'cake2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCake
            (FileName:                  'Ow1.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw1
            (FileName:                  'Ow2.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw2
            (FileName:                  'Ow3.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw3
            (FileName:                  'Ow4.ogg'; Path: ptVoices; AltPath: ptNone),// sndOw4
            (FileName:           'Firepunch1.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch1
            (FileName:           'Firepunch2.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch2
            (FileName:           'Firepunch3.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch3
            (FileName:           'Firepunch4.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch4
            (FileName:           'Firepunch5.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch5
            (FileName:           'Firepunch6.ogg'; Path: ptVoices; AltPath: ptNone),// sndFirePunch6
            (FileName:                'Melon.ogg'; Path: ptVoices; AltPath: ptNone),// sndMelon
            (FileName:              'Hellish.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellish
            (FileName:               'Yoohoo.ogg'; Path: ptSounds; AltPath: ptNone),// sndYoohoo
            (FileName:              'rcplane.ogg'; Path: ptSounds; AltPath: ptNone),// sndRCPlane
            (FileName:            'whipcrack.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhipCrack
            (FileName:'ride_of_the_valkyries.ogg'; Path: ptSounds; AltPath: ptNone),// sndRideOfTheValkyries
            (FileName:               'denied.ogg'; Path: ptSounds; AltPath: ptNone),// sndDenied
            (FileName:               'placed.ogg'; Path: ptSounds; AltPath: ptNone),// sndPlaced
            (FileName:          'baseballbat.ogg'; Path: ptSounds; AltPath: ptNone),// sndBaseballBat
            (FileName:                'steam.ogg'; Path: ptSounds; AltPath: ptNone),// sndVaporize
            (FileName:                 'warp.ogg'; Path: ptSounds; AltPath: ptNone),// sndWarp
            (FileName:          'suddendeath.ogg'; Path: ptSounds; AltPath: ptNone),// sndSuddenDeath
            (FileName:               'mortar.ogg'; Path: ptSounds; AltPath: ptNone),// sndMortar
            (FileName:         'shutterclick.ogg'; Path: ptSounds; AltPath: ptNone),// sndShutter
            (FileName:              'homerun.ogg'; Path: ptSounds; AltPath: ptNone),// sndHomerun
            (FileName:              'molotov.ogg'; Path: ptSounds; AltPath: ptNone),// sndMolotov
            (FileName:            'Takecover.ogg'; Path: ptVoices; AltPath: ptNone),// sndCover
            (FileName:                'Uh-oh.ogg'; Path: ptVoices; AltPath: ptNone),// sndUhOh
            (FileName:                 'Oops.ogg'; Path: ptVoices; AltPath: ptNone),// sndOops
            (FileName:                 'Nooo.ogg'; Path: ptVoices; AltPath: ptNone),// sndNooo
            (FileName:                'Hello.ogg'; Path: ptVoices; AltPath: ptNone),// sndHello
            (FileName:             'ropeshot.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeShot
            (FileName:           'ropeattach.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeAttach
            (FileName:          'roperelease.ogg'; Path: ptSounds; AltPath: ptNone),// sndRopeRelease
            (FileName:            'switchhog.ogg'; Path: ptSounds; AltPath: ptNone),// sndSwitchHog
            (FileName:              'Victory.ogg'; Path: ptVoices; AltPath: ptNone),// sndVictory
            (FileName:             'Flawless.ogg'; Path: ptVoices; AltPath: ptNone),// sndFlawless
            (FileName:         'sniperreload.ogg'; Path: ptSounds; AltPath: ptNone),// sndSniperReload
            (FileName:                'steps.ogg'; Path: ptSounds; AltPath: ptNone),// sndSteps
            (FileName:           'lowgravity.ogg'; Path: ptSounds; AltPath: ptNone),// sndLowGravity
            (FileName:           'hell_growl.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact1
            (FileName:            'hell_ooff.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact2
            (FileName:              'hell_ow.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact3
            (FileName:             'hell_ugh.ogg'; Path: ptSounds; AltPath: ptNone),// sndHellishImpact4
            (FileName:          'melonimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndMelonImpact
            (FileName:             'Droplet1.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet1
            (FileName:             'Droplet2.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet2
            (FileName:             'Droplet3.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndDroplet3
            (FileName:                  'egg.ogg'; Path: ptSounds; AltPath: ptNone),// sndEggBreak
            (FileName:             'drillgun.ogg'; Path: ptSounds; AltPath: ptNone),// sndDrillRocket
            (FileName:          'PoisonCough.ogg'; Path: ptVoices; AltPath: ptDefaultVoice),// sndPoisonCough
            (FileName:           'PoisonMoan.ogg'; Path: ptVoices; AltPath: ptDefaultVoice),// sndPoisonMoan
            (FileName:             'BirdyLay.ogg'; Path: ptSounds; AltPath: ptNone),// sndBirdyLay
            (FileName:              'Whistle.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhistle
            (FileName:             'beewater.ogg'; Path: ptSounds; AltPath: ptNone),// sndBeeWater
            (FileName:                   '1C.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano0
            (FileName:                   '2D.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano1
            (FileName:                   '3E.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano2
            (FileName:                   '4F.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano3
            (FileName:                   '5G.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano4
            (FileName:                   '6A.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano5
            (FileName:                   '7B.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano6
            (FileName:                   '8C.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano7
            (FileName:                   '9D.ogg'; Path: ptSounds; AltPath: ptNone),// sndPiano8
            (FileName:                 'skip.ogg'; Path: ptCurrTheme; AltPath: ptSounds),// sndSkip
            (FileName:              'sinegun.ogg'; Path: ptSounds; AltPath: ptNone),// sndSineGun
            (FileName:                'Ooff1.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff1
            (FileName:                'Ooff2.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff2
            (FileName:                'Ooff3.ogg'; Path: ptVoices; AltPath: ptNone),// sndOoff3
            (FileName:               'hammer.ogg'; Path: ptSounds; AltPath: ptNone),// sndWhack
            (FileName:           'Comeonthen.ogg'; Path: ptVoices; AltPath: ptNone),// sndComeonthen
            (FileName:            'parachute.ogg'; Path: ptSounds; AltPath: ptNone),// sndParachute
            (FileName:                 'bump.ogg'; Path: ptSounds; AltPath: ptNone),// sndBump
            (FileName:            'hogchant3.ogg'; Path: ptSounds; AltPath: ptNone),// sndResurrector
            (FileName:                'plane.ogg'; Path: ptSounds; AltPath: ptNone),// sndPlane
            (FileName:               'TARDIS.ogg'; Path: ptSounds; AltPath: ptNone),// sndTardis
            (FileName:    'frozen_hog_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndFrozenHogImpact
            (FileName:             'ice_beam.ogg'; Path: ptSounds; AltPath: ptNone),// sndIceBeam
            (FileName:           'hog_freeze.ogg'; Path: ptSounds; AltPath: ptNone), // sndHogFreeze
            (FileName:       'airmine_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndAirMineImpact
            (FileName:         'knife_impact.ogg'; Path: ptSounds; AltPath: ptNone),// sndKnifeImpact
            (FileName:            'extratime.ogg'; Path: ptSounds; AltPath: ptNone),// sndExtraTime
            (FileName:           'lasersight.ogg'; Path: ptSounds; AltPath: ptNone),// sndLaserSight
            (FileName:         'invulnerable.ogg'; Path: ptSounds; AltPath: ptNone),// sndInvulnerable
            (FileName:                  'ufo.ogg'; Path: ptSounds; AltPath: ptNone),// sndJetpackLaunch
            (FileName:         'jetpackboost.ogg'; Path: ptSounds; AltPath: ptNone),// sndJetpackBoost
            (FileName:           'portalshot.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalShot
            (FileName:         'portalswitch.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalSwitch
            (FileName:           'portalopen.ogg'; Path: ptSounds; AltPath: ptNone),// sndPortalOpen
            (FileName:            'blowtorch.ogg'; Path: ptSounds; AltPath: ptNone),// sndBlowTorch
            (FileName:           'countdown1.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown1
            (FileName:           'countdown2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown2
            (FileName:           'countdown3.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown3
            (FileName:           'countdown4.ogg'; Path: ptSounds; AltPath: ptNone),// sndCountdown4
            // TODO: Check which creeper (formerly rubberduck) sounds are needed, maybe rename them
            (FileName:      'rubberduck_drop.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperDrop
            (FileName:     'rubberduck_water.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperWater
            (FileName:       'rubberduck_die.ogg'; Path: ptSounds; AltPath: ptNone),// sndCreeperDie
            (FileName:              'custom1.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom1
            (FileName:              'custom2.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom2
            (FileName:              'custom3.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom3
            (FileName:              'custom4.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom4
            (FileName:              'custom5.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom5
            (FileName:              'custom6.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom6
            (FileName:              'custom7.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom7
            (FileName:              'custom8.ogg'; Path: ptSounds; AltPath: ptNone),// sndCustom8
            (FileName:              'minigun.ogg'; Path: ptSounds; AltPath: ptNone),// sndMinigun
            (FileName:         'flamethrower.ogg'; Path: ptSounds; AltPath: ptNone),// sndFlamethrower
            (FileName:        'ice_beam_idle.ogg'; Path: ptSounds; AltPath: ptNone),// sndIceBeamIdle
            (FileName:              'landgun.ogg'; Path: ptSounds; AltPath: ptNone),// sndLandGun
            (FileName:          'graveimpact.ogg'; Path: ptSounds; AltPath: ptNone),// sndCaseImpact
            // TODO: New Extra Damage sound
            (FileName:             'hell_ugh.ogg'; Path: ptSounds; AltPath: ptNone),// sndExtraDamage
            (FileName:        'firepunch_hit.ogg'; Path: ptSounds; AltPath: ptNone),// sndFirePunchHit
            (FileName:              'Grenade.ogg'; Path: ptVoices; AltPath: ptNone),// sndGrenade
            (FileName:        'Thisoneismine.ogg'; Path: ptVoices; AltPath: ptNone),// sndThisOneIsMine
            (FileName:              'Whatthe.ogg'; Path: ptVoices; AltPath: ptNone),// sndWhatThe
            (FileName:               'Solong.ogg'; Path: ptVoices; AltPath: ptNone),// sndSoLong
            (FileName:               'Ohdear.ogg'; Path: ptVoices; AltPath: ptNone),// sndOhDear
            (FileName:          'Gonnagetyou.ogg'; Path: ptVoices; AltPath: ptNone),// sndGonnaGetYou
            (FileName:                 'Drat.ogg'; Path: ptVoices; AltPath: ptNone),// sndDrat
            (FileName:               'Bugger.ogg'; Path: ptVoices; AltPath: ptNone),// sndBugger
            (FileName:              'Amazing.ogg'; Path: ptVoices; AltPath: ptNone),// sndAmazing
            (FileName:            'Brilliant.ogg'; Path: ptVoices; AltPath: ptNone),// sndBrilliant
            (FileName:            'Excellent.ogg'; Path: ptVoices; AltPath: ptNone),// sndExcellent
            (FileName:                 'Fire.ogg'; Path: ptVoices; AltPath: ptNone),// sndFire
            (FileName:            'Watchthis.ogg'; Path: ptVoices; AltPath: ptNone),// sndWatchThis
            (FileName:              'Runaway.ogg'; Path: ptVoices; AltPath: ptNone),// sndRunAway
            (FileName:              'Revenge.ogg'; Path: ptVoices; AltPath: ptNone),// sndRevenge
            (FileName:             'Cutitout.ogg'; Path: ptVoices; AltPath: ptNone),// sndCutItOut
            (FileName:         'Leavemealone.ogg'; Path: ptVoices; AltPath: ptNone),// sndLeaveMeAlone
            (FileName:                 'Ouch.ogg'; Path: ptVoices; AltPath: ptNone),// sndOuch
            (FileName:                  'Hmm.ogg'; Path: ptVoices; AltPath: ptNone),// sndHmm
            (FileName:                 'Kiss.ogg'; Path: ptSounds; AltPath: ptNone),// sndKiss
            (FileName:              'Flyaway.ogg'; Path: ptVoices; AltPath: ptNone),// sndFlyAway
            (FileName:           'planewater.ogg'; Path: ptSounds; AltPath: ptNone),// sndPlaneWater
            (FileName:         'dynamitefuse.ogg'; Path: ptSounds; AltPath: ptNone) // sndDynamiteFuse
            );


function  AskForVoicepack(name: shortstring): Pointer;
var i: Longword;
    tmp, nameStart, langName, path: shortstring;
begin
    nameStart:= name;
    i:= 0;

    { Adjust for language suffix: Voicepacks can have an optional language suffix.
    It's an underscore followed by an ISO 639-1 or ISO 639-2 language code.
    The suffix "_qau" is special, it will enable automatic language selection
    of this voicepack. For example, if team has set Default_qau as voicepack,
    and the player language is Russian, the actual voicepack will be Default_ru,
    provided it can be found on the disk.
    "qau" is a valid ISO 639-2 language code reserved for local use. }
    tmp:= Copy(name, Length(name) - 3, 4);
    if (tmp = '_qau') then
        name:= Copy(name, 1, Length(name) - 4);
    if (cLanguage <> 'en') and (tmp = '_qau') then
        begin
        langName:= name+'_'+cLanguage;
        path:= cPathz[ptVoices] + '/' + langName;
        if pfsExists(path) then
            name:= langName
        else
            if Length(cLanguage) > 3 then
                begin
                langName:= name+'_'+Copy(cLanguage,1,2);
                path:= cPathz[ptVoices] + '/' + langName;
                if pfsExists(path) then
                    name:= langName
                end
        end;

    path:= cPathz[ptVoices] + '/' + name;

    // Fallback to localized Default if voicepack can't be found at all
    if (nameStart <> 'Default_qau') and (not pfsExists(path)) then
        exit(AskForVoicepack('Default_qau'));

    while (voicepacks[i].name <> name) and (voicepacks[i].name <> '') and (i < cMaxTeams) 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 = 2;
var success: boolean;
    s: shortstring;
begin
    if not (isSoundEnabled or isMusicEnabled) then
        begin
        isAudioMuted:= true;
        cInitVolume:= 0;
        exit;
        end;
    WriteToConsole('Init sound...');
    success:= SDL_InitSubSystem(SDL_INIT_AUDIO) = 0;

    if success then
        begin
        WriteLnToConsole(msgOK);
        WriteToConsole('Open audio...');
        success:= Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, channels, 1024) = 0;
        end;

    if success then
        WriteLnToConsole(msgOK)
    else
    begin
        WriteLnToConsole(msgFailed);
        isSoundEnabled:= false;
        isMusicEnabled:= false;
        isAudioMuted:= true;
        cInitVolume:= 0;
    end;

    WriteToConsole('Init SDL_mixer... ');

    if (Mix_Init(MIX_INIT_OGG or MIX_INIT_OPUS) and MIX_INIT_OPUS) = 0 then
    begin
      s:= SDL_GetError();
      WriteToConsole('Cannot init OPUS: ' + s);

      if SDLCheck(Mix_Init(MIX_INIT_OGG) <> 0, 'Mix_Init', true) then exit;
    end;

    WriteLnToConsole(msgOK);

    // from uVariables to be used by other modules
    cIsSoundEnabled:= true;

    Mix_AllocateChannels(Succ(chanTPU));
    previousVolume:= cInitVolume;
    ChangeVolume(cInitVolume);
end;

procedure ResetSound;
begin
    isSoundEnabled:= isSEBackup;
end;

procedure SetSound(enabled: boolean);
begin
    isSEBackup:= isSoundEnabled;
    isSoundEnabled:= enabled;
end;

procedure SetAudioDampen(enabled: boolean);
begin
    isAutoDampening:= 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 played
procedure 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;

// Get a fallback voice, assuming that snd is not available. Returns sndNone if none is found.
function GetFallbackV(snd: TSound): TSound;
begin
    // Fallback to sndFirePunch1 / sndOw1 / sndOoff1 if a "higher-numbered" sound is missing
    if (snd in [sndFirePunch2, sndFirePunch3, sndFirePunch4, sndFirePunch5, sndFirePunch6]) then
        GetFallbackV := sndFirePunch1
    else if (snd in [sndOw2, sndOw3, sndOw4, sndOuch]) then
        GetFallbackV := sndOw1
    else if (snd in [sndOoff2, sndOoff3]) then
        GetFallbackV := sndOoff1
    // Other fallback sounds
    else if (snd = sndGrenade) then
        if random(2) = 0 then
            GetFallbackV := sndNooo
        else
            GetFallbackV := sndUhOh
    else if (snd in [sndDrat, sndBugger]) then
        GetFallbackV := sndStupid
    else if (snd in [sndGonnaGetYou, sndIllGetYou, sndJustYouWait, sndCutItOut, sndLeaveMeAlone]) then
        GetFallbackV := sndRegret
    else if (snd in [sndOhDear, sndSoLong]) then
        GetFallbackV := sndByeBye
    else if (snd in [sndWhatThe, sndUhOh]) then
        GetFallbackV := sndNooo
    else if (snd = sndRunAway) then
        GetFallbackV := sndOops
    else if (snd = sndThisOneIsMine) then
        GetFallbackV := sndReinforce
    else if (snd in [sndAmazing, sndBrilliant, sndExcellent]) then
        GetFallbackV := sndEnemyDown
    else if (snd = sndPoisonCough) then
        GetFallbackV := sndPoisonMoan
    else if (snd = sndPoisonMoan) then
        GetFallbackV := sndPoisonCough
    else if (snd = sndFlawless) then
        GetFallbackV := sndVictory
    else if (snd = sndSameTeam) then
        GetFallbackV := sndTraitor
    else if (snd = sndMelon) then
        GetFallbackV := sndCover
    // sndHmm is used for enemy turn start, so sndHello is an "okay" replacement
    else if (snd = sndHmm) then
        GetFallbackV := sndHello
    else
        GetFallbackV := sndNone;
end;

function PlaySound(snd: TSound): boolean;
begin
    PlaySound:= PlaySoundV(snd, nil, false, false, false);
end;

function PlaySound(snd: TSound; keepPlaying: boolean): boolean;
begin
    PlaySound:= PlaySoundV(snd, nil, keepPlaying, false, false);
end;

function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask: boolean): boolean;
begin
    PlaySound:= PlaySoundV(snd, nil, keepPlaying, ignoreMask, false);
end;

function PlaySound(snd: TSound; keepPlaying: boolean; ignoreMask, soundAsMusic: boolean): boolean;
begin
    PlaySound:= PlaySoundV(snd, nil, keepPlaying, ignoreMask, soundAsMusic);
end;

function PlaySoundV(snd: TSound; voicepack: PVoicepack): boolean;
begin
    PlaySoundV:= PlaySoundV(snd, voicepack, false, false, false);
end;

function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying: boolean): boolean;
begin
    PlaySoundV:= PlaySoundV(snd, voicepack, keepPlaying, false, false);
end;

function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask: boolean): boolean;
begin
    PlaySoundV:= PlaySoundV(snd, voicepack, keepPlaying, ignoreMask, false);
end;

function PlaySoundV(snd: TSound; voicepack: PVoicepack; keepPlaying, ignoreMask, soundAsMusic: boolean): boolean;
var s: shortstring;
tempSnd, loadSnd: TSound;
rwops: PSDL_RWops;
begin
    PlaySoundV:= false;
    if ((not isSoundEnabled) and (not (soundAsMusic and isMusicEnabled))) or fastUntilLag then
        exit;

    if keepPlaying and (lastChan[snd] <> -1) and (Mix_Playing(lastChan[snd]) <> 0) then
        exit;

    if (ignoreMask = false) and (MaskedSounds[snd] = true) then
        exit;

    if (voicepack <> nil) then
        begin
        if (voicepack^.chunks[snd] = nil) and (Soundz[snd].Path = ptVoices) and (Soundz[snd].FileName <> '') then
            begin
            loadSnd:= snd;
            s:= cPathz[Soundz[loadSnd].Path] + '/' + voicepack^.name + '/' + Soundz[loadSnd].FileName;

            // Fallback taunts
            if (not pfsExists(s)) then
                begin
                tempSnd := GetFallbackV(snd);
                if tempSnd <> sndNone then
                    begin
                    loadSnd := tempSnd;
                    //LastVoice.snd := tempSnd;
                    end;
                s:= cPathz[Soundz[loadSnd].Path] + '/' + voicepack^.name + '/' + Soundz[loadSnd].FileName;
                end;
            WriteToConsole(msgLoading + s + ' ... ');
            rwops := rwopsOpenRead(s);

            if rwops = nil then
                begin
                s:= cPathz[Soundz[loadSnd].AltPath] + '/' + Soundz[loadSnd].FileName;
                WriteToConsole(msgLoading + s + ' ... ');
                rwops := rwopsOpenRead(s);
                end;
            voicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 1);

            if voicepack^.chunks[snd] = nil then
                WriteLnToConsole(msgFailed)
            else
                WriteLnToConsole(msgOK)
            end;
        lastChan[snd]:= Mix_PlayChannelTimed(-1, voicepack^.chunks[snd], 0, -1);
        PlaySoundV:= true;
        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 + ' ... ');
            rwops := rwopsOpenRead(s);

            if rwops = nil then
                begin
                s:= cPathz[Soundz[snd].AltPath] + '/' + Soundz[snd].FileName;
                WriteToConsole(msgLoading + s + ' ... ');
                rwops := rwopsOpenRead(s);
                end;

            defVoicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 1);
            if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit;
            WriteLnToConsole(msgOK);
            end;
        lastChan[snd]:= Mix_PlayChannelTimed(-1, defVoicepack^.chunks[snd], 0, -1);
        PlaySoundV:= true;
        end;
end;

procedure PlayMusicSound(snd: TSound);
begin
    PauseMusic;
    PlaySound(snd, false, false, true);
end;

procedure StopMusicSound(snd: TSound);
begin
    StopSound(snd, true);
    ResumeMusic;
end;

procedure AddVoice(snd: TSound; voicepack: PVoicepack);
begin
    AddVoice(snd, voicepack, false, false);
end;

{
AddVoice: Add a voice to the voice queue.
* snd: Sound ID
* voicepack: Hedgehog voicepack
* ignoreMask: If true, the sound will be played anyway if masked by Lua
* isFallback: If true, this sound is added as fallback if the sound previously added to the
             queue was not found. Useful to make sure a voice is always played, even if
             a voicepack is incomplete.
             Example:
                 AddVoice(sndRevenge, voiceAttacker);
                 AddVoice(sndRegret, voiceVictim, false, true);
             --> plays sndRegret if sndRevenge could not be played.
}
procedure AddVoice(snd: TSound; voicepack: PVoicepack; ignoreMask, isFallback: boolean);
var i : LongInt;
begin

    if (not isSoundEnabled) or fastUntilLag or ((LastVoice.snd = snd) and  (LastVoice.voicepack = voicepack)) then
        exit;
    if (ignoreMask = false) and (MaskedSounds[snd] = true) 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 <= High(VoiceList)) 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;
    if(i <= High(VoiceList)) then
        begin
        VoiceList[i].snd:= snd;
        VoiceList[i].voicepack:= voicepack;
        VoiceList[i].isFallback:= isFallback;
        end
end;

procedure PlayNextVoice;
var i : LongInt;
    played : boolean;
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);

    played:= false;
    if (VoiceList[i].snd <> sndNone) and ((not VoiceList[i].isFallback) or LastVoiceFailed) then
        begin
        LastVoice.snd:= VoiceList[i].snd;
        LastVoice.voicepack:= VoiceList[i].voicepack;
        LastVoice.isFallback:= VoiceList[i].isFallback;
        VoiceList[i].snd:= sndNone;
        played:= PlaySoundV(LastVoice.snd, LastVoice.voicepack);
        // Remember if sound was not played.
        LastVoiceFailed:= (not played);
        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;
rwops: PSDL_RWops;
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 + ' ');
            rwops:=rwopsOpenRead(s);

            if rwops = nil then
                begin
                s:= cPathz[Soundz[snd].AltPath] + '/' + Soundz[snd].FileName;
                WriteToConsole(msgLoading + s + ' ... ');
                rwops:=rwopsOpenRead(s);
                end;

            voicepack^.chunks[snd]:= Mix_LoadWAV_RW(rwops, 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);
            if SDLCheck(defVoicepack^.chunks[snd] <> nil, 'Mix_LoadWAV_RW', true) then exit(-1);
            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
    StopSound(snd, false);
end;

procedure StopSound(snd: TSound; soundAsMusic: boolean);
begin
    if ((not isSoundEnabled) and (not (soundAsMusic and isMusicEnabled))) 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
        if isAudioMuted then
            Mix_HaltChannel(chn)
        else
            Mix_FadeOutChannel(chn, fadems);
end;

procedure PlayMusic;
var s: shortstring;
begin
    if (MusicFN = '') or (not isMusicEnabled) then
        exit;
    if SuddenDeath and (SDMusicFN <> '') then
         s:= '/Music/' + SDMusicFN
    else s:= '/Music/' + MusicFN;
    WriteToConsole(msgLoading + s + ' ');

    // Load normal music
    Mus:= Mix_LoadMUS_RW(rwopsOpenRead(s));
    SDLCheck(Mus <> nil, 'Mix_LoadMUS_RW', false);
    if Mus <> nil then
        WriteLnToConsole(msgOK);

    // If normal music failed, try to get fallback music
    if Mus = nil then
       begin
       WriteLnToConsole('Music not found. Trying fallback music.');
       if SuddenDeath and (FallbackSDMusicFN <> '') then
           s:= '/Music/' + FallbackSDMusicFN
       else if (not SuddenDeath) and (FallbackMusicFN <> '') then
           s:= '/Music/' + FallbackMusicFN
       else
           begin
           WriteLnToConsole('No fallback music configured!');
           s:= ''
           end;

       if (s <> '') then
           begin
           WriteLnToConsole(msgLoading + s + ' ');
           Mus:= Mix_LoadMUS_RW(rwopsOpenRead(s));
           SDLCheck(Mus <> nil, 'Mix_LoadMUS_RW', false);
           if Mus <> nil then
               WriteLnToConsole(msgOK)
           end;
       end;

    SDLCheck(Mix_FadeInMusic(Mus, -1, 3000) <> -1, 'Mix_FadeInMusic', false)
end;

procedure SetVolume(vol: LongInt);
begin
    cInitVolume:= vol;
end;

function GetVolumePercent(): LongInt;
begin
    GetVolumePercent:= Volume * 100 div MIX_MAX_VOLUME;
    // 0 and 100 will only be displayed when at min/max values
    // to avoid confusion.
    if ((GetVolumePercent = 0) and (Volume > 0)) then
        GetVolumePercent:= 1
    else if ((GetVolumePercent = 100) and (Volume < MIX_MAX_VOLUME)) then
        GetVolumePercent:= 99;
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:= GetVolumePercent();

    if (isMusicEnabled) then
        if (Volume = 0) then
            PauseMusic
            else
            ResumeMusic;

    isAudioMuted:= (Volume = 0);
end;

procedure DampenAudio;
begin
    if (isAudioMuted or (not isAutoDampening)) then
        exit;
    previousVolume:= Volume;
    ChangeVolume(-Volume * 7 div 9);
end;

procedure UndampenAudio;
begin
    if (isAudioMuted or (not isAutoDampening)) 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 ChangeVolume
end;

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;
        end
end;

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 preInitModule;
begin
    isMusicEnabled:= true;
    isSoundEnabled:= true;
    isAutoDampening:= true;
    cInitVolume:= 100;
end;

procedure initModule;
var t: LongInt;
    i: TSound;
begin
    RegisterVariable('voicepack', @chVoicepack, false);

    MusicFN:='';
    SDMusicFN:= 'sdmusic.ogg';
    FallbackMusicFN:='';
    FallbackSDMusicFN:= 'sdmusic.ogg';
    Mus:= nil;
    isAudioMuted:= false;
    isSEBackup:= isSoundEnabled;
    Volume:= 0;
    SoundTimerTicks:= 0;
    defVoicepack:= AskForVoicepack('Default_qau');
    LastVoiceFailed:= false;

    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.