• 🏆 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] RapidSound

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Code
JASS:
library RapidSound requires optional TimerUtils

    globals
        // Actually, just leave this value
        private constant real MIN_DELAY_FACTOR = 4.0
    endglobals

    /* v1.6

        Description
        ¯¯¯¯¯¯¯¯¯¯¯
            Allows you to play sounds rapidly and flawlessly without limit.
            Remember one sound file can only have one RSound instance.
        
        
        External Dependencies
        ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
            (Optional)
            TimerUtils by Vexorian
                wc3c.net/showthread.php?t=101322
    
    
        User API
        ¯¯¯¯¯¯¯¯
            struct RSound
        
                Instantiate an RapidSound instance
                    | static method create takes string fileName, boolean is3D, boolean autoStop, integer inRate, integer outRate returns thistype
                        autoStop => stop when sound is out of range
                        inRate   => fade in rate
                        outRate  => fade out rate
                
                Play the sound at given coordinate
                    | method play takes real x, real y, real z, integer volume returns nothing
                
                Stop sound
                    | method stop takes boolean fadeOut returns nothing
            
                Destroy RapidSound instance
                    | method kill takes nothing returns nothing
            
                Sound file duration (in second)
                    | method operator duration takes nothing returns real
    
    
        Resource Link
        ¯¯¯¯¯¯¯¯¯¯¯¯¯
            hiveworkshop.com/threads/snippet-rapidsound.258991/
    */

    struct RSound

        private static constant integer MAX_COUNT = 4
        private static integer  Counter = -1
        private static string   array StrLib
        private static thistype array StrDex
    
        private integer ct
        private integer lib
        private integer dex
        private real    dur
    
        private sound array snd[thistype.MAX_COUNT]
        private timer array tmr[thistype.MAX_COUNT]
    
        method operator duration takes nothing returns real
            return .dur*MIN_DELAY_FACTOR
        endmethod
    
        method kill takes nothing returns nothing
    
            local integer i
        
            set .ct = .ct - 1
            if .ct == 0 then
                set i = 0
                loop
                    exitwhen i == MAX_COUNT
                    call StopSound(.snd[i], true, false)
                    static if LIBRARY_TimerUtils then
                        call ReleaseTimer(.tmr[i])
                    else
                        call DestroyTimer(.tmr[i])
                    endif
                    set .snd[i] = null
                    set .tmr[i] = null
                    set i = i + 1
                endloop
                set StrLib[.lib] = StrLib[Counter]
                set StrDex[.lib] = StrDex[Counter]
                set Counter = Counter - 1
                call deallocate()
            endif
        
        endmethod
    
        method stop takes boolean fadeOut returns nothing
    
            local integer i = 0
        
            loop
                exitwhen i == MAX_COUNT
                call StopSound(.snd[i], false, fadeOut)
                set i = i + 1
            endloop
        
        endmethod
    
        method play takes real x, real y, real z, integer volume returns nothing
        
            set .dex = .dex + 1
            if .dex == MAX_COUNT then
                set .dex = 0
            endif
            if TimerGetRemaining(.tmr[.dex]) == 0 then
                call StopSound(.snd[.dex], false, false)
                call SetSoundPosition(.snd[.dex], x, y, z)
                call SetSoundVolume(.snd[.dex], volume)
                call StartSound(.snd[.dex])
                call TimerStart(.tmr[.dex], .dur, false, null)
            endif
        
        endmethod
    
        static method create takes string fileName, boolean is3D, boolean autoStop, integer inRate, integer outRate returns thistype
    
            local thistype this
            local integer  i = 0
            local boolean  b = true
        
            loop
                exitwhen i > Counter
                if fileName == StrLib[i] then
                    set b = false
                    exitwhen true
                endif
                set i = i + 1
            endloop
        
            if b then
                set this = allocate()
                set Counter = Counter + 1
                set StrLib[Counter] = fileName
                set StrDex[Counter] = this
            
                set .ct  = 1
                set .dex = -1
                set .lib = Counter
                set .dur = I2R(GetSoundFileDuration(fileName))/(1000.*MIN_DELAY_FACTOR)
                set i = 0
                loop
                    exitwhen i == MAX_COUNT
                    set .snd[i] = CreateSound(fileName, false, is3D, autoStop, inRate, outRate, "")
                    static if LIBRARY_TimerUtils then
                        set .tmr[i] = NewTimer()
                        call TimerStart(.tmr[i], 0, false, null)
                        call PauseTimer(.tmr[i])
                    else
                        set .tmr[i] = CreateTimer()
                    endif
                    set i = i + 1
                endloop
            else
                set this = StrDex[i]
                set .ct  = .ct + 1
            endif
        
            return this
        endmethod
    
    endstruct

