Skip to content

Commit 1fc3e2f

Browse files
DeeJayLSPSpartan322
authored andcommitted
Add support for AIFF files and other WAV formats
1 parent 178fda8 commit 1fc3e2f

File tree

8 files changed

+8928
-214
lines changed

8 files changed

+8928
-214
lines changed

COPYRIGHT.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ Comment: doctest
231231
Copyright: 2016-2023, Viktor Kirilov
232232
License: Expat
233233

234+
Files: ./thirdparty/dr_libs/
235+
Comment: dr_libs
236+
Copyright: 2024 David Reid
237+
License: public-domain or Unlicense or Expat
238+
234239
Files: ./thirdparty/embree/
235240
Comment: Embree
236241
Copyright: 2009-2021 Intel Corporation

doc/classes/AudioStreamWAV.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<class name="AudioStreamWAV" inherits="AudioStream" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
33
<brief_description>
4-
Stores audio data loaded from WAV files.
4+
Stores audio data loaded from WAV or AIFF files.
55
</brief_description>
66
<description>
7-
AudioStreamWAV stores sound samples loaded from WAV files. To play the stored sound, use an [AudioStreamPlayer] (for non-positional audio) or [AudioStreamPlayer2D]/[AudioStreamPlayer3D] (for positional audio). The sound can be looped.
7+
AudioStreamWAV stores sound samples loaded from WAV or AIFF files. To play the stored sound, use an [AudioStreamPlayer] (for non-positional audio) or [AudioStreamPlayer2D]/[AudioStreamPlayer3D] (for positional audio). The sound can be looped.
88
This class can also be used to store dynamically-generated PCM audio data. See also [AudioStreamGenerator] for procedural audio generation.
99
</description>
1010
<tutorials>

doc/classes/ResourceImporterWAV.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<class name="ResourceImporterWAV" inherits="ResourceImporter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
33
<brief_description>
4-
Imports a WAV audio file for playback.
4+
Imports data from a WAV/AIFF audio file for playback.
55
</brief_description>
66
<description>
7-
WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end devices.
8-
By default, Redot imports WAV files using the lossy Quite OK Audio compression. You may change this by setting the [member compress/mode] property.
7+
WAV/AIFF is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means a high number of sounds can be played at the same time, even on low-end devices.
8+
By default, Redot imports WAV/AIFF files using the lossy Quite OK Audio compression. You may change this by setting the [member compress/mode] property.
99
</description>
1010
<tutorials>
1111
<link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link>
@@ -25,7 +25,7 @@
2525
</member>
2626
<member name="edit/loop_mode" type="int" setter="" getter="" default="0">
2727
Controls how audio should loop.
28-
- [b]Detect From WAV:[/b] Uses loop information from the WAV metadata.
28+
- [b]Detect From WAV:[/b] Uses loop information from the WAV metadata (AIFF does not support this).
2929
- [b]Disabled:[/b] Don't loop audio, even if the metadata indicates the file playback should loop.
3030
- [b]Forward:[/b] Standard audio looping. Plays the audio forward from the beginning to [member edit/loop_end], then returns to [member edit/loop_begin] and repeats.
3131
- [b]Ping-Pong:[/b] Plays the audio forward until [member edit/loop_end], then backwards to [member edit/loop_begin], repeating this cycle.

