• 🏆 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!

Local sound is so confusing

Level 9
Joined
May 24, 2016
Messages
303
I heard about sound engine is kinda changed in 1.29+, but I wanna local sound compatible with both 1.26 and modern version, So i'd better do the universal way, or wont play local sound at all.

The current way I'm using is

JASS:
if IsPlayerInForce(GetLocalPlayer(),HumanForce) then

call PlaySound(gg_snd_MySound)

endif

But I'm not sure. I can't test it myself
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,570
I heard about sound engine is kinda changed in 1.29+, but I wanna local sound compatible with both 1.26 and modern version, So i'd better do the universal way, or wont play local sound at all.

The current way I'm using is

JASS:
if IsPlayerInForce(GetLocalPlayer(),HumanForce) then

call PlaySound(gg_snd_MySound)

endif

But I'm not sure. I can't test it myself

I don't think you want to check if a Player is in a Player Group locally. Instead, you could do this:

First, add a function for playing sounds for a specific Player to your map:
vJASS:
    function SoundPlayGeneralForPlayer takes string path, real pitch, player whichPlayer, boolean isloop returns sound
        local sound snd = CreateSound(path, isloop, false, false, 10, 10, "DefaultEAXON")
        // Stuff that must be performed immediately upon creation of sounds
        call SetSoundChannel(snd, 0)
        call SetSoundVolume(snd, 127)
        call SetSoundPitch(snd, pitch)
        // The sound will have no volume for unwanted players
        if GetLocalPlayer() != whichPlayer then
            call SetSoundVolume(snd, 0)
        endif
        call StartSound(snd)
        if isloop == false then
            call KillSoundWhenDone(snd)
        endif
        return snd
    endfunction
Now we can easily reference that function with one line of code.

Now I'm assuming you want to Play a sound for all of the Human Force players:
vJASS:
    function ExampleCallback takes nothing returns nothing
        call SoundPlayGeneralForPlayer(gg_snd_MySound, 1.0, GetEnumPlayer(), false)
    endfunction

    function Example takes nothing returns nothing
        call ForForce(HumanForce, function ExampleCallback)
    endfunction
gg_snd_MySound would have to be a string that references the path of the sound, so I'm not sure if it would work here.

The great thing about working in code is that you can create "helper" functions that make your life 100x easier. You only want to do things the "hard way" once. So with this you can reference SoundPlayGeneralForPlayer() thousands of times throughout your code and it'll save you literally 10,000+ lines of code. It also makes bug fixes and adjustments very easy to do since there's only ONE core function to edit.
 
Last edited:
Level 9
Joined
May 24, 2016
Messages
303

I don't think you want to check if a Player is in a Player Group locally. Instead, you could do this:

First, add a function for playing sounds for a specific Player to your map:
vJASS:
    function SoundPlayGeneralForPlayer takes string path, real pitch, player whichPlayer, boolean isloop returns sound
        local sound snd = CreateSound(path, isloop, false, false, 10, 10, "DefaultEAXON")
        // Stuff that must be performed immediately upon creation of sounds
        call SetSoundChannel(snd, 0)
        call SetSoundVolume(snd, 127)
        call SetSoundPitch(snd, pitch)
        // The sound will have no volume for unwanted players
        if GetLocalPlayer() != whichPlayer then
            call SetSoundVolume(snd, 0)
        endif
        call StartSound(snd)
        if isloop == false then
            call KillSoundWhenDone(snd)
        endif
        return snd
    endfunction
Now we can easily reference that function with one line of code.

Now I'm assuming you want to Play a sound for all of the Human Force players:
vJASS:
    function ExampleCallback takes nothing returns nothing
        call SoundPlayGeneralForPlayer(gg_snd_MySound, 1.0, GetEnumPlayer(), false)
    endfunction

    function Example takes nothing returns nothing
        call ForForce(HumanForce, function ExampleCallback)
    endfunction
gg_snd_MySound would have to be a string that references the path of the sound, so I'm not sure if it would work here.

