Audio Manager to handle the loading of sound effects in bulk
Years ago I purchased a game dev bundle on HumbleBundle. Part of that was a sound library called Pro Sound Collection. It’s pretty comprehensive, whether RPG or FPS, there are sounds for a ton of use cases. I might as well use them for something.
Luckily for me, the sound collection is pretty well organized. It is using a fairly decent directory structure and naming convention (e.g.
Guns_Weapons/Guns/gun_pistol_shot_01.wav
). Some sounds have up to 10+ variations, and the directory structure is arbitrarily deep. The whole thing is about 2GB of uncompressed audio.
When I wanted to wire them all up to Godot 4’s
AudioStreamRandomizer
I realized that each sound needed to be added one by one. I could not just drag and drop them onto the component. That would be a nice UX improvement and would help cut down on tedious work. Since what I’m doing is more exploratory in nature, I just want to be able to load sound effects quickly, not spend half a day clicking around in a UI to associate things. To help with that, I wrote a little Audio Manager script.
The goal is to have a script read an entire directory of audio files: load all the streams into memory, and apply some kind of taxonomy to handle them intelligently. For example, all the sounds with sequential numerical suffixes will be loaded into an AudioStreamRandomizer as variations.
Aside from being convenient this also makes switching sounds dynamically easier; At startup, the audio files are loaded as streams and kept track of in a Dictionary. If a sound is requested at runtime, the stream is immediately returned.
# AudioManager Singletonextends Node## Tracks all audio files and returns the stream set for a given name## ## The goal is to load sets of sound to have variations#### The files are named, e.g.: "res://audio/Footsteps/footstep_<set_name>_??.wav"var streams: Dictionary= {}@onreadyvar regex: RegEx=RegEx.new()## Retrieve an AudioStream##funcget_stream_set(steam_name: String):if streams.has(steam_name):return streams.get(steam_name)func_ready():_compile_regex()_load_audio(_dir_contents("res://audio/"))# # or load them individually# var file_list = _dir_contents("res://audio/Footsteps/")# file_list.append_array(_dir_contents("res://audio/Animals_Nature_Ambiences/"))## # recusively loads sub-directories# file_list.append_array(_dir_contents("res://audio/Guns_Weapons/"))## #file_list.append_array(_dir_contents("res://audio/Guns_Weapons/Bullets/"))# #file_list.append_array(_dir_contents("res://audio/Guns_Weapons/Guns/"))# #file_list.append_array(_dir_contents("res://audio/Guns_Weapons/Guns_Silenced/"))# #file_list.append_array(_dir_contents("res://audio/Guns_Weapons/Knife_Sword_Pick/"))## file_list.append_array(_dir_contents("res://audio/Sci-Fi/"))# file_list.append_array(_dir_contents("res://audio/Voice/Human Male A/"))# file_list.append_array(_dir_contents("res://audio/Whooshes/"))# _load_audio(file_list)func_dir_contents(path: String) -> Array[String]:var file_list : Array[String] = []var dir =DirAccess.open(path)if dir: dir.list_dir_begin()var file_name = dir.get_next()while file_name !="":if dir.current_is_dir():# recursively dive in file_list.append_array(_dir_contents("%s%s/" % [path, file_name]))else:if file_name.ends_with(".wav"): file_list.push_back(path + file_name) file_name = dir.get_next()else:print_debug("An error occurred when trying to access path: %s." % [path])return file_listfunc_load_audio(files: Array):for f in files:var asr: AudioStreamRandomizer=_get_stream_set_for_filename(f)var stream = AudioStreamWAV.new()var file =FileAccess.open(f, FileAccess.READ) stream.data = file.get_buffer(file.get_length())# file.close() stream.format =AudioStreamWAV.FORMAT_16_BITS stream.mix_rate =48000 stream.stereo =true asr.add_stream(-1, stream)if asr.streams_count >3: asr.playback_mode =AudioStreamRandomizer.PLAYBACK_RANDOM_NO_REPEATSpass## Regular Expression that extracts elements from the filename## E.g. Guns_Weapons/Guns/gun_pistol_shot_01.wav## becomes:## - category: gun## - name: pistol_shot## - digit: 01func_compile_regex():var result = regex.compile("(?<category>[a-z]+)_(?<name>[0-9a-z_]+)_(?<digit>[0-9a-f]+)\\.wav")if result !=OK: print_debug("AudioManager cannot compile regex.")func_get_stream_set_for_filename(filename: String) -> AudioStream:# for example: "res://audio/Footsteps/footstep_metal_high_run_02.wav"var result = regex.search(filename)var stream_name ="_junk"if result: stream_name = result.get_string("name")else:# print_debug("Filename %s doesn't match the pattern." % filename)passif !streams.has(stream_name):var rand: AudioStreamRandomizer=AudioStreamRandomizer.new()# rand.playback_mode = AudioStreamRandomizer.PLAYBACK_SEQUENTIAL rand.playback_mode =AudioStreamRandomizer.PLAYBACK_RANDOM rand.random_pitch =1 streams[stream_name] = randreturn streams.get(stream_name)
That said, this isn’t production ready. Loading audio files at runtime doesn’t work on arbitrary audio files, because there’s currently no easily accessible way to properly load the audio samples and this is just grabbing the raw file buffer. GDscript would have to parse the wave file header, otherwise, some sounds may start or end with some clicking. At this stage, the script is prototype quality.
This inventory system release refines a lot of architecture guide sections based on customer feedback and adds over 100 additional pages to the PDF guide. Features: Bug fixes:
Here’s a tutorial on how to create breakable objects in Blender and Godot. It covers the steps needed to design and implement breakable objects, including scripting and using physics properties to make objects break apart into smaller pieces upon collision or other interactions.
Audio Manager to handle the loading of sound effects in bulk
Years ago I purchased a game dev bundle on HumbleBundle. Part of that was a sound library called Pro Sound Collection. It’s pretty comprehensive, whether RPG or FPS, there are sounds for a ton of use cases. I might as well use them for something.
Luckily for me, the sound collection is pretty well organized. It is using a fairly decent directory structure and naming convention (e.g.
Guns_Weapons/Guns/gun_pistol_shot_01.wav
). Some sounds have up to 10+ variations, and the directory structure is arbitrarily deep. The whole thing is about 2GB of uncompressed audio.When I wanted to wire them all up to Godot 4’s
AudioStreamRandomizer
I realized that each sound needed to be added one by one. I could not just drag and drop them onto the component. That would be a nice UX improvement and would help cut down on tedious work. Since what I’m doing is more exploratory in nature, I just want to be able to load sound effects quickly, not spend half a day clicking around in a UI to associate things. To help with that, I wrote a little Audio Manager script.The goal is to have a script read an entire directory of audio files: load all the streams into memory, and apply some kind of taxonomy to handle them intelligently. For example, all the sounds with sequential numerical suffixes will be loaded into an AudioStreamRandomizer as variations.
Aside from being convenient this also makes switching sounds dynamically easier; At startup, the audio files are loaded as streams and kept track of in a Dictionary. If a sound is requested at runtime, the stream is immediately returned.
This is using the
DirAccess
andFileAccess
classes that Godot 4 beta 2 just added.That said, this isn’t production ready. Loading audio files at runtime doesn’t work on arbitrary audio files, because there’s currently no easily accessible way to properly load the audio samples and this is just grabbing the raw file buffer. GDscript would have to parse the wave file header, otherwise, some sounds may start or end with some clicking. At this stage, the script is prototype quality.
Related Posts
Inventory System v1.15 available
This inventory system release refines a lot of architecture guide sections based on customer feedback and adds over 100 additional pages to the PDF guide. Features: Bug fixes:
Fire, Rain, and Black Hole particle effects
This video is from December last year and uses Godot 4.0-beta7, but I thought it was interesting, because… particle effects!
Making breakable objects in Godot
Here’s a tutorial on how to create breakable objects in Blender and Godot. It covers the steps needed to design and implement breakable objects, including scripting and using physics properties to make objects break apart into smaller pieces upon collision or other interactions.
Inventory System v1.2 available
A few new features: Bug fixes: