Ambient Gain Formula

This function converts energy values to ambient gain values, and is invoked twice per emitter:

  • once with low-frequency energy values
  • once with high-frequency energy values

The default formula specifies that 10% of ambient occlusion energy, or 10% of ambient permeation energy is required for ambient sounds to be at max volume:

emitter.AmbientGainFormula = (
    bool lowFrequency,
    int ambientOcclusionRayCount,
    int ambientPermeationRayCount,
    int ambientPermeationBounceCount,
    float ambientOcclusionEnergy,
    float ambientPermeationEnergy)
{
    float gain = 0.0f;

    if (ambientOcclusionRayCount > 0)
    {
        // 10% of ambient occlusion energy is required for ambient sounds to be at max volume
        float energyThreshold = 0.1f * ambientOcclusionRayCount;
        gain += ambientOcclusionEnergy / energyThreshold;
    }

    if (ambientPermeationRayCount > 0 && ambientPermeationBounceCount > 0)
    {
        // 10% of ambient permeation energy is required for ambient sounds to be at max volume
        float energyThreshold = 0.1f * (ambientPermeationRayCount * ambientPermeationBounceCount);
        gain += ambientPermeationEnergy / energyThreshold;
    }

    return MathF.Min(1, gain);
}

In JS, the ambient gain formula is not configurable. Instead, set the energy cap properties on the emitter to define how much energy is required for ambient sounds to be at full volume:

// 10% of ambient occlusion energy required for ambient sounds to be at max volume
emitter.ambientOcclusionEnergyCap = 0.1;

// 10% of ambient permeation energy required for ambient sounds to be at max volume
emitter.ambientPermeationEnergyCap = 0.1;
float MyAmbientGainFormula(
    bool lowFrequency,
    int occlusionRayCount,
    int permeationRayCount,
    int permeationBounceCount,
    float occlusionEnergy,
    float permeationEnergy)
{
    float gain = 0.0f;

    if (occlusionRayCount > 0)
    {
        // 10% of ambient occlusion energy is required for ambient sounds to be at max volume
        float energyThreshold = 0.1f * occlusionRayCount;
        gain += occlusionEnergy / energyThreshold;
    }

    if (permeationRayCount > 0 && permeationBounceCount > 0)
    {
        // 10% of ambient permeation energy is required for ambient sounds to be at max volume
        float energyThreshold = 0.1f * (permeationRayCount * permeationBounceCount);
        gain += permeationEnergy / energyThreshold;
    }

    return gain < 1.0f ? gain : 1.0f;
}

void Initialise()
{
    VAEmitter* emitter = vaEmitterCreate();
    vaEmitterSetAmbientGainFormula(emitter, MyAmbientGainFormula);
}

For example if there's 5% energy in all ambient occlusion rays, and 5% energy in all ambient permeation rays, they will accumulate to the 10% energy threshold, and ambience will be at maximum volume.

Parameters:

  • lowFrequency - indicates whether this formula is invoked with low-frequency energy values or high-frequency energy values (this formula is invoked twice)
  • ambientOcclusionRayCount is the number of ambient occlusion rays that were cast by the source emitter
  • ambientPermeationRayCount is the number of ambient permeation rays that were cast by the source emitter
  • ambientPermeationBounceCount is the number of bounces per ambient permeation ray
  • ambientOcclusionEnergy is in the range 0.0 to 1.0, where 1.0 means all ambient occlusion rays reached the world edge with maximum energy remaining (i.e. no occlusion)
  • ambientPermeationEnergy is in the range 0.0 to 1.0, where 1.0 means all ambient permeation rays reached the world edge with maximum energy remaining (i.e. all rays passed through air)

The results of these functions are stored on the Emitter.AmbientFilterEmitter.ambientFiltervaEmitterGetAmbientFilter() low pass filter, which is null until raytracing has completed at least once:

if (emitter.AmbientFilter != null)
{
    var gainLF = emitter.AmbientFilter.GainLF;
    var gainHF = emitter.AmbientFilter.GainHF;
}
if (emitter.ambientFilter != null)
{
    var gainLF = emitter.ambientFilter.gainLF;
    var gainHF = emitter.ambientFilter.gainHF;
}
VALowPassFilter* ambientFilter = vaEmitterGetAmbientFilter(emitter);

if (ambientFilter != NULL)
{
    float gainLF = ambientFilter->gainLF;
    float gainHF = ambientFilter->gainHF;
}

Custom EAX Formulas

EAX reverb variables are currently calculated using generic formulas that provide a decent starting point for most environments. But you may want to adjust these formulas to suit the environment in your game.

These formula are not customisable in JS.

These overridden functions will run on background threads. Do not attempt to access data from the main thread here.

To use custom formulas, create a class that overrides CustomEAXFormulas.

public class MyEaxFormulas : CustomEAXFormulas
{
    // This function will run on a background thread
    public override float CalculateDiffusion()
    {
        return 0.5f;
    }
}

Then set it on the raytracing world:

var world = new World();
world.CustomEAXFormulas = new MyEaxFormulas();

To use custom formulas, create an instance of CustomEAXFormulas and override its functions:

float MyCalculateDiffusion(VACustomEAXFormulas* formulas)
{
    return 0.5f;
}

VACustomEAXFormulas* formulas = vaCustomEaxFormulasCreate();
formulas->calculateDiffusion = MyCalculateDiffusion;

vaWorldSetCustomEAXFormulas(ctx, formulas);

Reference

View the CustomEAXFormulas.cs file in the SDK > References folder to see how each reverb property is currently calculated.

EAX Presets

The below tables contain all values for every EAX reverb preset. Use these as a reference if you're calculating reverb settings yourself.

See Custom EAX Formulas for more details.

Density

                   Preset  Density
                psychotic  0.0625
      driving_incar_racer  0.0832
     driving_incar_sports  0.0832
            pipe_resonant  0.1373
               paddedcell  0.1715
                 bathroom  0.1715
    spacestation_cupboard  0.1715
   spacestation_smallroom  0.2109
spacestation_shortpassage  0.2109
  spacestation_mediumroom  0.2109
      spacestation_alcove  0.2109
            pipe_longthin  0.256
     driving_incar_luxury  0.256
                sewerpipe  0.3071
         factory_cupboard  0.3071
        factory_courtyard  0.3071
                  hallway  0.3645
               underwater  0.3645
                    dizzy  0.3645
        factory_smallroom  0.3645
     factory_shortpassage  0.3645
      factory_longpassage  0.3645
           factory_alcove  0.3645
   spacestation_largeroom  0.3645
                dustyroom  0.3645
      prefab_practiseroom  0.4022
                     room  0.4287
          carpetedhallway  0.4287
                  drugged  0.4287
       factory_mediumroom  0.4287
        factory_largeroom  0.4287
             factory_hall  0.4287
 spacestation_longpassage  0.4287
        spacestation_hall  0.4287
          prefab_workshop  0.4287
        driving_pitgarage  0.4287
               livingroom  0.9766
                     none  1
                  generic  1
                stoneroom  1
               auditorium  1
              concerthall  1
                     cave  1
                    arena  1
                   hangar  1
            stonecorridor  1
         castle_smallroom  1
      castle_shortpassage  1
        castle_mediumroom  1
         castle_largeroom  1
       castle_longpassage  1
              castle_hall  1
          castle_cupboard  1
         castle_courtyard  1
            castle_alcove  1
      icepalace_smallroom  1
   icepalace_shortpassage  1
     icepalace_mediumroom  1
      icepalace_largeroom  1
    icepalace_longpassage  1
           icepalace_hall  1
       icepalace_cupboard  1
      icepalace_courtyard  1
         icepalace_alcove  1
         wooden_smallroom  1
      wooden_shortpassage  1
        wooden_mediumroom  1
         wooden_largeroom  1
       wooden_longpassage  1
              wooden_hall  1
          wooden_cupboard  1
         wooden_courtyard  1
            wooden_alcove  1
       sport_emptystadium  1
        sport_squashcourt  1
  sport_smallswimmingpool  1
  sport_largeswimmingpool  1
          sport_gymnasium  1
        sport_fullstadium  1
      sport_stadiumtannoy  1
          prefab_outhouse  1
           prefab_caravan  1
                dome_tomb  1
               pipe_small  1
          dome_saintpauls  1
               pipe_large  1
                 backyard  1
            rollingplains  1
               deepcanyon  1
                    creek  1
                   valley  1
                    alley  1
                   forest  1
                     city  1
                mountains  1
                   quarry  1
                    plain  1
               parkinglot  1
              mood_heaven  1
                mood_hell  1
              mood_memory  1
      driving_commentator  1
   driving_fullgrandstand  1
  driving_emptygrandstand  1
           driving_tunnel  1
             city_streets  1
              city_subway  1
              city_museum  1
             city_library  1
           city_underpass  1
           city_abandoned  1
                   chapel  1
           smallwaterroom  1

Diffusion

                   Preset  Diffusion
            rollingplains  0
      driving_commentator  0
                    plain  0.21
                mountains  0.27
                   valley  0.28
                    alley  0.3
                   forest  0.3
                    creek  0.35
         castle_courtyard  0.42
                 backyard  0.45
                  drugged  0.5
                psychotic  0.5
                     city  0.5
    spacestation_cupboard  0.56
                dustyroom  0.56
        factory_courtyard  0.57
                mood_hell  0.57
           factory_alcove  0.59
      icepalace_courtyard  0.59
        driving_pitgarage  0.59
                    dizzy  0.6
         factory_cupboard  0.63
     factory_shortpassage  0.64
      factory_longpassage  0.64
         wooden_courtyard  0.65
           city_abandoned  0.69
   spacestation_smallroom  0.7
  sport_smallswimmingpool  0.7
           smallwaterroom  0.7
               deepcanyon  0.74
              city_subway  0.74
        factory_largeroom  0.75
             factory_hall  0.75
   icepalace_shortpassage  0.75
  spacestation_mediumroom  0.75
        sport_squashcourt  0.75
           icepalace_hall  0.76
    icepalace_longpassage  0.77
      spacestation_alcove  0.78
      sport_stadiumtannoy  0.78
             city_streets  0.78
                dome_tomb  0.79
                sewerpipe  0.8
      driving_incar_racer  0.8
     driving_incar_sports  0.8
              castle_hall  0.81
      icepalace_largeroom  0.81
   spacestation_largeroom  0.81
          sport_gymnasium  0.81
           driving_tunnel  0.81
         castle_largeroom  0.82
        factory_smallroom  0.82
       factory_mediumroom  0.82
 spacestation_longpassage  0.82
  sport_largeswimmingpool  0.82
          prefab_outhouse  0.82
              city_museum  0.82
             city_library  0.82
           city_underpass  0.82
       icepalace_cupboard  0.83
      icepalace_smallroom  0.84
         icepalace_alcove  0.84
                   chapel  0.84
              mood_memory  0.85
     icepalace_mediumroom  0.87
spacestation_shortpassage  0.87
        spacestation_hall  0.87
      prefab_practiseroom  0.87
          dome_saintpauls  0.87
         castle_smallroom  0.89
      castle_shortpassage  0.89
       castle_longpassage  0.89
          castle_cupboard  0.89
            castle_alcove  0.89
            pipe_longthin  0.91
            pipe_resonant  0.91
        castle_mediumroom  0.93
              mood_heaven  0.94
                     none  1
                  generic  1
               paddedcell  1
                     room  1
                 bathroom  1
               livingroom  1
                stoneroom  1
               auditorium  1
              concerthall  1
                     cave  1
                    arena  1
                   hangar  1
          carpetedhallway  1
                  hallway  1
            stonecorridor  1
               underwater  1
         wooden_smallroom  1
      wooden_shortpassage  1
        wooden_mediumroom  1
         wooden_largeroom  1
       wooden_longpassage  1
              wooden_hall  1
          wooden_cupboard  1
            wooden_alcove  1
       sport_emptystadium  1
        sport_fullstadium  1
          prefab_workshop  1
           prefab_caravan  1
               pipe_small  1
               pipe_large  1
                   quarry  1
               parkinglot  1
     driving_incar_luxury  1
   driving_fullgrandstand  1
  driving_emptygrandstand  1

Gain

                   Preset  Gain
                     none  0
                factory_*  0.2512
                    other  0.3162
      driving_commentator  3.1623

GainLF

                   Preset  GainLF
             city_library  0.0891
         castle_smallroom  0.1
      castle_shortpassage  0.1
        castle_mediumroom  0.1
       castle_longpassage  0.1
          castle_cupboard  0.1
            castle_alcove  0.1
         castle_largeroom  0.1259
           prefab_caravan  0.1259
          prefab_outhouse  0.1585
                   valley  0.1585
              castle_hall  0.1778
              city_museum  0.1778
         castle_courtyard  0.1995
       icepalace_cupboard  0.2239
                dome_tomb  0.2239
               pipe_small  0.2239
          dome_saintpauls  0.2239
               pipe_large  0.2239
      icepalace_smallroom  0.2818
   icepalace_shortpassage  0.2818
         icepalace_alcove  0.2818
        wooden_mediumroom  0.2818
         wooden_largeroom  0.2818
              wooden_hall  0.2818
            pipe_longthin  0.2818
            pipe_resonant  0.2818
      icepalace_courtyard  0.3162
         wooden_smallroom  0.3162
      wooden_shortpassage  0.3162
       wooden_longpassage  0.3162
          wooden_cupboard  0.3162
         wooden_courtyard  0.3162
            wooden_alcove  0.3162
              mood_memory  0.3548
    icepalace_longpassage  0.3981
          prefab_workshop  0.3981
     icepalace_mediumroom  0.4467
      icepalace_largeroom  0.4467
              mood_heaven  0.4467
                mood_hell  0.4467
        factory_smallroom  0.5012
     factory_shortpassage  0.5012
       factory_mediumroom  0.5012
      factory_longpassage  0.5012
         factory_cupboard  0.5012
           factory_alcove  0.5012
      sport_stadiumtannoy  0.5012
      prefab_practiseroom  0.5012
                 backyard  0.5012
                    creek  0.5012
      driving_commentator  0.5012
     driving_incar_luxury  0.5012
           icepalace_hall  0.5623
        driving_pitgarage  0.5623
        factory_largeroom  0.631
             factory_hall  0.631
        factory_courtyard  0.631
            rollingplains  0.631
               deepcanyon  0.631
   driving_fullgrandstand  0.631
                dustyroom  0.7079
       sport_emptystadium  0.7943
        sport_squashcourt  0.7943
        sport_fullstadium  0.7943
      driving_incar_racer  0.7943
  driving_emptygrandstand  0.7943
   spacestation_smallroom  0.8913
spacestation_shortpassage  0.8913
  spacestation_mediumroom  0.8913
   spacestation_largeroom  0.8913
 spacestation_longpassage  0.8913
        spacestation_hall  0.8913
    spacestation_cupboard  0.8913
      spacestation_alcove  0.8913
  sport_smallswimmingpool  0.8913
          sport_gymnasium  0.8913
           driving_tunnel  0.8913
             city_streets  0.8913
              city_subway  0.8913
           city_underpass  0.8913
           city_abandoned  0.8913
                     none  1
                  generic  1
               paddedcell  1
                     room  1
                 bathroom  1
               livingroom  1
                stoneroom  1
               auditorium  1
              concerthall  1
                     cave  1
                    arena  1
                   hangar  1
          carpetedhallway  1
                  hallway  1
            stonecorridor  1
                sewerpipe  1
               underwater  1
                  drugged  1
                    dizzy  1
                psychotic  1
  sport_largeswimmingpool  1
                    alley  1
                   forest  1
                     city  1
                mountains  1
                   quarry  1
                    plain  1
               parkinglot  1
     driving_incar_sports  1
                   chapel  1
           smallwaterroom  1

GainHF

                   Preset  GainHF
                     none  0
               paddedcell  0.001
               livingroom  0.001
          carpetedhallway  0.01
               underwater  0.01
            rollingplains  0.0112
                   forest  0.0224
                   valley  0.0282
                mountains  0.0562
        sport_fullstadium  0.0708
              wooden_hall  0.0794
         wooden_courtyard  0.0794
         wooden_largeroom  0.0891
           prefab_caravan  0.0891
        wooden_mediumroom  0.1
       wooden_longpassage  0.1
                    plain  0.1
     driving_incar_luxury  0.1
         wooden_smallroom  0.1122
          prefab_outhouse  0.1122
      wooden_shortpassage  0.1259
            wooden_alcove  0.1259
          wooden_cupboard  0.1413
          prefab_workshop  0.1413
               deepcanyon  0.1778
                    creek  0.1778
              city_museum  0.1778
                 bathroom  0.2512
                 backyard  0.2512
        castle_mediumroom  0.2818
         castle_largeroom  0.2818
              castle_hall  0.2818
          castle_cupboard  0.2818
      icepalace_courtyard  0.2818
   driving_fullgrandstand  0.2818
             city_library  0.2818
                   hangar  0.3162
                sewerpipe  0.3162
      castle_shortpassage  0.3162
        factory_courtyard  0.3162
        sport_squashcourt  0.3162
                   quarry  0.3162
                dome_tomb  0.3548
               pipe_small  0.3548
          dome_saintpauls  0.3548
               pipe_large  0.3548
                mood_hell  0.3548
         castle_smallroom  0.3981
       castle_longpassage  0.3981
      prefab_practiseroom  0.3981
                     city  0.3981
           driving_tunnel  0.3981
         castle_courtyard  0.4467
           icepalace_hall  0.4467
       sport_emptystadium  0.4467
          sport_gymnasium  0.4467
            pipe_longthin  0.4467
            pipe_resonant  0.4467
           city_underpass  0.4467
                    arena  0.4477
           smallwaterroom  0.4477
            castle_alcove  0.5012
       icepalace_cupboard  0.5012
              concerthall  0.5623
      icepalace_smallroom  0.5623
   icepalace_shortpassage  0.5623
     icepalace_mediumroom  0.5623
      icepalace_largeroom  0.5623
    icepalace_longpassage  0.5623
         icepalace_alcove  0.5623
      sport_stadiumtannoy  0.5623
      driving_commentator  0.5623
                   chapel  0.5623
               auditorium  0.5781
                     room  0.5929
                    dizzy  0.631
spacestation_shortpassage  0.631
  spacestation_mediumroom  0.631
   spacestation_largeroom  0.631
 spacestation_longpassage  0.631
        spacestation_hall  0.631
              mood_memory  0.631
     driving_incar_sports  0.631
                stoneroom  0.7079
                  hallway  0.7079
        factory_largeroom  0.7079
             factory_hall  0.7079
   spacestation_smallroom  0.7079
    spacestation_cupboard  0.7079
      spacestation_alcove  0.7079
        driving_pitgarage  0.7079
             city_streets  0.7079
              city_subway  0.7079
                    alley  0.7328
            stonecorridor  0.7612
        factory_smallroom  0.7943
     factory_shortpassage  0.7943
       factory_mediumroom  0.7943
      factory_longpassage  0.7943
         factory_cupboard  0.7943
           factory_alcove  0.7943
  sport_smallswimmingpool  0.7943
  sport_largeswimmingpool  0.7943
              mood_heaven  0.7943
           city_abandoned  0.7943
                dustyroom  0.7943
                psychotic  0.8404
                  generic  0.8913
                     cave  1
                  drugged  1
               parkinglot  1
      driving_incar_racer  1
  driving_emptygrandstand  1

DecayTime

                   Preset  DecayTime
     driving_incar_luxury  0.13
               paddedcell  0.17
      driving_incar_racer  0.17
     driving_incar_sports  0.17
          carpetedhallway  0.3
                     room  0.4
           prefab_caravan  0.43
         factory_cupboard  0.49
               livingroom  0.5
          wooden_cupboard  0.56
          castle_cupboard  0.67
       icepalace_cupboard  0.76
          prefab_workshop  0.76
    spacestation_cupboard  0.79
         wooden_smallroom  0.79
      prefab_practiseroom  1.12
                 backyard  1.12
      spacestation_alcove  1.16
         castle_smallroom  1.22
            wooden_alcove  1.22
          prefab_outhouse  1.38
        wooden_mediumroom  1.47
                     none  1.49
                  generic  1.49
                 bathroom  1.49
                  hallway  1.49
               underwater  1.49
                    alley  1.49
                   forest  1.49
                     city  1.49
                mountains  1.49
                   quarry  1.49
                    plain  1.49
      icepalace_smallroom  1.51
           smallwaterroom  1.51
            castle_alcove  1.64
               parkinglot  1.65
        factory_smallroom  1.72
   spacestation_smallroom  1.72
        driving_pitgarage  1.72
      wooden_shortpassage  1.75
   icepalace_shortpassage  1.79
         wooden_courtyard  1.79
             city_streets  1.79
                dustyroom  1.79
       wooden_longpassage  1.99
        castle_mediumroom  2.04
      icepalace_courtyard  2.04
         castle_courtyard  2.13
            rollingplains  2.13
                    creek  2.13
     icepalace_mediumroom  2.22
        sport_squashcourt  2.22
                stoneroom  2.31
      castle_shortpassage  2.32
        factory_courtyard  2.32
      driving_commentator  2.42
         castle_largeroom  2.53
     factory_shortpassage  2.53
      sport_stadiumtannoy  2.53
         wooden_largeroom  2.65
            stonecorridor  2.7
       factory_mediumroom  2.76
         icepalace_alcove  2.76
  sport_smallswimmingpool  2.76
             city_library  2.76
                sewerpipe  2.81
                   valley  2.88
                     cave  2.91
    icepalace_longpassage  3.01
  spacestation_mediumroom  3.01
   driving_fullgrandstand  3.01
              city_subway  3.01
              castle_hall  3.14
           factory_alcove  3.14
      icepalace_largeroom  3.14
          sport_gymnasium  3.14
              city_museum  3.28
           city_abandoned  3.28
       castle_longpassage  3.42
           driving_tunnel  3.42
              wooden_hall  3.45
spacestation_shortpassage  3.57
                mood_hell  3.57
           city_underpass  3.57
   spacestation_largeroom  3.89
               deepcanyon  3.89
              concerthall  3.92
      factory_longpassage  4.06
              mood_memory  4.06
                dome_tomb  4.18
        factory_largeroom  4.24
               auditorium  4.32
 spacestation_longpassage  4.62
  driving_emptygrandstand  4.62
                   chapel  4.62
               pipe_small  5.04
              mood_heaven  5.04
        sport_fullstadium  5.25
           icepalace_hall  5.49
  sport_largeswimmingpool  5.49
       sport_emptystadium  6.26
            pipe_resonant  6.81
        spacestation_hall  7.11
                    arena  7.24
             factory_hall  7.43
                psychotic  7.56
                  drugged  8.39
               pipe_large  8.45
            pipe_longthin  9.21
                   hangar  10.05
          dome_saintpauls  10.48
                    dizzy  17.23

DecayLFRatio

                   Preset  DecayLFRatio
                dome_tomb  0.1
               pipe_small  0.1
          dome_saintpauls  0.1
            pipe_longthin  0.1
               pipe_large  0.1
            pipe_resonant  0.1
      prefab_practiseroom  0.18
                dustyroom  0.21
         castle_courtyard  0.23
       icepalace_cupboard  0.26
      icepalace_smallroom  0.27
   icepalace_shortpassage  0.28
    icepalace_longpassage  0.28
         icepalace_alcove  0.28
         castle_smallroom  0.31
      castle_shortpassage  0.31
       castle_longpassage  0.31
          castle_cupboard  0.31
            castle_alcove  0.31
     icepalace_mediumroom  0.32
      icepalace_largeroom  0.32
          prefab_outhouse  0.35
                   valley  0.35
           icepalace_hall  0.38
      icepalace_courtyard  0.38
      driving_incar_racer  0.41
     driving_incar_sports  0.41
             city_library  0.41
        castle_mediumroom  0.46
                 backyard  0.46
            rollingplains  0.46
               deepcanyon  0.46
                    creek  0.46
     driving_incar_luxury  0.46
         castle_largeroom  0.5
   spacestation_smallroom  0.55
spacestation_shortpassage  0.55
  spacestation_mediumroom  0.55
 spacestation_longpassage  0.55
    spacestation_cupboard  0.55
      spacestation_alcove  0.55
        factory_courtyard  0.56
              mood_heaven  0.56
              mood_memory  0.56
              city_museum  0.57
   spacestation_largeroom  0.61
        spacestation_hall  0.61
              castle_hall  0.62
      sport_stadiumtannoy  0.68
      driving_commentator  0.68
       wooden_longpassage  0.79
         wooden_courtyard  0.79
        sport_fullstadium  0.8
        wooden_mediumroom  0.82
         wooden_largeroom  0.82
              wooden_hall  0.82
         wooden_smallroom  0.87
      wooden_shortpassage  0.87
        driving_pitgarage  0.87
          wooden_cupboard  0.91
            wooden_alcove  0.91
             city_streets  0.91
              city_subway  0.91
           city_underpass  0.91
           city_abandoned  0.91
                     none  1
                  generic  1
               paddedcell  1
                     room  1
                 bathroom  1
               livingroom  1
                stoneroom  1
               auditorium  1
              concerthall  1
                     cave  1
                    arena  1
                   hangar  1
          carpetedhallway  1
                  hallway  1
            stonecorridor  1
                sewerpipe  1
               underwater  1
                  drugged  1
                    dizzy  1
                psychotic  1
          prefab_workshop  1
           prefab_caravan  1
                    alley  1
                   forest  1
                     city  1
                mountains  1
                   quarry  1
                    plain  1
               parkinglot  1
       sport_emptystadium  1.1
  sport_smallswimmingpool  1.14
  sport_largeswimmingpool  1.14
           smallwaterroom  1.14
        sport_squashcourt  1.16
                   chapel  1.23
   driving_fullgrandstand  1.28
        factory_smallroom  1.31
     factory_shortpassage  1.31
       factory_mediumroom  1.31
        factory_largeroom  1.31
      factory_longpassage  1.31
             factory_hall  1.31
         factory_cupboard  1.31
           factory_alcove  1.31
           driving_tunnel  1.31
          sport_gymnasium  1.35
  driving_emptygrandstand  1.4
                mood_hell  2

DecayHFRatio

                   Preset  DecayHFRatio
               paddedcell  0.1
               livingroom  0.1
          carpetedhallway  0.1
               underwater  0.1
               pipe_small  0.1
               pipe_large  0.1
                sewerpipe  0.14
        sport_fullstadium  0.17
            pipe_longthin  0.18
            pipe_resonant  0.18
          dome_saintpauls  0.19
                dome_tomb  0.21
            rollingplains  0.21
               deepcanyon  0.21
                    creek  0.21
                mountains  0.21
                   hangar  0.23
                   valley  0.26
        factory_courtyard  0.29
              wooden_hall  0.3
         wooden_smallroom  0.32
                    arena  0.33
         wooden_largeroom  0.33
                 backyard  0.34
         wooden_courtyard  0.35
   spacestation_largeroom  0.38
        spacestation_hall  0.38
          prefab_outhouse  0.38
                dustyroom  0.38
       wooden_longpassage  0.4
     driving_incar_luxury  0.41
        wooden_mediumroom  0.42
          wooden_cupboard  0.46
                mood_hell  0.49
spacestation_shortpassage  0.5
  spacestation_mediumroom  0.5
      wooden_shortpassage  0.5
                    plain  0.5
        factory_largeroom  0.51
             factory_hall  0.51
       sport_emptystadium  0.51
                 bathroom  0.54
                   forest  0.54
                    dizzy  0.56
      prefab_practiseroom  0.56
               auditorium  0.59
                  hallway  0.59
         castle_courtyard  0.61
 spacestation_longpassage  0.62
            wooden_alcove  0.62
                stoneroom  0.64
                   chapel  0.64
        factory_smallroom  0.65
     factory_shortpassage  0.65
       factory_mediumroom  0.65
      factory_longpassage  0.65
         factory_cupboard  0.65
           factory_alcove  0.65
                     city  0.67
              concerthall  0.7
     driving_incar_sports  0.75
            stonecorridor  0.79
              castle_hall  0.79
    spacestation_cupboard  0.81
      spacestation_alcove  0.81
   spacestation_smallroom  0.82
              mood_memory  0.82
                     none  0.83
                  generic  0.83
                     room  0.83
         castle_smallroom  0.83
      castle_shortpassage  0.83
        castle_mediumroom  0.83
         castle_largeroom  0.83
       castle_longpassage  0.83
                   quarry  0.83
                    alley  0.86
          castle_cupboard  0.87
            castle_alcove  0.87
      sport_stadiumtannoy  0.88
      driving_commentator  0.88
             city_library  0.89
                psychotic  0.91
        sport_squashcourt  0.91
        driving_pitgarage  0.93
           driving_tunnel  0.94
          prefab_workshop  1
          sport_gymnasium  1.06
              mood_heaven  1.12
             city_streets  1.12
           city_underpass  1.12
           city_abandoned  1.17
      icepalace_courtyard  1.2
              city_subway  1.23
  sport_smallswimmingpool  1.25
           smallwaterroom  1.25
                     cave  1.3
  sport_largeswimmingpool  1.31
   driving_fullgrandstand  1.37
                  drugged  1.39
              city_museum  1.4
   icepalace_shortpassage  1.46
    icepalace_longpassage  1.46
         icepalace_alcove  1.46
           prefab_caravan  1.5
               parkinglot  1.5
      icepalace_smallroom  1.53
     icepalace_mediumroom  1.53
      icepalace_largeroom  1.53
           icepalace_hall  1.53
       icepalace_cupboard  1.53
  driving_emptygrandstand  1.75
      driving_incar_racer  2

ReflectionsGain

                   Preset  ReflectionsGain
                   quarry  0
                mood_hell  0
              mood_memory  0.0398
                mountains  0.0407
                  generic  0.05
                   forest  0.0525
                    plain  0.0585
             factory_hall  0.0631
       sport_emptystadium  0.0631
                     city  0.073
        sport_fullstadium  0.1
           icepalace_hall  0.1122
          carpetedhallway  0.1215
                    dizzy  0.1392
                   valley  0.1413
                     room  0.1503
              castle_hall  0.1778
        factory_largeroom  0.1778
        spacestation_hall  0.1778
          dome_saintpauls  0.1778
            rollingplains  0.1778
      driving_commentator  0.1995
               livingroom  0.2051
               parkinglot  0.2082
  driving_emptygrandstand  0.2082
         castle_courtyard  0.2239
        factory_courtyard  0.2239
              concerthall  0.2427
              mood_heaven  0.2427
                  hallway  0.2458
            stonecorridor  0.2472
               paddedcell  0.25
                    alley  0.25
      icepalace_largeroom  0.2512
              city_museum  0.2512
                    arena  0.2612
       factory_mediumroom  0.2818
      sport_stadiumtannoy  0.2818
             city_streets  0.2818
      icepalace_courtyard  0.3162
   spacestation_largeroom  0.3162
               deepcanyon  0.3162
   driving_fullgrandstand  0.3548
             city_library  0.3548
                dome_tomb  0.3868
     icepalace_mediumroom  0.3981
  spacestation_mediumroom  0.3981
          sport_gymnasium  0.3981
               pipe_large  0.3981
                    creek  0.3981
           city_underpass  0.3981
               auditorium  0.4032
                stoneroom  0.4411
         castle_largeroom  0.4467
        sport_squashcourt  0.4467
  sport_largeswimmingpool  0.4467
                 backyard  0.4467
           city_abandoned  0.4467
                   chapel  0.4467
                psychotic  0.4864
                     cave  0.5
                   hangar  0.5
   icepalace_shortpassage  0.5012
               pipe_small  0.5012
                dustyroom  0.5012
         wooden_courtyard  0.5623
        driving_pitgarage  0.5623
               underwater  0.5963
        castle_mediumroom  0.631
  sport_smallswimmingpool  0.631
                 bathroom  0.6531
        factory_smallroom  0.7079
            pipe_longthin  0.7079
            pipe_resonant  0.7079
           driving_tunnel  0.7079
              city_subway  0.7079
    icepalace_longpassage  0.7943
   spacestation_smallroom  0.7943
     driving_incar_luxury  0.7943
                  drugged  0.876
         castle_smallroom  0.8913
      castle_shortpassage  0.8913
       castle_longpassage  0.8913
      icepalace_smallroom  0.8913
      wooden_shortpassage  0.8913
        wooden_mediumroom  0.8913
         wooden_largeroom  0.8913
              wooden_hall  0.8913
          prefab_outhouse  0.8913
           smallwaterroom  0.8913
                     none  1
            castle_alcove  1
     factory_shortpassage  1
      factory_longpassage  1
spacestation_shortpassage  1
 spacestation_longpassage  1
         wooden_smallroom  1
       wooden_longpassage  1
          prefab_workshop  1
           prefab_caravan  1
     driving_incar_sports  1
       icepalace_cupboard  1.122
         icepalace_alcove  1.122
          wooden_cupboard  1.122
            wooden_alcove  1.122
         factory_cupboard  1.2589
      prefab_practiseroom  1.2589
          castle_cupboard  1.4125
           factory_alcove  1.4125
    spacestation_cupboard  1.4125
      spacestation_alcove  1.4125
                sewerpipe  1.6387
      driving_incar_racer  1.7783

LateReverbGain

                   Preset  LateReverbGain
                     none  0
         wooden_courtyard  0.1
                    plain  0.1089
                     city  0.1427
          carpetedhallway  0.1531
   driving_fullgrandstand  0.1778
                mountains  0.1919
                    creek  0.1995
             city_streets  0.1995
      driving_commentator  0.2512
  driving_emptygrandstand  0.2512
               parkinglot  0.2652
               livingroom  0.2805
        sport_fullstadium  0.2818
           city_abandoned  0.2818
      icepalace_courtyard  0.3162
               deepcanyon  0.3548
        factory_courtyard  0.3981
       sport_emptystadium  0.3981
                   valley  0.3981
       wooden_longpassage  0.4467
            rollingplains  0.4467
                    dizzy  0.4937
  sport_largeswimmingpool  0.5012
      sport_stadiumtannoy  0.5012
          sport_gymnasium  0.5623
     driving_incar_sports  0.5623
           icepalace_hall  0.631
        spacestation_hall  0.631
      wooden_shortpassage  0.631
          prefab_outhouse  0.631
                     cave  0.7063
         castle_courtyard  0.7079
            wooden_alcove  0.7079
            pipe_longthin  0.7079
                 backyard  0.7079
      driving_incar_racer  0.7079
           driving_tunnel  0.7079
               auditorium  0.717
                   forest  0.7682
         wooden_largeroom  0.7943
              wooden_hall  0.7943
        sport_squashcourt  0.7943
  sport_smallswimmingpool  0.7943
                   chapel  0.7943
             factory_hall  0.8913
         icepalace_alcove  0.8913
   spacestation_largeroom  0.8913
         wooden_smallroom  0.8913
        wooden_mediumroom  0.8913
              city_museum  0.8913
             city_library  0.8913
           city_underpass  0.8913
                    alley  0.9954
              concerthall  0.9977
           factory_alcove  1
      icepalace_largeroom  1
      spacestation_alcove  1
            pipe_resonant  1
                    arena  1.0186
                     room  1.0629
                stoneroom  1.1003
              castle_hall  1.122
        factory_largeroom  1.122
   icepalace_shortpassage  1.122
     icepalace_mediumroom  1.122