The great thing about working in code is that you can create "helper" functions that make your life 100x easier. You only want to do things the "hard way" once. So with this you can reference SoundPlayGeneralForPlayer() thousands of times throughout your code and it'll save you literally 10,000+ lines of code. It also makes bug fixes and adjustments very easy to do since there's only ONE core function to edit.
Thx you my friend. The one thing I don't understand quite understand, is
gg_snd_MySound would have to be a string that references the path of the sound, so I'm not sure if it would work here.
I'm not sure, that I'm using a path reference to the sound, since I only mention the name of the file itself. gg_snd_MySound is the file name I mention, and in my case I'm using the gold coin item sound, that looks like that gg_snd_GoldCoin, a default wc3 sound.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,570
Thx you my friend. The one thing I don't understand quite understand, is

I'm not sure, that I'm using a path reference to the sound, since I only mention the name of the file itself. gg_snd_MySound is the file name I mention, and in my case I'm using the gold coin item sound, that looks like that gg_snd_GoldCoin, a default wc3 sound.
Well, you can try to use that in the function's parameters, but I assume that it's a special type and not a string.

Inside of the Sound Editor you can determine the path for your sound by double clicking it (from the "Use As Sound" list). The path will be something like Human/Spells/Banish.
 
Level 9
Joined
May 24, 2016
Messages
303
Well, you can try to use that in the function's parameters, but I assume that it's a special type and not a string.

Inside of the Sound Editor you can determine the path for your sound by double clicking it (from the "Use As Sound" list). The path will be something like Human/Spells/Banish.
Did I understand you right, you suggest better using a path to file than the filename itself, right?
If so, Ill do that way. Thx you my friend.
 
Last edited:
Level 9
Joined
May 24, 2016
Messages
303
Well, you can try to use that in the function's parameters, but I assume that it's a special type and not a string.

Inside of the Sound Editor you can determine the path for your sound by double clicking it (from the "Use As Sound" list). The path will be something like Human/Spells/Banish.
I have a problem, it does not work, I dont hear anything (yeah im in "HumanForce" playerforce)
JASS:
function SoundPlayGeneralForPlayer takes string path, real pitch, player whichPlayer, boolean isloop returns sound
        local sound snd = CreateSound(path, isloop, false, false, 10, 10, "DefaultEAXON")
        // Stuff that must be performed immediately upon creation of sounds
        call SetSoundChannel(snd, 0)
        call SetSoundVolume(snd, 127)
        call SetSoundPitch(snd, pitch)
        // The sound will have no volume for unwanted players
        if GetLocalPlayer() != whichPlayer then
            call SetSoundVolume(snd, 0)
        endif
        call StartSound(snd)
        if isloop == false then
            call KillSoundWhenDone(snd)
        endif
        return snd
    endfunction

function GoldSoundCallback takes nothing returns nothing
        call SoundPlayGeneralForPlayer("Abilities\\Spells\\Items\\ResourceItems\\ReceiveGold", 1.0, GetEnumPlayer(), false)
    endfunction

// the trigger that activates it right below
...
call ForForce(HumanForce, function GoldSoundCallback)
..
You can check it out, default wc3 sound ReceiveGold is used as example
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,570
I have a problem, it does not work, I dont hear anything (yeah im in "HumanForce" playerforce)
JASS:
function SoundPlayGeneralForPlayer takes string path, real pitch, player whichPlayer, boolean isloop returns sound
        local sound snd = CreateSound(path, isloop, false, false, 10, 10, "DefaultEAXON")
        // Stuff that must be performed immediately upon creation of sounds
        call SetSoundChannel(snd, 0)
        call SetSoundVolume(snd, 127)
        call SetSoundPitch(snd, pitch)
        // The sound will have no volume for unwanted players
        if GetLocalPlayer() != whichPlayer then
            call SetSoundVolume(snd, 0)
        endif
        call StartSound(snd)
        if isloop == false then
            call KillSoundWhenDone(snd)
        endif
        return snd
    endfunction

function GoldSoundCallback takes nothing returns nothing
        call SoundPlayGeneralForPlayer("Abilities\\Spells\\Items\\ResourceItems\\ReceiveGold", 1.0, GetEnumPlayer(), false)
    endfunction

