• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] [Snippet] SoundFx

Level 9
Joined
Jun 21, 2012
Messages
432
JASS:
library SoundFx/*
*************************************************************************************************
*
*   *********************************************************************************************
*
*   */ uses /*
*       */ TimerUtils /* wc3c.net/showthread.php?t=101322
*       */ DimensionalArray /* hiveworkshop.com/forums/submissions-414/snipet-dimensional-array-265011/
*
*   *********************************************************************************************
*
*   SoundFx
*   ¯¯¯¯¯¯¯
*   v0.1.1.1
*   by Thelordmarshall
*   
*   This library is designed to play sounds every 0.03 seconds without pauses
*   and reduce the use of handles per sound (4 per Sound).
*
*   API:
*   ¯¯¯
*   struct SoundFx extends array
*
*       readonly real duration
*           
*       static method create takes string file, boolean looping, boolean is3D returns SoundFx
*       static method createEx takes string file, boolean looping, boolean is3D, boolean stopwhenoutofrange, integer fadeInRate, integer fadeOutRate, string eaxSetting returns SoundFx
*       method stop takes boolean fadeOut returns nothing
*
*       method operator isPlaying takes nothing returns boolean
*       method operator volume= takes integer vol returns nothing
*       method operator pitch= takes integer pitch returns nothing
*       
*       method play takes nothing returns nothing
*       method playOnUnit takes unit u returns nothing
*       method playOnPoint takes real x, real y, real z returns nothing
*       method playForPlayer takes player p returns nothing
*
*   Credits:
*   ¯¯¯¯¯¯¯
*       - Rising_Dusk, Magtheridon96: SoundUtils/SoundTools
*
***********************************************************************************************/
    struct SoundFx extends array
        private static constant integer MULTI_SOUND_SIZE=4 //not configurable
        private static Array sn=0
        private static integer size=0
        private static integer array list
        private static player forPlayer=null
        private integer current
        readonly real duration

        //! textmacro SoundFx_loopCode takes ARGUMENT_1,ARGUMENT_2,ARGUMENT_3
            local integer i=0
            loop
                exitwhen(MULTI_SOUND_SIZE==i)
                $ARGUMENT_1$
                $ARGUMENT_2$
                $ARGUMENT_3$
                set i=i+1
            endloop
        //! endtextmacro    
        
        method operator isPlaying takes nothing returns boolean
            //! runtexmacro SoundFx_loopCode("if(GetSoundIsPlaying(sn[this][i].sound))then","return true","endif")
            return false
        endmethod
        
        method operator volume= takes integer vol returns nothing
            //! runtexmacro SoundFx_loopCode("call SetSoundVolume(sn[this][i].sound,vol)","","")
        endmethod
        
        method operator pitch= takes integer pitch returns nothing
            //! runtexmacro SoundFx_loopCode("call SetSoundPitch(sn[this][i].sound,pitch)","","")
        endmethod
        
        private method checkSize takes nothing returns nothing
            if(MULTI_SOUND_SIZE==.current)then
                set .current=0
            endif
        endmethod
        
        private static method onSoundExpired takes nothing returns nothing
            local timer t=GetExpiredTimer()
            local sound s=sn[GetTimerData(t)].sound
            if(not GetSoundIsPlaying(s))then
                call StopSound(s,false,false)
            endif
            set s=null
            call ReleaseTimer(t)
        endmethod
        
        private static method delayPlay takes nothing returns nothing
            local timer t=GetExpiredTimer()
            local thistype this=GetTimerData(t)
            local integer i=0
            local integer id=GetHandleId(t)
            local player p=sn[id][0].player
            local sound s
            call .checkSize()
            set s=sn[this][.current].sound
            set .current=.current+1
            if(GetSoundIsPlaying(s))then
                call StopSound(s,false,false)
            endif
            if(null==p)then
                call StartSound(s)
            else
                if(GetLocalPlayer()==p)then
                    call StartSound(s)
                endif
                call sn[id][0].removeHandle()
            endif
            call TimerStart(NewTimerEx(GetHandleId(s)),.duration,false,function thistype.onSoundExpired)
            call ReleaseTimer(t)
            set s=null
        endmethod
        
        method play takes nothing returns nothing
            local timer t
            if(.duration>0)then
                set t=NewTimerEx(this)
                if(forPlayer!=null)then
                    set sn[GetHandleId(t)][0].handle=forPlayer
                    set forPlayer=null
                endif
                call TimerStart(t,0,false,function thistype.delayPlay)
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"[SoundFx] error: attempted to play null sound")
            endif
        endmethod
        
        method playOnUnit takes unit u returns nothing
            call .checkSize()
            call AttachSoundToUnit(sn[this][.current].sound,u)
            call .play()
        endmethod
        
        method playOnPoint takes real x, real y, real z returns nothing
            call .checkSize()
            call SetSoundPosition(sn[this][.current].sound,x,y,z)
            call .play()
        endmethod
        
        method playForPlayer takes player p returns nothing
            set forPlayer=p
            call .play()
        endmethod
        
        static method createEx takes string file, boolean looping, boolean is3D, boolean stopwhenoutofrange, integer fadeInRate, integer fadeOutRate, string eaxSetting returns SoundFx
            local thistype this=list[0]
            local sound s
            if(0==this)then
                set this=size+1
                set size=this
            else
                set list[0]=list[this]
            endif
            set .duration=GetSoundFileDuration(file)*.001
            loop
                exitwhen(MULTI_SOUND_SIZE==.current)
                set s=CreateSound(file,looping,is3D,stopwhenoutofrange,fadeInRate,fadeOutRate,eaxSetting)
                call SetSoundVolume(s,127)
                if(is3D)then
                    call SetSoundDistances(s,600,10000)
                    call SetSoundDistanceCutoff(s,3000)
                    call SetSoundConeAngles(s,0,0,127)
                    call SetSoundConeOrientation(s,0,0,0)
                endif
                set sn[this][.current].handle=s
                set sn[GetHandleId(s)].handle=s
                set .current=.current+1
            endloop
            set s=null
            return this
        endmethod
        
        static method create takes string file, boolean looping, boolean is3D returns SoundFx
            return createEx(file,looping,is3D,true,10,10,"")
        endmethod
        
        method stop takes boolean fadeOut returns nothing
            //! runtexmacro SoundFx_loopCode("call StopSound(sn[this][i].sound,false,fadeOut)","","")
        endmethod
        
        private static method onInit takes nothing returns nothing
            set sn=Array.create()
        endmethod
    endstruct