spacestation_shortpassage  1.122
  spacestation_mediumroom  1.122
          wooden_cupboard  1.122
          prefab_workshop  1.122
              mood_memory  1.122
                   hangar  1.256
                  generic  1.2589
      castle_shortpassage  1.2589
         castle_largeroom  1.2589
     factory_shortpassage  1.2589
      factory_longpassage  1.2589
    icepalace_longpassage  1.2589
 spacestation_longpassage  1.2589
          dome_saintpauls  1.2589
              mood_heaven  1.2589
        driving_pitgarage  1.2589
              city_subway  1.2589
                dustyroom  1.2589
               paddedcell  1.2691
       castle_longpassage  1.4125
            castle_alcove  1.4125
       factory_mediumroom  1.4125
      icepalace_smallroom  1.4125
   spacestation_smallroom  1.4125
      prefab_practiseroom  1.4125
                mood_hell  1.4125
           smallwaterroom  1.4125
            stonecorridor  1.5758
        castle_mediumroom  1.5849
               pipe_large  1.5849
     driving_incar_luxury  1.5849
                  hallway  1.6615
                dome_tomb  1.6788
        factory_smallroom  1.7783
    spacestation_cupboard  1.7783
                   quarry  1.7783
         castle_smallroom  1.9953
         factory_cupboard  1.9953
       icepalace_cupboard  1.9953
           prefab_caravan  1.9953
                psychotic  2.4378
               pipe_small  2.5119
                  drugged  3.1081
                sewerpipe  3.2471
                 bathroom  3.2734
          castle_cupboard  3.5481
               underwater  7.0795

LateReverbDelay

                   Preset  LateReverbDelay
              mood_memory  0
     driving_incar_sports  0
               paddedcell  0.002
                     room  0.003
               livingroom  0.004
                dustyroom  0.006
          castle_cupboard  0.007
     driving_incar_luxury  0.01
                     none  0.011
                  generic  0.011
                 bathroom  0.011
                  hallway  0.011
               underwater  0.011
         castle_smallroom  0.011
        castle_mediumroom  0.011
      icepalace_smallroom  0.011
        sport_squashcourt  0.011
      prefab_practiseroom  0.011
                    alley  0.011
                     city  0.011
          prefab_workshop  0.012
           prefab_caravan  0.012
               parkinglot  0.012
   spacestation_smallroom  0.013
               pipe_small  0.015
      driving_incar_racer  0.015
         castle_largeroom  0.016
       icepalace_cupboard  0.016
spacestation_shortpassage  0.016
        driving_pitgarage  0.016
                stoneroom  0.017
      driving_commentator  0.017
    spacestation_cupboard  0.018
      spacestation_alcove  0.018
   icepalace_shortpassage  0.019
            rollingplains  0.019
               deepcanyon  0.019
            stonecorridor  0.02
             city_library  0.02
                sewerpipe  0.021
                     cave  0.022
                dome_tomb  0.022
            pipe_longthin  0.022
            pipe_resonant  0.022
      castle_shortpassage  0.023
       castle_longpassage  0.023
       factory_mediumroom  0.023
        factory_largeroom  0.023
                 backyard  0.023
              castle_hall  0.024
        factory_smallroom  0.024
      wooden_shortpassage  0.024
            wooden_alcove  0.024
           city_abandoned  0.024
    icepalace_longpassage  0.025
                   quarry  0.025
             factory_hall  0.027
     icepalace_mediumroom  0.027
      icepalace_largeroom  0.027
          wooden_cupboard  0.028
             city_streets  0.028
              city_subway  0.028
              concerthall  0.029
         wooden_smallroom  0.029
        wooden_mediumroom  0.029
              mood_heaven  0.029
               auditorium  0.03
                    arena  0.03
                   hangar  0.03
          carpetedhallway  0.03
                  drugged  0.03
                    dizzy  0.03
                psychotic  0.03
         icepalace_alcove  0.03
  sport_smallswimmingpool  0.03
                mood_hell  0.03
           smallwaterroom  0.03
 spacestation_longpassage  0.031
                    creek  0.031
         factory_cupboard  0.032
         wooden_courtyard  0.032
               pipe_large  0.032
            castle_alcove  0.034
              city_museum  0.034
  spacestation_mediumroom  0.035
   spacestation_largeroom  0.035
         castle_courtyard  0.036
       wooden_longpassage  0.036
      factory_longpassage  0.037
           city_underpass  0.037
     factory_shortpassage  0.038
           factory_alcove  0.038
       sport_emptystadium  0.038
        sport_fullstadium  0.038
        factory_courtyard  0.039
          dome_saintpauls  0.042
      icepalace_courtyard  0.043
          prefab_outhouse  0.044
          sport_gymnasium  0.045
        spacestation_hall  0.047
           driving_tunnel  0.047
         wooden_largeroom  0.049
  sport_largeswimmingpool  0.049
   driving_fullgrandstand  0.049
  driving_emptygrandstand  0.049
                   chapel  0.049
           icepalace_hall  0.052
              wooden_hall  0.063
      sport_stadiumtannoy  0.063
                   forest  0.088
                   valley  0.1
                mountains  0.1
                    plain  0.1

EchoTime

                   Preset  EchoTime
      prefab_practiseroom  0.095
         factory_cupboard  0.107
                mood_hell  0.11
           factory_alcove  0.114
        factory_smallroom  0.119
          prefab_outhouse  0.121
                    alley  0.125
                   forest  0.125
                   quarry  0.125
              city_subway  0.125
        sport_squashcourt  0.126
              city_museum  0.13
             city_library  0.13
     factory_shortpassage  0.135
      factory_longpassage  0.135
         castle_smallroom  0.138
      castle_shortpassage  0.138
       castle_longpassage  0.138
          castle_cupboard  0.138
            castle_alcove  0.138
       icepalace_cupboard  0.143
          sport_gymnasium  0.146
        castle_mediumroom  0.155
         icepalace_alcove  0.161
      icepalace_smallroom  0.164
spacestation_shortpassage  0.172
       factory_mediumroom  0.174
   icepalace_shortpassage  0.177
                dome_tomb  0.177
  sport_smallswimmingpool  0.179
           smallwaterroom  0.179
    spacestation_cupboard  0.181
         castle_largeroom  0.185
     icepalace_mediumroom  0.186
    icepalace_longpassage  0.186
   spacestation_smallroom  0.188
      spacestation_alcove  0.192
                dustyroom  0.202
  spacestation_mediumroom  0.209
      icepalace_largeroom  0.214
           driving_tunnel  0.214
                 backyard  0.218
                    creek  0.218
  sport_largeswimmingpool  0.222
           icepalace_hall  0.226
        factory_largeroom  0.231
   spacestation_largeroom  0.233
      icepalace_courtyard  0.235
                     none  0.25
                  generic  0.25
               paddedcell  0.25
                     room  0.25
                 bathroom  0.25
               livingroom  0.25
                stoneroom  0.25
               auditorium  0.25
              concerthall  0.25
                     cave  0.25
                    arena  0.25
                   hangar  0.25
          carpetedhallway  0.25
                  hallway  0.25
            stonecorridor  0.25
                sewerpipe  0.25
               underwater  0.25
                  drugged  0.25
                    dizzy  0.25
                psychotic  0.25
              castle_hall  0.25
         castle_courtyard  0.25
             factory_hall  0.25
        factory_courtyard  0.25
 spacestation_longpassage  0.25
        spacestation_hall  0.25
         wooden_smallroom  0.25
      wooden_shortpassage  0.25
        wooden_mediumroom  0.25
         wooden_largeroom  0.25
       wooden_longpassage  0.25
              wooden_hall  0.25
          wooden_cupboard  0.25
         wooden_courtyard  0.25
            wooden_alcove  0.25
       sport_emptystadium  0.25
        sport_fullstadium  0.25
      sport_stadiumtannoy  0.25
          prefab_workshop  0.25
           prefab_caravan  0.25
               pipe_small  0.25
          dome_saintpauls  0.25
            pipe_longthin  0.25
               pipe_large  0.25
            pipe_resonant  0.25
            rollingplains  0.25
               deepcanyon  0.25
                   valley  0.25
                     city  0.25
                mountains  0.25
                    plain  0.25
               parkinglot  0.25
              mood_heaven  0.25
              mood_memory  0.25
      driving_commentator  0.25
        driving_pitgarage  0.25
      driving_incar_racer  0.25
     driving_incar_sports  0.25
     driving_incar_luxury  0.25
   driving_fullgrandstand  0.25
  driving_emptygrandstand  0.25
             city_streets  0.25
           city_underpass  0.25
           city_abandoned  0.25
                   chapel  0.25

EchoDepth

                   Preset  EchoDepth
                     none  0
                  generic  0
               paddedcell  0
                     room  0
                 bathroom  0
               livingroom  0
                stoneroom  0
               auditorium  0
              concerthall  0
                     cave  0
                    arena  0
                   hangar  0
          carpetedhallway  0
                  hallway  0
            stonecorridor  0
                sewerpipe  0
               underwater  0
                  drugged  0
                psychotic  0
              castle_hall  0
         wooden_smallroom  0
      wooden_shortpassage  0
        wooden_mediumroom  0
         wooden_largeroom  0
       wooden_longpassage  0
              wooden_hall  0
          wooden_cupboard  0
         wooden_courtyard  0
            wooden_alcove  0
       sport_emptystadium  0
        sport_fullstadium  0
          prefab_workshop  0
           prefab_caravan  0
               pipe_small  0
            pipe_longthin  0
               pipe_large  0
            pipe_resonant  0
                     city  0
               parkinglot  0
              mood_memory  0
      driving_incar_racer  0
     driving_incar_sports  0
     driving_incar_luxury  0
   driving_fullgrandstand  0
  driving_emptygrandstand  0
                   chapel  0
        castle_mediumroom  0.03
    icepalace_longpassage  0.04
                mood_hell  0.04
           driving_tunnel  0.05
                dustyroom  0.05
         castle_largeroom  0.07
        factory_smallroom  0.07
       factory_mediumroom  0.07
        factory_largeroom  0.07
             factory_hall  0.07
         factory_cupboard  0.07
         castle_smallroom  0.08
      castle_shortpassage  0.08
       castle_longpassage  0.08
          castle_cupboard  0.08
            castle_alcove  0.08
       icepalace_cupboard  0.08
              mood_heaven  0.08
   icepalace_shortpassage  0.09
         icepalace_alcove  0.09
           factory_alcove  0.1
      icepalace_largeroom  0.11
           icepalace_hall  0.11
        driving_pitgarage  0.11
     icepalace_mediumroom  0.12
          dome_saintpauls  0.12
      icepalace_smallroom  0.14
          sport_gymnasium  0.14
      prefab_practiseroom  0.14
           city_underpass  0.14
  sport_smallswimmingpool  0.15
           smallwaterroom  0.15
          prefab_outhouse  0.17
              city_museum  0.17
             city_library  0.17
        sport_squashcourt  0.19
                dome_tomb  0.19
spacestation_shortpassage  0.2
      sport_stadiumtannoy  0.2
             city_streets  0.2
           city_abandoned  0.2
      spacestation_alcove  0.21
              city_subway  0.21
     factory_shortpassage  0.23
      factory_longpassage  0.23
 spacestation_longpassage  0.23
        spacestation_hall  0.25
   spacestation_smallroom  0.26
   spacestation_largeroom  0.28
        factory_courtyard  0.29
  spacestation_mediumroom  0.31
    spacestation_cupboard  0.31
                 backyard  0.34
                    creek  0.34
                   valley  0.34
         castle_courtyard  0.37
      icepalace_courtyard  0.48
  sport_largeswimmingpool  0.55
                   quarry  0.7
                    alley  0.95
                    dizzy  1
            rollingplains  1
               deepcanyon  1
                   forest  1
                mountains  1
                    plain  1
      driving_commentator  1

ModulationTime

                   Preset  ModulationTime
                     none  0.25
                  generic  0.25
               paddedcell  0.25
                     room  0.25
                 bathroom  0.25
               livingroom  0.25
                stoneroom  0.25
               auditorium  0.25
              concerthall  0.25
                     cave  0.25
                    arena  0.25
                   hangar  0.25
          carpetedhallway  0.25
                  hallway  0.25
            stonecorridor  0.25
                sewerpipe  0.25
                  drugged  0.25
         castle_smallroom  0.25
      castle_shortpassage  0.25
        castle_mediumroom  0.25
         castle_largeroom  0.25
       castle_longpassage  0.25
              castle_hall  0.25
          castle_cupboard  0.25
         castle_courtyard  0.25
            castle_alcove  0.25
        factory_smallroom  0.25
     factory_shortpassage  0.25
       factory_mediumroom  0.25
        factory_largeroom  0.25
      factory_longpassage  0.25
             factory_hall  0.25
         factory_cupboard  0.25
        factory_courtyard  0.25
           factory_alcove  0.25
      icepalace_smallroom  0.25
   icepalace_shortpassage  0.25
     icepalace_mediumroom  0.25
      icepalace_largeroom  0.25
    icepalace_longpassage  0.25
           icepalace_hall  0.25
       icepalace_cupboard  0.25
      icepalace_courtyard  0.25
         icepalace_alcove  0.25
   spacestation_smallroom  0.25
spacestation_shortpassage  0.25
  spacestation_mediumroom  0.25
   spacestation_largeroom  0.25
 spacestation_longpassage  0.25
        spacestation_hall  0.25
    spacestation_cupboard  0.25
      spacestation_alcove  0.25
         wooden_smallroom  0.25
      wooden_shortpassage  0.25
        wooden_mediumroom  0.25
         wooden_largeroom  0.25
       wooden_longpassage  0.25
              wooden_hall  0.25
          wooden_cupboard  0.25
         wooden_courtyard  0.25
            wooden_alcove  0.25
       sport_emptystadium  0.25
        sport_squashcourt  0.25
          sport_gymnasium  0.25
        sport_fullstadium  0.25
      sport_stadiumtannoy  0.25
          prefab_workshop  0.25
      prefab_practiseroom  0.25
          prefab_outhouse  0.25
           prefab_caravan  0.25
                dome_tomb  0.25
               pipe_small  0.25
          dome_saintpauls  0.25
            pipe_longthin  0.25
               pipe_large  0.25
            pipe_resonant  0.25
                 backyard  0.25
            rollingplains  0.25
               deepcanyon  0.25
                    creek  0.25
                   valley  0.25
                    alley  0.25
                   forest  0.25
                     city  0.25
                mountains  0.25
                   quarry  0.25
                    plain  0.25
               parkinglot  0.25
      driving_commentator  0.25
        driving_pitgarage  0.25
      driving_incar_racer  0.25
     driving_incar_sports  0.25
     driving_incar_luxury  0.25
   driving_fullgrandstand  0.25
  driving_emptygrandstand  0.25
           driving_tunnel  0.25
             city_streets  0.25
              city_subway  0.25
              city_museum  0.25
             city_library  0.25
           city_underpass  0.25
           city_abandoned  0.25
                dustyroom  0.25
                   chapel  0.25
              mood_memory  0.474
                    dizzy  0.81
  sport_smallswimmingpool  0.895
           smallwaterroom  0.895
  sport_largeswimmingpool  1.159
               underwater  1.18
                mood_hell  2.109
              mood_heaven  2.742
                psychotic  4

ModulationDepth

                   Preset  ModulationDepth
                     none  0
                  generic  0
               paddedcell  0
                     room  0
                 bathroom  0
               livingroom  0
                stoneroom  0
               auditorium  0
              concerthall  0
                     cave  0
                    arena  0
                   hangar  0
          carpetedhallway  0
                  hallway  0
            stonecorridor  0
                sewerpipe  0
         castle_smallroom  0
      castle_shortpassage  0
        castle_mediumroom  0
         castle_largeroom  0
       castle_longpassage  0
              castle_hall  0
          castle_cupboard  0
         castle_courtyard  0
            castle_alcove  0
        factory_smallroom  0
     factory_shortpassage  0
       factory_mediumroom  0
        factory_largeroom  0
      factory_longpassage  0
             factory_hall  0
         factory_cupboard  0
        factory_courtyard  0
           factory_alcove  0
      icepalace_smallroom  0
   icepalace_shortpassage  0
     icepalace_mediumroom  0
      icepalace_largeroom  0
    icepalace_longpassage  0
           icepalace_hall  0
       icepalace_cupboard  0
      icepalace_courtyard  0
         icepalace_alcove  0
   spacestation_smallroom  0
spacestation_shortpassage  0
  spacestation_mediumroom  0
   spacestation_largeroom  0
 spacestation_longpassage  0
        spacestation_hall  0
    spacestation_cupboard  0
      spacestation_alcove  0
         wooden_smallroom  0
      wooden_shortpassage  0
        wooden_mediumroom  0
         wooden_largeroom  0
       wooden_longpassage  0
              wooden_hall  0
          wooden_cupboard  0
         wooden_courtyard  0
            wooden_alcove  0
       sport_emptystadium  0
        sport_squashcourt  0
          sport_gymnasium  0
        sport_fullstadium  0
      sport_stadiumtannoy  0
          prefab_workshop  0
      prefab_practiseroom  0
          prefab_outhouse  0
           prefab_caravan  0
                dome_tomb  0
               pipe_small  0
          dome_saintpauls  0
            pipe_longthin  0
               pipe_large  0
            pipe_resonant  0
                 backyard  0
            rollingplains  0
               deepcanyon  0
                    creek  0
                   valley  0
                    alley  0
                   forest  0
                     city  0
                mountains  0
                   quarry  0
                    plain  0
               parkinglot  0
      driving_commentator  0
        driving_pitgarage  0
      driving_incar_racer  0
     driving_incar_sports  0
     driving_incar_luxury  0
   driving_fullgrandstand  0
  driving_emptygrandstand  0
           driving_tunnel  0
             city_streets  0
              city_subway  0
              city_museum  0
             city_library  0
           city_underpass  0
           city_abandoned  0
                dustyroom  0
              mood_heaven  0.05
                   chapel  0.11
  sport_smallswimmingpool  0.19
           smallwaterroom  0.19
  sport_largeswimmingpool  0.21
                    dizzy  0.31
               underwater  0.348
              mood_memory  0.45
                mood_hell  0.52
                  drugged  1
                psychotic  1

AirAbsorptionGainHF

                  Preset  AirAbsorptionGainHF
              mood_memory  0.9886
      driving_commentator  0.9886
                dustyroom  0.9886
           city_underpass  0.992
           smallwaterroom  0.992
                     none  0.9943
                  generic  0.9943
               paddedcell  0.9943
                     room  0.9943
                 bathroom  0.9943
               livingroom  0.9943
                stoneroom  0.9943
               auditorium  0.9943
              concerthall  0.9943
                     cave  0.9943
                    arena  0.9943
                   hangar  0.9943
          carpetedhallway  0.9943
                  hallway  0.9943
            stonecorridor  0.9943
                sewerpipe  0.9943
               underwater  0.9943
                  drugged  0.9943
                    dizzy  0.9943
                psychotic  0.9943
         castle_smallroom  0.9943
      castle_shortpassage  0.9943
        castle_mediumroom  0.9943
         castle_largeroom  0.9943
       castle_longpassage  0.9943
              castle_hall  0.9943
          castle_cupboard  0.9943
         castle_courtyard  0.9943
            castle_alcove  0.9943
        factory_smallroom  0.9943
     factory_shortpassage  0.9943
       factory_mediumroom  0.9943
        factory_largeroom  0.9943
      factory_longpassage  0.9943
             factory_hall  0.9943
         factory_cupboard  0.9943
        factory_courtyard  0.9943
           factory_alcove  0.9943
      icepalace_smallroom  0.9943
   icepalace_shortpassage  0.9943
     icepalace_mediumroom  0.9943
      icepalace_largeroom  0.9943
    icepalace_longpassage  0.9943
           icepalace_hall  0.9943
       icepalace_cupboard  0.9943
      icepalace_courtyard  0.9943
         icepalace_alcove  0.9943
   spacestation_smallroom  0.9943
spacestation_shortpassage  0.9943
  spacestation_mediumroom  0.9943
   spacestation_largeroom  0.9943
 spacestation_longpassage  0.9943
        spacestation_hall  0.9943
    spacestation_cupboard  0.9943
      spacestation_alcove  0.9943
         wooden_smallroom  0.9943
      wooden_shortpassage  0.9943
        wooden_mediumroom  0.9943
         wooden_largeroom  0.9943
       wooden_longpassage  0.9943
              wooden_hall  0.9943
          wooden_cupboard  0.9943
         wooden_courtyard  0.9943
            wooden_alcove  0.9943
       sport_emptystadium  0.9943
        sport_squashcourt  0.9943
  sport_smallswimmingpool  0.9943
  sport_largeswimmingpool  0.9943
          sport_gymnasium  0.9943
        sport_fullstadium  0.9943
      sport_stadiumtannoy  0.9943
          prefab_workshop  0.9943
      prefab_practiseroom  0.9943
          prefab_outhouse  0.9943
           prefab_caravan  0.9943
                dome_tomb  0.9943
               pipe_small  0.9943
          dome_saintpauls  0.9943
            pipe_longthin  0.9943
               pipe_large  0.9943
            pipe_resonant  0.9943
                 backyard  0.9943
            rollingplains  0.9943
               deepcanyon  0.9943
                    creek  0.9943
                   valley  0.9943
                    alley  0.9943
                   forest  0.9943
                     city  0.9943
                mountains  0.9943
                   quarry  0.9943
                    plain  0.9943
               parkinglot  0.9943
                mood_hell  0.9943
        driving_pitgarage  0.9943
      driving_incar_racer  0.9943
     driving_incar_sports  0.9943
     driving_incar_luxury  0.9943
   driving_fullgrandstand  0.9943
  driving_emptygrandstand  0.9943
           driving_tunnel  0.9943
             city_streets  0.9943
              city_subway  0.9943
              city_museum  0.9943
             city_library  0.9943
                   chapel  0.9943
           city_abandoned  0.9966
              mood_heaven  0.9977

LFReference

                   Preset  LFReference
                dome_tomb  20
               pipe_small  20
          dome_saintpauls  20
            pipe_longthin  20
               pipe_large  20
            pipe_resonant  20
      icepalace_smallroom  99.6
   icepalace_shortpassage  99.6
     icepalace_mediumroom  99.6
      icepalace_largeroom  99.6
    icepalace_longpassage  99.6
           icepalace_hall  99.6
       icepalace_cupboard  99.6
      icepalace_courtyard  99.6
         icepalace_alcove  99.6
         wooden_smallroom  99.6
      wooden_shortpassage  99.6
        wooden_mediumroom  99.6
         wooden_largeroom  99.6
       wooden_longpassage  99.6
              wooden_hall  99.6
          wooden_cupboard  99.6
         wooden_courtyard  99.6
            wooden_alcove  99.6
          prefab_outhouse  107.5
                   valley  107.5
              city_museum  107.5
             city_library  107.5
         castle_smallroom  139.5
      castle_shortpassage  139.5
        castle_mediumroom  139.5
         castle_largeroom  139.5
       castle_longpassage  139.5
              castle_hall  139.5
          castle_cupboard  139.5
            castle_alcove  139.5
                mood_hell  139.5
           driving_tunnel  155.3
                dustyroom  163.3
        sport_squashcourt  211.2
          sport_gymnasium  211.2
      prefab_practiseroom  211.2
                 backyard  242.9
            rollingplains  242.9
               deepcanyon  242.9
                    creek  242.9
                     none  250
                  generic  250
               paddedcell  250
                     room  250
                 bathroom  250
               livingroom  250
                stoneroom  250
               auditorium  250
              concerthall  250
                     cave  250
                    arena  250
                   hangar  250
          carpetedhallway  250
                  hallway  250
            stonecorridor  250
                sewerpipe  250
               underwater  250
                  drugged  250
                    dizzy  250
                psychotic  250
         castle_courtyard  250
       sport_emptystadium  250
  sport_smallswimmingpool  250
  sport_largeswimmingpool  250
        sport_fullstadium  250
      sport_stadiumtannoy  250
          prefab_workshop  250
           prefab_caravan  250
                    alley  250
                   forest  250
                     city  250
                mountains  250
                   quarry  250
                    plain  250
               parkinglot  250
              mood_heaven  250
              mood_memory  250
      driving_commentator  250
        driving_pitgarage  250
   driving_fullgrandstand  250
  driving_emptygrandstand  250
             city_streets  250
              city_subway  250
           city_underpass  250
           city_abandoned  250
                   chapel  250
           smallwaterroom  250
      driving_incar_racer  251
     driving_incar_sports  251
     driving_incar_luxury  251
        factory_smallroom  362.5
     factory_shortpassage  362.5
       factory_mediumroom  362.5
        factory_largeroom  362.5
      factory_longpassage  362.5
             factory_hall  362.5
         factory_cupboard  362.5
        factory_courtyard  362.5
           factory_alcove  362.5
   spacestation_smallroom  458.2
spacestation_shortpassage  458.2
  spacestation_mediumroom  458.2
   spacestation_largeroom  458.2
 spacestation_longpassage  458.2
        spacestation_hall  458.2
    spacestation_cupboard  458.2
      spacestation_alcove  458.2

HFReference

                   Preset  HFReference
          prefab_outhouse  2854.4
                dome_tomb  2854.4
               pipe_small  2854.4
          dome_saintpauls  2854.4
            pipe_longthin  2854.4
               pipe_large  2854.4
            pipe_resonant  2854.4
                   valley  2854.4
              city_museum  2854.4
             city_library  2854.4
   spacestation_smallroom  3316.1
spacestation_shortpassage  3316.1
  spacestation_mediumroom  3316.1
   spacestation_largeroom  3316.1
 spacestation_longpassage  3316.1
        spacestation_hall  3316.1
    spacestation_cupboard  3316.1
      spacestation_alcove  3316.1
        factory_smallroom  3762.6
     factory_shortpassage  3762.6
       factory_mediumroom  3762.6
        factory_largeroom  3762.6
      factory_longpassage  3762.6
             factory_hall  3762.6
         factory_cupboard  3762.6
        factory_courtyard  3762.6
           factory_alcove  3762.6
                 backyard  4399.1
            rollingplains  4399.1
               deepcanyon  4399.1
                    creek  4399.1
         wooden_smallroom  4705
      wooden_shortpassage  4705
        wooden_mediumroom  4705
         wooden_largeroom  4705
       wooden_longpassage  4705
              wooden_hall  4705
          wooden_cupboard  4705
         wooden_courtyard  4705
            wooden_alcove  4705
                     none  5000
                  generic  5000
               paddedcell  5000
                     room  5000
                 bathroom  5000
               livingroom  5000
                stoneroom  5000
               auditorium  5000
              concerthall  5000
                     cave  5000
                    arena  5000
                   hangar  5000
          carpetedhallway  5000
                  hallway  5000
            stonecorridor  5000
                sewerpipe  5000
               underwater  5000
                  drugged  5000
                    dizzy  5000
                psychotic  5000
         castle_courtyard  5000
       sport_emptystadium  5000
  sport_smallswimmingpool  5000
  sport_largeswimmingpool  5000
        sport_fullstadium  5000
      sport_stadiumtannoy  5000
          prefab_workshop  5000
           prefab_caravan  5000
                    alley  5000
                   forest  5000
                     city  5000
                mountains  5000
                   quarry  5000
                    plain  5000
               parkinglot  5000
              mood_heaven  5000
                mood_hell  5000
              mood_memory  5000
      driving_commentator  5000
        driving_pitgarage  5000
           driving_tunnel  5000
             city_streets  5000
              city_subway  5000
           city_underpass  5000
           city_abandoned  5000
                   chapel  5000
           smallwaterroom  5000
         castle_smallroom  5168.6
      castle_shortpassage  5168.6
        castle_mediumroom  5168.6
         castle_largeroom  5168.6
       castle_longpassage  5168.6
              castle_hall  5168.6
          castle_cupboard  5168.6
            castle_alcove  5168.6
        sport_squashcourt  7176.9
          sport_gymnasium  7176.9
      prefab_practiseroom  7176.9
      driving_incar_racer  10268.2
     driving_incar_sports  10268.2
     driving_incar_luxury  10268.2
   driving_fullgrandstand  10420.2
  driving_emptygrandstand  10420.2
      icepalace_smallroom  12428.5
   icepalace_shortpassage  12428.5
     icepalace_mediumroom  12428.5
      icepalace_largeroom  12428.5
    icepalace_longpassage  12428.5
           icepalace_hall  12428.5
       icepalace_cupboard  12428.5
      icepalace_courtyard  12428.5
         icepalace_alcove  12428.5
                dustyroom  13046

RoomRolloffFactor

                   Preset  RoomRolloffFactor
                      all  0

DecayHFLimit

                   Preset  DecayHFLimit
                     cave  0
                  drugged  0
                    dizzy  0
                psychotic  0
         castle_courtyard  0
  sport_smallswimmingpool  0
  sport_largeswimmingpool  0
          prefab_workshop  0
          prefab_outhouse  0
           prefab_caravan  0
                dome_tomb  0
            pipe_longthin  0
            pipe_resonant  0
                 backyard  0
            rollingplains  0
               deepcanyon  0
                    creek  0
                   valley  0
                mountains  0
               parkinglot  0
                mood_hell  0
              mood_memory  0
        driving_pitgarage  0
   driving_fullgrandstand  0
  driving_emptygrandstand  0
              city_museum  0
             city_library  0
           smallwaterroom  0
                     none  1
                  generic  1
               paddedcell  1
                     room  1
                 bathroom  1
               livingroom  1
                stoneroom  1
               auditorium  1
              concerthall  1
                    arena  1
                   hangar  1
          carpetedhallway  1
                  hallway  1
            stonecorridor  1
                sewerpipe  1
               underwater  1
         castle_smallroom  1
      castle_shortpassage  1
        castle_mediumroom  1
         castle_largeroom  1
       castle_longpassage  1
              castle_hall  1
          castle_cupboard  1
            castle_alcove  1
        factory_smallroom  1
     factory_shortpassage  1
       factory_mediumroom  1
        factory_largeroom  1
      factory_longpassage  1
             factory_hall  1
         factory_cupboard  1
        factory_courtyard  1
           factory_alcove  1
      icepalace_smallroom  1
   icepalace_shortpassage  1
     icepalace_mediumroom  1
      icepalace_largeroom  1
    icepalace_longpassage  1
           icepalace_hall  1
       icepalace_cupboard  1
      icepalace_courtyard  1
         icepalace_alcove  1
   spacestation_smallroom  1
spacestation_shortpassage  1
  spacestation_mediumroom  1
   spacestation_largeroom  1
 spacestation_longpassage  1
        spacestation_hall  1
    spacestation_cupboard  1
      spacestation_alcove  1
         wooden_smallroom  1
      wooden_shortpassage  1
        wooden_mediumroom  1
         wooden_largeroom  1
       wooden_longpassage  1
              wooden_hall  1
          wooden_cupboard  1
         wooden_courtyard  1
            wooden_alcove  1
       sport_emptystadium  1
        sport_squashcourt  1
          sport_gymnasium  1
        sport_fullstadium  1
      sport_stadiumtannoy  1
      prefab_practiseroom  1
               pipe_small  1
          dome_saintpauls  1
               pipe_large  1
                    alley  1
                   forest  1
                     city  1
                   quarry  1
                    plain  1
              mood_heaven  1
      driving_commentator  1
      driving_incar_racer  1
     driving_incar_sports  1
     driving_incar_luxury  1
           driving_tunnel  1
             city_streets  1
              city_subway  1
           city_underpass  1
           city_abandoned  1
                dustyroom  1
                   chapel  1

Emitter Gain Formula

This function converts energy values to gain values, and is invoked twice per emitter:

  • once with low-frequency energy values
  • once with high-frequency energy values

The default formula specifies that 10% of occlusion energy, or 10% of permeation energy is required for this emitter to be at max volume:

emitter.GainFormula = (
    bool lowFrequency,
    int occlusionRayCount,
    int permeationRayCount,
    int permeationBounceCount,
    float occlusionEnergy,
    float permeationEnergy)
{
    float gain = 0.0f;

    if (occlusionRayCount > 0)
    {
        // 10% of occlusion energy is required for this emitter to be at max volume
        float energyThreshold = 0.1f * occlusionRayCount;
        gain += occlusionEnergy / energyThreshold;
    }

    if (permeationRayCount > 0 && permeationBounceCount > 0)
    {
        // 10% of permeation energy is required for this emitter to be at max volume
        float energyThreshold = 0.1f * (permeationRayCount * permeationBounceCount);
        gain += permeationEnergy / energyThreshold;
    }

    return MathF.Min(1, gain);
}

In JS, the gain formula is not configurable. Instead, set the energy cap properties on the emitter to define how much energy is required for it to be at full volume:

// 10% of occlusion energy is required for this emitter to be at max volume
emitter.occlusionEnergyCap = 0.1;

// 10% of permeation energy is required for this emitter to be at max volume
emitter.permeationEnergyCap = 0.1;
float GainFormula(
    bool lowFrequency,
    int occlusionRayCount,
    int permeationRayCount,
    int permeationBounceCount,
    float occlusionEnergy,
    float permeationEnergy)
{
    float gain = 0.0f;

    if (occlusionRayCount > 0)
    {
        // 10% of occlusion energy is required for this emitter to be at max volume
        float energyThreshold = 0.1f * occlusionRayCount;
        gain += occlusionEnergy / energyThreshold;
    }

    if (permeationRayCount > 0 && permeationBounceCount > 0)
    {
        // 10% of permeation energy is required for this emitter to be at max volume
        float energyThreshold = 0.1f * (permeationRayCount * permeationBounceCount);
        gain += permeationEnergy / energyThreshold;
    }

    return gain < 1.0f ? gain : 1.0f;
}

