2#include <Audio/AudioInterface.hpp>
3#include <Audio/Settings/Model.hpp>
4#include <Audio/Settings/View.hpp>
6#include <score/command/Dispatchers/SettingsCommandDispatcher.hpp>
7#include <score/tools/Bind.hpp>
8#include <score/widgets/SignalUtils.hpp>
10#include <ossia/detail/config.hpp>
12#include <ossia/audio/miniaudio_protocol.hpp>
15#include <QCoreApplication>
19#if OSSIA_ENABLE_MINIAUDIO
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)
37 ma_device_type type{};
39 ma_device_info info{};
42class GenericMiniAudioFactory
46 std::shared_ptr<ossia::miniaudio_context> m_context;
50 std::vector<MiniAudioCard> devices;
52 virtual ~GenericMiniAudioFactory()
override { }
54 std::shared_ptr<ossia::miniaudio_context> acquireContext()
58 m_context = std::make_shared<ossia::miniaudio_context>();
59 auto cfg = ma_context_config_init();
60 cfg.threadPriority = ma_thread_priority_realtime;
61 cfg.threadStackSize = 8388608;
62 ma_context_init(
nullptr, 0, &cfg, &m_context->context);
68 virtual bool compareDeviceId(
const ma_device_id&
id,
const QString& str)
const noexcept
71 compareDeviceId(
const ma_device_id&
id, std::string_view str)
const noexcept
73 virtual void setDeviceId(ma_device_id&
id,
const QString& str)
const noexcept = 0;
74 virtual QString deviceIdToString(
const ma_device_id&
id)
const noexcept = 0;
78 virtual void normalizeDeviceId(ma_device_id&
id)
const noexcept { }
81 virtual ma_share_mode shareMode() const noexcept {
return ma_share_mode_shared; }
87 virtual bool followsDefaultDevice() const noexcept {
return false; }
89 static constexpr int DefaultDeviceItemData = -1;
91 int defaultDeviceChannels(ma_device_type tp)
const noexcept
93 for(
const auto& d : devices)
95 if(d.type == tp && d.info.isDefault && d.info.nativeDataFormatCount > 0)
96 return d.info.nativeDataFormats[0].channels;
114 if(followsDefaultDevice() && set.getCardOut().isEmpty())
115 set.setCardOut(
"Default");
116 if(followsDefaultDevice() && set.getCardIn().isEmpty())
117 set.setCardIn(
"Default");
119 const MiniAudioCard* user_in{};
120 const MiniAudioCard* user_out{};
122 const MiniAudioCard* default_in{};
123 const MiniAudioCard* default_out{};
125 const MiniAudioCard* first_viable_in{};
126 const MiniAudioCard* first_viable_out{};
131 for(
auto& d : devices)
135 for(
int i = 0; i < d.info.nativeDataFormatCount; i++)
142 if(d.info.nativeDataFormatCount > 0 && d.info.nativeDataFormats[0].channels > 0)
147 if(d.type & ma_device_type::ma_device_type_capture)
149 if(compareDeviceId(d.id, set.getCardIn()))
152 first_viable_in = &d;
156 if(d.type & ma_device_type::ma_device_type_playback)
158 if(compareDeviceId(d.id, set.getCardOut()))
161 first_viable_out = &d;
168 const MiniAudioCard* card_in = user_in;
170 card_in = default_in;
172 card_in = first_viable_in;
173 if(set.getCardIn().startsWith(
"No device"))
176 const MiniAudioCard* card_out = user_out;
178 card_out = default_out;
180 card_out = first_viable_out;
181 if(set.getCardOut().startsWith(
"No device"))
184 if(followsDefaultDevice() && set.getCardIn() ==
"Default")
186 const MiniAudioCard* ref = default_in ? default_in : first_viable_in;
187 set.setDefaultIn(ref ? ref->info.nativeDataFormats[0].channels : 2);
191 set.setCardIn(deviceIdToString(card_in->id));
192 set.setDefaultIn(card_in->info.nativeDataFormats[0].channels);
196 set.setCardIn(devices.front().name);
200 if(followsDefaultDevice() && set.getCardOut() ==
"Default")
205 const MiniAudioCard* ref = default_out ? default_out : first_viable_out;
208 set.setDefaultOut(ref->info.nativeDataFormats[0].channels);
209 int current_rate = set.getRate();
210 if(
int fmt_count = ref->info.nativeDataFormatCount; fmt_count > 0)
212 auto* fmts = ref->info.nativeDataFormats;
213 if(std::none_of(fmts, fmts + fmt_count, [current_rate](
auto fmt) {
214 return fmt.sampleRate == current_rate;
217 set.setRate(ref->info.nativeDataFormats[0].sampleRate);
223 set.setDefaultOut(2);
228 set.setCardOut(deviceIdToString(card_out->id));
229 set.setDefaultOut(card_out->info.nativeDataFormats[0].channels);
231 int current_rate = set.getRate();
232 if(
int fmt_count = card_out->info.nativeDataFormatCount; fmt_count > 0)
234 auto* fmts = card_out->info.nativeDataFormats;
235 if(std::none_of(fmts, fmts + fmt_count, [current_rate](
auto fmt) {
236 return fmt.sampleRate == current_rate;
239 set.setRate(card_out->info.nativeDataFormats[0].sampleRate);
245 set.setCardOut(devices.front().name);
246 set.setDefaultOut(0);
260 auto ctx = acquireContext();
267 devices[0].name =
"No device";
268 memset(&devices[0].
id, 0,
sizeof(devices[0].
id));
269 memset(&devices[0].info, 0,
sizeof(devices[0].info));
271 setDeviceId(devices[0].
id,
"No device");
273 ma_context_enumerate_devices(
275 [](ma_context* ctx, ma_device_type dev_type,
const ma_device_info* dev_info,
276 void* data) -> ma_bool32 {
277 auto& self = *(GenericMiniAudioFactory*)data;
278 self.devices.emplace_back(dev_info->name, dev_type, dev_info->id);
283 for(std::size_t i = 1; i < devices.size(); i++)
285 auto& dev = devices[i];
287 ma_context_get_device_info(&m_context->context, dev.type, &dev.id, &dev.info);
288 normalizeDeviceId(dev.id);
289 dev.info.id = dev.id;
293 std::shared_ptr<ossia::audio_engine> make_engine(
297 ma_device_id info_in;
298 ma_device_id info_out;
299 memset(&info_in, 0,
sizeof(info_in));
300 memset(&info_out, 0,
sizeof(info_out));
302 auto card_in = set.getCardIn().toStdString();
303 auto card_out = set.getCardOut().toStdString();
305 bool found_in =
false, found_out =
false;
306 for(
auto& dev : this->devices)
308 if(compareDeviceId(dev.id, card_in))
313 if(compareDeviceId(dev.id, card_out))
323 auto explicit_device = [](
const QString& name) {
324 return !name.isEmpty() && name !=
"Default" && !name.startsWith(
"No device");
326 if(!found_in && explicit_device(set.getCardIn()))
327 setDeviceId(info_in, set.getCardIn());
328 if(!found_out && explicit_device(set.getCardOut()))
329 setDeviceId(info_out, set.getCardOut());
331 return std::make_shared<ossia::miniaudio_engine>(
332 acquireContext(),
"ossia score", info_in, info_out, set.getDefaultIn(),
333 set.getDefaultOut(), set.getRate(), set.getBufferSize(), shareMode());
336 void setCard(QComboBox* combo, ma_device_type tp, QString val)
339 if(followsDefaultDevice() && val ==
"Default")
341 for(
int i = 0; i < combo->count(); i++)
343 if(combo->itemData(i).toInt() == DefaultDeviceItemData)
345 combo->setCurrentIndex(i);
351 auto dev_it = ossia::find_if(devices, [&](
const MiniAudioCard& d) {
352 return compareDeviceId(d.id, val) && d.type == tp;
354 if(dev_it != devices.end())
356 int device_index = std::distance(devices.begin(), dev_it);
357 for(
int i = 0; i < combo->count(); i++)
359 if(combo->itemData(i).toInt() == device_index)
361 combo->setCurrentIndex(i);
367 combo->setCurrentIndex(0);
370 QWidget* make_settings(
395 auto w =
new QWidget{parent};
396 auto lay =
new QFormLayout{w};
398 auto card_list_in =
new QComboBox{w};
399 auto card_list_out =
new QComboBox{w};
402 card_list_in->addItem(devices.front().name +
" (capture)", 0);
403 card_list_out->addItem(devices.front().name +
" (playback)", 0);
406 if(followsDefaultDevice())
408 card_list_in->addItem(
409 QObject::tr(
"Default device"), DefaultDeviceItemData);
410 card_list_out->addItem(
411 QObject::tr(
"Default device"), DefaultDeviceItemData);
415 for(std::size_t i = 1; i < devices.size(); i++)
418 auto& dev = devices[i];
420 if(dev.info.nativeDataFormatCount > 0)
422 if(dev.info.nativeDataFormats[0].channels > 0)
424 if(dev.type == ma_device_type_capture)
426 card_list_in->addItem(dev.name, (
int)i);
429 if(dev.type == ma_device_type_playback)
430 card_list_out->addItem(dev.name, (
int)i);
446 lay->addRow(QObject::tr(
"Capture"), card_list_in);
448 auto update_dev_in = [
this, &m, &m_disp](
const MiniAudioCard& dev) {
449 if(!compareDeviceId(dev.id, m.getCardIn()))
451 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(
452 m, deviceIdToString(dev.id));
453 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
454 m, dev.info.nativeDataFormats[0].channels);
458 auto update_default_in = [
this, &m, &m_disp]() {
459 if(m.getCardIn() !=
"Default")
461 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(
462 m, QStringLiteral(
"Default"));
463 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
464 m, defaultDeviceChannels(ma_device_type_capture));
469 card_list_in, SignalUtils::QComboBox_currentIndexChanged_int(), &v,
470 [
this, card_list_in, update_dev_in, update_default_in](
int i) {
471 int data = card_list_in->itemData(i).toInt();
472 if(data == DefaultDeviceItemData)
475 update_dev_in(devices[data]);
478 if(m.getCardIn().isEmpty())
482 update_dev_in(devices.front());
487 setCard(card_list_in, ma_device_type_capture, m.getCardIn());
492 lay->addRow(QObject::tr(
"Playback"), card_list_out);
494 auto update_dev_out = [
this, &m, &m_disp](
const MiniAudioCard& dev) {
495 if(!compareDeviceId(dev.id, m.getCardOut()))
497 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
498 m, deviceIdToString(dev.id));
499 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
500 m, dev.info.nativeDataFormats[0].channels);
504 auto update_default_out = [
this, &m, &m_disp]() {
505 if(m.getCardOut() !=
"Default")
507 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
508 m, QStringLiteral(
"Default"));
509 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
510 m, defaultDeviceChannels(ma_device_type_playback));
515 card_list_out, SignalUtils::QComboBox_currentIndexChanged_int(), &v,
516 [
this, card_list_out, update_dev_out, update_default_out](
int i) {
517 int data = card_list_out->itemData(i).toInt();
518 if(data == DefaultDeviceItemData)
519 update_default_out();
521 update_dev_out(devices[data]);
524 if(m.getCardOut().isEmpty())
528 update_dev_out(devices.front());
533 setCard(card_list_out, ma_device_type_playback, m.getCardOut());
537 addBufferSizeWidget(*w, m, v);
538 addSampleRateWidget(*w, m, v);
540 con(m, &Model::changed, w, [
this, card_list_in, card_list_out, &m] {
541 setCard(card_list_in, ma_device_type_capture, m.getCardIn());
542 setCard(card_list_out, ma_device_type_playback, m.getCardOut());
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:25