endlibrary
 
I'm not sure if I like this... creative use of textmacros. It makes the code harder to read for just 3-4 saved lines.

Also:
JASS:
method operator pitch= takes integer pitch returns nothing
             //! runtexmacro SoundFx_loopCode("call SetSoundPitch(sn[this][i].sound,pitch)","","")
         endmethod
SetSoundPitch is bugged. It will apply incorrect pitch values when consecutively used on a running sound. It will also not be able to reset the pitch value back to 1 when the sound stopped.

This is how to fix this problem (obj being the sound handles and lp being the stored last pitch value of the corresponding handle):
JASS:
    private method setPitch takes integer i, real pitch returns nothing
         //due to the bugged pitch native, we need this workaround snippet in order to make it work
         if GetSoundIsPlaying(obj[i]) or GetSoundIsLoading(obj[i]) then
             call SetSoundPitch(obj[i], 1/lp[i])
             call SetSoundPitch(obj[i], pitch)
             set lp[i] = pitch
         else
             if pitch == 1 then
                 call SetSoundPitch(obj[i], 1.0001)
                 set lp[i] = 1.0001
             else
                 call SetSoundPitch(obj[i], pitch)
                 set lp[i] = pitch
             endif
         endif
     endmethod

Also, did you address the problem of two sounds of a same filepath (even on different handles) not being able to be played within a certain arbitrary time interval of roughly 0.1 seconds? The workaround for this is delaying the second sound, then adjusting the PlayPosition so that they line up correctly.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Since this requires DimensionalArray and you didn't reply until now on the feedback there: do you still work on this library? If not it would probably make sense to eliminate this requirement.

I also don't see why you would need a dimensional array here, since a normal hashtable would be absolutly sufficient for the job. IMO its an uneccessary requirement.

Also you give credits to Rising_Dusk and Magtheridon96 for SoundUtils/ SoundTools... what exactly makes this system preferable over those two?
 
Top