-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
raudio.c
2876 lines (2440 loc) · 107 KB
/
raudio.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**********************************************************************************************
*
* raudio v1.1 - A simple and easy-to-use audio library based on miniaudio
*
* FEATURES:
* - Manage audio device (init/close)
* - Manage raw audio context
* - Manage mixing channels
* - Load and unload audio files
* - Format wave data (sample rate, size, channels)
* - Play/Stop/Pause/Resume loaded audio
*
* CONFIGURATION:
* #define SUPPORT_MODULE_RAUDIO
* raudio module is included in the build
*
* #define RAUDIO_STANDALONE
* Define to use the module as standalone library (independently of raylib).
* Required types and functions are defined in the same module.
*
* #define SUPPORT_FILEFORMAT_WAV
* #define SUPPORT_FILEFORMAT_OGG
* #define SUPPORT_FILEFORMAT_MP3
* #define SUPPORT_FILEFORMAT_QOA
* #define SUPPORT_FILEFORMAT_FLAC
* #define SUPPORT_FILEFORMAT_XM
* #define SUPPORT_FILEFORMAT_MOD
* Selected desired fileformats to be supported for loading. Some of those formats are
* supported by default, to remove support, just comment unrequired #define in this module
*
* DEPENDENCIES:
* miniaudio.h - Audio device management lib (https://github.com/mackron/miniaudio)
* stb_vorbis.h - Ogg audio files loading (http://www.nothings.org/stb_vorbis/)
* dr_wav.h - WAV audio files loading (http://github.com/mackron/dr_libs)
* dr_mp3.h - MP3 audio file loading (https://github.com/mackron/dr_libs)
* dr_flac.h - FLAC audio file loading (https://github.com/mackron/dr_libs)
* jar_xm.h - XM module file loading
* jar_mod.h - MOD audio file loading
*
* CONTRIBUTORS:
* David Reid (github: @mackron) (Nov. 2017):
* - Complete port to miniaudio library
*
* Joshua Reisenauer (github: @kd7tck) (2015):
* - XM audio module support (jar_xm)
* - MOD audio module support (jar_mod)
* - Mixing channels support
* - Raw audio context support
*
*
* LICENSE: zlib/libpng
*
* Copyright (c) 2013-2024 Ramon Santamaria (@raysan5)
*
* This software is provided "as-is", without any express or implied warranty. In no event
* will the authors be held liable for any damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose, including commercial
* applications, and to alter it and redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not claim that you
* wrote the original software. If you use this software in a product, an acknowledgment
* in the product documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be misrepresented
* as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
**********************************************************************************************/
#if defined(RAUDIO_STANDALONE)
#include "raudio.h"
#else
#include "raylib.h" // Declares module functions
// Check if config flags have been externally provided on compilation line
#if !defined(EXTERNAL_CONFIG_FLAGS)
#include "config.h" // Defines module configuration flags
#endif
#include "utils.h" // Required for: fopen() Android mapping
#endif
#if defined(SUPPORT_MODULE_RAUDIO)
#if defined(_WIN32)
// To avoid conflicting windows.h symbols with raylib, some flags are defined
// WARNING: Those flags avoid inclusion of some Win32 headers that could be required
// by user at some point and won't be included...
//-------------------------------------------------------------------------------------
// If defined, the following flags inhibit definition of the indicated items.
#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_
#define NOVIRTUALKEYCODES // VK_*
#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_*
#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_*
#define NOSYSMETRICS // SM_*
#define NOMENUS // MF_*
#define NOICONS // IDI_*
#define NOKEYSTATES // MK_*
#define NOSYSCOMMANDS // SC_*
#define NORASTEROPS // Binary and Tertiary raster ops
#define NOSHOWWINDOW // SW_*
#define OEMRESOURCE // OEM Resource values
#define NOATOM // Atom Manager routines
#define NOCLIPBOARD // Clipboard routines
#define NOCOLOR // Screen colors
#define NOCTLMGR // Control and Dialog routines
#define NODRAWTEXT // DrawText() and DT_*
#define NOGDI // All GDI defines and routines
#define NOKERNEL // All KERNEL defines and routines
#define NOUSER // All USER defines and routines
//#define NONLS // All NLS defines and routines
#define NOMB // MB_* and MessageBox()
#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines
#define NOMETAFILE // typedef METAFILEPICT
#define NOMINMAX // Macros min(a,b) and max(a,b)
#define NOMSG // typedef MSG and associated routines
#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_*
#define NOSCROLL // SB_* and scrolling routines
#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc.
#define NOSOUND // Sound driver routines
#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines
#define NOWH // SetWindowsHook and WH_*
#define NOWINOFFSETS // GWL_*, GCL_*, associated routines
#define NOCOMM // COMM driver routines
#define NOKANJI // Kanji support stuff.
#define NOHELP // Help engine interface.
#define NOPROFILER // Profiler interface.
#define NODEFERWINDOWPOS // DeferWindowPos routines
#define NOMCX // Modem Configuration Extensions
// Type required before windows.h inclusion
typedef struct tagMSG *LPMSG;
#include <windows.h> // Windows functionality (miniaudio)
// Type required by some unused function...
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
#include <objbase.h> // Component Object Model (COM) header
#include <mmreg.h> // Windows Multimedia, defines some WAVE structs
#include <mmsystem.h> // Windows Multimedia, used by Windows GDI, defines DIBINDEX macro
// Some required types defined for MSVC/TinyC compiler
#if defined(_MSC_VER) || defined(__TINYC__)
#include "propidl.h"
#endif
#endif
#define MA_MALLOC RL_MALLOC
#define MA_FREE RL_FREE
#define MA_NO_JACK
#define MA_NO_WAV
#define MA_NO_FLAC
#define MA_NO_MP3
#define MA_NO_RESOURCE_MANAGER
#define MA_NO_NODE_GRAPH
#define MA_NO_ENGINE
#define MA_NO_GENERATION
// Threading model: Default: [0] COINIT_MULTITHREADED: COM calls objects on any thread (free threading)
#define MA_COINIT_VALUE 2 // [2] COINIT_APARTMENTTHREADED: Each object has its own thread (apartment model)
#define MINIAUDIO_IMPLEMENTATION
//#define MA_DEBUG_OUTPUT
#include "external/miniaudio.h" // Audio device initialization and management
#undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro
#include <stdlib.h> // Required for: malloc(), free()
#include <stdio.h> // Required for: FILE, fopen(), fclose(), fread()
#include <string.h> // Required for: strcmp() [Used in IsFileExtension(), LoadWaveFromMemory(), LoadMusicStreamFromMemory()]
#if defined(RAUDIO_STANDALONE)
#ifndef TRACELOG
#define TRACELOG(level, ...) printf(__VA_ARGS__)
#endif
// Allow custom memory allocators
#ifndef RL_MALLOC
#define RL_MALLOC(sz) malloc(sz)
#endif
#ifndef RL_CALLOC
#define RL_CALLOC(n,sz) calloc(n,sz)
#endif
#ifndef RL_REALLOC
#define RL_REALLOC(ptr,sz) realloc(ptr,sz)
#endif
#ifndef RL_FREE
#define RL_FREE(ptr) free(ptr)
#endif
#endif
#if defined(SUPPORT_FILEFORMAT_WAV)
#define DRWAV_MALLOC RL_MALLOC
#define DRWAV_REALLOC RL_REALLOC
#define DRWAV_FREE RL_FREE
#define DR_WAV_IMPLEMENTATION
#include "external/dr_wav.h" // WAV loading functions
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
// TODO: Remap stb_vorbis malloc()/free() calls to RL_MALLOC/RL_FREE
#include "external/stb_vorbis.c" // OGG loading functions
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
#define DRMP3_MALLOC RL_MALLOC
#define DRMP3_REALLOC RL_REALLOC
#define DRMP3_FREE RL_FREE
#define DR_MP3_IMPLEMENTATION
#include "external/dr_mp3.h" // MP3 loading functions
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
#define QOA_MALLOC RL_MALLOC
#define QOA_FREE RL_FREE
#if defined(_MSC_VER) // Disable some MSVC warning
#pragma warning(push)
#pragma warning(disable : 4018)
#pragma warning(disable : 4267)
#pragma warning(disable : 4244)
#endif
#define QOA_IMPLEMENTATION
#include "external/qoa.h" // QOA loading and saving functions
#include "external/qoaplay.c" // QOA stream playing helper functions
#if defined(_MSC_VER)
#pragma warning(pop) // Disable MSVC warning suppression
#endif
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
#define DRFLAC_MALLOC RL_MALLOC
#define DRFLAC_REALLOC RL_REALLOC
#define DRFLAC_FREE RL_FREE
#define DR_FLAC_IMPLEMENTATION
#define DR_FLAC_NO_WIN32_IO
#include "external/dr_flac.h" // FLAC loading functions
#endif
#if defined(SUPPORT_FILEFORMAT_XM)
#define JARXM_MALLOC RL_MALLOC
#define JARXM_FREE RL_FREE
#if defined(_MSC_VER) // Disable some MSVC warning
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
#define JAR_XM_IMPLEMENTATION
#include "external/jar_xm.h" // XM loading functions
#if defined(_MSC_VER)
#pragma warning(pop) // Disable MSVC warning suppression
#endif
#endif
#if defined(SUPPORT_FILEFORMAT_MOD)
#define JARMOD_MALLOC RL_MALLOC
#define JARMOD_FREE RL_FREE
#define JAR_MOD_IMPLEMENTATION
#include "external/jar_mod.h" // MOD loading functions
#endif
//----------------------------------------------------------------------------------
// Defines and Macros
//----------------------------------------------------------------------------------
#ifndef AUDIO_DEVICE_FORMAT
#define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (float-32bit)
#endif
#ifndef AUDIO_DEVICE_CHANNELS
#define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo
#endif
#ifndef AUDIO_DEVICE_SAMPLE_RATE
#define AUDIO_DEVICE_SAMPLE_RATE 0 // Device output sample rate
#endif
#ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS
#define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels
#endif
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
#if defined(RAUDIO_STANDALONE)
// Trace log level
// NOTE: Organized by priority level
typedef enum {
LOG_ALL = 0, // Display all logs
LOG_TRACE, // Trace logging, intended for internal use only
LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds
LOG_INFO, // Info logging, used for program execution info
LOG_WARNING, // Warning logging, used on recoverable failures
LOG_ERROR, // Error logging, used on unrecoverable failures
LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE)
LOG_NONE // Disable logging
} TraceLogLevel;
#endif
// Music context type
// NOTE: Depends on data structure provided by the library
// in charge of reading the different file types
typedef enum {
MUSIC_AUDIO_NONE = 0, // No audio context loaded
MUSIC_AUDIO_WAV, // WAV audio context
MUSIC_AUDIO_OGG, // OGG audio context
MUSIC_AUDIO_FLAC, // FLAC audio context
MUSIC_AUDIO_MP3, // MP3 audio context
MUSIC_AUDIO_QOA, // QOA audio context
MUSIC_MODULE_XM, // XM module audio context
MUSIC_MODULE_MOD // MOD module audio context
} MusicContextType;
// NOTE: Different logic is used when feeding data to the playback device
// depending on whether data is streamed (Music vs Sound)
typedef enum {
AUDIO_BUFFER_USAGE_STATIC = 0,
AUDIO_BUFFER_USAGE_STREAM
} AudioBufferUsage;
// Audio buffer struct
struct rAudioBuffer {
ma_data_converter converter; // Audio data converter
AudioCallback callback; // Audio buffer callback for buffer filling on audio threads
rAudioProcessor *processor; // Audio processor
float volume; // Audio buffer volume
float pitch; // Audio buffer pitch
float pan; // Audio buffer pan (0.0f to 1.0f)
bool playing; // Audio buffer state: AUDIO_PLAYING
bool paused; // Audio buffer state: AUDIO_PAUSED
bool looping; // Audio buffer looping, default to true for AudioStreams
int usage; // Audio buffer usage mode: STATIC or STREAM
bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer)
unsigned int sizeInFrames; // Total buffer size in frames
unsigned int frameCursorPos; // Frame cursor position
unsigned int framesProcessed; // Total frames processed in this buffer (required for play timing)
unsigned char *data; // Data buffer, on music stream keeps filling
rAudioBuffer *next; // Next audio buffer on the list
rAudioBuffer *prev; // Previous audio buffer on the list
};
// Audio processor struct
// NOTE: Useful to apply effects to an AudioBuffer
struct rAudioProcessor {
AudioCallback process; // Processor callback function
rAudioProcessor *next; // Next audio processor on the list
rAudioProcessor *prev; // Previous audio processor on the list
};
#define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision
// Audio data context
typedef struct AudioData {
struct {
ma_context context; // miniaudio context data
ma_device device; // miniaudio device
ma_mutex lock; // miniaudio mutex lock
bool isReady; // Check if audio device is ready
size_t pcmBufferSize; // Pre-allocated buffer size
void *pcmBuffer; // Pre-allocated buffer to read audio data from file/memory
} System;
struct {
AudioBuffer *first; // Pointer to first AudioBuffer in the list
AudioBuffer *last; // Pointer to last AudioBuffer in the list
int defaultSize; // Default audio buffer size for audio streams
} Buffer;
rAudioProcessor *mixedProcessor;
} AudioData;
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
static AudioData AUDIO = { // Global AUDIO context
// NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number
// After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a
// standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough
// In case of music-stalls, just increase this number
.Buffer.defaultSize = 0,
.mixedProcessor = NULL
};
//----------------------------------------------------------------------------------
// Module specific Functions Declaration
//----------------------------------------------------------------------------------
static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage);
// Reads audio data from an AudioBuffer object in internal/device formats
static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount);
static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount);
static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount);
static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer);
static bool IsAudioBufferPlayingInLockedState(AudioBuffer *buffer);
static void StopAudioBufferInLockedState(AudioBuffer *buffer);
static void UpdateAudioStreamInLockedState(AudioStream stream, const void *data, int frameCount);
#if defined(RAUDIO_STANDALONE)
static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension
static const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes the dot: .png)
static const char *GetFileName(const char *filePath); // Get pointer to filename for a path string
static const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string)
static unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read)
static bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write)
static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated
#endif
//----------------------------------------------------------------------------------
// AudioBuffer management functions declaration
// NOTE: Those functions are not exposed by raylib... for the moment
//----------------------------------------------------------------------------------
AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage);
void UnloadAudioBuffer(AudioBuffer *buffer);
bool IsAudioBufferPlaying(AudioBuffer *buffer);
void PlayAudioBuffer(AudioBuffer *buffer);
void StopAudioBuffer(AudioBuffer *buffer);
void PauseAudioBuffer(AudioBuffer *buffer);
void ResumeAudioBuffer(AudioBuffer *buffer);
void SetAudioBufferVolume(AudioBuffer *buffer, float volume);
void SetAudioBufferPitch(AudioBuffer *buffer, float pitch);
void SetAudioBufferPan(AudioBuffer *buffer, float pan);
void TrackAudioBuffer(AudioBuffer *buffer);
void UntrackAudioBuffer(AudioBuffer *buffer);
//----------------------------------------------------------------------------------
// Module Functions Definition - Audio Device initialization and Closing
//----------------------------------------------------------------------------------
// Initialize audio device
void InitAudioDevice(void)
{
// Init audio context
ma_context_config ctxConfig = ma_context_config_init();
ma_log_callback_init(OnLog, NULL);
ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context");
return;
}
// Init audio device
// NOTE: Using the default device. Format is floating point because it simplifies mixing
ma_device_config config = ma_device_config_init(ma_device_type_playback);
config.playback.pDeviceID = NULL; // NULL for the default playback AUDIO.System.device
config.playback.format = AUDIO_DEVICE_FORMAT;
config.playback.channels = AUDIO_DEVICE_CHANNELS;
config.capture.pDeviceID = NULL; // NULL for the default capture AUDIO.System.device
config.capture.format = ma_format_s16;
config.capture.channels = 1;
config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE;
config.dataCallback = OnSendAudioDataToDevice;
config.pUserData = NULL;
result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device");
ma_context_uninit(&AUDIO.System.context);
return;
}
// Mixing happens on a separate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may
// want to look at something a bit smarter later on to keep everything real-time, if that's necessary
if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing");
ma_device_uninit(&AUDIO.System.device);
ma_context_uninit(&AUDIO.System.context);
return;
}
// Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running
// while there's at least one sound being played
result = ma_device_start(&AUDIO.System.device);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device");
ma_device_uninit(&AUDIO.System.device);
ma_context_uninit(&AUDIO.System.context);
return;
}
TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully");
TRACELOG(LOG_INFO, " > Backend: miniaudio | %s", ma_get_backend_name(AUDIO.System.context.backend));
TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat));
TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.System.device.playback.channels, AUDIO.System.device.playback.internalChannels);
TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate);
TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods);
AUDIO.System.isReady = true;
}
// Close the audio device for all contexts
void CloseAudioDevice(void)
{
if (AUDIO.System.isReady)
{
ma_mutex_uninit(&AUDIO.System.lock);
ma_device_uninit(&AUDIO.System.device);
ma_context_uninit(&AUDIO.System.context);
AUDIO.System.isReady = false;
RL_FREE(AUDIO.System.pcmBuffer);
AUDIO.System.pcmBuffer = NULL;
AUDIO.System.pcmBufferSize = 0;
TRACELOG(LOG_INFO, "AUDIO: Device closed successfully");
}
else TRACELOG(LOG_WARNING, "AUDIO: Device could not be closed, not currently initialized");
}
// Check if device has been initialized successfully
bool IsAudioDeviceReady(void)
{
return AUDIO.System.isReady;
}
// Set master volume (listener)
void SetMasterVolume(float volume)
{
ma_device_set_master_volume(&AUDIO.System.device, volume);
}
// Get master volume (listener)
float GetMasterVolume(void)
{
float volume = 0.0f;
ma_device_get_master_volume(&AUDIO.System.device, &volume);
return volume;
}
//----------------------------------------------------------------------------------
// Module Functions Definition - Audio Buffer management
//----------------------------------------------------------------------------------
// Initialize a new audio buffer (filled with silence)
AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage)
{
AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer));
if (audioBuffer == NULL)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer");
return NULL;
}
if (sizeInFrames > 0) audioBuffer->data = RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1);
// Audio data runs through a format converter
ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate);
converterConfig.allowDynamicSampleRate = true;
ma_result result = ma_data_converter_init(&converterConfig, NULL, &audioBuffer->converter);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline");
RL_FREE(audioBuffer);
return NULL;
}
// Init audio buffer values
audioBuffer->volume = 1.0f;
audioBuffer->pitch = 1.0f;
audioBuffer->pan = 0.5f;
audioBuffer->callback = NULL;
audioBuffer->processor = NULL;
audioBuffer->playing = false;
audioBuffer->paused = false;
audioBuffer->looping = false;
audioBuffer->usage = usage;
audioBuffer->frameCursorPos = 0;
audioBuffer->sizeInFrames = sizeInFrames;
// Buffers should be marked as processed by default so that a call to
// UpdateAudioStream() immediately after initialization works correctly
audioBuffer->isSubBufferProcessed[0] = true;
audioBuffer->isSubBufferProcessed[1] = true;
// Track audio buffer to linked list next position
TrackAudioBuffer(audioBuffer);
return audioBuffer;
}
// Delete an audio buffer
void UnloadAudioBuffer(AudioBuffer *buffer)
{
if (buffer != NULL)
{
UntrackAudioBuffer(buffer);
ma_data_converter_uninit(&buffer->converter, NULL);
RL_FREE(buffer->data);
RL_FREE(buffer);
}
}
// Check if an audio buffer is playing from a program state without lock
bool IsAudioBufferPlaying(AudioBuffer *buffer)
{
bool result = false;
ma_mutex_lock(&AUDIO.System.lock);
result = IsAudioBufferPlayingInLockedState(buffer);
ma_mutex_unlock(&AUDIO.System.lock);
return result;
}
// Play an audio buffer
// NOTE: Buffer is restarted to the start
// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained
void PlayAudioBuffer(AudioBuffer *buffer)
{
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->playing = true;
buffer->paused = false;
buffer->frameCursorPos = 0;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
// Stop an audio buffer from a program state without lock
void StopAudioBuffer(AudioBuffer *buffer)
{
ma_mutex_lock(&AUDIO.System.lock);
StopAudioBufferInLockedState(buffer);
ma_mutex_unlock(&AUDIO.System.lock);
}
// Pause an audio buffer
void PauseAudioBuffer(AudioBuffer *buffer)
{
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->paused = true;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
// Resume an audio buffer
void ResumeAudioBuffer(AudioBuffer *buffer)
{
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->paused = false;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
// Set volume for an audio buffer
void SetAudioBufferVolume(AudioBuffer *buffer, float volume)
{
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->volume = volume;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
// Set pitch for an audio buffer
void SetAudioBufferPitch(AudioBuffer *buffer, float pitch)
{
if ((buffer != NULL) && (pitch > 0.0f))
{
ma_mutex_lock(&AUDIO.System.lock);
// Pitching is just an adjustment of the sample rate
// Note that this changes the duration of the sound:
// - higher pitches will make the sound faster
// - lower pitches make it slower
ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.sampleRateOut/pitch);
ma_data_converter_set_rate(&buffer->converter, buffer->converter.sampleRateIn, outputSampleRate);
buffer->pitch = pitch;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
// Set pan for an audio buffer
void SetAudioBufferPan(AudioBuffer *buffer, float pan)
{
if (pan < 0.0f) pan = 0.0f;
else if (pan > 1.0f) pan = 1.0f;
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->pan = pan;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
// Track audio buffer to linked list next position
void TrackAudioBuffer(AudioBuffer *buffer)
{
ma_mutex_lock(&AUDIO.System.lock);
{
if (AUDIO.Buffer.first == NULL) AUDIO.Buffer.first = buffer;
else
{
AUDIO.Buffer.last->next = buffer;
buffer->prev = AUDIO.Buffer.last;
}
AUDIO.Buffer.last = buffer;
}
ma_mutex_unlock(&AUDIO.System.lock);
}
// Untrack audio buffer from linked list
void UntrackAudioBuffer(AudioBuffer *buffer)
{
ma_mutex_lock(&AUDIO.System.lock);
{
if (buffer->prev == NULL) AUDIO.Buffer.first = buffer->next;
else buffer->prev->next = buffer->next;
if (buffer->next == NULL) AUDIO.Buffer.last = buffer->prev;
else buffer->next->prev = buffer->prev;
buffer->prev = NULL;
buffer->next = NULL;
}
ma_mutex_unlock(&AUDIO.System.lock);
}
//----------------------------------------------------------------------------------
// Module Functions Definition - Sounds loading and playing (.WAV)
//----------------------------------------------------------------------------------
// Load wave data from file
Wave LoadWave(const char *fileName)
{
Wave wave = { 0 };
// Loading file to memory
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
// Loading wave from memory data
if (fileData != NULL) wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, dataSize);
UnloadFileData(fileData);
return wave;
}
// Load wave from memory buffer, fileType refers to extension: i.e. ".wav"
// WARNING: File extension must be provided in lower-case
Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize)
{
Wave wave = { 0 };
if (false) { }
#if defined(SUPPORT_FILEFORMAT_WAV)
else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0))
{
drwav wav = { 0 };
bool success = drwav_init_memory(&wav, fileData, dataSize, NULL);
if (success)
{
wave.frameCount = (unsigned int)wav.totalPCMFrameCount;
wave.sampleRate = wav.sampleRate;
wave.sampleSize = 16;
wave.channels = wav.channels;
wave.data = (short *)RL_MALLOC((size_t)wave.frameCount*wave.channels*sizeof(short));
// NOTE: We are forcing conversion to 16bit sample size on reading
drwav_read_pcm_frames_s16(&wav, wave.frameCount, wave.data);
}
else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data");
drwav_uninit(&wav);
}
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0))
{
stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, dataSize, NULL, NULL);
if (oggData != NULL)
{
stb_vorbis_info info = stb_vorbis_get_info(oggData);
wave.sampleRate = info.sample_rate;
wave.sampleSize = 16; // By default, ogg data is 16 bit per sample (short)
wave.channels = info.channels;
wave.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData); // NOTE: It returns frames!
wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short));
// NOTE: Get the number of samples to process (be careful! we ask for number of shorts, not bytes!)
stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.frameCount*wave.channels);
stb_vorbis_close(oggData);
}
else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data");
}
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0))
{
drmp3_config config = { 0 };
unsigned long long int totalFrameCount = 0;
// NOTE: We are forcing conversion to 32bit float sample size on reading
wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, dataSize, &config, &totalFrameCount, NULL);
wave.sampleSize = 32;
if (wave.data != NULL)
{
wave.channels = config.channels;
wave.sampleRate = config.sampleRate;
wave.frameCount = (int)totalFrameCount;
}
else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data");
}
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0))
{
qoa_desc qoa = { 0 };
// NOTE: Returned sample data is always 16 bit?
wave.data = qoa_decode(fileData, dataSize, &qoa);
wave.sampleSize = 16;
if (wave.data != NULL)
{
wave.channels = qoa.channels;
wave.sampleRate = qoa.samplerate;
wave.frameCount = qoa.samples;
}
else TRACELOG(LOG_WARNING, "WAVE: Failed to load QOA data");
}
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0))
{
unsigned long long int totalFrameCount = 0;
// NOTE: We are forcing conversion to 16bit sample size on reading
wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL);
wave.sampleSize = 16;
if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount;
else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data");
}
#endif
else TRACELOG(LOG_WARNING, "WAVE: Data format not supported");
TRACELOG(LOG_INFO, "WAVE: Data loaded successfully (%i Hz, %i bit, %i channels)", wave.sampleRate, wave.sampleSize, wave.channels);
return wave;
}
// Checks if wave data is valid (data loaded and parameters)
bool IsWaveValid(Wave wave)
{
bool result = false;
if ((wave.data != NULL) && // Validate wave data available
(wave.frameCount > 0) && // Validate frame count
(wave.sampleRate > 0) && // Validate sample rate is supported
(wave.sampleSize > 0) && // Validate sample size is supported
(wave.channels > 0)) result = true; // Validate number of channels supported
return result;
}
// Load sound from file
// NOTE: The entire file is loaded to memory to be played (no-streaming)
Sound LoadSound(const char *fileName)
{
Wave wave = LoadWave(fileName);
Sound sound = LoadSoundFromWave(wave);
UnloadWave(wave); // Sound is loaded, we can unload wave
return sound;
}
// Load sound from wave data
// NOTE: Wave data must be unallocated manually
Sound LoadSoundFromWave(Wave wave)
{
Sound sound = { 0 };
if (wave.data != NULL)
{
// When using miniaudio we need to do our own mixing
// To simplify this we need convert the format of each sound to be consistent with
// the format used to open the playback AUDIO.System.device. We can do this two ways:
//
// 1) Convert the whole sound in one go at load time (here)
// 2) Convert the audio data in chunks at mixing time
//
// First option has been selected, format conversion is done on the loading stage
// The downside is that it uses more memory if the original sound is u8 or s16
ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32));
ma_uint32 frameCountIn = wave.frameCount;
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);
if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion");
AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC);
if (audioBuffer == NULL)
{
TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer");
return sound; // early return to avoid dereferencing the audioBuffer null pointer
}
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);
if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion");
sound.frameCount = frameCount;
sound.stream.sampleRate = AUDIO.System.device.sampleRate;
sound.stream.sampleSize = 32;
sound.stream.channels = AUDIO_DEVICE_CHANNELS;
sound.stream.buffer = audioBuffer;
}
return sound;
}
// Clone sound from existing sound data, clone does not own wave data
// NOTE: Wave data must be unallocated manually and will be shared across all clones
Sound LoadSoundAlias(Sound source)
{
Sound sound = { 0 };
if (source.stream.buffer->data != NULL)
{
AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC);
if (audioBuffer == NULL)
{
TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer");
return sound; // Early return to avoid dereferencing the audioBuffer null pointer
}
audioBuffer->sizeInFrames = source.stream.buffer->sizeInFrames;
audioBuffer->volume = source.stream.buffer->volume;
audioBuffer->data = source.stream.buffer->data;
sound.frameCount = source.frameCount;
sound.stream.sampleRate = AUDIO.System.device.sampleRate;
sound.stream.sampleSize = 32;
sound.stream.channels = AUDIO_DEVICE_CHANNELS;
sound.stream.buffer = audioBuffer;
}
return sound;
}
// Checks if a sound is valid (data loaded and buffers initialized)
bool IsSoundValid(Sound sound)
{
bool result = false;