// the trigger that activates it right below
...
call ForForce(HumanForce, function GoldSoundCallback)
..
You can check it out, default wc3 sound ReceiveGold is used as example
Make sure it's not set as a 3D sound:
1702649777402.png


For 3D sounds you need a new function that manages the position and distance cutoffs. You can see in the CreateSound() function it has booleans for 3D settings:
vJASS:
CreateSound(fileName, looping, is3D, stopwhenoutofrange, fadeInRate, fadeOutRate, eaxSetting)
is3D, stopwhenoutofrange

So with those in mind, here's a working 3D version:
vJASS:
function Sound3DPlayGeneralForPlayer takes string path, real pitch, player whichPlayer, boolean isloop, real x, real y, real z returns sound
        local sound snd = CreateSound(path, isloop, true, true, 10, 10, "DefaultEAXON")
        // Stuff that must be performed immediately upon creation of sounds
        call SetSoundChannel(snd, 0)
        call SetSoundVolume(snd, 127)
        call SetSoundPitch(snd, pitch)
        call SetSoundPosition(snd, x, y, z)
        call SetSoundDistances(snd, 750, 3000)
        call SetSoundDistanceCutoff(snd, 3000)
        // The sound will have no volume for unwanted players
        if GetLocalPlayer() != whichPlayer then
            call SetSoundVolume(snd, 0)
        endif
        call StartSound(snd)
        if isloop == false then
            call KillSoundWhenDone(snd)
        endif
        return snd
endfunction
For this one you would keep 3D Sound enabled inside of the Sound Editor. Note that you may want to make new parameters for the Distance settings, at the moment I'm using the default values of 750 and 3000.
 

Attachments

  • Sound Test.w3m
    18.1 KB · Views: 0
Last edited:
Level 19
Joined
Jan 3, 2022
Messages
320
I have a problem, it does not work, I dont hear anything (yeah im in "HumanForce" playerforce)
How to:
1. Restart game and launch the map / Open editor and F12 to playtest
2. Sound doesn't play on first map load in v1.27.0 (it does in 1.32.10)
3. 1.27: Restarting the map in any way will play the sound properly

Conclusion: it was fixed between 1.27.0 and 1.32.10
source
I haven't looked further, maybe it's a thing where Preload is required.

I don't think you want to check if a Player is in a Player Group locally. Instead, you could do this:
JASS:
if IsPlayerInForce(GetLocalPlayer(),HumanForce) then
I believe he used the condition correctly, as long as you remember that the THEN body is supposed to be sync-safe too. See how it's used in Blizzard.j: DisplayTextToForce (notably a constant handle 'player' is created only for some of the players).
Also if you wanted to play a sound to a group of players, creating handles and calling that helper function will be a lot of overhead. I don't know if it would cause issues.

// Stuff that must be performed immediately upon creation of sounds
I don't think these calls are necessary, unless you need to change the sound options. Especially for 3D sounds to set their position.

Make sure it's not set as a 3D sound:
I think these sound options only matter for variables that are "gg_snd_...name", i.e. GUI Trigger usage. But yes, @ToldYouSo uses it through GUI Sound Editor: "gg_snd_MySound"

I haven't touched on the Sound API beyond the few comments, I can't help further in this discussion.

My suggestion is to devote some time to test everything around sounds playing in 1.26 and latest Reforged and write it down in jassdoc (if you want to get it right).
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,570
Sorry if my last response was unclear but everything I suggested was after I had tested it (see test map attached) and had gotten it working. His exact issue was that he was playing a 3D sound using a function setup for non-3D sounds. In my original post I only suggested a solution for non-3D sounds because I forgot that you had to make the distinction.

But some of what you said may be true, I'm no expert on it. I always suggest using GetLocalPlayer() in a way that I know works as to avoid desyncs.

I imagine a more efficient solution would be to play the sound once for all players but have a variable for the volume which is adjusted locally. I assume he wants to play this sound after some major game event, in which case I doubt performance is even a factor. However, if it were for like custom footsteps or something that happens very often then I would definitely suggest a more efficient solution.
 
