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/thread.hpp>
45
46#include <miniaudio.h>
47
48#define OSSIA_AUDIO_MINIAUDIO 1
49
50namespace ossia
51{
52struct miniaudio_context
53{
54 ma_context context;
55};
56
57class miniaudio_engine final : public audio_engine
58{
59
60public:
61 std::vector<const ma_device_info*> devices;
62 bool is_duplex(const ma_device_id& card_in, const ma_device_id& card_out)
63 {
64#if defined(__APPLE__)
65 return true;
66 std::string_view i = card_in.coreaudio;
67 std::string_view o = card_out.coreaudio;
68 if(i.length() != o.length() || i.empty() || o.empty())
69 return false;
70 if(i.substr(0, i.size() - 1) == o.substr(0, o.size() - 1))
71 return true;
72
73#endif
74 return memcmp(&card_in, &card_out, sizeof(ma_device_id)) == 0;
75 }
76 miniaudio_engine(
77 std::shared_ptr<miniaudio_context> ctx, std::string name,
78 const ma_device_id& card_in, const ma_device_id& card_out, int inputs, int outputs,
79 int rate, int bs)
80 : m_ctx{std::move(ctx)}
81 {
82 ma_device_type dtype = ma_device_type_duplex;
83 if(inputs == 0)
84 dtype = ma_device_type_playback;
85 else if(outputs == 0)
86 dtype = ma_device_type_capture;
87
88 ma_device_config config = ma_device_config_init(dtype);
89
90 config.sampleRate = rate;
91 config.periodSizeInFrames = bs;
92 // A zeroed device id means "system default device". Passing NULL as the
93 // pDeviceID makes miniaudio open the OS default device and, on backends
94 // that support it (WASAPI in particular), automatically follow the OS
95 // default output/input when the user changes it while the stream runs.
96 auto is_default_id = [](const ma_device_id& id) {
97 ma_device_id zero{};
98 return memcmp(&id, &zero, sizeof(ma_device_id)) == 0;
99 };
100
101 if(outputs > 0)
102 {
103 config.playback.pDeviceID = is_default_id(card_out) ? nullptr : &card_out;
104 config.playback.channels = outputs;
105 config.playback.format = ma_format_f32;
106 // config.playback.shareMode = ma_share_mode_exclusive;
107 }
108
109 if(inputs > 0)
110 {
111 config.capture.pDeviceID = is_default_id(card_in) ? nullptr : &card_in;
112 config.capture.channels = inputs;
113 config.capture.format = ma_format_f32;
114 // config.capture.shareMode = ma_share_mode_exclusive;
115 }
116
117 config.dataCallback = callback;
118
119 config.performanceProfile = ma_performance_profile_low_latency;
120 config.noFixedSizedCallback = false;
121 config.noClip = false;
122 config.noDisableDenormals = false;
123 config.noPreSilencedOutputBuffer = false;
124
125 config.pUserData = this;
126
127 this->effective_buffer_size = bs;
128 this->effective_sample_rate = rate;
129 this->effective_inputs = inputs;
130 this->effective_outputs = outputs;
131
132 ins_data.resize(effective_inputs * bs + 16);
133 outs_data.resize(effective_outputs * bs + 16);
134 ins.resize(effective_inputs + 2);
135 outs.resize(effective_outputs + 2);
136
137 if(ma_device_init(&m_ctx->context, &config, &m_stream) != MA_SUCCESS)
138 {
139 config.performanceProfile = ma_performance_profile_conservative;
140 if(ma_device_init(&m_ctx->context, &config, &m_stream) != MA_SUCCESS)
141 {
142 throw std::runtime_error("Cannot initialize miniaudio");
143 }
144 }
145
146 if(ma_device_start(&m_stream) != MA_SUCCESS)
147 throw std::runtime_error("Cannot start miniaudio");
148 m_active = true;
149 }
150
151 bool running() const override
152 {
153 return m_active && ma_device_get_state(&m_stream) == ma_device_state_started;
154 }
155
156 void stop() override
157 {
158 audio_engine::stop();
159
160 if(m_active)
161 {
162 ma_device_stop(&m_stream);
163 ma_device_uninit(&m_stream);
164 }
165 m_active = false;
166 }
167
168 ~miniaudio_engine() override { stop(); }
169
170private:
171 static void
172 callback(ma_device* pDevice, void* output, const void* input, ma_uint32 nframes)
173 {
174#if !defined(__EMSCRIPTEN__)
175 [[maybe_unused]]
176 static const thread_local auto _
177 = [] {
178 ossia::set_thread_name("ossia audio 0");
179 ossia::set_thread_pinned(thread_type::Audio, 0);
180 return 0;
181 }();
182#endif
183
184 auto& self = *static_cast<miniaudio_engine*>(pDevice->pUserData);
185 self.tick_start();
186
187 if(self.stop_processing)
188 {
189 self.tick_clear();
190 return;
191 }
192
193 auto ins = self.ins.data();
194 auto ins_data = self.ins_data.data();
195 for(int i = 0; i < self.effective_inputs; i++)
196 ins[i] = ins_data + i * nframes;
197
198 const float* in_samples = static_cast<const float*>(input);
199 for(int c = 0; c < self.effective_inputs; c++)
200 for(ma_uint32 f = 0; f < nframes; f++)
201 ins[c][f] = in_samples[f * self.effective_inputs + c];
202
203 auto outs = self.outs.data();
204 auto outs_data = self.outs_data.data();
205 std::memset(outs_data, 0, sizeof(float) * self.effective_outputs * nframes);
206 for(int i = 0; i < self.effective_outputs; i++)
207 outs[i] = outs_data + i * nframes;
208
209#if defined(__EMSCRIPTEN__)
210 // On WASM audio worklets, std::chrono::steady_clock is unavailable.
211 // Use the sample count to derive time instead.
212 self.m_frames_elapsed += nframes;
213 double nsecs = (double)self.m_frames_elapsed / self.effective_sample_rate;
214#else
215 if(!self.m_start)
216 self.m_start = std::chrono::steady_clock::now();
217 auto now = std::chrono::steady_clock::now();
218 auto nsecs
219 = std::chrono::duration_cast<std::chrono::nanoseconds>(now - *self.m_start)
220 .count()
221 / 1e9;
222#endif
223
224 ossia::audio_tick_state ts{(float* const*)ins, outs, self.effective_inputs,
225 self.effective_outputs, nframes, nsecs};
226 if(self.audio_tick)
227 self.audio_tick(ts);
228
229 self.tick_end();
230
231 float* out_samples = static_cast<float*>(output);
232 for(int c = 0; c < self.effective_outputs; c++)
233 for(ma_uint32 f = 0; f < nframes; f++)
234 out_samples[f * self.effective_outputs + c] = outs[c][f];
235 }
236
237 std::shared_ptr<miniaudio_context> m_ctx;
238 ma_device m_stream;
239#if defined(__EMSCRIPTEN__)
240 uint64_t m_frames_elapsed{};
241#else
242 std::optional<std::chrono::steady_clock::time_point> m_start;
243#endif
244
245 boost::container::vector<float> ins_data;
246 boost::container::vector<float*> ins;
247 boost::container::vector<float> outs_data;
248 boost::container::vector<float*> outs;
249
250 bool m_active{};
251};
252}
253
254#endif
255// #endif
Definition git_info.h:7