Merge branch 'master' into event-recording-enhancements

This commit is contained in:
Matthew Owens 2023-03-06 15:21:34 +00:00
commit aa769ab1c4
No known key found for this signature in database
GPG Key ID: 8431780B61AAC7A9
10 changed files with 452 additions and 139 deletions

View File

@ -297,17 +297,17 @@ ifeq ($(PLATFORM),PLATFORM_WEB)
# --preload-file resources # specify a resources folder for data compilation # --preload-file resources # specify a resources folder for data compilation
# --source-map-base # allow debugging in browser with source map # --source-map-base # allow debugging in browser with source map
LDFLAGS += -s USE_GLFW=3 -s TOTAL_MEMORY=$(BUILD_WEB_HEAP_SIZE) -s FORCE_FILESYSTEM=1 LDFLAGS += -s USE_GLFW=3 -s TOTAL_MEMORY=$(BUILD_WEB_HEAP_SIZE) -s FORCE_FILESYSTEM=1
# Build using asyncify # Build using asyncify
ifeq ($(BUILD_WEB_ASYNCIFY),TRUE) ifeq ($(BUILD_WEB_ASYNCIFY),TRUE)
LDFLAGS += -s ASYNCIFY LDFLAGS += -s ASYNCIFY
endif endif
# Add resources building if required # Add resources building if required
ifeq ($(BUILD_WEB_RESOURCES),TRUE) ifeq ($(BUILD_WEB_RESOURCES),TRUE)
LDFLAGS += --preload-file $(BUILD_WEB_RESOURCES_PATH) LDFLAGS += --preload-file $(BUILD_WEB_RESOURCES_PATH)
endif endif
# Add debug mode flags if required # Add debug mode flags if required
ifeq ($(BUILD_MODE),DEBUG) ifeq ($(BUILD_MODE),DEBUG)
LDFLAGS += -s ASSERTIONS=1 --profiling LDFLAGS += -s ASSERTIONS=1 --profiling
@ -316,7 +316,7 @@ ifeq ($(PLATFORM),PLATFORM_WEB)
# Define a custom shell .html and output extension # Define a custom shell .html and output extension
LDFLAGS += --shell-file $(BUILD_WEB_SHELL) LDFLAGS += --shell-file $(BUILD_WEB_SHELL)
EXT = .html EXT = .html
# NOTE: Simple raylib examples are compiled to be interpreter with asyncify, that way, # NOTE: Simple raylib examples are compiled to be interpreter with asyncify, that way,
# we can compile same code for ALL platforms with no change required, but, working on bigger # we can compile same code for ALL platforms with no change required, but, working on bigger
# projects, code needs to be refactored to avoid a blocking while() loop, moving Update and Draw # projects, code needs to be refactored to avoid a blocking while() loop, moving Update and Draw
@ -354,7 +354,7 @@ ifeq ($(PLATFORM),PLATFORM_DESKTOP)
ifeq ($(RAYLIB_LIBTYPE),SHARED) ifeq ($(RAYLIB_LIBTYPE),SHARED)
LDLIBS += -lc LDLIBS += -lc
endif endif
# NOTE: On ARM 32bit arch, miniaudio requires atomics library # NOTE: On ARM 32bit arch, miniaudio requires atomics library
LDLIBS += -latomic LDLIBS += -latomic
endif endif

Binary file not shown.

View File