Last edited:
Level 9
Joined
May 24, 2016
Messages
303
I imagine a more efficient solution would be to play the sound once for all players but have a variable for the volume which is adjusted locally. I assume he wants to play this sound after some major game event,
So, call forforce and using that "layer" function GoldSoundCallback is not needed? Yeah the sound might be every 5 minds, so I dont wanna overload engine with any unneeded actions.
 
Level 9
Joined
May 24, 2016
Messages
303
I have not set gold coin as 3d sound, so IDK why it is not working (but your example of using 3d sound may come in handy, thx). Maybe I should really try preload function for the sound, IDK, I currently test my map on 1.26


I did not mention file format in a path string, so maybe that is the point? Maybe I should have used "Abilities\\Spells\\Items\\ResourceItems\\ReceiveGold.flac" as the path string for the function? (can't test it right now)


As for "overhead", is it possible to optimize it like that?
JASS:
// I place it in core trigger
local sound snd
local integer i = 0
     
loop
exitwhen i > 3 // the "HumanForce" actually consists of 4 players, from 0 to 4 player id
set snd = CreateSound("Abilities\\Spells\\Items\\ResourceItems\\ReceiveGold.flac", false, false, false, 10, 10, "DefaultEAXON")
call SetSoundVolume(snd, 127)
call SetSoundChannel(snd, 0)
       
call SetSoundPitch(snd, 1.0)
        if GetLocalPlayer() != Player(i) then
            call SetSoundVolume(snd, 0)
        endif
call StartSound(snd)

call KillSoundWhenDone(snd)
set i = i + 1
endloop
     
      //  if isloop == false then
       //     call KillSoundWhenDone(snd)
      //  endif
       // return snd
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,570
So, call forforce and using that "layer" function GoldSoundCallback is not needed? Yeah the sound might be every 5 minds, so I dont wanna overload engine with any unneeded actions.
Every 5 minutes is FAR from overloading, trust me, it's a non-factor outside of doing it 20 times per second.

Your solution is still creating the sound multiple times. You could try this:
vJASS:
function YourFunction1 takes nothing returns nothing
    local integer i = 0
    local real vol = 0.0
    local sound snd = CreateSound(path, false, true, true, 10, 10, "DefaultEAXON")
    call SetSoundChannel(snd, 0)
    call SetSoundPitch(snd, 1.0)

    // The sound will have no volume for unwanted players
    loop
        if GetLocalPlayer() == Player(i) then
            set vol = 127.0
        endif
        set i = i + 1
        exitwhen i >= 4
    endloop

    call SetSoundVolume(snd, vol)
    call StartSound(snd)
    if isloop == false then
        call KillSoundWhenDone(snd)
    endif
    set snd = null
endfunction
Or this could work assuming it doesn't desync. This would be useful if any Player could be in the HumanForce:
vJASS:
function YourFunction2 takes nothing returns nothing
    local integer i = 0
    local real vol = 0.0
    local sound snd = CreateSound(path, false, true, true, 10, 10, "DefaultEAXON")
    call SetSoundChannel(snd, 0)
    call SetSoundPitch(snd, 1.0)

    // The sound will have no volume for unwanted players
    loop
        if IsPlayerInForce(GetLocalPlayer(), HumanForce) then
            set vol = 127.0
        endif
        set i = i + 1
        exitwhen i >= 24
    endloop

    call SetSoundVolume(snd, vol)
    call StartSound(snd)
    if isloop == false then
        call KillSoundWhenDone(snd)
    endif
    set snd = null
endfunction
And of course account for whether it's a 3D sound or not.
 
Last edited:
Level 9
Joined
May 24, 2016
Messages
303
Every 5 minutes is FAR from overloading, trust me, it's a non-factor outside of doing it 20 times per second.

Your solution is still creating the sound multiple times.
Do you know should I actually use file format in a path string, like I mentioned before? I ask it because of timezone differences, I can open wc3 and test it only when you actually away busy (like many others here in hive)
 
Top