Skip to content

Move WAV loading functionality to a wav_loader module with dr_wav (adds AIFF support) #96545

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

DeeJayLSP
Copy link
Contributor

@DeeJayLSP DeeJayLSP commented Sep 4, 2024

Changes

Moves the entire AudioStreamWAV load functionality into a module, which uses dr_wav to read and decode data and will be required to use the respective AudioStreamWAV::load_from_* functions (not required for playing AudioStreamWAV audio or creating streams through AudioEffectRecord).

Formats already supported by Godot and loop info detection should remain supported without regressions.

Extra WAV encoding formats like A-law, μ-law, MS ADPCM and IMA ADPCM can now be imported.

Note: All of them will be decoded on load, so any loss will be retained and passed to the compress modes.

Note 2: Even if Godot is able to compress and play audio as IMA ADPCM, the way it's compressed within Godot doesn't match the usual compression done in WAV files, so those couldn't be imported without decoding anyway, unless a compatibility breaking change is done to modify how Godot encodes and plays it.

Indirectly fixes #90957.

AIFF support

dr_wav is also able to read AIFF files, so ResourceImporterWAV was enabled to recognize it too.

load_from_buffer and load_from_file should recognize AIFF data/files as well.

The importer was renamed from Microsoft WAV to Microsoft WAV/Apple AIFF to reflect that.

When it comes to sound effects, freesound.org provides much more AIFF than Ogg Vorbis files, so I thought adding support was reasonable:
image

wave, aif, aiff and aifc file extensions have been added as recognized extensions alongside wav in the importer.

Actually load from file instead of a file buffer

#93831 changed the load function to copy the entire file data into memory from using FileAccess to get values.

You can tell dr_wav to use specific read/seek callbacks, so I wrote callbacks to make load_from_file read using FileAccess instead of a memory copy.

Caveats

I noticed a 36 KiB size penalty on production template_release builds (71991600 -> 72028464). This PR was opened when the load procedure was editor-only, and during its development was moved into AudioStreamWAV to serve as a load function. I don't know if 36 KiB is too much for this, but as a side effect reduces the probability of errors.

And once again, it is a module that can be disabled.

@@ -35,6 +35,15 @@
#include "core/io/resource_saver.h"
#include "scene/resources/audio_stream_wav.h"

#define DRWAV_IMPLEMENTATION
#define DR_WAV_NO_STDIO
#define DR_WAV_LIBSNDFILE_COMPAT
Copy link
Contributor Author

@DeeJayLSP DeeJayLSP Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite not using libsndfile, the way Godot converts streams from/to f32 follows similar calculations, therefore not defining this leads to some test errors (save/reimport datas being different).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Horray

@DeeJayLSP
Copy link
Contributor Author

Added a check to detect if the container is RF64 or W64.

Even if they could be imported in theory, this is the best non-compatibility breaking way to deny getting a stream that is too big for AudioStreamPlaybackWAV. And it's not like anyone would export to those formats with a .wav extension.

@DeeJayLSP
Copy link
Contributor Author

DeeJayLSP commented Sep 6, 2024

Modified the check again, this time it actually checks the amount of samples (maximum of 2147483647).

Normally, the importer uses an int to store the total amount of samples, therefore using a limit shouldn't actually break compatibility (in fact, probably fixes a potential overflow on import).

@fire
Copy link
Member

fire commented Sep 6, 2024

Does the dr mp4 problem affect dr_wav? Not sure of the exact problem.

@DeeJayLSP
Copy link
Contributor Author

Does the dr mp4 problem affect dr_wav? Not sure of the exact problem.

The problem with dr_mp3 is that it doesn't read a VBR header that informs an amount of samples to skip at the start.

Doesn't apply to WAV.

@DeeJayLSP DeeJayLSP force-pushed the drwav branch 3 times, most recently from 0925491 to ce27e00 Compare September 9, 2024 03:53
@DeeJayLSP
Copy link
Contributor Author

Added .aifc extension to the list as it's supported too.

else if (loop.type == drwav_smpl_loop_type_backward)
loop_mode = AudioStreamWAV::LOOP_BACKWARD;
loop_begin = loop.firstSampleByteOffset;
loop_end = loop.lastSampleByteOffset;
Copy link
Contributor Author

@DeeJayLSP DeeJayLSP Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Byte" in the names here is misleading, it's actually the offsets of loop begin/end in frames, not bytes.

I have tested this considering the old read code actually copied a sample value directly, not a byte offset.

Works the same way as the previous implementation just fine.

@DeeJayLSP DeeJayLSP force-pushed the drwav branch 3 times, most recently from ffbc68e to 723e117 Compare September 11, 2024 04:02
@DeeJayLSP DeeJayLSP requested a review from a team as a code owner September 11, 2024 04:02
@DeeJayLSP
Copy link
Contributor Author

Updated ResourceImporterWAV documentation to point out it also supports AIFF.

@DeeJayLSP
Copy link
Contributor Author

DeeJayLSP commented Nov 14, 2024

Rebased.

I took the opportunity to redo how the data is read. dr_wav offers 3 ways: From a file through stdio, from memory and from custom read/seek functions.

Before the last push, it was loading the entire file data into the memory, then reading from memory, which resulted into a massive increase in memory usage during import if the file was too large.

Now it uses custom functions (lambdas) that tell FileAccess to react accordingly, effectively being an integration between FileAccess and dr_wav.

I don't know if I violated any Godot style guideline, so I'd like to ask for another review.

The patch in dr_wav could be applied directly into a define, so it was removed.

@migueldeicaza
Copy link
Contributor

AIFF is also the default format for Garage Band, so it comes in very handy to have this support.

Copy link
Member

@adamscott adamscott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like the idea of having a function load_from_dr_wav. It exposes needlessly the thirdparty used.

@DeeJayLSP DeeJayLSP force-pushed the drwav branch 2 times, most recently from 0900079 to 8ffb6ea Compare April 8, 2025 01:27
@DeeJayLSP
Copy link
Contributor Author

DeeJayLSP commented Apr 8, 2025

I realized removing a check from the file seek callback would speed up imports without making anything fail. I thought it was some kind of overhead from dr_wav.

@DeeJayLSP DeeJayLSP force-pushed the drwav branch 3 times, most recently from f1355f8 to fd16410 Compare April 8, 2025 19:08
@DeeJayLSP
Copy link
Contributor Author

DeeJayLSP commented Apr 12, 2025

Something that just came to mind: how about making this a module similar to vhacd?

All the vhacd module does is enabling Mesh::convex_decompose() to work. How about reworking this into something that makes AudioStreamWAV::load_from_buffer() and AudioStreamWAV::load_from_file() work?

The size penalty would still exist in distributed editor and template builds, but at least there would be an easy option to make a custom build without it.

@DeeJayLSP DeeJayLSP changed the title Add support for AIFF files and other WAV formats Add dr_wav module (AIFF support) Apr 12, 2025
@DeeJayLSP
Copy link
Contributor Author

DeeJayLSP commented Apr 12, 2025

Rework done.

The entire AudioStreamWAV::load_from_* functions have been reworked into a dr_wavwav_loader module that can be left out of engine builds.

Much like the vhacd module is required to use Mesh::convex_decompose(), the dr_wavwav_loader module will be required to use AudioStreamWAV::load_from_buffer() and AudioStreamWAV::load_from_file().

Since there is a small but considerable demand for AIFF support, but in order to do so in an easy way would result into a big size penalty, I thought a module would be the best approach.

@DeeJayLSP DeeJayLSP force-pushed the drwav branch 2 times, most recently from f8cddbe to bb2ce00 Compare April 12, 2025 13:08
@DeeJayLSP DeeJayLSP requested a review from a team as a code owner April 12, 2025 13:08
@DeeJayLSP DeeJayLSP requested a review from adamscott April 22, 2025 20:22
@DeeJayLSP DeeJayLSP changed the title Add dr_wav module (AIFF support) Move WAV loading functionality to a dr_wav module (adds AIFF support) May 24, 2025
@DeeJayLSP
Copy link
Contributor Author

Rebased and renamed to a more specific title.

@DeeJayLSP DeeJayLSP force-pushed the drwav branch 2 times, most recently from f54e0c8 to e265d2a Compare May 28, 2025 22:33
@DeeJayLSP DeeJayLSP changed the title Move WAV loading functionality to a dr_wav module (adds AIFF support) Move WAV loading functionality to a wav_loader module with dr_wav (adds AIFF support) May 28, 2025
@DeeJayLSP
Copy link
Contributor Author

DeeJayLSP commented May 28, 2025

Renamed the module to wav_loader to better reflect its purpose and avoid ambiguity, as it could cause people to think it is necessary for WAV playback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unsafe cast when loading WAV files
7 participants