• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 30507431705

30 Jul 2026 02:04AM UTC coverage: 15.263% (-0.06%) from 15.321%
30507431705

push

github

jcelerier
audio: simplify pretty name for Default device

0 of 2 new or added lines in 1 file covered. (0.0%)

1386 existing lines in 18 files now uncovered.

30460 of 199565 relevant lines covered (15.26%)

1075.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/plugins/score-plugin-audio/Audio/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

21
namespace Audio
22
{
23
struct 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

42
class GenericMiniAudioFactory
43
    : public QObject
44
    , public AudioFactory
45
{
46
  std::shared_ptr<ossia::miniaudio_context> m_context;
47

48
public:
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
  // When true, the factory exposes an extra "Default device" entry mapped to
76
  // the OS default device. The selection is stored as the "Default" sentinel
77
  // and make_engine passes a NULL miniaudio device id, so the backend follows
78
  // the OS default (with automatic rerouting on WASAPI) while running.
UNCOV
79
  virtual bool followsDefaultDevice() const noexcept { return false; }
×
80

81
  static constexpr int DefaultDeviceItemData = -1;
82

UNCOV
83
  int defaultDeviceChannels(ma_device_type tp) const noexcept
×
84
  {
85
    for(const auto& d : devices)
×
86
    {
UNCOV
87
      if(d.type == tp && d.info.isDefault && d.info.nativeDataFormatCount > 0)
×
88
        return d.info.nativeDataFormats[0].channels;
×
89
    }
UNCOV
90
    return 2;
×
91
  }
×
92

93
  void
UNCOV
94
  initialize(Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
×
95
  {
UNCOV
96
    acquireContext();
×
97
    // set.setDefaultIn(2);
98
    // set.setDefaultOut(2);
99
    // set.setRate(48000);
100
    // set.setBufferSize(256);
101
    // set.changed();
102

103
    // Backends that can follow the OS default (WASAPI) default to that mode when
104
    // nothing has been configured yet, so playback/capture track the Windows
105
    // mixer default output/input out of the box.
106
    if(followsDefaultDevice() && set.getCardOut().isEmpty())
×
UNCOV
107
      set.setCardOut("Default");
×
108
    if(followsDefaultDevice() && set.getCardIn().isEmpty())
×
UNCOV
109
      set.setCardIn("Default");
×
110

UNCOV
111
    const MiniAudioCard* user_in{};
×
UNCOV
112
    const MiniAudioCard* user_out{};
×
113

UNCOV
114
    const MiniAudioCard* default_in{};
×
115
    const MiniAudioCard* default_out{};
×
116

UNCOV
117
    const MiniAudioCard* first_viable_in{};
×
118
    const MiniAudioCard* first_viable_out{};
×
119

120
    // qDebug(Q_FUNC_INFO);
121
    // qDebug() << "looking for in:" << set.getCardIn();
122
    // qDebug() << "looking for out:" << set.getCardOut();
UNCOV
123
    for(auto& d : devices)
×
124
    {
125
      // qDebug() << "has device : " << d.info.name << d.info.nativeDataFormatCount;
126
      {
127
        for(int i = 0; i < d.info.nativeDataFormatCount; i++)
×
128
        {
129
          // auto fmt = d.info.nativeDataFormats[i];
130
          // qDebug() << "Format: " << magic_enum::enum_name(fmt.format) << fmt.channels
131
          //          << fmt.flags << fmt.sampleRate;
UNCOV
132
        }
×
133
      }
134
      if(d.info.nativeDataFormatCount > 0 && d.info.nativeDataFormats[0].channels > 0)
×
135
      {
136
        // qDebug() << "device id:" << d.id.coreaudio
137
        //          << d.info.nativeDataFormats[0].channels;
138

139
        if(d.type & ma_device_type::ma_device_type_capture)
×
140
        {
UNCOV
141
          if(compareDeviceId(d.id, set.getCardIn()))
×
142
            user_in = &d;
×
143
          //  qDebug() << "capture" << d.info.isDefault;
144
          first_viable_in = &d;
×
145
          if(d.info.isDefault)
×
146
            default_in = &d;
×
147
        }
×
148
        if(d.type & ma_device_type::ma_device_type_playback)
×
149
        {
150
          if(compareDeviceId(d.id, set.getCardOut()))
×
UNCOV
151
            user_out = &d;
×
152
          // qDebug() << "playback" << d.info.isDefault;
153
          first_viable_out = &d;
×
154
          if(d.info.isDefault)
×
155
            default_out = &d;
×
UNCOV
156
        }
×
UNCOV
157
      }
×
158
    }
159

160
    const MiniAudioCard* card_in = user_in;
×
UNCOV
161
    if(!card_in)
×
UNCOV
162
      card_in = default_in;
×
163
    if(!card_in)
×
UNCOV
164
      card_in = first_viable_in;
×
UNCOV
165
    if(set.getCardIn().startsWith("No device"))
×
166
      card_in = nullptr;
×
167

UNCOV
168
    const MiniAudioCard* card_out = user_out;
×
169
    if(!card_out)
×
170
      card_out = default_out;
×
UNCOV
171
    if(!card_out)
×
172
      card_out = first_viable_out;
×
173
    if(set.getCardOut().startsWith("No device"))
×
174
      card_out = nullptr;
×
175

UNCOV
176
    if(followsDefaultDevice() && set.getCardIn() == "Default")
×
177
    {
178
      const MiniAudioCard* ref = default_in ? default_in : first_viable_in;
×
179
      set.setDefaultIn(ref ? ref->info.nativeDataFormats[0].channels : 2);
×
180
    }
×
UNCOV
181
    else if(card_in)
×
182
    {
UNCOV
183
      set.setCardIn(deviceIdToString(card_in->id));
×
184
      set.setDefaultIn(card_in->info.nativeDataFormats[0].channels);
×
185
    }
×
186
    else
187
    {
188
      set.setCardIn(devices.front().name);
×
189
      set.setDefaultIn(0);
×
190
    }
191

UNCOV
192
    if(followsDefaultDevice() && set.getCardOut() == "Default")
×
193
    {
194
      // Keep the "Default device" sentinel so make_engine passes a NULL device
195
      // id and miniaudio follows the OS default output. Derive channels/rate
196
      // from the current OS default device.
UNCOV
197
      const MiniAudioCard* ref = default_out ? default_out : first_viable_out;
×
198
      if(ref)
×
199
      {
200
        set.setDefaultOut(ref->info.nativeDataFormats[0].channels);
×
201
        int current_rate = set.getRate();
×
UNCOV
202
        if(int fmt_count = ref->info.nativeDataFormatCount; fmt_count > 0)
×
203
        {
UNCOV
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;
×
207
          }))
208
          {
209
            set.setRate(ref->info.nativeDataFormats[0].sampleRate);
×
210
          }
×
211
        }
×
UNCOV
212
      }
×
213
      else
214
      {
215
        set.setDefaultOut(2);
×
216
      }
217
    }
×
218
    else if(card_out)
×
219
    {
220
      set.setCardOut(deviceIdToString(card_out->id));
×
UNCOV
221
      set.setDefaultOut(card_out->info.nativeDataFormats[0].channels);
×
222

UNCOV
223
      int current_rate = set.getRate();
×
UNCOV
224
      if(int fmt_count = card_out->info.nativeDataFormatCount; fmt_count > 0)
×
225
      {
UNCOV
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;
×
229
        }))
