Play sound needs some work.
Moderator: Moderators
I will know as soon as Spring gets done re-doing the hashes.
<waits>
Yes, it works.
However!
1. With 100 Units running this, I saw a 10FPS slowdown. Lemme quit everything else that might be eating CPU, and try this again. I recently set all of my graphics drivers back to nVidia's terrible defaults, too, during the whole, "let's get Blender to work, at all" debacle.
2. I noticed something very odd indeed: I am testing on NanoArena, which is voidwater, but dev console says water is eating 10% of total CPU. Strange...
3. I have lots of other LUA code running at this point, to test stuff, which is probably poorly-integrated, which is almost certainly getting in the way of good results.
4. With 100 of these running, I noticed some very nasty noise from the output, probably due to the fact that in COB, they're all running in lockstep, timewise, when I select a group that big and say, "go somewhere". I'll look at that. I can get around the problem by adding slight random variation, but that would require a seperate loop for the animation technique I'm using, and would lose some efficiency.
5. I have not thoroughly tested whether this can cause a lock state by interrupting script operation, whatever just happened during the test took FPS too low for me to pay any attention.
More (hopefully, much better) results when I clean things up and set up a proper run here.
<waits>
Yes, it works.
However!
1. With 100 Units running this, I saw a 10FPS slowdown. Lemme quit everything else that might be eating CPU, and try this again. I recently set all of my graphics drivers back to nVidia's terrible defaults, too, during the whole, "let's get Blender to work, at all" debacle.
2. I noticed something very odd indeed: I am testing on NanoArena, which is voidwater, but dev console says water is eating 10% of total CPU. Strange...
3. I have lots of other LUA code running at this point, to test stuff, which is probably poorly-integrated, which is almost certainly getting in the way of good results.
4. With 100 of these running, I noticed some very nasty noise from the output, probably due to the fact that in COB, they're all running in lockstep, timewise, when I select a group that big and say, "go somewhere". I'll look at that. I can get around the problem by adding slight random variation, but that would require a seperate loop for the animation technique I'm using, and would lose some efficiency.
5. I have not thoroughly tested whether this can cause a lock state by interrupting script operation, whatever just happened during the test took FPS too low for me to pay any attention.
More (hopefully, much better) results when I clean things up and set up a proper run here.
Hrmm. Something, no doubt one of my crappier pieces of LUA, is drawing huge amounts of CPU, with a remarkable pause every SlowUpdate.
Lemme clean out this filthy house for a minute. I don't think your code is at all to blame, because it should start/stop when the Units do, but it's clearly something else- probably one of several chunks of experimental wreckage I'm playing with...
Lemme clean out this filthy house for a minute. I don't think your code is at all to blame, because it should start/stop when the Units do, but it's clearly something else- probably one of several chunks of experimental wreckage I'm playing with...
Better, with all of the LuaRules Gadgets gone. Maybe 2 FPS cost, all other factors taken into consideration, and most of that's probably pushing the sounds, not the script. I'd say it's efficient enough to use, with reasonable care.
Smoth, to answer your questions, finally:
1. Here is (no-doubt crappily-written) LUA, to put into LuaCob's root, as main.lua, that will allow you to make use of this variation on play-sound:
2. You need to also have a file labeled, "LuaSounds.h" in the LuaCob Root. Really, like a useless limb here, given that I took out the handy dumpfile stuff, but meh, you don't really need it.
3. You should add these three lines to whatever header (such as my STANDARD_COMMANDS_GPL.h, from NanoBlobs, which will soon be STANDARD_COMMANDS_CC.h, so that we can all use it and quit maintaining different main headers for something that basic and trivial because of "fear of GPL" in a header file) you normally use when working with BOS (or manually add it to every BOS specifically, although it will have a nil effect on efficiency, so I wouldn't bother):
Lastly... to call the event whereever you need it, just write:
Where, in this case, the first argument corresponds to the variable value "name", the second argument is VolumeScale. I suspect you can use the XYZ arguments as well, although it's hardly necessary, imo, except for maybe a Gaia Unit that is used for simple sound control all over a map 
... that's all there is to it, basically. Not bad at all, now that it bloody works. Now I just need to solve the clipping issue, by looking at timing, and figure out why my FPS is still sucking major buttchunks... maybe I need to go to nVidia's site, or remove this driver version... it's been more trouble than it was worth, and it didn't help me get Blender working, either.
Smoth, to answer your questions, finally:
1. Here is (no-doubt crappily-written) LUA, to put into LuaCob's root, as main.lua, that will allow you to make use of this variation on play-sound:
Code: Select all
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- file: LuaCob/main.lua
-- brief: lua based sound calls for COB scripts
-- author: Dave Rodgers, with 'orrible 'acks by Argh
--
-- Copyright (C) 2007.
-- Licensed under the terms of the GNU GPL, v2 or later.
--
--------------------------------------------------------------------------------
AllowUnsafeChanges("USE AT YOUR OWN PERIL")
--
-- Allows the use of the following call-outs when
-- not in the GameFrame() or GotChatMsg() call-ins:
--
-- CreateUnit()
-- DestroyUnit()
-- TransferUnit()
-- CreateFeature()
-- DestroyFeature()
-- TransferFeature()
-- GiveOrderToUnit()
-- GiveOrderToUnitMap()
-- GiveOrderToUnitArray()
-- GiveOrderArrayToUnitMap()
-- GiveOrderArrayToUnitArray()
--
-- *** The string argument must be an exact match ***
do -- wrap print() in a closure
local origPrint = print
print = function(...)
if (arg[1]) then
arg[1] = Script.GetName() .. ': ' .. tostring(arg[1])
end
origPrint(unpack(arg))
end
end
function tprint(t)
for k,v in pairs(t) do
Spring.Echo(k, tostring(v))
end
end
if (not Spring.IsDevLuaEnabled()) then
VFS.Include(Script.GetName() .. "/gadgets.lua", nil, VFS.ZIP_ONLY)
Spring.Echo("LUACOB-MAIN (GADGETS)")
else
VFS.Include(Script.GetName() .. "/gadgets.lua", nil, VFS.RAW_ONLY)
Spring.Echo("LUACOB-MAIN (GADGETS) -- DEVMODE")
end
--------------------------------------------------------------------------------
--
-- Provides the following COB calls:
--
-- PlayUnitSound (sndID [, volume])
-- PlayLocalSound(sndID [, volume])
-- PlayWorldSound(sndID, x, y, z [, volume])
--
--
-- Note 1: [...] means optional
--
-- Note 2: this script loads its sound mapping from 'Units/LuaSounds.h'
--
-- Note 3: volume is a 0 to 100 number for local sounds,
-- but values higher than 100 do make a difference
-- for global sounds
--
-- Note 4: the unitID for PlayUnitSound does not have to be
-- explicitly passed by the COB script, it is done
-- automatically
--
--------------------------------------------------------------------------------
Spring.Echo('LuaCob: wired for sound')
local volumeScale = 0.01 -- percent
local soundMap = {}
-- parse the sound definition file
do
local soundFile = 'LuaCob/LuaSounds.h' -- for .devlua testing, please note, this is NOT how this code loads sounds
local text = VFS.LoadFile(soundFile) -- this is where it actually gets the sound list- from Spring's generated sound-list
if (text == nil) then
Spring.Echo('LuaCob: missing ' .. soundFile)
Script.Kill()
return
end
local fmt = '#define%s+%S+%s+(%d+)%s+/%*%s*(%S+)%s*%*/' --parses the string table, arranges *alphanumerically*, so 0001A is before 001A
for id, name in string.gfind(text, fmt) do
soundMap[tonumber(id)] = name
end
end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function SoundCheck(...)
local argCount = table.getn(arg)
if (argCount < 1) then
return
end
local name = soundMap[arg[1]]
if (name == nil) then
return
end
return name, argCount
end
function PlayUnitSound(unitID, unitDefID, unitTeam, ...) -- plays localized, team-only sound (I hope, gotta test with another person)
local name, args = SoundCheck(unpack(arg))-- unpack probably refers to COBHandler's unpack function, for doing number conversion here
if (name == nil) then return false end -- it's safe, you won't crash Spring if you screwed up
local x, y, z = Spring.GetUnitPosition(unitID)
if (x == nil) then return false end -- if it can't find your Unit in the game world, it won't play, dunno why we need this
if (args == 1) then
Spring.PlaySoundFile(name, 1.0, x, y, z) -- here we play the sound file N, where N == name, which was generated in SoundMap()
else
Spring.PlaySoundFile(name, arg[2] * volumeScale, x, y, z) -- here's the only real problem, volumeScale is still a bit nasty imo
end
return true
end
function PlayLocalSound(unitID, unitDefID, unitTeam, ...) -- plays localized, universal sound. Good for Krogoth-stomps ;-)
local name, args = SoundCheck(unpack(arg))
if (name == nil) then return false end
if (args == 1) then
Spring.PlaySoundFile(name)
else
Spring.PlaySoundFile(name, arg[2] * volumeScale + 50)
end
return true
end
function PlayWorldSound(unitID, unitDefID, unitTeam, ...) -- plays localized, world sound. Smoth, you know what I want to do with this ;-)
local name, args = SoundCheck(unpack(arg))
if (name == nil) then return false end
if (args <= 4) then return false end
if (args == 4) then
Spring.PlaySoundFile(name, 1.0, arg[2], arg[3], arg[4])
else
Spring.PlaySoundFile(name, arg[5] * volumeScale, arg[2], arg[3], arg[4])
end
return true
end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
3. You should add these three lines to whatever header (such as my STANDARD_COMMANDS_GPL.h, from NanoBlobs, which will soon be STANDARD_COMMANDS_CC.h, so that we can all use it and quit maintaining different main headers for something that basic and trivial because of "fear of GPL" in a header file) you normally use when working with BOS (or manually add it to every BOS specifically, although it will have a nil effect on efficiency, so I wouldn't bother):
Code: Select all
lua_PlayUnitSound() { return 0; }
lua_PlayLocalSound() { return 0; }
lua_PlayWorldSound() { return 0; }
Code: Select all
call-script lua_PlayUnitSound(1,100);

... that's all there is to it, basically. Not bad at all, now that it bloody works. Now I just need to solve the clipping issue, by looking at timing, and figure out why my FPS is still sucking major buttchunks... maybe I need to go to nVidia's site, or remove this driver version... it's been more trouble than it was worth, and it didn't help me get Blender working, either.
Last edited by Argh on 24 Aug 2007, 07:42, edited 1 time in total.
Careful with benchmarks regarding sound, playing lots of sounds simultaneously, especially with an on-board sound card, can put quite some load on the CPU simply by loading it with sound processing. This doesn't necessarily mean that Lua is slow, it can just mean that 100 sounds simultaneously give you quite a CPU hit (which is normal I think, often limits for simultaneous sounds are in the 8-32 range).
Hmm looks like all this does is give me volume and world or local control over the sounds. Doesn't really solve the sound sync problem.
BEFORE SOMEONE SAYS it AGAIN, randomized time on sounds that are made with every footstep on a game with mostly bipedal units is not aceptable. The animations and the sounds need to be in sync.
*sighs*
/me waves hand this is not the solution I was looking for.
BEFORE SOMEONE SAYS it AGAIN, randomized time on sounds that are made with every footstep on a game with mostly bipedal units is not aceptable. The animations and the sounds need to be in sync.
*sighs*
/me waves hand this is not the solution I was looking for.
No, it gives you user level sounds, world level sounds, and map level sounds, which aren't quite the same things.
You can vary the sounds by up to 35 milliseconds (i.e., just over a frame), and it's not at all noticable, really, and I actually feel like it's slightly more naturally randomized. IRL, our footsteps aren't perfectly synchronized. Even marching soldiers, who are trained carefully to march at exactly the same pace, aren't perfect, which is why that sound is so distinctive in terms of volume.
Any more than 50, it starts feeling strange, over 100, it doesn't work any more. But 30 milliseconds is more than enough.
At any rate, I'm sorry, but you wanted to know whether it worked, and yes it does, and whether it's reasonably efficient, and yes, it is.
I find the randomizer approach acceptable, and I would honestly suggest you test it, before rejecting it.
Another possible suggestion I have is that you could use a modified version of all this to play back randomized sounds that suggest what's going on- a clink here, an engine grumble there... without actually approaching the real complexity of the events IRL. I intend to have stompy guys with stompy, messy, noisy walk-patterns, tho
You can vary the sounds by up to 35 milliseconds (i.e., just over a frame), and it's not at all noticable, really, and I actually feel like it's slightly more naturally randomized. IRL, our footsteps aren't perfectly synchronized. Even marching soldiers, who are trained carefully to march at exactly the same pace, aren't perfect, which is why that sound is so distinctive in terms of volume.
Any more than 50, it starts feeling strange, over 100, it doesn't work any more. But 30 milliseconds is more than enough.
At any rate, I'm sorry, but you wanted to know whether it worked, and yes it does, and whether it's reasonably efficient, and yes, it is.
I find the randomizer approach acceptable, and I would honestly suggest you test it, before rejecting it.
Another possible suggestion I have is that you could use a modified version of all this to play back randomized sounds that suggest what's going on- a clink here, an engine grumble there... without actually approaching the real complexity of the events IRL. I intend to have stompy guys with stompy, messy, noisy walk-patterns, tho

I will give it a try later on. I am in the middle of a weapons functionality overhaul. I am just worried about having something like that running in lua. I am not sure how much lua can do per game cycle and am afraid in the future with this being in there I may overwork lua and cause odd glitches. That is why I am skeptical of the lua solution.
However, because you say it works I will give it a whirl. I just cannot do it at this moment as I started work on another part. However, when I get it tested I will respond with notes on it.
However, because you say it works I will give it a whirl. I just cannot do it at this moment as I started work on another part. However, when I get it tested I will respond with notes on it.
!@##!!!
PlayUnitSound does not result in the correct behavior. Tested with AI players, and sure enough, I can hear the footstomps of their mecha, as well as my own, so my time investment in this has been for nil
PlayUnitSound needs to be player-only. PlayLocalSound should be used for things we are OK with everybody hearing.
PlayUnitSound does not result in the correct behavior. Tested with AI players, and sure enough, I can hear the footstomps of their mecha, as well as my own, so my time investment in this has been for nil

PlayUnitSound needs to be player-only. PlayLocalSound should be used for things we are OK with everybody hearing.
PlayUnitSound() is behaving correctly, just not the way that you want it
to. Had I called it PlayUnitSoundInLocalLos(), then you could consider it
to be behaving incorrectly.
Luckily, it's a script that you can change. You'll have to send it to the
unsynced side to block playing sounds for the local player. The two
calls that you'll want are:
SendToUnsynced()
RecvFromSynced()
(the original code that I gave to the CA guys has examples in it)
Once the message is received on the unsynced side, you can use the
local player's allyteam information to determine whether or not the sound
should be played.
to. Had I called it PlayUnitSoundInLocalLos(), then you could consider it
to be behaving incorrectly.
Luckily, it's a script that you can change. You'll have to send it to the
unsynced side to block playing sounds for the local player. The two
calls that you'll want are:
SendToUnsynced()
RecvFromSynced()
(the original code that I gave to the CA guys has examples in it)
Once the message is received on the unsynced side, you can use the
local player's allyteam information to determine whether or not the sound
should be played.
Checked, using my handy grep parser, and found SendToUnsynced()... let's see, here:
Ok... this looks like it's just unpacking arguments from a Gadget, which is not really what I want here. Moreover, how do I apply it to this situation? Do I pass the args from, say:
To something like this?
And that's all that's necessary, to make it unsynced? Then it's down to a check against whether the player is allied?
Code: Select all
local function RecvFromSynced(...)
if (type(arg[1]) == 'string') then
-- a raw sync msg
local callInfoList = syncActions[arg[1]]
if (callInfoList == nil) then
return false
end
for i,callInfo in ipairs(callInfoList) do
local func = callInfo[1]
-- local gadget = callInfo[2]
if (func(unpack(arg))) then
return true
end
end
return false
end
Code: Select all
function PlayUnitSoundInLocalLos(unitID, unitDefID, unitTeam, ...) -- plays localized, team-only sound
local name, args = SoundCheck(unpack(arg))-- unpacking COB number
if (name == nil) then return false end -- it's safe, you won't crash Spring if you screwed up
end
return true
end
Code: Select all
local function RecvFromSynced(...)
local x, y, z = Spring.GetUnitPosition(unitID)
if (x == nil) then return false end
if (args == 1) then
Spring.PlaySoundFile(name, 1.0, x, y, z)
else
Spring.PlaySoundFile(name, arg[2] * volumeScale, x, y, z)
end
Ok, so with Actions.lua's definitions of Sync / Unsync stuff, we can write Gadgets to do either. I've cleared out the cloak-shield Gadget.
Now what? Can I just move my slight modification of Trepan's sound code to the UNSYNCED section, and since it's not SYNC, it will do what I need it to?
Code: Select all
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- file: LocalTeamSound.lua
-- brief: Adds local, user-only sounds to Units
-- author: Argh
--
-- Copyright (C) 2007.
-- Licensed under the terms of the GNU GPL, v2 or later.
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function gadget:GetInfo()
return {
name = "LocalTeamSound",
desc = "Adds local, user-only sounds to Units",
author = "Argh",
date = "May 02, 2007",
license = "GNU GPL, v2 or later",
layer = 1,
enabled = true -- loaded by default?
}
end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- FIXME: (TODO)
-- - wait for UnitFinished() before allowing cloak_shield?
-- - don't allow state changes during pauses (tied to the above)
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- Proposed Command ID Ranges:
--
-- all negative: Engine (build commands)
-- 0 - 999: Engine
-- 1000 - 9999: Group AI
-- 10000 - 19999: LuaUI
-- 20000 - 29999: LuaCob
-- 30000 - 39999: LuaRules
--
local CMD_CLOAK_SHIELD = 32101
local SYNCSTR = "unit_cloak_shield"
--------------------------------------------------------------------------------
-- COMMON
--------------------------------------------------------------------------------
if (gadgetHandler:IsSyncedCode()) then
--------------------------------------------------------------------------------
-- SYNCED
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------
-- SYNCED
--------------------------------------------------------------------------------
else
--------------------------------------------------------------------------------
-- UNSYNCED
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- UNSYNCED
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------
-- COMMON
--------------------------------------------------------------------------------
No, that doesn't work. Ok, so we need a gadget:somethingorother here.
So I rewrote things a bit, but now I get a syntax error:
Failed to load: localteamsound.lua ([string "LuaCob/Gadgets/localteamsound.lua"]:40: '<eof>' expected near 'else')
Where's the error? It looks like it's a closed statement, what am I doing wrong here?
So I rewrote things a bit, but now I get a syntax error:
Failed to load: localteamsound.lua ([string "LuaCob/Gadgets/localteamsound.lua"]:40: '<eof>' expected near 'else')
Where's the error? It looks like it's a closed statement, what am I doing wrong here?
Code: Select all
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
-- file: LocalTeamSound.lua
-- brief: Adds local, user-only sounds to Units
-- author: Argh
--
-- Copyright (C) 2007.
-- Licensed under the terms of the GNU GPL, v2 or later.
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function gadget:GetInfo()
return {
name = "LocalTeamSound",
desc = "Adds local, user-only sounds to Units",
author = "Argh",
date = "May 02, 2007",
license = "GNU GPL, v2 or later",
layer = 1,
enabled = true -- loaded by default?
}
end
--------------------------------------------------------------------------------
-- COMMON
--------------------------------------------------------------------------------
if (gadgetHandler:IsSyncedCode()) then
--------------------------------------------------------------------------------
-- SYNCED
--------------------------------------------------------------------------------
return false
end
--------------------------------------------------------------------------------
-- SYNCED
--------------------------------------------------------------------------------
else
--------------------------------------------------------------------------------
-- UNSYNCED
--------------------------------------------------------------------------------
function gadget:PlayLocalOnlySound(unitID, unitDefID, unitTeam, ...) -- plays localized, team-only sound (I hope, gotta test with another person)
local name, args = SoundCheck(unpack(arg))-- unpack probably refers to COBHandler's unpack function, for doing number conversion here
if (name == nil) then return false end -- it's safe, you won't crash Spring if you screwed up
local x, y, z = Spring.GetUnitPosition(unitID)
if (x == nil) then return false end -- if it can't find your Unit in the game world, it won't play, dunno why we need this
if (args == 1) then
Spring.PlaySoundFile(name, 1.0, x, y, z) -- here we play the sound file N, where N == name, which was generated in SoundMap()
else
Spring.PlaySoundFile(name, arg[2] * volumeScale, x, y, z) -- here's the only real problem, volumeScale is still a bit nasty imo
end
return true
end
--------------------------------------------------------------------------------
-- UNSYNCED
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------
-- COMMON
--------------------------------------------------------------------------------
Oh I should have tried making an idiot safe guide how to use the stuff. I know I was going trough the same thing myself. It was ages ago tough and I didn't go very far with this so I don't even remember the details, and I have no idea how LUA works or even what files go where.
Anyway for the footstep de-synching, you can simply desynch the walk animation itself, by adding a suitable:
wait randomnumber
where you start the walking script. I do this anyway since the animations being in perfect step looks bad as well.
Anyway for the footstep de-synching, you can simply desynch the walk animation itself, by adding a suitable:
wait randomnumber
where you start the walking script. I do this anyway since the animations being in perfect step looks bad as well.