[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Normalize EOL for all files that Git considers text files.\n* text=auto eol=lf\n\n# Only include the adodns folder when downloading from the Asset Library\n/**        export-ignore\n/addons    !export-ignore\n/addons/** !export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: tienne_k\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".gitignore",
    "content": "test_scene/\n*.uid\n\n# Godot 4+ specific ignores\n.godot/\n/android/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Tienne_k\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# raytraced-audio\nAdds procedural audio effects to Godot like echo, ambient outdoor sounds, and muffle.\n\n[Showcase Video](https://youtu.be/rFauZQ-tFTg?si=eUklNxLSs8ew2Zp6)\n\nCheck out [this amazing video](https://youtu.be/u6EuAUjq92k?si=6W-sGozYBQITEgQo) by **Vercidium** for insights on how this works!\n\nNo need to create and maintain zones while creating levels, just put the `RaytracedAudioListener` node in your scene and most features will be available for you to use!\n\nAdditionally, by using `RaytracedAudioPlayer3D`s instead of regular `AudioPlayer3D`s, sounds will get automatically muffled behind walls.\n\n### Audio buses\nRight off the bat, this plugin creates 2 new audio buses for you to use across your project:\na *\"Reverb\"* bus, and an *\"Ambient\"* bus.\nNote: both buses' names can be changed under `Project Settings > Raytraced Audio`.\n\nThe reverb bus controls echo / reverb.\nFor example, there will be a much bigger reverb in large enclosed rooms compared to small ones, or outside in the open.\n\nThe ambient bus controls the strength and pan of sounds coming from outside.\nFor example, in a room with a single opening leading outisde, sounds in this bus will appear to come from that opening, and will fade based on the player's distance to it.\n\n### Performace\n\nBecause the rays need to gather different informations about the environment, the actual number of processed rays can go up to:\n\n`rays_count * (2 + n)` where `n` is the number of enabled `RaytracedAudioPlayer3D`s in the scene.\n\nThat is:\n\n`(rays_count: configurable) * (1 ray that bounces around + 1 echo ray + (1 muffle ray per enabled RaytracedAudioPlayer3D))`\n\nRaytraced Audio also adds 2 performance monitors:\n - `raytraced_audio/raycast_updates`: How many raycast updates happened in the update tick\n - `raytraced_audio/enabled_players_count`: How many `RaytracedAudioPlayer3D`s are currently enabled in the scene\n\n\n### Installation\n\n#### Manual installation\n\n- Download or clone this repository\n- Copy the `addons/raytraced_audio` folder into your project's `addons/` folder\n- Enable the plugin in `Project Settings > Plugins > Raytraced Audio`\n\n"
  },
  {
    "path": "addons/raytraced_audio/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Tienne_k\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "addons/raytraced_audio/README.md",
    "content": "# raytraced-audio\nAdds procedural audio effects to Godot like echo, ambient outdoor sounds, and muffle.\n\nCheck out [this amazing video](https://youtu.be/u6EuAUjq92k?si=6W-sGozYBQITEgQo) by **Vercidium** for insights on how this works!\n\nNo need to create and maintain zones while creating levels, just put the `RaytracedAudioListener` node in your scene and most features will be available for you to use!\n\nAdditionally, by using `RaytracedAudioPlayer3D`s instead of regular `AudioPlayer3D`s, sounds will get automatically muffled behind walls.\n\n### Audio buses\nRight off the bat, this plugin creates 2 new audio buses for you to use across your project:\na *\"Reverb\"* bus, and an *\"Ambient\"* bus.\nNote: both buses' names can be changed under `Project Settings > Raytraced Audio`.\n\nThe reverb bus controls echo / reverb.\nFor example, there will be a much bigger reverb in large enclosed rooms compared to small ones, or outside in the open.\n\nThe ambient bus controls the strength and pan of sounds coming from outside.\nFor example, in a room with a single opening leading outisde, sounds in this bus will appear to come from that opening, and will fade based on the player's distance to it.\n\n### Performace\n\nBecause the rays need to gather different informations about the environment, the actual number of processed rays can go up to:\n\n`rays_count * (2 + n)` where `n` is the number of enabled `RaytracedAudioPlayer3D`s in the scene.\n\nThat is:\n\n`(rays_count: configurable) * (1 ray that bounces around + 1 echo ray + (1 muffle ray per enabled RaytracedAudioPlayer3D))`\n\nRaytraced Audio also adds 2 performance monitors:\n - `raytraced_audio/raycast_updates`: How many raycast updates happened in the update tick\n - `raytraced_audio/enabled_players_count`: How many `RaytracedAudioPlayer3D`s are currently enabled in the scene\n\n\n### Installation\n\n#### Manual installation\n\n- Download or clone this repository\n- Copy the `addons/raytraced_audio` folder into your project's `addons/` folder\n- Enable the plugin in `Project Settings > Plugins > Raytraced Audio`\n\n"
  },
  {
    "path": "addons/raytraced_audio/audio_ray.gd",
    "content": "extends RayCast3D\n\n# dont you worry about this class, habibi\n# shhhhhh its okay\n# ( -  ͜ʖ -)☞(ʘ_ʘ; )\n\nvar cast_dist: float = 0.0\nvar max_bounces: int = 1\nvar ray_scatter: Callable\n\nvar echo_dist: float = 0.0\nvar echo_count: int = 0\nvar bounces: int = 0\nvar has_bounced_this_tick: bool = false\nvar ray_casts_this_tick: int = 0\nvar escaped: bool = false\nvar escape_dir: Vector3 = Vector3.ZERO\n\nvar _space_state: PhysicsDirectSpaceState3D\n\n\nfunc _init(raycast_dist: float, max_bounce_count: int) -> void:\n\ttop_level = true\n\tenabled = false\n\tcast_dist = raycast_dist\n\tmax_bounces = max_bounce_count\n\n\nfunc _enter_tree() -> void:\n\towner = get_parent()\n\tassert(owner is RaytracedAudioListener)\n\n\nfunc _ready() -> void:\n\t_space_state = get_world_3d().direct_space_state\n\treset()\n\n\n# TODO: optimize\nfunc update():\n\t# Reset if needed\n\tif escaped or bounces > max_bounces:\n\t\treset()\n\n\tforce_raycast_update()\n\tray_casts_this_tick += 1\n\tbounces += 1\n\n\t# Escaped outside\n\tif !is_colliding():\n\t\tescaped = true\n\t\tglobal_position += target_position\n\t\t# Muffle ray: Check for line of sight with audio players\n\t\tfor player: RaytracedAudioPlayer3D in get_tree().get_nodes_in_group(RaytracedAudioPlayer3D.ENABLED_GROUP_NAME):\n\t\t\tvar has_line_of_sight: bool = _cast_ray(player.global_position).is_empty()\n\t\t\tplayer._lowpass_rays_count += int(has_line_of_sight)\n\t\treturn\n\n\t# Bounce\n\tvar hit_pos: Vector3 = get_collision_point()\n\tvar normal: Vector3 = get_collision_normal()\n\tglobal_position = hit_pos + normal * 0.1\n\ttarget_position = target_position.bounce(normal)\n\thas_bounced_this_tick = true\n\n\t# Muffle ray: Check for line of sight with audio players\n\tfor player: RaytracedAudioPlayer3D in get_tree().get_nodes_in_group(RaytracedAudioPlayer3D.ENABLED_GROUP_NAME):\n\t\tvar has_line_of_sight: bool = _cast_ray(player.global_position).is_empty()\n\t\tplayer._lowpass_rays_count += int(has_line_of_sight)\n\t\t# Same as:\n\t\t# if has_line_of_sight:\n\t\t# \tplayer.lowpass_rays_count += 1\n\n\t# Echo ray: Check for line of light with listener\n\t# Optimization: no need to raycast for first echo bounce\n\tif bounces == 1:\n\t\techo_dist = hit_pos.distance_to(owner.global_position)\n\t\techo_count += 1\n\t\tescape_dir = target_position.normalized()\n\telif _cast_ray(owner.global_position).is_empty():\n\t\t# The way is clear -> echo\n\t\techo_dist = hit_pos.distance_to(owner.global_position)\n\t\techo_count += 1\n\t\tescape_dir = owner.global_position.direction_to(hit_pos)\n\n\n# Return to the listener with a random direction\nfunc reset():\n\tvar dir: Vector3 = ray_scatter.call()\n\t\n\tglobal_position = owner.global_position\n\ttarget_position = dir * cast_dist\n\n\tbounces = 0\n\tescaped = false\n\tescape_dir = dir\n\treset_tick_stats()\n\n\n# Called after this ray is done ticking, in RaytracedAudioListener\nfunc reset_tick_stats() -> void:\n\thas_bounced_this_tick = false\n\tray_casts_this_tick = 0\n\techo_dist = 0.0\n\techo_count = 0\n\n\nfunc set_scatter_model(model: RaytracedAudioListener.RayScatterModel) -> void:\n\tmatch model:\n\t\tRaytracedAudioListener.RayScatterModel.RANDOM:\n\t\t\tray_scatter = _random_dir\n\t\tRaytracedAudioListener.RayScatterModel.XZ:\n\t\t\tray_scatter = _random_dir_xz_plane\n\t\t_:\n\t\t\tpush_error(\"Unknown ray scatter model: '\", model, \"'\")\n\t\t\tray_scatter = _random_dir\n\n\n\nfunc _cast_ray(to: Vector3) -> Dictionary:\n\tvar params: PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(\n\t\tglobal_position,\n\t\tto,\n\t\t0b01\n\t)\n\tray_casts_this_tick += 1\n\treturn _space_state.intersect_ray(params)\n\n\nfunc _random_dir_xz_plane() -> Vector3:\n\tvar  yaw = randf_range(0.0, TAU)\n\treturn Vector3(-sin(yaw), 0.0, -cos(yaw))\n\nfunc _random_dir() -> Vector3:\n\tvar theta: float = randf_range(0.0, TAU)\n\tvar y: float = randf_range(-1.0, 1.0)\n\tvar k: float = sqrt(1.0 - y*y)\n\treturn Vector3(k * cos(theta), k * sin(theta), y)\n"
  },
  {
    "path": "addons/raytraced_audio/plugin.cfg",
    "content": "[plugin]\n\nname=\"Raytraced Audio\"\ndescription=\"Adds procedural audio effects to Godot like echo, ambient outdoor sounds, and muffle\"\nauthor=\"Tienne_k\"\nversion=\"1.0\"\nscript=\"plugin.gd\"\n"
  },
  {
    "path": "addons/raytraced_audio/plugin.gd",
    "content": "@tool\nextends EditorPlugin\n\n\nfunc _enter_tree() -> void:\n\t_setup_settings()\n\n\t# For some reason, adding custom types like this fails to generate documentation sometimes??\n\t# add_custom_type(\n\t# \t\"RaytracedAudioListener\",\n\t# \t\"AudioListener3D\",\n\t# \tload(\"res://addons/raytraced_audio/raytraced_audio_listener.gd\"),\n\t# \tnull\n\t# )\n\t# add_custom_type(\n\t# \t\"RaytracedAudioPlayer3D\",\n\t# \t\"AudioStreamPlayer3D\",\n\t# \tload(\"res://addons/raytraced_audio/raytraced_audio_player_3d.gd\"),\n\t# \tnull\n\t# )\n\n\t_setup_audio_buses()\n\n\nfunc _exit_tree() -> void:\n\t# remove_custom_type(\"RaytracedAudioListener\")\n\t# remove_custom_type(\"RaytracedAudioPlayer3D\")\n\n\t_clean_up_settings()\n\t_clean_up_audio_buses()\n\n\nfunc _setup_settings() -> void:\n\tProjectSettings.set_setting(\"raytraced_audio/reverb_bus\", &\"RaytracedReverb\")\n\tProjectSettings.set_setting(\"raytraced_audio/ambient_bus\", &\"RaytracedAmbient\")\n\n\tif ProjectSettings.get_setting(\"audio/general/3d_panning_strength\") < 1.0:\n\t\tprint(\"[INFO] RaytracedAudio: I recommend setting Audio/General/3d_panning_strength in Project Settings to 1.0 or above\")\n\n\nfunc _clean_up_settings() -> void:\n\tProjectSettings.set_setting(\"raytraced_audio/reverb_bus\", null)\n\tProjectSettings.set_setting(\"raytraced_audio/ambient_bus\", null)\n\n\nfunc _setup_audio_buses() -> void:\n\tprint(\"[INFO] Raytraced Audio: setting up audio buses\")\n\t_clean_up_audio_buses()\n\n\t# Reverb\n\tvar i: int = AudioServer.bus_count\n\tAudioServer.add_bus()\n\tAudioServer.set_bus_name(i, ProjectSettings.get_setting(\"raytraced_audio/reverb_bus\", &\"RaytracedReverb\"))\n\tAudioServer.set_bus_send(i, &\"Master\")\n\tvar reverb: AudioEffectReverb = AudioEffectReverb.new()\n\treverb.hipass = 1.0\n\treverb.resource_name = \"reverb\"\n\tAudioServer.add_bus_effect(i, reverb)\n\n\t# Ambient\n\ti = AudioServer.bus_count\n\tAudioServer.add_bus()\n\tAudioServer.set_bus_name(i, ProjectSettings.get_setting(\"raytraced_audio/ambient_bus\", &\"RaytracedAmbient\"))\n\tAudioServer.set_bus_send(i, &\"Master\")\n\tvar panner: AudioEffectPanner = AudioEffectPanner.new()\n\tpanner.resource_name = \"pan\"\n\tAudioServer.add_bus_effect(i, panner)\n\n\t# There's a bug in Godot that makes it so the ambient bus doesnt show properly in the editor\n\t# This is \"fixable\" by adding a temporary bus afterwards and deleting it immediately because ofc.\n\t# fuck you (respectfully)\n\ti = AudioServer.bus_count\n\tAudioServer.add_bus()\n\tAudioServer.remove_bus(i)\n\n\nfunc _clean_up_audio_buses() -> void:\n\tvar i: int = AudioServer.get_bus_index(ProjectSettings.get_setting(\"raytraced_audio/reverb_bus\", &\"RaytracedReverb\"))\n\tif i != -1:\n\t\tAudioServer.remove_bus(i)\n\ti = AudioServer.get_bus_index(ProjectSettings.get_setting(\"raytraced_audio/ambient_bus\", &\"RaytracedAmbient\"))\n\tif i != -1:\n\t\tAudioServer.remove_bus(i)\n"
  },
  {
    "path": "addons/raytraced_audio/raytraced_audio_listener.gd",
    "content": "class_name RaytracedAudioListener extends AudioListener3D\n## 3D audio listener for raytraced audio\n##\n## [b]Audio buses[/b]\n## [br]This plugin creates 2 new audio buses for you to use across your project:\n## a [i]\"Reverb\"[/i] bus, and an [i]\"Ambient\"[/i] bus.\n## [br]Note: both buses' names can be changed under [code]Project Settings > Raytraced Audio[/code].\n## [br]\n## [br]The reverb bus controls echo / reverb.\n## [br]For example, there will be a much bigger reverb in large enclosed rooms compared to small ones, or outside in the open\n## [br]\n## [br]The ambient bus controls the strength and pan of sounds coming from outside.\n## [br]For example, in a room with a single opening leading outisde, sounds in this bus will appear to come from that opening, and will fade based on the player's distance to it\n## [br]\n## [br][b]Performace[/b]\n## [br]Raytraced Audio adds 2 performance monitors:\n## [br] - [code]raytraced_audio/raycast_updates[/code]: How many raycast updates happened in one update tick\n## [br] - [code]raytraced_audio/enabled_players_count[/code]: How many [RaytracedAudioPlayer3D]s are enabled in the scene\n## [br]\n## [br]Note: There should be only one [RaytracedAudioListener] in a given scene, just like how there should be only one AudioListener3D in a scene.\n## [br]See also [RaytracedAudioPlayer3D]\n\n## Speed of sound in m/s\nconst SPEED_OF_SOUND: float = 343.0\n## All [RaytracedAudioListener]s will be in this group\nconst GROUP_NAME: StringName = &\"raytraced_audio_listener\"\n\nconst AudioRay: Script = preload(\"res://addons/raytraced_audio/audio_ray.gd\")\n\nenum RayScatterModel {\n\t## Rays will be shot out in a random 3d direction\n\tRANDOM,\n\t## Rays will be shot out on the listener's XZ plane (i.e. [code]Vector3(random, 0, random)[/code])\n\tXZ,\n}\n\n## Emitted when this node is enabled\nsignal enabled\n## Emitted when this node is disabled\nsignal disabled\n## Emitted when any configuration for the rays are changed\n## [br]This includes: [member rays_count], [member max_raycast_dist], [member max_bounces], and [member ray_scatter_model]\nsignal ray_configuration_changed\n\n## List of rays instanced by this node\nvar rays: Array[AudioRay] = []\n\n## Enable or disable raycasting\n## [br]Disabled nodes can't be updated (see [method update]) even if [member auto_update] is set to [code]true[/code]\n@export var is_enabled: bool = true:\n\tset(v):\n\t\tif is_enabled == v:\n\t\t\treturn\n\t\tis_enabled = v\n\n\t\tif is_enabled:\n\t\t\tif is_node_ready():\n\t\t\t\tsetup()\n\t\t\tenabled.emit()\n\t\telse:\n\t\t\tdisabled.emit()\n\t\t\tif is_node_ready():\n\t\t\t\tclear()\n\n## Whether to update automatically\n## [br]If set to [code]true[/code], this [RaytracedAudioListener] will update every [i]process[/i] frame\n@export var auto_update: bool = true:\n\tset(v):\n\t\tauto_update = v\n\t\tset_process(auto_update)\n\n## Number of rays to use\n## [br]More rays and more bounces mean a more accurate model of the environment can be made\n## [br] See also [member max_bounces]\n## [br]\n## [br][i]Technical note[/i]:\n## [br] Because the rays need to gather different informations about the environment,\n##  the actual number of processed rays can go up to:\n## [br] [code]rays_count * (2 + n)[/code] where [code]n[/code] is the number of enabled [RaytracedAudioPlayer3D]s in the scene\n## [br] ([code]rays_count * (1 ray that bounces around + 1 echo ray + 1 muffle ray per enabled RaytracedAudioPlayer3D[/code])\n@export var rays_count: int = 4:\n\tset(v):\n\t\tif v == rays_count:\n\t\t\treturn\n\t\trays_count = maxi(v, 1)\n\t\tclear()\n\t\tsetup()\n\t\tray_configuration_changed.emit()\n\n## The maximum distance which any given ray instanced by this node will cast\n@export var max_raycast_dist: float = SPEED_OF_SOUND:\n\tset(v):\n\t\tmax_raycast_dist = v\n\t\t_update_ray_configuration()\n\t\tray_configuration_changed.emit()\n## [br]More rays and more bounces mean a more accurate model of the environment can be made\n## [br] See also [member rays_count]\n@export var max_bounces: int = 3:\n\tset(v):\n\t\tmax_bounces = v\n\t\t_update_ray_configuration()\n\t\tray_configuration_changed.emit()\n\n## Controls how rays will be instanced\n@export var ray_scatter_model: RayScatterModel = RayScatterModel.RANDOM:\n\tset(v):\n\t\tray_scatter_model = v\n\t\t_update_ray_configuration()\n\t\tray_configuration_changed.emit()\n\n@export_category(\"Muffle\")\n## Enables [RaytracedAudioPlayer3D]s muffling behind walls\n@export var muffle_enabled: bool = true\n## The interpolation strength of the muffle\n## [br]See [member muffle_enabled]\n@export_range(1.0, 25.0, 0.1) var muffle_interpolation: float = 5.0\n@export_category(\"Echo\")\n## Enables updates to the reverb audio bus\n## [br]See [code]Project Settings > Raytraced Audio > Reverb Bus[/code]\n@export var echo_enabled: bool = true\n## The \"intensity\" of the reverb\n## [br]More specifically, multiplies the reverb's room size by this amount\n## [br]The default is [code]2.0[/code] to account for sound waves coming back to the listener after hitting a wall\n@export var echo_room_size_multiplier: float = 2.0\n## The interpolation strength of the echo\n## [br]See [member echo_enabled]\n@export_range(1.0, 25.0, 0.1) var echo_interpolation: float = 5.0\n@export_category(\"Ambient\")\n## Enables updates to the ambient audio bus\n## [br]See [code]Project Settings > Raytraced Audio > Ambient Bus[/code]\n@export var ambient_enabled: bool = true\n## The interpolation strength of ambient sounds' direction (pan)\n## [br]See [member ambient_enabled], [member ambient_pan_strength]\n@export_range(1.0, 25.0, 0.1) var ambient_pan_interpolation: float = 5.0\n## How strong the pan between right and left ear will be\n## Setting this to 0 disables panning completely\n## [br]See [member ambient_enabled], [member ambient_pan_interpolation]\n@export_range(0.0, 1.0, 0.01) var ambient_pan_strength: float = 1.0\n## The interpolation strength of ambient sounds' volume\n## [br]See [member ambient_enabled]\n@export_range(1.0, 25.0, 0.1) var ambient_volume_interpolation: float = 5.0\n## How smoothly the ambient sounds will fade away when the [AudioListener3D] is no longer \"outside\"\n## Values close to 1 will make sounds linger for longer, while values close to 0 will make them drop suddenly\n## [br]See [member ambient_enabled]\n@export_range(0.0, 1.0, 0.001) var ambient_volume_attenuation: float = 0.998\n\n## The size of the room based on data gathered from rays, in world units\n## [br]Used in reverb calculation\nvar room_size: float = 0.0\n## How \"outside\" this [RaytracedAudioListener] is based on data gathered from rays, between 0 and 1\nvar ambience: float = 0.0\n## The direction to \"outside\" based on data gathered from rays\n## [br]Should average out to 0 when this [RaytracedAudioListener] is completely outside\nvar ambient_dir: Vector3 = Vector3.ZERO\n\n## For debugging purposes\n## [br]See the [code]raytraced_audio/raycast_updates[/code] performance monitor\nvar ray_casts_this_tick: int = 0\n\nvar _reverb_effect: AudioEffectReverb\nvar _pan_effect: AudioEffectPanner\n\n# Keep track of whether we've set up debug monitors\nvar _debug_monitors_setup: bool = false\n\n\nfunc _enter_tree() -> void:\n\tadd_to_group(GROUP_NAME)\n\n\nfunc _ready() -> void:\n\t# Reverb effect\n\tvar i: int = AudioServer.get_bus_index(ProjectSettings.get_setting(\"raytraced_audio/reverb_bus\"))\n\tif i == -1:\n\t\tpush_error(\"Failed to get reverb bus for raytraced audio. Disabling echo...\")\n\t\techo_enabled = false\n\telse:\n\t\t_reverb_effect = AudioServer.get_bus_effect(i, 0)\n\n\t# Pan effect\n\ti = AudioServer.get_bus_index(ProjectSettings.get_setting(\"raytraced_audio/ambient_bus\"))\n\tif i == -1:\n\t\tpush_error(\"Failed to get ambient bus for raytraced audio. Disabling ambience...\")\n\t\tambient_enabled = false\n\telse:\n\t\t_pan_effect = AudioServer.get_bus_effect(i, 0)\n\n\tif is_enabled:\n\t\tsetup()\n\n\tset_process(auto_update)\n\tif is_enabled:\n\t\tmake_current()\n\n\t_setup_debug()\n\n\nfunc _exit_tree() -> void:\n\t_cleanup_debug()\n\n\nfunc _setup_debug() -> void:\n\tif _debug_monitors_setup:\n\t\treturn\n\t\t\n\t_debug_monitors_setup = true\n\t\n\tPerformance.add_custom_monitor(&\"raytraced_audio/raycast_updates\", func():\n\t\t# Check if this instance is still valid\n\t\tif not is_instance_valid(self):\n\t\t\treturn 0\n\t\treturn ray_casts_this_tick\n\t)\n\n\tPerformance.add_custom_monitor(&\"raytraced_audio/enabled_players_count\", func():\n\t\t# Check if this instance is still valid and if we're in a scene tree\n\t\tif not is_instance_valid(self) or not is_inside_tree():\n\t\t\treturn 0\n\t\treturn get_tree().get_node_count_in_group(RaytracedAudioPlayer3D.ENABLED_GROUP_NAME)\n\t)\n\n\nfunc _cleanup_debug() -> void:\n\tif not _debug_monitors_setup:\n\t\treturn\n\t\t\n\t_debug_monitors_setup = false\n\t\n\t# Remove the custom monitors\n\tPerformance.remove_custom_monitor(&\"raytraced_audio/raycast_updates\")\n\tPerformance.remove_custom_monitor(&\"raytraced_audio/enabled_players_count\")\n\t\n\n\n## Initiates this [RaytracedAudioListener]'s rays\nfunc setup() -> void:\n\tfor __ in rays_count:\n\t\tvar rc: AudioRay = AudioRay.new(max_raycast_dist, max_bounces)\n\t\trc.set_scatter_model(ray_scatter_model)\n\t\tadd_child(rc, INTERNAL_MODE_BACK)\n\t\trays.push_back(rc)\n\n\n## Clears all created rays\nfunc clear():\n\tfor ray: AudioRay in rays:\n\t\tremove_child(ray)\n\t\tray.queue_free()\n\trays.clear()\n\n\nfunc _process(delta: float) -> void:\n\tif !auto_update:\n\t\tset_process(false)\n\t\treturn\n\tupdate(delta)\n\n\n## Updates this [RaytracedAudioListener]\n## [br]If you are updating this node manually (i.e. [member auto_update] is [code]false[/code]), this is the method to call\nfunc update(delta: float):\n\tray_casts_this_tick = 0\n\tif !is_enabled:\n\t\treturn\n\n\tvar echo: float = 0.0 # Avg echo from all rays\n\tvar echo_count: int = 0 # Number of echo rays that came back\n\tvar bounces_this_tick: int = 0\n\tvar escaped_count: int = 0\n\tvar escaped_dir: Vector3 = Vector3.ZERO # Avg escape direction\n\tvar escaped_strength: float = 0.0\n\n\t# Gather data\n\tfor ray: AudioRay in rays:\n\t\tray.update()\n\t\tray_casts_this_tick += ray.ray_casts_this_tick\n\n\t\techo += ray.echo_dist\n\t\techo_count += ray.echo_count\n\t\tbounces_this_tick += int(ray.has_bounced_this_tick)\n\n\t\tif ray.escaped:\n\t\t\tescaped_count += 1\n\t\t\tescaped_strength += 1.0 / ray.bounces\n\t\t\tescaped_dir += ray.escape_dir\n\n\t\tray.reset_tick_stats()\n\t\n\techo = 0.0 if echo_count == 0 else (echo / float(echo_count))\n\tescaped_dir = Vector3.ZERO if escaped_count == 0 else (escaped_dir / float(escaped_count))\n\n\tif muffle_enabled:\n\t\t_update_muffle(delta)\n\tif echo_enabled:\n\t\t_update_echo(echo, echo_count, bounces_this_tick, delta)\n\tif ambient_enabled:\n\t\t_update_ambient(escaped_strength, escaped_dir, delta)\n\n\nfunc _update_muffle(delta: float) -> void:\n\tfor player: RaytracedAudioPlayer3D in get_tree().get_nodes_in_group(RaytracedAudioPlayer3D.GROUP_NAME):\n\t\tplayer.update(self, delta)\n\n\nfunc _update_echo(echo: float, echo_count: int, bounces: int, delta: float) -> void:\n\t# Length -> echo delay\n\troom_size = lerpf(room_size, echo, 1.0 - exp(-delta * echo_interpolation))\n\tvar e: float = (room_size * echo_room_size_multiplier) / SPEED_OF_SOUND\n\t# print(\"e = \", e)\n\t_reverb_effect.room_size = lerpf(_reverb_effect.room_size, clampf(e, 0.0, 1.0), 1.0 - exp(-delta * echo_interpolation))\n\t_reverb_effect.predelay_msec = lerpf(_reverb_effect.predelay_msec, e * 1000, 1.0 - exp(-delta * echo_interpolation))\n\t_reverb_effect.predelay_feedback = lerpf(_reverb_effect.predelay_feedback, clampf(e, 0.0, 0.98), 1.0 - exp(-delta * echo_interpolation))\n\t# More rays % -> echo strength\n\tvar return_ratio: float = 0.0 if bounces == 0 else float(echo_count) / float(bounces)\n\t_reverb_effect.hipass = lerpf(_reverb_effect.hipass, 1.0 - return_ratio, 1.0 - exp(-delta * echo_interpolation))\n\n\nfunc _update_ambient(escaped_strength: float, escaped_dir: Vector3, delta: float) -> void:\n\tvar ambience_ratio: float = float(escaped_strength) / float(rays_count)\n\n\t# More rays % -> louder\n\tif escaped_strength > 0:\n\t\t#should it use delta? \n\t\t#ambience = lerpf(ambience, 1.0, 1.0 - exp(-delta * ambience_ratio))\n\t\tambience = lerpf(ambience, 1.0, ambience_ratio)\n\telse:\n\t\tambience *= ambient_volume_attenuation\n\tvar ambient_bus_idx: int = AudioServer.get_bus_index(ProjectSettings.get_setting(\"raytraced_audio/ambient_bus\"))\n\tvar volume: float = AudioServer.get_bus_volume_linear(ambient_bus_idx)\n\tAudioServer.set_bus_volume_linear(ambient_bus_idx, lerpf(volume, ambience, 1.0 - exp(-delta * ambient_volume_interpolation)))\n\t\n\t# Avg escape direction -> pan\n\tambient_dir = ambient_dir.slerp(escaped_dir, 1.0 - exp(-delta * ambient_pan_interpolation))\n\tvar target_pan: float = 0.0 if ambient_dir.is_zero_approx() else owner.transform.basis.x.dot(ambient_dir.normalized())\n\t_pan_effect.pan = target_pan * ambient_pan_strength\n\n\n# ✨ the name says it all ✨\nfunc _update_ray_configuration() -> void:\n\tfor ray: AudioRay in rays:\n\t\tray.cast_dist = max_raycast_dist\n\t\tray.max_bounces = max_bounces\n\t\tray.set_scatter_model(ray_scatter_model)\n"
  },
  {
    "path": "addons/raytraced_audio/raytraced_audio_player_3d.gd",
    "content": "class_name RaytracedAudioPlayer3D extends AudioStreamPlayer3D\n## Audio stream player that allows for audio muffling\n## \n## You can already use the reverb and ambient audio buses (see the documentation for [RaytracedAudioListener] for more details)\n## to make use of raytraced audio effects, but using [RaytracedAudioPlayer3D]s allows you to use more capabilities.\n## Namely, muffling sounds behind walls.\n## [br]\n## [br][b][color=yellow]Warning[/color][/b]: [RaytracedAudioPlayer3D]s will default to using the reverb audio bus\n## [br]If you wish to use another bus instead, please set it after this node has been added to the scene tree (i.e. after [code]_enter_tree()[/code])\n## [br]\n## [br][i]Technical note[/i]:\n## [br]Currently, there is no way to muffle only one audio player (or apply any effect for that matter).\n## [br]So we create a new bus for every audio player that is audible from the [RaytracedAudioListener]\n## [br]Godot doesn't provide methods to calculate the volume of an audio at certain distances, so we calculate\n## that ourselves (see [method calculate_audible_distance_threshold()])\n\n## All [RaytracedAudioPlayer3D]s (regardless of state) will be in this group\nconst GROUP_NAME: StringName = &\"raytraced_audio_player_3d\"\n## All [b]enabled[/b] [RaytracedAudioPlayer3D]s will be in this group\nconst ENABLED_GROUP_NAME: StringName = &\"enabled_raytraced_audio_player_3d\"\n## The lowpass frequency when completely muffled\nconst LOWPASS_MIN_HZ: float = 250.0\n## The lowpass frequency when completely not-muffled (?)\nconst LOWPASS_MAX_HZ: float = 20000.0\n## The factor the nyquist frequency is multiplied with to get a safe max_cutoff\nconst NYQUIST_MULTIPLIER: float = 0.95\n\n## Used in internal calculations\nconst LOG2: float = log(2.0)\n## Used in internal calculations\nconst LOG_MIN_HZ: float = log(LOWPASS_MIN_HZ) / LOG2\n## Used in internal calculations\nconst LOG_MAX_HZ: float = log(LOWPASS_MAX_HZ) / LOG2\n\n## Emitted when this node is enabled\n## [br]See also [constant ENABLED_GROUP_NAME]\nsignal enabled\n## Emitted when this node is disabled\nsignal disabled\n## Emitted when the maximum audible distance for this node is changed\n## [br]See also [member AudioStreamPlayer3D.max_distance] and [member audibility_threshold_db]\nsignal audible_distance_updated(distance: float)\n\n## The threshold (in decibels) at which sounds will be considered inaudible\n## [br]This is used to enable / disable this node when it's not audible to save resources\n## [br]The distance at which this node is no longer considered audible is automatically stored inside [member AudioStreamPlayer3D.max_distance]\n## (if not already set), also to save resources\n## [br]Though, setting [member AudioStreamPlayer3D.max_distance] doesn't update this field automatically\n## [br]See also [method get_volume_db_from_pos] and [method calculate_audible_distance_threshold]\n@export var audibility_threshold_db: float = -30.0:\n\tset(v):\n\t\taudibility_threshold_db = v\n\t\t# Max distance not configured\n\t\tif is_node_ready() and max_distance == 0.0:\n\t\t\tmax_distance = calculate_audible_distance_threshold()\n\t\t\taudible_distance_updated.emit(max_distance)\n\nvar _lowpass_rays_count: int = 0\nvar _lowpass_max_safe_cutoff: float = LOWPASS_MAX_HZ\nvar _is_enabled: bool = false\n\n\nfunc _enter_tree() -> void:\n\tadd_to_group(GROUP_NAME)\n\tbus = ProjectSettings.get_setting(\"raytraced_audio/reverb_bus\") # Fallback\n\nfunc _ready() -> void:\n\t# Max distance not configured\n\tif max_distance == 0.0:\n\t\tmax_distance = calculate_audible_distance_threshold()\n\t\taudible_distance_updated.emit(max_distance)\n\t\n\t# Update the lowpass limits on start\n\t_lowpass_max_safe_cutoff = calculate_lowpass_limit()\n\n\n## Enables this node\n## [br]Note: you should almost never have to worry about enabling / disabling [RaytracedAudioPlayer3D]s manually\n## [br]See [method update]\nfunc enable():\n\tif _is_enabled:\n\t\treturn\n\t_is_enabled = true\n\tvar i: int = _create_bus()\n\tbus = AudioServer.get_bus_name(i)\n\tadd_to_group(ENABLED_GROUP_NAME)\n\n\tenabled.emit()\n\n## Enables this node\n## [br]Note: you should almost never have to worry about enabling / disabling [RaytracedAudioPlayer3D]s manually\n## [br]See [method update]\nfunc disable():\n\tif !_is_enabled:\n\t\treturn\n\n\t# Don't remove the fallback bus lol\n\tif bus == ProjectSettings.get_setting(\"raytraced_audio/reverb_bus\"):\n\t\t_disable()\n\t\treturn\n\t\n\t# Remove this node's specific bus\n\tvar idx: int = AudioServer.get_bus_index(bus)\n\tif idx == -1:\n\t\tpush_warning(\"audio bus \", bus, \" not found\")\n\t\t_disable()\n\t\treturn\n\t_disable()\n\tAudioServer.remove_bus(idx)\n\nfunc _disable():\n\t_is_enabled = false\n\tbus = ProjectSettings.get_setting(\"raytraced_audio/reverb_bus\") # Fallback\n\tremove_from_group(ENABLED_GROUP_NAME)\n\t_lowpass_rays_count = 0\n\n\tdisabled.emit()\n\n\n# Returns the index of the created bus\nfunc _create_bus() -> int:\n\tvar i: int = AudioServer.bus_count\n\tAudioServer.add_bus()\n\tAudioServer.set_bus_name(i, generate_bus_name())\n\tAudioServer.set_bus_send(i, ProjectSettings.get_setting(\"raytraced_audio/reverb_bus\"))\n\tAudioServer.add_bus_effect(i, AudioEffectLowPassFilter.new())\n\treturn i\n\n## Returns a name for the audio bus created for this node\nfunc generate_bus_name() -> String:\n\treturn str(\"RTAudioPlayer3D_\", name, randi())\n\n## ✨ the name says it all ✨\nfunc is_enabled() -> bool:\n\treturn _is_enabled\n\n## Updates this [RaytracedAudioPlayer3D]\n## [br]This method will adjust the muffle of the played stream, as well as enable or disable it\n## based on whether it's audible from the given [RaytracedAudioListener]\n## [br]If you are updating this node manually, this is the method to call\nfunc update(listener: RaytracedAudioListener, delta: float) -> void:\n\tif _is_enabled:\n\t\t_update(listener.rays_count, listener.muffle_interpolation, delta)\n\n\t_lowpass_rays_count = 0\n\t\n\t# Enable based on position\n\tvar dist_sq: float = global_position.distance_squared_to(listener.global_position)\n\tif dist_sq > max_distance*max_distance or !playing:\n\t\tdisable()\n\telse:\n\t\tenable()\n\n\nfunc _update(rays_count: int, interpolation: float, delta: float):\n\tif bus == ProjectSettings.get_setting(\"raytraced_audio/reverb_bus\"):\n\t\t_disable()\n\t\treturn\n\n\tvar idx: int = AudioServer.get_bus_index(bus)\n\tif idx == -1:\n\t\tpush_error(\"audio bus \", bus, \" not found\")\n\t\t_disable()\n\telse:\n\t\tvar ratio: float = float(_lowpass_rays_count) / float(rays_count)\n\t\tratio = clamp(ratio, 0, 1)\n\t\tvar lowpass: AudioEffectLowPassFilter = AudioServer.get_bus_effect(idx, 0)\n\n\t\t# Frequencies aren't linear, they scale logarithmically (log2 space) +1 octave = 2x the frequency\n\t\t# So we scale frequencies down before lerping, then scale them back up\n\t\tvar log_t: float = lerpf(LOG_MIN_HZ, LOG_MAX_HZ, ratio)\n\t\tvar log_hz: float = log(lowpass.cutoff_hz) / LOG2 # Scale current frequency down:  log2(x) = ln(x) / ln(2)\n\n\t\tlog_hz = lerpf(log_hz, log_t, 1.0 - exp(-delta * interpolation)) # Lerp in scaled down space\n\t\tlowpass.cutoff_hz = min(pow(2, log_hz), _lowpass_max_safe_cutoff) # Limit at max cutoff\n\n\n# Translated from the godot repo\n## Calculates the volume (in decibels) of the audio stream from the given position\n## [br]Please note that the final volume will vary depending on the stream that is being played\nfunc get_volume_db_from_pos(from_pos: Vector3) -> float:\n\tconst CMP_EPSILON: float = 0.0001\n\n\tvar dist: float = from_pos.distance_to(global_position)\n\tvar vol: float = 0.0\n\tmatch attenuation_model:\n\t\tATTENUATION_INVERSE_DISTANCE:\n\t\t\tvol = linear_to_db(1.0 / ((dist / unit_size) + CMP_EPSILON))\n\t\tATTENUATION_INVERSE_SQUARE_DISTANCE:\n\t\t\tvar d: float = (dist / unit_size)\n\t\t\tvol = linear_to_db(1.0 / (d*d + CMP_EPSILON))\n\t\tATTENUATION_LOGARITHMIC:\n\t\t\tvol = -20.0 * log(dist / unit_size + CMP_EPSILON)\n\t\tATTENUATION_DISABLED:\n\t\t\tpass\n\t\t_:\n\t\t\tpush_error(\"Unknown attenuation type: '\", attenuation_model, \"'\")\n\tvol = minf(vol + volume_db, max_db)\n\treturn vol\n\n\n## Calculates the distance at which this [RaytracedAudioPlayer3D] is no longer considered audible\n## [br]See also [member audibility_threshold_db]\nfunc calculate_audible_distance_threshold() -> float:\n\tif max_distance > 0.0:\n\t\treturn max_distance\n\t\n\tmatch attenuation_model:\n\t\tATTENUATION_INVERSE_DISTANCE:\n\t\t\tvar t_lin: float = db_to_linear(audibility_threshold_db - volume_db)\n\t\t\t# Unit_size / dist < T_lin\n\t\t\treturn unit_size / t_lin\n\t\tATTENUATION_INVERSE_SQUARE_DISTANCE:\n\t\t\tvar t_lin: float = db_to_linear(audibility_threshold_db - volume_db)\n\t\t\t# (Unit_size / dist)^2 < T_lin\n\t\t\treturn sqrt(unit_size*unit_size / t_lin)\n\t\tATTENUATION_LOGARITHMIC:\n\t\t\tvar t_db: float = audibility_threshold_db - volume_db\n\t\t\t# -20 * log(dist / Unit_size) > T_db\n\t\t\treturn exp(t_db / -20.0) * unit_size\n\t\tATTENUATION_DISABLED:\n\t\t\treturn 0.0\n\t\t_:\n\t\t\tpush_error(\"Unknown attenuation model: '\", attenuation_model, \"'\")\n\t\t\treturn 0.0\n\n\n## Gets the current sampling rate of the AudioServer and calculates \n## a safe cutoff_hz value for the lowpass filter.\nfunc calculate_lowpass_limit() -> float:\n\tvar nyquist_frequency = AudioServer.get_mix_rate() * 0.5\n\treturn nyquist_frequency * NYQUIST_MULTIPLIER\n\n\n## Checks wheter this [RaytracedAudioPlayer3D] is considered audible from the given position\n## [br]See also [member audibility_threshold_db]\nfunc is_audible(from_pos: Vector3) -> bool:\n\treturn get_volume_db_from_pos(from_pos) >= audibility_threshold_db"
  },
  {
    "path": "default_bus_layout.tres",
    "content": "[gd_resource type=\"AudioBusLayout\" format=3 uid=\"uid://dfuwe48pov8j3\"]\n\n[sub_resource type=\"AudioEffectReverb\" id=\"AudioEffectReverb_j3pel\"]\nresource_name = \"reverb\"\nhipass = 1.0\n\n[sub_resource type=\"AudioEffectPanner\" id=\"AudioEffectPanner_g28q7\"]\nresource_name = \"pan\"\n\n[resource]\nbus/1/name = &\"RaytracedReverb\"\nbus/1/solo = false\nbus/1/mute = false\nbus/1/bypass_fx = false\nbus/1/volume_db = 0.0\nbus/1/send = &\"Master\"\nbus/1/effect/0/effect = SubResource(\"AudioEffectReverb_j3pel\")\nbus/1/effect/0/enabled = true\nbus/2/name = &\"RaytracedAmbient\"\nbus/2/solo = false\nbus/2/mute = false\nbus/2/bypass_fx = false\nbus/2/volume_db = 0.0\nbus/2/send = &\"Master\"\nbus/2/effect/0/effect = SubResource(\"AudioEffectPanner_g28q7\")\nbus/2/effect/0/enabled = true\n"
  },
  {
    "path": "icon.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://cswkq85176f4o\"\npath=\"res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://icon.png\"\ndest_files=[\"res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\n"
  },
  {
    "path": "icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://dxvcb2moy11s4\"\npath=\"res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://icon.svg\"\ndest_files=[\"res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex\"]\n\n[params]\n\ncompress/mode=0\ncompress/high_quality=false\ncompress/lossy_quality=0.7\ncompress/hdr_compression=1\ncompress/normal_map=0\ncompress/channel_pack=0\nmipmaps/generate=false\nmipmaps/limit=-1\nroughness/mode=0\nroughness/src_normal=\"\"\nprocess/fix_alpha_border=true\nprocess/premult_alpha=false\nprocess/normal_map_invert_y=false\nprocess/hdr_as_srgb=false\nprocess/hdr_clamp_exposure=false\nprocess/size_limit=0\ndetect_3d/compress_to=1\nsvg/scale=1.0\neditor/scale_with_editor_scale=false\neditor/convert_colors_with_editor_theme=false\n"
  },
  {
    "path": "project.godot",
    "content": "; Engine configuration file.\n; It's best edited using the editor UI and not directly,\n; since the parameters that go here are not all obvious.\n;\n; Format:\n;   [section] ; section goes between []\n;   param=value ; assign values to parameters\n\nconfig_version=5\n\n[animation]\n\ncompatibility/default_parent_skeleton_in_mesh_instance_3d=true\n\n[application]\n\nconfig/name=\"Raytraced Audio\"\nrun/main_scene=\"uid://dgx3qrieojg3x\"\nconfig/features=PackedStringArray(\"4.6\", \"GL Compatibility\")\nconfig/icon=\"uid://cswkq85176f4o\"\n\n[audio]\n\ngeneral/3d_panning_strength=1.0\n\n[editor_plugins]\n\nenabled=PackedStringArray(\"res://addons/raytraced_audio/plugin.cfg\")\n\n[raytraced_audio]\n\nreverb_bus=&\"RaytracedReverb\"\nambient_bus=&\"RaytracedAmbient\"\n\n[rendering]\n\nrenderer/rendering_method=\"gl_compatibility\"\nrenderer/rendering_method.mobile=\"gl_compatibility\"\n"
  }
]