230
        {
231
          set.setRate(card_out->info.nativeDataFormats[0].sampleRate);
×
UNCOV
232
        }
×
233
      }
×
UNCOV
234
    }
×
235
    else
236
    {
237
      set.setCardOut(devices.front().name);
×
238
      set.setDefaultOut(0);
×
239
    }
240

241
    set.changed();
×
242
  }
×
243

244
  void rescan()
×
245
  {
246
    auto ctx = acquireContext();
×
UNCOV
247
    if(!ctx)
×
UNCOV
248
      return;
×
UNCOV
249
    devices.clear();
×
250

UNCOV
251
    devices.resize(1);
×
252
    devices[0].name = "No device";
×
UNCOV
253
    memset(&devices[0].id, 0, sizeof(devices[0].id));
×
254
    memset(&devices[0].info, 0, sizeof(devices[0].info));
×
255

256
    setDeviceId(devices[0].id, "No device");
×
257

UNCOV
258
    ma_context_enumerate_devices(
×
UNCOV
259
        &m_context->context,
×
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);
×
UNCOV
264
      return 1;
×
265
    },
266
        this);
267

268
    for(std::size_t i = 1; i < devices.size(); i++)
×
269
    {
UNCOV
270
      auto& dev = devices[i];
×
271
      ma_context_get_device_info(&m_context->context, dev.type, &dev.id, &dev.info);
×
UNCOV
272
    }
