[System] SoundTools

Actually, I realized that my run function returns the sound played O-o
You can do this:

JASS:
local sound s = MySoundObject.run()
call SetSoundPitch(s, 0.8)
call SetSoundVolume(s, 0.2)

Those values in the struct aren't constants, just default values :p

edit
I have an idea, I can write a function called runEx() and give you the ability to pass in custom sound data so you don't have to waste performance with the sound function calls :D
 
Level 18
Joined
Oct 12, 2007
Messages
1,822
I'm trying to use this in my map, but it doesn't want to save.
I get this error. And who knows there is another error after I fix this.
Am I doing anything wrong? Or did I miss a requirement or someting?
 

Attachments

  • error.png
    error.png
    185.2 KB · Views: 174
Level 18
Joined
Oct 12, 2007
Messages
1,822
Hmm I had Table 3.0 in the map already. But that might be like 2 years old by now.

Doesn't it fckup other things in the map if I update Table right now?
 
Level 16
Joined
Aug 7, 2009
Messages
1,407
I presume that's Vexorian's Table - well, it won't screw up anything, though if you use it often like I do, it'll take you some time to update all of your resources. But at the end of the day it's worth it, as it's cleaner, and propably faster as well.

If I recall correctly there's a backwards compatible module attached to its post though, so if you implement that as well you won't have to change anything.
 
Level 18
Joined
Oct 12, 2007
Messages
1,822
I'm using this system at the moment and learning how to work with it.
I got a spell in my map that will let the hero do 5 melee swings within 1 second while he is paused with increased animation speed. So I want to put the sound inside the timer, and I would like it to play one out of three random combat sounds; medium axe chop flesh.
I thought I had it already, but for some reason it doesn't play the sounds anymore if I use the skill a second time.

This is what I've got in the init trigger of the spell: (Sound1, Sound2 and Sound3 are private global integers)
JASS:
set Sound1 = NewSound("Sound\\Units\\Combat\\MetalMediumChopFlesh1.wav", 522, false, true)
set Sound2 = NewSound("Sound\\Units\\Combat\\MetalMediumChopFlesh2.wav", 720, false, true)
set Sound3 = NewSound("Sound\\Units\\Combat\\MetalMediumChopFlesh3.wav", 723, false, true)

This is what I've got inside the loop:
JASS:
if GetRandomInt(1,3) == 1 then
    call RunSoundOnUnit(Sound1, Dat.t)
elseif GetRandomInt(1,2) == 1 then
    call RunSoundOnUnit(Sound2, Dat.t)
else
    call RunSoundOnUnit(Sound3, Dat.t)
endif
 
Okay, it seems that SoundTools fails to play the same sound instance twice.

I don't know how this just came up.

._.

edit
Updated to v3.0.0.0:

  • Fixed sound playing bug.
  • Added extra debug message.
  • Changed system structure.
  • Removed functions that counted the number of currently playing sounds. (Completely useless)
 
Last edited:
Okay, it seems that SoundTools fails to play the same sound instance twice.

I don't know how this just came up.

._.

edit
Updated to v3.0.0.0:

  • Fixed sound playing bug.
  • Added extra debug message.
  • Changed system structure.
  • Removed functions that counted the number of currently playing sounds. (Completely useless)
Hmm... I don't know how you do it, but I had trouble with all the sound limitations when creating my TabReader aswell.

Those were my observations and workarounds:

Problem: WC3 doesn't allow to play the same sound filepath (regardless of handle) twice in less than 0,1 seconds (only different paths). This is hardcoded and can not be avoided.
Fix: There is only one way to partially fix this: by taking the old sound and using SetSoundPosition to put it back to the beginning.

Problem: WC3 doesn't allow simultanous running of the same sound handle.
Fix: Simply create another soundhandle (up to 4 sound handles of the same filepath possible).

Problem: WC3 doesn't allow simultanous running of the same filepath more than 4 times.
Fix: Only possible solutions is importing the same sound again on a new filepath.

Problem: WC3 doesn't allow simultanous running of more than 16 sounds altogether.
Fix: None. I've tried everything.

Problem: Pitch shifting is buggy as hell.
Fix: Check out my pitch shifting function in the tab reader - it works 100% of the time, even if it looks weird.
 
Problem: WC3 doesn't allow to play the same sound filepath (regardless of handle) twice in less than 0,1 seconds (only different paths). This is hardcoded and can not be avoided.
Fix: There is only one way to partially fix this: by taking the old sound and using SetSoundPosition to put it back to the beginning.

