diff --git a/src/external/qoaplay.c b/src/external/qoaplay.c new file mode 100644 index 000000000..b4dc09c7e --- /dev/null +++ b/src/external/qoaplay.c @@ -0,0 +1,278 @@ +/******************************************************************************************* +* +* qoaplay - QOA stream playing helper functions +* +* qoaplay is a tiny abstraction to read and decode a QOA file "on the fly". +* It reads and decodes one frame at a time with minimal memory requirements. +* qoaplay also provides some functions to seek to a specific frame. +* +* LICENSE: MIT License +* +* Copyright (c) 2023 Dominic Szablewski (@phoboslab), reviewed by Ramon Santamaria (@raysan5) +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +**********************************************************************************************/ + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// QOA streaming data descriptor +typedef struct { + qoa_desc info; // QOA descriptor data + + FILE *file; // QOA file to read, if NULL, using memory buffer -> file_data + unsigned char *file_data; // QOA file data on memory + unsigned int file_data_size; // QOA file data on memory size + unsigned int file_data_offset; // QOA file data on memory offset for next read + + unsigned int first_frame_pos; // First frame position (after QOA header, required for offset) + unsigned int sample_position; // Current streaming sample position + + unsigned char *buffer; // Buffer used to read samples from file/memory (used on decoding) + unsigned int buffer_len; // Buffer length to read samples for streaming + + short *sample_data; // Sample data decoded + unsigned int sample_data_len; // Sample data decoded length + unsigned int sample_data_pos; // Sample data decoded position + +} qoaplay_desc; + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +qoaplay_desc *qoaplay_open(char *path); +qoaplay_desc *qoaplay_open_memory(const unsigned char *data, int data_size); +void qoaplay_close(qoaplay_desc *qoa_ctx); + +void qoaplay_rewind(qoaplay_desc *qoa_ctx); +void qoaplay_seek_frame(qoaplay_desc *qoa_ctx, int frame); +unsigned int qoaplay_decode(qoaplay_desc *qoa_ctx, float *sample_data, int num_samples); +unsigned int qoaplay_decode_frame(qoaplay_desc *qoa_ctx); +double qoaplay_get_duration(qoaplay_desc *qoa_ctx); +double qoaplay_get_time(qoaplay_desc *qoa_ctx); +int qoaplay_get_frame(qoaplay_desc *qoa_ctx); + +#if defined(__cplusplus) +} // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Open QOA file, keep FILE pointer to keep reading from file +qoaplay_desc *qoaplay_open(char *path) +{ + FILE *file = fopen(path, "rb"); + if (!file) return NULL; + + // Read and decode the file header + unsigned char header[QOA_MIN_FILESIZE]; + int read = fread(header, QOA_MIN_FILESIZE, 1, file); + if (!read) return NULL; + + qoa_desc qoa; + unsigned int first_frame_pos = qoa_decode_header(header, QOA_MIN_FILESIZE, &qoa); + if (!first_frame_pos) return NULL; + + // Rewind the file back to beginning of the first frame + fseek(file, first_frame_pos, SEEK_SET); + + // Allocate one chunk of memory for the qoaplay_desc struct + // + the sample data for one frame + // + a buffer to hold one frame of encoded data + unsigned int buffer_size = qoa_max_frame_size(&qoa); + unsigned int sample_data_size = qoa.channels*QOA_FRAME_LEN*sizeof(short)*2; + qoaplay_desc *qoa_ctx = QOA_MALLOC(sizeof(qoaplay_desc) + buffer_size + sample_data_size); + memset(qoa_ctx, 0, sizeof(qoaplay_desc)); + + qoa_ctx->file = file; + qoa_ctx->file_data = NULL; + qoa_ctx->file_data_size = 0; + qoa_ctx->file_data_offset = 0; + qoa_ctx->first_frame_pos = first_frame_pos; + + // Setup data pointers to previously allocated data + qoa_ctx->buffer = ((unsigned char *)qoa_ctx) + sizeof(qoaplay_desc); + qoa_ctx->sample_data = (short *)(((unsigned char *)qoa_ctx) + sizeof(qoaplay_desc) + buffer_size); + + qoa_ctx->info.channels = qoa.channels; + qoa_ctx->info.samplerate = qoa.samplerate; + qoa_ctx->info.samples = qoa.samples; + + return qoa_ctx; +} + +// Open QOA file from memory, no FILE pointer required +qoaplay_desc *qoaplay_open_memory(const unsigned char *data, int data_size) +{ + // Read and decode the file header + unsigned char header[QOA_MIN_FILESIZE]; + memcpy(header, data, QOA_MIN_FILESIZE); + + qoa_desc qoa; + unsigned int first_frame_pos = qoa_decode_header(header, QOA_MIN_FILESIZE, &qoa); + if (!first_frame_pos) return NULL; + + // Allocate one chunk of memory for the qoaplay_desc struct + // + the sample data for one frame + // + a buffer to hold one frame of encoded data + unsigned int buffer_size = qoa_max_frame_size(&qoa); + unsigned int sample_data_size = qoa.channels*QOA_FRAME_LEN*sizeof(short)*2; + qoaplay_desc *qoa_ctx = QOA_MALLOC(sizeof(qoaplay_desc) + buffer_size + sample_data_size); + memset(qoa_ctx, 0, sizeof(qoaplay_desc)); + + qoa_ctx->file = NULL; + + // Keep a copy of file data provided to be managed internally + qoa_ctx->file_data = (unsigned char *)QOA_MALLOC(data_size); + memcpy(qoa_ctx->file_data, data, data_size); + qoa_ctx->file_data_size = data_size; + qoa_ctx->file_data_offset = 0; + qoa_ctx->first_frame_pos = first_frame_pos; + + // Setup data pointers to previously allocated data + qoa_ctx->buffer = ((unsigned char *)qoa_ctx) + sizeof(qoaplay_desc); + qoa_ctx->sample_data = (short *)(((unsigned char *)qoa_ctx) + sizeof(qoaplay_desc) + buffer_size); + + qoa_ctx->info.channels = qoa.channels; + qoa_ctx->info.samplerate = qoa.samplerate; + qoa_ctx->info.samples = qoa.samples; + + return qoa_ctx; +} + +// Close QOA file (if open) and free internal memory +void qoaplay_close(qoaplay_desc *qoa_ctx) +{ + if (qoa_ctx->file) fclose(qoa_ctx->file); + + if ((qoa_ctx->file_data) && (qoa_ctx->file_data_size > 0)) + { + QOA_FREE(qoa_ctx->file_data); + qoa_ctx->file_data_size = 0; + } + + QOA_FREE(qoa_ctx); +} + +// Decode one frame from QOA data +unsigned int qoaplay_decode_frame(qoaplay_desc *qoa_ctx) +{ + if (qoa_ctx->file) qoa_ctx->buffer_len = fread(qoa_ctx->buffer, 1, qoa_max_frame_size(&qoa_ctx->info), qoa_ctx->file); + else + { + qoa_ctx->buffer_len = qoa_max_frame_size(&qoa_ctx->info); + memcpy(qoa_ctx->buffer, qoa_ctx->file_data + qoa_ctx->file_data_offset, qoa_ctx->buffer_len); + qoa_ctx->file_data_offset += qoa_ctx->buffer_len; + } + + unsigned int frame_len; + qoa_decode_frame(qoa_ctx->buffer, qoa_ctx->buffer_len, &qoa_ctx->info, qoa_ctx->sample_data, &frame_len); + qoa_ctx->sample_data_pos = 0; + qoa_ctx->sample_data_len = frame_len; + + return frame_len; +} + +// Rewind QOA file or memory pointer to beginning +void qoaplay_rewind(qoaplay_desc *qoa_ctx) +{ + if (qoa_ctx->file) fseek(qoa_ctx->file, qoa_ctx->first_frame_pos, SEEK_SET); + else qoa_ctx->file_data_offset = 0; + + qoa_ctx->sample_position = 0; + qoa_ctx->sample_data_len = 0; + qoa_ctx->sample_data_pos = 0; +} + +// Decode required QOA frames +unsigned int qoaplay_decode(qoaplay_desc *qoa_ctx, float *sample_data, int num_samples) +{ + int src_index = qoa_ctx->sample_data_pos*qoa_ctx->info.channels; + int dst_index = 0; + + for (int i = 0; i < num_samples; i++) + { + // Do we have to decode more samples? + if (qoa_ctx->sample_data_len - qoa_ctx->sample_data_pos == 0) + { + if (!qoaplay_decode_frame(qoa_ctx)) + { + // Loop to the beginning + qoaplay_rewind(qoa_ctx); + qoaplay_decode_frame(qoa_ctx); + } + + src_index = 0; + } + + // Normalize to -1..1 floats and write to dest + for (int c = 0; c < qoa_ctx->info.channels; c++) + { + sample_data[dst_index++] = qoa_ctx->sample_data[src_index++]/32768.0; + } + + qoa_ctx->sample_data_pos++; + qoa_ctx->sample_position++; + } + + return num_samples; +} + +// Get QOA total time duration in seconds +double qoaplay_get_duration(qoaplay_desc *qoa_ctx) +{ + return (double)qoa_ctx->info.samples/(double)qoa_ctx->info.samplerate; +} + +// Get QOA current time position in seconds +double qoaplay_get_time(qoaplay_desc *qoa_ctx) +{ + return (double)qoa_ctx->sample_position/(double)qoa_ctx->info.samplerate; +} + +// Get QOA current audio frame +int qoaplay_get_frame(qoaplay_desc *qoa_ctx) +{ + return qoa_ctx->sample_position/QOA_FRAME_LEN; +} + +// Seek QOA audio frame +void qoaplay_seek_frame(qoaplay_desc *qoa_ctx, int frame) +{ + if (frame < 0) frame = 0; + + if (frame > qoa_ctx->info.samples/QOA_FRAME_LEN) frame = qoa_ctx->info.samples/QOA_FRAME_LEN; + + qoa_ctx->sample_position = frame*QOA_FRAME_LEN; + qoa_ctx->sample_data_len = 0; + qoa_ctx->sample_data_pos = 0; + + unsigned int offset = qoa_ctx->first_frame_pos + frame*qoa_max_frame_size(&qoa_ctx->info); + + if (qoa_ctx->file) fseek(qoa_ctx->file, offset, SEEK_SET); + else qoa_ctx->file_data_offset = offset; +} diff --git a/src/raudio.c b/src/raudio.c index 207c0c490..03e4bcbaa 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -230,6 +230,7 @@ typedef struct tagBITMAPINFOHEADER { #define QOA_IMPLEMENTATION #include "external/qoa.h" // QOA loading and saving functions + #include "external/qoaplay.c" // QOA stream playing helper functions #endif #if defined(SUPPORT_FILEFORMAT_FLAC) @@ -287,21 +288,6 @@ typedef struct tagBITMAPINFOHEADER { //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- - -// Music context type -// NOTE: Depends on data structure provided by the library -// in charge of reading the different file types -typedef enum { - MUSIC_AUDIO_NONE = 0, // No audio context loaded - MUSIC_AUDIO_WAV, // WAV audio context - 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; - #if defined(RAUDIO_STANDALONE) // Trace log level // NOTE: Organized by priority level @@ -317,6 +303,20 @@ typedef enum { } TraceLogLevel; #endif +// Music context type +// NOTE: Depends on data structure provided by the library +// in charge of reading the different file types +typedef enum { + MUSIC_AUDIO_NONE = 0, // No audio context loaded + MUSIC_AUDIO_WAV, // WAV audio context + 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; + // NOTE: Different logic is used when feeding data to the playback device // depending on whether data is streamed (Music vs Sound) typedef enum { @@ -1322,7 +1322,7 @@ void UnloadWaveSamples(float *samples) } //---------------------------------------------------------------------------------- -// Module Functions Definition - Music loading and stream playing (.OGG) +// Module Functions Definition - Music loading and stream playing //---------------------------------------------------------------------------------- // Load music stream from file @@ -1395,21 +1395,16 @@ Music LoadMusicStream(const char *fileName) #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 - int result = 0; - + qoaplay_desc *ctxQoa = qoaplay_open(fileName); music.ctxType = MUSIC_AUDIO_QOA; music.ctxData = ctxQoa; - if (result > 0) + if (ctxQoa->file != NULL) { - 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); - + // NOTE: We are loading samples are 32bit float normalized data, so, + // we configure the output audio stream to also use float 32bit + music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels); + music.frameCount = ctxQoa->info.samples; music.looping = true; // Looping enabled by default musicLoaded = true; } @@ -1594,21 +1589,16 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, #if defined(SUPPORT_FILEFORMAT_QOA) else if (strcmp(fileType, ".qoa") == 0) { - qoa_desc *ctxQoa = RL_CALLOC(1, sizeof(qoa_desc)); - - // TODO: Init QOA context data - int result = 0; - + qoaplay_desc *ctxQoa = qoaplay_open_memory(data, dataSize); music.ctxType = MUSIC_AUDIO_QOA; music.ctxData = ctxQoa; - if (result > 0) + if ((ctxQoa->file_data != NULL) && (ctxQoa->file_data_size != 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); - + // NOTE: We are loading samples are 32bit float normalized data, so, + // we configure the output audio stream to also use float 32bit + music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels); + music.frameCount = ctxQoa->info.samples; music.looping = true; // Looping enabled by default musicLoaded = true; } @@ -1697,27 +1687,27 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, if (!musicLoaded) { if (false) { } - #if defined(SUPPORT_FILEFORMAT_WAV) +#if defined(SUPPORT_FILEFORMAT_WAV) else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); - #endif - #if defined(SUPPORT_FILEFORMAT_OGG) +#endif +#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) +#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) +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)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) +#endif +#if defined(SUPPORT_FILEFORMAT_XM) else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); - #endif - #if defined(SUPPORT_FILEFORMAT_MOD) +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } - #endif +#endif music.ctxData = NULL; TRACELOG(LOG_WARNING, "FILEIO: Music data could not be loaded"); @@ -1763,7 +1753,7 @@ void UnloadMusicStream(Music music) 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); } + else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); @@ -1821,7 +1811,7 @@ void StopMusicStream(Music music) 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; + case MUSIC_AUDIO_QOA: qoaplay_rewind((qoaplay_desc *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_FLAC) case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break; @@ -1856,7 +1846,7 @@ void SeekMusicStream(Music music, float position) 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; + case MUSIC_AUDIO_QOA: qoaplay_seek_frame((qoaplay_desc *)music.ctxData, positionInFrames); break; #endif #if defined(SUPPORT_FILEFORMAT_FLAC) case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break; @@ -1892,11 +1882,13 @@ void UpdateMusicStream(Music music) unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed unsigned int framesToStream = 0; // Total frames to be streamed + if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames; else framesToStream = framesLeft; int frameCountStillNeeded = framesToStream; - int frameCountRedTotal = 0; + int frameCountReadTotal = 0; + switch (music.ctxType) { #if defined(SUPPORT_FILEFORMAT_WAV) @@ -1906,8 +1898,8 @@ void UpdateMusicStream(Music music) { while (true) { - int frameCountRed = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); - frameCountRedTotal += frameCountRed; + int frameCountRed = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRed; frameCountStillNeeded -= frameCountRed; if (frameCountStillNeeded == 0) break; else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); @@ -1917,8 +1909,8 @@ void UpdateMusicStream(Music music) { while (true) { - int frameCountRed = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); - frameCountRedTotal += frameCountRed; + int frameCountRed = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRed; frameCountStillNeeded -= frameCountRed; if (frameCountStillNeeded == 0) break; else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); @@ -1931,8 +1923,8 @@ void UpdateMusicStream(Music music) { while (true) { - int frameCountRed = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize), frameCountStillNeeded*music.stream.channels); - frameCountRedTotal += frameCountRed; + int frameCountRed = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded*music.stream.channels); + frameCountReadTotal += frameCountRed; frameCountStillNeeded -= frameCountRed; if (frameCountStillNeeded == 0) break; else stb_vorbis_seek_start((stb_vorbis *)music.ctxData); @@ -1944,9 +1936,9 @@ void UpdateMusicStream(Music music) { while (true) { - int frameCountRed = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); - frameCountRedTotal += frameCountRed; - frameCountStillNeeded -= frameCountRed; + int frameCountRead = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; if (frameCountStillNeeded == 0) break; else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); } @@ -1955,7 +1947,18 @@ void UpdateMusicStream(Music music) #if defined(SUPPORT_FILEFORMAT_QOA) case MUSIC_AUDIO_QOA: { - // TODO: Read QOA required framecount to fill buffer to keep music playing + unsigned int frameCountRead = qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); + frameCountReadTotal += frameCountRead; + /* + while (true) + { + int frameCountRead = (int)qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else qoaplay_rewind((qoaplay_desc *)music.ctxData); + } + */ } break; #endif #if defined(SUPPORT_FILEFORMAT_FLAC) @@ -1964,8 +1967,8 @@ void UpdateMusicStream(Music music) while (true) { int frameCountRed = drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); - frameCountRedTotal += frameCountRed; - frameCountStillNeeded -= frameCountRed; + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; if (frameCountStillNeeded == 0) break; else drflac__seek_to_first_frame((drflac *)music.ctxData); } @@ -1978,7 +1981,6 @@ void UpdateMusicStream(Music music) if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream); else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)AUDIO.System.pcmBuffer, framesToStream); - //jar_xm_reset((jar_xm_context_t *)music.ctxData); } break; @@ -1988,7 +1990,6 @@ void UpdateMusicStream(Music music) { // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0); - //jar_mod_seek_start((jar_mod_context_t *)music.ctxData); } break; @@ -2056,7 +2057,7 @@ float GetMusicTimePlayed(Music music) float secondsPlayed = 0.0f; if (music.stream.buffer != NULL) { - #if defined(SUPPORT_FILEFORMAT_XM) +#if defined(SUPPORT_FILEFORMAT_XM) if (music.ctxType == MUSIC_MODULE_XM) { uint64_t framesPlayed = 0; @@ -2065,7 +2066,7 @@ float GetMusicTimePlayed(Music music) secondsPlayed = (float)framesPlayed/music.stream.sampleRate; } else - #endif +#endif { //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; int framesProcessed = (int)music.stream.buffer->framesProcessed; @@ -2115,7 +2116,7 @@ AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, un } // Checks if an audio stream is ready -RLAPI bool IsAudioStreamReady(AudioStream stream) +bool IsAudioStreamReady(AudioStream stream) { return ((stream.buffer != NULL) && // Validate stream buffer (stream.sampleRate > 0) && // Validate sample rate is supported @@ -2277,6 +2278,7 @@ void AttachAudioStreamProcessor(AudioStream stream, AudioCallback process) ma_mutex_unlock(&AUDIO.System.lock); } +// Remove processor from audio stream void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process) { ma_mutex_lock(&AUDIO.System.lock); @@ -2304,9 +2306,8 @@ void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process) } // Add processor to audio pipeline. Order of processors is important -// Works the same way as {Attach,Detach}AudioStreamProcessor functions, except -// these two work on the already mixed output just before sending it to the -// sound hardware. +// Works the same way as {Attach,Detach}AudioStreamProcessor() functions, except +// these two work on the already mixed output just before sending it to the sound hardware void AttachAudioMixedProcessor(AudioCallback process) { ma_mutex_lock(&AUDIO.System.lock); @@ -2330,6 +2331,7 @@ void AttachAudioMixedProcessor(AudioCallback process) ma_mutex_unlock(&AUDIO.System.lock); } +// Remove processor from audio pipeline void DetachAudioMixedProcessor(AudioCallback process) { ma_mutex_lock(&AUDIO.System.lock); @@ -2508,7 +2510,6 @@ static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, f return totalOutputFramesProcessed; } - // Sending audio data to device callback function // This function will be called when miniaudio needs more data // NOTE: All the mixing takes place here