vaEmitterSetGainFormula(emitter, GainFormula);

This function determines the volume of this emitter when it is discovered by other emitters. The ray/bounce parameters match the number of rays/bounces cast by the other emitter

These settings determine the volume of this emitter when it is discovered by other emitters

For example if there's 5% energy in all occlusion rays, and 5% energy in all permeation rays, they will accumulate to the 10% energy threshold, and the target will be at maximum volume.

Parameters:

  • lowFrequency - indicates whether this formula is invoked with low-frequency energy values or high-frequency energy values (this formula is invoked twice)
  • occlusionRayCount is the number of occlusion rays that were cast by the other emitter
  • permeationRayCount is the number of permeation rays that were cast by the other emitter
  • permeationBounceCount is the number of bounces per permeation ray
  • occlusionEnergy is in the range 0.0 to 1.0, where 1.0 means all occlusion rays reached the target with maximum energy remaining (i.e. no occlusion)
  • permeationEnergy is in the range 0.0 to 1.0, where 1.0 means all permeation rays reached the target with maximum energy remaining (i.e. all rays passed through air)

The results of these functions are stored directly on the emitter that cast occlusion/permeation rays, can can be accessed via listener.GetTargetFilter(target)).

Debug Window

When RenderingEnabled is set to true, a separate debug window will appear that renders the raytracing scene (3D primitives and emitters).

You can move the camera with the mouse and WASD keys, or control the camera via code:

var world = new World()
{
    RenderingEnabled = true,
};

// Update the camera
world.CameraPosition = new Vector(4, 10, 4);
world.CameraPitch = 0.0f;
world.CameraYaw = MathF.PI / 2;
world.FieldOfView = float.DegreesToRadians(50);
Not available in JS yet
Not available in C yet

Rendering can be toggled at runtime, but can only be set to true on one World at a time.

There is no debug window for JS. Rendering with WebGL to a HTML canvas is a planned feature. Roadmap

The debug window is not available in the C SDK. A workaround is planned for the C# SDK to read data from a native raytracing world and render it in a debug window. Roadmap

Full Code Example

using vaudio;

public class Demo
{
    World world;
    PrismPrimitive prism;
    Emitter listener;
    Emitter enemy;

    public Demo()
    {
        // Create a raytracing world
        world = new World()
        {
            WorldSize = new Vector(100),
            RenderingEnabled = true,
            OnReverbUpdated = OnReverbUpdated,
        };

        // Create a rectangular prism
        prism = new PrismPrimitive()
        {
            // Set its material
            material = MaterialType.Concrete,
            
            // Set its size
            size = new Vector(20, 120, 20),
            
            // Rotate and position it
            transform = Matrix.CreateRotationY(MathF.PI / 4) *
                        Matrix.CreateTranslation(50, 50, 50)
        };

        world.AddPrimitive(prism);

        // Create a listener emitter with all ray types enabled
        listener = new Emitter()
        {
            Name = "Listener",
            Position = new Vector(10, 50, 10),
            
            ReverbRayCount = 128,
            ReverbBounceCount = 64,
            ReverbEnergyCap = 128 * 64 * 0.05,
            MaxEchogramTime = 5000,
            EchogramGranularity = 50,
            
            OcclusionRayCount = 512,
            OcclusionBounceCount = 8,
            PermeationRayCount = 128,
            PermeationBounceCount = 3,
            
            AmbientOcclusionRayCount = 512,
            AmbientOcclusionBounceCount = 8,
            AmbientPermeationRayCount = 128,
            AmbientPermeationBounceCount = 4
        };
        
        world.AddEmitter(listener);
        
        // Create an enemy emitter
        enemy = new Emitter()
        {
            Name = "Enemy",
            Position = new Vector(90, 50, 10)
        };
        
        enemy.OnRaytracedByAnotherEmitter = (Emitter other) =>
        {
            var filter = other.GetTargetFilter(enemy);
            
            // PSEUDOCODE
            // Godot.PlaySound(SoundType.EnemySpawn, enemy.position, filter);       
        }
        
        world.AddEmitter(enemy);
        
        // Tell the listener to cast occlusion and permeation rays towards the enemy
        listener.AddTarget(enemy);
    }
    
    void OnReverbUpdated()
    {
        // Access the listener's reverb stats
        var returnedPercent = listener.ProcessedReverb.ReturningPercent;
        var decayTime = listener.EAX.DecayTime;
        
        // Access ambient stats
        var outsidePercent = listener.OutsidePercent;
        var ambientGainLF = listener.AmbientFilter.GainLF;
        
    }

    Stopwatch watch = Stopwatch.StartNew();

    public void Update()
    {
        // Rotate and position the prism in real time
        var rotation = watch.ElapsedMilliseconds / 1000.0f;

        prism.transform = Matrix.CreateRotationY(rotation) *
                          Matrix.CreateTranslation(50, 50, 50);
        
        // Perform raytracing on background threads
        world.Update();
    }
}
import { dotnet } from './_framework/dotnet.js';
import { wrapVA, MaterialType } from './vaudio-wrapper.js';

// Set this to the number of threads you'll use
const YOUR_THREAD_COUNT = 4;

// The .NET runtime needs 6 threads. Without this, the browser may freeze
const DOTNET_THREAD_COUNT = 6;

// Initialise the .NET runtime
const { getAssemblyExports, getConfig } = await dotnet
    .withDiagnosticTracing(false)
    .withConfig({
        jsThreadBlockingMode: "DangerousAllowBlockingWait", // Without this all calls to WASM are async
        pthreadPoolInitialSize: YOUR_THREAD_COUNT + DOTNET_THREAD_COUNT,
        pthreadPoolUnusedSize: 0,
    })
    .create();

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);

// Wrap all Web Assembly calls into JS objects with get/setters for ease of use
const va = wrapVA(exports.VAudioExports);

// Create a raytracing world
const world = va.World_Create();
world.worldSize = { x: 100, y: 100, z: 100 };
world.maximumConcurrencyLevel = YOUR_THREAD_COUNT;

world.onReverbUpdated = () =>
{
    // Access the listener's reverb stats
    const returnedPercent = listener.processedReverb.returningPercent;
    const decayTime = listener.eax.decayTime;
    
    // Access ambient stats
    const outsidePercent = listener.outsidePercent;
    const ambientGainLF = listener.ambientFilter.gainLF;
};


// Create a rectangular prism
const prism = va.PrismPrimitive_Create();

prism.material = MaterialType.Concrete;
prism.size = { x: 20, y: 120, z: 20 };
prism.transform = va.Matrix.Multiply(
    va.Matrix.CreateRotationY(Math.PI / 4),
    va.Matrix.CreateTranslation(50, 50, 50)
);

world.addPrimitive(prism);
   
// Create a listener emitter with all ray types enabled
const listener = va.Emitter_Create();
listener.name = "Listener";
listener.setPosition(10, 50, 10);

listener.reverbRayCount = 128;
listener.reverbBounceCount = 64;
listener.reverbEnergyCap = 128 * 64 * 0.05;
listener.maxEchogramTime = 5000;
listener.echogramGranularity = 50;

listener.occlusionRayCount = 512;
listener.occlusionBounceCount = 8;
listener.permeationRayCount = 128;
listener.permeationBounceCount = 3;
    
listener.ambientOcclusionRayCount = 512;
listener.ambientOcclusionBounceCount = 8;
listener.ambientPermeationRayCount = 128;
listener.ambientPermeationBounceCount = 4;
        
world.addEmitter(listener);
        
// Create an enemy emitter
const enemy = va.Emitter_Create();
enemy.name = "Enemy";
enemy.setPosition(90, 50, 10);

enemy.onRaytracedByAnotherEmitter = (other) => {
    const filter = other.getTargetFilter(enemy);
    
    // PSEUDOCODE
    // Godot.PlaySound(SoundType.EnemySpawn, enemy.position, filter);       
}

world.addEmitter(enemy);

// Tell the listener to cast occlusion and permeation rays towards the enemy
listener.addTarget(enemy);

// Update loop
const start = performance.now();

function update()
{
    const elapsed = performance.now() - start;

    // Rotate and position the prism in real time
    var rotation = elapsed / 1000.0;
    
    prism.transform = va.Matrix.Multiply(
        va.Matrix.CreateRotationY(rotation),
        va.Matrix.CreateTranslation(50, 50, 50)
    );
    
    // Perform raytracing on background threads
    world.update();
    
    requestAnimationFrame(update);
}

requestAnimationFrame(update);
#include <stdio.h>
#include <math.h>

#include "vaudio.h"

#ifdef _WIN32
    #include <windows.h>
    #define sleep_ms(ms) Sleep(ms)
#else
    #include <time.h>
    #define sleep_ms(ms) do { struct timespec ts = { (ms)/1000, ((ms)%1000)*1000000 }; nanosleep(&ts, NULL); } while(0)
#endif

VAWorld* world;
VAEmitter* listener;

void on_reverb_updated(void)
{
    // Access processed properties
    VAProcessedReverb* processed = vaEmitterGetProcessedReverb(listener);
    float returningPercent = processed->returningPercent;
        
    // Access precalculated EAX properties
    VAEAXReverb* eax = vaEmitterGetEAX(listener);
    float decayTime = eax->decayTime;    
    
    // Access ambient stats
    float outsidePercent = vaEmitterGetOutsidePercent(listener);
    
    VALowPassFilter* ambientFilter = vaEmitterGetAmbientFilter(listener);
    float ambientGainLF = ambientFilter->gainLF;
    float ambientGainHF = ambientFilter->gainHF;
}

void on_raytraced_by_another_emitter(VAEmitter* source, VAEmitter* target)
{
    VALowPassFilter* filter = vaEmitterGetTargetFilter(source, target);

    printf("Emitter raytraced by another. GainLF: %f. GainHF: %f\n", filter->gainLF, filter->gainHF);
    
    // PSEUDOCODE
    // UE.PlaySound(soundType, position, filter);
}

int main(void)
{
    // Create a raytracing world
    VAWorld* world = vaWorldCreate();
    vaWorldSetWorldSize(world, vaVectorCreate(100.0f, 100.0f, 100.0f));

    // Create a rectangular prism
    VAPrismPrimitive* prism = vaPrismPrimitiveCreate();
    
    vaPrismPrimitiveSetMaterial(prism, VAMaterialConcrete);
    vaPrismPrimitiveSetSize(prism, vaVectorCreate(15.0f, 15.0f, 15.0f));
    
    VAMatrix rotation = vaMatrixCreateRotationY(M_PI / 4);
    VAMatrix translate = vaMatrixCreateTranslation(50, 50, 50);
    VAMatrix transform = vaMatrixMultiply(&rotation, &translate);
    vaPrismPrimitiveSetTransform(prism, &transform);
    
    vaWorldAddPrimitive(world, prism);

    // Create a listener emitter with all ray types enabled
    VAEmitter* listener = vaEmitterCreate();
    vaEmitterSetName(listener, "Listener");
    vaEmitterSetPosition(listener, vaVectorCreate(10.0f, 50.0f, 10.0f));
    
    vaEmitterSetReverbRayCount(listener, 128);
    vaEmitterSetReverbBounceCount(listener, 64);
    vaEmitterSetReverbEnergyCap(listener, 128 * 64 * 0.05f);
    
    vaEmitterSetOcclusionRayCount(listener, 512);
    vaEmitterSetOcclusionBounceCount(listener, 8);
    vaEmitterSetPermeationRayCount(listener, 128);
    vaEmitterSetPermeationBounceCount(listener, 3);
    
    vaEmitterSetAmbientOcclusionRayCount(listener, 512);
    vaEmitterSetAmbientOcclusionBounceCount(listener, 8);
    vaEmitterSetAmbientPermeationRayCount(listener, 128);
    vaEmitterSetAmbientPermeationBounceCount(listener, 3);

    int errorCode = vaWorldAddEmitter(world, listener);
    assert(errorCode == 0);    
    
    // Create an enemy emitter
    VAEmitter* source = vaEmitterCreate();
    vaEmitterSetName(source, "Enemy");
    vaEmitterSetPosition(source, vaVectorCreate(90.0f, 50.0f, 10.0f));
    vaEmitterSetOnRaytracedByAnotherEmitterCallback(source, on_raytraced_by_another_emitter);
    
    errorCode = vaWorldAddEmitter(world, source);
    assert(errorCode == 0);    
    
    // Tell the listener to cast occlusion and permeation rays towards the enemy
    vaEmitterAddTarget(listener, source);

    float elapsed = 0.0f;

    while (true)
    {
        // Rotate and position the prism in real time
        float angle = elapsed / 1000.0f;
                
        VAMatrix rotation = vaMatrixCreateRotationY(angle);
        translate = vaMatrixCreateTranslation(50, 50, 50);
        transform = vaMatrixMultiply(&rotation, &translate);
        vaPrismPrimitiveSetTransform(prism, &transform);
        
        // Perform raytracing on background threads
        vaWorldUpdate(world);
        
        sleep_ms(16);
        elapsed += 16.0f;
    }
    
    return 0;
}

Threading Architecture

To reduce the performance impact on your application, all heavy lifting and raytracing is performed on background threads. The main thread does a small amount of work when World.Update()World.update()raytracing_world_update() is invoked, but you shouldn't notice any stutters or FPS drops.

The heavy work that runs on background threads includes:

The flow is:

Trail-Based Raytracing

When a ray is cast from an emitter, it bounces multiple times around the environment. Data about each bounce is stored and persists across updates.

The number of trails is equal to the largest ray count among:

The amount of bounces in a trail is equal to the largest bounce count among:

For example with ReverbBounceCountreverbBounceCountvaEmitterSetReverbBounceCount() set to 8 and OcclusionBounceCountocclusionBounceCountvaEmitterSetOcclusionBounceCount() set to 4, the trail will consist of 8 bounces. Reverb rays will fire back towards the emitter on every bounce, but occlusion line-of-sight rays will only fire on the first 4 bounces.

Trails are cached and only updated when primitives or emitters move. This cache is an important optimisation that enables raytracing to run in real time in complex scenes.

There are a few scenarios to be aware of that will clear this cache for all emitters:

An emitter's cache can also be cleared if:

For best performance, avoid changing material / air absorption / echogram / ray count / bounce count settings at runtime.

Visualisation Rays

Visualisation rays are separate from the above trails - they are cast in random directions every frame and do not benefit from caching.

Godot Changelog

Version 1.1.0

Updated to Vercidium Audio C# SDK v1.1.0

Changes:

Fixes:

Additions:

Optimisations:

Version 1.0.2

https://github.com/vercidium-patreon/godot_raytraced_audio/pull/2

Additions:

Changes:

Fixes:

Version 1.0.1

https://github.com/vercidium-patreon/godot_raytraced_audio/pull/1

Additions:

Changes::

SDK Changelog

C# - Version 1.2.0

Fixes:

Changes:

Additions:

Improvements:

Removed:

Optimisations:

C# - Version 1.1.1

Fixes:

Additions:

Changes:

Optimisations:

C# - Version 1.1.0

See Breaking Changes v1.1.0 for an overview of breaking changes and new features.

Fixes:

Changes:

Additions:

Optimisations:

C# - Version 1.0.6

Fixes:

Changes:

Optimisations:

C# - Version 1.0.5

Fixes:

Changes:

Additions:

C# - Version 1.0.4

Changes:

Additions:

C# - Version 1.0.3

Fixes:

Changes:

Additions:

Optimisations:

C# - Version 1.0.2

Fixes:

Changes:

Optimisations:

C# - Version 1.0.1

Fixes:

Changes:

CapsulePrimitive

A circular capsule with rounded ends.

public class CapsulePrimitive : Primitive
{
    // Length of the cylindrical body between the hemispherical caps
    public float length;

    // Radius of the capsule
    public float radius;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;
}
interface CapsulePrimitive {
    // Created via: va.CapsulePrimitive_Create()

    // Length of the cylindrical body between the hemispherical caps
    get length(); set length(v);

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // Radius of the capsule
    get radius(); set radius(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);
}
// Creates a new CapsulePrimitive with default values
VACapsulePrimitive* vaCapsulePrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaCapsulePrimitiveFree(VACapsulePrimitive* capsule);

// Radius of the capsule cylinder and end caps.
float vaCapsulePrimitiveGetRadius(const VACapsulePrimitive* capsule);

// Length of the capsule cylinder, excluding end caps.
float vaCapsulePrimitiveGetLength(const VACapsulePrimitive* capsule);

// World transform of the capsule.
const VAMatrix* vaCapsulePrimitiveGetTransform(const VACapsulePrimitive* capsule);

// Radius of the capsule cylinder and end caps.
void vaCapsulePrimitiveSetRadius(VACapsulePrimitive* capsule, float radius);

// Length of the capsule cylinder, excluding end caps.
void vaCapsulePrimitiveSetLength(VACapsulePrimitive* capsule, float length);