Actually, that is a pretty good solution. This kind of behavior is present in a lot of applications.
I'd be adding more overhead (an extra timer), so I guess I might make this optional :/

Problem: WC3 doesn't allow simultanous running of the same sound handle.
Fix: Simply create another soundhandle (up to 4 sound handles of the same filepath possible).

Well, this problem is fixed here, as I'm using one sound handle recycler for each sound type. I didn't know there can only be 4 of them =O

Problem: WC3 doesn't allow simultanous running of the same filepath more than 4 times.
Fix: Only possible solutions is importing the same sound again on a new filepath.

Problem: WC3 doesn't allow simultanous running of more than 16 sounds altogether.
Fix: None. I've tried everything.

Problem: Pitch shifting is buggy as hell.
Fix: Check out my pitch shifting function in the tab reader - it works 100% of the time, even if it looks weird.

You know, these should be documented somewhere.
At home, a few days ago, I started putting together some documents to contain all the Warcraft III knowledge that we have accumulated over the years.
I guess I could add these =o
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Hi. Just a question that should be obvious; when I use something like this:

JASS:
local Sound mySound = Sound.create(...)
mySound.runPlayer(Player(0))

Do I have to use mySound.release() or will it be cleaned up when it's finished playing?
 
You should replace all SetSoundPitch() natives in your system by this, as SetSoundPitch() is bugged when it is applied while the sound is running.
JASS:
    function SetPitch takes sound s, real pitch, real lastPitch returns real
        //due to the bugged pitch native, we need this workaround snippet in order to make it work
        if GetSoundIsPlaying(s) or GetSoundIsLoading(s) then
            call SetSoundPitch(s, 1/lastPitch)
            call SetSoundPitch(s, pitch)
            return pitch
        else
            if pitch == 1 then
                call SetSoundPitch(s, 1.0001)
                return 1.0001
            else
                call SetSoundPitch(s, pitch)
                return pitch
            endif
        endif
    endfunction
The real returned is the new pitch value of the sound. Store it to your specific sound instance to be able to pass it the next time this function runs.
 
Ah yes, thank you for reminding me about that bug.
It was awesome that you were able to find all those bugs through your production of that Tab Reader ;D
Yeah it gave me a hell of a headache.

And in the end, the poor sound engine of Warcraft III defeated the whole purpose of TabReader, as most of the mp3 compression is lost after importing a sound.
(WC3 doesn't support all sampling rate+quality combinations. Only a few are supported. If you happen to import a sound that is inbetween one of the supported combinations, the quality is reduced to the lower one, usually resulting in unwanted audio fragments - which means all imported sounds need to be high quality)
 
Level 3
Joined
Jun 26, 2013
Messages
43
Where is a wrapper for the most useful thing for sounds

// the following method must be called immediately after calling "StartSound"
native SetSoundPlayPosition takes sound soundHandle, integer millisecs returns nothing

With this native and pitch you can synthesize sustained oscillations with custom frequencies! The audio lib in wc3 can do powerful things :)
 
^ You have to input the sound itself instead of the struct instance. The method create will return the sound instance (integer), but you can get the actual sound object through any of the following methods:
JASS:
/*
*           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.
*/
Or the wrapper function equivalents.
 
Level 13
Joined
Mar 19, 2010
Messages
870
Soundtool bug?

Hi guys,

i have a problem in my map "Forsaken Bastion's Fall". I use your Soundtools and it works great BUT i spend a lot of time to check why "RunSoundForPlayer" in a loop does not work. I stopped it and created a small testmap.

Take a look and tell me why the loop don't play the sound for each player.
Start map in "debug mode".

Please help!

Reg.
 

Attachments

  • SoundTools-Test.w3m
    42.7 KB · Views: 79
Level 13
Joined
Mar 19, 2010
Messages
870
1. Trigger:

Code:
scope GameSounds initializer Init

	globals
		integer GLOBAL_SOUND_1 = 0
	endglobals

	private function Init takes nothing returns nothing
		set GLOBAL_SOUND_1 = NewSound("Sound\\Interface\\Hint.wav", 2006, false, false)
	endfunction
	
endscope

2. Trigger:

Code:
scope Test1 initializer Init

	private function Actions takes nothing returns nothing
		call RunSoundForPlayer(GLOBAL_SOUND_1, Player(0))
	endfunction

	//===========================================================================
	private function Init takes nothing returns nothing
		local trigger t = CreateTrigger()
		call TriggerRegisterPlayerChatEvent( t, Player(0), "sound 1", true )
		call TriggerAddAction( t, function Actions )
	endfunction

