v / thirdparty / sokol / sokol_audio.h
2613 lines · 2270 sloc · 102.26 KB · 5b1bfe38f91ee3f67f46d912f05f35777e15a4b3
Raw
1#if defined(SOKOL_IMPL) && !defined(SOKOL_AUDIO_IMPL)
2#define SOKOL_AUDIO_IMPL
3#endif
4#ifndef SOKOL_AUDIO_INCLUDED
5/*
6 sokol_audio.h -- cross-platform audio-streaming API
7
8 Project URL: https://github.com/floooh/sokol
9
10 Do this:
11 #define SOKOL_IMPL or
12 #define SOKOL_AUDIO_IMPL
13 before you include this file in *one* C or C++ file to create the
14 implementation.
15
16 Optionally provide the following defines with your own implementations:
17
18 SOKOL_DUMMY_BACKEND - use a dummy backend
19 SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
20 SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern)
21 SOKOL_API_DECL - same as SOKOL_AUDIO_API_DECL
22 SOKOL_API_IMPL - public function implementation prefix (default: -)
23
24 SAUDIO_RING_MAX_SLOTS - max number of slots in the push-audio ring buffer (default 1024)
25 SAUDIO_OSX_USE_SYSTEM_HEADERS - define this to force inclusion of system headers on
26 macOS instead of using embedded CoreAudio declarations
27 SAUDIO_ANDROID_AAUDIO - on Android, select the AAudio backend (default)
28 SAUDIO_ANDROID_SLES - on Android, select the OpenSLES backend
29
30 If sokol_audio.h is compiled as a DLL, define the following before
31 including the declaration or implementation:
32
33 SOKOL_DLL
34
35 On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport)
36 or __declspec(dllimport) as needed.
37
38 Link with the following libraries:
39
40 - on macOS: AudioToolbox
41 - on iOS: AudioToolbox, AVFoundation
42 - on FreeBSD: asound
43 - on Linux: asound
44 - on Android: link with OpenSLES or aaudio
45 - on Windows with MSVC or Clang toolchain: no action needed, libs are defined in-source via pragma-comment-lib
46 - on Windows with MINGW/MSYS2 gcc: compile with '-mwin32' and link with -lole32
47
48 FEATURE OVERVIEW
49 ================
50 You provide a mono- or stereo-stream of 32-bit float samples, which
51 Sokol Audio feeds into platform-specific audio backends:
52
53 - Windows: WASAPI
54 - Linux: ALSA
55 - FreeBSD: ALSA
56 - macOS: CoreAudio
57 - iOS: CoreAudio+AVAudioSession
58 - emscripten: WebAudio with ScriptProcessorNode
59 - Android: AAudio (default) or OpenSLES, select at build time
60
61 Sokol Audio will not do any buffer mixing or volume control, if you have
62 multiple independent input streams of sample data you need to perform the
63 mixing yourself before forwarding the data to Sokol Audio.
64
65 There are two mutually exclusive ways to provide the sample data:
66
67 1. Callback model: You provide a callback function, which will be called
68 when Sokol Audio needs new samples. On all platforms except emscripten,
69 this function is called from a separate thread.
70 2. Push model: Your code pushes small blocks of sample data from your
71 main loop or a thread you created. The pushed data is stored in
72 a ring buffer where it is pulled by the backend code when
73 needed.
74
75 The callback model is preferred because it is the most direct way to
76 feed sample data into the audio backends and also has less moving parts
77 (there is no ring buffer between your code and the audio backend).
78
79 Sometimes it is not possible to generate the audio stream directly in a
80 callback function running in a separate thread, for such cases Sokol Audio
81 provides the push-model as a convenience.
82
83 SOKOL AUDIO, SOLOUD AND MINIAUDIO
84 =================================
85 The WASAPI, ALSA, OpenSLES and CoreAudio backend code has been taken from the
86 SoLoud library (with some modifications, so any bugs in there are most
87 likely my fault). If you need a more fully-featured audio solution, check
88 out SoLoud, it's excellent:
89
90 https://github.com/jarikomppa/soloud
91
92 Another alternative which feature-wise is somewhere inbetween SoLoud and
93 sokol-audio might be MiniAudio:
94
95 https://github.com/mackron/miniaudio
96
97 GLOSSARY
98 ========
99 - stream buffer:
100 The internal audio data buffer, usually provided by the backend API. The
101 size of the stream buffer defines the base latency, smaller buffers have
102 lower latency but may cause audio glitches. Bigger buffers reduce or
103 eliminate glitches, but have a higher base latency.
104
105 - stream callback:
106 Optional callback function which is called by Sokol Audio when it
107 needs new samples. On Windows, macOS/iOS and Linux, this is called in
108 a separate thread, on WebAudio, this is called per-frame in the
109 browser thread.
110
111 - channel:
112 A discrete track of audio data, currently 1-channel (mono) and
113 2-channel (stereo) is supported and tested.
114
115 - sample:
116 The magnitude of an audio signal on one channel at a given time. In
117 Sokol Audio, samples are 32-bit float numbers in the range -1.0 to
118 +1.0.
119
120 - frame:
121 The tightly packed set of samples for all channels at a given time.
122 For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples.
123
124 - packet:
125 In Sokol Audio, a small chunk of audio data that is moved from the
126 main thread to the audio streaming thread in order to decouple the
127 rate at which the main thread provides new audio data, and the
128 streaming thread consuming audio data.
129
130 WORKING WITH SOKOL AUDIO
131 ========================
132 First call saudio_setup() with your preferred audio playback options.
133 In most cases you can stick with the default values, these provide
134 a good balance between low-latency and glitch-free playback
135 on all audio backends.
136
137 You should always provide a logging callback to be aware of any
138 warnings and errors. The easiest way is to use sokol_log.h for this:
139
140 #include "sokol_log.h"
141 // ...
142 saudio_setup(&(saudio_desc){
143 .logger = {
144 .func = slog_func,
145 }
146 });
147
148 If you want to use the callback-model, you need to provide a stream
149 callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb,
150 otherwise keep both function pointers zero-initialized.
151
152 Use push model and default playback parameters:
153
154 saudio_setup(&(saudio_desc){ .logger.func = slog_func });
155
156 Use stream callback model and default playback parameters:
157
158 saudio_setup(&(saudio_desc){
159 .stream_cb = my_stream_callback
160 .logger.func = slog_func,
161 });
162
163 The standard stream callback doesn't have a user data argument, if you want
164 that, use the alternative stream_userdata_cb and also set the user_data pointer:
165
166 saudio_setup(&(saudio_desc){
167 .stream_userdata_cb = my_stream_callback,
168 .user_data = &my_data
169 .logger.func = slog_func,
170 });
171
172 The following playback parameters can be provided through the
173 saudio_desc struct:
174
175 General parameters (both for stream-callback and push-model):
176
177 int sample_rate -- the sample rate in Hz, default: 44100
178 int num_channels -- number of channels, default: 1 (mono)
179 int buffer_frames -- number of frames in streaming buffer, default: 2048
180
181 The stream callback prototype (either with or without userdata):
182
183 void (*stream_cb)(float* buffer, int num_frames, int num_channels)
184 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data)
185 Function pointer to the user-provide stream callback.
186
187 Push-model parameters:
188
189 int packet_frames -- number of frames in a packet, default: 128
190 int num_packets -- number of packets in ring buffer, default: 64
191
192 The sample_rate and num_channels parameters are only hints for the audio
193 backend, it isn't guaranteed that those are the values used for actual
194 playback.
195
196 To get the actual parameters, call the following functions after
197 saudio_setup():
198
199 int saudio_sample_rate(void)
200 int saudio_channels(void);
201
202 It's unlikely that the number of channels will be different than requested,
203 but a different sample rate isn't uncommon.
204
205 (NOTE: there's an yet unsolved issue when an audio backend might switch
206 to a different sample rate when switching output devices, for instance
207 plugging in a bluetooth headset, this case is currently not handled in
208 Sokol Audio).
209
210 You can check if audio initialization was successful with
211 saudio_isvalid(). If backend initialization failed for some reason
212 (for instance when there's no audio device in the machine), this
213 will return false. Not checking for success won't do any harm, all
214 Sokol Audio function will silently fail when called after initialization
215 has failed, so apart from missing audio output, nothing bad will happen.
216
217 Before your application exits, you should call
218
219 saudio_shutdown();
220
221 This stops the audio thread (on Linux, Windows and macOS/iOS) and
222 properly shuts down the audio backend.
223
224 THE STREAM CALLBACK MODEL
225 =========================
226 To use Sokol Audio in stream-callback-mode, provide a callback function
227 like this in the saudio_desc struct when calling saudio_setup():
228
229 void stream_cb(float* buffer, int num_frames, int num_channels) {
230 ...
231 }
232
233 Or the alternative version with a user-data argument:
234
235 void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) {
236 my_data_t* my_data = (my_data_t*) user_data;
237 ...
238 }
239
240 The job of the callback function is to fill the *buffer* with 32-bit
241 float sample values.
242
243 To output silence, fill the buffer with zeros:
244
245 void stream_cb(float* buffer, int num_frames, int num_channels) {
246 const int num_samples = num_frames * num_channels;
247 for (int i = 0; i < num_samples; i++) {
248 buffer[i] = 0.0f;
249 }
250 }
251
252 For stereo output (num_channels == 2), the samples for the left
253 and right channel are interleaved:
254
255 void stream_cb(float* buffer, int num_frames, int num_channels) {
256 assert(2 == num_channels);
257 for (int i = 0; i < num_frames; i++) {
258 buffer[2*i + 0] = ...; // left channel
259 buffer[2*i + 1] = ...; // right channel
260 }
261 }
262
263 Please keep in mind that the stream callback function is running in a
264 separate thread, if you need to share data with the main thread you need
265 to take care yourself to make the access to the shared data thread-safe!
266
267 THE PUSH MODEL
268 ==============
269 To use the push-model for providing audio data, simply don't set (keep
270 zero-initialized) the stream_cb field in the saudio_desc struct when
271 calling saudio_setup().
272
273 To provide sample data with the push model, call the saudio_push()
274 function at regular intervals (for instance once per frame). You can
275 call the saudio_expect() function to ask Sokol Audio how much room is
276 in the ring buffer, but if you provide a continuous stream of data
277 at the right sample rate, saudio_expect() isn't required (it's a simple
278 way to sync/throttle your sample generation code with the playback
279 rate though).
280
281 With saudio_push() you may need to maintain your own intermediate sample
282 buffer, since pushing individual sample values isn't very efficient.
283 The following example is from the MOD player sample in
284 sokol-samples (https://github.com/floooh/sokol-samples):
285
286 const int num_frames = saudio_expect();
287 if (num_frames > 0) {
288 const int num_samples = num_frames * saudio_channels();
289 read_samples(flt_buf, num_samples);
290 saudio_push(flt_buf, num_frames);
291 }
292
293 Another option is to ignore saudio_expect(), and just push samples as they
294 are generated in small batches. In this case you *need* to generate the
295 samples at the right sample rate:
296
297 The following example is taken from the Tiny Emulators project
298 (https://github.com/floooh/chips-test), this is for mono playback,
299 so (num_samples == num_frames):
300
301 // tick the sound generator
302 if (ay38910_tick(&sys->psg)) {
303 // new sample is ready
304 sys->sample_buffer[sys->sample_pos++] = sys->psg.sample;
305 if (sys->sample_pos == sys->num_samples) {
306 // new sample packet is ready
307 saudio_push(sys->sample_buffer, sys->num_samples);
308 sys->sample_pos = 0;
309 }
310 }
311
312 THE WEBAUDIO BACKEND
313 ====================
314 The WebAudio backend is currently using a ScriptProcessorNode callback to
315 feed the sample data into WebAudio. ScriptProcessorNode has been
316 deprecated for a while because it is running from the main thread, with
317 the default initialization parameters it works 'pretty well' though.
318 Ultimately Sokol Audio will use Audio Worklets, but this requires a few
319 more things to fall into place (Audio Worklets implemented everywhere,
320 SharedArrayBuffers enabled again, and I need to figure out a 'low-cost'
321 solution in terms of implementation effort, since Audio Worklets are
322 a lot more complex than ScriptProcessorNode if the audio data needs to come
323 from the main thread).
324
325 The WebAudio backend is automatically selected when compiling for
326 emscripten (__EMSCRIPTEN__ define exists).
327
328 https://developers.google.com/web/updates/2017/12/audio-worklet
329 https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern
330
331 "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/
332
333 Also see: https://blog.paul.cx/post/a-wait-free-spsc-ringbuffer-for-the-web/
334
335 THE COREAUDIO BACKEND
336 =====================
337 The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined).
338 Since the CoreAudio API is implemented in C (not Objective-C) on macOS the
339 implementation part of Sokol Audio can be included into a C source file.
340
341 However on iOS, Sokol Audio must be compiled as Objective-C due to it's
342 reliance on the AVAudioSession object. The iOS code path support both
343 being compiled with or without ARC (Automatic Reference Counting).
344
345 For thread synchronisation, the CoreAudio backend will use the
346 pthread_mutex_* functions.
347
348 The incoming floating point samples will be directly forwarded to
349 CoreAudio without further conversion.
350
351 macOS and iOS applications that use Sokol Audio need to link with
352 the AudioToolbox framework.
353
354 THE WASAPI BACKEND
355 ==================
356 The WASAPI backend is automatically selected when compiling on Windows
357 (_WIN32 is defined).
358
359 For thread synchronisation a Win32 critical section is used.
360
361 WASAPI may use a different size for its own streaming buffer then requested,
362 so the base latency may be slightly bigger. The current backend implementation
363 converts the incoming floating point sample values to signed 16-bit
364 integers.
365
366 The required Windows system DLLs are linked with #pragma comment(lib, ...),
367 so you shouldn't need to add additional linker libs in the build process
368 (otherwise this is a bug which should be fixed in sokol_audio.h).
369
370 THE ALSA BACKEND
371 ================
372 The ALSA backend is automatically selected when compiling on Linux
373 ('linux' is defined).
374
375 For thread synchronisation, the pthread_mutex_* functions are used.
376
377 Samples are directly forwarded to ALSA in 32-bit float format, no
378 further conversion is taking place.
379
380 You need to link with the 'asound' library, and the <alsa/asoundlib.h>
381 header must be present (usually both are installed with some sort
382 of ALSA development package).
383
384
385 MEMORY ALLOCATION OVERRIDE
386 ==========================
387 You can override the memory allocation functions at initialization time
388 like this:
389
390 void* my_alloc(size_t size, void* user_data) {
391 return malloc(size);
392 }
393
394 void my_free(void* ptr, void* user_data) {
395 free(ptr);
396 }
397
398 ...
399 saudio_setup(&(saudio_desc){
400 // ...
401 .allocator = {
402 .alloc_fn = my_alloc,
403 .free_fn = my_free,
404 .user_data = ...,
405 }
406 });
407 ...
408
409 If no overrides are provided, malloc and free will be used.
410
411 This only affects memory allocation calls done by sokol_audio.h
412 itself though, not any allocations in OS libraries.
413
414 Memory allocation will only happen on the same thread where saudio_setup()
415 was called, so you don't need to worry about thread-safety.
416
417
418 ERROR REPORTING AND LOGGING
419 ===========================
420 To get any logging information at all you need to provide a logging callback in the setup call
421 the easiest way is to use sokol_log.h:
422
423 #include "sokol_log.h"
424
425 saudio_setup(&(saudio_desc){ .logger.func = slog_func });
426
427 To override logging with your own callback, first write a logging function like this:
428
429 void my_log(const char* tag, // e.g. 'saudio'
430 uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info
431 uint32_t log_item_id, // SAUDIO_LOGITEM_*
432 const char* message_or_null, // a message string, may be nullptr in release mode
433 uint32_t line_nr, // line number in sokol_audio.h
434 const char* filename_or_null, // source filename, may be nullptr in release mode
435 void* user_data)
436 {
437 ...
438 }
439
440 ...and then setup sokol-audio like this:
441
442 saudio_setup(&(saudio_desc){
443 .logger = {
444 .func = my_log,
445 .user_data = my_user_data,
446 }
447 });
448
449 The provided logging function must be reentrant (e.g. be callable from
450 different threads).
451
452 If you don't want to provide your own custom logger it is highly recommended to use
453 the standard logger in sokol_log.h instead, otherwise you won't see any warnings or
454 errors.
455
456 LICENSE
457 =======
458
459 zlib/libpng license
460
461 Copyright (c) 2018 Andre Weissflog
462
463 This software is provided 'as-is', without any express or implied warranty.
464 In no event will the authors be held liable for any damages arising from the
465 use of this software.
466
467 Permission is granted to anyone to use this software for any purpose,
468 including commercial applications, and to alter it and redistribute it
469 freely, subject to the following restrictions:
470
471 1. The origin of this software must not be misrepresented; you must not
472 claim that you wrote the original software. If you use this software in a
473 product, an acknowledgment in the product documentation would be
474 appreciated but is not required.
475
476 2. Altered source versions must be plainly marked as such, and must not
477 be misrepresented as being the original software.
478
479 3. This notice may not be removed or altered from any source
480 distribution.
481*/
482#define SOKOL_AUDIO_INCLUDED (1)
483#include <stddef.h> // size_t
484#include <stdint.h>
485#include <stdbool.h>
486
487#if defined(SOKOL_API_DECL) && !defined(SOKOL_AUDIO_API_DECL)
488#define SOKOL_AUDIO_API_DECL SOKOL_API_DECL
489#endif
490#ifndef SOKOL_AUDIO_API_DECL
491#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_AUDIO_IMPL)
492#define SOKOL_AUDIO_API_DECL __declspec(dllexport)
493#elif defined(_WIN32) && defined(SOKOL_DLL)
494#define SOKOL_AUDIO_API_DECL __declspec(dllimport)
495#else
496#define SOKOL_AUDIO_API_DECL extern
497#endif
498#endif
499
500#ifdef __cplusplus
501extern "C" {
502#endif
503
504/*
505 saudio_log_item
506
507 Log items are defined via X-Macros, and expanded to an
508 enum 'saudio_log_item', and in debug mode only,
509 corresponding strings.
510
511 Used as parameter in the logging callback.
512*/
513#define _SAUDIO_LOG_ITEMS \
514 _SAUDIO_LOGITEM_XMACRO(OK, "Ok") \
515 _SAUDIO_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed") \
516 _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_OPEN_FAILED, "snd_pcm_open() failed") \
517 _SAUDIO_LOGITEM_XMACRO(ALSA_FLOAT_SAMPLES_NOT_SUPPORTED, "floating point sample format not supported") \
518 _SAUDIO_LOGITEM_XMACRO(ALSA_REQUESTED_BUFFER_SIZE_NOT_SUPPORTED, "requested buffer size not supported") \
519 _SAUDIO_LOGITEM_XMACRO(ALSA_REQUESTED_CHANNEL_COUNT_NOT_SUPPORTED, "requested channel count not supported") \
520 _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_HW_PARAMS_SET_RATE_NEAR_FAILED, "snd_pcm_hw_params_set_rate_near() failed") \
521 _SAUDIO_LOGITEM_XMACRO(ALSA_SND_PCM_HW_PARAMS_FAILED, "snd_pcm_hw_params() failed") \
522 _SAUDIO_LOGITEM_XMACRO(ALSA_PTHREAD_CREATE_FAILED, "pthread_create() failed") \
523 _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_EVENT_FAILED, "CreateEvent() failed") \
524 _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_DEVICE_ENUMERATOR_FAILED, "CoCreateInstance() for IMMDeviceEnumerator failed") \
525 _SAUDIO_LOGITEM_XMACRO(WASAPI_GET_DEFAULT_AUDIO_ENDPOINT_FAILED, "IMMDeviceEnumerator.GetDefaultAudioEndpoint() failed") \
526 _SAUDIO_LOGITEM_XMACRO(WASAPI_DEVICE_ACTIVATE_FAILED, "IMMDevice.Activate() failed") \
527 _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_INITIALIZE_FAILED, "IAudioClient.Initialize() failed") \
528 _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_GET_BUFFER_SIZE_FAILED, "IAudioClient.GetBufferSize() failed") \
529 _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_GET_SERVICE_FAILED, "IAudioClient.GetService() failed") \
530 _SAUDIO_LOGITEM_XMACRO(WASAPI_AUDIO_CLIENT_SET_EVENT_HANDLE_FAILED, "IAudioClient.SetEventHandle() failed") \
531 _SAUDIO_LOGITEM_XMACRO(WASAPI_CREATE_THREAD_FAILED, "CreateThread() failed") \
532 _SAUDIO_LOGITEM_XMACRO(AAUDIO_STREAMBUILDER_OPEN_STREAM_FAILED, "AAudioStreamBuilder_openStream() failed") \
533 _SAUDIO_LOGITEM_XMACRO(AAUDIO_PTHREAD_CREATE_FAILED, "pthread_create() failed after AAUDIO_ERROR_DISCONNECTED") \
534 _SAUDIO_LOGITEM_XMACRO(AAUDIO_RESTARTING_STREAM_AFTER_ERROR, "restarting AAudio stream after error") \
535 _SAUDIO_LOGITEM_XMACRO(USING_AAUDIO_BACKEND, "using AAudio backend") \
536 _SAUDIO_LOGITEM_XMACRO(AAUDIO_CREATE_STREAMBUILDER_FAILED, "AAudio_createStreamBuilder() failed") \
537 _SAUDIO_LOGITEM_XMACRO(USING_SLES_BACKEND, "using OpenSLES backend") \
538 _SAUDIO_LOGITEM_XMACRO(SLES_CREATE_ENGINE_FAILED, "slCreateEngine() failed") \
539 _SAUDIO_LOGITEM_XMACRO(SLES_ENGINE_GET_ENGINE_INTERFACE_FAILED, "GetInterface() for SL_IID_ENGINE failed") \
540 _SAUDIO_LOGITEM_XMACRO(SLES_CREATE_OUTPUT_MIX_FAILED, "CreateOutputMix() failed") \
541 _SAUDIO_LOGITEM_XMACRO(SLES_MIXER_GET_VOLUME_INTERFACE_FAILED, "GetInterface() for SL_IID_VOLUME failed") \
542 _SAUDIO_LOGITEM_XMACRO(SLES_ENGINE_CREATE_AUDIO_PLAYER_FAILED, "CreateAudioPlayer() failed") \
543 _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_PLAY_INTERFACE_FAILED, "GetInterface() for SL_IID_PLAY failed") \
544 _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_VOLUME_INTERFACE_FAILED, "GetInterface() for SL_IID_VOLUME failed") \
545 _SAUDIO_LOGITEM_XMACRO(SLES_PLAYER_GET_BUFFERQUEUE_INTERFACE_FAILED, "GetInterface() for SL_IID_ANDROIDSIMPLEBUFFERQUEUE failed") \
546 _SAUDIO_LOGITEM_XMACRO(COREAUDIO_NEW_OUTPUT_FAILED, "AudioQueueNewOutput() failed") \
547 _SAUDIO_LOGITEM_XMACRO(COREAUDIO_ALLOCATE_BUFFER_FAILED, "AudioQueueAllocateBuffer() failed") \
548 _SAUDIO_LOGITEM_XMACRO(COREAUDIO_START_FAILED, "AudioQueueStart() failed") \
549 _SAUDIO_LOGITEM_XMACRO(BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE, "backend buffer size isn't multiple of packet size") \
550
551#define _SAUDIO_LOGITEM_XMACRO(item,msg) SAUDIO_LOGITEM_##item,
552typedef enum saudio_log_item {
553 _SAUDIO_LOG_ITEMS
554} saudio_log_item;
555#undef _SAUDIO_LOGITEM_XMACRO
556
557/*
558 saudio_logger
559
560 Used in saudio_desc to provide a custom logging and error reporting
561 callback to sokol-audio.
562*/
563typedef struct saudio_logger {
564 void (*func)(
565 const char* tag, // always "saudio"
566 uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info
567 uint32_t log_item_id, // SAUDIO_LOGITEM_*
568 const char* message_or_null, // a message string, may be nullptr in release mode
569 uint32_t line_nr, // line number in sokol_audio.h
570 const char* filename_or_null, // source filename, may be nullptr in release mode
571 void* user_data);
572 void* user_data;
573} saudio_logger;
574
575/*
576 saudio_allocator
577
578 Used in saudio_desc to provide custom memory-alloc and -free functions
579 to sokol_audio.h. If memory management should be overridden, both the
580 alloc_fn and free_fn function must be provided (e.g. it's not valid to
581 override one function but not the other).
582*/
583typedef struct saudio_allocator {
584 void* (*alloc_fn)(size_t size, void* user_data);
585 void (*free_fn)(void* ptr, void* user_data);
586 void* user_data;
587} saudio_allocator;
588
589typedef struct saudio_desc {
590 int sample_rate; // requested sample rate
591 int num_channels; // number of channels, default: 1 (mono)
592 int buffer_frames; // number of frames in streaming buffer
593 int packet_frames; // number of frames in a packet
594 int num_packets; // number of packets in packet queue
595 void (*stream_cb)(float* buffer, int num_frames, int num_channels); // optional streaming callback (no user data)
596 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); //... and with user data
597 void* user_data; // optional user data argument for stream_userdata_cb
598 saudio_allocator allocator; // optional allocation override functions
599 saudio_logger logger; // optional logging function (default: NO LOGGING!)
600} saudio_desc;
601
602/* setup sokol-audio */
603SOKOL_AUDIO_API_DECL void saudio_setup(const saudio_desc* desc);
604/* shutdown sokol-audio */
605SOKOL_AUDIO_API_DECL void saudio_shutdown(void);
606/* true after setup if audio backend was successfully initialized */
607SOKOL_AUDIO_API_DECL bool saudio_isvalid(void);
608/* return the saudio_desc.user_data pointer */
609SOKOL_AUDIO_API_DECL void* saudio_userdata(void);
610/* return a copy of the original saudio_desc struct */
611SOKOL_AUDIO_API_DECL saudio_desc saudio_query_desc(void);
612/* actual sample rate */
613SOKOL_AUDIO_API_DECL int saudio_sample_rate(void);
614/* return actual backend buffer size in number of frames */
615SOKOL_AUDIO_API_DECL int saudio_buffer_frames(void);
616/* actual number of channels */
617SOKOL_AUDIO_API_DECL int saudio_channels(void);
618/* return true if audio context is currently suspended (only in WebAudio backend, all other backends return false) */
619SOKOL_AUDIO_API_DECL bool saudio_suspended(void);
620/* get current number of frames to fill packet queue */
621SOKOL_AUDIO_API_DECL int saudio_expect(void);
622/* push sample frames from main thread, returns number of frames actually pushed */
623SOKOL_AUDIO_API_DECL int saudio_push(const float* frames, int num_frames);
624
625#ifdef __cplusplus
626} /* extern "C" */
627
628/* reference-based equivalents for c++ */
629inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc); }
630
631#endif
632#endif // SOKOL_AUDIO_INCLUDED
633
634// ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██
635// ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
636// ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██
637// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
638// ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████
639//
640// >>implementation
641#ifdef SOKOL_AUDIO_IMPL
642#define SOKOL_AUDIO_IMPL_INCLUDED (1)
643
644#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE)
645#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use saudio_desc.allocator to override memory allocation functions"
646#endif
647
648#include <stdlib.h> // alloc, free
649#include <string.h> // memset, memcpy
650#include <stddef.h> // size_t
651
652#ifndef SOKOL_API_IMPL
653 #define SOKOL_API_IMPL
654#endif
655#ifndef SOKOL_DEBUG
656 #ifndef NDEBUG
657 #define SOKOL_DEBUG
658 #endif
659#endif
660#ifndef SOKOL_ASSERT
661 #include <assert.h>
662 #define SOKOL_ASSERT(c) assert(c)
663#endif
664
665#ifndef _SOKOL_PRIVATE
666 #if defined(__GNUC__) || defined(__clang__)
667 #define _SOKOL_PRIVATE __attribute__((unused)) static
668 #else
669 #define _SOKOL_PRIVATE static
670 #endif
671#endif
672
673#ifndef _SOKOL_UNUSED
674 #define _SOKOL_UNUSED(x) (void)(x)
675#endif
676
677// platform detection defines
678#if defined(SOKOL_DUMMY_BACKEND)
679 // nothing
680#elif defined(__APPLE__)
681 #define _SAUDIO_APPLE (1)
682 #include <TargetConditionals.h>
683 #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
684 #define _SAUDIO_IOS (1)
685 #else
686 #define _SAUDIO_MACOS (1)
687 #endif
688#elif defined(__EMSCRIPTEN__)
689 #define _SAUDIO_EMSCRIPTEN (1)
690#elif defined(_WIN32)
691 #define _SAUDIO_WINDOWS (1)
692 #include <winapifamily.h>
693 #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP))
694 #error "sokol_audio.h no longer supports UWP"
695 #endif
696#elif defined(__ANDROID__)
697 #define _SAUDIO_ANDROID (1)
698 #if !defined(SAUDIO_ANDROID_SLES) && !defined(SAUDIO_ANDROID_AAUDIO)
699 #define SAUDIO_ANDROID_AAUDIO (1)
700 #endif
701#elif defined(__linux__) || defined(__unix__)
702 #define _SAUDIO_LINUX (1)
703#else
704#error "sokol_audio.h: Unknown platform"
705#endif
706
707// platform-specific headers and definitions
708#if defined(SOKOL_DUMMY_BACKEND)
709 #define _SAUDIO_NOTHREADS (1)
710#elif defined(_SAUDIO_WINDOWS)
711 #define _SAUDIO_WINTHREADS (1)
712 #ifndef WIN32_LEAN_AND_MEAN
713 #define WIN32_LEAN_AND_MEAN
714 #endif
715 #ifndef NOMINMAX
716 #define NOMINMAX
717 #endif
718 #include <windows.h>
719 #include <synchapi.h>
720 #pragma comment (lib, "kernel32")
721 #pragma comment (lib, "ole32")
722 #ifndef CINTERFACE
723 #define CINTERFACE
724 #endif
725 #ifndef COBJMACROS
726 #define COBJMACROS
727 #endif
728 #ifndef CONST_VTABLE
729 #define CONST_VTABLE
730 #endif
731 #include <mmdeviceapi.h>
732 /* TCC's audioclient.h may miss these transitive audio format/channel mask definitions. */
733 #include <mmreg.h>
734 #include <audioclient.h>
735 static const IID _saudio_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2} };
736 static const IID _saudio_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6} };
737 static const CLSID _saudio_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e} };
738 static const IID _saudio_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2} };
739 static const IID _saudio_IID_Devinterface_Audio_Render = { 0xe6327cad, 0xdcec, 0x4949, {0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2} };
740 static const IID _saudio_IID_IActivateAudioInterface_Completion_Handler = { 0x94ea2b94, 0xe9cc, 0x49e0, {0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90} };
741 static const GUID _saudio_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} };
742 #if defined(__cplusplus)
743 #define _SOKOL_AUDIO_WIN32COM_ID(x) (x)
744 #else
745 #define _SOKOL_AUDIO_WIN32COM_ID(x) (&x)
746 #endif
747 /* fix for Visual Studio 2015 SDKs */
748 #ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
749 #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
750 #endif
751 #ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
752 #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
753 #endif
754 #ifdef _MSC_VER
755 #pragma warning(push)
756 #pragma warning(disable:4505) /* unreferenced local function has been removed */
757 #endif
758#elif defined(_SAUDIO_APPLE)
759 #define _SAUDIO_PTHREADS (1)
760 #include <pthread.h>
761 #if defined(_SAUDIO_IOS)
762 // always use system headers on iOS (for now at least)
763 #if !defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
764 #define SAUDIO_OSX_USE_SYSTEM_HEADERS (1)
765 #endif
766 #if !defined(__cplusplus)
767 #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields)
768 #error "sokol_audio.h on iOS requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)"
769 #endif
770 #endif
771 #include <AudioToolbox/AudioToolbox.h>
772 #include <AVFoundation/AVFoundation.h>
773 #else
774 #if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
775 #include <AudioToolbox/AudioToolbox.h>
776 #endif
777 #endif
778#elif defined(_SAUDIO_ANDROID)
779 #define _SAUDIO_PTHREADS (1)
780 #include <pthread.h>
781 #if defined(SAUDIO_ANDROID_SLES)
782 #include "SLES/OpenSLES_Android.h"
783 #elif defined(SAUDIO_ANDROID_AAUDIO)
784 #include "aaudio/AAudio.h"
785 #endif
786#elif defined(_SAUDIO_LINUX)
787 #if !defined(__FreeBSD__)
788 #include <alloca.h>
789 #endif
790 #define _SAUDIO_PTHREADS (1)
791 #include <pthread.h>
792 #define ALSA_PCM_NEW_HW_PARAMS_API
793 #include <alsa/asoundlib.h>
794#elif defined(__EMSCRIPTEN__)
795 #define _SAUDIO_NOTHREADS (1)
796 #include <emscripten/emscripten.h>
797#endif
798
799#define _saudio_def(val, def) (((val) == 0) ? (def) : (val))
800#define _saudio_def_flt(val, def) (((val) == 0.0f) ? (def) : (val))
801
802#define _SAUDIO_DEFAULT_SAMPLE_RATE (44100)
803#define _SAUDIO_DEFAULT_BUFFER_FRAMES (2048)
804#define _SAUDIO_DEFAULT_PACKET_FRAMES (128)
805#define _SAUDIO_DEFAULT_NUM_PACKETS ((_SAUDIO_DEFAULT_BUFFER_FRAMES/_SAUDIO_DEFAULT_PACKET_FRAMES)*4)
806
807#ifndef SAUDIO_RING_MAX_SLOTS
808#define SAUDIO_RING_MAX_SLOTS (1024)
809#endif
810
811// ███████ ████████ ██████ ██ ██ ██████ ████████ ███████
812// ██ ██ ██ ██ ██ ██ ██ ██ ██
813// ███████ ██ ██████ ██ ██ ██ ██ ███████
814// ██ ██ ██ ██ ██ ██ ██ ██ ██
815// ███████ ██ ██ ██ ██████ ██████ ██ ███████
816//
817// >>structs
818#if defined(_SAUDIO_PTHREADS)
819
820typedef struct {
821 pthread_mutex_t mutex;
822} _saudio_mutex_t;
823
824#elif defined(_SAUDIO_WINTHREADS)
825
826typedef struct {
827 CRITICAL_SECTION critsec;
828} _saudio_mutex_t;
829
830#elif defined(_SAUDIO_NOTHREADS)
831
832typedef struct {
833 int dummy_mutex;
834} _saudio_mutex_t;
835
836#endif
837
838#if defined(SOKOL_DUMMY_BACKEND)
839
840typedef struct {
841 int dummy;
842} _saudio_dummy_backend_t;
843
844#elif defined(_SAUDIO_APPLE)
845
846#if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
847
848typedef AudioQueueRef _saudio_AudioQueueRef;
849typedef AudioQueueBufferRef _saudio_AudioQueueBufferRef;
850typedef AudioStreamBasicDescription _saudio_AudioStreamBasicDescription;
851typedef OSStatus _saudio_OSStatus;
852
853#define _saudio_kAudioFormatLinearPCM (kAudioFormatLinearPCM)
854#define _saudio_kLinearPCMFormatFlagIsFloat (kLinearPCMFormatFlagIsFloat)
855#define _saudio_kAudioFormatFlagIsPacked (kAudioFormatFlagIsPacked)
856
857#else
858#ifdef __cplusplus
859extern "C" {
860#endif
861
862// embedded AudioToolbox declarations
863typedef uint32_t _saudio_AudioFormatID;
864typedef uint32_t _saudio_AudioFormatFlags;
865typedef int32_t _saudio_OSStatus;
866typedef uint32_t _saudio_SMPTETimeType;
867typedef uint32_t _saudio_SMPTETimeFlags;
868typedef uint32_t _saudio_AudioTimeStampFlags;
869typedef void* _saudio_CFRunLoopRef;
870typedef void* _saudio_CFStringRef;
871typedef void* _saudio_AudioQueueRef;
872
873#define _saudio_kAudioFormatLinearPCM ('lpcm')
874#define _saudio_kLinearPCMFormatFlagIsFloat (1U << 0)
875#define _saudio_kAudioFormatFlagIsPacked (1U << 3)
876
877typedef struct _saudio_AudioStreamBasicDescription {
878 double mSampleRate;
879 _saudio_AudioFormatID mFormatID;
880 _saudio_AudioFormatFlags mFormatFlags;
881 uint32_t mBytesPerPacket;
882 uint32_t mFramesPerPacket;
883 uint32_t mBytesPerFrame;
884 uint32_t mChannelsPerFrame;
885 uint32_t mBitsPerChannel;
886 uint32_t mReserved;
887} _saudio_AudioStreamBasicDescription;
888
889typedef struct _saudio_AudioStreamPacketDescription {
890 int64_t mStartOffset;
891 uint32_t mVariableFramesInPacket;
892 uint32_t mDataByteSize;
893} _saudio_AudioStreamPacketDescription;
894
895typedef struct _saudio_SMPTETime {
896 int16_t mSubframes;
897 int16_t mSubframeDivisor;
898 uint32_t mCounter;
899 _saudio_SMPTETimeType mType;
900 _saudio_SMPTETimeFlags mFlags;
901 int16_t mHours;
902 int16_t mMinutes;
903 int16_t mSeconds;
904 int16_t mFrames;
905} _saudio_SMPTETime;
906
907typedef struct _saudio_AudioTimeStamp {
908 double mSampleTime;
909 uint64_t mHostTime;
910 double mRateScalar;
911 uint64_t mWordClockTime;
912 _saudio_SMPTETime mSMPTETime;
913 _saudio_AudioTimeStampFlags mFlags;
914 uint32_t mReserved;
915} _saudio_AudioTimeStamp;
916
917typedef struct _saudio_AudioQueueBuffer {
918 const uint32_t mAudioDataBytesCapacity;
919 void* const mAudioData;
920 uint32_t mAudioDataByteSize;
921 void * mUserData;
922 const uint32_t mPacketDescriptionCapacity;
923 _saudio_AudioStreamPacketDescription* const mPacketDescriptions;
924 uint32_t mPacketDescriptionCount;
925} _saudio_AudioQueueBuffer;
926typedef _saudio_AudioQueueBuffer* _saudio_AudioQueueBufferRef;
927
928typedef void (*_saudio_AudioQueueOutputCallback)(void* user_data, _saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer);
929
930extern _saudio_OSStatus AudioQueueNewOutput(const _saudio_AudioStreamBasicDescription* inFormat, _saudio_AudioQueueOutputCallback inCallbackProc, void* inUserData, _saudio_CFRunLoopRef inCallbackRunLoop, _saudio_CFStringRef inCallbackRunLoopMode, uint32_t inFlags, _saudio_AudioQueueRef* outAQ);
931extern _saudio_OSStatus AudioQueueDispose(_saudio_AudioQueueRef inAQ, bool inImmediate);
932extern _saudio_OSStatus AudioQueueAllocateBuffer(_saudio_AudioQueueRef inAQ, uint32_t inBufferByteSize, _saudio_AudioQueueBufferRef* outBuffer);
933extern _saudio_OSStatus AudioQueueEnqueueBuffer(_saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer, uint32_t inNumPacketDescs, const _saudio_AudioStreamPacketDescription* inPacketDescs);
934extern _saudio_OSStatus AudioQueueStart(_saudio_AudioQueueRef inAQ, const _saudio_AudioTimeStamp * inStartTime);
935extern _saudio_OSStatus AudioQueueStop(_saudio_AudioQueueRef inAQ, bool inImmediate);
936
937#ifdef __cplusplus
938} // extern "C"
939#endif
940
941#endif // SAUDIO_OSX_USE_SYSTEM_HEADERS
942
943typedef struct {
944 _saudio_AudioQueueRef ca_audio_queue;
945 #if defined(_SAUDIO_IOS)
946 id ca_interruption_handler;
947 #endif
948} _saudio_apple_backend_t;
949
950#elif defined(_SAUDIO_LINUX)
951
952typedef struct {
953 snd_pcm_t* device;
954 float* buffer;
955 int buffer_byte_size;
956 int buffer_frames;
957 pthread_t thread;
958 bool thread_stop;
959} _saudio_alsa_backend_t;
960
961#elif defined(SAUDIO_ANDROID_SLES)
962
963#define SAUDIO_SLES_NUM_BUFFERS (2)
964
965typedef struct {
966 pthread_mutex_t mutex;
967 pthread_cond_t cond;
968 int count;
969} _saudio_sles_semaphore_t;
970
971typedef struct {
972 SLObjectItf engine_obj;
973 SLEngineItf engine;
974 SLObjectItf output_mix_obj;
975 SLVolumeItf output_mix_vol;
976 SLDataLocator_OutputMix out_locator;
977 SLDataSink dst_data_sink;
978 SLObjectItf player_obj;
979 SLPlayItf player;
980 SLVolumeItf player_vol;
981 SLAndroidSimpleBufferQueueItf player_buffer_queue;
982
983 int16_t* output_buffers[SAUDIO_SLES_NUM_BUFFERS];
984 float* src_buffer;
985 int active_buffer;
986 _saudio_sles_semaphore_t buffer_sem;
987 pthread_t thread;
988 volatile int thread_stop;
989 SLDataLocator_AndroidSimpleBufferQueue in_locator;
990} _saudio_sles_backend_t;
991
992#elif defined(SAUDIO_ANDROID_AAUDIO)
993
994typedef struct {
995 AAudioStreamBuilder* builder;
996 AAudioStream* stream;
997 pthread_t thread;
998 pthread_mutex_t mutex;
999} _saudio_aaudio_backend_t;
1000
1001#elif defined(_SAUDIO_WINDOWS)
1002
1003typedef struct {
1004 HANDLE thread_handle;
1005 HANDLE buffer_end_event;
1006 bool stop;
1007 UINT32 dst_buffer_frames;
1008 int src_buffer_frames;
1009 int src_buffer_byte_size;
1010 int src_buffer_pos;
1011 float* src_buffer;
1012} _saudio_wasapi_thread_data_t;
1013
1014typedef struct {
1015 IMMDeviceEnumerator* device_enumerator;
1016 IMMDevice* device;
1017 IAudioClient* audio_client;
1018 IAudioRenderClient* render_client;
1019 _saudio_wasapi_thread_data_t thread;
1020} _saudio_wasapi_backend_t;
1021
1022#elif defined(_SAUDIO_EMSCRIPTEN)
1023
1024typedef struct {
1025 uint8_t* buffer;
1026} _saudio_web_backend_t;
1027
1028#else
1029#error "unknown platform"
1030#endif
1031
1032#if defined(SOKOL_DUMMY_BACKEND)
1033typedef _saudio_dummy_backend_t _saudio_backend_t;
1034#elif defined(_SAUDIO_APPLE)
1035typedef _saudio_apple_backend_t _saudio_backend_t;
1036#elif defined(_SAUDIO_EMSCRIPTEN)
1037typedef _saudio_web_backend_t _saudio_backend_t;
1038#elif defined(_SAUDIO_WINDOWS)
1039typedef _saudio_wasapi_backend_t _saudio_backend_t;
1040#elif defined(SAUDIO_ANDROID_SLES)
1041typedef _saudio_sles_backend_t _saudio_backend_t;
1042#elif defined(SAUDIO_ANDROID_AAUDIO)
1043typedef _saudio_aaudio_backend_t _saudio_backend_t;
1044#elif defined(_SAUDIO_LINUX)
1045typedef _saudio_alsa_backend_t _saudio_backend_t;
1046#endif
1047
1048/* a ringbuffer structure */
1049typedef struct {
1050 int head; // next slot to write to
1051 int tail; // next slot to read from
1052 int num; // number of slots in queue
1053 int queue[SAUDIO_RING_MAX_SLOTS];
1054} _saudio_ring_t;
1055
1056/* a packet FIFO structure */
1057typedef struct {
1058 bool valid;
1059 int packet_size; /* size of a single packets in bytes(!) */
1060 int num_packets; /* number of packet in fifo */
1061 uint8_t* base_ptr; /* packet memory chunk base pointer (dynamically allocated) */
1062 int cur_packet; /* current write-packet */
1063 int cur_offset; /* current byte-offset into current write packet */
1064 _saudio_mutex_t mutex; /* mutex for thread-safe access */
1065 _saudio_ring_t read_queue; /* buffers with data, ready to be streamed */
1066 _saudio_ring_t write_queue; /* empty buffers, ready to be pushed to */
1067} _saudio_fifo_t;
1068
1069/* sokol-audio state */
1070typedef struct {
1071 bool valid;
1072 bool setup_called;
1073 void (*stream_cb)(float* buffer, int num_frames, int num_channels);
1074 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data);
1075 void* user_data;
1076 int sample_rate; /* sample rate */
1077 int buffer_frames; /* number of frames in streaming buffer */
1078 int bytes_per_frame; /* filled by backend */
1079 int packet_frames; /* number of frames in a packet */
1080 int num_packets; /* number of packets in packet queue */
1081 int num_channels; /* actual number of channels */
1082 saudio_desc desc;
1083 _saudio_fifo_t fifo;
1084 _saudio_backend_t backend;
1085} _saudio_state_t;
1086
1087_SOKOL_PRIVATE _saudio_state_t _saudio;
1088
1089_SOKOL_PRIVATE bool _saudio_has_callback(void) {
1090 return (_saudio.stream_cb || _saudio.stream_userdata_cb);
1091}
1092
1093_SOKOL_PRIVATE void _saudio_stream_callback(float* buffer, int num_frames, int num_channels) {
1094 if (_saudio.stream_cb) {
1095 _saudio.stream_cb(buffer, num_frames, num_channels);
1096 }
1097 else if (_saudio.stream_userdata_cb) {
1098 _saudio.stream_userdata_cb(buffer, num_frames, num_channels, _saudio.user_data);
1099 }
1100}
1101
1102// ██ ██████ ██████ ██████ ██ ███ ██ ██████
1103// ██ ██ ██ ██ ██ ██ ████ ██ ██
1104// ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███
1105// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1106// ███████ ██████ ██████ ██████ ██ ██ ████ ██████
1107//
1108// >>logging
1109#if defined(SOKOL_DEBUG)
1110#define _SAUDIO_LOGITEM_XMACRO(item,msg) #item ": " msg,
1111static const char* _saudio_log_messages[] = {
1112 _SAUDIO_LOG_ITEMS
1113};
1114#undef _SAUDIO_LOGITEM_XMACRO
1115#endif // SOKOL_DEBUG
1116
1117#define _SAUDIO_PANIC(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 0, __LINE__)
1118#define _SAUDIO_ERROR(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 1, __LINE__)
1119#define _SAUDIO_WARN(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 2, __LINE__)
1120#define _SAUDIO_INFO(code) _saudio_log(SAUDIO_LOGITEM_ ##code, 3, __LINE__)
1121
1122static void _saudio_log(saudio_log_item log_item, uint32_t log_level, uint32_t line_nr) {
1123 if (_saudio.desc.logger.func) {
1124 #if defined(SOKOL_DEBUG)
1125 const char* filename = __FILE__;
1126 const char* message = _saudio_log_messages[log_item];
1127 #else
1128 const char* filename = 0;
1129 const char* message = 0;
1130 #endif
1131 _saudio.desc.logger.func("saudio", log_level, log_item, message, line_nr, filename, _saudio.desc.logger.user_data);
1132 }
1133 else {
1134 // for log level PANIC it would be 'undefined behaviour' to continue
1135 if (log_level == 0) {
1136 abort();
1137 }
1138 }
1139}
1140
1141// ███ ███ ███████ ███ ███ ██████ ██████ ██ ██
1142// ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██
1143// ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████
1144// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1145// ██ ██ ███████ ██ ██ ██████ ██ ██ ██
1146//
1147// >>memory
1148_SOKOL_PRIVATE void _saudio_clear(void* ptr, size_t size) {
1149 SOKOL_ASSERT(ptr && (size > 0));
1150 memset(ptr, 0, size);
1151}
1152
1153_SOKOL_PRIVATE void* _saudio_malloc(size_t size) {
1154 SOKOL_ASSERT(size > 0);
1155 void* ptr;
1156 if (_saudio.desc.allocator.alloc_fn) {
1157 ptr = _saudio.desc.allocator.alloc_fn(size, _saudio.desc.allocator.user_data);
1158 } else {
1159 ptr = malloc(size);
1160 }
1161 if (0 == ptr) {
1162 _SAUDIO_PANIC(MALLOC_FAILED);
1163 }
1164 return ptr;
1165}
1166
1167_SOKOL_PRIVATE void* _saudio_malloc_clear(size_t size) {
1168 void* ptr = _saudio_malloc(size);
1169 _saudio_clear(ptr, size);
1170 return ptr;
1171}
1172
1173_SOKOL_PRIVATE void _saudio_free(void* ptr) {
1174 if (_saudio.desc.allocator.free_fn) {
1175 _saudio.desc.allocator.free_fn(ptr, _saudio.desc.allocator.user_data);
1176 } else {
1177 free(ptr);
1178 }
1179}
1180
1181// ███ ███ ██ ██ ████████ ███████ ██ ██
1182// ████ ████ ██ ██ ██ ██ ██ ██
1183// ██ ████ ██ ██ ██ ██ █████ ███
1184// ██ ██ ██ ██ ██ ██ ██ ██ ██
1185// ██ ██ ██████ ██ ███████ ██ ██
1186//
1187// >>mutex
1188#if defined(_SAUDIO_NOTHREADS)
1189
1190_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { (void)m; }
1191_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { (void)m; }
1192_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { (void)m; }
1193_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { (void)m; }
1194
1195#elif defined(_SAUDIO_PTHREADS)
1196
1197_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) {
1198 pthread_mutexattr_t attr;
1199 pthread_mutexattr_init(&attr);
1200 pthread_mutex_init(&m->mutex, &attr);
1201}
1202
1203_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) {
1204 pthread_mutex_destroy(&m->mutex);
1205}
1206
1207_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) {
1208 pthread_mutex_lock(&m->mutex);
1209}
1210
1211_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) {
1212 pthread_mutex_unlock(&m->mutex);
1213}
1214
1215#elif defined(_SAUDIO_WINTHREADS)
1216
1217_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) {
1218 InitializeCriticalSection(&m->critsec);
1219}
1220
1221_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) {
1222 DeleteCriticalSection(&m->critsec);
1223}
1224
1225_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) {
1226 EnterCriticalSection(&m->critsec);
1227}
1228
1229_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) {
1230 LeaveCriticalSection(&m->critsec);
1231}
1232#else
1233#error "sokol_audio.h: unknown platform!"
1234#endif
1235
1236// ██████ ██ ███ ██ ██████ ██████ ██ ██ ███████ ███████ ███████ ██████
1237// ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1238// ██████ ██ ██ ██ ██ ██ ███ ██████ ██ ██ █████ █████ █████ ██████
1239// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1240// ██ ██ ██ ██ ████ ██████ ██████ ██████ ██ ██ ███████ ██ ██
1241//
1242// >>ringbuffer
1243_SOKOL_PRIVATE int _saudio_ring_idx(_saudio_ring_t* ring, int i) {
1244 return (i % ring->num);
1245}
1246
1247_SOKOL_PRIVATE void _saudio_ring_init(_saudio_ring_t* ring, int num_slots) {
1248 SOKOL_ASSERT((num_slots + 1) <= SAUDIO_RING_MAX_SLOTS);
1249 ring->head = 0;
1250 ring->tail = 0;
1251 /* one slot reserved to detect 'full' vs 'empty' */
1252 ring->num = num_slots + 1;
1253}
1254
1255_SOKOL_PRIVATE bool _saudio_ring_full(_saudio_ring_t* ring) {
1256 return _saudio_ring_idx(ring, ring->head + 1) == ring->tail;
1257}
1258
1259_SOKOL_PRIVATE bool _saudio_ring_empty(_saudio_ring_t* ring) {
1260 return ring->head == ring->tail;
1261}
1262
1263_SOKOL_PRIVATE int _saudio_ring_count(_saudio_ring_t* ring) {
1264 int count;
1265 if (ring->head >= ring->tail) {
1266 count = ring->head - ring->tail;
1267 }
1268 else {
1269 count = (ring->head + ring->num) - ring->tail;
1270 }
1271 SOKOL_ASSERT(count < ring->num);
1272 return count;
1273}
1274
1275_SOKOL_PRIVATE void _saudio_ring_enqueue(_saudio_ring_t* ring, int val) {
1276 SOKOL_ASSERT(!_saudio_ring_full(ring));
1277 ring->queue[ring->head] = val;
1278 ring->head = _saudio_ring_idx(ring, ring->head + 1);
1279}
1280
1281_SOKOL_PRIVATE int _saudio_ring_dequeue(_saudio_ring_t* ring) {
1282 SOKOL_ASSERT(!_saudio_ring_empty(ring));
1283 int val = ring->queue[ring->tail];
1284 ring->tail = _saudio_ring_idx(ring, ring->tail + 1);
1285 return val;
1286}
1287
1288// ███████ ██ ███████ ██████
1289// ██ ██ ██ ██ ██
1290// █████ ██ █████ ██ ██
1291// ██ ██ ██ ██ ██
1292// ██ ██ ██ ██████
1293//
1294// >>fifo
1295_SOKOL_PRIVATE void _saudio_fifo_init_mutex(_saudio_fifo_t* fifo) {
1296 /* this must be called before initializing both the backend and the fifo itself! */
1297 _saudio_mutex_init(&fifo->mutex);
1298}
1299
1300_SOKOL_PRIVATE void _saudio_fifo_destroy_mutex(_saudio_fifo_t* fifo) {
1301 _saudio_mutex_destroy(&fifo->mutex);
1302}
1303
1304_SOKOL_PRIVATE void _saudio_fifo_init(_saudio_fifo_t* fifo, int packet_size, int num_packets) {
1305 /* NOTE: there's a chicken-egg situation during the init phase where the
1306 streaming thread must be started before the fifo is actually initialized,
1307 thus the fifo init must already be protected from access by the fifo_read() func.
1308 */
1309 _saudio_mutex_lock(&fifo->mutex);
1310 SOKOL_ASSERT((packet_size > 0) && (num_packets > 0));
1311 fifo->packet_size = packet_size;
1312 fifo->num_packets = num_packets;
1313 fifo->base_ptr = (uint8_t*) _saudio_malloc((size_t)(packet_size * num_packets));
1314 fifo->cur_packet = -1;
1315 fifo->cur_offset = 0;
1316 _saudio_ring_init(&fifo->read_queue, num_packets);
1317 _saudio_ring_init(&fifo->write_queue, num_packets);
1318 for (int i = 0; i < num_packets; i++) {
1319 _saudio_ring_enqueue(&fifo->write_queue, i);
1320 }
1321 SOKOL_ASSERT(_saudio_ring_full(&fifo->write_queue));
1322 SOKOL_ASSERT(_saudio_ring_count(&fifo->write_queue) == num_packets);
1323 SOKOL_ASSERT(_saudio_ring_empty(&fifo->read_queue));
1324 SOKOL_ASSERT(_saudio_ring_count(&fifo->read_queue) == 0);
1325 fifo->valid = true;
1326 _saudio_mutex_unlock(&fifo->mutex);
1327}
1328
1329_SOKOL_PRIVATE void _saudio_fifo_shutdown(_saudio_fifo_t* fifo) {
1330 SOKOL_ASSERT(fifo->base_ptr);
1331 _saudio_free(fifo->base_ptr);
1332 fifo->base_ptr = 0;
1333 fifo->valid = false;
1334}
1335
1336_SOKOL_PRIVATE int _saudio_fifo_writable_bytes(_saudio_fifo_t* fifo) {
1337 _saudio_mutex_lock(&fifo->mutex);
1338 int num_bytes = (_saudio_ring_count(&fifo->write_queue) * fifo->packet_size);
1339 if (fifo->cur_packet != -1) {
1340 num_bytes += fifo->packet_size - fifo->cur_offset;
1341 }
1342 _saudio_mutex_unlock(&fifo->mutex);
1343 SOKOL_ASSERT((num_bytes >= 0) && (num_bytes <= (fifo->num_packets * fifo->packet_size)));
1344 return num_bytes;
1345}
1346
1347/* write new data to the write queue, this is called from main thread */
1348_SOKOL_PRIVATE int _saudio_fifo_write(_saudio_fifo_t* fifo, const uint8_t* ptr, int num_bytes) {
1349 /* returns the number of bytes written, this will be smaller then requested
1350 if the write queue runs full
1351 */
1352 int all_to_copy = num_bytes;
1353 while (all_to_copy > 0) {
1354 /* need to grab a new packet? */
1355 if (fifo->cur_packet == -1) {
1356 _saudio_mutex_lock(&fifo->mutex);
1357 if (!_saudio_ring_empty(&fifo->write_queue)) {
1358 fifo->cur_packet = _saudio_ring_dequeue(&fifo->write_queue);
1359 }
1360 _saudio_mutex_unlock(&fifo->mutex);
1361 SOKOL_ASSERT(fifo->cur_offset == 0);
1362 }
1363 /* append data to current write packet */
1364 if (fifo->cur_packet != -1) {
1365 int to_copy = all_to_copy;
1366 const int max_copy = fifo->packet_size - fifo->cur_offset;
1367 if (to_copy > max_copy) {
1368 to_copy = max_copy;
1369 }
1370 uint8_t* dst = fifo->base_ptr + fifo->cur_packet * fifo->packet_size + fifo->cur_offset;
1371 memcpy(dst, ptr, (size_t)to_copy);
1372 ptr += to_copy;
1373 fifo->cur_offset += to_copy;
1374 all_to_copy -= to_copy;
1375 SOKOL_ASSERT(fifo->cur_offset <= fifo->packet_size);
1376 SOKOL_ASSERT(all_to_copy >= 0);
1377 }
1378 else {
1379 /* early out if we're starving */
1380 int bytes_copied = num_bytes - all_to_copy;
1381 SOKOL_ASSERT((bytes_copied >= 0) && (bytes_copied < num_bytes));
1382 return bytes_copied;
1383 }
1384 /* if write packet is full, push to read queue */
1385 if (fifo->cur_offset == fifo->packet_size) {
1386 _saudio_mutex_lock(&fifo->mutex);
1387 _saudio_ring_enqueue(&fifo->read_queue, fifo->cur_packet);
1388 _saudio_mutex_unlock(&fifo->mutex);
1389 fifo->cur_packet = -1;
1390 fifo->cur_offset = 0;
1391 }
1392 }
1393 SOKOL_ASSERT(all_to_copy == 0);
1394 return num_bytes;
1395}
1396
1397/* read queued data, this is called form the stream callback (maybe separate thread) */
1398_SOKOL_PRIVATE int _saudio_fifo_read(_saudio_fifo_t* fifo, uint8_t* ptr, int num_bytes) {
1399 /* NOTE: fifo_read might be called before the fifo is properly initialized */
1400 _saudio_mutex_lock(&fifo->mutex);
1401 int num_bytes_copied = 0;
1402 if (fifo->valid) {
1403 SOKOL_ASSERT(0 == (num_bytes % fifo->packet_size));
1404 SOKOL_ASSERT(num_bytes <= (fifo->packet_size * fifo->num_packets));
1405 const int num_packets_needed = num_bytes / fifo->packet_size;
1406 uint8_t* dst = ptr;
1407 /* either pull a full buffer worth of data, or nothing */
1408 if (_saudio_ring_count(&fifo->read_queue) >= num_packets_needed) {
1409 for (int i = 0; i < num_packets_needed; i++) {
1410 int packet_index = _saudio_ring_dequeue(&fifo->read_queue);
1411 _saudio_ring_enqueue(&fifo->write_queue, packet_index);
1412 const uint8_t* src = fifo->base_ptr + packet_index * fifo->packet_size;
1413 memcpy(dst, src, (size_t)fifo->packet_size);
1414 dst += fifo->packet_size;
1415 num_bytes_copied += fifo->packet_size;
1416 }
1417 SOKOL_ASSERT(num_bytes == num_bytes_copied);
1418 }
1419 }
1420 _saudio_mutex_unlock(&fifo->mutex);
1421 return num_bytes_copied;
1422}
1423
1424// ██████ ██ ██ ███ ███ ███ ███ ██ ██
1425// ██ ██ ██ ██ ████ ████ ████ ████ ██ ██
1426// ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ████
1427// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1428// ██████ ██████ ██ ██ ██ ██ ██
1429//
1430// >>dummy
1431#if defined(SOKOL_DUMMY_BACKEND)
1432_SOKOL_PRIVATE bool _saudio_dummy_backend_init(void) {
1433 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1434 return true;
1435};
1436_SOKOL_PRIVATE void _saudio_dummy_backend_shutdown(void) { };
1437
1438// █████ ██ ███████ █████
1439// ██ ██ ██ ██ ██ ██
1440// ███████ ██ ███████ ███████
1441// ██ ██ ██ ██ ██ ██
1442// ██ ██ ███████ ███████ ██ ██
1443//
1444// >>alsa
1445#elif defined(_SAUDIO_LINUX)
1446
1447/* the streaming callback runs in a separate thread */
1448_SOKOL_PRIVATE void* _saudio_alsa_cb(void* param) {
1449 _SOKOL_UNUSED(param);
1450 while (!_saudio.backend.thread_stop) {
1451 /* snd_pcm_writei() will be blocking until it needs data */
1452 int write_res = snd_pcm_writei(_saudio.backend.device, _saudio.backend.buffer, (snd_pcm_uframes_t)_saudio.backend.buffer_frames);
1453 if (write_res < 0) {
1454 /* underrun occurred */
1455 snd_pcm_prepare(_saudio.backend.device);
1456 }
1457 else {
1458 /* fill the streaming buffer with new data */
1459 if (_saudio_has_callback()) {
1460 _saudio_stream_callback(_saudio.backend.buffer, _saudio.backend.buffer_frames, _saudio.num_channels);
1461 }
1462 else {
1463 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.buffer, _saudio.backend.buffer_byte_size)) {
1464 /* not enough read data available, fill the entire buffer with silence */
1465 _saudio_clear(_saudio.backend.buffer, (size_t)_saudio.backend.buffer_byte_size);
1466 }
1467 }
1468 }
1469 }
1470 return 0;
1471}
1472
1473_SOKOL_PRIVATE bool _saudio_alsa_backend_init(void) {
1474 int dir; uint32_t rate;
1475 int rc = snd_pcm_open(&_saudio.backend.device, "default", SND_PCM_STREAM_PLAYBACK, 0);
1476 if (rc < 0) {
1477 _SAUDIO_ERROR(ALSA_SND_PCM_OPEN_FAILED);
1478 return false;
1479 }
1480
1481 /* configuration works by restricting the 'configuration space' step
1482 by step, we require all parameters except the sample rate to
1483 match perfectly
1484 */
1485 snd_pcm_hw_params_t* params = 0;
1486 snd_pcm_hw_params_alloca(¶ms);
1487 snd_pcm_hw_params_any(_saudio.backend.device, params);
1488 snd_pcm_hw_params_set_access(_saudio.backend.device, params, SND_PCM_ACCESS_RW_INTERLEAVED);
1489 if (0 > snd_pcm_hw_params_set_format(_saudio.backend.device, params, SND_PCM_FORMAT_FLOAT_LE)) {
1490 _SAUDIO_ERROR(ALSA_FLOAT_SAMPLES_NOT_SUPPORTED);
1491 goto error;
1492 }
1493 if (0 > snd_pcm_hw_params_set_buffer_size(_saudio.backend.device, params, (snd_pcm_uframes_t)_saudio.buffer_frames)) {
1494 _SAUDIO_ERROR(ALSA_REQUESTED_BUFFER_SIZE_NOT_SUPPORTED);
1495 goto error;
1496 }
1497 if (0 > snd_pcm_hw_params_set_channels(_saudio.backend.device, params, (uint32_t)_saudio.num_channels)) {
1498 _SAUDIO_ERROR(ALSA_REQUESTED_CHANNEL_COUNT_NOT_SUPPORTED);
1499 goto error;
1500 }
1501 /* let ALSA pick a nearby sampling rate */
1502 rate = (uint32_t) _saudio.sample_rate;
1503 dir = 0;
1504 if (0 > snd_pcm_hw_params_set_rate_near(_saudio.backend.device, params, &rate, &dir)) {
1505 _SAUDIO_ERROR(ALSA_SND_PCM_HW_PARAMS_SET_RATE_NEAR_FAILED);
1506 goto error;
1507 }
1508 if (0 > snd_pcm_hw_params(_saudio.backend.device, params)) {
1509 _SAUDIO_ERROR(ALSA_SND_PCM_HW_PARAMS_FAILED);
1510 goto error;
1511 }
1512
1513 /* read back actual sample rate and channels */
1514 _saudio.sample_rate = (int)rate;
1515 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1516
1517 /* allocate the streaming buffer */
1518 _saudio.backend.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame;
1519 _saudio.backend.buffer_frames = _saudio.buffer_frames;
1520 _saudio.backend.buffer = (float*) _saudio_malloc_clear((size_t)_saudio.backend.buffer_byte_size);
1521
1522 /* create the buffer-streaming start thread */
1523 if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_alsa_cb, 0)) {
1524 _SAUDIO_ERROR(ALSA_PTHREAD_CREATE_FAILED);
1525 goto error;
1526 }
1527
1528 return true;
1529error:
1530 if (_saudio.backend.device) {
1531 snd_pcm_close(_saudio.backend.device);
1532 _saudio.backend.device = 0;
1533 }
1534 return false;
1535};
1536
1537_SOKOL_PRIVATE void _saudio_alsa_backend_shutdown(void) {
1538 SOKOL_ASSERT(_saudio.backend.device);
1539 _saudio.backend.thread_stop = true;
1540 pthread_join(_saudio.backend.thread, 0);
1541 snd_pcm_drain(_saudio.backend.device);
1542 snd_pcm_close(_saudio.backend.device);
1543 _saudio_free(_saudio.backend.buffer);
1544};
1545
1546// ██ ██ █████ ███████ █████ ██████ ██
1547// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1548// ██ █ ██ ███████ ███████ ███████ ██████ ██
1549// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██
1550// ███ ███ ██ ██ ███████ ██ ██ ██ ██
1551//
1552// >>wasapi
1553#elif defined(_SAUDIO_WINDOWS)
1554
1555/* fill intermediate buffer with new data and reset buffer_pos */
1556_SOKOL_PRIVATE void _saudio_wasapi_fill_buffer(void) {
1557 if (_saudio_has_callback()) {
1558 _saudio_stream_callback(_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_frames, _saudio.num_channels);
1559 }
1560 else {
1561 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_byte_size)) {
1562 /* not enough read data available, fill the entire buffer with silence */
1563 _saudio_clear(_saudio.backend.thread.src_buffer, (size_t)_saudio.backend.thread.src_buffer_byte_size);
1564 }
1565 }
1566}
1567
1568_SOKOL_PRIVATE int _saudio_wasapi_min(int a, int b) {
1569 return (a < b) ? a : b;
1570}
1571
1572_SOKOL_PRIVATE void _saudio_wasapi_submit_buffer(int num_frames) {
1573 BYTE* wasapi_buffer = 0;
1574 if (FAILED(IAudioRenderClient_GetBuffer(_saudio.backend.render_client, num_frames, &wasapi_buffer))) {
1575 return;
1576 }
1577 SOKOL_ASSERT(wasapi_buffer);
1578
1579 /* copy samples to WASAPI buffer, refill source buffer if needed */
1580 int num_remaining_samples = num_frames * _saudio.num_channels;
1581 int buffer_pos = _saudio.backend.thread.src_buffer_pos;
1582 const int buffer_size_in_samples = _saudio.backend.thread.src_buffer_byte_size / (int)sizeof(float);
1583 float* dst = (float*)wasapi_buffer;
1584 const float* dst_end = dst + num_remaining_samples;
1585 _SOKOL_UNUSED(dst_end); // suppress unused warning in release mode
1586 const float* src = _saudio.backend.thread.src_buffer;
1587
1588 while (num_remaining_samples > 0) {
1589 if (0 == buffer_pos) {
1590 _saudio_wasapi_fill_buffer();
1591 }
1592 const int samples_to_copy = _saudio_wasapi_min(num_remaining_samples, buffer_size_in_samples - buffer_pos);
1593 SOKOL_ASSERT((buffer_pos + samples_to_copy) <= buffer_size_in_samples);
1594 SOKOL_ASSERT((dst + samples_to_copy) <= dst_end);
1595 memcpy(dst, &src[buffer_pos], (size_t)samples_to_copy * sizeof(float));
1596 num_remaining_samples -= samples_to_copy;
1597 SOKOL_ASSERT(num_remaining_samples >= 0);
1598 buffer_pos += samples_to_copy;
1599 dst += samples_to_copy;
1600
1601 SOKOL_ASSERT(buffer_pos <= buffer_size_in_samples);
1602 if (buffer_pos == buffer_size_in_samples) {
1603 buffer_pos = 0;
1604 }
1605 }
1606 _saudio.backend.thread.src_buffer_pos = buffer_pos;
1607 IAudioRenderClient_ReleaseBuffer(_saudio.backend.render_client, num_frames, 0);
1608}
1609
1610_SOKOL_PRIVATE DWORD WINAPI _saudio_wasapi_thread_fn(LPVOID param) {
1611 (void)param;
1612 _saudio_wasapi_submit_buffer(_saudio.backend.thread.src_buffer_frames);
1613 IAudioClient_Start(_saudio.backend.audio_client);
1614 while (!_saudio.backend.thread.stop) {
1615 WaitForSingleObject(_saudio.backend.thread.buffer_end_event, INFINITE);
1616 UINT32 padding = 0;
1617 if (FAILED(IAudioClient_GetCurrentPadding(_saudio.backend.audio_client, &padding))) {
1618 continue;
1619 }
1620 SOKOL_ASSERT(_saudio.backend.thread.dst_buffer_frames >= padding);
1621 int num_frames = (int)_saudio.backend.thread.dst_buffer_frames - (int)padding;
1622 if (num_frames > 0) {
1623 _saudio_wasapi_submit_buffer(num_frames);
1624 }
1625 }
1626 return 0;
1627}
1628
1629_SOKOL_PRIVATE void _saudio_wasapi_release(void) {
1630 if (_saudio.backend.thread.src_buffer) {
1631 _saudio_free(_saudio.backend.thread.src_buffer);
1632 _saudio.backend.thread.src_buffer = 0;
1633 }
1634 if (_saudio.backend.render_client) {
1635 IAudioRenderClient_Release(_saudio.backend.render_client);
1636 _saudio.backend.render_client = 0;
1637 }
1638 if (_saudio.backend.audio_client) {
1639 IAudioClient_Release(_saudio.backend.audio_client);
1640 _saudio.backend.audio_client = 0;
1641 }
1642 if (_saudio.backend.device) {
1643 IMMDevice_Release(_saudio.backend.device);
1644 _saudio.backend.device = 0;
1645 }
1646 if (_saudio.backend.device_enumerator) {
1647 IMMDeviceEnumerator_Release(_saudio.backend.device_enumerator);
1648 _saudio.backend.device_enumerator = 0;
1649 }
1650 if (0 != _saudio.backend.thread.buffer_end_event) {
1651 CloseHandle(_saudio.backend.thread.buffer_end_event);
1652 _saudio.backend.thread.buffer_end_event = 0;
1653 }
1654}
1655
1656_SOKOL_PRIVATE bool _saudio_wasapi_backend_init(void) {
1657 REFERENCE_TIME dur;
1658 /* CoInitializeEx could have been called elsewhere already, in which
1659 case the function returns with S_FALSE (thus it does not make much
1660 sense to check the result)
1661 */
1662 HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
1663 _SOKOL_UNUSED(hr);
1664 _saudio.backend.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0);
1665 if (0 == _saudio.backend.thread.buffer_end_event) {
1666 _SAUDIO_ERROR(WASAPI_CREATE_EVENT_FAILED);
1667 goto error;
1668 }
1669 if (FAILED(CoCreateInstance(_SOKOL_AUDIO_WIN32COM_ID(_saudio_CLSID_IMMDeviceEnumerator),
1670 0, CLSCTX_ALL,
1671 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IMMDeviceEnumerator),
1672 (void**)&_saudio.backend.device_enumerator)))
1673 {
1674 _SAUDIO_ERROR(WASAPI_CREATE_DEVICE_ENUMERATOR_FAILED);
1675 goto error;
1676 }
1677 if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(_saudio.backend.device_enumerator,
1678 eRender, eConsole,
1679 &_saudio.backend.device)))
1680 {
1681 _SAUDIO_ERROR(WASAPI_GET_DEFAULT_AUDIO_ENDPOINT_FAILED);
1682 goto error;
1683 }
1684 if (FAILED(IMMDevice_Activate(_saudio.backend.device,
1685 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient),
1686 CLSCTX_ALL, 0,
1687 (void**)&_saudio.backend.audio_client)))
1688 {
1689 _SAUDIO_ERROR(WASAPI_DEVICE_ACTIVATE_FAILED);
1690 goto error;
1691 }
1692
1693 WAVEFORMATEXTENSIBLE fmtex;
1694 _saudio_clear(&fmtex, sizeof(fmtex));
1695 fmtex.Format.nChannels = (WORD)_saudio.num_channels;
1696 fmtex.Format.nSamplesPerSec = (DWORD)_saudio.sample_rate;
1697 fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
1698 fmtex.Format.wBitsPerSample = 32;
1699 fmtex.Format.nBlockAlign = (fmtex.Format.nChannels * fmtex.Format.wBitsPerSample) / 8;
1700 fmtex.Format.nAvgBytesPerSec = fmtex.Format.nSamplesPerSec * fmtex.Format.nBlockAlign;
1701 fmtex.Format.cbSize = 22; /* WORD + DWORD + GUID */
1702 fmtex.Samples.wValidBitsPerSample = 32;
1703 if (_saudio.num_channels == 1) {
1704 fmtex.dwChannelMask = SPEAKER_FRONT_CENTER;
1705 }
1706 else {
1707 fmtex.dwChannelMask = SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT;
1708 }
1709 fmtex.SubFormat = _saudio_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
1710 dur = (REFERENCE_TIME)
1711 (((double)_saudio.buffer_frames) / (((double)_saudio.sample_rate) * (1.0/10000000.0)));
1712 if (FAILED(IAudioClient_Initialize(_saudio.backend.audio_client,
1713 AUDCLNT_SHAREMODE_SHARED,
1714 AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
1715 dur, 0, (WAVEFORMATEX*)&fmtex, 0)))
1716 {
1717 _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_INITIALIZE_FAILED);
1718 goto error;
1719 }
1720 if (FAILED(IAudioClient_GetBufferSize(_saudio.backend.audio_client, &_saudio.backend.thread.dst_buffer_frames))) {
1721 _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_GET_BUFFER_SIZE_FAILED);
1722 goto error;
1723 }
1724 if (FAILED(IAudioClient_GetService(_saudio.backend.audio_client,
1725 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioRenderClient),
1726 (void**)&_saudio.backend.render_client)))
1727 {
1728 _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_GET_SERVICE_FAILED);
1729 goto error;
1730 }
1731 if (FAILED(IAudioClient_SetEventHandle(_saudio.backend.audio_client, _saudio.backend.thread.buffer_end_event))) {
1732 _SAUDIO_ERROR(WASAPI_AUDIO_CLIENT_SET_EVENT_HANDLE_FAILED);
1733 goto error;
1734 }
1735 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1736 _saudio.backend.thread.src_buffer_frames = _saudio.buffer_frames;
1737 _saudio.backend.thread.src_buffer_byte_size = _saudio.backend.thread.src_buffer_frames * _saudio.bytes_per_frame;
1738
1739 /* allocate an intermediate buffer for sample format conversion */
1740 _saudio.backend.thread.src_buffer = (float*) _saudio_malloc((size_t)_saudio.backend.thread.src_buffer_byte_size);
1741
1742 /* create streaming thread */
1743 _saudio.backend.thread.thread_handle = CreateThread(NULL, 0, _saudio_wasapi_thread_fn, 0, 0, 0);
1744 if (0 == _saudio.backend.thread.thread_handle) {
1745 _SAUDIO_ERROR(WASAPI_CREATE_THREAD_FAILED);
1746 goto error;
1747 }
1748 return true;
1749error:
1750 _saudio_wasapi_release();
1751 return false;
1752}
1753
1754_SOKOL_PRIVATE void _saudio_wasapi_backend_shutdown(void) {
1755 if (_saudio.backend.thread.thread_handle) {
1756 _saudio.backend.thread.stop = true;
1757 SetEvent(_saudio.backend.thread.buffer_end_event);
1758 WaitForSingleObject(_saudio.backend.thread.thread_handle, INFINITE);
1759 CloseHandle(_saudio.backend.thread.thread_handle);
1760 _saudio.backend.thread.thread_handle = 0;
1761 }
1762 if (_saudio.backend.audio_client) {
1763 IAudioClient_Stop(_saudio.backend.audio_client);
1764 }
1765 _saudio_wasapi_release();
1766 CoUninitialize();
1767}
1768
1769// ██ ██ ███████ ██████ █████ ██ ██ ██████ ██ ██████
1770// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1771// ██ █ ██ █████ ██████ ███████ ██ ██ ██ ██ ██ ██ ██
1772// ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1773// ███ ███ ███████ ██████ ██ ██ ██████ ██████ ██ ██████
1774//
1775// >>webaudio
1776#elif defined(_SAUDIO_EMSCRIPTEN)
1777
1778#ifdef __cplusplus
1779extern "C" {
1780#endif
1781
1782EMSCRIPTEN_KEEPALIVE int _saudio_emsc_pull(int num_frames) {
1783 SOKOL_ASSERT(_saudio.backend.buffer);
1784 if (num_frames == _saudio.buffer_frames) {
1785 if (_saudio_has_callback()) {
1786 _saudio_stream_callback((float*)_saudio.backend.buffer, num_frames, _saudio.num_channels);
1787 }
1788 else {
1789 const int num_bytes = num_frames * _saudio.bytes_per_frame;
1790 if (0 == _saudio_fifo_read(&_saudio.fifo, _saudio.backend.buffer, num_bytes)) {
1791 /* not enough read data available, fill the entire buffer with silence */
1792 _saudio_clear(_saudio.backend.buffer, (size_t)num_bytes);
1793 }
1794 }
1795 int res = (int) _saudio.backend.buffer;
1796 return res;
1797 }
1798 else {
1799 return 0;
1800 }
1801}
1802
1803#ifdef __cplusplus
1804} /* extern "C" */
1805#endif
1806
1807/* setup the WebAudio context and attach a ScriptProcessorNode */
1808EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size), {
1809 Module._saudio_context = null;
1810 Module._saudio_node = null;
1811 if (typeof AudioContext !== 'undefined') {
1812 Module._saudio_context = new AudioContext({
1813 sampleRate: sample_rate,
1814 latencyHint: 'interactive',
1815 });
1816 }
1817 else {
1818 Module._saudio_context = null;
1819 console.log('sokol_audio.h: no WebAudio support');
1820 }
1821 if (Module._saudio_context) {
1822 console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate);
1823 Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels);
1824 Module._saudio_node.onaudioprocess = (event) => {
1825 const num_frames = event.outputBuffer.length;
1826 const ptr = __saudio_emsc_pull(num_frames);
1827 if (ptr) {
1828 const num_channels = event.outputBuffer.numberOfChannels;
1829 for (let chn = 0; chn < num_channels; chn++) {
1830 const chan = event.outputBuffer.getChannelData(chn);
1831 for (let i = 0; i < num_frames; i++) {
1832 chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)]
1833 }
1834 }
1835 }
1836 };
1837 Module._saudio_node.connect(Module._saudio_context.destination);
1838
1839 // in some browsers, WebAudio needs to be activated on a user action
1840 const resume_webaudio = () => {
1841 if (Module._saudio_context) {
1842 if (Module._saudio_context.state === 'suspended') {
1843 Module._saudio_context.resume();
1844 }
1845 }
1846 };
1847 document.addEventListener('click', resume_webaudio, {once:true});
1848 document.addEventListener('touchend', resume_webaudio, {once:true});
1849 document.addEventListener('keydown', resume_webaudio, {once:true});
1850 return 1;
1851 }
1852 else {
1853 return 0;
1854 }
1855});
1856
1857/* shutdown the WebAudioContext and ScriptProcessorNode */
1858EM_JS(void, saudio_js_shutdown, (void), {
1859 \x2F\x2A\x2A @suppress {missingProperties} \x2A\x2F
1860 const ctx = Module._saudio_context;
1861 if (ctx !== null) {
1862 if (Module._saudio_node) {
1863 Module._saudio_node.disconnect();
1864 }
1865 ctx.close();
1866 Module._saudio_context = null;
1867 Module._saudio_node = null;
1868 }
1869});
1870
1871/* get the actual sample rate back from the WebAudio context */
1872EM_JS(int, saudio_js_sample_rate, (void), {
1873 if (Module._saudio_context) {
1874 return Module._saudio_context.sampleRate;
1875 }
1876 else {
1877 return 0;
1878 }
1879});
1880
1881/* get the actual buffer size in number of frames */
1882EM_JS(int, saudio_js_buffer_frames, (void), {
1883 if (Module._saudio_node) {
1884 return Module._saudio_node.bufferSize;
1885 }
1886 else {
1887 return 0;
1888 }
1889});
1890
1891/* return 1 if the WebAudio context is currently suspended, else 0 */
1892EM_JS(int, saudio_js_suspended, (void), {
1893 if (Module._saudio_context) {
1894 if (Module._saudio_context.state === 'suspended') {
1895 return 1;
1896 }
1897 else {
1898 return 0;
1899 }
1900 }
1901});
1902
1903_SOKOL_PRIVATE bool _saudio_webaudio_backend_init(void) {
1904 if (saudio_js_init(_saudio.sample_rate, _saudio.num_channels, _saudio.buffer_frames)) {
1905 _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels;
1906 _saudio.sample_rate = saudio_js_sample_rate();
1907 _saudio.buffer_frames = saudio_js_buffer_frames();
1908 const size_t buf_size = (size_t) (_saudio.buffer_frames * _saudio.bytes_per_frame);
1909 _saudio.backend.buffer = (uint8_t*) _saudio_malloc(buf_size);
1910 return true;
1911 }
1912 else {
1913 return false;
1914 }
1915}
1916
1917_SOKOL_PRIVATE void _saudio_webaudio_backend_shutdown(void) {
1918 saudio_js_shutdown();
1919 if (_saudio.backend.buffer) {
1920 _saudio_free(_saudio.backend.buffer);
1921 _saudio.backend.buffer = 0;
1922 }
1923}
1924
1925// █████ █████ ██ ██ ██████ ██ ██████
1926// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1927// ███████ ███████ ██ ██ ██ ██ ██ ██ ██
1928// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
1929// ██ ██ ██ ██ ██████ ██████ ██ ██████
1930//
1931// >>aaudio
1932#elif defined(SAUDIO_ANDROID_AAUDIO)
1933
1934_SOKOL_PRIVATE aaudio_data_callback_result_t _saudio_aaudio_data_callback(AAudioStream* stream, void* user_data, void* audio_data, int32_t num_frames) {
1935 _SOKOL_UNUSED(user_data);
1936 _SOKOL_UNUSED(stream);
1937 if (_saudio_has_callback()) {
1938 _saudio_stream_callback((float*)audio_data, (int)num_frames, _saudio.num_channels);
1939 }
1940 else {
1941 uint8_t* ptr = (uint8_t*)audio_data;
1942 int num_bytes = _saudio.bytes_per_frame * num_frames;
1943 if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) {
1944 // not enough read data available, fill the entire buffer with silence
1945 memset(ptr, 0, (size_t)num_bytes);
1946 }
1947 }
1948 return AAUDIO_CALLBACK_RESULT_CONTINUE;
1949}
1950
1951_SOKOL_PRIVATE bool _saudio_aaudio_start_stream(void) {
1952 if (AAudioStreamBuilder_openStream(_saudio.backend.builder, &_saudio.backend.stream) != AAUDIO_OK) {
1953 _SAUDIO_ERROR(AAUDIO_STREAMBUILDER_OPEN_STREAM_FAILED);
1954 return false;
1955 }
1956 AAudioStream_requestStart(_saudio.backend.stream);
1957 return true;
1958}
1959
1960_SOKOL_PRIVATE void _saudio_aaudio_stop_stream(void) {
1961 if (_saudio.backend.stream) {
1962 AAudioStream_requestStop(_saudio.backend.stream);
1963 AAudioStream_close(_saudio.backend.stream);
1964 _saudio.backend.stream = 0;
1965 }
1966}
1967
1968_SOKOL_PRIVATE void* _saudio_aaudio_restart_stream_thread_fn(void* param) {
1969 _SOKOL_UNUSED(param);
1970 _SAUDIO_WARN(AAUDIO_RESTARTING_STREAM_AFTER_ERROR);
1971 pthread_mutex_lock(&_saudio.backend.mutex);
1972 _saudio_aaudio_stop_stream();
1973 _saudio_aaudio_start_stream();
1974 pthread_mutex_unlock(&_saudio.backend.mutex);
1975 return 0;
1976}
1977
1978_SOKOL_PRIVATE void _saudio_aaudio_error_callback(AAudioStream* stream, void* user_data, aaudio_result_t error) {
1979 _SOKOL_UNUSED(stream);
1980 _SOKOL_UNUSED(user_data);
1981 if (error == AAUDIO_ERROR_DISCONNECTED) {
1982 if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_aaudio_restart_stream_thread_fn, 0)) {
1983 _SAUDIO_ERROR(AAUDIO_PTHREAD_CREATE_FAILED);
1984 }
1985 }
1986}
1987
1988_SOKOL_PRIVATE void _saudio_aaudio_backend_shutdown(void) {
1989 pthread_mutex_lock(&_saudio.backend.mutex);
1990 _saudio_aaudio_stop_stream();
1991 pthread_mutex_unlock(&_saudio.backend.mutex);
1992 if (_saudio.backend.builder) {
1993 AAudioStreamBuilder_delete(_saudio.backend.builder);
1994 _saudio.backend.builder = 0;
1995 }
1996 pthread_mutex_destroy(&_saudio.backend.mutex);
1997}
1998
1999_SOKOL_PRIVATE bool _saudio_aaudio_backend_init(void) {
2000 _SAUDIO_INFO(USING_AAUDIO_BACKEND);
2001
2002 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
2003
2004 pthread_mutexattr_t attr;
2005 pthread_mutexattr_init(&attr);
2006 pthread_mutex_init(&_saudio.backend.mutex, &attr);
2007
2008 if (AAudio_createStreamBuilder(&_saudio.backend.builder) != AAUDIO_OK) {
2009 _SAUDIO_ERROR(AAUDIO_CREATE_STREAMBUILDER_FAILED);
2010 _saudio_aaudio_backend_shutdown();
2011 return false;
2012 }
2013
2014 AAudioStreamBuilder_setFormat(_saudio.backend.builder, AAUDIO_FORMAT_PCM_FLOAT);
2015 AAudioStreamBuilder_setSampleRate(_saudio.backend.builder, _saudio.sample_rate);
2016 AAudioStreamBuilder_setChannelCount(_saudio.backend.builder, _saudio.num_channels);
2017 AAudioStreamBuilder_setBufferCapacityInFrames(_saudio.backend.builder, _saudio.buffer_frames * 2);
2018 AAudioStreamBuilder_setFramesPerDataCallback(_saudio.backend.builder, _saudio.buffer_frames);
2019 AAudioStreamBuilder_setDataCallback(_saudio.backend.builder, _saudio_aaudio_data_callback, 0);
2020 AAudioStreamBuilder_setErrorCallback(_saudio.backend.builder, _saudio_aaudio_error_callback, 0);
2021
2022 if (!_saudio_aaudio_start_stream()) {
2023 _saudio_aaudio_backend_shutdown();
2024 return false;
2025 }
2026
2027 return true;
2028}
2029
2030// ██████ ██████ ███████ ███ ██ ███████ ██ ███████ ███████
2031// ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██
2032// ██ ██ ██████ █████ ██ ██ ██ ███████ ██ █████ ███████
2033// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
2034// ██████ ██ ███████ ██ ████ ███████ ███████ ███████ ███████
2035//
2036// >>opensles
2037// >>sles
2038#elif defined(SAUDIO_ANDROID_SLES)
2039
2040_SOKOL_PRIVATE void _saudio_sles_semaphore_init(_saudio_sles_semaphore_t* sem) {
2041 sem->count = 0;
2042 int r = pthread_mutex_init(&sem->mutex, NULL);
2043 SOKOL_ASSERT(r == 0);
2044 r = pthread_cond_init(&sem->cond, NULL);
2045 SOKOL_ASSERT(r == 0);
2046 (void)(r);
2047}
2048
2049_SOKOL_PRIVATE void _saudio_sles_semaphore_destroy(_saudio_sles_semaphore_t* sem) {
2050 pthread_cond_destroy(&sem->cond);
2051 pthread_mutex_destroy(&sem->mutex);
2052}
2053
2054_SOKOL_PRIVATE void _saudio_sles_semaphore_post(_saudio_sles_semaphore_t* sem, int count) {
2055 int r = pthread_mutex_lock(&sem->mutex);
2056 SOKOL_ASSERT(r == 0);
2057 for (int ii = 0; ii < count; ii++) {
2058 r = pthread_cond_signal(&sem->cond);
2059 SOKOL_ASSERT(r == 0);
2060 }
2061 sem->count += count;
2062 r = pthread_mutex_unlock(&sem->mutex);
2063 SOKOL_ASSERT(r == 0);
2064 (void)(r);
2065}
2066
2067_SOKOL_PRIVATE bool _saudio_sles_semaphore_wait(_saudio_sles_semaphore_t* sem) {
2068 int r = pthread_mutex_lock(&sem->mutex);
2069 SOKOL_ASSERT(r == 0);
2070 while (r == 0 && sem->count <= 0) {
2071 r = pthread_cond_wait(&sem->cond, &sem->mutex);
2072 }
2073 bool ok = (r == 0);
2074 if (ok) {
2075 --sem->count;
2076 }
2077 r = pthread_mutex_unlock(&sem->mutex);
2078 (void)(r);
2079 return ok;
2080}
2081
2082/* fill intermediate buffer with new data and reset buffer_pos */
2083_SOKOL_PRIVATE void _saudio_sles_fill_buffer(void) {
2084 int src_buffer_frames = _saudio.buffer_frames;
2085 if (_saudio_has_callback()) {
2086 _saudio_stream_callback(_saudio.backend.src_buffer, src_buffer_frames, _saudio.num_channels);
2087 }
2088 else {
2089 const int src_buffer_byte_size = src_buffer_frames * _saudio.num_channels * (int)sizeof(float);
2090 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.src_buffer, src_buffer_byte_size)) {
2091 /* not enough read data available, fill the entire buffer with silence */
2092 _saudio_clear(_saudio.backend.src_buffer, (size_t)src_buffer_byte_size);
2093 }
2094 }
2095}
2096
2097_SOKOL_PRIVATE void SLAPIENTRY _saudio_sles_play_cb(SLPlayItf player, void *context, SLuint32 event) {
2098 _SOKOL_UNUSED(context);
2099 _SOKOL_UNUSED(player);
2100 if (event & SL_PLAYEVENT_HEADATEND) {
2101 _saudio_sles_semaphore_post(&_saudio.backend.buffer_sem, 1);
2102 }
2103}
2104
2105_SOKOL_PRIVATE void* _saudio_sles_thread_fn(void* param) {
2106 _SOKOL_UNUSED(param);
2107 while (!_saudio.backend.thread_stop) {
2108 /* get next output buffer, advance, next buffer. */
2109 int16_t* out_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
2110 _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_SLES_NUM_BUFFERS;
2111 int16_t* next_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
2112
2113 /* queue this buffer */
2114 const int buffer_size_bytes = _saudio.buffer_frames * _saudio.num_channels * (int)sizeof(short);
2115 (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, out_buffer, (SLuint32)buffer_size_bytes);
2116
2117 /* fill the next buffer */
2118 _saudio_sles_fill_buffer();
2119 const int num_samples = _saudio.num_channels * _saudio.buffer_frames;
2120 for (int i = 0; i < num_samples; ++i) {
2121 next_buffer[i] = (int16_t) (_saudio.backend.src_buffer[i] * 0x7FFF);
2122 }
2123
2124 _saudio_sles_semaphore_wait(&_saudio.backend.buffer_sem);
2125 }
2126
2127 return 0;
2128}
2129
2130_SOKOL_PRIVATE void _saudio_sles_backend_shutdown(void) {
2131 _saudio.backend.thread_stop = 1;
2132 pthread_join(_saudio.backend.thread, 0);
2133
2134 if (_saudio.backend.player_obj) {
2135 (*_saudio.backend.player_obj)->Destroy(_saudio.backend.player_obj);
2136 }
2137
2138 if (_saudio.backend.output_mix_obj) {
2139 (*_saudio.backend.output_mix_obj)->Destroy(_saudio.backend.output_mix_obj);
2140 }
2141
2142 if (_saudio.backend.engine_obj) {
2143 (*_saudio.backend.engine_obj)->Destroy(_saudio.backend.engine_obj);
2144 }
2145
2146 for (int i = 0; i < SAUDIO_SLES_NUM_BUFFERS; i++) {
2147 _saudio_free(_saudio.backend.output_buffers[i]);
2148 }
2149 _saudio_free(_saudio.backend.src_buffer);
2150}
2151
2152_SOKOL_PRIVATE bool _saudio_sles_backend_init(void) {
2153 _SAUDIO_INFO(USING_SLES_BACKEND);
2154
2155 _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels;
2156
2157 for (int i = 0; i < SAUDIO_SLES_NUM_BUFFERS; ++i) {
2158 const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
2159 _saudio.backend.output_buffers[i] = (int16_t*) _saudio_malloc_clear((size_t)buffer_size_bytes);
2160 }
2161
2162 {
2163 const int buffer_size_bytes = _saudio.bytes_per_frame * _saudio.buffer_frames;
2164 _saudio.backend.src_buffer = (float*) _saudio_malloc_clear((size_t)buffer_size_bytes);
2165 }
2166
2167 /* Create engine */
2168 const SLEngineOption opts[] = { { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE } };
2169 if (slCreateEngine(&_saudio.backend.engine_obj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) {
2170 _SAUDIO_ERROR(SLES_CREATE_ENGINE_FAILED);
2171 _saudio_sles_backend_shutdown();
2172 return false;
2173 }
2174
2175 (*_saudio.backend.engine_obj)->Realize(_saudio.backend.engine_obj, SL_BOOLEAN_FALSE);
2176 if ((*_saudio.backend.engine_obj)->GetInterface(_saudio.backend.engine_obj, SL_IID_ENGINE, &_saudio.backend.engine) != SL_RESULT_SUCCESS) {
2177 _SAUDIO_ERROR(SLES_ENGINE_GET_ENGINE_INTERFACE_FAILED);
2178 _saudio_sles_backend_shutdown();
2179 return false;
2180 }
2181
2182 /* Create output mix. */
2183 {
2184 const SLInterfaceID ids[] = { SL_IID_VOLUME };
2185 const SLboolean req[] = { SL_BOOLEAN_FALSE };
2186
2187 if ((*_saudio.backend.engine)->CreateOutputMix(_saudio.backend.engine, &_saudio.backend.output_mix_obj, 1, ids, req) != SL_RESULT_SUCCESS) {
2188 _SAUDIO_ERROR(SLES_CREATE_OUTPUT_MIX_FAILED);
2189 _saudio_sles_backend_shutdown();
2190 return false;
2191 }
2192 (*_saudio.backend.output_mix_obj)->Realize(_saudio.backend.output_mix_obj, SL_BOOLEAN_FALSE);
2193
2194 if ((*_saudio.backend.output_mix_obj)->GetInterface(_saudio.backend.output_mix_obj, SL_IID_VOLUME, &_saudio.backend.output_mix_vol) != SL_RESULT_SUCCESS) {
2195 _SAUDIO_WARN(SLES_MIXER_GET_VOLUME_INTERFACE_FAILED);
2196 }
2197 }
2198
2199 /* android buffer queue */
2200 _saudio.backend.in_locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
2201 _saudio.backend.in_locator.numBuffers = SAUDIO_SLES_NUM_BUFFERS;
2202
2203 /* data format */
2204 SLDataFormat_PCM format;
2205 format.formatType = SL_DATAFORMAT_PCM;
2206 format.numChannels = (SLuint32)_saudio.num_channels;
2207 format.samplesPerSec = (SLuint32) (_saudio.sample_rate * 1000);
2208 format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
2209 format.containerSize = 16;
2210 format.endianness = SL_BYTEORDER_LITTLEENDIAN;
2211
2212 if (_saudio.num_channels == 2) {
2213 format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
2214 } else {
2215 format.channelMask = SL_SPEAKER_FRONT_CENTER;
2216 }
2217
2218 SLDataSource src;
2219 src.pLocator = &_saudio.backend.in_locator;
2220 src.pFormat = &format;
2221
2222 /* Output mix. */
2223 _saudio.backend.out_locator.locatorType = SL_DATALOCATOR_OUTPUTMIX;
2224 _saudio.backend.out_locator.outputMix = _saudio.backend.output_mix_obj;
2225
2226 _saudio.backend.dst_data_sink.pLocator = &_saudio.backend.out_locator;
2227 _saudio.backend.dst_data_sink.pFormat = NULL;
2228
2229 /* setup player */
2230 {
2231 const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
2232 const SLboolean req[] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE };
2233
2234 if ((*_saudio.backend.engine)->CreateAudioPlayer(_saudio.backend.engine, &_saudio.backend.player_obj, &src, &_saudio.backend.dst_data_sink, sizeof(ids) / sizeof(ids[0]), ids, req) != SL_RESULT_SUCCESS)
2235 {
2236 _SAUDIO_ERROR(SLES_ENGINE_CREATE_AUDIO_PLAYER_FAILED);
2237 _saudio_sles_backend_shutdown();
2238 return false;
2239 }
2240 (*_saudio.backend.player_obj)->Realize(_saudio.backend.player_obj, SL_BOOLEAN_FALSE);
2241
2242 if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_PLAY, &_saudio.backend.player) != SL_RESULT_SUCCESS) {
2243 _SAUDIO_ERROR(SLES_PLAYER_GET_PLAY_INTERFACE_FAILED);
2244 _saudio_sles_backend_shutdown();
2245 return false;
2246 }
2247 if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_VOLUME, &_saudio.backend.player_vol) != SL_RESULT_SUCCESS) {
2248 _SAUDIO_ERROR(SLES_PLAYER_GET_VOLUME_INTERFACE_FAILED);
2249 }
2250 if ((*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_saudio.backend.player_buffer_queue) != SL_RESULT_SUCCESS) {
2251 _SAUDIO_ERROR(SLES_PLAYER_GET_BUFFERQUEUE_INTERFACE_FAILED);
2252 _saudio_sles_backend_shutdown();
2253 return false;
2254 }
2255 }
2256
2257 /* begin */
2258 {
2259 const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
2260 (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, _saudio.backend.output_buffers[0], (SLuint32)buffer_size_bytes);
2261 _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_SLES_NUM_BUFFERS;
2262
2263 (*_saudio.backend.player)->RegisterCallback(_saudio.backend.player, _saudio_sles_play_cb, NULL);
2264 (*_saudio.backend.player)->SetCallbackEventsMask(_saudio.backend.player, SL_PLAYEVENT_HEADATEND);
2265 (*_saudio.backend.player)->SetPlayState(_saudio.backend.player, SL_PLAYSTATE_PLAYING);
2266 }
2267
2268 /* create the buffer-streaming start thread */
2269 if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_sles_thread_fn, 0)) {
2270 _saudio_sles_backend_shutdown();
2271 return false;
2272 }
2273
2274 return true;
2275}
2276
2277// ██████ ██████ ██████ ███████ █████ ██ ██ ██████ ██ ██████
2278// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
2279// ██ ██ ██ ██████ █████ ███████ ██ ██ ██ ██ ██ ██ ██
2280// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
2281// ██████ ██████ ██ ██ ███████ ██ ██ ██████ ██████ ██ ██████
2282//
2283// >>coreaudio
2284#elif defined(_SAUDIO_APPLE)
2285
2286#if defined(_SAUDIO_IOS)
2287#if __has_feature(objc_arc)
2288#define _SAUDIO_OBJC_RELEASE(obj) { obj = nil; }
2289#else
2290#define _SAUDIO_OBJC_RELEASE(obj) { [obj release]; obj = nil; }
2291#endif
2292
2293@interface _saudio_interruption_handler : NSObject { }
2294@end
2295
2296@implementation _saudio_interruption_handler
2297-(id)init {
2298 self = [super init];
2299 AVAudioSession* session = [AVAudioSession sharedInstance];
2300 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:session];
2301 return self;
2302}
2303
2304-(void)dealloc {
2305 [self remove_handler];
2306 #if !__has_feature(objc_arc)
2307 [super dealloc];
2308 #endif
2309}
2310
2311-(void)remove_handler {
2312 [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionInterruptionNotification" object:nil];
2313}
2314
2315-(void)handle_interruption:(NSNotification*)notification {
2316 AVAudioSession* session = [AVAudioSession sharedInstance];
2317 SOKOL_ASSERT(session);
2318 NSDictionary* dict = notification.userInfo;
2319 SOKOL_ASSERT(dict);
2320 NSInteger type = [[dict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
2321 switch (type) {
2322 case AVAudioSessionInterruptionTypeBegan:
2323 if (_saudio.backend.ca_audio_queue) {
2324 AudioQueuePause(_saudio.backend.ca_audio_queue);
2325 }
2326 [session setActive:false error:nil];
2327 break;
2328 case AVAudioSessionInterruptionTypeEnded:
2329 [session setActive:true error:nil];
2330 if (_saudio.backend.ca_audio_queue) {
2331 AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
2332 }
2333 break;
2334 default:
2335 break;
2336 }
2337}
2338@end
2339#endif // _SAUDIO_IOS
2340
2341/* NOTE: the buffer data callback is called on a separate thread! */
2342_SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, _saudio_AudioQueueRef queue, _saudio_AudioQueueBufferRef buffer) {
2343 _SOKOL_UNUSED(user_data);
2344 if (_saudio_has_callback()) {
2345 const int num_frames = (int)buffer->mAudioDataByteSize / _saudio.bytes_per_frame;
2346 const int num_channels = _saudio.num_channels;
2347 _saudio_stream_callback((float*)buffer->mAudioData, num_frames, num_channels);
2348 }
2349 else {
2350 uint8_t* ptr = (uint8_t*)buffer->mAudioData;
2351 int num_bytes = (int) buffer->mAudioDataByteSize;
2352 if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) {
2353 /* not enough read data available, fill the entire buffer with silence */
2354 _saudio_clear(ptr, (size_t)num_bytes);
2355 }
2356 }
2357 AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
2358}
2359
2360_SOKOL_PRIVATE void _saudio_coreaudio_backend_shutdown(void) {
2361 if (_saudio.backend.ca_audio_queue) {
2362 AudioQueueStop(_saudio.backend.ca_audio_queue, true);
2363 AudioQueueDispose(_saudio.backend.ca_audio_queue, false);
2364 _saudio.backend.ca_audio_queue = 0;
2365 }
2366 #if defined(_SAUDIO_IOS)
2367 /* remove interruption handler */
2368 if (_saudio.backend.ca_interruption_handler != nil) {
2369 [_saudio.backend.ca_interruption_handler remove_handler];
2370 _SAUDIO_OBJC_RELEASE(_saudio.backend.ca_interruption_handler);
2371 }
2372 /* deactivate audio session */
2373 AVAudioSession* session = [AVAudioSession sharedInstance];
2374 SOKOL_ASSERT(session);
2375 [session setActive:false error:nil];;
2376 #endif // _SAUDIO_IOS
2377}
2378
2379_SOKOL_PRIVATE bool _saudio_coreaudio_backend_init(void) {
2380 SOKOL_ASSERT(0 == _saudio.backend.ca_audio_queue);
2381
2382 #if defined(_SAUDIO_IOS)
2383 /* activate audio session */
2384 AVAudioSession* session = [AVAudioSession sharedInstance];
2385 SOKOL_ASSERT(session != nil);
2386 [session setCategory: AVAudioSessionCategoryPlayback error:nil];
2387 [session setActive:true error:nil];
2388
2389 /* create interruption handler */
2390 _saudio.backend.ca_interruption_handler = [[_saudio_interruption_handler alloc] init];
2391 #endif
2392
2393 /* create an audio queue with fp32 samples */
2394 _saudio_AudioStreamBasicDescription fmt;
2395 _saudio_clear(&fmt, sizeof(fmt));
2396 fmt.mSampleRate = (double) _saudio.sample_rate;
2397 fmt.mFormatID = _saudio_kAudioFormatLinearPCM;
2398 fmt.mFormatFlags = _saudio_kLinearPCMFormatFlagIsFloat | _saudio_kAudioFormatFlagIsPacked;
2399 fmt.mFramesPerPacket = 1;
2400 fmt.mChannelsPerFrame = (uint32_t) _saudio.num_channels;
2401 fmt.mBytesPerFrame = (uint32_t)sizeof(float) * (uint32_t)_saudio.num_channels;
2402 fmt.mBytesPerPacket = fmt.mBytesPerFrame;
2403 fmt.mBitsPerChannel = 32;
2404 _saudio_OSStatus res = AudioQueueNewOutput(&fmt, _saudio_coreaudio_callback, 0, NULL, NULL, 0, &_saudio.backend.ca_audio_queue);
2405 if (0 != res) {
2406 _SAUDIO_ERROR(COREAUDIO_NEW_OUTPUT_FAILED);
2407 return false;
2408 }
2409 SOKOL_ASSERT(_saudio.backend.ca_audio_queue);
2410
2411 /* create 2 audio buffers */
2412 for (int i = 0; i < 2; i++) {
2413 _saudio_AudioQueueBufferRef buf = NULL;
2414 const uint32_t buf_byte_size = (uint32_t)_saudio.buffer_frames * fmt.mBytesPerFrame;
2415 res = AudioQueueAllocateBuffer(_saudio.backend.ca_audio_queue, buf_byte_size, &buf);
2416 if (0 != res) {
2417 _SAUDIO_ERROR(COREAUDIO_ALLOCATE_BUFFER_FAILED);
2418 _saudio_coreaudio_backend_shutdown();
2419 return false;
2420 }
2421 buf->mAudioDataByteSize = buf_byte_size;
2422 _saudio_clear(buf->mAudioData, buf->mAudioDataByteSize);
2423 AudioQueueEnqueueBuffer(_saudio.backend.ca_audio_queue, buf, 0, NULL);
2424 }
2425
2426 /* init or modify actual playback parameters */
2427 _saudio.bytes_per_frame = (int)fmt.mBytesPerFrame;
2428
2429 /* ...and start playback */
2430 res = AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
2431 if (0 != res) {
2432 _SAUDIO_ERROR(COREAUDIO_START_FAILED);
2433 _saudio_coreaudio_backend_shutdown();
2434 return false;
2435 }
2436 return true;
2437}
2438
2439#else
2440#error "unsupported platform"
2441#endif
2442
2443bool _saudio_backend_init(void) {
2444 #if defined(SOKOL_DUMMY_BACKEND)
2445 return _saudio_dummy_backend_init();
2446 #elif defined(_SAUDIO_LINUX)
2447 return _saudio_alsa_backend_init();
2448 #elif defined(_SAUDIO_WINDOWS)
2449 return _saudio_wasapi_backend_init();
2450 #elif defined(_SAUDIO_EMSCRIPTEN)
2451 return _saudio_webaudio_backend_init();
2452 #elif defined(SAUDIO_ANDROID_AAUDIO)
2453 return _saudio_aaudio_backend_init();
2454 #elif defined(SAUDIO_ANDROID_SLES)
2455 return _saudio_sles_backend_init();
2456 #elif defined(_SAUDIO_APPLE)
2457 return _saudio_coreaudio_backend_init();
2458 #else
2459 #error "unknown platform"
2460 #endif
2461}
2462
2463void _saudio_backend_shutdown(void) {
2464 #if defined(SOKOL_DUMMY_BACKEND)
2465 _saudio_dummy_backend_shutdown();
2466 #elif defined(_SAUDIO_LINUX)
2467 _saudio_alsa_backend_shutdown();
2468 #elif defined(_SAUDIO_WINDOWS)
2469 _saudio_wasapi_backend_shutdown();
2470 #elif defined(_SAUDIO_EMSCRIPTEN)
2471 _saudio_webaudio_backend_shutdown();
2472 #elif defined(SAUDIO_ANDROID_AAUDIO)
2473 _saudio_aaudio_backend_shutdown();
2474 #elif defined(SAUDIO_ANDROID_SLES)
2475 _saudio_sles_backend_shutdown();
2476 #elif defined(_SAUDIO_APPLE)
2477 return _saudio_coreaudio_backend_shutdown();
2478 #else
2479 #error "unknown platform"
2480 #endif
2481}
2482
2483// ██████ ██ ██ ██████ ██ ██ ██████
2484// ██ ██ ██ ██ ██ ██ ██ ██ ██
2485// ██████ ██ ██ ██████ ██ ██ ██
2486// ██ ██ ██ ██ ██ ██ ██ ██
2487// ██ ██████ ██████ ███████ ██ ██████
2488//
2489// >>public
2490SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) {
2491 SOKOL_ASSERT(!_saudio.valid);
2492 SOKOL_ASSERT(!_saudio.setup_called);
2493 SOKOL_ASSERT(desc);
2494 SOKOL_ASSERT((desc->allocator.alloc_fn && desc->allocator.free_fn) || (!desc->allocator.alloc_fn && !desc->allocator.free_fn));
2495 _saudio_clear(&_saudio, sizeof(_saudio));
2496 _saudio.setup_called = true;
2497 _saudio.desc = *desc;
2498 _saudio.stream_cb = desc->stream_cb;
2499 _saudio.stream_userdata_cb = desc->stream_userdata_cb;
2500 _saudio.user_data = desc->user_data;
2501 _saudio.sample_rate = _saudio_def(_saudio.desc.sample_rate, _SAUDIO_DEFAULT_SAMPLE_RATE);
2502 _saudio.buffer_frames = _saudio_def(_saudio.desc.buffer_frames, _SAUDIO_DEFAULT_BUFFER_FRAMES);
2503 _saudio.packet_frames = _saudio_def(_saudio.desc.packet_frames, _SAUDIO_DEFAULT_PACKET_FRAMES);
2504 _saudio.num_packets = _saudio_def(_saudio.desc.num_packets, _SAUDIO_DEFAULT_NUM_PACKETS);
2505 _saudio.num_channels = _saudio_def(_saudio.desc.num_channels, 1);
2506 _saudio_fifo_init_mutex(&_saudio.fifo);
2507 if (_saudio_backend_init()) {
2508 /* the backend might not support the requested exact buffer size,
2509 make sure the actual buffer size is still a multiple of
2510 the requested packet size
2511 */
2512 if (0 != (_saudio.buffer_frames % _saudio.packet_frames)) {
2513 _SAUDIO_ERROR(BACKEND_BUFFER_SIZE_ISNT_MULTIPLE_OF_PACKET_SIZE);
2514 _saudio_backend_shutdown();
2515 return;
2516 }
2517 SOKOL_ASSERT(_saudio.bytes_per_frame > 0);
2518 _saudio_fifo_init(&_saudio.fifo, _saudio.packet_frames * _saudio.bytes_per_frame, _saudio.num_packets);
2519 _saudio.valid = true;
2520 }
2521 else {
2522 _saudio_fifo_destroy_mutex(&_saudio.fifo);
2523 }
2524}
2525
2526SOKOL_API_IMPL void saudio_shutdown(void) {
2527 SOKOL_ASSERT(_saudio.setup_called);
2528 _saudio.setup_called = false;
2529 if (_saudio.valid) {
2530 _saudio_backend_shutdown();
2531 _saudio_fifo_shutdown(&_saudio.fifo);
2532 _saudio_fifo_destroy_mutex(&_saudio.fifo);
2533 _saudio.valid = false;
2534 }
2535}
2536
2537SOKOL_API_IMPL bool saudio_isvalid(void) {
2538 return _saudio.valid;
2539}
2540
2541SOKOL_API_IMPL void* saudio_userdata(void) {
2542 SOKOL_ASSERT(_saudio.setup_called);
2543 return _saudio.desc.user_data;
2544}
2545
2546SOKOL_API_IMPL saudio_desc saudio_query_desc(void) {
2547 SOKOL_ASSERT(_saudio.setup_called);
2548 return _saudio.desc;
2549}
2550
2551SOKOL_API_IMPL int saudio_sample_rate(void) {
2552 SOKOL_ASSERT(_saudio.setup_called);
2553 return _saudio.sample_rate;
2554}
2555
2556SOKOL_API_IMPL int saudio_buffer_frames(void) {
2557 SOKOL_ASSERT(_saudio.setup_called);
2558 return _saudio.buffer_frames;
2559}
2560
2561SOKOL_API_IMPL int saudio_channels(void) {
2562 SOKOL_ASSERT(_saudio.setup_called);
2563 return _saudio.num_channels;
2564}
2565
2566SOKOL_API_IMPL bool saudio_suspended(void) {
2567 SOKOL_ASSERT(_saudio.setup_called);
2568 #if defined(_SAUDIO_EMSCRIPTEN)
2569 if (_saudio.valid) {
2570 return 1 == saudio_js_suspended();
2571 }
2572 else {
2573 return false;
2574 }
2575 #else
2576 return false;
2577 #endif
2578}
2579
2580SOKOL_API_IMPL int saudio_expect(void) {
2581 SOKOL_ASSERT(_saudio.setup_called);
2582 if (_saudio.valid) {
2583 const int num_frames = _saudio_fifo_writable_bytes(&_saudio.fifo) / _saudio.bytes_per_frame;
2584 return num_frames;
2585 }
2586 else {
2587 return 0;
2588 }
2589}
2590
2591SOKOL_API_IMPL int saudio_push(const float* frames, int num_frames) {
2592 SOKOL_ASSERT(_saudio.setup_called);
2593 SOKOL_ASSERT(frames && (num_frames > 0));
2594 if (_saudio.valid) {
2595 const int num_bytes = num_frames * _saudio.bytes_per_frame;
2596 const int num_written = _saudio_fifo_write(&_saudio.fifo, (const uint8_t*)frames, num_bytes);
2597 return num_written / _saudio.bytes_per_frame;
2598 }
2599 else {
2600 return 0;
2601 }
2602}
2603
2604#undef _saudio_def
2605#undef _saudio_def_flt
2606
2607#if defined(_SAUDIO_WINDOWS)
2608#ifdef _MSC_VER
2609#pragma warning(pop)
2610#endif
2611#endif
2612
2613#endif /* SOKOL_AUDIO_IMPL */
2614