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

Generated with cgit - Back to sebastiano.tronto.net