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