-
-
Notifications
You must be signed in to change notification settings - Fork 1
Implement a dedicated slider.wav sound effect that triggers only during manual slider adjustments #578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Implement a dedicated slider.wav sound effect that triggers only during manual slider adjustments #578
Changes from 32 commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
e2b619c
Merge pull request #573 from ikostan/main
ikostan 6e01bc9
Create slider.wav
espanakosta-jpg 9d4036a
Create slider.wav.import
espanakosta-jpg cacff88
Merge pull request #577 from ikostan/main
ikostan 0a50250
Merge pull request #586 from ikostan/main
ikostan 6f83b6f
[DOCUMENTATION] Milestone #16 README.md update #456
ikostan 84b3cff
Update README.md
ikostan 5ddea6d
https://github.com/ikostan/SkyLockAssault/issues/565
ikostan 77c27ad
[FEATURE] Connect VolumeSlider to Contextual Audio Feedback #566
ikostan c415e22
Update globals.gd
ikostan 461b09d
Update volume_slider.gd
ikostan d747662
Update .all-contributorsrc
ikostan d668e65
suggestion (bug_risk): Programmatic updates while the slider has focu…
ikostan 41a65f5
Update README.md
ikostan 95acc70
Update .all-contributorsrc
ikostan 4e2fda8
Merge branch 'SFX' of https://github.com/ikostan/SkyLockAssault into SFX
ikostan 3f5112f
Update volume_slider.gd
ikostan ba62539
Update volume_slider.gd
ikostan 0741179
audio_constants.gd singleton specifically built for preventing typos …
ikostan 2e5bca6
Update volume_slider.gd
ikostan 63e6a2a
Update README.md
ikostan 28d30a3
Update scripts/managers/audio_manager.gd
ikostan 4d17a2f
Update audio_manager.gd
ikostan 9261c1a
[FEATURE] Verify Signal Decoupling for Web and UI Sync #567
ikostan b71e4a9
New GUT unit tests
ikostan 11f4eba
Update volume_slider.gd
ikostan dc1ebea
Update volume_slider.gd
ikostan ec7ecb0
Update audio_manager.gd
ikostan 14a6f1d
Update audio_manager.gd
ikostan 1caf738
New GUT tests
ikostan 4eb74e0
Update test_volume_slider.gd
ikostan 2c92711
issue (bug_risk): Guard against invalid audio bus names to avoid runt…
ikostan fc74ecc
Update scripts/ui/components/volume_slider.gd
ikostan 0b6700a
Update scripts/ui/components/volume_slider.gd
ikostan 951c849
Test updates
ikostan 07d4012
Update scripts/ui/components/volume_slider.gd
ikostan df74365
Drag state may get stuck if release event is missed.
ikostan 47596d6
suggestion (performance): Avoid repeated warnings and load attempts f…
ikostan 71997b5
Use one English variant consistently (centralised vs centralized).
ikostan b1872eb
Restore AudioManager state after each test to prevent cross-suite lea…
ikostan 566f5bc
Rate-limit test can be flaky under load.
ikostan 2db4d9b
Test mutates live AudioManager / audio server state without isolation.
ikostan a760da5
test_sfx_guard_allows_valid_interaction will trigger real audio playb…
ikostan 9b50fbb
Duplicate section header.
ikostan b8c448f
Connect finished before calling play() for robustness.
ikostan e14872c
Update audio_manager.gd
ikostan a001b09
Update audio_manager.gd
ikostan becbbbb
Encapsulating VolumeSlider
ikostan 7c3afcd
Update volume_slider.gd
ikostan d545aa1
Guard ordering: _previous_value is committed before interaction/rate …
ikostan ddf4015
Debounce timer restarts even when the new value is effectively identi…
ikostan c179b1d
Update test_volume_slider.gd
ikostan 12c21c3
Update volume_slider.gd
ikostan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| [remap] | ||
|
|
||
| importer="wav" | ||
| type="AudioStreamWAV" | ||
| uid="uid://6asmf6p6ftp5" | ||
| path="res://.godot/imported/slider.wav-562467666e90bfee2364ab6985972073.sample" | ||
|
|
||
| [deps] | ||
|
|
||
| source_file="res://files/sounds/sfx/slider.wav" | ||
| dest_files=["res://.godot/imported/slider.wav-562467666e90bfee2364ab6985972073.sample"] | ||
|
|
||
| [params] | ||
|
|
||
| force/8_bit=false | ||
| force/mono=false | ||
| force/max_rate=false | ||
| force/max_rate_hz=44100 | ||
| edit/trim=false | ||
| edit/normalize=false | ||
| edit/loop_mode=0 | ||
| edit/loop_begin=0 | ||
| edit/loop_end=-1 | ||
| compress/mode=2 | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,48 +1,157 @@ | ||
| ## Copyright (C) 2025 Egor Kostan | ||
| ## SPDX-License-Identifier: GPL-3.0-or-later | ||
| # New: Register as global class for testing and reuse | ||
| class_name VolumeSlider | ||
| ## volume_slider.gd | ||
| ## | ||
| ## Handles the volume control slider UI component. | ||
| ## Sends volume updates to AudioManager and handles debounced saving. | ||
| ## Plays rate-limited SFX exclusively on manual user interactions, | ||
| ## safely ignoring programmatic volume changes to prevent audio spam. | ||
|
|
||
| class_name VolumeSlider | ||
| extends HSlider | ||
|
|
||
| ## The cooldown in milliseconds to prevent audio spam during rapid slider drags. | ||
| const SFX_COOLDOWN_MS: int = 60 | ||
|
|
||
| ## The name of the audio bus this slider controls (e.g., "Master", "Music"). | ||
| @export var bus_name: String | ||
|
|
||
| ## The internal index of the audio bus, resolved at runtime. | ||
| var bus_index: int | ||
|
|
||
| # New: Debounce timer for saving settings | ||
| ## Debounce timer for saving settings to avoid disk I/O spam during continuous sliding. | ||
| var save_debounce_timer: Timer | ||
|
|
||
| # --- SFX Rate Limiting and State --- | ||
|
|
||
| ## Timestamp of the last played SFX to enforce the cooldown. | ||
| var _last_sfx_time: int = 0 | ||
|
|
||
| ## Tracks the previous value to ensure SFX only plays on actual deltas. | ||
| var _previous_value: float = -1.0 | ||
|
|
||
| ## Tracks whether the user is actively holding the mouse button down over the slider. | ||
| var _is_dragging: bool = false | ||
|
|
||
|
|
||
| ## Initializes the slider, resolves the bus index, syncs the initial value without | ||
| ## triggering signals, and sets up the debounce timer. | ||
| ## :rtype: void | ||
| func _ready() -> void: | ||
| # Get bus id by name | ||
| bus_index = AudioServer.get_bus_index(bus_name) | ||
|
sourcery-ai[bot] marked this conversation as resolved.
|
||
|
|
||
| # Guard against invalid audio bus names to avoid runtime errors | ||
| if bus_index == -1: | ||
| var err_msg: String = ( | ||
| "VolumeSlider Error: Invalid audio bus name '%s'. Disabling slider." % bus_name | ||
| ) | ||
| Globals.log_message(err_msg, Globals.LogLevel.ERROR) | ||
|
|
||
| # Kill all interactions on this dead component | ||
| editable = false | ||
| mouse_filter = Control.MOUSE_FILTER_IGNORE | ||
| focus_mode = Control.FOCUS_NONE | ||
| return | ||
|
|
||
| # Set current bus volume value first (without triggering signal yet) | ||
| value = db_to_linear(AudioServer.get_bus_volume_db(bus_index)) | ||
| var initial_val: float = db_to_linear(AudioServer.get_bus_volume_db(bus_index)) | ||
| _previous_value = initial_val | ||
| value = initial_val | ||
|
|
||
| # Now connect signal for future changes | ||
| value_changed.connect(_on_value_changed) | ||
|
|
||
| # New: Initialize debounce timer (0.5s delay, one-shot) | ||
| # Safely track input without overriding the base _gui_input virtual method | ||
| gui_input.connect(_on_gui_input) | ||
|
|
||
| # Initialize debounce timer (0.5s delay, one-shot) | ||
| save_debounce_timer = Timer.new() | ||
| save_debounce_timer.wait_time = 0.5 | ||
| save_debounce_timer.one_shot = true | ||
| save_debounce_timer.timeout.connect(_on_debounce_timeout) | ||
| add_child(save_debounce_timer) # Add to scene tree for auto-processing | ||
| add_child(save_debounce_timer) | ||
|
|
||
|
|
||
| ## Safe method for external scripts to update the slider without triggering SFX or saves. | ||
| ## Use this instead of modifying `value` directly when restoring settings. | ||
| ## :param new_value: The target volume (0.0 to 1.0). | ||
| ## :type new_value: float | ||
| ## :rtype: void | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| func set_value_programmatically(new_value: float) -> void: | ||
| # Guard against external updates if the bus is invalid | ||
| if bus_index == -1: | ||
| return | ||
|
|
||
| # Godot 4 native method: updates visual value without emitting 'value_changed' | ||
| set_value_no_signal(new_value) | ||
|
|
||
| # Explicitly sync the audio backend, since the signal was bypassed | ||
| AudioServer.set_bus_volume_db(bus_index, linear_to_db(new_value)) | ||
| AudioManager.set_volume(bus_name, new_value) | ||
|
|
||
| # Sync the delta tracker so the next manual interaction calculates correctly | ||
| _previous_value = new_value | ||
|
sourcery-ai[bot] marked this conversation as resolved.
Outdated
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
| ## Tracks mouse drag state for accurate interaction gating, even if the cursor | ||
| ## leaves the slider's bounding box while dragging. | ||
| ## :param event: The input event passed by the UI system. | ||
| ## :type event: InputEvent | ||
| ## :rtype: void | ||
| func _on_gui_input(event: InputEvent) -> void: | ||
| if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: | ||
| _is_dragging = event.pressed | ||
|
sourcery-ai[bot] marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| ## Signal listener for when the slider value changes manually. | ||
| ## :param new_value: The new volume level from the slider (0.0 to 1.0). | ||
| ## :type new_value: float | ||
| ## :rtype: void | ||
| func _on_value_changed(new_value: float) -> void: | ||
| AudioServer.set_bus_volume_db(bus_index, linear_to_db(new_value)) | ||
| AudioManager.set_volume(bus_name, new_value) | ||
|
|
||
| # Attempt to play interaction feedback | ||
| _handle_slider_sfx(new_value) | ||
|
|
||
| # Godot automatically restarts an active timer when start() is called | ||
| save_debounce_timer.start() | ||
|
|
||
|
|
||
| ## Guards SFX playback against redundant values and rapid spam. | ||
| ## Ensures sound only plays during legitimate, rate-limited user interactions. | ||
| ## :param new_value: The updated slider value. | ||
| ## :type new_value: float | ||
| ## :rtype: void | ||
| func _handle_slider_sfx(new_value: float) -> void: | ||
| # Guard 1: Only play if the value actually changed (float-safe delta check) | ||
| if is_equal_approx(new_value, _previous_value): | ||
| return | ||
|
|
||
| # Commit the value tracker early so delta checks stay accurate even if rate-limited | ||
| _previous_value = new_value | ||
|
|
||
| # Guard 2: Only play if user is actively interacting (Mouse Drag or Keyboard Focus) | ||
| var is_mouse_active: bool = _is_dragging | ||
| var is_keyboard_active: bool = has_focus() | ||
|
|
||
| if not (is_mouse_active or is_keyboard_active): | ||
| return | ||
|
|
||
| # Guard 3: Rate limit to prevent audio spam during rapid drags | ||
| var current_time: int = Time.get_ticks_msec() | ||
| if current_time - _last_sfx_time < SFX_COOLDOWN_MS: | ||
| return | ||
|
|
||
| # Commit time state only after all guards pass | ||
| _last_sfx_time = current_time | ||
|
|
||
| # change bus value/volume | ||
| func _on_value_changed(value: float) -> void: | ||
| AudioServer.set_bus_volume_db(bus_index, linear_to_db(value)) | ||
| AudioManager.set_volume(bus_name, value) | ||
| # New: Start/restart debounce timer instead of immediate save | ||
| if save_debounce_timer.is_stopped(): | ||
| save_debounce_timer.start() | ||
| else: | ||
| save_debounce_timer.stop() | ||
| save_debounce_timer.start() | ||
| AudioManager.play_sfx(AudioConstants.SFX_SLIDER) | ||
|
|
||
|
|
||
| # New: Called on timer timeout—perform the batched save | ||
| ## Called on timer timeout—performs the batched disk save. | ||
| ## :rtype: void | ||
| func _on_debounce_timeout() -> void: | ||
| AudioManager.save_volumes() | ||
| Globals.log_message("Debounced settings save triggered.", Globals.LogLevel.DEBUG) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.