[System] Simulate 3D Sound

Scripts

13 years
edited 8 years
This library will allow you to simulate a 3D sound. It will have most of the features of a 3D sound, except for cones and velocity. Most of these are guesstimates, but when I compared it to the regular 3D sounds, they were very similar.

I don't have a 100% concrete grasp on what each field does, so if you have more insight on it, feel free to share. I interpreted it from a lot of testing, but even then there are still a few things I can't account for.

Anyway, why have this system? (1) 3D sounds are incredibly annoying. As far as I can tell, mp3's and 2-channel sounds don't work as them. This will allow you to use it as a regular sound, so you won't have to worry about any of that. (2) If you have a 3D sound, you cannot use SetSoundPlayPosition(...). If your sound is not 3D, you cannot have 3D features (obviously). This system will serve as a compromise, having the sound as non-3D and emulating the features of 3D sounds to get the best aspects of both sides. 

Enjoy.

Code (jass) Select
library Simulate3DSound /* v.1.0.1.1
*********************************************************************
*
*   Simulates a 3D Sound using a regular sound. This extends the
*   functionality for regular sounds, allowing you to apply 3D
*   sound effects and positioning. In addition, you can use the
*   sound functions that require the sound to be normal (non-3D).
*
*   This will allow you to use sounds that normally wouldn't work
*   as 3D (for example, 2-channel wav).
*
*********************************************************************
*
*   */uses/*
*   
*       */ TimerUtils /*    [url]http://www.wc3c.net/showthread.php?t=101322[/url]
*
********************************************************************
*
*   Description of Arguments
*
*       sound s
*
*           - The base sound to be played. The function will play
*             the sound for you. This sound must already exist as
*             an object (you have to create it).
*
*       unit u
*       
*           - The sound will follow this unit around. The closer
*             the camera is to this unit, the louder the sound.
*
*       real x, y
*
*           - The sound will project from this point. The volume
*             is loudest within the point's area.
*
*       real minD (minimum distance)
*
*           - Determines how far the camera can go without attenuating
*             the volume. From the origin to this distance, the sound's
*             volume will be 100%.
*
*       real maxD (maximum distance)
*
*           - Once the camera is past the minimum distance, it will
*             gradually reduce until it hits the maximum distance.
*             Once it is at the maximum distance or beyond, it will
*             no longer lose volume unless it hits the distance cutoff.
*
*       real distCutoff (distance cutoff)
*
*           - The sound will fade to 0% volume once it hits this distance.
*             If the camera is farther than "distCutoff" when the sound
*             is played, the sound will not be heard by that player.
*
********************************************************************
*
*   Functions
*
*       function BindSoundToUnit takes sound s, unit u, real minD, real maxD, real distCut returns Sim3DSound
*           - Attaches a regular sound to a unit.
*       function BindSoundToPoint takes sound s, real x, real y, real minD, real maxD, real distCut returns Sim3DSound
*           - Attaches a regular sound to a point.
*       function SetSoundUnit takes Sim3DSound s, unit u returns nothing
*           - Allows you to switch the unit the sound is projecting from.
*       function SetSoundCoordinates takes Sim3DSound s, real x, real y returns nothing
*           - Allows you to move the sound to another point.
*       function BindSoundToUnitForPlayer takes player p, sound s, unit u, real minD, real maxD, real distCut returns Sim3DSound
*       function BindSoundToPointForPlayer takes player p, sound s, real x, real y, real minD, real maxD, real distCut returns Sim3DSound
*
********************************************************************
*
*   Bugs
*
*       This doesn't behave 100% like Warcraft 3 3D sounds. It does
*       not increase volume in relation to zoom (I may add that feature
*       in the future), and the "minimum distance" does not behave
*       exactly like Warcraft 3's. I'm still trying to figure it out.
*       However, it is realistic and works well.
*
********************************************************************/

    globals
        // Determines how often the volume is updated
        private constant real PERIOD = 0.2
    endglobals

    private struct Sim3DSound
        private sound snd
        private unit source
        private real sourceX
        private real sourceY
        private boolean adjust
       
        private real initial
        private real factor
        private real dur
        private real maxD
        private real distCut
       
        private static method expire takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local real dx 
            local real dy           
            local real dist
            local integer volume = 0
           
            if this.source == null then
                set dx = this.sourceX - GetCameraTargetPositionX()
                set dy = this.sourceY - GetCameraTargetPositionY()
            else
                set dx = GetUnitX(this.source) - GetCameraTargetPositionX()
                set dy = GetUnitY(this.source) - GetCameraTargetPositionY()
            endif
            set dist = SquareRoot(dx * dx + dy * dy)
           
            if this.adjust then
                if dist > maxD and dist < distCut then
                    call SetSoundVolume(snd, R2I(this.initial - maxD * this.factor))
                else
                    set volume = R2I(this.initial - dist * this.factor) // linear reduction
                    if volume < 0 then
                        call SetSoundVolume(snd, 1)
                    else
                        call SetSoundVolume(snd, volume)
                    endif
                endif
            endif
            if this.dur <= 0 then
                call ReleaseTimer(GetExpiredTimer())
            endif
            set this.dur = this.dur - PERIOD
        endmethod
       
        method setSoundPosition takes real x, real y returns nothing
            set this.sourceX = x
            set this.sourceY = y
        endmethod
       
        method setSource takes unit u returns nothing
            set this.source = u
        endmethod
       
        static method createOnPoint takes sound s, real x, real y, real minDist, real maxDist, real distCutoff returns thistype
            local thistype this = thistype.create()
            local real dx = x - GetCameraTargetPositionX()
            local real dy = y - GetCameraTargetPositionY()
            local real dist = dx*dx + dy*dy
           
            set this.snd = s
            set this.source = null
            set this.sourceX = x
            set this.sourceY = y
            set this.distCut = distCutoff
            set this.factor = 127 / distCutoff
            set this.initial = 127 + minDist * this.factor
            set this.maxD = maxDist
            set this.dur = GetSoundDuration(s) * 0.001
            set this.adjust = dist < distCutoff * distCutoff
            if not this.adjust then
                call SetSoundVolume(s, 1)
            endif
           
            call TimerStart(NewTimerEx(this), PERIOD, true, function thistype.expire)
            return this
        endmethod
       
        static method attachToUnit takes sound s, unit u, real minDist, real maxDist, real distCutoff returns thistype
            local thistype this = thistype.create()
            local real dx = GetUnitX(u) - GetCameraTargetPositionX()
            local real dy = GetUnitY(u) - GetCameraTargetPositionY()
            local real dist = dx*dx + dy*dy
           
            set this.snd = s
            set this.source = u
            set this.distCut = distCutoff
            set this.factor = 127 / distCutoff
            set this.initial = 127 + minDist * this.factor
            set this.maxD = maxDist
            set this.dur = GetSoundDuration(s) * 0.001
            set this.adjust = dist < distCutoff * distCutoff
            if not this.adjust then
                call SetSoundVolume(s, 1)
            endif
           
            call TimerStart(NewTimerEx(this), PERIOD, true, function thistype.expire)
            return this
        endmethod
    endstruct
   
    function BindSoundToUnit takes sound s, unit u, real minD, real maxD, real distCut returns Sim3DSound
        if not GetSoundIsPlaying(s) then
            call StartSound(s)
        endif
        return Sim3DSound.attachToUnit(s, u, minD, maxD, distCut)
    endfunction
   
    function BindSoundToPoint takes sound s, real x, real y, real minD, real maxD, real distCut returns Sim3DSound
        if not GetSoundIsPlaying(s) then
            call StartSound(s)
        endif
        return Sim3DSound.createOnPoint(s, x, y, minD, maxD, distCut)
    endfunction
   
    function SetSoundUnit takes Sim3DSound s, unit u returns nothing
        call s.setSource(u)
    endfunction
   
    function SetSoundCoordinates takes Sim3DSound s, real x, real y returns nothing
        call s.setSoundPosition(x, y)
    endfunction
   
    function BindSoundToUnitForPlayer takes player p, sound s, unit u, real minD, real maxD, real distCut returns Sim3DSound
        if GetLocalPlayer() == p then
            if not GetSoundIsPlaying(s) then
                call StartSound(s)
            endif
        endif
        return Sim3DSound.attachToUnit(s, u, minD, maxD, distCut)
    endfunction
   
    function BindSoundToPointForPlayer takes player p, sound s, real x, real y, real minD, real maxD, real distCut returns Sim3DSound
        if GetLocalPlayer() == p then
            if not GetSoundIsPlaying(s) then
                call StartSound(s)
            endif
        endif
        return Sim3DSound.createOnPoint(s, x, y, minD, maxD, distCut)
    endfunction
   