// Sets the world transform. Must not contain scale components.
void vaCapsulePrimitiveSetTransform(VACapsulePrimitive* capsule, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaCapsulePrimitiveGetMaterial(const VACapsulePrimitive* capsule);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaCapsulePrimitiveSetMaterial(VACapsulePrimitive* capsule, VAMaterialType material);

This is the same as CylinderPrimitive but with rounded caps on either end that extend outside the ends of the cylinder, i.e. the real length of this primitive is length + radius * 2.

The transform matrix should only contain rotation and translation operations, not scale. The size of the capsule should be controlled via the radius and length properties

ConePrimitive

A cone with a circular base.

public class ConePrimitive : Primitive
{
    // Height of the cone from base to apex
    public float height;

    // Radius of the cone base
    public float radius;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;
}
interface ConePrimitive {
    // Created via: va.ConePrimitive_Create()

    // Height of the cone from base to apex
    get height(); set height(v);

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // Radius of the cone base
    get radius(); set radius(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);
}
// Creates a new ConePrimitive with default values
VAConePrimitive* vaConePrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaConePrimitiveFree(VAConePrimitive* cone);

// Radius of the cone base.
float vaConePrimitiveGetRadius(const VAConePrimitive* cone);

// Height of the cone.
float vaConePrimitiveGetHeight(const VAConePrimitive* cone);

// World transform of the cone.
const VAMatrix* vaConePrimitiveGetTransform(const VAConePrimitive* cone);

// Radius of the cone base.
void vaConePrimitiveSetRadius(VAConePrimitive* cone, float radius);

// Height of the cone.
void vaConePrimitiveSetHeight(VAConePrimitive* cone, float height);

// Sets the world transform. Must not contain scale components.
void vaConePrimitiveSetTransform(VAConePrimitive* cone, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaConePrimitiveGetMaterial(const VAConePrimitive* cone);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaConePrimitiveSetMaterial(VAConePrimitive* cone, VAMaterialType material);

The base of the cone is centered at (0, 0, 0), and the tip extends upwards to (0, height, 0)

The transform matrix should only contain rotation and translation operations, not scale. The size of the cone should be controlled via the radius and height properties

CylinderPrimitive

A cylinder.

public class CylinderPrimitive : Primitive
{
    // Length of the cylinder along its axis
    public float length;

    // Radius of the cylinder
    public float radius;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;
}
interface CylinderPrimitive {
    // Created via: va.CylinderPrimitive_Create()

    // Length of the cylinder along its axis
    get length(); set length(v);

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // Radius of the cylinder
    get radius(); set radius(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);
}
// Creates a new CylinderPrimitive with default values
VACylinderPrimitive* vaCylinderPrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaCylinderPrimitiveFree(VACylinderPrimitive* cylinder);

// Radius of the cylinder.
float vaCylinderPrimitiveGetRadius(const VACylinderPrimitive* cylinder);

// Length of the cylinder along its axis.
float vaCylinderPrimitiveGetLength(const VACylinderPrimitive* cylinder);

// World transform of the cylinder.
const VAMatrix* vaCylinderPrimitiveGetTransform(const VACylinderPrimitive* cylinder);

// Radius of the cylinder.
void vaCylinderPrimitiveSetRadius(VACylinderPrimitive* cylinder, float radius);

// Length of the cylinder along its axis.
void vaCylinderPrimitiveSetLength(VACylinderPrimitive* cylinder, float length);

// Sets the world transform. Must not contain scale components.
void vaCylinderPrimitiveSetTransform(VACylinderPrimitive* cylinder, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaCylinderPrimitiveGetMaterial(const VACylinderPrimitive* cylinder);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaCylinderPrimitiveSetMaterial(VACylinderPrimitive* cylinder, VAMaterialType material);

The transform matrix should only contain rotation and translation operations, not scale. The size of the cylinder should be controlled via the radius and length properties

DiskPrimitive

A flat circular disk.

public class DiskPrimitive : Primitive
{
    // Radius of the disk
    public float radius;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;
}
interface DiskPrimitive {
    // Created via: va.DiskPrimitive_Create()

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // Radius of the disk
    get radius(); set radius(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);
}
// Creates a new DiskPrimitive with default values
VADiskPrimitive* vaDiskPrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaDiskPrimitiveFree(VADiskPrimitive* disk);

// Radius of the disk.
float vaDiskPrimitiveGetRadius(const VADiskPrimitive* disk);

// World transform of the disk.
const VAMatrix* vaDiskPrimitiveGetTransform(const VADiskPrimitive* disk);

// Radius of the disk.
void vaDiskPrimitiveSetRadius(VADiskPrimitive* disk, float radius);

// Sets the world transform. Must not contain scale components.
void vaDiskPrimitiveSetTransform(VADiskPrimitive* disk, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaDiskPrimitiveGetMaterial(const VADiskPrimitive* disk);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaDiskPrimitiveSetMaterial(VADiskPrimitive* disk, VAMaterialType material);

The transform matrix should only contain rotation and translation operations, not scale. The size of the disk should be controlled via the radius property

HalfSpherePrimitive

Half of a sphere.

public class HalfSpherePrimitive : Primitive
{
    // Radius of the hemisphere
    public float radius;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;
}
interface HalfSpherePrimitive {
    // Created via: va.HalfSpherePrimitive_Create()

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // Radius of the hemisphere
    get radius(); set radius(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);
}
// Creates a new HalfSpherePrimitive with default values
VAHalfSpherePrimitive* vaHalfSpherePrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaHalfSpherePrimitiveFree(VAHalfSpherePrimitive* halfSphere);

// Radius of the half-sphere.
float vaHalfSpherePrimitiveGetRadius(VAHalfSpherePrimitive* halfSphere);

// World transform of the half-sphere.
const VAMatrix* vaHalfSpherePrimitiveGetTransform(VAHalfSpherePrimitive* halfSphere);

// Radius of the half-sphere.
void vaHalfSpherePrimitiveSetRadius(VAHalfSpherePrimitive* halfSphere, float radius);

// Sets the world transform. Must not contain scale components.
void vaHalfSpherePrimitiveSetTransform(VAHalfSpherePrimitive* halfSphere, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaHalfSpherePrimitiveGetMaterial(const VAHalfSpherePrimitive* halfSphere);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaHalfSpherePrimitiveSetMaterial(VAHalfSpherePrimitive* halfSphere, VAMaterialType material);

WARNING: The transform matrix should only contain rotation and translation operations, not scale. The size of the prism should be controlled via the size property

MeshPrimitive

A triangulated mesh.

TODO

public class MeshPrimitive : Primitive
{
    // Whether sound can permeate through this mesh. Set to true for watertight meshes.
    public bool supportsPermeation;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;

    // Create a mesh primitive from a list of vertices
    public MeshPrimitive(MaterialType material, List<Vector> vertices, Vector minBounds, Vector maxBounds, Matrix transform);

    // Create a mesh primitive from an array of vertices
    public MeshPrimitive(MaterialType material, Vector[] vertices, Vector minBounds, Vector maxBounds, Matrix transform);

    // Dispose the OpenGL vertex buffer (dev build only)
    public void DisposeBuffer(bool alreadyCurrent = false);
}
VAMeshPrimitive* vaMeshPrimitiveCreate(VAMaterialType material, const VAVector* vertices, int vertexCount, VAVector minBounds, VAVector maxBounds, const VAMatrix* transform);

// Frees all resources owned by this primitive
void vaMeshPrimitiveFree(VAMeshPrimitive* primitive);

// World transform matrix for this mesh.
const VAMatrix* vaMeshPrimitiveGetTransform(const VAMeshPrimitive* primitive);

// Sets the world transform matrix; updates all triangle bounds.
void vaMeshPrimitiveSetTransform(VAMeshPrimitive* primitive, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaMeshPrimitiveGetMaterial(const VAMeshPrimitive* cylinder);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaMeshPrimitiveSetMaterial(VAMeshPrimitive* cylinder, VAMaterialType material);

// Whether sound can permeate through this mesh. Set to true for watertight meshes.
bool vaMeshPrimitiveGetSupportsPermeation(const VAMeshPrimitive* primitive);

// Whether sound can permeate through this mesh. Set to true for watertight meshes.
void vaMeshPrimitiveSetSupportsPermeation(VAMeshPrimitive* primitive, bool supportsPermeation);

Unlike other primitives, a MeshPrimitive must be initialised by its constructor, with all data already available.

Once created, only transform and material can be updated. The vertices will be processed into a BVH and cannot be updated.

After creation, the vertices list can be safely cleared/disposed, or used to create another MeshPrimitive.

minBounds and maxBounds must be the minimum and maximum bounds of the 3D model when at rest, i.e. Matrix.Identity.

If the mesh is enclosed, set {C#:supportsPermeation|C:mesh_primitive_set_supports_permeation() to true. If it is not enclosed (e.g. heightmap), permeation is not supported, and rays will lose energy based on the PlaneTransmissionLF/HF fields instead. See Materials.

PlanePrimitive

A flat rectangular plane.

public class PlanePrimitive : Primitive
{
    // Height of the plane
    public float height;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;

    // Width of the plane
    public float width;
}
interface PlanePrimitive {
    // Created via: va.PlanePrimitive_Create()

    // Height of the plane
    get height(); set height(v);

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);

    // Width of the plane
    get width(); set width(v);
}
// Creates a new PlanePrimitive with default values
VAPlanePrimitive* vaPlanePrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaPlanePrimitiveFree(VAPlanePrimitive* plane);

// Width of the plane.
float vaPlanePrimitiveGetWidth(VAPlanePrimitive* plane);

// Height of the plane.
float vaPlanePrimitiveGetHeight(VAPlanePrimitive* plane);

// World transform of the plane.
const VAMatrix* vaPlanePrimitiveGetTransform(VAPlanePrimitive* plane);

// Width of the plane.
void vaPlanePrimitiveSetWidth(VAPlanePrimitive* plane, float width);

// Height of the plane.
void vaPlanePrimitiveSetHeight(VAPlanePrimitive* plane, float height);

// Sets the world transform. Must not contain scale components.
void vaPlanePrimitiveSetTransform(VAPlanePrimitive* plane, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaPlanePrimitiveGetMaterial(const VAPlanePrimitive* plane);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaPlanePrimitiveSetMaterial(VAPlanePrimitive* plane, VAMaterialType material);

Primitive

Base primitive class with a common material field.

public abstract class Primitive
{
    // Determines the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    public MaterialType material;
}
TODO

PrismPrimitive

A rectangular prism.

public class PrismPrimitive : Primitive
{
    // Dimensions of the prism along each axis
    public Vector size;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;
}
interface PrismPrimitive {
    // Created via: va.PrismPrimitive_Create()

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // Dimensions of the prism along each axis
    get size(); set size(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);
}
// Creates a new PrismPrimitive with default values
VAPrismPrimitive* vaPrismPrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaPrismPrimitiveFree(VAPrismPrimitive* prism);

// Width, height, and depth of the prism.
VAVector vaPrismPrimitiveGetSize(const VAPrismPrimitive* prism);

// World transform of the prism.
const VAMatrix* vaPrismPrimitiveGetTransform(const VAPrismPrimitive* prism);

// Width, height, and depth of the prism.
void vaPrismPrimitiveSetSize(VAPrismPrimitive* prism, VAVector size);

// Sets the world transform. Must not contain scale components.
void vaPrismPrimitiveSetTransform(VAPrismPrimitive* prism, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaPrismPrimitiveGetMaterial(const VAPrismPrimitive* prism);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaPrismPrimitiveSetMaterial(VAPrismPrimitive* prism, VAMaterialType material);

The transform matrix should only contain rotation and translation operations, not scale. The size of the prism should be controlled via the size property

RectangularConePrimitive

A cone with a rectangle base.

public class RectangularConePrimitive : Primitive
{
    // Height of the cone from base to apex
    public float height;

    // Length of the rectangular base
    public float length;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;

    // Width of the rectangular base
    public float width;
}
interface RectangularConePrimitive {
    // Created via: va.RectangularConePrimitive_Create()

    // Height of the cone from base to apex
    get height(); set height(v);

    // Length of the rectangular base
    get length(); set length(v);

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);

    // Width of the rectangular base
    get width(); set width(v);
}
// Creates a new RectangularConePrimitive with default values
VARectangularConePrimitive* vaRectangularConePrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaRectangularConePrimitiveFree(VARectangularConePrimitive* rectCone);

// Width of the rectangular cone base.
float vaRectangularConePrimitiveGetWidth(VARectangularConePrimitive* rectCone);

// Length of the rectangular cone base.
float vaRectangularConePrimitiveGetLength(VARectangularConePrimitive* rectCone);

// Height of the rectangular cone.
float vaRectangularConePrimitiveGetHeight(VARectangularConePrimitive* rectCone);

// World transform of the rectangular cone.
const VAMatrix* vaRectangularConePrimitiveGetTransform(VARectangularConePrimitive* rectCone);

// Width of the rectangular cone base.
void vaRectangularConePrimitiveSetWidth(VARectangularConePrimitive* rectCone, float width);

// Length of the rectangular cone base.
void vaRectangularConePrimitiveSetLength(VARectangularConePrimitive* rectCone, float length);

// Height of the rectangular cone.
void vaRectangularConePrimitiveSetHeight(VARectangularConePrimitive* rectCone, float height);

// Sets the world transform. Must not contain scale components.
void vaRectangularConePrimitiveSetTransform(VARectangularConePrimitive* rectCone, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaRectangularConePrimitiveGetMaterial(const VARectangularConePrimitive* rectCone);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaRectangularConePrimitiveSetMaterial(VARectangularConePrimitive* rectCone, VAMaterialType material);

The base of the cone is centered at (0, 0, 0), and the tip extends upwards to (0, height, 0).

The transform matrix should only contain rotation and translation operations, not scale. The size of the cone should be controlled via the width, length and height properties

SpherePrimitive

A sphere.

public class SpherePrimitive : Primitive
{
    // Center position of the sphere in world space
    public Vector center;

    // Radius of the sphere
    public float radius;
}
interface SpherePrimitive {
    // Created via: va.SpherePrimitive_Create()

    // Center position of the sphere in world space
    get center(); set center(v);

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // Radius of the sphere
    get radius(); set radius(v);
}
// Creates a new SpherePrimitive with default values
VASpherePrimitive* vaSpherePrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaSpherePrimitiveFree(VASpherePrimitive* sphere);

// Center of the sphere.
VAVector vaSpherePrimitiveGetCenter(VASpherePrimitive* sphere);

// Radius of the sphere.
float vaSpherePrimitiveGetRadius(VASpherePrimitive* sphere);

// Center of the sphere.
void vaSpherePrimitiveSetCenter(VASpherePrimitive* sphere, VAVector center);

// Radius of the sphere.
void vaSpherePrimitiveSetRadius(VASpherePrimitive* sphere, float radius);

// Material assigned to this primitive.
VAMaterialType vaSpherePrimitiveGetMaterial(const VASpherePrimitive* sphere);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaSpherePrimitiveSetMaterial(VASpherePrimitive* sphere, VAMaterialType material);

TrianglePrimitive

A triangle.

public class TrianglePrimitive : Primitive
{
    public Vector position0;
    public Vector position1;
    public Vector position2;
}
// Creates a new TrianglePrimitive with default values
VATrianglePrimitive* vaTrianglePrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaTrianglePrimitiveFree(VATrianglePrimitive* triangle);

// First vertex position.
VAVector vaTrianglePrimitiveGetPosition0(VATrianglePrimitive* triangle);

// Second vertex position.
VAVector vaTrianglePrimitiveGetPosition1(VATrianglePrimitive* triangle);

// Third vertex position.
VAVector vaTrianglePrimitiveGetPosition2(VATrianglePrimitive* triangle);

// First vertex position.
void vaTrianglePrimitiveSetPosition0(VATrianglePrimitive* triangle, VAVector position);

// Second vertex position.
void vaTrianglePrimitiveSetPosition1(VATrianglePrimitive* triangle, VAVector position);

// Third vertex position.
void vaTrianglePrimitiveSetPosition2(VATrianglePrimitive* triangle, VAVector position);

// Material assigned to this primitive.
VAMaterialType vaTrianglePrimitiveGetMaterial(const VATrianglePrimitive* triangle);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaTrianglePrimitiveSetMaterial(VATrianglePrimitive* triangle, VAMaterialType material);

TriangularConePrimitive

A cone with a triangle base.

public class TriangularConePrimitive : Primitive
{
    // The height of the cone
    public float height;

    // The radius of the triangular base
    public float radius;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;
}
interface TriangularConePrimitive {
    // Created via: va.TriangularConePrimitive_Create()

    // The height of the cone
    get height(); set height(v);

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // The radius of the triangular base
    get radius(); set radius(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);
}
// Creates a new TriangularConePrimitive with default values
VATriangularConePrimitive* vaTriangularConePrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaTriangularConePrimitiveFree(VATriangularConePrimitive* cone);

// Radius of the triangular cone base.
float vaTriangularConePrimitiveGetRadius(VATriangularConePrimitive* cone);

// Height of the triangular cone.
float vaTriangularConePrimitiveGetHeight(VATriangularConePrimitive* cone);

// World transform of the triangular cone.
const VAMatrix* vaTriangularConePrimitiveGetTransform(VATriangularConePrimitive* cone);

// Radius of the triangular cone base.
void vaTriangularConePrimitiveSetRadius(VATriangularConePrimitive* cone, float radius);

// Height of the triangular cone.
void vaTriangularConePrimitiveSetHeight(VATriangularConePrimitive* cone, float height);

// Sets the world transform. Must not contain scale components.
void vaTriangularConePrimitiveSetTransform(VATriangularConePrimitive* cone, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaTriangularConePrimitiveGetMaterial(const VATriangularConePrimitive* cone);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaTriangularConePrimitiveSetMaterial(VATriangularConePrimitive* cone, VAMaterialType material);

The base of the cone is centered at (0, 0, 0), and the tip extends upwards to (0, height, 0).

The transform matrix should only contain rotation and translation operations, not scale. The size of the cone should be controlled via the radius and height properties

TriangularPrismPrimitive

A prism with a triangle base.

public class TriangularPrismPrimitive : Primitive
{
    // Length of the prism along its axis
    public float length;

    // Circumradius of the triangular cross-section
    public float radius;

    // Must only contain rotation and translation components, not scale
    public Matrix transform;
}
interface TriangularPrismPrimitive {
    // Created via: va.TriangularPrismPrimitive_Create()

    // Length of the prism along its axis
    get length(); set length(v);

    // Controls the amount of energy lost when rays bounce off this primitive, permeate through it, and scatter off it
    get material(); set material(v);

    // Circumradius of the triangular cross-section
    get radius(); set radius(v);

    // This transform must only contain rotation and translation components, not scale
    get transform(); set transform(v);
}
// Creates a new TriangularPrismPrimitive with default values
VATriangularPrismPrimitive* vaTriangularPrismPrimitiveCreate(void);

// Frees all resources owned by this primitive
void vaTriangularPrismPrimitiveFree(VATriangularPrismPrimitive* prism);

// Radius of the triangular prism cross-section.
float vaTriangularPrismPrimitiveGetRadius(VATriangularPrismPrimitive* prism);

// Length of the triangular prism along its axis.
float vaTriangularPrismPrimitiveGetLength(VATriangularPrismPrimitive* prism);

// World transform of the triangular prism.
const VAMatrix* vaTriangularPrismPrimitiveGetTransform(VATriangularPrismPrimitive* prism);

// Radius of the triangular prism cross-section.
void vaTriangularPrismPrimitiveSetRadius(VATriangularPrismPrimitive* prism, float radius);

// Length of the triangular prism along its axis.
void vaTriangularPrismPrimitiveSetLength(VATriangularPrismPrimitive* prism, float length);

// Sets the world transform. Must not contain scale components.
void vaTriangularPrismPrimitiveSetTransform(VATriangularPrismPrimitive* prism, const VAMatrix* transform);

// Material assigned to this primitive.
VAMaterialType vaTriangularPrismPrimitiveGetMaterial(const VATriangularPrismPrimitive* prism);

// Material assigned to this primitive. Must not be VAMaterialAir.
void vaTriangularPrismPrimitiveSetMaterial(VATriangularPrismPrimitive* prism, VAMaterialType material);

The transform matrix should only contain rotation and translation operations, not scale. The size of the prism should be controlled via the radius and length properties

CustomEAXFormulas

Virtual class that can be overridden with custom formulas.

public class CustomEAXFormulas
{
    // Calculates the EAX density parameter based on the number of energy peaks in the early echogram
    public virtual float CalculateDensity();

    // Calculates the EAX diffusion parameter based on how smoothly energy accumulates over time
    public virtual float CalculateDiffusion();

    // Calculates the low and high frequency gain values relative to a reference energy level
    public virtual void CalculateFrequencyGains(float referenceEnergy, out float lfGain, out float hfGain);

    // Calculates the delay in seconds before late reverb begins, based on the 25% energy accumulation point
    public virtual float CalculateLateReverbDelay();

    // Calculates the gain for early reflections and late reverb by splitting the echogram at a transition point
    public virtual void CalculateReflectionsAndLateReverbGain(float earlyLateTransitionMs, float referenceEnergy, out float reflectionsGain, out float lateReverbGain);

    // Calculates the delay in seconds before the first significant reflection is detected
    public virtual float CalculateReflectionsDelay(float energyThreshold);

    // Calculates the RT60 reverberation time in seconds for a given echogram using linear regression on the decay slope
    public virtual float CalculateRT60(float[] echogram);

    // Initialises the formula calculator with echogram data from a processed reverb result
    public virtual void Initialise(ProcessedReverb processed, float totalReturningEnergyLF, float totalReturningEnergyHF, float echogramGranularity, float[] echogramLF, float[] echogramHF, float[] echogramAverage);
}
struct VACustomEAXFormulas
{
    // Virtual function pointers
    CustomEAXFormulas_InitialiseFunc initialise;
    CustomEAXFormulas_CalculateDiffusionFunc calculateDiffusion;
    CustomEAXFormulas_CalculateDensityFunc calculateDensity;
    CustomEAXFormulas_CalculateReflectionsDelayFunc calculateReflectionsDelay;
    CustomEAXFormulas_CalculateLateReverbDelayFunc calculateLateReverbDelay;
    CustomEAXFormulas_CalculateFrequencyGainsFunc calculateFrequencyGains;
    CustomEAXFormulas_CalculateReflectionsAndLateReverbGainFunc calculateReflectionsAndLateReverbGain;
    CustomEAXFormulas_CalculateRT60Func calculateRt60;
    CustomEAXFormulas_TryCalculateDecayTimeFunc tryCalculateDecayTime;

    // Data members
    float* tempDiffusionCumulative;
    int tempDiffusionCumulativeLength;

    float* echogramDb;
    int echogramDbLength;

    float binDurationMs;

    VAProcessedReverb* processed;
    float totalReturningEnergyLF;
    float totalReturningEnergyHF;
    float totalReturningEnergyAverage;
    float totalReturningEnergy;
    float* echogramLF;
    float* echogramHF;
    float* echogramAverage;
    int echogramLength;

    float maxEnergy;
};

VACustomEAXFormulas* vaCustomEaxFormulasCreate(void);

void vaCustomEaxFormulasFree(VACustomEAXFormulas* formulas);

EAXConstants

EAX reverb constants from OpenAL.

public class EAXConstants
{
    public const float AIR_ABSORPTION_GAIN_HF_MAX = 1.0f;

    public const float AIR_ABSORPTION_GAIN_HF_MIN = 0.892f;

    public const int DECAY_HF_LIMIT_MAX = 1;

    public const int DECAY_HF_LIMIT_MIN = 0;

    public const float DECAY_HF_RATIO_MAX = 2.0f;

    public const float DECAY_HF_RATIO_MIN = 0.1f;

    public const float DECAY_LF_RATIO_MAX = 2.0f;

    public const float DECAY_LF_RATIO_MIN = 0.1f;

    public const float DECAY_TIME_MAX = 20.0f;

    public const float DECAY_TIME_MIN = 0.1f;

    public const float DENSITY_MAX = 1.0f;

    public const float DENSITY_MIN = 0.0f;

    public const float DIFFUSION_MAX = 1.0f;

    public const float DIFFUSION_MIN = 0.0f;

    public const float ECHO_DEPTH_MAX = 1.0f;

    public const float ECHO_DEPTH_MIN = 0.0f;

    public const float ECHO_TIME_MAX = 0.25f;

    public const float ECHO_TIME_MIN = 0.075f;

    public const float GAIN_HF_MAX = 1.0f;

    public const float GAIN_HF_MIN = 0.0f;

    public const float GAIN_LF_MAX = 1.0f;

    public const float GAIN_LF_MIN = 0.0f;

    public const float GAIN_MAX = 1.0f;

    public const float GAIN_MIN = 0.0f;

    public const float HF_REFERENCE_MAX = 20000f;

    public const float HF_REFERENCE_MIN = 1000f;

    public const float LATE_REVERB_DELAY_MAX = 0.1f;

    public const float LATE_REVERB_DELAY_MIN = 0.0f;

    public const float LATE_REVERB_GAIN_MAX = 10.0f;

    public const float LATE_REVERB_GAIN_MIN = 0.0f;

    public const float LF_REFERENCE_MAX = 1000f;

    public const float LF_REFERENCE_MIN = 20f;

    public const float MODULATION_DEPTH_MAX = 1.0f;

    public const float MODULATION_DEPTH_MIN = 0.0f;

    public const float MODULATION_TIME_MAX = 4.0f;

    public const float MODULATION_TIME_MIN = 0.04f;

    public const float REFLECTIONS_DELAY_MAX = 0.3f;

    public const float REFLECTIONS_DELAY_MIN = 0.0f;

    public const float REFLECTIONS_GAIN_MAX = 3.16f;

    public const float REFLECTIONS_GAIN_MIN = 0.0f;

    public const float ROOM_ROLLOFF_FACTOR_MAX = 10.0f;

    public const float ROOM_ROLLOFF_FACTOR_MIN = 0.0f;

    public static Vector LATE_REVERB_PAN_MAX = new(1);

    public static Vector LATE_REVERB_PAN_MIN = new(-1);

    public static Vector REFLECTIONS_PAN_MAX = new(1);

    public static Vector REFLECTIONS_PAN_MIN = new(-1);
}
const EAXConstants = va.EAXConstants;

class EAXConstants {
    // All values are read-only
    get AIR_ABSORPTION_GAIN_HF_MAX();
    get AIR_ABSORPTION_GAIN_HF_MIN();
    get DECAY_HF_LIMIT_MAX();
    get DECAY_HF_LIMIT_MIN();
    get DECAY_HF_RATIO_MAX();
    get DECAY_HF_RATIO_MIN();
    get DECAY_LF_RATIO_MAX();
    get DECAY_LF_RATIO_MIN();
    get DECAY_TIME_MAX();
    get DECAY_TIME_MIN();
    get DENSITY_MAX();
    get DENSITY_MIN();
    get DIFFUSION_MAX();
    get DIFFUSION_MIN();
    get ECHO_DEPTH_MAX();
    get ECHO_DEPTH_MIN();
    get ECHO_TIME_MAX();
    get ECHO_TIME_MIN();
    get GAIN_HF_MAX();
    get GAIN_HF_MIN();
    get GAIN_LF_MAX();
    get GAIN_LF_MIN();
    get GAIN_MAX();
    get GAIN_MIN();
    get HF_REFERENCE_MAX();
    get HF_REFERENCE_MIN();
    get LATE_REVERB_DELAY_MAX();
    get LATE_REVERB_DELAY_MIN();
    get LATE_REVERB_GAIN_MAX();
    get LATE_REVERB_GAIN_MIN();
    get LATE_REVERB_PAN_MAX();
    get LATE_REVERB_PAN_MIN();
    get LF_REFERENCE_MAX();
    get LF_REFERENCE_MIN();
    get MODULATION_DEPTH_MAX();
    get MODULATION_DEPTH_MIN();
    get MODULATION_TIME_MAX();
    get MODULATION_TIME_MIN();
    get REFLECTIONS_DELAY_MAX();
    get REFLECTIONS_DELAY_MIN();
    get REFLECTIONS_GAIN_MAX();
    get REFLECTIONS_GAIN_MIN();
    get REFLECTIONS_PAN_MAX();
    get REFLECTIONS_PAN_MIN();
    get ROOM_ROLLOFF_FACTOR_MAX();
    get ROOM_ROLLOFF_FACTOR_MIN();
}

EAXReverb

Contains EAX properties and per-emitter gain and pan.

public class EAXReverb
{
    // Linear gain applied per meter of distance for high-frequency air absorption (0.892–1)
    public float AirAbsorptionGainHF;

    // Whether to limit high-frequency decay time to the air absorption limit (0 or 1)
    public int DecayHFLimit;

    // Ratio of high-frequency decay time to mid-frequency decay time (0.1–2)
    public float DecayHFRatio;

    // Ratio of low-frequency decay time to mid-frequency decay time (0.1–2)
    public float DecayLFRatio;

    // Reverberation decay time at mid frequencies, in seconds (0.1–20)
    public float DecayTime;

    // Modal density of the late reverberation (0–1)
    public float Density;

    // Echo diffusion of the late reverberation (0–1)
    public float Diffusion;

    // Amplitude of the echo effect (0–1)
    public float EchoDepth;

    // Cycling time of the echo effect, in seconds (0.075–0.25)
    public float EchoTime;

    // Overall linear gain of the reverb (0–1)
    public float Gain;

    // High-frequency gain of the reverb (0–1)
    public float GainHF;

    // Low-frequency gain of the reverb (0–1)
    public float GainLF;

    // Reference frequency for high-frequency decay ratio, in Hz (1000–20000)
    public float HFReference;

    // Delay of late reverberation relative to early reflections, in seconds (0–0.1)
    public float LateReverbDelay;

    // Linear gain of late reverberation (0–10)
    public float LateReverbGain;

    // Reference frequency for low-frequency decay ratio, in Hz (20–1000)
    public float LFReference;

    // Amplitude of the modulation effect (0–1)
    public float ModulationDepth;

    // Cycling time of the modulation effect, in seconds (0.04–4)
    public float ModulationTime;

    // Delay before early reflections are heard, in seconds (0–0.3)
    public float ReflectionsDelay;

    // Linear gain of early reflections (0–3.16)
    public float ReflectionsGain;

    // The direction that both early reflections and late reverb should be heard from, relative to each emitters with HasRelativeReverb set to true
    public Dictionary<Emitter, Vector> RelativeDirections;

    // The gain for this reverb effect, relative to each emitter with HasRelativeReverb set to true
    public Dictionary<Emitter, float> RelativeGains;

    // Rolloff factor for reflected sound sources (0–10)
    public float RoomRolloffFactor;

    public override bool Equals(object obj);

    public override int GetHashCode();

    // Create an empty EAXReverb object
    public static EAXReverb CreateEmpty();
}
interface EAXReverb {

    // Linear gain applied per meter of distance for high-frequency air absorption (0.892–1)
    get airAbsorptionGainHF();

    // Whether to limit high-frequency decay time to the air absorption limit (0 or 1)
    get decayHFLimit();

    // Ratio of high-frequency decay time to mid-frequency decay time (0.1–2)
    get decayHFRatio();

    // Ratio of low-frequency decay time to mid-frequency decay time (0.1–2)
    get decayLFRatio();

    // Reverberation decay time at mid frequencies, in seconds (0.1–20)
    get decayTime();

    // Modal density of the late reverberation (0–1)
    get density();

    // Echo diffusion of the late reverberation (0–1)
    get diffusion();

    // Amplitude of the echo effect (0–1)
    get echoDepth();

    // Cycling time of the echo effect, in seconds (0.075–0.25)
    get echoTime();

    // Overall linear gain of the reverb (0–1)
    get gain();

    // High-frequency gain of the reverb (0–1)
    get gainHF();

    // Low-frequency gain of the reverb (0–1)
    get gainLF();

    // Reference frequency for high-frequency decay ratio, in Hz (1000–20000)
    get hfReference();

    // Delay of late reverberation relative to early reflections, in seconds (0–0.1)
    get lateReverbDelay();

    // Linear gain of late reverberation (0–10)
    get lateReverbGain();

    // Reference frequency for low-frequency decay ratio, in Hz (20–1000)
    get lfReference();

    // Amplitude of the modulation effect (0–1)
    get modulationDepth();

    // Cycling time of the modulation effect, in seconds (0.04–4)
    get modulationTime();

    // Delay before early reflections are heard, in seconds (0–0.3)
    get reflectionsDelay();

    // Linear gain of early reflections (0–3.16)
    get reflectionsGain();

    // Rolloff factor for reflected sound sources (0–10)
    get roomRolloffFactor();

    // Returns the reverb direction for the given emitter. Returns null if the emitter does not have hasRelativeReverb set to true
    relativeDirections(emitter);

    // Returns the effect slot gain for the given emitter. Returns -1 if the emitter does not have hasRelativeReverb set to true
    relativeGains(emitter);
}
typedef struct VAEAXReverb
{
    float outsidePercent;
    float returningPercent;
    float materialAbsorptionLF;
    float materialAbsorptionHF;
    float materialRoughness;

    List* relativeDirections;       /* Direction that reverb should be heard from, relative to each emitter with hasRelativeReverb=true. Access via vaEaxReverbGetRelativeDirection */
    List* relativeGains;            /* Gain for this reverb effect, relative to each emitter with hasRelativeReverb=true. Access via vaEaxReverbGetRelativeGain */

    float reflectionsDelay;         /* Delay before early reflections are heard, in seconds (0–0.3) */
    float density;                  /* Modal density of the late reverberation (0–1) */
    float diffusion;                /* Echo diffusion of the late reverberation (0–1) */
    float gainLF;                   /* Low-frequency gain of the reverb (0–1) */
    float gainHF;                   /* High-frequency gain of the reverb (0–1) */
    float gain;                     /* Overall linear gain of the reverb (0–1) */
    float decayTime;                /* Reverberation decay time at mid frequencies, in seconds (0.1–20) */
    float decayLFRatio;             /* Ratio of low-frequency decay time to mid-frequency decay time (0.1–2) */
    float decayHFRatio;             /* Ratio of high-frequency decay time to mid-frequency decay time (0.1–2) */
    float reflectionsGain;          /* Linear gain of early reflections (0–3.16) */
    float lateReverbGain;           /* Linear gain of late reverberation (0–10) */
    float lateReverbDelay;          /* Delay of late reverberation relative to early reflections, in seconds (0–0.1) */
    float echoTime;                 /* Cycling time of the echo effect, in seconds (0.075–0.25) */
    float echoDepth;                /* Amplitude of the echo effect (0–1) */
    float modulationTime;           /* Cycling time of the modulation effect, in seconds (0.04–4) */
    float modulationDepth;          /* Amplitude of the modulation effect (0–1) */
    float airAbsorptionGainHF;      /* Linear gain applied per meter of distance for high-frequency air absorption (0.892–1) */
    float hfReference;              /* Reference frequency for high-frequency decay ratio, in Hz (1000–20000) */
    float lfReference;              /* Reference frequency for low-frequency decay ratio, in Hz (20–1000) */
    float roomRolloffFactor;        /* Rolloff factor for reflected sound sources (0–10) */
    int decayHFLimit;               /* Whether to limit high-frequency decay time to the air absorption limit (0 or 1) */

    int isTempBackground;
} VAEAXReverb;

// Returns NULL if no entry exists for this emitter.
VAVector* vaEaxReverbGetRelativeDirection(const VAEAXReverb* eax, const VAEmitter* emitter);

// Returns NULL if no entry exists for this emitter.
float* vaEaxReverbGetRelativeGain(const VAEAXReverb* eax, const VAEmitter* emitter);

EAXUtils

Helper functions for comparing EAX objects.

public static class EAXUtils
{
    // Finds the candidate reverb preset most similar to the target
    public static EAXReverb FindBestMatch(EAXReverb target, List<EAXReverb> candidates, out int index);

    // Computes a similarity score between two reverb presets
    public static float GetSimilarity(EAXReverb a, EAXReverb b);
}
const EAXUtils = va.EAXUtils;

class EAXUtils {
    // Returns a similarity score between two EAX reverb result objects
    static getSimilarity(a, b);

    // Returns { result, index } — result is the best-matching candidate or null
    static findBestMatch(target, candidates);
}

ProcessedReverb

Contains reverb data that has been processed into a more dev-friendly format.

public class ProcessedReverb
{
    // The average absorption of all surfaces that all rays hit.
    public float MaterialAbsorption;

    // The average high-frequency absorption of all surfaces that all rays hit.
    public float MaterialAbsorptionHF;

    // The average low-frequency absorption of all surfaces that all rays hit.
    public float MaterialAbsorptionLF;

    // The average roughness/scattering of all surfaces that all rays hit.
    public float MaterialRoughness;

    // Inverse of MaterialRoughness.
    public float MaterialSmoothness;

    // The high-frequency reverberation decay time measured from the echogram.
    // Calculated using RT20, RT30, or RT60 method depending on echogram length and room size.
    // Used to calculate EAX DecayTime and DecayHFRatio.
    public float MeasuredDecayTimeHF;

    // The low-frequency reverberation decay time measured from the echogram.
    // Calculated using RT20, RT30, or RT60 method depending on echogram length and room size.
    // Used to calculate EAX DecayTime and DecayLFRatio.
    public float MeasuredDecayTimeLF;

    // The percentage of energy that escaped outside (hit the world edge).
    // Calculated as raw.OutsideTotal / (ReverbRayCount * ReverbBounceCount)
    public float OutsidePercent;

    // The percentage of energy that returned back to the emitter.
    // Calculated as raw.ReturnedTotal / (ReverbRayCount * ReverbBounceCount)
    public float ReturnedPercent;
}
interface ProcessedReverb {

    // Average absorption of all surfaces that all rays hit
    get materialAbsorption();

    // Average high-frequency absorption of all surfaces that all rays hit
    get materialAbsorptionHF();

    // Average low-frequency absorption of all surfaces that all rays hit
    get materialAbsorptionLF();

    // Average roughness/scattering of all surfaces that all rays hit
    get materialRoughness();

    // Inverse of materialRoughness
    get materialSmoothness();

    // High-frequency reverberation decay time measured from the echogram, in seconds
    get measuredDecayTimeHF();

    // Low-frequency reverberation decay time measured from the echogram, in seconds
    get measuredDecayTimeLF();

    // Percentage of energy that escaped outside the world edge
    get outsidePercent();

    // Percentage of energy that returned back to the emitter
    get returnedPercent();
}
typedef struct VAProcessedReverb
{
    // percentages
    float returningPercent;        /* Percentage of energy that returned to the emitter. Calculated as raw.returned_total / (reverb_ray_count * reverb_bounce_count) */
    float outsidePercent;          /* Percentage of energy that escaped outside. Calculated as raw.outside_total / (reverb_ray_count * reverb_bounce_count) */

    // decay times
    float measuredDecayTimeLF;   /* Low-frequency reverberation decay time from the echogram, in seconds. Calculated using RT20/RT30/RT60 method */
    float measuredDecayTimeHF;   /* High-frequency reverberation decay time from the echogram, in seconds. Calculated using RT20/RT30/RT60 method */

    // scattering
    float materialRoughness;       /* Average roughness/scattering of all surfaces hit by rays */

    // absorption
    float materialAbsorptionLF;   /* Average low-frequency absorption of all surfaces hit by rays */
    float materialAbsorptionHF;   /* Average high-frequency absorption of all surfaces hit by rays */
} VAProcessedReverb;

// Returns the average of material_absorption_lf and material_absorption_hf
float vaProcessedReverbGetMaterialAbsorption(const VAProcessedReverb* p);

RawReverb

Contains raw reverb data.

public class RawReverb
{
    // The total length of all rays that returned to the listener. Use this to calculate the average returning ray length, i.e (DistanceTotal / ReverbRayCount)
    public float DistanceTotal;

    // The total number of times that a ray escaped outside (hit the world bounds). Will be in the range 0.0 to ReverbRayCount * ReverbBounceCount
    public float OutsideTotal;

    // The total number of times that a ray returned to the listener. Will be in the range 0.0 to ReverbRayCount * ReverbBounceCount
    public float ReturnedTotal;
}
interface RawReverb {

    // Total length of all rays that returned to the listener
    get distanceTotal();

    // Total number of times a ray escaped outside the world bounds (0 to reverbRayCount * reverbBounceCount)
    get outsideTotal();

    // Total number of times a ray returned to the listener (0 to reverbRayCount * reverbBounceCount)
    get returnedTotal();
}
typedef struct VARawReverb
{
    float distanceTotal;   /* Total length of all rays that returned to the listener. Divide by reverb_ray_count for average distance */
    float returnedTotal;   /* Total energy that returned to the listener (range: 0 to reverb_ray_count * reverb_bounce_count) */
    float outsideTotal;    /* Total energy that escaped outside the world bounds (range: 0 to reverb_ray_count * reverb_bounce_count) */
} VARawReverb;

AirAbsorptionSettings

Controls how rays lose energy when travelling through air.

public class AirAbsorptionSettings
{
    // Custom air absorption formula for high frequencies. Takes distance (meters) and returns energy loss as a percentage
    public Func<float, float> CustomFormulaHF;

    // Custom formula for low frequency sound. Takes distance (meters) and returns energy loss as a percentage
    public Func<float, float> CustomFormulaLF;

    // Relative humidity as a percentage (0–1)
    public float Humidity;

    // Atmospheric pressure in Pascals
    public float Pressure;

    // Air temperature in degrees Celsius
    public float Temperature;

    public AirAbsorptionSettings();
}
interface AirAbsorptionSettings {
    // Created via: va.AirAbsorptionSettings_Create()

    // Relative humidity as a percentage (0–1)
    get humidity(); set humidity(v);

    // Atmospheric pressure in Pascals
    get pressure(); set pressure(v);

    // Air temperature in degrees Celsius
    get temperature(); set temperature(v);
}
// Creates a new AirAbsorptionSettings with default values
VAAirAbsorptionSettings* vaAirAbsorptionCreate();

// Returns true if all settings values are within valid ranges
bool vaAirAbsorptionValidate(const VAAirAbsorptionSettings* settings);

// Setters
// Relative humidity as a percentage (0–1)
void vaAirAbsorptionSetHumidity(VAAirAbsorptionSettings* settings, float value);

// Air temperature in degrees Celsius
void vaAirAbsorptionSetTemperature(VAAirAbsorptionSettings* settings, float value);

// Atmospheric pressure in Pascals
void vaAirAbsorptionSetPressure(VAAirAbsorptionSettings* settings, float value);

// Custom formula for low frequency sound. NULL to use default
void vaAirAbsorptionSetCustomFormulaLF(VAAirAbsorptionSettings* settings, VAAirAbsorptionCustomFormula value);

// Custom formula for high frequency sound. NULL to use default
void vaAirAbsorptionSetCustomFormulaHF(VAAirAbsorptionSettings* settings, VAAirAbsorptionCustomFormula value);

// Getters
// Relative humidity as a percentage (0–1)
float vaAirAbsorptionGetHumidity(const VAAirAbsorptionSettings* settings);

// Air temperature in degrees Celsius
float vaAirAbsorptionGetTemperature(const VAAirAbsorptionSettings* settings);

// Atmospheric pressure in Pascals
float vaAirAbsorptionGetPressure(const VAAirAbsorptionSettings* settings);

// Custom LF formula. NULL means default is used
VAAirAbsorptionCustomFormula vaAirAbsorptionGetCustomFormulaLF(const VAAirAbsorptionSettings* settings);

// Custom HF formula. NULL means default is used
VAAirAbsorptionCustomFormula vaAirAbsorptionGetCustomFormulaHF(const VAAirAbsorptionSettings* settings);

// Frees all resources owned by this settings object
void vaAirAbsorptionFree(VAAirAbsorptionSettings* settings);

Color

Determines the color of trails and primitives in the debug window. Used in the dev build only.

public struct Color
{
    // Alpha channel
    public byte A;

    // Packed 32-bit ARGB value
    public uint ARGB;

    // Blue channel
    public byte B;

    // Green channel
    public byte G;

    // Red channel
    public byte R;

    // Packed 32-bit ABGR value
    public uint Value;

    // Creates a color from byte components. Alpha defaults to 255
    public Color(byte red, byte green, byte blue, byte alpha = 255);

    // Creates a color from another color with a alpha value
    public Color(Color other, float alpha);

    // Creates a color from a packed ABGR value
    public Color(uint abgr);

    public override bool Equals(object obj);

    public override int GetHashCode();

    public override string ToString();

    // Returns a copy of this color with the specified normalized alpha
    public Color WithAlpha(float alpha);

    // Returns true if two colors differ
    public static bool operator !=(Color a, Color b);

    // Returns true if two colors have identical values
    public static bool operator ==(Color a, Color b);

    // Linearly interpolates between two colors
    public static Color Interpolate(Color A, Color B, double pos);
}

Not available in JS or C.

Emitter

A 3D position that casts rays, and can be discovered by other emitters.

Read more: Emitters.

public class Emitter
{
    // Indicates whether this Emitter's EAX is blended to produced grouped EAX. Set this to false for listener emitters
    public bool AffectsGroupedEAX = true;

    // This object contains the LF and HF gain for ambient sounds, to be applied to a low pass filter
    public LowPassFilter AmbientFilter;

    // The custom formula for calculating the low- and high-frequency gains in for AmbientFilter. This is invoked for both low-frequency and high-frequency energy. Set to null to use the default formula.
    // Parameters: (bool lowFrequency, int ambientOcclusionRayCount, int ambientPermeationRayCount, int ambientPermeationBounceCount, float ambientOcclusionEnergy, float ambientPermeationEnergy)
    // Returns: float
    public Func<bool, int, int, int, float, float, float> AmbientGainFormula;

    // Number of bounces per ambient occlusion ray
    public int AmbientOcclusionBounceCount;

    // True if both AmbientOcclusionRayCount and AmbientOcclusionBounceCount are greater than zero
    public bool AmbientOcclusionEnabled;

    // The percentage of occlusion energy required for the emitter to be at full volume. Defaults to 15% of this emitter's AmbientOcclusionRayCount.
    public float AmbientOcclusionEnergyCap = 0.15f;

    // Number of ambient occlusion rays cast
    public int AmbientOcclusionRayCount;

    // Number of bounces per ambient permeation ray
    public int AmbientPermeationBounceCount;

    // The color of ambientPermeation rays in the debug window (dev build only)
    public Color AmbientPermeationColor = Color.Yellow.WithAlpha(0.0f);

    // True if both AmbientPermeationRayCount and AmbientPermeationBounceCount are greater than zero
    public bool AmbientPermeationEnabled;

    // The percentage of permeation energy required for the emitter to be at full volume. Defaults to 15% of this emitter's AmbientPermeationRayCount * AmbientPermeationBounceCount.
    public float AmbientPermeationEnergyCap = 0.15f;

    // Number of ambient permeation rays cast
    public int AmbientPermeationRayCount;

    // Whether this emitter casts rays. False if all ray counts and/or bounce counts are set to 0.
    public bool CastsRays;

    // Whether to clamp this emitter's position to the world bounds, to prevent it from going out of bounds
    public bool ClampPosition;

    // EAX reverb properties for the listener. Contains parameters compatible with EAX reverb effects
    public EAXReverb EAX;

    // The length (in milliseconds) of each entry in the echogram. Defaults to 50ms
    public int EchogramGranularity = 50;

    // The custom formula for calculating this emitter's low- and high-frequency gains when it is the target of another emitter. This is invoked for both low-frequency and high-frequency energy. Set to null to use the default formula.
    // Parameters: (bool lowFrequency, int occlusionRayCount, int permeationRayCount, int permeationBounceCount, float occlusionEnergy, float permeationEnergy)
    // Returns: float
    public Func<bool, int, int, int, float, float, float> GainFormula;

    // Index indicating which GroupedEAX reverb effect should be used for this emitter
    public int GroupedEAXIndex = -1;

    // Whether this emitter is used as a reference point for calculating relative reverb gain and direction
    public bool HasRelativeReverb;

    // True if this emitter hasn't cast its own rays yet.
    public bool Initialising = true;

    // A custom logging callback. Defaults to WriteLine()
    public Action<string> LogCallback;

    // A custom logging callback. Defaults to Error.WriteLine()
    public Action<string> LogErrorCallback;

    // How long (in milliseconds) the echogram records data for. Returning reverb rays after this period will be ignored. Defaults to 5000ms
    public int MaxEchogramTime = 5000;

    // The threshold below which permeation rays are cancelled to prevent unnecessary traversal. Clamped to minimum of 0
    public float MinimumPermeationEnergy = 0.01f;

    // User-defined name of this emitter
    public string Name = string.Empty;

    // Number of bounces per occlusion ray
    public int OcclusionBounceCount;

    // The color of occlusion rays in the debug window (dev build only)
    public Color OcclusionColor = Color.Green.WithAlpha(0.0f);

    // True if both OcclusionRayCount and OcclusionBounceCount are greater than zero
    public bool OcclusionEnabled;

    // The percentage of occlusion energy required for the emitter to be at full volume. Defaults to 15% of the other emitter's OcclusionRayCount.
    public float OcclusionEnergyCap = 0.15f;

    // Number of occlusion rays cast
    public int OcclusionRayCount;

    // Whether to only scatter rays around their yaw (no pitch or Y component)
    public bool OnlyScatterXZ;

    // A callback that is invoked when another emitter raytraces this emitter for the first time. The first argument is the other emitter that raytraced this emitter.
    public Action<Emitter> OnRaytracedByAnotherEmitter;

    // A callback that is invoked after this emitter casts its rays for the first time.
    public Action OnRaytracingComplete;

    // The percentage of ambient occlusion rays that reached the edge of the world. Ranges from 0.0 to 1.0
    public float OutsidePercent;

    // When defined, rays will be cast out of these positions rather than the default Position
    public Vector[] OverridePositions;

    // When defined, rays will be cast in these directions rather than the default spherical ray distribution
    public Vector[] OverrideRayDirections;

    // Number of bounces per permeation ray
    public int PermeationBounceCount;

    // The color of permeation rays in the debug window (dev build only)
    public Color PermeationColor = Color.Orange.WithAlpha(0.0f);

    // True if both PermeationRayCount and PermeationBounceCount are greater than zero
    public bool PermeationEnabled;

    // The percentage of permeation energy required for the emitter to be at full volume. Defaults to 15% of the other emitter's PermeationRayCount * PermeationBounceCount.
    public float PermeationEnergyCap = 0.15f;

    // Number of permeation rays cast
    public int PermeationRayCount;

    // The position of this Emitter. Can be a Vector3F or IPosition
    public IPosition Position = Vector.Zero;

    // Contains reverb data that has been transformed into more usable parameters
    public ProcessedReverb ProcessedReverb;

    // Whether to render each trail a different color (dev build only)
    public bool RandomTrailColor;

    // Raw reverb properties calculated directly from raytracing
    public RawReverb RawReverb;

    // A ray trail will be re-created if an old ray bounce position is too far away from the new ray bounce position. This setting controls the allowed distance between old and new ray bounce positions. Clamped to minimum of 0.
    public float RefreshDistanceThreshold = 1.0f;

    // The number of trails that are rebuilt from scratch each frame to prevent staleness when the listener moves. Clamped to minimum of 0.
    public int RefreshRayCount = 16;

    // The lower bound of the relative reverb blend range
    public float RelativeReverbInnerThreshold = 0.6f;

    // The upper bound of the relative reverb blend range
    public float RelativeReverbOuterThreshold = 0.8f;

    // The number of emitters that are allocated ahead of time, to prevent runtime allocations. Clamped to minimum of 0
    public int ReservedEmitterTargets;

    // Number of bounces per reverb ray
    public int ReverbBounceCount;

    // The color of reverb rays in the debug window (dev build only)
    public Color ReverbColor = Color.Cyan.WithAlpha(0.0f);

    // True if both ReverbRayCount and ReverbBounceCount are greater than zero
    public bool ReverbEnabled;

    // The amount of returning energy required for reverb to be at maximum volume. Defaults to 100. Must adjust this setting if adjusting reverb ray and bounce counts.
    public float ReverbEnergyCap = 100.0f;

    // Number of reverb rays cast
    public int ReverbRayCount;

    // A seed used to randomise scattering vectors
    public int ScatteringSeed;

    // Number of bounces per trail
    public int TrailBounceCount;

    // The color of ray trails in the debug window (dev build only)
    public Color TrailColor = Color.White.WithAlpha(0.2f);

    // Number of trails this emitter will create
    public int TrailCount;

    // User-defined type for this emitter
    public int Type;

    // Number of times each visualisation ray bounces
    public int VisualisationBounceCount;

    // Callback that is invoked with VisualisationData for each bounce produced by an emitter's visualisation rays. Do not modify the array or access it outside this callback.
    public Action<VisualisationData[]> VisualisationCallback;

    // True if both VisualisationRayCount and VisualisationBounceCount are greater than zero
    public bool VisualisationEnabled;

    // Number of visualisation rays cast
    public int VisualisationRayCount;

    // How often - in milliseconds - to cast visualisation rays
    public int VisualisationUpdateFrequency;

    // Emitters outside the world bounds will not be raytraced. Set ClampPosition to true to keep this emitter inside the world bounds
    public bool WithinWorldBounds;

    public Emitter();

    // Adds an emitter to this emitter's target list
    public void AddTarget(Emitter target);

    // Get an object containing the LF and HF gain for a target emitter, to be applied to a low pass filter.
    // Only access when HasRaytracedTarget(Emitter) is true.
    public LowPassFilter GetTargetFilter(Emitter target);

    // Returns whether a target emitter has been raytraced. If true, it is safe to check the target's filter via GetTargetFilter
    public bool HasRaytracedTarget(Emitter target);

    // Returns whether the target emitter is in this emitter's target list
    public bool HasTarget(Emitter target);

    // Removes an emitter from this emitter's target list
    public void RemoveTarget(Emitter target);

    // Call this function to invalidate the ray cache - all memory will be cleaned up and all rays will be re-cast
    public void ResetTrails();

    // Validates this emitter's configuration
    public void Validate();
}
interface Emitter {
    // Created via: va.Emitter_Create()

    // Whether this emitter's EAX is blended to produce grouped EAX. Set to false for listener emitters
    get affectsGroupedEAX(); set affectsGroupedEAX(v);

    // Number of bounces per ambient occlusion ray
    get ambientOcclusionBounceCount(); set ambientOcclusionBounceCount(v);

    // Percentage of occlusion energy required for ambient sounds to be at full volume
    get ambientOcclusionEnergyCap(); set ambientOcclusionEnergyCap(v);

    // Number of ambient occlusion rays cast
    get ambientOcclusionRayCount(); set ambientOcclusionRayCount(v);

    // Number of bounces per ambient permeation ray
    get ambientPermeationBounceCount(); set ambientPermeationBounceCount(v);

    // Percentage of permeation energy required for ambient sounds to be at full volume
    get ambientPermeationEnergyCap(); set ambientPermeationEnergyCap(v);

    // Number of ambient permeation rays cast
    get ambientPermeationRayCount(); set ambientPermeationRayCount(v);

    // Whether to clamp this emitter's position to the world bounds
    get clampPosition(); set clampPosition(v);

    // Length (in milliseconds) of each entry in the echogram. 10ms is recommended
    get echogramGranularity(); set echogramGranularity(v);

    // Index indicating which grouped EAX reverb effect should be used for this emitter
    get groupedEAXIndex(); set groupedEAXIndex(v);

    // Whether this emitter is used as a reference point for calculating relative reverb gain and direction
    get hasRelativeReverb(); set hasRelativeReverb(v);

    // True if this emitter hasn't cast its own rays yet
    get initialising(); set initialising(v);

    // How long (in milliseconds) the echogram records data for. Returning reverb rays after this period are ignored
    get maxEchogramTime(); set maxEchogramTime(v);

    // Threshold below which permeation rays are cancelled to prevent unnecessary traversal
    get minimumPermeationEnergy(); set minimumPermeationEnergy(v);

    // Number of bounces per occlusion ray
    get occlusionBounceCount(); set occlusionBounceCount(v);

    // Percentage of occlusion energy required for the emitter to be at full volume
    get occlusionEnergyCap(); set occlusionEnergyCap(v);

    // Number of occlusion rays cast
    get occlusionRayCount(); set occlusionRayCount(v);

    // Whether to only scatter rays around their yaw (no pitch or Y component)
    get onlyScatterXZ(); set onlyScatterXZ(v);

    // Percentage of ambient occlusion rays that reached the edge of the world (0.0 to 1.0)
    get outsidePercent(); set outsidePercent(v);

    // Number of bounces per permeation ray
    get permeationBounceCount(); set permeationBounceCount(v);

    // Percentage of permeation energy required for the emitter to be at full volume
    get permeationEnergyCap(); set permeationEnergyCap(v);

    // Number of permeation rays cast
    get permeationRayCount(); set permeationRayCount(v);

    // 3D position of this emitter
    get position(); set position(v);

    // A ray trail is re-created if an old bounce position is further than this distance from the new position
    get refreshDistanceThreshold(); set refreshDistanceThreshold(v);

    // Number of trails rebuilt from scratch each frame to prevent staleness when the listener moves
    get refreshRayCount(); set refreshRayCount(v);

    // Lower bound of the relative reverb blend range
    get relativeReverbInnerThreshold(); set relativeReverbInnerThreshold(v);

    // Upper bound of the relative reverb blend range
    get relativeReverbOuterThreshold(); set relativeReverbOuterThreshold(v);

    // Number of emitters allocated ahead of time to prevent runtime allocations
    get reservedEmitterTargets(); set reservedEmitterTargets(v);

    // Number of bounces per reverb ray
    get reverbBounceCount(); set reverbBounceCount(v);

    // Amount of returning energy required for reverb to be at maximum volume
    get reverbEnergyCap(); set reverbEnergyCap(v);

    // Number of reverb rays cast
    get reverbRayCount(); set reverbRayCount(v);

    // User-defined type for this emitter
    get type(); set type(v);

    // Number of times each visualisation ray bounces
    get visualisationBounceCount(); set visualisationBounceCount(v);

    // Number of visualisation rays cast
    get visualisationRayCount(); set visualisationRayCount(v);

    // How often (in milliseconds) to cast visualisation rays
    get visualisationUpdateFrequency(); set visualisationUpdateFrequency(v);

    // Low- and high-frequency gain for ambient sounds
    get ambientFilter();

    // True if both ambientOcclusionRayCount and ambientOcclusionBounceCount are greater than zero
    get ambientOcclusionEnabled();

    // True if both ambientPermeationRayCount and ambientPermeationBounceCount are greater than zero
    get ambientPermeationEnabled();

    // Whether this emitter casts rays. False if all ray counts and/or bounce counts are 0
    get castsRays();

    // EAX reverb properties for the listener, compatible with EAX reverb effects
    get eax();

    // True if both occlusionRayCount and occlusionBounceCount are greater than zero
    get occlusionEnabled();

    // True if both permeationRayCount and permeationBounceCount are greater than zero
    get permeationEnabled();

    // Reverb data transformed into more usable parameters
    get processedReverb();

    // Raw reverb properties calculated directly from raytracing
    get rawReverb();

    // True if both reverbRayCount and reverbBounceCount are greater than zero
    get reverbEnabled();

    // Number of bounces per trail
    get trailBounceCount();

    // Number of trails this emitter will create
    get trailCount();

    // True if both visualisationRayCount and visualisationBounceCount are greater than zero
    get visualisationEnabled();

    // Whether this emitter is within the world bounds. Emitters outside the world are not raytraced
    get withinWorldBounds();

    // When defined, rays are cast from these positions rather than the emitter's position
    set overridePositions(v);

    // When defined, rays are cast in these directions rather than the default spherical distribution
    set overrideRayDirections(v);

    // Invoked with each bounce produced by visualisation rays. Do not store the array outside the callback
    set visualisationCallback(v);

    // Invoked when another emitter raytraces this emitter for the first time
    set onRaytracedByAnotherEmitter(fn);

    // Invoked after this emitter casts its rays for the first time
    set onRaytracingComplete(fn);

    // Adds an emitter to this emitter's target list
    addTarget(target);

    // Returns an object containing the LF and HF gain for a target emitter. Only access when hasRaytracedTarget is true
    getTargetFilter(target);

    // Returns the current ray trails for this emitter
    getWasmTrails(prev?);

    // Returns whether a target emitter has been raytraced. If true, it is safe to check the target's filter via getTargetFilter
    hasRaytracedTarget(target);

    // Returns whether the target emitter is in this emitter's target list
    hasTarget(target);

    // Removes an emitter from this emitter's target list
    removeTarget(target);

    // Invalidates the ray cache — all memory is cleaned up and all rays are re-cast
    resetTrails();

    // Sets the position of this emitter
    setPosition(x, y, z);

    // Validates this emitter's configuration
    validate();
}
// Creates a new emitter with default settings.
VAEmitter* vaEmitterCreate(void);

// Frees all resources owned by this emitter.
void vaEmitterDestroy(VAEmitter* emitter);

// Returns true if target has been added to this emitter via vaEmitterAddTarget().
bool vaEmitterHasTarget(VAEmitter* emitter, VAEmitter* target);

// Returns true if raytracing has produced results for this target at least once.
bool vaEmitterHasRaytracedTarget(VAEmitter* emitter, VAEmitter* target);

// Only safe to call when vaEmitterHasRaytracedTarget() returns true.
VALowPassFilter* vaEmitterGetTargetFilter(VAEmitter* emitter, VAEmitter* target);

int vaEmitterAddTarget(VAEmitter* emitter, VAEmitter* target);

// Removes a previously added target from this emitter.
void vaEmitterRemoveTarget(VAEmitter* emitter, VAEmitter* target);

// Invalidates the ray cache; all rays will be re-cast on the next Update().
void vaEmitterResetTrails(VAEmitter* emitter);

// Number of reverb rays cast per frame.
void vaEmitterSetReverbRayCount(VAEmitter* emitter, int value);

// Maximum number of bounces per reverb ray.
void vaEmitterSetReverbBounceCount(VAEmitter* emitter, int value);

// Number of occlusion rays cast per frame.
void vaEmitterSetOcclusionRayCount(VAEmitter* emitter, int value);

// Maximum number of bounces per occlusion ray.
void vaEmitterSetOcclusionBounceCount(VAEmitter* emitter, int value);

// Number of permeation rays cast per frame.
void vaEmitterSetPermeationRayCount(VAEmitter* emitter, int value);

// Maximum number of bounces per permeation ray.
void vaEmitterSetPermeationBounceCount(VAEmitter* emitter, int value);

// Number of ambient occlusion rays cast per frame.
void vaEmitterSetAmbientOcclusionRayCount(VAEmitter* emitter, int value);

// Maximum number of bounces per ambient occlusion ray.
void vaEmitterSetAmbientOcclusionBounceCount(VAEmitter* emitter, int value);

// Number of ambient permeation rays cast per frame.
void vaEmitterSetAmbientPermeationRayCount(VAEmitter* emitter, int value);

// Maximum number of bounces per ambient permeation ray.
void vaEmitterSetAmbientPermeationBounceCount(VAEmitter* emitter, int value);

// Energy threshold below which permeation rays are cancelled to prevent unnecessary traversal.
void vaEmitterSetMinimumPermeationEnergy(VAEmitter* emitter, float value);

int vaEmitterSetReverbEnergyCap(VAEmitter* emitter, float value);

int vaEmitterSetOverridePositions(VAEmitter* emitter, VAVector* positions, int count);

int vaEmitterSetOverrideRayDirections(VAEmitter* emitter, VAVector* directions, int count);

int vaEmitterSetGainFormula(VAEmitter* emitter, VAEmitterGainFormula formula);

int vaEmitterSetAmbientGainFormula(VAEmitter* emitter, VAEmitterGainFormula formula);

// True if reverb ray count and bounce count are both greater than zero.
bool vaEmitterReverbEnabled(const VAEmitter* emitter);

// True if occlusion ray count and bounce count are both greater than zero.
bool vaEmitterOcclusionEnabled(const VAEmitter* emitter);

// True if permeation ray count and bounce count are both greater than zero.
bool vaEmitterPermeationEnabled(const VAEmitter* emitter);

// True if ambient occlusion ray count and bounce count are both greater than zero.
bool vaEmitterAmbientOcclusionEnabled(const VAEmitter* emitter);

// True if ambient permeation ray count and bounce count are both greater than zero.
bool vaEmitterAmbientPermeationEnabled(const VAEmitter* emitter);

// True if visualisation ray count and bounce count are both greater than zero.
bool vaEmitterVisualisationEnabled(const VAEmitter* emitter);

// False if all ray counts and/or bounce counts are set to 0.
bool vaEmitterCastsRays(const VAEmitter* emitter);

// Emitters outside the world bounds will not be raytraced; set clampPosition to true to keep them inside the world.
bool vaEmitterWithinWorldBounds(const VAEmitter* emitter);

// Invoked after this emitter casts its rays for the first time.
void vaEmitterSetOnRaytracingCompleteCallback(VAEmitter* emitter, VAEmitterOnRaytracingComplete callback);

// Invoked when another emitter raytraces this emitter for the first time.
void vaEmitterSetOnRaytracedByAnotherEmitterCallback(VAEmitter* emitter, VAEmitterOnRaytracedByAnotherEmitter callback);

// Invoked with bounce data produced by visualisation rays; do not modify the array or access it outside the callback.
void vaEmitterSetVisualisationCallback(VAEmitter* emitter, VAEmitterVisualisationCallback callback);

// Invoked for informational log messages from this emitter.
void vaEmitterSetLogCallback(VAEmitter* emitter, VALogCallback callback);

// Invoked for error log messages from this emitter.
void vaEmitterSetLogErrorCallback(VAEmitter* emitter, VALogCallback callback);

// World-space position of this emitter.
void vaEmitterSetPosition(VAEmitter* emitter, VAVector position);

// World-space position of this emitter.
VAVector vaEmitterGetPosition(const VAEmitter* emitter);

// When true, the emitter has not yet received its first raytracing result.
bool vaEmitterGetInitialising(const VAEmitter* emitter);

// Whether this emitter's EAX is blended into grouped EAX. Set to false for listener emitters.
void vaEmitterSetAffectsGroupedEax(VAEmitter* emitter, bool value);

// Whether this emitter's EAX is blended into grouped EAX.
bool vaEmitterGetAffectsGroupedEax(const VAEmitter* emitter);

// Which grouped EAX reverb effect applies to this emitter (-1 = none).
void vaEmitterSetGroupedEaxIndex(VAEmitter* emitter, int value);

// Index into the world's grouped eax list (-1 = none).
int vaEmitterGetGroupedEaxIndex(const VAEmitter* emitter);

// Read-only percentage of energy in ambient occlusion rays that reached the edge of the world (0–1).
float vaEmitterGetOutsidePercent(const VAEmitter* emitter);

// Whether this emitter is used as a reference point for calculating relative reverb gain and direction.
void vaEmitterSetHasRelativeReverb(VAEmitter* emitter, bool value);

bool vaEmitterGetHasRelativeReverb(const VAEmitter* emitter);

// Lower bound of the relative reverb blend range.
void vaEmitterSetRelativeReverbInnerThreshold(VAEmitter* emitter, float value);

float vaEmitterGetRelativeReverbInnerThreshold(const VAEmitter* emitter);

// Upper bound of the relative reverb blend range.
void vaEmitterSetRelativeReverbOuterThreshold(VAEmitter* emitter, float value);

float vaEmitterGetRelativeReverbOuterThreshold(const VAEmitter* emitter);

// When true, position is clamped to world bounds to prevent going out of bounds.
void vaEmitterSetClampPosition(VAEmitter* emitter, bool value);

bool vaEmitterGetClampPosition(const VAEmitter* emitter);

// Seed used to randomise scattering vectors.
void vaEmitterSetScatteringSeed(VAEmitter* emitter, int value);

int vaEmitterGetScatteringSeed(const VAEmitter* emitter);

// Display name for debugging and logging.
void vaEmitterSetName(VAEmitter* emitter, const char* name);

// Display name for debugging and logging.
const char* vaEmitterGetName(const VAEmitter* emitter);

// User-defined integer tag for categorising emitters.
void vaEmitterSetType(VAEmitter* emitter, int value);

// User-defined integer tag for categorising emitters.
int vaEmitterGetType(const VAEmitter* emitter);

// Number of reverb rays cast per frame.
int vaEmitterGetReverbRayCount(const VAEmitter* emitter);

// Maximum number of bounces per reverb ray.
int vaEmitterGetReverbBounceCount(const VAEmitter* emitter);

// Number of occlusion rays cast per frame.
int vaEmitterGetOcclusionRayCount(const VAEmitter* emitter);

// Maximum number of bounces per occlusion ray.
int vaEmitterGetOcclusionBounceCount(const VAEmitter* emitter);

// Number of permeation rays cast per frame.
int vaEmitterGetPermeationRayCount(const VAEmitter* emitter);

// Maximum number of bounces per permeation ray.
int vaEmitterGetPermeationBounceCount(const VAEmitter* emitter);

// Number of ambient occlusion rays cast per frame.
int vaEmitterGetAmbientOcclusionRayCount(const VAEmitter* emitter);

// Maximum number of bounces per ambient occlusion ray.
int vaEmitterGetAmbientOcclusionBounceCount(const VAEmitter* emitter);

// Number of ambient permeation rays cast per frame.
int vaEmitterGetAmbientPermeationRayCount(const VAEmitter* emitter);

// Maximum number of bounces per ambient permeation ray.
int vaEmitterGetAmbientPermeationBounceCount(const VAEmitter* emitter);

// Number of visualisation rays cast per visualisation update.
int vaEmitterGetVisualisationRayCount(const VAEmitter* emitter);

// Number of visualisation rays cast per visualisation update.
void vaEmitterSetVisualisationRayCount(VAEmitter* emitter, int value);

// Maximum number of bounces per visualisation ray.
int vaEmitterGetVisualisationBounceCount(const VAEmitter* emitter);

// Maximum number of bounces per visualisation ray.
void vaEmitterSetVisualisationBounceCount(VAEmitter* emitter, int value);

// Interval in milliseconds between visualisation ray updates.
int vaEmitterGetVisualisationUpdateFrequency(const VAEmitter* emitter);

// Interval in milliseconds between visualisation ray updates.
void vaEmitterSetVisualisationUpdateFrequency(VAEmitter* emitter, int value);

// Total number of trails (reverb + occlusion + permeation) owned by this emitter.
int vaEmitterGetTrailCount(const VAEmitter* emitter);

// Total bounce budget per trail across all ray types.
int vaEmitterGetTrailBounceCount(const VAEmitter* emitter);

// How long (ms) the echogram records data. Returning reverb rays after this period are ignored. Defaults to 5000ms.
int vaEmitterGetMaxEchogramTime(const VAEmitter* emitter);

// How long (ms) the echogram records data. Returning reverb rays after this period are ignored. Defaults to 5000ms.
void vaEmitterSetMaxEchogramTime(VAEmitter* emitter, int value);

// Length (ms) of each entry in the echogram. Defaults to 50ms.
int vaEmitterGetEchogramGranularity(const VAEmitter* emitter);

// Length (ms) of each entry in the echogram. Defaults to 50ms.
void vaEmitterSetEchogramGranularity(VAEmitter* emitter, int value);

// Number of trails rebuilt from scratch each frame to prevent staleness when the emitter moves. Minimum 0.
int vaEmitterGetRefreshRayCount(const VAEmitter* emitter);

// Number of trails rebuilt from scratch each frame to prevent staleness when the emitter moves. Minimum 0.
void vaEmitterSetRefreshRayCount(VAEmitter* emitter, int value);

// A trail is re-created when an old bounce position is this far from the new position. Minimum 0.
float vaEmitterGetRefreshDistanceThreshold(const VAEmitter* emitter);

// A trail is re-created when an old bounce position is this far from the new position. Minimum 0.
void vaEmitterSetRefreshDistanceThreshold(VAEmitter* emitter, float value);

// Amount of returning energy required for reverb to be at maximum volume. Defaults to 100.
float vaEmitterGetReverbEnergyCap(const VAEmitter* emitter);

// Percentage of occlusion energy required for the emitter to be at full volume. Defaults to 15% of occlusionRayCount.
float vaEmitterGetOcclusionEnergyCap(const VAEmitter* emitter);

int vaEmitterSetOcclusionEnergyCap(VAEmitter* emitter, float value);

// Percentage of permeation energy required for the emitter to be at full volume. Defaults to 15% of permeationRayCount * permeationBounceCount.
float vaEmitterGetPermeationEnergyCap(const VAEmitter* emitter);

int vaEmitterSetPermeationEnergyCap(VAEmitter* emitter, float value);

// Percentage of ambient occlusion energy required for the emitter to be at full volume.
float vaEmitterGetAmbientOcclusionEnergyCap(const VAEmitter* emitter);

int vaEmitterSetAmbientOcclusionEnergyCap(VAEmitter* emitter, float value);

// Percentage of ambient permeation energy required for the emitter to be at full volume.
float vaEmitterGetAmbientPermeationEnergyCap(const VAEmitter* emitter);

int vaEmitterSetAmbientPermeationEnergyCap(VAEmitter* emitter, float value);

// Number of target emitter slots pre-allocated. Minimum 0.
int vaEmitterGetReservedEmitterCount(const VAEmitter* emitter);

// Number of target emitter slots pre-allocated. Minimum 0.
void vaEmitterSetReservedEmitterCount(VAEmitter* emitter, int value);

// Energy threshold below which permeation rays are cancelled to prevent unnecessary traversal.
float vaEmitterGetMinimumPermeationEnergy(const VAEmitter* emitter);

// Raw reverb results; only valid after raytracing has run at least once.
VARawReverb* vaEmitterGetRawReverb(const VAEmitter* emitter);

// Processed reverb results; only valid after raytracing has run at least once.
VAProcessedReverb* vaEmitterGetProcessedReverb(const VAEmitter* emitter);

// EAX reverb results; only valid after raytracing has run at least once.
VAEAXReverb* vaEmitterGetEAX(const VAEmitter* emitter);

// LF and HF gain for ambient sounds to apply to a low pass filter.
VALowPassFilter* vaEmitterGetAmbientFilter(const VAEmitter* emitter);

FuncPosition

Interface for setting the position of an emitter.

Invoked when RaytracingContext.Update() is called.

public class FuncPosition : IPosition
{
    public FuncPosition(Func<Vector> position);

    public Vector GetPosition();
}

Not available in JS or C.

IPosition

Interface for setting the position of an emitter.

Invoked when RaytracingContext.Update() is called.

public interface IPosition
{
    // Returns a 3D position
    public Vector GetPosition();
}

Not available in JS or C.

LowPassFilter

Contains low-frequency and high-frequency gains.

public class LowPassFilter
{
    // High-frequency gain in the range 0.0 to 1.0
    public float GainHF;

    // Low-frequency gain in the range 0.0 to 1.0
    public float GainLF;
}
interface LowPassFilter {
    // Created via: va.LowPassFilter_Create()

    // High-frequency gain in the range 0.0 to 1.0
    get gainHF(); set gainHF(v);

    // Low-frequency gain in the range 0.0 to 1.0
    get gainLF(); set gainLF(v);
}
typedef struct VALowPassFilter
{
    // Low-frequency gain in the range 0.0 to 1.0
    float gainLF;
    // High-frequency gain in the range 0.0 to 1.0
    float gainHF;
} VALowPassFilter;

MaterialProperties

Stores properties for a single material.

Read more: Materials.

public class MaterialProperties
{
    // Percentage of high-frequency energy that is lost on each bounce (0.0 to 1.0).
    public float AbsorptionHF;

    // Percentage of low-frequency energy that is lost on each bounce (0.0 to 1.0).
    public float AbsorptionLF;

    // Percentage of high-frequency energy lost when a ray passes through a flat PlanePrimitive, DiskPrimitive, TrianglePrimitive or non-watertight MeshPrimitive. Ranges from 0.0 to 1.0
    public float PlaneTransmissionHF;

    // Percentage of low-frequency energy lost when a ray passes through a flat PlanePrimitive, DiskPrimitive, TrianglePrimitive or non-watertight MeshPrimitive. Ranges from 0.0 to 1.0
    public float PlaneTransmissionLF;

    // Scattering strength (0.0 to 1.0), where 0.0 has no scattering and 1.0 skews the ray reflection direction by up to 90 degrees
    public float Scattering;

    // High-frequency transmission in decibels per meter (0.0 or greater).
    public float TransmissionHF;

    // Low-frequency transmission in decibels per meter (0.0 or greater).
    public float TransmissionLF;

    // Create a MaterialProperties with all values set to 0
    public MaterialProperties();

    // Create a MaterialProperties with all values provided
    public MaterialProperties(float absorptionLF, float absorptionHF, float scattering, float transmissionLF, float transmissionHF, float planeTransmissionLF, float planeTransmissionHF);

    // Create a MaterialProperties with the same values as another
    public MaterialProperties(MaterialProperties prop);

    // Copy all properties from another MaterialProperties
    public void Update(MaterialProperties prop);

    // Update all properties
    public void Update(float absorptionLF, float absorptionHF, float scattering, float transmissionLF, float transmissionHF, float planeTransmissionLF, float planeTransmissionHF);

    // Returns the dB/m transmission value that reduces energy to 0.1% after travelling metersToBlock meters through a material
    public static float TransmissionForThickness(float metersToBlock);
}
interface MaterialProperties {
    // Created via: va.MaterialProperties_Create()
    // Also: va.MaterialProperties_CreateWith(absorptionLF, absorptionHF, scattering, transmissionLF, transmissionHF, planeTransmissionLF, planeTransmissionHF)

    // Percentage of high-frequency energy lost on each bounce (0.0 to 1.0)
    get absorptionHF(); set absorptionHF(v);

    // Percentage of low-frequency energy lost on each bounce (0.0 to 1.0)
    get absorptionLF(); set absorptionLF(v);

    // Percentage of high-frequency energy lost when a ray passes through a flat plane, disk, triangle, or non-watertight mesh (0.0 to 1.0)
    get planeTransmissionHF(); set planeTransmissionHF(v);

    // Percentage of low-frequency energy lost when a ray passes through a flat plane, disk, triangle, or non-watertight mesh (0.0 to 1.0)
    get planeTransmissionLF(); set planeTransmissionLF(v);

    // Scattering strength (0.0 to 1.0), where 0.0 has no scattering and 1.0 scatters up to 90 degrees from the reflected direction
    get scattering(); set scattering(v);

    // High-frequency transmission in dB/m (0.0 or greater)
    get transmissionHF(); set transmissionHF(v);

    // Low-frequency transmission in dB/m (0.0 or greater)
    get transmissionLF(); set transmissionLF(v);
}

MaterialType

Materials define how much energy is lost when rays hit and pass through primitives.

There are 23 default materials. You can customise them and create your own. Read more: Materials.

public enum MaterialType
{
    Air = 0,
    Brick,
    Cloth,
    Concrete,
    ConcretePolished,
    Dirt,
    Glass,
    Grass,
    Gravel,
    Gyprock,
    Ice,
    Leaf,
    Marble,
    Metal,
    Mud,
    Rock,
    Sand,
    Snow,
    Tile,
    Tree,
    Water,
    WoodIndoor,
    WoodOutdoor,
}
// Available via: import { MaterialType } from 'vaudio';
const MaterialType = {
    Air: 0,
    Brick: 1,
    Cloth: 2,
    Concrete: 3,
    ConcretePolished: 4,
    Dirt: 5,
    Glass: 6,
    Grass: 7,
    Gravel: 8,
    Gyprock: 9,
    Ice: 10,
    Leaf: 11,
    Marble: 12,
    Metal: 13,
    Mud: 14,
    Rock: 15,
    Sand: 16,
    Snow: 17,
    Tile: 18,
    Tree: 19,
    Water: 20,
    WoodIndoor: 21,
    WoodOutdoor: 22,
};

Default Materials

For every material:

The transmission columns contain two values:

Material Absorption LF Absorption HF Scattering Transmission LF Transmission HF Source
Brick 0.31 0.52 0.4 60 (0.5m) 120 (0.25m) Link
Cloth 0.72 0.91 0.7 10 (3m) 60 (0.5m) Link
Concrete 0.31 0.55 0.4 50 (0.6m) 100 (0.3m) Link
Concrete Polished 0.06 0.12 0.1 50 (0.6m) 100 (0.3m) Link
Dirt 0.48 0.88 0.5 20 (1.5m) 75 (0.4m) Link
Glass 0.08 0.12 0.05 150 (0.2m) 600 (0.05m)
Grass 0.71 0.93 0.8 7.5 (4m) 37.5 (0.8m) Link
Gravel 0.06 0.12 0.7 30 (1m) 100 (0.3m) Link
Gyprock 0.04 0.08 0.05 75 (0.4m) 200 (0.15m) Link
Ice 0.01 0.03 0.01 200 (0.15m) 375 (0.08m)
Leaf 0.71 0.91 0.8 6 (5m) 30 (1m) Link
Marble 0.06 0.12 0.03 60 (0.5m) 150 (0.2m) Link
Metal 0.05 0.02 0.01 300 (0.1m) 600 (0.05m) Link
Mud 0.56 0.82 0.5 25 (1.2m) 85.71 (0.35m) Link
Rock 0.31 0.51 0.4 50 (0.6m) 100 (0.3m) Link
Sand 0.65 0.92 0.7 15 (2m) 60 (0.5m) Link
Snow 0.65 0.92 0.7 10 (3m) 50 (0.6m) Link
Tile 0.06 0.12 0.03 60 (0.5m) 150 (0.2m) Link
Tree 0.22 0.46 0.3 20 (1.5m) 75 (0.4m) Link
Water 0.06 0.12 0.1 60 (0.5m) 150 (0.2m)
Wood Indoor 0.12 0.3 0.1 75 (0.4m) 200 (0.15m) Link
Wood Outdoor 0.16 0.37 0.2 60 (0.5m) 150 (0.2m) Link

Matrix

Struct for a 4x4 matrix.

public struct Matrix
{
    public float M11;

    public float M12;

    public float M13;

    public float M14;

    public float M21;

    public float M22;

    public float M23;

    public float M24;

    public float M31;

    public float M32;

    public float M33;

    public float M34;

    public float M41;

    public float M42;

    public float M43;

    public float M44;

    public override bool Equals(object obj);

    public override int GetHashCode();

    // Returns the inverse of this matrix
    public Matrix Inverse();

    // Returns true if all elements are exactly equal to those in other
    public bool IsEqual(Matrix other);

    // Converts this matrix to a Matrix4x4
    public System.Numerics.Matrix4x4 ToNumerics();

    // Returns true if a does not equal b
    public static bool operator !=(Matrix a, Matrix b);

    // The identity matrix
    public static Matrix Identity = new(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);

    // Returns true if a equals b
    public static bool operator ==(Matrix a, Matrix b);

    // Create a rotation matrix from a Quaternion
    public static Matrix CreateFromQuaternion(System.Numerics.Quaternion q);

    // Create a transposed rotation matrix from a Quaternion
    public static Matrix CreateFromQuaternionTransposed(System.Numerics.Quaternion q);

    public static Matrix CreateOrthoProjection(float left, float top, float right, float bottom, float near, float far);

    // Create a rotation matrix around the X axis
    public static Matrix CreateRotationX(float rads);

    // Create a rotation matrix around the Y axis
    public static Matrix CreateRotationY(float rads);

    // Create a rotation matrix around the Z axis
    public static Matrix CreateRotationZ(float rads);

    // Create a scale matrix. Note scale matrices can only be set on MeshPrimitive and not any other primitive
    public static Matrix CreateScale(float scaleX, float scaleY, float scaleZ);

    // Create a scale matrix. Note scale matrices can only be set on MeshPrimitive and not any other primitive
    public static Matrix CreateScale(Vector v);

    // Create a scale matrix. Note scale matrices can only be set on MeshPrimitive and not any other primitive
    public static Matrix CreateScale(float scaleFactor);

    // Create a translation matrix
    public static Matrix CreateTranslation(System.Numerics.Vector3 vec);

    // Create a translation matrix
    public static Matrix CreateTranslation(Vector vec);

    // Create a translation matrix
    public static Matrix CreateTranslation(float x, float y, float z);

    // Multiply two matrices together
    public static Matrix operator *(Matrix a, Matrix b);
}
const Matrix = va.Matrix;

class Matrix {
    // Static properties
    static Identity;       // { m11, m12, ..., m44 }

    // Static factory methods
    static CreateTranslation(x, y, z);
    static CreateRotationX(rads);
    static CreateRotationY(rads);
    static CreateRotationZ(rads);
    static CreateScale(x, y?, z?);
    static CreateFromQuaternion(x, y, z, w);
    static Multiply(a, b);
}
typedef struct VAMatrix
{
    float m11;
    float m12;
    float m13;
    float m14;
    float m21;
    float m22;
    float m23;
    float m24;
    float m31;
    float m32;
    float m33;
    float m34;
    float m41;
    float m42;
    float m43;
    float m44;
} VAMatrix;

// Initializes a matrix with all 16 elements in column-major order (each group of 4 is one column)
VAMatrix vaMatrixCreate(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44);

// Returns the inverse of the matrix. Asserts if the determinant is zero (no inverse exists)
VAMatrix vaMatrixInverse(const VAMatrix* mat);

// Creates a translation matrix
VAMatrix vaMatrixCreateTranslation(float x, float y, float z);

// Creates a rotation matrix around the X axis. rads is the angle in radians
VAMatrix vaMatrixCreateRotationX(float rads);

// Creates a rotation matrix around the Y axis. rads is the angle in radians
VAMatrix vaMatrixCreateRotationY(float rads);

// Creates a rotation matrix around the Z axis. rads is the angle in radians
VAMatrix vaMatrixCreateRotationZ(float rads);

// Creates a scale matrix. Note scale matrices can only be set on mesh primitives and not any other primitive
VAMatrix vaMatrixCreateScale(float scaleX, float scaleY, float scaleZ);

// Multiplies two matrices together
VAMatrix vaMatrixMultiply(const VAMatrix* a, const VAMatrix* b);

// Returns true if all elements of a are exactly equal to those in b
bool vaMatrixIsEqual(const VAMatrix* a, const VAMatrix* b);

ThreadStatistics

Helper class for retrieving and caching the number of threads available.

public static class ThreadStatistics
{
    // The number of threads available for background tasks, excluding the main thread
    public static int BackgroundThreadCount;

    // The minimum number of threads in the thread pool
    public static int ThreadCount;
}

Not available in JS or C.

VAResult

Integer return code indicating the result of a C API call.

typedef int VAResult;

#define VA_SUCCESS           0
#define VA_INVALID_VALUE     1
#define VA_OUT_OF_RANGE      2

Example:

#include <math.h>

// Returns VA_SUCCESS
VAResult result = vaWorldSetMaterialAbsorptionLF(world, VAMaterialConcrete, 0.5f);

// Returns VA_INVALID_VALUE
VAResult result = vaWorldSetMaterialAbsorptionLF(world, VAMaterialConcrete, NAN);

// Returns VA_OUT_OF_RANGE
VAResult result = vaWorldSetMaterialAbsorptionLF(world, VAMaterialConcrete, -13.5f);

Not available in C# or JS.

Vector

Struct for a 3D vector.

public struct Vector : IPosition
{
    // Gets or sets a component by index (0=X, 1=Y, 2=Z)
    public float this[int index];

    // The length of this vector
    public float Magnitude;

    // Returns a normalized copy of this vector
    public Vector Normalized;

    // The X component
    public float X;

    // The Y component
    public float Y;

    // The Z component
    public float Z;

    // Creates a vector with all components set to v
    public Vector(float v);

    // Creates a vector with the specified components
    public Vector(float x, float y, float z);

    // Creates a vector from a Vector3
    public Vector(System.Numerics.Vector3 v);

    public override bool Equals(object obj);

    public override int GetHashCode();

    // IPosition implementation, returns itself
    public Vector GetPosition();

    // Returns true if any component of this vector is greater than the corresponding component in the other vector
    public bool GreaterAny(Vector v);

    // Returns true if any component of this vector is greater than or equal to the corresponding component in the other vector
    public bool GreaterEqualAny(Vector v);

    // Returns true if all components of this vector equal those of other
    public bool IsEqual(Vector other);

    // Returns true if any component of this vector is less than the corresponding component in the other vector
    public bool LessAny(Vector v);

    // Returns true if any component of this vector is less than or equal to the corresponding component in the other vector
    public bool LessEqualAny(Vector v);

    // Normalizes this vector in place
    public void Normalize();

    // Converts to Vector3
    public System.Numerics.Vector3 ToNumerics();

    public override string ToString();

    // Returns true if a does not equal b
    public static bool operator !=(Vector a, Vector b);

    // Returns true if all components of a are greater than or equal to those of b
    public static bool operator >=(Vector a, Vector b);

    // A vector with all components set to MaxValue
    public static Vector MAX = new(float.MaxValue);

    // A vector with all components set to MinValue
    public static Vector MIN = new(float.MinValue);

    // A vector with all components set to one
    public static Vector One = new(1);

    // Returns true if all components of a are less than or equal to those of b
    public static bool operator <=(Vector a, Vector b);

    // Returns true if a equals b
    public static bool operator ==(Vector a, Vector b);

    // A unit vector pointing upward (0, 1, 0)
    public static Vector Up = new(0, 1, 0);

    // A vector with all components set to zero
    public static Vector Zero = new(0);

    // Returns true if all components of a are greater than those of b
    public static bool operator >(Vector a, Vector b);

    // Returns the cross product of a and b
    public static Vector Cross(Vector a, Vector b);

    // Returns the dot product of a and b
    public static float Dot(Vector a, Vector b);

    // Creates a direction vector from pitch and yaw angles in radians
    public static Vector FromPitchYaw(float pitch, float yaw);

    // Returns the component-wise maximum of a and b
    public static Vector GetMax(Vector a, Vector b);

    // Returns the component-wise minimum of a and b
    public static Vector GetMin(Vector a, Vector b);

    // Interpolates from one vector to another
    public static Vector Lerp(Vector a, Vector b, float c);

    // Returns true if all components of a are less than those of b
    public static bool operator <(Vector a, Vector b);

    // Reflects direction about the normal
    public static Vector Reflect(Vector direction, Vector normal);

    // Transforms a normal vector by a matrix, ignoring translation
    public static Vector TransformNormal(Vector a, Matrix b);

    // Scales a by scalar b
    public static Vector operator *(float b, Vector a);

    // Scales a by scalar b
    public static Vector operator *(Vector a, float b);

    // Transforms a vector by a matrix, including translation
    public static Vector operator *(Vector a, Matrix b);

    // Returns the component-wise product of a and b
    public static Vector operator *(Vector a, Vector b);

    // Returns the component-wise sum of a and b
    public static Vector operator +(Vector a, Vector b);

    // Returns the negation of a
    public static Vector operator -(Vector a);

    // Returns the component-wise difference of a and b
    public static Vector operator -(Vector a, Vector b);

    // Divides scalar b by each component of a
    public static Vector operator /(float b, Vector a);

    // Divides a by scalar b
    public static Vector operator /(Vector a, float b);

    // Returns the component-wise quotient of a and b
    public static Vector operator /(Vector a, Vector b);
}
typedef struct VAVector
{
    float x;
    float y;
    float z;
} VAVector;

// Creates a vector with the specified components
VAVector vaVectorCreate(float x, float y, float z);

// Creates a vector with all components set to value
VAVector vaVectorCreateUniform(float value);

// Returns the length of v
float vaVectorMagnitude(VAVector v);

// Returns the component-wise sum of a and b
VAVector vaVectorAdd(VAVector a, VAVector b);

// Returns the component-wise difference of a and b
VAVector vaVectorSubtract(VAVector a, VAVector b);

// Scales a by scalar
VAVector vaVectorMultiplyScalar(VAVector a, float scalar);

// Divides a by scalar
VAVector vaVectorDivideScalar(VAVector a, float scalar);

// Returns true if all components of a are greater than or equal to those of b
bool vaVectorGreaterEqual(VAVector a, VAVector b);

// Returns true if all components of a are less than or equal to those of b
bool vaVectorLessEqual(VAVector a, VAVector b);

// Returns true if any component of a is less than the corresponding component of b
bool vaVectorLessAny(VAVector a, VAVector b);

// Returns true if any component of a is greater than the corresponding component of b
bool vaVectorGreaterAny(VAVector a, VAVector b);

// Returns true if all components of a equal those of b
bool vaVectorEqual(VAVector a, VAVector b);

// Returns the component-wise minimum of a and b
VAVector vaVectorMin(VAVector a, VAVector b);

// Returns the component-wise maximum of a and b
VAVector vaVectorMax(VAVector a, VAVector b);

// Gets a component by index (0=x, 1=y, 2=z)
float vaVectorGetComponent(VAVector v, int index);

// Returns the dot product of a and b
float vaVectorDot(VAVector a, VAVector b);

// Returns the cross product of a and b
VAVector vaVectorCross(VAVector a, VAVector b);

// Returns the negation of a
VAVector vaVectorNegate(VAVector a);

// Returns a normalized copy of v
VAVector vaVectorNormalize(VAVector v);

// Creates a direction vector from pitch and yaw angles in radians
VAVector vaVectorFromPitchYaw(float pitch, float yaw);

// Transforms a point by a matrix, including translation (w=1)
VAVector vaVectorMultiplyMatrix(VAVector a, const VAMatrix* b);

// Transforms a normal vector by a matrix, ignoring translation, and normalizes the result
VAVector vaVectorTransformNormal(VAVector a, const VAMatrix* b);

// Returns true if any component of vec is NaN or infinity
bool vaVectorIsNanOrInfinity(VAVector vec);

// Reflects direction about normal
VAVector vaVectorReflect(VAVector direction, VAVector normal);

VisualisationData

Struct that contains position and normal data.

public struct VisualisationData
{
    // The world-space normal of the ray bounce. Guaranteed to be normalised
    public Vector normal;

    // The world-space position of the ray bounce
    public Vector position;
}
interface VisualisationData {
    // Created via: va.VisualisationData_Create()

    // The world-space normal of the ray bounce. Guaranteed to be normalised
    get normal(); set normal(v);

    // The world-space position of the ray bounce
    get position(); set position(v);
}
typedef struct VAVisualisationData
{
    VAVector position;
    VAVector normal;
} VAVisualisationData;

World

Contains emitters, primitives, materials and many customisable settings. Manages its own raytracing and multithreading.

public class World
{
    // Air absorption settings. Set to null to disable air absorption.
    public AirAbsorptionSettings AirAbsorption;

    // The average time (in milliseconds) spent in the analysing thread after the raytracing threads complete. This phase runs after raytracing completes and calculate reverb properties.
    // Use this to monitor raytracing performance and adjust ray counts if needed.
    public float AnalysisTime;

    // True while background raytracing threads are still running.
    // After setting PendingShutdown, continue calling Update until this is false, then call Dispose.
    public bool AreThreadsRunning;

    // The pitch of the camera in the debug window.
    public float CameraPitch;

    // The position of the camera in the debug window.
    public Vector CameraPosition;

    // The yaw of the camera in the debug window.
    public float CameraYaw;

    // Custom formulas for calculating EAX properties (diffusion, density, etc). Set to null to use default formulas.
    public CustomEAXFormulas CustomEAXFormulas;

    // Whether Emitters outside the world have 0 occlusion/permeation energy (true) or maximum energy (false).
    public bool EmittersOutsideTheWorldAreMuffled;

    // The epsilon value used for ray offsets, world bounds clamping and line-of-sight tests. Defaults to 0.01f
    public float Epsilon;

    // The field of view (in radians) of the camera in the debug window.
    public float FieldOfView;

    // List of grouped EAX reverb properties for all emitters. Contains parameters compatible with EAX reverb effects.
    public List<EAXReverb> GroupedEAX = [];

    // Inverse speed of sound in seconds per meter. Defaults to 1.0f / 343.0f. Affects reverb calculation
    public float InverseSpeedOfSound;

    // A custom logging callback. Defaults to WriteLine()
    public Action<string> LogCallback;

    // Whether to log memory allocation warnings
    public bool LogMemoryAllocationWarnings = true;

    // The average time (in milliseconds) spent by Update on the main thread. This includes time spent handling previous raytracing results, applying new settings,
    // processing primitive updates and submitting work to background threads.
    // Use this metric to monitor main thread performance impact.
    public float MainThreadTime;

    // The maximum bounds of the world (Position + Size)
    public Vector MaxBounds;

    // The maximum amount of threads that can run in parallel for this world
    public int MaximumConcurrencyLevel;

    // The maximum number of grouped EAX reverb properties created for all emitters. Higher values increase accuracy but are more expensive to run.
    public int MaximumGroupedEAXCount = 2;

    // Gets meters per world unit. Affects air absorption and reverb calculation.
    public float MetersPerUnit = 1;

    // Callback that is invoked when keys are pressed
    public Action<KeyEvent> OnKeyEvent;

    // This is invoked after EAX reverb results are updated, and before emitter.OnRaytracingComplete callbacks are invoked. This gives you a chance to update your EAX effects, so they can be applied to an emitter in it's OnRaytracingComplete callback.
    public Action OnReverbUpdated = null;

    // When set to true, Update will stop submitting work to background threads.
    // When ThreadsRunning becomes false, it is safe to call Dispose.
    public bool PendingShutdown = false;

    // The minimum bounds of the world.
    // Emitters outside the world will not be raytraced, and Primitives that are fully outside these bounds will not affect raytracing.
    public Vector Position;

    // The average time (in milliseconds) spent in the preparation thread before the raytracing threads begin. This phase updates the BVH (Bounding Volume Hierarchy) acceleration structure with new, modified,
    // and removed primitives. Higher values are a result of complex scene changes.
    public float PreparationTime;

    // The percentage of rays that were reused from previous frames, rather than being cast this frame. Higher values indicate better performance.
    public float RayCachePercent;

    // The number of rays cast this frame.
    public int RaysCastThisFrame = 0;

    // The average time (in milliseconds) spent in the raytracing threads. This is the real-world elapsed time it takes for raytracing to complete, not the sum of all thread times.
    // This value is automatically adjusted based on how many threads perform raytracing.
    // Use this to monitor raytracing performance and adjust ray counts if needed.
    public float RaytracingTime;

    // High-frequency reference (Hz) for air absorption, reverb, and material scattering
    public float ReferenceFrequencyHF = 4000;

    // Low-frequency reference (Hz) for air absorption, reverb, and material scattering
    public float ReferenceFrequencyLF = 300;

    // Whether to render the raytracing scene in a separate window. Only one world can have rendering enabled
    public bool RenderingEnabled;

    // Returns true when raytracing has run at least once, and it is safe to access reverb objects
    public bool ReverbCalculated;

    // The size of the world.
    // Emitters outside the world will not be raytraced, and Primitives that are fully outside these bounds will be ignored
    public Vector Size;

    // The total number of rays that could potentially be cast each frame, including trail rays, reverb rays, occlusion rays, permeation rays and visualisation rays
    public int TotalPossibleRayCount;

    // The number of work items to split trails across for load balancing. A higher workItemCount helps evenly distribute work across all threads.
    public int WorkItemCount = 128;

    // Whether the entire world is considered indoors or outdoors. When false, reverb rays stop checking for line-of-sight after hitting the world edge. Defaults to false.
    public bool WorldIsIndoors;

    // Create a new world
    public World();

    // Add an Emitter to the world.
    // This method is thread-safe and will not affect the current raytracing threads.
    public Emitter AddEmitter(Emitter emitter);

    // Add a new material to this world
    public void AddMaterial(MaterialType type, MaterialProperties properties, Color color);

    // Adds a 3D object to the raytracing scene.
    // This method is thread-safe and will not affect the current raytracing threads.
    // Primitives completely outside the world bounds will be ignored during raytracing.
    public void AddPrimitive(Primitive primitive);

    // Waits for background thread to complete, then disposes everything.
    // After calling this method, this world cannot be reused.
    public void Dispose();

    // Get properties for a specific material.
    public MaterialProperties GetMaterial(MaterialType type);

    // Gets the debug rendering color for a specific material type
    public Color GetMaterialColor(MaterialType type);

    // Returns true if a material exists
    public bool HasMaterial(MaterialType type);

    // Returns true if a key is pressed in the debug window.
    public bool IsKeyPressed(Silk.NET.Input.Key keyCode);

    // Remove an Emitter from the world.
    // This method is thread-safe and will not affect the current raytracing threads.
    // This emitter's OnRaytracingComplete callback will not be invoked.
    public void RemoveEmitter(Emitter emitter);

    // Removes a 3D object from the raytracing scene.
    // This method is thread-safe and will not affect the current raytracing threads.
    public void RemovePrimitive(Primitive primitive);

    // Sets the debug rendering color for a specific material type
    public void SetMaterialColor(MaterialType type, Color color);

    // Updates the raytracing simulation. Call this method regularly to process raytracing results and submit new work.
    // 
    // This method does nothing if background raytracing threads are still running. When threads are idle, it performs the following operations:
    // - Handles the last raytracing results, updating reverb objects and invoking OnRaytracedByAnotherEmitter callbacks
    // - Applies new settings and resizes memory buffers if needed (e.g., if ray counts were changed)
    // - Processes new, modified, and removed primitives
    // - Starts raytracing again on background threads
    // 
    // This method must be called from the main thread. The actual raytracing runs on background threads.
    // Calling this more frequently (multiple times per frame) is safe and can reduce latency for emitter updates.
    public void Update();

    // Blocks the calling thread until all background raytracing threads complete, then handles the results (updates reverb objects and invokes OnRaytracingComplete and OnRaytracedByAnotherEmitter callbacks for each emitter).
    public void Wait();
}
interface World {
    // Created via: va.World_Create()

    // Air absorption settings. Set to null to disable air absorption
    get airAbsorption(); set airAbsorption(v);

    // Whether emitters outside the world have 0 occlusion/permeation energy (true) or maximum energy (false)
    get emittersOutsideTheWorldAreMuffled(); set emittersOutsideTheWorldAreMuffled(v);

    // Epsilon value for ray offsets, world bounds clamping and line-of-sight tests. Defaults to 0.01
    get epsilon(); set epsilon(v);

    // Whether to expose trail data for rendering
    get exposeTrails(); set exposeTrails(v);

    // Inverse speed of sound in seconds per meter. Defaults to 1/343. Affects reverb calculation
    get inverseSpeedOfSound(); set inverseSpeedOfSound(v);

    // Whether to log memory allocation warnings
    get logMemoryAllocationWarnings(); set logMemoryAllocationWarnings(v);

    // Maximum bounds of the world (position + size)
    get maxBounds(); set maxBounds(v);

    // Maximum number of threads that can run in parallel for this world
    get maximumConcurrencyLevel(); set maximumConcurrencyLevel(v);

    // Maximum number of grouped EAX reverb properties. Higher values increase accuracy but cost more
    get maximumGroupedEAXCount(); set maximumGroupedEAXCount(v);

    // Meters per world unit. Affects air absorption and reverb calculation
    get metersPerUnit(); set metersPerUnit(v);

    // When set to true, update() stops submitting work to background threads. Poll areThreadsRunning until false, then call destroy()
    get pendingShutdown(); set pendingShutdown(v);

    // Minimum bounds of the world. Emitters and primitives outside these bounds are ignored
    get position(); set position(v);

    // High-frequency reference in Hz for air absorption, reverb, and material scattering
    get referenceFrequencyHF(); set referenceFrequencyHF(v);

    // Low-frequency reference in Hz for air absorption, reverb, and material scattering
    get referenceFrequencyLF(); set referenceFrequencyLF(v);

    // Size of the world. Emitters and primitives fully outside these bounds are ignored
    get size(); set size(v);

    // Number of work items to split trails across for load balancing
    get workItemCount(); set workItemCount(v);

    // Whether the entire world is considered indoors or outdoors. Affects reverb ray behaviour at world bounds
    get worldIsIndoors(); set worldIsIndoors(v);

    // Average time (in milliseconds) spent in the analysis thread calculating reverb properties
    get analysisTime();

    // True while background raytracing threads are still running. Poll this after setting pendingShutdown, then call destroy() when false
    get areThreadsRunning();

    // Averaged EAX reverb objects for all emitters with affectsGroupedEAX set to true
    get groupedEAX();

    // Average time (in milliseconds) spent by update() on the main thread
    get mainThreadTime();

    // Average time (in milliseconds) spent in the preparation thread updating the BVH
    get preparationTime();

    // Average real-world elapsed time (in milliseconds) for raytracing threads to complete
    get raytracingTime();

    // True when raytracing has run at least once and reverb objects are safe to access
    get reverbCalculated();

    // Invoked after EAX reverb results are updated, before onRaytracingComplete callbacks
    set onReverbUpdated(fn);

    // Adds an emitter to the raytracing scene
    addEmitter(e);

    // Adds or replaces a material in this world
    addMaterial(materialType, properties);

    // Adds a 3D primitive to the raytracing scene
    addPrimitive(p);

    // Returns the number of background threads used by this world
    getBackgroundThreadCount();

    // Returns the acoustic properties for a specific material
    getMaterial(materialType);

    // Returns the total number of threads used by this world
    getThreadCount();

    // Returns true if a material exists in this world
    hasMaterial(materialType);

    // Removes an emitter from the raytracing scene
    removeEmitter(e);

    // Removes a 3D primitive from the raytracing scene
    removePrimitive(p);

    // Updates the raytracing simulation. Call regularly to process results and submit new work
    update();

    // Blocks until all background raytracing threads complete and handles the results
    wait();
}
// Creates a new world
VAWorld* vaWorldCreate();

// Waits for background thread to complete, then frees all resources. After calling this method, this world cannot be reused.
void vaWorldFree(VAWorld* world);

void vaWorldUpdate(VAWorld* world);

// Blocks the calling thread until all background raytracing threads complete, then handles the results (updates reverb objects and invokes OnRaytracingComplete and OnRaytracedByAnotherEmitter callbacks for each emitter).
void vaWorldWait(VAWorld* world);

void vaWorldSetPendingShutdown(VAWorld* world);

bool vaWorldGetThreadsRunning(VAWorld* world);

// Number of rays cast this frame.
int vaWorldGetRaysCastThisFrame(const VAWorld* world);

// List of grouped EAX reverb properties for all emitters. Contains parameters compatible with EAX reverb effects.
const VAEAXReverb** vaWorldGetGroupedEAX(const VAWorld* world);

// Current number of grouped EAX effects (may be less than the maximum).
int vaWorldGetGroupedEAXCount(const VAWorld* world);

// Primitive management — use the macros below, not these directly
void vaWorldAddPrimitive_(VAWorld* world, void* primitive);

void vaWorldRemovePrimitive_(VAWorld* world, void* primitive);

// The minimum bounds of the world. Emitters outside the world will not be raytraced, and primitives fully outside these bounds will not affect raytracing.
VAVector vaWorldGetPosition(const VAWorld* world);

// Set the world position. Returns VA_SUCCESS, or VA_INVALID_VALUE if the new value is NaN or Infinity.
VAResult vaWorldSetPosition(VAWorld* world, VAVector position);

// The size of the world. Emitters outside the world will not be raytraced, and primitives fully outside these bounds will be ignored.
VAVector vaWorldGetSize(const VAWorld* world);

// Set the world size. Returns VA_SUCCESS, or VA_INVALID_VALUE if the new value is NaN, Infinity or less than or equal to (0, 0, 0).
VAResult vaWorldSetSize(VAWorld* world, VAVector size);

// The maximum bounds of the world (Position + Size).
VAVector vaWorldGetMaxBounds(const VAWorld* world);

// Set the max bounds of the world. Returns VA_SUCCESS, or VA_INVALID_VALUE if the new value is NaN, Infinity or less than or equal to (0, 0, 0).
VAResult vaWorldSetMaxBounds(VAWorld* world, VAVector maxBounds);

// Number of emitters in this world
int vaWorldGetEmitterCount(const VAWorld* world);

// Total number of rays that could potentially be cast each frame across all emitters.
int vaWorldGetTotalPossibleRayCount(const VAWorld* world);

// Percentage of rays reused from previous frames rather than cast this frame. Higher values indicate better performance.
float vaWorldGetRayCachePercent(const VAWorld* world);

// The maximum number of grouped EAX reverb properties created for all emitters. Higher values increase accuracy but are more expensive to run. Must be >= 2. Returns VA_SUCCESS, or VA_OUT_OF_RANGE if value is less than 2.
VAResult vaWorldSetMaximumGroupedEAXCount(VAWorld* world, int value);

// The number of work items to split trails across for load balancing. A higher value helps evenly distribute work across all threads. Must be >= 1. Returns VA_SUCCESS, or VA_OUT_OF_RANGE if value is less than 1.
VAResult vaWorldSetWorkItemCount(VAWorld* world, int value);

// The maximum amount of threads that can run in parallel for this world. Must be >= 1. Returns VA_SUCCESS, or VA_OUT_OF_RANGE if value is less than 1.
VAResult vaWorldSetMaximumConcurrencyLevel(VAWorld* world, int value);

// Gets meters per world unit. Affects air absorption and reverb calculation. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if <= 0.
VAResult vaWorldSetMetersPerUnit(VAWorld* world, float value);

// Inverse speed of sound in seconds per meter. Defaults to 1.0f / 343.0f. Affects reverb calculation. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if <= 0.
VAResult vaWorldSetInverseSpeedOfSound(VAWorld* world, float value);

// Low-frequency reference (Hz) for air absorption, reverb, and material scattering. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if <= 0.
VAResult vaWorldSetReferenceFrequencyLF(VAWorld* world, float value);

// High-frequency reference (Hz) for air absorption, reverb, and material scattering. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if <= 0.
VAResult vaWorldSetReferenceFrequencyHF(VAWorld* world, float value);

// Air absorption settings
void* vaWorldGetAirAbsorption(VAWorld* world);

// Air absorption settings. Pass NULL to disable air absorption. Returns VA_SUCCESS, or VA_INVALID_VALUE if validation fails.
VAResult vaWorldSetAirAbsorption(VAWorld* world, VAAirAbsorptionSettings* value);

// Relative humidity as a percentage (0–1).
void vaWorldSetAirAbsorptionHumidity(VAWorld* world, float value);

// Air temperature in degrees Celsius.
void vaWorldSetAirAbsorptionTemperature(VAWorld* world, float value);

// Atmospheric pressure in Pascals.
void vaWorldSetAirAbsorptionPressure(VAWorld* world, float value);

// Custom LF air absorption formula. Takes distance in meters, returns energy loss as a percentage. NULL to use default.
void vaWorldSetAirAbsorptionCustomFormulaLF(VAWorld* world, VAAirAbsorptionCustomFormula value);

// Custom HF air absorption formula. Takes distance in meters, returns energy loss as a percentage. NULL to use default.
void vaWorldSetAirAbsorptionCustomFormulaHF(VAWorld* world, VAAirAbsorptionCustomFormula value);

// Custom formulas for calculating EAX properties (diffusion, density, etc). Pass NULL to use default formulas.
void vaWorldSetCustomEAXFormulas(VAWorld* world, VACustomEAXFormulas* formulas);

// Whether emitters outside the world have 0 occlusion/permeation energy (true) or maximum energy (false).
void vaWorldSetEmittersOutsideTheWorldAreMuffled(VAWorld* world, bool value);

// The maximum number of grouped EAX reverb properties created for all emitters.
int vaWorldGetMaximumGroupedEAXCount(const VAWorld* world);

// The number of work items to split trails across for load balancing.
int vaWorldGetWorkItemCount(const VAWorld* world);

// The maximum amount of threads that can run in parallel for this world.
int vaWorldGetMaximumConcurrencyLevel(const VAWorld* world);

// Whether the system is running in single-threaded mode.
bool vaWorldGetSingleThreaded(const VAWorld* world);

// Whether the system is running in single-threaded mode.
void vaWorldSetSingleThreaded(VAWorld* world, bool value);

// Gets meters per world unit. Affects air absorption and reverb calculation.
float vaWorldGetMetersPerUnit(const VAWorld* world);

// Inverse speed of sound in seconds per meter. Defaults to 1.0f / 343.0f. Affects reverb calculation.
float vaWorldGetInverseSpeedOfSound(const VAWorld* world);

// Low-frequency reference (Hz) for air absorption, reverb, and material scattering.
float vaWorldGetReferenceFrequencyLF(const VAWorld* world);

// High-frequency reference (Hz) for air absorption, reverb, and material scattering.
float vaWorldGetReferenceFrequencyHF(const VAWorld* world);

// Whether emitters outside the world have 0 occlusion/permeation energy (true) or maximum energy (false).
bool vaWorldGetEmittersOutsideTheWorldAreMuffled(const VAWorld* world);

// TODO
float vaWorldGetEpsilon(const VAWorld* world);

// TODO
void vaWorldSetEpsilon(VAWorld* world, float value);

// Add a new material to this world.
void vaWorldCreateMaterial(VAWorld* world, int materialId);

// Gets the percentage of low-frequency energy that is lost when a ray bounces off this material, in the range 0.0 to 1.0
float vaWorldGetMaterialAbsorptionLF(const VAWorld* world, int materialId);

// Gets the percentage of high-frequency energy that is lost when a ray bounces off this material, in the range 0.0 to 1.0
float vaWorldGetMaterialAbsorptionHF(const VAWorld* world, int materialId);

// Gets the scattering strength when a ray bounces off this material, in the range 0.0 to 1.0 (0.0 = no scattering, 1.0 skews the ray reflection direction by up to 90 degrees)
float vaWorldGetMaterialScattering(const VAWorld* world, int materialId);

// Gets how much low-frequency energy is lost (in dB/meter) when a permeation ray passes through this primitive, in the range 0.0 or greater
float vaWorldGetMaterialTransmissionLF(const VAWorld* world, int materialId);

// Gets how much high-frequency energy is lost (in dB/meter) when a permeation ray passes through this primitive, in the range 0.0 or greater
float vaWorldGetMaterialTransmissionHF(const VAWorld* world, int materialId);

// Gets the percentage of low-frequency energy that is lost when a permeation ray passes through a flat VAPlanePrimitive, VADiskPrimitive, VATrianglePrimitive, or non-watertight VAMeshPrimitive, in the range 0.0 to 1.0
float vaWorldGetMaterialPlaneTransmissionLF(const VAWorld* world, int materialId);

// Gets the percentage of high-frequency energy that is lost when a permeation ray passes through a flat VAPlanePrimitive, VADiskPrimitive, VATrianglePrimitive, or non-watertight VAMeshPrimitive, in the range 0.0 to 1.0
float vaWorldGetMaterialPlaneTransmissionHF(const VAWorld* world, int materialId);

// Updates the percentage of low-frequency energy that is lost when a ray bounces off this material. Must be in the range 0.0 to 1.0. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if < 0.0 or > 1.0.
VAResult vaWorldSetMaterialAbsorptionLF(VAWorld* world, int materialId, float value);

// Updates the percentage of high-frequency energy that is lost when a ray bounces off this material. Must be in the range 0.0 to 1.0. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if < 0.0 or > 1.0.
VAResult vaWorldSetMaterialAbsorptionHF(VAWorld* world, int materialId, float value);

// Updates the scattering strength (0.0 = no scattering, 1.0 skews the ray reflection direction by up to 90 degrees). Must be in the range 0.0 to 1.0. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if < 0.0 or > 1.0.
VAResult vaWorldSetMaterialScattering(VAWorld* world, int materialId, float value);

// Updates how much low-frequency energy is lost (in dB/meter) when a permeation ray passes through this primitive. Must be 0.0 or greater. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if < 0.0.
VAResult vaWorldSetMaterialTransmissionLF(VAWorld* world, int materialId, float value);

// Updates how much high-frequency energy is lost (in dB/meter) when a permeation ray passes through this primitive. Must be 0.0 or greater. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if < 0.0.
VAResult vaWorldSetMaterialTransmissionHF(VAWorld* world, int materialId, float value);

// Updates the percentage of low-frequency energy that is lost when a permeation ray passes through a flat VAPlanePrimitive, VADiskPrimitive, VATrianglePrimitive, or non-watertight VAMeshPrimitive. Must be in the range 0.0 to 1.0. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if < 0.0.
VAResult vaWorldSetMaterialPlaneTransmissionLF(VAWorld* world, int materialId, float value);

// Updates the percentage of high-frequency energy that is lost when a permeation ray passes through a flat VAPlanePrimitive, VADiskPrimitive, VATrianglePrimitive, or non-watertight VAMeshPrimitive. Must be in the range 0.0 to 1.0. Returns VA_SUCCESS, VA_INVALID_VALUE if NaN or Infinity, or VA_OUT_OF_RANGE if < 0.0.
VAResult vaWorldSetMaterialPlaneTransmissionHF(VAWorld* world, int materialId, float value);

// Average time (ms) spent by vaWorldUpdate() on the main thread
double vaWorldGetMainThreadTime(VAWorld* world);

// Average time (ms) spent in background raytracing threads
double vaWorldGetRaytracingTime(VAWorld* world);

// Average time (ms) spent in the preparation thread
double vaWorldGetPreparationTime(VAWorld* world);

// Average time (ms) spent in the analysis thread
double vaWorldGetAnalysisTime(VAWorld* world);

VAResult vaWorldAddEmitter(VAWorld* world, VAEmitter* emitter);

// Remove an emitter from the world. Thread-safe. This emitter's OnRaytracingComplete callback will not be invoked.
void vaWorldRemoveEmitter(VAWorld* world, VAEmitter* emitter);

// Returns true when raytracing has run at least once, and it is safe to access reverb objects.
bool vaWorldGetReverbCalculated(const VAWorld* world);

// Invoked after EAX reverb results are updated, and before emitter OnRaytracingComplete callbacks are invoked.
void vaWorldSetOnReverbUpdatedCallback(VAWorld* world, void (*callback)(void));

// A custom logging callback.
void vaWorldSetLogCallback(VAWorld* world, VALogCallback callback);

// Whether to log memory allocation warnings.
void vaWorldSetLogMemoryAllocationWarnings(VAWorld* world, bool value);

// Converts a world-space vector to a listener-relative pan direction given the listener's pitch and yaw angles.
VAVector vaWorldCalculateListenerRelativePan(VAVector worldVector, float listenerPitch, float listenerYaw);

WorkItemCount

This setting controls how raytracing work is divided across multiple background threads. If you have 8 threads, it seems logical to split the work into 8 work items (one for each thread). However some threads might finish earlier than others, meaning not all threads will be fully utilised.

To prevent threads from idling while other threads run, you can divide the raytracing work into smaller work items. To do this, set workItemCount to a number higher than maximumConcurrencyLevel:

var world = new World()
{
    OcclusionRayCount = 1024,
    OcclusionBounceCount = 8,
    WorkItemCount = 128,
    MaximumConcurrencyLevel = 8,
};

This means 8 background threads will perform raytracing, and will take work from a pool of 128 work items. On average each thread should process 128/8 = 16 work items each.

Air Absorption

Rays lose low- and high-frequency energy based on how far they travel through air.

There is an in-built air absorption formula that's based on humidity, temperature and pressure. You can configure it like so:

world.AirAbsorption.HumidityPercent = 0.1f;
world.AirAbsorption.TemperatureCelsius = 30.0f;
world.AirAbsorption.PressurePascals = 101325.0f;
world.airAbsorption.humidityPercent = 0.1;
world.airAbsorption.temperatureCelsius = 30.0;
world.airAbsorption.pressurePascals = 101325.0;

These settings should be configured once on startup. Changing these values at runtime will clear the ray cache, causing all rays to be re-cast

Air absorption is also affected by the following World settings:

world.MetersPerUnit = 1.0f;
world.InverseSpeedOfSound = 1.0f / 343.0f;
world.ReferenceFrequencyLF = 300.0f;  // Hz
world.ReferenceFrequencyHF = 4000.0f; // Hz
world.metersPerUnit = 1.0;
world.inverseSpeedOfSound = 1.0 / 343.0;
world.referenceFrequencyLF = 300.0;  // Hz
world.referenceFrequencyHF = 4000.0; // Hz
vaWorldSetMetersPerUnit(world, 1.0f);
vaWorldSetInverseSpeedOfSound(world, 1.0f / 343.0f);
vaWorldSetReferenceFrequencyLF(world, 300.0f);  // Hz
vaWorldSetReferenceFrequencyHF(world, 4000.0f); // Hz

These settings should be configured once on startup. Changing these values at runtime will clear the ray cache, causing all rays to be re-cast

TODO - clear ray cache when changing these 4 world settings?

You can also provide custom formulas for air absorption:

world.AirAbsorption.CustomFormulaLF = (float distanceMeters) =>
{
    // Return how much LF energy should be lost, in the range 0.0 to 1.0
    return MathF.Min(1.0f, distanceMeters / 15000.0f);
};

world.AirAbsorption.CustomFormulaHF = (float distanceMeters) =>
{
    // Return how much HF energy should be lost, in the range 0.0 to 1.0
    return MathF.Min(1.0f, distanceMeters / 2000.0f);
};

You can also provide custom formulas for air absorption:

float CustomFormulaLF(float distanceMeters)
{
    // Return how much LF energy should be lost, in the range 0.0 to 1.0
    return fminf(1.0f, distanceMeters / 15000.0f);
}

float CustomFormulaHF(float distanceMeters)
{
    // Return how much HF energy should be lost, in the range 0.0 to 1.0
    return fminf(1.0f, distanceMeters / 2000.0f);
}

void Initialise()
{
    vaWorldSetAirAbsorptionCustomFormulaLF(world, CustomFormulaLF);
    vaWorldSetAirAbsorptionCustomFormulaHF(world, CustomFormulaHF);
}

You can also completely disable air absorption:

world.AirAbsorption = null;
world.airAbsorption = null;

Ambience

Emitters can cast rays to determine how muffled ambient sounds should be. To enable this, configure ambient occlusion and/or ambient permeation rays on the emitter:

const listener = va.Emitter_Create();

listener.ambientOcclusionRayCount = 1024;
listener.ambientOcclusionBounceCount = 8;
listener.ambientPermeationRayCount = 128;
listener.ambientPermeationBounceCount = 3;
VAEmitter* emitter = vaEmitterCreate();

vaEmitterSetAmbientOcclusionRayCount(emitter, 1024);
vaEmitterSetAmbientOcclusionBounceCount(emitter, 8);
vaEmitterSetAmbientPermeationRayCount(emitter, 128);

// TODO - comment says 'max' bounces. should just say bounces
vaEmitterSetAmbientPermeationBounceCount(emitter, 3);

Ambient occlusion rays bounce around the environment, losing energy based on the materials they hit, and from air absorption. These rays are marked as 'outside' when they hit the edge of the world. The energy remaining in the rays when they hit the world edge is used below to determine ambience volume.

Ambient permeation rays bounce around the environment, but do not lose energy based on materials or air absorption. Instead, on each bounce they cast an extra ray in a random direction. These extra rays travel through the world, losing energy based on the material and thickness of the primitives they pass through. The energy remaining in the rays when they reach the world edge is used below to determine ambience volume.

Materials and air absorption affect low- and high-frequency energy differently, which is why thunder becomes muffled through walls. Read more about this in Materials and Air Absorption.

You must also set the AmbientOcclusionEnergyCapambientOcclusionEnergyCapvaEmitterSetAmbientOcclusionEnergyCap() and AmbientPermeationEnergyCapambientPermeationEnergyCapvaEmitterSetAmbientPermeationEnergyCap() fields, which control the threshold for ambience to be at maximum volume.

// 50% of occlusion energy is required for max ambience volume
emitter.AmbientOcclusionEnergyCap = 0.5;

// 50% of permeation energy is required for max ambience volume
emitter.AmbientPermeationEnergyCap = 0.5;
// 50% of occlusion energy is required for max ambience volume
emitter.ambientOcclusionEnergyCap = 0.5;

// 50% of permeation energy is required for max ambience volume
emitter.ambientPermeationEnergyCap = 0.5;
// 50% of occlusion energy is required for max ambience volume
vaEmitterSetAmbientOcclusionEnergyCap(emitter, 0.5f);

// 50% of permeation energy is required for max ambience volume
vaEmitterSetAmbientPermeationEnergyCap(emitter, 0.5f);

The 'percent' of energy refers to the total possible energy across all rays. For example, if air absorption is disabled and all occlusion rays hit the world edge without bouncing on any materials - or if all permeation rays reach the world edge and don't pass through any primitives - then the rays will still have 100% energy when they reach the world edge.

For example, if there are 10 ambient occlusion rays, the total possible energy is 10. If each ray loses half of its energy by the time it reaches the world edge, the total energy across all rays is 5, meaning 50% of ambient occlusion energy remained. Then if AmbientOcclusionEnergyCap is 0.5 (50%), ambient volume will be at 100% volume.

Volume is calculated separately for occlusion and permeation rays, and then summed together, capped at 100%.

This process occurs for both low- and high-frequency energy, allowing thunder to sound deep / muffled behind a thick wall

These volume values can be accessed via emitter.AmbientFilteremitter.ambientFiltervaEmitterGetAmbientFilter(), which is null until raytracing is performed at least once:

// Wait for raytracing to complete
if (listener.AmbientFilter != null)
{
    var gainLF = listener.AmbientFilter.GainLF;
    var gainHF = listener.AmbientFilter.GainHF;
}
// Wait for raytracing to complete
if (listener.ambientFilter != null)
{
    var gainLF = listener.ambientFilter.gainLF;
    var gainHF = listener.ambientFilter.gainHF;
}
VALowPassFilter* ambientFilter = vaEmitterGetAmbientFilter(listener);

if (ambientFilter != NULL)
{
    float gainLF = ambientFilter->gainLF;
    float gainHF = ambientFilter->gainHF;
}

Custom Formulas

These energy totals are passed to the Emitter.AmbientGainFormula function. This function converts them to low-pass filter gain values, and is invoked once for low-frequency energy and once for high-frequency energy:

listener = new Emitter()
{
    AmbientGainFormula = (
        bool lowFrequency,
        int ambientOcclusionRayCount,
        int ambientPermeationRayCount,
        int ambientPermeationBounceCount,
        float ambientOcclusionEnergy,
        float ambientPermeationEnergy
    ) => 
    {
        float gain = 0.0f;
    
        if (ambientOcclusionRayCount > 0)
        {
            // 50% of energy is required for max ambience volume
            float energyThreshold = 0.5f * ambientOcclusionRayCount;
            gain += ambientOcclusionEnergy / energyThreshold;
        }
    
        if (ambientPermeationRayCount > 0 && ambientPermeationBounceCount > 0)
        {
            // 50% of energy is required for max ambience volume
            float energyThreshold = 0.5f * (ambientPermeationRayCount * ambientPermeationBounceCount);
            gain += ambientPermeationEnergy / energyThreshold;
        }
    
        return MathF.Min(1, gain);
    }
}

These energy totals are passed to the ambientGainFormula function. This function converts them to low-pass filter gain values, and is invoked once for low-frequency energy and once for high-frequency energy:

float myAmbientGainFormula(
    bool lowFrequency,
    int occlusionRayCount,
    int permeationRayCount,
    int permeationBounceCount,
    float occlusionEnergy,
    float permeationEnergy
)
{
    float gain = 0.0f;

    if (occlusionRayCount > 0)
    {
        // 50% of energy is required for max ambience volume
        float energyThreshold = 0.5f * occlusionRayCount;
        gain += occlusionEnergy / energyThreshold;
    }

    if (permeationRayCount > 0 && permeationBounceCount > 0)
    {
        // 50% of energy is required for max ambience volume
        float energyThreshold = 0.5f * (permeationRayCount * permeationBounceCount);
        gain += permeationEnergy / energyThreshold;
    }

    return gain < 1.0f ? gain : 1.0f;
}

vaEmitterSetAmbientGainFormula(listener, myAmbientGainFormula);

This formula is optional. If left untouched or set to null, it will default to requiring 10% of energy for max volume. Read more: Ambient Gain Formula.

Emitters

An Emitter is a 3D position that casts rays, and can be discovered by other emitters.

Emitters can be created at any time, with customisable position and ray count properties:

var emitter = new Emitter()
{
    Name = "Player",
    Position = new Vector(10),
    ReverbRayCount = 128,
    ReverbBounceCount = 64,
};

world.AddEmitter(emitter);
const emitter = va.Emitter_Create();

emitter.name = 'Player',
emitter.setPosition(10, 10, 10);
emitter.reverbRayCount = 128;
emitter.reverbBounceCount = 64;

world.addEmitter(emitter);
VAEmitter* emitter = vaEmitterCreate();

vaEmitterSetName(emitter, "Player");
vaEmitterSetPosition(emitter, vaVectorCreate(10.0f, 10.0f, 10.0f));
vaEmitterSetReverbRayCount(emitter, 128);
vaEmitterSetReverbBounceCount(emitter, 64);

vaWorldAddEmitter(ctx, emitter);

The full list of properties is here: Emitter.

Position Interface

Rather than manually setting the position of each emitter every frame, it's best to implement the IPosition interface on your game objects, and then assign your object directly to the emitter's position.

For example if you want an emitter to follow an enemy in your game, your Enemy class would implement IPosition:

public class Enemy : IPosition
{
    public Vector position;
    
    // Implement the interface
    public Vector GetPosition() => position;
}

Then use the enemy directly as the emitter's position

var enemy = new Enemy();
var emitter = new Emitter()
{
    Position = enemy
};

This emitter's position will now automatically match the enemy's position.

The Initial Raytrace

When an emitter is created, it starts in an initialising state where it is waiting to be raytraced. Do not play sounds in your game engine yet, as this emitter might be muffled.

New emitters are raytraced when world.Update() is called:

void Update()
{
    var emitter1 = new Emitter();
    var emitter2 = new Emitter();
    var emitter3 = new Emitter();
    
    world.AddEmitter(emitter1);
    world.AddEmitter(emitter2);
    world.AddEmitter(emitter3);
    
    // The 3 emitters will be raytraced together
    world.Update();
}
function update()
{
    var emitter1 = va.Emitter_Create();
    var emitter2 = va.Emitter_Create();
    var emitter3 = va.Emitter_Create();
    
    world.addEmitter(emitter1);
    world.addEmitter(emitter2);
    world.addEmitter(emitter3);
    
    // The 3 emitters will be raytraced together
    world.update();
}
void Update()
{
    VAEmitter* emitter1 = vaEmitterCreate();
    VAEmitter* emitter2 = vaEmitterCreate();
    VAEmitter* emitter3 = vaEmitterCreate();
    
    vaWorldAddEmitter(ctx, emitter1);
    vaWorldAddEmitter(ctx, emitter2);
    vaWorldAddEmitter(ctx, emitter3);
    
    // The 3 emitters will be raytraced together
    vaWorldUpdate(ctx);
}

world.Update()world.update()vaWorldUpdate() must be called regularly. Once per frame is typical, but you can call it as often as you like

Raytracing Results

The first time an emitter is raytraced, its OnRaytracingComplete()onRaytracingComplete()vaEmitterSetOnRaytracingCompleteCallback callback will fire. This callback will only contain reverb and ambient data:

var emitter = new Emitter()
{
    Position = new Vector(10),
    ReverbRayCount = 128,
    ReverbBounceCount = 64,
    AmbientOcclusionRayCount = 128,
    AmbientOcclusionBounceCount = 64,
    AmbientPermeationRayCount = 128,
    AmbientPermeationBounceCount = 4,
};

// Access stats when raytracing first completes
emitter.OnRaytracingComplete = () =>
{ 
    // Reverb
    var decayTime = emitter.EAX.DecayTime;    
    
    // Calculated from ambient occlusion rays
    var outsidePercent = emitter.OutsidePercent;    
    
    // Calculated from emitter.AmbientGainFormula
    var ambientGainLF = emitter.AmbientFilter.GainLF;
    var ambientGainHF = emitter.AmbientFilter.GainHF;
};
const emitter = va.Emitter_Create();

emitter.setPosition(10, 10, 10);
emitter.reverbRayCount = 128;
emitter.reverbBounceCount = 64;
emitter.ambientOcclusionRayCount = 128;
emitter.ambientOcclusionBounceCount = 64;
emitter.ambientPermeationRayCount = 128;
emitter.ambientPermeationBounceCount = 4;

// Access stats when raytracing first completes
emitter.onRaytracingComplete = () =>
{ 
    // Reverb
    const decayTime = emitter.eax.decayTime;    
    
    // Calculated from ambient occlusion rays
    const outsidePercent = emitter.outsidePercent;    
    
    // Calculated from emitter.AmbientGainFormula
    const ambientGainLF = emitter.ambientFilter.gainLF;
    const ambientGainHF = emitter.ambientFilter.gainHF;
};
VAEmitter* emitter;

void OnRaytracingComplete()
{
    // Reverb
    VAEAXReverb* eax = vaEmitterGetEAX(emitter);
    float decayTime = eax->decayTime;
    
    // Calculated from ambient occlusion rays
    float outsidePercent = vaEmitterGetOutsidePercent(emitter);    
    
    // Calculated from emitter.AmbientGainFormula
    VALowPassFilter* filter = vaEmitterGetAmbientFilter(emitter);
    float ambientGainLF = filter->gainLF;
    float ambientGainHF = filter->gainHF;
}

void CreateEmitter()
{
    VAEmitter* emitter = vaEmitterCreate();
    
    vaEmitterSetPosition(emitter, vaVectorCreate(10.0f, 10.0f, 10.0f));
    vaEmitterSetReverbRayCount(emitter, 128);
    vaEmitterSetReverbBounceCount(emitter, 64);
    vaEmitterSetAmbientOcclusionRayCount(emitter, 128);
    vaEmitterSetAmbientOcclusionBounceCount(emitter, 64);
    vaEmitterSetAmbientPermeationRayCount(emitter, 128);
    vaEmitterSetAmbientPermeationBounceCount(emitter, 4);
    
    vaEmitterSetOnRaytracingCompleteCallback(emitter, OnRaytracingComplete);
}

To determine how muffled an emitter is, it must be raytraced by another emitter. It's typical to have a 'listener' emitter that follows the camera, and multiple 'target' emitters that are raytraced:

// Create listener and enemy
var listener = new Emitter()
{
    Name = "Camera",
    Position = new FuncPosition(() => cameraPosition),
    ReverbRayCount = 128,
    ReverbBounceCount = 64,
    
    // Determine how many rays to cast towards other rays
    OcclusionRayCount = 1024,
    OcclusionBounceCount = 8,
};

world.AddEmitter(listener);

var enemyEmitter = new Emitter()
{
    Name = "Enemy",
    Position = enemy,
    ReverbRayCount = 32,
    ReverbBounceCount = 8,
};

world.AddEmitter(enemyEmitter);
listener.AddTarget(enemyEmitter);

// Callback when raytracing completes
enemyEmitter.OnRaytracedByAnotherEmitter = (Emitter other) => 
{
    var filter = other.GetTargetFilter(enemyEmitter);
    
    var gainLF = filter.gainLF;
    var gainHF = filter.gainHF;
    
    // PSEUDOCODE - play a sound with a low pass filter
    Godot.PlaySound(SoundType.Footstep, enemyEmitter.position, filter);
};
// Create listener and enemy
const listener = va.Emitter_Create();
listener.name = 'Camera';
listener.setPosition(cameraPosition.x, cameraPosition.y, cameraPosition.z);
listener.reverbRayCount = 128;
listener.reverbBounceCount = 64;

// Determine how many rays to cast towards other emitters
listener.occlusionRayCount = 1024;
listener.occlusionBounceCount = 8;

world.addEmitter(listener);

const enemyEmitter = va.Emitter_Create();
enemyEmitter.name = 'Enemy';
enemyEmitter.setPosition(enemy.x, enemy.y, enemy.z);
enemyEmitter.reverbRayCount = 32;
enemyEmitter.reverbBounceCount = 64;

world.addEmitter(enemyEmitter);
listener.addTarget(enemyEmitter);

// Callback when raytracing completes
enemyEmitter.onRaytracedByAnotherEmitter = (other) =>
{
    const filter = other.getTargetFilter(enemyEmitter);
    
    const gainLF = filter.gainLF;
    const gainHF = filter.gainHF;
    
    // PSEUDOCODE - play a sound with a low pass filter
    playSound('Footstep', enemyEmitter.position, filter);
};
VAEmitter* enemyEmitter;

void OnEnemyRaytracedByAnother(VAEmitter* other)
{
    VALowPassFilter* filter = vaEmitterGetTargetFilter(other, enemyEmitter);
    
    float gainLF = filter->gainLF;
    float gainHF = filter->gainHF;
    
    // PSEUDOCODE - play a sound with a low pass filter
    play_sound(SOUND_FOOTSTEP, vaEmitterGetPosition(enemyEmitter), filter);
}

void CreateEmitters()
{
    // Create listener and enemy
    VAEmitter* listener = vaEmitterCreate();

    vaEmitterSetName(listener, "Camera");
    vaEmitterSetPosition(listener, cameraPosition);
    vaEmitterSetReverbRayCount(listener, 128);
    vaEmitterSetReverbBounceCount(listener, 64);

    // Determine how many rays to cast towards other emitters
    vaEmitterSetOcclusionRayCount(listener, 1024);
    vaEmitterSetOcclusionBounceCount(listener, 8);

    vaWorldAddEmitter(ctx, listener);

    enemyEmitter = vaEmitterCreate();

    vaEmitterSetName(enemyEmitter, "Enemy");
    vaEmitterSetPosition(enemyEmitter, enemyPosition);
    vaEmitterSetReverbRayCount(enemyEmitter, 32);
    vaEmitterSetReverbBounceCount(enemyEmitter, 64);

    vaWorldAddEmitter(ctx, enemyEmitter);
    vaEmitterAddTarget(listener, enemyEmitter);
    vaEmitterSetOnRaytracedByAnotherEmitterCallback(enemyEmitter, OnEnemyRaytracedByAnother);
}

Getting Started

Download the Vercidium Audio SDK from https://vercidium.com.

The SDK contains:

Use the dropdown in the top-left of this website to choose your preferred language. The content on each page will update to match.

You have selected: C#

You have selected: JS

You have selected: C

C# Setup

The C# SDK comes with two builds:

  • dev build, with debug window rendering
  • production build

I recommend integrating with the dev build first, so you can check your raytracing scene is set up correctly in the debug window. Ensure your final game ships with the production build for maximum performance and compatibility.

To add Vercidium Audio to your C# project, add this to your .csproj file:

<PropertyGroup>
    <!-- Replace this with the path to your vaudio SDK -->
    <VAudioDir>path\to\your\vaudio\folder</VAudioDir>
</PropertyGroup>
    
<!-- Add vaudio.dll to your project -->
<ItemGroup>
    <Reference Include="vaudio">
        <HintPath>$(VAudioDir)\vaudio.dll</HintPath>
    </Reference>
</ItemGroup>

<!-- Copy dev build dependencies. If using the production build, you can delete this block -->
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
    <!-- Copy dependencies to the build directory -->
    <Copy SourceFiles="$(VAudioDir)\glfw3.dll" DestinationFolder="$(OutDir)" />
    <Copy SourceFiles="$(VAudioDir)\libSkiaSharp.dll" DestinationFolder="$(ProjectDir)" />
    <Copy SourceFiles="$(VAudioDir)\libHarfBuzzSharp.dll" DestinationFolder="$(ProjectDir)" />
</Target>

JavaScript / Web Assembly Setup

The JS SDK contains a vaudio-wrapper.js file and a _framework folder:

  • the _framework folder contains the .wasm Web Assembly binaries and JS .NET runtime
  • the JS wrapper provides a nicer JS API, over the raw Web Assembly calls

To use this code you must initialise the .NET runtime and create the JS wrapper:

import { dotnet } from './_framework/dotnet.js';
import { wrapVA } from './vaudio-wrapper.js';

// Set this to the number of threads you'll use
const YOUR_THREAD_COUNT = 4;

// The .NET runtime needs 6 threads. Without this, the browser may freeze
const DOTNET_THREAD_COUNT = 6;

// Initialise the .NET runtime
const { getAssemblyExports, getConfig } = await dotnet
    .withDiagnosticTracing(false)
    .withConfig({
        jsThreadBlockingMode: "DangerousAllowBlockingWait", // Without this all calls to WASM are async
        pthreadPoolInitialSize: YOUR_THREAD_COUNT + DOTNET_THREAD_COUNT,
        pthreadPoolUnusedSize: 0,
    })
    .create();

const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);

// Wrap all Web Assembly calls into JS objects with get/setters for ease of use
const va = wrapVA(exports.VAudioExports);

C Setup

The C SDK package contains:

  • include/vaudio.h — single header with all declarations
  • windows/vaudionative.dll + vaudionative.lib
  • linux/libvaudionative.so
  • macos/libvaudionative.dylib
  • android/<abi>/libvaudionative.so (arm64-v8a, armeabi-v7a, x86, x86_64)

Include the header and link against the library for your platform:

#include "vaudio.h"

Windows (MSVC):

cl your_app.c /I path\to\include /link path\to\windows\vaudionative.lib

Linux / macOS:

gcc your_app.c -Ipath/to/include -Lpath/to/linux -lvaudionative -o your_app

Copy the shared library (vaudionative.dll / libvaudionative.so / libvaudionative.dylib) to the same directory as your executable, or add its directory to your library search path (PATH on Windows, LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on macOS).

Create a World

Create a world, which will store emitters, primitives and materials. Read more: Worlds.

var world = new World()
{
    WorldSize = new Vector(100),
    RenderingEnabled = true,
};
const world = va.World_Create();
world.worldSize = { x: 100, y: 100, z: 100 };
int main(void)
{
    VAWorld* ctx = vaWorldCreate();
    vaWorldSetWorldSize(ctx, vaVectorCreate(100.0f, 100.0f, 100.0f));
}

Primitives

Create a copy of your game's world using low-poly primitives. Read more: Primitives.

var prism = new PrismPrimitive()
{
    // Every primitive must have a material
    material = MaterialType.Concrete,
    
    // Size must be separate from transform
    size = new Vector(15),

    // Rotate and position it
    transform = Matrix.CreateRotationX(MathF.PI / 4) *
                Matrix.CreateTranslation(30, 30, 30)
};

world.AddPrimitive(prism);
const prism = va.PrismPrimitive_Create();

prism.material = MaterialType.Concrete;
prism.size = { x: 15, y: 15, z: 15 };
prism.transform = va.Matrix.Multiply(
    va.Matrix.CreateRotationY(Math.PI / 4),
    va.Matrix.CreateTranslation(30, 30, 30)
);

world.addPrimitive(prism);
#include <math.h> // For M_PI

VAPrismPrimitive* prism = vaPrismPrimitiveCreate();

vaPrismPrimitiveSetMaterial(prism, VAMaterialConcrete);
vaPrismPrimitiveSetSize(prism, vaVectorCreate(15.0f, 15.0f, 15.0f));

VAMatrix rotation = vaMatrixCreateRotationX(M_PI / 4);
VAMatrix translate = vaMatrixCreateTranslation(30, 30, 30);
VAMatrix transform = vaMatrixMultiply(&rotation, &translate);
vaPrismPrimitiveSetTransform(prism, &transform);

vaWorldAddPrimitive(ctx, prism);

Emitters

Create a listener emitter that casts occlusion rays. Read more: Emitters.

var listener = new Emitter()
{
    Position = new Vector(4),
    OcclusionRayCount = 256,
    OcclusionBounceCount = 8,
};

world.AddEmitter(listener);
const emitter = va.Emitter_Create();
emitter.setPosition(4, 4, 4);
emitter.occlusionRayCount = 256;
emitter.occlusionBounceCount = 8;

world.addEmitter(emitter);
VAEmitter* listener = vaEmitterCreate();
vaEmitterSetPosition(listener, vaVectorCreate(4, 4, 4));
vaEmitterSetOcclusionRayCount(listener, 256);
vaEmitterSetOcclusionBounceCount(listener, 8);

int errorCode = vaWorldAddEmitter(ctx, listener);
assert(errorCode == 0);

Create another emitter that is discovered by the listener:

var target = new Emitter()
{
    Position = new Vector(10),
};

world.AddEmitter(target);

// Cast occlusion rays towards the emitter
listener.AddTarget(target);

// This callback is invoked when the listener raytraces the target
target.OnRaytracedByAnotherEmitter = (Emitter other) =>
{
    // 'other' is the listener emitter
    LowPassFilter filter = other.GetTargetFilter(target);

    // Print low-pass filter gains    
    Console.WriteLine($"OnRaytracedByAnotherEmitter: {filter.gainLF}, {filter.gainHF}");

    // PSEUDOCODE - play a sound
    Godot.PlaySound(SoundType.Explosion, target.position, filter);
};
const target = va.Emitter_Create();
target.setPosition(10, 10, 10);

world.addEmitter(target);

// Cast occlusion rays towards the emitter
listener.addTarget(target);

// This callback is invoked when the listener raytraces the target
target.onRaytracedByAnotherEmitter = (other) => {

    // 'other' is the listener emitter
    const filter = other.getTargetFilter(target);

    // Print low-pass filter gains  
    console.log(`onRaytracedByAnotherEmitter: ${filter.gainLF}, ${filter.gainHF}`);
        
    // PSEUDOCODE - play a sound
    PlaySound('/explosion.ogg', target.position, filter);
}
// This callback is invoked when the listener raytraces the target
void on_raytraced_by_another_emitter(VAEmitter* source, VAEmitter* target)
{
    VALowPassFilter* filter = vaEmitterGetTargetFilter(source, target);

    // Print low-pass filter gains
    printf("on_raytraced_by_another_emitter: %f. GainHF: %f\n", filter->gainLF, filter->gainHF);
}

VAEmitter* target = vaEmitterCreate();
vaEmitterSetPosition(target, vaVectorCreate(10, 10, 10));

int errorCode = vaWorldAddEmitter(ctx, target);
assert(errorCode == 0);

// Cast occlusion rays towards the emitter
vaEmitterAddTarget(listener, target);

// Set the callback function on the target
vaEmitterSetOnRaytracedByAnotherEmitterCallback(target, on_raytraced_by_another_emitter);

Update Loop

Update the world every frame. This will perform raytracing and handle input for the debug rendering window.

public void Update()
{
    // Update anything at any time (emitter, primitive, etc)
    listener.Position = new Vector(20, 20, 20);
    
    // Perform raytracing on background threads and invoke callbacks
    world.Update();
}
function update()
{
    // Update anything at any time (emitter, primitive, etc)
    emitter.setPosition(20, 20, 20);
    
    // Perform raytracing on background threads and invoke callbacks
    ctx.update();
    
    requestAnimationFrame(update);
}

requestAnimationFrame(update);
void Update()
{
    // Update anything at any time (emitter, primitive, etc)
    vaEmitterSetPosition(listener, vaVectorCreate(20, 20, 20));
    
    // Perform raytracing on background threads and invoke callbacks
    vaWorldUpdate(ctx);
}

Reverb

Enable reverb rays on an emitter and set the energy cap required for max reverb volume. Read more: Reverb.

listener.ReverbRayCount = 32;
listener.ReverbBounceCount = 64;
listener.ReverbEnergyCap = 32 * 64 * 0.05;
listener.reverbRayCount = 32;
listener.reverbBounceCount = 64;
listener.reverbEnergyCap = 32 * 64 * 0.05;
vaEmitterSetReverbRayCount(listener, 32);
vaEmitterSetReverbBounceCount(listener, 64);
vaEmitterSetReverbEnergyCap(listener, 32 * 64 * 0.05);

Then access reverb properties via a callback:

world.OnReverbUpdated = () =>
{
    // Access processed properties
    var returning = listener.ProcessedReverb.ReturningPercent;
    Console.WriteLine($"{(int)(returning * 100)}% of energy returns to the listener");    

    // Access precalculated EAX properties
    var decayTime = listener.EAX.DecayTime;
}
world.onReverbUpdated = () =>
{
    // Access processed properties
    const returning = listener.processedReverb.returningPercent;
    console.log(`${Math.floor(returning * 100)}% of energy returns to the listener`);
    
    // Access precalculated EAX properties
    const decayTime = listener.eax.decayTime;
}
void OnReverbUpdated()
{
    // Access processed properties
    VAProcessedReverb* processed = vaEmitterGetProcessedReverb(listener);
    printf("%f%% of energy returns to the listener", processed->returningPercent);
    
    // Access precalculated EAX properties
    VAEAXReverb* eax = vaEmitterGetEAX(listener);
    float decayTime = eax->decayTime;    
}

vaWorldSetOnReverbUpdatedCallback(ctx, OnReverbUpdated);

Ambience

Enable ambient rays on an emitter. Read more: Ambience.

Then access results via a callback:

emitter.OnRaytracingComplete = () =>
{
    var ambientGainLF = listener.AmbientFilter.GainLF;
    var ambientGainHF = listener.AmbientFilter.GainHF;
}
emitter.onRaytracingComplete = () =>
{
    var ambientGainLF = listener.ambientFilter.gainLF;
    var ambientGainHF = listener.ambientFilter.gainHF;
}
void OnRaytracingComplete()
{
    VALowPassFilter* ambientFilter = vaEmitterGetAmbientFilter(listener);
    
    float ambientGainLF = ambientFilter->gainLF;
    float ambientGainHF = ambientFilter->gainHF;
}

vaEmitterSetOnRaytracingCompleteCallback(listener, OnRaytracingComplete);

Continue reading:

Advanced:

Materials

Each material has fields for:

The MaterialProperties classinterfacestruct contains all of these properties for a single material.

Customising Materials

There are 23 default materials for common materials like dirt, grass, concrete, metal, cloth, etc. You can customise these directly on your raytracing world:

var concrete = world.GetMaterial(MaterialType.Concrete);

concrete.AbsorptionLF = 0.2f; // Lose 20% of low-frequency energy on each bounce
concrete.AbsorptionHF = 0.3f; // Lose 30% of high-frequency energy on each bounce
import { MaterialType } from 'vaudio-wrapper.js';

const concrete = world.getMaterial(MaterialType.Concrete);

concrete.absorptionLF = 0.2; // Lose 20% of low-frequency energy on each bounce
concrete.absorptionHF = 0.3; // Lose 30% of high-frequency energy on each bounce
// Lose 20% of low-frequency energy on each bounce
vaWorldSetMaterialAbsorptionLF(world, VAMaterialConcrete, 0.2f);

// Lose 30% of high-frequency energy on each bounce
vaWorldSetMaterialAbsorptionHF(world, VAMaterialConcrete, 0.3f);

Editing a material at runtime will cause the ray cache to be cleared and all rays to be re-cast

Creating Materials

Materials can be created on a World object.

The first 1000 material IDs (0 to 999 inclusive) are reserved, so start from 1000:

const int ALIEN = 1000;
                                             // Absorption  Scattering  Transmission
var alienProperties = new MaterialProperties(0.5f, 0.6f,    0.7f,       4, 7, 0.1f, 0.2f);
var alienColour = new Color(255, 0, 255);

world.AddMaterial((MaterialType)ALIEN, alienProperties, alienColour);
const ALIEN = 1000;
                                                         // Absorption  Scattering  Transmission
const alienProperties = va.MaterialProperties_CreateWith(0.5f, 0.6f,    0.7f,       4, 7, 0.1f, 0.2f);

world.addMaterial(ALIEN, alienProperties);
const int MATERIAL_ALIEN = 1000;
vaWorldCreateMaterial(world, MATERIAL_ALIEN);

vaWorldSetMaterialAbsorptionLF(world, MATERIAL_ALIEN, 0.5f);
vaWorldSetMaterialAbsorptionHF(world, MATERIAL_ALIEN, 0.6f);

vaWorldSetMaterialScattering(world, MATERIAL_ALIEN, 0.7f);

vaWorldSetMaterialTransmissionLF(world, MATERIAL_ALIEN, 4);
vaWorldSetMaterialTransmissionHF(world, MATERIAL_ALIEN, 7);

vaWorldSetMaterialPlaneTransmissionLF(world, MATERIAL_ALIEN, 0.1f);
vaWorldSetMaterialPlaneTransmissionHF(world, MATERIAL_ALIEN, 0.2f);

Notes:

Transmission Helpers

The TransmissionLF and TransmissionHF fields are defined as decibels per meter, which is a tricky unit to work with. There's a transmission->thickness helper function that I recommend using instead:

const concrete = world.GetMaterial(MaterialType.Concrete);

// A ray will lose all low-frequency energy after travelling 20 meters through a concrete primitive
concrete.TransmissionLF = MaterialProperties.TransmissionForThickness(20);

// A ray will lose all high-frequency energy after travelling 5 meters through a concrete primitive
concrete.TransmissionHF = MaterialProperties.TransmissionForThickness(5);
import { transmissionForThickness, MaterialType } from 'vaudio-wrapper.js';

const concrete = world.getMaterial(MaterialType.Concrete);

// A ray loses all low-frequency energy after travelling 20 meters through a concrete primitive
concrete.transmissionLF = transmissionForThickness(20);

// A ray loses all high-frequency energy after travelling 5 meters through a concrete primitive
concrete.transmissionHF = transmissionForThickness(5);
// A ray loses all low-frequency energy after travelling 20 meters through a concrete primitive
vaWorldSetMaterialTransmissionLF(world, VAMaterialConcrete, materialPropertiesTransmissionForThickness(20));

// A ray loses all high-frequency energy after travelling 5 meters through a concrete primitive
vaWorldSetMaterialTransmissionHF(world, VAMaterialConcrete, materialPropertiesTransmissionForThickness(5));

By default, 1 world unit is 1 meter. You can change this via the World.MetersPerUnit fieldWorld.metersPerUnit fieldvaWorldSetMetersPerUnit() function.

Validation

Editing material fields may throw an exception if it violates any of the below:

Muffling

To determine how muffled an emitter is, you must cast occlusion or permeation rays towards it from another emitter (usually your listener).

var listener = new Emitter()
{
    OcclusionRayCount = 1024,
    OcclusionBounceCount = 8,
    PermeationRayCount = 128,
    PermeationBounceCount = 3,
};

world.AddEmitter(listener);

var target = new Emitter();

world.AddEmitter(target);

// Enable muffling
listener.AddTarget(target);
const listener = va.Emitter_Create();
listener.occlusionRayCount = 1024;
listener.occlusionBounceCount = 8;
listener.permeationRayCount = 128;
listener.permeationBounceCount = 3;

world.addEmitter(listener);

const target = va.Emitter_Create();

world.addEmitter(target);

// Enable muffling
listener.addTarget(target);
VAEmitter* listener = vaEmitterCreate();
vaEmitterSetOcclusionRayCount(listener, 1024);
vaEmitterSetOcclusionBounceCount(listener, 8);
vaEmitterSetPermeationRayCount(listener, 128);
vaEmitterSetPermeationBounceCount(listener, 3);

vaWorldAddEmitter(ctx, listener);

VAEmitter* target = vaEmitterCreate();

vaWorldAddEmitter(ctx, target);

// Enable muffling
vaEmitterAddTarget(listener, target);

AddTarget()addTarget()vaEmitterAddTarget() will throw an exception if both occlusion and permeation are disabled (ray/bounce count is 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 (LOS) with each of the emitter's targets.

Once LOS is found, the ray stops checking for LOS. 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 achieving LOS with the target (material absorption is zero, and air absorption is disabled).

The minimum energy it can have is 0.0, which means it did not achieve LOS 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 or air absorption. Instead, on each bounce they cast an extra ray directly towards each target, travelling through primitives and losing energy based on:

The maximum energy a permeation ray can have is 1.0 x permeationBounceCount, as 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 are converted to low-frequency and high-frequency filter gains via the Emitter.GainFormula functionemitter.occlusionEnergyCap and emitter.permeationEnergyCap fieldsvaEmitterSetGainFormula function.

Read more: Emitter Gain Formula

Accessing Low Pass Filters

When a target emitter is first raytraced, its low-pass filter can be accessed via a callback:

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.gainHF;
    
    // 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);
}

world.AddEmitter(target);
const target = va.Emitter_Create();

target.onRaytracedByAnotherEmitter = (other) => {
    const filter = other.getTargetFilter(target);

    // These fields contain the results of gainFormula
    //  and range from 0.0 to 1.0
    const gainLF = filter.gainLF;
    const gainHF = filter.gainHF;

    // PSEUDOCODE - apply the gains to a low pass filter
    applyLowPassFilter(sound, gainLF, gainHF);

    // Play the sound AFTER setting the filter, so it's muffled
    //  correctly from the beginning
    playSound(sound);
};

world.addEmitter(target);
void onRaytracedByAnotherEmitter(VAEmitter* source, VAEmitter* target)
{
    VALowPassFilter* filter = vaEmitterGetTargetFilter(source, target);

    // These fields contain the results of the gain formula
    //  and range from 0.0f to 1.0f
    float gainLF = filter->gainLF;
    float gainHF = filter->gainHF;

    // PSEUDOCODE - apply the gains to a low pass filter
    applyLowPassFilter(sound, gainLF, gainHF);

    // Play the sound AFTER setting the filter, so it's muffled
    //  correctly from the beginning
    playSound(sound);
}

vaEmitterSetOnRaytracedByAnotherEmitterCallback(target, onRaytracedByAnotherEmitter);
vaWorldAddEmitter(ctx, target);

Once the emitter has been raytraced, its filter will be updated every frame and can be accessed at any time:

if (listener.HasRaytracedTarget(target))
{
    var filter = listener.GetTargetFilter(target);
    
    var gainLF = filter.gainLF;
    var gainHF = filter.gainHF;
    
    // PSEUDOCODE
    Godot.UpdateLowPassFilter(sound, gainLF, gainHF);
};
if (listener.hasRaytracedTarget(target)) {
    const filter = listener.getTargetFilter(target);

    const gainLF = filter.gainLF;
    const gainHF = filter.gainHF;

    // PSEUDOCODE
    updateLowPassFilter(sound, gainLF, gainHF);
}
if (vaEmitterHasRaytracedTarget(listener, target))
{
    VALowPassFilter* filter = vaEmitterGetTargetFilter(listener, target);

    float gainLF = filter->gainLF;
    float gainHF = filter->gainHF;

    // PSEUDOCODE
    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:

var target = new Emitter();

target.OnRaytracedByAnotherEmitter = (Emitter Other) => ...

// This is invoked after OnRaytracedByAnotherEmitter()
target.OnRaytracingComplete = () =>
{
    world.RemoveEmitter(target);
};

world.AddEmitter(target);
const target = va.Emitter_Create();

target.onRaytracedByAnotherEmitter = (other) => { /* ... */ };

// This is invoked after onRaytracedByAnotherEmitter
target.onRaytracingComplete = () => {
    world.removeEmitter(target);
};

world.addEmitter(target);
VAEmitter* target = vaEmitterCreate();

vaEmitterSetOnRaytracedByAnotherEmitterCallback(target, onRaytracedByAnotherEmitter);

// This is invoked after onRaytracedByAnotherEmitter
void onRaytracingComplete(void)
{
    vaWorldRemoveEmitter(ctx, target);
}

vaEmitterSetOnRaytracingCompleteCallback(target, onRaytracingComplete);
vaWorldAddEmitter(ctx, 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 footstep / 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 sound

Primitives

Vercidium Audio operates against a low-poly copy of the world, similar to a physics engine.

To add a primitive to the simulation, invoke the AddPrimitive function on the world:

var prism = new PrismPrimitive()
{
    // Every primitive must have a material
    material = MaterialType.Metal,
    
    // Size MUST be separate to the transform
    size = new Vector(15),

    // Rotate and position it
    transform = Matrix.CreateRotationX(MathF.PI / 4) *
                Matrix.CreateTranslation(50, 50, 50)
};

world.AddPrimitive(prism);
// Create a rectangular prism
const prism = va.PrismPrimitive_Create();

prism.material = MaterialType.Concrete;
prism.size = { x: 15, y: 15, z: 15 };
prism.transform = va.Matrix.Multiply(
    va.Matrix.CreateRotationY(Math.PI / 4),
    va.Matrix.CreateTranslation(50, 50, 50)
);

world.addPrimitive(prism);
// Create a rectangular prism
VAPrismPrimitive* prism = vaPrismPrimitiveCreate();

vaPrismPrimitiveSetMaterial(prism, VAMaterialConcrete);
vaPrismPrimitiveSetSize(prism, vaVectorCreate(15.0f, 15.0f, 15.0f));

VAMatrix rotation = vaMatrixCreateRotationY(M_PI / 4);
VAMatrix translate = vaMatrixCreateTranslation(50, 50, 50);
VAMatrix transform = vaMatrixMultiply(&rotation, &translate);
vaPrismPrimitiveSetTransform(prism, &transform);

vaWorldAddPrimitive(world, prism);

An exception will be thrown if a primitive is created with MaterialType.AirMaterialType.AirVAMaterialAir.

Below is the full list of primitives.

Updating Primitives

The position/rotation/material/etc of primitives can be updated at any time, even while the background raytracing threads are running. The updates will apply when the background threads finish running.

When a primitive is updated, it will automatically be flagged as 'dirty'. The world keeps track of all dirty primitives, and will refresh the bounding volume hierarchy (BVH) that is used to speed up raytracing intersections.

This all occurs on background threads so there should be minimal impact on the main thread, but raytracing will be slightly delayed by this preparation work. You can check how long preparation and BVH updates take via the world.PreparationTime field.

Reverb

This file is an overview - more details are provided on each sub-page.

Enable Reverb

Enable reverb on an emitter by providing the following settings:

var listener = new Emitter()
{
    // Required
    ReverbRayCount = 32,
    ReverbBounceCount = 64,
    ReverbEnergyCap = 32 * 64 * 0.05,
    
    // Optional
    MaxEchogramTime = 5000, // milliseconds
    EchogramGranularity = 50, // milliseconds
    AffectsGroupedEAX = false,
    HasRelativeReverb = false,
    RelativeReverbInnerThreshold = 0.5f,
    RelativeReverbOuterThreshold = 0.8f,
};

world.AddEmitter(listener);
const emitter = va.Emitter_Create();

// Required
emitter.reverbRayCount = 32;
emitter.reverbBounceCount = 64;
emitter.reverbEnergyCap = 32 * 64 * 0.05;

// Optional
emitter.maxEchogramTime = 5000; // milliseconds
emitter.echogramGranularity = 50; // milliseconds
emitter.affectsGroupedEAX = false;
emitter.hasRelativeReverb = false;
emitter.relativeReverbInnerThreshold = 0.5;
emitter.relativeReverbOuterThreshold = 0.8;

world.addEmitter(emitter);
VAEmitter* emitter = vaEmitterCreate();

// Required
vaEmitterSetReverbRayCount(emitter, 32);
vaEmitterSetReverbBounceCount(emitter, 64);
vaEmitterSetReverbEnergyCap(emitter, 32 * 64 * 0.05f);

// Optional
vaEmitterSetMaxEchogramTime(emitter, 5000); // milliseconds
vaEmitterSetEchogramGranularity(emitter, 50); // milliseconds
vaEmitterSetAffectsGroupedEax(emitter, false);
vaEmitterSetHasRelativeReverb(emitter, false);
vaEmitterSetRelativeReverbInnerThreshold(emitter, 0.5f);
vaEmitterSetRelativeReverbOuterThreshold(emitter, 0.8f);

vaWorldAddEmitter(ctx, emitter);

I recommend having a much higher bounce count for reverb than other ray types (occlusion, permeation, etc) to ensure the echogram is filled. Read more about each reverb setting in the sections below.

Reverb Energy Cap

To control how much energy is required for reverb to be at max volume, you can set an energy cap on each emitter. Read more: Reverb Energy Cap.

Echograms

An emitter's echogram must be set up correctly for reverb to sound correct. Read more: Echogram Refinement.

Access Reverb Results

Reverb results are exposed via the OnReverbUpdated callback on the World. This gives you a chance to create/update reverb effects before playing a sound.

var world = new World()
{
    OnReverbUpdated = () => 
    {
        // Access reverb stats
        Console.WriteLine(listener.RawReverb.ReturnedTotal);
        Console.WriteLine(listener.ProcessedReverb.OutsidePercent);
        Console.WriteLine(listener.EAX.DecayTime);
        
        // Update reverb effects
        // ...
    }
};
world.onReverbUpdated = () => {
    // Access reverb stats
    console.log(listener.rawReverb.returnedTotal);
    console.log(listener.processedReverb.outsidePercent);
    console.log(listener.eax.decayTime);

    // Update reverb effects
    // ...
};
void OnReverbUpdated() {
    // Access reverb stats
    printf("%f\n", vaEmitterGetRawReverb(emitter)->returnedTotal);
    printf("%f\n", vaEmitterGetProcessedReverb(emitter)->outsidePercent);
    printf("%f\n", vaEmitterGetEAX(emitter)->decayTime);

    // Update reverb effects
    // ...
}

vaWorldSetOnReverbUpdatedCallback(ctx, OnReverbUpdated);

The values in the EAX object can be copied directly onto an EAX reverb effect in your engine.

If you wish to calculate these values yourself, see Custom EAX Formulas.

If you wish to calculate these values yourself, see Custom EAX Formulas.

Planned feature: calculate reverb properties for Godot, Unreal, FMOD, Wwise reverb systems

Raw Reverb

Emitter.RawReverb contains the raw results of raytracing, such as the total energy in all rays returning to the listener. Read more: RawReverb.

Processed Reverb

Emitter.ProcessedReverb contains the results of raytracing that have been processed into a more dev-friendly format. Read more: ProcessedReverb.

Grouped EAX

Every emitter has its own EAX object, but it's expensive to run many reverb effects in real time. The engine will group emitters with similar reverb properties together. These grouped EAX objects can be accessed in the world.GroupedEAX list.

Read more: Grouped EAX.

Directional Reverb

The direction and volume of each Grouped EAX can be calculated relative to an emitter.

To enable this, set relative reverb to true on the emitter that you would like to hear directional reverb from (usually just your main listener emitter):

var listener = new Emitter()
{
    HasRelativeReverb = true,
};
const listener = va.Emitter_Create();
listener.hasRelativeReverb = true;

Then access the relative direction and volume on each entry in world.GroupedEAX:

world.OnReverbUpdated = () => 
{
     foreach (var eax in world.GroupedEAX)
     {
         Vector pan = eax.RelativeDirections[listener];
         float gain = eax.RelativeGains[listener];
     }
};
world.onReverbUpdated = () => {
    for (const eax of world.groupedEAX) {
        const pan = eax.relativeDirections(listener);
        const gain = eax.relativeGains(listener);
    }
};
void OnReverbUpdated()
{
    const VAEAXReverb** grouped_eax = vaWorldGetGroupedEAX(ctx);
    int count = vaWorldGetGroupedEAXCount(ctx);

    for (int i = 0; i < count; i++)
    {
        VAVector* pan = vaEaxReverbGetRelativeDirection(grouped_eax[i], listener);
        float* gain = vaEaxReverbGetRelativeGain(grouped_eax[i], listener);
    }
}

Read more: Volume and Direction.

Troubleshooting

First add vaudio.dll as a dependency to your project, as well as helpers to copy the dependencies and resource folder to the output directory:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
    </PropertyGroup> 

    <!-- Add vaudio.dll to your project -->
    <ItemGroup>
        <Reference Include="vaudio">
            <HintPath>path\to\your\vaudio\folder\vaudio.dll</HintPath>
        </Reference>
    </ItemGroup>
</Project>

Then create a World and run the project:

using vaudio;

static void Main(string[] args)
{
    var world = new World()
    {
        WorldSize = new(100),
        RenderingEnabled = true,
    }

    while (true)
    {
        world.Update();
        System.Threading.Thread.Sleep(16);
    }
}

The first time you run this, you will likely face some errors. Below is a list of solutions for each error.

Native Libraries Failed to Load

When rendering is enabled, vaudio extracts native libraries (glfw3.dll, libSkiaSharp.dll, libHarfBuzzSharp.dll) to a temp folder on first run. If this fails, you will see an exception like:

IOException: Vercidium Audio could not extract 'glfw3.dll' to 'C:\Users\...\AppData\Local\Temp\vaudio\1.1.2\'.

Permission issue — the process does not have write access to the temp folder. Check that %TEMP% is writable by the current user.

Antivirus blocking — some AV software quarantines DLLs extracted to temp by unrecognised processes. Add an exclusion for %TEMP%\vaudio\ in your AV settings.

First, serve the wasm folder over HTTP — the .wasm binaries and .NET runtime cannot be loaded from a file:// URL. Use any local dev server:

npx serve .

Then initialise the .NET runtime and create the JS wrapper. Read more: Getting Started.

The first time you run this, you will likely face some errors. Below is a list of solutions for each error.

SharedArrayBuffer Not Available

Multi-threading requires SharedArrayBuffer, which browsers only expose on cross-origin isolated pages. You will see an error like:

SharedArrayBuffer is not defined

Your server must send these two HTTP headers on every response:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Most dev servers support custom headers in a config file. For example, with Vite (vite.config.js):

export default {
    server: {
        headers: {
            'Cross-Origin-Opener-Policy': 'same-origin',
            'Cross-Origin-Embedder-Policy': 'require-corp',
        },
    },
};

Module Type Error

If you see an error like:

Cannot use import statement in a non-module world

Make sure your package.json contains "type": "module". The wasm folder already includes this file — if you copy files into your own project, include it too.

Thread Count

If the browser freezes or raytracing appears to stall, the thread pool may be too small. The .NET runtime needs 6 threads of its own. Set pthreadPoolInitialSize to at least YOUR_THREAD_COUNT + 6:

const { getAssemblyExports, getConfig } = await dotnet
    .withConfig({
        jsThreadBlockingMode: "DangerousAllowBlockingWait",
        pthreadPoolInitialSize: YOUR_THREAD_COUNT + 6,
        pthreadPoolUnusedSize: 0,
    })
    .create();

Without jsThreadBlockingMode: "DangerousAllowBlockingWait", all calls into Web Assembly are asynchronous, which is more difficult to work with.

First, copy the shared library for your platform next to your executable:

Platform File
Windows windows/vaudionative.dll
Linux linux/libvaudionative.so
macOS macos/libvaudionative.dylib
Android android/<abi>/libvaudionative.so

Then include the header and link against the import library:

#include "vaudio.h"

The first time you run this, you will likely face some errors. Below is a list of solutions for each error.

Missing Shared Library

On Windows, if you see:

The code execution cannot proceed because vaudionative.dll was not found.

Copy windows/vaudionative.dll to the same directory as your executable, or add its directory to PATH.

On Linux/macOS, if you see:

error while loading shared libraries: libvaudionative.so: cannot open shared object file

Add the library's directory to LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS):

export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH   # Linux
export DYLD_LIBRARY_PATH=/path/to/lib:$DYLD_LIBRARY_PATH  # macOS

Linker Error (Windows / MSVC)

If the project compiles but you get unresolved external symbol errors at link time, make sure you are linking against windows/vaudionative.lib:

cl your_app.c /I path\to\include /link path\to\windows\vaudionative.lib

Linker Error (Linux / macOS)

Pass the library directory with -L and the library name with -l:

gcc your_app.c -Ipath/to/include -Lpath/to/linux -lvaudionative -o your_app

Visualisation

Visualisation rays are cast in random directions, and the position of each bounce can be used to render symbols/particles in your game.

To enable audio visualisation on an emitter, set these four settings:

var emitter = new Emitter()
{
    VisualisationRayCount = 32,
    VisualisationBounceCount = 3,
    VisualisationUpdateFrequency = 500, // milliseconds
    VisualisationCallback = (VisualisationData[] data) =>
    {
        for (int i = 0; i < data.Length; i++)
        {
            var position = data[i].position;
            var normal = data[i].normal;
        }            
    }
};
const emitter = va.Emitter_Create();

emitter.visualisationRayCount = 32;
emitter.visualisationBounceCount = 3;
emitter.visualisationUpdateFrequency = 500; // milliseconds
emitter.visualisationCallback = (data) =>
{
    for (let i = 0; i < data.length; i++)
    {
        var position = data[i].position;
        var normal = data[i].normal;
    }            
};
void VisualisationCallback(VAVisualisationData* data, int count)
{
    for (var i = 0; i < count; i++)
    {
        var position = data[i].position;
        var normal = data[i].normal;
    }            
}

void Initialise()
{
    VAEmitter* emitter = vaEmitterCreate();
    
    vaEmitterSetVisualisationRayCount(emitter, 32);
    vaEmitterSetVisualisationBounceCount(emitter, 3);
    vaEmitterSetVisualisationUpdateFrequency(emitter, 500); // milliseconds
    vaEmitterSetVisualisationCallback(emitter, VisualisationCallback);
}

VisualisationUpdateFrequencyvisualisationUpdateFrequencyvaEmitterSetVisualisationUpdateFrequency() controls how often visualisation rays should be cast.

VisualisationCallbackvisualisationCallbackThe callback passed to vaEmitterSetVisualisationCallback() will be invoked on the main thread when raytracing completes.

It's up to you to render shapes/symbols/etc in your game. The simplest approach is to copy the position and normal data directly to an instance buffer, similar to a particle system.

Worlds

A world is composed of:

Settings can be customised at any time, either when creating a world or at runtime:

var world = new World()
{
    Position = new Vector(0),
    Size = new Vector(100),
};
const world = vaudio.World_Create();
world.position = { x: 0, y: 0, z: 0 };
world.size = { x: 100, y: 100, z: 100 };
VAWorld* ctx = vaWorldCreate();
vaWorldSetPosition(ctx, vaVectorCreate(0, 0, 0));
vaWorldSetSize(ctx, vaVectorCreate(100, 100, 100));

The full list of fields and methods is available here: World.

Air Absorption

Customise humidity, temperature and pressure settings, or provide your own custom formula. Read more: Air Absorption.

world.AirAbsorption = new AirAbsorptionSettings()
{
    HumidityPercent = 0.4f,
    TemperatureCelsius = 26
};
const airAbsorption = vaudio.AirAbsorptionSettings_Create();
airAbsorption.humidityPercent = 0.4;
airAbsorption.temperatureCelsius = 26;
world.airAbsorption = airAbsorption;
VAAirAbsorptionSettings* airAbsorption = vaAirAbsorptionCreate();
vaAirAbsorptionSetHumidity(airAbsorption, 0.4f);
vaAirAbsorptionSetTemperature(airAbsorption, 26);
vaWorldSetAirAbsorption(ctx, airAbsorption);

Materials

Customise absorption, scattering and transmission values for existing materials, or create new materials. Read more: Materials.

var metal = world.GetMaterial(MaterialType.Metal);
metal.AbsorptionLF = 0.1f;
const metal = world.getMaterial(MaterialType.Metal);
metal.absorptionLF = 0.1;

CustomEaxFormulas

This class allows overriding the default formulas for calculating EAX reverb properties. Read more: Custom EAX Formulas.

Dispose

To safely dispose a raytracing world, we need to wait for the background threads to finish. To do this, set the world.PendingShutdown to trueset the world.pendingShutdown field to truecall vaWorldSetPendingShutdown and continue calling world.Update()world.update()vaWorldUpdate. This flag indicates to the world that it should not submit any more work to the background threads.

Then check world.AreThreadsRunningworld.areThreadsRunningvaWorldGetThreadsRunning to determine when the world is safe to dispose.

World world;

public void Update()
{
    if (world == null)
        return;

    // Tell the world we would like to shut it down
    world.PendingShutdown = true;
    world.Update();

    // Dispose everything when threads are no longer running
    if (world.PendingShutdown && !world.AreThreadsRunning)
    {
        world.Dispose();
        world = null;

        // Forget about emitters and prisms 
        listener = null;
        prism = null;
    }
}
let world;

function update()
{
    if (world == null)
        return;

    // Tell the world we would like to shut it down
    world.pendingShutdown = true;
    world.update();

    // Dispose everything when threads are no longer running
    if (world.pendingShutdown && !world.areThreadsRunning)
    {
        world.dispose();
        world = null;

        // Forget about emitters and prisms 
        listener = null;
        prism = null;
    }
}
VAWorld* ctx;
bool shouldShutdown = true;

void update()
{
    if (ctx == NULL)
        return;

    // Tell the world we would like to shut it down
    if (shouldShutdown)
        vaWorldSetPendingShutdown(ctx);

    vaWorldUpdate(ctx);

    // Dispose everything when threads are no longer running
    if (shouldShutdown && !vaWorldGetThreadsRunning(ctx))
    {
        // Free the world
        vaWorldFree(ctx);
        
        // Free all your emitters/primitives
        vaEmitterDestroy(listener);
        vaPrismPrimitiveFree(prism);
        
        ctx = NULL;
    }
}

Echogram Refinement

Empty Bins

EAX properties can only be calculated correctly if the emitter's echogram contains enough data. Echograms are rendered in the debug window, and show how much energy is returning back to the emitter over time. The length of the echogram is determined by Emitter.MaxEchogramTimeemitter.maxEchogramTimevaEmitterSetMaxEchogramTime(), and the duration of each 'bin' (a bar in the image below) is controlled by Emitter.EchogramGranularityemitter.echogramGranularityvaEmitterSetEchogramGranularity().

Red bars indicates bins with zero energy, and they break the calculation of EAX properties such as decay time, diffusion, density and delay:

echogram_with_gap.png

To ensure there are no gaps in your echograms, you can either:

An echogram with higher granularity has fewer bins and is less likely to have gaps:

echogram_without_gaps.png

Emitter.EchogramGranularityemitter.echogramGranularityvaEmitterSetEchogramGranularity() defaults to 50 (milliseconds) - increase it to 60, 80, 100, etc. until your echogram no longer has any red gaps. Alternatively, increase the number of reverb rays until all bins are populated.

Max Duration

If you have a larger environment, or highly reflective materials, you may need to increase Emitter.MaxEchogramTimeemitter.maxEchogramTimevaEmitterSetMaxEchogramTime() to fully capture the reverb tail.

For example, for a large building with a reflective concrete material, an echogram with 1000ms worth of data looks like this:

short_echogram.png

The bars on the far right still contain energy, which means our echogram isn't long enough. I recommend increasing Emitter.MaxEchogramTimeemitter.maxEchogramTimevaEmitterSetMaxEchogramTime() until you see red bars at the end:

echogram_with_red_bars_at_end.png

This means the echogram is now large enough to capture reverb for the 'worst case' scenario, i.e. the largest/most reflective environment in your game. This ensures all environments in your game will have correct reverb as well.

Summary

In the image below:

three_echograms.png

Grouped EAX

It's expensive to run many reverb effects in real time, so it's best to group emitters with similar reverb properties together.

Set MaximumGroupedEAXCountmaximumGroupedEAXCountvaWorldSetMaximumGroupedEAXCount() to the number of reverb effects your game has, and set AffectsGroupedEAXaffectsGroupedEAXvaEmitterSetAffectsGroupedEax() to true on the emitters that should be grouped together. I recommend setting this to false on your listener emitter, and true on all other emitters.

var world = new World()
{
    MaximumGroupedEAXCount = 4,
};

var emitter = new Emitter()
{
    AffectsGroupedEAX = true,
};

world.AddEmitter(emitter);
const world = va.createWorld();
world.maximumGroupedEAXCount = 4;

const emitter = va.createEmitter();
emitter.affectsGroupedEAX = true;

world.addEmitter(emitter);

Grouping Logic

All emitters with AffectsGroupedEAXaffectsGroupedEAXvaEmitterGetAffectsGroupedEax() set to true will have their EAX objects iteratively combined together to produce N grouped EAX objects.

EAX objects are compared via their:

Accessing Grouped EAX

Grouped EAX objects can be accessed via World.GroupedEAXWorld.groupedEAXvaWorldGetGroupedEAX(). This list will be empty until raytracing has run at least once - I recommend accessing this list in the OnReverbUpdated callbackonReverbUpdated callbackcallback passed to vaWorldSetOnReverbUpdatedCallback():

List<ALReverbEffect> effects = [];

world.OnReverbUpdated = () =>
{       
    for (int i = 0; i < world.GroupedEAX.Count; i++)
    {
        var grouped = world.GroupedEAX[i];
        
        // Copy properties to an OpenAL EAX reverb effect (or whichever reverb system your game uses)
        effects[i].DecayTime = grouped.DecayTime;
        effects[i].GainLF = grouped.GainLF;
        // etc...
    }
}
world.onReverbUpdated = () => {
    for (let i = 0; i < world.groupedEAX.length; i++) {
        const grouped = world.groupedEAX[i];

        // Copy properties to an OpenAL EAX reverb effect (or whichever reverb system your game uses)
        effects[i].decayTime = grouped.decayTime;
        effects[i].gainLF = grouped.gainLF;
        // etc...
    }
};
void OnReverbUpdated()
{
    int count = vaWorldGetGroupedEAXCount(world);
    const VAEAXReverb** groupedEAX = vaWorldGetGroupedEAX(world);

    for (int i = 0; i < count; i++)
    {
        const VAEAXReverb* grouped = groupedEAX[i];

        // Copy properties to an OpenAL EAX reverb effect (or whichever reverb system your game uses)
        effects[i].decayTime = grouped->decayTime;
        effects[i].gainLF = grouped->gainLF;
        // etc...
    }
}

void Initialise()
{
    vaWorldSetOnReverbUpdatedCallback(world, OnReverbUpdated);
}

When an emitter finishes raytracing, use its GroupedEAXIndexgroupedEAXIndexvaEmitterGetGroupedEaxIndex() to select the correct reverb effect to use for that emitter's sound playback:

emitter.OnRaytracedByAnotherEmitter = (Emitter other) =>
{
    ALReverbEffect effect = effects[emitter.GroupedEAXIndex];
    
    // OpenAL - apply the reverb effect to the source
    AL.Source3i(sourceID, AL.AL_AUXILIARY_SEND_FILTER, effect.effectSlotID, 0, 0);
}
emitter.onRaytracedByAnotherEmitter = (other) => {
    const effect = effects[emitter.groupedEAXIndex];

    // WebAudio - apply the reverb effect to the source
    // ...
};
void onRaytracedByAnotherEmitter(VAEmitter* source, VAEmitter* target)
{
    int index = vaEmitterGetGroupedEaxIndex(target);

    // Apply the reverb effect at index to the source
    // ...
}

vaEmitterSetOnRaytracedByAnotherEmitterCallback(emitter, onRaytracedByAnotherEmitter);

Ensure you've set up a callback for World.OnReverbUpdatedWorld.onReverbUpdatedvaWorldSetOnReverbUpdatedCallback(), so that you can update your reverb effects before playing sounds in the Emitter.OnRaytracedByAnotherEmitter callbackEmitter.onRaytracedByAnotherEmitter callbackcallback passed to vaEmitterSetOnRaytracedByAnotherEmitterCallback

Volume and Direction

The volume and direction of an emitter's reverb is relative. For example, an emitter in the same room as your listener emitter should have clear reverb, but another emitter in another room should have quiet reverb.

To enable this, set Emitter.HasRelativeReverbEmitter.hasRelativeReverbvaEmitterSetHasRelativeReverb() to true on your listener emitter. The relative gain and relative direction of each grouped EAX will then be automatically calculated for your listener emitter.

Access the gain and direction like so:

var listener = new Emitter()
{
    HasRelativeReverb = true,
};

world.OnReverbUpdated = () =>
{       
    for (int i = 0; i < world.GroupedEAX.Count; i++)
    {
        // Get the gain of this EAX slot relative to this listener
        effects[i].EffectSlotGain = grouped.RelativeGains[listener];
        
        // Get the direction of this EAX slot relative to this listener
        Vector direction = grouped.RelativeDirections[listener];
        effects[i].ReflectionsPan = direction;
        effects[i].LateReverbPan = direction;
    }        
}
const listener = va.createEmitter();
listener.hasRelativeReverb = true;

world.onReverbUpdated = () => {
    for (let i = 0; i < world.groupedEAX.length; i++) {
        const grouped = world.groupedEAX[i];

        // Get the gain of this EAX slot relative to this listener
        effects[i].effectSlotGain = grouped.relativeGains(listener);
        
        const direction = grouped.relativeDirections(listener);
        effects[i].reflectionsPan = direction;
        effects[i].lateReverbPan = direction;
    }
};
void OnReverbUpdated()
{
    int count = vaWorldGetGroupedEAXCount(world);
    const VAEAXReverb** groupedEAX = vaWorldGetGroupedEAX(world);

    for (int i = 0; i < count; i++)
    {
        const VAEAXReverb* grouped = groupedEAX[i];

        // Get the gain of this EAX slot relative to this listener
        float* gain = vaEaxReverGetRelativeGain(grouped, listener);
        VAVector* direction = vaEaxReverGetRelativeDirection(grouped, listener);
        
        // ERROR: gain/direction is not calculated for this emitter
        if (gain == NULL || direction == NULL)
            continue;
            
        effects[i].effectSlotGain = *gain;
        effects[i].reflectionsPan = *direction;
        effects[i].lateReverbPan = *direction;
    }
}

void Initialise()
{
    VAWorld* world = vaWorldCreate();
    vaWorldSetOnReverbUpdatedCallback(world, OnReverbUpdated);

    VAEmitter* listener = vaEmitterCreate();
    vaEmitterSetHasRelativeReverb(listener, true);
}

This gain is in the range 0.0 to 1.0, and should control the final output volume of that EAX reverb effect, (e.g. the effectSlotGain in OpenAL/EAX).

Do not apply a low pass filter between the audio source and the reverb effect in your game - the reverb effect should process raw audio data. Instead, the volume of the reverb effect's output should be controlled by the gain relative to your listener emitter. This allows reverb to be muffled on the other side of a wall.

The direction vector is not normalised - its magnitude indicates how strong the reverb directionality is. For example if the direction is (0, 0, 0), you'll hear reverb all around you, and if the pan vector is (0, 0, 1), you'll only hear reverb in that direction.

It's rare that the direction of all reverb rays will average to a pan vector of (0, 0, 0) (fully diffuse), so you can customise the minimum threshold that's required for reverb to be fully diffuse:

Emitter listener;

void Initialise()
{
    listener = new Emitter()
    {
        HasRelativeReverb = true,
        RelativeReverbInnerThreshold = 0.5f,
        RelativeReverbOuterThreshold = 0.8f,
    };
    
    world.AddEmitter(listener);
}
const listener = va.Emitter_Create();
listener.hasRelativeReverb = true;
listener.relativeReverbInnerThreshold = 0.5;
listener.relativeReverbOuterThreshold = 0.8;

world.addEmitter(listener);

In the example above, pan will blend from fully directional at 0.8f magnitude, to fully diffuse at 0.5f magnitude. This means you don't need to be fully enclosed in a room to hear the reverb all around you.

Reverb Energy Cap

It's difficult to compute the maximum amount of reverb energy that can return to a player's ears ahead-of-time, since every environment is different. Previously the volume of reverb was controlled by the energy in all reverb rays that return to the emitter, and dividing it by emitter.ReverbRayCount * emitter.ReverbBounceCount.

However not all energy should have to return to the emitter in order for reverb to be at max volume. Especially with higher ReverbBounceCount values, rays will naturally travel further away from the emitter. But this means less energy can return to the emitter, and therefore the volume of reverb drops.

This is inaccurate - rather than comparing the returning energy against the total possible returning energy - we should compare it against a user-defined threshold. This gives you control over how much energy needs to return to the emitter for reverb to be at max volume.

In the code below, only 20% of energy is required for reverb to be at max volume. If reverb is too loud, you can increase this energy cap.

var listener = new Emitter()
{
    ReverbRayCount = 128,
    ReverbBounceCount = 64,
    ReverbEnergyCap = 128 * 64 * 0.2f, // 20%
}
const emitter = va.Emitter_Create();
emitter.reverbRayCount = 128;
emitter.reverbBounceCount = 64;
emitter.reverbEnergyCap = 128 * 64 * 0.2; // 20%
VAEmitter* emitter = vaEmitterCreate();
vaEmitterSetReverbRayCount(emitter, 128);
vaEmitterSetReverbBounceCount(emitter, 64);
vaEmitterSetReverbEnergyCap(emitter, 128 * 64 * 0.2f); // 20%