From 12fbbb82bcc93e5bb0606db518349800d3dedf4c Mon Sep 17 00:00:00 2001 From: Rob Kelly Date: Mon, 9 Dec 2024 02:57:08 -0700 Subject: [PATCH] Volume mixer settings with persistence to disk --- default_bus_layout.tres | 1 + project.godot | 1 + src/game/game_settings.gd | 19 +++++ src/ui/main_theme.tres | 9 +++ src/ui/menus/settings_menu/settings_menu.gd | 19 ++++- src/ui/menus/settings_menu/settings_menu.tscn | 8 ++- .../volume_slider/volume_slider.gd | 70 +++++++++++++++++++ .../volume_slider/volume_slider.tscn | 51 ++++++++++++++ 8 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 src/ui/menus/settings_menu/volume_slider/volume_slider.gd create mode 100644 src/ui/menus/settings_menu/volume_slider/volume_slider.tscn diff --git a/default_bus_layout.tres b/default_bus_layout.tres index 2c4ae19..5307834 100644 --- a/default_bus_layout.tres +++ b/default_bus_layout.tres @@ -1,6 +1,7 @@ [gd_resource type="AudioBusLayout" format=3 uid="uid://cwxndi28bii8d"] [resource] +bus/0/volume_db = 0.0 bus/1/name = &"SFX" bus/1/solo = false bus/1/mute = false diff --git a/project.godot b/project.godot index 5fb6396..100d6b3 100644 --- a/project.godot +++ b/project.godot @@ -61,6 +61,7 @@ config/controls/camera/y_axis_acceleration=30.0 config/controls/camera/invert_pitch=false config/accessibility/enable_screen_shake=true config/accessibility/enable_hit_lag=true +audio/buses/override_bus_layout="user://audio_bus_layout.tres" [global_group] diff --git a/src/game/game_settings.gd b/src/game/game_settings.gd index 2f5a7d1..8566f74 100644 --- a/src/game/game_settings.gd +++ b/src/game/game_settings.gd @@ -2,6 +2,7 @@ class_name GameSettingsType extends Node ## Container for project settings, for quick runtime access. var settings_file: String +var audio_bus_file: String var free_camera_speed: float var x_sensitivity: float @@ -15,10 +16,13 @@ var enable_hit_lag: bool func _init() -> void: ProjectSettings.settings_changed.connect(_read_settings) + _read_settings() + _load_audio_bus_override() func _read_settings() -> void: settings_file = ProjectSettings.get_setting("application/config/project_settings_override") + audio_bus_file = ProjectSettings.get_setting("game/audio/buses/override_bus_layout") free_camera_speed = ProjectSettings.get_setting("game/config/controls/camera/free_camera_speed") x_sensitivity = ProjectSettings.get_setting("game/config/controls/camera/x_axis_sensitivity") @@ -31,3 +35,18 @@ func _read_settings() -> void: "game/config/accessibility/enable_screen_shake" ) enable_hit_lag = ProjectSettings.get_setting("game/config/accessibility/enable_hit_lag") + + +func _load_audio_bus_override() -> void: + # Load override audio bus file + if FileAccess.file_exists(audio_bus_file): + print_debug("Loading audio bus override from ", audio_bus_file) + var bus_layout: AudioBusLayout = load(audio_bus_file) + AudioServer.set_bus_layout(bus_layout) + + +func write() -> void: + print_debug("Writing settings to ", settings_file) + ProjectSettings.save_custom(settings_file) + print_debug("Writing audio bus levels to ", audio_bus_file) + ResourceSaver.save(AudioServer.generate_bus_layout(), audio_bus_file) diff --git a/src/ui/main_theme.tres b/src/ui/main_theme.tres index 9c18625..86a173b 100644 --- a/src/ui/main_theme.tres +++ b/src/ui/main_theme.tres @@ -56,6 +56,12 @@ CancelButton/colors/font_outline_color = Color(0, 0, 0, 1) CancelButton/constants/outline_size = 6 CancelButton/font_sizes/font_size = 24 CancelButton/fonts/font = ExtResource("2_5ty6u") +ChannelMixerIcon/base_type = &"Label" +ChannelMixerIcon/colors/font_color = Color(1, 1, 0.870588, 1) +ChannelMixerIcon/colors/font_outline_color = Color(0, 0, 0, 1) +ChannelMixerIcon/constants/outline_size = 5 +ChannelMixerIcon/font_sizes/font_size = 24 +ChannelMixerIcon/fonts/font = ExtResource("2_8kux8") CheckerContainerEven/base_type = &"PanelContainer" CheckerContainerEven/styles/panel = SubResource("StyleBoxFlat_rq1no") CheckerContainerOdd/base_type = &"PanelContainer" @@ -75,10 +81,13 @@ ClubSelectLabelDisabled/colors/font_outline_color = Color(0.2, 0.2, 0.2, 1) ClubSelectLabelDisabled/colors/font_shadow_color = Color(0.2, 0.2, 0.2, 1) ClubSelectLabelDisabled/font_sizes/font_size = 84 ClubSelectLabelDisabled/fonts/font = ExtResource("1_3rv2b") +HeaderLarge/constants/outline_size = 8 HeaderLarge/font_sizes/font_size = 28 +HeaderMedium/constants/outline_size = 6 HeaderMedium/font_sizes/font_size = 24 HeaderSmall/font_sizes/font_size = 20 HeaderXLarge/base_type = &"Label" +HeaderXLarge/constants/outline_size = 10 HeaderXLarge/font_sizes/font_size = 36 InputBindButton/base_type = &"UIButton" InputBindButton/colors/font_color = Color(1, 1, 0.870588, 1) diff --git a/src/ui/menus/settings_menu/settings_menu.gd b/src/ui/menus/settings_menu/settings_menu.gd index de10c60..3b9dcc3 100644 --- a/src/ui/menus/settings_menu/settings_menu.gd +++ b/src/ui/menus/settings_menu/settings_menu.gd @@ -2,11 +2,14 @@ extends MarginContainer ## Menu allowing the user to adjust game configuration. const SETTINGS_GROUP := "Settings" +const VOLUME_GROUP := "VolumeSliders" +@onready var bus_mixer_list: HBoxContainer = %BusMixerList @onready var control_binding_list: VBoxContainer = %ControlBindingList func _ready() -> void: + populate_volume_mixers() populate_control_bindings() @@ -16,12 +19,23 @@ func _get_settings_elements() -> Array[Setting]: return elements +func _get_volume_sliders() -> Array[VolumeSlider]: + var elements: Array[VolumeSlider] = [] + elements.assign(get_tree().get_nodes_in_group(VOLUME_GROUP)) + return elements + + func populate_control_bindings() -> void: for action: StringName in InputMap.get_actions(): if not action.begins_with("ui_"): control_binding_list.add_child(ControlBinding.create(action)) +func populate_volume_mixers() -> void: + for idx: int in range(AudioServer.bus_count): + bus_mixer_list.add_child(VolumeSlider.create(idx)) + + ## Close menu without applying settings. func cancel() -> void: queue_free() @@ -31,12 +45,13 @@ func cancel() -> void: func apply() -> void: for setting: Setting in _get_settings_elements(): setting.apply() + for mixer: VolumeSlider in _get_volume_sliders(): + mixer.apply() ## Write all applied settings to disk. func save_settings() -> void: - print_debug("Writing settings to ", Game.settings.settings_file) - ProjectSettings.save_custom(Game.settings.settings_file) + Game.settings.write() BindingLoader.write() diff --git a/src/ui/menus/settings_menu/settings_menu.tscn b/src/ui/menus/settings_menu/settings_menu.tscn index 1cefd14..f98739a 100644 --- a/src/ui/menus/settings_menu/settings_menu.tscn +++ b/src/ui/menus/settings_menu/settings_menu.tscn @@ -14,7 +14,7 @@ script = ExtResource("1_lbcn7") [node name="TabContainer" type="TabContainer" parent="."] layout_mode = 2 -current_tab = 3 +current_tab = 2 [node name="Game" type="MarginContainer" parent="TabContainer"] visible = false @@ -196,7 +196,6 @@ theme_type_variation = &"SettingsListMargin" layout_mode = 2 [node name="Audio" type="MarginContainer" parent="TabContainer"] -visible = false layout_mode = 2 theme_type_variation = &"SettingsPageContainer" metadata/_tab_index = 2 @@ -229,10 +228,13 @@ size_flags_horizontal = 3 size_flags_vertical = 3 theme_type_variation = &"SettingsListMargin" -[node name="SettingsList" type="VBoxContainer" parent="TabContainer/Audio/VBoxContainer/ScrollContainer/MarginContainer"] +[node name="BusMixerList" type="HBoxContainer" parent="TabContainer/Audio/VBoxContainer/ScrollContainer/MarginContainer"] +unique_name_in_owner = true layout_mode = 2 +theme_override_constants/separation = 16 [node name="Controls" type="MarginContainer" parent="TabContainer"] +visible = false layout_mode = 2 theme_type_variation = &"SettingsPageContainer" metadata/_tab_index = 3 diff --git a/src/ui/menus/settings_menu/volume_slider/volume_slider.gd b/src/ui/menus/settings_menu/volume_slider/volume_slider.gd new file mode 100644 index 0000000..b6af97c --- /dev/null +++ b/src/ui/menus/settings_menu/volume_slider/volume_slider.gd @@ -0,0 +1,70 @@ +class_name VolumeSlider extends VBoxContainer +## Input element for adjusting the volume of a single bus. + +const SCENE := preload("res://src/ui/menus/settings_menu/volume_slider/volume_slider.tscn") +const VOLUME_FACTOR := 50.0 + +@export var bus_idx: int + +var value: float: + get: + return slider.value + set(v): + slider.set_value_no_signal(v) + spin_box.set_value_no_signal(v) + _set_bus_volume(v) + +var _applied_value: float + +@onready var bus_name: Label = %BusName +@onready var slider: VSlider = %Slider +@onready var spin_box: SpinBox = %SpinBox +@onready var audio_demo: AudioStreamPlayer = %AudioDemo + + +func _ready() -> void: + bus_name.text = AudioServer.get_bus_name(bus_idx) + audio_demo.bus = bus_name.text + _applied_value = _db_to_volume(AudioServer.get_bus_volume_db(bus_idx)) + value = _applied_value + + +func _exit_tree() -> void: + # Revert volume to the value from the last time `apply` was called + _set_bus_volume(_applied_value) + + +func _db_to_volume(db: float) -> float: + return db_to_linear(db) * VOLUME_FACTOR + + +func _volume_to_db(volume: float) -> float: + return linear_to_db(volume / VOLUME_FACTOR) + + +func _on_component_value_changed(new_value: float) -> void: + value = new_value + + +func _set_bus_volume(new_value: float) -> void: + AudioServer.set_bus_volume_db(bus_idx, _volume_to_db(new_value)) + + +func apply() -> void: + # Prevent value from being reverted during destructor. + _applied_value = value + + +func _on_slider_drag_ended(value_changed: bool) -> void: + if value_changed: + audio_demo.play() + + +func _on_spin_box_value_changed(_new_value: float) -> void: + audio_demo.play() + + +static func create(_bus_idx: int) -> VolumeSlider: + var instance: VolumeSlider = SCENE.instantiate() + instance.bus_idx = _bus_idx + return instance diff --git a/src/ui/menus/settings_menu/volume_slider/volume_slider.tscn b/src/ui/menus/settings_menu/volume_slider/volume_slider.tscn new file mode 100644 index 0000000..2bf7c9c --- /dev/null +++ b/src/ui/menus/settings_menu/volume_slider/volume_slider.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=3 format=3 uid="uid://d2ya6ly01724l"] + +[ext_resource type="Script" path="res://src/ui/menus/settings_menu/volume_slider/volume_slider.gd" id="1_rfe0k"] +[ext_resource type="AudioStream" uid="uid://cllhgumv1fkju" path="res://assets/sound/sfx/player/drive1.wav" id="2_822gu"] + +[node name="VolumeSlider" type="VBoxContainer" groups=["VolumeSliders"]] +custom_minimum_size = Vector2(300, 0) +script = ExtResource("1_rfe0k") + +[node name="NameContainer" type="HBoxContainer" parent="."] +layout_mode = 2 +alignment = 1 + +[node name="Icon" type="Label" parent="NameContainer"] +layout_mode = 2 +theme_type_variation = &"ChannelMixerIcon" +text = "🎧" + +[node name="BusName" type="Label" parent="NameContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 8 +theme_type_variation = &"HeaderMedium" +text = "Channel" + +[node name="Slider" type="VSlider" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 1 +size_flags_vertical = 3 +step = 0.1 + +[node name="DigitalDisplay" type="HBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="SpinBox" type="SpinBox" parent="DigitalDisplay"] +unique_name_in_owner = true +layout_mode = 2 +step = 0.1 +alignment = 2 +suffix = "%" + +[node name="AudioDemo" type="AudioStreamPlayer" parent="."] +unique_name_in_owner = true +stream = ExtResource("2_822gu") + +[connection signal="drag_ended" from="Slider" to="." method="_on_slider_drag_ended"] +[connection signal="value_changed" from="Slider" to="." method="_on_component_value_changed"] +[connection signal="value_changed" from="DigitalDisplay/SpinBox" to="." method="_on_component_value_changed"] +[connection signal="value_changed" from="DigitalDisplay/SpinBox" to="." method="_on_spin_box_value_changed"]