×
UNCOV
273
  }
×
274

275
  std::shared_ptr<ossia::audio_engine> make_engine(
×
276
      const Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
277
  {
278
    ma_device_id info_in;
279
    ma_device_id info_out;
UNCOV
280
    memset(&info_in, 0, sizeof(info_in));
×
UNCOV
281
    memset(&info_out, 0, sizeof(info_out));
×
282

UNCOV
283
    auto card_in = set.getCardIn().toStdString();
×
UNCOV
284
    auto card_out = set.getCardOut().toStdString();
×
285

UNCOV
286
    for(auto& dev : this->devices)
×
287
    {
UNCOV
288
      if(compareDeviceId(dev.id, card_in))
×
UNCOV
289
        info_in = dev.id;
×
UNCOV
290
      if(compareDeviceId(dev.id, card_out))
×
UNCOV
291
        info_out = dev.id;
×
292
    }
293

UNCOV
294
    return std::make_shared<ossia::miniaudio_engine>(
×
UNCOV
295
        acquireContext(), "ossia score", info_in, info_out, set.getDefaultIn(),
×
296
        set.getDefaultOut(), set.getRate(), set.getBufferSize());
×
297
  }
×
298

299
  void setCard(QComboBox* combo, ma_device_type tp, QString val)
×
300
  {
301
    // "Default device" sentinel: select the dedicated combo entry.
UNCOV
302
    if(followsDefaultDevice() && val == "Default")
×
303
    {
304
      for(int i = 0; i < combo->count(); i++)
×
305
      {
UNCOV
306
        if(combo->itemData(i).toInt() == DefaultDeviceItemData)
×
307
        {
UNCOV
308
          combo->setCurrentIndex(i);
×
UNCOV
309
          return;
×
310
        }
UNCOV
311
      }
×
312
    }
×
313

314
    auto dev_it = ossia::find_if(devices, [&](const MiniAudioCard& d) {
×
UNCOV
315
      return compareDeviceId(d.id, val) && d.type == tp;
×
316
    });
UNCOV
317
    if(dev_it != devices.end())
×
318
    {
UNCOV
319
      int device_index = std::distance(devices.begin(), dev_it);
×
320
      for(int i = 0; i < combo->count(); i++)
×
321
      {
322
        if(combo->itemData(i).toInt() == device_index)
×
323
        {
324
          combo->setCurrentIndex(i);
×
325
          return;
×
326
        }
UNCOV
327
      }
×
UNCOV
328
    }
×
329

UNCOV
330
    combo->setCurrentIndex(0);
×
UNCOV
331
  }
×
332

UNCOV
333
  QWidget* make_settings(
×
334
      Audio::Settings::Model& m, Audio::Settings::View& v,
335
      score::SettingsCommandDispatcher& m_disp, QWidget* parent) override
336
  {
UNCOV
337
    acquireContext();
×
338
    /* Not useful: hotplug does not seem to work
339
    struct UpdateWhenVisible : public QWidget
340
    {
341
    public:
342
      using QWidget::QWidget;
343

344
      bool event(QEvent* e) override
345
      {
346
        if(e->type() == QEvent::Show)
347
        {
348
          auto& self = score::AppContext().interfaces<Audio::AudioFactoryList>();
349
          auto cf = static_cast<CoreAudioFactory*>(
350
              self.get(CoreAudioFactory::static_concreteKey()));
351
          cf->rescan();
352
        }
353
        return QWidget::event(e);
354
      }
355
    };
356
    */
357

UNCOV
358
    auto w = new QWidget{parent};
×
359
    auto lay = new QFormLayout{w};
×
360

361
    auto card_list_in = new QComboBox{w};
×
362
    auto card_list_out = new QComboBox{w};
×
363

364
    // Disabled case
UNCOV
365
    card_list_in->addItem(devices.front().name + " (capture)", 0);
×
366
    card_list_out->addItem(devices.front().name + " (playback)", 0);
×
367

368
    // Optional "follow the OS default device" entry (e.g. WASAPI).
UNCOV
369
    if(followsDefaultDevice())
×
370
    {
371
      card_list_in->addItem(
×
NEW
372
          QObject::tr("Default device"), DefaultDeviceItemData);
×
373
      card_list_out->addItem(
×
NEW
374
          QObject::tr("Default device"), DefaultDeviceItemData);
×
UNCOV
375
    }
×
376

377
    // qDebug() << Q_FUNC_INFO << "Devices: ";
378
    for(std::size_t i = 1; i < devices.size(); i++)
×
379
    {
380

381
      auto& dev = devices[i];
×
382
      // qDebug() << i << dev.info.name << dev.id.coreaudio << dev.type;
383
      if(dev.info.nativeDataFormatCount > 0)
×
384
      {
385
        if(dev.info.nativeDataFormats[0].channels > 0)
×
386
        {
387
          if(dev.type == ma_device_type_capture)
×
388
          {
UNCOV
389
            card_list_in->addItem(dev.name, (int)i);
×
390
            // qDebug() << "adding to card list in" << i << dev.name;
UNCOV
391
          }
×
392
          if(dev.type == ma_device_type_playback)
×
UNCOV
393
            card_list_out->addItem(dev.name, (int)i);
×
394
        }
×
395
      }
×
396
    }
×
397

398
    /*
399
    for(int i = 0; i < card_list_in->count(); i++)
400
      qDebug() << Q_FUNC_INFO << "card in " << i << card_list_in->itemText(i)
401
               << card_list_in->itemData(i);
402
    for(int i = 0; i < card_list_out->count(); i++)
403
      qDebug() << Q_FUNC_INFO << "card out " << i << card_list_out->itemText(i)
404
               << card_list_out->itemData(i);
405
               */
406
    using Model = Audio::Settings::Model;
407

408
    {
409
      lay->addRow(QObject::tr("Capture"), card_list_in);
×
410

411
      auto update_dev_in = [this, &m, &m_disp](const MiniAudioCard& dev) {
×
UNCOV
412
        if(!compareDeviceId(dev.id, m.getCardIn()))
×
413
        {
UNCOV
414
          m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(
×
UNCOV
415
              m, deviceIdToString(dev.id));
×
UNCOV
416
          m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
×
UNCOV
417
              m, dev.info.nativeDataFormats[0].channels);
×
UNCOV
418
        }
×
UNCOV
419
      };
×
420

UNCOV
421
      auto update_default_in = [this, &m, &m_disp]() {
×
UNCOV
422
        if(m.getCardIn() != "Default")
×
423
        {
UNCOV
424
          m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(
×
UNCOV
425
              m, QStringLiteral("Default"));
×
UNCOV
426
          m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
×
UNCOV
427
              m, defaultDeviceChannels(ma_device_type_capture));
×
UNCOV
428
        }
×
UNCOV
429
      };
×
430

UNCOV
431
      QObject::connect(
×
UNCOV
432
          card_list_in, SignalUtils::QComboBox_currentIndexChanged_int(), &v,
×
UNCOV
433
          [this, card_list_in, update_dev_in, update_default_in](int i) {
×
UNCOV
434
        int data = card_list_in->itemData(i).toInt();
×
UNCOV
435
        if(data == DefaultDeviceItemData)
×
UNCOV
436
          update_default_in();
×
437
        else
UNCOV
438
          update_dev_in(devices[data]);
×
UNCOV
439
      });
×
440

UNCOV
441
      if(m.getCardIn().isEmpty())
×
442
      {
UNCOV
443
        if(!devices.empty())
×
444
        {
UNCOV
445
          update_dev_in(devices.front());
×
UNCOV
446
        }
×
UNCOV
447
      }
×
448
      else
449
      {
UNCOV
450
        setCard(card_list_in, ma_device_type_capture, m.getCardIn());
×
451
      }
452
    }
