[System] SoundTools

Scripts

14 years
edited 8 years
This system allows you to play a sound immediately after creating it (Which is impossible inside Warcraft III)

Basically, all what it does is start a timer and play the sound.
It also recycles these sounds to decrease the amount of RAM used up.

Code (jass) Select
/***********************************************
*
*   SoundTools
*   v2.3.0.0
*   By Magtheridon96
*
*   (Special Thanks to Rising_Dusk for SoundUtils)
*
*   - Allows you to play sounds immediately after creating them.
*   - Uses a sound recycler to increase efficiency and save RAM.
*
*   API:
*   ----
*
*
*       constant boolean DEFAULT_SOUND_STOPS_ON_LEAVE_RANGE
*       constant integer DEFAULT_SOUND_FADE_IN_RATE
*       constant integer DEFAULT_SOUND_FADE_OUT_RATE
*       constant string  DEFAULT_SOUND_EAX_SETTINGS
*       constant integer DEFAULT_SOUND_VOLUME
*       constant integer DEFAULT_SOUND_PITCH
*
*
*       struct Sound extends array
*
*           readonly string file
*           readonly integer duration
*           readonly boolean looping
*           readonly boolean is3D
*           readonly boolean stopOnLeaveRange
*           readonly integer fadeIn
*           readonly integer fadeOut
*           readonly string eaxSetting
*
*           static method create takes string fileName, integer duration, boolean looping, boolean is3D returns thistype
*               - Creates a sound struct given the filepath, the duration in milliseconds, whether it is looping or not, and whether it is 3D or not.
*           static method createEx takes string fileName, integer duration, boolean looping, boolean is3D, boolean stopOnExitRange, integer fadeIn, integer fadeOut, string eaxSetting returns thistype
*               - In addition to static method create, this allows you to specificy whether the sound stops when the player leaves range, the fadeIn/fadeOut rates and the EAX Setting.
*           static method release takes sound s returns boolean
*               - Releases a sound and throws it into the recycler. Also stops the sound.
*
*           static method getTotalInstances takes nothing returns integer
*               - Determines the total number of sounds playing.
*           method getInstances takes nothing returns integer
*               - Determines the total number of sounds of a certain type that are running.
*
*           method run takes nothing returns sound
*               - Plays the sound.
*           method runEx takes integer volume, real pitch returns sound
*               - Plays the sound. This function allows you to pass in extra arguments.
*
*           method runUnit takes unit whichUnit returns sound
*               - Plays the sound on a unit.
*           method runPoint takes real x, real y, real z returns sound
*               - Plays the sound at a point.
*           method runPlayer takes player whichPlayer returns sound
*               - Plays the sound for a player.
*
*           method runUnitEx takes unit whichUnit, integer volume, real pitch returns sound
*               - Plays the sound on a unit. This function allows you to pass in extra arguments.
*           method runPointEx takes real x, real y, real z, integer volume, real pitch returns sound
*               - Plays the sound at a point. This function allows you to pass in extra arguments.
*           method runPlayerEx takes player whichPlayer, integer volume, real pitch returns sound
*               - Plays the sound for a player. This function allows you to pass in extra arguments.
*
*       function NewSound takes string fileName, integer duration, boolean looping, boolean is3D returns Sound
*           - Creates a sound struct given the filepath, the duration in milliseconds, whether it is looping or not, and whether it is 3D or not.
*       function NewSoundEx takes string fileName, integer duration, boolean looping, boolean is3D, boolean stop, integer fadeInRate, integer fadeOutRate, string eax returns Sound
*           - In addition to static method create, this allows you to specificy whether the sound stops when the player leaves range, the fadeIn/fadeOut rates and the EAX Setting.
*       function GetTotalSoundInstances takes nothing returns integer
*           - Determines the total number of sounds playing.
*       function GetSoundInstances takes Sound this returns integer
*           - Determines the total number of sounds of a certain type that are running.
*       function ReleaseSound takes sound s returns boolean
*           - Releases a sound and throws it into the recycler. Also stops the sound.
*       function RunSound takes Sound this returns sound
*           - Plays the sound.
*       function RunSoundEx takes Sound this, integer volume, real pitch returns sound
*           - Plays the sound. This function allows you to pass in extra arguments.
*       function RunSoundOnUnit takes Sound this, unit whichUnit returns sound
*           - Plays the sound on a unit.
*       function RunSoundAtPoint takes Sound this, real x, real y, real z returns sound
*           - Plays the sound at a point.
*       function RunSoundForPlayer takes Sound this, player p returns sound
*           - Plays the sound for a player.
*       function RunSoundOnUnitEx takes Sound this, unit whichUnit, integer volume, real pitch returns sound
*           - Plays the sound on a unit. This function allows you to pass in extra arguments.
*       function RunSoundAtPointEx takes Sound this, real x, real y, real z, integer volume, real pitch returns sound
*           - Plays the sound at a point. This function allows you to pass in extra arguments.
*       function RunSoundForPlayerEx takes Sound this, player p, integer volume, real pitch returns sound
*           - Plays the sound for a player. This function allows you to pass in extra arguments.
*
***********************************************/
library SoundTools requires Table, TimerUtils
   
    /*
    *   Configuration.
    */
   
    globals
        constant boolean DEFAULT_SOUND_STOPS_ON_LEAVE_RANGE = true
        constant integer DEFAULT_SOUND_FADE_IN_RATE = 10
        constant integer DEFAULT_SOUND_FADE_OUT_RATE = 10
        constant string  DEFAULT_SOUND_EAX_SETTINGS = "CombatSoundsEAX"
        constant integer DEFAULT_SOUND_VOLUME = 127
        constant integer DEFAULT_SOUND_PITCH = 1
    endglobals
   
    globals
        private constant integer SOUND_CHANNEL = 5
        private constant integer SOUND_MIN_DIST = 600
        private constant integer SOUND_MAX_DIST = 10000
        private constant integer SOUND_DIST_CUT = 3000
    endglobals
   
    /*
    *   Configuration Ends here.
    */
   
    struct Sound extends array
        private static key tbK
        private static key ptK
        private static Table tb = tbK
        private static Table pt = ptK
        private static integer index = 1
       
        private static Table array stack
        private static integer array count
        private static integer array playing
        private static sound snd = null
        private static integer totalPlaying = 0
       
        readonly string file
        readonly integer duration
        readonly boolean looping
        readonly boolean is3D
        readonly boolean stopOnLeaveRange
        readonly integer fadeIn
        readonly integer fadeOut
        readonly string eaxSetting
       
        static method createEx takes string fileName, integer dur, boolean loopng, boolean isTD, boolean stop, integer fadeInRate, integer fadeOutRate, string eax returns thistype
            local thistype this = index
            set index = index + 1
           
            set this.file               = fileName
            set this.duration           = dur
            set this.looping            = loopng
            set this.is3D               = isTD
            set this.stopOnLeaveRange   = stop
            set this.fadeIn             = fadeInRate
            set this.fadeOut            = fadeOutRate
            set this.eaxSetting         = eax
           
            set stack[this] = Table.create()
            return this
        endmethod
       
        static method create takes string fileName, integer dur, boolean loopng, boolean isTD returns thistype
            return createEx(fileName, dur, loopng, isTD, DEFAULT_SOUND_STOPS_ON_LEAVE_RANGE, DEFAULT_SOUND_FADE_IN_RATE, DEFAULT_SOUND_FADE_OUT_RATE, DEFAULT_SOUND_EAX_SETTINGS)
        endmethod
       
        static method getTotalInstances takes nothing returns integer
            return totalPlaying
        endmethod
       
        method getInstances takes nothing returns integer
            return playing[this]
        endmethod
       
        private static method recycle takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local sound s = tb.sound[GetHandleId(t)]
            local integer this = GetTimerData(t)
           
            /*
            *   Stop the sound and push it to the stack.
            */
            call StopSound(s, false, true)
            set stack[this].sound[count[this]] = s
            set count[this] = count[this] + 1
           
            set totalPlaying = totalPlaying - 1
            set playing[this] = playing[this] - 1
           
            /*
            *   Null locals and release timer.
            */
            call ReleaseTimer(t)
            set t = null
            set s = null
        endmethod
       
        private static integer array next
        private static sound array media
       
        private static method runSounds takes nothing returns nothing
            local thistype this = next[0]
            local timer t
           
            call ReleaseTimer(GetExpiredTimer())
           
            loop
                exitwhen this == 0
               
                /*
                *   Play the sound
                */
                call StartSound(media[this])
               
                /*
                *   If the sound is not looping, we recycle
                *   it and push it into the stack after it
                *   finishes playing.
                */
                if not this.looping then
                    set t = NewTimerEx(this)
                    set tb.sound[GetHandleId(t)] = media[this]
                    call TimerStart(t, this.duration * 0.001, false, function thistype.recycle)
                endif
               
                set media[this] = null
                set this = next[this]
            endloop
           
            /*
            *   Fast stack destruction.
            */
            set next[0] = 0
           
            set t = null
        endmethod
       
        private method play takes nothing returns sound
            /*
            *   If the run-stack is empty, we start the run-timer.
            */
            if next[0] == 0 then
                call TimerStart(NewTimer(), 0, false, function thistype.runSounds)
            endif
           
            /*
            *   This prevents sounds of the same type from being
            *   played in the same thread. This is good for
            *   performance because playing the same sound-type
            *   twice at the same time will make no difference.
            */
            if media[this] == null then
                /*
                *   Push instance to run-stack.
                */
                set next[this] = next[0]
                set next[0] = this
               
                /*
                *   Increase the number of sounds playing.
                */
                set totalPlaying = totalPlaying + 1
                set playing[this] = playing[this] + 1
               
                /*
                *   We check if the stack is empty.
                */
                if count[this] == 0 then
                    /*
                    *   Create a new sound and point it to the struct instance
                    */
                    set media[this] = CreateSound(this.file, this.looping, this.is3D, this.stopOnLeaveRange, this.fadeIn, this.fadeOut, this.eaxSetting)
                    set pt[GetHandleId(media[this])] = this
                   
                    /*
                    *   Set the duration of the sound and the channel
                    */
                    call SetSoundDuration(media[this], this.duration)
                    call SetSoundChannel(snd, SOUND_CHANNEL)
                   
                    /*
                    *   If the sound is 3D, we configure it some more.
                    */
                    if this.is3D then
                        call SetSoundDistances(media[this], SOUND_MIN_DIST, SOUND_MAX_DIST)
                        call SetSoundDistanceCutoff(media[this], SOUND_DIST_CUT)
                        call SetSoundConeOrientation(media[this], 0, 0, 0)
                    endif
                else
                    /*
                    *   We have a sound in our stack, so we pop it.
                    */
                    set count[this] = count[this] - 1
                    return stack[this].sound[count[this]]
                endif
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[SoundTools]Warning: Attempted to run the same sound twice.")
            endif
           
            return media[this]
        endmethod
       
        method run takes nothing returns sound
            set snd = this.play()
           
            /*
            *   Configure volume and pitch to default values.
            */
            call SetSoundVolume(snd, DEFAULT_SOUND_VOLUME)
            call SetSoundPitch(snd, DEFAULT_SOUND_PITCH)
           
            /*
            *   If the sound is 3D, we configure the cone angle volume.
            */
            if this.is3D then
                call SetSoundConeAngles(snd, 0, 0, DEFAULT_SOUND_VOLUME)
            endif
           
            return snd
        endmethod
       
        method runEx takes integer volume, real pitch returns sound
            set snd = this.play()
           
            /*
            *   Configure volume and pitch.
            */
            call SetSoundVolume(snd, volume)
            call SetSoundPitch(snd, pitch)
           
            /*
            *   If the sound is 3D, we configure the cone angle volume.
            */
            if this.is3D then
                call SetSoundConeAngles(snd, 0, 0, volume)
            endif
           
            return snd
        endmethod
       
        static method release takes sound s returns boolean
            local integer id = GetHandleId(s)
            local integer this = pt[id]
           
            if s == null then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[SoundTools]Error: Attempted to release a null sound.")
                return false
            elseif this == 0 then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[SoundTools]Error: Attempted to release a sound not allocated by RunSound.")
                return false
            endif
           
            /*
            *   Stop the sound and push it to the stack.
            */
            call StopSound(s, false, true)
            set stack[this].sound[count[this]] = s
            set count[this] = count[this] + 1
           
            set totalPlaying = totalPlaying - 1
            set playing[this] = playing[this] - 1
           
            return true
        endmethod
       
        method runUnit takes unit whichUnit returns sound
            set snd = this.run()
            call AttachSoundToUnit(snd, whichUnit)
            return snd
        endmethod
       
        method runPoint takes real x, real y, real z returns sound
            set snd = this.run()
            call SetSoundPosition(snd, x, y, z)
            return snd
        endmethod
       
        method runPlayer takes player p returns sound
            set snd = this.run()
            if GetLocalPlayer() != p then
                call SetSoundVolume(snd, 0)
            endif
            return snd
        endmethod
       
        method runUnitEx takes unit whichUnit, integer volume, real pitch returns sound
            set snd = this.runEx(volume, pitch)
            call AttachSoundToUnit(snd, whichUnit)
            return snd
        endmethod
       
        method runPointEx takes real x, real y, real z, integer volume, real pitch returns sound
            set snd = this.runEx(volume, pitch)
            call SetSoundPosition(snd, x, y, z)
            return snd
        endmethod
       
        method runPlayerEx takes player p, integer volume, real pitch returns sound
            set snd = this.runEx(volume, pitch)
            if GetLocalPlayer() != p then
                call SetSoundVolume(snd, 0)
            endif
            return snd
        endmethod
       
    endstruct
   
    function NewSoundEx takes string fileName, integer duration, boolean looping, boolean is3D, boolean stop, integer fadeInRate, integer fadeOutRate, string eax returns Sound
        return Sound.createEx(fileName, duration, looping, is3D, stop, fadeInRate, fadeOutRate, eax)
    endfunction
   
    function NewSound takes string fileName, integer duration, boolean looping, boolean is3D returns Sound
        return Sound.create(fileName, duration, looping, is3D)
    endfunction
   
    function GetTotalSoundInstances takes nothing returns integer
        return Sound.getTotalInstances()
    endfunction
   
    function GetSoundInstances takes Sound this returns integer
        return this.getInstances()
    endfunction
   
    function RunSound takes Sound this returns sound
        return this.run()
    endfunction
   
    function RunSoundEx takes Sound this, integer volume, real pitch returns sound
        return this.runEx(volume, pitch)
    endfunction
   
    function ReleaseSound takes sound s returns boolean
        return Sound.release(s)
    endfunction
   
    function RunSoundOnUnit takes Sound this, unit whichUnit returns sound
        return this.runUnit(whichUnit)
    endfunction
   
    function RunSoundAtPoint takes Sound this, real x, real y, real z returns sound
        return this.runPoint(x, y, z)
    endfunction
   
    function RunSoundForPlayer takes Sound this, player p returns sound
        return this.runPlayer(p)
    endfunction
   
    function RunSoundOnUnitEx takes Sound this, unit whichUnit, integer volume, real pitch returns sound
        return this.runUnitEx(whichUnit, volume, pitch)
    endfunction
   
    function RunSoundAtPointEx takes Sound this, real x, real y, real z, integer volume, real pitch returns sound
        return this.runPointEx(x, y, z, volume, pitch)
    endfunction
   
    function RunSoundForPlayerEx takes Sound this, player p, integer volume, real pitch returns sound
        return this.runPlayerEx(p, volume, pitch)
    endfunction
   
endlibrary


Rising_Dusk's SoundUtils is the original version of this.
This is a more optimal and cleaner version that's easier to read and understand.

Feel free to comment.