@ -15,6 +15,11 @@
#include "raylib.h" #include "raylib.h"
#include <stdlib.h> // Required for: calloc(), free()
#define MAX_FILEPATH_RECORDED 4096
#define MAX_FILEPATH_SIZE 2048
//------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------
// Program main entry point // Program main entry point
//------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------
@ -27,7 +32,14 @@ int main(void)
InitWindow(screenWidth, screenHeight, "raylib [core] example - drop files"); InitWindow(screenWidth, screenHeight, "raylib [core] example - drop files");
FilePathList droppedFiles = { 0 }; int filePathCounter = 0;
char *filePaths[MAX_FILEPATH_RECORDED] = { 0 }; // We will register a maximum of filepaths
// Allocate space for the required file paths
for (int i = 0; i < MAX_FILEPATH_RECORDED; i++)
{
filePaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_SIZE, 1);
}
SetTargetFPS(60); // Set our game to run at 60 frames-per-second SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
@ -39,11 +51,18 @@ int main(void)
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
if (IsFileDropped()) if (IsFileDropped())
{ {
// Is some files have been previously loaded, unload them FilePathList droppedFiles = LoadDroppedFiles();
if (droppedFiles.count > 0) UnloadDroppedFiles(droppedFiles);
for (int i = 0, offset = filePathCounter; i < droppedFiles.count; i++)
// Load new dropped files {
droppedFiles = LoadDroppedFiles(); if (filePathCounter < (MAX_FILEPATH_RECORDED - 1))
{
TextCopy(filePaths[offset + i], droppedFiles.paths[i]);
filePathCounter++;
}
}
UnloadDroppedFiles(droppedFiles); // Unload filepaths from memory
} }
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@ -53,20 +72,20 @@ int main(void)
ClearBackground(RAYWHITE); ClearBackground(RAYWHITE);
if (droppedFiles.count == 0) DrawText("Drop your files to this window!", 100, 40, 20, DARKGRAY); if (filePathCounter == 0) DrawText("Drop your files to this window!", 100, 40, 20, DARKGRAY);
else else
{ {
DrawText("Dropped files:", 100, 40, 20, DARKGRAY); DrawText("Dropped files:", 100, 40, 20, DARKGRAY);
for (unsigned int i = 0; i < droppedFiles.count; i++) for (unsigned int i = 0; i < filePathCounter; i++)
{ {
if (i%2 == 0) DrawRectangle(0, 85 + 40*i, screenWidth, 40, Fade(LIGHTGRAY, 0.5f)); if (i%2 == 0) DrawRectangle(0, 85 + 40*i, screenWidth, 40, Fade(LIGHTGRAY, 0.5f));
else DrawRectangle(0, 85 + 40*i, screenWidth, 40, Fade(LIGHTGRAY, 0.3f)); else DrawRectangle(0, 85 + 40*i, screenWidth, 40, Fade(LIGHTGRAY, 0.3f));
DrawText(droppedFiles.paths[i], 120, 100 + 40*i, 10, GRAY); DrawText(filePaths[i], 120, 100 + 40*i, 10, GRAY);
} }
DrawText("Drop new files...", 100, 110 + 40*droppedFiles.count, 20, DARKGRAY); DrawText("Drop new files...", 100, 110 + 40*filePathCounter, 20, DARKGRAY);
} }
EndDrawing(); EndDrawing();
@ -75,7 +94,10 @@ int main(void)
// De-Initialization // De-Initialization
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
UnloadDroppedFiles(droppedFiles); // Unload files memory for (int i = 0; i < MAX_FILEPATH_RECORDED; i++)
{
RL_FREE(filePaths[i]); // Free allocated memory for all filepaths
}
CloseWindow(); // Close window and OpenGL context CloseWindow(); // Close window and OpenGL context
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------

View File

@ -214,7 +214,7 @@
#define SUPPORT_FILEFORMAT_WAV 1 #define SUPPORT_FILEFORMAT_WAV 1
#define SUPPORT_FILEFORMAT_OGG 1 #define SUPPORT_FILEFORMAT_OGG 1
#define SUPPORT_FILEFORMAT_MP3 1 #define SUPPORT_FILEFORMAT_MP3 1
//#define SUPPORT_FILEFORMAT_QOA 1 #define SUPPORT_FILEFORMAT_QOA 1
//#define SUPPORT_FILEFORMAT_FLAC 1 //#define SUPPORT_FILEFORMAT_FLAC 1
#define SUPPORT_FILEFORMAT_XM 1 #define SUPPORT_FILEFORMAT_XM 1
#define SUPPORT_FILEFORMAT_MOD 1 #define SUPPORT_FILEFORMAT_MOD 1

14
src/external/qoa.h vendored
View File