editor/import/resource_importer_wav.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ String ResourceImporterWAV::get_visible_name() const {
4444

4545
void ResourceImporterWAV::get_recognized_extensions(List<String> *p_extensions) const {
4646
p_extensions->push_back("wav");
47+
p_extensions->push_back("wave");
48+
p_extensions->push_back("aif");
49+
p_extensions->push_back("aiff");
50+
p_extensions->push_back("aifc");
4751
}
4852

4953
String ResourceImporterWAV::get_save_extension() const {

scene/resources/audio_stream_wav.cpp

Lines changed: 39 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@
3535
#include "core/io/file_access_memory.h"
3636
#include "core/io/marshalls.h"
3737

38+
#define DRWAV_IMPLEMENTATION
39+
#define DRWAV_NO_STDIO
40+
#define DR_WAV_LIBSNDFILE_COMPAT
41+
#define DRWAV_MALLOC(sz) memalloc(sz)
42+
#define DRWAV_REALLOC(p, sz) memrealloc(p, sz)
43+
#define DRWAV_FREE(p) \
44+
if (p) \
45+
memfree(p)
46+
47+
#include "thirdparty/dr_libs/dr_wav.h"
48+
3849
const float TRIM_DB_LIMIT = -50;
3950
const int TRIM_FADE_OUT_FRAMES = 500;
4051

@@ -599,7 +610,7 @@ Vector<uint8_t> AudioStreamWAV::get_data() const {
599610

600611
Error AudioStreamWAV::save_to_wav(const String &p_path) {
601612
if (format == AudioStreamWAV::FORMAT_IMA_ADPCM || format == AudioStreamWAV::FORMAT_QOA) {
602-
WARN_PRINT("Saving IMA_ADPCM and QOA samples is not supported yet");
613+
WARN_PRINT("Saving IMA ADPCM and QOA samples is not supported.");
603614
return ERR_UNAVAILABLE;
604615
}
605616

@@ -620,11 +631,9 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
620631
byte_pr_sample = 1;
621632
break;
622633
case AudioStreamWAV::FORMAT_16_BITS:
623-
case AudioStreamWAV::FORMAT_QOA:
624634
byte_pr_sample = 2;
625635
break;
626-
case AudioStreamWAV::FORMAT_IMA_ADPCM:
627-
byte_pr_sample = 4;
636+
default:
628637
break;
629638
}
630639

@@ -653,8 +662,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
653662
file->store_32(sub_chunk_2_size); //Subchunk2Size
654663

655664
// Add data
656-
Vector<uint8_t> stream_data = get_data();
657-
const uint8_t *read_data = stream_data.ptr();
665+
const uint8_t *read_data = data.ptr() + DATA_PAD;
658666
switch (format) {
659667
case AudioStreamWAV::FORMAT_8_BITS:
660668
for (unsigned int i = 0; i < data_bytes; i++) {
@@ -663,14 +671,12 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
663671
}
664672
break;
665673
case AudioStreamWAV::FORMAT_16_BITS:
666-
case AudioStreamWAV::FORMAT_QOA:
667674
for (unsigned int i = 0; i < data_bytes / 2; i++) {
668675
uint16_t data_point = decode_uint16(&read_data[i * 2]);
669676
file->store_16(data_point);
670677
}
671678
break;
672-
case AudioStreamWAV::FORMAT_IMA_ADPCM:
673-
//Unimplemented
679+
default:
674680
break;
675681
}
676682

@@ -726,220 +732,45 @@ Ref<AudioSample> AudioStreamWAV::generate_sample() const {
726732
}
727733

728734
Ref<AudioStreamWAV> AudioStreamWAV::load_from_buffer(const Vector<uint8_t> &p_stream_data, const Dictionary &p_options) {
729-
// /* STEP 1, READ WAVE FILE */
730-
731-
Ref<FileAccessMemory> file;
732-
file.instantiate();
733-
Error err = file->open_custom(p_stream_data.ptr(), p_stream_data.size());
734-
ERR_FAIL_COND_V_MSG(err != OK, Ref<AudioStreamWAV>(), "Cannot create memfile for WAV file buffer.");
735+
// STEP 1, READ FILE
735736

736-
/* CHECK RIFF */
737-
char riff[5];
738-
riff[4] = 0;
739-
file->get_buffer((uint8_t *)&riff, 4); //RIFF
740-
741-
if (riff[0] != 'R' || riff[1] != 'I' || riff[2] != 'F' || riff[3] != 'F') {
742-
ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), vformat("Not a WAV file. File should start with 'RIFF', but found '%s', in file of size %d bytes", riff, file->get_length()));
737+
drwav wav;
738+
if (!drwav_init_memory_with_metadata(&wav, p_stream_data.ptr(), p_stream_data.size(), DRWAV_WITH_METADATA, nullptr)) {
739+
ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), "Audio data is invalid, corrupted or an unsupported format.");
743740
}
744741

745-
/* GET FILESIZE */
746-
747-
// The file size in header is 8 bytes less than the actual size.
748-
// See https://docs.fileformat.com/audio/wav/
749-
const int FILE_SIZE_HEADER_OFFSET = 8;
750-
uint32_t file_size_header = file->get_32() + FILE_SIZE_HEADER_OFFSET;
751-
uint64_t file_size = file->get_length();
752-
if (file_size != file_size_header) {
753-
WARN_PRINT(vformat("File size %d is %s than the expected size %d.", file_size, file_size > file_size_header ? "larger" : "smaller", file_size_header));
742+
if (wav.totalPCMFrameCount > INT32_MAX) {
743+
ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), "Audio data exceeds maximum supported size of 2,147,483,647 frames.");
754744
}
755745

756-
/* CHECK WAVE */
746+
int format_bits = wav.bitsPerSample;
747+
int format_channels = wav.channels;
748+
int format_freq = wav.sampleRate;
749+
int frames = wav.totalPCMFrameCount;
757750

