Skip to content

Commit 9cb3a16

Browse files
committed
Merge pull request #91014 from DeeJayLSP/qoa-wav-playback
Add QOA (Quite OK Audio) as a WAV compression mode
2 parents 9284410 + b9cbf2c commit 9cb3a16

File tree

9 files changed

+1057
-43
lines changed

9 files changed

+1057
-43
lines changed

COPYRIGHT.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@ Comment: PolyPartition / Triangulator
411411
Copyright: 2011-2021, Ivan Fratric and contributors
412412
License: Expat
413413

414+
Files: ./thirdparty/misc/qoa.h
415+
Comment: Quite OK Audio Format
416+
Copyright: 2023, Dominic Szablewski
417+
License: Expat
418+
414419
Files: ./thirdparty/misc/r128.c
415420
./thirdparty/misc/r128.h
416421
Comment: r128 library

doc/classes/AudioStreamWAV.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<return type="int" enum="Error" />
1616
<param index="0" name="path" type="String" />
1717
<description>
18-
Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM format can't be saved.
18+
Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or QOA formats can't be saved.
1919
[b]Note:[/b] A [code].wav[/code] extension is automatically appended to [param path] if it is missing.
2020
</description>
2121
</method>
@@ -56,6 +56,9 @@
5656
<constant name="FORMAT_IMA_ADPCM" value="2" enum="Format">
5757
Audio is compressed using IMA ADPCM.
5858
</constant>
59+
<constant name="FORMAT_QOA" value="3" enum="Format">
60+
Audio is compressed as QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]).
61+
</constant>
5962
<constant name="LOOP_DISABLED" value="0" enum="LoopMode">
6063
Audio does not loop.
6164
</constant>

doc/classes/ResourceImporterWAV.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
The compression mode to use on import.
1515
[b]Disabled:[/b] Imports audio data without any compression. This results in the highest possible quality.
1616
[b]RAM (Ima-ADPCM):[/b] Performs fast lossy compression on import. Low CPU cost, but quality is noticeably decreased compared to Ogg Vorbis or even MP3.
17+
[b]QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]):[/b] Performs lossy compression on import. CPU cost is slightly higher than IMA-ADPCM, but quality is much higher.
1718
</member>
1819
<member name="edit/loop_begin" type="int" setter="" getter="" default="0">
1920
The begin loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b] or [b]Backward[/b]. This is set in seconds after the beginning of the audio file.

editor/import/resource_importer_wav.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ void ResourceImporterWAV::get_import_options(const String &p_path, List<ImportOp
9090
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_mode", PROPERTY_HINT_ENUM, "Detect From WAV,Disabled,Forward,Ping-Pong,Backward", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
9191
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_begin"), 0));
9292
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_end"), -1));
93-
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Disabled,RAM (Ima-ADPCM)"), 0));
93+
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Disabled,RAM (Ima-ADPCM),QOA (Quite OK Audio)"), 0));
9494
}
9595

9696
Error ResourceImporterWAV::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
@@ -454,13 +454,13 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
454454
is16 = false;
455455
}
456456

457-
Vector<uint8_t> dst_data;
457+
Vector<uint8_t> pcm_data;
458458
AudioStreamWAV::Format dst_format;
459459

