[System] SoundTools

Level 19
Joined
Mar 18, 2012
Messages
1,717
Reset the volume to the default volume, when popping a sound file from the stack.
Otherwise the handle may be muted for x players.

method release could required extra safety, so you don't push a sound handle to the stack,
which is already part of the stack.

I really like SoundTools, we should brainstorm a bit to improve it.
 
Level 19
Joined
Mar 18, 2012
Messages
1,717
I had the idea to use the force native in the entire API.
Everything we need is already available in the blizzad.j
JASS:
    force              bj_FORCE_ALL_PLAYERS        = null
    force array        bj_FORCE_PLAYER
by default a sound volume within the stack is 0.
If a player is in passed in force "audience", the volume gets adjusted.
This adds a small overhead in contrast to SoundTools 3.x, but solves all API problems
i.e. playing a sound on a unit for a player, etc...
in method this.run(audience)
JASS:
            if IsPlayerInForce(GetLocalPlayer(), audience) then
                call SetSoundVolume(media[this], DEFAULT_SOUND_VOLUME)
            endif

Something like this:
JASS:
/***********************************************
*
*   SoundTools
*   v4.0
*   By -
*
*   (Special Thanks to Rising_Dusk, Magtheridon96)
*
*   - Allows you to play sounds immediately after creating them.
*   - Uses a sound recycler to increase efficiency and save RAM.
*
*   Requirements:
*   -------------
*
*       - Table by Bribe
*           - hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*       - TimerUtils by Vexorian
*           - wc3c.net/showthread.php?t=101322
*
*   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.
*
*           method run takes nothing returns sound
*               - Plays the sound.
*           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 runEx takes integer volume, integer pitch returns sound
*               - Plays the sound. This function allows you to pass in extra arguments.
*           method runUnitEx takes unit whichUnit, integer volume, integer 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, integer 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, integer 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 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, integer 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.
*
*   Credits:
*   --------
*
*       - Rising_Dusk (The original system)
*       - Zwiebelchen (Research - He found a ton of Wc3 sound bugs and ways to fix them)
*
***********************************************/
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
    
    /*
    *   End of Configuration
    */
    
    private module Inits 
        private static method onInit takes nothing returns nothing
            call thistype.init()
        endmethod
    endmodule
    struct Sound extends array

        private static Table tb = 0
        private static Table pt = 0
        private static method init takes nothing returns nothing
            set tb = Table.create()
            set pt = Table.create()
        endmethod
        implement Inits 

        private static integer alloc = 0
        
        //*  Stack per instance:
        //*  ===================
        private Table   stack
        private integer size
        
        readonly string  file
        readonly integer duration
        readonly boolean looping
        readonly boolean is3D
        readonly boolean stopOnLeaveRange
        readonly integer fadeIn
        readonly integer fadeOut
        readonly string  eaxSetting
        
        private real pitch
        
        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 = alloc + 1
            set thistype.alloc  = integer(this)
            
            set pitch = 1
            set file = fileName
            set duration = dur
            set looping = loopng
            set is3D = isTD
            set fadeIn = fadeInRate
            set fadeOut = fadeOutRate
            set eaxSetting = eax
            set stopOnLeaveRange = stop
            
            set stack = 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
        
        // Credits to Zwiebelchen for this function
        // He discovered a bug with sound pitches and this function was written to fix that.
        method setSoundPitch takes sound s, real newPitch returns nothing
            if GetSoundIsPlaying(s) or GetSoundIsLoading(s) then
                call SetSoundPitch(s, 1/pitch)
                call SetSoundPitch(s, newPitch)
                set pitch = newPitch
            elseif newPitch == 1 then
                call SetSoundPitch(s, 1.0001)
                set pitch = 1.0001
            else
                call SetSoundPitch(s, newPitch)
                set pitch = newPitch
            endif
        endmethod
        
        private static sound snd
        
        private method get takes nothing returns sound
            if (0 == size) then
            
                /**
                *   Create new sound and point it to
                *   Sound struct instance.
                */
                set snd = CreateSound(file, looping, is3D, stopOnLeaveRange, fadeIn, fadeOut, eaxSetting)
                set pt[GetHandleId(snd)] = this
                
                /**
                *   Configure sound
                */
                call SetSoundDuration(snd, duration)
                call SetSoundChannel(snd, SOUND_CHANNEL)
                call SetSoundVolume(snd, 0)
                call setSoundPitch(snd, DEFAULT_SOUND_PITCH)
                
                /**
                *   Proper 3D sound configuration
                */
                if is3D then
                    call SetSoundDistances(snd, SOUND_MIN_DIST, SOUND_MAX_DIST)
                    call SetSoundDistanceCutoff(snd, SOUND_DIST_CUT)
                    call SetSoundConeAngles(snd, 0, 0, DEFAULT_SOUND_VOLUME)
                    call SetSoundConeOrientation(snd, 0, 0, 0)
                endif
                
                return snd
            endif
            
            //*  Pop:
            //*  ====
            set size = size - 1
            return stack.sound[size]
        endmethod
        
        private method push takes sound s returns nothing
            call SetSoundVolume(s, 0)
            set stack.sound[size] = s
            set size = size + 1
        endmethod
        
        private static method recycle takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local sound s = tb.sound[GetHandleId(t)]
            
            /**
            *   Stop sound and push to the
            *   stack.
            */
            call StopSound(s, false, true)
            call thistype(GetTimerData(t)).push(s)
            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 (0 == this)
                
                /**
                *   Play the sound.
                */
                call StartSound(media[this])
                
                /*
                *   If it is not looping,
                *   we can recycle it when 
                *   it finishes playing.
                */
                if not looping then
                    set t = NewTimerEx(this)
                    set tb.sound[GetHandleId(t)] = media[this]
                    call TimerStart(t, duration * 0.001, false, function thistype.recycle)
                endif
                
                set media[this] = null
                set this = next[this]
            endloop
            
            set next[0] = 0
            
            set t = null
        endmethod
        
        method run takes force audience returns sound
            debug if (0 == this) then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[SoundTools]Error: Attempted to play null sound.")
                debug return null
            debug endif
            
            if next[0] == 0 then
                call TimerStart(NewTimer(), 0, false, function thistype.runSounds)
            endif
            
            if media[this] == null then
                set next[this] = next[0]
                set next[0] = this
                set media[this] = this.get()
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[SoundTools]Warning: Attempted to run the same sound twice.")
            endif
            if IsPlayerInForce(GetLocalPlayer(), audience) then
                call SetSoundVolume(media[this], DEFAULT_SOUND_VOLUME)
            endif
            return media[this]
        endmethod
        
        method runEx takes integer volume, integer newPitch, force audience returns sound
            set snd = this.run(audience)
            if IsPlayerInForce(GetLocalPlayer(), audience) then
                call SetSoundVolume(media[this], volume)
                call setSoundPitch(snd, newPitch)
            endif
            return snd
        endmethod
        
        static method release takes sound s returns boolean
            local integer id = GetHandleId(s)
            
            if s == null then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[SoundTools]Error: Attempted to release a null sound.")
                return false
            elseif pt[id] == 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)
            call thistype(pt[id]).push(s)
            
            return true
        endmethod
        
        method runUnit takes unit whichUnit, force audience returns sound
            set snd = run(audience)
            call AttachSoundToUnit(snd, whichUnit)
            return snd
        endmethod
        
        method runUnitEx takes unit whichUnit, integer volume, integer newPitch. force audience returns sound
            set snd = runUnit(whichUnit)
            if IsPlayerInForce(GetLocalPlayer(), audience) then
                call SetSoundVolume(snd, volume)
                call setSoundPitch(snd, newPitch)
            endif
            return snd
        endmethod
        
        method runPoint takes real x, real y, real z, force audience returns sound
            set snd = run(audience)
            call SetSoundPosition(snd, x, y, z)
            return snd
        endmethod
        
        method runPointEx takes real x, real y, real z, integer volume, integer newPitch, force audience returns sound
            set snd = runPoint(audience, x, y, z)
            if IsPlayerInForce(GetLocalPlayer(), audience) then
                call SetSoundVolume(snd, volume)
                call setSoundPitch(snd, newPitch)
            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 RunSound takes Sound this, force audience returns sound
        return this.run(audience)
    endfunction
    
    function RunSoundEx takes Sound this, integer volume, integer pitch, force audience returns sound
        return this.runEx(volume, pitch, audience)
    endfunction
    
    function ReleaseSound takes sound s returns boolean
        return Sound.release(s)
    endfunction
    
    function RunSoundOnUnit takes Sound this, unit whichUnit, force audience returns sound
        return this.runUnit(audience, whichUnit, audience)
    endfunction
    
    function RunSoundOnUnitEx takes Sound this, unit whichUnit, integer volume, integer pitch, force audience returns sound
        return this.runUnitEx(whichUnit, volume, pitch, audience)
    endfunction
    
    function RunSoundAtPoint takes Sound this, real x, real y, real z, force audience returns sound
        return this.runPoint(x, y, z, audience)
    endfunction
    
    function RunSoundAtPointEx takes Sound this, real x, real y, real z, integer volume, integer pitch, force audience returns sound
        return this.runPointEx(x, y, z, volume, pitch, audience)
    endfunction
    
endlibrary
 
Level 3
Joined
Jun 19, 2010
Messages
49
Magtheridon96 said:
I'm not an active member of this community anymore.

Feel free to edit my posts and resources however you want.
ah, sad...
i just wanted to ask whether it is possible to add an option for delaying sounds so users don't need to create timers themself...
 
Top