0/**********************************************************************************************
1*
2* raudio v1.1 - A simple and easy-to-use audio library based on miniaudio
3*
4* FEATURES:
5* - Manage audio device (init/close)
6* - Manage raw audio context
7* - Manage mixing channels
8* - Load and unload audio files
9* - Format wave data (sample rate, size, channels)
10* - Play/Stop/Pause/Resume loaded audio
11*
12* CONFIGURATION:
13* #define SUPPORT_MODULE_RAUDIO
14* raudio module is included in the build
15*
16* #define RAUDIO_STANDALONE
17* Define to use the module as standalone library (independently of raylib)
18* Required types and functions are defined in the same module
19*
20* #define SUPPORT_FILEFORMAT_WAV
21* #define SUPPORT_FILEFORMAT_OGG
22* #define SUPPORT_FILEFORMAT_MP3
23* #define SUPPORT_FILEFORMAT_QOA
24* #define SUPPORT_FILEFORMAT_FLAC
25* #define SUPPORT_FILEFORMAT_XM
26* #define SUPPORT_FILEFORMAT_MOD
27* Selected desired fileformats to be supported for loading. Some of those formats are
28* supported by default, to remove support, just comment unrequired #define in this module
29*
30* DEPENDENCIES:
31* miniaudio.h - Audio device management lib (https://github.com/mackron/miniaudio)
32* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/)
33* dr_wav.h - WAV audio files loading (http://github.com/mackron/dr_libs)
34* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs)
35* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs)
36* jar_xm.h - XM module file loading
37* jar_mod.h - MOD audio file loading
38*
39* CONTRIBUTORS:
40* David Reid (github: @mackron) (Nov. 2017):
41* - Complete port to miniaudio library
42*
43* Joshua Reisenauer (github: @kd7tck) (2015):
44* - XM audio module support (jar_xm)
45* - MOD audio module support (jar_mod)
46* - Mixing channels support
47* - Raw audio context support
48*
49*
50* LICENSE: zlib/libpng
51*
52* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5)
53*
54* This software is provided "as-is", without any express or implied warranty. In no event
55* will the authors be held liable for any damages arising from the use of this software.
56*
57* Permission is granted to anyone to use this software for any purpose, including commercial
58* applications, and to alter it and redistribute it freely, subject to the following restrictions:
59*
60* 1. The origin of this software must not be misrepresented; you must not claim that you
61* wrote the original software. If you use this software in a product, an acknowledgment
62* in the product documentation would be appreciated but is not required.
63*
64* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
65* as being the original software.
66*
67* 3. This notice may not be removed or altered from any source distribution.
68*
69**********************************************************************************************/
71#if defined(RAUDIO_STANDALONE)
72 #include "raudio.h"
73#else
74 #include "raylib.h" // Declares module functions
76 // Check if config flags have been externally provided on compilation line
77 #if !defined(EXTERNAL_CONFIG_FLAGS)
78 #include "config.h" // Defines module configuration flags
79 #endif
80 #include "utils.h" // Required for: fopen() Android mapping
81#endif
83#if defined(SUPPORT_MODULE_RAUDIO) || defined(RAUDIO_STANDALONE)
85#if defined(_WIN32)
86// To avoid conflicting windows.h symbols with raylib, some flags are defined
87// WARNING: Those flags avoid inclusion of some Win32 headers that could be required
88// by user at some point and won't be included...
89//-------------------------------------------------------------------------------------
91// If defined, the following flags inhibit definition of the indicated items
92#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_
93#define NOVIRTUALKEYCODES // VK_*
94#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_*
95#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_*
96#define NOSYSMETRICS // SM_*
97#define NOMENUS // MF_*
98#define NOICONS // IDI_*
99#define NOKEYSTATES // MK_*
100#define NOSYSCOMMANDS // SC_*
101#define NORASTEROPS // Binary and Tertiary raster ops
102#define NOSHOWWINDOW // SW_*
103#define OEMRESOURCE // OEM Resource values
104#define NOATOM // Atom Manager routines
105#define NOCLIPBOARD // Clipboard routines
106#define NOCOLOR // Screen colors
107#define NOCTLMGR // Control and Dialog routines
108#define NODRAWTEXT // DrawText() and DT_*
109#define NOGDI // All GDI defines and routines
110#define NOKERNEL // All KERNEL defines and routines
111#define NOUSER // All USER defines and routines
112//#define NONLS // All NLS defines and routines
113#define NOMB // MB_* and MessageBox()
114#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines
115#define NOMETAFILE // typedef METAFILEPICT
116#define NOMINMAX // Macros min(a,b) and max(a,b)
117#define NOMSG // typedef MSG and associated routines
118#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_*
119#define NOSCROLL // SB_* and scrolling routines
120#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc.
121#define NOSOUND // Sound driver routines
122#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines
123#define NOWH // SetWindowsHook and WH_*
124#define NOWINOFFSETS // GWL_*, GCL_*, associated routines
125#define NOCOMM // COMM driver routines
126#define NOKANJI // Kanji support stuff
127#define NOHELP // Help engine interface
128#define NOPROFILER // Profiler interface
129#define NODEFERWINDOWPOS // DeferWindowPos routines
130#define NOMCX // Modem Configuration Extensions
132// Type required before windows.h inclusion
133typedef struct tagMSG *LPMSG;
135#include <windows.h> // Windows functionality (miniaudio)
137// Type required by some unused function...
138typedef struct tagBITMAPINFOHEADER {
139 DWORD biSize;
140 LONG biWidth;
141 LONG biHeight;
142 WORD biPlanes;
143 WORD biBitCount;
144 DWORD biCompression;
145 DWORD biSizeImage;
146 LONG biXPelsPerMeter;
147 LONG biYPelsPerMeter;
148 DWORD biClrUsed;
149 DWORD biClrImportant;
150} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
152#include <objbase.h> // Component Object Model (COM) header
153#include <mmreg.h> // Windows Multimedia, defines some WAVE structs
154#include <mmsystem.h> // Windows Multimedia, used by Windows GDI, defines DIBINDEX macro
156// Some required types defined for MSVC/TinyC compiler
157#if defined(_MSC_VER) || defined(__TINYC__)
158 #include "propidl.h"
159#endif
160#endif
162#define MA_MALLOC RL_MALLOC
163#define MA_FREE RL_FREE
165#define MA_NO_JACK
166#define MA_NO_WAV
167#define MA_NO_FLAC
168#define MA_NO_MP3
169#define MA_NO_RESOURCE_MANAGER
170#define MA_NO_NODE_GRAPH
171#define MA_NO_ENGINE
172#define MA_NO_GENERATION
174// Threading model: Default: [0] COINIT_MULTITHREADED: COM calls objects on any thread (free threading)
175#define MA_COINIT_VALUE 2 // [2] COINIT_APARTMENTTHREADED: Each object has its own thread (apartment model)
177#define MINIAUDIO_IMPLEMENTATION
178//#define MA_DEBUG_OUTPUT
179#include "external/miniaudio.h" // Audio device initialization and management
180#undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro
182#include <stdlib.h> // Required for: malloc(), free()
183#include <stdio.h> // Required for: FILE, fopen(), fclose(), fread()
184#include <string.h> // Required for: strcmp() [Used in IsFileExtension(), LoadWaveFromMemory(), LoadMusicStreamFromMemory()]
186#if defined(RAUDIO_STANDALONE)
187 #ifndef TRACELOG
188 #define TRACELOG(level, ...) printf(__VA_ARGS__)
189 #endif
191 // Allow custom memory allocators
192 #ifndef RL_MALLOC
193 #define RL_MALLOC(sz) malloc(sz)
194 #endif
195 #ifndef RL_CALLOC
196 #define RL_CALLOC(n,sz) calloc(n,sz)
197 #endif
198 #ifndef RL_REALLOC
199 #define RL_REALLOC(ptr,sz) realloc(ptr,sz)
200 #endif
201 #ifndef RL_FREE
202 #define RL_FREE(ptr) free(ptr)
203 #endif
204#endif
206#if defined(SUPPORT_FILEFORMAT_WAV)
207 #define DRWAV_MALLOC RL_MALLOC
208 #define DRWAV_REALLOC RL_REALLOC
209 #define DRWAV_FREE RL_FREE
211 #define DR_WAV_IMPLEMENTATION
212 #include "external/dr_wav.h" // WAV loading functions
213#endif
215#if defined(SUPPORT_FILEFORMAT_OGG)
216 // TODO: Remap stb_vorbis malloc()/free() calls to RL_MALLOC/RL_FREE
217 #include "external/stb_vorbis.c" // OGG loading functions
218#endif
220#if defined(SUPPORT_FILEFORMAT_MP3)
221 #define DRMP3_MALLOC RL_MALLOC
222 #define DRMP3_REALLOC RL_REALLOC
223 #define DRMP3_FREE RL_FREE
225 #define DR_MP3_IMPLEMENTATION
226 #include "external/dr_mp3.h" // MP3 loading functions
227#endif
229#if defined(SUPPORT_FILEFORMAT_QOA)
230 #define QOA_MALLOC RL_MALLOC
231 #define QOA_FREE RL_FREE
233 #if defined(_MSC_VER) // Disable some MSVC warning
234 #pragma warning(push)
235 #pragma warning(disable : 4018)
236 #pragma warning(disable : 4267)
237 #pragma warning(disable : 4244)
238 #endif
240 #define QOA_IMPLEMENTATION
241 #include "external/qoa.h" // QOA loading and saving functions
242 #include "external/qoaplay.c" // QOA stream playing helper functions
244 #if defined(_MSC_VER)
245 #pragma warning(pop) // Disable MSVC warning suppression
246 #endif
247#endif
249#if defined(SUPPORT_FILEFORMAT_FLAC)
250 #define DRFLAC_MALLOC RL_MALLOC
251 #define DRFLAC_REALLOC RL_REALLOC
252 #define DRFLAC_FREE RL_FREE
254 #define DR_FLAC_IMPLEMENTATION
255 #define DR_FLAC_NO_WIN32_IO
256 #include "external/dr_flac.h" // FLAC loading functions
257#endif
259#if defined(SUPPORT_FILEFORMAT_XM)
260 #define JARXM_MALLOC RL_MALLOC
261 #define JARXM_FREE RL_FREE
263 #if defined(_MSC_VER) // Disable some MSVC warning
264 #pragma warning(push)
265 #pragma warning(disable : 4244)
266 #endif
268 #define JAR_XM_IMPLEMENTATION
269 #include "external/jar_xm.h" // XM loading functions
271 #if defined(_MSC_VER)
272 #pragma warning(pop) // Disable MSVC warning suppression
273 #endif
274#endif
276#if defined(SUPPORT_FILEFORMAT_MOD)
277 #define JARMOD_MALLOC RL_MALLOC
278 #define JARMOD_FREE RL_FREE
280 #define JAR_MOD_IMPLEMENTATION
281 #include "external/jar_mod.h" // MOD loading functions
282#endif
284//----------------------------------------------------------------------------------
285// Defines and Macros
286//----------------------------------------------------------------------------------
287#ifndef AUDIO_DEVICE_FORMAT
288 #define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (float-32bit)
289#endif
290#ifndef AUDIO_DEVICE_CHANNELS
291 #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo
292#endif
293#ifndef AUDIO_DEVICE_SAMPLE_RATE
294 #define AUDIO_DEVICE_SAMPLE_RATE 0 // Device output sample rate
295#endif
297#ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS
298 #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels
299#endif
301//----------------------------------------------------------------------------------
302// Types and Structures Definition
303//----------------------------------------------------------------------------------
304#if defined(RAUDIO_STANDALONE)
305// Trace log level
306// NOTE: Organized by priority level
307typedef enum {
308 LOG_ALL = 0, // Display all logs
309 LOG_TRACE, // Trace logging, intended for internal use only
310 LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds
311 LOG_INFO, // Info logging, used for program execution info
312 LOG_WARNING, // Warning logging, used on recoverable failures
313 LOG_ERROR, // Error logging, used on unrecoverable failures
314 LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE)
315 LOG_NONE // Disable logging
316} TraceLogLevel;
317#endif
319// Music context type
320// NOTE: Depends on data structure provided by the library
321// in charge of reading the different file types
322typedef enum {
323 MUSIC_AUDIO_NONE = 0, // No audio context loaded
324 MUSIC_AUDIO_WAV, // WAV audio context
325 MUSIC_AUDIO_OGG, // OGG audio context
326 MUSIC_AUDIO_FLAC, // FLAC audio context
327 MUSIC_AUDIO_MP3, // MP3 audio context
328 MUSIC_AUDIO_QOA, // QOA audio context
329 MUSIC_MODULE_XM, // XM module audio context
330 MUSIC_MODULE_MOD // MOD module audio context
331} MusicContextType;
333// NOTE: Different logic is used when feeding data to the playback device
334// depending on whether data is streamed (Music vs Sound)
335typedef enum {
336 AUDIO_BUFFER_USAGE_STATIC = 0,
337 AUDIO_BUFFER_USAGE_STREAM
338} AudioBufferUsage;
340// Audio buffer struct
341struct rAudioBuffer {
342 ma_data_converter converter; // Audio data converter
344 AudioCallback callback; // Audio buffer callback for buffer filling on audio threads
345 rAudioProcessor *processor; // Audio processor
347 float volume; // Audio buffer volume
348 float pitch; // Audio buffer pitch
349 float pan; // Audio buffer pan (0.0f to 1.0f)
351 bool playing; // Audio buffer state: AUDIO_PLAYING
352 bool paused; // Audio buffer state: AUDIO_PAUSED
353 bool looping; // Audio buffer looping, default to true for AudioStreams
354 int usage; // Audio buffer usage mode: STATIC or STREAM
356 bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer)
357 unsigned int sizeInFrames; // Total buffer size in frames
358 unsigned int frameCursorPos; // Frame cursor position
359 unsigned int framesProcessed; // Total frames processed in this buffer (required for play timing)
361 unsigned char *data; // Data buffer, on music stream keeps filling
363 rAudioBuffer *next; // Next audio buffer on the list
364 rAudioBuffer *prev; // Previous audio buffer on the list
365};
367// Audio processor struct
368// NOTE: Useful to apply effects to an AudioBuffer
369struct rAudioProcessor {
370 AudioCallback process; // Processor callback function
371 rAudioProcessor *next; // Next audio processor on the list
372 rAudioProcessor *prev; // Previous audio processor on the list
373};
375#define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision
377// Audio data context
378typedef struct AudioData {
379 struct {
380 ma_context context; // miniaudio context data
381 ma_device device; // miniaudio device
382 ma_mutex lock; // miniaudio mutex lock
383 bool isReady; // Check if audio device is ready
384 size_t pcmBufferSize; // Pre-allocated buffer size
385 void *pcmBuffer; // Pre-allocated buffer to read audio data from file/memory
386 } System;
387 struct {
388 AudioBuffer *first; // Pointer to first AudioBuffer in the list
389 AudioBuffer *last; // Pointer to last AudioBuffer in the list
390 int defaultSize; // Default audio buffer size for audio streams
391 } Buffer;
392 rAudioProcessor *mixedProcessor;
393} AudioData;
395//----------------------------------------------------------------------------------
396// Global Variables Definition
397//----------------------------------------------------------------------------------
398static AudioData AUDIO = { // Global AUDIO context
400 // NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number
401 // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a
402 // standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough
403 // In case of music-stalls, just increase this number
404 .Buffer.defaultSize = 0,
405 .mixedProcessor = NULL
406};
408//----------------------------------------------------------------------------------
409// Module Internal Functions Declaration
410//----------------------------------------------------------------------------------
411static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage);
413// Reads audio data from an AudioBuffer object in internal/device formats
414static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount);
415static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount);
417static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount);
418static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer);
420static bool IsAudioBufferPlayingInLockedState(AudioBuffer *buffer);
421static void StopAudioBufferInLockedState(AudioBuffer *buffer);
422static void UpdateAudioStreamInLockedState(AudioStream stream, const void *data, int frameCount);
424#if defined(RAUDIO_STANDALONE)
425static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension
426static const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes the dot: .png)
427static const char *GetFileName(const char *filePath); // Get pointer to filename for a path string
428static const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string)
430static unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read)
431static bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write)
432static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated
433#endif
435//----------------------------------------------------------------------------------
436// AudioBuffer management functions declaration
437// NOTE: Those functions are not exposed by raylib... for the moment
438//----------------------------------------------------------------------------------
439AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage);
440void UnloadAudioBuffer(AudioBuffer *buffer);
442bool IsAudioBufferPlaying(AudioBuffer *buffer);
443void PlayAudioBuffer(AudioBuffer *buffer);
444void StopAudioBuffer(AudioBuffer *buffer);
445void PauseAudioBuffer(AudioBuffer *buffer);
446void ResumeAudioBuffer(AudioBuffer *buffer);
447void SetAudioBufferVolume(AudioBuffer *buffer, float volume);
448void SetAudioBufferPitch(AudioBuffer *buffer, float pitch);
449void SetAudioBufferPan(AudioBuffer *buffer, float pan);
450void TrackAudioBuffer(AudioBuffer *buffer);
451void UntrackAudioBuffer(AudioBuffer *buffer);
453//----------------------------------------------------------------------------------
454// Module Functions Definition - Audio Device initialization and Closing
455//----------------------------------------------------------------------------------
457// Initialize audio device
458void InitAudioDevice(void)
459{
460 // Init audio context
461 ma_context_config ctxConfig = ma_context_config_init();
462 ma_log_callback_init(OnLog, NULL);
464 ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context);
465 if (result != MA_SUCCESS)
466 {
467 TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context");
468 return;
469 }
471 // Init audio device
472 // NOTE: Using the default device. Format is floating point because it simplifies mixing
473 ma_device_config config = ma_device_config_init(ma_device_type_playback);
474 config.playback.pDeviceID = NULL; // NULL for the default playback AUDIO.System.device
475 config.playback.format = AUDIO_DEVICE_FORMAT;
476 config.playback.channels = AUDIO_DEVICE_CHANNELS;
477 config.capture.pDeviceID = NULL; // NULL for the default capture AUDIO.System.device
478 config.capture.format = ma_format_s16;
479 config.capture.channels = 1;
480 config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE;
481 config.dataCallback = OnSendAudioDataToDevice;
482 config.pUserData = NULL;
484 result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device);
485 if (result != MA_SUCCESS)
486 {
487 TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device");
488 ma_context_uninit(&AUDIO.System.context);
489 return;
490 }
492 // Mixing happens on a separate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may
493 // want to look at something a bit smarter later on to keep everything real-time, if that's necessary
494 if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS)
495 {
496 TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing");
497 ma_device_uninit(&AUDIO.System.device);
498 ma_context_uninit(&AUDIO.System.context);
499 return;
500 }
502 // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running
503 // while there's at least one sound being played
504 result = ma_device_start(&AUDIO.System.device);
505 if (result != MA_SUCCESS)
506 {
507 TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device");
508 ma_device_uninit(&AUDIO.System.device);
509 ma_context_uninit(&AUDIO.System.context);
510 return;
511 }
513 TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully");
514 TRACELOG(LOG_INFO, " > Backend: miniaudio | %s", ma_get_backend_name(AUDIO.System.context.backend));
515 TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat));
516 TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.System.device.playback.channels, AUDIO.System.device.playback.internalChannels);
517 TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate);
518 TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods);
520 AUDIO.System.isReady = true;
521}
523// Close the audio device for all contexts
524void CloseAudioDevice(void)
525{
526 if (AUDIO.System.isReady)
527 {
528 ma_mutex_uninit(&AUDIO.System.lock);
529 ma_device_uninit(&AUDIO.System.device);
530 ma_context_uninit(&AUDIO.System.context);
532 AUDIO.System.isReady = false;
533 RL_FREE(AUDIO.System.pcmBuffer);
534 AUDIO.System.pcmBuffer = NULL;
535 AUDIO.System.pcmBufferSize = 0;
537 TRACELOG(LOG_INFO, "AUDIO: Device closed successfully");
538 }
539 else TRACELOG(LOG_WARNING, "AUDIO: Device could not be closed, not currently initialized");
540}
542// Check if device has been initialized successfully
543bool IsAudioDeviceReady(void)
544{
545 return AUDIO.System.isReady;
546}
548// Set master volume (listener)
549void SetMasterVolume(float volume)
550{
551 ma_device_set_master_volume(&AUDIO.System.device, volume);
552}
554// Get master volume (listener)
555float GetMasterVolume(void)
556{
557 float volume = 0.0f;
558 ma_device_get_master_volume(&AUDIO.System.device, &volume);
559 return volume;
560}
562//----------------------------------------------------------------------------------
563// Module Functions Definition - Audio Buffer management
564//----------------------------------------------------------------------------------
566// Initialize a new audio buffer (filled with silence)
567AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage)
568{
569 AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer));
571 if (audioBuffer == NULL)
572 {
573 TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer");
574 return NULL;
575 }
577 if (sizeInFrames > 0) audioBuffer->data = (unsigned char *)RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1);
579 // Audio data runs through a format converter
580 ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate);
581 converterConfig.allowDynamicSampleRate = true;
583 ma_result result = ma_data_converter_init(&converterConfig, NULL, &audioBuffer->converter);
585 if (result != MA_SUCCESS)
586 {
587 TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline");
588 RL_FREE(audioBuffer);
589 return NULL;
590 }
592 // Init audio buffer values
593 audioBuffer->volume = 1.0f;
594 audioBuffer->pitch = 1.0f;
595 audioBuffer->pan = 0.0f; // Center
597 audioBuffer->callback = NULL;
598 audioBuffer->processor = NULL;
600 audioBuffer->playing = false;
601 audioBuffer->paused = false;
602 audioBuffer->looping = false;
604 audioBuffer->usage = usage;
605 audioBuffer->frameCursorPos = 0;
606 audioBuffer->framesProcessed = 0;
607 audioBuffer->sizeInFrames = sizeInFrames;
609 // Buffers should be marked as processed by default so that a call to
610 // UpdateAudioStream() immediately after initialization works correctly
611 audioBuffer->isSubBufferProcessed[0] = true;
612 audioBuffer->isSubBufferProcessed[1] = true;
614 // Track audio buffer to linked list next position
615 TrackAudioBuffer(audioBuffer);
617 return audioBuffer;
618}
620// Delete an audio buffer
621void UnloadAudioBuffer(AudioBuffer *buffer)
622{
623 if (buffer != NULL)
624 {
625 UntrackAudioBuffer(buffer);
626 ma_data_converter_uninit(&buffer->converter, NULL);
627 RL_FREE(buffer->data);
628 RL_FREE(buffer);
629 }
630}
632// Check if an audio buffer is playing from a program state without lock
633bool IsAudioBufferPlaying(AudioBuffer *buffer)
634{
635 bool result = false;
636 ma_mutex_lock(&AUDIO.System.lock);
637 result = IsAudioBufferPlayingInLockedState(buffer);
638 ma_mutex_unlock(&AUDIO.System.lock);
639 return result;
640}
642// Play an audio buffer
643// NOTE: Buffer is restarted to the start
644// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained
645void PlayAudioBuffer(AudioBuffer *buffer)
646{
647 if (buffer != NULL)
648 {
649 ma_mutex_lock(&AUDIO.System.lock);
650 buffer->playing = true;
651 buffer->paused = false;
652 buffer->frameCursorPos = 0;
653 buffer->framesProcessed = 0;
654 buffer->isSubBufferProcessed[0] = true;
655 buffer->isSubBufferProcessed[1] = true;
656 ma_mutex_unlock(&AUDIO.System.lock);
657 }
658}
660// Stop an audio buffer from a program state without lock
661void StopAudioBuffer(AudioBuffer *buffer)
662{
663 ma_mutex_lock(&AUDIO.System.lock);
664 StopAudioBufferInLockedState(buffer);
665 ma_mutex_unlock(&AUDIO.System.lock);
666}
668// Pause an audio buffer
669void PauseAudioBuffer(AudioBuffer *buffer)
670{
671 if (buffer != NULL)
672 {
673 ma_mutex_lock(&AUDIO.System.lock);
674 buffer->paused = true;
675 ma_mutex_unlock(&AUDIO.System.lock);
676 }
677}
679// Resume an audio buffer
680void ResumeAudioBuffer(AudioBuffer *buffer)
681{
682 if (buffer != NULL)
683 {
684 ma_mutex_lock(&AUDIO.System.lock);
685 buffer->paused = false;
686 ma_mutex_unlock(&AUDIO.System.lock);
687 }
688}
690// Set volume for an audio buffer
691void SetAudioBufferVolume(AudioBuffer *buffer, float volume)
692{
693 if (buffer != NULL)
694 {
695 ma_mutex_lock(&AUDIO.System.lock);
696 buffer->volume = volume;
697 ma_mutex_unlock(&AUDIO.System.lock);
698 }
699}
701// Set pitch for an audio buffer
702void SetAudioBufferPitch(AudioBuffer *buffer, float pitch)
703{
704 if ((buffer != NULL) && (pitch > 0.0f))
705 {
706 ma_mutex_lock(&AUDIO.System.lock);
707 // Pitching is just an adjustment of the sample rate
708 // Note that this changes the duration of the sound:
709 // - higher pitches will make the sound faster
710 // - lower pitches make it slower
711 ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.sampleRateOut/pitch);
712 ma_data_converter_set_rate(&buffer->converter, buffer->converter.sampleRateIn, outputSampleRate);
714 buffer->pitch = pitch;
715 ma_mutex_unlock(&AUDIO.System.lock);
716 }
717}
719// Set pan for an audio buffer
720void SetAudioBufferPan(AudioBuffer *buffer, float pan)
721{
722 if (pan < -1.0f) pan = -1.0f;
723 else if (pan > 1.0f) pan = 1.0f;
725 if (buffer != NULL)
726 {
727 ma_mutex_lock(&AUDIO.System.lock);
728 buffer->pan = pan;
729 ma_mutex_unlock(&AUDIO.System.lock);
730 }
731}
733// Track audio buffer to linked list next position
734void TrackAudioBuffer(AudioBuffer *buffer)
735{
736 ma_mutex_lock(&AUDIO.System.lock);
737 {
738 if (AUDIO.Buffer.first == NULL) AUDIO.Buffer.first = buffer;
739 else
740 {
741 AUDIO.Buffer.last->next = buffer;
742 buffer->prev = AUDIO.Buffer.last;
743 }
745 AUDIO.Buffer.last = buffer;
746 }
747 ma_mutex_unlock(&AUDIO.System.lock);
748}
750// Untrack audio buffer from linked list
751void UntrackAudioBuffer(AudioBuffer *buffer)
752{
753 ma_mutex_lock(&AUDIO.System.lock);
754 {
755 if (buffer->prev == NULL) AUDIO.Buffer.first = buffer->next;
756 else buffer->prev->next = buffer->next;
758 if (buffer->next == NULL) AUDIO.Buffer.last = buffer->prev;
759 else buffer->next->prev = buffer->prev;
761 buffer->prev = NULL;
762 buffer->next = NULL;
763 }
764 ma_mutex_unlock(&AUDIO.System.lock);
765}
767//----------------------------------------------------------------------------------
768// Module Functions Definition - Sounds loading and playing (.WAV)
769//----------------------------------------------------------------------------------
771// Load wave data from file
772Wave LoadWave(const char *fileName)
773{
774 Wave wave = { 0 };
776 // Loading file to memory
777 int dataSize = 0;
778 unsigned char *fileData = LoadFileData(fileName, &dataSize);
780 // Loading wave from memory data
781 if (fileData != NULL) wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, dataSize);
783 UnloadFileData(fileData);
785 return wave;
786}
788// Load wave from memory buffer, fileType refers to extension: i.e. ".wav"
789// WARNING: File extension must be provided in lower-case
790Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize)
791{
792 Wave wave = { 0 };
794 if (false) { }
795#if defined(SUPPORT_FILEFORMAT_WAV)
796 else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0))
797 {
798 drwav wav = { 0 };
799 bool success = drwav_init_memory(&wav, fileData, dataSize, NULL);
801 if (success)
802 {
803 wave.frameCount = (unsigned int)wav.totalPCMFrameCount;
804 wave.sampleRate = wav.sampleRate;
805 wave.sampleSize = 16;
806 wave.channels = wav.channels;
807 wave.data = (short *)RL_MALLOC((size_t)wave.frameCount*wave.channels*sizeof(short));
809 // NOTE: We are forcing conversion to 16bit sample size on reading
810 drwav_read_pcm_frames_s16(&wav, wave.frameCount, (drwav_int16 *)wave.data);
811 }
812 else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data");
814 drwav_uninit(&wav);
815 }
816#endif
817#if defined(SUPPORT_FILEFORMAT_OGG)
818 else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0))
819 {
820 stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, dataSize, NULL, NULL);
822 if (oggData != NULL)
823 {
824 stb_vorbis_info info = stb_vorbis_get_info(oggData);
826 wave.sampleRate = info.sample_rate;
827 wave.sampleSize = 16; // By default, ogg data is 16 bit per sample (short)
828 wave.channels = info.channels;
829 wave.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData); // NOTE: It returns frames!
830 wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short));
832 // NOTE: Get the number of samples to process (be careful! we ask for number of shorts, not bytes!)
833 stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.frameCount*wave.channels);
834 stb_vorbis_close(oggData);
835 }
836 else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data");
837 }
838#endif
839#if defined(SUPPORT_FILEFORMAT_MP3)
840 else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0))
841 {
842 drmp3_config config = { 0 };
843 unsigned long long int totalFrameCount = 0;
845 // NOTE: We are forcing conversion to 32bit float sample size on reading
846 wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, dataSize, &config, &totalFrameCount, NULL);
847 wave.sampleSize = 32;
849 if (wave.data != NULL)
850 {
851 wave.channels = config.channels;
852 wave.sampleRate = config.sampleRate;
853 wave.frameCount = (int)totalFrameCount;
854 }
855 else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data");
857 }
858#endif
859#if defined(SUPPORT_FILEFORMAT_QOA)
860 else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0))
861 {
862 qoa_desc qoa = { 0 };
864 // NOTE: Returned sample data is always 16 bit?
865 wave.data = qoa_decode(fileData, dataSize, &qoa);
866 wave.sampleSize = 16;
868 if (wave.data != NULL)
869 {
870 wave.channels = qoa.channels;
871 wave.sampleRate = qoa.samplerate;
872 wave.frameCount = qoa.samples;
873 }
874 else TRACELOG(LOG_WARNING, "WAVE: Failed to load QOA data");
876 }
877#endif
878#if defined(SUPPORT_FILEFORMAT_FLAC)
879 else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0))
880 {
881 unsigned long long int totalFrameCount = 0;
883 // NOTE: We are forcing conversion to 16bit sample size on reading
884 wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL);
885 wave.sampleSize = 16;
887 if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount;
888 else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data");
889 }
890#endif
891 else TRACELOG(LOG_WARNING, "WAVE: Data format not supported");
893 TRACELOG(LOG_INFO, "WAVE: Data loaded successfully (%i Hz, %i bit, %i channels)", wave.sampleRate, wave.sampleSize, wave.channels);
895 return wave;
896}
898// Checks if wave data is valid (data loaded and parameters)
899bool IsWaveValid(Wave wave)
900{
901 bool result = false;
903 if ((wave.data != NULL) && // Validate wave data available
904 (wave.frameCount > 0) && // Validate frame count
905 (wave.sampleRate > 0) && // Validate sample rate is supported
906 (wave.sampleSize > 0) && // Validate sample size is supported
907 (wave.channels > 0)) result = true; // Validate number of channels supported
909 return result;
910}
912// Load sound from file
913// NOTE: The entire file is loaded to memory to be played (no-streaming)
914Sound LoadSound(const char *fileName)
915{
916 Wave wave = LoadWave(fileName);
918 Sound sound = LoadSoundFromWave(wave);
920 UnloadWave(wave); // Sound is loaded, we can unload wave
922 return sound;
923}
925// Load sound from wave data
926// NOTE: Wave data must be unallocated manually
927Sound LoadSoundFromWave(Wave wave)
928{
929 Sound sound = { 0 };
931 if (wave.data != NULL)
932 {
933 // When using miniaudio we need to do our own mixing
934 // To simplify this we need convert the format of each sound to be consistent with
935 // the format used to open the playback AUDIO.System.device. We can do this two ways:
936 //
937 // 1) Convert the whole sound in one go at load time (here)
938 // 2) Convert the audio data in chunks at mixing time
939 //
940 // First option has been selected, format conversion is done on the loading stage
941 // The downside is that it uses more memory if the original sound is u8 or s16
942 ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32));
943 ma_uint32 frameCountIn = wave.frameCount;
945 ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate);
946 if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion");
948 AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC);
949 if (audioBuffer == NULL)
950 {
951 TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer");
952 return sound; // early return to avoid dereferencing the audioBuffer null pointer
953 }
955 frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate);
956 if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion");
958 sound.frameCount = frameCount;
959 sound.stream.sampleRate = AUDIO.System.device.sampleRate;
960 sound.stream.sampleSize = 32;
961 sound.stream.channels = AUDIO_DEVICE_CHANNELS;
962 sound.stream.buffer = audioBuffer;
963 }
965 return sound;
966}
968// Clone sound from existing sound data, clone does not own wave data
969// NOTE: Wave data must be unallocated manually and will be shared across all clones
970Sound LoadSoundAlias(Sound source)
971{
972 Sound sound = { 0 };
974 if (source.stream.buffer->data != NULL)
975 {
976 AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC);
978 if (audioBuffer == NULL)
979 {
980 TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer");
981 return sound; // Early return to avoid dereferencing the audioBuffer null pointer
982 }
984 audioBuffer->sizeInFrames = source.stream.buffer->sizeInFrames;
985 audioBuffer->data = source.stream.buffer->data;
987 // Initalize the buffer as if it was new
988 audioBuffer->volume = 1.0f;
989 audioBuffer->pitch = 1.0f;
990 audioBuffer->pan = 0.0f; // Center
992 sound.frameCount = source.frameCount;
993 sound.stream.sampleRate = AUDIO.System.device.sampleRate;
994 sound.stream.sampleSize = 32;
995 sound.stream.channels = AUDIO_DEVICE_CHANNELS;
996 sound.stream.buffer = audioBuffer;
997 }
999 return sound;
1000}
1002// Checks if a sound is valid (data loaded and buffers initialized)
1003bool IsSoundValid(Sound sound)
1004{
1005 bool result = false;
1007 if ((sound.frameCount > 0) && // Validate frame count
1008 (sound.stream.buffer != NULL) && // Validate stream buffer
1009 (sound.stream.sampleRate > 0) && // Validate sample rate is supported
1010 (sound.stream.sampleSize > 0) && // Validate sample size is supported
1011 (sound.stream.channels > 0)) result = true; // Validate number of channels supported
1013 return result;
1014}
1016// Unload wave data
1017void UnloadWave(Wave wave)
1018{
1019 RL_FREE(wave.data);
1020 //TRACELOG(LOG_INFO, "WAVE: Unloaded wave data from RAM");
1021}
1023// Unload sound
1024void UnloadSound(Sound sound)
1025{
1026 UnloadAudioBuffer(sound.stream.buffer);
1027 //TRACELOG(LOG_INFO, "SOUND: Unloaded sound data from RAM");
1028}
1030void UnloadSoundAlias(Sound alias)
1031{
1032 // Untrack and unload just the sound buffer, not the sample data, it is shared with the source for the alias
1033 if (alias.stream.buffer != NULL)
1034 {
1035 UntrackAudioBuffer(alias.stream.buffer);
1036 ma_data_converter_uninit(&alias.stream.buffer->converter, NULL);
1037 RL_FREE(alias.stream.buffer);
1038 }
1039}
1041// Update sound buffer with new data
1042// PARAMS: [data], format must match sound.stream.sampleSize, default 32 bit float - stereo
1043// PARAMS: [frameCount] must not exceed sound.frameCount
1044void UpdateSound(Sound sound, const void *data, int frameCount)
1045{
1046 if (sound.stream.buffer != NULL)
1047 {
1048 StopAudioBuffer(sound.stream.buffer);
1050 memcpy(sound.stream.buffer->data, data, frameCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.formatIn, sound.stream.buffer->converter.channelsIn));
1051 }
1052}
1054// Export wave data to file
1055bool ExportWave(Wave wave, const char *fileName)
1056{
1057 bool success = false;
1059 if (false) { }
1060#if defined(SUPPORT_FILEFORMAT_WAV)
1061 else if (IsFileExtension(fileName, ".wav"))
1062 {
1063 drwav wav = { 0 };
1064 drwav_data_format format = { 0 };
1065 format.container = drwav_container_riff;
1066 if (wave.sampleSize == 32) format.format = DR_WAVE_FORMAT_IEEE_FLOAT;
1067 else format.format = DR_WAVE_FORMAT_PCM;
1068 format.channels = wave.channels;
1069 format.sampleRate = wave.sampleRate;
1070 format.bitsPerSample = wave.sampleSize;
1072 void *fileData = NULL;
1073 size_t fileDataSize = 0;
1074 success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL);
1075 if (success) success = (int)drwav_write_pcm_frames(&wav, wave.frameCount, wave.data);
1076 drwav_result result = drwav_uninit(&wav);
1078 if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize);
1080 drwav_free(fileData, NULL);
1081 }
1082#endif
1083#if defined(SUPPORT_FILEFORMAT_QOA)
1084 else if (IsFileExtension(fileName, ".qoa"))
1085 {
1086 if (wave.sampleSize == 16)
1087 {
1088 qoa_desc qoa = { 0 };
1089 qoa.channels = wave.channels;
1090 qoa.samplerate = wave.sampleRate;
1091 qoa.samples = wave.frameCount;
1093 int bytesWritten = qoa_write(fileName, (const short *)wave.data, &qoa);
1094 if (bytesWritten > 0) success = true;
1095 }
1096 else TRACELOG(LOG_WARNING, "AUDIO: Wave data must be 16 bit per sample for QOA format export");
1097 }
1098#endif
1099 else if (IsFileExtension(fileName, ".raw"))
1100 {
1101 // Export raw sample data (without header)
1102 // NOTE: It's up to the user to track wave parameters
1103 success = SaveFileData(fileName, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8);
1104 }
1106 if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName);
1107 else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave data", fileName);
1109 return success;
1110}
1112// Export wave sample data to code (.h)
1113bool ExportWaveAsCode(Wave wave, const char *fileName)
1114{
1115 bool success = false;
1117#ifndef TEXT_BYTES_PER_LINE
1118 #define TEXT_BYTES_PER_LINE 20
1119#endif
1121 int waveDataSize = wave.frameCount*wave.channels*wave.sampleSize/8;
1123 // NOTE: Text data buffer size is estimated considering wave data size in bytes
1124 // and requiring 12 char bytes for every byte; the actual size varies, but
1125 // the longest possible char being appended is "%.4ff,\n ", which is 12 bytes
1126 char *txtData = (char *)RL_CALLOC(waveDataSize*12 + 2000, sizeof(char));
1128 int byteCount = 0;
1129 byteCount += sprintf(txtData + byteCount, "\n//////////////////////////////////////////////////////////////////////////////////\n");
1130 byteCount += sprintf(txtData + byteCount, "// //\n");
1131 byteCount += sprintf(txtData + byteCount, "// WaveAsCode exporter v1.1 - Wave data exported as an array of bytes //\n");
1132 byteCount += sprintf(txtData + byteCount, "// //\n");
1133 byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n");
1134 byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n");
1135 byteCount += sprintf(txtData + byteCount, "// //\n");
1136 byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2025 Ramon Santamaria (@raysan5) //\n");
1137 byteCount += sprintf(txtData + byteCount, "// //\n");
1138 byteCount += sprintf(txtData + byteCount, "//////////////////////////////////////////////////////////////////////////////////\n\n");
1140 // Get file name from path and convert variable name to uppercase
1141 char varFileName[256] = { 0 };
1142 strncpy(varFileName, GetFileNameWithoutExt(fileName), 256 - 1);
1143 for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; }
1145 // Add wave information
1146 byteCount += sprintf(txtData + byteCount, "// Wave data information\n");
1147 byteCount += sprintf(txtData + byteCount, "#define %s_FRAME_COUNT %u\n", varFileName, wave.frameCount);
1148 byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate);
1149 byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize);
1150 byteCount += sprintf(txtData + byteCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels);
1152 // Write wave data as an array of values
1153 // Wave data is exported as byte array for 8/16bit and float array for 32bit float data
1154 // NOTE: Frame data exported is channel-interlaced: frame01[sampleChannel1, sampleChannel2, ...], frame02[], frame03[]
1155 if (wave.sampleSize == 32)
1156 {
1157 byteCount += sprintf(txtData + byteCount, "static float %s_DATA[%i] = {\n", varFileName, waveDataSize/4);
1158 for (int i = 1; i < waveDataSize/4; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.4ff,\n " : "%.4ff, "), ((float *)wave.data)[i - 1]);
1159 byteCount += sprintf(txtData + byteCount, "%.4ff };\n", ((float *)wave.data)[waveDataSize/4 - 1]);
1160 }
1161 else
1162 {
1163 byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, waveDataSize);
1164 for (int i = 1; i < waveDataSize; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n " : "0x%x, "), ((unsigned char *)wave.data)[i - 1]);
1165 byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]);
1166 }
1168 // NOTE: Text data length exported is determined by '\0' (NULL) character
1169 success = SaveFileText(fileName, txtData);
1171 RL_FREE(txtData);
1173 if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave as code exported successfully", fileName);
1174 else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave as code", fileName);
1176 return success;
1177}
1179// Play a sound
1180void PlaySound(Sound sound)
1181{
1182 PlayAudioBuffer(sound.stream.buffer);
1183}
1185// Pause a sound
1186void PauseSound(Sound sound)
1187{
1188 PauseAudioBuffer(sound.stream.buffer);
1189}
1191// Resume a paused sound
1192void ResumeSound(Sound sound)
1193{
1194 ResumeAudioBuffer(sound.stream.buffer);
1195}
1197// Stop reproducing a sound
1198void StopSound(Sound sound)
1199{
1200 StopAudioBuffer(sound.stream.buffer);
1201}
1203// Check if a sound is playing
1204bool IsSoundPlaying(Sound sound)
1205{
1206 bool result = false;
1208 if (IsAudioBufferPlaying(sound.stream.buffer)) result = true;
1210 return result;
1211}
1213// Set volume for a sound
1214void SetSoundVolume(Sound sound, float volume)
1215{
1216 SetAudioBufferVolume(sound.stream.buffer, volume);
1217}
1219// Set pitch for a sound
1220void SetSoundPitch(Sound sound, float pitch)
1221{
1222 SetAudioBufferPitch(sound.stream.buffer, pitch);
1223}
1225// Set pan for a sound
1226void SetSoundPan(Sound sound, float pan)
1227{
1228 SetAudioBufferPan(sound.stream.buffer, pan);
1229}
1231// Convert wave data to desired format
1232void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels)
1233{
1234 ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32));
1235 ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32));
1237 ma_uint32 frameCountIn = wave->frameCount;
1238 ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate);
1240 if (frameCount == 0)
1241 {
1242 TRACELOG(LOG_WARNING, "WAVE: Failed to get frame count for format conversion");
1243 return;
1244 }
1246 void *data = RL_MALLOC(frameCount*channels*(sampleSize/8));
1248 frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate);
1249 if (frameCount == 0)
1250 {
1251 RL_FREE(wave->data);
1252 TRACELOG(LOG_WARNING, "WAVE: Failed format conversion");
1253 return;
1254 }
1256 wave->frameCount = frameCount;
1257 wave->sampleSize = sampleSize;
1258 wave->sampleRate = sampleRate;
1259 wave->channels = channels;
1261 RL_FREE(wave->data);
1262 wave->data = data;
1263}
1265// Copy a wave to a new wave
1266Wave WaveCopy(Wave wave)
1267{
1268 Wave newWave = { 0 };
1270 newWave.data = RL_MALLOC(wave.frameCount*wave.channels*wave.sampleSize/8);
1272 if (newWave.data != NULL)
1273 {
1274 // NOTE: Size must be provided in bytes
1275 memcpy(newWave.data, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8);
1277 newWave.frameCount = wave.frameCount;
1278 newWave.sampleRate = wave.sampleRate;
1279 newWave.sampleSize = wave.sampleSize;
1280 newWave.channels = wave.channels;
1281 }
1283 return newWave;
1284}
1286// Crop a wave to defined frames range
1287// NOTE: Security check in case of out-of-range
1288void WaveCrop(Wave *wave, int initFrame, int finalFrame)
1289{
1290 if ((initFrame >= 0) && (initFrame < finalFrame) && ((unsigned int)finalFrame <= wave->frameCount))
1291 {
1292 int frameCount = finalFrame - initFrame;
1294 void *data = RL_MALLOC(frameCount*wave->channels*wave->sampleSize/8);
1296 memcpy(data, (unsigned char *)wave->data + (initFrame*wave->channels*wave->sampleSize/8), frameCount*wave->channels*wave->sampleSize/8);
1298 RL_FREE(wave->data);
1299 wave->data = data;
1300 wave->frameCount = (unsigned int)frameCount;
1301 }
1302 else TRACELOG(LOG_WARNING, "WAVE: Crop range out of bounds");
1303}
1305// Load samples data from wave as a floats array
1306// NOTE 1: Returned sample values are normalized to range [-1..1]
1307// NOTE 2: Sample data allocated should be freed with UnloadWaveSamples()
1308float *LoadWaveSamples(Wave wave)
1309{
1310 float *samples = (float *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(float));
1312 // NOTE: sampleCount is the total number of interlaced samples (including channels)
1314 for (unsigned int i = 0; i < wave.frameCount*wave.channels; i++)
1315 {
1316 if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 128)/128.0f;
1317 else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32768.0f;
1318 else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i];
1319 }
1321 return samples;
1322}
1324// Unload samples data loaded with LoadWaveSamples()
1325void UnloadWaveSamples(float *samples)
1326{
1327 RL_FREE(samples);
1328}
1330//----------------------------------------------------------------------------------
1331// Module Functions Definition - Music loading and stream playing
1332//----------------------------------------------------------------------------------
1334// Load music stream from file
1335Music LoadMusicStream(const char *fileName)
1336{
1337 Music music = { 0 };
1338 bool musicLoaded = false;
1340 if (false) { }
1341#if defined(SUPPORT_FILEFORMAT_WAV)
1342 else if (IsFileExtension(fileName, ".wav"))
1343 {
1344 drwav *ctxWav = (drwav *)RL_CALLOC(1, sizeof(drwav));
1345 bool success = drwav_init_file(ctxWav, fileName, NULL);
1347 if (success)
1348 {
1349 music.ctxType = MUSIC_AUDIO_WAV;
1350 music.ctxData = ctxWav;
1351 int sampleSize = ctxWav->bitsPerSample;
1352 if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream()
1354 music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels);
1355 music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount;
1356 music.looping = true; // Looping enabled by default
1357 musicLoaded = true;
1358 }
1359 else
1360 {
1361 RL_FREE(ctxWav);
1362 }
1363 }
1364#endif
1365#if defined(SUPPORT_FILEFORMAT_OGG)
1366 else if (IsFileExtension(fileName, ".ogg"))
1367 {
1368 // Open ogg audio stream
1369 stb_vorbis *ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL);
1371 if (ctxOgg != NULL)
1372 {
1373 music.ctxType = MUSIC_AUDIO_OGG;
1374 music.ctxData = ctxOgg;
1375 stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info
1377 // OGG bit rate defaults to 16 bit, it's enough for compressed format
1378 music.stream = LoadAudioStream(info.sample_rate, 16, info.channels);
1380 // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels
1381 music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
1382 music.looping = true; // Looping enabled by default
1383 musicLoaded = true;
1384 }
1385 else
1386 {
1387 stb_vorbis_close(ctxOgg);
1388 }
1389 }
1390#endif
1391#if defined(SUPPORT_FILEFORMAT_MP3)
1392 else if (IsFileExtension(fileName, ".mp3"))
1393 {
1394 drmp3 *ctxMp3 = (drmp3 *)RL_CALLOC(1, sizeof(drmp3));
1395 int result = drmp3_init_file(ctxMp3, fileName, NULL);
1397 if (result > 0)
1398 {
1399 music.ctxType = MUSIC_AUDIO_MP3;
1400 music.ctxData = ctxMp3;
1401 music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels);
1402 music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3);
1403 music.looping = true; // Looping enabled by default
1404 musicLoaded = true;
1405 }
1406 else
1407 {
1408 RL_FREE(ctxMp3);
1409 }
1410 }
1411#endif
1412#if defined(SUPPORT_FILEFORMAT_QOA)
1413 else if (IsFileExtension(fileName, ".qoa"))
1414 {
1415 qoaplay_desc *ctxQoa = qoaplay_open(fileName);
1417 if (ctxQoa != NULL)
1418 {
1419 music.ctxType = MUSIC_AUDIO_QOA;
1420 music.ctxData = ctxQoa;
1421 // NOTE: We are loading samples are 32bit float normalized data, so,
1422 // we configure the output audio stream to also use float 32bit
1423 music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels);
1424 music.frameCount = ctxQoa->info.samples;
1425 music.looping = true; // Looping enabled by default
1426 musicLoaded = true;
1427 }
1428 else{} //No uninit required
1429 }
1430#endif
1431#if defined(SUPPORT_FILEFORMAT_FLAC)
1432 else if (IsFileExtension(fileName, ".flac"))
1433 {
1434 drflac *ctxFlac = drflac_open_file(fileName, NULL);
1436 if (ctxFlac != NULL)
1437 {
1438 music.ctxType = MUSIC_AUDIO_FLAC;
1439 music.ctxData = ctxFlac;
1440 int sampleSize = ctxFlac->bitsPerSample;
1441 if (ctxFlac->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream()
1442 music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels);
1443 music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount;
1444 music.looping = true; // Looping enabled by default
1445 musicLoaded = true;
1446 }
1447 else
1448 {
1449 drflac_free(ctxFlac, NULL);
1450 }
1451 }
1452#endif
1453#if defined(SUPPORT_FILEFORMAT_XM)
1454 else if (IsFileExtension(fileName, ".xm"))
1455 {
1456 jar_xm_context_t *ctxXm = NULL;
1457 int result = jar_xm_create_context_from_file(&ctxXm, AUDIO.System.device.sampleRate, fileName);
1459 if (result == 0) // XM AUDIO.System.context created successfully
1460 {
1461 music.ctxType = MUSIC_MODULE_XM;
1462 music.ctxData = ctxXm;
1463 jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops
1465 unsigned int bits = 32;
1466 if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16;
1467 else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8;
1469 // NOTE: Only stereo is supported for XM
1470 music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS);
1471 music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo)
1472 music.looping = true; // Looping enabled by default
1473 jar_xm_reset(ctxXm); // Make sure we start at the beginning of the song
1474 musicLoaded = true;
1475 }
1476 else
1477 {
1478 jar_xm_free_context(ctxXm);
1479 }
1480 }
1481#endif
1482#if defined(SUPPORT_FILEFORMAT_MOD)
1483 else if (IsFileExtension(fileName, ".mod"))
1484 {
1485 jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_CALLOC(1, sizeof(jar_mod_context_t));
1486 jar_mod_init(ctxMod);
1487 int result = jar_mod_load_file(ctxMod, fileName);
1489 if (result > 0)
1490 {
1491 music.ctxType = MUSIC_MODULE_MOD;
1492 music.ctxData = ctxMod;
1493 // NOTE: Only stereo is supported for MOD
1494 music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS);
1495 music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo)
1496 music.looping = true; // Looping enabled by default
1497 musicLoaded = true;
1498 }
1499 else
1500 {
1501 jar_mod_unload(ctxMod);
1502 RL_FREE(ctxMod);
1503 }
1504 }
1505#endif
1506 else TRACELOG(LOG_WARNING, "STREAM: [%s] File format not supported", fileName);
1508 if (!musicLoaded)
1509 {
1510 TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", fileName);
1511 }
1512 else
1513 {
1514 // Show some music stream info
1515 TRACELOG(LOG_INFO, "FILEIO: [%s] Music file loaded successfully", fileName);
1516 TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate);
1517 TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize);
1518 TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi");
1519 TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount);
1520 }
1522 return music;
1523}
1525// Load music stream from memory buffer, fileType refers to extension: i.e. ".wav"
1526// WARNING: File extension must be provided in lower-case
1527Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize)
1528{
1529 Music music = { 0 };
1530 bool musicLoaded = false;
1532 if (false) { }
1533#if defined(SUPPORT_FILEFORMAT_WAV)
1534 else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0))
1535 {
1536 drwav *ctxWav = (drwav *)RL_CALLOC(1, sizeof(drwav));
1538 bool success = drwav_init_memory(ctxWav, (const void *)data, dataSize, NULL);
1540 if (success)
1541 {
1542 music.ctxType = MUSIC_AUDIO_WAV;
1543 music.ctxData = ctxWav;
1544 int sampleSize = ctxWav->bitsPerSample;
1545 if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream()
1547 music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels);
1548 music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount;
1549 music.looping = true; // Looping enabled by default
1550 musicLoaded = true;
1551 }
1552 else
1553 {
1554 drwav_uninit(ctxWav);
1555 RL_FREE(ctxWav);
1556 }
1557 }
1558#endif
1559#if defined(SUPPORT_FILEFORMAT_OGG)
1560 else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0))
1561 {
1562 // Open ogg audio stream
1563 stb_vorbis *ctxOgg = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL);
1565 if (ctxOgg != NULL)
1566 {
1567 music.ctxType = MUSIC_AUDIO_OGG;
1568 music.ctxData = ctxOgg;
1569 stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info
1571 // OGG bit rate defaults to 16 bit, it's enough for compressed format
1572 music.stream = LoadAudioStream(info.sample_rate, 16, info.channels);
1574 // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels
1575 music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
1576 music.looping = true; // Looping enabled by default
1577 musicLoaded = true;
1578 }
1579 else
1580 {
1581 stb_vorbis_close(ctxOgg);
1582 }
1583 }
1584#endif
1585#if defined(SUPPORT_FILEFORMAT_MP3)
1586 else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0))
1587 {
1588 drmp3 *ctxMp3 = (drmp3 *)RL_CALLOC(1, sizeof(drmp3));
1589 int success = drmp3_init_memory(ctxMp3, (const void *)data, dataSize, NULL);
1591 if (success)
1592 {
1593 music.ctxType = MUSIC_AUDIO_MP3;
1594 music.ctxData = ctxMp3;
1595 music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels);
1596 music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3);
1597 music.looping = true; // Looping enabled by default
1598 musicLoaded = true;
1599 }
1600 else
1601 {
1602 drmp3_uninit(ctxMp3);
1603 RL_FREE(ctxMp3);
1604 }
1605 }
1606#endif
1607#if defined(SUPPORT_FILEFORMAT_QOA)
1608 else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0))
1609 {
1610 qoaplay_desc *ctxQoa = NULL;
1611 if ((data != NULL) && (dataSize > 0))
1612 {
1613 ctxQoa = qoaplay_open_memory(data, dataSize);
1614 }
1616 if (ctxQoa != NULL)
1617 {
1618 music.ctxType = MUSIC_AUDIO_QOA;
1619 music.ctxData = ctxQoa;
1620 // NOTE: We are loading samples are 32bit float normalized data, so,
1621 // we configure the output audio stream to also use float 32bit
1622 music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels);
1623 music.frameCount = ctxQoa->info.samples;
1624 music.looping = true; // Looping enabled by default
1625 musicLoaded = true;
1626 }
1627 else{} //No uninit required
1628 }
1629#endif
1630#if defined(SUPPORT_FILEFORMAT_FLAC)
1631 else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0))
1632 {
1633 drflac *ctxFlac = drflac_open_memory((const void *)data, dataSize, NULL);
1635 if (ctxFlac != NULL)
1636 {
1637 music.ctxType = MUSIC_AUDIO_FLAC;
1638 music.ctxData = ctxFlac;
1639 int sampleSize = ctxFlac->bitsPerSample;
1640 if (ctxFlac->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream()
1641 music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels);
1642 music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount;
1643 music.looping = true; // Looping enabled by default
1644 musicLoaded = true;
1645 }
1646 else
1647 {
1648 drflac_free(ctxFlac, NULL);
1649 }
1650 }
1651#endif
1652#if defined(SUPPORT_FILEFORMAT_XM)
1653 else if ((strcmp(fileType, ".xm") == 0) || (strcmp(fileType, ".XM") == 0))
1654 {
1655 jar_xm_context_t *ctxXm = NULL;
1656 int result = jar_xm_create_context_safe(&ctxXm, (const char *)data, dataSize, AUDIO.System.device.sampleRate);
1657 if (result == 0) // XM AUDIO.System.context created successfully
1658 {
1659 music.ctxType = MUSIC_MODULE_XM;
1660 music.ctxData = ctxXm;
1661 jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops
1663 unsigned int bits = 32;
1664 if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16;
1665 else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8;
1667 // NOTE: Only stereo is supported for XM
1668 music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, 2);
1669 music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo)
1670 music.looping = true; // Looping enabled by default
1671 jar_xm_reset(ctxXm); // Make sure we start at the beginning of the song
1673 musicLoaded = true;
1674 }
1675 else
1676 {
1677 jar_xm_free_context(ctxXm);
1678 }
1679 }
1680#endif
1681#if defined(SUPPORT_FILEFORMAT_MOD)
1682 else if ((strcmp(fileType, ".mod") == 0) || (strcmp(fileType, ".MOD") == 0))
1683 {
1684 jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_MALLOC(sizeof(jar_mod_context_t));
1685 int result = 0;
1687 jar_mod_init(ctxMod);
1689 // Copy data to allocated memory for default UnloadMusicStream
1690 unsigned char *newData = (unsigned char *)RL_MALLOC(dataSize);
1691 int it = dataSize/sizeof(unsigned char);
1692 for (int i = 0; i < it; i++) newData[i] = data[i];
1694 // Memory loaded version for jar_mod_load_file()
1695 if (dataSize && (dataSize < 32*1024*1024))
1696 {
1697 ctxMod->modfilesize = dataSize;
1698 ctxMod->modfile = newData;
1699 if (jar_mod_load(ctxMod, (void *)ctxMod->modfile, dataSize)) result = dataSize;
1700 }
1702 if (result > 0)
1703 {
1704 music.ctxType = MUSIC_MODULE_MOD;
1705 music.ctxData = ctxMod;
1707 // NOTE: Only stereo is supported for MOD
1708 music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, 2);
1709 music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo)
1710 music.looping = true; // Looping enabled by default
1711 musicLoaded = true;
1712 }
1713 else
1714 {
1715 jar_mod_unload(ctxMod);
1716 RL_FREE(ctxMod);
1717 }
1718 }
1719#endif
1720 else TRACELOG(LOG_WARNING, "STREAM: Data format not supported");
1722 if (!musicLoaded)
1723 {
1724 TRACELOG(LOG_WARNING, "FILEIO: Music data could not be loaded");
1725 }
1726 else
1727 {
1728 // Show some music stream info
1729 TRACELOG(LOG_INFO, "FILEIO: Music data loaded successfully");
1730 TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate);
1731 TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize);
1732 TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi");
1733 TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount);
1734 }
1736 return music;
1737}
1739// Checks if a music stream is valid (context and buffers initialized)
1740bool IsMusicValid(Music music)
1741{
1742 return ((music.ctxData != NULL) && // Validate context loaded
1743 (music.frameCount > 0) && // Validate audio frame count
1744 (music.stream.sampleRate > 0) && // Validate sample rate is supported
1745 (music.stream.sampleSize > 0) && // Validate sample size is supported
1746 (music.stream.channels > 0)); // Validate number of channels supported
1747}
1749// Unload music stream
1750void UnloadMusicStream(Music music)
1751{
1752 UnloadAudioStream(music.stream);
1754 if (music.ctxData != NULL)
1755 {
1756 if (false) { }
1757#if defined(SUPPORT_FILEFORMAT_WAV)
1758 else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData);
1759#endif
1760#if defined(SUPPORT_FILEFORMAT_OGG)
1761 else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData);
1762#endif
1763#if defined(SUPPORT_FILEFORMAT_MP3)
1764 else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); }
1765#endif
1766#if defined(SUPPORT_FILEFORMAT_QOA)
1767 else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData);
1768#endif
1769#if defined(SUPPORT_FILEFORMAT_FLAC)
1770 else if (music.ctxType == MUSIC_AUDIO_FLAC) { drflac_close((drflac *)music.ctxData); drflac_free((drflac *)music.ctxData, NULL); }
1771#endif
1772#if defined(SUPPORT_FILEFORMAT_XM)
1773 else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData);
1774#endif
1775#if defined(SUPPORT_FILEFORMAT_MOD)
1776 else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); }
1777#endif
1778 }
1779}
1781// Start music playing (open stream) from beginning
1782void PlayMusicStream(Music music)
1783{
1784 PlayAudioStream(music.stream);
1785}
1787// Pause music playing
1788void PauseMusicStream(Music music)
1789{
1790 PauseAudioStream(music.stream);
1791}
1793// Resume music playing
1794void ResumeMusicStream(Music music)
1795{
1796 ResumeAudioStream(music.stream);
1797}
1799// Stop music playing (close stream)
1800void StopMusicStream(Music music)
1801{
1802 StopAudioStream(music.stream);
1804 switch (music.ctxType)
1805 {
1806#if defined(SUPPORT_FILEFORMAT_WAV)
1807 case MUSIC_AUDIO_WAV: drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); break;
1808#endif
1809#if defined(SUPPORT_FILEFORMAT_OGG)
1810 case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break;
1811#endif
1812#if defined(SUPPORT_FILEFORMAT_MP3)
1813 case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break;
1814#endif
1815#if defined(SUPPORT_FILEFORMAT_QOA)
1816 case MUSIC_AUDIO_QOA: qoaplay_rewind((qoaplay_desc *)music.ctxData); break;
1817#endif
1818#if defined(SUPPORT_FILEFORMAT_FLAC)
1819 case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break;
1820#endif
1821#if defined(SUPPORT_FILEFORMAT_XM)
1822 case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break;
1823#endif
1824#if defined(SUPPORT_FILEFORMAT_MOD)
1825 case MUSIC_MODULE_MOD: jar_mod_seek_start((jar_mod_context_t *)music.ctxData); break;
1826#endif
1827 default: break;
1828 }
1829}
1831// Seek music to a certain position (in seconds)
1832void SeekMusicStream(Music music, float position)
1833{
1834 // Seeking is not supported in module formats
1835 if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) return;
1837 unsigned int positionInFrames = (unsigned int)(position*music.stream.sampleRate);
1839 switch (music.ctxType)
1840 {
1841#if defined(SUPPORT_FILEFORMAT_WAV)
1842 case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, positionInFrames); break;
1843#endif
1844#if defined(SUPPORT_FILEFORMAT_OGG)
1845 case MUSIC_AUDIO_OGG: stb_vorbis_seek_frame((stb_vorbis *)music.ctxData, positionInFrames); break;
1846#endif
1847#if defined(SUPPORT_FILEFORMAT_MP3)
1848 case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break;
1849#endif
1850#if defined(SUPPORT_FILEFORMAT_QOA)
1851 case MUSIC_AUDIO_QOA:
1852 {
1853 int qoaFrame = positionInFrames/QOA_FRAME_LEN;
1854 qoaplay_seek_frame((qoaplay_desc *)music.ctxData, qoaFrame); // Seeks to QOA frame, not PCM frame
1856 // We need to compute QOA frame number and update positionInFrames
1857 positionInFrames = ((qoaplay_desc *)music.ctxData)->sample_position;
1858 } break;
1859#endif
1860#if defined(SUPPORT_FILEFORMAT_FLAC)
1861 case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break;
1862#endif
1863 default: break;
1864 }
1866 ma_mutex_lock(&AUDIO.System.lock);
1867 music.stream.buffer->framesProcessed = positionInFrames;
1868 music.stream.buffer->isSubBufferProcessed[0] = true;
1869 music.stream.buffer->isSubBufferProcessed[1] = true;
1870 ma_mutex_unlock(&AUDIO.System.lock);
1871}
1873// Update (re-fill) music buffers if data already processed
1874void UpdateMusicStream(Music music)
1875{
1876 if (music.stream.buffer == NULL) return;
1877 if (!music.stream.buffer->playing) return;
1879 ma_mutex_lock(&AUDIO.System.lock);
1881 unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2;
1883 // On first call of this function we lazily pre-allocated a temp buffer to read audio files/memory data in
1884 int frameSize = music.stream.channels*music.stream.sampleSize/8;
1885 unsigned int pcmSize = subBufferSizeInFrames*frameSize;
1887 if (AUDIO.System.pcmBufferSize < pcmSize)
1888 {
1889 RL_FREE(AUDIO.System.pcmBuffer);
1890 AUDIO.System.pcmBuffer = RL_CALLOC(1, pcmSize);
1891 AUDIO.System.pcmBufferSize = pcmSize;
1892 }
1894 // Check both sub-buffers to check if they require refilling
1895 for (int i = 0; i < 2; i++)
1896 {
1897 unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed
1898 unsigned int framesToStream = 0; // Total frames to be streamed
1900 if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames;
1901 else framesToStream = framesLeft;
1903 if (framesToStream == 0)
1904 {
1905 // Check if both buffers have been processed
1906 if (music.stream.buffer->isSubBufferProcessed[0] && music.stream.buffer->isSubBufferProcessed[1])
1907 {
1908 ma_mutex_unlock(&AUDIO.System.lock);
1909 StopMusicStream(music);
1910 return;
1911 }
1913 ma_mutex_unlock(&AUDIO.System.lock);
1914 return;
1915 }
1917 if (!music.stream.buffer->isSubBufferProcessed[i]) continue; // No refilling required, move to next sub-buffer
1919 int frameCountStillNeeded = framesToStream;
1920 int frameCountReadTotal = 0;
1922 switch (music.ctxType)
1923 {
1924 #if defined(SUPPORT_FILEFORMAT_WAV)
1925 case MUSIC_AUDIO_WAV:
1926 {
1927 if (music.stream.sampleSize == 16)
1928 {
1929 while (true)
1930 {
1931 int frameCountRead = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
1932 frameCountReadTotal += frameCountRead;
1933 frameCountStillNeeded -= frameCountRead;
1934 if (frameCountStillNeeded == 0) break;
1935 else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData);
1936 }
1937 }
1938 else if (music.stream.sampleSize == 32)
1939 {
1940 while (true)
1941 {
1942 int frameCountRead = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
1943 frameCountReadTotal += frameCountRead;
1944 frameCountStillNeeded -= frameCountRead;
1945 if (frameCountStillNeeded == 0) break;
1946 else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData);
1947 }
1948 }
1949 } break;
1950 #endif
1951 #if defined(SUPPORT_FILEFORMAT_OGG)
1952 case MUSIC_AUDIO_OGG:
1953 {
1954 while (true)
1955 {
1956 int frameCountRead = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded*music.stream.channels);
1957 frameCountReadTotal += frameCountRead;
1958 frameCountStillNeeded -= frameCountRead;
1959 if (frameCountStillNeeded == 0) break;
1960 else stb_vorbis_seek_start((stb_vorbis *)music.ctxData);
1961 }
1962 } break;
1963 #endif
1964 #if defined(SUPPORT_FILEFORMAT_MP3)
1965 case MUSIC_AUDIO_MP3:
1966 {
1967 while (true)
1968 {
1969 int frameCountRead = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
1970 frameCountReadTotal += frameCountRead;
1971 frameCountStillNeeded -= frameCountRead;
1972 if (frameCountStillNeeded == 0) break;
1973 else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData);
1974 }
1975 } break;
1976 #endif
1977 #if defined(SUPPORT_FILEFORMAT_QOA)
1978 case MUSIC_AUDIO_QOA:
1979 {
1980 unsigned int frameCountRead = qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream);
1981 frameCountReadTotal += frameCountRead;
1982 /*
1983 while (true)
1984 {
1985 int frameCountRead = (int)qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded);
1986 frameCountReadTotal += frameCountRead;
1987 frameCountStillNeeded -= frameCountRead;
1988 if (frameCountStillNeeded == 0) break;
1989 else qoaplay_rewind((qoaplay_desc *)music.ctxData);
1990 }
1991 */
1992 } break;
1993 #endif
1994 #if defined(SUPPORT_FILEFORMAT_FLAC)
1995 case MUSIC_AUDIO_FLAC:
1996 {
1997 while (true)
1998 {
1999 int frameCountRead = (int)drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
2000 frameCountReadTotal += frameCountRead;
2001 frameCountStillNeeded -= frameCountRead;
2002 if (frameCountStillNeeded == 0) break;
2003 else drflac__seek_to_first_frame((drflac *)music.ctxData);
2004 }
2005 } break;
2006 #endif
2007 #if defined(SUPPORT_FILEFORMAT_XM)
2008 case MUSIC_MODULE_XM:
2009 {
2010 // NOTE: Internally we consider 2 channels generation, so sampleCount/2
2011 if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream);
2012 else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream);
2013 else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)AUDIO.System.pcmBuffer, framesToStream);
2014 //jar_xm_reset((jar_xm_context_t *)music.ctxData);
2016 } break;
2017 #endif
2018 #if defined(SUPPORT_FILEFORMAT_MOD)
2019 case MUSIC_MODULE_MOD:
2020 {
2021 // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2
2022 jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0);
2023 //jar_mod_seek_start((jar_mod_context_t *)music.ctxData);
2025 } break;
2026 #endif
2027 default: break;
2028 }
2030 UpdateAudioStreamInLockedState(music.stream, AUDIO.System.pcmBuffer, framesToStream);
2031 }
2033 ma_mutex_unlock(&AUDIO.System.lock);
2034}
2036// Check if any music is playing
2037bool IsMusicStreamPlaying(Music music)
2038{
2039 return IsAudioStreamPlaying(music.stream);
2040}
2042// Set volume for music
2043void SetMusicVolume(Music music, float volume)
2044{
2045 SetAudioStreamVolume(music.stream, volume);
2046}
2048// Set pitch for music
2049void SetMusicPitch(Music music, float pitch)
2050{
2051 SetAudioBufferPitch(music.stream.buffer, pitch);
2052}
2054// Set pan for a music
2055void SetMusicPan(Music music, float pan)
2056{
2057 SetAudioBufferPan(music.stream.buffer, pan);
2058}
2060// Get music time length (in seconds)
2061float GetMusicTimeLength(Music music)
2062{
2063 float totalSeconds = 0.0f;
2065 totalSeconds = (float)music.frameCount/music.stream.sampleRate;
2067 return totalSeconds;
2068}
2070// Get current music time played (in seconds)
2071float GetMusicTimePlayed(Music music)
2072{
2073 float secondsPlayed = 0.0f;
2074 if (music.stream.buffer != NULL)
2075 {
2076#if defined(SUPPORT_FILEFORMAT_XM)
2077 if (music.ctxType == MUSIC_MODULE_XM)
2078 {
2079 uint64_t framesPlayed = 0;
2081 jar_xm_get_position((jar_xm_context_t *)music.ctxData, NULL, NULL, NULL, &framesPlayed);
2082 secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
2083 }
2084 else
2085#endif
2086 {
2087 ma_mutex_lock(&AUDIO.System.lock);
2088 //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels;
2089 int framesProcessed = (int)music.stream.buffer->framesProcessed;
2090 int subBufferSize = (int)music.stream.buffer->sizeInFrames/2;
2091 int framesInFirstBuffer = music.stream.buffer->isSubBufferProcessed[0]? 0 : subBufferSize;
2092 int framesInSecondBuffer = music.stream.buffer->isSubBufferProcessed[1]? 0 : subBufferSize;
2093 int framesInBuffers = framesInFirstBuffer + framesInSecondBuffer;
2094 if (((unsigned int)framesInBuffers > music.frameCount) && !music.looping) framesInBuffers = music.frameCount;
2095 int framesSentToMix = music.stream.buffer->frameCursorPos%subBufferSize;
2096 int framesPlayed = (framesProcessed - framesInBuffers + framesSentToMix)%(int)music.frameCount;
2097 if (framesPlayed < 0) framesPlayed += music.frameCount;
2098 secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
2099 ma_mutex_unlock(&AUDIO.System.lock);
2100 }
2101 }
2103 return secondsPlayed;
2104}
2106// Load audio stream (to stream audio pcm data)
2107AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels)
2108{
2109 AudioStream stream = { 0 };
2111 stream.sampleRate = sampleRate;
2112 stream.sampleSize = sampleSize;
2113 stream.channels = channels;
2115 ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32));
2117 // The size of a streaming buffer must be at least double the size of a period
2118 unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames;
2120 // If the buffer is not set, compute one that would give us a buffer good enough for a decent frame rate at the device bit size/rate
2121 int deviceBitsPerSample = AUDIO.System.device.playback.format;
2122 if (deviceBitsPerSample > 4) deviceBitsPerSample = 4;
2123 deviceBitsPerSample *= AUDIO.System.device.playback.channels;
2125 unsigned int subBufferSize = (AUDIO.Buffer.defaultSize == 0)? (AUDIO.System.device.sampleRate/30*deviceBitsPerSample) : AUDIO.Buffer.defaultSize;
2127 if (subBufferSize < periodSize) subBufferSize = periodSize;
2129 // Create a double audio buffer of defined size
2130 stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM);
2132 if (stream.buffer != NULL)
2133 {
2134 stream.buffer->looping = true; // Always loop for streaming buffers
2135 TRACELOG(LOG_INFO, "STREAM: Initialized successfully (%i Hz, %i bit, %s)", stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo");
2136 }
2137 else TRACELOG(LOG_WARNING, "STREAM: Failed to load audio buffer, stream could not be created");
2139 return stream;
2140}
2142// Checks if an audio stream is valid (buffers initialized)
2143bool IsAudioStreamValid(AudioStream stream)
2144{
2145 return ((stream.buffer != NULL) && // Validate stream buffer
2146 (stream.sampleRate > 0) && // Validate sample rate is supported
2147 (stream.sampleSize > 0) && // Validate sample size is supported
2148 (stream.channels > 0)); // Validate number of channels supported
2149}
2151// Unload audio stream and free memory
2152void UnloadAudioStream(AudioStream stream)
2153{
2154 UnloadAudioBuffer(stream.buffer);
2156 TRACELOG(LOG_INFO, "STREAM: Unloaded audio stream data from RAM");
2157}
2159// Update audio stream buffers with data
2160// NOTE 1: Only updates one buffer of the stream source: dequeue -> update -> queue
2161// NOTE 2: To dequeue a buffer it needs to be processed: IsAudioStreamProcessed()
2162void UpdateAudioStream(AudioStream stream, const void *data, int frameCount)
2163{
2164 ma_mutex_lock(&AUDIO.System.lock);
2165 UpdateAudioStreamInLockedState(stream, data, frameCount);
2166 ma_mutex_unlock(&AUDIO.System.lock);
2167}
2169// Check if any audio stream buffers requires refill
2170bool IsAudioStreamProcessed(AudioStream stream)
2171{
2172 if (stream.buffer == NULL) return false;
2174 bool result = false;
2175 ma_mutex_lock(&AUDIO.System.lock);
2176 result = stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1];
2177 ma_mutex_unlock(&AUDIO.System.lock);
2178 return result;
2179}
2181// Play audio stream
2182void PlayAudioStream(AudioStream stream)
2183{
2184 PlayAudioBuffer(stream.buffer);
2185}
2187// Play audio stream
2188void PauseAudioStream(AudioStream stream)
2189{
2190 PauseAudioBuffer(stream.buffer);
2191}
2193// Resume audio stream playing
2194void ResumeAudioStream(AudioStream stream)
2195{
2196 ResumeAudioBuffer(stream.buffer);
2197}
2199// Check if audio stream is playing
2200bool IsAudioStreamPlaying(AudioStream stream)
2201{
2202 return IsAudioBufferPlaying(stream.buffer);
2203}
2205// Stop audio stream
2206void StopAudioStream(AudioStream stream)
2207{
2208 StopAudioBuffer(stream.buffer);
2209}
2211// Set volume for audio stream (1.0 is max level)
2212void SetAudioStreamVolume(AudioStream stream, float volume)
2213{
2214 SetAudioBufferVolume(stream.buffer, volume);
2215}
2217// Set pitch for audio stream (1.0 is base level)
2218void SetAudioStreamPitch(AudioStream stream, float pitch)
2219{
2220 SetAudioBufferPitch(stream.buffer, pitch);
2221}
2223// Set pan for audio stream
2224void SetAudioStreamPan(AudioStream stream, float pan)
2225{
2226 SetAudioBufferPan(stream.buffer, pan);
2227}
2229// Default size for new audio streams
2230void SetAudioStreamBufferSizeDefault(int size)
2231{
2232 AUDIO.Buffer.defaultSize = size;
2233}
2235// Audio thread callback to request new data
2236void SetAudioStreamCallback(AudioStream stream, AudioCallback callback)
2237{
2238 if (stream.buffer != NULL)
2239 {
2240 ma_mutex_lock(&AUDIO.System.lock);
2241 stream.buffer->callback = callback;
2242 ma_mutex_unlock(&AUDIO.System.lock);
2243 }
2244}
2246// Add processor to audio stream. Contrary to buffers, the order of processors is important
2247// The new processor must be added at the end. As there aren't supposed to be a lot of processors attached to
2248// a given stream, we iterate through the list to find the end. That way we don't need a pointer to the last element
2249void AttachAudioStreamProcessor(AudioStream stream, AudioCallback process)
2250{
2251 ma_mutex_lock(&AUDIO.System.lock);
2253 rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor));
2254 processor->process = process;
2256 rAudioProcessor *last = stream.buffer->processor;
2258 while (last && last->next)
2259 {
2260 last = last->next;
2261 }
2262 if (last)
2263 {
2264 processor->prev = last;
2265 last->next = processor;
2266 }
2267 else stream.buffer->processor = processor;
2269 ma_mutex_unlock(&AUDIO.System.lock);
2270}
2272// Remove processor from audio stream
2273void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process)
2274{
2275 ma_mutex_lock(&AUDIO.System.lock);
2277 rAudioProcessor *processor = stream.buffer->processor;
2279 while (processor)
2280 {
2281 rAudioProcessor *next = processor->next;
2282 rAudioProcessor *prev = processor->prev;
2284 if (processor->process == process)
2285 {
2286 if (stream.buffer->processor == processor) stream.buffer->processor = next;
2287 if (prev) prev->next = next;
2288 if (next) next->prev = prev;
2290 RL_FREE(processor);
2291 }
2293 processor = next;
2294 }
2296 ma_mutex_unlock(&AUDIO.System.lock);
2297}
2299// Add processor to audio pipeline. Order of processors is important
2300// Works the same way as {Attach,Detach}AudioStreamProcessor() functions, except
2301// these two work on the already mixed output just before sending it to the sound hardware
2302void AttachAudioMixedProcessor(AudioCallback process)
2303{
2304 ma_mutex_lock(&AUDIO.System.lock);
2306 rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor));
2307 processor->process = process;
2309 rAudioProcessor *last = AUDIO.mixedProcessor;
2311 while (last && last->next)
2312 {
2313 last = last->next;
2314 }
2315 if (last)
2316 {
2317 processor->prev = last;
2318 last->next = processor;
2319 }
2320 else AUDIO.mixedProcessor = processor;
2322 ma_mutex_unlock(&AUDIO.System.lock);
2323}
2325// Remove processor from audio pipeline
2326void DetachAudioMixedProcessor(AudioCallback process)
2327{
2328 ma_mutex_lock(&AUDIO.System.lock);
2330 rAudioProcessor *processor = AUDIO.mixedProcessor;
2332 while (processor)
2333 {
2334 rAudioProcessor *next = processor->next;
2335 rAudioProcessor *prev = processor->prev;
2337 if (processor->process == process)
2338 {
2339 if (AUDIO.mixedProcessor == processor) AUDIO.mixedProcessor = next;
2340 if (prev) prev->next = next;
2341 if (next) next->prev = prev;
2343 RL_FREE(processor);
2344 }
2346 processor = next;
2347 }
2349 ma_mutex_unlock(&AUDIO.System.lock);
2350}
2352//----------------------------------------------------------------------------------
2353// Module Internal Functions Definition
2354//----------------------------------------------------------------------------------
2355// Log callback function
2356static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage)
2357{
2358 TRACELOG(LOG_WARNING, "miniaudio: %s", pMessage); // All log messages from miniaudio are errors
2359}
2361// Reads audio data from an AudioBuffer object in internal format
2362static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount)
2363{
2364 // Using audio buffer callback
2365 if (audioBuffer->callback)
2366 {
2367 audioBuffer->callback(framesOut, frameCount);
2368 audioBuffer->framesProcessed += frameCount;
2370 return frameCount;
2371 }
2373 ma_uint32 subBufferSizeInFrames = (audioBuffer->sizeInFrames > 1)? audioBuffer->sizeInFrames/2 : audioBuffer->sizeInFrames;
2374 ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames;
2376 if (currentSubBufferIndex > 1) return 0;
2378 // Another thread can update the processed state of buffers, so
2379 // we just take a copy here to try and avoid potential synchronization problems
2380 bool isSubBufferProcessed[2] = { 0 };
2381 isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0];
2382 isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1];
2384 ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn);
2386 // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0
2387 ma_uint32 framesRead = 0;
2388 while (1)
2389 {
2390 // We break from this loop differently depending on the buffer's usage
2391 // - For static buffers, we simply fill as much data as we can
2392 // - For streaming buffers we only fill half of the buffer that are processed
2393 // Unprocessed halves must keep their audio data in-tact
2394 if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC)
2395 {
2396 if (framesRead >= frameCount) break;
2397 }
2398 else
2399 {
2400 if (isSubBufferProcessed[currentSubBufferIndex]) break;
2401 }
2403 ma_uint32 totalFramesRemaining = (frameCount - framesRead);
2404 if (totalFramesRemaining == 0) break;
2406 ma_uint32 framesRemainingInOutputBuffer;
2407 if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC)
2408 {
2409 framesRemainingInOutputBuffer = audioBuffer->sizeInFrames - audioBuffer->frameCursorPos;
2410 }
2411 else
2412 {
2413 ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames*currentSubBufferIndex;
2414 framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer);
2415 }
2417 ma_uint32 framesToRead = totalFramesRemaining;
2418 if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer;
2420 memcpy((unsigned char *)framesOut + (framesRead*frameSizeInBytes), audioBuffer->data + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes);
2421 audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->sizeInFrames;
2422 framesRead += framesToRead;
2424 // If we've read to the end of the buffer, mark it as processed
2425 if (framesToRead == framesRemainingInOutputBuffer)
2426 {
2427 audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true;
2428 isSubBufferProcessed[currentSubBufferIndex] = true;
2430 currentSubBufferIndex = (currentSubBufferIndex + 1)%2;
2432 // We need to break from this loop if we're not looping
2433 if (!audioBuffer->looping)
2434 {
2435 StopAudioBufferInLockedState(audioBuffer);
2436 break;
2437 }
2438 }
2439 }
2441 // Zero-fill excess
2442 ma_uint32 totalFramesRemaining = (frameCount - framesRead);
2443 if (totalFramesRemaining > 0)
2444 {
2445 memset((unsigned char *)framesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes);
2447 // For static buffers we can fill the remaining frames with silence for safety, but we don't want
2448 // to report those frames as "read". The reason for this is that the caller uses the return value
2449 // to know whether a non-looping sound has finished playback
2450 if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining;
2451 }
2453 return framesRead;
2454}
2456// Reads audio data from an AudioBuffer object in device format, returned data will be in a format appropriate for mixing
2457static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount)
2458{
2459 // What's going on here is that we're continuously converting data from the AudioBuffer's internal format to the mixing format, which
2460 // should be defined by the output format of the data converter. We do this until frameCount frames have been output. The important
2461 // detail to remember here is that we never, ever attempt to read more input data than is required for the specified number of output
2462 // frames. This can be achieved with ma_data_converter_get_required_input_frame_count()
2463 ma_uint8 inputBuffer[4096] = { 0 };
2464 ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn);
2466 ma_uint32 totalOutputFramesProcessed = 0;
2467 while (totalOutputFramesProcessed < frameCount)
2468 {
2469 ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed;
2470 ma_uint64 inputFramesToProcessThisIteration = 0;
2472 (void)ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration, &inputFramesToProcessThisIteration);
2473 if (inputFramesToProcessThisIteration > inputBufferFrameCap)
2474 {
2475 inputFramesToProcessThisIteration = inputBufferFrameCap;
2476 }
2478 float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.channelsOut);
2480 // At this point we can convert the data to our mixing format
2481 ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration);
2482 ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration;
2483 ma_data_converter_process_pcm_frames(&audioBuffer->converter, inputBuffer, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration);
2485 totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; // Safe cast
2487 if (inputFramesProcessedThisIteration < inputFramesToProcessThisIteration) break; // Ran out of input data
2489 // This should never be hit, but added here for safety
2490 // Ensures we get out of the loop when no input nor output frames are processed
2491 if ((inputFramesProcessedThisIteration == 0) && (outputFramesProcessedThisIteration == 0)) break;
2492 }
2494 return totalOutputFramesProcessed;
2495}
2497// Sending audio data to device callback function
2498// This function will be called when miniaudio needs more data
2499// NOTE: All the mixing takes place here
2500static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount)
2501{
2502 (void)pDevice;
2504 // Mixing is basically just an accumulation, we need to initialize the output buffer to 0
2505 memset(pFramesOut, 0, frameCount*pDevice->playback.channels*ma_get_bytes_per_sample(pDevice->playback.format));
2507 // Using a mutex here for thread-safety which makes things not real-time
2508 // This is unlikely to be necessary for this project, but may want to consider how you might want to avoid this
2509 ma_mutex_lock(&AUDIO.System.lock);
2510 {
2511 for (AudioBuffer *audioBuffer = AUDIO.Buffer.first; audioBuffer != NULL; audioBuffer = audioBuffer->next)
2512 {
2513 // Ignore stopped or paused sounds
2514 if (!audioBuffer->playing || audioBuffer->paused) continue;
2516 ma_uint32 framesRead = 0;
2518 while (1)
2519 {
2520 if (framesRead >= frameCount) break;
2522 // Just read as much data as we can from the stream
2523 ma_uint32 framesToRead = (frameCount - framesRead);
2525 while (framesToRead > 0)
2526 {
2527 float tempBuffer[1024] = { 0 }; // Frames for stereo
2529 ma_uint32 framesToReadRightNow = framesToRead;
2530 if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS)
2531 {
2532 framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS;
2533 }
2535 ma_uint32 framesJustRead = ReadAudioBufferFramesInMixingFormat(audioBuffer, tempBuffer, framesToReadRightNow);
2536 if (framesJustRead > 0)
2537 {
2538 float *framesOut = (float *)pFramesOut + (framesRead*AUDIO.System.device.playback.channels);
2539 float *framesIn = tempBuffer;
2541 // Apply processors chain if defined
2542 rAudioProcessor *processor = audioBuffer->processor;
2543 while (processor)
2544 {
2545 processor->process(framesIn, framesJustRead);
2546 processor = processor->next;
2547 }
2549 MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer);
2551 framesToRead -= framesJustRead;
2552 framesRead += framesJustRead;
2553 }
2555 if (!audioBuffer->playing)
2556 {
2557 framesRead = frameCount;
2558 break;
2559 }
2561 // If we weren't able to read all the frames we requested, break
2562 if (framesJustRead < framesToReadRightNow)
2563 {
2564 if (!audioBuffer->looping)
2565 {
2566 StopAudioBufferInLockedState(audioBuffer);
2567 break;
2568 }
2569 else
2570 {
2571 // Should never get here, but just for safety,
2572 // move the cursor position back to the start and continue the loop
2573 audioBuffer->frameCursorPos = 0;
2574 continue;
2575 }
2576 }
2577 }
2579 // If for some reason we weren't able to read every frame we'll need to break from the loop
2580 // Not doing this could theoretically put us into an infinite loop
2581 if (framesToRead > 0) break;
2582 }
2583 }
2584 }
2586 rAudioProcessor *processor = AUDIO.mixedProcessor;
2587 while (processor)
2588 {
2589 processor->process(pFramesOut, frameCount);
2590 processor = processor->next;
2591 }
2593 ma_mutex_unlock(&AUDIO.System.lock);
2594}
2596// Main mixing function, pretty simple in this project, just an accumulation
2597// NOTE: framesOut is both an input and an output, it is initially filled with zeros outside of this function
2598static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer)
2599{
2600 const float localVolume = buffer->volume;
2601 const ma_uint32 channels = AUDIO.System.device.playback.channels;
2603 if (channels == 2) // We consider panning
2604 {
2605 const float right = (buffer->pan + 1.0f)/2.0f; // Normalize: [-1..1] -> [0..1]
2606 const float left = 1.0f - right;
2608 // Fast sine approximation in [0..1] for pan law: y = 0.5f*x*(3 - x*x);
2609 const float levels[2] = { localVolume*0.5f*left*(3.0f - left*left), localVolume*0.5f*right*(3.0f - right*right) };
2611 float *frameOut = framesOut;
2612 const float *frameIn = framesIn;
2614 for (ma_uint32 frame = 0; frame < frameCount; frame++)
2615 {
2616 frameOut[0] += (frameIn[0]*levels[0]);
2617 frameOut[1] += (frameIn[1]*levels[1]);
2619 frameOut += 2;
2620 frameIn += 2;
2621 }
2622 }
2623 else // We do not consider panning
2624 {
2625 for (ma_uint32 frame = 0; frame < frameCount; frame++)
2626 {
2627 for (ma_uint32 c = 0; c < channels; c++)
2628 {
2629 float *frameOut = framesOut + (frame*channels);
2630 const float *frameIn = framesIn + (frame*channels);
2632 // Output accumulates input multiplied by volume to provided output (usually 0)
2633 frameOut[c] += (frameIn[c]*localVolume);
2634 }
2635 }
2636 }
2637}
2639// Check if an audio buffer is playing, assuming the audio system mutex has been locked
2640static bool IsAudioBufferPlayingInLockedState(AudioBuffer *buffer)
2641{
2642 bool result = false;
2644 if (buffer != NULL) result = (buffer->playing && !buffer->paused);
2646 return result;
2647}
2649// Stop an audio buffer, assuming the audio system mutex has been locked
2650static void StopAudioBufferInLockedState(AudioBuffer *buffer)
2651{
2652 if (buffer != NULL)
2653 {
2654 if (IsAudioBufferPlayingInLockedState(buffer))
2655 {
2656 buffer->playing = false;
2657 buffer->paused = false;
2658 buffer->frameCursorPos = 0;
2659 buffer->framesProcessed = 0;
2660 buffer->isSubBufferProcessed[0] = true;
2661 buffer->isSubBufferProcessed[1] = true;
2662 }
2663 }
2664}
2666// Update audio stream, assuming the audio system mutex has been locked
2667static void UpdateAudioStreamInLockedState(AudioStream stream, const void *data, int frameCount)
2668{
2669 if (stream.buffer != NULL)
2670 {
2671 if (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1])
2672 {
2673 ma_uint32 subBufferToUpdate = 0;
2675 if (stream.buffer->isSubBufferProcessed[0] && stream.buffer->isSubBufferProcessed[1])
2676 {
2677 // Both buffers are available for updating
2678 // Update the first one and make sure the cursor is moved back to the front
2679 subBufferToUpdate = 0;
2680 stream.buffer->frameCursorPos = 0;
2681 }
2682 else
2683 {
2684 // Just update whichever sub-buffer is processed
2685 subBufferToUpdate = (stream.buffer->isSubBufferProcessed[0])? 0 : 1;
2686 }
2688 ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2;
2689 unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate);
2691 stream.buffer->framesProcessed += frameCount;
2693 // Does this API expect a whole buffer to be updated in one go?
2694 // Assuming so, but if not will need to change this logic
2695 if (subBufferSizeInFrames >= (ma_uint32)frameCount)
2696 {
2697 ma_uint32 framesToWrite = (ma_uint32)frameCount;
2699 ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8);
2700 memcpy(subBuffer, data, bytesToWrite);
2702 // Any leftover frames should be filled with zeros
2703 ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite;
2705 if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8));
2707 stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false;
2708 }
2709 else TRACELOG(LOG_WARNING, "STREAM: Attempting to write too many frames to buffer");
2710 }
2711 else TRACELOG(LOG_WARNING, "STREAM: Buffer not available for updating");
2712 }
2713}
2715// Some required functions for audio standalone module version
2716#if defined(RAUDIO_STANDALONE)
2717// Check file extension
2718static bool IsFileExtension(const char *fileName, const char *ext)
2719{
2720 bool result = false;
2721 const char *fileExt;
2723 if ((fileExt = strrchr(fileName, '.')) != NULL)
2724 {
2725 if (strcmp(fileExt, ext) == 0) result = true;
2726 }
2728 return result;
2729}
2731// Get pointer to extension for a filename string (includes the dot: .png)
2732static const char *GetFileExtension(const char *fileName)
2733{
2734 const char *dot = strrchr(fileName, '.');
2736 if (!dot || dot == fileName) return NULL;
2738 return dot;
2739}
2741// String pointer reverse break: returns right-most occurrence of charset in text
2742static const char *strprbrk(const char *text, const char *charset)
2743{
2744 const char *latestMatch = NULL;
2746 for (; (text != NULL) && (text = strpbrk(text, charset)); latestMatch = text++) { }
2748 return latestMatch;
2749}
2751// Get pointer to filename for a path string
2752static const char *GetFileName(const char *filePath)
2753{
2754 const char *fileName = NULL;
2755 if (filePath != NULL) fileName = strprbrk(filePath, "\\/");
2757 if (!fileName) return filePath;
2759 return fileName + 1;
2760}
2762// Get filename string without extension (uses static string)
2763static const char *GetFileNameWithoutExt(const char *filePath)
2764{
2765 #define MAX_FILENAMEWITHOUTEXT_LENGTH 256
2767 static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH] = { 0 };
2768 memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH);
2770 if (filePath != NULL) strncpy(fileName, GetFileName(filePath), MAX_FILENAMEWITHOUTEXT_LENGTH - 1); // Get filename with extension
2772 int fileNameLength = (int)strlen(fileName); // Get size in bytes
2774 for (int i = 0; (i < fileNameLength) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++)
2775 {
2776 if (fileName[i] == '.')
2777 {
2778 // NOTE: We break on first '.' found
2779 fileName[i] = '\0';
2780 break;
2781 }
2782 }
2784 return fileName;
2785}
2787// Load data from file into a buffer
2788static unsigned char *LoadFileData(const char *fileName, int *dataSize)
2789{
2790 unsigned char *data = NULL;
2791 *dataSize = 0;
2793 if (fileName != NULL)
2794 {
2795 FILE *file = fopen(fileName, "rb");
2797 if (file != NULL)
2798 {
2799 // WARNING: On binary streams SEEK_END could not be found,
2800 // using fseek() and ftell() could not work in some (rare) cases
2801 fseek(file, 0, SEEK_END);
2802 int size = ftell(file);
2803 fseek(file, 0, SEEK_SET);
2805 if (size > 0)
2806 {
2807 data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char));
2809 // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements]
2810 unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file);
2811 *dataSize = count;
2813 if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName);
2814 else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName);
2815 }
2816 else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName);
2818 fclose(file);
2819 }
2820 else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName);
2821 }
2822 else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid");
2824 return data;
2825}
2827// Save data to file from buffer
2828static bool SaveFileData(const char *fileName, void *data, int dataSize)
2829{
2830 if (fileName != NULL)
2831 {
2832 FILE *file = fopen(fileName, "wb");
2834 if (file != NULL)
2835 {
2836 unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), dataSize, file);
2838 if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName);
2839 else if (count != dataSize) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName);
2840 else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName);
2842 fclose(file);
2843 }
2844 else
2845 {
2846 TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName);
2847 return false;
2848 }
2849 }
2850 else
2851 {
2852 TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid");
2853 return false;
2854 }
2856 return true;
2857}
2859// Save text data to file (write), string must be '\0' terminated
2860static bool SaveFileText(const char *fileName, char *text)
2861{
2862 if (fileName != NULL)
2863 {
2864 FILE *file = fopen(fileName, "wt");
2866 if (file != NULL)
2867 {
2868 int count = 0;
2869 if (text != NULL) count = fprintf(file, "%s", text);
2871 if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName);
2872 else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName);
2874 fclose(file);
2875 }
2876 else
2877 {
2878 TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName);
2879 return false;
2880 }
2881 }
2882 else
2883 {
2884 TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid");
2885 return false;
2886 }
2888 return true;
2889}
2890#endif
2892#undef AudioBuffer
2894#endif // SUPPORT_MODULE_RAUDIO
index : raylib-jai
---