endlibrary

Demo
JASS:
scope test

    globals
        RSound test
    endglobals

    function Trig_test_Actions takes nothing returns nothing
        call test.play(0, 0, 0, 200)
    endfunction

    //===========================================================================
    function InitTrig_test takes nothing returns nothing
        set test = RSound.create("Abilities\\Weapons\\Catapult\\CatapultMissile2.wav", false, false, 12700, 12700)
        call TimerStart(CreateTimer(), 0.1, true, function Trig_test_Actions)
    endfunction

endscope
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Why don't you guys try the demo code first? :)

Can you not play the same sound handle more than once at a time?
You can't play one sound handle twice or more at once. And one sound file can only be played 4 times at once.

I kind of agree, this seems to be way too simple wrapper
Since when simple is prohibited? As long as it does what it was meant for, I think there's no problem.
 

Deleted member 219079

D

Deleted member 219079

I think this is really useful :)
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Why don't you guys try the demo code first? :)


You can't play one sound handle twice or more at once. And one sound file can only be played 4 times at once.


Since when simple is prohibited? As long as it does what it was meant for, I think there's no problem.

if some things are so simple that anyone can do it with a bit of time, they shouldnt be prohibited(posting library containing forward declaration for IsUnitAlive native for instance)
 
It's actually rather hit and miss to tell if a 'wrapper' library gets deleted into oblivion. Using vJASS greatly assists in staying out of such a void. Odd that you can only have four soundobjects loaded from the same file. That doesn't even make sense programmatically, even for Blizzard. Aside from testing, how did you come to know of this?I can't test it, because of vJASS.
 

Deleted member 219079

D

Deleted member 219079

Hmm, this criticism :p is this normal for jass resources or is this personal?
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
It's actually rather hit and miss to tell if a 'wrapper' library gets deleted into oblivion. Using vJASS greatly assists in staying out of such a void. Odd that you can only have four soundobjects loaded from the same file. That doesn't even make sense programmatically, even for Blizzard. Aside from testing, how did you come to know of this?I can't test it, because of vJASS.
I did extensive testing on sounds when I wrote this:
http://www.hiveworkshop.com/forums/jass-resources-412/system-tabreader-music-interpreter-215824/

This is what I found out:

1) You can only play the same sound handle once
2) You can only play the same sound filepath 4 times
3) You can only play 16 sounds in general
4) Sounds of the same filepath (on different sound handles) must have a delay of at least 0.1 seconds inbetween them to be played (this, however, can be overcome by starting them early/later and then using SetSoundPosition()
5) The SetSoundPitch native is bugged and doesn't always apply the correct pitch. I wrote a snippet to catch all bugged implementations:

JASS:
globals
         sound array obj
         real array lp
endglobals

function 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
endfunction

