[
  {
    "path": ".gitattributes",
    "content": "# Normalize EOL for all files that Git considers text files.\n* text=auto eol=lf\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "patreon: hugemenace\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "on:\n  push:\n    branches:\n      - main\n\npermissions:\n  checks: write\n  contents: write\n  pull-requests: write\n\nname: Release\n\njobs:\n  release-please:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Create Tag & Changelog\n        uses: google-github-actions/release-please-action@v4\n        id: release\n      - uses: actions/checkout@main\n        if: ${{ steps.release.outputs.release_created }}\n      - name: Prepare Release Artifact\n        if: ${{ steps.release.outputs.release_created }}\n        run: |\n          mkdir -p resonate-${{ steps.release.outputs.tag_name }}/addons\n          cp -r addons/resonate resonate-${{ steps.release.outputs.tag_name }}/addons/\n          cp -r docs resonate-${{ steps.release.outputs.tag_name }}/addons/resonate/\n      - name: Create Release Artifact\n        if: ${{ steps.release.outputs.release_created }}\n        uses: thedoctor0/zip-release@0.7.6\n        with:\n          type: 'zip'\n          filename: 'resonate-${{ steps.release.outputs.tag_name }}.zip'\n          path: 'resonate-${{ steps.release.outputs.tag_name }}'\n      - name: Publish Release\n        if: ${{ steps.release.outputs.release_created }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: gh release upload ${{ steps.release.outputs.tag_name }} resonate-${{ steps.release.outputs.tag_name }}.zip\n"
  },
  {
    "path": ".gitignore",
    "content": "# Godot 4+ specific ignores\n.godot/\n"
  },
  {
    "path": ".release-please-manifest.json",
    "content": "{\n    \".\": \"2.4.0\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [2.4.0](https://github.com/hugemenace/resonate/compare/v2.3.4...v2.4.0) (2024-03-08)\n\n\n### Features\n\n* add an auto-loop option to the MusicManager's play method to allow non-looping tracks to repeat playback with an integrated crossfade ([f3885c7](https://github.com/hugemenace/resonate/commit/f3885c7263788ec097cf5369768823829dcfdd9d))\n* allow polyphonic players to be stopped and synchronise the is_playing state with the underlying polyphonic streams ([b91e062](https://github.com/hugemenace/resonate/commit/b91e06279a45a5b50aa1d84f7389cf9a06f1a718))\n\n## [2.3.4](https://github.com/hugemenace/resonate/compare/v2.3.3...v2.3.4) (2024-02-23)\n\n\n### Bug Fixes\n\n* fix sound and music manager parse errors on exported projects ([081e753](https://github.com/hugemenace/resonate/commit/081e75382c5a984790051986a5b8da48cb484759))\n\n## [2.3.3](https://github.com/hugemenace/resonate/compare/v2.3.2...v2.3.3) (2024-02-17)\n\n\n### Bug Fixes\n\n* ensure that the Sound and Music managers ref-count banks to avoid unexpected removal for multi-users ([2fb7223](https://github.com/hugemenace/resonate/commit/2fb72237d8cc217721a8d1fa92d4be5b9202515d))\n* fix the Music and Sound manager default bank bus settings and migrate older addon values if present ([9d614db](https://github.com/hugemenace/resonate/commit/9d614db05e4744c885c22f2def05459bfe9ac176))\n\n## [2.3.2](https://github.com/hugemenace/resonate/compare/v2.3.1...v2.3.2) (2024-02-09)\n\n\n### Bug Fixes\n\n* add missing reset_* methods to the null audio players ([8cd9b7c](https://github.com/hugemenace/resonate/commit/8cd9b7c7c0d68a15532ea0fb5053de1788a2b4cd))\n* alter the attachment logic for 2D and 3D players to avoid issues with players being freed before returning to the pool (deprecates auto_release) ([d53d856](https://github.com/hugemenace/resonate/commit/d53d856683b7d15f2b0ef9a55c7ebbf8de57956b))\n* replace the remote transform behaviour with an inbuilt follow-target mechanism to avoid audio artifacts ([b741318](https://github.com/hugemenace/resonate/commit/b7413187e8863e47888b7e520adf8c2d863cf23e))\n\n## [2.3.1](https://github.com/hugemenace/resonate/compare/v2.3.0...v2.3.1) (2024-02-07)\n\n\n### Bug Fixes\n\n* ensure 3D players registered as auto-released aren't lost during scene tree exiting events ([aa0d702](https://github.com/hugemenace/resonate/commit/aa0d702d7c4f8494e9fc50dd98594aabee2b90db))\n* fix the project settings registration process when the addon loads ([4b17eec](https://github.com/hugemenace/resonate/commit/4b17eecbb3998c5c8d8c0f8fc8f781f5d7ffd66c))\n\n## [2.3.0](https://github.com/hugemenace/resonate/compare/v2.2.0...v2.3.0) (2024-02-06)\n\n\n### Features\n\n* add a volume variable to sound events (bank-configured) ([6b3d66d](https://github.com/hugemenace/resonate/commit/6b3d66d97bb4258e1f6f50b226604b548e682571))\n* add enhanced return types, signals, and helper methods to the music manager ([e621269](https://github.com/hugemenace/resonate/commit/e6212692baaf777bab0391c8ab7fddb876cc8117))\n* add null object pattern to the sound manager for instances and some additional helper methods and events for implementation ([df8eb86](https://github.com/hugemenace/resonate/commit/df8eb86ee2d979ed220388d19ed57e21730005e0))\n* add pitch variable to sound event resources and fix pitch and volume behaviour across audio stream players ([5918f8e](https://github.com/hugemenace/resonate/commit/5918f8eef87a2b14c4d71fdda4911ecc90a1b8a9))\n* add play_varied, play_at_position_varied, and play_on_node_varied methods to the sound manager ([3f2ec86](https://github.com/hugemenace/resonate/commit/3f2ec86ac70c414039c274a21cc6c84d157894ea))\n* add reset_volume, reset_pitch, and reset_all methods to pooled audio stream players ([07ec5ef](https://github.com/hugemenace/resonate/commit/07ec5efb70742ee874641d048ab3616f368e8dc4))\n* add the quick_instance method to the sound manager for bulk instances, or for shorter registration calls ([03f2515](https://github.com/hugemenace/resonate/commit/03f2515a84c9b19395ffe118d25132dbc9f2663d))\n\n\n### Bug Fixes\n\n* ensure that pooled audio stream players return the player volume back to the base volume when calling trigger after trigger_varied, and that trigger_varied isn't affected by the base volume on polyphonic playback ([f53f709](https://github.com/hugemenace/resonate/commit/f53f709e314ef54f537736b903290492acb94ed8))\n* fix the \"signal already connected\" error in multiple single-script auto_released instances and ensure the connection is deferred ([f7b2553](https://github.com/hugemenace/resonate/commit/f7b25535f8b8bfc0d40330bfb2ea7307330b69e5))\n* fix the error handling & messages for missing streams or players in the sound manager ([9e4d27d](https://github.com/hugemenace/resonate/commit/9e4d27d60c11cff8e2c0f4088c2170c0c598e775))\n* fix the volume adjustment in the trigger_varied method on non-polyphonic pooled audio stream players ([1ed5ebd](https://github.com/hugemenace/resonate/commit/1ed5ebd23d657f66b487fae326b6170d448254f4))\n* increase the volume step resolution on music stem resources to allow for fractional dBs ([51cf761](https://github.com/hugemenace/resonate/commit/51cf76164dbad187179501da3c39f072d6f67a66))\n* push a warning when instanced sound events that contain looping variations are released with \"finish playing\" - these will be forced to stop playback ([fdce4d2](https://github.com/hugemenace/resonate/commit/fdce4d2764a8ffd2c617064a5a352bdb6484e6bd))\n\n## [2.2.0](https://github.com/hugemenace/resonate/compare/v2.1.0...v2.2.0) (2024-02-01)\n\n\n### Features\n\n* add a warning for when a music track contains non-looping stems ([3577b09](https://github.com/hugemenace/resonate/commit/3577b09a8fa80547891ddc97916319a112b5d90b))\n* add auto_release and auto_stop convenience methods to the sound and music managers ([5640a57](https://github.com/hugemenace/resonate/commit/5640a57725bedd0098cbf08337a82c71c52d180f))\n\n\n### Bug Fixes\n\n* ensure that pooled audio stream players immediately stop playback (by default) on release ([3cf44cd](https://github.com/hugemenace/resonate/commit/3cf44cd075035d2879e9c1ec722cc0741217ee19))\n* ensure the pool entity release method immediately stops the playback of looping stems even if finish_playing is set ([8014e20](https://github.com/hugemenace/resonate/commit/8014e205ba32b4dcdd9fdf1484c1c7423b79348a))\n\n## [2.1.0](https://github.com/hugemenace/resonate/compare/v2.0.0...v2.1.0) (2024-01-31)\n\n\n### Features\n\n* add automatic detection of new sound and music banks during scene changes & runtime node insertion/deletion ([f964908](https://github.com/hugemenace/resonate/commit/f964908558b95adc18c0f3c92365f7eb37e664ae))\n* enhance the is_playing method on the MusicManager to allow bank and track scoping ([90eb8d3](https://github.com/hugemenace/resonate/commit/90eb8d317203fc260f79a45e11ec07e4ff8fca98))\n\n## [2.0.0](https://github.com/hugemenace/resonate/compare/v1.0.2...v2.0.0) (2024-01-30)\n\n\n### ⚠ BREAKING CHANGES\n\n* rename MusicResource to MusicTrackResource\n\n### Features\n\n* add a bus override option to MusicBanks and MusicTrackResources ([61ede7e](https://github.com/hugemenace/resonate/commit/61ede7ec1574f22fc84779427282b802a9523682))\n* add global MusicManager and individual stem volume management ([02696cd](https://github.com/hugemenace/resonate/commit/02696cdade253a2d5374f73229f24624c59f0e0b))\n* add labels to MusicBanks to allow the grouping/scoping of music tracks ([1192122](https://github.com/hugemenace/resonate/commit/1192122e5d5673184d3a820238154824cbe0c093))\n* expose the MusicManager's loaded status via a has_loaded public variable ([4145ac0](https://github.com/hugemenace/resonate/commit/4145ac0cb7dc8b5767de0d9a9628c79991bf119c))\n\n\n### Bug Fixes\n\n* rename MusicResource to MusicTrackResource ([8ba7355](https://github.com/hugemenace/resonate/commit/8ba73556b28ef5b830e75c53d2b50c6dd9f3697e))\n* update the build script to include a top-level name & version directory in the release .zip and also bundle-in a copy of the documentation ([8db1ae4](https://github.com/hugemenace/resonate/commit/8db1ae4fc04f11364cc556aff095dfe92a3e5431))\n\n## [1.0.2](https://github.com/hugemenace/resonate/compare/v1.0.1...v1.0.2) (2024-01-29)\n\n\n### Bug Fixes\n\n* remove the extraneous pool_updated signal and PoolType enum from the SoundManager ([2066868](https://github.com/hugemenace/resonate/commit/2066868ff96f831d83624761addefd88af7e92b8))\n* remove the SoundManager type references from the pooled audio stream players to avoid errors when the addon is disabled ([c3e3cd9](https://github.com/hugemenace/resonate/commit/c3e3cd91a3098d59eb2e24dd225a9e8121f94d02))\n\n## [1.0.1](https://github.com/hugemenace/resonate/compare/v1.0.0...v1.0.1) (2024-01-28)\n\n\n### Bug Fixes\n\n* ensure the release zip includes the addons directory ([fc543d3](https://github.com/hugemenace/resonate/commit/fc543d33fde886c467fe071ee48e23371deda408))\n\n## [1.0.0](https://github.com/hugemenace/resonate/compare/v0.1.0...v1.0.0) (2024-01-28)\n\n\n### Features\n\n* add a pool_updated event to the SoundManager ([0ada03c](https://github.com/hugemenace/resonate/commit/0ada03c4bb5805bcf9274210143cd060996626fc))\n* add music track crossfading, stem toggle fading, and update the example music scene ([d3b180f](https://github.com/hugemenace/resonate/commit/d3b180fd08ee52144f4c496fbbb863e05c80d42f))\n* add pool stats and tidy up the simple example scene ([03c7b4c](https://github.com/hugemenace/resonate/commit/03c7b4c174fc53e7e5fe77e6aebe189b6b301ae1))\n* add process modes to music and sound banks ([d3f6142](https://github.com/hugemenace/resonate/commit/d3f61421f6d142c38c334b5add041ba63ad03403))\n* add trigger_varied to pooled* audio streams to allow simplified pitch and volume control for reserved instances ([ee0bc2b](https://github.com/hugemenace/resonate/commit/ee0bc2b7337dda290ebadefcc79d317ee0dcd040))\n* complete the 2D example scene ([a6a55fa](https://github.com/hugemenace/resonate/commit/a6a55fa04802d2c7f409fe3c6a3136b08ff00d04))\n* complete the 3D example scene ([2a93dc7](https://github.com/hugemenace/resonate/commit/2a93dc7bcc94e5e06d98ab8f9857ff243176271c))\n* complete the example music scene ([0e9fb4c](https://github.com/hugemenace/resonate/commit/0e9fb4c964307f54af5cf387f4141ce7d9b76d8a))\n* complete the polyphonic example scene ([50cfcd2](https://github.com/hugemenace/resonate/commit/50cfcd2bf196d2a6c232a449cc615327c92b71a3))\n* complete the simple example scene ([28a07c6](https://github.com/hugemenace/resonate/commit/28a07c68a50e42bc7415eaabe055f4d30149e6c0))\n\n\n### Bug Fixes\n\n* change the SoundManager's pool_type enum name to PoolType ([1705d10](https://github.com/hugemenace/resonate/commit/1705d10a03ebefb637802f3b3bfc47619db6e855))\n* change the SoundManager's private _loaded member variable to a public has_loaded ([25477e3](https://github.com/hugemenace/resonate/commit/25477e3741f72e0036bdea641de70e987a624435))\n* ensure that music is played through the bus configured in the project settings ([ed9b4c7](https://github.com/hugemenace/resonate/commit/ed9b4c7a9f64f3c64662aa045da445836d1fb9a2))\n* fix the music example scene and add an is_playing function to the music manager ([9289a40](https://github.com/hugemenace/resonate/commit/9289a407c026dc638fb8dbbc9567d06e7f6db663))\n* remove the reserved parameter from the pooled audio stream player's configure functions and ensure play* functions are set to auto-release when triggered ([822d6ee](https://github.com/hugemenace/resonate/commit/822d6eef439a8347c1dc4d82cc60f29632121ed0))\n\n\n### Miscellaneous Chores\n\n* release 1.0.0 ([c3945bc](https://github.com/hugemenace/resonate/commit/c3945bc7f93c124ca2a8900b71265deae813ae55))\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2024 I.A JONES & T. STRATHEARN\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."
  },
  {
    "path": "README.md",
    "content": "<img src='reasonate-github-header.jpg' width='100%'>\n\n# ***Resonate***\n\n> [!IMPORTANT]\n> This project was originally developed and maintained by Huge Menace. **As of Jan 2026, it is officially maintained by Widgit Gaming**. Future updates, issues, and discussions are hosted at 👉 [gitlab.com/widgitgaming/godot/resonate](https://gitlab.com/widgitgaming/godot/resonate) — Thank you to the community for your support!\n\nAn all-in-one sound and music management addon for the Godot game engine (see compatibility).\n\n## Features\n- Pooled audio stream players.\n- Automatic 2D and 3D space detection.\n- Polyphonic playback.\n- Stemmed music tracks.\n- Music crossfading.\n\n## TL;DR\n\nResonate has two core systems: the **SoundManager** and the **MusicManager**.\n\nThe **SoundManager** automatically pools and orchestrates **AudioStreamPlayers** for you and gives you control over the players when needed. \n\nThe **MusicManager** composes music tracks built from ***stems*** and supports the (cross)fading of tracks or stems out of the box.\n\n## Docs\n\n- [Getting started](docs/getting-started.md)\n- [SoundManager documentation](docs/sound-manager.md)\n- [MusicManager documentation](docs/music-manager.md)\n\n## Examples\n\nThis repo is a Godot project you can clone and run.\n\nInside the `examples/` folder are scenes demonstrating all the Sound and Music Manager's features.\n\n## Getting the addon\n\nYou have a few different options:\n\n- You can download Resonate from the [Godot Asset Library](https://godotengine.org/asset-library/asset/2546) (or from within the editor). \n- You can grab the latest version from the [Github releases page](https://github.com/hugemenace/resonate/releases).\n- You can also clone or download this repo, and extract the `addons/resonate` directory into the root folder of your Godot project.\n- You can grab the addon from [Gumroad](https://hugemenace.gumroad.com/l/resonate-godot-addon) (if you'd like to financially support this project).\n\n## License\n\nThis project is provided ***free for personal and commercial use*** under the [MIT license](LICENSE) ❤\n"
  },
  {
    "path": "addons/resonate/LICENSE",
    "content": "Copyright 2024 I.A JONES & T. STRATHEARN\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."
  },
  {
    "path": "addons/resonate/music_manager/music_bank.gd",
    "content": "class_name MusicBank\nextends Node\n## A container used to store & group music tracks in your scene.\n\n\n## This bank's unique identifier.\n@export var label: String\n\n## The bus to use for all tracks played from this bank.[br][br]\n## [b]Note:[/b] this will override the bus set in your project settings (Audio/Manager/Music/Bank)\n@export var bus: String\n\n## The underlying process mode for all tracks played from this bank.\n@export var mode: Node.ProcessMode\n\n## The collection of tracks associated with this bank.\n@export var tracks: Array[MusicTrackResource]\n"
  },
  {
    "path": "addons/resonate/music_manager/music_bank.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://bxfmecl088q55\"\npath=\"res://.godot/imported/music_bank.svg-4038ad3a11ca952c7a39bede8a3b962e.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/resonate/music_manager/music_bank.svg\"\ndest_files=[\"res://.godot/imported/music_bank.svg-4038ad3a11ca952c7a39bede8a3b962e.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": "addons/resonate/music_manager/music_manager.gd",
    "content": "extends Node\n## The MusicManager is responsible for all music in your game.\n##\n## It manages the playback of music tracks, which are constructed with one or more\n## stems. Each stem can be independently enabled or disabled, allowing for more dynamic\n## playback. Music tracks can also be crossfaded when switching from one to another.\n##\n## @tutorial(View example scenes): https://github.com/hugemenace/resonate/tree/main/examples\n\n\nconst ResonateSettings = preload(\"../shared/resonate_settings.gd\")\nvar _settings = ResonateSettings.new()\n\n## Emitted only once when the MusicManager has finished setting up and \n## is ready to play music tracks and enable and disable stems.\nsignal loaded\n\n## Emitted every time the MusicManager detects that a MusicBank has\n## been added or removed from the scene tree.\nsignal banks_updated\n\n## Emitted whenever [signal MusicManager.loaded] or \n## [signal MusicManager.pools_updated] is emitted.\nsignal updated\n\n## Whether the MusicManager has completed setup and is ready to\n## play music tracks and enable and disable stems.\nvar has_loaded: bool = false\n\nvar _music_table: Dictionary = {}\nvar _music_table_hash: int\nvar _music_streams: Array[StemmedMusicStreamPlayer] = []\nvar _volume: float\n\n\n# ------------------------------------------------------------------------------\n# Lifecycle methods\n# ------------------------------------------------------------------------------\n\n\nfunc _init():\n\tprocess_mode = Node.PROCESS_MODE_ALWAYS\n\n\nfunc _ready() -> void:\n\t_auto_add_music()\n\t\n\tvar scene_root = get_tree().root.get_tree()\n\tscene_root.node_added.connect(_on_scene_node_added)\n\tscene_root.node_removed.connect(_on_scene_node_removed)\n\n\nfunc _process(_p_delta) -> void:\n\tif _music_table_hash != _music_table.hash():\n\t\t_music_table_hash = _music_table.hash()\n\t\tbanks_updated.emit()\n\t\tupdated.emit()\n\t\t\n\tif has_loaded:\n\t\treturn\n\t\t\n\thas_loaded = true\n\tloaded.emit()\n\tupdated.emit()\n\n\n# ------------------------------------------------------------------------------\n# Public methods\n# ------------------------------------------------------------------------------\n\n\n## Play a music track from a SoundBank, and optionally fade-in or crossfade over\n## the provided [b]p_crossfade_time[/b]. If [b]p_auto_loop[/b] is set, the track \n## will be automatically played again once its longest stem is about to finish playing,\n## taking into account the amount of time required to crossfade into the next loop.\nfunc play(p_bank_label: String, p_track_name: String, p_crossfade_time: float = 5.0, p_auto_loop: bool = false) -> bool:\n\tif not has_loaded:\n\t\tpush_error(\"Resonate - The music track [%s] on bank [%s] can't be played as the MusicManager has not loaded yet. Use the [loaded] signal/event to determine when it is ready.\" % [p_track_name, p_bank_label])\n\t\treturn false\n\t\t\n\tif not _music_table.has(p_bank_label):\n\t\tpush_error(\"Resonate - Tried to play the music track [%s] from an unknown bank [%s].\" % [p_track_name, p_bank_label])\n\t\treturn false\n\t\t\n\tif not _music_table[p_bank_label][\"tracks\"].has(p_track_name):\n\t\tpush_error(\"Resonate - Tried to play an unknown music track [%s] from the bank [%s].\" % [p_track_name, p_bank_label])\n\t\treturn false\n\t\n\tvar bank = _music_table[p_bank_label] as Dictionary\n\tvar track = bank[\"tracks\"][p_track_name] as Dictionary\n\tvar stems = track[\"stems\"] as Array\n\t\n\tif stems.size() == 0:\n\t\tpush_error(\"Resonate - The music track [%s] on bank [%s] has no stems, you'll need to add one at minimum.\" % [p_track_name, p_bank_label])\n\t\treturn false\n\t\t\n\tfor stem in stems:\n\t\tif stem.stream == null:\n\t\t\tpush_error(\"Resonate - The stem [%s] on the music track [%s] on bank [%s] does not have an audio stream, you'll need to add one.\" % [stem.name, p_track_name, p_bank_label])\n\t\t\treturn false\n\t\t\t\n\t\tif not p_auto_loop and not ResonateUtils.is_stream_looped(stem.stream):\n\t\t\tpush_warning(\"Resonate - The stem [%s] on the music track [%s] on bank [%s] is not set to loop, which will cause it to work incorrectly.\" % [stem.name, p_track_name, p_bank_label])\n\t\n\tvar bus = _get_bus(bank.bus, track.bus)\n\tvar player = StemmedMusicStreamPlayer.create(p_bank_label, p_track_name, bus, bank.mode, _volume, p_auto_loop)\n\t\n\tif _music_streams.size() > 0:\n\t\tfor stream in _music_streams:\n\t\t\tstream.stop_stems(p_crossfade_time)\n\t\n\t_music_streams.append(player)\n\t\n\tadd_child(player)\n\t\n\tplayer.start_stems(stems, p_crossfade_time)\n\tplayer.stopped.connect(_on_player_stopped.bind(player))\n\t\n\tif p_auto_loop:\n\t\tplayer.auto_loop_completed.connect(_on_auto_loop_completed, CONNECT_REFERENCE_COUNTED)\n\t\n\treturn true\n\n\n## Check whether the MusicManager is playing from a specific bank, or any track\n## with the given name, or more specifically a certain track from a certain bank.\nfunc is_playing(p_bank_label: String = \"\", p_track_name: String = \"\") -> bool:\n\tif not has_loaded:\n\t\treturn false\n\t\n\tif _music_streams.size() == 0:\n\t\treturn false\n\t\t\n\tvar current_player = _get_current_player()\n\tvar is_playing = not current_player.is_stopping\n\tvar bank_label = current_player.bank_label\n\tvar track_name = current_player.track_name\n\t\n\tif p_bank_label == \"\" and p_track_name == \"\":\n\t\treturn is_playing\n\t\t\n\tif p_bank_label != \"\" and p_track_name == \"\":\n\t\treturn bank_label == p_bank_label and is_playing\n\t\t\n\tif p_bank_label == \"\" and p_track_name != \"\":\n\t\treturn track_name == p_track_name and is_playing\n\t\t\n\treturn bank_label == p_bank_label and track_name == p_track_name and is_playing\n\n\n## Stop the playback of all music.\nfunc stop(p_fade_time: float = 5.0) -> void:\n\tif not _is_playing_music():\n\t\tpush_warning(\"Resonate - Cannot stop the music track as there is no music currently playing.\")\n\t\treturn\n\t\t\n\tvar current_player = _get_current_player()\n\t\n\tcurrent_player.stop_stems(p_fade_time)\n\n\n## Check whether the MusicManager should skip playing a new track. It will return true if the \n## MusicManager has not loaded yet, or if the flag you provide is not [b]false[/b] or [b]null[/b].\nfunc should_skip_playing(p_flag) -> bool:\n\treturn not has_loaded or (p_flag != false and p_flag != null)\n\n\n## Set the volume of the current music track (if playing) and all future tracks to be played.\nfunc set_volume(p_volume: float) -> void:\n\t_volume = p_volume\n\t\n\tif not _is_playing_music():\n\t\treturn\n\t\t\n\tvar current_player = _get_current_player()\n\t\n\tcurrent_player.set_volume(_volume)\n\n\n## Enable the specified stem on the currently playing music track.\nfunc enable_stem(p_name: String, p_fade_time: float = 2.0) -> void:\n\t_set_stem(p_name, true, p_fade_time)\n\n\n## Disable the specified stem on the currently playing music track.\nfunc disable_stem(p_name: String, p_fade_time: float = 2.0) -> void:\n\t_set_stem(p_name, false, p_fade_time)\n\n\n## Set the volume for the specified stem on the currently playing music track.\nfunc set_stem_volume(p_name: String, p_volume: float) -> void:\n\tif not _is_playing_music():\n\t\tpush_warning(\"Resonate - Cannot set the volume of stem [%s] as there is no music currently playing.\" % p_name)\n\t\treturn\n\t\t\n\tvar current_player = _get_current_player()\n\t\n\tcurrent_player.set_stem_volume(p_name, p_volume)\n\n\n## Get the underlying details of the provided stem for the currently playing music track.\nfunc get_stem_details(p_name: String) -> Variant:\n\tif not _is_playing_music():\n\t\tpush_warning(\"Resonate - Cannot get the details for stem [%s] as there is no music currently playing.\" % p_name)\n\t\treturn\n\t\t\n\tvar current_player = _get_current_player()\n\t\n\treturn current_player.get_stem_details(p_name)\n\n\n## Will automatically stop the provided music track when the provided \n## [b]p_base[/b] is removed from the scene tree.\nfunc stop_on_exit(p_base: Node, p_bank_label: String, p_track_name: String, p_fade_time: float = 5.0) -> void:\n\tp_base.tree_exiting.connect(_on_music_player_exiting.bind(p_bank_label, p_track_name, p_fade_time))\n\n\n## Will automatically stop the provided music track when the provided \n## [b]p_base[/b] is removed from the scene tree.[br][br]\n## [b]Note:[/b] This method has been deprecated, please use [method MusicManager.stop_on_exit] instead.\n## @deprecated\nfunc auto_stop(p_base: Node, p_bank_label: String, p_track_name: String, p_fade_time: float = 5.0) -> void:\n\tpush_warning(\"Resonate - auto_stop has been deprecated, please use stop_on_exit instead.\")\n\tstop_on_exit(p_base, p_bank_label, p_track_name, p_fade_time)\n\n\n## Manually add a new SoundBank into the music track cache.\nfunc add_bank(p_bank: MusicBank) -> void:\n\t_add_bank(p_bank)\n\n\n## Remove the provided bank from the music track cache.\nfunc remove_bank(p_bank_label: String) -> void:\n\tif not _music_table.has(p_bank_label):\n\t\treturn\n\t\t\n\t_music_table.erase(p_bank_label)\n\n\n## Clear all banks from the music track cache.\nfunc clear_banks() -> void:\n\t_music_table.clear()\n\n\n# ------------------------------------------------------------------------------\n# Private methods\n# ------------------------------------------------------------------------------\n\n\nfunc _on_scene_node_added(p_node: Node) -> void:\n\tif not p_node is MusicBank:\n\t\treturn\n\t\t\n\t_add_bank(p_node)\n\t\n\t\nfunc _on_scene_node_removed(p_node: Node) -> void:\n\tif not p_node is MusicBank:\n\t\treturn\n\t\t\n\t_remove_bank(p_node)\n\n\nfunc _auto_add_music() -> void:\n\tvar music_banks = ResonateUtils.find_all_nodes(self, \"MusicBank\")\n\t\n\tfor music_bank in music_banks:\n\t\t_add_bank(music_bank)\n\t\t\n\t_music_table_hash = _music_table.hash()\n\n\nfunc _add_bank(p_bank: MusicBank) -> void:\n\tif _music_table.has(p_bank.label):\n\t\t_music_table[p_bank.label][\"ref_count\"] = \\\n\t\t\t\t_music_table[p_bank.label][\"ref_count\"] + 1\n\t\t\n\t\treturn\n\t\t\n\t_music_table[p_bank.label] = {\n\t\t\"name\": p_bank.label,\n\t\t\"bus\": p_bank.bus,\n\t\t\"mode\": p_bank.mode,\n\t\t\"tracks\": _create_tracks(p_bank.tracks),\n\t\t\"ref_count\": 1,\n\t}\n\n\nfunc _remove_bank(p_bank: MusicBank) -> void:\n\tif not _music_table.has(p_bank.label):\n\t\treturn\n\t\n\tif _music_table[p_bank.label][\"ref_count\"] == 1:\n\t\t_music_table.erase(p_bank.label)\n\t\treturn\n\t\n\t_music_table[p_bank.label][\"ref_count\"] = \\\n\t\t\t_music_table[p_bank.label][\"ref_count\"] - 1\n\n\nfunc _create_tracks(p_tracks: Array[MusicTrackResource]) -> Dictionary:\n\tvar tracks = {}\n\t\n\tfor track in p_tracks:\n\t\ttracks[track.name] = {\n\t\t\t\"name\": track.name,\n\t\t\t\"bus\": track.bus,\n\t\t\t\"stems\": _create_stems(track.stems),\n\t\t}\n\n\treturn tracks\n\n\nfunc _create_stems(p_stems: Array[MusicStemResource]) -> Array:\n\tvar stems = []\n\t\n\tfor stem in p_stems:\n\t\tstems.append({\n\t\t\t\"name\": stem.name,\n\t\t\t\"enabled\": stem.enabled,\n\t\t\t\"volume\": stem.volume,\n\t\t\t\"stream\": stem.stream,\n\t\t})\n\t\t\n\treturn stems\n\n\nfunc _get_bus(p_bank_bus: String, p_track_bus: String) -> String:\n\tif p_track_bus != null and p_track_bus != \"\":\n\t\treturn p_track_bus\n\t\n\tif p_bank_bus != null and p_bank_bus != \"\":\n\t\treturn p_bank_bus\n\t\t\n\treturn ProjectSettings.get_setting(\n\t\t_settings.MUSIC_BANK_BUS_SETTING_NAME,\n\t\t_settings.MUSIC_BANK_BUS_SETTING_DEFAULT)\n\n\nfunc _is_playing_music() -> bool:\n\treturn _music_streams.size() > 0\n\n\nfunc _get_current_player() -> StemmedMusicStreamPlayer:\n\tif _music_streams.size() == 0:\n\t\treturn null\n\t\t\n\treturn _music_streams.back() as StemmedMusicStreamPlayer\n\n\nfunc _set_stem(p_name: String, p_enabled: bool, p_fade_time: float) -> void:\n\tif not _is_playing_music():\n\t\tpush_warning(\"Resonate - Cannot toggle the stem [%s] as there is no music currently playing.\" % p_name)\n\t\treturn\n\t\t\n\tvar current_player = _get_current_player()\n\t\n\tcurrent_player.toggle_stem(p_name, p_enabled, p_fade_time)\n\n\nfunc _on_music_player_exiting(p_bank_label: String, p_track_name: String, p_fade_time: float) -> void:\n\tif not is_playing(p_bank_label, p_track_name):\n\t\treturn\n\t\n\tstop(p_fade_time)\n\n\nfunc _on_player_stopped(p_player: StemmedMusicStreamPlayer) -> void:\n\tif not _is_playing_music():\n\t\treturn\n\t\t\n\t_music_streams.erase(p_player)\n\tremove_child(p_player)\n\n\nfunc _on_auto_loop_completed(p_bank_label: String, p_track_name: String, p_crossfade_time: float) -> void:\n\tplay(p_bank_label, p_track_name, p_crossfade_time, true)\n"
  },
  {
    "path": "addons/resonate/music_manager/music_stem_resource.gd",
    "content": "class_name MusicStemResource\nextends Resource\n## A container used to store the details of one particular music track's stem.\n\n\n## This stem's unique identifier within the scope of the track it belongs to.\n@export var name: String = \"\"\n\n## Whether this stem will start playing automatically when the track starts.\n@export var enabled: bool = false\n\n## The volume of the stem.\n@export_range(-80.0, 6.0, 0.1, \"suffix:dB\") var volume: float = 0.0\n\n## The audio stream associated with this stem.\n@export var stream: AudioStream\n"
  },
  {
    "path": "addons/resonate/music_manager/music_track_resource.gd",
    "content": "class_name MusicTrackResource\nextends Resource\n## A container used to store the details of a music track and all of its corresponding stems.\n\n\n## This track's unique identifier within the scope of the bank it belongs to.\n@export var name: String = \"\"\n\n## The bus to use for this particular track.[br][br]\n## [b]Note:[/b] this will override the bus set on the bank, or in your project settings (Audio/Manager/Music/Bank)\n@export var bus: String = \"\"\n\n## The collection of stems that make up this track.\n@export var stems: Array[MusicStemResource]\n"
  },
  {
    "path": "addons/resonate/music_manager/stemmed_music_stream_player.gd",
    "content": "class_name StemmedMusicStreamPlayer\nextends AudioStreamPlayer\n## An extended AudioStreamPlayer capable of managing and playing a \n## collection of stems that make up a music track.\n\n\n## Emitted when this player has completely stopped playing a track.\nsignal stopped\n\n## Emitted when this player has finished the current auto-loop and should begin playback again.\nsignal auto_loop_completed(p_bank_label: String, p_track_name: String, p_crossfade_time: float)\n\n## True when this player is in the process of shutting down and stopping a track.\nvar is_stopping: bool\n\n## The label of the bank that this player's track came from.\nvar bank_label: String\n\n## The name of the track associated with this player.\nvar track_name: String\n\nconst _DISABLED_VOLUME: float = -80\nconst _START_TRANS: Tween.TransitionType = Tween.TRANS_QUART\nconst _START_EASE: Tween.EaseType = Tween.EASE_OUT\nconst _STOP_TRANS: Tween.TransitionType = Tween.TRANS_QUART\nconst _STOP_EASE: Tween.EaseType = Tween.EASE_IN\n\nvar _fade_tween: Tween\nvar _stem_table: Dictionary\nvar _max_volume: float\nvar _crossfade_time: float\nvar _auto_loop: bool\nvar _track_length: float\nvar _current_play_time: float\nvar _loop_endpoint_reached_emitted: bool\n\n\n# ------------------------------------------------------------------------------\n# Public methods\n# ------------------------------------------------------------------------------\n\n\nfunc _process(p_delta):\n\tif is_stopping:\n\t\treturn\n\t\n\tif not _auto_loop:\n\t\treturn\n\t\n\tif _loop_endpoint_reached_emitted:\n\t\treturn\n\t\n\t_current_play_time += p_delta\n\tif _current_play_time >= (_track_length - _crossfade_time):\n\t\tauto_loop_completed.emit(bank_label, track_name, _crossfade_time)\n\t\t_loop_endpoint_reached_emitted = true\n\n\n# ------------------------------------------------------------------------------\n# Public methods\n# ------------------------------------------------------------------------------\n\n\n## Create a new player associated with a given bank and track.\nstatic func create(p_bank_label: String, p_track_name: String, p_bus: String, p_mode: Node.ProcessMode, p_max_volume: float, p_auto_loop: bool) -> StemmedMusicStreamPlayer:\n\tvar player = StemmedMusicStreamPlayer.new()\n\tvar stream = AudioStreamPolyphonic.new()\n\t\n\tplayer.bank_label = p_bank_label\n\tplayer.track_name = p_track_name\n\tplayer.stream = stream\n\tplayer.process_mode = p_mode\n\tplayer.bus = p_bus\n\tplayer.volume_db = _DISABLED_VOLUME\n\tplayer.is_stopping = false\n\tplayer._max_volume = p_max_volume\n\tplayer._auto_loop = p_auto_loop\n\t\n\treturn player\n\n\n## Start the collection of stems associated with the track on this player.\n## This is what fundamentally starts the music track.[br][br]\n## [b]Note:[/b] this should only be called once.\nfunc start_stems(p_stems: Array, p_crossfade_time: float) -> void:\n\tif playing:\n\t\treturn\n\t\t\n\tstream.polyphony = p_stems.size()\n\tmax_polyphony = p_stems.size()\n\t\n\tplay()\n\t\n\tvar playback = get_stream_playback() as AudioStreamPlaybackPolyphonic\n\tvar max_length = 0\n\t\n\tfor stem in p_stems:\n\t\tvar stream = stem.stream as AudioStream\n\t\tvar length = stream.get_length()\n\t\tif length > max_length:\n\t\t\tmax_length = length\n\t\t\n\t\tvar stream_id = playback.play_stream(stem.stream)\n\t\tvar max_volume = stem.volume\n\t\tvar volume = max_volume if stem.enabled else _DISABLED_VOLUME\n\t\t\n\t\tplayback.set_stream_volume(stream_id, volume)\n\t\t\n\t\t_stem_table[stem.name] = {\n\t\t\t\"name\": stem.name,\n\t\t\t\"enabled\": stem.enabled,\n\t\t\t\"stream_id\": stream_id,\n\t\t\t\"volume\": volume,\n\t\t\t\"max_volume\": max_volume,\n\t\t\t\"tween\": null,\n\t\t}\n\t\n\t_track_length = max_length\n\t_crossfade_time = p_crossfade_time\n\t\n\t_fade_tween = create_tween()\n\t_fade_tween \\\n\t\t\t.tween_property(self, \"volume_db\", _max_volume, p_crossfade_time) \\\n\t\t\t.set_trans(_START_TRANS) \\\n\t\t\t.set_ease(_START_EASE)\n\t\n\n## Toggle (enable or disable) the specified stem associated with the track on this player.\nfunc toggle_stem(p_name: String, p_enabled: bool, p_fade_time: float) -> void:\n\tif not _stem_table.has(p_name):\n\t\tpush_warning(\"Resonate - Cannot toggle the stem [%s] on music track [%s] from bank [%s] as it does not exist.\" % [p_name, track_name, bank_label])\n\t\treturn\n\t\t\n\tvar playback = get_stream_playback() as AudioStreamPlaybackPolyphonic\n\tvar stem = _stem_table[p_name]\n\tvar old_tween = stem.tween as Tween\n\tvar new_tween = create_tween()\n\tvar target_volume = stem.max_volume if p_enabled else _DISABLED_VOLUME\n\t\n\tif old_tween != null:\n\t\told_tween.kill()\n\t\n\t_stem_table[p_name][\"tween\"] = new_tween\n\t_stem_table[p_name][\"enabled\"] = p_enabled\n\t\n\tvar transition = _START_TRANS if p_enabled else _STOP_TRANS\n\tvar easing = _START_EASE if p_enabled else _STOP_EASE\n\t\n\tnew_tween \\\n\t\t\t.tween_method(_tween_stem_volume.bind(p_name), stem.volume, target_volume, p_fade_time) \\\n\t\t\t.set_trans(transition) \\\n\t\t\t.set_ease(easing)\n\n\n## Set the volume of this player.[br][br]\n## [b]Note:[/b] if called when the player is still fading in or out, it will \n## immediately cancel the fade and set the volume at specified level.\nfunc set_volume(p_volume: float) -> void:\n\tif _fade_tween != null and _fade_tween.is_running():\n\t\t_fade_tween.kill()\n\t\t\n\t_max_volume = p_volume\n\tvolume_db = p_volume\n\t\n\n## Set the volume of a specific stem associated with this track.[br][br]\n## [b]Note:[/b] if called when the stem is still fading in or out, it will \n## immediately cancel the fade and set the volume at specified level.\nfunc set_stem_volume(p_name: String, p_volume: float) -> void:\n\tvar playback = get_stream_playback() as AudioStreamPlaybackPolyphonic\n\tvar stem = _stem_table[p_name]\n\t\n\tif stem[\"tween\"] != null and stem[\"tween\"].is_running():\n\t\tstem[\"tween\"].kill()\n\t\n\tplayback.set_stream_volume(stem.stream_id, p_volume)\n\t\n\t_stem_table[p_name][\"volume\"] = p_volume\n\t_stem_table[p_name][\"max_volume\"] = p_volume\n\n\n## This will stop all stems associated with this player, causing it to shut-down and stop.\nfunc stop_stems(p_fade_time: float) -> void:\n\tif is_stopping:\n\t\treturn\n\t\t\n\tis_stopping = true\n\t\n\tvar tween = create_tween()\n\ttween \\\n\t\t\t.tween_property(self, \"volume_db\", _DISABLED_VOLUME, p_fade_time) \\\n\t\t\t.set_trans(_STOP_TRANS) \\\n\t\t\t.set_ease(_STOP_EASE)\n\t\n\ttween.finished.connect(_on_stop_stems_tween_finished)\n\n\n## Get the underlying details of the provided stem for the currently playing music track.\nfunc get_stem_details(p_name: String) -> Variant:\n\tif not _stem_table.has(p_name):\n\t\tpush_warning(\"Resonate - Cannot get the details for stem [%s] on music track [%s] from bank [%s] as it does not exist.\" % [p_name, track_name, bank_label])\n\t\treturn null\n\t\t\n\tvar stem = _stem_table[p_name]\n\t\n\treturn {\n\t\t\"name\": stem.name,\n\t\t\"enabled\": stem.enabled,\n\t\t\"volume\": stem.volume,\n\t}\n\n\n# ------------------------------------------------------------------------------\n# Private methods\n# ------------------------------------------------------------------------------\n\n\nfunc _tween_stem_volume(p_target_volume: float, p_name: String) -> void:\n\tvar playback = get_stream_playback() as AudioStreamPlaybackPolyphonic\n\tvar stem = _stem_table[p_name]\n\t\n\tplayback.set_stream_volume(stem.stream_id, p_target_volume)\n\t\n\t_stem_table[p_name][\"volume\"] = p_target_volume\n\n\nfunc _on_stop_stems_tween_finished() -> void:\n\tstopped.emit()\n"
  },
  {
    "path": "addons/resonate/plugin.cfg",
    "content": "[plugin]\n\nname=\"Resonate\"\ndescription=\"\"\nauthor=\"HugeMenace\"\n;x-release-please-start-version\nversion=\"2.4.0\"\n;x-release-please-end\nscript=\"plugin.gd\"\n"
  },
  {
    "path": "addons/resonate/plugin.gd",
    "content": "@tool\nextends EditorPlugin\n\n\nconst ResonateSettings = preload(\"shared/resonate_settings.gd\")\nvar _settings = ResonateSettings.new()\n\n\nfunc _enter_tree():\n\tadd_autoload_singleton(\"SoundManager\", \"sound_manager/sound_manager.gd\")\n\tadd_autoload_singleton(\"MusicManager\", \"music_manager/music_manager.gd\")\n\tadd_custom_type(\"SoundBank\", \"Node\", preload(\"sound_manager/sound_bank.gd\"), preload(\"sound_manager/sound_bank.svg\"))\n\tadd_custom_type(\"MusicBank\", \"Node\", preload(\"music_manager/music_bank.gd\"), preload(\"music_manager/music_bank.svg\"))\n\t\n\tadd_project_setting(\n\t\t_settings.SOUND_BANK_BUS_SETTING_NAME,\n\t\t_settings.SOUND_BANK_BUS_SETTING_DEFAULT,\n\t\t_settings.SOUND_BANK_BUS_SETTING_ACTUAL,\n\t\tTYPE_STRING)\n\t\t\n\tadd_project_setting(\n\t\t_settings.POOL_1D_SIZE_SETTING_NAME,\n\t\t_settings.POOL_1D_SIZE_SETTING_DEFAULT,\n\t\t_settings.POOL_1D_SIZE_SETTING_ACTUAL,\n\t\tTYPE_INT, PROPERTY_HINT_RANGE, \"1,128\")\n\t\t\n\tadd_project_setting(\n\t\t_settings.POOL_2D_SIZE_SETTING_NAME,\n\t\t_settings.POOL_2D_SIZE_SETTING_DEFAULT,\n\t\t_settings.POOL_2D_SIZE_SETTING_ACTUAL,\n\t\tTYPE_INT, PROPERTY_HINT_RANGE, \"1,128\")\n\t\t\n\tadd_project_setting(\n\t\t_settings.POOL_3D_SIZE_SETTING_NAME,\n\t\t_settings.POOL_3D_SIZE_SETTING_DEFAULT,\n\t\t_settings.POOL_3D_SIZE_SETTING_ACTUAL,\n\t\tTYPE_INT, PROPERTY_HINT_RANGE, \"1,128\")\n\t\t\n\tadd_project_setting(\n\t\t_settings.MAX_POLYPHONY_SETTING_NAME,\n\t\t_settings.MAX_POLYPHONY_SETTING_DEFAULT,\n\t\t_settings.MAX_POLYPHONY_SETTING_ACTUAL,\n\t\tTYPE_INT, PROPERTY_HINT_RANGE, \"1,128\")\n\t\t\n\tadd_project_setting(\n\t\t_settings.MUSIC_BANK_BUS_SETTING_NAME,\n\t\t_settings.MUSIC_BANK_BUS_SETTING_DEFAULT,\n\t\t_settings.MUSIC_BANK_BUS_SETTING_ACTUAL,\n\t\tTYPE_STRING)\n\t\t\n\tmigrate_old_bus_settings()\n\n\nfunc _exit_tree():\n\tremove_autoload_singleton(\"SoundManager\")\n\tremove_autoload_singleton(\"MusicManager\")\n\tremove_custom_type(\"SoundBank\")\n\tremove_custom_type(\"MusicBank\")\n\n\nfunc add_project_setting(p_name: String, p_default, p_actual, p_type: int, p_hint: int = PROPERTY_HINT_NONE, p_hint_string: String = \"\"):\n\tif ProjectSettings.has_setting(p_name): \n\t\treturn\n\n\tProjectSettings.set_setting(p_name, p_actual)\n\t\n\tProjectSettings.add_property_info({\n\t\t\"name\": p_name,\n\t\t\"type\": p_type,\n\t\t\"hint\": p_hint,\n\t\t\"hint_string\": p_hint_string,\n\t})\n\t\n\tProjectSettings.set_initial_value(p_name, p_default)\n\t\n\tvar error: int = ProjectSettings.save()\n\t\n\tif error: \n\t\tpush_error(\"Resonate - Encountered error %d when saving project settings.\" % error)\n\n\nfunc migrate_old_bus_settings():\n\t# This migration helps to ensure that users upgrading from an old version of Resonate\n\t# to a version that uses the \"*_BANK_BUS_SETTING*\" ids won't loose their previous\n\t# audio bus settings. After migration occurs, the old settings are deleted.\n\t\n\tif ProjectSettings.has_setting(\"audio/manager/sound/bank\"):\n\t\tvar value = ProjectSettings.get_setting(\n\t\t\t\t\"audio/manager/sound/bank\",\n\t\t\t\t_settings.SOUND_BANK_BUS_SETTING_ACTUAL)\n\t\t\n\t\tProjectSettings.set_setting(_settings.SOUND_BANK_BUS_SETTING_NAME, value)\n\t\tProjectSettings.clear(\"audio/manager/sound/bank\")\n\t\t\n\tif ProjectSettings.has_setting(\"audio/manager/music/bank\"):\n\t\tvar value = ProjectSettings.get_setting(\n\t\t\t\t\"audio/manager/music/bank\",\n\t\t\t\t_settings.MUSIC_BANK_BUS_SETTING_ACTUAL)\n\t\t\n\t\tProjectSettings.set_setting(_settings.MUSIC_BANK_BUS_SETTING_NAME, value)\n\t\tProjectSettings.clear(\"audio/manager/music/bank\")\n\t\t\n"
  },
  {
    "path": "addons/resonate/shared/resonate_settings.gd",
    "content": "extends RefCounted\n\n\nconst SOUND_BANK_BUS_SETTING_NAME = \"audio/manager/sound/bus\"\nconst SOUND_BANK_BUS_SETTING_DEFAULT = \"\"\nconst SOUND_BANK_BUS_SETTING_ACTUAL = \"Sound\"\n\nconst POOL_1D_SIZE_SETTING_NAME = \"audio/manager/sound/pool_1D_size\"\nconst POOL_1D_SIZE_SETTING_DEFAULT = 1\nconst POOL_1D_SIZE_SETTING_ACTUAL = 16\n\nconst POOL_2D_SIZE_SETTING_NAME = \"audio/manager/sound/pool_2D_size\"\nconst POOL_2D_SIZE_SETTING_DEFAULT = 1\nconst POOL_2D_SIZE_SETTING_ACTUAL = 16\n\nconst POOL_3D_SIZE_SETTING_NAME = \"audio/manager/sound/pool_3D_size\"\nconst POOL_3D_SIZE_SETTING_DEFAULT = 1\nconst POOL_3D_SIZE_SETTING_ACTUAL = 16\n\nconst MAX_POLYPHONY_SETTING_NAME = \"audio/manager/sound/max_polyphony\"\nconst MAX_POLYPHONY_SETTING_DEFAULT = 8\nconst MAX_POLYPHONY_SETTING_ACTUAL = 32\n\nconst MUSIC_BANK_BUS_SETTING_NAME = \"audio/manager/music/bus\"\nconst MUSIC_BANK_BUS_SETTING_DEFAULT = \"\"\nconst MUSIC_BANK_BUS_SETTING_ACTUAL = \"Music\"\n"
  },
  {
    "path": "addons/resonate/shared/resonate_utils.gd",
    "content": "class_name ResonateUtils\nextends RefCounted\n\n\nstatic func is_stream_looped(p_stream) -> bool:\n\tif p_stream is AudioStreamMP3:\n\t\treturn p_stream.loop\n\t\t\n\tif p_stream is AudioStreamOggVorbis:\n\t\treturn p_stream.loop\n\t\t\n\tif p_stream is AudioStreamWAV:\n\t\treturn p_stream.loop_mode != AudioStreamWAV.LOOP_DISABLED\n\t\t\n\treturn false\n\n\nstatic func find_all_nodes(p_base: Node, p_type: String) -> Array:\n\tvar root_nodes = p_base.get_tree().root.get_children()\n\tvar results = []\n\t\n\tfor node in root_nodes:\n\t\tresults.append_array(node.find_children(\"*\", p_type))\n\t\t\n\treturn results\n\n\nstatic func is_vector(p_node: Variant) -> bool:\n\treturn p_node is Vector2 or p_node is Vector3\n\t\n\nstatic func is_node(p_node: Variant) -> bool:\n\treturn p_node is Node2D or p_node is Node3D\n\t\n\nstatic func is_2d_node(p_node: Variant) -> bool:\n\treturn p_node is Vector2 or p_node is Node2D\n\n\nstatic func is_3d_node(p_node: Variant) -> bool:\n\treturn p_node is Vector3 or p_node is Node3D\n"
  },
  {
    "path": "addons/resonate/sound_manager/null_pooled_audio_stream_player.gd",
    "content": "class_name NullPooledAudioStreamPlayer\nextends PooledAudioStreamPlayer\n## An extension of PooledAudioStreamPlayer that nerfs all of its public methods.\n\n\n## Whether this player is a [PooledAudioStreamPlayer], or a Null instance.\nfunc is_null() -> bool:\n\treturn true\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.trigger]\nfunc trigger() -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.trigger_varied]\nfunc trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.reset_volume]\nfunc reset_volume() -> void:\n\treturn\n\t\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.reset_pitch]\nfunc reset_pitch() -> void:\n\treturn\n\t\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.reset_all]\nfunc reset_all() -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer.release]\nfunc release(p_finish_playing: bool = false) -> void:\n\treturn\n"
  },
  {
    "path": "addons/resonate/sound_manager/null_pooled_audio_stream_player_2d.gd",
    "content": "class_name NullPooledAudioStreamPlayer2D\nextends PooledAudioStreamPlayer2D\n## An extension of PooledAudioStreamPlayer2D that nerfs all of its public methods.\n\n\n## Whether this player is a [PooledAudioStreamPlayer2D], or a Null instance.\nfunc is_null() -> bool:\n\treturn true\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.trigger]\nfunc trigger() -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.trigger_varied]\nfunc trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.reset_volume]\nfunc reset_volume() -> void:\n\treturn\n\t\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.reset_pitch]\nfunc reset_pitch() -> void:\n\treturn\n\t\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.reset_all]\nfunc reset_all() -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer2D.release]\nfunc release(p_finish_playing: bool = false) -> void:\n\treturn\n"
  },
  {
    "path": "addons/resonate/sound_manager/null_pooled_audio_stream_player_3d.gd",
    "content": "class_name NullPooledAudioStreamPlayer3D\nextends PooledAudioStreamPlayer3D\n## An extension of PooledAudioStreamPlayer3D that nerfs all of its public methods.\n\n\n## Whether this player is a [PooledAudioStreamPlayer3D], or a Null instance.\nfunc is_null() -> bool:\n\treturn true\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.trigger]\nfunc trigger() -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.trigger_varied]\nfunc trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.reset_volume]\nfunc reset_volume() -> void:\n\treturn\n\t\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.reset_pitch]\nfunc reset_pitch() -> void:\n\treturn\n\t\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.reset_all]\nfunc reset_all() -> void:\n\treturn\n\n\n## A nerfed (does nothing) version of [method PooledAudioStreamPlayer3D.release]\nfunc release(p_finish_playing: bool = false) -> void:\n\treturn\n"
  },
  {
    "path": "addons/resonate/sound_manager/pool_entity.gd",
    "content": "class_name PoolEntity\nextends RefCounted\n## An abstract/static class to house all of the common PooledAudioStreamPlayer* functionality.\n\n\nconst ResonateSettings = preload(\"../shared/resonate_settings.gd\")\n\nenum FollowType {DISABLED, IDLE, PHYSICS}\n\n\n## Create a new PooledAudioStreamPlayer*.\nstatic func create(p_base) -> Variant:\n\tp_base.process_mode = Node.PROCESS_MODE_ALWAYS\n\t\n\treturn p_base\n\n\n## Configure a PooledAudioStreamPlayer*.\nstatic func configure(p_base, p_streams: Array, p_reserved: bool, p_bus: String, p_poly: bool, p_volume: float, p_pitch: float, p_mode: Node.ProcessMode) -> void:\n\tp_base.streams = p_streams\n\tp_base.poly = p_poly\n\tp_base.bus = p_bus\n\tp_base.process_mode = p_mode\n\tp_base.reserved = p_reserved\n\tp_base.releasing = false\n\tp_base.volume_db = p_volume if not p_poly else 0.0\n\tp_base.pitch_scale = p_pitch if not p_poly else 1.0\n\tp_base.base_volume = p_volume\n\tp_base.base_pitch = p_pitch\n\tp_base.follow_target = null\n\tp_base.follow_type = FollowType.DISABLED\n\t\n\tif not p_base.poly:\n\t\treturn\n\t\n\tvar _settings = ResonateSettings.new()\n\t\n\tvar max_polyphony = ProjectSettings.get_setting(\n\t\t_settings.MAX_POLYPHONY_SETTING_NAME,\n\t\t_settings.MAX_POLYPHONY_SETTING_DEFAULT)\n\t\n\tp_base.stream = AudioStreamPolyphonic.new()\n\tp_base.max_polyphony = max_polyphony\n\tp_base.stream.polyphony = max_polyphony\n\t\n\n## Attach a PooledAudioStreamPlayer* to a position or node.\nstatic func attach_to(p_base, p_node: Variant) -> void:\n\tif p_node == null:\n\t\treturn\n\t\n\tif ResonateUtils.is_vector(p_node):\n\t\tp_base.global_position = p_node\n\t\n\tif ResonateUtils.is_node(p_node):\n\t\tp_base.follow_target = p_node\n\t\tp_base.follow_type = FollowType.IDLE\n\n\n## Stop the underlying stream player if not polyphonic streams are currently playing.\nstatic func update_poly_playback_state(p_base) -> void:\n\tif not p_base.poly:\n\t\treturn\n\t\n\tif not p_base.playing:\n\t\treturn\n\t\n\tvar playback = p_base.get_stream_playback() as AudioStreamPlaybackPolyphonic\n\t\n\tif playback == null:\n\t\tp_base.stop()\n\t\treturn\n\t\n\tfor stream_id in p_base.poly_stream_ids:\n\t\tif playback.is_stream_playing(stream_id):\n\t\t\treturn\n\t\n\tp_base.poly_stream_ids.clear()\n\tp_base.stop()\n\t\n\n## Sync a PooledAudioStreamPlayer*'s transform with its target's when applicable. \nstatic func sync_process(p_base) -> void:\n\tif p_base.follow_target == null:\n\t\treturn\n\t\t\n\tif not is_instance_valid(p_base.follow_target):\n\t\treturn\n\t\t\n\tif p_base.follow_type != FollowType.IDLE:\n\t\treturn\n\t\n\tp_base.global_position = p_base.follow_target.global_position\n\t\n\n## Sync a PooledAudioStreamPlayer*'s transform with its target's\n## when applicable during the physics step. \nstatic func sync_physics_process(p_base) -> void:\n\tif p_base.follow_target == null:\n\t\treturn\n\t\t\n\tif not is_instance_valid(p_base.follow_target):\n\t\treturn\n\t\t\n\tif p_base.follow_type != FollowType.PHYSICS:\n\t\treturn\n\t\n\tp_base.global_position = p_base.follow_target.global_position\n\n\n## Trigger a PooledAudioStreamPlayer*.\nstatic func trigger(p_base, p_varied: bool, p_pitch: float, p_volume: float) -> void:\n\tif p_base.streams.size() == 0:\n\t\tpush_warning(\"Resonate - The player [%s] does not contain any streams, ensure you're using the SoundManager to instance it correctly.\" % p_base.name)\n\t\treturn\n\t\t\n\tvar next_stream = p_base.streams.pick_random()\n\t\n\tif not p_base.poly and p_varied:\n\t\tp_base.volume_db = p_volume\n\t\tp_base.pitch_scale = p_pitch\n\t\n\tif not p_base.poly:\n\t\tp_base.stream = next_stream\n\t\tp_base.play()\n\t\treturn\n\t\n\tif not p_base.playing:\n\t\tp_base.play()\n\t\n\tvar playback = p_base.get_stream_playback() as AudioStreamPlaybackPolyphonic\n\tvar stream_volume = p_volume if p_varied else p_base.base_volume\n\tvar stream_pitch = p_pitch if p_varied else p_base.base_pitch\n\t\n\tvar stream_id = playback.play_stream(next_stream, 0, stream_volume, stream_pitch)\n\t\n\tif stream_id != AudioStreamPlaybackPolyphonic.INVALID_ID:\n\t\tp_base.poly_stream_ids.append(stream_id)\n\n\n## Reset the volume of a PooledAudioStreamPlayer*.\nstatic func reset_volume(p_base) -> void:\n\tp_base.volume_db = p_base.base_volume\n\t\n\n## Reset the pitch of a PooledAudioStreamPlayer*.\nstatic func reset_pitch(p_base) -> void:\n\tp_base.pitch_scale = p_base.base_pitch\n\t\n\n## Reset both the volume and pitch of a PooledAudioStreamPlayer*.\nstatic func reset_all(p_base) -> void:\n\tp_base.volume_db = p_base.base_volume\n\tp_base.pitch_scale = p_base.base_pitch\n\n\n## Release a PooledAudioStreamPlayer* back into the pool.\nstatic func release(p_base, p_finish_playing: bool) -> void:\n\tif p_base.releasing:\n\t\treturn\n\t\t\n\tvar has_loops = p_base.streams.any(ResonateUtils.is_stream_looped)\n\t\n\tif p_finish_playing and has_loops:\n\t\tpush_warning(\"Resonate - The player [%s] has looping streams and therefore will never release itself back to the pool (as playback continues indefinitely). It will be forced to stop.\" % p_base.name)\n\t\tp_base.stop()\n\t\t\n\tif not p_finish_playing:\n\t\tp_base.stop()\n\t\t\n\tp_base.reserved = false\n\tp_base.process_mode = Node.PROCESS_MODE_ALWAYS\n\tp_base.releasing = true\n\tp_base.released.emit()\n\n\n## A callback to release a PooledAudioStreamPlayer* when it finishes playing.\nstatic func finished(p_base) -> void:\n\tif p_base.reserved:\n\t\treturn\n\t\t\n\tp_base.release()\n"
  },
  {
    "path": "addons/resonate/sound_manager/pooled_audio_stream_player.gd",
    "content": "class_name PooledAudioStreamPlayer\nextends AudioStreamPlayer\n## An extension of AudioStreamPlayer that manages sequential and \n## polyphonic playback as part of a pool of players.\n\n\n## Emitted when this player has been released and should return to the pool.\nsignal released\n\n## Whether this player has been reserved.\nvar reserved: bool\n\n## Whether this player is in the process of being released.\nvar releasing: bool\n\n## Whether this player has been configured to support polyphonic playback.\nvar poly: bool\n\n## A collection of all active polyphonic stream IDs.\nvar poly_stream_ids: Array\n\n## The collection of streams configured on this player.\nvar streams: Array\n\n## The base/fallback volume of this player.\nvar base_volume: float\n\n## The base/fallback pitch of this player.\nvar base_pitch: float\n\n## The target this player should follow in 2D or 3D space.\nvar follow_target: Node\n\n## When the player should sync its transform when following a target.\nvar follow_type: PoolEntity.FollowType\n\n\n# ------------------------------------------------------------------------------\n# Lifecycle methods\n# ------------------------------------------------------------------------------\n\n\nfunc _ready() -> void:\n\tfinished.connect(_on_finished)\n\t\n\nfunc _process(_p_delta) -> void:\n\tPoolEntity.sync_process(self)\n\tPoolEntity.update_poly_playback_state(self)\n\t\n\nfunc _physics_process(_p_delta) -> void:\n\tPoolEntity.sync_physics_process(self)\n\n\n# ------------------------------------------------------------------------------\n# Public methods\n# ------------------------------------------------------------------------------\n\n\n## Returns a new player.\nstatic func create() -> PooledAudioStreamPlayer:\n\treturn PoolEntity.create(PooledAudioStreamPlayer.new())\n\n\n## Whether this player is a [NullPooledAudioStreamPlayer], or real instance.\nfunc is_null() -> bool:\n\treturn false\n\n\n## Configure this player with the given streams and charateristics.\nfunc configure(p_streams: Array, p_reserved: bool, p_bus: String, p_poly: bool, p_volume: float, p_pitch: float, p_mode: Node.ProcessMode) -> void:\n\tPoolEntity.configure(self, p_streams, p_reserved, p_bus, p_poly, p_volume, p_pitch, p_mode)\n\n\n## Attach this player to a 2D/3D position or node.\nfunc attach_to(p_node: Variant) -> void:\n\tPoolEntity.attach_to(self, p_node)\n\n\n## Trigger (play) a random variation associated with this player.\nfunc trigger() -> void:\n\tPoolEntity.trigger(self, false, 1.0, 0.0)\n\n\n## Trigger (play) a random variation associated with this\n## player with the given volume and pitch settings.\nfunc trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:\n\tPoolEntity.trigger(self, true, p_pitch, p_volume)\n\n\n## Reset the volume of this player back to the default set in its bank.\nfunc reset_volume() -> void:\n\tPoolEntity.reset_volume(self)\n\t\n\n## Reset the pitch of this player back to the default set in its bank.\nfunc reset_pitch() -> void:\n\tPoolEntity.reset_pitch(self)\n\t\n\n## Reset both the volume and pitch of this player back to the default set in its bank.\nfunc reset_all() -> void:\n\tPoolEntity.reset_all(self)\n\n\n## Release this player back to the pool, and optionally\n## wait for it to finish playing before doing so.\nfunc release(p_finish_playing: bool = false) -> void:\n\tPoolEntity.release(self, p_finish_playing)\n\n\n# ------------------------------------------------------------------------------\n# Private methods\n# ------------------------------------------------------------------------------\n\n\nfunc _on_finished() -> void:\n\tPoolEntity.finished(self)\n"
  },
  {
    "path": "addons/resonate/sound_manager/pooled_audio_stream_player_2d.gd",
    "content": "class_name PooledAudioStreamPlayer2D\nextends AudioStreamPlayer2D\n## An extension of AudioStreamPlayer2D that manages sequential and \n## polyphonic playback as part of a pool of players.\n\n\n## Emitted when this player has been released and should return to the pool.\nsignal released\n\n## Whether this player has been reserved.\nvar reserved: bool\n\n## Whether this player is in the process of being released.\nvar releasing: bool\n\n## Whether this player has been configured to support polyphonic playback.\nvar poly: bool\n\n## A collection of all active polyphonic stream IDs.\nvar poly_stream_ids: Array\n\n## The collection of streams configured on this player.\nvar streams: Array\n\n## The base/fallback volume of this player.\nvar base_volume: float\n\n## The base/fallback pitch of this player.\nvar base_pitch: float\n\n## The target this player should follow in 2D or 3D space.\nvar follow_target: Node\n\n## When the player should sync its transform when following a target.\nvar follow_type: PoolEntity.FollowType\n\n\n# ------------------------------------------------------------------------------\n# Lifecycle methods\n# ------------------------------------------------------------------------------\n\n\nfunc _ready() -> void:\n\tfinished.connect(_on_finished)\n\t\n\nfunc _process(_p_delta) -> void:\n\tPoolEntity.sync_process(self)\n\tPoolEntity.update_poly_playback_state(self)\n\t\n\nfunc _physics_process(_p_delta) -> void:\n\tPoolEntity.sync_physics_process(self)\n\n\n# ------------------------------------------------------------------------------\n# Public methods\n# ------------------------------------------------------------------------------\n\n\n## Returns a new player.\nstatic func create() -> PooledAudioStreamPlayer2D:\n\treturn PoolEntity.create(PooledAudioStreamPlayer2D.new())\n\n\n## Whether this player is a [NullPooledAudioStreamPlayer2D], or real instance.\nfunc is_null() -> bool:\n\treturn false\n\n\n## Configure this player with the given streams and charateristics.\nfunc configure(p_streams: Array, p_reserved: bool, p_bus: String, p_poly: bool, p_volume: float, p_pitch: float, p_mode: Node.ProcessMode) -> void:\n\tPoolEntity.configure(self, p_streams, p_reserved, p_bus, p_poly, p_volume, p_pitch, p_mode)\n\t\n\n## Attach this player to a 2D/3D position or node.\nfunc attach_to(p_node: Variant) -> void:\n\tPoolEntity.attach_to(self, p_node)\n\n\n## Trigger (play) a random variation associated with this player.\nfunc trigger() -> void:\n\tPoolEntity.trigger(self, false, 1.0, 0.0)\n\t\n\n## Trigger (play) a random variation associated with this\n## player with the given volume and pitch settings.\nfunc trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:\n\tPoolEntity.trigger(self, true, p_pitch, p_volume)\n\n\n## Reset the volume of this player back to the default set in its bank.\nfunc reset_volume() -> void:\n\tPoolEntity.reset_volume(self)\n\t\n\n## Reset the pitch of this player back to the default set in its bank.\nfunc reset_pitch() -> void:\n\tPoolEntity.reset_pitch(self)\n\t\n\n## Reset both the volume and pitch of this player back to the default set in its bank.\nfunc reset_all() -> void:\n\tPoolEntity.reset_all(self)\n\n\n## Release this player back to the pool, and optionally\n## wait for it to finish playing before doing so.\nfunc release(p_finish_playing: bool = false) -> void:\n\tPoolEntity.release(self, p_finish_playing)\n\n\n# ------------------------------------------------------------------------------\n# Private methods\n# ------------------------------------------------------------------------------\n\n\nfunc _on_finished() -> void:\n\tPoolEntity.finished(self)\n"
  },
  {
    "path": "addons/resonate/sound_manager/pooled_audio_stream_player_3d.gd",
    "content": "class_name PooledAudioStreamPlayer3D\nextends AudioStreamPlayer3D\n## An extension of AudioStreamPlayer3D that manages sequential and \n## polyphonic playback as part of a pool of players.\n\n\n## Emitted when this player has been released and should return to the pool.\nsignal released\n\n## Whether this player has been reserved.\nvar reserved: bool\n\n## Whether this player is in the process of being released.\nvar releasing: bool\n\n## Whether this player has been configured to support polyphonic playback.\nvar poly: bool\n\n## A collection of all active polyphonic stream IDs.\nvar poly_stream_ids: Array\n\n## The collection of streams configured on this player.\nvar streams: Array\n\n## The base/fallback volume of this player.\nvar base_volume: float\n\n## The base/fallback pitch of this player.\nvar base_pitch: float\n\n## The target this player should follow in 2D or 3D space.\nvar follow_target: Node\n\n## When the player should sync its transform when following a target.\nvar follow_type: PoolEntity.FollowType\n\n\n# ------------------------------------------------------------------------------\n# Lifecycle methods\n# ------------------------------------------------------------------------------\n\n\nfunc _ready() -> void:\n\tfinished.connect(_on_finished)\n\t\n\nfunc _process(_p_delta) -> void:\n\tPoolEntity.sync_process(self)\n\tPoolEntity.update_poly_playback_state(self)\n\t\n\nfunc _physics_process(_p_delta) -> void:\n\tPoolEntity.sync_physics_process(self)\n\n\n# ------------------------------------------------------------------------------\n# Public methods\n# ------------------------------------------------------------------------------\n\n\n## Returns a new player.\nstatic func create() -> PooledAudioStreamPlayer3D:\n\treturn PoolEntity.create(PooledAudioStreamPlayer3D.new())\n\n\n## Whether this player is a [NullPooledAudioStreamPlayer3D], or real instance.\nfunc is_null() -> bool:\n\treturn false\n\n\n## Configure this player with the given streams and charateristics.\nfunc configure(p_streams: Array, p_reserved: bool, p_bus: String, p_poly: bool, p_volume: float, p_pitch: float, p_mode: Node.ProcessMode) -> void:\n\tPoolEntity.configure(self, p_streams, p_reserved, p_bus, p_poly, p_volume, p_pitch, p_mode)\n\n\n## Attach this player to a 2D/3D position or node.\nfunc attach_to(p_node: Variant) -> void:\n\tPoolEntity.attach_to(self, p_node)\n\n\n## Trigger (play) a random variation associated with this player.\nfunc trigger() -> void:\n\tPoolEntity.trigger(self, false, 1.0, 0.0)\n\n\n## Trigger (play) a random variation associated with this\n## player with the given volume and pitch settings.\nfunc trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void:\n\tPoolEntity.trigger(self, true, p_pitch, p_volume)\n\n\n## Reset the volume of this player back to the default set in its bank.\nfunc reset_volume() -> void:\n\tPoolEntity.reset_volume(self)\n\t\n\n## Reset the pitch of this player back to the default set in its bank.\nfunc reset_pitch() -> void:\n\tPoolEntity.reset_pitch(self)\n\t\n\n## Reset both the volume and pitch of this player back to the default set in its bank.\nfunc reset_all() -> void:\n\tPoolEntity.reset_all(self)\n\n\n## Release this player back to the pool, and optionally\n## wait for it to finish playing before doing so.\nfunc release(p_finish_playing: bool = false) -> void:\n\tPoolEntity.release(self, p_finish_playing)\n\n\n# ------------------------------------------------------------------------------\n# Private methods\n# ------------------------------------------------------------------------------\n\n\nfunc _on_finished() -> void:\n\tPoolEntity.finished(self)\n"
  },
  {
    "path": "addons/resonate/sound_manager/sound_bank.gd",
    "content": "class_name SoundBank\nextends Node\n## A container used to store & group sound events in your scene.\n\n\n## This bank's unique identifier.\n@export var label: String\n\n## The bus to use for all sound events in this bank.[br][br]\n## [b]Note:[/b] this will override the bus set in your project settings (Audio/Manager/Sound/Bank)\n@export var bus: String\n\n## The underlying process mode for all sound events in this bank.\n@export var mode: Node.ProcessMode\n\n## The collection of sound events associated with this bank.\n@export var events: Array[SoundEventResource]\n"
  },
  {
    "path": "addons/resonate/sound_manager/sound_bank.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://ntgipyp6jv34\"\npath=\"res://.godot/imported/sound_bank.svg-d1b3f43714c54a9c122e6a364a95c7d8.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://addons/resonate/sound_manager/sound_bank.svg\"\ndest_files=[\"res://.godot/imported/sound_bank.svg-d1b3f43714c54a9c122e6a364a95c7d8.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": "addons/resonate/sound_manager/sound_event_resource.gd",
    "content": "class_name SoundEventResource\nextends Resource\n## The container used to store the details of a sound event.\n\n\n## This sound event's unique identifier.\n@export var name: String = \"\"\n\n## The bus to use for all sound events in this bank.[br][br]\n## [b]Note:[/b] this will override the bus set in this events sound bank, \n## or your project settings (Audio/Manager/Sound/Bank)\n@export var bus: String = \"\"\n\n## The volume of the sound event.\n@export_range(-80.0, 6.0, 0.1, \"suffix:dB\") var volume: float = 0.0\n\n## The pitch of the sound event.\n@export var pitch: float = 1.0\n\n## The collection of audio streams (variations) associated with this sound event.\n@export var streams: Array[AudioStream]\n"
  },
  {
    "path": "addons/resonate/sound_manager/sound_manager.gd",
    "content": "extends Node\n## The SoundManager is responsible for all sound events in your game.\n##\n## It manages pools of 1D, 2D, and 3D audio stream players, which can be used\n## for single-shot sound events, or reserved by scripts for repetitive & exclusive use.\n## Sound events can contain many variations which will be chosen and played at random.\n## Playback can be achieved both sequentially and polyphonically.\n##\n## @tutorial(View example scenes): https://github.com/hugemenace/resonate/tree/main/examples\n\n\nconst ResonateSettings = preload(\"../shared/resonate_settings.gd\")\nvar _settings = ResonateSettings.new()\n\n## Emitted only once when the SoundManager has finished setting up and \n## is ready to play or instance sound events.\nsignal loaded\n\n## Emitted every time the SoundManager detects that a SoundBank has\n## been added or removed from the scene tree.\nsignal banks_updated\n\n## Emitted every time one of the player pools is updated.\nsignal pools_updated\n\n## Emitted whenever [signal SoundManager.loaded], [signal SoundManager.banks_updated],\n## or [signal SoundManager.pools_updated] is emitted.\nsignal updated\n\n## Whether the SoundManager has completed setup and is ready to play or instance sound events.\nvar has_loaded: bool = false\n\nvar _1d_players: Array[PooledAudioStreamPlayer] = []\nvar _2d_players: Array[PooledAudioStreamPlayer2D] = []\nvar _3d_players: Array[PooledAudioStreamPlayer3D] = []\nvar _event_table: Dictionary = {}\nvar _event_table_hash: int\n\n\n# ------------------------------------------------------------------------------\n# Lifecycle methods\n# ------------------------------------------------------------------------------\n\n\nfunc _init():\n\tprocess_mode = Node.PROCESS_MODE_ALWAYS\n\n\nfunc _ready() -> void:\n\t_initialise_pool(ProjectSettings.get_setting(\n\t\t\t_settings.POOL_1D_SIZE_SETTING_NAME,\n\t\t\t_settings.POOL_1D_SIZE_SETTING_DEFAULT),\n\t\t\t_create_player_1d)\n\t\t\t\n\t_initialise_pool(ProjectSettings.get_setting(\n\t\t\t_settings.POOL_2D_SIZE_SETTING_NAME,\n\t\t\t_settings.POOL_2D_SIZE_SETTING_DEFAULT),\n\t\t\t_create_player_2d)\n\t\t\t\n\t_initialise_pool(ProjectSettings.get_setting(\n\t\t\t_settings.POOL_3D_SIZE_SETTING_NAME,\n\t\t\t_settings.POOL_3D_SIZE_SETTING_DEFAULT),\n\t\t\t_create_player_3d)\n\t\n\t_auto_add_events()\n\t\n\tvar scene_root = get_tree().root.get_tree()\n\tscene_root.node_added.connect(_on_scene_node_added)\n\tscene_root.node_removed.connect(_on_scene_node_removed)\n\t\n\nfunc _process(_p_delta) -> void:\n\tif _event_table_hash != _event_table.hash():\n\t\t_event_table_hash = _event_table.hash()\n\t\tbanks_updated.emit()\n\t\tupdated.emit()\n\t\t\n\tif has_loaded:\n\t\treturn\n\t\t\n\thas_loaded = true\n\tloaded.emit()\n\tupdated.emit()\n\n\n# ------------------------------------------------------------------------------\n# Public methods\n# ------------------------------------------------------------------------------\n\n\n## Returns a new Null player (null object pattern) which mimics a [PooledAudioStreamPlayer],\n## allowing you to call methods such as [method PooledAudioStreamPlayer.trigger]\n## without the need to wrap the call in a null check.\nfunc null_instance() -> NullPooledAudioStreamPlayer:\n\treturn NullPooledAudioStreamPlayer.new()\n\t\n\n## Returns a new Null player (null object pattern) which mimics a [PooledAudioStreamPlayer2D],\n## allowing you to call methods such as [method PooledAudioStreamPlayer2D.trigger]\n## without the need to wrap the call in a null check.\nfunc null_instance_2d() -> NullPooledAudioStreamPlayer2D:\n\treturn NullPooledAudioStreamPlayer2D.new()\n\t\n\n## Returns a new Null player (null object pattern) which mimics a [PooledAudioStreamPlayer3D],\n## allowing you to call methods such as [method PooledAudioStreamPlayer3D.trigger]\n## without the need to wrap the call in a null check.\nfunc null_instance_3d() -> NullPooledAudioStreamPlayer3D:\n\treturn NullPooledAudioStreamPlayer3D.new()\n\n\n## Used to determine whether the given [b]p_instance[/b] variable can be instantiated. It will return \n## true if the SoundManager hasn't loaded yet, if the instance is already instantiated, \n## or if the instance has been instantiated but is currently being released.\nfunc should_skip_instancing(p_instance) -> bool:\n\tif not has_loaded:\n\t\treturn true\n\t\t\n\tif p_instance != null and p_instance.releasing:\n\t\treturn true\n\t\n\tif p_instance != null and not p_instance.is_null():\n\t\treturn true\n\t\t\n\treturn false\n\n\n## This a shorthand method used to instantiate a new instance while optionally configuring it \n## to be automatically released when the given [b]p_base[/b] is removed from the scene tree.[br][br]\n## The [b]p_factory[/b] callable is used to create the instance required. See example below:[br][br]\n## [codeblock]\n## _instance_note_one = SoundManager.quick_instance(_instance_note_one,\n##\t\t\tSoundManager.instance.bind(\"example\", \"one\"), self)\n## [/codeblock]\nfunc quick_instance(p_instance, p_factory: Callable, p_base: Node = null, p_finish_playing: bool = false) -> Variant:\n\tif should_skip_instancing(p_instance):\n\t\treturn\n\t\t\n\tvar new_instance = p_factory.call()\n\t\n\tif p_base != null:\n\t\trelease_on_exit(p_base, new_instance, p_finish_playing)\n\t\t\n\treturn new_instance\n\n\n## Play a sound event from a SoundBank.\nfunc play(p_bank_label: String, p_event_name: String, p_bus: String = \"\") -> void:\n\tvar instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, null)\n\tinstance.trigger()\n\tinstance.release(true)\n\t\n\t\n## Play a sound event from a SoundBank at a specific [b]Vector2[/b] or [b]Vector3[/b] position.\nfunc play_at_position(p_bank_label: String, p_event_name: String, p_position, p_bus: String = \"\") -> void:\n\tvar instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, p_position)\n\tinstance.trigger()\n\tinstance.release(true)\n\t\n\n## Play a sound event from a SoundBank on a [b]Node2D[/b] or [b]Node3D[/b]. This causes the sound to\n## synchronise with the Node's global position - causing it to move in 2D or 3D space along with the Node.\nfunc play_on_node(p_bank_label: String, p_event_name: String, p_node, p_bus: String = \"\") -> void:\n\tvar instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, p_node)\n\tinstance.trigger()\n\tinstance.release(true)\n\t\n\n## Play a sound event from a SoundBank with the provided pitch and/or volume.\nfunc play_varied(p_bank_label: String, p_event_name: String, p_pitch: float = 1.0, p_volume: float = 0.0, p_bus: String = \"\") -> void:\n\tvar instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, null)\n\tinstance.trigger_varied(p_pitch, p_volume)\n\tinstance.release(true)\n\t\n\n## Play a sound event from a SoundBank at a specific [b]Vector2[/b] or [b]Vector3[/b] \n## position with the provided pitch and/or volume.\nfunc play_at_position_varied(p_bank_label: String, p_event_name: String, p_position, p_pitch: float = 1.0, p_volume: float = 0.0, p_bus: String = \"\") -> void:\n\tvar instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, p_position)\n\tinstance.trigger_varied(p_pitch, p_volume)\n\tinstance.release(true)\n\t\n\n## Play a sound event from a SoundBank on a [b]Node2D[/b] or [b]Node3D[/b] with the provided pitch \n## and/or volume. This causes the sound to synchronise with the Node's global position - causing \n## it to move in 2D or 3D space along with the Node.\nfunc play_on_node_varied(p_bank_label: String, p_event_name: String, p_node, p_pitch: float = 1.0, p_volume: float = 0.0, p_bus: String = \"\") -> void:\n\tvar instance = _instance_manual(p_bank_label, p_event_name, false, p_bus, false, p_node)\n\tinstance.trigger_varied(p_pitch, p_volume)\n\tinstance.release(true)\n\n\n## Returns a reserved [PooledAudioStreamPlayer] for you to use exclusively until it is told to \n## [method PooledAudioStreamPlayer.release] or is automatically released when registered\n## with [method SoundManager.release_on_exit].\nfunc instance(p_bank_label: String, p_event_name: String, p_bus: String = \"\") -> Variant:\n\treturn _instance_manual(p_bank_label, p_event_name, true, p_bus, false, null)\n\t\n\n## Returns a reserved [PooledAudioStreamPlayer2D] or [PooledAudioStreamPlayer3D] (depending on the \n## type of [b]p_position[/b]) placed at a specific 2D or 3D position in the world. You will have \n## exclusive use of it until it is told to [method PooledAudioStreamPlayer.release] or is automatically \n## released when registered with [method SoundManager.release_on_exit].\nfunc instance_at_position(p_bank_label: String, p_event_name: String, p_position, p_bus: String = \"\") -> Variant:\n\treturn _instance_manual(p_bank_label, p_event_name, true, p_bus, false, p_position)\n\t\n\n## Returns a reserved [PooledAudioStreamPlayer2D] or [PooledAudioStreamPlayer3D] (depending on the \n## type of [b]p_node[/b]) which will synchronise its global position with [b]p_node[/b]. You will have \n## exclusive use of it until it is told to [method PooledAudioStreamPlayer.release] or is automatically \n## released when registered with [method SoundManager.release_on_exit].\nfunc instance_on_node(p_bank_label: String, p_event_name: String, p_node, p_bus: String = \"\") -> Variant:\n\treturn _instance_manual(p_bank_label, p_event_name, true, p_bus, false, p_node)\n\t\n\n## Returns a reserved [PooledAudioStreamPlayer] for you to use exclusively until it is told to \n## [method PooledAudioStreamPlayer.release] or is automatically released when registered\n## with [method SoundManager.release_on_exit].[br][br]\n## [b]Note:[/b] This method will mark the reserved player as polyphonic (able to play \n## multiple event variations simultaneously.)\nfunc instance_poly(p_bank_label: String, p_event_name: String, p_bus: String = \"\") -> Variant:\n\treturn _instance_manual(p_bank_label, p_event_name, true, p_bus, true, null)\n\t\n\n## Returns a reserved [PooledAudioStreamPlayer2D] or [PooledAudioStreamPlayer3D] (depending on the \n## type of [b]p_position[/b]) placed at a specific 2D or 3D position in the world. You will have \n## exclusive use of it until it is told to [method PooledAudioStreamPlayer.release] or is automatically \n## released when registered with [method SoundManager.release_on_exit].[br][br]\n## [b]Note:[/b] This method will mark the reserved player as polyphonic (able to play \n## multiple event variations simultaneously.)\nfunc instance_at_position_poly(p_bank_label: String, p_event_name: String, p_position, p_bus: String = \"\") -> Variant:\n\treturn _instance_manual(p_bank_label, p_event_name, true, p_bus, true, p_position)\n\t\n\n## Returns a reserved [PooledAudioStreamPlayer2D] or [PooledAudioStreamPlayer3D] (depending on the \n## type of [b]p_node[/b]) which will synchronise its global position with [b]p_node[/b]. You will have \n## exclusive use of it until it is told to [method PooledAudioStreamPlayer.release] or is automatically \n## released when registered with [method SoundManager.release_on_exit].[br][br]\n## [b]Note:[/b] This method will mark the reserved player as polyphonic (able to play \n## multiple event variations simultaneously.)\nfunc instance_on_node_poly(p_bank_label: String, p_event_name: String, p_node, p_bus: String = \"\") -> Variant:\n\treturn _instance_manual(p_bank_label, p_event_name, true, p_bus, true, p_node)\n\n\n## Will automatically release the given [b]p_instance[/b] when the provided \n## [b]p_base[/b] is removed from the scene tree.\nfunc release_on_exit(p_base: Node, p_instance: Node, p_finish_playing: bool = false) -> void:\n\tif p_instance == null or p_base == null:\n\t\treturn\n\t\n\tp_base.tree_exiting.connect(p_instance.release.bind(p_finish_playing))\n\n\n## Will automatically release the given [b]p_instance[/b] when the provided \n## [b]p_base[/b] is removed from the scene tree.[br][br]\n## [b]Note:[/b] This method has been deprecated, please use [method SoundManager.release_on_exit] instead.\n## @deprecated\nfunc auto_release(p_base: Node, p_instance: Node, p_finish_playing: bool = false) -> Variant:\n\tpush_warning(\"Resonate - auto_release has been deprecated, please use release_on_exit instead.\")\n\t\n\tif p_instance == null:\n\t\treturn p_instance\n\t\n\trelease_on_exit(p_base, p_instance, p_finish_playing)\n\t\n\treturn p_instance\n\n\n## Manually add a new SoundBank into the event cache.\nfunc add_bank(p_bank: SoundBank) -> void:\n\t_add_bank(p_bank)\n\n\n## Remove the provided bank from the event cache.\nfunc remove_bank(p_bank_label: String) -> void:\n\tif not _event_table.has(p_bank_label):\n\t\treturn\n\t\t\n\t_event_table.erase(p_bank_label)\n\n\n## Clear all banks from the event cache.\nfunc clear_banks() -> void:\n\t_event_table.clear()\n\n\n# ------------------------------------------------------------------------------\n# Private methods\n# ------------------------------------------------------------------------------\n\n\nfunc _on_scene_node_added(p_node: Node) -> void:\n\tif not p_node is SoundBank:\n\t\treturn\n\t\t\n\t_add_bank(p_node)\n\t\n\t\nfunc _on_scene_node_removed(p_node: Node) -> void:\n\tif not p_node is SoundBank:\n\t\treturn\n\t\t\n\t_remove_bank(p_node)\n\n\nfunc _initialise_pool(p_size: int, p_creator_fn: Callable) -> void:\n\tfor i in p_size:\n\t\tp_creator_fn.call_deferred()\n\n\nfunc _auto_add_events() -> void:\n\tvar sound_banks = ResonateUtils.find_all_nodes(self, \"SoundBank\")\n\t\n\tfor sound_bank in sound_banks:\n\t\t_add_bank(sound_bank)\n\t\t\n\t_event_table_hash = _event_table.hash()\n\t\t\n\nfunc _add_bank(p_bank: SoundBank) -> void:\n\tif _event_table.has(p_bank.label):\n\t\t_event_table[p_bank.label][\"ref_count\"] = \\\n\t\t\t\t_event_table[p_bank.label][\"ref_count\"] + 1\n\t\t\n\t\treturn\n\t\t\n\t_event_table[p_bank.label] = {\n\t\t\"name\": p_bank.label,\n\t\t\"bus\": p_bank.bus,\n\t\t\"mode\": p_bank.mode,\n\t\t\"events\": _create_events(p_bank.events),\n\t\t\"ref_count\": 1,\n\t}\n\t\n\nfunc _remove_bank(p_bank: SoundBank) -> void:\n\tif not _event_table.has(p_bank.label):\n\t\treturn\n\t\n\tif _event_table[p_bank.label][\"ref_count\"] == 1:\n\t\t_event_table.erase(p_bank.label)\n\t\treturn\n\t\n\t_event_table[p_bank.label][\"ref_count\"] = \\\n\t\t\t_event_table[p_bank.label][\"ref_count\"] - 1\n\t\n\nfunc _create_events(p_events: Array[SoundEventResource]) -> Dictionary:\n\tvar events = {}\n\t\n\tfor event in p_events:\n\t\tevents[event.name] = {\n\t\t\t\"name\": event.name,\n\t\t\t\"bus\": event.bus,\n\t\t\t\"volume\": event.volume,\n\t\t\t\"pitch\": event.pitch,\n\t\t\t\"streams\": event.streams,\n\t\t}\n\t\t\n\treturn events\n\n\nfunc _get_bus(p_bank_bus: String, p_event_bus: String) -> String:\n\tif p_event_bus != null and p_event_bus != \"\":\n\t\treturn p_event_bus\n\t\n\tif p_bank_bus != null and p_bank_bus != \"\":\n\t\treturn p_bank_bus\n\t\t\n\treturn ProjectSettings.get_setting(\n\t\t_settings.SOUND_BANK_BUS_SETTING_NAME,\n\t\t_settings.SOUND_BANK_BUS_SETTING_DEFAULT)\n\n\nfunc _instance_manual(p_bank_label: String, p_event_name: String, p_reserved: bool = false, p_bus: String = \"\", p_poly: bool = false, p_attachment = null) -> Variant:\n\tif not has_loaded:\n\t\tpush_error(\"Resonate - The event [%s] on bank [%s] can't be instanced as the SoundManager has not loaded yet. Use the [loaded] signal/event to determine when it is ready.\" % [p_event_name, p_bank_label])\n\t\treturn _get_null_player(p_attachment)\n\t\t\n\tif not _event_table.has(p_bank_label):\n\t\tpush_error(\"Resonate - Tried to instance the event [%s] from an unknown bank [%s].\" % [p_event_name, p_bank_label])\n\t\treturn _get_null_player(p_attachment)\n\t\t\n\tif not _event_table[p_bank_label][\"events\"].has(p_event_name):\n\t\tpush_error(\"Resonate - Tried to instance an unknown event [%s] from the bank [%s].\" % [p_event_name, p_bank_label])\n\t\treturn _get_null_player(p_attachment)\n\t\n\tvar bank = _event_table[p_bank_label] as Dictionary\n\tvar event = bank[\"events\"][p_event_name] as Dictionary\n\t\n\tif event.streams.size() == 0:\n\t\tpush_error(\"Resonate - The event [%s] on bank [%s] has no streams, you'll need to add one at minimum.\" % [p_event_name, p_bank_label])\n\t\treturn _get_null_player(p_attachment)\n\t\t\n\tvar player = _get_player(p_attachment)\n\t\n\tif player == null:\n\t\tpush_warning(\"Resonate - The event [%s] on bank [%s] can't be instanced; no pooled players available.\" % [p_event_name, p_bank_label])\n\t\treturn _get_null_player(p_attachment)\n\t\t\n\tvar bus = p_bus if p_bus != \"\" else _get_bus(bank.bus, event.bus)\n\t\n\tplayer.configure(event.streams, p_reserved, bus, p_poly, event.volume, event.pitch, bank.mode)\n\tplayer.attach_to(p_attachment)\n\t\n\treturn player\n\n\nfunc _is_player_free(p_player) -> bool:\n\treturn not p_player.playing and not p_player.reserved\n\n\nfunc _get_player_from_pool(p_pool: Array) -> Variant:\n\tif p_pool.size() == 0:\n\t\tpush_error(\"Resonate - Player pool has not been initialised. This can occur when calling a [play/instance*] function from [_ready].\")\n\t\treturn null\n\t\n\tfor player in p_pool:\n\t\tif _is_player_free(player):\n\t\t\treturn player\n\t\n\tpush_warning(\"Resonate - Player pool exhausted, consider increasing the pool size in the project settings (Audio/Manager/Pooling) or releasing unused audio stream players.\")\n\treturn null\n\t\n\nfunc _get_player_1d() -> PooledAudioStreamPlayer:\n\treturn _get_player_from_pool(_1d_players)\n\t\n\nfunc _get_player_2d() -> PooledAudioStreamPlayer2D:\n\treturn _get_player_from_pool(_2d_players)\n\t\n\nfunc _get_player_3d() -> PooledAudioStreamPlayer3D:\n\treturn _get_player_from_pool(_3d_players)\n\n\nfunc _get_player(p_attachment = null) -> Variant:\n\tif ResonateUtils.is_2d_node(p_attachment):\n\t\treturn _get_player_2d()\n\t\n\tif ResonateUtils.is_3d_node(p_attachment):\n\t\treturn _get_player_3d()\n\t\t\n\treturn _get_player_1d()\n\n\nfunc _get_null_player(p_attachment = null) -> Variant:\n\tif ResonateUtils.is_2d_node(p_attachment):\n\t\treturn null_instance_2d()\n\t\n\tif ResonateUtils.is_3d_node(p_attachment):\n\t\treturn null_instance_3d()\n\t\t\n\treturn null_instance()\n\n\nfunc _add_player_to_pool(p_player, p_pool) -> Variant:\n\tadd_child(p_player)\n\t\n\tp_player.released.connect(_on_player_released.bind(p_player))\n\tp_player.finished.connect(_on_player_finished.bind(p_player))\n\tp_pool.append(p_player)\n\t\n\treturn p_player\n\n\nfunc _create_player_1d() -> PooledAudioStreamPlayer:\n\treturn _add_player_to_pool(PooledAudioStreamPlayer.create(), _1d_players)\n\t\n\nfunc _create_player_2d() -> PooledAudioStreamPlayer2D:\n\treturn _add_player_to_pool(PooledAudioStreamPlayer2D.create(), _2d_players)\n\t\n\t\nfunc _create_player_3d() -> PooledAudioStreamPlayer3D:\n\treturn _add_player_to_pool(PooledAudioStreamPlayer3D.create(), _3d_players)\n\n\nfunc _on_player_released(p_player: Node) -> void:\n\tif p_player.playing:\n\t\treturn\n\t\n\tpools_updated.emit()\n\tupdated.emit()\n\t\n\nfunc _on_player_finished(p_player: Node) -> void:\n\tif p_player.reserved:\n\t\treturn\n\t\t\n\tpools_updated.emit()\n\tupdated.emit()\n"
  },
  {
    "path": "audio/music_stems/breakbeat_drums_stem.mp3.import",
    "content": "[remap]\n\nimporter=\"mp3\"\ntype=\"AudioStreamMP3\"\nuid=\"uid://clkjvyfm227dt\"\npath=\"res://.godot/imported/breakbeat_drums_stem.mp3-20b1452215af88bd5c19cbdc0120040c.mp3str\"\n\n[deps]\n\nsource_file=\"res://audio/music_stems/breakbeat_drums_stem.mp3\"\ndest_files=[\"res://.godot/imported/breakbeat_drums_stem.mp3-20b1452215af88bd5c19cbdc0120040c.mp3str\"]\n\n[params]\n\nloop=true\nloop_offset=0\nbpm=140.0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "audio/music_stems/breakbeat_melody_stem.mp3.import",
    "content": "[remap]\n\nimporter=\"mp3\"\ntype=\"AudioStreamMP3\"\nuid=\"uid://ba8e5llasqjhk\"\npath=\"res://.godot/imported/breakbeat_melody_stem.mp3-56160c730f32e58f1cb8c6befe129e69.mp3str\"\n\n[deps]\n\nsource_file=\"res://audio/music_stems/breakbeat_melody_stem.mp3\"\ndest_files=[\"res://.godot/imported/breakbeat_melody_stem.mp3-56160c730f32e58f1cb8c6befe129e69.mp3str\"]\n\n[params]\n\nloop=true\nloop_offset=0\nbpm=140.0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "audio/music_stems/breakbeat_pad_stem.mp3.import",
    "content": "[remap]\n\nimporter=\"mp3\"\ntype=\"AudioStreamMP3\"\nuid=\"uid://sjkaeg8p0qig\"\npath=\"res://.godot/imported/breakbeat_pad_stem.mp3-e58ead83629b9866131aff536e307866.mp3str\"\n\n[deps]\n\nsource_file=\"res://audio/music_stems/breakbeat_pad_stem.mp3\"\ndest_files=[\"res://.godot/imported/breakbeat_pad_stem.mp3-e58ead83629b9866131aff536e307866.mp3str\"]\n\n[params]\n\nloop=true\nloop_offset=0\nbpm=140.0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "audio/music_stems/house_bass_pad_stem.mp3.import",
    "content": "[remap]\n\nimporter=\"mp3\"\ntype=\"AudioStreamMP3\"\nuid=\"uid://bhbxnxeuk62q0\"\npath=\"res://.godot/imported/house_bass_pad_stem.mp3-bf6488bffc812b1b3a0bdcc8582523e2.mp3str\"\n\n[deps]\n\nsource_file=\"res://audio/music_stems/house_bass_pad_stem.mp3\"\ndest_files=[\"res://.godot/imported/house_bass_pad_stem.mp3-bf6488bffc812b1b3a0bdcc8582523e2.mp3str\"]\n\n[params]\n\nloop=true\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "audio/music_stems/house_drums_stem.mp3.import",
    "content": "[remap]\n\nimporter=\"mp3\"\ntype=\"AudioStreamMP3\"\nuid=\"uid://bcv4ev20vqxx2\"\npath=\"res://.godot/imported/house_drums_stem.mp3-44722d3fa5eaed738862780bc6f9a2c4.mp3str\"\n\n[deps]\n\nsource_file=\"res://audio/music_stems/house_drums_stem.mp3\"\ndest_files=[\"res://.godot/imported/house_drums_stem.mp3-44722d3fa5eaed738862780bc6f9a2c4.mp3str\"]\n\n[params]\n\nloop=true\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "audio/music_stems/house_melody_stem.mp3.import",
    "content": "[remap]\n\nimporter=\"mp3\"\ntype=\"AudioStreamMP3\"\nuid=\"uid://dq5s5n0folm21\"\npath=\"res://.godot/imported/house_melody_stem.mp3-a8a250926da00e8e86b09a739ce09e4b.mp3str\"\n\n[deps]\n\nsource_file=\"res://audio/music_stems/house_melody_stem.mp3\"\ndest_files=[\"res://.godot/imported/house_melody_stem.mp3-a8a250926da00e8e86b09a739ce09e4b.mp3str\"]\n\n[params]\n\nloop=true\nloop_offset=0\nbpm=0\nbeat_count=0\nbar_beats=4\n"
  },
  {
    "path": "audio/sounds/blaster.wav.import",
    "content": "[remap]\n\nimporter=\"wav\"\ntype=\"AudioStreamWAV\"\nuid=\"uid://cy4r18lxb4rbh\"\npath=\"res://.godot/imported/blaster.wav-efe4f879684d64f1eb83adf3227d91ab.sample\"\n\n[deps]\n\nsource_file=\"res://audio/sounds/blaster.wav\"\ndest_files=[\"res://.godot/imported/blaster.wav-efe4f879684d64f1eb83adf3227d91ab.sample\"]\n\n[params]\n\nforce/8_bit=false\nforce/mono=true\nforce/max_rate=false\nforce/max_rate_hz=44100\nedit/trim=false\nedit/normalize=false\nedit/loop_mode=0\nedit/loop_begin=0\nedit/loop_end=-1\ncompress/mode=0\n"
  },
  {
    "path": "audio/sounds/note_c5.wav.import",
    "content": "[remap]\n\nimporter=\"wav\"\ntype=\"AudioStreamWAV\"\nuid=\"uid://de550fklyhu88\"\npath=\"res://.godot/imported/note_c5.wav-0c5ae09afd8333f8d738cc036453e061.sample\"\n\n[deps]\n\nsource_file=\"res://audio/sounds/note_c5.wav\"\ndest_files=[\"res://.godot/imported/note_c5.wav-0c5ae09afd8333f8d738cc036453e061.sample\"]\n\n[params]\n\nforce/8_bit=false\nforce/mono=false\nforce/max_rate=false\nforce/max_rate_hz=44100\nedit/trim=false\nedit/normalize=false\nedit/loop_mode=0\nedit/loop_begin=0\nedit/loop_end=-1\ncompress/mode=0\n"
  },
  {
    "path": "audio/sounds/note_e5.wav.import",
    "content": "[remap]\n\nimporter=\"wav\"\ntype=\"AudioStreamWAV\"\nuid=\"uid://b35ao3bdtstka\"\npath=\"res://.godot/imported/note_e5.wav-297f657b0896a529a466947ad314187e.sample\"\n\n[deps]\n\nsource_file=\"res://audio/sounds/note_e5.wav\"\ndest_files=[\"res://.godot/imported/note_e5.wav-297f657b0896a529a466947ad314187e.sample\"]\n\n[params]\n\nforce/8_bit=false\nforce/mono=false\nforce/max_rate=false\nforce/max_rate_hz=44100\nedit/trim=false\nedit/normalize=false\nedit/loop_mode=0\nedit/loop_begin=0\nedit/loop_end=-1\ncompress/mode=0\n"
  },
  {
    "path": "audio/sounds/note_g5.wav.import",
    "content": "[remap]\n\nimporter=\"wav\"\ntype=\"AudioStreamWAV\"\nuid=\"uid://bmg3dvylup5k\"\npath=\"res://.godot/imported/note_g5.wav-d7fdc8c79a744d03c9a83d9ffeced1bc.sample\"\n\n[deps]\n\nsource_file=\"res://audio/sounds/note_g5.wav\"\ndest_files=[\"res://.godot/imported/note_g5.wav-d7fdc8c79a744d03c9a83d9ffeced1bc.sample\"]\n\n[params]\n\nforce/8_bit=false\nforce/mono=false\nforce/max_rate=false\nforce/max_rate_hz=44100\nedit/trim=false\nedit/normalize=false\nedit/loop_mode=0\nedit/loop_begin=0\nedit/loop_end=-1\ncompress/mode=0\n"
  },
  {
    "path": "default_bus_layout.tres",
    "content": "[gd_resource type=\"AudioBusLayout\" format=3 uid=\"uid://3vigst7csyr8\"]\n\n[resource]\nbus/0/volume_db = -2.0\nbus/1/name = &\"Music\"\nbus/1/solo = false\nbus/1/mute = false\nbus/1/bypass_fx = false\nbus/1/volume_db = 0.0\nbus/1/send = &\"Master\"\nbus/2/name = &\"Sound\"\nbus/2/solo = false\nbus/2/mute = false\nbus/2/bypass_fx = false\nbus/2/volume_db = 0.0\nbus/2/send = &\"Master\"\n"
  },
  {
    "path": "docs/getting-started.md",
    "content": "# Getting started\n\nGodot provides a simple and functional audio engine with all the raw ingredients. However, it leaves it up to you to decide how to organise, manage and orchestrate audio in your projects.\n\n**Resonate** is an *audio manager* designed to fill this gap, providing both convenient and flexible ways to implement audio more easily in Godot. \n\n## Installation\n\nOnce you have downloaded Resonate into your project `addons/` directory, open **Project > Project Settings** and go to the **Plugins** tab. Click on the **Enable** checkbox to enable the plugin.\n\nIt's good practice to reload your project to ensure freshly installed plugins work correctly. Go to **Project > Reload Current Project**.\n\nResonate has two core systems: the **SoundManager** and the **MusicManager** which are available as global singletons (Autoload). You can confirm if these are available and enabled under **Project > Project Settings > Autoload**.\n\nThe `SoundManager` and `MusicManager` will now be available from any GDScript file. However, Resonate needs to initialise and load properly, so you should be aware of the [script execution order](#script-execution-order).\n\n## Concepts\n\nThe following concepts explain how the various components of Resonate work together.\n\n**Sound** (one-shots, sound effects, dialogue) is managed differently compared to **Music.** This is why Resonate comprises of two core systems: the `SoundManager` and `MusicManager`.\n\n### Sound\n\nA **SoundEvent** is any sound that can be triggered, controlled and stopped from code. Anything in your game that produces a sound will need an equivalent SoundEvent. Instead of playing an AudioStream directly, you trigger events in response to gameplay and Resonate takes care of the rest. Events are made up of one or more AudioStreams which are treated as variations to be played at random when the event is triggered, e.g. triggering a \"footsteps\" event plays one of several pre-recorded variations to add variety and realism to your sound design. You can add as many variations to an event as you need. Using an event name means you have a consistent reference used to trigger sounds, but can easily update the AudioStream files associated with each event independently.\n\nA **SoundBank** is a collection of SoundEvents. As your project grows, SoundBanks help you organise related sound events such as \"dialogue\" or \"impacts\" sound effects into groups. You can have as many SoundBanks as you want or need to keep your project organised in a way that suits your game's architecture. Banks also act a little bit like a namespace, e.g. creating separate \"player\" and \"npc\" SoundBanks allows you to trigger a \"death\" dialogue sound event from either bank without name collisions.\n\n### Music\n\nA **MusicTrack** is a piece of music comprised of one or more **Stems**. By default, tracks will fade in/out when played or stopped, and this fade time can be configured. If you play a MusicTrack when another is already playing, the two tracks will be cross-faded so that the new track takes over.\n\nLayers of a MusicTrack are called **Stems**, which typically represent different instruments or mix busses, e.g. \"drums\", \"bass\", \"melody\". This allows for stems to be enabled and disabled independently. By default, stems fade in/out and this fade time can be configured. Care should be taken to ensure Stems are set to loop (see import settings) and that they are either all of the same length or a measure division that enables them to loop in sync with each other. Stems can be used like layers in order to create a dynamic and changing composition. In the context of sound design for games this is sometimes referred to as *vertical composition*. As an example, you could enable a \"drums\" stem in response to an increase in gameplay tension, then disable it when the tension dissipates.\n\nA **MusicBank** is a collection of MusicTracks. As your project grows, MusicBanks help you organise related music tracks into groups. You can have as many MusicBanks as you want or need to keep your project organised in a way that suits your game's architecture. Banks also act a little bit like a namespace, e.g. creating separate MusicBanks for distinct levels or areas in your game world allows you to name and trigger an \"ambient\" or \"combat\" music track from either bank without name collisions. This can help you standardise how you integrate with other game systems, or simply organise audio with a consistent labelling schema.\n\n## Script execution order\n\nResonate needs to initialise `PooledAudioStreamPlayers` and search the entire scene/node tree for every `SoundBank` and `MusicBank`. This process requires at least one game tick to complete.\n\nTherefore, to immediately trigger sounds or music upon your game's launch, you need to subscribe to the relevant `loaded` signal. The following concepts apply to both the `SoundManager` and `MusicManager`:\n\n```GDScript\nfunc _ready() -> void:\n\tMusicManager.loaded.connect(on_music_manager_loaded)\n\t\nfunc on_music_manager_loaded() -> void:\n\tMusicManager.play(\"boss_fight\")\n```\n\nYou can also perform a safety check to ensure `MusicManager.has_loaded` is true before a function call.\n\n## Scene changes & runtime node creation\n\nResonate will scan the scene tree for all music and sound banks when your game launches, which it uses internally to create lookup tables. It will also automatically update those tables whenever a node is inserted or removed from the scene tree. If you load a script at runtime attempting to use either the MusicManager or SoundManager, you can leverage the `updated` signal to ensure you're ready to play music or trigger sound events without issues:\n\n```GDScript\nvar _instance_jump: PooledAudioStreamPlayer = SoundManager.null_instance()\n\nfunc _ready():\n\tSoundManager.updated.connect(on_sound_manager_updated)\n\nfunc _input(p_event: InputEvent) -> void:\n\tif p_event.is_action_pressed(\"jump\"):\n\t\t_instance_jump.trigger()\n\nfunc on_sound_manager_updated() -> void:\n\tif SoundManager.should_skip_instancing(_instance_jump):\n\t\treturn\n\t\n\t_instance_jump = SoundManager.instance(\"player\", \"jump\")\n\n\tSoundManager.release_on_exit(self, _instance_jump)\n```\n\n## Digging deeper\n\nTo understand the music or sound managers in more detail, view examples of setting up banks, or inspect their corresponding APIs, check out the dedicated [MusicManager](music-manager.md) or [SoundManager](sound-manager.md) documentation.\n"
  },
  {
    "path": "docs/images/add-music-bank-node.jpg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://decojxomfjk5j\"\npath=\"res://.godot/imported/add-music-bank-node.jpg-e8c51bb61324a44c5e2f517c657b487e.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://docs/images/add-music-bank-node.jpg\"\ndest_files=[\"res://.godot/imported/add-music-bank-node.jpg-e8c51bb61324a44c5e2f517c657b487e.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": "docs/images/add-sound-bank-node.jpg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://dyf0vqo1r1frq\"\npath=\"res://.godot/imported/add-sound-bank-node.jpg-b2f670516538f11b95735e6253306141.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://docs/images/add-sound-bank-node.jpg\"\ndest_files=[\"res://.godot/imported/add-sound-bank-node.jpg-b2f670516538f11b95735e6253306141.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": "docs/images/music-banks.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://coy730qpivrbt\"\npath=\"res://.godot/imported/music-banks.png-ccdadc82979cc50e9bc6e13c7765c483.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://docs/images/music-banks.png\"\ndest_files=[\"res://.godot/imported/music-banks.png-ccdadc82979cc50e9bc6e13c7765c483.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": "docs/images/music-manager.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://c33xlcqiwygve\"\npath=\"res://.godot/imported/music-manager.png-9e8dfcf14c60a02ae344f87e1e3c81b9.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://docs/images/music-manager.png\"\ndest_files=[\"res://.godot/imported/music-manager.png-9e8dfcf14c60a02ae344f87e1e3c81b9.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": "docs/images/set-soundbank-label.jpg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://cn3ojlclw1u8i\"\npath=\"res://.godot/imported/set-soundbank-label.jpg-23229b0a77b1ed27b07cc2145c4d2e18.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://docs/images/set-soundbank-label.jpg\"\ndest_files=[\"res://.godot/imported/set-soundbank-label.jpg-23229b0a77b1ed27b07cc2145c4d2e18.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": "docs/images/sound-banks.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://b1q8c8jsyx4xs\"\npath=\"res://.godot/imported/sound-banks.png-e6053c754f48df4c35b1ce7bfd0c02f7.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://docs/images/sound-banks.png\"\ndest_files=[\"res://.godot/imported/sound-banks.png-e6053c754f48df4c35b1ce7bfd0c02f7.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": "docs/images/sound-manager.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://mg8fvg6efg77\"\npath=\"res://.godot/imported/sound-manager.png-4662b0febe7313414142e7d2003ba9f9.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://docs/images/sound-manager.png\"\ndest_files=[\"res://.godot/imported/sound-manager.png-4662b0febe7313414142e7d2003ba9f9.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": "docs/music-manager.md",
    "content": "# MusicManager\n\n## Introduction\n\nThe **MusicManager** is responsible for playing music tracks. It does so through a **StemmedMusicStreamPlayer** (SMSP), which extends Godot's **AudioStreamPlayer**. The core feature of SMSPs, as the name suggests, is the management and playback of ***stems***.\n\n![MusicManager](images/music-manager.png)\n\nStems are music tracks split horizontally. For example, a music track may be split into pad, melody, and drum stems. Each of these stems can be played in isolation or in-sync with any or all of the other stems. This feature (while not required) allows you to craft more dynamic in-game music. For example, while the player is far enough away from a boss, only the pad stem plays. Then when the player gets closer, the melody stem gets added in. Finally, when the player is within the boss's detection distance, the drum stem gets added in. This helps you form a music track that grows in intensity without having to swap music tracks in and out entirely. It's a more organic and seamless transition.\n\n### MusicBanks\n\nThe way you configure music is through the use of **MusicBanks**. Each MusicBank contains one or more music tracks, each of which contains one or more stems. Each stem contains one audio stream.\n\n![MusicBanks](images/music-banks.png)\n\n**MusicBanks** are automatically discovered by the **MusicManager** when your game starts, and can be located anywhere in your active scene(s).\n\n### Fading & crossfading\n\nWhenever you start or stop either an entire music track or a single stem, you can provide a (cross)fade time. If you want either of those to start immediately, just provide a (cross)fade time of zero seconds.\n\n## Usage\n\n### Creating MusicBanks\n\n#### Step 1\n\nAdd a new MusicBank node to your scene.\n\n![MusicBankNode](images/add-music-bank-node.jpg)\n\n#### Step 2\n\nGive your new MusicBank a label and then create a new MusicTrackResource. A MusicTrackResource represents one track in your music bank. Each track requires a name, which you will use to start it from your script(s).\n\n![AddMusicResource](images/add-music-resource.gif)\n\n#### Step 3\n\nCreate as many MusicStemResources as you track requires. Each stem requires a name, which you will use to enable or disable it from your script(s). If you mark a stem as enabled at the resource level, it will automatically be started for you when you play the music track. If often makes sense to have you core stem enabled by default.\n\n![AddMusicStemResource](images/add-music-stem-resources.gif)\n\n### Playing music\n\nTo start a new music track, just call the `play` method on the MusicManager with the name of the bank and track you want to play.\n\n```GDScript\nMusicManager.play(\"combat\", \"boss_fight\")\n```\n\nTo enable or disable stems on the currently playing track, just call `enabled_stem` or `disable_stem`.\n\n```GDScript\nMusicManager.enable_stem(\"melody\")\nMusicManager.disable_stem(\"drums\")\n```\n"
  },
  {
    "path": "docs/sound-manager.md",
    "content": "# SoundManager\n\n## Introduction\n\nThe **SoundManager** is responsible for triggering sounds. It does so through **PooledAudioStreamPlayers** (PASPs), which extend Godot's native **AudioStreamPlayers** (ASPs). PASPs, like ASPs, support audio playback in 1D, 2D, and 3D space, making them useful for any game sound effect.\n\n![SoundManager](images/sound-manager.png)\n\nSound events can be configured with multiple variations (audio streams) which are chosen at random when played. This can help create organic-sounding events such as footsteps, gunshots, collisions, etc.\n\n### SoundBanks\n\nThe way you configure sound events and their variations is through the use of **SoundBanks**. Each **SoundBank** you create has a name and several associated events, among other configuration options.\n\n![SoundManager](images/sound-banks.png)\n\n**SoundBanks** are automatically discovered and loaded by the **SoundManager** when your game starts. This allows you to co-locate your **SoundBanks** with the entities or systems they belong to.\n\nAll registered sound events can be triggered in three ways: uniformly, at a fixed position, or attached to a node. Uniformly triggered sound events always play in 1D space and, therefore, will be heard as if coming from all directions (no stereo or 3D panning). Sound events triggered at a fixed position (Vector2/Vector3) or attached to a node (Node2D/Node3D) will automatically be positioned in 2D or 3D space. They will be heard accordingly through the use of panning.\n\nWhen you trigger a sound event, the **SoundManager** will retrieve a free PASP from one of its appropriate 1D, 2D, or 3D pools. After the event, the PASP will be freed and returned to the pool, available for the next event. Using pools means you do not have to insert ASPs into your scenes manually. For performance reasons, however, the **SoundManager** will limit how many PASPs it creates in each pool. This limit can be configured under `Audio/Manager/Sound` in your project settings.\n\nWhen you want to play a particular event consistently, you can request exclusive use of a PASP from the **SoundManager**. When doing so, it will not be automatically returned to its pool when an event has finished playing. It can be manually released if you wish to return it to its pool.\n\n### Polyphony\n\nBy default, every PASP, when told to trigger an event, will play the event once. If instructed to trigger the event again while a previous variation is still playing, it will stop playback and immediately begin playing a new random variation. Polyphonic playback can be enabled in situations where this is undesirable, for instance, playing rapid gunshots. When told to trigger, polyphonic playback will start playing a random variation concurrently with all other variations already playing. The maximum number of concurrent variations a PASP can play can be configured under `Audio/Manager/Sound` in your project settings.\n\n## Usage\n\n### Creating SoundBanks\n\n#### Step 1\n\nAdd a new **SoundBank** node to your scene.\n\n![SoundBankNode](images/add-sound-bank-node.jpg)\n\n#### Step 2\n\nSet the label for your new **SoundBank**. **SoundBanks** are flexible in that they allow you to group your sounds however you want. The label in this case is the group name. Example labels could be \"player\", \"UI\", \"gunshots\", etc. The name you provide here is what you will use when calling the play or instance functions from your script(s).\n\n![SoundBankNode](images/set-soundbank-label.jpg)\n\n#### Step 3\n\nCreate a new **SoundEventResource**. Each **SoundEventResource** is a single **event** in a **SoundBank**. The name you provide here is what you will use when calling the play or instance functions from your script(s).\n\n![SoundBankNode](images/add-sound-event-resource.gif)\n\n#### Step 4\n\nAdd as many streams (variations) to the event as you need. These variations, chosen at random, are played when you trigger the event from your script(s).\n\n![SoundBankNode](images/add-sound-event-resource-streams.gif)\n\nYou are now ready to trigger the event from your script(s).\n\n### Triggering events\n\n#### Simple\n\nThere are two ways to trigger events with the **SoundManager**. The first way is to automatically trigger the event and have the **SoundManager** handle everything for you. \n\n```GDScript\nSoundManager.play(\"player\", \"footsteps\")\n```\n\nUsing this approach (any **SoundManager** method starting with `play`) will cause the **SoundManager** to pick a free player from the pool, trigger your event on it, and immediately return it to the pool once the sound has finished playing.\n\n#### Advanced\n\nThe second way is to manually trigger events. To manually trigger an event, you need to first `instance` a sound event. When a sound event is instanced, the **SoundManager** will return a reserved **PooledAudioStreamPlayer**. You can save a reference to this player and call the `trigger` method on it whenever you want to trigger the reserved event.\n\n`trigger() -> void`\n\nSee the following example below:\n\n```GDScript\nvar instance = SoundManager.instance_poly(\"player\", \"footsteps\")\n\ninstance.trigger()\n```\n\nWhen using an instanced sound event, its your duty to release it back to the pool if you're done using it. This can be achieved by calling the `release` method.\n\n```GDScript\ninstance.release()\n```\n\nHowever, it's often the case that an instanced sound event will be used indefinitely by the calling script, in which case you do not need to call `release`.\n\n#### Automatic space detection\n\nWhen calling the `play` or `instance` methods, the **SoundManager** will use a 1D space **PooledAudioStreamPlayer**. If you require an event to be played in 2D or 3D space, you'll need to use one of the extended `play` or `instance` methods (see the API references below.)\n\n#### Polyphonic playback\n\nThe `instance` method also offers a further extension which allows you to reserve a **PooledAudioStreamPlayer** in a polyphonic configuration (see the API references below.)\n\n#### Varying pitch and volume\n\nAs it's quite common to want to vary the pitch and/or volume of an event, we've added an extended version of the `trigger` method called `trigger_varied`:\n\n`trigger_varied(p_pitch: float = 1.0, p_volume: float = 0.0) -> void`\n\nThe `trigger_varied` method works for both polyphonic and non-polyphonic events.\n"
  },
  {
    "path": "examples/2D/2d.gd",
    "content": "extends Node2D\n\n\n# For reference, it's worth taking a moment to inspect the SoundBank attached to \n# this example scene. SoundBanks hold the configuration for all of your sound \n# events and the variations (AudioStreams) associated with the event.\n\n\n@onready var moving_target = $MovingTarget\n\n# We instantiate a Null PooledAudioStreamPlayer* here, which removes the need to\n# perform null checks before calling methods such as trigger() in your scripts.\nvar _instance: PooledAudioStreamPlayer2D = SoundManager.null_instance_2d()\n\n\nfunc _ready() -> void:\n\t# As the SoundManager requires some preparation when the game loads, we need \n\t# to hook into one or more of its lifecycle events before trying to play or \n\t# instance an audio event. In this example, we've use the `updated` event \n\t# as it's fired whenever any part of the SoundManager's state updates.\n\tSoundManager.updated.connect(on_sound_manager_updated)\n\t\n\nfunc on_sound_manager_updated() -> void:\n\t# The method call below is an inbuilt guard-clause that'll help us avoid \n\t# instancing an audio event when the SoundManager has not loaded, or when \n\t# we've already replaced our Null instance with a real one further down.\n\tif SoundManager.should_skip_instancing(_instance):\n\t\treturn\n\t\t\n\t_instance = SoundManager.instance_on_node(\"2d\", \"drums\", moving_target)\n\t_instance.trigger()\n"
  },
  {
    "path": "examples/2D/2d.tscn",
    "content": "[gd_scene load_steps=10 format=3 uid=\"uid://coebcgearnost\"]\n\n[ext_resource type=\"Script\" path=\"res://examples/2d/2d.gd\" id=\"1_3xxt8\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_bank.gd\" id=\"2_8j415\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_event_resource.gd\" id=\"3_cnd76\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://bcv4ev20vqxx2\" path=\"res://audio/music_stems/house_drums_stem.mp3\" id=\"4_q31s7\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://tysg4xge4hsu\" path=\"res://shared/title_label_settings.tres\" id=\"7_wknu3\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bldprl7vev3uv\" path=\"res://shared/description_label_settings.tres\" id=\"8_h0mrm\"]\n[ext_resource type=\"Script\" path=\"res://examples/2d/moving_target.gd\" id=\"9_kf013\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_5hea7\"]\nscript = ExtResource(\"3_cnd76\")\nname = \"drums\"\nbus = \"\"\nvolume = 0.0\npitch = 1.0\nstreams = Array[AudioStream]([ExtResource(\"4_q31s7\")])\n\n[sub_resource type=\"BoxMesh\" id=\"BoxMesh_rk1yn\"]\nsize = Vector3(100, 100, 100)\n\n[node name=\"2D\" type=\"Node2D\"]\nscript = ExtResource(\"1_3xxt8\")\n\n[node name=\"SoundBank\" type=\"Node\" parent=\".\"]\nscript = ExtResource(\"2_8j415\")\nlabel = \"2d\"\nevents = Array[ExtResource(\"3_cnd76\")]([SubResource(\"Resource_5hea7\")])\n\n[node name=\"Title\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 50.0\noffset_right = 208.0\noffset_bottom = 103.0\ntext = \"2d\"\nlabel_settings = ExtResource(\"7_wknu3\")\nuppercase = true\n\n[node name=\"Description\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 125.0\noffset_right = 824.0\noffset_bottom = 185.0\ntext = \"This example uses the \\\"drums\\\" sound event (see the SoundBank.)\nIt has been attached to the square below, causing the sound to move along with it in 2D space.\"\nlabel_settings = ExtResource(\"8_h0mrm\")\n\n[node name=\"MovingTarget\" type=\"MeshInstance2D\" parent=\".\"]\nposition = Vector2(-100, 600)\nscale = Vector2(1.117, 1)\nmesh = SubResource(\"BoxMesh_rk1yn\")\nscript = ExtResource(\"9_kf013\")\n\n[node name=\"AudioListener2D\" type=\"AudioListener2D\" parent=\".\"]\nposition = Vector2(600, 400)\ncurrent = true\n"
  },
  {
    "path": "examples/2D/moving_target.gd",
    "content": "extends MeshInstance2D\n\n\nvar _time: float\n\n\nfunc _process(p_delta) -> void:\n\t_time += p_delta\n\t\n\tvar weight = (1.0 + sin(_time)) / 2.0\n\tvar x_position = lerpf(-100.0, 1300.0, weight)\n\t\n\tglobal_position.x = x_position\n"
  },
  {
    "path": "examples/3D/3d.gd",
    "content": "extends Node3D\n\n\n# For reference, it's worth taking a moment to inspect the SoundBank attached to \n# this example scene. SoundBanks hold the configuration for all of your sound \n# events and the variations (AudioStreams) associated with the event.\n\n\n@onready var moving_target = $Pivot/MovingTarget\n\n# We instantiate a Null PooledAudioStreamPlayer* here, which removes the need to\n# perform null checks before calling methods such as trigger() in your scripts.\nvar _instance: PooledAudioStreamPlayer3D = SoundManager.null_instance_3d()\n\n\nfunc _ready() -> void:\n\t# As the SoundManager requires some preparation when the game loads, we need \n\t# to hook into one or more of its lifecycle events before trying to play or \n\t# instance an audio event. In this example, we've use the `updated` event \n\t# as it's fired whenever any part of the SoundManager's state updates.\n\tSoundManager.updated.connect(on_sound_manager_updated)\n\t\n\nfunc on_sound_manager_updated() -> void:\n\t# The method call below is an inbuilt guard-clause that'll help us avoid \n\t# instancing an audio event when the SoundManager has not loaded, or when \n\t# we've already replaced our Null instance with a real one further down.\n\tif SoundManager.should_skip_instancing(_instance):\n\t\treturn\n\t\t\n\t_instance = SoundManager.instance_on_node(\"3d\", \"drums\", moving_target)\n\t_instance.trigger()\n"
  },
  {
    "path": "examples/3D/3d.tscn",
    "content": "[gd_scene load_steps=11 format=3 uid=\"uid://3kswph4l3ba6\"]\n\n[ext_resource type=\"Script\" path=\"res://examples/3d/3d.gd\" id=\"1_4y185\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_bank.gd\" id=\"2_xngck\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_event_resource.gd\" id=\"3_3oot1\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://bcv4ev20vqxx2\" path=\"res://audio/music_stems/house_drums_stem.mp3\" id=\"4_lnff3\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://tysg4xge4hsu\" path=\"res://shared/title_label_settings.tres\" id=\"7_ythoj\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bldprl7vev3uv\" path=\"res://shared/description_label_settings.tres\" id=\"8_6qcxl\"]\n[ext_resource type=\"Script\" path=\"res://examples/3d/pivot.gd\" id=\"9_leetf\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_5hea7\"]\nscript = ExtResource(\"3_3oot1\")\nname = \"drums\"\nbus = \"\"\nvolume = 0.0\npitch = 1.0\nstreams = Array[AudioStream]([ExtResource(\"4_lnff3\")])\n\n[sub_resource type=\"CapsuleMesh\" id=\"CapsuleMesh_ajqyv\"]\n\n[sub_resource type=\"BoxMesh\" id=\"BoxMesh_aceq4\"]\n\n[node name=\"3D\" type=\"Node3D\"]\nscript = ExtResource(\"1_4y185\")\n\n[node name=\"SoundBank\" type=\"Node\" parent=\".\"]\nscript = ExtResource(\"2_xngck\")\nlabel = \"3d\"\nevents = Array[ExtResource(\"3_3oot1\")]([SubResource(\"Resource_5hea7\")])\n\n[node name=\"Title\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 50.0\noffset_right = 208.0\noffset_bottom = 103.0\ntext = \"3d\"\nlabel_settings = ExtResource(\"7_ythoj\")\nuppercase = true\n\n[node name=\"Description\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 125.0\noffset_right = 824.0\noffset_bottom = 185.0\ntext = \"This example uses the \\\"drums\\\" sound event (see the SoundBank.)\nIt has been attached to the box below, causing the sound to move along with it in 3D space.\nA listener has been placed on the capsule mesh.\"\nlabel_settings = ExtResource(\"8_6qcxl\")\n\n[node name=\"AudioListener3D\" type=\"AudioListener3D\" parent=\".\"]\n\n[node name=\"ListenerMesh\" type=\"MeshInstance3D\" parent=\"AudioListener3D\"]\nmesh = SubResource(\"CapsuleMesh_ajqyv\")\n\n[node name=\"Pivot\" type=\"Node3D\" parent=\".\"]\nscript = ExtResource(\"9_leetf\")\n\n[node name=\"MovingTarget\" type=\"MeshInstance3D\" parent=\"Pivot\"]\ntransform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -7)\nmesh = SubResource(\"BoxMesh_aceq4\")\nskeleton = NodePath(\"../..\")\n\n[node name=\"DirectionalLight3D\" type=\"DirectionalLight3D\" parent=\".\"]\ntransform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 19, 27)\n\n[node name=\"Camera3D\" type=\"Camera3D\" parent=\".\"]\ntransform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 10, 12)\n"
  },
  {
    "path": "examples/3D/pivot.gd",
    "content": "extends Node3D\n\n\nfunc _process(p_delta):\n\trotate_y(TAU * p_delta * 0.1)\n"
  },
  {
    "path": "examples/bulk_instancing/bulk_instancing.gd",
    "content": "extends Node2D\n\n\n# For reference, it's worth taking a moment to inspect the SoundBank attached to \n# this example scene. SoundBanks hold the configuration for all of your sound \n# events and the variations (AudioStreams) associated with the event.\n\n\nvar _instance_note_one: PooledAudioStreamPlayer = SoundManager.null_instance()\nvar _instance_note_two: PooledAudioStreamPlayer = SoundManager.null_instance()\nvar _instance_note_three: PooledAudioStreamPlayer = SoundManager.null_instance()\n\n\nfunc _ready() -> void:\n\t# As the SoundManager requires some preparation when the game loads, we need \n\t# to hook into one or more of its lifecycle events before trying to play or \n\t# instance an audio event. In this example, we've use the `updated` event \n\t# as it's fired whenever any part of the SoundManager's state updates.\n\tSoundManager.updated.connect(on_sound_manager_updated)\n\t\n\nfunc _input(p_event: InputEvent) -> void:\n\tif p_event.is_action_pressed(\"one\"):\n\t\t_instance_note_one.trigger()\n\t\n\tif p_event.is_action_pressed(\"two\"):\n\t\t_instance_note_two.trigger()\n\t\t\n\tif p_event.is_action_pressed(\"three\"):\n\t\t_instance_note_three.trigger()\n\n\nfunc on_sound_manager_updated() -> void:\n\t# We'll use the quick_instance method here as it'll take care of a number of\n\t# repetitive steps that would otherwise be necessary to set up all three \n\t# instances. It also ensures that each instance is only registered once.\n\t\n\t_instance_note_one = SoundManager.quick_instance(_instance_note_one,\n\t\t\tSoundManager.instance.bind(\"bulk\", \"one\"), self)\n\t\t\t\n\t_instance_note_two = SoundManager.quick_instance(_instance_note_two,\n\t\t\tSoundManager.instance.bind(\"bulk\", \"two\"), self)\n\t\t\t\n\t_instance_note_three = SoundManager.quick_instance(_instance_note_three,\n\t\t\tSoundManager.instance.bind(\"bulk\", \"three\"), self)\n"
  },
  {
    "path": "examples/bulk_instancing/bulk_instancing.tscn",
    "content": "[gd_scene load_steps=12 format=3 uid=\"uid://dgmta52w7wn21\"]\n\n[ext_resource type=\"Script\" path=\"res://examples/bulk_instancing/bulk_instancing.gd\" id=\"1_0yybq\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_bank.gd\" id=\"2_r0ory\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_event_resource.gd\" id=\"3_hf22l\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://de550fklyhu88\" path=\"res://audio/sounds/note_c5.wav\" id=\"4_uqepo\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://b35ao3bdtstka\" path=\"res://audio/sounds/note_e5.wav\" id=\"5_w24x2\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://bmg3dvylup5k\" path=\"res://audio/sounds/note_g5.wav\" id=\"6_1thix\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://tysg4xge4hsu\" path=\"res://shared/title_label_settings.tres\" id=\"7_mpcft\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bldprl7vev3uv\" path=\"res://shared/description_label_settings.tres\" id=\"8_3ggq1\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_nfhjk\"]\nscript = ExtResource(\"3_hf22l\")\nname = \"one\"\nbus = \"\"\nvolume = 0.0\npitch = 1.0\nstreams = Array[AudioStream]([ExtResource(\"4_uqepo\")])\n\n[sub_resource type=\"Resource\" id=\"Resource_5d8ei\"]\nscript = ExtResource(\"3_hf22l\")\nname = \"two\"\nbus = \"\"\nvolume = 0.0\npitch = 1.0\nstreams = Array[AudioStream]([ExtResource(\"5_w24x2\")])\n\n[sub_resource type=\"Resource\" id=\"Resource_8ke84\"]\nscript = ExtResource(\"3_hf22l\")\nname = \"three\"\nbus = \"\"\nvolume = 0.0\npitch = 1.0\nstreams = Array[AudioStream]([ExtResource(\"6_1thix\")])\n\n[node name=\"BulkInstancing\" type=\"Node2D\"]\nscript = ExtResource(\"1_0yybq\")\n\n[node name=\"SoundBank\" type=\"Node\" parent=\".\"]\nscript = ExtResource(\"2_r0ory\")\nlabel = \"bulk\"\nevents = Array[ExtResource(\"3_hf22l\")]([SubResource(\"Resource_nfhjk\"), SubResource(\"Resource_5d8ei\"), SubResource(\"Resource_8ke84\")])\n\n[node name=\"Title\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 50.0\noffset_right = 208.0\noffset_bottom = 103.0\ntext = \"Bulk Instancing\"\nlabel_settings = ExtResource(\"7_mpcft\")\nuppercase = true\n\n[node name=\"Description\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 125.0\noffset_right = 824.0\noffset_bottom = 185.0\ntext = \"This example instances 3 sound events from a single bank.\nPress \\\"1\\\" on your keyboard to trigger the first event.\nPress \\\"2\\\" on your keyboard to trigger the second event.\nPress \\\"3\\\" on your keyboard to trigger the third event.\n\"\nlabel_settings = ExtResource(\"8_3ggq1\")\n"
  },
  {
    "path": "examples/music/music.gd",
    "content": "extends Node2D\n\n\n# For reference, it's worth taking a moment to inspect the MusicBank attached to \n# this example scene. MusicBanks hold the configuration for all of your music \n# tracks and the stems (MusicStemResources) associated with the music track.\n\n\n@onready var stem_details = $StemDetails\n\nconst _TRACKS: Array[String] = [\"house\", \"breakbeat\"]\n\nvar _is_playing: bool\nvar _track_number: int = 0\nvar _track_name: String = _TRACKS[_track_number]\n\n\nfunc _ready() -> void:\n\t# As the MusicManager requires some preparation when the game loads, we need \n\t# to hook into one or more of its lifecycle events before trying to play a\n\t# music track and/or stems. In this example, we've use the `updated` event \n\t# as it's fired whenever any part of the MusicManager's state updates.\n\tMusicManager.updated.connect(on_music_manager_updated)\n\n\nfunc _input(p_event: InputEvent) -> void:\n\tif p_event.is_action_pressed(\"one\"):\n\t\tMusicManager.enable_stem(\"melody\")\n\t\t\n\tif p_event.is_action_released(\"one\"):\n\t\tMusicManager.disable_stem(\"melody\")\n\t\t\n\tif p_event.is_action_pressed(\"two\"):\n\t\tMusicManager.enable_stem(\"drums\")\n\t\t\n\tif p_event.is_action_released(\"two\"):\n\t\tMusicManager.disable_stem(\"drums\")\n\t\t\n\tif p_event.is_action_pressed(\"three\"):\n\t\t_track_number = _track_number + 1 if (_track_number + 1) < _TRACKS.size() else 0\n\t\t_track_name = _TRACKS[_track_number]\n\t\t\n\t\tMusicManager.play(\"instrumental\", _track_name)\n\t\t\t\t\n\tif p_event.is_action_pressed(\"four\"):\n\t\tMusicManager.stop()\n\n\nfunc _process(_p_delta):\n\tif not MusicManager.is_playing():\n\t\tstem_details.text = \"No music playing.\"\n\t\treturn\n\t\t\n\tvar melody_stem = MusicManager.get_stem_details(\"melody\")\n\tvar drums_stem = MusicManager.get_stem_details(\"drums\")\n\t\n\tif melody_stem == null or drums_stem == null:\n\t\tstem_details.text = \"Stem details unavailable.\"\n\t\treturn\n\t\n\tstem_details.text = \"\"\"Current track: %s\n\tMelody stem:\n\t - Volume: %ddB\n\t - Enabled: %s\n\tDrums stem:\n\t - Volume: %ddB\n\t - Enabled: %s\n\t\"\"\" % [_track_name, melody_stem.volume, melody_stem.enabled, drums_stem.volume, drums_stem.enabled]\n\n\nfunc on_music_manager_updated() -> void:\n\t# The method call below is an inbuilt guard-clause that'll help us avoid playing \n\t# a music track and/or stems when the MusicManager has not loaded, or when we've \n\t# already set the `_is_playing` variable to true (returned by the play method).\n\tif MusicManager.should_skip_playing(_is_playing):\n\t\treturn\n\t\t\n\t_is_playing = MusicManager.play(\"instrumental\", _track_name)\n"
  },
  {
    "path": "examples/music/music.tscn",
    "content": "[gd_scene load_steps=22 format=3 uid=\"uid://jw5kbo20k3hh\"]\n\n[ext_resource type=\"Script\" path=\"res://examples/music/music.gd\" id=\"1_b17ub\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/music_manager/music_bank.gd\" id=\"2_cql1n\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/music_manager/music_track_resource.gd\" id=\"3_o75hm\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/music_manager/music_stem_resource.gd\" id=\"4_ksop8\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bldprl7vev3uv\" path=\"res://shared/description_label_settings.tres\" id=\"5_cg2qn\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://tysg4xge4hsu\" path=\"res://shared/title_label_settings.tres\" id=\"5_vbmlt\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://sjkaeg8p0qig\" path=\"res://audio/music_stems/breakbeat_pad_stem.mp3\" id=\"5_wctds\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://ba8e5llasqjhk\" path=\"res://audio/music_stems/breakbeat_melody_stem.mp3\" id=\"6_hgf2k\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://clkjvyfm227dt\" path=\"res://audio/music_stems/breakbeat_drums_stem.mp3\" id=\"7_57fs0\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://bhbxnxeuk62q0\" path=\"res://audio/music_stems/house_bass_pad_stem.mp3\" id=\"8_hxbnm\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://dq5s5n0folm21\" path=\"res://audio/music_stems/house_melody_stem.mp3\" id=\"9_3rpxp\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://bcv4ev20vqxx2\" path=\"res://audio/music_stems/house_drums_stem.mp3\" id=\"10_13th8\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bnsew6gimofj5\" path=\"res://shared/stats_label_settings.tres\" id=\"13_787wl\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_ognhn\"]\nscript = ExtResource(\"4_ksop8\")\nname = \"pad\"\nenabled = true\nvolume = 0.0\nstream = ExtResource(\"5_wctds\")\n\n[sub_resource type=\"Resource\" id=\"Resource_4apoh\"]\nscript = ExtResource(\"4_ksop8\")\nname = \"melody\"\nenabled = false\nvolume = 0.0\nstream = ExtResource(\"6_hgf2k\")\n\n[sub_resource type=\"Resource\" id=\"Resource_x14g1\"]\nscript = ExtResource(\"4_ksop8\")\nname = \"drums\"\nenabled = false\nvolume = 0.0\nstream = ExtResource(\"7_57fs0\")\n\n[sub_resource type=\"Resource\" id=\"Resource_1ncxf\"]\nscript = ExtResource(\"3_o75hm\")\nname = \"breakbeat\"\nbus = \"\"\nstems = Array[ExtResource(\"4_ksop8\")]([SubResource(\"Resource_ognhn\"), SubResource(\"Resource_4apoh\"), SubResource(\"Resource_x14g1\")])\n\n[sub_resource type=\"Resource\" id=\"Resource_ee683\"]\nscript = ExtResource(\"4_ksop8\")\nname = \"pad\"\nenabled = true\nvolume = 0.0\nstream = ExtResource(\"8_hxbnm\")\n\n[sub_resource type=\"Resource\" id=\"Resource_64pmf\"]\nscript = ExtResource(\"4_ksop8\")\nname = \"melody\"\nenabled = false\nvolume = 0.0\nstream = ExtResource(\"9_3rpxp\")\n\n[sub_resource type=\"Resource\" id=\"Resource_slh24\"]\nscript = ExtResource(\"4_ksop8\")\nname = \"drums\"\nenabled = false\nvolume = 0.0\nstream = ExtResource(\"10_13th8\")\n\n[sub_resource type=\"Resource\" id=\"Resource_pmaoq\"]\nscript = ExtResource(\"3_o75hm\")\nname = \"house\"\nbus = \"\"\nstems = Array[ExtResource(\"4_ksop8\")]([SubResource(\"Resource_ee683\"), SubResource(\"Resource_64pmf\"), SubResource(\"Resource_slh24\")])\n\n[node name=\"Music\" type=\"Node2D\"]\nscript = ExtResource(\"1_b17ub\")\n\n[node name=\"MusicBank\" type=\"Node\" parent=\".\"]\nscript = ExtResource(\"2_cql1n\")\nlabel = \"instrumental\"\ntracks = Array[ExtResource(\"3_o75hm\")]([SubResource(\"Resource_1ncxf\"), SubResource(\"Resource_pmaoq\")])\n\n[node name=\"Title\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 50.0\noffset_right = 208.0\noffset_bottom = 103.0\ntext = \"Music\"\nlabel_settings = ExtResource(\"5_vbmlt\")\nuppercase = true\n\n[node name=\"Description\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 125.0\noffset_right = 824.0\noffset_bottom = 185.0\ntext = \"This example automatically plays the \\\"exploration\\\" music track (see MusicBank.)\nHold down \\\"1\\\" on your keyboard to enable the \\\"melody\\\" stem.\nHold down \\\"2\\\" on your keyboard to enable the \\\"drums\\\" stem.\nPress \\\"3\\\" on your keyboard to start the next track.\nPress \\\"4\\\" on your keyboard to stop the current track.\"\nlabel_settings = ExtResource(\"5_cg2qn\")\n\n[node name=\"StemDetails\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 315.0\noffset_right = 824.0\noffset_bottom = 375.0\ntext = \"{ stem details }\"\nlabel_settings = ExtResource(\"13_787wl\")\n"
  },
  {
    "path": "examples/polyphonic/polyphonic.gd",
    "content": "extends Node2D\n\n\n# For reference, it's worth taking a moment to inspect the SoundBank attached to \n# this example scene. SoundBanks hold the configuration for all of your sound \n# events and the variations (AudioStreams) associated with the event.\n\n\n# We instantiate a Null PooledAudioStreamPlayer* here, which removes the need to\n# perform null checks before calling methods such as trigger() in your scripts.\nvar _instance: PooledAudioStreamPlayer = SoundManager.null_instance()\n\n\nfunc _ready() -> void:\n\t# As the SoundManager requires some preparation when the game loads, we need \n\t# to hook into one or more of its lifecycle events before trying to play or \n\t# instance an audio event. In this example, we've use the `updated` event \n\t# as it's fired whenever any part of the SoundManager's state updates.\n\tSoundManager.updated.connect(on_sound_manager_updated)\n\t\n\nfunc _input(p_event: InputEvent) -> void:\n\tif p_event.is_action_pressed(\"one\"):\n\t\t_instance.trigger()\n\t\t\n\tif p_event.is_action_pressed(\"two\"):\n\t\t_instance.trigger_varied(randf_range(0.9, 1.2), randf_range(-2.0, 0.0))\n\n\nfunc on_sound_manager_updated() -> void:\n\t# The method call below is an inbuilt guard-clause that'll help us avoid \n\t# instancing an audio event when the SoundManager has not loaded, or when \n\t# we've already replaced our Null instance with a real one further down.\n\tif SoundManager.should_skip_instancing(_instance):\n\t\treturn\n\t\n\t_instance = SoundManager.instance_poly(\"polyphonic\", \"blaster\")\n"
  },
  {
    "path": "examples/polyphonic/polyphonic.tscn",
    "content": "[gd_scene load_steps=8 format=3 uid=\"uid://ys1gdvqjju2q\"]\n\n[ext_resource type=\"Script\" path=\"res://examples/polyphonic/polyphonic.gd\" id=\"1_apacb\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_bank.gd\" id=\"2_pol04\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_event_resource.gd\" id=\"3_s5fje\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://cy4r18lxb4rbh\" path=\"res://audio/sounds/blaster.wav\" id=\"4_pis6r\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://tysg4xge4hsu\" path=\"res://shared/title_label_settings.tres\" id=\"7_v2tg6\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bldprl7vev3uv\" path=\"res://shared/description_label_settings.tres\" id=\"8_qupom\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_hqydm\"]\nscript = ExtResource(\"3_s5fje\")\nname = \"blaster\"\nbus = \"\"\nvolume = 0.0\npitch = 1.0\nstreams = Array[AudioStream]([ExtResource(\"4_pis6r\")])\n\n[node name=\"Polyphonic\" type=\"Node2D\"]\nscript = ExtResource(\"1_apacb\")\n\n[node name=\"SoundBank\" type=\"Node\" parent=\".\"]\nscript = ExtResource(\"2_pol04\")\nlabel = \"polyphonic\"\nevents = Array[ExtResource(\"3_s5fje\")]([SubResource(\"Resource_hqydm\")])\n\n[node name=\"Title\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 50.0\noffset_right = 208.0\noffset_bottom = 103.0\ntext = \"Polyphonic\"\nlabel_settings = ExtResource(\"7_v2tg6\")\nuppercase = true\n\n[node name=\"Description\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 125.0\noffset_right = 824.0\noffset_bottom = 185.0\ntext = \"This example uses the \\\"blaster\\\" sound event (see SoundBank.)\nPress \\\"1\\\" on your keyboard to trigger the event.\nPress \\\"2\\\" on you keyboard to trigger the event with slight pitch and volume variations.\"\nlabel_settings = ExtResource(\"8_qupom\")\n"
  },
  {
    "path": "examples/scene_changes/scene_changes.gd",
    "content": "extends Node2D\n\n\n# This script is simply glue code for the two other example scenes in this folder.\n# It's worth inspecting scene_one and scene_two to learn how to handle runtime \n# scene insertion and deletion while utilising the Sound and Music Managers.\n\n\n@onready var scene_details = $SceneDetails\n\nconst _SCENE_NAMES: Array[String] = [\"scene_one\", \"scene_two\"]\nconst _SCENES: Dictionary = {\n\t\"scene_one\": preload(\"res://examples/scene_changes/scene_one.tscn\"),\n\t\"scene_two\": preload(\"res://examples/scene_changes/scene_two.tscn\"),\n}\n\nvar _current_scene: int = 0\nvar _current_scene_ref: Node\n\n\nfunc _ready() -> void:\n\tload_scene(_current_scene)\n\t\n\nfunc _process(_p_delta) -> void:\n\tscene_details.text = \"Current scene: %s\" % _SCENE_NAMES[_current_scene]\n\n\nfunc _input(p_event) -> void:\n\tif p_event.is_action_pressed(\"one\"):\n\t\t_current_scene = _current_scene + 1 if (_current_scene + 1) < _SCENE_NAMES.size() else 0\n\t\tload_scene(_current_scene)\n\n\nfunc load_scene(p_index: int) -> void:\n\tif _current_scene_ref != null:\n\t\tremove_child(_current_scene_ref)\n\t\n\tvar scene_name = _SCENE_NAMES[p_index]\n\t_current_scene_ref = _SCENES[scene_name].instantiate()\n\t\n\tadd_child(_current_scene_ref)\n"
  },
  {
    "path": "examples/scene_changes/scene_changes.tscn",
    "content": "[gd_scene load_steps=5 format=3 uid=\"uid://bcqg6ao82u1by\"]\n\n[ext_resource type=\"Script\" path=\"res://examples/scene_changes/scene_changes.gd\" id=\"1_p51s5\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://tysg4xge4hsu\" path=\"res://shared/title_label_settings.tres\" id=\"2_rh3pj\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bldprl7vev3uv\" path=\"res://shared/description_label_settings.tres\" id=\"3_k4a5g\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bnsew6gimofj5\" path=\"res://shared/stats_label_settings.tres\" id=\"4_gpwbw\"]\n\n[node name=\"SceneChanges\" type=\"Node2D\"]\nscript = ExtResource(\"1_p51s5\")\n\n[node name=\"Title\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 50.0\noffset_right = 208.0\noffset_bottom = 103.0\ntext = \"Scene Changes\"\nlabel_settings = ExtResource(\"2_rh3pj\")\nuppercase = true\n\n[node name=\"Description\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 125.0\noffset_right = 824.0\noffset_bottom = 185.0\ntext = \"This example loads different scenes at runtime to demonstrate automatic bank detection.\nPress \\\"1\\\" on your keyboard to cycle scenes.\"\nlabel_settings = ExtResource(\"3_k4a5g\")\n\n[node name=\"SceneDetails\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 215.0\noffset_right = 824.0\noffset_bottom = 275.0\ntext = \"{ scene details }\"\nlabel_settings = ExtResource(\"4_gpwbw\")\n"
  },
  {
    "path": "examples/scene_changes/scene_one.gd",
    "content": "extends Node2D\n\n\n# For reference, it's worth taking a moment to inspect the MusicBank attached to \n# this example scene. MusicBanks hold the configuration for all of your music \n# tracks and the stems (MusicStemResources) associated with the music track.\n\n\n@onready var timer: Timer = $Timer\n\nvar _is_playing: bool\n\n\nfunc _ready():\n\t# As the MusicManager requires some preparation when the game loads, we need \n\t# to hook into one or more of its lifecycle events before trying to play a\n\t# music track and/or stems. In this example, we've use the `updated` event \n\t# as it's fired whenever any part of the MusicManager's state updates.\n\tMusicManager.updated.connect(on_music_manager_updated)\n\n\nfunc on_music_manager_updated() -> void:\n\t# The method call below is an inbuilt guard-clause that'll help us avoid playing \n\t# a music track and/or stems when the MusicManager has not loaded, or when we've \n\t# already set the `_is_playing` variable to true (returned by the play method).\n\tif MusicManager.should_skip_playing(_is_playing):\n\t\treturn\n\t\n\t_is_playing = MusicManager.play(\"scene_one\", \"track_a\")\n\t\n\t# If you know that a scene will be inserted or removed at runtime, and you\n\t# don't want to hook into lifecycle events to ensure your music tracks\n\t# stop playing, you can instruct the MusicManager to do it for you.\n\tMusicManager.stop_on_exit(self, \"scene_one\", \"track_a\")\n"
  },
  {
    "path": "examples/scene_changes/scene_one.tscn",
    "content": "[gd_scene load_steps=8 format=3 uid=\"uid://qfk8ucywmppo\"]\n\n[ext_resource type=\"Script\" path=\"res://examples/scene_changes/scene_one.gd\" id=\"1_ac5rj\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/music_manager/music_bank.gd\" id=\"4_fi4fr\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/music_manager/music_track_resource.gd\" id=\"5_wtd24\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/music_manager/music_stem_resource.gd\" id=\"6_4afm1\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://bcv4ev20vqxx2\" path=\"res://audio/music_stems/house_drums_stem.mp3\" id=\"8_qa3at\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_6vob4\"]\nscript = ExtResource(\"6_4afm1\")\nname = \"stem_a\"\nenabled = true\nvolume = 0.0\nstream = ExtResource(\"8_qa3at\")\n\n[sub_resource type=\"Resource\" id=\"Resource_q7ce0\"]\nscript = ExtResource(\"5_wtd24\")\nname = \"track_a\"\nbus = \"\"\nstems = Array[ExtResource(\"6_4afm1\")]([SubResource(\"Resource_6vob4\")])\n\n[node name=\"SceneOne\" type=\"Node2D\"]\nscript = ExtResource(\"1_ac5rj\")\n\n[node name=\"MusicBank\" type=\"Node\" parent=\".\"]\nscript = ExtResource(\"4_fi4fr\")\nlabel = \"scene_one\"\ntracks = Array[ExtResource(\"5_wtd24\")]([SubResource(\"Resource_q7ce0\")])\n\n[node name=\"Timer\" type=\"Timer\" parent=\".\"]\nautostart = true\n"
  },
  {
    "path": "examples/scene_changes/scene_two.gd",
    "content": "extends Node2D\n\n\n# For reference, it's worth taking a moment to inspect the SoundBank attached to \n# this example scene. SoundBanks hold the configuration for all of your sound \n# events and the variations (AudioStreams) associated with the event.\n\n\n@onready var timer: Timer = $Timer\n\n# We instantiate a Null PooledAudioStreamPlayer* here, which removes the need to\n# perform null checks before calling methods such as trigger() in your scripts.\nvar _instance: PooledAudioStreamPlayer = SoundManager.null_instance()\n\n\nfunc _ready():\n\t# As the SoundManager requires some preparation when the game loads, we need \n\t# to hook into one or more of its lifecycle events before trying to play or \n\t# instance an audio event. In this example, we've use the `updated` event \n\t# as it's fired whenever any part of the SoundManager's state updates.\n\tSoundManager.updated.connect(on_sound_manager_updated)\n\t\n\ttimer.timeout.connect(on_timer_timeout)\n\n\nfunc on_sound_manager_updated() -> void:\n\t# The method call below is an inbuilt guard-clause that'll help us avoid \n\t# instancing an audio event when the SoundManager has not loaded, or when \n\t# we've already replaced our Null instance with a real one further down.\n\tif SoundManager.should_skip_instancing(_instance):\n\t\treturn\n\n\t_instance = SoundManager.instance(\"scene_two\", \"note\")\n\n\t# If you know that a scene will be inserted or removed at runtime, and you\n\t# don't want to hook into lifecycle events to ensure your instances are\n\t# being released, you can instruct the SoundManager to do it for you.\n\tSoundManager.release_on_exit(self, _instance, true)\n\n\nfunc on_timer_timeout():\n\t_instance.trigger()\n"
  },
  {
    "path": "examples/scene_changes/scene_two.tscn",
    "content": "[gd_scene load_steps=8 format=3 uid=\"uid://cyme2yjik1ioe\"]\n\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_bank.gd\" id=\"1_cdph3\"]\n[ext_resource type=\"Script\" path=\"res://examples/scene_changes/scene_two.gd\" id=\"1_fw31x\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_event_resource.gd\" id=\"2_la3fn\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://de550fklyhu88\" path=\"res://audio/sounds/note_c5.wav\" id=\"3_ll4y0\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://b35ao3bdtstka\" path=\"res://audio/sounds/note_e5.wav\" id=\"5_a7sct\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://bmg3dvylup5k\" path=\"res://audio/sounds/note_g5.wav\" id=\"6_37wm4\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_tk6xx\"]\nscript = ExtResource(\"2_la3fn\")\nname = \"note\"\nbus = \"\"\nvolume = 0.0\npitch = 1.0\nstreams = Array[AudioStream]([ExtResource(\"3_ll4y0\"), ExtResource(\"5_a7sct\"), ExtResource(\"6_37wm4\")])\n\n[node name=\"SceneTwo\" type=\"Node2D\"]\nscript = ExtResource(\"1_fw31x\")\n\n[node name=\"SoundBank\" type=\"Node\" parent=\".\"]\nscript = ExtResource(\"1_cdph3\")\nlabel = \"scene_two\"\nevents = Array[ExtResource(\"2_la3fn\")]([SubResource(\"Resource_tk6xx\")])\n\n[node name=\"Timer\" type=\"Timer\" parent=\".\"]\nautostart = true\n"
  },
  {
    "path": "examples/simple/simple.gd",
    "content": "extends Node2D\n\n\n# For reference, it's worth taking a moment to inspect the SoundBank attached to \n# this example scene. SoundBanks hold the configuration for all of your sound \n# events and the variations (AudioStreams) associated with the event.\n\n\nfunc _input(p_event: InputEvent) -> void:\n\tif p_event.is_action_pressed(\"one\"):\n\t\tSoundManager.play(\"simple\", \"note\")\n\t\t\n\tif p_event.is_action_pressed(\"two\"):\n\t\tSoundManager.play_varied(\"simple\", \"note\", randf_range(0.8, 1.2), randf_range(-8, 0))\n"
  },
  {
    "path": "examples/simple/simple.tscn",
    "content": "[gd_scene load_steps=10 format=3 uid=\"uid://dfy6ygeu8pya5\"]\n\n[ext_resource type=\"Script\" path=\"res://examples/simple/simple.gd\" id=\"1_fvr5o\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://tysg4xge4hsu\" path=\"res://shared/title_label_settings.tres\" id=\"2_6x4oj\"]\n[ext_resource type=\"LabelSettings\" uid=\"uid://bldprl7vev3uv\" path=\"res://shared/description_label_settings.tres\" id=\"3_ljjap\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_event_resource.gd\" id=\"3_qyv4q\"]\n[ext_resource type=\"Script\" path=\"res://addons/resonate/sound_manager/sound_bank.gd\" id=\"4_cwmja\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://de550fklyhu88\" path=\"res://audio/sounds/note_c5.wav\" id=\"4_it7rc\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://b35ao3bdtstka\" path=\"res://audio/sounds/note_e5.wav\" id=\"5_vxpmp\"]\n[ext_resource type=\"AudioStream\" uid=\"uid://bmg3dvylup5k\" path=\"res://audio/sounds/note_g5.wav\" id=\"6_kifph\"]\n\n[sub_resource type=\"Resource\" id=\"Resource_1ir4x\"]\nscript = ExtResource(\"3_qyv4q\")\nname = \"note\"\nbus = \"\"\nvolume = 0.0\npitch = 1.0\nstreams = Array[AudioStream]([ExtResource(\"4_it7rc\"), ExtResource(\"5_vxpmp\"), ExtResource(\"6_kifph\")])\n\n[node name=\"Simple\" type=\"Node2D\"]\nscript = ExtResource(\"1_fvr5o\")\n\n[node name=\"SoundBank\" type=\"Node\" parent=\".\"]\nscript = ExtResource(\"4_cwmja\")\nlabel = \"simple\"\nevents = Array[ExtResource(\"3_qyv4q\")]([SubResource(\"Resource_1ir4x\")])\n\n[node name=\"Title\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 50.0\noffset_right = 208.0\noffset_bottom = 103.0\ntext = \"Simple\"\nlabel_settings = ExtResource(\"2_6x4oj\")\nuppercase = true\n\n[node name=\"Description\" type=\"Label\" parent=\".\"]\noffset_left = 100.0\noffset_top = 125.0\noffset_right = 824.0\noffset_bottom = 185.0\ntext = \"This example uses the \\\"note\\\" sound event (see SoundBank.)\nPress \\\"1\\\" on your keyboard to trigger the event.\nPress \\\"2\\\" on your keyboard to trigger the event with varied pitch and volume.\nEach time an event is triggered, one of its variations are chosen randomly.\"\nlabel_settings = ExtResource(\"3_ljjap\")\n"
  },
  {
    "path": "icon.svg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://dxujf3r8cbmga\"\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[application]\n\nconfig/name=\"Resonate\"\nconfig/features=PackedStringArray(\"4.2\", \"Forward Plus\")\nconfig/icon=\"res://icon.svg\"\n\n[audio]\n\nmanager/sound/pool_1D_size=16\nmanager/sound/pool_2D_size=16\nmanager/sound/pool_3D_size=16\nmanager/sound/max_polyphony=32\nmanager/sound/bus=\"Sound\"\nmanager/music/bus=\"Music\"\n\n[autoload]\n\nSoundManager=\"*res://addons/resonate/sound_manager/sound_manager.gd\"\nMusicManager=\"*res://addons/resonate/music_manager/music_manager.gd\"\n\n[display]\n\nwindow/size/viewport_width=1200\nwindow/size/viewport_height=800\n\n[editor_plugins]\n\nenabled=PackedStringArray(\"res://addons/resonate/plugin.cfg\")\n\n[file_customization]\n\nfolder_colors={\n\"res://addons/resonate/\": \"orange\"\n}\n\n[input]\n\none={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":-1,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":49,\"key_label\":0,\"unicode\":49,\"echo\":false,\"script\":null)\n]\n}\ntwo={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":-1,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":50,\"key_label\":0,\"unicode\":50,\"echo\":false,\"script\":null)\n]\n}\nthree={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":-1,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":51,\"key_label\":0,\"unicode\":51,\"echo\":false,\"script\":null)\n]\n}\nfour={\n\"deadzone\": 0.5,\n\"events\": [Object(InputEventKey,\"resource_local_to_scene\":false,\"resource_name\":\"\",\"device\":-1,\"window_id\":0,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"pressed\":false,\"keycode\":0,\"physical_keycode\":52,\"key_label\":0,\"unicode\":52,\"echo\":false,\"script\":null)\n]\n}\n\n[rendering]\n\nenvironment/defaults/default_clear_color=Color(0.0588235, 0.0588235, 0.0588235, 1)\n"
  },
  {
    "path": "reasonate-github-header.jpg.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://bb75idsxlhjl7\"\npath=\"res://.godot/imported/reasonate-github-header.jpg-02c12c49b558975276fb580939e5b7bd.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://reasonate-github-header.jpg\"\ndest_files=[\"res://.godot/imported/reasonate-github-header.jpg-02c12c49b558975276fb580939e5b7bd.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": "release-please-config.json",
    "content": "{\n    \"packages\": {\n        \".\": {\n            \"release-type\": \"simple\",\n            \"extra-files\": [\n                \"addons/resonate/plugin.cfg\"\n            ]\n        }\n    }\n}\n"
  },
  {
    "path": "resonate-logo.png.import",
    "content": "[remap]\n\nimporter=\"texture\"\ntype=\"CompressedTexture2D\"\nuid=\"uid://dughwn5cisraq\"\npath=\"res://.godot/imported/resonate-logo.png-6a8a4a98c9457c7843a50699ddface48.ctex\"\nmetadata={\n\"vram_texture\": false\n}\n\n[deps]\n\nsource_file=\"res://resonate-logo.png\"\ndest_files=[\"res://.godot/imported/resonate-logo.png-6a8a4a98c9457c7843a50699ddface48.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": "shared/description_label_settings.tres",
    "content": "[gd_resource type=\"LabelSettings\" format=3 uid=\"uid://bldprl7vev3uv\"]\n\n[resource]\nline_spacing = 8.0\nfont_size = 18\n"
  },
  {
    "path": "shared/stats_label_settings.tres",
    "content": "[gd_resource type=\"LabelSettings\" format=3 uid=\"uid://bnsew6gimofj5\"]\n\n[resource]\nline_spacing = 8.0\nfont_color = Color(0.117647, 0.8, 0.858824, 1)\n"
  },
  {
    "path": "shared/title_label_settings.tres",
    "content": "[gd_resource type=\"LabelSettings\" format=3 uid=\"uid://tysg4xge4hsu\"]\n\n[resource]\nfont_size = 38\nfont_color = Color(0.92549, 0.490196, 0, 1)\n"
  }
]