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