OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
miniaudio_protocol.hpp
1#pragma once
2#include <ossia/detail/config.hpp>
3
4#if __has_include(<miniaudio.h>)
5#define OSSIA_ENABLE_MINIAUDIO 1
6// The single-header implementation is compiled exactly once, in miniaudio.cpp.
7// Every other includer of this header only sees declarations, so there is no
8// duplicate-symbol clash when libossia and score-plugin-audio are statically
9// linked into the same binary (e.g. wasm).
10#if defined(__APPLE__)
11#define MA_NO_RUNTIME_LINKING 1
12#endif
13#define MA_ENABLE_ONLY_SPECIFIC_BACKENDS 1
14#if defined(__EMSCRIPTEN__)
15#define MA_ENABLE_WEBAUDIO 1
16#define MA_ENABLE_AUDIO_WORKLETS 1
17// The AudioWorklet thread runs the whole ossia execution tick (every node,
18// including deep gfx/ISF exec-node call chains). miniaudio allocates a fixed
19// heap buffer for that thread's stack; its 128KB default is far too small and
20// a deep tick silently overruns it, corrupting adjacent heap allocations
21// (manifesting much later as OOB / null-function crashes on the main thread).
22// Give it a desktop-sized stack instead.
23#define MA_AUDIO_WORKLETS_THREAD_STACK_SIZE 4194304
24#elif defined(_WIN32)
25#define MA_ENABLE_WASAPI 1
26#else
27#define MA_ENABLE_COREAUDIO 1
28#define MA_ENABLE_ALSA 1
29#endif
30#define MA_NO_WAV 1
31#define MA_NO_FLAC 1
32#define MA_NO_MP3 1
33#define MA_NO_RESOURCE_MANAGER 1
34#define MA_NO_NODE_GRAPH 1
35#define MA_NO_GENERATION 1
36#define MA_MAX_CHANNELS 1024
37// Export the miniaudio API from libossia so consumers (score-plugin-audio)
38// link against this one copy instead of compiling their own. OSSIA_EXPORT is
39// empty in static builds, dllexport/visibility when building the ossia shared
40// lib, and dllimport when compiling against it.
41#define MA_API OSSIA_EXPORT
42
43#include <ossia/audio/audio_engine.hpp>
44#include <ossia/detail/pod_vector.hpp>
45#include <ossia/detail/thread.hpp>
46
47#include <miniaudio.h>
48
49#define OSSIA_AUDIO_MINIAUDIO 1
50
51namespace ossia
52{
53struct miniaudio_context
54{
55 ma_context context;
56};
57
58class miniaudio_engine final : public audio_engine
59{
60
61public:
62 std::vector<const ma_device_info*> devices;
63 bool is_duplex(const ma_device_id& card_in, const ma_device_id& card_out)
64 {
65#if defined(__APPLE__)
66 return true;
67 std::string_view i = card_in.coreaudio;
68 std::string_view o = card_out.coreaudio;
69 if(i.length() != o.length() || i.empty() || o.empty())
70 return false;
71 if(i.substr(0, i.size() - 1) == o.substr(0, o.size() - 1))
72 return true;
73
74#endif
75 return memcmp(&card_in, &card_out, sizeof(ma_device_id)) == 0;
76 }
77 miniaudio_engine(
78 std::shared_ptr<miniaudio_context> ctx, std::string name,
79 const ma_device_id& card_in, const ma_device_id& card_out, int inputs, int outputs,
80 int rate, int bs)
81 : m_ctx{std::move(ctx)}
82 {
83 ma_device_type dtype = ma_device_type_duplex;
84 if(inputs == 0)
85 dtype = ma_device_type_playback;
86 else if(outputs == 0)
87 dtype = ma_device_type_capture;
88
89 ma_device_config config = ma_device_config_init(dtype);
90
91 config.sampleRate = rate;
92 config.periodSizeInFrames = bs;
93 // A zeroed device id means "system default device". Passing NULL as the
94 // pDeviceID makes miniaudio open the OS default device and, on backends
95 // that support it (WASAPI in particular), automatically follow the OS
96 // default output/input when the user changes it while the stream runs.
97 auto is_default_id = [](const ma_device_id& id) {
98 ma_device_id zero{};
99 return memcmp(&id, &zero, sizeof(ma_device_id)) == 0;
100 };
101
102 if(outputs > 0)
103 {
104 config.playback.pDeviceID = is_default_id(card_out) ? nullptr : &card_out;
105 config.playback.channels = outputs;
106 config.playback.format = ma_format_f32;
107 // config.playback.shareMode = ma_share_mode_exclusive;
108 }
109
110 if(inputs > 0)
111 {
112 config.capture.pDeviceID = is_default_id(card_in) ? nullptr : &card_in;
113 config.capture.channels = inputs;
114 config.capture.format = ma_format_f32;
115 // config.capture.shareMode = ma_share_mode_exclusive;
116 }
117
118 config.dataCallback = callback;
119
120 config.performanceProfile = ma_performance_profile_low_latency;
121 config.noFixedSizedCallback = false;
122 config.noClip = false;
123 config.noDisableDenormals = false;
124 config.noPreSilencedOutputBuffer = false;
125
126 config.pUserData = this;
127
128 this->effective_buffer_size = bs;
129 this->effective_sample_rate = rate;
130 this->effective_inputs = inputs;
131 this->effective_outputs = outputs;
132
133 ins_data.resize(effective_inputs * bs + 16);
134 outs_data.resize(effective_outputs * bs + 16);
135 ins.resize(effective_inputs + 2);
136 outs.resize(effective_outputs + 2);
137
138 if(ma_device_init(&m_ctx->context, &config, &m_stream) != MA_SUCCESS)
139 {
140 config.performanceProfile = ma_performance_profile_conservative;
141 if(ma_device_init(&m_ctx->context, &config, &m_stream) != MA_SUCCESS)
142 {
143 throw std::runtime_error("Cannot initialize miniaudio");
144 }
145 }
146
147 if(ma_device_start(&m_stream) != MA_SUCCESS)
148 throw std::runtime_error("Cannot start miniaudio");
149 m_active = true;
150 }
151
152 bool running() const override
153 {
154 return m_active && ma_device_get_state(&m_stream) == ma_device_state_started;
155 }
156
157 void stop() override
158 {
159 audio_engine::stop();
160
161 if(m_active)
162 {
163 ma_device_stop(&m_stream);
164 ma_device_uninit(&m_stream);
165 }
166 m_active = false;
167 }
168
169 ~miniaudio_engine() override { stop(); }
170
171private:
172 static void
173 callback(ma_device* pDevice, void* output, const void* input, ma_uint32 nframes)
174 {
175#if !defined(__EMSCRIPTEN__)
176 [[maybe_unused]]
177 static const thread_local auto _
178 = [] {
179 ossia::set_thread_name("ossia audio 0");
180 ossia::set_thread_pinned(thread_type::Audio, 0);
181 return 0;
182 }();
183#endif
184
185 auto& self = *static_cast<miniaudio_engine*>(pDevice->pUserData);
186 self.tick_start();
187
188 if(self.stop_processing)
189 {
190 self.tick_clear();
191 return;
192 }
193
194 auto ins = self.ins.data();
195 auto ins_data = self.ins_data.data();
196 for(int i = 0; i < self.effective_inputs; i++)
197 ins[i] = ins_data + i * nframes;
198
199 const float* in_samples = static_cast<const float*>(input);
200 for(int c = 0; c < self.effective_inputs; c++)
201 for(ma_uint32 f = 0; f < nframes; f++)
202 ins[c][f] = in_samples[f * self.effective_inputs + c];
203
204 auto outs = self.outs.data();
205 auto outs_data = self.outs_data.data();
206 std::memset(outs_data, 0, sizeof(float) * self.effective_outputs * nframes);
207 for(int i = 0; i < self.effective_outputs; i++)
208 outs[i] = outs_data + i * nframes;
209
210#if defined(__EMSCRIPTEN__)
211 // On WASM audio worklets, std::chrono::steady_clock is unavailable.
212 // Use the sample count to derive time instead.
213 self.m_frames_elapsed += nframes;
214 double nsecs = (double)self.m_frames_elapsed / self.effective_sample_rate;
215#else
216 if(!self.m_start)
217 self.m_start = std::chrono::steady_clock::now();
218 auto now = std::chrono::steady_clock::now();
219 auto nsecs
220 = std::chrono::duration_cast<std::chrono::nanoseconds>(now - *self.m_start)
221 .count()
222 / 1e9;
223#endif
224
225 ossia::audio_tick_state ts{(float* const*)ins, outs, self.effective_inputs,
226 self.effective_outputs, nframes, nsecs};
227 if(self.audio_tick)
228 self.audio_tick(ts);
229
230 self.tick_end();
231
232 float* out_samples = static_cast<float*>(output);
233 for(int c = 0; c < self.effective_outputs; c++)
234 for(ma_uint32 f = 0; f < nframes; f++)
235 out_samples[f * self.effective_outputs + c] = outs[c][f];
236 }
237
238 std::shared_ptr<miniaudio_context> m_ctx;
239 ma_device m_stream;
240#if defined(__EMSCRIPTEN__)
241 uint64_t m_frames_elapsed{};
242#else
243 std::optional<std::chrono::steady_clock::time_point> m_start;
244#endif
245
246 ossia::float_vector ins_data;
247 ossia::pod_vector<float*> ins;
248 ossia::float_vector outs_data;
249 ossia::pod_vector<float*> outs;
250
251 bool m_active{};
252};
253}
254
255#endif
256// #endif
Definition git_info.h:7