v / thirdparty / sokol /
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_LOG(msg) - your own logging function (default: puts(msg))
21 SOKOL_MALLOC(s) - your own malloc() implementation (default: malloc(s))
22 SOKOL_FREE(p) - your own free() implementation (default: free(p))
23 SOKOL_AUDIO_API_DECL- public function declaration prefix (default: extern)
24 SOKOL_API_DECL - same as SOKOL_AUDIO_API_DECL
25 SOKOL_API_IMPL - public function implementation prefix (default: -)
26
27 SAUDIO_RING_MAX_SLOTS - max number of slots in the push-audio ring buffer (default 1024)
28 SAUDIO_OSX_USE_SYSTEM_HEADERS - define this to force inclusion of system headers on
29 macOS instead of using embedded CoreAudio declarations
30
31 If sokol_audio.h is compiled as a DLL, define the following before
32 including the declaration or implementation:
33
34 SOKOL_DLL
35
36 On Windows, SOKOL_DLL will define SOKOL_AUDIO_API_DECL as __declspec(dllexport)
37 or __declspec(dllimport) as needed.
38
39 Link with the following libraries:
40
41 - on macOS: AudioToolbox
42 - on iOS: AudioToolbox, AVFoundation
43 - on Linux: asound
44 - on Android: link with OpenSLES
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 - macOS: CoreAudio
56 - iOS: CoreAudio+AVAudioSession
57 - emscripten: WebAudio with ScriptProcessorNode
58 - Android: OpenSLES
59
60 Sokol Audio will not do any buffer mixing or volume control, if you have
61 multiple independent input streams of sample data you need to perform the
62 mixing yourself before forwarding the data to Sokol Audio.
63
64 There are two mutually exclusive ways to provide the sample data:
65
66 1. Callback model: You provide a callback function, which will be called
67 when Sokol Audio needs new samples. On all platforms except emscripten,
68 this function is called from a separate thread.
69 2. Push model: Your code pushes small blocks of sample data from your
70 main loop or a thread you created. The pushed data is stored in
71 a ring buffer where it is pulled by the backend code when
72 needed.
73
74 The callback model is preferred because it is the most direct way to
75 feed sample data into the audio backends and also has less moving parts
76 (there is no ring buffer between your code and the audio backend).
77
78 Sometimes it is not possible to generate the audio stream directly in a
79 callback function running in a separate thread, for such cases Sokol Audio
80 provides the push-model as a convenience.
81
82 SOKOL AUDIO, SOLOUD AND MINIAUDIO
83 =================================
84 The WASAPI, ALSA, OpenSLES and CoreAudio backend code has been taken from the
85 SoLoud library (with some modifications, so any bugs in there are most
86 likely my fault). If you need a more fully-featured audio solution, check
87 out SoLoud, it's excellent:
88
89 https://github.com/jarikomppa/soloud
90
91 Another alternative which feature-wise is somewhere inbetween SoLoud and
92 sokol-audio might be MiniAudio:
93
94 https://github.com/mackron/miniaudio
95
96 GLOSSARY
97 ========
98 - stream buffer:
99 The internal audio data buffer, usually provided by the backend API. The
100 size of the stream buffer defines the base latency, smaller buffers have
101 lower latency but may cause audio glitches. Bigger buffers reduce or
102 eliminate glitches, but have a higher base latency.
103
104 - stream callback:
105 Optional callback function which is called by Sokol Audio when it
106 needs new samples. On Windows, macOS/iOS and Linux, this is called in
107 a separate thread, on WebAudio, this is called per-frame in the
108 browser thread.
109
110 - channel:
111 A discrete track of audio data, currently 1-channel (mono) and
112 2-channel (stereo) is supported and tested.
113
114 - sample:
115 The magnitude of an audio signal on one channel at a given time. In
116 Sokol Audio, samples are 32-bit float numbers in the range -1.0 to
117 +1.0.
118
119 - frame:
120 The tightly packed set of samples for all channels at a given time.
121 For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples.
122
123 - packet:
124 In Sokol Audio, a small chunk of audio data that is moved from the
125 main thread to the audio streaming thread in order to decouple the
126 rate at which the main thread provides new audio data, and the
127 streaming thread consuming audio data.
128
129 WORKING WITH SOKOL AUDIO
130 ========================
131 First call saudio_setup() with your preferred audio playback options.
132 In most cases you can stick with the default values, these provide
133 a good balance between low-latency and glitch-free playback
134 on all audio backends.
135
136 If you want to use the callback-model, you need to provide a stream
137 callback function either in saudio_desc.stream_cb or saudio_desc.stream_userdata_cb,
138 otherwise keep both function pointers zero-initialized.
139
140 Use push model and default playback parameters:
141
142 saudio_setup(&(saudio_desc){0});
143
144 Use stream callback model and default playback parameters:
145
146 saudio_setup(&(saudio_desc){
147 .stream_cb = my_stream_callback
148 });
149
150 The standard stream callback doesn't have a user data argument, if you want
151 that, use the alternative stream_userdata_cb and also set the user_data pointer:
152
153 saudio_setup(&(saudio_desc){
154 .stream_userdata_cb = my_stream_callback,
155 .user_data = &my_data
156 });
157
158 The following playback parameters can be provided through the
159 saudio_desc struct:
160
161 General parameters (both for stream-callback and push-model):
162
163 int sample_rate -- the sample rate in Hz, default: 44100
164 int num_channels -- number of channels, default: 1 (mono)
165 int buffer_frames -- number of frames in streaming buffer, default: 2048
166
167 The stream callback prototype (either with or without userdata):
168
169 void (*stream_cb)(float* buffer, int num_frames, int num_channels)
170 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data)
171 Function pointer to the user-provide stream callback.
172
173 Push-model parameters:
174
175 int packet_frames -- number of frames in a packet, default: 128
176 int num_packets -- number of packets in ring buffer, default: 64
177
178 The sample_rate and num_channels parameters are only hints for the audio
179 backend, it isn't guaranteed that those are the values used for actual
180 playback.
181
182 To get the actual parameters, call the following functions after
183 saudio_setup():
184
185 int saudio_sample_rate(void)
186 int saudio_channels(void);
187
188 It's unlikely that the number of channels will be different than requested,
189 but a different sample rate isn't uncommon.
190
191 (NOTE: there's an yet unsolved issue when an audio backend might switch
192 to a different sample rate when switching output devices, for instance
193 plugging in a bluetooth headset, this case is currently not handled in
194 Sokol Audio).
195
196 You can check if audio initialization was successful with
197 saudio_isvalid(). If backend initialization failed for some reason
198 (for instance when there's no audio device in the machine), this
199 will return false. Not checking for success won't do any harm, all
200 Sokol Audio function will silently fail when called after initialization
201 has failed, so apart from missing audio output, nothing bad will happen.
202
203 Before your application exits, you should call
204
205 saudio_shutdown();
206
207 This stops the audio thread (on Linux, Windows and macOS/iOS) and
208 properly shuts down the audio backend.
209
210 THE STREAM CALLBACK MODEL
211 =========================
212 To use Sokol Audio in stream-callback-mode, provide a callback function
213 like this in the saudio_desc struct when calling saudio_setup():
214
215 void stream_cb(float* buffer, int num_frames, int num_channels) {
216 ...
217 }
218
219 Or the alternative version with a user-data argument:
220
221 void stream_userdata_cb(float* buffer, int num_frames, int num_channels, void* user_data) {
222 my_data_t* my_data = (my_data_t*) user_data;
223 ...
224 }
225
226 The job of the callback function is to fill the *buffer* with 32-bit
227 float sample values.
228
229 To output silence, fill the buffer with zeros:
230
231 void stream_cb(float* buffer, int num_frames, int num_channels) {
232 const int num_samples = num_frames * num_channels;
233 for (int i = 0; i < num_samples; i++) {
234 buffer[i] = 0.0f;
235 }
236 }
237
238 For stereo output (num_channels == 2), the samples for the left
239 and right channel are interleaved:
240
241 void stream_cb(float* buffer, int num_frames, int num_channels) {
242 assert(2 == num_channels);
243 for (int i = 0; i < num_frames; i++) {
244 buffer[2*i + 0] = ...; // left channel
245 buffer[2*i + 1] = ...; // right channel
246 }
247 }
248
249 Please keep in mind that the stream callback function is running in a
250 separate thread, if you need to share data with the main thread you need
251 to take care yourself to make the access to the shared data thread-safe!
252
253 THE PUSH MODEL
254 ==============
255 To use the push-model for providing audio data, simply don't set (keep
256 zero-initialized) the stream_cb field in the saudio_desc struct when
257 calling saudio_setup().
258
259 To provide sample data with the push model, call the saudio_push()
260 function at regular intervals (for instance once per frame). You can
261 call the saudio_expect() function to ask Sokol Audio how much room is
262 in the ring buffer, but if you provide a continuous stream of data
263 at the right sample rate, saudio_expect() isn't required (it's a simple
264 way to sync/throttle your sample generation code with the playback
265 rate though).
266
267 With saudio_push() you may need to maintain your own intermediate sample
268 buffer, since pushing individual sample values isn't very efficient.
269 The following example is from the MOD player sample in
270 sokol-samples (https://github.com/floooh/sokol-samples):
271
272 const int num_frames = saudio_expect();
273 if (num_frames > 0) {
274 const int num_samples = num_frames * saudio_channels();
275 read_samples(flt_buf, num_samples);
276 saudio_push(flt_buf, num_frames);
277 }
278
279 Another option is to ignore saudio_expect(), and just push samples as they
280 are generated in small batches. In this case you *need* to generate the
281 samples at the right sample rate:
282
283 The following example is taken from the Tiny Emulators project
284 (https://github.com/floooh/chips-test), this is for mono playback,
285 so (num_samples == num_frames):
286
287 // tick the sound generator
288 if (ay38910_tick(&sys->psg)) {
289 // new sample is ready
290 sys->sample_buffer[sys->sample_pos++] = sys->psg.sample;
291 if (sys->sample_pos == sys->num_samples) {
292 // new sample packet is ready
293 saudio_push(sys->sample_buffer, sys->num_samples);
294 sys->sample_pos = 0;
295 }
296 }
297
298 THE WEBAUDIO BACKEND
299 ====================
300 The WebAudio backend is currently using a ScriptProcessorNode callback to
301 feed the sample data into WebAudio. ScriptProcessorNode has been
302 deprecated for a while because it is running from the main thread, with
303 the default initialization parameters it works 'pretty well' though.
304 Ultimately Sokol Audio will use Audio Worklets, but this requires a few
305 more things to fall into place (Audio Worklets implemented everywhere,
306 SharedArrayBuffers enabled again, and I need to figure out a 'low-cost'
307 solution in terms of implementation effort, since Audio Worklets are
308 a lot more complex than ScriptProcessorNode if the audio data needs to come
309 from the main thread).
310
311 The WebAudio backend is automatically selected when compiling for
312 emscripten (__EMSCRIPTEN__ define exists).
313
314 https://developers.google.com/web/updates/2017/12/audio-worklet
315 https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern
316
317 "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/
318
319 THE COREAUDIO BACKEND
320 =====================
321 The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined).
322 Since the CoreAudio API is implemented in C (not Objective-C) on macOS the
323 implementation part of Sokol Audio can be included into a C source file.
324
325 However on iOS, Sokol Audio must be compiled as Objective-C due to it's
326 reliance on the AVAudioSession object. The iOS code path support both
327 being compiled with or without ARC (Automatic Reference Counting).
328
329 For thread synchronisation, the CoreAudio backend will use the
330 pthread_mutex_* functions.
331
332 The incoming floating point samples will be directly forwarded to
333 CoreAudio without further conversion.
334
335 macOS and iOS applications that use Sokol Audio need to link with
336 the AudioToolbox framework.
337
338 THE WASAPI BACKEND
339 ==================
340 The WASAPI backend is automatically selected when compiling on Windows
341 (_WIN32 is defined).
342
343 For thread synchronisation a Win32 critical section is used.
344
345 WASAPI may use a different size for its own streaming buffer then requested,
346 so the base latency may be slightly bigger. The current backend implementation
347 converts the incoming floating point sample values to signed 16-bit
348 integers.
349
350 The required Windows system DLLs are linked with #pragma comment(lib, ...),
351 so you shouldn't need to add additional linker libs in the build process
352 (otherwise this is a bug which should be fixed in sokol_audio.h).
353
354 THE ALSA BACKEND
355 ================
356 The ALSA backend is automatically selected when compiling on Linux
357 ('linux' is defined).
358
359 For thread synchronisation, the pthread_mutex_* functions are used.
360
361 Samples are directly forwarded to ALSA in 32-bit float format, no
362 further conversion is taking place.
363
364 You need to link with the 'asound' library, and the <alsa/asoundlib.h>
365 header must be present (usually both are installed with some sort
366 of ALSA development package).
367
368 LICENSE
369 =======
370
371 zlib/libpng license
372
373 Copyright (c) 2018 Andre Weissflog
374
375 This software is provided 'as-is', without any express or implied warranty.
376 In no event will the authors be held liable for any damages arising from the
377 use of this software.
378
379 Permission is granted to anyone to use this software for any purpose,
380 including commercial applications, and to alter it and redistribute it
381 freely, subject to the following restrictions:
382
383 1. The origin of this software must not be misrepresented; you must not
384 claim that you wrote the original software. If you use this software in a
385 product, an acknowledgment in the product documentation would be
386 appreciated but is not required.
387
388 2. Altered source versions must be plainly marked as such, and must not
389 be misrepresented as being the original software.
390
391 3. This notice may not be removed or altered from any source
392 distribution.
393*/
394#define SOKOL_AUDIO_INCLUDED (1)
395#include <stdint.h>
396#include <stdbool.h>
397
398#if defined(SOKOL_API_DECL) && !defined(SOKOL_AUDIO_API_DECL)
399#define SOKOL_AUDIO_API_DECL SOKOL_API_DECL
400#endif
401#ifndef SOKOL_AUDIO_API_DECL
402#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_AUDIO_IMPL)
403#define SOKOL_AUDIO_API_DECL __declspec(dllexport)
404#elif defined(_WIN32) && defined(SOKOL_DLL)
405#define SOKOL_AUDIO_API_DECL __declspec(dllimport)
406#else
407#define SOKOL_AUDIO_API_DECL extern
408#endif
409#endif
410
411#ifdef __cplusplus
412extern "C" {
413#endif
414
415typedef struct saudio_desc {
416 int sample_rate; /* requested sample rate */
417 int num_channels; /* number of channels, default: 1 (mono) */
418 int buffer_frames; /* number of frames in streaming buffer */
419 int packet_frames; /* number of frames in a packet */
420 int num_packets; /* number of packets in packet queue */
421 void (*stream_cb)(float* buffer, int num_frames, int num_channels); /* optional streaming callback (no user data) */
422 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data); /*... and with user data */
423 void* user_data; /* optional user data argument for stream_userdata_cb */
424} saudio_desc;
425
426/* setup sokol-audio */
427SOKOL_AUDIO_API_DECL void saudio_setup(const saudio_desc* desc);
428/* shutdown sokol-audio */
429SOKOL_AUDIO_API_DECL void saudio_shutdown(void);
430/* true after setup if audio backend was successfully initialized */
431SOKOL_AUDIO_API_DECL bool saudio_isvalid(void);
432/* return the saudio_desc.user_data pointer */
433SOKOL_AUDIO_API_DECL void* saudio_userdata(void);
434/* return a copy of the original saudio_desc struct */
435SOKOL_AUDIO_API_DECL saudio_desc saudio_query_desc(void);
436/* actual sample rate */
437SOKOL_AUDIO_API_DECL int saudio_sample_rate(void);
438/* return actual backend buffer size in number of frames */
439SOKOL_AUDIO_API_DECL int saudio_buffer_frames(void);
440/* actual number of channels */
441SOKOL_AUDIO_API_DECL int saudio_channels(void);
442/* get current number of frames to fill packet queue */
443SOKOL_AUDIO_API_DECL int saudio_expect(void);
444/* push sample frames from main thread, returns number of frames actually pushed */
445SOKOL_AUDIO_API_DECL int saudio_push(const float* frames, int num_frames);
446
447#ifdef __cplusplus
448} /* extern "C" */
449
450/* reference-based equivalents for c++ */
451inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc); }
452
453#endif
454#endif // SOKOL_AUDIO_INCLUDED
455
456/*=== IMPLEMENTATION =========================================================*/
457#ifdef SOKOL_AUDIO_IMPL
458#define SOKOL_AUDIO_IMPL_INCLUDED (1)
459#include <string.h> // memset, memcpy
460#include <stddef.h> // size_t
461
462#ifndef SOKOL_API_IMPL
463 #define SOKOL_API_IMPL
464#endif
465#ifndef SOKOL_DEBUG
466 #ifndef NDEBUG
467 #define SOKOL_DEBUG (1)
468 #endif
469#endif
470#ifndef SOKOL_ASSERT
471 #include <assert.h>
472 #define SOKOL_ASSERT(c) assert(c)
473#endif
474#ifndef SOKOL_MALLOC
475 #include <stdlib.h>
476 #define SOKOL_MALLOC(s) malloc(s)
477 #define SOKOL_FREE(p) free(p)
478#endif
479#ifndef SOKOL_LOG
480 #ifdef SOKOL_DEBUG
481 #include <stdio.h>
482 #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
483 #else
484 #define SOKOL_LOG(s)
485 #endif
486#endif
487
488#ifndef _SOKOL_PRIVATE
489 #if defined(__GNUC__) || defined(__clang__)
490 #define _SOKOL_PRIVATE __attribute__((unused)) static
491 #else
492 #define _SOKOL_PRIVATE static
493 #endif
494#endif
495
496#ifndef _SOKOL_UNUSED
497 #define _SOKOL_UNUSED(x) (void)(x)
498#endif
499
500// platform detection defines
501#if defined(SOKOL_DUMMY_BACKEND)
502 // nothing
503#elif defined(__APPLE__)
504 #define _SAUDIO_APPLE (1)
505 #include <TargetConditionals.h>
506 #if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
507 #define _SAUDIO_IOS (1)
508 #else
509 #define _SAUDIO_MACOS (1)
510 #endif
511#elif defined(__EMSCRIPTEN__)
512 #define _SAUDIO_EMSCRIPTEN
513#elif defined(_WIN32)
514 #define _SAUDIO_WINDOWS (1)
515 #include <winapifamily.h>
516 #if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP))
517 #define _SAUDIO_UWP (1)
518 #else
519 #define _SAUDIO_WIN32 (1)
520 #endif
521#elif defined(__ANDROID__)
522 #define _SAUDIO_ANDROID (1)
523#elif defined(__linux__) || defined(__unix__)
524 #define _SAUDIO_LINUX (1)
525#else
526#error "sokol_audio.h: Unknown platform"
527#endif
528
529// platform-specific headers and definitions
530#if defined(SOKOL_DUMMY_BACKEND)
531 #define _SAUDIO_NOTHREADS (1)
532#elif defined(_SAUDIO_WINDOWS)
533 #define _SAUDIO_WINTHREADS (1)
534 #ifndef WIN32_LEAN_AND_MEAN
535 #define WIN32_LEAN_AND_MEAN
536 #endif
537 #ifndef NOMINMAX
538 #define NOMINMAX
539 #endif
540 #include <windows.h>
541 #include <synchapi.h>
542 #if defined(_SAUDIO_UWP)
543 #pragma comment (lib, "WindowsApp")
544 #else
545 #pragma comment (lib, "kernel32")
546 #pragma comment (lib, "ole32")
547 #endif
548 #ifndef CINTERFACE
549 #define CINTERFACE
550 #endif
551 #ifndef COBJMACROS
552 #define COBJMACROS
553 #endif
554 #ifndef CONST_VTABLE
555 #define CONST_VTABLE
556 #endif
557 #include <mmdeviceapi.h>
558 #include <audioclient.h>
559 static const IID _saudio_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
560 static const IID _saudio_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, { 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } };
561 static const CLSID _saudio_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, { 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } };
562 static const IID _saudio_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,{ 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
563 static const IID _saudio_IID_Devinterface_Audio_Render = { 0xe6327cad, 0xdcec, 0x4949, {0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2 } };
564 static const IID _saudio_IID_IActivateAudioInterface_Completion_Handler = { 0x94ea2b94, 0xe9cc, 0x49e0, {0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90} };
565 #if defined(__cplusplus)
566 #define _SOKOL_AUDIO_WIN32COM_ID(x) (x)
567 #else
568 #define _SOKOL_AUDIO_WIN32COM_ID(x) (&x)
569 #endif
570 /* fix for Visual Studio 2015 SDKs */
571 #ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
572 #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
573 #endif
574 #ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
575 #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
576 #endif
577 #ifdef _MSC_VER
578 #pragma warning(push)
579 #pragma warning(disable:4505) /* unreferenced local function has been removed */
580 #endif
581#elif defined(_SAUDIO_APPLE)
582 #define _SAUDIO_PTHREADS (1)
583 #include <pthread.h>
584 #if defined(_SAUDIO_IOS)
585 // always use system headers on iOS (for now at least)
586 #if !defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
587 #define SAUDIO_OSX_USE_SYSTEM_HEADERS (1)
588 #endif
589 #if !defined(__cplusplus)
590 #if __has_feature(objc_arc) && !__has_feature(objc_arc_fields)
591 #error "sokol_audio.h on iOS requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)"
592 #endif
593 #endif
594 #include <AudioToolbox/AudioToolbox.h>
595 #include <AVFoundation/AVFoundation.h>
596 #else
597 #if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
598 #include <AudioToolbox/AudioToolbox.h>
599 #endif
600 #endif
601#elif defined(_SAUDIO_ANDROID)
602 #define _SAUDIO_PTHREADS (1)
603 #include <pthread.h>
604 #include "SLES/OpenSLES_Android.h"
605#elif defined(_SAUDIO_LINUX)
606 #define _SAUDIO_PTHREADS (1)
607 #include <pthread.h>
608 #define ALSA_PCM_NEW_HW_PARAMS_API
609 #include <alsa/asoundlib.h>
610#elif defined(__EMSCRIPTEN__)
611 #define _SAUDIO_NOTHREADS (1)
612 #include <emscripten/emscripten.h>
613#endif
614
615#define _saudio_def(val, def) (((val) == 0) ? (def) : (val))
616#define _saudio_def_flt(val, def) (((val) == 0.0f) ? (def) : (val))
617
618#define _SAUDIO_DEFAULT_SAMPLE_RATE (44100)
619#define _SAUDIO_DEFAULT_BUFFER_FRAMES (2048)
620#define _SAUDIO_DEFAULT_PACKET_FRAMES (128)
621#define _SAUDIO_DEFAULT_NUM_PACKETS ((_SAUDIO_DEFAULT_BUFFER_FRAMES/_SAUDIO_DEFAULT_PACKET_FRAMES)*4)
622
623#ifndef SAUDIO_RING_MAX_SLOTS
624#define SAUDIO_RING_MAX_SLOTS (1024)
625#endif
626
627/*=== MUTEX WRAPPER DECLARATIONS =============================================*/
628#if defined(_SAUDIO_PTHREADS)
629
630typedef struct {
631 pthread_mutex_t mutex;
632} _saudio_mutex_t;
633
634#elif defined(_SAUDIO_WINTHREADS)
635
636typedef struct {
637 CRITICAL_SECTION critsec;
638} _saudio_mutex_t;
639
640#elif defined(_SAUDIO_NOTHREADS)
641
642typedef struct {
643 int dummy_mutex;
644} _saudio_mutex_t;
645
646#endif
647
648/*=== DUMMY BACKEND DECLARATIONS =============================================*/
649#if defined(SOKOL_DUMMY_BACKEND)
650
651typedef struct {
652 int dummy_backend;
653} _saudio_backend_t;
654
655/*=== COREAUDIO BACKEND DECLARATIONS =========================================*/
656#elif defined(_SAUDIO_APPLE)
657
658#if defined(SAUDIO_OSX_USE_SYSTEM_HEADERS)
659
660typedef AudioQueueRef _saudio_AudioQueueRef;
661typedef AudioQueueBufferRef _saudio_AudioQueueBufferRef;
662typedef AudioStreamBasicDescription _saudio_AudioStreamBasicDescription;
663typedef OSStatus _saudio_OSStatus;
664
665#define _saudio_kAudioFormatLinearPCM (kAudioFormatLinearPCM)
666#define _saudio_kLinearPCMFormatFlagIsFloat (kLinearPCMFormatFlagIsFloat)
667#define _saudio_kAudioFormatFlagIsPacked (kAudioFormatFlagIsPacked)
668
669#else
670
671// embedded AudioToolbox declarations
672typedef uint32_t _saudio_AudioFormatID;
673typedef uint32_t _saudio_AudioFormatFlags;
674typedef int32_t _saudio_OSStatus;
675typedef uint32_t _saudio_SMPTETimeType;
676typedef uint32_t _saudio_SMPTETimeFlags;
677typedef uint32_t _saudio_AudioTimeStampFlags;
678typedef void* _saudio_CFRunLoopRef;
679typedef void* _saudio_CFStringRef;
680typedef void* _saudio_AudioQueueRef;
681
682#define _saudio_kAudioFormatLinearPCM ('lpcm')
683#define _saudio_kLinearPCMFormatFlagIsFloat (1U << 0)
684#define _saudio_kAudioFormatFlagIsPacked (1U << 3)
685
686typedef struct _saudio_AudioStreamBasicDescription {
687 double mSampleRate;
688 _saudio_AudioFormatID mFormatID;
689 _saudio_AudioFormatFlags mFormatFlags;
690 uint32_t mBytesPerPacket;
691 uint32_t mFramesPerPacket;
692 uint32_t mBytesPerFrame;
693 uint32_t mChannelsPerFrame;
694 uint32_t mBitsPerChannel;
695 uint32_t mReserved;
696} _saudio_AudioStreamBasicDescription;
697
698typedef struct _saudio_AudioStreamPacketDescription {
699 int64_t mStartOffset;
700 uint32_t mVariableFramesInPacket;
701 uint32_t mDataByteSize;
702} _saudio_AudioStreamPacketDescription;
703
704typedef struct _saudio_SMPTETime {
705 int16_t mSubframes;
706 int16_t mSubframeDivisor;
707 uint32_t mCounter;
708 _saudio_SMPTETimeType mType;
709 _saudio_SMPTETimeFlags mFlags;
710 int16_t mHours;
711 int16_t mMinutes;
712 int16_t mSeconds;
713 int16_t mFrames;
714} _saudio_SMPTETime;
715
716typedef struct _saudio_AudioTimeStamp {
717 double mSampleTime;
718 uint64_t mHostTime;
719 double mRateScalar;
720 uint64_t mWordClockTime;
721 _saudio_SMPTETime mSMPTETime;
722 _saudio_AudioTimeStampFlags mFlags;
723 uint32_t mReserved;
724} _saudio_AudioTimeStamp;
725
726typedef struct _saudio_AudioQueueBuffer {
727 const uint32_t mAudioDataBytesCapacity;
728 void* const mAudioData;
729 uint32_t mAudioDataByteSize;
730 void * mUserData;
731 const uint32_t mPacketDescriptionCapacity;
732 _saudio_AudioStreamPacketDescription* const mPacketDescriptions;
733 uint32_t mPacketDescriptionCount;
734} _saudio_AudioQueueBuffer;
735typedef _saudio_AudioQueueBuffer* _saudio_AudioQueueBufferRef;
736
737typedef void (*_saudio_AudioQueueOutputCallback)(void* user_data, _saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer);
738
739extern _saudio_OSStatus AudioQueueNewOutput(const _saudio_AudioStreamBasicDescription* inFormat, _saudio_AudioQueueOutputCallback inCallbackProc, void* inUserData, _saudio_CFRunLoopRef inCallbackRunLoop, _saudio_CFStringRef inCallbackRunLoopMode, uint32_t inFlags, _saudio_AudioQueueRef* outAQ);
740extern _saudio_OSStatus AudioQueueDispose(_saudio_AudioQueueRef inAQ, bool inImmediate);
741extern _saudio_OSStatus AudioQueueAllocateBuffer(_saudio_AudioQueueRef inAQ, uint32_t inBufferByteSize, _saudio_AudioQueueBufferRef* outBuffer);
742extern _saudio_OSStatus AudioQueueEnqueueBuffer(_saudio_AudioQueueRef inAQ, _saudio_AudioQueueBufferRef inBuffer, uint32_t inNumPacketDescs, const _saudio_AudioStreamPacketDescription* inPacketDescs);
743extern _saudio_OSStatus AudioQueueStart(_saudio_AudioQueueRef inAQ, const _saudio_AudioTimeStamp * inStartTime);
744extern _saudio_OSStatus AudioQueueStop(_saudio_AudioQueueRef inAQ, bool inImmediate);
745#endif // SAUDIO_OSX_USE_SYSTEM_HEADERS
746
747typedef struct {
748 _saudio_AudioQueueRef ca_audio_queue;
749 #if defined(_SAUDIO_IOS)
750 id ca_interruption_handler;
751 #endif
752} _saudio_backend_t;
753
754/*=== ALSA BACKEND DECLARATIONS ==============================================*/
755#elif defined(_SAUDIO_LINUX)
756
757typedef struct {
758 snd_pcm_t* device;
759 float* buffer;
760 int buffer_byte_size;
761 int buffer_frames;
762 pthread_t thread;
763 bool thread_stop;
764} _saudio_backend_t;
765
766/*=== OpenSLES BACKEND DECLARATIONS ==============================================*/
767#elif defined(_SAUDIO_ANDROID)
768
769#define SAUDIO_NUM_BUFFERS 2
770
771typedef struct {
772 pthread_mutex_t mutex;
773 pthread_cond_t cond;
774 int count;
775} _saudio_semaphore_t;
776
777typedef struct {
778 SLObjectItf engine_obj;
779 SLEngineItf engine;
780 SLObjectItf output_mix_obj;
781 SLVolumeItf output_mix_vol;
782 SLDataLocator_OutputMix out_locator;
783 SLDataSink dst_data_sink;
784 SLObjectItf player_obj;
785 SLPlayItf player;
786 SLVolumeItf player_vol;
787 SLAndroidSimpleBufferQueueItf player_buffer_queue;
788
789 int16_t* output_buffers[SAUDIO_NUM_BUFFERS];
790 float* src_buffer;
791 int active_buffer;
792 _saudio_semaphore_t buffer_sem;
793 pthread_t thread;
794 volatile int thread_stop;
795 SLDataLocator_AndroidSimpleBufferQueue in_locator;
796} _saudio_backend_t;
797
798/*=== WASAPI BACKEND DECLARATIONS ============================================*/
799#elif defined(_SAUDIO_WINDOWS)
800
801typedef struct {
802 HANDLE thread_handle;
803 HANDLE buffer_end_event;
804 bool stop;
805 UINT32 dst_buffer_frames;
806 int src_buffer_frames;
807 int src_buffer_byte_size;
808 int src_buffer_pos;
809 float* src_buffer;
810} _saudio_wasapi_thread_data_t;
811
812typedef struct {
813 #if defined(_SAUDIO_UWP)
814 LPOLESTR interface_activation_audio_interface_uid_string;
815 IActivateAudioInterfaceAsyncOperation* interface_activation_operation;
816 BOOL interface_activation_success;
817 HANDLE interface_activation_mutex;
818 #else
819 IMMDeviceEnumerator* device_enumerator;
820 IMMDevice* device;
821 #endif
822 IAudioClient* audio_client;
823 IAudioRenderClient* render_client;
824 int si16_bytes_per_frame;
825 _saudio_wasapi_thread_data_t thread;
826} _saudio_backend_t;
827
828/*=== WEBAUDIO BACKEND DECLARATIONS ==========================================*/
829#elif defined(_SAUDIO_EMSCRIPTEN)
830
831typedef struct {
832 uint8_t* buffer;
833} _saudio_backend_t;
834
835#else
836#error "unknown platform"
837#endif
838
839/*=== GENERAL DECLARATIONS ===================================================*/
840
841/* a ringbuffer structure */
842typedef struct {
843 int head; // next slot to write to
844 int tail; // next slot to read from
845 int num; // number of slots in queue
846 int queue[SAUDIO_RING_MAX_SLOTS];
847} _saudio_ring_t;
848
849/* a packet FIFO structure */
850typedef struct {
851 bool valid;
852 int packet_size; /* size of a single packets in bytes(!) */
853 int num_packets; /* number of packet in fifo */
854 uint8_t* base_ptr; /* packet memory chunk base pointer (dynamically allocated) */
855 int cur_packet; /* current write-packet */
856 int cur_offset; /* current byte-offset into current write packet */
857 _saudio_mutex_t mutex; /* mutex for thread-safe access */
858 _saudio_ring_t read_queue; /* buffers with data, ready to be streamed */
859 _saudio_ring_t write_queue; /* empty buffers, ready to be pushed to */
860} _saudio_fifo_t;
861
862/* sokol-audio state */
863typedef struct {
864 bool valid;
865 void (*stream_cb)(float* buffer, int num_frames, int num_channels);
866 void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data);
867 void* user_data;
868 int sample_rate; /* sample rate */
869 int buffer_frames; /* number of frames in streaming buffer */
870 int bytes_per_frame; /* filled by backend */
871 int packet_frames; /* number of frames in a packet */
872 int num_packets; /* number of packets in packet queue */
873 int num_channels; /* actual number of channels */
874 saudio_desc desc;
875 _saudio_fifo_t fifo;
876 _saudio_backend_t backend;
877} _saudio_state_t;
878
879static _saudio_state_t _saudio;
880
881_SOKOL_PRIVATE bool _saudio_has_callback(void) {
882 return (_saudio.stream_cb || _saudio.stream_userdata_cb);
883}
884
885_SOKOL_PRIVATE void _saudio_stream_callback(float* buffer, int num_frames, int num_channels) {
886 if (_saudio.stream_cb) {
887 _saudio.stream_cb(buffer, num_frames, num_channels);
888 }
889 else if (_saudio.stream_userdata_cb) {
890 _saudio.stream_userdata_cb(buffer, num_frames, num_channels, _saudio.user_data);
891 }
892}
893
894/*=== MUTEX IMPLEMENTATION ===================================================*/
895#if defined(_SAUDIO_NOTHREADS)
896
897_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { (void)m; }
898_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { (void)m; }
899_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { (void)m; }
900_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { (void)m; }
901
902#elif defined(_SAUDIO_PTHREADS)
903
904_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) {
905 pthread_mutexattr_t attr;
906 pthread_mutexattr_init(&attr);
907 pthread_mutex_init(&m->mutex, &attr);
908}
909
910_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) {
911 pthread_mutex_destroy(&m->mutex);
912}
913
914_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) {
915 pthread_mutex_lock(&m->mutex);
916}
917
918_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) {
919 pthread_mutex_unlock(&m->mutex);
920}
921
922#elif defined(_SAUDIO_WINTHREADS)
923
924_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) {
925 InitializeCriticalSection(&m->critsec);
926}
927
928_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) {
929 DeleteCriticalSection(&m->critsec);
930}
931
932_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) {
933 EnterCriticalSection(&m->critsec);
934}
935
936_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) {
937 LeaveCriticalSection(&m->critsec);
938}
939#else
940#error "unknown platform!"
941#endif
942
943/*=== RING-BUFFER QUEUE IMPLEMENTATION =======================================*/
944_SOKOL_PRIVATE int _saudio_ring_idx(_saudio_ring_t* ring, int i) {
945 return (i % ring->num);
946}
947
948_SOKOL_PRIVATE void _saudio_ring_init(_saudio_ring_t* ring, int num_slots) {
949 SOKOL_ASSERT((num_slots + 1) <= SAUDIO_RING_MAX_SLOTS);
950 ring->head = 0;
951 ring->tail = 0;
952 /* one slot reserved to detect 'full' vs 'empty' */
953 ring->num = num_slots + 1;
954}
955
956_SOKOL_PRIVATE bool _saudio_ring_full(_saudio_ring_t* ring) {
957 return _saudio_ring_idx(ring, ring->head + 1) == ring->tail;
958}
959
960_SOKOL_PRIVATE bool _saudio_ring_empty(_saudio_ring_t* ring) {
961 return ring->head == ring->tail;
962}
963
964_SOKOL_PRIVATE int _saudio_ring_count(_saudio_ring_t* ring) {
965 int count;
966 if (ring->head >= ring->tail) {
967 count = ring->head - ring->tail;
968 }
969 else {
970 count = (ring->head + ring->num) - ring->tail;
971 }
972 SOKOL_ASSERT(count < ring->num);
973 return count;
974}
975
976_SOKOL_PRIVATE void _saudio_ring_enqueue(_saudio_ring_t* ring, int val) {
977 SOKOL_ASSERT(!_saudio_ring_full(ring));
978 ring->queue[ring->head] = val;
979 ring->head = _saudio_ring_idx(ring, ring->head + 1);
980}
981
982_SOKOL_PRIVATE int _saudio_ring_dequeue(_saudio_ring_t* ring) {
983 SOKOL_ASSERT(!_saudio_ring_empty(ring));
984 int val = ring->queue[ring->tail];
985 ring->tail = _saudio_ring_idx(ring, ring->tail + 1);
986 return val;
987}
988
989/*--- a packet fifo for queueing audio data from main thread ----------------*/
990_SOKOL_PRIVATE void _saudio_fifo_init_mutex(_saudio_fifo_t* fifo) {
991 /* this must be called before initializing both the backend and the fifo itself! */
992 _saudio_mutex_init(&fifo->mutex);
993}
994
995_SOKOL_PRIVATE void _saudio_fifo_init(_saudio_fifo_t* fifo, int packet_size, int num_packets) {
996 /* NOTE: there's a chicken-egg situation during the init phase where the
997 streaming thread must be started before the fifo is actually initialized,
998 thus the fifo init must already be protected from access by the fifo_read() func.
999 */
1000 _saudio_mutex_lock(&fifo->mutex);
1001 SOKOL_ASSERT((packet_size > 0) && (num_packets > 0));
1002 fifo->packet_size = packet_size;
1003 fifo->num_packets = num_packets;
1004 fifo->base_ptr = (uint8_t*) SOKOL_MALLOC((size_t)(packet_size * num_packets));
1005 SOKOL_ASSERT(fifo->base_ptr);
1006 fifo->cur_packet = -1;
1007 fifo->cur_offset = 0;
1008 _saudio_ring_init(&fifo->read_queue, num_packets);
1009 _saudio_ring_init(&fifo->write_queue, num_packets);
1010 for (int i = 0; i < num_packets; i++) {
1011 _saudio_ring_enqueue(&fifo->write_queue, i);
1012 }
1013 SOKOL_ASSERT(_saudio_ring_full(&fifo->write_queue));
1014 SOKOL_ASSERT(_saudio_ring_count(&fifo->write_queue) == num_packets);
1015 SOKOL_ASSERT(_saudio_ring_empty(&fifo->read_queue));
1016 SOKOL_ASSERT(_saudio_ring_count(&fifo->read_queue) == 0);
1017 fifo->valid = true;
1018 _saudio_mutex_unlock(&fifo->mutex);
1019}
1020
1021_SOKOL_PRIVATE void _saudio_fifo_shutdown(_saudio_fifo_t* fifo) {
1022 SOKOL_ASSERT(fifo->base_ptr);
1023 SOKOL_FREE(fifo->base_ptr);
1024 fifo->base_ptr = 0;
1025 fifo->valid = false;
1026 _saudio_mutex_destroy(&fifo->mutex);
1027}
1028
1029_SOKOL_PRIVATE int _saudio_fifo_writable_bytes(_saudio_fifo_t* fifo) {
1030 _saudio_mutex_lock(&fifo->mutex);
1031 int num_bytes = (_saudio_ring_count(&fifo->write_queue) * fifo->packet_size);
1032 if (fifo->cur_packet != -1) {
1033 num_bytes += fifo->packet_size - fifo->cur_offset;
1034 }
1035 _saudio_mutex_unlock(&fifo->mutex);
1036 SOKOL_ASSERT((num_bytes >= 0) && (num_bytes <= (fifo->num_packets * fifo->packet_size)));
1037 return num_bytes;
1038}
1039
1040/* write new data to the write queue, this is called from main thread */
1041_SOKOL_PRIVATE int _saudio_fifo_write(_saudio_fifo_t* fifo, const uint8_t* ptr, int num_bytes) {
1042 /* returns the number of bytes written, this will be smaller then requested
1043 if the write queue runs full
1044 */
1045 int all_to_copy = num_bytes;
1046 while (all_to_copy > 0) {
1047 /* need to grab a new packet? */
1048 if (fifo->cur_packet == -1) {
1049 _saudio_mutex_lock(&fifo->mutex);
1050 if (!_saudio_ring_empty(&fifo->write_queue)) {
1051 fifo->cur_packet = _saudio_ring_dequeue(&fifo->write_queue);
1052 }
1053 _saudio_mutex_unlock(&fifo->mutex);
1054 SOKOL_ASSERT(fifo->cur_offset == 0);
1055 }
1056 /* append data to current write packet */
1057 if (fifo->cur_packet != -1) {
1058 int to_copy = all_to_copy;
1059 const int max_copy = fifo->packet_size - fifo->cur_offset;
1060 if (to_copy > max_copy) {
1061 to_copy = max_copy;
1062 }
1063 uint8_t* dst = fifo->base_ptr + fifo->cur_packet * fifo->packet_size + fifo->cur_offset;
1064 memcpy(dst, ptr, (size_t)to_copy);
1065 ptr += to_copy;
1066 fifo->cur_offset += to_copy;
1067 all_to_copy -= to_copy;
1068 SOKOL_ASSERT(fifo->cur_offset <= fifo->packet_size);
1069 SOKOL_ASSERT(all_to_copy >= 0);
1070 }
1071 else {
1072 /* early out if we're starving */
1073 int bytes_copied = num_bytes - all_to_copy;
1074 SOKOL_ASSERT((bytes_copied >= 0) && (bytes_copied < num_bytes));
1075 return bytes_copied;
1076 }
1077 /* if write packet is full, push to read queue */
1078 if (fifo->cur_offset == fifo->packet_size) {
1079 _saudio_mutex_lock(&fifo->mutex);
1080 _saudio_ring_enqueue(&fifo->read_queue, fifo->cur_packet);
1081 _saudio_mutex_unlock(&fifo->mutex);
1082 fifo->cur_packet = -1;
1083 fifo->cur_offset = 0;
1084 }
1085 }
1086 SOKOL_ASSERT(all_to_copy == 0);
1087 return num_bytes;
1088}
1089
1090/* read queued data, this is called form the stream callback (maybe separate thread) */
1091_SOKOL_PRIVATE int _saudio_fifo_read(_saudio_fifo_t* fifo, uint8_t* ptr, int num_bytes) {
1092 /* NOTE: fifo_read might be called before the fifo is properly initialized */
1093 _saudio_mutex_lock(&fifo->mutex);
1094 int num_bytes_copied = 0;
1095 if (fifo->valid) {
1096 SOKOL_ASSERT(0 == (num_bytes % fifo->packet_size));
1097 SOKOL_ASSERT(num_bytes <= (fifo->packet_size * fifo->num_packets));
1098 const int num_packets_needed = num_bytes / fifo->packet_size;
1099 uint8_t* dst = ptr;
1100 /* either pull a full buffer worth of data, or nothing */
1101 if (_saudio_ring_count(&fifo->read_queue) >= num_packets_needed) {
1102 for (int i = 0; i < num_packets_needed; i++) {
1103 int packet_index = _saudio_ring_dequeue(&fifo->read_queue);
1104 _saudio_ring_enqueue(&fifo->write_queue, packet_index);
1105 const uint8_t* src = fifo->base_ptr + packet_index * fifo->packet_size;
1106 memcpy(dst, src, (size_t)fifo->packet_size);
1107 dst += fifo->packet_size;
1108 num_bytes_copied += fifo->packet_size;
1109 }
1110 SOKOL_ASSERT(num_bytes == num_bytes_copied);
1111 }
1112 }
1113 _saudio_mutex_unlock(&fifo->mutex);
1114 return num_bytes_copied;
1115}
1116
1117/*=== DUMMY BACKEND IMPLEMENTATION ===========================================*/
1118#if defined(SOKOL_DUMMY_BACKEND)
1119_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1120 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1121 return true;
1122};
1123_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { };
1124
1125/*=== COREAUDIO BACKEND IMPLEMENTATION =======================================*/
1126#elif defined(_SAUDIO_APPLE)
1127
1128#if defined(_SAUDIO_IOS)
1129#if __has_feature(objc_arc)
1130#define _SAUDIO_OBJC_RELEASE(obj) { obj = nil; }
1131#else
1132#define _SAUDIO_OBJC_RELEASE(obj) { [obj release]; obj = nil; }
1133#endif
1134
1135@interface _saudio_interruption_handler : NSObject { }
1136@end
1137
1138@implementation _saudio_interruption_handler
1139-(id)init {
1140 self = [super init];
1141 AVAudioSession* session = [AVAudioSession sharedInstance];
1142 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:session];
1143 return self;
1144}
1145
1146-(void)dealloc {
1147 [self remove_handler];
1148 #if !__has_feature(objc_arc)
1149 [super dealloc];
1150 #endif
1151}
1152
1153-(void)remove_handler {
1154 [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionInterruptionNotification" object:nil];
1155}
1156
1157-(void)handle_interruption:(NSNotification*)notification {
1158 AVAudioSession* session = [AVAudioSession sharedInstance];
1159 SOKOL_ASSERT(session);
1160 NSDictionary* dict = notification.userInfo;
1161 SOKOL_ASSERT(dict);
1162 NSInteger type = [[dict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
1163 switch (type) {
1164 case AVAudioSessionInterruptionTypeBegan:
1165 AudioQueuePause(_saudio.backend.ca_audio_queue);
1166 [session setActive:false error:nil];
1167 break;
1168 case AVAudioSessionInterruptionTypeEnded:
1169 [session setActive:true error:nil];
1170 AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
1171 break;
1172 default:
1173 break;
1174 }
1175}
1176@end
1177#endif // _SAUDIO_IOS
1178
1179/* NOTE: the buffer data callback is called on a separate thread! */
1180_SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, _saudio_AudioQueueRef queue, _saudio_AudioQueueBufferRef buffer) {
1181 _SOKOL_UNUSED(user_data);
1182 if (_saudio_has_callback()) {
1183 const int num_frames = (int)buffer->mAudioDataByteSize / _saudio.bytes_per_frame;
1184 const int num_channels = _saudio.num_channels;
1185 _saudio_stream_callback((float*)buffer->mAudioData, num_frames, num_channels);
1186 }
1187 else {
1188 uint8_t* ptr = (uint8_t*)buffer->mAudioData;
1189 int num_bytes = (int) buffer->mAudioDataByteSize;
1190 if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) {
1191 /* not enough read data available, fill the entire buffer with silence */
1192 memset(ptr, 0, (size_t)num_bytes);
1193 }
1194 }
1195 AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
1196}
1197
1198_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1199 SOKOL_ASSERT(0 == _saudio.backend.ca_audio_queue);
1200
1201 #if defined(_SAUDIO_IOS)
1202 /* activate audio session */
1203 AVAudioSession* session = [AVAudioSession sharedInstance];
1204 SOKOL_ASSERT(session != nil);
1205 [session setCategory: AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
1206 [session setActive:true error:nil];
1207
1208 /* create interruption handler */
1209 _saudio.backend.ca_interruption_handler = [[_saudio_interruption_handler alloc] init];
1210 #endif // _SAUDIO_IOS
1211
1212 /* create an audio queue with fp32 samples */
1213 _saudio_AudioStreamBasicDescription fmt;
1214 memset(&fmt, 0, sizeof(fmt));
1215 fmt.mSampleRate = (double) _saudio.sample_rate;
1216 fmt.mFormatID = _saudio_kAudioFormatLinearPCM;
1217 fmt.mFormatFlags = _saudio_kLinearPCMFormatFlagIsFloat | _saudio_kAudioFormatFlagIsPacked;
1218 fmt.mFramesPerPacket = 1;
1219 fmt.mChannelsPerFrame = (uint32_t) _saudio.num_channels;
1220 fmt.mBytesPerFrame = (uint32_t)sizeof(float) * (uint32_t)_saudio.num_channels;
1221 fmt.mBytesPerPacket = fmt.mBytesPerFrame;
1222 fmt.mBitsPerChannel = 32;
1223 _saudio_OSStatus res = AudioQueueNewOutput(&fmt, _saudio_coreaudio_callback, 0, NULL, NULL, 0, &_saudio.backend.ca_audio_queue);
1224 SOKOL_ASSERT((res == 0) && _saudio.backend.ca_audio_queue);
1225
1226 /* create 2 audio buffers */
1227 for (int i = 0; i < 2; i++) {
1228 _saudio_AudioQueueBufferRef buf = NULL;
1229 const uint32_t buf_byte_size = (uint32_t)_saudio.buffer_frames * fmt.mBytesPerFrame;
1230 res = AudioQueueAllocateBuffer(_saudio.backend.ca_audio_queue, buf_byte_size, &buf);
1231 SOKOL_ASSERT((res == 0) && buf);
1232 buf->mAudioDataByteSize = buf_byte_size;
1233 memset(buf->mAudioData, 0, buf->mAudioDataByteSize);
1234 AudioQueueEnqueueBuffer(_saudio.backend.ca_audio_queue, buf, 0, NULL);
1235 }
1236
1237 /* init or modify actual playback parameters */
1238 _saudio.bytes_per_frame = (int)fmt.mBytesPerFrame;
1239
1240 /* ...and start playback */
1241 res = AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
1242 SOKOL_ASSERT(0 == res);
1243
1244 return true;
1245}
1246
1247_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1248 AudioQueueStop(_saudio.backend.ca_audio_queue, true);
1249 AudioQueueDispose(_saudio.backend.ca_audio_queue, false);
1250 _saudio.backend.ca_audio_queue = NULL;
1251 #if defined(_SAUDIO_IOS)
1252 /* remove interruption handler */
1253 if (_saudio.backend.ca_interruption_handler != nil) {
1254 [_saudio.backend.ca_interruption_handler remove_handler];
1255 _SAUDIO_OBJC_RELEASE(_saudio.backend.ca_interruption_handler);
1256 }
1257 /* deactivate audio session */
1258 AVAudioSession* session = [AVAudioSession sharedInstance];
1259 SOKOL_ASSERT(session);
1260 [session setActive:false error:nil];;
1261 #endif // _SAUDIO_IOS
1262}
1263
1264/*=== ALSA BACKEND IMPLEMENTATION ============================================*/
1265#elif defined(_SAUDIO_LINUX)
1266
1267/* the streaming callback runs in a separate thread */
1268_SOKOL_PRIVATE void* _saudio_alsa_cb(void* param) {
1269 _SOKOL_UNUSED(param);
1270 while (!_saudio.backend.thread_stop) {
1271 /* snd_pcm_writei() will be blocking until it needs data */
1272 int write_res = snd_pcm_writei(_saudio.backend.device, _saudio.backend.buffer, (snd_pcm_uframes_t)_saudio.backend.buffer_frames);
1273 if (write_res < 0) {
1274 /* underrun occurred */
1275 snd_pcm_prepare(_saudio.backend.device);
1276 }
1277 else {
1278 /* fill the streaming buffer with new data */
1279 if (_saudio_has_callback()) {
1280 _saudio_stream_callback(_saudio.backend.buffer, _saudio.backend.buffer_frames, _saudio.num_channels);
1281 }
1282 else {
1283 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.buffer, _saudio.backend.buffer_byte_size)) {
1284 /* not enough read data available, fill the entire buffer with silence */
1285 memset(_saudio.backend.buffer, 0, (size_t)_saudio.backend.buffer_byte_size);
1286 }
1287 }
1288 }
1289 }
1290 return 0;
1291}
1292
1293_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1294 int dir; uint32_t rate;
1295 int rc = snd_pcm_open(&_saudio.backend.device, "default", SND_PCM_STREAM_PLAYBACK, 0);
1296 if (rc < 0) {
1297 SOKOL_LOG("sokol_audio.h: snd_pcm_open() failed");
1298 return false;
1299 }
1300
1301 /* configuration works by restricting the 'configuration space' step
1302 by step, we require all parameters except the sample rate to
1303 match perfectly
1304 */
1305 snd_pcm_hw_params_t* params = 0;
1306 snd_pcm_hw_params_alloca(¶ms);
1307 snd_pcm_hw_params_any(_saudio.backend.device, params);
1308 snd_pcm_hw_params_set_access(_saudio.backend.device, params, SND_PCM_ACCESS_RW_INTERLEAVED);
1309 if (0 > snd_pcm_hw_params_set_format(_saudio.backend.device, params, SND_PCM_FORMAT_FLOAT_LE)) {
1310 SOKOL_LOG("sokol_audio.h: float samples not supported");
1311 goto error;
1312 }
1313 if (0 > snd_pcm_hw_params_set_buffer_size(_saudio.backend.device, params, (snd_pcm_uframes_t)_saudio.buffer_frames)) {
1314 SOKOL_LOG("sokol_audio.h: requested buffer size not supported");
1315 goto error;
1316 }
1317 if (0 > snd_pcm_hw_params_set_channels(_saudio.backend.device, params, (uint32_t)_saudio.num_channels)) {
1318 SOKOL_LOG("sokol_audio.h: requested channel count not supported");
1319 goto error;
1320 }
1321 /* let ALSA pick a nearby sampling rate */
1322 rate = (uint32_t) _saudio.sample_rate;
1323 dir = 0;
1324 if (0 > snd_pcm_hw_params_set_rate_near(_saudio.backend.device, params, &rate, &dir)) {
1325 SOKOL_LOG("sokol_audio.h: snd_pcm_hw_params_set_rate_near() failed");
1326 goto error;
1327 }
1328 if (0 > snd_pcm_hw_params(_saudio.backend.device, params)) {
1329 SOKOL_LOG("sokol_audio.h: snd_pcm_hw_params() failed");
1330 goto error;
1331 }
1332
1333 /* read back actual sample rate and channels */
1334 _saudio.sample_rate = (int)rate;
1335 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1336
1337 /* allocate the streaming buffer */
1338 _saudio.backend.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame;
1339 _saudio.backend.buffer_frames = _saudio.buffer_frames;
1340 _saudio.backend.buffer = (float*) SOKOL_MALLOC((size_t)_saudio.backend.buffer_byte_size);
1341 memset(_saudio.backend.buffer, 0, (size_t)_saudio.backend.buffer_byte_size);
1342
1343 /* create the buffer-streaming start thread */
1344 if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_alsa_cb, 0)) {
1345 SOKOL_LOG("sokol_audio.h: pthread_create() failed");
1346 goto error;
1347 }
1348
1349 return true;
1350error:
1351 if (_saudio.backend.device) {
1352 snd_pcm_close(_saudio.backend.device);
1353 _saudio.backend.device = 0;
1354 }
1355 return false;
1356};
1357
1358_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1359 SOKOL_ASSERT(_saudio.backend.device);
1360 _saudio.backend.thread_stop = true;
1361 pthread_join(_saudio.backend.thread, 0);
1362 snd_pcm_drain(_saudio.backend.device);
1363 snd_pcm_close(_saudio.backend.device);
1364 SOKOL_FREE(_saudio.backend.buffer);
1365};
1366
1367/*=== WASAPI BACKEND IMPLEMENTATION ==========================================*/
1368#elif defined(_SAUDIO_WINDOWS)
1369
1370#if defined(_SAUDIO_UWP)
1371/* Minimal implementation of an IActivateAudioInterfaceCompletionHandler COM object in plain C.
1372 Meant to be a static singleton (always one reference when add/remove reference)
1373 and implements IUnknown and IActivateAudioInterfaceCompletionHandler when queryinterface'd
1374
1375 Do not know why but IActivateAudioInterfaceCompletionHandler's GUID is not the one system queries for,
1376 so I'm advertising the one actually requested.
1377*/
1378_SOKOL_PRIVATE HRESULT STDMETHODCALLTYPE _saudio_interface_completion_handler_queryinterface(IActivateAudioInterfaceCompletionHandler* instance, REFIID riid, void** ppvObject) {
1379 if (!ppvObject) {
1380 return E_POINTER;
1381 }
1382
1383 if (IsEqualIID(riid, _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IActivateAudioInterface_Completion_Handler)) || IsEqualIID(riid, _SOKOL_AUDIO_WIN32COM_ID(IID_IUnknown)))
1384 {
1385 *ppvObject = (void*)instance;
1386 return S_OK;
1387 }
1388
1389 *ppvObject = NULL;
1390 return E_NOINTERFACE;
1391}
1392
1393_SOKOL_PRIVATE ULONG STDMETHODCALLTYPE _saudio_interface_completion_handler_addref_release(IActivateAudioInterfaceCompletionHandler* instance) {
1394 _SOKOL_UNUSED(instance);
1395 return 1;
1396}
1397
1398_SOKOL_PRIVATE HRESULT STDMETHODCALLTYPE _saudio_backend_activate_audio_interface_cb(IActivateAudioInterfaceCompletionHandler* instance, IActivateAudioInterfaceAsyncOperation* activateOperation) {
1399 _SOKOL_UNUSED(instance);
1400 WaitForSingleObject(_saudio.backend.interface_activation_mutex, INFINITE);
1401 _saudio.backend.interface_activation_success = TRUE;
1402 HRESULT activation_result;
1403 if (FAILED(activateOperation->lpVtbl->GetActivateResult(activateOperation, &activation_result, (IUnknown**)(&_saudio.backend.audio_client))) || FAILED(activation_result)) {
1404 _saudio.backend.interface_activation_success = FALSE;
1405 }
1406
1407 ReleaseMutex(_saudio.backend.interface_activation_mutex);
1408 return S_OK;
1409}
1410#endif // _SAUDIO_UWP
1411
1412/* fill intermediate buffer with new data and reset buffer_pos */
1413_SOKOL_PRIVATE void _saudio_wasapi_fill_buffer(void) {
1414 if (_saudio_has_callback()) {
1415 _saudio_stream_callback(_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_frames, _saudio.num_channels);
1416 }
1417 else {
1418 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_byte_size)) {
1419 /* not enough read data available, fill the entire buffer with silence */
1420 memset(_saudio.backend.thread.src_buffer, 0, (size_t)_saudio.backend.thread.src_buffer_byte_size);
1421 }
1422 }
1423}
1424
1425_SOKOL_PRIVATE void _saudio_wasapi_submit_buffer(int num_frames) {
1426 BYTE* wasapi_buffer = 0;
1427 if (FAILED(IAudioRenderClient_GetBuffer(_saudio.backend.render_client, num_frames, &wasapi_buffer))) {
1428 return;
1429 }
1430 SOKOL_ASSERT(wasapi_buffer);
1431
1432 /* convert float samples to int16_t, refill float buffer if needed */
1433 const int num_samples = num_frames * _saudio.num_channels;
1434 int16_t* dst = (int16_t*) wasapi_buffer;
1435 int buffer_pos = _saudio.backend.thread.src_buffer_pos;
1436 const int buffer_float_size = _saudio.backend.thread.src_buffer_byte_size / (int)sizeof(float);
1437 float* src = _saudio.backend.thread.src_buffer;
1438 for (int i = 0; i < num_samples; i++) {
1439 if (0 == buffer_pos) {
1440 _saudio_wasapi_fill_buffer();
1441 }
1442 dst[i] = (int16_t) (src[buffer_pos] * 0x7FFF);
1443 buffer_pos += 1;
1444 if (buffer_pos == buffer_float_size) {
1445 buffer_pos = 0;
1446 }
1447 }
1448 _saudio.backend.thread.src_buffer_pos = buffer_pos;
1449
1450 IAudioRenderClient_ReleaseBuffer(_saudio.backend.render_client, num_frames, 0);
1451}
1452
1453_SOKOL_PRIVATE DWORD WINAPI _saudio_wasapi_thread_fn(LPVOID param) {
1454 (void)param;
1455 _saudio_wasapi_submit_buffer(_saudio.backend.thread.src_buffer_frames);
1456 IAudioClient_Start(_saudio.backend.audio_client);
1457 while (!_saudio.backend.thread.stop) {
1458 WaitForSingleObject(_saudio.backend.thread.buffer_end_event, INFINITE);
1459 UINT32 padding = 0;
1460 if (FAILED(IAudioClient_GetCurrentPadding(_saudio.backend.audio_client, &padding))) {
1461 continue;
1462 }
1463 SOKOL_ASSERT(_saudio.backend.thread.dst_buffer_frames >= padding);
1464 int num_frames = (int)_saudio.backend.thread.dst_buffer_frames - (int)padding;
1465 if (num_frames > 0) {
1466 _saudio_wasapi_submit_buffer(num_frames);
1467 }
1468 }
1469 return 0;
1470}
1471
1472_SOKOL_PRIVATE void _saudio_wasapi_release(void) {
1473 if (_saudio.backend.thread.src_buffer) {
1474 SOKOL_FREE(_saudio.backend.thread.src_buffer);
1475 _saudio.backend.thread.src_buffer = 0;
1476 }
1477 if (_saudio.backend.render_client) {
1478 IAudioRenderClient_Release(_saudio.backend.render_client);
1479 _saudio.backend.render_client = 0;
1480 }
1481 if (_saudio.backend.audio_client) {
1482 IAudioClient_Release(_saudio.backend.audio_client);
1483 _saudio.backend.audio_client = 0;
1484 }
1485 #if defined(_SAUDIO_UWP)
1486 if (_saudio.backend.interface_activation_audio_interface_uid_string) {
1487 CoTaskMemFree(_saudio.backend.interface_activation_audio_interface_uid_string);
1488 _saudio.backend.interface_activation_audio_interface_uid_string = 0;
1489 }
1490 if (_saudio.backend.interface_activation_operation) {
1491 IActivateAudioInterfaceAsyncOperation_Release(_saudio.backend.interface_activation_operation);
1492 _saudio.backend.interface_activation_operation = 0;
1493 }
1494 #else
1495 if (_saudio.backend.device) {
1496 IMMDevice_Release(_saudio.backend.device);
1497 _saudio.backend.device = 0;
1498 }
1499 if (_saudio.backend.device_enumerator) {
1500 IMMDeviceEnumerator_Release(_saudio.backend.device_enumerator);
1501 _saudio.backend.device_enumerator = 0;
1502 }
1503 #endif
1504 if (0 != _saudio.backend.thread.buffer_end_event) {
1505 CloseHandle(_saudio.backend.thread.buffer_end_event);
1506 _saudio.backend.thread.buffer_end_event = 0;
1507 }
1508}
1509
1510_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1511 REFERENCE_TIME dur;
1512 /* UWP Threads are CoInitialized by default with a different threading model, and this call fails
1513 See https://github.com/Microsoft/cppwinrt/issues/6#issuecomment-253930637 */
1514 #if defined(_SAUDIO_WIN32)
1515 /* CoInitializeEx could have been called elsewhere already, in which
1516 case the function returns with S_FALSE (thus it does not make much
1517 sense to check the result)
1518 */
1519 HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
1520 _SOKOL_UNUSED(hr);
1521 #endif
1522 _saudio.backend.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0);
1523 if (0 == _saudio.backend.thread.buffer_end_event) {
1524 SOKOL_LOG("sokol_audio wasapi: failed to create buffer_end_event");
1525 goto error;
1526 }
1527 #if defined(_SAUDIO_UWP)
1528 _saudio.backend.interface_activation_mutex = CreateMutexA(NULL, FALSE, "interface_activation_mutex");
1529 if (_saudio.backend.interface_activation_mutex == NULL) {
1530 SOKOL_LOG("sokol_audio wasapi: failed to create interface activation mutex");
1531 goto error;
1532 }
1533 if (FAILED(StringFromIID(_SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_Devinterface_Audio_Render), &_saudio.backend.interface_activation_audio_interface_uid_string))) {
1534 SOKOL_LOG("sokol_audio wasapi: failed to get default audio device ID string");
1535 goto error;
1536 }
1537
1538 /* static instance of the fake COM object */
1539 static IActivateAudioInterfaceCompletionHandlerVtbl completion_handler_interface_vtable = {
1540 _saudio_interface_completion_handler_queryinterface,
1541 _saudio_interface_completion_handler_addref_release,
1542 _saudio_interface_completion_handler_addref_release,
1543 _saudio_backend_activate_audio_interface_cb
1544 };
1545 static IActivateAudioInterfaceCompletionHandler completion_handler_interface = { &completion_handler_interface_vtable };
1546
1547 if (FAILED(ActivateAudioInterfaceAsync(_saudio.backend.interface_activation_audio_interface_uid_string, _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient), NULL, &completion_handler_interface, &_saudio.backend.interface_activation_operation))) {
1548 SOKOL_LOG("sokol_audio wasapi: failed to get default audio device ID string");
1549 goto error;
1550 }
1551 while (!(_saudio.backend.audio_client)) {
1552 if (WaitForSingleObject(_saudio.backend.interface_activation_mutex, 10) != WAIT_TIMEOUT) {
1553 ReleaseMutex(_saudio.backend.interface_activation_mutex);
1554 }
1555 }
1556
1557 if (!(_saudio.backend.interface_activation_success)) {
1558 SOKOL_LOG("sokol_audio wasapi: interface activation failed. Unable to get audio client");
1559 goto error;
1560 }
1561
1562 #else
1563 if (FAILED(CoCreateInstance(_SOKOL_AUDIO_WIN32COM_ID(_saudio_CLSID_IMMDeviceEnumerator),
1564 0, CLSCTX_ALL,
1565 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IMMDeviceEnumerator),
1566 (void**)&_saudio.backend.device_enumerator)))
1567 {
1568 SOKOL_LOG("sokol_audio wasapi: failed to create device enumerator");
1569 goto error;
1570 }
1571 if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(_saudio.backend.device_enumerator,
1572 eRender, eConsole,
1573 &_saudio.backend.device)))
1574 {
1575 SOKOL_LOG("sokol_audio wasapi: GetDefaultAudioEndPoint failed");
1576 goto error;
1577 }
1578 if (FAILED(IMMDevice_Activate(_saudio.backend.device,
1579 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient),
1580 CLSCTX_ALL, 0,
1581 (void**)&_saudio.backend.audio_client)))
1582 {
1583 SOKOL_LOG("sokol_audio wasapi: device activate failed");
1584 goto error;
1585 }
1586 #endif
1587 WAVEFORMATEX fmt;
1588 memset(&fmt, 0, sizeof(fmt));
1589 fmt.nChannels = (WORD)_saudio.num_channels;
1590 fmt.nSamplesPerSec = (DWORD)_saudio.sample_rate;
1591 fmt.wFormatTag = WAVE_FORMAT_PCM;
1592 fmt.wBitsPerSample = 16;
1593 fmt.nBlockAlign = (fmt.nChannels * fmt.wBitsPerSample) / 8;
1594 fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign;
1595 dur = (REFERENCE_TIME)
1596 (((double)_saudio.buffer_frames) / (((double)_saudio.sample_rate) * (1.0/10000000.0)));
1597 if (FAILED(IAudioClient_Initialize(_saudio.backend.audio_client,
1598 AUDCLNT_SHAREMODE_SHARED,
1599 AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
1600 dur, 0, &fmt, 0)))
1601 {
1602 SOKOL_LOG("sokol_audio wasapi: audio client initialize failed");
1603 goto error;
1604 }
1605 if (FAILED(IAudioClient_GetBufferSize(_saudio.backend.audio_client, &_saudio.backend.thread.dst_buffer_frames))) {
1606 SOKOL_LOG("sokol_audio wasapi: audio client get buffer size failed");
1607 goto error;
1608 }
1609 if (FAILED(IAudioClient_GetService(_saudio.backend.audio_client,
1610 _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioRenderClient),
1611 (void**)&_saudio.backend.render_client)))
1612 {
1613 SOKOL_LOG("sokol_audio wasapi: audio client GetService failed");
1614 goto error;
1615 }
1616 if (FAILED(IAudioClient_SetEventHandle(_saudio.backend.audio_client, _saudio.backend.thread.buffer_end_event))) {
1617 SOKOL_LOG("sokol_audio wasapi: audio client SetEventHandle failed");
1618 goto error;
1619 }
1620 _saudio.backend.si16_bytes_per_frame = _saudio.num_channels * (int)sizeof(int16_t);
1621 _saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
1622 _saudio.backend.thread.src_buffer_frames = _saudio.buffer_frames;
1623 _saudio.backend.thread.src_buffer_byte_size = _saudio.backend.thread.src_buffer_frames * _saudio.bytes_per_frame;
1624
1625 /* allocate an intermediate buffer for sample format conversion */
1626 _saudio.backend.thread.src_buffer = (float*) SOKOL_MALLOC((size_t)_saudio.backend.thread.src_buffer_byte_size);
1627 SOKOL_ASSERT(_saudio.backend.thread.src_buffer);
1628
1629 /* create streaming thread */
1630 _saudio.backend.thread.thread_handle = CreateThread(NULL, 0, _saudio_wasapi_thread_fn, 0, 0, 0);
1631 if (0 == _saudio.backend.thread.thread_handle) {
1632 SOKOL_LOG("sokol_audio wasapi: CreateThread failed");
1633 goto error;
1634 }
1635 return true;
1636error:
1637 _saudio_wasapi_release();
1638 return false;
1639}
1640
1641_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1642 if (_saudio.backend.thread.thread_handle) {
1643 _saudio.backend.thread.stop = true;
1644 SetEvent(_saudio.backend.thread.buffer_end_event);
1645 WaitForSingleObject(_saudio.backend.thread.thread_handle, INFINITE);
1646 CloseHandle(_saudio.backend.thread.thread_handle);
1647 _saudio.backend.thread.thread_handle = 0;
1648 }
1649 if (_saudio.backend.audio_client) {
1650 IAudioClient_Stop(_saudio.backend.audio_client);
1651 }
1652 _saudio_wasapi_release();
1653
1654 #if defined(_SAUDIO_WIN32)
1655 CoUninitialize();
1656 #endif
1657}
1658
1659/*=== EMSCRIPTEN BACKEND IMPLEMENTATION ======================================*/
1660#elif defined(_SAUDIO_EMSCRIPTEN)
1661
1662#ifdef __cplusplus
1663extern "C" {
1664#endif
1665
1666EMSCRIPTEN_KEEPALIVE int _saudio_emsc_pull(int num_frames) {
1667 SOKOL_ASSERT(_saudio.backend.buffer);
1668 if (num_frames == _saudio.buffer_frames) {
1669 if (_saudio_has_callback()) {
1670 _saudio_stream_callback((float*)_saudio.backend.buffer, num_frames, _saudio.num_channels);
1671 }
1672 else {
1673 const int num_bytes = num_frames * _saudio.bytes_per_frame;
1674 if (0 == _saudio_fifo_read(&_saudio.fifo, _saudio.backend.buffer, num_bytes)) {
1675 /* not enough read data available, fill the entire buffer with silence */
1676 memset(_saudio.backend.buffer, 0, (size_t)num_bytes);
1677 }
1678 }
1679 int res = (int) _saudio.backend.buffer;
1680 return res;
1681 }
1682 else {
1683 return 0;
1684 }
1685}
1686
1687#ifdef __cplusplus
1688} /* extern "C" */
1689#endif
1690
1691/* setup the WebAudio context and attach a ScriptProcessorNode */
1692EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size), {
1693 Module._saudio_context = null;
1694 Module._saudio_node = null;
1695 if (typeof AudioContext !== 'undefined') {
1696 Module._saudio_context = new AudioContext({
1697 sampleRate: sample_rate,
1698 latencyHint: 'interactive',
1699 });
1700 }
1701 else if (typeof webkitAudioContext !== 'undefined') {
1702 Module._saudio_context = new webkitAudioContext({
1703 sampleRate: sample_rate,
1704 latencyHint: 'interactive',
1705 });
1706 }
1707 else {
1708 Module._saudio_context = null;
1709 console.log('sokol_audio.h: no WebAudio support');
1710 }
1711 if (Module._saudio_context) {
1712 console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate);
1713 Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels);
1714 Module._saudio_node.onaudioprocess = function pump_audio(event) {
1715 var num_frames = event.outputBuffer.length;
1716 var ptr = __saudio_emsc_pull(num_frames);
1717 if (ptr) {
1718 var num_channels = event.outputBuffer.numberOfChannels;
1719 for (var chn = 0; chn < num_channels; chn++) {
1720 var chan = event.outputBuffer.getChannelData(chn);
1721 for (var i = 0; i < num_frames; i++) {
1722 chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)]
1723 }
1724 }
1725 }
1726 };
1727 Module._saudio_node.connect(Module._saudio_context.destination);
1728
1729 // in some browsers, WebAudio needs to be activated on a user action
1730 var resume_webaudio = function() {
1731 if (Module._saudio_context) {
1732 if (Module._saudio_context.state === 'suspended') {
1733 Module._saudio_context.resume();
1734 }
1735 }
1736 };
1737 document.addEventListener('click', resume_webaudio, {once:true});
1738 document.addEventListener('touchstart', resume_webaudio, {once:true});
1739 document.addEventListener('keydown', resume_webaudio, {once:true});
1740 return 1;
1741 }
1742 else {
1743 return 0;
1744 }
1745});
1746
1747/* shutdown the WebAudioContext and ScriptProcessorNode */
1748EM_JS(void, saudio_js_shutdown, (void), {
1749 if (Module._saudio_context !== null) {
1750 if (Module._saudio_node) {
1751 Module._saudio_node.disconnect();
1752 }
1753 Module._saudio_context.close();
1754 Module._saudio_context = null;
1755 Module._saudio_node = null;
1756 }
1757});
1758
1759/* get the actual sample rate back from the WebAudio context */
1760EM_JS(int, saudio_js_sample_rate, (void), {
1761 if (Module._saudio_context) {
1762 return Module._saudio_context.sampleRate;
1763 }
1764 else {
1765 return 0;
1766 }
1767});
1768
1769/* get the actual buffer size in number of frames */
1770EM_JS(int, saudio_js_buffer_frames, (void), {
1771 if (Module._saudio_node) {
1772 return Module._saudio_node.bufferSize;
1773 }
1774 else {
1775 return 0;
1776 }
1777});
1778
1779_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1780 if (saudio_js_init(_saudio.sample_rate, _saudio.num_channels, _saudio.buffer_frames)) {
1781 _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels;
1782 _saudio.sample_rate = saudio_js_sample_rate();
1783 _saudio.buffer_frames = saudio_js_buffer_frames();
1784 const size_t buf_size = (size_t) (_saudio.buffer_frames * _saudio.bytes_per_frame);
1785 _saudio.backend.buffer = (uint8_t*) SOKOL_MALLOC(buf_size);
1786 return true;
1787 }
1788 else {
1789 return false;
1790 }
1791}
1792
1793_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1794 saudio_js_shutdown();
1795 if (_saudio.backend.buffer) {
1796 SOKOL_FREE(_saudio.backend.buffer);
1797 _saudio.backend.buffer = 0;
1798 }
1799}
1800
1801/*=== ANDROID BACKEND IMPLEMENTATION ======================================*/
1802#elif defined(_SAUDIO_ANDROID)
1803
1804#ifdef __cplusplus
1805extern "C" {
1806#endif
1807
1808_SOKOL_PRIVATE void _saudio_semaphore_init(_saudio_semaphore_t* sem) {
1809 sem->count = 0;
1810 int r = pthread_mutex_init(&sem->mutex, NULL);
1811 SOKOL_ASSERT(r == 0);
1812
1813 r = pthread_cond_init(&sem->cond, NULL);
1814 SOKOL_ASSERT(r == 0);
1815
1816 (void)(r);
1817}
1818
1819_SOKOL_PRIVATE void _saudio_semaphore_destroy(_saudio_semaphore_t* sem)
1820{
1821 pthread_cond_destroy(&sem->cond);
1822 pthread_mutex_destroy(&sem->mutex);
1823}
1824
1825_SOKOL_PRIVATE void _saudio_semaphore_post(_saudio_semaphore_t* sem, int count)
1826{
1827 int r = pthread_mutex_lock(&sem->mutex);
1828 SOKOL_ASSERT(r == 0);
1829
1830 for (int ii = 0; ii < count; ii++) {
1831 r = pthread_cond_signal(&sem->cond);
1832 SOKOL_ASSERT(r == 0);
1833 }
1834
1835 sem->count += count;
1836 r = pthread_mutex_unlock(&sem->mutex);
1837 SOKOL_ASSERT(r == 0);
1838
1839 (void)(r);
1840}
1841
1842_SOKOL_PRIVATE bool _saudio_semaphore_wait(_saudio_semaphore_t* sem)
1843{
1844 int r = pthread_mutex_lock(&sem->mutex);
1845 SOKOL_ASSERT(r == 0);
1846
1847 while (r == 0 && sem->count <= 0) {
1848 r = pthread_cond_wait(&sem->cond, &sem->mutex);
1849 }
1850
1851 bool ok = (r == 0);
1852 if (ok) {
1853 --sem->count;
1854 }
1855 r = pthread_mutex_unlock(&sem->mutex);
1856 (void)(r);
1857 return ok;
1858}
1859
1860/* fill intermediate buffer with new data and reset buffer_pos */
1861_SOKOL_PRIVATE void _saudio_opensles_fill_buffer(void) {
1862 int src_buffer_frames = _saudio.buffer_frames;
1863 if (_saudio_has_callback()) {
1864 _saudio_stream_callback(_saudio.backend.src_buffer, src_buffer_frames, _saudio.num_channels);
1865 }
1866 else {
1867 const int src_buffer_byte_size = src_buffer_frames * _saudio.num_channels * (int)sizeof(float);
1868 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.src_buffer, src_buffer_byte_size)) {
1869 /* not enough read data available, fill the entire buffer with silence */
1870 memset(_saudio.backend.src_buffer, 0x0, (size_t)src_buffer_byte_size);
1871 }
1872 }
1873}
1874
1875_SOKOL_PRIVATE void SLAPIENTRY _saudio_opensles_play_cb(SLPlayItf player, void *context, SLuint32 event) {
1876 (void)(context);
1877 (void)(player);
1878
1879 if (event & SL_PLAYEVENT_HEADATEND) {
1880 _saudio_semaphore_post(&_saudio.backend.buffer_sem, 1);
1881 }
1882}
1883
1884_SOKOL_PRIVATE void* _saudio_opensles_thread_fn(void* param) {
1885 while (!_saudio.backend.thread_stop) {
1886 /* get next output buffer, advance, next buffer. */
1887 int16_t* out_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
1888 _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS;
1889 int16_t* next_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
1890
1891 /* queue this buffer */
1892 const int buffer_size_bytes = _saudio.buffer_frames * _saudio.num_channels * (int)sizeof(short);
1893 (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, out_buffer, (SLuint32)buffer_size_bytes);
1894
1895 /* fill the next buffer */
1896 _saudio_opensles_fill_buffer();
1897 const int num_samples = _saudio.num_channels * _saudio.buffer_frames;
1898 for (int i = 0; i < num_samples; ++i) {
1899 next_buffer[i] = (int16_t) (_saudio.backend.src_buffer[i] * 0x7FFF);
1900 }
1901
1902 _saudio_semaphore_wait(&_saudio.backend.buffer_sem);
1903 }
1904
1905 return 0;
1906}
1907
1908_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1909 _saudio.backend.thread_stop = 1;
1910 pthread_join(_saudio.backend.thread, 0);
1911
1912 if (_saudio.backend.player_obj) {
1913 (*_saudio.backend.player_obj)->Destroy(_saudio.backend.player_obj);
1914 }
1915
1916 if (_saudio.backend.output_mix_obj) {
1917 (*_saudio.backend.output_mix_obj)->Destroy(_saudio.backend.output_mix_obj);
1918 }
1919
1920 if (_saudio.backend.engine_obj) {
1921 (*_saudio.backend.engine_obj)->Destroy(_saudio.backend.engine_obj);
1922 }
1923
1924 for (int i = 0; i < SAUDIO_NUM_BUFFERS; i++) {
1925 SOKOL_FREE(_saudio.backend.output_buffers[i]);
1926 }
1927 SOKOL_FREE(_saudio.backend.src_buffer);
1928}
1929
1930_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1931 _saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels;
1932
1933 for (int i = 0; i < SAUDIO_NUM_BUFFERS; ++i) {
1934 const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
1935 _saudio.backend.output_buffers[i] = (int16_t*) SOKOL_MALLOC((size_t)buffer_size_bytes);
1936 SOKOL_ASSERT(_saudio.backend.output_buffers[i]);
1937 memset(_saudio.backend.output_buffers[i], 0x0, (size_t)buffer_size_bytes);
1938 }
1939
1940 {
1941 const int buffer_size_bytes = _saudio.bytes_per_frame * _saudio.buffer_frames;
1942 _saudio.backend.src_buffer = (float*) SOKOL_MALLOC((size_t)buffer_size_bytes);
1943 SOKOL_ASSERT(_saudio.backend.src_buffer);
1944 memset(_saudio.backend.src_buffer, 0x0, (size_t)buffer_size_bytes);
1945 }
1946
1947 /* Create engine */
1948 const SLEngineOption opts[] = { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE };
1949 if (slCreateEngine(&_saudio.backend.engine_obj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) {
1950 SOKOL_LOG("sokol_audio opensles: slCreateEngine failed");
1951 _saudio_backend_shutdown();
1952 return false;
1953 }
1954
1955 (*_saudio.backend.engine_obj)->Realize(_saudio.backend.engine_obj, SL_BOOLEAN_FALSE);
1956 if ((*_saudio.backend.engine_obj)->GetInterface(_saudio.backend.engine_obj, SL_IID_ENGINE, &_saudio.backend.engine) != SL_RESULT_SUCCESS) {
1957 SOKOL_LOG("sokol_audio opensles: GetInterface->Engine failed");
1958 _saudio_backend_shutdown();
1959 return false;
1960 }
1961
1962 /* Create output mix. */
1963 {
1964 const SLInterfaceID ids[] = { SL_IID_VOLUME };
1965 const SLboolean req[] = { SL_BOOLEAN_FALSE };
1966
1967 if( (*_saudio.backend.engine)->CreateOutputMix(_saudio.backend.engine, &_saudio.backend.output_mix_obj, 1, ids, req) != SL_RESULT_SUCCESS)
1968 {
1969 SOKOL_LOG("sokol_audio opensles: CreateOutputMix failed");
1970 _saudio_backend_shutdown();
1971 return false;
1972 }
1973 (*_saudio.backend.output_mix_obj)->Realize(_saudio.backend.output_mix_obj, SL_BOOLEAN_FALSE);
1974
1975 if((*_saudio.backend.output_mix_obj)->GetInterface(_saudio.backend.output_mix_obj, SL_IID_VOLUME, &_saudio.backend.output_mix_vol) != SL_RESULT_SUCCESS) {
1976 SOKOL_LOG("sokol_audio opensles: GetInterface->OutputMixVol failed");
1977 }
1978 }
1979
1980 /* android buffer queue */
1981 _saudio.backend.in_locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
1982 _saudio.backend.in_locator.numBuffers = SAUDIO_NUM_BUFFERS;
1983
1984 /* data format */
1985 SLDataFormat_PCM format;
1986 format.formatType = SL_DATAFORMAT_PCM;
1987 format.numChannels = (SLuint32)_saudio.num_channels;
1988 format.samplesPerSec = (SLuint32) (_saudio.sample_rate * 1000);
1989 format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
1990 format.containerSize = 16;
1991 format.endianness = SL_BYTEORDER_LITTLEENDIAN;
1992
1993 if (_saudio.num_channels == 2) {
1994 format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
1995 } else {
1996 format.channelMask = SL_SPEAKER_FRONT_CENTER;
1997 }
1998
1999 SLDataSource src;
2000 src.pLocator = &_saudio.backend.in_locator;
2001 src.pFormat = &format;
2002
2003 /* Output mix. */
2004 _saudio.backend.out_locator.locatorType = SL_DATALOCATOR_OUTPUTMIX;
2005 _saudio.backend.out_locator.outputMix = _saudio.backend.output_mix_obj;
2006
2007 _saudio.backend.dst_data_sink.pLocator = &_saudio.backend.out_locator;
2008 _saudio.backend.dst_data_sink.pFormat = NULL;
2009
2010 /* setup player */
2011 {
2012 const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
2013 const SLboolean req[] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE };
2014
2015 (*_saudio.backend.engine)->CreateAudioPlayer(_saudio.backend.engine, &_saudio.backend.player_obj, &src, &_saudio.backend.dst_data_sink, sizeof(ids) / sizeof(ids[0]), ids, req);
2016
2017 (*_saudio.backend.player_obj)->Realize(_saudio.backend.player_obj, SL_BOOLEAN_FALSE);
2018
2019 (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_PLAY, &_saudio.backend.player);
2020 (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_VOLUME, &_saudio.backend.player_vol);
2021
2022 (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_saudio.backend.player_buffer_queue);
2023 }
2024
2025 /* begin */
2026 {
2027 const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
2028 (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, _saudio.backend.output_buffers[0], (SLuint32)buffer_size_bytes);
2029 _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS;
2030
2031 (*_saudio.backend.player)->RegisterCallback(_saudio.backend.player, _saudio_opensles_play_cb, NULL);
2032 (*_saudio.backend.player)->SetCallbackEventsMask(_saudio.backend.player, SL_PLAYEVENT_HEADATEND);
2033 (*_saudio.backend.player)->SetPlayState(_saudio.backend.player, SL_PLAYSTATE_PLAYING);
2034 }
2035
2036 /* create the buffer-streaming start thread */
2037 if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_opensles_thread_fn, 0)) {
2038 _saudio_backend_shutdown();
2039 return false;
2040 }
2041
2042 return true;
2043}
2044
2045#ifdef __cplusplus
2046} /* extern "C" */
2047#endif
2048
2049#else
2050#error "unsupported platform"
2051#endif
2052
2053/*=== PUBLIC API FUNCTIONS ===================================================*/
2054SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) {
2055 SOKOL_ASSERT(!_saudio.valid);
2056 SOKOL_ASSERT(desc);
2057 memset(&_saudio, 0, sizeof(_saudio));
2058 _saudio.desc = *desc;
2059 _saudio.stream_cb = desc->stream_cb;
2060 _saudio.stream_userdata_cb = desc->stream_userdata_cb;
2061 _saudio.user_data = desc->user_data;
2062 _saudio.sample_rate = _saudio_def(_saudio.desc.sample_rate, _SAUDIO_DEFAULT_SAMPLE_RATE);
2063 _saudio.buffer_frames = _saudio_def(_saudio.desc.buffer_frames, _SAUDIO_DEFAULT_BUFFER_FRAMES);
2064 _saudio.packet_frames = _saudio_def(_saudio.desc.packet_frames, _SAUDIO_DEFAULT_PACKET_FRAMES);
2065 _saudio.num_packets = _saudio_def(_saudio.desc.num_packets, _SAUDIO_DEFAULT_NUM_PACKETS);
2066 _saudio.num_channels = _saudio_def(_saudio.desc.num_channels, 1);
2067 _saudio_fifo_init_mutex(&_saudio.fifo);
2068 if (_saudio_backend_init()) {
2069 /* the backend might not support the requested exact buffer size,
2070 make sure the actual buffer size is still a multiple of
2071 the requested packet size
2072 */
2073 if (0 != (_saudio.buffer_frames % _saudio.packet_frames)) {
2074 SOKOL_LOG("sokol_audio.h: actual backend buffer size isn't multiple of requested packet size");
2075 _saudio_backend_shutdown();
2076 return;
2077 }
2078 SOKOL_ASSERT(_saudio.bytes_per_frame > 0);
2079 _saudio_fifo_init(&_saudio.fifo, _saudio.packet_frames * _saudio.bytes_per_frame, _saudio.num_packets);
2080 _saudio.valid = true;
2081 }
2082}
2083
2084SOKOL_API_IMPL void saudio_shutdown(void) {
2085 if (_saudio.valid) {
2086 _saudio_backend_shutdown();
2087 _saudio_fifo_shutdown(&_saudio.fifo);
2088 _saudio.valid = false;
2089 }
2090}
2091
2092SOKOL_API_IMPL bool saudio_isvalid(void) {
2093 return _saudio.valid;
2094}
2095
2096SOKOL_API_IMPL void* saudio_userdata(void) {
2097 return _saudio.desc.user_data;
2098}
2099
2100SOKOL_API_IMPL saudio_desc saudio_query_desc(void) {
2101 return _saudio.desc;
2102}
2103
2104SOKOL_API_IMPL int saudio_sample_rate(void) {
2105 return _saudio.sample_rate;
2106}
2107
2108SOKOL_API_IMPL int saudio_buffer_frames(void) {
2109 return _saudio.buffer_frames;
2110}
2111
2112SOKOL_API_IMPL int saudio_channels(void) {
2113 return _saudio.num_channels;
2114}
2115
2116SOKOL_API_IMPL int saudio_expect(void) {
2117 if (_saudio.valid) {
2118 const int num_frames = _saudio_fifo_writable_bytes(&_saudio.fifo) / _saudio.bytes_per_frame;
2119 return num_frames;
2120 }
2121 else {
2122 return 0;
2123 }
2124}
2125
2126SOKOL_API_IMPL int saudio_push(const float* frames, int num_frames) {
2127 SOKOL_ASSERT(frames && (num_frames > 0));
2128 if (_saudio.valid) {
2129 const int num_bytes = num_frames * _saudio.bytes_per_frame;
2130 const int num_written = _saudio_fifo_write(&_saudio.fifo, (const uint8_t*)frames, num_bytes);
2131 return num_written / _saudio.bytes_per_frame;
2132 }
2133 else {
2134 return 0;
2135 }
2136}
2137
2138#undef _saudio_def
2139#undef _saudio_def_flt
2140
2141#if defined(_SAUDIO_WINDOWS)
2142#ifdef _MSC_VER
2143#pragma warning(pop)
2144#endif
2145#endif
2146
2147#endif /* SOKOL_AUDIO_IMPL */