And errr, don't ask how I found that out. It still haunts me at night.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Thanks for precious info sir Zwie (finally I spelled your name correctly!), but
4) Sounds of the same filepath (on different sound handles) must have a delay of at least 0.1 seconds inbetween them to be played (this, however, can be overcome by starting them early/later and then using SetSoundPosition()
I don't think that one is true. In the demo map I even play the sound every 0.05 seconds and it sounds played correctly (human ears).
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Thanks for precious info sir Zwie (finally I spelled your name correctly!), but

I don't think that one is true. In the demo map I even play the sound every 0.05 seconds and it sounds played correctly (human ears).
I'm not perfectly sure about the length of the required delay. 0.05 seconds might be enough. Maybe it's also ping dependant.
However, very small delays like 0.00 or 0.01 seconds do not work; the sound is simply not played in those cases (checked via "GetSoundIsLoading" or "GetSoundIsPlaying" natives).
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
I wouldn't be surprised at all if it depends your hardware. That should not be ping dependant imho.
Simply because sounds are not synced by nature.
For example, if a player has turned off the sound in his wc3 options, sounds won't be played at all, same if his wc3 is minimized.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I wouldn't be surprised at all if it depends your hardware. That should not be ping dependant imho.
Simply because sounds are not synced by nature.
For example, if a player has turned off the sound in his wc3 options, sounds won't be played at all, same if his wc3 is minimized.
I think this is due to code optimization. Usually, game threads are executed by a priority list; core mechanics and inputs first, then graphics, then other fluff. That's why games still work and accept input even when there's very low FPS.
So depending on how busy the game currently is, the delay might be longer or shorter. That doesn't necessarily have to do with the hardware used. There might be engine limitations.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I agree on writing a proper Sound library, as SoundTools ( approved ) also has some flaws like:
  • Sounds played for a player only, will afterwards be unplayable for any other player. ( Volume 0 )
  • Playing that sound for another player, will make it be unplayable for all players. ( Volume 0 )
  • Sounds manually released are stopped and pushed into the recycler, but stopped again when the timer expires.
    This results in the fact, that a newly played sound my stop in the middile, if relased before
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I'm a fan of our SoundTools library, but there are unsolved issues when playing sounds
first locally then global. If I remember correctly the sound volume remains 0
for all clients except one. It's a small fix in the approved library not dramatic enough
for a replacement.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Just want to say that probably I won't work on this new sound tools anytime soon. If somebody has the motivation to build it, just do it. Working on sound library is very intriguing for me actually, since sound is one nasty thing to handle but has a lot of utilizations, it could be one of the most useful scripts ever, srsly it's very intriguing. But well, I'm working on maps now and it's a bit more intriguing. :p
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Update. Fixed bug regarding to timer thing.

EDIT:
Is it really a must to upgrade this into a SoundTool? Couldn't it be just a system that serves only a specific purpose, which is to play sound rapidly? Don't you think a SoundTool that preserves 4 sound handles at once on creation is bad?
 
Last edited:
Level 14
Joined
Jul 1, 2008
Messages
1,314
Hi Quilnez, this seems to be really great, I will try to use it, since it is smaller than SoundTools and is good for simulating multiple thunder effects for example.

So I would say, this actually has a right to exist next to the bigger SoundTools.

so you are sure it works at 0.01? DO you have to set 12700 as the fade parameters of the sounds, or does it also work with other values?

Thank you!
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Thunder sounds for your Harvest Moon, ey?

Well, why do you want to play a sound in such a very small fraction of time anyway? But yeah it should work.
It should work as well with any fade time I guess, as long as the rate is not too low (slow fade in). If it's too low I'm afraid the sound will be replayed before anything's been heard (in case it's played with such extreme play rate like 0.01 or so).

I hope this can help in your project :) Good luck... Good luck in beating my Destiny Crude project : P (it's harvest moon based too xD)

Thanks for the support!
 
Last edited:
Level 14
Joined
Jul 1, 2008
Messages
1,314
Yep, thats what I will use it for ;)
I made something like this but your code is way better so I would like to exchange it ..
Thanks for the answer about your system, I asked because of a gattling gun minimap in that harvest moon map, that I want to enable to fire faster. but I guess I should not exceed 0.1 anyway. Have gotten "greedy" when I saw this :p

I hope this can help in your project :) Good luck... Good luck in beating my Destiny Crude project : P (it's harvest moon based too xD)

thank you once more - and good luck to you too. :)