endlibrary


EDIT: Updated with test map and support for SoundTools. (just use call BindSound...(soundObject.run()) or whatever the method is.
13 years
WOAHHH!!! man!!! this is awesome!!! just one thing. I consider to add a test map and with it an example about how to use it.
13 years
I added a test map. Although, I didn't really know what to explain. The test map is really hastily-made though, nothing fancy. You can play the sound, then you can move your camera away and watch the system work its magic. That is about it.

I added one example in GUI-jass and one in vJASS.
13 years
Great!!! That's what I call a nice resource. In fact this baby have some nice purpose in a secret project of mine :)
13 years
This seems like a nice resource.

From what I can see, this should work well in multiplayer since no local factors will lead to any variations in the local handle and string tables.

Approved.
13 years
These are what the sound fields are supposed to represent:

Min Distance - The sound level at the minimum distance that the sound can be heard.
Max Distance - The sound level at the maximum distance that the sound can be heard.
Distance Cutoff - The distance at which the sound will begin to fade out.

I found it in the readme for the sound editor. Oops. Anyway, I may update this eventually to mimic it more accurately now that I know the proper definitions, but for now this system should come pretty close (or at least, it should be able to offer similar behavior).
13 years
Purgeandfire rank: newbie
wat?
give him epic guy rank (?)
13 years
Thanks darkly. :)

Bugfix. I was referring to this.source when it was null. Sorry to anyone who had issues with it.