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