endscope

3. Trigger:

Code:
scope Test2 initializer Init

	private function Actions takes nothing returns nothing
		local integer i = 0
		loop
			exitwhen i > 10
			call RunSoundForPlayer(GLOBAL_SOUND_1, Player(i))
			set i = i + 1
		endloop
	endfunction

	//===========================================================================
	private function Init takes nothing returns nothing
		local trigger t = CreateTrigger()
		call TriggerRegisterPlayerChatEvent( t, Player(0), "sound 2", true )
		call TriggerAddAction( t, function Actions )
	endfunction

endscope

auf typing "sound 2" you see that:

attachment.php
 

Attachments

  • soundtools.jpg
    soundtools.jpg
    303 KB · Views: 207

Cokemonkey11

Code Reviewer
Level 27
Joined
May 9, 2006
Messages
3,414
This is my snippet for sound that I used in the past -

JASS:
library Sound requires Table
    private struct slop
        string snd
    endstruct
    
    struct Sound
        private static HandleTable preloadDB
        
        private static method preloadAfter takes nothing returns nothing
            local timer time=GetExpiredTimer()
            local slop s=preloadDB[time]
            local sound sd=CreateSound(s.snd,false,false,false,12700,12700,"")
            call SetSoundVolume(sd,1)
            call StartSound(sd)
            call KillSoundWhenDone(sd)
            call s.destroy()
            set sd=null
            call DestroyTimer(time)
            set time=null
        endmethod
        
        public static method preload takes string str returns nothing
            local timer time=CreateTimer()
            local slop s=slop.create()
            set s.snd=str
            set preloadDB[time]=s
            call TimerStart(time,.01,false,function thistype.preloadAfter)
            set time=null
        endmethod
        
        public static method play3D takes string str, real pitch, real x, real y, real z returns nothing
            local sound s = CreateSound(str,false,true,true,12700,12700,"")
            call SetSoundPosition(s,x,y,z)
            call SetSoundVolume(s,127)
            call SetSoundPitch(s,pitch)
            call StartSound(s)
            call KillSoundWhenDone(s)
            set s = null
        endmethod
		
		public static method playForPlayer takes string str, real pitch, player who returns nothing
			local sound s = CreateSound(str,false,false,false,12700,12700,"")
			if GetLocalPlayer()==who then
				call SetSoundVolume(s,127)
			else
				call SetSoundVolume(s,0)
			endif
			call SetSoundPitch(s,pitch)
			call StartSound(s)
			call KillSoundWhenDone(s)
			set s = null
		endmethod
        
		public static method play takes string str, real pitch, integer vol returns nothing
			local sound s = CreateSound(str,false,false,false,12700,12700,"")
			call SetSoundVolume(s,vol)
			call SetSoundPitch(s,pitch)
			call StartSound(s)
			call KillSoundWhenDone(s)
			set s = null
		endmethod
		
        private static method onInit takes nothing returns nothing
            set preloadDB=HandleTable.create()
        endmethod
    endstruct
endlibrary

All you have to do is preload the sound once. Why should I use your massive library?
 
Level 13
Joined
Mar 19, 2010
Messages
870
In my map i start tutorials for each player when a player picks a hero. To pick a hero he needs to run with his peon in a circle and after he do this a cinematic starts for this player and in this cinematic i play a sound for this player.

And as you can imagin not all player select its hero to the same time. So if the second player select the hero 10s later the sound needs to play later and only for him. Every player see its own Cinematic with this played sound.

You understand me?
 

Cokemonkey11

Code Reviewer
Level 27
Joined
May 9, 2006
Messages
3,414
Massive? It's mostly comments and wrappers.

If you remove all the comments and inlineable wrappers it's still 236 lines. My snippet is 70, without TimerUtils.

Also, why are you looping and playing the sound once for each player when you can play it for everyone? :v

I'm not.
 
Level 13
Joined
Mar 19, 2010
Messages
870
yes.

edit: I have a flag called "MAP_HOSTED_BY_BOT" and if this is true, i start the tutorial (cinematic) for all players to the same time and i think exaclty this is the point where the sound does not play (for nobody). This is exactly the same thing like in the test map.
But i use the same tutorial fore the case if MAP_HOSTED_BY_BOT == false, where the tutorials starts if the player do someting ( like the pick hero i mentioned ).
 
Last edited:
Top