@ -22,12 +22,12 @@ not in the file header. A decoder may peek into the first frame of the file to
find these values. find these values.
In a valid QOA file all frames have the same number of channels and the same 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 samplerate. These restrictions may be relaxed for streaming. This remains to
be decided. be decided.
All values in a QOA file are BIG ENDIAN. Luckily, EVERYTHING in a QOA file, 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 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. just a read_u64() that does the byte swapping if necessary.
In pseudocode, the file layout is as follows: In pseudocode, the file layout is as follows:
@ -66,7 +66,7 @@ Wheras the 64bit qoa_slice_t is defined as follows:
`sf_index` defines the scalefactor to use for this slice as an index into the `sf_index` defines the scalefactor to use for this slice as an index into the
qoa_scalefactor_tab[16] qoa_scalefactor_tab[16]
`r00`--`r19` are the residuals for the individiual samples, divided by the `r00`--`r19` are the residuals for the individual samples, divided by the
scalefactor and quantized by the qoa_quant_tab[]. scalefactor and quantized by the qoa_quant_tab[].
In the decoder, a prediction of the next sample is computed by multiplying the In the decoder, a prediction of the next sample is computed by multiplying the
@ -153,7 +153,7 @@ typedef unsigned long long qoa_uint64_t;
/* The quant_tab provides an index into the dequant_tab for residuals in the /* 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 range of -8 .. 8. It maps this range to just 3bits and becomes less accurate at
the higher end. Note that the residual zero is identical to the lowest positive 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 value. This is mostly fine, since the qoa_div() function always rounds away
from zero. */ from zero. */
@ -169,7 +169,7 @@ static int qoa_quant_tab[17] = {
less accurate at the higher end. In theory, the highest scalefactor that we 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 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 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 residual of one quarter of the 16 bit range is high sufficient. I.e. with the
scalefactor 2048 times the quant range of 8 we can encode residuals up to 2**14. scalefactor 2048 times the quant range of 8 we can encode residuals up to 2**14.
The scalefactor values are computed as: The scalefactor values are computed as:
@ -230,7 +230,7 @@ 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 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 subtracts the residual to each weight, based on the corresponding sample from
the history. This, suprisingly, is sufficent to get worthwhile predictions. the history. This, surprisingly, is sufficient to get worthwhile predictions.
This is all done with fixed point integers. Hence the right-shifts when updating This is all done with fixed point integers. Hence the right-shifts when updating
the weights and calculating the prediction. */ the weights and calculating the prediction. */
@ -369,7 +369,7 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
int dequantized = qoa_dequant_tab[scalefactor][quantized]; int dequantized = qoa_dequant_tab[scalefactor][quantized];
int reconstructed = qoa_clamp(predicted + dequantized, -32768, 32767); int reconstructed = qoa_clamp(predicted + dequantized, -32768, 32767);
int error = (sample - reconstructed); long long error = (sample - reconstructed);
current_error += error * error; current_error += error * error;
if (current_error > best_error) { if (current_error > best_error) {
break; break;

278
src/external/qoaplay.c vendored Normal file
View File

@ -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;
}

View File

@ -168,6 +168,10 @@ typedef struct tagBITMAPINFOHEADER {
#define MA_NO_WAV #define MA_NO_WAV
#define MA_NO_FLAC #define MA_NO_FLAC
#define MA_NO_MP3 #define MA_NO_MP3
// Threading model: Default: [0] COINIT_MULTITHREADED: COM calls objects on any thread (free threading)
#define MA_COINIT_VALUE 2 // [2] COINIT_APARTMENTTHREADED: Each object has its own thread (apartment model)
#define MINIAUDIO_IMPLEMENTATION #define MINIAUDIO_IMPLEMENTATION
//#define MA_DEBUG_OUTPUT //#define MA_DEBUG_OUTPUT
#include "external/miniaudio.h" // Audio device initialization and management #include "external/miniaudio.h" // Audio device initialization and management
@ -226,6 +230,7 @@ typedef struct tagBITMAPINFOHEADER {
#define QOA_IMPLEMENTATION #define QOA_IMPLEMENTATION
#include "external/qoa.h" // QOA loading and saving functions #include "external/qoa.h" // QOA loading and saving functions
#include "external/qoaplay.c" // QOA stream playing helper functions
#endif #endif
#if defined(SUPPORT_FILEFORMAT_FLAC) #if defined(SUPPORT_FILEFORMAT_FLAC)
@ -283,21 +288,6 @@ typedef struct tagBITMAPINFOHEADER {
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Types and Structures Definition // 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) #if defined(RAUDIO_STANDALONE)
// Trace log level // Trace log level
// NOTE: Organized by priority level // NOTE: Organized by priority level
@ -313,6 +303,20 @@ typedef enum {
} TraceLogLevel; } TraceLogLevel;
#endif #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 // NOTE: Different logic is used when feeding data to the playback device
// depending on whether data is streamed (Music vs Sound) // depending on whether data is streamed (Music vs Sound)
typedef enum { typedef enum {
@ -999,13 +1003,17 @@ bool ExportWave(Wave wave, const char *fileName)
#if defined(SUPPORT_FILEFORMAT_QOA) #if defined(SUPPORT_FILEFORMAT_QOA)
else if (IsFileExtension(fileName, ".qoa")) else if (IsFileExtension(fileName, ".qoa"))
{ {
qoa_desc qoa = { 0 }; if (wave.sampleSize == 16)
qoa.channels = wave.channels; {
qoa.samplerate = wave.sampleRate; qoa_desc qoa = { 0 };
qoa.samples = wave.frameCount; qoa.channels = wave.channels;
qoa.samplerate = wave.sampleRate;
qoa.samples = wave.frameCount;
// TODO: Review wave.data format required for export int bytesWritten = qoa_write(fileName, wave.data, &qoa);
success = qoa_write(fileName, wave.data, &qoa); if (bytesWritten > 0) success = true;
}
else TRACELOG(LOG_WARNING, "AUDIO: Wave data must be 16 bit per sample for QOA format export");
} }
#endif #endif
else if (IsFileExtension(fileName, ".raw")) else if (IsFileExtension(fileName, ".raw"))
@ -1314,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 // Load music stream from file
@ -1387,21 +1395,16 @@ Music LoadMusicStream(const char *fileName)
#if defined(SUPPORT_FILEFORMAT_QOA) #if defined(SUPPORT_FILEFORMAT_QOA)
else if (IsFileExtension(fileName, ".qoa")) else if (IsFileExtension(fileName, ".qoa"))
{ {
qoa_desc *ctxQoa = RL_CALLOC(1, sizeof(qoa_desc)); qoaplay_desc *ctxQoa = qoaplay_open(fileName);
// TODO: QOA stream support: Init context from file
int result = 0;
music.ctxType = MUSIC_AUDIO_QOA; music.ctxType = MUSIC_AUDIO_QOA;
music.ctxData = ctxQoa; music.ctxData = ctxQoa;
if (result > 0) if (ctxQoa->file != NULL)
{ {
music.stream = LoadAudioStream(ctxQoa->samplerate, 16, ctxQoa->channels); // NOTE: We are loading samples are 32bit float normalized data, so,
// we configure the output audio stream to also use float 32bit
// TODO: Read next frame(s) from QOA stream music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels);
//music.frameCount = qoa_decode_frame(const unsigned char *bytes, unsigned int size, ctxQoa, short *sample_data, unsigned int *frame_len); music.frameCount = ctxQoa->info.samples;
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1586,21 +1589,16 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data,
#if defined(SUPPORT_FILEFORMAT_QOA) #if defined(SUPPORT_FILEFORMAT_QOA)
else if (strcmp(fileType, ".qoa") == 0) else if (strcmp(fileType, ".qoa") == 0)
{ {
qoa_desc *ctxQoa = RL_CALLOC(1, sizeof(qoa_desc)); qoaplay_desc *ctxQoa = qoaplay_open_memory(data, dataSize);
// TODO: Init QOA context data
int result = 0;
music.ctxType = MUSIC_AUDIO_QOA; music.ctxType = MUSIC_AUDIO_QOA;
music.ctxData = ctxQoa; music.ctxData = ctxQoa;
if (result > 0) if ((ctxQoa->file_data != NULL) && (ctxQoa->file_data_size != 0))
{ {
music.stream = LoadAudioStream(ctxQoa->samplerate, 16, ctxQoa->channels); // NOTE: We are loading samples are 32bit float normalized data, so,
// we configure the output audio stream to also use float 32bit
// TODO: Read next frame(s) from QOA stream music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels);
//music.frameCount = qoa_decode_frame(const unsigned char *bytes, unsigned int size, ctxQoa, short *sample_data, unsigned int *frame_len); music.frameCount = ctxQoa->info.samples;
music.looping = true; // Looping enabled by default music.looping = true; // Looping enabled by default
musicLoaded = true; musicLoaded = true;
} }
@ -1689,27 +1687,27 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data,
if (!musicLoaded) if (!musicLoaded)
{ {
if (false) { } if (false) { }
#if defined(SUPPORT_FILEFORMAT_WAV) #if defined(SUPPORT_FILEFORMAT_WAV)
else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData);
#endif #endif
#if defined(SUPPORT_FILEFORMAT_OGG) #if defined(SUPPORT_FILEFORMAT_OGG)
else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData);
#endif #endif
#if defined(SUPPORT_FILEFORMAT_MP3) #if defined(SUPPORT_FILEFORMAT_MP3)
else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); }
#endif #endif
#if defined(SUPPORT_FILEFORMAT_QOA) #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 #endif
#if defined(SUPPORT_FILEFORMAT_FLAC) #if defined(SUPPORT_FILEFORMAT_FLAC)
else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL);
#endif #endif
#if defined(SUPPORT_FILEFORMAT_XM) #if defined(SUPPORT_FILEFORMAT_XM)
else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData);
#endif #endif
#if defined(SUPPORT_FILEFORMAT_MOD) #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); } 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; music.ctxData = NULL;
TRACELOG(LOG_WARNING, "FILEIO: Music data could not be loaded"); TRACELOG(LOG_WARNING, "FILEIO: Music data could not be loaded");
@ -1755,7 +1753,7 @@ void UnloadMusicStream(Music music)
else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); }
#endif #endif
#if defined(SUPPORT_FILEFORMAT_QOA) #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 #endif
#if defined(SUPPORT_FILEFORMAT_FLAC) #if defined(SUPPORT_FILEFORMAT_FLAC)
else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL);
@ -1813,7 +1811,7 @@ void StopMusicStream(Music music)
case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break; case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break;
#endif #endif
#if defined(SUPPORT_FILEFORMAT_QOA) #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 #endif
#if defined(SUPPORT_FILEFORMAT_FLAC) #if defined(SUPPORT_FILEFORMAT_FLAC)
case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break; case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break;
@ -1848,7 +1846,7 @@ void SeekMusicStream(Music music, float position)
case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break; case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break;
#endif #endif
#if defined(SUPPORT_FILEFORMAT_QOA) #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 #endif
#if defined(SUPPORT_FILEFORMAT_FLAC) #if defined(SUPPORT_FILEFORMAT_FLAC)
case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break; case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break;
@ -1884,11 +1882,13 @@ void UpdateMusicStream(Music music)
unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed
unsigned int framesToStream = 0; // Total frames to be streamed unsigned int framesToStream = 0; // Total frames to be streamed
if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames; if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames;
else framesToStream = framesLeft; else framesToStream = framesLeft;
int frameCountStillNeeded = framesToStream; int frameCountStillNeeded = framesToStream;
int frameCountRedTotal = 0; int frameCountReadTotal = 0;
switch (music.ctxType) switch (music.ctxType)
{ {
#if defined(SUPPORT_FILEFORMAT_WAV) #if defined(SUPPORT_FILEFORMAT_WAV)
@ -1898,8 +1898,8 @@ void UpdateMusicStream(Music music)
{ {
while (true) while (true)
{ {
int frameCountRed = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); int frameCountRed = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
frameCountRedTotal += frameCountRed; frameCountReadTotal += frameCountRed;
frameCountStillNeeded -= frameCountRed; frameCountStillNeeded -= frameCountRed;
if (frameCountStillNeeded == 0) break; if (frameCountStillNeeded == 0) break;
else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData);
@ -1909,8 +1909,8 @@ void UpdateMusicStream(Music music)
{ {
while (true) while (true)
{ {
int frameCountRed = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); int frameCountRed = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
frameCountRedTotal += frameCountRed; frameCountReadTotal += frameCountRed;
frameCountStillNeeded -= frameCountRed; frameCountStillNeeded -= frameCountRed;
if (frameCountStillNeeded == 0) break; if (frameCountStillNeeded == 0) break;
else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData);
@ -1923,8 +1923,8 @@ void UpdateMusicStream(Music music)
{ {
while (true) 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); 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);
frameCountRedTotal += frameCountRed; frameCountReadTotal += frameCountRed;
frameCountStillNeeded -= frameCountRed; frameCountStillNeeded -= frameCountRed;
if (frameCountStillNeeded == 0) break; if (frameCountStillNeeded == 0) break;
else stb_vorbis_seek_start((stb_vorbis *)music.ctxData); else stb_vorbis_seek_start((stb_vorbis *)music.ctxData);
@ -1936,9 +1936,9 @@ void UpdateMusicStream(Music music)
{ {
while (true) while (true)
{ {
int frameCountRed = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); int frameCountRead = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
frameCountRedTotal += frameCountRed; frameCountReadTotal += frameCountRead;
frameCountStillNeeded -= frameCountRed; frameCountStillNeeded -= frameCountRead;
if (frameCountStillNeeded == 0) break; if (frameCountStillNeeded == 0) break;
else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData);
} }
@ -1947,7 +1947,18 @@ void UpdateMusicStream(Music music)
#if defined(SUPPORT_FILEFORMAT_QOA) #if defined(SUPPORT_FILEFORMAT_QOA)
case MUSIC_AUDIO_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; } break;
#endif #endif
#if defined(SUPPORT_FILEFORMAT_FLAC) #if defined(SUPPORT_FILEFORMAT_FLAC)
@ -1955,9 +1966,9 @@ void UpdateMusicStream(Music music)
{ {
while (true) while (true)
{ {
int frameCountRed = drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountRedTotal*frameSize)); int frameCountRead = drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
frameCountRedTotal += frameCountRed; frameCountReadTotal += frameCountRead;
frameCountStillNeeded -= frameCountRed; frameCountStillNeeded -= frameCountRead;
if (frameCountStillNeeded == 0) break; if (frameCountStillNeeded == 0) break;
else drflac__seek_to_first_frame((drflac *)music.ctxData); else drflac__seek_to_first_frame((drflac *)music.ctxData);
} }
@ -1970,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); 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_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); 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); //jar_xm_reset((jar_xm_context_t *)music.ctxData);
} break; } break;
@ -1980,7 +1990,6 @@ void UpdateMusicStream(Music music)
{ {
// NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 // 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_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0);
//jar_mod_seek_start((jar_mod_context_t *)music.ctxData); //jar_mod_seek_start((jar_mod_context_t *)music.ctxData);
} break; } break;
@ -2048,7 +2057,7 @@ float GetMusicTimePlayed(Music music)
float secondsPlayed = 0.0f; float secondsPlayed = 0.0f;
if (music.stream.buffer != NULL) if (music.stream.buffer != NULL)
{ {
#if defined(SUPPORT_FILEFORMAT_XM) #if defined(SUPPORT_FILEFORMAT_XM)
if (music.ctxType == MUSIC_MODULE_XM) if (music.ctxType == MUSIC_MODULE_XM)
{ {
uint64_t framesPlayed = 0; uint64_t framesPlayed = 0;
@ -2057,7 +2066,7 @@ float GetMusicTimePlayed(Music music)
secondsPlayed = (float)framesPlayed/music.stream.sampleRate; secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
} }
else 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; //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; int framesProcessed = (int)music.stream.buffer->framesProcessed;
@ -2107,7 +2116,7 @@ AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, un
} }
// Checks if an audio stream is ready // Checks if an audio stream is ready
RLAPI bool IsAudioStreamReady(AudioStream stream) bool IsAudioStreamReady(AudioStream stream)
{ {
return ((stream.buffer != NULL) && // Validate stream buffer return ((stream.buffer != NULL) && // Validate stream buffer
(stream.sampleRate > 0) && // Validate sample rate is supported (stream.sampleRate > 0) && // Validate sample rate is supported
@ -2269,6 +2278,7 @@ void AttachAudioStreamProcessor(AudioStream stream, AudioCallback process)
ma_mutex_unlock(&AUDIO.System.lock); ma_mutex_unlock(&AUDIO.System.lock);
} }
// Remove processor from audio stream
void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process) void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process)
{ {
ma_mutex_lock(&AUDIO.System.lock); ma_mutex_lock(&AUDIO.System.lock);
@ -2296,9 +2306,8 @@ void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process)
} }
// Add processor to audio pipeline. Order of processors is important // Add processor to audio pipeline. Order of processors is important
// Works the same way as {Attach,Detach}AudioStreamProcessor functions, except // Works the same way as {Attach,Detach}AudioStreamProcessor() functions, except
// these two work on the already mixed output just before sending it to the // these two work on the already mixed output just before sending it to the sound hardware
// sound hardware.
void AttachAudioMixedProcessor(AudioCallback process) void AttachAudioMixedProcessor(AudioCallback process)
{ {
ma_mutex_lock(&AUDIO.System.lock); ma_mutex_lock(&AUDIO.System.lock);
@ -2322,6 +2331,7 @@ void AttachAudioMixedProcessor(AudioCallback process)
ma_mutex_unlock(&AUDIO.System.lock); ma_mutex_unlock(&AUDIO.System.lock);
} }
// Remove processor from audio pipeline
void DetachAudioMixedProcessor(AudioCallback process) void DetachAudioMixedProcessor(AudioCallback process)
{ {
ma_mutex_lock(&AUDIO.System.lock); ma_mutex_lock(&AUDIO.System.lock);
@ -2500,7 +2510,6 @@ static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, f
return totalOutputFramesProcessed; return totalOutputFramesProcessed;
} }
// Sending audio data to device callback function // Sending audio data to device callback function
// This function will be called when miniaudio needs more data // This function will be called when miniaudio needs more data
// NOTE: All the mixing takes place here // NOTE: All the mixing takes place here

View File

@ -160,7 +160,7 @@ Matrix GetCameraProjectionMatrix(Camera* camera, float aspect);
// MatrixOrtho() // MatrixOrtho()
// MatrixIdentity() // MatrixIdentity()
// raylib required functionality: // raylib required functionality:
// GetMouseDelta() // GetMouseDelta()
// GetMouseWheelMove() // GetMouseWheelMove()
// IsKeyDown() // IsKeyDown()
@ -223,7 +223,7 @@ Vector3 GetCameraRight(Camera *camera)
{ {
Vector3 forward = GetCameraForward(camera); Vector3 forward = GetCameraForward(camera);
Vector3 up = GetCameraUp(camera); Vector3 up = GetCameraUp(camera);
return Vector3CrossProduct(forward, up); return Vector3CrossProduct(forward, up);
} }
@ -251,7 +251,7 @@ void CameraMoveForward(Camera *camera, float distance, bool moveInWorldPlane)
void CameraMoveUp(Camera *camera, float distance) void CameraMoveUp(Camera *camera, float distance)
{ {
Vector3 up = GetCameraUp(camera); Vector3 up = GetCameraUp(camera);
// Scale by distance // Scale by distance
up = Vector3Scale(up, distance); up = Vector3Scale(up, distance);
@ -410,7 +410,7 @@ Matrix GetCameraProjectionMatrix(Camera *camera, float aspect)
return MatrixOrtho(-right, right, -top, top, CAMERA_CULL_DISTANCE_NEAR, CAMERA_CULL_DISTANCE_FAR); return MatrixOrtho(-right, right, -top, top, CAMERA_CULL_DISTANCE_NEAR, CAMERA_CULL_DISTANCE_FAR);
} }
return MatrixIdentity(); return MatrixIdentity();
} }
@ -425,7 +425,7 @@ void UpdateCamera(Camera *camera, int mode)
bool rotateAroundTarget = ((mode == CAMERA_THIRD_PERSON) || (mode == CAMERA_ORBITAL)); bool rotateAroundTarget = ((mode == CAMERA_THIRD_PERSON) || (mode == CAMERA_ORBITAL));
bool lockView = ((mode == CAMERA_FIRST_PERSON) || (mode == CAMERA_THIRD_PERSON) || (mode == CAMERA_ORBITAL)); bool lockView = ((mode == CAMERA_FIRST_PERSON) || (mode == CAMERA_THIRD_PERSON) || (mode == CAMERA_ORBITAL));
bool rotateUp = (mode == CAMERA_FREE); bool rotateUp = (mode == CAMERA_FREE);
if (mode == CAMERA_ORBITAL) if (mode == CAMERA_ORBITAL)
{ {
// Orbital can just orbit // Orbital can just orbit
@ -446,7 +446,7 @@ void UpdateCamera(Camera *camera, int mode)
CameraYaw(camera, -mousePositionDelta.x*CAMERA_MOUSE_MOVE_SENSITIVITY, rotateAroundTarget); CameraYaw(camera, -mousePositionDelta.x*CAMERA_MOUSE_MOVE_SENSITIVITY, rotateAroundTarget);
CameraPitch(camera, -mousePositionDelta.y*CAMERA_MOUSE_MOVE_SENSITIVITY, lockView, rotateAroundTarget, rotateUp); CameraPitch(camera, -mousePositionDelta.y*CAMERA_MOUSE_MOVE_SENSITIVITY, lockView, rotateAroundTarget, rotateUp);
// Camera movement // Camera movement
if (IsKeyDown(KEY_W)) CameraMoveForward(camera, CAMERA_MOVE_SPEED, moveInWorldPlane); if (IsKeyDown(KEY_W)) CameraMoveForward(camera, CAMERA_MOVE_SPEED, moveInWorldPlane);
if (IsKeyDown(KEY_A)) CameraMoveRight(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane); if (IsKeyDown(KEY_A)) CameraMoveRight(camera, -CAMERA_MOVE_SPEED, moveInWorldPlane);

View File

@ -1558,7 +1558,7 @@ void ClearWindowState(unsigned int flags)
// Set icon for window (only PLATFORM_DESKTOP) // Set icon for window (only PLATFORM_DESKTOP)
// NOTE 1: Image must be in RGBA format, 8bit per channel // NOTE 1: Image must be in RGBA format, 8bit per channel
// NOTE 2: Image is scaled by the OS for all required sizes // NOTE 2: Image is scaled by the OS for all required sizes
void SetWindowIcon(Image image) void SetWindowIcon(Image image)
{ {
#if defined(PLATFORM_DESKTOP) #if defined(PLATFORM_DESKTOP)
@ -1589,7 +1589,7 @@ void SetWindowIcon(Image image)
// Set icon for window (multiple images, only PLATFORM_DESKTOP) // Set icon for window (multiple images, only PLATFORM_DESKTOP)
// NOTE 1: Images must be in RGBA format, 8bit per channel // NOTE 1: Images must be in RGBA format, 8bit per channel
// NOTE 2: The multiple images are used depending on provided sizes // NOTE 2: The multiple images are used depending on provided sizes
// Standard Windows icon sizes: 256, 128, 96, 64, 48, 32, 24, 16 // Standard Windows icon sizes: 256, 128, 96, 64, 48, 32, 24, 16
void SetWindowIcons(Image *images, int count) void SetWindowIcons(Image *images, int count)
{ {
#if defined(PLATFORM_DESKTOP) #if defined(PLATFORM_DESKTOP)
@ -1716,12 +1716,13 @@ void *GetWindowHandle(void)
// NOTE: Returned handle is: void *HWND (windows.h) // NOTE: Returned handle is: void *HWND (windows.h)
return glfwGetWin32Window(CORE.Window.handle); return glfwGetWin32Window(CORE.Window.handle);
#endif #endif
#if defined(__linux__) #if defined(PLATFORM_DESKTOP) && defined(__linux__)
// NOTE: Returned handle is: unsigned long Window (X.h) // NOTE: Returned handle is: unsigned long Window (X.h)
// typedef unsigned long XID; // typedef unsigned long XID;
// typedef XID Window; // typedef XID Window;
//unsigned long id = (unsigned long)glfwGetX11Window(window); //unsigned long id = (unsigned long)glfwGetX11Window(window);
return NULL; // TODO: Find a way to return value... cast to void *? //return NULL; // TODO: Find a way to return value... cast to void *?
return (void *)CORE.Window.handle;
#endif #endif
#if defined(__APPLE__) #if defined(__APPLE__)
// NOTE: Returned handle is: (objc_object *) // NOTE: Returned handle is: (objc_object *)
@ -2615,7 +2616,7 @@ bool IsShaderReady(Shader shader)
// The following locations are tried to be set automatically (locs[i] >= 0), // The following locations are tried to be set automatically (locs[i] >= 0),
// any of them can be checked for validation but the only mandatory one is, afaik, SHADER_LOC_VERTEX_POSITION // any of them can be checked for validation but the only mandatory one is, afaik, SHADER_LOC_VERTEX_POSITION
// NOTE: Users can also setup manually their own attributes/uniforms and do not used the default raylib ones // NOTE: Users can also setup manually their own attributes/uniforms and do not used the default raylib ones
// Vertex shader attribute locations (default) // Vertex shader attribute locations (default)
// shader.locs[SHADER_LOC_VERTEX_POSITION] // Set by default internal shader // shader.locs[SHADER_LOC_VERTEX_POSITION] // Set by default internal shader
// shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] // Set by default internal shader // shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] // Set by default internal shader
@ -5633,25 +5634,28 @@ static void CursorEnterCallback(GLFWwindow *window, int enter)
// GLFW3 Window Drop Callback, runs when drop files into window // GLFW3 Window Drop Callback, runs when drop files into window
static void WindowDropCallback(GLFWwindow *window, int count, const char **paths) static void WindowDropCallback(GLFWwindow *window, int count, const char **paths)
{ {
// In case previous dropped filepaths have not been freed, we free them if (count > 0)
if (CORE.Window.dropFileCount > 0)
{ {
for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]); // In case previous dropped filepaths have not been freed, we free them
if (CORE.Window.dropFileCount > 0)
{
for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) RL_FREE(CORE.Window.dropFilepaths[i]);
RL_FREE(CORE.Window.dropFilepaths); RL_FREE(CORE.Window.dropFilepaths);
CORE.Window.dropFileCount = 0; CORE.Window.dropFileCount = 0;
CORE.Window.dropFilepaths = NULL; CORE.Window.dropFilepaths = NULL;
} }
// WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy // WARNING: Paths are freed by GLFW when the callback returns, we must keep an internal copy
CORE.Window.dropFileCount = count; CORE.Window.dropFileCount = count;
CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *)); CORE.Window.dropFilepaths = (char **)RL_CALLOC(CORE.Window.dropFileCount, sizeof(char *));
for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++) for (unsigned int i = 0; i < CORE.Window.dropFileCount; i++)
{ {
CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); CORE.Window.dropFilepaths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char));
strcpy(CORE.Window.dropFilepaths[i], paths[i]); strcpy(CORE.Window.dropFilepaths[i], paths[i]);
}
} }
} }
#endif #endif