460460
if (compression == 1) {
461461
dst_format = AudioStreamWAV::FORMAT_IMA_ADPCM;
462462
if (format_channels == 1) {
463-
_compress_ima_adpcm(data, dst_data);
463+
_compress_ima_adpcm(data, pcm_data);
464464
} else {
465465
//byte interleave
466466
Vector<float> left;
@@ -482,9 +482,9 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
482482
_compress_ima_adpcm(right, bright);
483483

484484
int dl = bleft.size();
485-
dst_data.resize(dl * 2);
485+
pcm_data.resize(dl * 2);
486486

487-
uint8_t *w = dst_data.ptrw();
487+
uint8_t *w = pcm_data.ptrw();
488488
const uint8_t *rl = bleft.ptr();
489489
const uint8_t *rr = bright.ptr();
490490

@@ -496,13 +496,14 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
496496

497497
} else {
498498
dst_format = is16 ? AudioStreamWAV::FORMAT_16_BITS : AudioStreamWAV::FORMAT_8_BITS;
499-
dst_data.resize(data.size() * (is16 ? 2 : 1));
499+
bool enforce16 = is16 || compression == 2;
500+
pcm_data.resize(data.size() * (enforce16 ? 2 : 1));
500501
{
501-
uint8_t *w = dst_data.ptrw();
502+
uint8_t *w = pcm_data.ptrw();
502503

503504
int ds = data.size();
504505
for (int i = 0; i < ds; i++) {
505-
if (is16) {
506+
if (enforce16) {
506507
int16_t v = CLAMP(data[i] * 32768, -32768, 32767);
507508
encode_uint16(v, &w[i * 2]);
508509
} else {
@@ -513,6 +514,23 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
513514
}
514515
}
515516

517+
Vector<uint8_t> dst_data;
518+
if (compression == 2) {
519+
dst_format = AudioStreamWAV::FORMAT_QOA;
520+
qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
521+
uint32_t qoa_len = 0;
522+
523+
desc.samplerate = rate;
524+
desc.samples = frames;
525+
desc.channels = format_channels;
526+
527+
void *encoded = qoa_encode((short *)pcm_data.ptrw(), &desc, &qoa_len);
528+
dst_data.resize(qoa_len);
529+
memcpy(dst_data.ptrw(), encoded, qoa_len);
530+
} else {
531+
dst_data = pcm_data;
532+
}
533+
516534
Ref<AudioStreamWAV> sample;
517535
sample.instantiate();
518536
sample->set_data(dst_data);

scene/resources/audio_stream_wav.cpp

Lines changed: 113 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,15 @@ void AudioStreamPlaybackWAV::seek(double p_time) {
8686
offset = uint64_t(p_time * base->mix_rate) << MIX_FRAC_BITS;
8787
}
8888

89-
template <typename Depth, bool is_stereo, bool is_ima_adpcm>
90-
void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm) {
89+
template <typename Depth, bool is_stereo, bool is_ima_adpcm, bool is_qoa>
90+
void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int64_t &p_offset, int32_t &p_increment, uint32_t p_amount, IMA_ADPCM_State *p_ima_adpcm, QOA_State *p_qoa) {
9191
// this function will be compiled branchless by any decent compiler
9292

93-
int32_t final, final_r, next, next_r;
93+
int32_t final = 0, final_r = 0, next = 0, next_r = 0;
9494
while (p_amount) {
9595
p_amount--;
9696
int64_t pos = p_offset >> MIX_FRAC_BITS;
97-
if (is_stereo && !is_ima_adpcm) {
97+
if (is_stereo && !is_ima_adpcm && !is_qoa) {
9898
pos <<= 1;
9999
}
100100

@@ -175,32 +175,77 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
175175
}
176176

177177
} else {
178-
final = p_src[pos];
179-
if (is_stereo) {
180-
final_r = p_src[pos + 1];
181-
}
178+
if (is_qoa) {
179+
if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates.
180+
for (int i = 0; i < 2; i++) {
181+
// Sign operations prevent triple decoding on backward loops, maxing prevents pop.
182+
uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1);
183+
uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len;
184+
185+
if (p_qoa->data_ofs != new_data_ofs) {
186+
p_qoa->data_ofs = new_data_ofs;
187+
const uint8_t *src_ptr = (const uint8_t *)base->data;
188+
src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD;
189+
qoa_decode_frame(src_ptr, p_qoa->frame_len, p_qoa->desc, p_qoa->dec, &p_qoa->dec_len);
190+
}
182191

183-
if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */
184-
final <<= 8;
192+
uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels;
193+
194+
if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) {
195+
final = p_qoa->dec[dec_idx];
196+
p_qoa->cache[0] = final;
197+
if (is_stereo) {
198+
final_r = p_qoa->dec[dec_idx + 1];
199+
p_qoa->cache_r[0] = final_r;
200+
}
201+
} else {
202+
next = p_qoa->dec[dec_idx];
203+
p_qoa->cache[1] = next;
204+
if (is_stereo) {
205+
next_r = p_qoa->dec[dec_idx + 1];
206+
p_qoa->cache_r[1] = next_r;
207+
}
208+
}
209+
}
210+
p_qoa->cache_pos = pos;
211+
} else {
212+
final = p_qoa->cache[0];
213+
if (is_stereo) {
214+
final_r = p_qoa->cache_r[0];
215+
}
216+
217+
next = p_qoa->cache[1];
218+
if (is_stereo) {
219+
next_r = p_qoa->cache_r[1];
220+
}
221+
}
222+
} else {
223+
final = p_src[pos];
185224
if (is_stereo) {
186-
final_r <<= 8;
225+
final_r = p_src[pos + 1];
187226
}
188-
}
189227

190-
if (is_stereo) {
191-
next = p_src[pos + 2];
192-
next_r = p_src[pos + 3];
193-
} else {
194-
next = p_src[pos + 1];
195-
}
228+
if constexpr (sizeof(Depth) == 1) { /* conditions will not exist anymore when compiled! */
229+
final <<= 8;
230+
if (is_stereo) {
231+
final_r <<= 8;
232+
}
233+
}
196234

197-
if constexpr (sizeof(Depth) == 1) {
198-
next <<= 8;
199235
if (is_stereo) {
200-
next_r <<= 8;
236+
next = p_src[pos + 2];
237+
next_r = p_src[pos + 3];
238+
} else {
239+
next = p_src[pos + 1];
201240
}
202-
}
203241

242+
if constexpr (sizeof(Depth) == 1) {
243+
next <<= 8;
244+
if (is_stereo) {
245+
next_r <<= 8;
246+
}
247+
}
248+
}
204249
int32_t frac = int64_t(p_offset & MIX_FRAC_MASK);
205250

206251
final = final + ((next - final) * frac >> MIX_FRAC_BITS);
@@ -240,6 +285,9 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
240285
case AudioStreamWAV::FORMAT_IMA_ADPCM:
241286
len *= 2;
242287
break;
288+
case AudioStreamWAV::FORMAT_QOA:
289+
len = qoa.desc->samples * qoa.desc->channels;
290+
break;
243291
}
244292

245293
if (base->stereo) {
@@ -368,27 +416,34 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
368416
switch (base->format) {
369417
case AudioStreamWAV::FORMAT_8_BITS: {
370418
if (is_stereo) {
371-
do_resample<int8_t, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm);
419+
do_resample<int8_t, true, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
372420
} else {
373-
do_resample<int8_t, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm);
421+
do_resample<int8_t, false, false, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
374422
}
375423
} break;
376424
case AudioStreamWAV::FORMAT_16_BITS: {
377425
if (is_stereo) {
378-
do_resample<int16_t, true, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm);
426+
do_resample<int16_t, true, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
379427
} else {
380-
do_resample<int16_t, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm);
428+
do_resample<int16_t, false, false, false>((int16_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
381429
}
382430

383431
} break;
384432
case AudioStreamWAV::FORMAT_IMA_ADPCM: {
385433
if (is_stereo) {
386-
do_resample<int8_t, true, true>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm);
434+
do_resample<int8_t, true, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
387435
} else {
388-
do_resample<int8_t, false, true>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm);
436+
do_resample<int8_t, false, true, false>((int8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
389437
}
390438

391439
} break;
440+
case AudioStreamWAV::FORMAT_QOA: {
441+
if (is_stereo) {
442+
do_resample<uint8_t, true, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
443+
} else {
444+
do_resample<uint8_t, false, false, true>((uint8_t *)data, dst_buff, offset, increment, target, ima_adpcm, &qoa);
445+
}
446+
} break;
392447
}
393448

