├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── contributing.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── .travis ├── download-fmod.sh └── download_android_sdk.sh ├── LICENSE ├── README.md ├── SCsub ├── api ├── .gitkeep └── COPY_FMOD_API_HERE ├── callbacks.h ├── config.py ├── demo ├── Banks │ └── Desktop │ │ ├── Master.bank │ │ └── Master.strings.bank ├── Scenes │ └── Main.tscn ├── Scripts │ ├── FMOD.gd │ ├── Listener.gd │ └── Main.gd ├── default_env.tres ├── icon.png ├── icon.png.import └── project.godot ├── docs └── building.md ├── godot_fmod.cpp ├── godot_fmod.h ├── register_types.cpp └── register_types.h /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug you've discovered with the integration. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Configuration** 27 | - OS: [e.g. Windows] 28 | - FMOD version [eg. 2.00.00] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature for the integration. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Hey there! Thank you for your interest in my project. If you wish to contribute code or new features, here are some guidelines and helpful information. 3 | 4 | ## What prior knowledge do I need 5 | 6 | These will mostly apply to programmers but if you are a sound designer or a composer there are other ways to contribute as well. Refer to the [contribution guidelines](https://github.com/alexfonseka/godot-fmod-integration/blob/master/.github/contributing.md#contribution-guidelines) section. 7 | 8 | - You would need some basic knowledge of C++ and the FMOD API. 9 | - A basic understanding of how the Godot game engine works. 10 | - A basic understanding of Python scripting and the SCons build system. 11 | 12 | ## Contribution guidelines 13 | 14 | ### Bug reports 15 | - Short summary of the problem in 1-3 sentences followed by a more detailed description of the issue (if possible). 16 | - Steps to reproduce the behavior. Provide sample code and other resources if available. 17 | - Screenshots or a short video (if possible). 18 | - An existing workaround that you've found (if possible). 19 | 20 | ### Feature requests 21 | - The potential use cases you see for the (new) feature. 22 | - Your role (eg. Sound designer, composer, programmer etc.) 23 | 24 | ### Pull requests 25 | - Include a short summary of the changes made. 26 | - Your changes and or additions must adhere to the [MIT License](https://github.com/alexfonseka/godot-fmod-integration/blob/master/LICENSE). 27 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | _**The following is simply a checklist for you as a contributer. Do not include this as part of your Pull Request submission**._ 2 | 3 | ### Pull Request Checklist 4 | 5 | - Are your changes in a different PR branch apart from master? If not, please create one and merge your changes into it before submitting. 6 | - Have you tested your contribution for immediate issues and or compilation errors? 7 | - Is your code properly formatted? 8 | - Does your coding style and function naming convention match the rest of the project? 9 | - Do all of your changes and or additions adhere to the [MIT License](https://github.com/alexfonseka/godot-fmod-integration/blob/master/LICENSE)? Note that should your PR be merged, it will be merged under the aforementioned MIT License. 10 | 11 | Thank you for contributing to open source. 12 | 13 | _**The above is simply a checklist for you as a contributer. Do not include it as part of your Pull Request submission**._ 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # FMOD API 2 | api/fsbank 3 | api/core 4 | api/studio 5 | 6 | demo/.import/ 7 | demo/test_sounds/ 8 | 9 | __pycache__ 10 | *.obj 11 | *.pyc 12 | *.d 13 | *.o 14 | 15 | .vscode 16 | 17 | *DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | dist: xenial 3 | osx_image: xcode10.1 4 | 5 | matrix: 6 | include: 7 | - name: Linux 8 | os: linux 9 | compiler: gcc 10 | addons: 11 | apt: 12 | packages: [scons, pkg-config, build-essential, p7zip-full] 13 | env: PLATFORM=x11 ADDITIONAL_FLAGS="tools=no use_llvm=yes target=release bits=64" 14 | 15 | - name: MacOS 16 | os: osx 17 | osx_image: xcode10.2 18 | compiler: clang 19 | env: ADDITIONAL_FLAGS="" PLATFORM="osx" 20 | addons: 21 | homebrew: 22 | packages: 23 | - jq 24 | - p7zip 25 | 26 | - name: Windows 27 | os: windows 28 | env: ADDITIONAL_FLAGS="" PLATFORM="windows" 29 | 30 | - name: Android ARM 31 | os: linux 32 | env: ADDITIONAL_FLAGS="android_arch=armv7" PLATFORM="android" 33 | addons: 34 | apt: 35 | sources: 36 | - llvm-toolchain-xenial-6.0 37 | packages: 38 | [scons, pkg-config, build-essential, p7zip-full, clang-format-6.0] 39 | 40 | - name: Android ARM64 41 | os: linux 42 | env: ADDITIONAL_FLAGS="android_arch=arm64v8" PLATFORM="android" 43 | addons: 44 | apt: 45 | sources: 46 | - llvm-toolchain-xenial-6.0 47 | packages: 48 | [scons, pkg-config, build-essential, p7zip-full, clang-format-6.0] 49 | 50 | - name: iOS 51 | os: osx 52 | osx_image: xcode10.2 53 | compiler: clang 54 | env: ADDITIONAL_FLAGS="target=release" PLATFORM="iphone" 55 | addons: 56 | homebrew: 57 | packages: 58 | - jq 59 | - p7zip 60 | 61 | install: 62 | - if [[ "$PLATFORM" == "x11" ]]; then 63 | sudo apt-get install -y build-essential scons pkg-config libx11-dev libxcursor-dev libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libfreetype6-dev libssl-dev libudev-dev libxi-dev libxrandr-dev yasm; 64 | fi 65 | 66 | - if [[ "$PLATFORM" == "android" ]]; then 67 | cd .. && sh godot-fmod-integration/.travis/download_android_sdk.sh && cd godot-fmod-integration; 68 | fi 69 | 70 | - rm -rf api 71 | - mv .travis/download-fmod.sh ./ && chmod +x download-fmod.sh && ./download-fmod.sh $FMODUSER $FMODPASS $PLATFORM 72 | - if [[ "$PLATFORM" == "x11" ]]; then tar -xvf fmodstudioapi20006linux.tar.gz; fi 73 | - if [[ "$PLATFORM" == "osx" ]]; then hdiutil attach fmodstudioapi20006mac-installer.dmg; fi 74 | - if [[ "$PLATFORM" == "windows" ]]; then 75 | 7z x fmodstudioapi20006win-installer.exe; 76 | mv api/core/lib/x64/fmod_vc.lib api/core/lib/x64/fmod_vc.windows.tools.64.lib; 77 | mv api/studio/lib/x64/fmodstudio_vc.lib api/studio/lib/x64/fmodstudio_vc.windows.tools.64.lib; 78 | fi 79 | - if [[ "$PLATFORM" == "iphone" ]]; then hdiutil attach fmodstudioapi20006ios-installer.dmg; fi 80 | - if [[ "$PLATFORM" == "android" ]]; then tar -xvf fmodstudioapi20006android.tar.gz; fi 81 | - if [[ "$PLATFORM" == "x11" ]]; then mv fmodstudioapi20006linux/api ./; fi 82 | - if [[ "$PLATFORM" == "osx" ]]; then cp -r "/Volumes/FMOD Programmers API Mac/FMOD Programmers API/api" ./; fi 83 | - if [[ "$PLATFORM" == "iphone" ]]; then cp -r "/Volumes/FMOD Programmers API iOS/FMOD Programmers API/api" ./; fi 84 | - if [[ "$PLATFORM" == "android" ]]; then mv fmodstudioapi20006android/api ./; fi 85 | 86 | - cd .. && git clone https://github.com/godotengine/godot.git && cd godot && git checkout 3.1.1-stable 87 | - mv ../godot-fmod-integration modules/fmod 88 | 89 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 90 | brew update; 91 | brew install scons; 92 | fi 93 | 94 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then 95 | curl -LO https://downloads.sourceforge.net/project/scons/scons-local/3.0.5/scons-local-3.0.5.zip; 96 | unzip scons-local-3.0.5.zip; 97 | fi 98 | 99 | script: 100 | - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then 101 | export SCONS="./scons.bat"; 102 | else 103 | export SCONS="scons"; 104 | fi 105 | 106 | - if [[ "$PLATFORM" == "android" ]]; then 107 | $SCONS platform=$PLATFORM $ADDITIONAL_FLAGS target=release; 108 | $SCONS platform=$PLATFORM $ADDITIONAL_FLAGS target=debug; 109 | else 110 | $SCONS platform=$PLATFORM $ADDITIONAL_FLAGS; 111 | fi 112 | 113 | - if [[ "$STATIC_CHECKS" == "yes" ]]; then 114 | sh ./misc/travis/clang-format.sh; 115 | fi 116 | 117 | - if [[ "$ADDITIONAL_FLAGS" == "android_arch=arm64v8" ]]; then 118 | rm platform/android/java/libs/release/arm64-v8a/libfmod.so && rm platform/android/java/libs/release/arm64-v8a/libfmodstudio.so; 119 | rm platform/android/java/libs/debug/arm64-v8a/libfmod.so && rm platform/android/java/libs/debug/arm64-v8a/libfmodstudio.so; 120 | tar zcvf libgodot.android_arm64-v8a.tar.gz platform/android/java/libs/; 121 | mv libgodot.android_arm64-v8a.tar.gz bin/; 122 | fi 123 | 124 | - if [[ "$ADDITIONAL_FLAGS" == "android_arch=armv7" ]]; then 125 | rm platform/android/java/libs/release/armeabi-v7a/libfmod.so && rm platform/android/java/libs/release/armeabi-v7a/libfmodstudio.so; 126 | rm platform/android/java/libs/debug/armeabi-v7a/libfmod.so && rm platform/android/java/libs/debug/armeabi-v7a/libfmodstudio.so; 127 | tar zcvf libgodot.android_armeabi-v7a.tar.gz platform/android/java/libs/; 128 | mv libgodot.android_armeabi-v7a.tar.gz bin/; 129 | fi 130 | 131 | deploy: 132 | provider: releases 133 | api_key: 134 | secure: $TRAVIS_TOKEN 135 | file: 136 | - 'bin/godot.windows.tools.64.exe' 137 | - 'bin/godot.x11.opt.64.llvm' 138 | - 'bin/godot.osx.tools.64' 139 | - 'bin/libgodot.android_arm64-v8a.tar.gz' 140 | - 'bin/libgodot.android_armeabi-v7a.tar.gz' 141 | - 'bin/libgodot.iphone.opt.arm64.a' 142 | skip_cleanup: true 143 | on: 144 | tags: true 145 | -------------------------------------------------------------------------------- /.travis/download-fmod.sh: -------------------------------------------------------------------------------- 1 | TOKEN=$(curl -X POST -u $1:$2 https://www.fmod.com/api-login | jq -r '.token') 2 | if [[ $3 == "x11" ]] 3 | then 4 | URL=$(curl -H 'Accept: application/json' -H "Authorization: Bearer ${TOKEN}" https://www.fmod.com/api-get-download-link\?path\=files/fmodstudio/api/Linux/\&filename\=fmodstudioapi20006linux.tar.gz\&user\=$1 | jq -r '.url') 5 | wget -O fmodstudioapi20006linux.tar.gz $URL 6 | fi 7 | if [[ $3 == "osx" ]] 8 | then 9 | URL=$(curl -H 'Accept: application/json' -H "Authorization: Bearer ${TOKEN}" https://www.fmod.com/api-get-download-link\?path\=files/fmodstudio/api/Mac/\&filename\=fmodstudioapi20006mac-installer.dmg\&user\=$1 | jq -r '.url') 10 | wget -O fmodstudioapi20006mac-installer.dmg $URL 11 | fi 12 | if [[ $3 == "windows" ]] 13 | then 14 | URL=$(curl -H 'Accept: application/json' -H "Authorization: Bearer ${TOKEN}" https://www.fmod.com/api-get-download-link\?path\=files/fmodstudio/api/Win/\&filename\=fmodstudioapi20006win-installer.exe\&user\=$1 | jq -r '.url') 15 | wget -O fmodstudioapi20006win-installer.exe $URL 16 | fi 17 | if [[ $3 == "iphone" ]] 18 | then 19 | URL=$(curl -H 'Accept: application/json' -H "Authorization: Bearer ${TOKEN}" https://www.fmod.com/api-get-download-link\?path\=files/fmodstudio/api/iOS/\&filename\=fmodstudioapi20006ios-installer.dmg\&user\=$1 | jq -r '.url') 20 | wget -O fmodstudioapi20006ios-installer.dmg $URL 21 | fi 22 | if [[ $3 == "android" ]] 23 | then 24 | URL=$(curl -H 'Accept: application/json' -H "Authorization: Bearer ${TOKEN}" https://www.fmod.com/api-get-download-link\?path\=files/fmodstudio/api/Android/\&filename\=fmodstudioapi20006android.tar.gz\&user\=$1 | jq -r '.url') 25 | wget -O fmodstudioapi20006android.tar.gz $URL 26 | fi 27 | -------------------------------------------------------------------------------- /.travis/download_android_sdk.sh: -------------------------------------------------------------------------------- 1 | ANDROID_BASE_URL=http://dl.google.com/android/repository 2 | 3 | ANDROID_SDK_RELEASE=4333796 4 | ANDROID_SDK_DIR=android-sdk 5 | ANDROID_SDK_FILENAME=sdk-tools-linux-$ANDROID_SDK_RELEASE.zip 6 | ANDROID_SDK_URL=$ANDROID_BASE_URL/$ANDROID_SDK_FILENAME 7 | ANDROID_SDK_PATH=$GODOT_BUILD_TOOLS_PATH/$ANDROID_SDK_DIR 8 | ANDROID_SDK_SHA256=92ffee5a1d98d856634e8b71132e8a95d96c83a63fde1099be3d86df3106def9 9 | 10 | ANDROID_NDK_RELEASE=r18 11 | ANDROID_NDK_DIR=android-ndk 12 | ANDROID_NDK_FILENAME=android-ndk-$ANDROID_NDK_RELEASE-linux-x86_64.zip 13 | ANDROID_NDK_URL=$ANDROID_BASE_URL/$ANDROID_NDK_FILENAME 14 | ANDROID_NDK_PATH=$GODOT_BUILD_TOOLS_PATH/$ANDROID_NDK_DIR 15 | ANDROID_NDK_SHA1=2ac2e8e1ef73ed551cac3a1479bb28bd49369212 16 | 17 | 18 | curl -L -O $ANDROID_SDK_URL 19 | unzip -qq $ANDROID_SDK_FILENAME -d $ANDROID_SDK_DIR 20 | 21 | curl -L -O $ANDROID_NDK_URL 22 | unzip -qq $ANDROID_NDK_FILENAME 23 | mv android-ndk-$ANDROID_NDK_RELEASE $ANDROID_NDK_ROOT 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alex Fonseka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | I started developing this back in 2019 but no longer maintain it. If you found this recently and are looking to integrate FMOD into a new Godot project, consider using the [GDNative fork of this repo by utopia-rise](https://github.com/utopia-rise/fmod-gdnative). 2 | 3 | Some of the documentation present here may still prove useful, so do have a read if you're interested. 4 | 5 | All the integration code in this repo is MIT-licensed but in order to publish your FMOD-powered game commercially, you must register your project with FMOD and obtain a license. You don't need a license to get started however and when you're ready to publish, getting one is free for small projects and indies. Always check for the latest licensing terms on FMOD's website in case they get updated. 6 | 7 | All the best with your Godot project - Alex. 8 | 9 | P.S. Shoutout to Mike from Game From Scratch for covering this repo on his YouTube channel ✌ 10 | ___ 11 | 12 | # FMOD Studio integration for Godot 13 | 14 | A Godot C++ module that provides an integration and GDScript bindings for the FMOD Studio API. 15 | 16 | This module exposes most of the Studio API functions to Godot's GDScript and also provides helpers for performing common functions like attaching Studio events to Godot nodes and playing 3D/positional audio. _It is still very much a work in progress and some API functions are not yet exposed._ Feel free to tweak/extend it based on your project's needs. 17 | 18 | ### Latest release 19 | 20 | Precompiled engine binaries for Windows, macOS and Linux with FMOD Studio already integrated, is available for downloading in the [Releases](https://github.com/alexfonseka/godot-fmod-integration/releases) tab. 21 | 22 | | Current build status | [![Build Status](https://travis-ci.com/alexfonseka/godot-fmod-integration.svg?branch=master)](https://travis-ci.com/alexfonseka/godot-fmod-integration) | 23 | | -------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------: | 24 | 25 | 26 | ### Building 27 | 28 | If you wish to compile the module yourself, build instructions are available [here](https://github.com/alexfonseka/godot-fmod-integration/blob/master/docs/building.md). 29 | 30 | ## Using the module 31 | 32 | - [Basic usage](https://github.com/alexfonseka/godot-fmod-integration#basic-usage) 33 | - [Calling Studio events](https://github.com/alexfonseka/godot-fmod-integration#calling-studio-events) 34 | - [Using the integration helpers](https://github.com/alexfonseka/godot-fmod-integration#using-the-integration-helpers) 35 | - [Timeline marker & music beat callbacks](https://github.com/alexfonseka/godot-fmod-integration#timeline-marker--music-beat-callbacks) 36 | - [Playing sounds using FMOD Core / Low Level API](https://github.com/alexfonseka/godot-fmod-integration#playing-sounds-using-fmod-core--low-level-api) 37 | - [Changing the default audio output device](https://github.com/alexfonseka/godot-fmod-integration#changing-the-default-audio-output-device) 38 | - [Profiling & querying performance data](https://github.com/alexfonseka/godot-fmod-integration#profiling--querying-performance-data) 39 | 40 | ### Basic usage 41 | 42 | Start playing sounds in just 5 lines of GDScript! 43 | 44 | ```gdscript 45 | extends Node 46 | 47 | func _ready(): 48 | # initialize FMOD 49 | # initializing with the LIVE_UPDATE flag lets you 50 | # connect to Godot from the FMOD Studio editor 51 | # and author events in realtime 52 | Fmod.system_init(1024, Fmod.FMOD_STUDIO_INIT_LIVEUPDATE, Fmod.FMOD_INIT_VOL0_BECOMES_VIRTUAL) 53 | 54 | # load banks 55 | # place your banks inside the project directory 56 | Fmod.bank_load("./Banks/Desktop/Master.bank", Fmod.FMOD_STUDIO_LOAD_BANK_NORMAL) 57 | Fmod.bank_load("./Banks/Desktop/Master.strings.bank", Fmod.FMOD_STUDIO_LOAD_BANK_NORMAL) 58 | 59 | # register a listener 60 | Fmod.system_add_listener($Listener) 61 | 62 | # play some events 63 | Fmod.play_one_shot("event:/Footstep", $SoundSource1) 64 | Fmod.play_one_shot("event:/Gunshot", $SoundSource2) 65 | 66 | func _process(delta): 67 | # update FMOD every tick 68 | # calling system_update also updates the listener 3D position 69 | # and 3D positions of any attached event instances 70 | Fmod.system_update() 71 | ``` 72 | 73 | ### Calling Studio events 74 | 75 | One-shots are great for quick sounds which you would want to simply fire and forget. But what about something a bit more complex like a looping sound or an interactive music event with a bunch of states? Here's an example of a Studio event called manually (ie. not directly managed by the integration). You can then call functions on that specific instance such as setting parameters. Remember to release the instance once you're done with it! 76 | 77 | ```gdscript 78 | # create an event instance 79 | # this is a music event that has been authored in the Studio editor 80 | var my_music_event = Fmod.create_event_instance("event:/Waveshaper - Wisdom of Rage") 81 | 82 | # start the event 83 | Fmod.event_start(my_music_event) 84 | 85 | # wait a bit 86 | yield(music_state_timer, "timeout") 87 | 88 | # setting an event parameter 89 | # in this case causes the music to transition to the next phase 90 | Fmod.event_set_parameter(my_music_event, "State", 2.0) 91 | 92 | # wait a bit 93 | yield(music_timer, "timeout") 94 | 95 | # stop the event 96 | Fmod.event_stop(my_music_event, Fmod.FMOD_STUDIO_STOP_ALLOWFADEOUT) 97 | 98 | # schedule the event for release 99 | Fmod.event_release(my_music_event) 100 | ``` 101 | 102 | ### Using the integration helpers 103 | 104 | These are helper functions provided by the integration for playing events and attaching event instances to Godot Nodes for 3D/positional audio. The listener position and 3D attributes of any attached instances are automatically updated every time you call `system_update()`. Instances are also automatically cleaned up once finished so you don't have to manually call `event_release()`. 105 | 106 | ```gdscript 107 | # play an event at this Node's position 108 | # 3D attributes are only set ONCE 109 | # parameters cannot be set 110 | Fmod.play_one_shot("event:/Footstep", self) 111 | 112 | # same as play_one_shot but lets you set initial parameters 113 | # subsequent parameters cannot be set 114 | Fmod.play_one_shot_with_params("event:/Footstep", self, { "Surface": 1.0, "Speed": 2.0 }) 115 | 116 | # play an event attached to this Node 117 | # 3D attributes are automatically set every frame (when update is called) 118 | # parameters cannot be set 119 | Fmod.play_one_shot_attached("event:/Footstep", self) 120 | 121 | # same as play_one_shot_attached but lets you set initial parameters 122 | # subsequent parameters cannot be set 123 | Fmod.play_one_shot_attached_with_params("event:/Footstep", self, { "Surface": 1.0, "Speed": 2.0 }) 124 | 125 | # attaches a manually called instance to a Node 126 | # once attached, 3D attributes are automatically set every frame (when update is called) 127 | Fmod.attach_instance_to_node(event_instance, self) 128 | 129 | # detaches the instance from its Node 130 | Fmod.detach_instance_from_node(event_instance) 131 | 132 | # quick helpers for pausing and muting 133 | # affects all events including manually called instances 134 | Fmod.pause_all_events() 135 | Fmod.unpause_all_events() 136 | Fmod.mute_all_events() 137 | Fmod.unmute_all_events() 138 | 139 | # returns True if a bank is currently loading 140 | Fmod.banks_still_loading() 141 | 142 | # blocks the calling thread until all sample loading is done 143 | Fmod.wait_for_all_loads() 144 | ``` 145 | 146 | ### Timeline marker & music beat callbacks 147 | 148 | You can have events subscribe to Studio callbacks to implement rhythm based game mechanics. Event callbacks leverage Godot's signal system and you can connect your callback functions through the integration. 149 | 150 | ```gdscript 151 | # create a new event instance 152 | var my_music_event = Fmod.create_event_instance("event:/schmid - 140 Part 2B") 153 | 154 | # request callbacks from this instance 155 | # in this case request both Marker and Beat callbacks 156 | Fmod.event_set_callback(my_music_event, 157 | Fmod.FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER | Fmod.FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT) 158 | 159 | # hook up our signals 160 | Fmod.connect("timeline_beat", self, "_on_beat") 161 | Fmod.connect("timeline_marker", self, "_on_marker") 162 | 163 | # will be called on every musical beat 164 | func _on_beat(params): 165 | print(params) 166 | 167 | # will be called whenever a new marker is encountered 168 | func _on_marker(params): 169 | print(params) 170 | ``` 171 | 172 | In the above example, `params` is a Dictionary which contains parameters passed in by FMOD. These vary from each callback. For beat callbacks it will contain fields such as the current beat, current bar, time signature etc. For marker callbacks it will contain the marker name etc. The event_id of the instance that triggered the callback will also be passed in. You can use this to filter out individual callbacks if multiple events are subscribed. 173 | 174 | ### Playing sounds using FMOD Core / Low Level API 175 | 176 | You can load and play any sound file in your project directory using the FMOD Low Level API bindings. Similar to Studio events these instances have to be released manually. Refer to FMOD's documentation pages for a list of compatible sound formats. If you're using FMOD Studio it's unlikely you'll have to use this API though. 177 | 178 | ```gdscript 179 | # create a sound 180 | var my_sound = Fmod.sound_create("./ta-da.wav", Fmod.FMOD_DEFAULT) 181 | 182 | # play the sound 183 | # this returns a handle to the channel 184 | var channel_id = Fmod.sound_play(my_sound) 185 | 186 | # wait a bit 187 | yield(sound_timer, "timeout") 188 | 189 | Fmod.sound_stop(channel_id) 190 | Fmod.sound_release(my_sound) 191 | ``` 192 | 193 | ### Changing the default audio output device 194 | 195 | By default, FMOD will use the primary audio output device as determined by the operating system. This can be changed at runtime, ideally through your game's Options Menu. 196 | 197 | Here, `system_get_available_drivers()` returns an Array which contains a Dictionary for every audio driver found. Each Dictionary contains fields such as the name, sample rate 198 | and speaker config of the respective driver. Most importantly, it contains the id for that driver. 199 | 200 | ```gdscript 201 | # retrieve all available audio drivers 202 | var drivers = Fmod.system_get_available_drivers() 203 | 204 | # change the audio driver 205 | # you must pass in the id of the respective driver 206 | Fmod.system_set_driver(id) 207 | 208 | # retrieve the id of the currently set driver 209 | var id = Fmod.system_get_driver() 210 | ``` 211 | 212 | ### Profiling & querying performance data 213 | 214 | `system_get_performance_data()` returns an object which contains current performance stats for CPU, Memory and File Streaming usage of both FMOD Studio and the Core System. 215 | 216 | ```gdscript 217 | # called every frame 218 | var perf_data = Fmod.system_get_performance_data() 219 | 220 | print(perf_data.CPU) 221 | print(perf_data.memory) 222 | print(perf_data.file) 223 | ``` 224 | 225 | ## Contributing 226 | 227 | This project is still a work in progress and is probably not yet ready for use in full-blown production. If you run into issues (crashes, memory leaks, broken 3D sound etc.) let us know through the [issue tracker](https://github.com/alexfonseka/godot-fmod-integration/issues). If you are a programmer, sound designer or a composer and wish to contribute, the contribution guidelines are available [here](https://github.com/alexfonseka/godot-fmod-integration/blob/master/.github/contributing.md). Thank you for being interested in this project! ✌ 228 | 229 | ## An update from the creator 230 | 231 | Unfortunately, due to full-time work and personal reasons, the development of this project has slowed down significantly and I'm no longer able to actively maintain and contribute new features. 232 | 233 | However, this does not mean that the project is abandoned. All the work I've contributed so far is licensed under MIT, one of the most liberal open source licenses available. So feel free to use/modify/extend the code for your own projects - commercial or otherwise. Note that the MIT license only applies to the integration, not the FMOD SDK itself, which is proprietary and is not included in this repository. 234 | -------------------------------------------------------------------------------- /SCsub: -------------------------------------------------------------------------------- 1 | Import('env') 2 | import subprocess 3 | 4 | def sys_exec(args): 5 | proc = subprocess.Popen(args, stdout=subprocess.PIPE) 6 | (out, err) = proc.communicate() 7 | return out.rstrip("\r\n").lstrip() 8 | 9 | module_env = env.Clone() 10 | module_env.add_source_files(env.modules_sources,"*.cpp") 11 | module_env.Append(CPPPATH=["#modules/fmod/api/core/inc", "#modules/fmod/api/studio/inc"]) 12 | 13 | if env["platform"] == "iphone": 14 | sys_exec(["cp", "api/core/lib/libfmod_iphoneos.a", "../../platform/iphone/libfmod_iphoneos.a"]) 15 | sys_exec(["cp", "api/studio/lib/libfmodstudio_iphoneos.a", "../../platform/iphone/libfmodstudio_iphoneos.a"]) 16 | -------------------------------------------------------------------------------- /api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heraldofgargos/godot-fmod-integration/db1dc3568a1ec365f75248087e415a80bdf0e2ba/api/.gitkeep -------------------------------------------------------------------------------- /api/COPY_FMOD_API_HERE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heraldofgargos/godot-fmod-integration/db1dc3568a1ec365f75248087e415a80bdf0e2ba/api/COPY_FMOD_API_HERE -------------------------------------------------------------------------------- /callbacks.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* callbacks.h */ 3 | /*************************************************************************/ 4 | /* */ 5 | /* FMOD Studio module and bindings for the Godot game engine */ 6 | /* */ 7 | /*************************************************************************/ 8 | /* Copyright (c) 2020 Alex Fonseka */ 9 | /* */ 10 | /* Permission is hereby granted, free of charge, to any person obtaining */ 11 | /* a copy of this software and associated documentation files (the */ 12 | /* "Software"), to deal in the Software without restriction, including */ 13 | /* without limitation the rights to use, copy, modify, merge, publish, */ 14 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 15 | /* permit persons to whom the Software is furnished to do so, subject to */ 16 | /* the following conditions: */ 17 | /* */ 18 | /* The above copyright notice and this permission notice shall be */ 19 | /* included in all copies or substantial portions of the Software. */ 20 | /* */ 21 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 22 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 23 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 24 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 25 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 26 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 27 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 28 | /*************************************************************************/ 29 | 30 | #pragma once 31 | 32 | #include "core/dictionary.h" 33 | #include "core/map.h" 34 | 35 | #include "api/studio/inc/fmod_studio.hpp" 36 | 37 | namespace Callbacks { 38 | 39 | struct CallbackInfo { 40 | Dictionary markerCallbackInfo; 41 | Dictionary beatCallbackInfo; 42 | Dictionary soundCallbackInfo; 43 | 44 | bool markerSignalEmitted = true; 45 | bool beatSignalEmitted = true; 46 | bool soundSignalEmitted = true; 47 | }; 48 | 49 | extern Mutex *mut; 50 | 51 | FMOD_RESULT F_CALLBACK eventCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE *event, void *parameters); 52 | 53 | } // namespace Callbacks 54 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | def can_build(env, platform): 2 | return platform == "x11" or platform == "windows" or platform == "osx" or platform == "android" or platform == "iphone" 3 | 4 | 5 | def configure(env): 6 | if env["platform"] == "windows": 7 | env.Append(LIBS=["fmod_vc", "fmodstudio_vc"]) 8 | if env["bits"] == "32": 9 | env.Append(LIBPATH=["#modules/fmod/api/core/lib/x86/", 10 | "#modules/fmod/api/studio/lib/x86/"]) 11 | else: 12 | env.Append(LIBPATH=["#modules/fmod/api/core/lib/x64/", 13 | "#modules/fmod/api/studio/lib/x64/"]) 14 | 15 | elif env["platform"] == "x11": 16 | env.Append(LIBS=["fmod", "fmodstudio"]) 17 | if env["bits"] == "32": 18 | env.Append( 19 | LIBPATH=["#modules/fmod/api/core/lib/x86/", 20 | "#modules/fmod/api/studio/lib/x86/"]) 21 | else: 22 | env.Append( 23 | LIBPATH=["#modules/fmod/api/core/lib/x86_64/", 24 | "#modules/fmod/api/studio/lib/x86_64/"]) 25 | 26 | elif env["platform"] == "osx": 27 | env.Append(LIBS=["fmod", "fmodstudio"]) 28 | env.Append( 29 | LIBPATH=["#modules/fmod/api/core/lib/", "#modules/fmod/api/studio/lib/"]) 30 | 31 | elif env["platform"] == "android": 32 | if env["android_arch"] == "arm64v8": 33 | env.Append(LIBPATH=["#modules/fmod/api/core/lib/arm64-v8a", 34 | "#modules/fmod/api/studio/lib/arm64-v8a"]) 35 | else: 36 | env.Append(LIBPATH=["#modules/fmod/api/core/lib/armeabi-v7a", 37 | "#modules/fmod/api/studio/lib/armeabi-v7a"]) 38 | env.Append(LIBS=["fmod", "fmodstudio"]) 39 | 40 | elif env["platform"] == "iphone": 41 | env.Append(LIBPATH=["#modules/fmod/api/core/lib/", 42 | "#modules/fmod/api/studio/lib/"]) 43 | env.Append(LIBS=["libfmod_iphoneos.a", "libfmodstudio_iphoneos.a"]) 44 | -------------------------------------------------------------------------------- /demo/Banks/Desktop/Master.bank: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heraldofgargos/godot-fmod-integration/db1dc3568a1ec365f75248087e415a80bdf0e2ba/demo/Banks/Desktop/Master.bank -------------------------------------------------------------------------------- /demo/Banks/Desktop/Master.strings.bank: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heraldofgargos/godot-fmod-integration/db1dc3568a1ec365f75248087e415a80bdf0e2ba/demo/Banks/Desktop/Master.strings.bank -------------------------------------------------------------------------------- /demo/Scenes/Main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://Scripts/Main.gd" type="Script" id=1] 4 | [ext_resource path="res://icon.png" type="Texture" id=2] 5 | [ext_resource path="res://Scripts/Listener.gd" type="Script" id=3] 6 | 7 | [node name="Main" type="Node2D"] 8 | script = ExtResource( 1 ) 9 | 10 | [node name="Listener" type="Sprite" parent="."] 11 | position = Vector2( 522.105, 320 ) 12 | texture = ExtResource( 2 ) 13 | script = ExtResource( 3 ) 14 | 15 | [node name="ListenerLabel" type="RichTextLabel" parent="Listener"] 16 | margin_left = -28.421 17 | margin_top = -53.6842 18 | margin_right = 59.579 19 | margin_bottom = -19.6842 20 | text = "LISTENER" 21 | 22 | [node name="SoundSource1" type="Sprite" parent="."] 23 | modulate = Color( 0.960784, 0.141176, 0.737255, 1 ) 24 | position = Vector2( 112.632, 270.526 ) 25 | texture = ExtResource( 2 ) 26 | 27 | [node name="SoundSource1Label" type="RichTextLabel" parent="SoundSource1"] 28 | modulate = Color( 0, 0, 0, 1 ) 29 | margin_left = -38.3158 30 | margin_top = -56.2632 31 | margin_right = 80.6842 32 | margin_bottom = -29.2632 33 | text = "CAR ENGINE" 34 | 35 | [node name="SoundSource2" type="Sprite" parent="."] 36 | modulate = Color( 0.960784, 0.141176, 0.737255, 1 ) 37 | position = Vector2( 897.895, 270.526 ) 38 | texture = ExtResource( 2 ) 39 | 40 | [node name="SoundSource2Label" type="RichTextLabel" parent="SoundSource2"] 41 | modulate = Color( 0, 0, 0, 1 ) 42 | margin_left = -35.1578 43 | margin_top = -56.2632 44 | margin_right = 83.8422 45 | margin_bottom = -29.2632 46 | text = "WATERFALL" 47 | 48 | -------------------------------------------------------------------------------- /demo/Scripts/FMOD.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | func _ready(): 4 | # set up FMOD 5 | Fmod.system_set_software_format(0, Fmod.FMOD_SPEAKERMODE_STEREO, 0) 6 | Fmod.system_init(1024, Fmod.FMOD_STUDIO_INIT_LIVEUPDATE, Fmod.FMOD_INIT_VOL0_BECOMES_VIRTUAL) 7 | 8 | # load banks 9 | Fmod.bank_load("./Banks/Desktop/Master.bank", Fmod.FMOD_STUDIO_LOAD_BANK_NORMAL) 10 | Fmod.bank_load("./Banks/Desktop/Master.strings.bank", Fmod.FMOD_STUDIO_LOAD_BANK_NORMAL) 11 | 12 | # wait for bank loads 13 | Fmod.wait_for_all_loads() 14 | 15 | # connect signals 16 | Fmod.connect("timeline_beat", self, "_on_beat") 17 | Fmod.connect("timeline_marker", self, "_on_marker") 18 | Fmod.connect("sound_played", self, "_on_sound_played") 19 | Fmod.connect("sound_stopped", self, "_on_sound_stopped") 20 | 21 | func _on_beat(params): 22 | print(params) 23 | 24 | func _on_marker(params): 25 | print(params) 26 | 27 | func _on_sound_played(params): 28 | print(params) 29 | 30 | func _on_sound_stopped(params): 31 | print(params) 32 | 33 | #warning-ignore:unused_argument 34 | func _process(delta): 35 | Fmod.system_update() 36 | -------------------------------------------------------------------------------- /demo/Scripts/Listener.gd: -------------------------------------------------------------------------------- 1 | extends Sprite 2 | 3 | export var speed = 125 4 | 5 | func _ready(): 6 | pass 7 | 8 | func _physics_process(delta): 9 | if Input.is_action_pressed("ui_right"): 10 | position.x = position.x + (speed * delta) 11 | 12 | if Input.is_action_pressed("ui_left"): 13 | position.x = position.x - (speed * delta) 14 | 15 | if Input.is_action_pressed("ui_up"): 16 | position.y = position.y - (speed * delta) 17 | 18 | if Input.is_action_pressed("ui_down"): 19 | position.y = position.y + (speed * delta) 20 | -------------------------------------------------------------------------------- /demo/Scripts/Main.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | func _ready(): 4 | # register a listener 5 | Fmod.system_add_listener($Listener) 6 | 7 | # play some events 8 | # technically these are not one-shots but this is just for demo's sake 9 | Fmod.play_one_shot("event:/Car engine", $SoundSource1) 10 | Fmod.play_one_shot("event:/Waterfall", $SoundSource2) -------------------------------------------------------------------------------- /demo/default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | 6 | [resource] 7 | 8 | background_mode = 2 9 | background_sky = SubResource( 1 ) 10 | 11 | -------------------------------------------------------------------------------- /demo/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heraldofgargos/godot-fmod-integration/db1dc3568a1ec365f75248087e415a80bdf0e2ba/demo/icon.png -------------------------------------------------------------------------------- /demo/icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | 7 | [deps] 8 | 9 | source_file="res://icon.png" 10 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 11 | 12 | [params] 13 | 14 | compress/mode=0 15 | compress/lossy_quality=0.7 16 | compress/hdr_mode=0 17 | compress/bptc_ldr=0 18 | compress/normal_map=0 19 | flags/repeat=0 20 | flags/filter=true 21 | flags/mipmaps=false 22 | flags/anisotropic=false 23 | flags/srgb=2 24 | process/fix_alpha_border=true 25 | process/premult_alpha=false 26 | process/HDR_as_SRGB=false 27 | process/invert_color=false 28 | stream=false 29 | size_limit=0 30 | detect_3d=true 31 | svg/scale=1.0 32 | -------------------------------------------------------------------------------- /demo/project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ ] 12 | _global_script_class_icons={ 13 | 14 | } 15 | 16 | [application] 17 | 18 | config/name="FMOD integration demo" 19 | run/main_scene="res://Scenes/Main.tscn" 20 | config/icon="res://icon.png" 21 | 22 | [autoload] 23 | 24 | FMOD="*res://Scripts/FMOD.gd" 25 | 26 | [rendering] 27 | 28 | environment/default_environment="res://default_env.tres" 29 | -------------------------------------------------------------------------------- /docs/building.md: -------------------------------------------------------------------------------- 1 | ## Building the module 2 | 3 | To compile the module for a different version of Godot, follow these instructions. Precompiled engine binaries are available in the Releases tab but it is recommended to perform a recompilation of the engine with the version of Godot that is currently being used in your project. 4 | 5 | 1. [Download the FMOD Studio API](https://www.fmod.com/download) (You need to create an account) and extract it somewhere on your system. This integration currently uses the 2.00.02 release but any 2.XX.XX version should be compatible. 6 | 2. Clone the version of Godot currently being used in your project or simply clone the latest version from the [master branch](https://github.com/godotengine/godot). 7 | 3. `cd` into the source directory and add the FMOD integration as a submodule into the `modules` directory `git submodule add https://github.com/alexfonseka/godot-fmod-integration modules/fmod`. 8 | 4. Copy the contents of the `api` directory of the FMOD API into the module's `api` directory `modules/fmod/api`. On Windows this is (usually) found at `C:/Program Files (x86)/FMOD SoundSystem/FMOD Studio API Windows/api`. 9 | 5. Recompile the engine. For more information on compiling the engine, refer to the [Godot documentation](https://docs.godotengine.org/en/latest/development/compiling/index.html). 10 | 6. Place the FMOD dynamically linking library files within the `bin` directory for Godot to start. Eg. on Windows these would be `fmod.dll` and `fmodstudio.dll`. When shipping, these files have to be included with the release. 11 | 12 | ### Godot FMOD GDNative 13 | 14 | Alternatively, the GDNative version of the integration is being developed [here](https://github.com/utopia-rise/fmod-gdnative). This allows the use of FMOD Studio without an engine recompilation 👍. 15 | 16 | ### For Android build targets 17 | 18 | Before building the engine, you should first create the environment variable to get the NDK path. 19 | 20 | `export ANDROID_NDK_ROOT=pathToYourNDK` 21 | 22 | In order to get FMOD working on Android, you need to make Fmod java static initialization in Godot's Android export 23 | template. To do so, follow the next steps. 24 | 25 | - Add fmod.jar as dependency in your project. 26 | In order to add FMOD to Gradle you should have dependencies looking like this : 27 | 28 | ``` 29 | dependencies { 30 | implementation "com.android.support:support-core-utils:28.0.0" 31 | compile files("libs/fmod.jar") 32 | } 33 | ``` 34 | 35 | - Modify `onCreate` and `onDestroy` methods in `Godot` Java class 36 | 37 | For `onCreate` you should initialize Java part of FMOD. 38 | 39 | ```java 40 | @Override 41 | protected void onCreate(Bundle icicle) { 42 | 43 | super.onCreate(icicle); 44 | FMOD.init(this); 45 | Window window = getWindow(); 46 | ... 47 | } 48 | ``` 49 | 50 | For `onDestroy` method, you should close Java part of FMOD. 51 | 52 | ```java 53 | @Override 54 | protected void onDestroy() { 55 | 56 | if (mPaymentsManager != null) mPaymentsManager.destroy(); 57 | for (int i = 0; i < singleton_count; i++) { 58 | singletons[i].onMainDestroy(); 59 | } 60 | FMOD.close(); 61 | super.onDestroy(); 62 | } 63 | ``` 64 | 65 | - Then run `./gradlew build` to generate an apk export template. You can then use it in your project to get FMOD working 66 | on Android. 67 | -------------------------------------------------------------------------------- /godot_fmod.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* godot_fmod.cpp */ 3 | /*************************************************************************/ 4 | /* */ 5 | /* FMOD Studio module and bindings for the Godot game engine */ 6 | /* */ 7 | /*************************************************************************/ 8 | /* Copyright (c) 2020 Alex Fonseka */ 9 | /* */ 10 | /* Permission is hereby granted, free of charge, to any person obtaining */ 11 | /* a copy of this software and associated documentation files (the */ 12 | /* "Software"), to deal in the Software without restriction, including */ 13 | /* without limitation the rights to use, copy, modify, merge, publish, */ 14 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 15 | /* permit persons to whom the Software is furnished to do so, subject to */ 16 | /* the following conditions: */ 17 | /* */ 18 | /* The above copyright notice and this permission notice shall be */ 19 | /* included in all copies or substantial portions of the Software. */ 20 | /* */ 21 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 22 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 23 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 24 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 25 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 26 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 27 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 28 | /*************************************************************************/ 29 | 30 | #include "godot_fmod.h" 31 | 32 | Mutex *Callbacks::mut; 33 | 34 | Fmod *Fmod::singleton = nullptr; 35 | 36 | void Fmod::init(int numOfChannels, int studioFlags, int flags) { 37 | // initialize FMOD Studio and FMOD Core System with provided flags 38 | if (checkErrors(system->initialize(numOfChannels, studioFlags, flags, nullptr))) { 39 | print_line("FMOD Sound System: Successfully initialized"); 40 | if (studioFlags & FMOD_STUDIO_INIT_LIVEUPDATE) 41 | print_line("FMOD Sound System: Live update enabled!"); 42 | } else 43 | print_error("FMOD Sound System: Failed to initialize :|"); 44 | } 45 | 46 | void Fmod::update() { 47 | // clean up one shots 48 | for (auto e = events.front(); e; e = e->next()) { 49 | FMOD::Studio::EventInstance *eventInstance = e->get(); 50 | EventInfo *eventInfo = getEventInfo(eventInstance); 51 | if (eventInfo->gameObj) { 52 | if (isNull(eventInfo->gameObj)) { 53 | FMOD_STUDIO_STOP_MODE m = FMOD_STUDIO_STOP_IMMEDIATE; 54 | checkErrors(eventInstance->stop(m)); 55 | releaseOneEvent(eventInstance); 56 | continue; 57 | } 58 | updateInstance3DAttributes(eventInstance, eventInfo->gameObj); 59 | } 60 | } 61 | 62 | // clean up invalid channel references 63 | clearChannelRefs(); 64 | 65 | // update listener position 66 | setListenerAttributes(); 67 | 68 | // if events are subscribed to callbacks, update them 69 | runCallbacks(); 70 | 71 | // finally, dispatch an update call to FMOD 72 | checkErrors(system->update()); 73 | } 74 | 75 | void Fmod::updateInstance3DAttributes(FMOD::Studio::EventInstance *instance, Object *o) { 76 | // try to set 3D attributes 77 | if (instance && !isNull(o)) { 78 | CanvasItem *ci = Object::cast_to(o); 79 | if (ci != nullptr) { // GameObject is 2D 80 | Transform2D t2d = ci->get_transform(); 81 | Vector2 posVector = t2d.get_origin() / distanceScale; 82 | // in 2D, the distance is measured in pixels 83 | // TODO: Revise the set3DAttributes call. In 2D, the emitters must directly face the listener. 84 | Vector3 pos(posVector.x, 0.0f, posVector.y), 85 | up(0, 1, 0), forward(0, 0, 1), vel(0, 0, 0); // TODO: add doppler 86 | FMOD_3D_ATTRIBUTES attr = get3DAttributes(toFmodVector(pos), toFmodVector(up), toFmodVector(forward), toFmodVector(vel)); 87 | checkErrors(instance->set3DAttributes(&attr)); 88 | } else { // GameObject is 3D 89 | // needs testing 90 | Spatial *s = Object::cast_to(o); 91 | Transform t = s->get_transform(); 92 | Vector3 pos = t.get_origin() / distanceScale; 93 | Vector3 up = t.get_basis().elements[1]; 94 | Vector3 forward = t.get_basis().elements[2]; 95 | Vector3 vel(0, 0, 0); 96 | FMOD_3D_ATTRIBUTES attr = get3DAttributes(toFmodVector(pos), toFmodVector(up), toFmodVector(forward), toFmodVector(vel)); 97 | checkErrors(instance->set3DAttributes(&attr)); 98 | } 99 | } 100 | } 101 | 102 | void Fmod::shutdown() { 103 | checkErrors(system->unloadAll()); 104 | checkErrors(system->release()); 105 | } 106 | 107 | void Fmod::setListenerAttributes() { 108 | if (listeners.size() == 0) { 109 | if (listenerWarning) { 110 | print_error("FMOD Sound System: No listeners are set!"); 111 | listenerWarning = false; 112 | } 113 | return; 114 | } 115 | 116 | clearNullListeners(); 117 | 118 | for (int i = 0; i < listeners.size(); i++) { 119 | auto listener = listeners[i]; 120 | 121 | CanvasItem *ci = Object::cast_to(listener.gameObj); 122 | if (ci != nullptr) { // Listener is in 2D space 123 | Transform2D t2d = ci->get_transform(); 124 | Vector2 posVector = t2d.get_origin() / distanceScale; 125 | // in 2D, the distance is measured in pixels 126 | // TODO: Revise the set3DAttributes call. In 2D, the listener must be a few units away from 127 | // the emitters (or the screen) and must face them directly. 128 | Vector3 pos(posVector.x, 0.0f, posVector.y), 129 | up(0, 1, 0), forward(0, 0, 1), vel(0, 0, 0); // TODO: add doppler 130 | FMOD_3D_ATTRIBUTES attr = get3DAttributes(toFmodVector(pos), toFmodVector(up), toFmodVector(forward), toFmodVector(vel)); 131 | if (!listener.listenerLock) checkErrors(system->setListenerAttributes(i, &attr)); 132 | 133 | } else { // Listener is in 3D space 134 | // needs testing 135 | Spatial *s = Object::cast_to(listener.gameObj); 136 | Transform t = s->get_transform(); 137 | Vector3 pos = t.get_origin() / distanceScale; 138 | Vector3 up = t.get_basis().elements[1]; 139 | Vector3 forward = t.get_basis().elements[2]; 140 | Vector3 vel(0, 0, 0); 141 | FMOD_3D_ATTRIBUTES attr = get3DAttributes(toFmodVector(pos), toFmodVector(up), toFmodVector(forward), toFmodVector(vel)); 142 | if (!listener.listenerLock) checkErrors(system->setListenerAttributes(i, &attr)); 143 | } 144 | } 145 | } 146 | 147 | void Fmod::addListener(Object *gameObj) { 148 | if (listeners.size() == FMOD_MAX_LISTENERS) { 149 | print_error("FMOD Sound System: Could not add listener. System already at max listeners."); 150 | return; 151 | } 152 | Listener listener; 153 | listener.gameObj = gameObj; 154 | listeners.push_back(listener); 155 | checkErrors(system->setNumListeners(listeners.size())); 156 | } 157 | 158 | void Fmod::removeListener(uint8_t index) { 159 | if (index < 0 || index + 1 > listeners.size()) { 160 | print_error("FMOD Sound System: Invalid listener ID"); 161 | return; 162 | } 163 | listeners.erase(listeners.begin() + index); 164 | checkErrors(system->setNumListeners(listeners.size() == 0 ? 1 : listeners.size())); 165 | std::string s = "FMOD Sound System: Listener at index " + std::to_string(index) + " was removed"; 166 | print_line(s.c_str()); 167 | } 168 | 169 | void Fmod::setSoftwareFormat(int sampleRate, int speakerMode, int numRawSpeakers) { 170 | auto m = static_cast(speakerMode); 171 | checkErrors(coreSystem->setSoftwareFormat(sampleRate, m, numRawSpeakers)); 172 | } 173 | 174 | void Fmod::setGlobalParameterByName(const String ¶meterName, float value) { 175 | checkErrors(system->setParameterByName(parameterName.ascii().get_data(), value)); 176 | } 177 | 178 | float Fmod::getGlobalParameterByName(const String ¶meterName) { 179 | float value = 0.f; 180 | checkErrors(system->getParameterByName(parameterName.ascii().get_data(), &value)); 181 | return value; 182 | } 183 | 184 | void Fmod::setGlobalParameterByID(const Array &idPair, float value) { 185 | if (idPair.size() != 2) { 186 | print_error("FMOD Sound System: Invalid parameter ID"); 187 | return; 188 | } 189 | FMOD_STUDIO_PARAMETER_ID id; 190 | id.data1 = idPair[0]; 191 | id.data2 = idPair[1]; 192 | checkErrors(system->setParameterByID(id, value)); 193 | } 194 | 195 | float Fmod::getGlobalParameterByID(const Array &idPair) { 196 | if (idPair.size() != 2) { 197 | print_error("FMOD Sound System: Invalid parameter ID"); 198 | return -1.f; 199 | } 200 | FMOD_STUDIO_PARAMETER_ID id; 201 | id.data1 = idPair[0]; 202 | id.data2 = idPair[1]; 203 | float value = -1.f; 204 | checkErrors(system->getParameterByID(id, &value)); 205 | return value; 206 | } 207 | 208 | Dictionary Fmod::getGlobalParameterDescByName(const String ¶meterName) { 209 | Dictionary paramDesc; 210 | FMOD_STUDIO_PARAMETER_DESCRIPTION pDesc; 211 | if (checkErrors(system->getParameterDescriptionByName(parameterName.ascii().get_data(), &pDesc))) { 212 | paramDesc["name"] = String(pDesc.name); 213 | paramDesc["id_first"] = pDesc.id.data1; 214 | paramDesc["id_second"] = pDesc.id.data2; 215 | paramDesc["minimum"] = pDesc.minimum; 216 | paramDesc["maximum"] = pDesc.maximum; 217 | paramDesc["default_value"] = pDesc.defaultvalue; 218 | } 219 | 220 | return paramDesc; 221 | } 222 | 223 | Dictionary Fmod::getGlobalParameterDescByID(const Array &idPair) { 224 | if (idPair.size() != 2) { 225 | print_error("FMOD Sound System: Invalid parameter ID"); 226 | return Dictionary(); 227 | } 228 | Dictionary paramDesc; 229 | FMOD_STUDIO_PARAMETER_ID id; 230 | id.data1 = idPair[0]; 231 | id.data2 = idPair[1]; 232 | FMOD_STUDIO_PARAMETER_DESCRIPTION pDesc; 233 | if (checkErrors(system->getParameterDescriptionByID(id, &pDesc))) { 234 | paramDesc["name"] = String(pDesc.name); 235 | paramDesc["id_first"] = pDesc.id.data1; 236 | paramDesc["id_second"] = pDesc.id.data2; 237 | paramDesc["minimum"] = pDesc.minimum; 238 | paramDesc["maximum"] = pDesc.maximum; 239 | paramDesc["default_value"] = pDesc.defaultvalue; 240 | } 241 | 242 | return paramDesc; 243 | } 244 | 245 | uint32_t Fmod::getGlobalParameterDescCount() { 246 | int count = 0; 247 | checkErrors(system->getParameterDescriptionCount(&count)); 248 | return count; 249 | } 250 | 251 | Array Fmod::getGlobalParameterDescList() { 252 | Array a; 253 | FMOD_STUDIO_PARAMETER_DESCRIPTION descList[256]; 254 | int count = 0; 255 | checkErrors(system->getParameterDescriptionList(descList, 256, &count)); 256 | for (int i = 0; i < count; i++) { 257 | auto pDesc = descList[i]; 258 | Dictionary paramDesc; 259 | paramDesc["name"] = String(pDesc.name); 260 | paramDesc["id_first"] = pDesc.id.data1; 261 | paramDesc["id_second"] = pDesc.id.data2; 262 | paramDesc["minimum"] = pDesc.minimum; 263 | paramDesc["maximum"] = pDesc.maximum; 264 | paramDesc["default_value"] = pDesc.defaultvalue; 265 | a.append(paramDesc); 266 | } 267 | return a; 268 | } 269 | 270 | Array Fmod::getAvailableDrivers() { 271 | Array driverList; 272 | int numDrivers = 0; 273 | 274 | checkErrors(coreSystem->getNumDrivers(&numDrivers)); 275 | 276 | for (int i = 0; i < numDrivers; i++) { 277 | char name[256]; 278 | int sampleRate; 279 | FMOD_SPEAKERMODE speakerMode; 280 | int speakerModeChannels; 281 | checkErrors(coreSystem->getDriverInfo(i, name, 256, nullptr, &sampleRate, &speakerMode, &speakerModeChannels)); 282 | String nameStr(name); 283 | 284 | Dictionary driverInfo; 285 | driverInfo["id"] = i; 286 | driverInfo["name"] = nameStr; 287 | driverInfo["sample_rate"] = sampleRate; 288 | driverInfo["speaker_mode"] = (int)speakerMode; 289 | driverInfo["number_of_channels"] = speakerModeChannels; 290 | driverList.push_back(driverInfo); 291 | } 292 | 293 | return driverList; 294 | } 295 | 296 | int Fmod::getDriver() { 297 | int driverId = 0; 298 | checkErrors(coreSystem->getDriver(&driverId)); 299 | return driverId; 300 | } 301 | 302 | void Fmod::setDriver(uint8_t id) { 303 | checkErrors(coreSystem->setDriver(id)); 304 | } 305 | 306 | Dictionary Fmod::getPerformanceData() { 307 | Dictionary performanceData; 308 | 309 | // get the CPU usage 310 | FMOD_STUDIO_CPU_USAGE cpuUsage; 311 | checkErrors(system->getCPUUsage(&cpuUsage)); 312 | Dictionary cpuPerfData; 313 | cpuPerfData["dsp"] = cpuUsage.dspusage; 314 | cpuPerfData["geometry"] = cpuUsage.geometryusage; 315 | cpuPerfData["stream"] = cpuUsage.streamusage; 316 | cpuPerfData["studio"] = cpuUsage.studiousage; 317 | cpuPerfData["update"] = cpuUsage.updateusage; 318 | performanceData["CPU"] = cpuPerfData; 319 | 320 | // get the memory usage 321 | int currentAlloc = 0; 322 | int maxAlloc = 0; 323 | checkErrors(FMOD::Memory_GetStats(¤tAlloc, &maxAlloc)); 324 | Dictionary memPerfData; 325 | memPerfData["currently_allocated"] = currentAlloc; 326 | memPerfData["max_allocated"] = maxAlloc; 327 | performanceData["memory"] = memPerfData; 328 | 329 | // get the file usage 330 | long long sampleBytesRead = 0; 331 | long long streamBytesRead = 0; 332 | long long otherBytesRead = 0; 333 | checkErrors(coreSystem->getFileUsage(&sampleBytesRead, &streamBytesRead, &otherBytesRead)); 334 | Dictionary filePerfData; 335 | filePerfData["sample_bytes_read"] = (uint64_t)sampleBytesRead; 336 | filePerfData["stream_bytes_read"] = (uint64_t)streamBytesRead; 337 | filePerfData["other_bytes_read"] = (uint64_t)otherBytesRead; 338 | performanceData["file"] = filePerfData; 339 | 340 | return performanceData; 341 | } 342 | 343 | void Fmod::setListenerLock(uint8_t index, bool isLocked) { 344 | if (index < 0 || index + 1 > listeners.size()) { 345 | print_error("FMOD Sound System: Invalid listener ID"); 346 | return; 347 | } 348 | Listener *listener = &listeners[index]; 349 | listener->listenerLock = isLocked; 350 | } 351 | 352 | bool Fmod::getListenerLock(uint8_t index) { 353 | if (index < 0 || index + 1 > listeners.size()) { 354 | print_error("FMOD Sound System: Invalid listener ID"); 355 | return false; 356 | } 357 | return listeners[index].listenerLock; 358 | } 359 | 360 | void Fmod::waitForAllLoads() { 361 | checkErrors(system->flushSampleLoading()); 362 | } 363 | 364 | String Fmod::loadbank(const String &pathToBank, int flags) { 365 | if (banks.has(pathToBank)) return pathToBank; // bank is already loaded 366 | FMOD::Studio::Bank *bank = nullptr; 367 | checkErrors(system->loadBankFile(pathToBank.ascii().get_data(), flags, &bank)); 368 | if (bank) { 369 | banks.insert(pathToBank, bank); 370 | return pathToBank; 371 | } 372 | return pathToBank; 373 | } 374 | 375 | void Fmod::unloadBank(const String &pathToBank) { 376 | if (!banks.has(pathToBank)) return; // bank is not loaded 377 | auto bank = banks.find(pathToBank); 378 | if (bank->value()) { 379 | checkErrors(bank->value()->unload()); 380 | banks.erase(pathToBank); 381 | } 382 | } 383 | 384 | int Fmod::getBankLoadingState(const String &pathToBank) { 385 | if (!banks.has(pathToBank)) return -1; // bank is not loaded 386 | auto bank = banks.find(pathToBank); 387 | if (bank->value()) { 388 | FMOD_STUDIO_LOADING_STATE state; 389 | checkErrors(bank->value()->getLoadingState(&state)); 390 | return state; 391 | } 392 | return -1; 393 | } 394 | 395 | int Fmod::getBankBusCount(const String &pathToBank) { 396 | if (banks.has(pathToBank)) { 397 | int count; 398 | auto bank = banks.find(pathToBank); 399 | if (bank->value()) checkErrors(bank->value()->getBusCount(&count)); 400 | return count; 401 | } 402 | return -1; 403 | } 404 | 405 | int Fmod::getBankEventCount(const String &pathToBank) { 406 | if (banks.has(pathToBank)) { 407 | int count; 408 | auto bank = banks.find(pathToBank); 409 | if (bank->value()) checkErrors(bank->value()->getEventCount(&count)); 410 | return count; 411 | } 412 | return -1; 413 | } 414 | 415 | int Fmod::getBankStringCount(const String &pathToBank) { 416 | if (banks.has(pathToBank)) { 417 | int count; 418 | auto bank = banks.find(pathToBank); 419 | if (bank->value()) checkErrors(bank->value()->getStringCount(&count)); 420 | return count; 421 | } 422 | return -1; 423 | } 424 | 425 | int Fmod::getBankVCACount(const String &pathToBank) { 426 | if (banks.has(pathToBank)) { 427 | int count; 428 | auto bank = banks.find(pathToBank); 429 | if (bank->value()) checkErrors(bank->value()->getVCACount(&count)); 430 | return count; 431 | } 432 | return -1; 433 | } 434 | 435 | uint64_t Fmod::descCreateInstance(uint64_t descHandle) { 436 | if (!ptrToEventDescMap.has(descHandle)) return 0; 437 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 438 | auto instance = createInstance(desc, false, nullptr); 439 | if (instance) 440 | return (uint64_t)instance; 441 | return 0; 442 | } 443 | 444 | int Fmod::descGetLength(uint64_t descHandle) { 445 | if (!ptrToEventDescMap.has(descHandle)) return -1; 446 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 447 | int length = 0; 448 | checkErrors(desc->getLength(&length)); 449 | return length; 450 | } 451 | 452 | String Fmod::descGetPath(uint64_t descHandle) { 453 | if (!ptrToEventDescMap.has(descHandle)) return String("Invalid handle!"); 454 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 455 | char path[256]; 456 | int retrived = 0; 457 | checkErrors(desc->getPath(path, 256, &retrived)); 458 | return String(path); 459 | } 460 | 461 | Array Fmod::descGetInstanceList(uint64_t descHandle) { 462 | Array array; 463 | if (!ptrToEventDescMap.has(descHandle)) return array; 464 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 465 | FMOD::Studio::EventInstance *arr[128]; 466 | int count = 0; 467 | checkErrors(desc->getInstanceList(arr, 128, &count)); 468 | for (int i = 0; i < count; i++) { 469 | array.append((uint64_t)arr[i]); 470 | } 471 | return array; 472 | } 473 | 474 | int Fmod::descGetInstanceCount(uint64_t descHandle) { 475 | if (!ptrToEventDescMap.has(descHandle)) return -1; 476 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 477 | int count = 0; 478 | checkErrors(desc->getInstanceCount(&count)); 479 | return count; 480 | } 481 | 482 | void Fmod::descReleaseAllInstances(uint64_t descHandle) { 483 | if (!ptrToEventDescMap.has(descHandle)) return; 484 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 485 | 486 | checkErrors(desc->releaseAllInstances()); 487 | } 488 | 489 | void Fmod::descLoadSampleData(uint64_t descHandle) { 490 | if (!ptrToEventDescMap.has(descHandle)) return; 491 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 492 | checkErrors(desc->loadSampleData()); 493 | } 494 | 495 | void Fmod::descUnloadSampleData(uint64_t descHandle) { 496 | if (!ptrToEventDescMap.has(descHandle)) return; 497 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 498 | checkErrors(desc->unloadSampleData()); 499 | } 500 | 501 | int Fmod::descGetSampleLoadingState(uint64_t descHandle) { 502 | if (!ptrToEventDescMap.has(descHandle)) return -1; 503 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 504 | FMOD_STUDIO_LOADING_STATE s; 505 | checkErrors(desc->getSampleLoadingState(&s)); 506 | return s; 507 | } 508 | 509 | bool Fmod::descIs3D(uint64_t descHandle) { 510 | if (!ptrToEventDescMap.has(descHandle)) return false; 511 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 512 | bool is3D = false; 513 | checkErrors(desc->is3D(&is3D)); 514 | return is3D; 515 | } 516 | 517 | bool Fmod::descIsOneShot(uint64_t descHandle) { 518 | if (!ptrToEventDescMap.has(descHandle)) return false; 519 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 520 | bool isOneShot = false; 521 | checkErrors(desc->isOneshot(&isOneShot)); 522 | return isOneShot; 523 | } 524 | 525 | bool Fmod::descIsSnapshot(uint64_t descHandle) { 526 | if (!ptrToEventDescMap.has(descHandle)) return false; 527 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 528 | bool isSnapshot = false; 529 | checkErrors(desc->isSnapshot(&isSnapshot)); 530 | return isSnapshot; 531 | } 532 | 533 | bool Fmod::descIsStream(uint64_t descHandle) { 534 | if (!ptrToEventDescMap.has(descHandle)) return false; 535 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 536 | bool isStream = false; 537 | checkErrors(desc->isStream(&isStream)); 538 | return isStream; 539 | } 540 | 541 | bool Fmod::descHasCue(uint64_t descHandle) { 542 | if (!ptrToEventDescMap.has(descHandle)) return false; 543 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 544 | bool hasCue = false; 545 | checkErrors(desc->hasCue(&hasCue)); 546 | return hasCue; 547 | } 548 | 549 | float Fmod::descGetMaximumDistance(uint64_t descHandle) { 550 | if (!ptrToEventDescMap.has(descHandle)) return 0.f; 551 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 552 | float maxDist = 0.f; 553 | checkErrors(desc->getMaximumDistance(&maxDist)); 554 | return maxDist; 555 | } 556 | 557 | float Fmod::descGetMinimumDistance(uint64_t descHandle) { 558 | if (!ptrToEventDescMap.has(descHandle)) return 0.f; 559 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 560 | float minDist = 0.f; 561 | checkErrors(desc->getMinimumDistance(&minDist)); 562 | return minDist; 563 | } 564 | 565 | float Fmod::descGetSoundSize(uint64_t descHandle) { 566 | if (!ptrToEventDescMap.has(descHandle)) return 0.f; 567 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 568 | float soundSize = 0.f; 569 | checkErrors(desc->getSoundSize(&soundSize)); 570 | return soundSize; 571 | } 572 | 573 | Dictionary Fmod::descGetParameterDescriptionByName(uint64_t descHandle, const String &name) { 574 | Dictionary paramDesc; 575 | if (!ptrToEventDescMap.has(descHandle)) return paramDesc; 576 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 577 | 578 | FMOD_STUDIO_PARAMETER_DESCRIPTION pDesc; 579 | if (checkErrors(desc->getParameterDescriptionByName(name.ascii().get_data(), &pDesc))) { 580 | paramDesc["name"] = String(pDesc.name); 581 | paramDesc["id_first"] = pDesc.id.data1; 582 | paramDesc["id_second"] = pDesc.id.data2; 583 | paramDesc["minimum"] = pDesc.minimum; 584 | paramDesc["maximum"] = pDesc.maximum; 585 | paramDesc["default_value"] = pDesc.defaultvalue; 586 | } 587 | 588 | return paramDesc; 589 | } 590 | 591 | Dictionary Fmod::descGetParameterDescriptionByID(uint64_t descHandle, const Array &idPair) { 592 | Dictionary paramDesc; 593 | if (!ptrToEventDescMap.has(descHandle) || idPair.size() != 2) return paramDesc; 594 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 595 | FMOD_STUDIO_PARAMETER_ID paramId; 596 | paramId.data1 = (unsigned int)idPair[0]; 597 | paramId.data2 = (unsigned int)idPair[1]; 598 | FMOD_STUDIO_PARAMETER_DESCRIPTION pDesc; 599 | if (checkErrors(desc->getParameterDescriptionByID(paramId, &pDesc))) { 600 | paramDesc["name"] = String(pDesc.name); 601 | paramDesc["id_first"] = pDesc.id.data1; 602 | paramDesc["id_second"] = pDesc.id.data2; 603 | paramDesc["minimum"] = pDesc.minimum; 604 | paramDesc["maximum"] = pDesc.maximum; 605 | paramDesc["default_value"] = pDesc.defaultvalue; 606 | } 607 | return paramDesc; 608 | } 609 | 610 | int Fmod::descGetParameterDescriptionCount(uint64_t descHandle) { 611 | if (!ptrToEventDescMap.has(descHandle)) return 0; 612 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 613 | int count = 0; 614 | checkErrors(desc->getParameterDescriptionCount(&count)); 615 | return count; 616 | } 617 | 618 | Dictionary Fmod::descGetParameterDescriptionByIndex(uint64_t descHandle, int index) { 619 | Dictionary paramDesc; 620 | if (!ptrToEventDescMap.has(descHandle)) return paramDesc; 621 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 622 | FMOD_STUDIO_PARAMETER_DESCRIPTION pDesc; 623 | if (checkErrors(desc->getParameterDescriptionByIndex(index, &pDesc))) { 624 | paramDesc["name"] = String(pDesc.name); 625 | paramDesc["id_first"] = pDesc.id.data1; 626 | paramDesc["id_second"] = pDesc.id.data2; 627 | paramDesc["minimum"] = pDesc.minimum; 628 | paramDesc["maximum"] = pDesc.maximum; 629 | paramDesc["default_value"] = pDesc.defaultvalue; 630 | } 631 | return paramDesc; 632 | } 633 | 634 | Dictionary Fmod::descGetUserProperty(uint64_t descHandle, String name) { 635 | Dictionary propDesc; 636 | if (!ptrToEventDescMap.has(descHandle)) return propDesc; 637 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 638 | FMOD_STUDIO_USER_PROPERTY uProp; 639 | if (checkErrors(desc->getUserProperty(name.ascii().get_data(), &uProp))) { 640 | FMOD_STUDIO_USER_PROPERTY_TYPE fType = uProp.type; 641 | if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER) 642 | propDesc[String(uProp.name)] = uProp.intvalue; 643 | else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN) 644 | propDesc[String(uProp.name)] = (bool)uProp.boolvalue; 645 | else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT) 646 | propDesc[String(uProp.name)] = uProp.floatvalue; 647 | else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_STRING) 648 | propDesc[String(uProp.name)] = String(uProp.stringvalue); 649 | } 650 | 651 | return propDesc; 652 | } 653 | 654 | int Fmod::descGetUserPropertyCount(uint64_t descHandle) { 655 | if (!ptrToEventDescMap.has(descHandle)) return -1; 656 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 657 | int count = 0; 658 | checkErrors(desc->getUserPropertyCount(&count)); 659 | return count; 660 | } 661 | 662 | Dictionary Fmod::descUserPropertyByIndex(uint64_t descHandle, int index) { 663 | Dictionary propDesc; 664 | if (!ptrToEventDescMap.has(descHandle)) return propDesc; 665 | auto desc = ptrToEventDescMap.find(descHandle)->value(); 666 | FMOD_STUDIO_USER_PROPERTY uProp; 667 | if (checkErrors(desc->getUserPropertyByIndex(index, &uProp))) { 668 | FMOD_STUDIO_USER_PROPERTY_TYPE fType = uProp.type; 669 | if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_INTEGER) 670 | propDesc[String(uProp.name)] = uProp.intvalue; 671 | else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_BOOLEAN) 672 | propDesc[String(uProp.name)] = (bool)uProp.boolvalue; 673 | else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_FLOAT) 674 | propDesc[String(uProp.name)] = uProp.floatvalue; 675 | else if (fType == FMOD_STUDIO_USER_PROPERTY_TYPE_STRING) 676 | propDesc[String(uProp.name)] = String(uProp.stringvalue); 677 | } 678 | 679 | return propDesc; 680 | } 681 | 682 | uint64_t Fmod::createEventInstance(const String &eventPath) { 683 | FMOD::Studio::EventInstance *instance = createInstance(eventPath, false, nullptr); 684 | if (instance) { 685 | uint64_t instanceId = (uint64_t)instance; 686 | events.insert(instanceId, instance); 687 | return instanceId; 688 | } 689 | return 0; 690 | } 691 | 692 | FMOD::Studio::EventInstance *Fmod::createInstance(const String eventPath, const bool isOneShot, Object *gameObject) { 693 | if (!eventDescriptions.has(eventPath)) { 694 | FMOD::Studio::EventDescription *desc = nullptr; 695 | auto res = checkErrors(system->getEvent(eventPath.ascii().get_data(), &desc)); 696 | if (!res) return 0; 697 | eventDescriptions.insert(eventPath, desc); 698 | } 699 | auto desc = eventDescriptions.find(eventPath); 700 | FMOD::Studio::EventInstance *instance; 701 | checkErrors(desc->value()->createInstance(&instance)); 702 | if (instance && (!isOneShot || gameObject)) { 703 | auto *eventInfo = new EventInfo(); 704 | eventInfo->gameObj = gameObject; 705 | instance->setUserData(eventInfo); 706 | auto instanceId = (uint64_t)instance; 707 | events[instanceId] = instance; 708 | } 709 | return instance; 710 | } 711 | 712 | FMOD::Studio::EventInstance *Fmod::createInstance(FMOD::Studio::EventDescription *eventDesc, bool isOneShot, Object *gameObject) { 713 | auto desc = eventDesc; 714 | FMOD::Studio::EventInstance *instance; 715 | checkErrors(desc->createInstance(&instance)); 716 | if (instance && (!isOneShot || gameObject)) { 717 | auto *eventInfo = new EventInfo(); 718 | eventInfo->gameObj = gameObject; 719 | instance->setUserData(eventInfo); 720 | auto instanceId = (uint64_t)instance; 721 | events[instanceId] = instance; 722 | } 723 | return instance; 724 | } 725 | 726 | float Fmod::getEventParameterByName(uint64_t instanceId, const String ¶meterName) { 727 | float p = -1; 728 | if (!events.has(instanceId)) return p; 729 | auto i = events.find(instanceId); 730 | if (i->value()) 731 | checkErrors(i->value()->getParameterByName(parameterName.ascii().get_data(), &p)); 732 | return p; 733 | } 734 | 735 | void Fmod::setEventParameterByName(uint64_t instanceId, const String ¶meterName, float value) { 736 | if (!events.has(instanceId)) return; 737 | auto i = events.find(instanceId); 738 | if (i->value()) checkErrors(i->value()->setParameterByName(parameterName.ascii().get_data(), value)); 739 | } 740 | 741 | float Fmod::getEventParameterByID(uint64_t instanceId, const Array &idPair) { 742 | if (!events.has(instanceId) || idPair.size() != 2) return -1.0f; 743 | auto i = events.find(instanceId); 744 | if (i->value()) { 745 | FMOD_STUDIO_PARAMETER_ID id; 746 | id.data1 = idPair[0]; 747 | id.data2 = idPair[1]; 748 | float value; 749 | checkErrors(i->value()->getParameterByID(id, &value)); 750 | return value; 751 | } 752 | return -1.0f; 753 | } 754 | 755 | void Fmod::setEventParameterByID(uint64_t instanceId, const Array &idPair, float value) { 756 | if (!events.has(instanceId) || idPair.size() != 2) return; 757 | auto i = events.find(instanceId); 758 | if (i->value()) { 759 | FMOD_STUDIO_PARAMETER_ID id; 760 | id.data1 = idPair[0]; 761 | id.data2 = idPair[1]; 762 | checkErrors(i->value()->setParameterByID(id, value)); 763 | } 764 | } 765 | 766 | void Fmod::releaseEvent(uint64_t instanceId) { 767 | if (!events.has(instanceId)) return; 768 | auto i = events.find(instanceId); 769 | FMOD::Studio::EventInstance *event = i->value(); 770 | if (event) { 771 | releaseOneEvent(event); 772 | } 773 | } 774 | 775 | void Fmod::releaseOneEvent(FMOD::Studio::EventInstance *eventInstance) { 776 | Callbacks::mut->lock(); 777 | EventInfo *eventInfo = getEventInfo(eventInstance); 778 | eventInstance->setUserData(nullptr); 779 | events.erase((uint64_t)eventInstance); 780 | checkErrors(eventInstance->release()); 781 | delete &eventInfo; 782 | Callbacks::mut->unlock(); 783 | } 784 | 785 | void Fmod::clearNullListeners() { 786 | std::vector queue; 787 | for (int i = 0; i < listeners.size(); i++) { 788 | if (isNull(listeners[i].gameObj)) { 789 | queue.push_back(i); 790 | std::string s = "FMOD Sound System: Listener at index " + std::to_string(i) + " was freed."; 791 | print_line(s.c_str()); 792 | } 793 | } 794 | for (int i = 0; i < queue.size(); i++) { 795 | int index = queue[i]; 796 | if (i != 0) index--; 797 | listeners.erase(listeners.begin() + index); 798 | } 799 | checkErrors(system->setNumListeners(listeners.size() == 0 ? 1 : listeners.size())); 800 | } 801 | 802 | void Fmod::clearChannelRefs() { 803 | if (channels.size() == 0) return; 804 | 805 | std::vector refs; 806 | for (auto e = channels.front(); e; e = e->next()) { 807 | // Check if the channel is valid by calling any of its getters 808 | bool isPaused = false; 809 | FMOD_RESULT res = e->get()->getPaused(&isPaused); 810 | if (res != FMOD_OK) 811 | refs.push_back(e->key()); 812 | } 813 | for (auto ref : refs) 814 | channels.erase(ref); 815 | } 816 | 817 | void Fmod::startEvent(uint64_t instanceId) { 818 | if (!events.has(instanceId)) return; 819 | auto i = events.find(instanceId); 820 | if (i->value()) checkErrors(i->value()->start()); 821 | } 822 | 823 | void Fmod::stopEvent(uint64_t instanceId, int stopMode) { 824 | if (!events.has(instanceId)) return; 825 | auto i = events.find(instanceId); 826 | if (i->value()) { 827 | auto m = static_cast(stopMode); 828 | checkErrors(i->value()->stop(m)); 829 | } 830 | } 831 | 832 | void Fmod::triggerEventCue(uint64_t instanceId) { 833 | if (!events.has(instanceId)) return; 834 | auto i = events.find(instanceId); 835 | if (i->value()) checkErrors(i->value()->triggerCue()); 836 | } 837 | 838 | int Fmod::getEventPlaybackState(uint64_t instanceId) { 839 | if (!events.has(instanceId)) 840 | return -1; 841 | else { 842 | auto i = events.find(instanceId); 843 | if (i->value()) { 844 | FMOD_STUDIO_PLAYBACK_STATE s; 845 | checkErrors(i->value()->getPlaybackState(&s)); 846 | return s; 847 | } 848 | return -1; 849 | } 850 | } 851 | 852 | bool Fmod::getEventPaused(uint64_t instanceId) { 853 | if (!events.has(instanceId)) return false; 854 | auto i = events.find(instanceId); 855 | bool paused = false; 856 | if (i->value()) checkErrors(i->value()->getPaused(&paused)); 857 | return paused; 858 | } 859 | 860 | void Fmod::setEventPaused(uint64_t instanceId, bool paused) { 861 | if (!events.has(instanceId)) return; 862 | auto i = events.find(instanceId); 863 | if (i->value()) checkErrors(i->value()->setPaused(paused)); 864 | } 865 | 866 | float Fmod::getEventPitch(uint64_t instanceId) { 867 | if (!events.has(instanceId)) return 0.0f; 868 | auto i = events.find(instanceId); 869 | float pitch = 0.0f; 870 | if (i->value()) checkErrors(i->value()->getPitch(&pitch)); 871 | return pitch; 872 | } 873 | 874 | void Fmod::setEventPitch(uint64_t instanceId, float pitch) { 875 | if (!events.has(instanceId)) return; 876 | auto i = events.find(instanceId); 877 | if (i->value()) checkErrors(i->value()->setPitch(pitch)); 878 | } 879 | 880 | float Fmod::getEventVolume(uint64_t instanceId) { 881 | if (!events.has(instanceId)) return 0.0f; 882 | auto i = events.find(instanceId); 883 | float volume = 0.0f; 884 | FMOD::Studio::EventInstance *event = i->value(); 885 | checkErrors(event->getVolume(&volume)); 886 | return volume; 887 | } 888 | 889 | void Fmod::setEventVolume(uint64_t instanceId, float volume) { 890 | if (!events.has(instanceId)) return; 891 | auto i = events.find(instanceId); 892 | FMOD::Studio::EventInstance *event = i->value(); 893 | checkErrors(event->setVolume(volume)); 894 | } 895 | 896 | int Fmod::getEventTimelinePosition(uint64_t instanceId) { 897 | if (!events.has(instanceId)) return 0; 898 | auto i = events.find(instanceId); 899 | int tp = 0; 900 | if (i->value()) checkErrors(i->value()->getTimelinePosition(&tp)); 901 | return tp; 902 | } 903 | 904 | void Fmod::setEventTimelinePosition(uint64_t instanceId, int position) { 905 | if (!events.has(instanceId)) return; 906 | auto i = events.find(instanceId); 907 | if (i->value()) checkErrors(i->value()->setTimelinePosition(position)); 908 | } 909 | 910 | float Fmod::getEventReverbLevel(uint64_t instanceId, int index) { 911 | if (!events.has(instanceId)) return 0.0f; 912 | auto i = events.find(instanceId); 913 | float rvl = 0.0f; 914 | if (i->value()) checkErrors(i->value()->getReverbLevel(index, &rvl)); 915 | return rvl; 916 | } 917 | 918 | void Fmod::setEventReverbLevel(uint64_t instanceId, int index, float level) { 919 | if (!events.has(instanceId)) return; 920 | auto i = events.find(instanceId); 921 | if (i->value()) checkErrors(i->value()->setReverbLevel(index, level)); 922 | } 923 | 924 | bool Fmod::isEventVirtual(uint64_t instanceId) { 925 | if (!events.has(instanceId)) return false; 926 | auto i = events.find(instanceId); 927 | bool v = false; 928 | if (i->value()) checkErrors(i->value()->isVirtual(&v)); 929 | return v; 930 | } 931 | 932 | bool Fmod::getBusMute(const String &busPath) { 933 | loadBus(busPath); 934 | if (!buses.has(busPath)) return false; 935 | bool mute = false; 936 | auto bus = buses.find(busPath); 937 | checkErrors(bus->value()->getMute(&mute)); 938 | return mute; 939 | } 940 | 941 | bool Fmod::getBusPaused(const String &busPath) { 942 | loadBus(busPath); 943 | if (!buses.has(busPath)) return false; 944 | bool paused = false; 945 | auto bus = buses.find(busPath); 946 | checkErrors(bus->value()->getPaused(&paused)); 947 | return paused; 948 | } 949 | 950 | float Fmod::getBusVolume(const String &busPath) { 951 | loadBus(busPath); 952 | if (!buses.has(busPath)) return 0.0f; 953 | float volume = 0.0f; 954 | auto bus = buses.find(busPath); 955 | checkErrors(bus->value()->getVolume(&volume)); 956 | return volume; 957 | } 958 | 959 | void Fmod::setBusMute(const String &busPath, bool mute) { 960 | loadBus(busPath); 961 | if (!buses.has(busPath)) return; 962 | auto bus = buses.find(busPath); 963 | checkErrors(bus->value()->setMute(mute)); 964 | } 965 | 966 | void Fmod::setBusPaused(const String &busPath, bool paused) { 967 | loadBus(busPath); 968 | if (!buses.has(busPath)) return; 969 | auto bus = buses.find(busPath); 970 | checkErrors(bus->value()->setPaused(paused)); 971 | } 972 | 973 | void Fmod::setBusVolume(const String &busPath, float volume) { 974 | loadBus(busPath); 975 | if (!buses.has(busPath)) return; 976 | auto bus = buses.find(busPath); 977 | checkErrors(bus->value()->setVolume(volume)); 978 | } 979 | 980 | void Fmod::stopAllBusEvents(const String &busPath, int stopMode) { 981 | loadBus(busPath); 982 | if (!buses.has(busPath)) return; 983 | auto bus = buses.find(busPath); 984 | auto m = static_cast(stopMode); 985 | checkErrors(bus->value()->stopAllEvents(m)); 986 | } 987 | 988 | bool Fmod::isNull(Object *o) { 989 | CanvasItem *ci = Object::cast_to(o); 990 | Spatial *s = Object::cast_to(o); 991 | if (ci == nullptr && s == nullptr) 992 | // an object cannot be 2D and 3D at the same time 993 | // which means if the first cast returned null then the second cast also returned null 994 | return true; 995 | return false; // all g. 996 | } 997 | 998 | Fmod::EventInfo *Fmod::getEventInfo(FMOD::Studio::EventInstance *eventInstance) { 999 | EventInfo *eventInfo; 1000 | eventInstance->getUserData((void **)&eventInfo); 1001 | return eventInfo; 1002 | } 1003 | 1004 | void Fmod::loadBus(const String &busPath) { 1005 | if (!buses.has(busPath)) { 1006 | FMOD::Studio::Bus *b = nullptr; 1007 | checkErrors(system->getBus(busPath.ascii().get_data(), &b)); 1008 | if (b) buses.insert(busPath, b); 1009 | } 1010 | } 1011 | 1012 | void Fmod::loadVCA(const String &VCAPath) { 1013 | if (!VCAs.has(VCAPath)) { 1014 | FMOD::Studio::VCA *vca = nullptr; 1015 | checkErrors(system->getVCA(VCAPath.ascii().get_data(), &vca)); 1016 | if (vca) VCAs.insert(VCAPath, vca); 1017 | } 1018 | } 1019 | 1020 | FMOD_VECTOR Fmod::toFmodVector(Vector3 vec) { 1021 | FMOD_VECTOR fv; 1022 | fv.x = vec.x; 1023 | fv.y = vec.y; 1024 | fv.z = vec.z; 1025 | return fv; 1026 | } 1027 | 1028 | FMOD_3D_ATTRIBUTES Fmod::get3DAttributes(FMOD_VECTOR pos, FMOD_VECTOR up, FMOD_VECTOR forward, FMOD_VECTOR vel) { 1029 | FMOD_3D_ATTRIBUTES f3d; 1030 | f3d.forward = forward; 1031 | f3d.position = pos; 1032 | f3d.up = up; 1033 | f3d.velocity = vel; 1034 | return f3d; 1035 | } 1036 | 1037 | void Fmod::playOneShot(const String &eventName, Object *gameObj) { 1038 | FMOD::Studio::EventInstance *instance = createInstance(eventName, true, nullptr); 1039 | if (instance) { 1040 | // set 3D attributes once 1041 | if (!isNull(gameObj)) { 1042 | updateInstance3DAttributes(instance, gameObj); 1043 | } 1044 | checkErrors(instance->start()); 1045 | checkErrors(instance->release()); 1046 | } 1047 | } 1048 | 1049 | void Fmod::playOneShotWithParams(const String &eventName, Object *gameObj, const Dictionary ¶meters) { 1050 | FMOD::Studio::EventInstance *instance = createInstance(eventName, true, nullptr); 1051 | if (instance) { 1052 | // set 3D attributes once 1053 | if (!isNull(gameObj)) { 1054 | updateInstance3DAttributes(instance, gameObj); 1055 | } 1056 | // set the initial parameter values 1057 | auto keys = parameters.keys(); 1058 | for (int i = 0; i < keys.size(); i++) { 1059 | String k = keys[i]; 1060 | float v = parameters[keys[i]]; 1061 | checkErrors(instance->setParameterByName(k.ascii().get_data(), v)); 1062 | } 1063 | checkErrors(instance->start()); 1064 | checkErrors(instance->release()); 1065 | } 1066 | } 1067 | 1068 | void Fmod::playOneShotAttached(const String &eventName, Object *gameObj) { 1069 | if (!isNull(gameObj)) { 1070 | FMOD::Studio::EventInstance *instance = createInstance(eventName, true, gameObj); 1071 | if (instance) { 1072 | checkErrors(instance->start()); 1073 | } 1074 | } 1075 | } 1076 | 1077 | void Fmod::playOneShotAttachedWithParams(const String &eventName, Object *gameObj, const Dictionary ¶meters) { 1078 | if (!isNull(gameObj)) { 1079 | FMOD::Studio::EventInstance *instance = createInstance(eventName, true, gameObj); 1080 | if (instance) { 1081 | // set the initial parameter values 1082 | auto keys = parameters.keys(); 1083 | for (int i = 0; i < keys.size(); i++) { 1084 | String k = keys[i]; 1085 | float v = parameters[keys[i]]; 1086 | checkErrors(instance->setParameterByName(k.ascii().get_data(), v)); 1087 | } 1088 | checkErrors(instance->start()); 1089 | } 1090 | } 1091 | } 1092 | 1093 | void Fmod::attachInstanceToNode(uint64_t instanceId, Object *gameObj) { 1094 | if (!events.has(instanceId) || isNull(gameObj)) return; 1095 | auto i = events.find(instanceId); 1096 | FMOD::Studio::EventInstance *event = i->value(); 1097 | if (event) { 1098 | EventInfo *eventInfo = getEventInfo(event); 1099 | eventInfo->gameObj = gameObj; 1100 | } 1101 | } 1102 | 1103 | void Fmod::detachInstanceFromNode(uint64_t instanceId) { 1104 | if (!events.has(instanceId)) return; 1105 | auto instance = events.find(instanceId); 1106 | FMOD::Studio::EventInstance *event = instance->value(); 1107 | if (event) { 1108 | EventInfo *eventInfo = getEventInfo(event); 1109 | eventInfo->gameObj = nullptr; 1110 | } 1111 | } 1112 | 1113 | void Fmod::pauseAllEvents() { 1114 | if (banks.size() > 1) { 1115 | FMOD::Studio::Bus *masterBus = nullptr; 1116 | if (checkErrors(system->getBus("bus:/", &masterBus))) { 1117 | masterBus->setPaused(true); 1118 | } 1119 | } 1120 | } 1121 | 1122 | void Fmod::unpauseAllEvents() { 1123 | if (banks.size() > 1) { 1124 | FMOD::Studio::Bus *masterBus = nullptr; 1125 | if (checkErrors(system->getBus("bus:/", &masterBus))) { 1126 | masterBus->setPaused(false); 1127 | } 1128 | } 1129 | } 1130 | 1131 | void Fmod::muteAllEvents() { 1132 | if (banks.size() > 1) { 1133 | FMOD::Studio::Bus *masterBus = nullptr; 1134 | if (checkErrors(system->getBus("bus:/", &masterBus))) { 1135 | masterBus->setMute(true); 1136 | } 1137 | } 1138 | } 1139 | 1140 | void Fmod::unmuteAllEvents() { 1141 | if (banks.size() > 1) { 1142 | FMOD::Studio::Bus *masterBus = nullptr; 1143 | if (checkErrors(system->getBus("bus:/", &masterBus))) { 1144 | masterBus->setMute(false); 1145 | } 1146 | } 1147 | } 1148 | 1149 | bool Fmod::banksStillLoading() { 1150 | for (auto e = banks.front(); e; e = e->next()) { 1151 | auto bank = e->get(); 1152 | FMOD_STUDIO_LOADING_STATE s; 1153 | checkErrors(bank->getLoadingState(&s)); 1154 | if (s == FMOD_STUDIO_LOADING_STATE_LOADING) { 1155 | return true; 1156 | } 1157 | } 1158 | return false; 1159 | } 1160 | 1161 | float Fmod::getVCAVolume(const String &VCAPath) { 1162 | loadVCA(VCAPath); 1163 | if (!VCAs.has(VCAPath)) return 0.0f; 1164 | auto vca = VCAs.find(VCAPath); 1165 | float volume = 0.0f; 1166 | checkErrors(vca->value()->getVolume(&volume)); 1167 | return volume; 1168 | } 1169 | 1170 | void Fmod::setVCAVolume(const String &VCAPath, float volume) { 1171 | loadVCA(VCAPath); 1172 | if (!VCAs.has(VCAPath)) return; 1173 | auto vca = VCAs.find(VCAPath); 1174 | checkErrors(vca->value()->setVolume(volume)); 1175 | } 1176 | 1177 | uint64_t Fmod::playSound(uint64_t handle) { 1178 | if (sounds.has(handle)) { 1179 | auto s = sounds.find(handle)->value(); 1180 | FMOD::Channel *channel = nullptr; 1181 | checkErrors(coreSystem->playSound(s, nullptr, true, &channel)); 1182 | if (channel) { 1183 | checkErrors(channel->setPaused(false)); 1184 | channels.insert((uint64_t)channel, channel); 1185 | return (uint64_t)channel; 1186 | } 1187 | } 1188 | return 0; 1189 | } 1190 | 1191 | void Fmod::setSoundPaused(uint64_t channelHandle, bool paused) { 1192 | if (channels.has(channelHandle)) { 1193 | auto c = channels.find(channelHandle)->value(); 1194 | checkErrors(c->setPaused(paused)); 1195 | } 1196 | } 1197 | 1198 | void Fmod::stopSound(uint64_t channelHandle) { 1199 | if (channels.has(channelHandle)) { 1200 | auto c = channels.find(channelHandle)->value(); 1201 | checkErrors(c->stop()); 1202 | } 1203 | } 1204 | 1205 | bool Fmod::isSoundPlaying(uint64_t channelHandle) { 1206 | if (channels.has(channelHandle)) { 1207 | auto c = channels.find(channelHandle)->value(); 1208 | bool isPlaying = false; 1209 | checkErrors(c->isPlaying(&isPlaying)); 1210 | return isPlaying; 1211 | } 1212 | return false; 1213 | } 1214 | 1215 | void Fmod::setSoundVolume(uint64_t channelHandle, float volume) { 1216 | if (channels.has(channelHandle)) { 1217 | auto c = channels.find(channelHandle)->value(); 1218 | checkErrors(c->setVolume(volume)); 1219 | } 1220 | } 1221 | 1222 | float Fmod::getSoundVolume(uint64_t channelHandle) { 1223 | if (channels.has(channelHandle)) { 1224 | auto c = channels.find(channelHandle)->value(); 1225 | float volume = 0.f; 1226 | checkErrors(c->getVolume(&volume)); 1227 | return volume; 1228 | } 1229 | return 0.f; 1230 | } 1231 | 1232 | float Fmod::getSoundPitch(uint64_t channelHandle) { 1233 | if (channels.has(channelHandle)) { 1234 | auto c = channels.find(channelHandle)->value(); 1235 | float pitch = 0.f; 1236 | checkErrors(c->getPitch(&pitch)); 1237 | return pitch; 1238 | } 1239 | return 0.f; 1240 | } 1241 | 1242 | void Fmod::setSoundPitch(uint64_t channelHandle, float pitch) { 1243 | if (channels.has(channelHandle)) { 1244 | auto c = channels.find(channelHandle)->value(); 1245 | checkErrors(c->setPitch(pitch)); 1246 | } 1247 | } 1248 | 1249 | uint64_t Fmod::createSound(const String &path, int mode) { 1250 | FMOD::Sound *sound = nullptr; 1251 | checkErrors(coreSystem->createSound(path.ascii().get_data(), mode, nullptr, &sound)); 1252 | if (sound) { 1253 | checkErrors(sound->setLoopCount(0)); 1254 | sounds.insert((uint64_t)sound, sound); 1255 | } 1256 | 1257 | return (uint64_t)sound; 1258 | } 1259 | 1260 | void Fmod::releaseSound(uint64_t handle) { 1261 | if (!sounds.has(handle)) { 1262 | print_error("FMOD Sound System: Invalid handle"); 1263 | return; 1264 | } 1265 | auto sound = sounds.find(handle); 1266 | if (sound->value()) { 1267 | checkErrors(sound->value()->release()); 1268 | sounds.erase(handle); 1269 | } 1270 | } 1271 | 1272 | void Fmod::setSound3DSettings(float dopplerScale, float distanceFactor, float rollOffScale) { 1273 | if (distanceFactor > 0 && checkErrors(coreSystem->set3DSettings(dopplerScale, distanceFactor, rollOffScale))) { 1274 | distanceScale = distanceFactor; 1275 | print_line("FMOD Sound System: Successfully set global 3D settings"); 1276 | } else { 1277 | print_error("FMOD Sound System: Failed to set 3D settings :|"); 1278 | } 1279 | } 1280 | 1281 | int Fmod::getSystemNumListeners() { 1282 | return listeners.size(); 1283 | } 1284 | 1285 | float Fmod::getSystemListenerWeight(uint8_t index) { 1286 | if (index < 0 || index + 1 > listeners.size()) { 1287 | print_error("FMOD Sound System: Invalid listener ID"); 1288 | return -1; 1289 | } 1290 | float weight = 0; 1291 | checkErrors(system->getListenerWeight(index, &weight)); 1292 | return weight; 1293 | } 1294 | 1295 | void Fmod::setSystemListenerWeight(uint8_t index, float weight) { 1296 | if (index < 0 || index + 1 > listeners.size()) { 1297 | print_error("FMOD Sound System: Invalid listener ID"); 1298 | return; 1299 | } 1300 | checkErrors(system->setListenerWeight(index, weight)); 1301 | } 1302 | 1303 | Dictionary Fmod::getSystemListener3DAttributes(uint8_t index) { 1304 | if (index < 0 || index + 1 > listeners.size()) { 1305 | print_error("FMOD Sound System: Invalid listener ID"); 1306 | return Dictionary(); 1307 | } 1308 | FMOD_3D_ATTRIBUTES attr; 1309 | checkErrors(system->getListenerAttributes(index, &attr)); 1310 | Dictionary _3Dattr; 1311 | Vector3 forward(attr.forward.x, attr.forward.y, attr.forward.z); 1312 | Vector3 up(attr.up.x, attr.up.y, attr.up.z); 1313 | Vector3 position(attr.position.x, attr.position.y, attr.position.z); 1314 | Vector3 velocity(attr.velocity.x, attr.velocity.y, attr.velocity.z); 1315 | _3Dattr["forward"] = forward; 1316 | _3Dattr["position"] = position; 1317 | _3Dattr["up"] = up; 1318 | _3Dattr["velocity"] = velocity; 1319 | return _3Dattr; 1320 | } 1321 | 1322 | void Fmod::setSystemListener3DAttributes(uint8_t index, Vector3 forward, Vector3 position, Vector3 up, Vector3 velocity) { 1323 | if (index < 0 || index + 1 > listeners.size()) { 1324 | print_error("FMOD Sound System: Invalid listener ID"); 1325 | return; 1326 | } 1327 | FMOD_3D_ATTRIBUTES attr; 1328 | attr.forward = toFmodVector(forward); 1329 | attr.position = toFmodVector(position); 1330 | attr.up = toFmodVector(up); 1331 | attr.velocity = toFmodVector(velocity); 1332 | checkErrors(system->setListenerAttributes(index, &attr)); 1333 | } 1334 | 1335 | uint64_t Fmod::getEvent(const String &path) { 1336 | if (!eventDescriptions.has(path)) { 1337 | FMOD::Studio::EventDescription *desc = nullptr; 1338 | auto res = checkErrors(system->getEvent(path.ascii().get_data(), &desc)); 1339 | if (!res) return 0; 1340 | eventDescriptions.insert(path, desc); 1341 | } 1342 | auto desc = eventDescriptions.find(path)->value(); 1343 | auto ptr = (uint64_t)desc; 1344 | ptrToEventDescMap.insert(ptr, desc); 1345 | 1346 | return ptr; 1347 | } 1348 | 1349 | void Fmod::setCallback(uint64_t instanceId, int callbackMask) { 1350 | if (!events.has(instanceId)) return; 1351 | FMOD::Studio::EventInstance *event = events.find(instanceId)->value(); 1352 | if (event) { 1353 | checkErrors(event->setCallback(Callbacks::eventCallback, callbackMask)); 1354 | } 1355 | } 1356 | 1357 | uint64_t Fmod::getEventDescription(uint64_t instanceId) { 1358 | if (!events.has(instanceId)) return 0; 1359 | 1360 | auto instance = events.find(instanceId)->value(); 1361 | FMOD::Studio::EventDescription *desc = nullptr; 1362 | checkErrors(instance->getDescription(&desc)); 1363 | auto ptr = (uint64_t)desc; 1364 | ptrToEventDescMap.insert(ptr, desc); 1365 | 1366 | return ptr; 1367 | } 1368 | 1369 | void Fmod::setEvent3DAttributes(uint64_t instanceId, Vector3 forward, Vector3 position, Vector3 up, Vector3 velocity) { 1370 | if (!events.has(instanceId)) return; 1371 | auto instance = events.find(instanceId)->value(); 1372 | FMOD_3D_ATTRIBUTES attr; 1373 | attr.forward = toFmodVector(forward); 1374 | attr.position = toFmodVector(position); 1375 | attr.up = toFmodVector(up); 1376 | attr.velocity = toFmodVector(velocity); 1377 | checkErrors(instance->set3DAttributes(&attr)); 1378 | } 1379 | 1380 | Dictionary Fmod::getEvent3DAttributes(uint64_t instanceId) { 1381 | if (!events.has(instanceId)) { 1382 | print_error("Invalid event instance handle"); 1383 | return Dictionary(); 1384 | } 1385 | auto instance = events.find(instanceId)->value(); 1386 | FMOD_3D_ATTRIBUTES attr; 1387 | checkErrors(instance->get3DAttributes(&attr)); 1388 | Dictionary _3Dattr; 1389 | Vector3 forward(attr.forward.x, attr.forward.y, attr.forward.z); 1390 | Vector3 up(attr.up.x, attr.up.y, attr.up.z); 1391 | Vector3 position(attr.position.x, attr.position.y, attr.position.z); 1392 | Vector3 velocity(attr.velocity.x, attr.velocity.y, attr.velocity.z); 1393 | _3Dattr["forward"] = forward; 1394 | _3Dattr["position"] = position; 1395 | _3Dattr["up"] = up; 1396 | _3Dattr["velocity"] = velocity; 1397 | return _3Dattr; 1398 | } 1399 | 1400 | void Fmod::setEventListenerMask(uint64_t instanceId, int mask) { 1401 | if (!events.has(instanceId)) { 1402 | print_error("Invalid event instance handle"); 1403 | return; 1404 | } 1405 | auto instance = events.find(instanceId)->value(); 1406 | checkErrors(instance->setListenerMask(mask)); 1407 | } 1408 | 1409 | uint32_t Fmod::getEventListenerMask(uint64_t instanceId) { 1410 | if (!events.has(instanceId)) { 1411 | print_error("Invalid event instance handle"); 1412 | return 0; 1413 | } 1414 | auto instance = events.find(instanceId)->value(); 1415 | uint32_t mask = 0; 1416 | checkErrors(instance->getListenerMask(&mask)); 1417 | return mask; 1418 | } 1419 | 1420 | // runs on the Studio update thread, not the game thread 1421 | FMOD_RESULT F_CALLBACK Callbacks::eventCallback(FMOD_STUDIO_EVENT_CALLBACK_TYPE type, FMOD_STUDIO_EVENTINSTANCE *event, void *parameters) { 1422 | 1423 | FMOD::Studio::EventInstance *instance = (FMOD::Studio::EventInstance *)event; 1424 | auto instanceId = (uint64_t)instance; 1425 | Fmod::EventInfo *eventInfo; 1426 | mut->lock(); 1427 | // check if instance is still valid 1428 | if (!instance) { 1429 | mut->unlock(); 1430 | return FMOD_OK; 1431 | } 1432 | instance->getUserData((void **)&eventInfo); 1433 | if (eventInfo) { 1434 | Callbacks::CallbackInfo callbackInfo = eventInfo->callbackInfo; 1435 | 1436 | if (type == FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER) { 1437 | FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES *props = (FMOD_STUDIO_TIMELINE_MARKER_PROPERTIES *)parameters; 1438 | callbackInfo.markerCallbackInfo["event_id"] = instanceId; 1439 | callbackInfo.markerCallbackInfo["name"] = props->name; 1440 | callbackInfo.markerCallbackInfo["position"] = props->position; 1441 | callbackInfo.markerSignalEmitted = false; 1442 | } else if (type == FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT) { 1443 | FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES *props = (FMOD_STUDIO_TIMELINE_BEAT_PROPERTIES *)parameters; 1444 | callbackInfo.beatCallbackInfo["event_id"] = instanceId; 1445 | callbackInfo.beatCallbackInfo["beat"] = props->beat; 1446 | callbackInfo.beatCallbackInfo["bar"] = props->bar; 1447 | callbackInfo.beatCallbackInfo["tempo"] = props->tempo; 1448 | callbackInfo.beatCallbackInfo["time_signature_upper"] = props->timesignatureupper; 1449 | callbackInfo.beatCallbackInfo["time_signature_lower"] = props->timesignaturelower; 1450 | callbackInfo.beatCallbackInfo["position"] = props->position; 1451 | callbackInfo.beatSignalEmitted = false; 1452 | } else if (type == FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED || type == FMOD_STUDIO_EVENT_CALLBACK_SOUND_STOPPED) { 1453 | FMOD::Sound *sound = (FMOD::Sound *)parameters; 1454 | char n[256]; 1455 | sound->getName(n, 256); 1456 | String name(n); 1457 | String mType = type == FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED ? "played" : "stopped"; 1458 | callbackInfo.soundCallbackInfo["name"] = name; 1459 | callbackInfo.soundCallbackInfo["type"] = mType; 1460 | callbackInfo.soundSignalEmitted = false; 1461 | } 1462 | } 1463 | mut->unlock(); 1464 | return FMOD_OK; 1465 | } 1466 | 1467 | void Fmod::runCallbacks() { 1468 | Callbacks::mut->lock(); 1469 | for (auto e = events.front(); e; e = e->next()) { 1470 | FMOD::Studio::EventInstance *eventInstance = e->get(); 1471 | Callbacks::CallbackInfo cbInfo = getEventInfo(eventInstance)->callbackInfo; 1472 | // check for Marker callbacks 1473 | if (!cbInfo.markerSignalEmitted) { 1474 | emit_signal("timeline_marker", cbInfo.markerCallbackInfo); 1475 | cbInfo.markerSignalEmitted = true; 1476 | } 1477 | 1478 | // check for Beat callbacks 1479 | if (!cbInfo.beatSignalEmitted) { 1480 | emit_signal("timeline_beat", cbInfo.beatCallbackInfo); 1481 | cbInfo.beatSignalEmitted = true; 1482 | } 1483 | 1484 | // check for Sound callbacks 1485 | if (!cbInfo.soundSignalEmitted) { 1486 | if (cbInfo.soundCallbackInfo["type"] == "played") 1487 | emit_signal("sound_played", cbInfo.soundCallbackInfo); 1488 | else 1489 | emit_signal("sound_stopped", cbInfo.soundCallbackInfo); 1490 | cbInfo.soundSignalEmitted = true; 1491 | } 1492 | } 1493 | Callbacks::mut->unlock(); 1494 | } 1495 | 1496 | void Fmod::_bind_methods() { 1497 | /* System functions */ 1498 | ClassDB::bind_method(D_METHOD("system_init", "num_of_channels", "studio_flags", "flags"), &Fmod::init); 1499 | ClassDB::bind_method(D_METHOD("system_update"), &Fmod::update); 1500 | ClassDB::bind_method(D_METHOD("system_shutdown"), &Fmod::shutdown); 1501 | ClassDB::bind_method(D_METHOD("system_add_listener", "node"), &Fmod::addListener); 1502 | ClassDB::bind_method(D_METHOD("system_remove_listener", "index"), &Fmod::removeListener); 1503 | ClassDB::bind_method(D_METHOD("system_set_software_format", "sample_rate", "speaker_mode", "num_raw_speakers"), &Fmod::setSoftwareFormat); 1504 | ClassDB::bind_method(D_METHOD("system_set_parameter_by_name", "name", "value"), &Fmod::setGlobalParameterByName); 1505 | ClassDB::bind_method(D_METHOD("system_get_parameter_by_name", "name"), &Fmod::getGlobalParameterByName); 1506 | ClassDB::bind_method(D_METHOD("system_set_parameter_by_id", "id_pair", "value"), &Fmod::setGlobalParameterByID); 1507 | ClassDB::bind_method(D_METHOD("system_get_parameter_by_id", "id_pair"), &Fmod::getGlobalParameterByID); 1508 | ClassDB::bind_method(D_METHOD("system_get_parameter_desc_by_name", "name"), &Fmod::getGlobalParameterDescByName); 1509 | ClassDB::bind_method(D_METHOD("system_get_parameter_desc_by_id", "id_pair"), &Fmod::getGlobalParameterDescByID); 1510 | ClassDB::bind_method(D_METHOD("system_get_parameter_desc_count"), &Fmod::getGlobalParameterDescCount); 1511 | ClassDB::bind_method(D_METHOD("system_get_parameter_desc_list"), &Fmod::getGlobalParameterDescList); 1512 | ClassDB::bind_method(D_METHOD("system_get_num_listeners"), &Fmod::getSystemNumListeners); 1513 | ClassDB::bind_method(D_METHOD("system_get_listener_weight", "index"), &Fmod::getSystemListenerWeight); 1514 | ClassDB::bind_method(D_METHOD("system_set_listener_weight", "index", "weight"), &Fmod::setSystemListenerWeight); 1515 | ClassDB::bind_method(D_METHOD("system_get_listener_attributes", "index"), &Fmod::getSystemListener3DAttributes); 1516 | ClassDB::bind_method(D_METHOD("system_set_listener_attributes", "index", "forward", "position", "up", "velocity"), &Fmod::setSystemListener3DAttributes); 1517 | ClassDB::bind_method(D_METHOD("system_set_sound_3d_settings", "dopplerScale", "distanceFactor", "rollOffScale"), &Fmod::setSound3DSettings); 1518 | ClassDB::bind_method(D_METHOD("system_get_available_drivers"), &Fmod::getAvailableDrivers); 1519 | ClassDB::bind_method(D_METHOD("system_get_driver"), &Fmod::getDriver); 1520 | ClassDB::bind_method(D_METHOD("system_set_driver", "id"), &Fmod::setDriver); 1521 | ClassDB::bind_method(D_METHOD("system_get_performance_data"), &Fmod::getPerformanceData); 1522 | ClassDB::bind_method(D_METHOD("system_get_event", "path"), &Fmod::getEvent); 1523 | ClassDB::bind_method(D_METHOD("system_set_listener_lock", "index", "is_locked"), &Fmod::setListenerLock); 1524 | ClassDB::bind_method(D_METHOD("system_get_listener_lock", "index"), &Fmod::getListenerLock); 1525 | 1526 | /* Integration helper functions */ 1527 | ClassDB::bind_method(D_METHOD("create_event_instance", "event_path"), &Fmod::createEventInstance); 1528 | ClassDB::bind_method(D_METHOD("play_one_shot", "event_name", "node"), &Fmod::playOneShot); 1529 | ClassDB::bind_method(D_METHOD("play_one_shot_with_params", "event_name", "node", "initial_parameters"), &Fmod::playOneShotWithParams); 1530 | ClassDB::bind_method(D_METHOD("play_one_shot_attached", "event_name", "node"), &Fmod::playOneShotAttached); 1531 | ClassDB::bind_method(D_METHOD("play_one_shot_attached_with_params", "event_name", "node", "initial_parameters"), &Fmod::playOneShotAttachedWithParams); 1532 | ClassDB::bind_method(D_METHOD("attach_instance_to_node", "id", "node"), &Fmod::attachInstanceToNode); 1533 | ClassDB::bind_method(D_METHOD("detach_instance_from_node", "id"), &Fmod::detachInstanceFromNode); 1534 | ClassDB::bind_method(D_METHOD("pause_all_events"), &Fmod::pauseAllEvents); 1535 | ClassDB::bind_method(D_METHOD("unpause_all_events"), &Fmod::unpauseAllEvents); 1536 | ClassDB::bind_method(D_METHOD("mute_all_events"), &Fmod::muteAllEvents); 1537 | ClassDB::bind_method(D_METHOD("unmute_all_events"), &Fmod::unmuteAllEvents); 1538 | ClassDB::bind_method(D_METHOD("banks_still_loading"), &Fmod::banksStillLoading); 1539 | ClassDB::bind_method(D_METHOD("wait_for_all_loads"), &Fmod::waitForAllLoads); 1540 | 1541 | /* Bank functions */ 1542 | ClassDB::bind_method(D_METHOD("bank_load", "path_to_bank", "flags"), &Fmod::loadbank); 1543 | ClassDB::bind_method(D_METHOD("bank_unload", "path_to_bank"), &Fmod::unloadBank); 1544 | ClassDB::bind_method(D_METHOD("bank_get_loading_state", "path_to_bank"), &Fmod::getBankLoadingState); 1545 | ClassDB::bind_method(D_METHOD("bank_get_bus_count", "path_to_bank"), &Fmod::getBankBusCount); 1546 | ClassDB::bind_method(D_METHOD("bank_get_event_count", "path_to_bank"), &Fmod::getBankEventCount); 1547 | ClassDB::bind_method(D_METHOD("bank_get_string_count", "path_to_bank"), &Fmod::getBankStringCount); 1548 | ClassDB::bind_method(D_METHOD("bank_get_vca_count", "path_to_bank"), &Fmod::getBankVCACount); 1549 | 1550 | /* EventDescription functions */ 1551 | ClassDB::bind_method(D_METHOD("event_desc_create_instance", "desc_handle"), &Fmod::descCreateInstance); 1552 | ClassDB::bind_method(D_METHOD("event_desc_get_length", "desc_handle"), &Fmod::descGetLength); 1553 | ClassDB::bind_method(D_METHOD("event_desc_get_path", "desc_handle"), &Fmod::descGetPath); 1554 | ClassDB::bind_method(D_METHOD("event_desc_get_instance_list", "desc_handle"), &Fmod::descGetInstanceList); 1555 | ClassDB::bind_method(D_METHOD("event_desc_get_instance_count", "desc_handle"), &Fmod::descGetInstanceCount); 1556 | ClassDB::bind_method(D_METHOD("event_desc_release_all_instances", "desc_handle"), &Fmod::descReleaseAllInstances); 1557 | ClassDB::bind_method(D_METHOD("event_desc_load_sample_data", "desc_handle"), &Fmod::descLoadSampleData); 1558 | ClassDB::bind_method(D_METHOD("event_desc_unload_sample_data", "desc_handle"), &Fmod::descUnloadSampleData); 1559 | ClassDB::bind_method(D_METHOD("event_desc_get_sample_loading_state", "desc_handle"), &Fmod::descGetSampleLoadingState); 1560 | ClassDB::bind_method(D_METHOD("event_desc_is_3D", "desc_handle"), &Fmod::descIs3D); 1561 | ClassDB::bind_method(D_METHOD("event_desc_is_oneshot", "desc_handle"), &Fmod::descIsOneShot); 1562 | ClassDB::bind_method(D_METHOD("event_desc_is_snapshot", "desc_handle"), &Fmod::descIsSnapshot); 1563 | ClassDB::bind_method(D_METHOD("event_desc_is_stream", "desc_handle"), &Fmod::descIsStream); 1564 | ClassDB::bind_method(D_METHOD("event_desc_has_cue", "desc_handle"), &Fmod::descHasCue); 1565 | ClassDB::bind_method(D_METHOD("event_desc_get_maximum_distance", "desc_handle"), &Fmod::descGetMaximumDistance); 1566 | ClassDB::bind_method(D_METHOD("event_desc_get_minimum_distance", "desc_handle"), &Fmod::descGetMinimumDistance); 1567 | ClassDB::bind_method(D_METHOD("event_desc_get_sound_size", "desc_handle"), &Fmod::descGetSoundSize); 1568 | ClassDB::bind_method(D_METHOD("event_desc_get_parameter_desc_by_name", "desc_handle", "parameter_name"), &Fmod::descGetParameterDescriptionByName); 1569 | ClassDB::bind_method(D_METHOD("event_desc_get_parameter_desc_by_id", "desc_handle", "id_pair"), &Fmod::descGetParameterDescriptionByID); 1570 | ClassDB::bind_method(D_METHOD("event_desc_get_parameter_description_count", "desc_handle"), &Fmod::descGetParameterDescriptionCount); 1571 | ClassDB::bind_method(D_METHOD("event_desc_get_parameter_desc_by_index", "desc_handle", "index"), &Fmod::descGetParameterDescriptionByIndex); 1572 | ClassDB::bind_method(D_METHOD("event_desc_get_user_property", "desc_handle", "name"), &Fmod::descGetUserProperty); 1573 | ClassDB::bind_method(D_METHOD("event_desc_get_user_property_count", "desc_handle"), &Fmod::descGetUserPropertyCount); 1574 | ClassDB::bind_method(D_METHOD("event_desc_get_user_property_by_index", "desc_handle", "index"), &Fmod::descGetParameterDescriptionByIndex); 1575 | 1576 | /* EventInstance functions */ 1577 | ClassDB::bind_method(D_METHOD("event_get_parameter_by_name", "handle", "parameter_name"), &Fmod::getEventParameterByName); 1578 | ClassDB::bind_method(D_METHOD("event_set_parameter_by_name", "handle", "parameter_name", "value"), &Fmod::setEventParameterByName); 1579 | ClassDB::bind_method(D_METHOD("event_get_parameter_by_id", "handle", "parameter_id_pair"), &Fmod::getEventParameterByID); 1580 | ClassDB::bind_method(D_METHOD("event_set_parameter_by_id", "handle", "parameter_id_pair", "value"), &Fmod::setEventParameterByID); 1581 | ClassDB::bind_method(D_METHOD("event_release", "handle"), &Fmod::releaseEvent); 1582 | ClassDB::bind_method(D_METHOD("event_start", "handle"), &Fmod::startEvent); 1583 | ClassDB::bind_method(D_METHOD("event_stop", "handle", "stop_mode"), &Fmod::stopEvent); 1584 | ClassDB::bind_method(D_METHOD("event_trigger_cue", "handle"), &Fmod::triggerEventCue); 1585 | ClassDB::bind_method(D_METHOD("event_get_playback_state", "handle"), &Fmod::getEventPlaybackState); 1586 | ClassDB::bind_method(D_METHOD("event_get_paused", "handle"), &Fmod::getEventPaused); 1587 | ClassDB::bind_method(D_METHOD("event_set_paused", "handle", "paused"), &Fmod::setEventPaused); 1588 | ClassDB::bind_method(D_METHOD("event_get_pitch", "handle"), &Fmod::getEventPitch); 1589 | ClassDB::bind_method(D_METHOD("event_set_pitch", "handle", "pitch"), &Fmod::setEventPitch); 1590 | ClassDB::bind_method(D_METHOD("event_get_volume", "handle"), &Fmod::getEventVolume); 1591 | ClassDB::bind_method(D_METHOD("event_set_volume", "handle", "volume"), &Fmod::setEventVolume); 1592 | ClassDB::bind_method(D_METHOD("event_get_timeline_position", "handle"), &Fmod::getEventTimelinePosition); 1593 | ClassDB::bind_method(D_METHOD("event_set_timeline_position", "handle", "position"), &Fmod::setEventTimelinePosition); 1594 | ClassDB::bind_method(D_METHOD("event_get_reverb_level", "handle", "index"), &Fmod::getEventReverbLevel); 1595 | ClassDB::bind_method(D_METHOD("event_set_reverb_level", "handle", "index", "level"), &Fmod::setEventReverbLevel); 1596 | ClassDB::bind_method(D_METHOD("event_is_virtual", "handle"), &Fmod::isEventVirtual); 1597 | ClassDB::bind_method(D_METHOD("event_set_callback", "handle", "callback_mask"), &Fmod::setCallback); 1598 | ClassDB::bind_method(D_METHOD("event_get_description", "handle"), &Fmod::getEventDescription); 1599 | ClassDB::bind_method(D_METHOD("event_set_3D_attributes", "handle", "forward", "position", "up", "velocity"), &Fmod::setEvent3DAttributes); 1600 | ClassDB::bind_method(D_METHOD("event_get_3D_attributes", "handle"), &Fmod::getEvent3DAttributes); 1601 | ClassDB::bind_method(D_METHOD("event_set_listener_mask", "handle", "mask"), &Fmod::setEventListenerMask); 1602 | ClassDB::bind_method(D_METHOD("event_get_listener_mask", "handle"), &Fmod::getEventListenerMask); 1603 | 1604 | /* Bus functions */ 1605 | ClassDB::bind_method(D_METHOD("bus_get_mute", "path_to_bus"), &Fmod::getBusMute); 1606 | ClassDB::bind_method(D_METHOD("bus_get_paused", "path_to_bus"), &Fmod::getBusPaused); 1607 | ClassDB::bind_method(D_METHOD("bus_get_volume", "path_to_bus"), &Fmod::getBusVolume); 1608 | ClassDB::bind_method(D_METHOD("bus_set_mute", "path_to_bus", "mute"), &Fmod::setBusMute); 1609 | ClassDB::bind_method(D_METHOD("bus_set_paused", "path_to_bus", "paused"), &Fmod::setBusPaused); 1610 | ClassDB::bind_method(D_METHOD("bus_set_volume", "path_to_bus", "volume"), &Fmod::setBusVolume); 1611 | ClassDB::bind_method(D_METHOD("bus_stop_all_events", "path_to_bus", "stop_mode"), &Fmod::stopAllBusEvents); 1612 | 1613 | /* VCA functions */ 1614 | ClassDB::bind_method(D_METHOD("vca_get_volume", "path_to_vca"), &Fmod::getVCAVolume); 1615 | ClassDB::bind_method(D_METHOD("vca_set_volume", "path_to_vca", "volume"), &Fmod::setVCAVolume); 1616 | 1617 | /* Core (Low Level) Sound functions */ 1618 | ClassDB::bind_method(D_METHOD("sound_create", "path_to_sound", "mode"), &Fmod::createSound); 1619 | ClassDB::bind_method(D_METHOD("sound_play", "handle"), &Fmod::playSound); 1620 | ClassDB::bind_method(D_METHOD("sound_stop", "handle"), &Fmod::stopSound); 1621 | ClassDB::bind_method(D_METHOD("sound_release", "handle"), &Fmod::releaseSound); 1622 | ClassDB::bind_method(D_METHOD("sound_set_paused", "channel_handle", "paused"), &Fmod::setSoundPaused); 1623 | ClassDB::bind_method(D_METHOD("sound_is_playing", "channel_handle"), &Fmod::isSoundPlaying); 1624 | ClassDB::bind_method(D_METHOD("sound_set_volume", "channel_handle", "volume"), &Fmod::setSoundVolume); 1625 | ClassDB::bind_method(D_METHOD("sound_get_volume", "channel_handle"), &Fmod::getSoundVolume); 1626 | ClassDB::bind_method(D_METHOD("sound_set_pitch", "channel_handle", "pitch"), &Fmod::setSoundPitch); 1627 | ClassDB::bind_method(D_METHOD("sound_get_pitch", "channel_handle"), &Fmod::getSoundPitch); 1628 | 1629 | /* Event Callback Signals */ 1630 | ADD_SIGNAL(MethodInfo("timeline_beat", PropertyInfo(Variant::DICTIONARY, "params"))); 1631 | ADD_SIGNAL(MethodInfo("timeline_marker", PropertyInfo(Variant::DICTIONARY, "params"))); 1632 | ADD_SIGNAL(MethodInfo("sound_played", PropertyInfo(Variant::DICTIONARY, "params"))); 1633 | ADD_SIGNAL(MethodInfo("sound_stopped", PropertyInfo(Variant::DICTIONARY, "params"))); 1634 | 1635 | /* FMOD_INITFLAGS */ 1636 | BIND_CONSTANT(FMOD_INIT_NORMAL); 1637 | BIND_CONSTANT(FMOD_INIT_STREAM_FROM_UPDATE); 1638 | BIND_CONSTANT(FMOD_INIT_MIX_FROM_UPDATE); 1639 | BIND_CONSTANT(FMOD_INIT_3D_RIGHTHANDED); 1640 | BIND_CONSTANT(FMOD_INIT_CHANNEL_LOWPASS); 1641 | BIND_CONSTANT(FMOD_INIT_CHANNEL_DISTANCEFILTER); 1642 | BIND_CONSTANT(FMOD_INIT_PROFILE_ENABLE); 1643 | BIND_CONSTANT(FMOD_INIT_VOL0_BECOMES_VIRTUAL); 1644 | BIND_CONSTANT(FMOD_INIT_GEOMETRY_USECLOSEST); 1645 | BIND_CONSTANT(FMOD_INIT_PREFER_DOLBY_DOWNMIX); 1646 | BIND_CONSTANT(FMOD_INIT_THREAD_UNSAFE); 1647 | BIND_CONSTANT(FMOD_INIT_PROFILE_METER_ALL); 1648 | 1649 | /* FMOD_STUDIO_INITFLAGS */ 1650 | BIND_CONSTANT(FMOD_STUDIO_INIT_NORMAL); 1651 | BIND_CONSTANT(FMOD_STUDIO_INIT_LIVEUPDATE); 1652 | BIND_CONSTANT(FMOD_STUDIO_INIT_ALLOW_MISSING_PLUGINS); 1653 | BIND_CONSTANT(FMOD_STUDIO_INIT_SYNCHRONOUS_UPDATE); 1654 | BIND_CONSTANT(FMOD_STUDIO_INIT_DEFERRED_CALLBACKS); 1655 | BIND_CONSTANT(FMOD_STUDIO_INIT_LOAD_FROM_UPDATE); 1656 | 1657 | /* FMOD_STUDIO_LOAD_BANK_FLAGS */ 1658 | BIND_CONSTANT(FMOD_STUDIO_LOAD_BANK_NORMAL); 1659 | BIND_CONSTANT(FMOD_STUDIO_LOAD_BANK_NONBLOCKING); 1660 | BIND_CONSTANT(FMOD_STUDIO_LOAD_BANK_DECOMPRESS_SAMPLES); 1661 | 1662 | /* FMOD_STUDIO_LOADING_STATE */ 1663 | BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_UNLOADING); 1664 | BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_LOADING); 1665 | BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_LOADED); 1666 | BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_ERROR); 1667 | BIND_CONSTANT(FMOD_STUDIO_LOADING_STATE_UNLOADED); 1668 | 1669 | /* FMOD_STUDIO_PLAYBACK_STATE */ 1670 | BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_PLAYING); 1671 | BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_SUSTAINING); 1672 | BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_STOPPED); 1673 | BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_STARTING); 1674 | BIND_CONSTANT(FMOD_STUDIO_PLAYBACK_STOPPING); 1675 | 1676 | /* FMOD_STUDIO_STOP_MODE */ 1677 | BIND_CONSTANT(FMOD_STUDIO_STOP_ALLOWFADEOUT); 1678 | BIND_CONSTANT(FMOD_STUDIO_STOP_IMMEDIATE); 1679 | 1680 | /* FMOD_STUDIO_EVENT_CALLBACK_TYPE */ 1681 | BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_MARKER); 1682 | BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_TIMELINE_BEAT); 1683 | BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_SOUND_PLAYED); 1684 | BIND_CONSTANT(FMOD_STUDIO_EVENT_CALLBACK_SOUND_STOPPED); 1685 | 1686 | /* FMOD_SPEAKERMODE */ 1687 | BIND_CONSTANT(FMOD_SPEAKERMODE_DEFAULT); 1688 | BIND_CONSTANT(FMOD_SPEAKERMODE_RAW); 1689 | BIND_CONSTANT(FMOD_SPEAKERMODE_MONO); 1690 | BIND_CONSTANT(FMOD_SPEAKERMODE_STEREO); 1691 | BIND_CONSTANT(FMOD_SPEAKERMODE_QUAD); 1692 | BIND_CONSTANT(FMOD_SPEAKERMODE_SURROUND); 1693 | BIND_CONSTANT(FMOD_SPEAKERMODE_5POINT1); 1694 | BIND_CONSTANT(FMOD_SPEAKERMODE_7POINT1); 1695 | BIND_CONSTANT(FMOD_SPEAKERMODE_7POINT1POINT4); 1696 | BIND_CONSTANT(FMOD_SPEAKERMODE_MAX); 1697 | 1698 | /* FMOD_MODE */ 1699 | BIND_CONSTANT(FMOD_DEFAULT); 1700 | BIND_CONSTANT(FMOD_LOOP_OFF); 1701 | BIND_CONSTANT(FMOD_LOOP_NORMAL); 1702 | BIND_CONSTANT(FMOD_LOOP_BIDI); 1703 | BIND_CONSTANT(FMOD_2D); 1704 | BIND_CONSTANT(FMOD_3D); 1705 | BIND_CONSTANT(FMOD_CREATESTREAM); 1706 | BIND_CONSTANT(FMOD_CREATESAMPLE); 1707 | BIND_CONSTANT(FMOD_CREATECOMPRESSEDSAMPLE); 1708 | BIND_CONSTANT(FMOD_OPENUSER); 1709 | BIND_CONSTANT(FMOD_OPENMEMORY); 1710 | BIND_CONSTANT(FMOD_OPENMEMORY_POINT); 1711 | BIND_CONSTANT(FMOD_OPENRAW); 1712 | BIND_CONSTANT(FMOD_OPENONLY); 1713 | BIND_CONSTANT(FMOD_ACCURATETIME); 1714 | BIND_CONSTANT(FMOD_MPEGSEARCH); 1715 | BIND_CONSTANT(FMOD_NONBLOCKING); 1716 | BIND_CONSTANT(FMOD_UNIQUE); 1717 | BIND_CONSTANT(FMOD_3D_HEADRELATIVE); 1718 | BIND_CONSTANT(FMOD_3D_WORLDRELATIVE); 1719 | BIND_CONSTANT(FMOD_3D_INVERSEROLLOFF); 1720 | BIND_CONSTANT(FMOD_3D_LINEARROLLOFF); 1721 | BIND_CONSTANT(FMOD_3D_LINEARSQUAREROLLOFF); 1722 | BIND_CONSTANT(FMOD_3D_INVERSETAPEREDROLLOFF); 1723 | BIND_CONSTANT(FMOD_3D_CUSTOMROLLOFF); 1724 | BIND_CONSTANT(FMOD_3D_IGNOREGEOMETRY); 1725 | BIND_CONSTANT(FMOD_IGNORETAGS); 1726 | BIND_CONSTANT(FMOD_LOWMEM); 1727 | BIND_CONSTANT(FMOD_VIRTUAL_PLAYFROMSTART); 1728 | } 1729 | 1730 | Fmod *Fmod::getSingleton() { 1731 | return singleton; 1732 | } 1733 | 1734 | Fmod::Fmod() { 1735 | singleton = this; 1736 | system = nullptr; 1737 | coreSystem = nullptr; 1738 | Callbacks::mut = Mutex::create(); 1739 | checkErrors(FMOD::Studio::System::create(&system)); 1740 | checkErrors(system->getCoreSystem(&coreSystem)); 1741 | } 1742 | 1743 | Fmod::~Fmod() { 1744 | Callbacks::mut->~Mutex(); 1745 | singleton = nullptr; 1746 | } 1747 | -------------------------------------------------------------------------------- /godot_fmod.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* godot_fmod.h */ 3 | /*************************************************************************/ 4 | /* */ 5 | /* FMOD Studio module and bindings for the Godot game engine */ 6 | /* */ 7 | /*************************************************************************/ 8 | /* Copyright (c) 2020 Alex Fonseka */ 9 | /* */ 10 | /* Permission is hereby granted, free of charge, to any person obtaining */ 11 | /* a copy of this software and associated documentation files (the */ 12 | /* "Software"), to deal in the Software without restriction, including */ 13 | /* without limitation the rights to use, copy, modify, merge, publish, */ 14 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 15 | /* permit persons to whom the Software is furnished to do so, subject to */ 16 | /* the following conditions: */ 17 | /* */ 18 | /* The above copyright notice and this permission notice shall be */ 19 | /* included in all copies or substantial portions of the Software. */ 20 | /* */ 21 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 22 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 23 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 24 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 25 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 26 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 27 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 28 | /*************************************************************************/ 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | 35 | #include "core/array.h" 36 | #include "core/dictionary.h" 37 | #include "core/map.h" 38 | #include "core/node_path.h" 39 | #include "core/object.h" 40 | #include "core/reference.h" 41 | #include "core/vector.h" 42 | #include "scene/2d/canvas_item.h" 43 | #include "scene/3d/spatial.h" 44 | #include "scene/main/node.h" 45 | 46 | #include "api/core/inc/fmod.hpp" 47 | #include "api/core/inc/fmod_errors.h" 48 | #include "api/studio/inc/fmod_studio.hpp" 49 | 50 | #include "callbacks.h" 51 | 52 | class Fmod : public Object { 53 | public: 54 | struct EventInfo { 55 | // GameObject to which this event is attached 56 | Object *gameObj = nullptr; 57 | 58 | // Callback info associated with this event 59 | Callbacks::CallbackInfo callbackInfo = Callbacks::CallbackInfo(); 60 | }; 61 | 62 | private: 63 | GDCLASS(Fmod, Object); 64 | 65 | FMOD::Studio::System *system; 66 | FMOD::System *coreSystem; 67 | 68 | bool listenerWarning = true; 69 | float distanceScale = 1.0f; 70 | 71 | struct Listener { 72 | // GameObject to which this listener is attached 73 | Object *gameObj = nullptr; 74 | 75 | // When true, locks the listener in place, disabling internal 3D attribute updates. 76 | // 3D attributes can still be manually set with a set3DAttributes call. 77 | bool listenerLock = false; 78 | }; 79 | std::vector listeners; 80 | 81 | Map banks; 82 | Map eventDescriptions; 83 | Map ptrToEventDescMap; 84 | Map buses; 85 | Map VCAs; 86 | Map events; 87 | 88 | // For playing sounds using FMOD Core / Low Level 89 | Map sounds; 90 | Map channels; 91 | 92 | FMOD_3D_ATTRIBUTES get3DAttributes(FMOD_VECTOR pos, FMOD_VECTOR up, FMOD_VECTOR forward, FMOD_VECTOR vel); 93 | FMOD_VECTOR toFmodVector(Vector3 vec); 94 | void setListenerAttributes(); 95 | void updateInstance3DAttributes(FMOD::Studio::EventInstance *i, Object *o); 96 | bool isNull(Object *o); 97 | void loadBus(const String &busPath); 98 | void loadVCA(const String &VCAPath); 99 | void runCallbacks(); 100 | FMOD::Studio::EventInstance *createInstance(String eventPath, bool isOneShot, Object *gameObject); 101 | FMOD::Studio::EventInstance *createInstance(FMOD::Studio::EventDescription *eventDesc, bool isOneShot, Object *gameObject); 102 | EventInfo *getEventInfo(FMOD::Studio::EventInstance *eventInstance); 103 | void releaseOneEvent(FMOD::Studio::EventInstance *eventInstance); 104 | void clearNullListeners(); 105 | void clearChannelRefs(); 106 | 107 | inline int checkErrors(FMOD_RESULT result) { 108 | if (result != FMOD_OK) { 109 | String err = String("FMOD Sound System: ") + String(FMOD_ErrorString(result)); 110 | print_error(err.ascii().get_data()); 111 | return 0; 112 | } 113 | return 1; 114 | } 115 | 116 | protected: 117 | static Fmod *singleton; 118 | static void _bind_methods(); 119 | 120 | public: 121 | /* System functions */ 122 | void init(int numOfChannels, int studioFlags, int flags); 123 | void update(); 124 | void shutdown(); 125 | void addListener(Object *gameObj); 126 | void removeListener(uint8_t index); 127 | void setSoftwareFormat(int sampleRate, int speakerMode, int numRawSpeakers); 128 | void setSound3DSettings(float dopplerScale, float distanceFactor, float rollOffScale); 129 | int getSystemNumListeners(); 130 | float getSystemListenerWeight(uint8_t index); 131 | void setSystemListenerWeight(uint8_t index, float weight); 132 | Dictionary getSystemListener3DAttributes(uint8_t index); 133 | void setSystemListener3DAttributes(uint8_t index, Vector3 forward, Vector3 position, Vector3 up, Vector3 velocity); 134 | uint64_t getEvent(const String &path); 135 | void setGlobalParameterByName(const String ¶meterName, float value); 136 | float getGlobalParameterByName(const String ¶meterName); 137 | void setGlobalParameterByID(const Array &idPair, float value); 138 | float getGlobalParameterByID(const Array &idPair); 139 | Dictionary getGlobalParameterDescByName(const String ¶meterName); 140 | Dictionary getGlobalParameterDescByID(const Array &idPair); 141 | uint32_t getGlobalParameterDescCount(); 142 | Array getGlobalParameterDescList(); 143 | Array getAvailableDrivers(); 144 | int getDriver(); 145 | void setDriver(uint8_t id); 146 | Dictionary getPerformanceData(); 147 | void setListenerLock(uint8_t index, bool isLocked); 148 | bool getListenerLock(uint8_t index); 149 | 150 | /* Helper functions */ 151 | uint64_t createEventInstance(const String &eventPath); 152 | void playOneShot(const String &eventName, Object *gameObj); 153 | void playOneShotWithParams(const String &eventName, Object *gameObj, const Dictionary ¶meters); 154 | void playOneShotAttached(const String &eventName, Object *gameObj); 155 | void playOneShotAttachedWithParams(const String &eventName, Object *gameObj, const Dictionary ¶meters); 156 | void attachInstanceToNode(uint64_t instanceId, Object *gameObj); 157 | void detachInstanceFromNode(uint64_t instanceId); 158 | void pauseAllEvents(); 159 | void unpauseAllEvents(); 160 | void muteAllEvents(); 161 | void unmuteAllEvents(); 162 | bool banksStillLoading(); 163 | void waitForAllLoads(); 164 | 165 | /* Bank functions */ 166 | String loadbank(const String &pathToBank, int flags); 167 | void unloadBank(const String &pathToBank); 168 | int getBankLoadingState(const String &pathToBank); 169 | int getBankBusCount(const String &pathToBank); 170 | int getBankEventCount(const String &pathToBank); 171 | int getBankStringCount(const String &pathToBank); 172 | int getBankVCACount(const String &pathToBank); 173 | 174 | /* EventDescription functions */ 175 | uint64_t descCreateInstance(uint64_t descHandle); 176 | int descGetLength(uint64_t descHandle); 177 | String descGetPath(uint64_t descHandle); 178 | Array descGetInstanceList(uint64_t descHandle); 179 | int descGetInstanceCount(uint64_t descHandle); 180 | void descReleaseAllInstances(uint64_t descHandle); 181 | void descLoadSampleData(uint64_t descHandle); 182 | void descUnloadSampleData(uint64_t descHandle); 183 | int descGetSampleLoadingState(uint64_t descHandle); 184 | bool descIs3D(uint64_t descHandle); 185 | bool descIsOneShot(uint64_t descHandle); 186 | bool descIsSnapshot(uint64_t descHandle); 187 | bool descIsStream(uint64_t descHandle); 188 | bool descHasCue(uint64_t descHandle); 189 | float descGetMaximumDistance(uint64_t descHandle); 190 | float descGetMinimumDistance(uint64_t descHandle); 191 | float descGetSoundSize(uint64_t descHandle); 192 | Dictionary descGetParameterDescriptionByName(uint64_t descHandle, const String &name); 193 | Dictionary descGetParameterDescriptionByID(uint64_t descHandle, const Array &idPair); 194 | int descGetParameterDescriptionCount(uint64_t descHandle); 195 | Dictionary descGetParameterDescriptionByIndex(uint64_t descHandle, int index); 196 | Dictionary descGetUserProperty(uint64_t descHandle, String name); 197 | int descGetUserPropertyCount(uint64_t descHandle); 198 | Dictionary descUserPropertyByIndex(uint64_t descHandle, int index); 199 | 200 | /* EventInstance functions */ 201 | float getEventParameterByName(uint64_t instanceId, const String ¶meterName); 202 | void setEventParameterByName(uint64_t instanceId, const String ¶meterName, float value); 203 | float getEventParameterByID(uint64_t instanceId, const Array &idPair); 204 | void setEventParameterByID(uint64_t instanceId, const Array &idPair, float value); 205 | void releaseEvent(uint64_t instanceId); 206 | void startEvent(uint64_t instanceId); 207 | void stopEvent(uint64_t instanceId, int stopMode); 208 | void triggerEventCue(uint64_t instanceId); 209 | int getEventPlaybackState(uint64_t instanceId); 210 | bool getEventPaused(uint64_t instanceId); 211 | void setEventPaused(uint64_t instanceId, bool paused); 212 | float getEventPitch(uint64_t instanceId); 213 | void setEventPitch(uint64_t instanceId, float pitch); 214 | float getEventVolume(uint64_t instanceId); 215 | void setEventVolume(uint64_t instanceId, float volume); 216 | int getEventTimelinePosition(uint64_t instanceId); 217 | void setEventTimelinePosition(uint64_t instanceId, int position); 218 | float getEventReverbLevel(uint64_t instanceId, int index); 219 | void setEventReverbLevel(uint64_t instanceId, int index, float level); 220 | bool isEventVirtual(uint64_t instanceId); 221 | void setCallback(uint64_t instanceId, int callbackMask); 222 | uint64_t getEventDescription(uint64_t instanceId); 223 | void setEvent3DAttributes(uint64_t instanceId, Vector3 forward, Vector3 position, Vector3 up, Vector3 velocity); 224 | Dictionary getEvent3DAttributes(uint64_t instanceId); 225 | void setEventListenerMask(uint64_t instanceId, int mask); 226 | uint32_t getEventListenerMask(uint64_t instanceId); 227 | 228 | /* Bus functions */ 229 | bool getBusMute(const String &busPath); 230 | bool getBusPaused(const String &busPath); 231 | float getBusVolume(const String &busPath); 232 | void setBusMute(const String &busPath, bool mute); 233 | void setBusPaused(const String &busPath, bool paused); 234 | void setBusVolume(const String &busPath, float volume); 235 | void stopAllBusEvents(const String &busPath, int stopMode); 236 | 237 | /* VCA functions */ 238 | float getVCAVolume(const String &VCAPath); 239 | void setVCAVolume(const String &VCAPath, float volume); 240 | 241 | /* Core (Low Level) Sound functions */ 242 | uint64_t createSound(const String &path, int mode); 243 | uint64_t playSound(uint64_t handle); 244 | void releaseSound(uint64_t handle); 245 | /* --- */ 246 | void setSoundPaused(uint64_t channelHandle, bool paused); 247 | void stopSound(uint64_t channelHandle); 248 | bool isSoundPlaying(uint64_t channelHandle); 249 | void setSoundVolume(uint64_t channelHandle, float volume); 250 | float getSoundVolume(uint64_t channelHandle); 251 | float getSoundPitch(uint64_t channelHandle); 252 | void setSoundPitch(uint64_t channelHandle, float pitch); 253 | 254 | static Fmod *getSingleton(); 255 | 256 | Fmod(); 257 | ~Fmod(); 258 | }; 259 | -------------------------------------------------------------------------------- /register_types.cpp: -------------------------------------------------------------------------------- 1 | #include "register_types.h" 2 | #include "core/class_db.h" 3 | #include "core/engine.h" 4 | 5 | #include "godot_fmod.h" 6 | 7 | static Fmod *fmodPtr = nullptr; 8 | 9 | void register_fmod_types() { 10 | ClassDB::register_class(); 11 | fmodPtr = memnew(Fmod); 12 | Engine::get_singleton()->add_singleton(Engine::Singleton("Fmod", Fmod::getSingleton())); 13 | } 14 | 15 | void unregister_fmod_types() { 16 | memdelete(fmodPtr); 17 | } 18 | -------------------------------------------------------------------------------- /register_types.h: -------------------------------------------------------------------------------- 1 | void register_fmod_types(); 2 | void unregister_fmod_types(); --------------------------------------------------------------------------------