453

454
    {
UNCOV
455
      lay->addRow(QObject::tr("Playback"), card_list_out);
×
456

UNCOV
457
      auto update_dev_out = [this, &m, &m_disp](const MiniAudioCard& dev) {
×
UNCOV
458
        if(!compareDeviceId(dev.id, m.getCardOut()))
×
459
        {
UNCOV
460
          m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
×
UNCOV
461
              m, deviceIdToString(dev.id));
×
UNCOV
462
          m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
×
UNCOV
463
              m, dev.info.nativeDataFormats[0].channels);
×
UNCOV
464
        }
×
UNCOV
465
      };
×
466

UNCOV
467
      auto update_default_out = [this, &m, &m_disp]() {
×
UNCOV
468
        if(m.getCardOut() != "Default")
×
469
        {
UNCOV
470
          m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
×
UNCOV
471
              m, QStringLiteral("Default"));
×
UNCOV
472
          m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
×
UNCOV
473
              m, defaultDeviceChannels(ma_device_type_playback));
×
UNCOV
474
        }
×
UNCOV
475
      };
×
476

UNCOV
477
      QObject::connect(
×
UNCOV
478
          card_list_out, SignalUtils::QComboBox_currentIndexChanged_int(), &v,
×
UNCOV
479
          [this, card_list_out, update_dev_out, update_default_out](int i) {
×
UNCOV
480
        int data = card_list_out->itemData(i).toInt();
×
UNCOV
481
        if(data == DefaultDeviceItemData)
×
UNCOV
482
          update_default_out();
×
483
        else
UNCOV
484
          update_dev_out(devices[data]);
×
UNCOV
485
      });
×
486

UNCOV
487
      if(m.getCardOut().isEmpty())
×
488
      {
UNCOV
489
        if(!devices.empty())
×
490
        {
UNCOV
491
          update_dev_out(devices.front());
×
UNCOV
492
        }
×
UNCOV
493
      }
×
494
      else
495
      {
UNCOV
496
        setCard(card_list_out, ma_device_type_playback, m.getCardOut());
×
497
      }
498
    }
499

UNCOV
500
    addBufferSizeWidget(*w, m, v);
×
UNCOV
501
    addSampleRateWidget(*w, m, v);
×
502

UNCOV
503
    con(m, &Model::changed, w, [this, card_list_in, card_list_out, &m] {
×
UNCOV
504
      setCard(card_list_in, ma_device_type_capture, m.getCardIn());
×
UNCOV
505
      setCard(card_list_out, ma_device_type_playback, m.getCardOut());
×
UNCOV
506
    });
×
UNCOV
507
    return w;
×
UNCOV
508
  }
×
509
};
510

511
}
512

513
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc