Random Audio Player
The Random Audio Player is a versatile audio playback system designed to play random audio files from a specified folder. It can be used for background music, sound effects, or any other audio that needs to be played at random intervals. The player is designed to be flexible and easy to use, making it suitable for various applications in the game. The player is unsuitable for applications where songs need to be played in a specific order or need to loop.
The Core
At the heart of everything is the RandomAudioPlayer
class. This handles loading audio files, picking them at random, and scheduling playback. It also emits a signal when it’s time to play a sound, allowing connection to actual AudioStream
(and variant) nodes:
## Base class for random audio playback functionalityclass_name RandomAudioPlayerextends Node
## Signal to notify the player to start playing the audiosignal play_audio(sound: AudioStream, sound_name: String)
var audio_folder: Stringvar file_extensions: Array[String]var min_interval: floatvar max_interval: floatvar avoid_repeats: boolvar start_playing: bool
var available_audio: Array[AudioStream] = []var current_index: int = -1var _timer: Timer
func _init(_audio_folder : String, _file_extentions : Array[String] = ["ogg", "wav", "mp3"], _min_interval : float = 5.0, _max_interval: float = 15.0, _start_playing : bool = true, _avoid_repeats : bool = true) -> void: audio_folder = _audio_folder file_extensions = _file_extentions min_interval = _min_interval max_interval = _max_interval avoid_repeats = _avoid_repeats start_playing = _start_playing
_load_audio_files()
func _ready() -> void: _setup_timer() if available_audio.size() > 0: # Start playing immediately if start_playing is true if start_playing: _play_random_audio() else: # Start the timer without playing immediately _timer.start(randf_range(min_interval, max_interval)) else: push_warning("No audio files found in: %s" % audio_folder)
func get_current_audio_name() -> String: if current_index >= 0 and current_index < available_audio.size(): return available_audio[current_index].resource_path.get_file().get_basename() return ""
func _load_audio_files() -> void: var dir := DirAccess.open(audio_folder) if dir: dir.list_dir_begin() var file_name: String = dir.get_next() while file_name != "": if not dir.current_is_dir() and file_name.get_extension() in file_extensions: var audio = load(audio_folder.path_join(file_name)) if audio is AudioStream: available_audio.append(audio) file_name = dir.get_next() else: push_error("Could not open audio directory: %s" % audio_folder)
func _setup_timer() -> void: _timer = Timer.new() add_child(_timer) _timer.timeout.connect(_play_random_audio)
func _play_random_audio() -> void: if available_audio.size() == 0: return
print("Playing random audio")
# Select next audio ensuring no immediate repeats if avoid_repeats is true var next_index := current_index if available_audio.size() > 1 and avoid_repeats: while next_index == current_index: next_index = randi() % available_audio.size()
current_index = next_index var audio := available_audio[current_index]
# Custom playback implementation in child classes play_audio.emit(audio, get_current_audio_name())
# Schedule next playback _schedule_next_playback(audio)
func _schedule_next_playback(audio : AudioStream) -> void: var interval := randf_range(min_interval, max_interval) _timer.start(interval + audio.get_length()) # Wait until current audio ends + random interval
How It Works
What’s great about this system is its simplicity. Point it to a folder with audio files, and it handles the rest. Here’s what’s happening under the hood:
- It scans a folder for audio files (ogg, wav, mp3).
- It loads them all into memory (so keep folder sizes reasonable).
- It picks files at random.
- It avoids repetition if specified. This is done by keeping track of the last played file and ensuring the next one is different.
- This means that if there are only two files in the folder, it will play them back to back.
- It avoids repetition if specified. This is done by keeping track of the last played file and ensuring the next one is different.
- Between the sounds, it waits for a random interval (between
min_interval
andmax_interval
seconds) before playing the next one.- This is done by using a timer that adds the current sound’s length to the randomly chosen interval. Effectively, this means that the next sound will be played after a random interval, plus the length of the current sound.
- If the current sound is 5 seconds long, and the random interval is 10 seconds, the next sound will be played after 15 seconds.
- This is done by using a timer that adds the current sound’s length to the randomly chosen interval. Effectively, this means that the next sound will be played after a random interval, plus the length of the current sound.
The base class doesn’t actually play audio itself. It emits a signal with the required information, which can be connected to any AudioStream
or AudioStreamPlayer
node. This uses composition instead of inheritance, since the different audio players have different parents.
Random Music Player
This plays audio files from a specified folder at random intervals, using AudioStreamPlayer
. This means the audio is coming from a fixed position, not a 2D or 3D space. This is perfect for music playback or sound effects that don’t need to be spatialized:
## Plays a random song from a specified folder at random intervals.extends AudioStreamPlayer
@export var music_folder: String = "res://ui/game_menu/art/random_music/" ## Folder containing music files@export var file_extensions: Array[String] = ["ogg", "wav", "mp3"] ## Supported audio formats@export var min_interval: float = 30.0 ## Minimum time between songs in seconds@export var max_interval: float = 60.0 ## Maximum time between songs in seconds@export var avoid_repeats: bool = true ## Avoid playing the same song consecutively
func _ready() -> void: var _random_player := RandomAudioPlayer.new(music_folder, file_extensions, min_interval, max_interval, false, avoid_repeats) _random_player.play_audio.connect(_on_play_sound) add_child(_random_player)
func _on_play_sound(sound: AudioStream, sound_name: String) -> void: print("Playing music: ", sound_name) stream = sound play()
This is perfect for menu screens or ambient background music. Just tweak the intervals to your liking - longer breaks work well for background music, shorter ones for more continuous play.
Random Sound Player
This plays audio files from a specified folder at random intervals, using AudioStreamPlayer3D
. This means the audio is coming from a 3D position in space. This is perfect for sound effects that need to be spatialized, but where the exact position doesn’t matter, like ambient sounds:
## Plays a random sound effect from a specified folder at random intervals.extends AudioStreamPlayer3D@export var sounds_folder: String = "res://ui/game_menu/art/random_sounds/" ## Folder containing sound effect files@export var min_random_distance: float = 2.0 ## Minimum distance from the player in 3D space@export var max_random_distance: float = 10.0 ## Maximum distance from the player in 3D space@export var file_extensions: Array[String] = ["ogg", "wav", "mp3"] ## Supported audio formats@export var min_interval: float = 5.0 ## Minimum time between sound effects in seconds@export var max_interval: float = 15.0 ## Maximum time between sound effects in seconds@export var avoid_repeats: bool = true ## Avoid playing the same sound consecutively
func _ready() -> void: var _random_player := RandomAudioPlayer.new(sounds_folder, file_extensions, min_interval, max_interval, true, avoid_repeats) _random_player.play_audio.connect(_on_play_sound) add_child(_random_player)
func _on_play_sound(sound: AudioStream, sound_name: String) -> void: print("Playing sound: ", sound_name) stream = sound # Set random position in 3D space var random_angle := randf_range(0, 2 * PI) var random_distance := randf_range(min_random_distance, max_random_distance) position = Vector3( cos(random_angle) * random_distance, cos(random_angle) * random_distance, sin(random_angle) * random_distance ) play()
Tips for Best Results
- Group similar sounds together - Organize sounds by type or purpose to make them easier to manage and ensure consistent playback.
- Keep file counts reasonable - 5-10 variations are usually enough for most purposes. Adding too many variations worsens performance and can make it harder to find specific sounds later on.
- Match volumes carefully - Big volume differences between files can break immersion.
- Use descriptive filenames - This helps with debugging and makes it easier to find specific sounds later on.