View File

@ -320,7 +320,7 @@ Image LoadImageAnim(const char *fileName, int *frames)
#else #else
if (false) { } if (false) { }
#endif #endif
else else
{ {
image = LoadImage(fileName); image = LoadImage(fileName);
frameCount = 1; frameCount = 1;
@ -507,7 +507,7 @@ Image LoadImageFromScreen(void)
bool IsImageReady(Image image) bool IsImageReady(Image image)
{ {
return ((image.data != NULL) && // Validate pixel data available return ((image.data != NULL) && // Validate pixel data available
(image.width > 0) && (image.width > 0) &&
(image.height > 0) && // Validate image size (image.height > 0) && // Validate image size
(image.format > 0) && // Validate image format (image.format > 0) && // Validate image format
(image.mipmaps > 0)); // Validate image mipmaps (at least 1 for basic mipmap level) (image.mipmaps > 0)); // Validate image mipmaps (at least 1 for basic mipmap level)
@ -3340,10 +3340,10 @@ RenderTexture2D LoadRenderTexture(int width, int height)
bool IsTextureReady(Texture2D texture) bool IsTextureReady(Texture2D texture)
{ {
// TODO: Validate maximum texture size supported by GPU? // TODO: Validate maximum texture size supported by GPU?
return ((texture.id > 0) && // Validate OpenGL id return ((texture.id > 0) && // Validate OpenGL id
(texture.width > 0) && (texture.width > 0) &&
(texture.height > 0) && // Validate texture size (texture.height > 0) && // Validate texture size
(texture.format > 0) && // Validate texture pixel format (texture.format > 0) && // Validate texture pixel format
(texture.mipmaps > 0)); // Validate texture mipmaps (at least 1 for basic mipmap level) (texture.mipmaps > 0)); // Validate texture mipmaps (at least 1 for basic mipmap level)
} }