394449
dst_buff += target;
@@ -412,6 +467,16 @@ void AudioStreamPlaybackWAV::tag_used_streams() {
412467

413468
AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {}
414469

470+
AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {
471+
if (qoa.desc) {
472+
memfree(qoa.desc);
473+
}
474+
475+
if (qoa.dec) {
476+
memfree(qoa.dec);
477+
}
478+
}
479+
415480
/////////////////////
416481

417482
void AudioStreamWAV::set_format(Format p_format) {
@@ -475,6 +540,10 @@ double AudioStreamWAV::get_length() const {
475540
case AudioStreamWAV::FORMAT_IMA_ADPCM:
476541
len *= 2;
477542
break;
543+
case AudioStreamWAV::FORMAT_QOA:
544+
qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
545+
qoa_decode_header((uint8_t *)data + DATA_PAD, QOA_MIN_FILESIZE, &desc);
546+
len = desc.samples * desc.channels;
478547
}
479548

480549
if (stereo) {
@@ -526,8 +595,8 @@ Vector<uint8_t> AudioStreamWAV::get_data() const {
526595
}
527596

528597
Error AudioStreamWAV::save_to_wav(const String &p_path) {
529-
if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) {
530-
WARN_PRINT("Saving IMA_ADPC samples are not supported yet");
598+
if (format == AudioStreamWAV::FORMAT_IMA_ADPCM || format == AudioStreamWAV::FORMAT_QOA) {
599+
WARN_PRINT("Saving IMA_ADPCM and QOA samples is not supported yet");
531600
return ERR_UNAVAILABLE;
532601
}
533602

@@ -548,6 +617,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
548617
byte_pr_sample = 1;
549618
break;
550619
case AudioStreamWAV::FORMAT_16_BITS:
620+
case AudioStreamWAV::FORMAT_QOA:
551621
byte_pr_sample = 2;
552622
break;
553623
case AudioStreamWAV::FORMAT_IMA_ADPCM:
@@ -590,6 +660,7 @@ Error AudioStreamWAV::save_to_wav(const String &p_path) {
590660
}
591661
break;
592662
case AudioStreamWAV::FORMAT_16_BITS:
663+
case AudioStreamWAV::FORMAT_QOA:
593664
for (unsigned int i = 0; i < data_bytes / 2; i++) {
594665
uint16_t data_point = decode_uint16(&read_data[i * 2]);
595666
file->store_16(data_point);
@@ -607,6 +678,16 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() {
607678
Ref<AudioStreamPlaybackWAV> sample;
608679
sample.instantiate();
609680
sample->base = Ref<AudioStreamWAV>(this);
681+
682+
if (format == AudioStreamWAV::FORMAT_QOA) {
683+
sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc));
684+
qoa_decode_header((uint8_t *)data + DATA_PAD, QOA_MIN_FILESIZE, sample->qoa.desc);
685+
sample->qoa.frame_len = qoa_max_frame_size(sample->qoa.desc);
686+
int samples_len = (sample->qoa.desc->samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc->samples);
687+
int alloc_len = sample->qoa.desc->channels * samples_len * sizeof(int16_t);
688+
sample->qoa.dec = (int16_t *)memalloc(alloc_len);
689+
}
690+
610691
return sample;
611692
}
612693

@@ -639,7 +720,7 @@ void AudioStreamWAV::_bind_methods() {
639720
ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav);
640721

641722
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data");
642-
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
723+
ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM,QOA"), "set_format", "get_format");
643724
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode");
644725
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
645726
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end");
@@ -649,6 +730,7 @@ void AudioStreamWAV::_bind_methods() {
649730
BIND_ENUM_CONSTANT(FORMAT_8_BITS);
650731
BIND_ENUM_CONSTANT(FORMAT_16_BITS);
651732
BIND_ENUM_CONSTANT(FORMAT_IMA_ADPCM);
733+
BIND_ENUM_CONSTANT(FORMAT_QOA);
652734

653735
BIND_ENUM_CONSTANT(LOOP_DISABLED);
654736
BIND_ENUM_CONSTANT(LOOP_FORWARD);

0 commit comments

Comments
 (0)