1#pragma once
2/*
3 sokol_audio.h -- cross-platform audio-streaming API
4
5 Do this:
6 #define SOKOL_IMPL
7 before you include this file in *one* C or C++ file to create the
8 implementation.
9
10 Optionally provide the following defines with your own implementations:
11
12 SOKOL_AUDIO_NO_BACKEND - use a dummy backend
13 SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
14 SOKOL_LOG(msg) - your own logging function (default: puts(msg))
15 SOKOL_MALLOC(s) - your own malloc() implementation (default: malloc(s))
16 SOKOL_FREE(p) - your own free() implementation (default: free(p))
17 SOKOL_API_DECL - public function declaration prefix (default: extern)
18 SOKOL_API_IMPL - public function implementation prefix (default: -)
19
20 FEATURE OVERVIEW
21 ================
22 You provide a mono- or stereo-stream of 32-bit float samples, which
23 Sokol Audio feeds into platform-specific audio backends:
24
25 - Windows: WASAPI
26 - Linux: ALSA (link with asound)
27 - macOS/iOS: CoreAudio (link with AudioToolbox)
28 - emscripten: WebAudio with ScriptProcessorNode
29
30 Sokol Audio will not do any buffer mixing or volume control, if you have
31 multiple independent input streams of sample data you need to perform the
32 mixing yourself before forwarding the data to Sokol Audio.
33
34 There are two mutually exclusive ways to provide the sample data:
35
36 1. Callback model: You provide a callback function, which will be called
37 when Sokol Audio needs new samples. On all platforms except emscripten,
38 this function is called from a separate thread.
39 2. Push model: Your code pushes small blocks of sample data from your
40 main loop or a thread you created. The pushed data is stored in
41 a ring buffer where it is pulled by the backend code when
42 needed.
43
44 The callback model is preferred because it is the most direct way to
45 feed sample data into the audio backends and also has less moving parts
46 (there is no ring buffer between your code and the audio backend).
47
48 Sometimes it is not possible to generate the audio stream directly in a
49 callback function running in a separate thread, for such cases Sokol Audio
50 provides the push-model as a convenience.
51
52 SOKOL AUDIO AND SOLOUD
53 ======================
54 The WASAPI, ALSA and CoreAudio backend code has been taken from the
55 SoLoud library (with some modifications, so any bugs in there are most
56 likely my fault). If you need a more fully-featured audio solution, check
57 out SoLoud, it's excellent:
58
59 https://github.com/jarikomppa/soloud
60
61 GLOSSARY
62 ========
63 - stream buffer:
64 The internal audio data buffer, usually provided by the backend API. The
65 size of the stream buffer defines the base latency, smaller buffers have
66 lower latency but may cause audio glitches. Bigger buffers reduce or
67 eliminate glitches, but have a higher base latency.
68
69 - stream callback:
70 Optional callback function which is called by Sokol Audio when it
71 needs new samples. On Windows, macOS/iOS and Linux, this is called in
72 a separate thread, on WebAudio, this is called per-frame in the
73 browser thread.
74
75 - channel:
76 A discrete track of audio data, currently 1-channel (mono) and
77 2-channel (stereo) is supported and tested.
78
79 - sample:
80 The magnitude of an audio signal on one channel at a given time. In
81 Sokol Audio, samples are 32-bit float numbers in the range -1.0 to
82 +1.0.
83
84 - frame:
85 The tightly packed set of samples for all channels at a given time.
86 For mono 1 frame is 1 sample. For stereo, 1 frame is 2 samples.
87
88 - packet:
89 In Sokol Audio, a small chunk of audio data that is moved from the
90 main thread to the audio streaming thread in order to decouple the
91 rate at which the main thread provides new audio data, and the
92 streaming thread consuming audio data.
93
94 WORKING WITH SOKOL AUDIO
95 ========================
96 First call saudio_setup() with your preferred audio playback options.
97 In most cases you can stick with the default values, these provide
98 a good balance between low-latency and glitch-free playback
99 on all audio backends.
100
101 If you want to use the callback-model, you need to provide a stream
102 callback function in stream_cb, otherwise keep the stream_cb member
103 initialized to zero.
104
105 Use push model and default playback parameters:
106
107 saudio_setup(&(saudio_desc){0});
108
109 Use stream callback model and default playback parameters:
110
111 saudio_setup(&(saudio_desc){
112 .stream_cb = my_stream_callback
113 });
114
115 The following playback parameters can be provided through the
116 saudio_desc struct:
117
118 General parameters (both for stream-callback and push-model):
119
120 int sample_rate -- the sample rate in Hz, default: 44100
121 int num_channels -- number of channels, default: 1 (mono)
122 int buffer_frames -- number of frames in streaming buffer, default: 2048
123
124 Stream callback parameters:
125
126 void (*stream_cb)(float* buffer, int num_frames, int num_channels)
127 Function pointer to the user-provide stream callback.
128
129 Push-model parameters:
130
131 int packet_frames -- number of frames in a packet, default: 128
132 int num_packets -- number of packets in ring buffer, default: 64
133
134 The sample_rate and num_channels parameters are only hints for the audio
135 backend, it isn't guaranteed that those are the values used for actual
136 playback.
137
138 To get the actual parameters, call the following functions after
139 saudio_setup():
140
141 int saudio_sample_rate(void)
142 int saudio_channels(void);
143
144 It's unlikely that the number of channels will be different than requested,
145 but a different sample rate isn't uncommon.
146
147 (NOTE: there's an yet unsolved issue when an audio backend might switch
148 to a different sample rate when switching output devices, for instance
149 plugging in a bluetooth headset, this case is currently not handled in
150 Sokol Audio).
151
152 You can check if audio initialization was successfull with
153 saudio_isvalid(). If backend initialization failed for some reason
154 (for instance when there's no audio device in the machine), this
155 will return false. Not checking for success won't do any harm, all
156 Sokol Audio function will silently fail when called after initialization
157 has failed, so apart from missing audio output, nothing bad will happen.
158
159 Before your application exits, you should call
160
161 saudio_shutdown();
162
163 This stops the audio thread (on Linux, Windows and macOS/iOS) and
164 properly shuts down the audio backend.
165
166 THE STREAM CALLBACK MODEL
167 =========================
168 To use Sokol Audio in stream-callback-mode, provide a callback function
169 like this in the saudio_desc struct when calling saudio_setup():
170
171 void stream_cb(float* buffer, int num_frames, int num_channels) { ... }
172
173 The job of the callback function is to fill the *buffer* with 32-bit
174 float sample values.
175
176 To output silence, fill the buffer with zeros:
177
178 void stream_cb(float* buffer, int num_frames, int num_channels) {
179 const int num_samples = num_frames * num_channels;
180 for (int i = 0; i < num_samples; i++) {
181 buffer[i] = 0.0f;
182 }
183 }
184
185 For stereo output (num_channels == 2), the samples for the left
186 and right channel are interleaved:
187
188 void stream_cb(float* buffer, int num_frames, int num_channels) {
189 assert(2 == num_channels);
190 for (int i = 0; i < num_frames; i++) {
191 buffer[2*i + 0] = ...; // left channel
192 buffer[2*i + 1] = ...; // right channel
193 }
194 }
195
196 Please keep in mind that the stream callback function is running in a
197 separate thread, if you need to share data with the main thread you need
198 to take care yourself to make the access to the shared data thread-safe!
199
200 THE PUSH MODEL
201 ==============
202 To use the push-model for providing audio data, simply don't set (keep
203 zero-initialized) the stream_cb field in the saudio_desc struct when
204 calling saudio_setup().
205
206 To provide sample data with the push model, call the saudio_push()
207 function at regular intervals (for instance once per frame). You can
208 call the saudio_expect() function to ask Sokol Audio how much room is
209 in the ring buffer, but if you provide a continuous stream of data
210 at the right sample rate, saudio_expect() isn't required (it's a simple
211 way to sync/throttle your sample generation code with the playback
212 rate though).
213
214 With saudio_push() you may need to maintain your own intermediate sample
215 buffer, since pushing individual sample values isn't very efficient.
216 The following example is from the MOD player sample in
217 sokol-samples (https://github.com/floooh/sokol-samples):
218
219 const int num_frames = saudio_expect();
220 if (num_frames > 0) {
221 const int num_samples = num_frames * saudio_channels();
222 read_samples(flt_buf, num_samples);
223 saudio_push(flt_buf, num_frames);
224 }
225
226 Another option is to ignore saudio_expect(), and just push samples as they
227 are generated in small batches. In this case you *need* to generate the
228 samples at the right sample rate:
229
230 The following example is taken from the Tiny Emulators project
231 (https://github.com/floooh/chips-test), this is for mono playback,
232 so (num_samples == num_frames):
233
234 // tick the sound generator
235 if (ay38910_tick(&sys->psg)) {
236 // new sample is ready
237 sys->sample_buffer[sys->sample_pos++] = sys->psg.sample;
238 if (sys->sample_pos == sys->num_samples) {
239 // new sample packet is ready
240 saudio_push(sys->sample_buffer, sys->num_samples);
241 sys->sample_pos = 0;
242 }
243 }
244
245 THE WEBAUDIO BACKEND
246 ====================
247 The WebAudio backend is currently using a ScriptProcessorNode callback to
248 feed the sample data into WebAudio. ScriptProcessorNode has been
249 deprecated for a while because it is running from the main thread, with
250 the default initialization parameters it works 'pretty well' though.
251 Ultimately Sokol Audio will use Audio Worklets, but this requires a few
252 more things to fall into place (Audio Worklets implemented everywhere,
253 SharedArrayBuffers enabled again, and I need to figure out a 'low-cost'
254 solution in terms of implementation effort, since Audio Worklets are
255 a lot more complex than ScriptProcessorNode if the audio data needs to come
256 from the main thread).
257
258 The WebAudio backend is automatically selected when compiling for
259 emscripten (__EMSCRIPTEN__ define exists).
260
261 https://developers.google.com/web/updates/2017/12/audio-worklet
262 https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern
263
264 "Blob URLs": https://www.html5rocks.com/en/tutorials/workers/basics/
265
266 THE COREAUDIO BACKEND
267 =====================
268 The CoreAudio backend is selected on macOS and iOS (__APPLE__ is defined).
269 Since the CoreAudio API is implemented in C (not Objective-C) the
270 implementation part of Sokol Audio can be included into a C source file.
271
272 For thread synchronisation, the CoreAudio backend will use the
273 pthread_mutex_* functions.
274
275 The incoming floating point samples will be directly forwarded to
276 CoreAudio without further conversion.
277
278 macOS and iOS applications that use Sokol Audio need to link with
279 the AudioToolbox framework.
280
281 THE WASAPI BACKEND
282 ==================
283 The WASAPI backend is automatically selected when compiling on Windows
284 (_WIN32 is defined).
285
286 For thread synchronisation a Win32 critical section is used.
287
288 WASAPI may use a different size for its own streaming buffer then requested,
289 so the base latency may be slightly bigger. The current backend implementation
290 convertes the incoming floating point sample values to signed 16-bit
291 integers.
292
293 The required Windows system DLLs are linked with #pragma comment(lib, ...),
294 so you shouldn't need to add additional linker libs in the build process
295 (otherwise this is a bug which should be fixed in sokol_audio.h).
296
297 THE ALSA BACKEND
298 ================
299 The ALSA backend is automatically selected when compiling on Linux
300 ('linux' is defined).
301
302 For thread synchronisation, the pthread_mutex_* functions are used.
303
304 Samples are directly forwarded to ALSA in 32-bit float format, no
305 further conversion is taking place.
306
307 You need to link with the 'asound' library, and the <alsa/asoundlib.h>
308 header must be present (usually both are installed with some sort
309 of ALSA development package).
310
311 LICENSE
312 =======
313
314 zlib/libpng license
315
316 Copyright (c) 2018 Andre Weissflog
317
318 This software is provided 'as-is', without any express or implied warranty.
319 In no event will the authors be held liable for any damages arising from the
320 use of this software.
321
322 Permission is granted to anyone to use this software for any purpose,
323 including commercial applications, and to alter it and redistribute it
324 freely, subject to the following restrictions:
325
326 1. The origin of this software must not be misrepresented; you must not
327 claim that you wrote the original software. If you use this software in a
328 product, an acknowledgment in the product documentation would be
329 appreciated but is not required.
330
331 2. Altered source versions must be plainly marked as such, and must not
332 be misrepresented as being the original software.
333
334 3. This notice may not be removed or altered from any source
335 distribution.
336*/
337#include <stdint.h>
338#include <stdbool.h>
339
340#ifndef SOKOL_API_DECL
341 #define SOKOL_API_DECL extern
342#endif
343
344#ifdef __cplusplus
345extern "C" {
346#endif
347
348typedef struct {
349 int sample_rate; /* requested sample rate */
350 int num_channels; /* number of channels, default: 1 (mono) */
351 int buffer_frames; /* number of frames in streaming buffer */
352 int packet_frames; /* number of frames in a packet */
353 int num_packets; /* number of packets in packet queue */
354 void (*stream_cb)(float* buffer, int num_frames, int num_channels); /* optional streaming callback */
355} saudio_desc;
356
357/* setup sokol-audio */
358SOKOL_API_DECL void saudio_setup(const saudio_desc* desc);
359/* shutdown sokol-audio */
360SOKOL_API_DECL void saudio_shutdown(void);
361/* true after setup if audio backend was successfully initialized */
362SOKOL_API_DECL bool saudio_isvalid(void);
363/* actual sample rate */
364SOKOL_API_DECL int saudio_sample_rate(void);
365/* actual backend buffer size */
366SOKOL_API_DECL int saudio_buffer_size(void);
367/* actual number of channels */
368SOKOL_API_DECL int saudio_channels(void);
369/* get current number of frames to fill packet queue */
370SOKOL_API_DECL int saudio_expect(void);
371/* push sample frames from main thread, returns number of frames actually pushed */
372SOKOL_API_DECL int saudio_push(const float* frames, int num_frames);
373
374#ifdef __cplusplus
375} /* extern "C" */
376#endif
377
378/*--- IMPLEMENTATION ---------------------------------------------------------*/
379#ifdef SOKOL_IMPL
380#include <string.h> /* memset, memcpy */
381
382#ifdef _MSC_VER
383#pragma warning(push)
384#pragma warning(disable:4505) /* unreferenced local function has been removed */
385#endif
386
387#ifndef SOKOL_API_IMPL
388 #define SOKOL_API_IMPL
389#endif
390#ifndef SOKOL_DEBUG
391 #ifdef _DEBUG
392 #define SOKOL_DEBUG (1)
393 #endif
394#endif
395#ifndef SOKOL_ASSERT
396 #include <assert.h>
397 #define SOKOL_ASSERT(c) assert(c)
398#endif
399#ifndef SOKOL_MALLOC
400 #include <stdlib.h>
401 #define SOKOL_MALLOC(s) malloc(s)
402 #define SOKOL_FREE(p) free(p)
403#endif
404#ifndef SOKOL_LOG
405 #ifdef SOKOL_DEBUG
406 #include <stdio.h>
407 #define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
408 #else
409 #define SOKOL_LOG(s)
410 #endif
411#endif
412
413#ifndef _SOKOL_PRIVATE
414 #if defined(__GNUC__)
415 #define _SOKOL_PRIVATE __attribute__((unused)) static
416 #else
417 #define _SOKOL_PRIVATE static
418 #endif
419#endif
420
421#define _saudio_def(val, def) (((val) == 0) ? (def) : (val))
422#define _saudio_def_flt(val, def) (((val) == 0.0f) ? (def) : (val))
423
424/*--- implementation-private constants ---------------------------------------*/
425#define _SAUDIO_DEFAULT_SAMPLE_RATE (44100)
426#define _SAUDIO_DEFAULT_BUFFER_FRAMES (2048)
427#define _SAUDIO_DEFAULT_PACKET_FRAMES (128)
428#define _SAUDIO_DEFAULT_NUM_PACKETS ((_SAUDIO_DEFAULT_BUFFER_FRAMES/_SAUDIO_DEFAULT_PACKET_FRAMES)*4)
429#define _SAUDIO_RING_MAX_SLOTS (128)
430
431/*--- mutex wrappers ---------------------------------------------------------*/
432#if defined(__APPLE__) || defined(linux)
433#include "pthread.h"
434static pthread_mutex_t _saudio_mutex;
435
436_SOKOL_PRIVATE void _saudio_mutex_init(void) {
437 pthread_mutexattr_t attr;
438 pthread_mutexattr_init(&attr);
439 pthread_mutex_init(&_saudio_mutex, &attr);
440}
441
442_SOKOL_PRIVATE void _saudio_mutex_destroy(void) {
443 pthread_mutex_destroy(&_saudio_mutex);
444}
445
446_SOKOL_PRIVATE void _saudio_mutex_lock(void) {
447 pthread_mutex_lock(&_saudio_mutex);
448}
449
450_SOKOL_PRIVATE void _saudio_mutex_unlock(void) {
451 pthread_mutex_unlock(&_saudio_mutex);
452}
453#elif defined(_WIN32)
454#ifndef WIN32_LEAN_AND_MEAN
455#define WIN32_LEAN_AND_MEAN
456#endif
457#include <windows.h>
458#include <synchapi.h>
459#pragma comment (lib, "kernel32.lib")
460#pragma comment (lib, "ole32.lib")
461
462CRITICAL_SECTION _saudio_crit;
463
464_SOKOL_PRIVATE void _saudio_mutex_init(void) {
465 InitializeCriticalSection(&_saudio_crit);
466}
467
468_SOKOL_PRIVATE void _saudio_mutex_destroy(void) {
469 DeleteCriticalSection(&_saudio_crit);
470}
471
472_SOKOL_PRIVATE void _saudio_mutex_lock(void) {
473 EnterCriticalSection(&_saudio_crit);
474}
475
476_SOKOL_PRIVATE void _saudio_mutex_unlock(void) {
477 LeaveCriticalSection(&_saudio_crit);
478}
479#else
480_SOKOL_PRIVATE void _saudio_mutex_init(void) { }
481_SOKOL_PRIVATE void _saudio_mutex_destroy(void) { }
482_SOKOL_PRIVATE void _saudio_mutex_lock(void) { }
483_SOKOL_PRIVATE void _saudio_mutex_unlock(void) { }
484#endif
485
486/*--- a ring-buffer queue implementation -------------------------------------*/
487typedef struct {
488 int head; /* next slot to write to */
489 int tail; /* next slot to read from */
490 int num; /* number of slots in queue */
491 int queue[_SAUDIO_RING_MAX_SLOTS];
492} _saudio_ring;
493
494_SOKOL_PRIVATE uint16_t _saudio_ring_idx(_saudio_ring* ring, int i) {
495 return (uint16_t) (i % ring->num);
496}
497
498_SOKOL_PRIVATE void _saudio_ring_init(_saudio_ring* ring, int num_slots) {
499 SOKOL_ASSERT((num_slots + 1) <= _SAUDIO_RING_MAX_SLOTS);
500 ring->head = 0;
501 ring->tail = 0;
502 /* one slot reserved to detect 'full' vs 'empty' */
503 ring->num = num_slots + 1;
504 memset(ring->queue, 0, sizeof(ring->queue));
505}
506
507_SOKOL_PRIVATE bool _saudio_ring_full(_saudio_ring* ring) {
508 return _saudio_ring_idx(ring, ring->head + 1) == ring->tail;
509}
510
511_SOKOL_PRIVATE bool _saudio_ring_empty(_saudio_ring* ring) {
512 return ring->head == ring->tail;
513}
514
515_SOKOL_PRIVATE int _saudio_ring_count(_saudio_ring* ring) {
516 int count;
517 if (ring->head >= ring->tail) {
518 count = ring->head - ring->tail;
519 }
520 else {
521 count = (ring->head + ring->num) - ring->tail;
522 }
523 SOKOL_ASSERT((count >= 0) && (count < ring->num));
524 return count;
525}
526
527_SOKOL_PRIVATE void _saudio_ring_enqueue(_saudio_ring* ring, int val) {
528 SOKOL_ASSERT(!_saudio_ring_full(ring));
529 ring->queue[ring->head] = val;
530 ring->head = _saudio_ring_idx(ring, ring->head + 1);
531}
532
533_SOKOL_PRIVATE int _saudio_ring_dequeue(_saudio_ring* ring) {
534 SOKOL_ASSERT(!_saudio_ring_empty(ring));
535 int val = ring->queue[ring->tail];
536 ring->tail = _saudio_ring_idx(ring, ring->tail + 1);
537 return val;
538}
539
540/*--- a packet fifo for queueing audio data from main thread ----------------*/
541typedef struct {
542 bool valid;
543 int packet_size; /* size of a single packets in bytes(!) */
544 int num_packets; /* number of packet in fifo */
545 uint8_t* base_ptr; /* packet memory chunk base pointer (dynamically allocated) */
546 int cur_packet; /* current write-packet */
547 int cur_offset; /* current byte-offset into current write packet */
548 _saudio_ring read_queue; /* buffers with data, ready to be streamed */
549 _saudio_ring write_queue; /* empty buffers, ready to be pushed to */
550} _saudio_fifo;
551
552_SOKOL_PRIVATE void _saudio_fifo_init(_saudio_fifo* fifo, int packet_size, int num_packets) {
553 /* NOTE: there's a chicken-egg situation during the init phase where the
554 streaming thread must be started before the fifo is actually initialized,
555 thus the fifo init must already be protected from access by the fifo_read() func.
556 */
557 _saudio_mutex_lock();
558 SOKOL_ASSERT((packet_size > 0) && (num_packets > 0));
559 memset(fifo, 0, sizeof(_saudio_fifo));
560 fifo->packet_size = packet_size;
561 fifo->num_packets = num_packets;
562 fifo->base_ptr = (uint8_t*) SOKOL_MALLOC(packet_size * num_packets);
563 SOKOL_ASSERT(fifo->base_ptr);
564 fifo->cur_packet = -1;
565 fifo->cur_offset = 0;
566 _saudio_ring_init(&fifo->read_queue, num_packets);
567 _saudio_ring_init(&fifo->write_queue, num_packets);
568 for (int i = 0; i < num_packets; i++) {
569 _saudio_ring_enqueue(&fifo->write_queue, i);
570 }
571 SOKOL_ASSERT(_saudio_ring_full(&fifo->write_queue));
572 SOKOL_ASSERT(_saudio_ring_count(&fifo->write_queue) == num_packets);
573 SOKOL_ASSERT(_saudio_ring_empty(&fifo->read_queue));
574 SOKOL_ASSERT(_saudio_ring_count(&fifo->read_queue) == 0);
575 fifo->valid = true;
576 _saudio_mutex_unlock();
577}
578
579_SOKOL_PRIVATE void _saudio_fifo_shutdown(_saudio_fifo* fifo) {
580 SOKOL_ASSERT(fifo->base_ptr);
581 SOKOL_FREE(fifo->base_ptr);
582 fifo->base_ptr = 0;
583 fifo->valid = false;
584}
585
586_SOKOL_PRIVATE int _saudio_fifo_writable_bytes(_saudio_fifo* fifo) {
587 _saudio_mutex_lock();
588 int num_bytes = (_saudio_ring_count(&fifo->write_queue) * fifo->packet_size);
589 if (fifo->cur_packet != -1) {
590 num_bytes += fifo->packet_size - fifo->cur_offset;
591 }
592 _saudio_mutex_unlock();
593 SOKOL_ASSERT((num_bytes >= 0) && (num_bytes <= (fifo->num_packets * fifo->packet_size)));
594 return num_bytes;
595}
596
597/* write new data to the write queue, this is called from main thread */
598_SOKOL_PRIVATE int _saudio_fifo_write(_saudio_fifo* fifo, const uint8_t* ptr, int num_bytes) {
599 /* returns the number of bytes written, this will be smaller then requested
600 if the write queue runs full
601 */
602 int all_to_copy = num_bytes;
603 while (all_to_copy > 0) {
604 /* need to grab a new packet? */
605 if (fifo->cur_packet == -1) {
606 _saudio_mutex_lock();
607 if (!_saudio_ring_empty(&fifo->write_queue)) {
608 fifo->cur_packet = _saudio_ring_dequeue(&fifo->write_queue);
609 }
610 _saudio_mutex_unlock();
611 SOKOL_ASSERT(fifo->cur_offset == 0);
612 }
613 /* append data to current write packet */
614 if (fifo->cur_packet != -1) {
615 int to_copy = all_to_copy;
616 const int max_copy = fifo->packet_size - fifo->cur_offset;
617 if (to_copy > max_copy) {
618 to_copy = max_copy;
619 }
620 uint8_t* dst = fifo->base_ptr + fifo->cur_packet * fifo->packet_size + fifo->cur_offset;
621 memcpy(dst, ptr, to_copy);
622 ptr += to_copy;
623 fifo->cur_offset += to_copy;
624 all_to_copy -= to_copy;
625 SOKOL_ASSERT(fifo->cur_offset <= fifo->packet_size);
626 SOKOL_ASSERT(all_to_copy >= 0);
627 }
628 else {
629 /* early out if we're starving */
630 int bytes_copied = num_bytes - all_to_copy;
631 SOKOL_ASSERT((bytes_copied >= 0) && (bytes_copied < num_bytes));
632 return bytes_copied;
633 }
634 /* if write packet is full, push to read queue */
635 if (fifo->cur_offset == fifo->packet_size) {
636 _saudio_mutex_lock();
637 _saudio_ring_enqueue(&fifo->read_queue, fifo->cur_packet);
638 _saudio_mutex_unlock();
639 fifo->cur_packet = -1;
640 fifo->cur_offset = 0;
641 }
642 }
643 SOKOL_ASSERT(all_to_copy == 0);
644 return num_bytes;
645}
646
647/* read queued data, this is called form the stream callback (maybe separate thread) */
648_SOKOL_PRIVATE int _saudio_fifo_read(_saudio_fifo* fifo, uint8_t* ptr, int num_bytes) {
649 /* NOTE: fifo_read might be called before the fifo is properly initialized */
650 _saudio_mutex_lock();
651 int num_bytes_copied = 0;
652 if (fifo->valid) {
653 SOKOL_ASSERT(0 == (num_bytes % fifo->packet_size));
654 SOKOL_ASSERT(num_bytes <= (fifo->packet_size * fifo->num_packets));
655 const int num_packets_needed = num_bytes / fifo->packet_size;
656 uint8_t* dst = ptr;
657 /* either pull a full buffer worth of data, or nothing */
658 if (_saudio_ring_count(&fifo->read_queue) >= num_packets_needed) {
659 for (int i = 0; i < num_packets_needed; i++) {
660 int packet_index = _saudio_ring_dequeue(&fifo->read_queue);
661 _saudio_ring_enqueue(&fifo->write_queue, packet_index);
662 const uint8_t* src = fifo->base_ptr + packet_index * fifo->packet_size;
663 memcpy(dst, src, fifo->packet_size);
664 dst += fifo->packet_size;
665 num_bytes_copied += fifo->packet_size;
666 }
667 SOKOL_ASSERT(num_bytes == num_bytes_copied);
668 }
669 }
670 _saudio_mutex_unlock();
671 return num_bytes_copied;
672}
673
674/* sokol-audio state */
675typedef struct {
676 bool valid;
677 void (*stream_cb)(float* buffer, int num_frames, int num_channels);
678 int sample_rate; /* sample rate */
679 int buffer_frames; /* number of frames in streaming buffer */
680 int bytes_per_frame; /* filled by backend */
681 int packet_frames; /* number of frames in a packet */
682 int num_packets; /* number of packets in packet queue */
683 int num_channels; /* actual number of channels */
684 saudio_desc desc;
685 _saudio_fifo fifo;
686} _saudio_state;
687static _saudio_state _saudio;
688
689/*=== DUMMY BACKEND ==========================================================*/
690#if defined(SOKOL_AUDIO_NO_BACKEND)
691_SOKOL_PRIVATE bool _saudio_backend_init(void) { return false; };
692_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { };
693
694/*=== COREAUDIO BACKEND ======================================================*/
695#elif defined(__APPLE__)
696#include <AudioToolbox/AudioToolbox.h>
697
698static AudioQueueRef _saudio_ca_audio_queue;
699
700/* NOTE: the buffer data callback is called on a separate thread! */
701_SOKOL_PRIVATE void _sapp_ca_callback(void* user_data, AudioQueueRef queue, AudioQueueBufferRef buffer) {
702 if (_saudio.stream_cb) {
703 const int num_frames = buffer->mAudioDataByteSize / _saudio.bytes_per_frame;
704 const int num_channels = _saudio.num_channels;
705 _saudio.stream_cb((float*)buffer->mAudioData, num_frames, num_channels);
706 }
707 else {
708 uint8_t* ptr = (uint8_t*)buffer->mAudioData;
709 int num_bytes = (int) buffer->mAudioDataByteSize;
710 if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) {
711 /* not enough read data available, fill the entire buffer with silence */
712 memset(ptr, 0, num_bytes);
713 }
714 }
715 AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
716}
717
718_SOKOL_PRIVATE bool _saudio_backend_init(void) {
719 SOKOL_ASSERT(0 == _saudio_ca_audio_queue);
720
721 /* create an audio queue with fp32 samples */
722 AudioStreamBasicDescription fmt;
723 memset(&fmt, 0, sizeof(fmt));
724 fmt.mSampleRate = (Float64) _saudio.sample_rate;
725 fmt.mFormatID = kAudioFormatLinearPCM;
726 fmt.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagIsPacked;
727 fmt.mFramesPerPacket = 1;
728 fmt.mChannelsPerFrame = _saudio.num_channels;
729 fmt.mBytesPerFrame = sizeof(float) * _saudio.num_channels;
730 fmt.mBytesPerPacket = fmt.mBytesPerFrame;
731 fmt.mBitsPerChannel = 32;
732 OSStatus res = AudioQueueNewOutput(&fmt, _sapp_ca_callback, 0, NULL, NULL, 0, &_saudio_ca_audio_queue);
733 SOKOL_ASSERT((res == 0) && _saudio_ca_audio_queue);
734
735 /* create 2 audio buffers */
736 for (int i = 0; i < 2; i++) {
737 AudioQueueBufferRef buf = NULL;
738 const uint32_t buf_byte_size = _saudio.buffer_frames * fmt.mBytesPerFrame;
739 res = AudioQueueAllocateBuffer(_saudio_ca_audio_queue, buf_byte_size, &buf);
740 SOKOL_ASSERT((res == 0) && buf);
741 buf->mAudioDataByteSize = buf_byte_size;
742 memset(buf->mAudioData, 0, buf->mAudioDataByteSize);
743 AudioQueueEnqueueBuffer(_saudio_ca_audio_queue, buf, 0, NULL);
744 }
745
746 /* init or modify actual playback parameters */
747 _saudio.bytes_per_frame = fmt.mBytesPerFrame;
748
749 /* ...and start playback */
750 res = AudioQueueStart(_saudio_ca_audio_queue, NULL);
751 SOKOL_ASSERT(0 == res);
752
753 return true;
754}
755
756_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
757 AudioQueueStop(_saudio_ca_audio_queue, true);
758 AudioQueueDispose(_saudio_ca_audio_queue, false);
759 _saudio_ca_audio_queue = NULL;
760}
761
762/*=== ALSA BACKEND ===========================================================*/
763#elif defined(linux)
764#define ALSA_PCM_NEW_HW_PARAMS_API
765#include <alsa/asoundlib.h>
766
767typedef struct {
768 snd_pcm_t* device;
769 float* buffer;
770 int buffer_byte_size;
771 int buffer_frames;
772 pthread_t thread;
773 bool thread_stop;
774} _saudio_alsa_state;
775static _saudio_alsa_state _saudio_alsa;
776
777/* the streaming callback runs in a separate thread */
778_SOKOL_PRIVATE void* _saudio_alsa_cb(void* param) {
779 while (!_saudio_alsa.thread_stop) {
780 /* snd_pcm_writei() will be blocking until it needs data */
781 int write_res = snd_pcm_writei(_saudio_alsa.device, _saudio_alsa.buffer, _saudio_alsa.buffer_frames);
782 if (write_res < 0) {
783 /* underrun occurred */
784 snd_pcm_prepare(_saudio_alsa.device);
785 }
786 else {
787 /* fill the streaming buffer with new data */
788 if (_saudio.stream_cb) {
789 _saudio.stream_cb(_saudio_alsa.buffer, _saudio_alsa.buffer_frames, _saudio.num_channels);
790 }
791 else {
792 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio_alsa.buffer, _saudio_alsa.buffer_byte_size)) {
793 /* not enough read data available, fill the entire buffer with silence */
794 memset(_saudio_alsa.buffer, 0, _saudio_alsa.buffer_byte_size);
795 }
796 }
797 }
798 }
799 return 0;
800}
801
802_SOKOL_PRIVATE bool _saudio_backend_init(void) {
803 memset(&_saudio_alsa, 0, sizeof(_saudio_alsa));
804 int rc = snd_pcm_open(&_saudio_alsa.device, "default", SND_PCM_STREAM_PLAYBACK, 0);
805 if (rc < 0) {
806 return false;
807 }
808 snd_pcm_hw_params_t* params = 0;
809 snd_pcm_hw_params_alloca(&params);
810 snd_pcm_hw_params_any(_saudio_alsa.device, params);
811 snd_pcm_hw_params_set_access(_saudio_alsa.device, params, SND_PCM_ACCESS_RW_INTERLEAVED);
812 snd_pcm_hw_params_set_channels(_saudio_alsa.device, params, _saudio.num_channels);
813 snd_pcm_hw_params_set_buffer_size(_saudio_alsa.device, params, _saudio.buffer_frames);
814 if (0 > snd_pcm_hw_params_test_format(_saudio_alsa.device, params, SND_PCM_FORMAT_FLOAT_LE)) {
815 goto error;
816 }
817 else {
818 snd_pcm_hw_params_set_format(_saudio_alsa.device, params, SND_PCM_FORMAT_FLOAT_LE);
819 }
820 unsigned int val = _saudio.sample_rate;
821 int dir = 0;
822 if (0 > snd_pcm_hw_params_set_rate_near(_saudio_alsa.device, params, &val, &dir)) {
823 goto error;
824 }
825 if (0 > snd_pcm_hw_params(_saudio_alsa.device, params)) {
826 goto error;
827 }
828
829 /* read back actual sample rate and channels */
830 snd_pcm_hw_params_get_rate(params, &val, &dir);
831 _saudio.sample_rate = val;
832 snd_pcm_hw_params_get_channels(params, &val);
833 SOKOL_ASSERT((int)val == _saudio.num_channels);
834 _saudio.bytes_per_frame = _saudio.num_channels * sizeof(float);
835
836 /* allocate the streaming buffer */
837 _saudio_alsa.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame;
838 _saudio_alsa.buffer_frames = _saudio.buffer_frames;
839 _saudio_alsa.buffer = (float*) SOKOL_MALLOC(_saudio_alsa.buffer_byte_size);
840 memset(_saudio_alsa.buffer, 0, _saudio_alsa.buffer_byte_size);
841
842 /* create the buffer-streaming start thread */
843 if (0 != pthread_create(&_saudio_alsa.thread, 0, _saudio_alsa_cb, 0)) {
844 goto error;
845 }
846
847 return true;
848error:
849 if (_saudio_alsa.device) {
850 snd_pcm_close(_saudio_alsa.device);
851 _saudio_alsa.device = 0;
852 }
853 return false;
854};
855
856_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
857 SOKOL_ASSERT(_saudio_alsa.device);
858 _saudio_alsa.thread_stop = true;
859 pthread_join(_saudio_alsa.thread, 0);
860 snd_pcm_drain(_saudio_alsa.device);
861 snd_pcm_close(_saudio_alsa.device);
862 SOKOL_FREE(_saudio_alsa.buffer);
863};
864
865/*=== WASAPI BACKEND =========================================================*/
866#elif defined(_WIN32)
867#ifndef WIN32_LEAN_AND_MEAN
868#define WIN32_LEAN_AND_MEAN
869#endif
870#ifndef CINTERFACE
871#define CINTERFACE
872#endif
873#ifndef COBJMACROS
874#define COBJMACROS
875#endif
876#ifndef CONST_VTABLE
877#define CONST_VTABLE
878#endif
879#include <mmdeviceapi.h>
880#include <audioclient.h>
881
882static const IID _saudio_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
883static const IID _saudio_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, { 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } };
884static const CLSID _saudio_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, { 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } };
885static const IID _saudio_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,{ 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
886
887/* fix for Visual Studio 2015 SDKs */
888#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
889#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
890#endif
891#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
892#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
893#endif
894
895typedef struct {
896 HANDLE thread_handle;
897 HANDLE buffer_end_event;
898 bool stop;
899 UINT32 dst_buffer_frames;
900 int src_buffer_frames;
901 int src_buffer_byte_size;
902 int src_buffer_pos;
903 float* src_buffer;
904} _saudio_wasapi_thread_data;
905
906typedef struct {
907 IMMDeviceEnumerator* device_enumerator;
908 IMMDevice* device;
909 IAudioClient* audio_client;
910 IAudioRenderClient* render_client;
911 int si16_bytes_per_frame;
912 _saudio_wasapi_thread_data thread;
913} _saudio_wasapi_state;
914static _saudio_wasapi_state _saudio_wasapi;
915
916/* fill intermediate buffer with new data and reset buffer_pos */
917_SOKOL_PRIVATE void _saudio_wasapi_fill_buffer(void) {
918 if (_saudio.stream_cb) {
919 _saudio.stream_cb(_saudio_wasapi.thread.src_buffer, _saudio_wasapi.thread.src_buffer_frames, _saudio.num_channels);
920 }
921 else {
922 if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio_wasapi.thread.src_buffer, _saudio_wasapi.thread.src_buffer_byte_size)) {
923 /* not enough read data available, fill the entire buffer with silence */
924 memset(_saudio_wasapi.thread.src_buffer, 0, _saudio_wasapi.thread.src_buffer_byte_size);
925 }
926 }
927}
928
929_SOKOL_PRIVATE void _saudio_wasapi_submit_buffer(UINT32 num_frames) {
930 BYTE* wasapi_buffer = 0;
931 if (FAILED(IAudioRenderClient_GetBuffer(_saudio_wasapi.render_client, num_frames, &wasapi_buffer))) {
932 return;
933 }
934 SOKOL_ASSERT(wasapi_buffer);
935
936 /* convert float samples to int16_t, refill float buffer if needed */
937 const int num_samples = num_frames * _saudio.num_channels;
938 int16_t* dst = (int16_t*) wasapi_buffer;
939 uint32_t buffer_pos = _saudio_wasapi.thread.src_buffer_pos;
940 const uint32_t buffer_float_size = _saudio_wasapi.thread.src_buffer_byte_size / sizeof(float);
941 float* src = _saudio_wasapi.thread.src_buffer;
942 for (int i = 0; i < num_samples; i++) {
943 if (0 == buffer_pos) {
944 _saudio_wasapi_fill_buffer();
945 }
946 dst[i] = (int16_t) (src[buffer_pos] * 0x7FFF);
947 buffer_pos += 1;
948 if (buffer_pos == buffer_float_size) {
949 buffer_pos = 0;
950 }
951 }
952 _saudio_wasapi.thread.src_buffer_pos = buffer_pos;
953
954 IAudioRenderClient_ReleaseBuffer(_saudio_wasapi.render_client, num_frames, 0);
955}
956
957_SOKOL_PRIVATE DWORD WINAPI _saudio_wasapi_thread_fn(LPVOID param) {
958 (void)param;
959 _saudio_wasapi_submit_buffer(_saudio_wasapi.thread.src_buffer_frames);
960 IAudioClient_Start(_saudio_wasapi.audio_client);
961 while (!_saudio_wasapi.thread.stop) {
962 WaitForSingleObject(_saudio_wasapi.thread.buffer_end_event, INFINITE);
963 UINT32 padding = 0;
964 if (FAILED(IAudioClient_GetCurrentPadding(_saudio_wasapi.audio_client, &padding))) {
965 continue;
966 }
967 SOKOL_ASSERT(_saudio_wasapi.thread.dst_buffer_frames >= (int)padding);
968 UINT32 num_frames = _saudio_wasapi.thread.dst_buffer_frames - padding;
969 if (num_frames > 0) {
970 _saudio_wasapi_submit_buffer(num_frames);
971 }
972 }
973 return 0;
974}
975
976_SOKOL_PRIVATE void _saudio_wasapi_release(void) {
977 if (_saudio_wasapi.thread.src_buffer) {
978 SOKOL_FREE(_saudio_wasapi.thread.src_buffer);
979 _saudio_wasapi.thread.src_buffer = 0;
980 }
981 if (_saudio_wasapi.render_client) {
982 IAudioRenderClient_Release(_saudio_wasapi.render_client);
983 _saudio_wasapi.render_client = 0;
984 }
985 if (_saudio_wasapi.audio_client) {
986 IAudioClient_Release(_saudio_wasapi.audio_client);
987 _saudio_wasapi.audio_client = 0;
988 }
989 if (_saudio_wasapi.device) {
990 IMMDevice_Release(_saudio_wasapi.device);
991 _saudio_wasapi.device = 0;
992 }
993 if (_saudio_wasapi.device_enumerator) {
994 IMMDeviceEnumerator_Release(_saudio_wasapi.device_enumerator);
995 _saudio_wasapi.device_enumerator = 0;
996 }
997 if (0 != _saudio_wasapi.thread.buffer_end_event) {
998 CloseHandle(_saudio_wasapi.thread.buffer_end_event);
999 _saudio_wasapi.thread.buffer_end_event = 0;
1000 }
1001}
1002
1003_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1004 memset(&_saudio_wasapi, 0, sizeof(_saudio_wasapi));
1005 if (FAILED(CoInitializeEx(0, COINIT_MULTITHREADED))) {
1006 SOKOL_LOG("sokol_audio wasapi: CoInitializeEx failed");
1007 return false;
1008 }
1009 _saudio_wasapi.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0);
1010 if (0 == _saudio_wasapi.thread.buffer_end_event) {
1011 SOKOL_LOG("sokol_audio wasapi: failed to create buffer_end_event");
1012 goto error;
1013 }
1014 if (FAILED(CoCreateInstance(&_saudio_CLSID_IMMDeviceEnumerator,
1015 0, CLSCTX_ALL,
1016 &_saudio_IID_IMMDeviceEnumerator,
1017 (void**)&_saudio_wasapi.device_enumerator)))
1018 {
1019 SOKOL_LOG("sokol_audio wasapi: failed to create device enumerator");
1020 goto error;
1021 }
1022 if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(_saudio_wasapi.device_enumerator,
1023 eRender, eConsole,
1024 &_saudio_wasapi.device)))
1025 {
1026 SOKOL_LOG("sokol_audio wasapi: GetDefaultAudioEndPoint failed");
1027 goto error;
1028 }
1029 if (FAILED(IMMDevice_Activate(_saudio_wasapi.device,
1030 &_saudio_IID_IAudioClient,
1031 CLSCTX_ALL, 0,
1032 (void**)&_saudio_wasapi.audio_client)))
1033 {
1034 SOKOL_LOG("sokol_audio wasapi: device activate failed");
1035 goto error;
1036 }
1037 WAVEFORMATEX fmt;
1038 memset(&fmt, 0, sizeof(fmt));
1039 fmt.nChannels = (WORD) _saudio.num_channels;
1040 fmt.nSamplesPerSec = _saudio.sample_rate;
1041 fmt.wFormatTag = WAVE_FORMAT_PCM;
1042 fmt.wBitsPerSample = 16;
1043 fmt.nBlockAlign = (fmt.nChannels * fmt.wBitsPerSample) / 8;
1044 fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign;
1045 REFERENCE_TIME dur = (REFERENCE_TIME)
1046 (((double)_saudio.buffer_frames) / (((double)_saudio.sample_rate) * (1.0/10000000.0)));
1047 if (FAILED(IAudioClient_Initialize(_saudio_wasapi.audio_client,
1048 AUDCLNT_SHAREMODE_SHARED,
1049 AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
1050 dur, 0, &fmt, 0)))
1051 {
1052 SOKOL_LOG("sokol_audio wasapi: audio client initialize failed");
1053 goto error;
1054 }
1055 if (FAILED(IAudioClient_GetBufferSize(_saudio_wasapi.audio_client, &_saudio_wasapi.thread.dst_buffer_frames))) {
1056 SOKOL_LOG("sokol_audio wasapi: audio client get buffer size failed");
1057 goto error;
1058 }
1059 if (FAILED(IAudioClient_GetService(_saudio_wasapi.audio_client,
1060 &_saudio_IID_IAudioRenderClient,
1061 (void**)&_saudio_wasapi.render_client)))
1062 {
1063 SOKOL_LOG("sokol_audio wasapi: audio client GetService failed");
1064 goto error;
1065 }
1066 if (FAILED(IAudioClient_SetEventHandle(_saudio_wasapi.audio_client, _saudio_wasapi.thread.buffer_end_event))) {
1067 SOKOL_LOG("sokol_audio wasapi: audio client SetEventHandle failed");
1068 goto error;
1069 }
1070 _saudio_wasapi.si16_bytes_per_frame = _saudio.num_channels * sizeof(int16_t);
1071 _saudio.bytes_per_frame = _saudio.num_channels * sizeof(float);
1072 _saudio_wasapi.thread.src_buffer_frames = _saudio.buffer_frames;
1073 _saudio_wasapi.thread.src_buffer_byte_size = _saudio_wasapi.thread.src_buffer_frames * _saudio.bytes_per_frame;
1074
1075 /* allocate an intermediate buffer for sample format conversion */
1076 _saudio_wasapi.thread.src_buffer = (float*) SOKOL_MALLOC(_saudio_wasapi.thread.src_buffer_byte_size);
1077 SOKOL_ASSERT(_saudio_wasapi.thread.src_buffer);
1078
1079 /* create streaming thread */
1080 _saudio_wasapi.thread.thread_handle = CreateThread(NULL, 0, _saudio_wasapi_thread_fn, 0, 0, 0);
1081 if (0 == _saudio_wasapi.thread.thread_handle) {
1082 SOKOL_LOG("sokol_audio wasapi: CreateThread failed");
1083 goto error;
1084 }
1085
1086 return true;
1087
1088error:
1089 _saudio_wasapi_release();
1090 return false;
1091}
1092
1093_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1094 if (_saudio_wasapi.thread.thread_handle) {
1095 _saudio_wasapi.thread.stop = true;
1096 SetEvent(_saudio_wasapi.thread.buffer_end_event);
1097 WaitForSingleObject(_saudio_wasapi.thread.thread_handle, INFINITE);
1098 CloseHandle(_saudio_wasapi.thread.thread_handle);
1099 _saudio_wasapi.thread.thread_handle = 0;
1100 }
1101 if (_saudio_wasapi.audio_client) {
1102 IAudioClient_Stop(_saudio_wasapi.audio_client);
1103 }
1104 _saudio_wasapi_release();
1105 CoUninitialize();
1106}
1107
1108/*=== EMSCRIPTEN BACKEND =====================================================*/
1109
1110#elif defined(__EMSCRIPTEN__)
1111#include <emscripten/emscripten.h>
1112
1113static uint8_t* _saudio_emsc_buffer;
1114
1115EMSCRIPTEN_KEEPALIVE int _saudio_emsc_pull(int num_frames) {
1116 SOKOL_ASSERT(_saudio_emsc_buffer);
1117 if (num_frames == _saudio.buffer_frames) {
1118 if (_saudio.stream_cb) {
1119 _saudio.stream_cb((float*)_saudio_emsc_buffer, num_frames, _saudio.num_channels);
1120 }
1121 else {
1122 const int num_bytes = num_frames * _saudio.bytes_per_frame;
1123 if (0 == _saudio_fifo_read(&_saudio.fifo, _saudio_emsc_buffer, num_bytes)) {
1124 /* not enough read data available, fill the entire buffer with silence */
1125 memset(_saudio_emsc_buffer, 0, num_bytes);
1126 }
1127 }
1128 int res = (int) _saudio_emsc_buffer;
1129 return res;
1130 }
1131 else {
1132 return 0;
1133 }
1134}
1135
1136/* setup the WebAudio context and attach a ScriptProcessorNode */
1137EM_JS(int, _saudio_js_init, (int sample_rate, int num_channels, int buffer_size), {
1138 Module._saudio_context = null;
1139 Module._saudio_node = null;
1140 if (typeof AudioContext !== 'undefined') {
1141 Module._saudio_context = new AudioContext({
1142 sampleRate: sample_rate,
1143 latencyHint: 'interactive',
1144 });
1145 console.log('sokol_audio.h: created AudioContext');
1146 }
1147 else if (typeof webkitAudioContext !== 'undefined') {
1148 Module._saudio_context = new webkitAudioContext({
1149 sampleRate: sample_rate,
1150 latencyHint: 'interactive',
1151 });
1152 console.log('sokol_audio.h: created webkitAudioContext');
1153 }
1154 else {
1155 Module._saudio_context = null;
1156 console.log('sokol_audio.h: no WebAudio support');
1157 }
1158 if (Module._saudio_context) {
1159 console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate);
1160 Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels);
1161 Module._saudio_node.onaudioprocess = function pump_audio(event) {
1162 var num_frames = event.outputBuffer.length;
1163 var ptr = __saudio_emsc_pull(num_frames);
1164 if (ptr) {
1165 var num_channels = event.outputBuffer.numberOfChannels;
1166 for (var chn = 0; chn < num_channels; chn++) {
1167 var chan = event.outputBuffer.getChannelData(chn);
1168 for (var i = 0; i < num_frames; i++) {
1169 chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)]
1170 }
1171 }
1172 }
1173 };
1174 Module._saudio_node.connect(Module._saudio_context.destination);
1175
1176 // in some browsers, WebAudio needs to be activated on a user action
1177 var resume_webaudio = function() {
1178 if (Module._saudio_context) {
1179 if (Module._saudio_context.state === 'suspended') {
1180 Module._saudio_context.resume();
1181 }
1182 }
1183 };
1184 document.addEventListener('click', resume_webaudio, {once:true});
1185 document.addEventListener('touchstart', resume_webaudio, {once:true});
1186 document.addEventListener('keydown', resume_webaudio, {once:true});
1187 return 1;
1188 }
1189 else {
1190 return 0;
1191 }
1192});
1193
1194/* get the actual sample rate back from the WebAudio context */
1195EM_JS(int, _saudio_js_sample_rate, (), {
1196 if (Module._saudio_context) {
1197 return Module._saudio_context.sampleRate;
1198 }
1199 else {
1200 return 0;
1201 }
1202});
1203
1204/* get the actual buffer size in number of frames */
1205EM_JS(int, _saudio_js_buffer_frames, (), {
1206 if (Module._saudio_node) {
1207 return Module._saudio_node.bufferSize;
1208 }
1209 else {
1210 return 0;
1211 }
1212});
1213
1214_SOKOL_PRIVATE bool _saudio_backend_init(void) {
1215 if (_saudio_js_init(_saudio.sample_rate, _saudio.num_channels, _saudio.buffer_frames)) {
1216 _saudio.bytes_per_frame = sizeof(float) * _saudio.num_channels;
1217 _saudio.sample_rate = _saudio_js_sample_rate();
1218 _saudio.buffer_frames = _saudio_js_buffer_frames();
1219 const int buf_size = _saudio.buffer_frames * _saudio.bytes_per_frame;
1220 _saudio_emsc_buffer = SOKOL_MALLOC(buf_size);
1221 return true;
1222 }
1223 else {
1224 return false;
1225 }
1226}
1227
1228_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
1229 /* on HTML5, there's always a 'hard exit' without warning,
1230 so nothing useful to do here
1231 */
1232}
1233
1234#else /* dummy backend */
1235_SOKOL_PRIVATE bool _saudio_backend_init(void) { return false; };
1236_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { };
1237#endif
1238
1239/*=== PUBLIC API FUNCTIONS ===================================================*/
1240SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) {
1241 SOKOL_ASSERT(!_saudio.valid);
1242 SOKOL_ASSERT(desc);
1243 memset(&_saudio, 0, sizeof(_saudio));
1244 _saudio.desc = *desc;
1245 _saudio.stream_cb = desc->stream_cb;
1246 _saudio.sample_rate = _saudio_def(_saudio.desc.sample_rate, _SAUDIO_DEFAULT_SAMPLE_RATE);
1247 _saudio.buffer_frames = _saudio_def(_saudio.desc.buffer_frames, _SAUDIO_DEFAULT_BUFFER_FRAMES);
1248 _saudio.packet_frames = _saudio_def(_saudio.desc.packet_frames, _SAUDIO_DEFAULT_PACKET_FRAMES);
1249 _saudio.num_packets = _saudio_def(_saudio.desc.num_packets, _SAUDIO_DEFAULT_NUM_PACKETS);
1250 _saudio.num_channels = _saudio_def(_saudio.desc.num_channels, 1);
1251 _saudio_mutex_init();
1252 if (_saudio_backend_init()) {
1253 SOKOL_ASSERT(0 == (_saudio.buffer_frames % _saudio.packet_frames));
1254 SOKOL_ASSERT(_saudio.bytes_per_frame > 0);
1255 _saudio_fifo_init(&_saudio.fifo, _saudio.packet_frames * _saudio.bytes_per_frame, _saudio.num_packets);
1256 _saudio.valid = true;
1257 }
1258}
1259
1260SOKOL_API_IMPL void saudio_shutdown(void) {
1261 if (_saudio.valid) {
1262 _saudio_backend_shutdown();
1263 _saudio_fifo_shutdown(&_saudio.fifo);
1264 _saudio.valid = false;
1265 }
1266 _saudio_mutex_destroy();
1267}
1268
1269SOKOL_API_IMPL bool saudio_isvalid(void) {
1270 return _saudio.valid;
1271}
1272
1273SOKOL_API_IMPL int saudio_sample_rate(void) {
1274 return _saudio.sample_rate;
1275}
1276
1277SOKOL_API_IMPL int saudio_buffer_frames(void) {
1278 return _saudio.buffer_frames;
1279}
1280
1281SOKOL_API_IMPL int saudio_channels(void) {
1282 return _saudio.num_channels;
1283}
1284
1285SOKOL_API_IMPL int saudio_expect(void) {
1286 if (_saudio.valid) {
1287 const int num_frames = _saudio_fifo_writable_bytes(&_saudio.fifo) / _saudio.bytes_per_frame;
1288 return num_frames;
1289 }
1290 else {
1291 return 0;
1292 }
1293}
1294
1295SOKOL_API_IMPL int saudio_push(const float* frames, int num_frames) {
1296 SOKOL_ASSERT(frames && (num_frames > 0));
1297 if (_saudio.valid) {
1298 const int num_bytes = num_frames * _saudio.bytes_per_frame;
1299 const int num_written = _saudio_fifo_write(&_saudio.fifo, (const uint8_t*)frames, num_bytes);
1300 return num_written / _saudio.bytes_per_frame;
1301 }
1302 else {
1303 return 0;
1304 }
1305}
1306
1307#undef _saudio_def
1308#undef _saudio_def_flt
1309
1310#ifdef _MSC_VER
1311#pragma warning(pop)
1312#endif
1313
1314#endif /* SOKOL_IMPL */
1315