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;
49 std::vector<MiniAudioCard> devices;
51 virtual ~GenericMiniAudioFactory()
override { }
53 std::shared_ptr<ossia::miniaudio_context> acquireContext()
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);
67 virtual bool compareDeviceId(
const ma_device_id&
id,
const QString& str)
const noexcept
70 compareDeviceId(
const ma_device_id&
id, std::string_view str)
const noexcept
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;
79 virtual bool followsDefaultDevice() const noexcept {
return false; }
81 static constexpr int DefaultDeviceItemData = -1;
83 int defaultDeviceChannels(ma_device_type tp)
const noexcept
85 for(
const auto& d : devices)
87 if(d.type == tp && d.info.isDefault && d.info.nativeDataFormatCount > 0)
88 return d.info.nativeDataFormats[0].channels;
106 if(followsDefaultDevice() && set.getCardOut().isEmpty())
107 set.setCardOut(
"Default");
108 if(followsDefaultDevice() && set.getCardIn().isEmpty())
109 set.setCardIn(
"Default");
111 const MiniAudioCard* user_in{};
112 const MiniAudioCard* user_out{};
114 const MiniAudioCard* default_in{};
115 const MiniAudioCard* default_out{};
117 const MiniAudioCard* first_viable_in{};
118 const MiniAudioCard* first_viable_out{};
123 for(
auto& d : devices)
127 for(
int i = 0; i < d.info.nativeDataFormatCount; i++)
134 if(d.info.nativeDataFormatCount > 0 && d.info.nativeDataFormats[0].channels > 0)
139 if(d.type & ma_device_type::ma_device_type_capture)
141 if(compareDeviceId(d.id, set.getCardIn()))
144 first_viable_in = &d;
148 if(d.type & ma_device_type::ma_device_type_playback)
150 if(compareDeviceId(d.id, set.getCardOut()))
153 first_viable_out = &d;
160 const MiniAudioCard* card_in = user_in;
162 card_in = default_in;
164 card_in = first_viable_in;
165 if(set.getCardIn().startsWith(
"No device"))
168 const MiniAudioCard* card_out = user_out;
170 card_out = default_out;
172 card_out = first_viable_out;
173 if(set.getCardOut().startsWith(
"No device"))
176 if(followsDefaultDevice() && set.getCardIn() ==
"Default")
178 const MiniAudioCard* ref = default_in ? default_in : first_viable_in;
179 set.setDefaultIn(ref ? ref->info.nativeDataFormats[0].channels : 2);
183 set.setCardIn(deviceIdToString(card_in->id));
184 set.setDefaultIn(card_in->info.nativeDataFormats[0].channels);
188 set.setCardIn(devices.front().name);
192 if(followsDefaultDevice() && set.getCardOut() ==
"Default")
197 const MiniAudioCard* ref = default_out ? default_out : first_viable_out;
200 set.setDefaultOut(ref->info.nativeDataFormats[0].channels);
201 int current_rate = set.getRate();
202 if(
int fmt_count = ref->info.nativeDataFormatCount; fmt_count > 0)
204 auto* fmts = ref->info.nativeDataFormats;
205 if(std::none_of(fmts, fmts + fmt_count, [current_rate](
auto fmt) {
206 return fmt.sampleRate == current_rate;
209 set.setRate(ref->info.nativeDataFormats[0].sampleRate);
215 set.setDefaultOut(2);
220 set.setCardOut(deviceIdToString(card_out->id));
221 set.setDefaultOut(card_out->info.nativeDataFormats[0].channels);
223 int current_rate = set.getRate();
224 if(
int fmt_count = card_out->info.nativeDataFormatCount; fmt_count > 0)
226 auto* fmts = card_out->info.nativeDataFormats;
227 if(std::none_of(fmts, fmts + fmt_count, [current_rate](
auto fmt) {
228 return fmt.sampleRate == current_rate;
231 set.setRate(card_out->info.nativeDataFormats[0].sampleRate);
237 set.setCardOut(devices.front().name);
238 set.setDefaultOut(0);
246 auto ctx = acquireContext();
252 devices[0].name =
"No device";
253 memset(&devices[0].
id, 0,
sizeof(devices[0].
id));
254 memset(&devices[0].info, 0,
sizeof(devices[0].info));
256 setDeviceId(devices[0].
id,
"No device");
258 ma_context_enumerate_devices(
260 [](ma_context* ctx, ma_device_type dev_type,
const ma_device_info* dev_info,
261 void* data) -> ma_bool32 {
262 auto& self = *(GenericMiniAudioFactory*)data;
263 self.devices.emplace_back(dev_info->name, dev_type, dev_info->id);
268 for(std::size_t i = 1; i < devices.size(); i++)
270 auto& dev = devices[i];
271 ma_context_get_device_info(&m_context->context, dev.type, &dev.id, &dev.info);
275 std::shared_ptr<ossia::audio_engine> make_engine(
278 ma_device_id info_in;
279 ma_device_id info_out;
280 memset(&info_in, 0,
sizeof(info_in));
281 memset(&info_out, 0,
sizeof(info_out));
283 auto card_in = set.getCardIn().toStdString();
284 auto card_out = set.getCardOut().toStdString();
286 for(
auto& dev : this->devices)
288 if(compareDeviceId(dev.id, card_in))
290 if(compareDeviceId(dev.id, card_out))
294 return std::make_shared<ossia::miniaudio_engine>(
295 acquireContext(),
"ossia score", info_in, info_out, set.getDefaultIn(),
296 set.getDefaultOut(), set.getRate(), set.getBufferSize());
299 void setCard(QComboBox* combo, ma_device_type tp, QString val)
302 if(followsDefaultDevice() && val ==
"Default")
304 for(
int i = 0; i < combo->count(); i++)
306 if(combo->itemData(i).toInt() == DefaultDeviceItemData)
308 combo->setCurrentIndex(i);
314 auto dev_it = ossia::find_if(devices, [&](
const MiniAudioCard& d) {
315 return compareDeviceId(d.id, val) && d.type == tp;
317 if(dev_it != devices.end())
319 int device_index = std::distance(devices.begin(), dev_it);
320 for(
int i = 0; i < combo->count(); i++)
322 if(combo->itemData(i).toInt() == device_index)
324 combo->setCurrentIndex(i);
330 combo->setCurrentIndex(0);
333 QWidget* make_settings(
358 auto w =
new QWidget{parent};
359 auto lay =
new QFormLayout{w};
361 auto card_list_in =
new QComboBox{w};
362 auto card_list_out =
new QComboBox{w};
365 card_list_in->addItem(devices.front().name +
" (capture)", 0);
366 card_list_out->addItem(devices.front().name +
" (playback)", 0);
369 if(followsDefaultDevice())
371 card_list_in->addItem(
372 QObject::tr(
"Default device"), DefaultDeviceItemData);
373 card_list_out->addItem(
374 QObject::tr(
"Default device"), DefaultDeviceItemData);
378 for(std::size_t i = 1; i < devices.size(); i++)
381 auto& dev = devices[i];
383 if(dev.info.nativeDataFormatCount > 0)
385 if(dev.info.nativeDataFormats[0].channels > 0)
387 if(dev.type == ma_device_type_capture)
389 card_list_in->addItem(dev.name, (
int)i);
392 if(dev.type == ma_device_type_playback)
393 card_list_out->addItem(dev.name, (
int)i);
409 lay->addRow(QObject::tr(
"Capture"), card_list_in);
411 auto update_dev_in = [
this, &m, &m_disp](
const MiniAudioCard& dev) {
412 if(!compareDeviceId(dev.id, m.getCardIn()))
414 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(
415 m, deviceIdToString(dev.id));
416 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
417 m, dev.info.nativeDataFormats[0].channels);
421 auto update_default_in = [
this, &m, &m_disp]() {
422 if(m.getCardIn() !=
"Default")
424 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(
425 m, QStringLiteral(
"Default"));
426 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
427 m, defaultDeviceChannels(ma_device_type_capture));
432 card_list_in, SignalUtils::QComboBox_currentIndexChanged_int(), &v,
433 [
this, card_list_in, update_dev_in, update_default_in](
int i) {
434 int data = card_list_in->itemData(i).toInt();
435 if(data == DefaultDeviceItemData)
438 update_dev_in(devices[data]);
441 if(m.getCardIn().isEmpty())
445 update_dev_in(devices.front());
450 setCard(card_list_in, ma_device_type_capture, m.getCardIn());
455 lay->addRow(QObject::tr(
"Playback"), card_list_out);
457 auto update_dev_out = [
this, &m, &m_disp](
const MiniAudioCard& dev) {
458 if(!compareDeviceId(dev.id, m.getCardOut()))
460 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
461 m, deviceIdToString(dev.id));
462 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
463 m, dev.info.nativeDataFormats[0].channels);
467 auto update_default_out = [
this, &m, &m_disp]() {
468 if(m.getCardOut() !=
"Default")
470 m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
471 m, QStringLiteral(
"Default"));
472 m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
473 m, defaultDeviceChannels(ma_device_type_playback));
478 card_list_out, SignalUtils::QComboBox_currentIndexChanged_int(), &v,
479 [
this, card_list_out, update_dev_out, update_default_out](
int i) {
480 int data = card_list_out->itemData(i).toInt();
481 if(data == DefaultDeviceItemData)
482 update_default_out();
484 update_dev_out(devices[data]);
487 if(m.getCardOut().isEmpty())
491 update_dev_out(devices.front());
496 setCard(card_list_out, ma_device_type_playback, m.getCardOut());
500 addBufferSizeWidget(*w, m, v);
501 addSampleRateWidget(*w, m, v);
503 con(m, &Model::changed, w, [
this, card_list_in, card_list_out, &m] {
504 setCard(card_list_in, ma_device_type_capture, m.getCardIn());
505 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