To determine how muffled an emitter is, you must cast occlusion or permeation rays towards it from your main emitter.
```cs
var listener = new Emitter()
{
OcclusionRayCount = 1024,
OcclusionBounceCount = 8,
PermeationRayCount = 128,
PermeationBounceCount = 3,
};
context.AddEmitter(listener);
var target = new Emitter();
context.AddEmitter(target);
// Enable muffling
listener.AddTarget(target);
```
> `AddTarget` will throw an exception if `OcclusionRayCount` and `PermeationRayCount` are 0 on the listener
## Occlusion
Occlusion rays bounce around the environment and lose energy based on the materials they hit, and the distance they travel. On each bounce they check for line-of-sight with each of the emitter's targets.
Once line-of-sight is found, the ray stops bouncing. Occlusion rays only find the **shortest** path to each target - they are designed for speed.
The maximum energy an occlusion ray can have is `1.0`, which means it lost no energy before line-of-sight with the target (material absorption is 0, and air absorption is disabled).
The minimum energy it can have is `0.0`, which means it did not find line-of-sight with the target.
## Permeation
Permeation rays are similar to occlusion rays, but produce more realistic results at the expense of speed. They bounce around the environment and do not lose energy based on materials. Instead, on each bounce they cast a ray directly towards each target, travelling through primitives and losing energy based on:
- the primitive's material's `Transmission` fields (decibel loss per meter)
- how long they spend inside the primitive
The maximum energy a permeation ray can have is `1.0 x permeationBounceCount`, because it accumulates energy on each bounce.
The minimum energy a permeation ray can have is `0.0`, which means the geometry is so thick that no energy permeated through.
## Converting Energy to Low Pass Filters
Occlusion and permeation energy is converted to low-frequency and high-frequency filter gains via the `Emitter.GainFormula` callback. This function is invoked twice, once for low-frequency energy values and once with high-frequency energy values.
Since not all rays will discover an emitter, I recommend setting a threshold (e.g. 15% in the example below) that must be met for an emitter to be at full volume:
```cs
listener.GainFormula = (
bool lowFrequency,
int occlusionRayCount,
int permeationRayCount,
int permeationBounceCount,
float occlusionEnergy,
float permeationEnergy
) =>
{
float totalEnergy = 0.0f;
// Must check if > 0, as occlusion might be disabled but permeation is enabled
if (occlusionRayCount > 0)
{
// 15% of energy is considered enough for the emitter to be at full volume
float rayThreshold = 0.15f * occlusionRayCount;
totalEnergy += occlusionEnergy / rayThreshold;
}
if (permeationRayCount > 0)
{
float rayThreshold = 0.15f * permeationRayCount * permeationBounceCount;
totalEnergy += permeationEnergy / rayThreshold;
}
return MathF.Min(1, totalEnergy);
}
```
All energy in all occlusion rays is accumulated into an `occlusionEnergy` field, which is in the range `0.0` to `occlusionRayCount`.
All energy in all permeation rays is accumulated into an `permeationEnergy` field, which is in the range `0.0` to `permeationRayCount * permeationBounceCount`.
## Accessing Low Pass Filters
When a target emitter is first raytraced, its low-pass filter can be accessed via a callback:
```cs
var target = new Emitter();
target.OnRaytracedByAnotherEmitter = (Emitter other) =>
{
var filter = other.GetTargetFilter(target);
// These fields contain the results of GainFormula
// and range from 0.0f to 1.0f
var gainLF = filter.gainLF;
var gainHF = filter.gainLF;
// PSUEDOCODE - apply the gains to a low pass filter
Godot.ApplyLowPassFilter(sound, gainLF, gainHF);
// Play the sound AFTER setting the filter, so it's muffled
// correctly from the beginning
Godot.PlaySound(sound);
}
context.AddEmitter(target);
```
Now that the sound is playing, you can update the filter every frame:
```cs
if (listener.HasRaytracedTarget(target))
{
var filter = listener.GetTargetFilter(target);
var gainLF = filter.gainLF;
var gainHF = filter.gainLF;
// PSEUDOCODE
Godot.UpdateLowPassFilter(sound, gainLF, gainHF);
};
```
## Optimisations
For short sounds like gunfire and footsteps, I recommend deleting the emitter once it has been raytraced. It's not worth continuously updating an emitter that only plays a short sound:
```cs
var target = new Emitter();
target.OnRaytracedByAnotherEmitter = (Emitter Other) => ...
// This is invoked after OnRaytracedByAnotherEmitter()
target.OnRaytracingComplete = () =>
{
context.RemoveEmitter(target);
};
context.RemoveEmitter(target);
```
If an entity in your game will play many sounds (e.g. an enemy playing footsteps), I recommend setting the emitter on the entity itself, and re-use it for multiple sounds.
In this case you wouldn't play the sound in the `OnRaytracedByAnotherEmitter()` callback - instead you'd access the filter via `listener.GetTargetFilter(target)` each time you play a foostep / gunfire sound.
> `listener.GetTargetFilter()` will throw an exception if you access it before the listener has raytraced the emitter. It's worth waiting for the `OnRaytracedByAnotherEmitter()` callback to fire first, or check `listener.HasRaytracedTarget(target)` when playing each footstep sound