Anyway I dont think my game can compete with your project, but on the other hand, it is not my intention. Actually I was pretty happy to see your project in the section there, because Harvest Moon games are great and it is really cool to see another approach to it!

oh and I know you were not serious by the way, so let the harvesting battle beginn xD!
 
An awesome library to play sounds rapidly and simultaneously, which is in my opinion the most asked feature when using sound libraries.

For people who need full sound fixes can still use [System] SoundTools which deals with problems with sound pitch, cone angle, cutoff distance, etc, for 3D sounds.
The fixes by SoundTools are nice, but also makes it a lot more complicated and uses custom approaches, which makes me feel RapidSound has a right to exist next to it, as leighter and straight forward library for just playing sounds.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,516
Still desyncs.

I suggest simply leaving a warning that the library should not be used for long internal sound files - in particular ones with human voices.

If you want to avoid the possibility of a desync in any context, then you have to destroy sound handles with KillSoundWhenDone. Note though that this might have a small, unavoidable leak (the handle ID is presumably never destroyed).

Edit: see Cokemonkey11 / microrunnertd / source / wurst / systems / Sounds.jurst — Bitbucket
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Really, the only part related to the timer is:
JASS:
           if TimerGetRemaining(.tmr[.dex]) == 0 then
               call StopSound(.snd[.dex], false, false)
               call SetSoundPosition(.snd[.dex], x, y, z)
               call SetSoundVolume(.snd[.dex], volume)
               call StartSound(.snd[.dex])
               call TimerStart(.tmr[.dex], .dur, false, null)
           endif
I really have no idea how would it cause desync (disconnect)
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
You realize that his condition will hold true at different times on different machines, right?

How do you not see that it's a desync hazard?
Lol how many times I have to tell you, I'm fully aware it may desync if the timer has different interval. But it it will not cause disconnect. So I think it's not a problem.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
As far as I know, things will only disconnect if we create agents/handles locally (and some other cases of course). I will take my words back if only you have tested this library specifically in that circumstance and able to reproduce the disconnect (desync).

Things like modifying sound properties and starting timers locally do not cause desync. And it's also not merely supposition but 100% based on experiences. If they do cause desync, my Glideon and Coconut Party map wouldn't be multiplayer maps since the first place.

And in this library, specifically, the asynchronous timers will only happen merely in splits of miliseconds, then things will be back synchronized, as soon as the timer is expired. And why do you want to play a "voice" sound so rapidly anyway? You want to make a rap music or something? You are only looking for problems, which are not even sure to exist. Gz
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
the game will actually desync in any invariance of agents data, not just creation, for instance moving unit locally will desync, firing trigger locally will desync, firing timer locally will desync etc etc.

Afaik game desyncs the first moment that you access agent in any way that is not synced, so if you call GetTimerRemaining with the timer having different remaining time on two clients, you have instant desync, and it doesnt matter if it happens in 2 minute timer or 2 microsecond timer
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
the game will actually desync in any invariance of agents data, not just creation, for instance moving unit locally will desync, firing trigger locally will desync, firing timer locally will desync etc etc.

Afaik game desyncs the first moment that you access agent in any way that is not synced, so if you call GetTimerRemaining with the timer having different remaining time on two clients, you have instant desync, and it doesnt matter if it happens in 2 minute timer or 2 microsecond timer
Sorry but once my map Glideon did have timers running locally (even tho I have changed the approach in more recent versions). And been tested in multiplayer mode for hours and no desync ever happened. Unless my eyes were deceiving me for months. LOL in fact I run hundreds of timers asynchronously (ask BPower, once I told him the story lol), I mean literally hundreds. Yeah I know it's terrible but heck, it did not desync ;)

so if you call GetTimerRemaining with the timer having different remaining time on two clients, you have instant desync
Well now, afaik, it's really depend on what actions you do afterward. It's basically like getting camera property values where it will return different value each player, and still do not desync.
 
Last edited:
Top