758-
char wave[5];
759-
wave[4] = 0;
760-
file->get_buffer((uint8_t *)&wave, 4); //WAVE
761-
762-
if (wave[0] != 'W' || wave[1] != 'A' || wave[2] != 'V' || wave[3] != 'E') {
763-
ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), vformat("Not a WAV file. Header should contain 'WAVE', but found '%s', in file of size %d bytes", wave, file->get_length()));
764-
}
765-
766-
// Let users override potential loop points from the WAV.
767-
// We parse the WAV loop points only with "Detect From WAV" (0).
768751
int import_loop_mode = p_options["edit/loop_mode"];
769752

770-
int format_bits = 0;
771-
int format_channels = 0;
772-
773-
AudioStreamWAV::LoopMode loop_mode = AudioStreamWAV::LOOP_DISABLED;
774-
uint16_t compression_code = 1;
775-
bool format_found = false;
776-
bool data_found = false;
777-
int format_freq = 0;
778753
int loop_begin = 0;
779754
int loop_end = 0;
780-
int frames = 0;
781-
782-
Vector<float> data;
783-
784-
while (!file->eof_reached()) {
785-
/* chunk */
786-
char chunk_id[4];
787-
file->get_buffer((uint8_t *)&chunk_id, 4); //RIFF
788-
789-
/* chunk size */
790-
uint32_t chunksize = file->get_32();
791-
uint32_t file_pos = file->get_position(); //save file pos, so we can skip to next chunk safely
792-
793-
if (file->eof_reached()) {
794-
//ERR_PRINT("EOF REACH");
795-
break;
796-
}
797-
798-
if (chunk_id[0] == 'f' && chunk_id[1] == 'm' && chunk_id[2] == 't' && chunk_id[3] == ' ' && !format_found) {
799-
/* IS FORMAT CHUNK */
800-
801-
//Issue: #7755 : Not a bug - usage of other formats (format codes) are unsupported in current importer version.
802-
//Consider revision for engine version 3.0
803-
compression_code = file->get_16();
804-
if (compression_code != 1 && compression_code != 3) {
805-
ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), "Format not supported for WAVE file (not PCM). Save WAVE files as uncompressed PCM or IEEE float instead.");
806-
}
807-
808-
format_channels = file->get_16();
809-
if (format_channels != 1 && format_channels != 2) {
810-
ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), "Format not supported for WAVE file (not stereo or mono).");
811-
}
812-
813-
format_freq = file->get_32(); //sampling rate
814-
815-
file->get_32(); // average bits/second (unused)
816-
file->get_16(); // block align (unused)
817-
format_bits = file->get_16(); // bits per sample
818-
819-
if (format_bits % 8 || format_bits == 0) {
820-
ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), "Invalid amount of bits in the sample (should be one of 8, 16, 24 or 32).");
821-
}
822-
823-
if (compression_code == 3 && format_bits % 32) {
824-
ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), "Invalid amount of bits in the IEEE float sample (should be 32 or 64).");
825-
}
826-
827-
/* Don't need anything else, continue */
828-
format_found = true;
829-
}
830-
831-
if (chunk_id[0] == 'd' && chunk_id[1] == 'a' && chunk_id[2] == 't' && chunk_id[3] == 'a' && !data_found) {
832-
/* IS DATA CHUNK */
833-
data_found = true;
834-
835-
if (!format_found) {
836-
ERR_PRINT("'data' chunk before 'format' chunk found.");
755+
AudioStreamWAV::LoopMode loop_mode = AudioStreamWAV::LOOP_DISABLED;
756+
if (import_loop_mode == 0) {
757+
for (uint32_t meta = 0; meta < wav.metadataCount; ++meta) {
758+
drwav_metadata md = wav.pMetadata[meta];
759+
if (md.type == drwav_metadata_type_smpl && md.data.smpl.sampleLoopCount > 0) {
760+
drwav_smpl_loop loop = md.data.smpl.pLoops[0];
761+
loop_mode = (AudioStreamWAV::LoopMode)(loop.type + 1);
762+
loop_begin = loop.firstSampleByteOffset;
763+
loop_end = loop.lastSampleByteOffset;
837764
break;
838765
}
839-
840-
uint64_t remaining_bytes = file_size - file_pos;
841-
frames = chunksize;
842-
if (remaining_bytes < chunksize) {
843-
WARN_PRINT("Data chunk size is smaller than expected. Proceeding with actual data size.");
844-
frames = remaining_bytes;
845-
}
846-
847-
ERR_FAIL_COND_V(format_channels == 0, Ref<AudioStreamWAV>());
848-
frames /= format_channels;
849-
frames /= (format_bits >> 3);
850-
851-
/*print_line("chunksize: "+itos(chunksize));
852-
print_line("channels: "+itos(format_channels));
853-
print_line("bits: "+itos(format_bits));
854-
*/
855-
856-
data.resize(frames * format_channels);
857-
858-
if (compression_code == 1) {
859-
if (format_bits == 8) {
860-
for (int i = 0; i < frames * format_channels; i++) {
861-
// 8 bit samples are UNSIGNED
862-
863-
data.write[i] = int8_t(file->get_8() - 128) / 128.f;
864-
}
865-
} else if (format_bits == 16) {
866-
for (int i = 0; i < frames * format_channels; i++) {
867-
//16 bit SIGNED
868-
869-
data.write[i] = int16_t(file->get_16()) / 32768.f;
870-
}
871-
} else {
872-
for (int i = 0; i < frames * format_channels; i++) {
873-
//16+ bits samples are SIGNED
874-
// if sample is > 16 bits, just read extra bytes
875-
876-
uint32_t s = 0;
877-
for (int b = 0; b < (format_bits >> 3); b++) {
878-
s |= ((uint32_t)file->get_8()) << (b * 8);
879-
}
880-
s <<= (32 - format_bits);
881-
882-
data.write[i] = (int32_t(s) >> 16) / 32768.f;
883-
}
884-
}
885-
} else if (compression_code == 3) {
886-
if (format_bits == 32) {
887-
for (int i = 0; i < frames * format_channels; i++) {
888-
//32 bit IEEE Float
889-
890-
data.write[i] = file->get_float();
891-
}
892-
} else if (format_bits == 64) {
893-
for (int i = 0; i < frames * format_channels; i++) {
894-
//64 bit IEEE Float
895-
896-
data.write[i] = file->get_double();
897-
}
898-
}
899-
}
900-
901-
// This is commented out due to some weird edge case seemingly in FileAccessMemory, doesn't seem to have any side effects though.
902-
// if (file->eof_reached()) {
903-
// ERR_FAIL_V_MSG(Ref<AudioStreamWAV>(), "Premature end of file.");
904-
// }
905766
}
767+
}
906768

907-
if (import_loop_mode == 0 && chunk_id[0] == 's' && chunk_id[1] == 'm' && chunk_id[2] == 'p' && chunk_id[3] == 'l') {
908-
// Loop point info!
909-
910-
/**
911-
* Consider exploring next document:
912-
* http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/RIFFNEW.pdf
913-
* Especially on page:
914-
* 16 - 17
915-
* Timestamp:
916-
* 22:38 06.07.2017 GMT
917-
**/
918-
919-
for (int i = 0; i < 10; i++) {
920-
file->get_32(); // i wish to know why should i do this... no doc!
921-
}
769+
Vector<float> data;
770+
data.resize(frames * format_channels);
771+
drwav_read_pcm_frames_f32(&wav, frames, data.ptrw());
922772

923-
// only read 0x00 (loop forward), 0x01 (loop ping-pong) and 0x02 (loop backward)
924-
// Skip anything else because it's not supported, reserved for future uses or sampler specific
925-
// from https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl (loop type values table)
926-
int loop_type = file->get_32();
927-
if (loop_type == 0x00 || loop_type == 0x01 || loop_type == 0x02) {
928-
if (loop_type == 0x00) {
929-
loop_mode = AudioStreamWAV::LOOP_FORWARD;
930-
} else if (loop_type == 0x01) {
931-
loop_mode = AudioStreamWAV::LOOP_PINGPONG;
932-
} else if (loop_type == 0x02) {
933-
loop_mode = AudioStreamWAV::LOOP_BACKWARD;
934-
}
935-
loop_begin = file->get_32();
936-
loop_end = file->get_32();
937-
}
938-
}
939-
// Move to the start of the next chunk. Note that RIFF requires a padding byte for odd
940-
// chunk sizes.
941-
file->seek(file_pos + chunksize + (chunksize & 1));
942-
}
773+
drwav_uninit(&wav);
943774

944775
// STEP 2, APPLY CONVERSIONS
945776

thirdparty/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,18 @@ Files extracted from upstream source:
186186
- `LICENSE.txt`
187187

188188

189+
## dr_libs
190+
191+
- Upstream: https://github.com/mackron/dr_libs
192+
- Version: git (da35f9d6c7374a95353fd1df1d394d44ab66cf01, 2024)
193+
- License: Public Domain or Unlicense or MIT
194+
195+
Files extracted from upstream source:
196+
197+
- `dr_wav.h`
198+
- `LICENSE`
199+
200+
189201
## embree
190202

191203
- Upstream: https://github.com/embree/embree

0 commit comments

Comments
 (0)