Hello
I'm back with a new release this time a free script . So i was working on my Air Defense script and wanted to emergancy alarm system when an enemy or unknowken vehicel enters the radar zone.
I was ready to release that update until I noticed something, the sound isn't stereo and it doesn't matter if you're near the radar or not it sounds the same so I thought I'd do something about it.
I started searching for sound scripts and 3d spatial scripts and i found xsound and someother paid ones but as always i wanted to do it myself.
And that's how I started, first I looked at those free resources to see how they do it, and to my surprise, they all used the "Howler.js" spatial plugin, so I grabbed that and started testing it.
Spatial Sound
First I created a class and named it "SpatialSound" and create a new Howl as "sound_instance".
class SpatialSound {
sound_instance = null; // it could be a private but let's not talk about that
position = { x: 0, y: 0, z: 0 };
rotation = { x: 0, y: 0, z: 0 };
constructor(id, source, position, rotation, loop, destroyOnFinish) {
source = "./sfx/" + source; // source = 'alarm.mp3'
this.sound_instance = new Howl({
html5: false,
src: [source],
volume: 1,
onplay: () => {},
onstop: () => {},
onend: () => {},
loop: true,
});
}
}
If we have a sound we must have a way to play it, right? Let's add the "play" method.
play = () => {
this.sound_instance.play();
};
Now it was time to test to see if it works.
Audio Panner
Hmm, it's working but we want spatial audio! let's search howler docs In plugin: spatial Ok, what we need is a panner! let's add that
class SpatialSound {
sound_instance = null; // it could be a private but let's not talk about that
position = { x: 0, y: 0, z: 0 };
rotation = { x: 0, y: 0, z: 0 };
constructor(id, source, position, rotation, loop, destroyOnFinish) {
source = "./sfx/" + source; // source = 'alarm.mp3'
this.sound_instance = new Howl({
html5: false,
src: [source],
volume: 1,
onplay: () => {},
onstop: () => {},
onend: () => {},
loop: true,
});
this.sound_instance.pannerAttr({
panningModel: "HRTF",
distanceModel: "linear",
refDistance: 0.5,
maxDistance: 100,
rolloffFactor: 5,
coneInnerAngle: 360,
coneOuterAngle: 360,
coneOuterGain: 0.3,
});
}
}
Ok, it is time to test.
Well, i don't see a diffrent let's check what we need
Listener
So far we've created a sound source and everything is fine, but to hear that sound we need something or someone to listen to that source,
it this case it's Howler
and then we need to either move the Listener
or SpatialSound
.
Let's make a simple Listener
class
class Listener {
static position = { x: 0, y: 0, z: 0 };
static rotation = { x: 0, y: 0, z: 0 };
static move = (position) => {
Howler.pos(position.x, position.y, position.z);
};
static rotate = (rotate) => {
Howler.orientation(rotate.x, rotate.y, rotate.z, 0, 1, 0);
};
}
Ok, it is time to test.
Nice, it's working!
Howler.orientation
So far so good I took this code to the game and tested it. The first thing I noticed was the "Audio Panner". It wasn't working well, sometimes I heard the audio where It shouldn't be! After about 30 minutes it was time for more research, I finally looked at "Howler.orientation" and found the two mistakes I've made.
- I'm feeding it the raw rotation values but it has to be the direction (in this case the direction of the camera)
- Those 0, 1, 0 args are up values and they determine the axis of our rotation.
Let's start with first issue, we need camera direction Well at the time I didn't know how to do it until I stumbled upon a code by charleshacks, It was another "space voice script" and he had the code that I needed.
function getCameraDirection()
local cameraRotation = GetGameplayCamRot(0)
local radiansZ = (cameraRotation.z * 0.0174532924)
local radiansX = (cameraRotation.x * 0.0174532924)
local xCos = math.abs(math.cos(radiansX))
return {
x = (-math.sin(radiansZ) * xCos),
y = (math.cos(radiansZ) * xCos),
z = math.sin(radiansX),
}
end
And don't forget to change the global functions to local functions
local abs = math.abs
local sin = math.sin
local cos = math.cos
local function getDirection(rotation)
local radiansZ = (rotation.z * 0.0174532924)
local radiansX = (rotation.x * 0.0174532924)
local xCos = abs(cos(radiansX))
return {
x = (-sin(radiansZ) * xCos),
y = (cos(radiansZ) * xCos),
z = sin(radiansX)
}
end
Now that we have the camera orientation, let's fix the second problem
Howler.orientation(x,y,z, 0, 1, 0) That means we're rotating around the Y axis but if you've ever made a Mod for Gtav you've probably noticed that everything rotates around the Z axis, that means we have to use 0, 0, 1.
After testing these changes, I would say that it was working very well and the script should be finished at this point, but for some reason I thought if we're inside a building why not turn down the volume and muffle the audio (At that time I did not know what I was getting myself into 🤕)
End
If you are interested let me and I write how I removed "Howler.js" and built it from scratch with "WebAudio". ❤️