Loading...
Searching...
No Matches
GenericMiniAudioInterface.hpp
1#pragma once
2#include <Audio/AudioInterface.hpp>
3#include <Audio/Settings/Model.hpp>
4#include <Audio/Settings/View.hpp>
5
6#include <score/command/Dispatchers/SettingsCommandDispatcher.hpp>
7#include <score/tools/Bind.hpp>
8#include <score/widgets/SignalUtils.hpp>
9
10#include <ossia/detail/config.hpp>
11
12#include <ossia/audio/miniaudio_protocol.hpp>
13
14#include <QComboBox>
15#include <QCoreApplication>
16#include <QFormLayout>
17
18#include <version>
19#if OSSIA_ENABLE_MINIAUDIO
20
21namespace Audio
22{
23struct MiniAudioCard
24{
25 MiniAudioCard() = default;
26 MiniAudioCard(const MiniAudioCard&) = default;
27 MiniAudioCard& operator=(const MiniAudioCard&) = default;
28 MiniAudioCard(MiniAudioCard&&) noexcept = default;
29 MiniAudioCard& operator=(MiniAudioCard&&) noexcept = default;
30 MiniAudioCard(QString n, ma_device_type t, ma_device_id id)
31 : name{n}
32 , type{t}
33 , id{id}
34 {
35 }
36 QString name;
37 ma_device_type type{};
38 ma_device_id id{};
39 ma_device_info info{};
40};
41
42class GenericMiniAudioFactory
43 : public QObject
44 , public AudioFactory
45{
46 std::shared_ptr<ossia::miniaudio_context> m_context;
47
48public:
49 std::vector<MiniAudioCard> devices;
50
51 virtual ~GenericMiniAudioFactory() override { }
52
53 std::shared_ptr<ossia::miniaudio_context> acquireContext()
54 {
55 if(!m_context)
56 {
57 m_context = std::make_shared<ossia::miniaudio_context>();
58 auto cfg = ma_context_config_init();
59 cfg.threadPriority = ma_thread_priority_realtime;
60 cfg.threadStackSize = 8388608;
61 ma_context_init(nullptr, 0, &cfg, &m_context->context);
62 }
63
64 return m_context;
65 }
66
67 virtual bool compareDeviceId(const ma_device_id& id, const QString& str) const noexcept
68 = 0;
69 virtual bool
70 compareDeviceId(const ma_device_id& id, std::string_view str) const noexcept
71 = 0;
72 virtual void setDeviceId(ma_device_id& id, const QString& str) const noexcept = 0;
73 virtual QString deviceIdToString(const ma_device_id& id) const noexcept = 0;
74
75 void
76 initialize(Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
77 {
78 acquireContext();
79 // set.setDefaultIn(2);
80 // set.setDefaultOut(2);
81 // set.setRate(48000);
82 // set.setBufferSize(256);
83 // set.changed();
84
85 const MiniAudioCard* user_in{};
86 const MiniAudioCard* user_out{};
87
88 const MiniAudioCard* default_in{};
89 const MiniAudioCard* default_out{};
90
91 const MiniAudioCard* first_viable_in{};
92 const MiniAudioCard* first_viable_out{};
93
94 // qDebug(Q_FUNC_INFO);
95 // qDebug() << "looking for in:" << set.getCardIn();
96 // qDebug() << "looking for out:" << set.getCardOut();
97 for(auto& d : devices)
98 {
99 // qDebug() << "has device : " << d.info.name << d.info.nativeDataFormatCount;
100 {
101 for(int i = 0; i < d.info.nativeDataFormatCount; i++)
102 {
103 auto fmt = d.info.nativeDataFormats[i];
104 // qDebug() << "Format: " << magic_enum::enum_name(fmt.format) << fmt.channels
105 // << fmt.flags << fmt.sampleRate;
106 }
107 }
108 if(d.info.nativeDataFormatCount > 0 && d.info.nativeDataFormats[0].channels > 0)
109 {
110 // qDebug() << "device id:" << d.id.coreaudio
111 // << d.info.nativeDataFormats[0].channels;
112
113 if(d.type & ma_device_type::ma_device_type_capture)
114 {
115 if(compareDeviceId(d.id, set.getCardIn()))
116 user_in = &d;
117 // qDebug() << "capture" << d.info.isDefault;
118 first_viable_in = &d;
119 if(d.info.isDefault)
120 default_in = &d;
121 }
122 if(d.type & ma_device_type::ma_device_type_playback)
123 {
124 if(compareDeviceId(d.id, set.getCardOut()))
125 user_out = &d;
126 // qDebug() << "playback" << d.info.isDefault;
127 first_viable_out = &d;
128 if(d.info.isDefault)
129 default_out = &d;
130 }
131 }
132 }
133
134 const MiniAudioCard* card_in = user_in;
135 if(!card_in)
136 card_in = default_in;
137 if(!card_in)
138 card_in = first_viable_in;
139 if(set.getCardIn().startsWith("No device"))
140 card_in = nullptr;
141
142 const MiniAudioCard* card_out = user_out;
143 if(!card_out)
144 card_out = default_out;
145 if(!card_out)
146 card_out = first_viable_out;
147 if(set.getCardOut().startsWith("No device"))
148 card_out = nullptr;
149
150 if(card_in)
151 {
152 // qDebug() << Q_FUNC_INFO << card_in->id.coreaudio;
153 set.setCardIn(deviceIdToString(card_in->id));
154 set.setDefaultIn(card_in->info.nativeDataFormats[0].channels);
155 }
156 else
157 {
158 // qDebug() << Q_FUNC_INFO << "no input";
159 set.setCardIn(devices.front().name);
160 set.setDefaultIn(0);
161 }
162
163 if(card_out)
164 {
165 // qDebug() << Q_FUNC_INFO << card_out->id.coreaudio;
166 set.setCardOut(deviceIdToString(card_out->id));
167 set.setDefaultOut(card_out->info.nativeDataFormats[0].channels);
168
169 int current_rate = set.getRate();
170 if(int fmt_count = card_out->info.nativeDataFormatCount; fmt_count > 0)
171 {
172 auto* fmts = card_out->info.nativeDataFormats;
173 if(std::none_of(fmts, fmts + fmt_count, [current_rate](auto fmt) {
174 return fmt.sampleRate == current_rate;
175 }))
176 {
177 set.setRate(card_out->info.nativeDataFormats[0].sampleRate);
178 }
179 }
180 }
181 else
182 {
183 // qDebug() << Q_FUNC_INFO << "no output";
184 set.setCardOut(devices.front().name);
185 set.setDefaultOut(0);
186 }
187
188 set.changed();
189 }
190
191 void rescan()
192 {
193 auto ctx = acquireContext();
194 if(!ctx)
195 return;
196 devices.clear();
197
198 devices.resize(1);
199 devices[0].name = "No device";
200 memset(&devices[0].id, 0, sizeof(devices[0].id));
201 memset(&devices[0].info, 0, sizeof(devices[0].info));
202
203 setDeviceId(devices[0].id, "No device");
204
205 ma_context_enumerate_devices(
206 &m_context->context,
207 [](ma_context* ctx, ma_device_type dev_type, const ma_device_info* dev_info,
208 void* data) -> ma_bool32 {
209 auto& self = *(GenericMiniAudioFactory*)data;
210 self.devices.emplace_back(dev_info->name, dev_type, dev_info->id);
211 return 1;
212 },
213 this);
214
215 for(std::size_t i = 1; i < devices.size(); i++)
216 {
217 auto& dev = devices[i];
218 ma_context_get_device_info(&m_context->context, dev.type, &dev.id, &dev.info);
219 }
220 }
221
222 std::shared_ptr<ossia::audio_engine> make_engine(
223 const Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
224 {
225 ma_device_id info_in;
226 ma_device_id info_out;
227 memset(&info_in, 0, sizeof(info_in));
228 memset(&info_out, 0, sizeof(info_out));
229
230 auto card_in = set.getCardIn().toStdString();
231 auto card_out = set.getCardOut().toStdString();
232
233 for(auto& dev : this->devices)
234 {
235 if(compareDeviceId(dev.id, card_in))
236 info_in = dev.id;
237 if(compareDeviceId(dev.id, card_out))
238 info_out = dev.id;
239 }
240
241 return std::make_shared<ossia::miniaudio_engine>(
242 acquireContext(), "ossia score", info_in, info_out, set.getDefaultIn(),
243 set.getDefaultOut(), set.getRate(), set.getBufferSize());
244 }
245
246 void setCard(QComboBox* combo, ma_device_type tp, QString val)
247 {
248 //qDebug() << Q_FUNC_INFO << "looking for: " << val << "in " << combo->count();
249 auto dev_it
250 = ossia::find_if(devices, [&, id = val.toStdString()](const MiniAudioCard& d) {
251 // qDebug() << d.id.coreaudio << " == " << id << bool(d.id.coreaudio == id) << " ???";
252 return d.id.coreaudio == id && d.type == tp;
253 });
254 if(dev_it != devices.end())
255 {
256 int device_index = std::distance(devices.begin(), dev_it);
257 for(int i = 0; i < combo->count(); i++)
258 {
259 // qDebug() << device_index << i << combo->itemText(i) << combo->itemData(i);
260 if(combo->itemData(i).toInt() == device_index)
261 {
262 combo->setCurrentIndex(i);
263 return;
264 }
265 }
266 }
267
268 combo->setCurrentIndex(0);
269 }
270
271 QWidget* make_settings(
273 score::SettingsCommandDispatcher& m_disp, QWidget* parent) override
274 {
275 acquireContext();
276 /* Not useful: hotplug does not seem to work
277 struct UpdateWhenVisible : public QWidget
278 {
279 public:
280 using QWidget::QWidget;
281
282 bool event(QEvent* e) override
283 {
284 if(e->type() == QEvent::Show)
285 {
286 auto& self = score::AppContext().interfaces<Audio::AudioFactoryList>();
287 auto cf = static_cast<CoreAudioFactory*>(
288 self.get(CoreAudioFactory::static_concreteKey()));
289 cf->rescan();
290 }
291 return QWidget::event(e);
292 }
293 };
294 */
295
296 auto w = new QWidget{parent};
297 auto lay = new QFormLayout{w};
298
299 auto card_list_in = new QComboBox{w};
300 auto card_list_out = new QComboBox{w};
301
302 // Disabled case
303 card_list_in->addItem(devices.front().name + " (capture)", 0);
304 card_list_out->addItem(devices.front().name + " (playback)", 0);
305
306 // qDebug() << Q_FUNC_INFO << "Devices: ";
307 for(std::size_t i = 1; i < devices.size(); i++)
308 {
309
310 auto& dev = devices[i];
311 // qDebug() << i << dev.info.name << dev.id.coreaudio << dev.type;
312 if(dev.info.nativeDataFormatCount > 0)
313 {
314 if(dev.info.nativeDataFormats[0].channels > 0)
315 {
316 if(dev.type == ma_device_type_capture)
317 {
318 card_list_in->addItem(dev.name, (int)i);
319 // qDebug() << "adding to card list in" << i << dev.name;
320 }
321 if(dev.type == ma_device_type_playback)
322 card_list_out->addItem(dev.name, (int)i);
323 }
324 }
325 }
326
327 /*
328 for(int i = 0; i < card_list_in->count(); i++)
329 qDebug() << Q_FUNC_INFO << "card in " << i << card_list_in->itemText(i)
330 << card_list_in->itemData(i);
331 for(int i = 0; i < card_list_out->count(); i++)
332 qDebug() << Q_FUNC_INFO << "card out " << i << card_list_out->itemText(i)
333 << card_list_out->itemData(i);
334 */
335 using Model = Audio::Settings::Model;
336
337 {
338 lay->addRow(QObject::tr("Capture"), card_list_in);
339
340 auto update_dev_in = [=, &m, &m_disp](const MiniAudioCard& dev) {
341 if(!compareDeviceId(dev.id, m.getCardIn()))
342 {
343 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(
344 m, deviceIdToString(dev.id));
345 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
346 m, dev.info.nativeDataFormats[0].channels);
347 }
348 };
349
350 QObject::connect(
351 card_list_in, SignalUtils::QComboBox_currentIndexChanged_int(), &v,
352 [=](int i) {
353 auto& device = devices[card_list_in->itemData(i).toInt()];
354 update_dev_in(device);
355 });
356
357 if(m.getCardIn().isEmpty())
358 {
359 if(!devices.empty())
360 {
361 update_dev_in(devices.front());
362 }
363 }
364 else
365 {
366 setCard(card_list_in, ma_device_type_capture, m.getCardIn());
367 }
368 }
369
370 {
371 lay->addRow(QObject::tr("Playback"), card_list_out);
372
373 auto update_dev_out = [=, &m, &m_disp](const MiniAudioCard& dev) {
374 if(!compareDeviceId(dev.id, m.getCardOut()))
375 {
376 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
377 m, deviceIdToString(dev.id));
378 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
379 m, dev.info.nativeDataFormats[0].channels);
380 }
381 };
382
383 QObject::connect(
384 card_list_out, SignalUtils::QComboBox_currentIndexChanged_int(), &v,
385 [=](int i) {
386 auto& device = devices[card_list_out->itemData(i).toInt()];
387 update_dev_out(device);
388 });
389
390 if(m.getCardOut().isEmpty())
391 {
392 if(!devices.empty())
393 {
394 update_dev_out(devices.front());
395 }
396 }
397 else
398 {
399 setCard(card_list_out, ma_device_type_playback, m.getCardOut());
400 }
401 }
402
403 addBufferSizeWidget(*w, m, v);
404 addSampleRateWidget(*w, m, v);
405
406 con(m, &Model::changed, w, [=, &m] {
407 setCard(card_list_in, ma_device_type_capture, m.getCardIn());
408 setCard(card_list_out, ma_device_type_playback, m.getCardOut());
409 });
410 return w;
411 }
412};
413
414}
415
416#endif
Definition score-plugin-audio/Audio/Settings/Model.hpp:22
Definition score-plugin-audio/Audio/Settings/View.hpp:19
Definition SettingsCommandDispatcher.hpp:10
Used to access all the application-wide state and structures.
Definition ApplicationContext.hpp:24