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
41#define MA_API OSSIA_EXPORT
43#include <ossia/audio/audio_engine.hpp>
44#include <ossia/detail/pod_vector.hpp>
45#include <ossia/detail/thread.hpp>
49#define OSSIA_AUDIO_MINIAUDIO 1
53struct miniaudio_context
58class miniaudio_engine final :
public audio_engine
62 std::vector<const ma_device_info*> devices;
63 bool is_duplex(
const ma_device_id& card_in,
const ma_device_id& card_out)
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())
71 if(i.substr(0, i.size() - 1) == o.substr(0, o.size() - 1))
75 return memcmp(&card_in, &card_out,
sizeof(ma_device_id)) == 0;
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,
81 : m_ctx{std::move(ctx)}
83 ma_device_type dtype = ma_device_type_duplex;
85 dtype = ma_device_type_playback;
87 dtype = ma_device_type_capture;
89 ma_device_config config = ma_device_config_init(dtype);
91 config.sampleRate = rate;
92 config.periodSizeInFrames = bs;
97 auto is_default_id = [](
const ma_device_id& id) {
99 return memcmp(&
id, &zero,
sizeof(ma_device_id)) == 0;
104 config.playback.pDeviceID = is_default_id(card_out) ? nullptr : &card_out;
105 config.playback.channels = outputs;
106 config.playback.format = ma_format_f32;
112 config.capture.pDeviceID = is_default_id(card_in) ? nullptr : &card_in;
113 config.capture.channels = inputs;
114 config.capture.format = ma_format_f32;
118 config.dataCallback = callback;
120 config.performanceProfile = ma_performance_profile_low_latency;
121 config.noFixedSizedCallback =
false;
122 config.noClip =
false;
123 config.noDisableDenormals =
false;
124 config.noPreSilencedOutputBuffer =
false;
126 config.pUserData =
this;
128 this->effective_buffer_size = bs;
129 this->effective_sample_rate = rate;
130 this->effective_inputs = inputs;
131 this->effective_outputs = outputs;
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);
138 if(ma_device_init(&m_ctx->context, &config, &m_stream) != MA_SUCCESS)
140 config.performanceProfile = ma_performance_profile_conservative;
141 if(ma_device_init(&m_ctx->context, &config, &m_stream) != MA_SUCCESS)
143 throw std::runtime_error(
"Cannot initialize miniaudio");
147 if(ma_device_start(&m_stream) != MA_SUCCESS)
148 throw std::runtime_error(
"Cannot start miniaudio");
152 bool running()
const override
154 return m_active && ma_device_get_state(&m_stream) == ma_device_state_started;
159 audio_engine::stop();
163 ma_device_stop(&m_stream);
164 ma_device_uninit(&m_stream);
169 ~miniaudio_engine()
override { stop(); }
173 callback(ma_device* pDevice,
void* output,
const void* input, ma_uint32 nframes)
175#if !defined(__EMSCRIPTEN__)
177 static const thread_local auto _
179 ossia::set_thread_name(
"ossia audio 0");
180 ossia::set_thread_pinned(thread_type::Audio, 0);
185 auto& self = *
static_cast<miniaudio_engine*
>(pDevice->pUserData);
188 if(self.stop_processing)
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;
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];
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;
210#if defined(__EMSCRIPTEN__)
213 self.m_frames_elapsed += nframes;
214 double nsecs = (double)self.m_frames_elapsed / self.effective_sample_rate;
217 self.m_start = std::chrono::steady_clock::now();
218 auto now = std::chrono::steady_clock::now();
220 = std::chrono::duration_cast<std::chrono::nanoseconds>(now - *self.m_start)
225 ossia::audio_tick_state ts{(
float*
const*)ins, outs, self.effective_inputs,
226 self.effective_outputs, nframes, nsecs};
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];
238 std::shared_ptr<miniaudio_context> m_ctx;
240#if defined(__EMSCRIPTEN__)
241 uint64_t m_frames_elapsed{};
243 std::optional<std::chrono::steady_clock::time_point> m_start;
246 ossia::float_vector ins_data;
247 ossia::pod_vector<float*> ins;
248 ossia::float_vector outs_data;
249 ossia::pod_vector<float*> outs;