2#include <ossia/detail/config.hpp>
4#if __has_include(<miniaudio.h>)
5#define OSSIA_ENABLE_MINIAUDIO 1
11#define MA_NO_RUNTIME_LINKING 1
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
23#define MA_AUDIO_WORKLETS_THREAD_STACK_SIZE 4194304
25#define MA_ENABLE_WASAPI 1
27#define MA_ENABLE_COREAUDIO 1
28#define MA_ENABLE_ALSA 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
40#define MA_PTHREAD_REALTIME_THREAD_PRIORITY 95
45#define MA_API OSSIA_EXPORT
47#include <ossia/audio/audio_engine.hpp>
49#include <ossia/detail/pod_vector.hpp>
50#include <ossia/detail/thread.hpp>
54#if defined(__EMSCRIPTEN__)
55#include <emscripten/em_asm.h>
58#define OSSIA_AUDIO_MINIAUDIO 1
62struct miniaudio_context
67class miniaudio_engine final :
public audio_engine
71 std::vector<const ma_device_info*> devices;
72 bool is_duplex(
const ma_device_id& card_in,
const ma_device_id& card_out)
76 std::string_view i = card_in.coreaudio;
77 std::string_view o = card_out.coreaudio;
78 if(i.length() != o.length() || i.empty() || o.empty())
80 if(i.substr(0, i.size() - 1) == o.substr(0, o.size() - 1))
84 return memcmp(&card_in, &card_out,
sizeof(ma_device_id)) == 0;
87 std::shared_ptr<miniaudio_context> ctx, std::string name,
88 const ma_device_id& card_in,
const ma_device_id& card_out,
int inputs,
int outputs,
89 int rate,
int bs, ma_share_mode share = ma_share_mode_shared)
90 : m_ctx{std::move(ctx)}
92 ma_device_type dtype = ma_device_type_duplex;
94 dtype = ma_device_type_playback;
96 dtype = ma_device_type_capture;
98 ma_device_config config = ma_device_config_init(dtype);
100 config.sampleRate = rate;
101 config.periodSizeInFrames = bs;
103 config.periods = int64_t(bs) * 1000 < 2 * int64_t(rate) ? 4 : 3;
108 auto is_default_id = [](
const ma_device_id& id) {
110 return memcmp(&
id, &zero,
sizeof(ma_device_id)) == 0;
115 config.playback.pDeviceID = is_default_id(card_out) ? nullptr : &card_out;
116 config.playback.channels = outputs;
117 config.playback.format = ma_format_f32;
122 config.capture.pDeviceID = is_default_id(card_in) ? nullptr : &card_in;
123 config.capture.channels = inputs;
124 config.capture.format = ma_format_f32;
127 config.dataCallback = callback;
129 config.performanceProfile = ma_performance_profile_low_latency;
130 config.noFixedSizedCallback =
false;
131 config.noClip =
false;
132 config.noDisableDenormals =
false;
133 config.noPreSilencedOutputBuffer =
false;
135 config.pUserData =
this;
137 this->effective_buffer_size = bs;
138 this->effective_sample_rate = rate;
139 this->effective_inputs = inputs;
140 this->effective_outputs = outputs;
142 ins_data.resize(effective_inputs * bs + 16);
143 outs_data.resize(effective_outputs * bs + 16);
144 ins.resize(effective_inputs + 2);
145 outs.resize(effective_outputs + 2);
150 ma_result res = MA_ERROR;
151 for(ma_share_mode mode : {share, ma_share_mode_shared})
153 config.playback.shareMode = mode;
154 config.capture.shareMode = mode;
157 res = ma_device_init(&m_ctx->context, &config, &m_stream);
158 if(res == MA_SUCCESS)
160 m_exclusive = mode == ma_share_mode_exclusive;
163 "miniaudio: exclusive access refused, using the device in shared mode");
166 if(mode == ma_share_mode_shared)
169 if(res != MA_SUCCESS)
170 throw std::runtime_error(
171 std::string(
"miniaudio: cannot open the device: ") + ma_result_description(res));
173 if(res = ma_device_start(&m_stream); res != MA_SUCCESS)
175 ma_device_uninit(&m_stream);
176 throw std::runtime_error(
177 std::string(
"miniaudio: cannot start the device: ")
178 + ma_result_description(res));
181 auto to_client_frames = [rate](ma_uint32 frames, ma_uint32 device_rate) {
182 return device_rate > 0 ? int(int64_t(frames) * rate / device_rate) : int(frames);
185 effective_output_latency = to_client_frames(
186 m_stream.playback.internalPeriodSizeInFrames * m_stream.playback.internalPeriods,
187 m_stream.playback.internalSampleRate);
189 effective_input_latency = to_client_frames(
190 m_stream.capture.internalPeriodSizeInFrames,
191 m_stream.capture.internalSampleRate);
193#if defined(__EMSCRIPTEN__)
201 const node = emscriptenGetAudioObject($0);
202 const ctx = emscriptenGetAudioObject($1);
204 node.connect(ctx.destination);
206 m_stream.webaudio.audioWorklet, m_stream.webaudio.audioContext);
213 bool exclusive() const noexcept {
return m_exclusive; }
215 bool running()
const override
217 return m_active && ma_device_get_state(&m_stream) == ma_device_state_started;
220 bool failed() const noexcept
override
222 return m_active && !stop_processing
223 && ma_device_get_state(&m_stream) == ma_device_state_stopped;
228 audio_engine::stop();
232 ma_device_stop(&m_stream);
233 ma_device_uninit(&m_stream);
238 ~miniaudio_engine()
override { stop(); }
242 callback(ma_device* pDevice,
void* output,
const void* input, ma_uint32 nframes)
244#if !defined(__EMSCRIPTEN__)
246 static const thread_local auto _
248 ossia::set_thread_name(
"ossia audio 0");
249 ossia::set_thread_pinned(thread_type::Audio, 0);
250#if defined(__linux__)
257 auto& self = *
static_cast<miniaudio_engine*
>(pDevice->pUserData);
260 if(self.stop_processing)
266 auto ins = self.ins.data();
267 auto ins_data = self.ins_data.data();
268 for(
int i = 0; i < self.effective_inputs; i++)
269 ins[i] = ins_data + i * nframes;
271 const float* in_samples =
static_cast<const float*
>(input);
272 for(
int c = 0; c < self.effective_inputs; c++)
273 for(ma_uint32 f = 0; f < nframes; f++)
274 ins[c][f] = in_samples[f * self.effective_inputs + c];
276 auto outs = self.outs.data();
277 auto outs_data = self.outs_data.data();
278 std::memset(outs_data, 0,
sizeof(
float) * self.effective_outputs * nframes);
279 for(
int i = 0; i < self.effective_outputs; i++)
280 outs[i] = outs_data + i * nframes;
282#if defined(__EMSCRIPTEN__)
285 self.m_frames_elapsed += nframes;
286 double nsecs = (double)self.m_frames_elapsed / self.effective_sample_rate;
289 self.m_start = std::chrono::steady_clock::now();
290 auto now = std::chrono::steady_clock::now();
292 = std::chrono::duration_cast<std::chrono::nanoseconds>(now - *self.m_start)
297 ossia::audio_tick_state ts{(
float*
const*)ins, outs, self.effective_inputs,
298 self.effective_outputs, nframes, nsecs};
304 float* out_samples =
static_cast<float*
>(output);
305 for(
int c = 0; c < self.effective_outputs; c++)
306 for(ma_uint32 f = 0; f < nframes; f++)
307 out_samples[f * self.effective_outputs + c] = outs[c][f];
310 std::shared_ptr<miniaudio_context> m_ctx;
312#if defined(__EMSCRIPTEN__)
313 uint64_t m_frames_elapsed{};
315 std::optional<std::chrono::steady_clock::time_point> m_start;
318 ossia::float_vector ins_data;
319 ossia::pod_vector<float*> ins;
320 ossia::float_vector outs_data;
321 ossia::pod_vector<float*> outs;
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120
bool set_current_thread_realtime(int prio, bool algo_fifo, bool log)
For threads created by a library, e.g.
Definition thread.cpp:197