From 901c4553d2b9c23337f10083f6726cdd71927cf5 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 4 Feb 2023 20:20:21 +0100 Subject: [PATCH] ADDED: QOA audio format support -WIP- --- src/config.h | 5 +- src/external/qoa.h | 658 +++++++++++++++++++++++++++++++++++++++++++++ src/raudio.c | 306 +++++++++++++-------- 3 files changed, 861 insertions(+), 108 deletions(-) create mode 100644 src/external/qoa.h diff --git a/src/config.h b/src/config.h index f67ea3cb0..be24c0190 100644 --- a/src/config.h +++ b/src/config.h @@ -211,10 +211,11 @@ // Desired audio fileformats to be supported for loading #define SUPPORT_FILEFORMAT_WAV 1 #define SUPPORT_FILEFORMAT_OGG 1 -#define SUPPORT_FILEFORMAT_XM 1 -#define SUPPORT_FILEFORMAT_MOD 1 #define SUPPORT_FILEFORMAT_MP3 1 +//#define SUPPORT_FILEFORMAT_QOA 1 //#define SUPPORT_FILEFORMAT_FLAC 1 +#define SUPPORT_FILEFORMAT_XM 1 +#define SUPPORT_FILEFORMAT_MOD 1 // raudio: Configuration values //------------------------------------------------------------------------------------ diff --git a/src/external/qoa.h b/src/external/qoa.h new file mode 100644 index 000000000..aae575512 --- /dev/null +++ b/src/external/qoa.h @@ -0,0 +1,658 @@ +/* + +Copyright (c) 2023, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + +QOA - The "Quite OK Audio" format for fast, lossy audio compression + + +-- Data Format + +A QOA file has an 8 byte file header, followed by a number of frames. Each frame +consists of an 8 byte frame header, the current 8 byte en-/decoder state per +channel and 256 slices per channel. Each slice is 8 bytes wide and encodes 20 +samples of audio data. + +Note that the last frame of a file may contain less than 256 slices per channel. +The last slice (per channel) in the last frame may contain less 20 samples, but +the slice will still be 8 bytes wide, with the unused samples zeroed out. + +The samplerate and number of channels is only stated in the frame headers, but +not in the file header. A decoder may peek into the first frame of the file to +find these values. + +In a valid QOA file all frames have the same number of channels and the same +samplerate. These restriction may be releaxed for streaming. This remains to +be decided. + +All values in a QOA file are BIG ENDIAN. Luckily, EVERYTHING in a QOA file, +including the headers, is 64 bit aligned, so it's possible to read files with +just a read_u64() that does the byte swapping if neccessary. + +In pseudocode, the file layout is as follows: + +struct { + struct { + char magic[4]; // magic bytes 'qoaf' + uint32_t samples; // number of samples per channel in this file + } file_header; // = 64 bits + + struct { + struct { + uint8_t num_channels; // number of channels + uint24_t samplerate; // samplerate in hz + uint16_t fsamples; // sample count per channel in this frame + uint16_t fsize; // frame size (including the frame header) + } frame_header; // = 64 bits + + struct { + int16_t history[4]; // = 64 bits + int16_t weights[4]; // = 64 bits + } lms_state[num_channels]; + + qoa_slice_t slices[256][num_channels]; // = 64 bits each + } frames[samples * channels / qoa_max_framesize()]; +} qoa_file; + +Wheras the 64bit qoa_slice_t is defined as follows: + +.- QOA_SLICE -- 64 bits, 20 samples --------------------------/ /------------. +| Byte[0] | Byte[1] | Byte[2] \ \ Byte[7] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 / / 2 1 0 | +|------------+--------+--------+--------+---------+---------+-\ \--+---------| +| sf_index | r00 | r01 | r02 | r03 | r04 | / / | r19 | +`-------------------------------------------------------------\ \------------` + +`sf_index` defines the scalefactor to use for this slice as an index into the +qoa_scalefactor_tab[16] + +`r00`--`r19` are the residuals for the individiual samples, divided by the +scalefactor and quantized by the qoa_quant_tab[]. + +In the decoder, a prediction of the next sample is computed by multiplying the +state (the last four output samples) with the predictor. The residual from the +slice is then dequantized using the qoa_dequant_tab[] and added to the +prediction. The result is clamped to int16 to form the final output sample. + +*/ + + + +/* ----------------------------------------------------------------------------- + Header - Public functions */ + +#ifndef QOA_H +#define QOA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define QOA_MIN_FILESIZE 16 +#define QOA_MAX_CHANNELS 8 + +#define QOA_SLICE_LEN 20 +#define QOA_SLICES_PER_FRAME 256 +#define QOA_FRAME_LEN (QOA_SLICES_PER_FRAME * QOA_SLICE_LEN) +#define QOA_LMS_LEN 4 +#define QOA_MAGIC 0x716f6166 /* 'qoaf' */ + +#define QOA_FRAME_SIZE(channels, slices) \ + (8 + QOA_LMS_LEN * 4 * channels + 8 * slices * channels) + +typedef struct { + int history[QOA_LMS_LEN]; + int weights[QOA_LMS_LEN]; +} qoa_lms_t; + +typedef struct { + unsigned int channels; + unsigned int samplerate; + unsigned int samples; + qoa_lms_t lms[QOA_MAX_CHANNELS]; + #ifdef QOA_RECORD_TOTAL_ERROR + double error; + #endif +} qoa_desc; + +unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes); +unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes); +void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len); + +unsigned int qoa_max_frame_size(qoa_desc *qoa); +unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa); +unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len); +short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *file); + +#ifndef QOA_NO_STDIO + +int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa); +void *qoa_read(const char *filename, qoa_desc *qoa); + +#endif /* QOA_NO_STDIO */ + + +#ifdef __cplusplus +} +#endif +#endif /* QOA_H */ + + +/* ----------------------------------------------------------------------------- + Implementation */ + +#ifdef QOA_IMPLEMENTATION +#include + +#ifndef QOA_MALLOC + #define QOA_MALLOC(sz) malloc(sz) + #define QOA_FREE(p) free(p) +#endif + +typedef unsigned long long qoa_uint64_t; + + +/* The quant_tab provides an index into the dequant_tab for residuals in the +range of -8 .. 8. It maps this range to just 3bits and becommes less accurate at +the higher end. Note that the residual zero is identical to the lowest positive +value. This is mostly fine, since the qoa_div() function always rounds away +from zero. */ + +static int qoa_quant_tab[17] = { + 7, 7, 7, 5, 5, 3, 3, 1, /* -8..-1 */ + 0, /* 0 */ + 0, 2, 2, 4, 4, 6, 6, 6 /* 1.. 8 */ +}; + + +/* We have 16 different scalefactors. Like the quantized residuals these become +less accurate at the higher end. In theory, the highest scalefactor that we +would need to encode the highest 16bit residual is (2**16)/8 = 8192. However we +rely on the LMS filter to predict samples accurately enough that a maximum +residual of one quarter of the 16 bit range is high sufficent. I.e. with the +scalefactor 2048 times the quant range of 8 we can encode residuals up to 2**14. + +The scalefactor values are computed as: +scalefactor_tab[s] <- round(pow(s + 1, 2.75)) */ + +static int qoa_scalefactor_tab[16] = { + 1, 7, 21, 45, 84, 138, 211, 304, 421, 562, 731, 928, 1157, 1419, 1715, 2048 +}; + + +/* The reciprocal_tab maps each of the 16 scalefactors to their rounded +reciprocals 1/scalefactor. This allows us to calculate the scaled residuals in +the encoder with just one multiplication instead of an expensive division. We +do this in .16 fixed point with integers, instead of floats. + +The reciprocal_tab is computed as: +reciprocal_tab[s] <- ((1<<16) + scalefactor_tab[s] - 1) / scalefactor_tab[s] */ + +static int qoa_reciprocal_tab[16] = { + 65536, 9363, 3121, 1457, 781, 475, 311, 216, 156, 117, 90, 71, 57, 47, 39, 32 +}; + + +/* The dequant_tab maps each of the scalefactors and quantized residuals to +their unscaled & dequantized version. + +Since qoa_div rounds away from the zero, the smallest entries are mapped to 3/4 +instead of 1. The dequant_tab assumes the following dequantized values for each +of the quant_tab indices and is computed as: +float dqt[8] = {0.75, -0.75, 2.5, -2.5, 4.5, -4.5, 7, -7}; +dequant_tab[s][q] <- round(scalefactor_tab[s] * dqt[q]) */ + +static int qoa_dequant_tab[16][8] = { + { 1, -1, 3, -3, 5, -5, 7, -7}, + { 5, -5, 18, -18, 32, -32, 49, -49}, + { 16, -16, 53, -53, 95, -95, 147, -147}, + { 34, -34, 113, -113, 203, -203, 315, -315}, + { 63, -63, 210, -210, 378, -378, 588, -588}, + { 104, -104, 345, -345, 621, -621, 966, -966}, + { 158, -158, 528, -528, 950, -950, 1477, -1477}, + { 228, -228, 760, -760, 1368, -1368, 2128, -2128}, + { 316, -316, 1053, -1053, 1895, -1895, 2947, -2947}, + { 422, -422, 1405, -1405, 2529, -2529, 3934, -3934}, + { 548, -548, 1828, -1828, 3290, -3290, 5117, -5117}, + { 696, -696, 2320, -2320, 4176, -4176, 6496, -6496}, + { 868, -868, 2893, -2893, 5207, -5207, 8099, -8099}, + {1064, -1064, 3548, -3548, 6386, -6386, 9933, -9933}, + {1286, -1286, 4288, -4288, 7718, -7718, 12005, -12005}, + {1536, -1536, 5120, -5120, 9216, -9216, 14336, -14336}, +}; + + +/* The Least Mean Squares Filter is the heart of QOA. It predicts the next +sample based on the previous 4 reconstructed samples. It does so by continuously +adjusting 4 weights based on the residual of the previous prediction. + +The next sample is predicted as the sum of (weight[i] * history[i]). + +The adjustment of the weights is done with a "Sign-Sign-LMS" that adds or +subtracts the residual to each weight, based on the corresponding sample from +the history. This, suprisingly, is sufficent to get worthwhile predictions. + +This is all done with fixed point integers. Hence the right-shifts when updating +the weights and calculating the prediction. */ + +static int qoa_lms_predict(qoa_lms_t *lms) { + int prediction = 0; + for (int i = 0; i < QOA_LMS_LEN; i++) { + prediction += lms->weights[i] * lms->history[i]; + } + return prediction >> 13; +} + +static void qoa_lms_update(qoa_lms_t *lms, int sample, int residual) { + int delta = residual >> 4; + for (int i = 0; i < QOA_LMS_LEN; i++) { + lms->weights[i] += lms->history[i] < 0 ? -delta : delta; + } + + for (int i = 0; i < QOA_LMS_LEN-1; i++) { + lms->history[i] = lms->history[i+1]; + } + lms->history[QOA_LMS_LEN-1] = sample; +} + + +/* qoa_div() implements a rounding division, but avoids rounding to zero for +small numbers. E.g. 0.1 will be rounded to 1. Note that 0 itself still +returns as 0, which is handled in the qoa_quant_tab[]. +qoa_div() takes an index into the .16 fixed point qoa_reciprocal_tab as an +argument, so it can do the division with a cheaper integer multiplication. */ + +static inline int qoa_div(int v, int scalefactor) { + int reciprocal = qoa_reciprocal_tab[scalefactor]; + int n = (v * reciprocal + (1 << 15)) >> 16; + n = n + ((v > 0) - (v < 0)) - ((n > 0) - (n < 0)); /* round away from 0 */ + return n; +} + +static inline int qoa_clamp(int v, int min, int max) { + return (v < min) ? min : (v > max) ? max : v; +} + +static inline qoa_uint64_t qoa_read_u64(const unsigned char *bytes, unsigned int *p) { + qoa_uint64_t v = + (qoa_uint64_t)bytes[(*p)+0] << 56 | (qoa_uint64_t)bytes[(*p)+1] << 48 | + (qoa_uint64_t)bytes[(*p)+2] << 40 | (qoa_uint64_t)bytes[(*p)+3] << 32 | + (qoa_uint64_t)bytes[(*p)+4] << 24 | (qoa_uint64_t)bytes[(*p)+5] << 16 | + (qoa_uint64_t)bytes[(*p)+6] << 8 | (qoa_uint64_t)bytes[(*p)+7]; + *p += 8; + return v; +} + +static inline void qoa_write_u64(qoa_uint64_t v, unsigned char *bytes, unsigned int *p) { + bytes[(*p)++] = (v >> 56) & 0xff; + bytes[(*p)++] = (v >> 48) & 0xff; + bytes[(*p)++] = (v >> 40) & 0xff; + bytes[(*p)++] = (v >> 32) & 0xff; + bytes[(*p)++] = (v >> 24) & 0xff; + bytes[(*p)++] = (v >> 16) & 0xff; + bytes[(*p)++] = (v >> 8) & 0xff; + bytes[(*p)++] = (v >> 0) & 0xff; +} + + +/* ----------------------------------------------------------------------------- + Encoder */ + +unsigned int qoa_encode_header(qoa_desc *qoa, unsigned char *bytes) { + unsigned int p = 0; + qoa_write_u64(((qoa_uint64_t)QOA_MAGIC << 32) | qoa->samples, bytes, &p); + return p; +} + +unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned int frame_len, unsigned char *bytes) { + unsigned int channels = qoa->channels; + + unsigned int p = 0; + unsigned int slices = (frame_len + QOA_SLICE_LEN - 1) / QOA_SLICE_LEN; + unsigned int frame_size = QOA_FRAME_SIZE(channels, slices); + + /* Write the frame header */ + qoa_write_u64(( + (qoa_uint64_t)qoa->channels << 56 | + (qoa_uint64_t)qoa->samplerate << 32 | + (qoa_uint64_t)frame_len << 16 | + (qoa_uint64_t)frame_size + ), bytes, &p); + + /* Write the current LMS state */ + for (int c = 0; c < channels; c++) { + qoa_uint64_t weights = 0; + qoa_uint64_t history = 0; + for (int i = 0; i < QOA_LMS_LEN; i++) { + history = (history << 16) | (qoa->lms[c].history[i] & 0xffff); + weights = (weights << 16) | (qoa->lms[c].weights[i] & 0xffff); + } + qoa_write_u64(history, bytes, &p); + qoa_write_u64(weights, bytes, &p); + } + + /* We encode all samples with the channels interleaved on a slice level. + E.g. for stereo: (ch-0, slice 0), (ch 1, slice 0), (ch 0, slice 1), ...*/ + for (int sample_index = 0; sample_index < frame_len; sample_index += QOA_SLICE_LEN) { + + for (int c = 0; c < channels; c++) { + int slice_len = qoa_clamp(QOA_SLICE_LEN, 0, frame_len - sample_index); + int slice_start = sample_index * channels + c; + int slice_end = (sample_index + slice_len) * channels + c; + + /* Brute for search for the best scalefactor. Just go through all + 16 scalefactors, encode all samples for the current slice and + meassure the total squared error. */ + qoa_uint64_t best_error = -1; + qoa_uint64_t best_slice; + qoa_lms_t best_lms; + + for (int scalefactor = 0; scalefactor < 16; scalefactor++) { + + /* We have to reset the LMS state to the last known good one + before trying each scalefactor, as each pass updates the LMS + state when encoding. */ + qoa_lms_t lms = qoa->lms[c]; + qoa_uint64_t slice = scalefactor; + qoa_uint64_t current_error = 0; + + for (int si = slice_start; si < slice_end; si += channels) { + int sample = sample_data[si]; + int predicted = qoa_lms_predict(&lms); + + int residual = sample - predicted; + int scaled = qoa_div(residual, scalefactor); + int clamped = qoa_clamp(scaled, -8, 8); + int quantized = qoa_quant_tab[clamped + 8]; + int dequantized = qoa_dequant_tab[scalefactor][quantized]; + int reconstructed = qoa_clamp(predicted + dequantized, -32768, 32767); + + int error = (sample - reconstructed); + current_error += error * error; + if (current_error > best_error) { + break; + } + + qoa_lms_update(&lms, reconstructed, dequantized); + slice = (slice << 3) | quantized; + } + + if (current_error < best_error) { + best_error = current_error; + best_slice = slice; + best_lms = lms; + } + } + + qoa->lms[c] = best_lms; + #ifdef QOA_RECORD_TOTAL_ERROR + qoa->error += best_error; + #endif + + /* If this slice was shorter than QOA_SLICE_LEN, we have to left- + shift all encoded data, to ensure the rightmost bits are the empty + ones. This should only happen in the last frame of a file as all + slices are completely filled otherwise. */ + best_slice <<= (QOA_SLICE_LEN - slice_len) * 3; + qoa_write_u64(best_slice, bytes, &p); + } + } + + return p; +} + +void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len) { + if ( + qoa->samples == 0 || + qoa->samplerate == 0 || qoa->samplerate > 0xffffff || + qoa->channels == 0 || qoa->channels > QOA_MAX_CHANNELS + ) { + return NULL; + } + + /* Calculate the encoded size and allocate */ + unsigned int num_frames = (qoa->samples + QOA_FRAME_LEN-1) / QOA_FRAME_LEN; + unsigned int num_slices = (qoa->samples + QOA_SLICE_LEN-1) / QOA_SLICE_LEN; + unsigned int encoded_size = 8 + /* 8 byte file header */ + num_frames * 8 + /* 8 byte frame headers */ + num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */ + num_slices * 8 * qoa->channels; /* 8 byte slices */ + + unsigned char *bytes = QOA_MALLOC(encoded_size); + + for (int c = 0; c < qoa->channels; c++) { + /* Set the initial LMS weights to {0, 0, -1, 2}. This helps with the + prediction of the first few ms of a file. */ + qoa->lms[c].weights[0] = 0; + qoa->lms[c].weights[1] = 0; + qoa->lms[c].weights[2] = -(1<<13); + qoa->lms[c].weights[3] = (1<<14); + + /* Explicitly set the history samples to 0, as we might have some + garbage in there. */ + for (int i = 0; i < QOA_LMS_LEN; i++) { + qoa->lms[c].history[i] = 0; + } + } + + + /* Encode the header and go through all frames */ + unsigned int p = qoa_encode_header(qoa, bytes); + #ifdef QOA_RECORD_TOTAL_ERROR + qoa->error = 0; + #endif + + int frame_len = QOA_FRAME_LEN; + for (int sample_index = 0; sample_index < qoa->samples; sample_index += frame_len) { + frame_len = qoa_clamp(QOA_FRAME_LEN, 0, qoa->samples - sample_index); + const short *frame_samples = sample_data + sample_index * qoa->channels; + unsigned int frame_size = qoa_encode_frame(frame_samples, qoa, frame_len, bytes + p); + p += frame_size; + } + + *out_len = p; + return bytes; +} + + + +/* ----------------------------------------------------------------------------- + Decoder */ + +unsigned int qoa_max_frame_size(qoa_desc *qoa) { + return QOA_FRAME_SIZE(qoa->channels, QOA_SLICES_PER_FRAME); +} + +unsigned int qoa_decode_header(const unsigned char *bytes, int size, qoa_desc *qoa) { + unsigned int p = 0; + if (size < QOA_MIN_FILESIZE) { + return 0; + } + + + /* Read the file header, verify the magic number ('qoaf') and read the + total number of samples. */ + qoa_uint64_t file_header = qoa_read_u64(bytes, &p); + + if ((file_header >> 32) != QOA_MAGIC) { + return 0; + } + + qoa->samples = file_header & 0xffffffff; + if (!qoa->samples) { + return 0; + } + + /* Peek into the first frame header to get the number of channels and + the samplerate. */ + qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); + qoa->channels = (frame_header >> 56) & 0x0000ff; + qoa->samplerate = (frame_header >> 32) & 0xffffff; + + if (qoa->channels == 0 || qoa->samples == 0 || qoa->samplerate == 0) { + return 0; + } + + return 8; +} + +unsigned int qoa_decode_frame(const unsigned char *bytes, unsigned int size, qoa_desc *qoa, short *sample_data, unsigned int *frame_len) { + unsigned int p = 0; + *frame_len = 0; + + if (size < 8 + QOA_LMS_LEN * 4 * qoa->channels) { + return 0; + } + + /* Read and verify the frame header */ + qoa_uint64_t frame_header = qoa_read_u64(bytes, &p); + int channels = (frame_header >> 56) & 0x0000ff; + int samplerate = (frame_header >> 32) & 0xffffff; + int samples = (frame_header >> 16) & 0x00ffff; + int frame_size = (frame_header ) & 0x00ffff; + + int data_size = frame_size - 8 - QOA_LMS_LEN * 4 * channels; + int num_slices = data_size / 8; + int max_total_samples = num_slices * QOA_SLICE_LEN; + + if ( + channels != qoa->channels || + samplerate != qoa->samplerate || + frame_size > size || + samples * channels > max_total_samples + ) { + return 0; + } + + + /* Read the LMS state: 4 x 2 bytes history, 4 x 2 bytes weights per channel */ + for (int c = 0; c < channels; c++) { + qoa_uint64_t history = qoa_read_u64(bytes, &p); + qoa_uint64_t weights = qoa_read_u64(bytes, &p); + for (int i = 0; i < QOA_LMS_LEN; i++) { + qoa->lms[c].history[i] = ((signed short)(history >> 48)); + history <<= 16; + qoa->lms[c].weights[i] = ((signed short)(weights >> 48)); + weights <<= 16; + } + } + + + /* Decode all slices for all channels in this frame */ + for (int sample_index = 0; sample_index < samples; sample_index += QOA_SLICE_LEN) { + for (int c = 0; c < channels; c++) { + qoa_uint64_t slice = qoa_read_u64(bytes, &p); + + int scalefactor = (slice >> 60) & 0xf; + int slice_start = sample_index * channels + c; + int slice_end = qoa_clamp(sample_index + QOA_SLICE_LEN, 0, samples) * channels + c; + + for (int si = slice_start; si < slice_end; si += channels) { + int predicted = qoa_lms_predict(&qoa->lms[c]); + int quantized = (slice >> 57) & 0x7; + int dequantized = qoa_dequant_tab[scalefactor][quantized]; + int reconstructed = qoa_clamp(predicted + dequantized, -32768, 32767); + + sample_data[si] = reconstructed; + slice <<= 3; + + qoa_lms_update(&qoa->lms[c], reconstructed, dequantized); + } + } + } + + *frame_len = samples; + return p; +} + +short *qoa_decode(const unsigned char *bytes, int size, qoa_desc *qoa) { + unsigned int p = qoa_decode_header(bytes, size, qoa); + if (!p) { + return NULL; + } + + /* Calculate the required size of the sample buffer and allocate */ + int total_samples = qoa->samples * qoa->channels; + short *sample_data = QOA_MALLOC(total_samples * sizeof(short)); + + unsigned int sample_index = 0; + unsigned int frame_len; + unsigned int frame_size; + + /* Decode all frames */ + do { + short *sample_ptr = sample_data + sample_index * qoa->channels; + frame_size = qoa_decode_frame(bytes + p, size - p, qoa, sample_ptr, &frame_len); + + p += frame_size; + sample_index += frame_len; + } while (frame_size && sample_index < qoa->samples); + + qoa->samples = sample_index; + return sample_data; +} + + + +/* ----------------------------------------------------------------------------- + File read/write convenience functions */ + +#ifndef QOA_NO_STDIO +#include + +int qoa_write(const char *filename, const short *sample_data, qoa_desc *qoa) { + FILE *f = fopen(filename, "wb"); + unsigned int size; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoa_encode(sample_data, qoa, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fclose(f); + + QOA_FREE(encoded); + return size; +} + +void *qoa_read(const char *filename, qoa_desc *qoa) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *data; + short *sample_data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + + data = QOA_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = fread(data, 1, size, f); + fclose(f); + + sample_data = qoa_decode(data, bytes_read, qoa); + QOA_FREE(data); + return sample_data; +} + +#endif /* QOA_NO_STDIO */ +#endif /* QOA_IMPLEMENTATION */ diff --git a/src/raudio.c b/src/raudio.c index 591d6f69f..90de7fe9e 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -21,10 +21,11 @@ * * #define SUPPORT_FILEFORMAT_WAV * #define SUPPORT_FILEFORMAT_OGG +* #define SUPPORT_FILEFORMAT_MP3 +* #define SUPPORT_FILEFORMAT_QOA +* #define SUPPORT_FILEFORMAT_FLAC * #define SUPPORT_FILEFORMAT_XM * #define SUPPORT_FILEFORMAT_MOD -* #define SUPPORT_FILEFORMAT_FLAC -* #define SUPPORT_FILEFORMAT_MP3 * Selected desired fileformats to be supported for loading. Some of those formats are * supported by default, to remove support, just comment unrequired #define in this module * @@ -196,37 +197,6 @@ typedef struct tagBITMAPINFOHEADER { #endif #endif -#if defined(SUPPORT_FILEFORMAT_OGG) - // TODO: Remap stb_vorbis malloc()/free() calls to RL_MALLOC/RL_FREE - #include "external/stb_vorbis.c" // OGG loading functions -#endif - -#if defined(SUPPORT_FILEFORMAT_XM) - #define JARXM_MALLOC RL_MALLOC - #define JARXM_FREE RL_FREE - -#if defined(_MSC_VER ) // jar xm has warnings on windows, so disable them just for this file -#pragma warning( push ) -#pragma warning( disable : 4244) -#endif - - #define JAR_XM_IMPLEMENTATION - #include "external/jar_xm.h" // XM loading functions - -#if defined(_MSC_VER ) -#pragma warning( pop ) -#endif - -#endif - -#if defined(SUPPORT_FILEFORMAT_MOD) - #define JARMOD_MALLOC RL_MALLOC - #define JARMOD_FREE RL_FREE - - #define JAR_MOD_IMPLEMENTATION - #include "external/jar_mod.h" // MOD loading functions -#endif - #if defined(SUPPORT_FILEFORMAT_WAV) #define DRWAV_MALLOC RL_MALLOC #define DRWAV_REALLOC RL_REALLOC @@ -236,6 +206,11 @@ typedef struct tagBITMAPINFOHEADER { #include "external/dr_wav.h" // WAV loading functions #endif +#if defined(SUPPORT_FILEFORMAT_OGG) + // TODO: Remap stb_vorbis malloc()/free() calls to RL_MALLOC/RL_FREE + #include "external/stb_vorbis.c" // OGG loading functions +#endif + #if defined(SUPPORT_FILEFORMAT_MP3) #define DRMP3_MALLOC RL_MALLOC #define DRMP3_REALLOC RL_REALLOC @@ -245,6 +220,14 @@ typedef struct tagBITMAPINFOHEADER { #include "external/dr_mp3.h" // MP3 loading functions #endif +#if defined(SUPPORT_FILEFORMAT_QOA) + #define QOA_MALLOC RL_MALLOC + #define QOA_FREE RL_FREE + + #define QOA_IMPLEMENTATION + #include "external/qoa.h" // QOA loading and saving functions +#endif + #if defined(SUPPORT_FILEFORMAT_FLAC) #define DRFLAC_MALLOC RL_MALLOC #define DRFLAC_REALLOC RL_REALLOC @@ -255,6 +238,31 @@ typedef struct tagBITMAPINFOHEADER { #include "external/dr_flac.h" // FLAC loading functions #endif +#if defined(SUPPORT_FILEFORMAT_XM) + #define JARXM_MALLOC RL_MALLOC + #define JARXM_FREE RL_FREE + + #if defined(_MSC_VER ) // jar_xm has warnings on windows, so disable them just for this file + #pragma warning( push ) + #pragma warning( disable : 4244) + #endif + + #define JAR_XM_IMPLEMENTATION + #include "external/jar_xm.h" // XM loading functions + + #if defined(_MSC_VER ) + #pragma warning( pop ) + #endif +#endif + +#if defined(SUPPORT_FILEFORMAT_MOD) + #define JARMOD_MALLOC RL_MALLOC + #define JARMOD_FREE RL_FREE + + #define JAR_MOD_IMPLEMENTATION + #include "external/jar_mod.h" // MOD loading functions +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -285,6 +293,7 @@ typedef enum { MUSIC_AUDIO_OGG, // OGG audio context MUSIC_AUDIO_FLAC, // FLAC audio context MUSIC_AUDIO_MP3, // MP3 audio context + MUSIC_AUDIO_QOA, // QOA audio context MUSIC_MODULE_XM, // XM module audio context MUSIC_MODULE_MOD // MOD module audio context } MusicContextType; @@ -795,19 +804,6 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data"); } #endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - else if (strcmp(fileType, ".flac") == 0) - { - unsigned long long int totalFrameCount = 0; - - // NOTE: We are forcing conversion to 16bit sample size on reading - wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); - wave.sampleSize = 16; - - if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount; - else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); - } -#endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (strcmp(fileType, ".mp3") == 0) { @@ -827,6 +823,38 @@ Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data"); } +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (strcmp(fileType, ".qoa") == 0) + { + qoa_desc qoa = { 0 }; + + // NOTE: Returned sample data is always 16 bit? + wave.data = qoa_decode(fileData, dataSize, &qoa); + wave.sampleSize = 16; + + if (wave.data != NULL) + { + wave.channels = qoa.channels; + wave.sampleRate = qoa.samplerate; + wave.frameCount = qoa.samples; + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load QOA data"); + + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (strcmp(fileType, ".flac") == 0) + { + unsigned long long int totalFrameCount = 0; + + // NOTE: We are forcing conversion to 16bit sample size on reading + wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); + wave.sampleSize = 16; + + if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount; + else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); + } #endif else TRACELOG(LOG_WARNING, "WAVE: Data format not supported"); @@ -1316,23 +1344,6 @@ Music LoadMusicStream(const char *fileName) } } #endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - else if (IsFileExtension(fileName, ".flac")) - { - music.ctxType = MUSIC_AUDIO_FLAC; - music.ctxData = drflac_open_file(fileName, NULL); - - if (music.ctxData != NULL) - { - drflac *ctxFlac = (drflac *)music.ctxData; - - music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); - music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; - music.looping = true; // Looping enabled by default - musicLoaded = true; - } - } -#endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (IsFileExtension(fileName, ".mp3")) { @@ -1351,6 +1362,45 @@ Music LoadMusicStream(const char *fileName) } } #endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (IsFileExtension(fileName, ".qoa")) + { + qoa_desc *ctxQoa = RL_CALLOC(1, sizeof(qoa_desc)); + + // TODO: QOA stream support: Init context from file + + music.ctxType = MUSIC_AUDIO_QOA; + music.ctxData = ctxQoa; + + if (result > 0) + { + music.stream = LoadAudioStream(ctxQoa->samplerate, 16, ctxQoa->channels); + + // TODO: Read next frame(s) from QOA stream + //music.frameCount = qoa_decode_frame(const unsigned char *bytes, unsigned int size, ctxQoa, short *sample_data, unsigned int *frame_len); + + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (IsFileExtension(fileName, ".flac")) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_file(fileName, NULL); + + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; + + music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif #if defined(SUPPORT_FILEFORMAT_XM) else if (IsFileExtension(fileName, ".xm")) { @@ -1408,12 +1458,15 @@ Music LoadMusicStream(const char *fileName) #if defined(SUPPORT_FILEFORMAT_OGG) else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); #endif - #if defined(SUPPORT_FILEFORMAT_FLAC) - else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); - #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } #endif + #if defined(SUPPORT_FILEFORMAT_QOA) + else if (music.ctxType == MUSIC_AUDIO_QOA) { /*TODO: Release QOA context data*/ RL_FREE(music.ctxData); } + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); + #endif #if defined(SUPPORT_FILEFORMAT_XM) else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); #endif @@ -1467,18 +1520,23 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, } } #endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - else if (strcmp(fileType, ".flac") == 0) +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (strcmp(fileType, ".ogg") == 0) { - music.ctxType = MUSIC_AUDIO_FLAC; - music.ctxData = drflac_open_memory((const void*)data, dataSize, NULL); + // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; + //music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); + music.ctxData = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL); if (music.ctxData != NULL) { - drflac *ctxFlac = (drflac *)music.ctxData; + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info - music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); - music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels + music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData); music.looping = true; // Looping enabled by default musicLoaded = true; } @@ -1502,23 +1560,40 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, } } #endif -#if defined(SUPPORT_FILEFORMAT_OGG) - else if (strcmp(fileType, ".ogg") == 0) +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (strcmp(fileType, ".qoa") == 0) { - // Open ogg audio stream - music.ctxType = MUSIC_AUDIO_OGG; - //music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); - music.ctxData = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL); + qoa_desc *ctxQoa = RL_CALLOC(1, sizeof(qoa_desc)); + + // TODO: Init QOA context data + + music.ctxType = MUSIC_AUDIO_QOA; + music.ctxData = ctxQoa; - if (music.ctxData != NULL) + if (success) { - stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + music.stream = LoadAudioStream(ctxQoa->samplerate, 16, ctxQoa->channels); + + // TODO: Read next frame(s) from QOA stream + //music.frameCount = qoa_decode_frame(const unsigned char *bytes, unsigned int size, ctxQoa, short *sample_data, unsigned int *frame_len); + + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (strcmp(fileType, ".flac") == 0) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_memory((const void*)data, dataSize, NULL); - // OGG bit rate defaults to 16 bit, it's enough for compressed format - music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; - // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels - music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData); + music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; music.looping = true; // Looping enabled by default musicLoaded = true; } @@ -1593,14 +1668,17 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, #if defined(SUPPORT_FILEFORMAT_WAV) else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); #endif - #if defined(SUPPORT_FILEFORMAT_FLAC) - else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); + #if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } #endif - #if defined(SUPPORT_FILEFORMAT_OGG) - else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); + #if defined(SUPPORT_FILEFORMAT_QOA) + else if (music.ctxType == MUSIC_AUDIO_QOA) { /*TODO: Release QOA context*/ RL_FREE(music.ctxData); } + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); #endif #if defined(SUPPORT_FILEFORMAT_XM) else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); @@ -1645,12 +1723,15 @@ void UnloadMusicStream(Music music) #if defined(SUPPORT_FILEFORMAT_OGG) else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); #endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); -#endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } #endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (music.ctxType == MUSIC_AUDIO_QOA) { /*TODO: Release QOA context*/ RL_FREE(music.ctxData); } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); +#endif #if defined(SUPPORT_FILEFORMAT_XM) else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); #endif @@ -1700,12 +1781,15 @@ void StopMusicStream(Music music) #if defined(SUPPORT_FILEFORMAT_OGG) case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break; #endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break; -#endif #if defined(SUPPORT_FILEFORMAT_MP3) case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break; #endif +#if defined(SUPPORT_FILEFORMAT_QOA) + case MUSIC_AUDIO_QOA: /*TODO: Restart QOA context to beginning*/ break; +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break; +#endif #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break; #endif @@ -1732,11 +1816,14 @@ void SeekMusicStream(Music music, float position) #if defined(SUPPORT_FILEFORMAT_OGG) case MUSIC_AUDIO_OGG: stb_vorbis_seek_frame((stb_vorbis *)music.ctxData, positionInFrames); break; #endif -#if defined(SUPPORT_FILEFORMAT_FLAC) - case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break; -#endif #if defined(SUPPORT_FILEFORMAT_MP3) case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break; +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + case MUSIC_AUDIO_QOA: /*TODO: Seek to specific QOA frame*/ break; +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break; #endif default: break; } @@ -1754,6 +1841,7 @@ void UpdateMusicStream(Music music) // On first call of this function we lazily pre-allocated a temp buffer to read audio files/memory data in int frameSize = music.stream.channels*music.stream.sampleSize/8; unsigned int pcmSize = subBufferSizeInFrames*frameSize; + if (AUDIO.System.pcmBufferSize < pcmSize) { RL_FREE(AUDIO.System.pcmBuffer); @@ -1815,29 +1903,35 @@ void UpdateMusicStream(Music music) } } break; #endif - #if defined(SUPPORT_FILEFORMAT_FLAC) - case MUSIC_AUDIO_FLAC: + #if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: { while (true) { - int frameCountRed = drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); + int frameCountRed = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); frameCountRedTotal += frameCountRed; frameCountStillNeeded -= frameCountRed; if (frameCountStillNeeded == 0) break; - else drflac__seek_to_first_frame((drflac *)music.ctxData); + else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); } } break; #endif - #if defined(SUPPORT_FILEFORMAT_MP3) - case MUSIC_AUDIO_MP3: + #if defined(SUPPORT_FILEFORMAT_QOA) + case MUSIC_AUDIO_QOA: + { + // TODO: Read QOA required framecount to fill buffer to keep music playing + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: { while (true) { - int frameCountRed = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); + int frameCountRed = drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); frameCountRedTotal += frameCountRed; frameCountStillNeeded -= frameCountRed; if (frameCountStillNeeded == 0) break; - else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); + else drflac__seek_to_first_frame((drflac *)music.ctxData); } } break; #endif