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#else
18#define MA_ENABLE_COREAUDIO 1
19#define MA_ENABLE_ALSA 1
20#endif
21#define MA_NO_WAV 1
22#define MA_NO_FLAC 1
23#define MA_NO_MP3 1
24#define MA_NO_RESOURCE_MANAGER 1
25#define MA_NO_NODE_GRAPH 1
26#define MA_NO_GENERATION 1
27#define MA_MAX_CHANNELS 1024
28// Export the miniaudio API from libossia so consumers (score-plugin-audio)
29// link against this one copy instead of compiling their own. OSSIA_EXPORT is
30// empty in static builds, dllexport/visibility when building the ossia shared
31// lib, and dllimport when compiling against it.
32#define MA_API OSSIA_EXPORT
33
34#include <ossia/audio/audio_engine.hpp>
35#include <ossia/detail/thread.hpp>
36
37#include <miniaudio.h>
38
39#define OSSIA_AUDIO_MINIAUDIO 1
40
41namespace ossia
42{
43struct miniaudio_context
44{
45 ma_context context;
46};
47
48class miniaudio_engine final : public audio_engine
49{
50
51public:
52 std::vector<const ma_device_info*> devices;
53 bool is_duplex(const ma_device_id& card_in, const ma_device_id& card_out)
54 {
55#if defined(__APPLE__)
56 return true;
57 std::string_view i = card_in.coreaudio;
58 std::string_view o = card_out.coreaudio;
59 if(i.length() != o.length() || i.empty() || o.empty())
60 return false;
61 if(i.substr(0, i.size() - 1) == o.substr(0, o.size() - 1))
62 return true;
63
64#endif
65 return memcmp(&card_in, &card_out, sizeof(ma_device_id)) == 0;
66 }
67 miniaudio_engine(
68 std::shared_ptr<miniaudio_context> ctx, std::string name,
69 const ma_device_id& card_in, const ma_device_id& card_out, int inputs, int outputs,
70 int rate, int bs)
71 : m_ctx{std::move(ctx)}
72 {
73 ma_device_type dtype = ma_device_type_duplex;
74 if(inputs == 0)
75 dtype = ma_device_type_playback;
76 else if(outputs == 0)
77 dtype = ma_device_type_capture;
78
79 ma_device_config config = ma_device_config_init(dtype);
80
81 config.sampleRate = rate;
82 config.periodSizeInFrames = bs;
83
84 if(outputs > 0)
85 {
86 config.playback.pDeviceID = &card_out;
87 config.playback.channels = outputs;
88 config.playback.format = ma_format_f32;
89 // config.playback.shareMode = ma_share_mode_exclusive;
90 }
91
92 if(inputs > 0)
93 {
94 config.capture.pDeviceID = &card_in;
95 config.capture.channels = inputs;
96 config.capture.format = ma_format_f32;
97 // config.capture.shareMode = ma_share_mode_exclusive;
98 }
99
100 config.dataCallback = callback;
101
102 config.performanceProfile = ma_performance_profile_low_latency;
103 config.noFixedSizedCallback = false;
104 config.noClip = false;
105 config.noDisableDenormals = false;
106 config.noPreSilencedOutputBuffer = false;
107
108 config.pUserData = this;
109
110 this->effective_buffer_size = bs;
111 this->effective_sample_rate = rate;
112 this->effective_inputs = inputs;
113 this->effective_outputs = outputs;
114
115 ins_data.resize(effective_inputs * bs + 16);
116 outs_data.resize(effective_outputs * bs + 16);
117 ins.resize(effective_inputs + 2);
118 outs.resize(effective_outputs + 2);
119
120 if(ma_device_init(&m_ctx->context, &config, &m_stream) != MA_SUCCESS)
121 {
122 config.performanceProfile = ma_performance_profile_conservative;
123 if(ma_device_init(&m_ctx->context, &config, &m_stream) != MA_SUCCESS)
124 {
125 throw std::runtime_error("Cannot initialize miniaudio");
126 }
127 }
128
129 if(ma_device_start(&m_stream) != MA_SUCCESS)
130 throw std::runtime_error("Cannot start miniaudio");
131 m_active = true;
132 }
133
134 bool running() const override
135 {
136 return m_active && ma_device_get_state(&m_stream) == ma_device_state_started;
137 }
138
139 void stop() override
140 {
141 audio_engine::stop();
142
143 if(m_active)
144 {
145 ma_device_stop(&m_stream);
146 ma_device_uninit(&m_stream);
147 }
148 m_active = false;
149 }
150
151 ~miniaudio_engine() override { stop(); }
152
153private:
154 static void
155 callback(ma_device* pDevice, void* output, const void* input, ma_uint32 nframes)
156 {
157#if !defined(__EMSCRIPTEN__)
158 [[maybe_unused]]
159 static const thread_local auto _
160 = [] {
161 ossia::set_thread_name("ossia audio 0");
162 ossia::set_thread_pinned(thread_type::Audio, 0);
163 return 0;
164 }();
165#endif
166
167 auto& self = *static_cast<miniaudio_engine*>(pDevice->pUserData);
168 self.tick_start();
169
170 if(self.stop_processing)
171 {
172 self.tick_clear();
173 return;
174 }
175
176 auto ins = self.ins.data();
177 auto ins_data = self.ins_data.data();
178 for(int i = 0; i < self.effective_inputs; i++)
179 ins[i] = ins_data + i * nframes;
180
181 const float* in_samples = static_cast<const float*>(input);
182 for(int c = 0; c < self.effective_inputs; c++)
183 for(ma_uint32 f = 0; f < nframes; f++)
184 ins[c][f] = in_samples[f * self.effective_inputs + c];
185
186 auto outs = self.outs.data();
187 auto outs_data = self.outs_data.data();
188 std::memset(outs_data, 0, sizeof(float) * self.effective_outputs * nframes);
189 for(int i = 0; i < self.effective_outputs; i++)
190 outs[i] = outs_data + i * nframes;
191
192#if defined(__EMSCRIPTEN__)
193 // On WASM audio worklets, std::chrono::steady_clock is unavailable.
194 // Use the sample count to derive time instead.
195 self.m_frames_elapsed += nframes;
196 double nsecs = (double)self.m_frames_elapsed / self.effective_sample_rate;
197#else
198 if(!self.m_start)
199 self.m_start = std::chrono::steady_clock::now();
200 auto now = std::chrono::steady_clock::now();
201 auto nsecs
202 = std::chrono::duration_cast<std::chrono::nanoseconds>(now - *self.m_start)
203 .count()
204 / 1e9;
205#endif
206
207 ossia::audio_tick_state ts{(float* const*)ins, outs, self.effective_inputs,
208 self.effective_outputs, nframes, nsecs};
209 self.audio_tick(ts);
210
211 self.tick_end();
212
213 float* out_samples = static_cast<float*>(output);
214 for(int c = 0; c < self.effective_outputs; c++)
215 for(ma_uint32 f = 0; f < nframes; f++)
216 out_samples[f * self.effective_outputs + c] = outs[c][f];
217 }
218
219 std::shared_ptr<miniaudio_context> m_ctx;
220 ma_device m_stream;
221#if defined(__EMSCRIPTEN__)
222 uint64_t m_frames_elapsed{};
223#else
224 std::optional<std::chrono::steady_clock::time_point> m_start;
225#endif
226
227 boost::container::vector<float> ins_data;
228 boost::container::vector<float*> ins;
229 boost::container::vector<float> outs_data;
230 boost::container::vector<float*> outs;
231
232 bool m_active{};
233};
234}
235
236#endif
237// #endif
Definition git_info.h:7