Created
February 26, 2026 16:08
-
-
Save jakubtomsu/ec8675231e6cf19e04bfaa20cc7e19f8 to your computer and use it in GitHub Desktop.
Odin QOA encoding/decoding test https://github.com/phoboslab/qoa
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Bindings for qoa.h | |
| package cqoa | |
| foreign import lib "qoa.lib" | |
| RECORD_TOTAL_ERROR :: true | |
| MIN_FILESIZE :: 16 | |
| MAX_CHANNELS :: 8 | |
| SLICE_LEN :: 20 | |
| SLICES_PER_FRAME :: 256 | |
| FRAME_LEN :: (SLICES_PER_FRAME * SLICE_LEN) | |
| LMS_LEN :: 4 | |
| MAGIC :: 0x716f6166 /* 'qoaf' */ | |
| LMS :: struct { | |
| history: [LMS_LEN]i32, | |
| weights: [LMS_LEN]i32, | |
| } | |
| Desc :: struct { | |
| channels: u32, | |
| samplerate: u32, | |
| samples: u32, | |
| lms: [MAX_CHANNELS]LMS, | |
| error: (f64 when RECORD_TOTAL_ERROR else struct {}), | |
| } | |
| @(link_prefix = "qoa_", default_calling_convention = "c") | |
| foreign lib { | |
| max_frame_size :: proc(qoa: ^Desc) -> u32 --- | |
| encode_header :: proc(qoa: ^Desc, bytes: [^]byte) -> u32 --- | |
| encode_frame :: proc(sample_data: [^]i16, qoa: ^Desc, frame_len: u32, bytes: [^]byte) -> u32 --- | |
| encode :: proc(sample_data: [^]i16, qoa: ^Desc, out_len: ^u32) -> [^]byte --- | |
| decode_header :: proc(bytes: [^]byte, size: i32, qoa: ^Desc) -> u32 --- | |
| decode_frame :: proc(bytes: [^]byte, size: u32, qoa: ^Desc, sample_data: [^]i16, frame_len: ^u32) -> u32 --- | |
| decode :: proc(bytes: [^]byte, size: i32, file: ^Desc) -> [^]u16 --- | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package qoa | |
| import "core:slice" | |
| import "base:runtime" | |
| import "core:testing" | |
| import "core:log" | |
| import "core:strings" | |
| import "../wav" | |
| import "cqoa" | |
| // Try a different linker if the files bloat compile times too much. | |
| // The samples are from: | |
| // https://qoaformat.org/samples/ | |
| // Unzip and put them into a qoa_test_samples directory. | |
| @(private) | |
| _wav_data := [][]runtime.Load_Directory_File{ | |
| // #load_directory("qoa_test_samples/oculus_audio_pack"), | |
| // #load_directory("qoa_test_samples/sqam"), | |
| #load_directory("qoa_test_samples/bandcamp"), | |
| } | |
| @(private) | |
| _qoa_data := [][]runtime.Load_Directory_File{ | |
| // #load_directory("qoa_test_samples/oculus_audio_pack/qoa"), | |
| // #load_directory("qoa_test_samples/sqam/qoa"), | |
| #load_directory("qoa_test_samples/bandcamp/qoa"), | |
| } | |
| @(private) | |
| _qoa_wav_data := [][]runtime.Load_Directory_File{ | |
| // #load_directory("qoa_test_samples/oculus_audio_pack/qoa_wav"), | |
| // #load_directory("qoa_test_samples/sqam/qoa"), | |
| #load_directory("qoa_test_samples/bandcamp/qoa"), | |
| } | |
| @(test) | |
| file_sanity_test :: proc(t: ^testing.T) { | |
| testing.expect(t, len(_wav_data) == len(_qoa_data)) | |
| testing.expect(t, len(_wav_data) == len(_qoa_wav_data)) | |
| for dir, i in _wav_data { | |
| testing.expect(t, len(dir) == len(_qoa_data[i])) | |
| testing.expect(t, len(dir) == len(_qoa_wav_data[i])) | |
| for file, j in dir { | |
| testing.expect(t, len(strings.common_prefix(file.name, _qoa_data[i][j].name)) > 5) | |
| testing.expect(t, len(strings.common_prefix(file.name, _qoa_wav_data[i][j].name)) > 5) | |
| } | |
| } | |
| } | |
| @(test) | |
| encode_test_cqoa :: proc(t: ^testing.T) { | |
| for dir, i in _wav_data { | |
| for file, j in dir { | |
| defer free_all(context.temp_allocator) | |
| wav_header, wav_data, wav_ok := wav.decode_header(file.data) | |
| testing.expect(t, wav_ok) | |
| assert(wav_header.format.bits_per_sample == 16) | |
| samples := wav.reinterpret_bytes(i16, wav_data) | |
| num_samples := len(samples) / int(wav_header.format.num_channels) | |
| // log.infof("Encode %s: %i samples, %i channels, %ihz", file.name, num_samples, wav_header.format.num_channels, wav_header.format.sample_rate) | |
| desc := Desc{ | |
| samplerate = wav_header.format.sample_rate, | |
| channels = u32(wav_header.format.num_channels), | |
| } | |
| qoa_enc, qoa_ok := encode(&desc, samples, context.temp_allocator) | |
| testing.expect(t, qoa_ok) | |
| cqoa_len: u32 | |
| cqoa_buf := cqoa.encode(&samples[0], &cqoa.Desc{ | |
| samplerate = wav_header.format.sample_rate, | |
| samples = u32(num_samples), | |
| channels = u32(wav_header.format.num_channels), | |
| }, &cqoa_len) | |
| cqoa_enc := cqoa_buf[:cqoa_len] | |
| testing.expect(t, len(qoa_enc) == len(cqoa_enc)) | |
| testing.expectf(t, slice.equal(qoa_enc, cqoa_enc), "%s: data mismatch", file.name) | |
| } | |
| } | |
| } | |
| // @(test) | |
| // test_cqoa_encode_vs_test_sample_data :: proc(t: ^testing.T) { | |
| // for dir, i in _wav_data { | |
| // for file, j in dir { | |
| // defer free_all(context.temp_allocator) | |
| // wav_header, wav_data, wav_ok := wav.decode_header(file.data) | |
| // testing.expect(t, wav_ok) | |
| // assert(wav_header.format.bits_per_sample == 16) | |
| // samples := wav.reinterpret_bytes(i16, wav_data) | |
| // num_samples := len(samples) / int(wav_header.format.num_channels) | |
| // // log.infof("CQOA Encode %s: %i samples, %i channels, %ihz", file.name, num_samples, wav_header.format.num_channels, wav_header.format.sample_rate) | |
| // cqoa_len: u32 | |
| // cqoa_buf := cqoa.encode(&samples[0], &cqoa.Desc{ | |
| // samplerate = wav_header.format.sample_rate, | |
| // samples = u32(num_samples), | |
| // channels = u32(wav_header.format.num_channels), | |
| // }, &cqoa_len) | |
| // cqoa_enc := cqoa_buf[:cqoa_len] | |
| // qoa_enc := _qoa_data[i][j].data | |
| // testing.expect(t, len(qoa_enc) == len(cqoa_enc)) | |
| // testing.expectf(t, slice.equal(qoa_enc, cqoa_enc), "%s: data mismatch", file.name) | |
| // } | |
| // } | |
| // } | |
| // @(test) | |
| // encode_test :: proc(t: ^testing.T) { | |
| // for dir, i in _wav_data { | |
| // for file, j in dir { | |
| // defer free_all(context.temp_allocator) | |
| // wav_header, wav_data, wav_ok := wav.decode_header(file.data) | |
| // testing.expect(t, wav_ok) | |
| // assert(wav_header.format.bits_per_sample == 16) | |
| // samples := wav.reinterpret_bytes(i16, wav_data) | |
| // log.infof("Encode %s: %i samples, %i channels, %ihz", file.name, len(samples) / int(wav_header.format.num_channels), wav_header.format.num_channels, wav_header.format.sample_rate) | |
| // desc := Desc{ | |
| // samplerate = wav_header.format.sample_rate, | |
| // channels = u32(wav_header.format.num_channels), | |
| // } | |
| // qoa_enc, qoa_ok := encode(&desc, samples, context.temp_allocator) | |
| // testing.expect(t, qoa_ok) | |
| // qoa_src := _qoa_data[i][j].data | |
| // testing.expect(t, len(qoa_enc) == len(qoa_src)) | |
| // if !testing.expectf(t, slice.equal(qoa_enc, qoa_src), "%s: data mismatch", file.name) { | |
| // n := 0 | |
| // for i in 0..<len(qoa_enc) { | |
| // if qoa_enc[i] != qoa_src[i] { | |
| // n += 1 | |
| // // log.infof("%s: %i: %i != %i", file.name, i, qoa_enc[i], qoa_src[i]) | |
| // } | |
| // } | |
| // log.errorf("\tnum different samples: %i", n) | |
| // } | |
| // } | |
| // } | |
| // } | |
| @(test) | |
| decode_test :: proc(t: ^testing.T) { | |
| for dir, i in _wav_data { | |
| for file, j in dir { | |
| defer free_all(context.temp_allocator) | |
| wav_header, wav_data, wav_ok := wav.decode_header(file.data) | |
| testing.expect(t, wav_ok) | |
| assert(wav_header.format.bits_per_sample == 16) | |
| orig_samples := wav.reinterpret_bytes(i16, wav_data) | |
| qoa_src := _qoa_data[i][j].data | |
| qoa_wav_src := _qoa_wav_data[i][j].data | |
| // log.infof("Decode %s: %i samples, %i channels, %ihz", file.name, len(orig_samples) / int(wav_header.format.num_channels), wav_header.format.num_channels, wav_header.format.sample_rate) | |
| qoa_wav_header, qoa_wav_data, qoa_wav_ok := wav.decode_header(qoa_wav_src) | |
| testing.expect(t, qoa_wav_ok) | |
| assert(qoa_wav_header.format.bits_per_sample == 16) | |
| expected_samples := wav.reinterpret_bytes(i16, qoa_wav_data) | |
| desc, decoded_samples, decode_ok := decode(qoa_src, context.temp_allocator) | |
| testing.expect(t, decode_ok) | |
| testing.expect(t, desc.samples > 0) | |
| testing.expect(t, desc.channels > 0) | |
| testing.expect(t, desc.samplerate > 0) | |
| testing.expect(t, desc.channels == u32(wav_header.format.num_channels)) | |
| testing.expect(t, desc.channels == u32(qoa_wav_header.format.num_channels)) | |
| testing.expect(t, desc.samplerate == wav_header.format.sample_rate) | |
| testing.expect(t, desc.samplerate == qoa_wav_header.format.sample_rate) | |
| testing.expect(t, len(decoded_samples) == len(orig_samples)) | |
| testing.expect(t, len(decoded_samples) == len(expected_samples)) | |
| testing.expect(t, slice.equal(decoded_samples, expected_samples)) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment