WASAPIPortAudioInterface.hpp
1 #pragma once
2 #include <Audio/AudioInterface.hpp>
3 #include <Audio/PortAudioInterface.hpp>
4 #include <Audio/Settings/Model.hpp>
5 
6 #include <score/widgets/SignalUtils.hpp>
7 
8 #include <QComboBox>
9 #include <QFormLayout>
10 
11 namespace Audio
12 {
13 #if __has_include(<pa_win_wasapi.h>)
14 class WASAPIFactory final
15  : public QObject
16  , public AudioFactory
17 {
18  SCORE_CONCRETE("afcd9c64-0367-4fa1-b2bb-ee65b1c5e5a7")
19 public:
20  std::vector<PortAudioCard> devices;
21 
22  WASAPIFactory() { rescan(); }
23 
24  ~WASAPIFactory() override { }
25  bool available() const noexcept override { return true; }
26  void
27  initialize(Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
28  {
29  auto device_in = ossia::find_if(devices, [&](const PortAudioCard& dev) {
30  return dev.raw_name == set.getCardIn() && dev.hostapi != paInDevelopment;
31  });
32  auto device_out = ossia::find_if(devices, [&](const PortAudioCard& dev) {
33  return dev.raw_name == set.getCardOut() && dev.hostapi != paInDevelopment;
34  });
35 
36  if(device_in == devices.end() || device_out == devices.end())
37  {
38  set.setCardIn(devices.back().raw_name);
39  set.setCardOut(devices.back().raw_name);
40  set.setDefaultIn(devices.back().inputChan);
41  set.setDefaultOut(devices.back().outputChan);
42  set.setRate(devices.back().rate);
43 
44  set.changed();
45  }
46  else
47  {
48  if(device_out != devices.end())
49  {
50  set.setDefaultIn(device_out->inputChan);
51  set.setDefaultOut(device_out->outputChan);
52  set.setRate(device_out->rate);
53 
54  set.changed();
55  }
56  }
57  }
58 
59  void rescan()
60  {
61  devices.clear();
62  PortAudioScope portaudio;
63 
64  devices.push_back(PortAudioCard{{}, {}, QObject::tr("No device"), -1, 0, 0, {}});
65  for(int i = 0; i < Pa_GetHostApiCount(); i++)
66  {
67  auto hostapi = Pa_GetHostApiInfo(i);
68  if(hostapi->type == PaHostApiTypeId::paWASAPI)
69  {
70  for(int card = 0; card < hostapi->deviceCount; card++)
71  {
72  auto dev_idx = Pa_HostApiDeviceIndexToDeviceIndex(i, card);
73  auto dev = Pa_GetDeviceInfo(dev_idx);
74  if(dev->maxOutputChannels > 0)
75  {
76  auto raw_name = QString::fromUtf8(Pa_GetDeviceInfo(dev_idx)->name);
77  devices.push_back(PortAudioCard{
78  "WASAPI", raw_name, raw_name, dev_idx, dev->maxInputChannels,
79  dev->maxOutputChannels, hostapi->type, dev->defaultSampleRate});
80  }
81  }
82  }
83  }
84  }
85 
86  QString prettyName() const override { return QObject::tr("WASAPI"); }
87  std::shared_ptr<ossia::audio_engine> make_engine(
88  const Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
89  {
90  return std::make_shared<ossia::portaudio_engine>(
91  "ossia score", set.getCardIn().toStdString(), set.getCardOut().toStdString(),
92  set.getDefaultIn(), set.getDefaultOut(), set.getRate(), set.getBufferSize(),
93  paWASAPI);
94  }
95 
96  void setCard(QComboBox* combo, QString val)
97  {
98  auto dev_it = ossia::find_if(
99  devices, [&](const PortAudioCard& d) { return d.raw_name == val; });
100  if(dev_it != devices.end())
101  {
102  combo->setCurrentIndex(dev_it->out_index);
103  }
104  }
105 
106  QWidget* make_settings(
108  score::SettingsCommandDispatcher& m_disp, QWidget* parent) override
109  {
110  auto w = new QWidget{parent};
111  auto lay = new QFormLayout{w};
112 
113  auto card_list = new QComboBox{w};
114 
115  // Disabled case
116  card_list->addItem(devices.front().pretty_name, 0);
117  devices.front().out_index = 0;
118 
119  // Normal devices
120  for(std::size_t i = 1; i < devices.size(); i++)
121  {
122  auto& card = devices[i];
123  card_list->addItem(card.pretty_name, (int)i);
124  card.out_index = card_list->count() - 1;
125  }
126 
127  using Model = Audio::Settings::Model;
128 
129  {
130  lay->addRow(QObject::tr("Device"), card_list);
131 
132  auto update_dev = [=, &m, &m_disp](const PortAudioCard& dev) {
133  if(dev.raw_name != m.getCardOut())
134  {
135  m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(m, dev.raw_name);
136  m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
137  m, dev.raw_name);
138  m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
139  m, dev.inputChan);
140  m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
141  m, dev.outputChan);
142  }
143  };
144 
145  QObject::connect(
146  card_list, SignalUtils::QComboBox_currentIndexChanged_int(), &v, [=](int i) {
147  auto& device = devices[card_list->itemData(i).toInt()];
148  update_dev(device);
149  });
150 
151  if(m.getCardOut().isEmpty())
152  {
153  if(!devices.empty())
154  {
155  update_dev(devices.front());
156  }
157  }
158  else
159  {
160  setCard(card_list, m.getCardOut());
161  }
162  }
163 
164  addBufferSizeWidget(*w, m, v);
165  addSampleRateWidget(*w, m, v);
166 
167  con(m, &Model::changed, w, [=, &m] { setCard(card_list, m.getCardOut()); });
168  return w;
169  }
170 };
171 #endif
172 }
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