MMEPortAudioInterface.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_wmme.h>)
14 
15 class MMEFactory final : public AudioFactory
16 {
17  SCORE_CONCRETE("f5950e60-dac3-4254-bfb7-b94c96c679aa")
18 public:
19  std::vector<PortAudioCard> devices;
20 
21  MMEFactory() { rescan(); }
22 
23  ~MMEFactory() override { }
24  bool available() const noexcept override { return true; }
25  void
26  initialize(Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
27  {
28  auto device_in = ossia::find_if(devices, [&](const PortAudioCard& dev) {
29  return dev.raw_name == set.getCardIn() && dev.hostapi != paInDevelopment;
30  });
31  auto device_out = ossia::find_if(devices, [&](const PortAudioCard& dev) {
32  return dev.raw_name == set.getCardOut() && dev.hostapi != paInDevelopment;
33  });
34 
35  if(device_in == devices.end() || device_out == devices.end())
36  {
37  auto device_in = ossia::find_if(devices, [&](const PortAudioCard& dev) {
38  return dev.raw_name == "Microsoft Sound Mapper - Input";
39  });
40  auto device_out = ossia::find_if(devices, [&](const PortAudioCard& dev) {
41  return dev.raw_name == "Microsoft Sound Mapper - Output";
42  });
43 
44  if(device_in != devices.end() && device_out != devices.end())
45  {
46  set.setCardIn(device_in->raw_name);
47  set.setCardOut(device_out->raw_name);
48  set.setDefaultIn(device_in->inputChan);
49  set.setDefaultOut(device_out->outputChan);
50  set.setRate(device_out->rate);
51  }
52  else
53  {
54  auto device_in = ossia::find_if(
55  devices, [&](const PortAudioCard& dev) { return dev.inputChan > 0; });
56  auto device_out = ossia::find_if(
57  devices, [&](const PortAudioCard& dev) { return dev.outputChan > 0; });
58  if(device_in != devices.end())
59  {
60  set.setCardIn(device_in->raw_name);
61  set.setDefaultIn(device_in->inputChan);
62  }
63  else
64  {
65  set.setCardIn("");
66  set.setDefaultIn(0);
67  }
68 
69  if(device_out != devices.end())
70  {
71  set.setCardOut(device_out->raw_name);
72  set.setDefaultOut(device_out->outputChan);
73  set.setRate(device_out->rate);
74  }
75  else
76  {
77  set.setCardOut("");
78  set.setDefaultOut(0);
79  set.setRate(44100);
80  }
81  }
82 
83  set.changed();
84  }
85  else
86  {
87  if(device_out != devices.end())
88  {
89  set.setDefaultIn(device_out->inputChan);
90  set.setDefaultOut(device_out->outputChan);
91  set.setRate(device_out->rate);
92 
93  set.changed();
94  }
95  }
96  }
97 
98  void rescan()
99  {
100  devices.clear();
101 
102  PortAudioScope portaudio;
103 
104  devices.push_back(PortAudioCard{{}, {}, QObject::tr("No device"), -1, 0, 0, {}});
105  for(int i = 0; i < Pa_GetHostApiCount(); i++)
106  {
107  auto hostapi = Pa_GetHostApiInfo(i);
108  if(hostapi->type == PaHostApiTypeId::paMME)
109  {
110  for(int card = 0; card < hostapi->deviceCount; card++)
111  {
112  auto dev_idx = Pa_HostApiDeviceIndexToDeviceIndex(i, card);
113  auto dev = Pa_GetDeviceInfo(dev_idx);
114  auto raw_name = QString::fromUtf8(Pa_GetDeviceInfo(dev_idx)->name);
115 
116  devices.push_back(PortAudioCard{
117  "MME", raw_name, raw_name, dev_idx, dev->maxInputChannels,
118  dev->maxOutputChannels, hostapi->type, dev->defaultSampleRate});
119  }
120  break;
121  }
122  }
123  }
124 
125  QString prettyName() const override { return QObject::tr("MME"); }
126  std::shared_ptr<ossia::audio_engine> make_engine(
127  const Audio::Settings::Model& set, const score::ApplicationContext& ctx) override
128  {
129  return std::make_shared<ossia::portaudio_engine>(
130  "ossia score", set.getCardIn().toStdString(), set.getCardOut().toStdString(),
131  set.getDefaultIn(), set.getDefaultOut(), set.getRate(), 1024, paMME);
132  }
133 
134  void setCardIn(QComboBox* combo, QString val)
135  {
136  auto dev_it = ossia::find_if(devices, [&](const PortAudioCard& d) {
137  return d.raw_name == val && d.inputChan > 0;
138  });
139  if(dev_it != devices.end())
140  {
141  combo->setCurrentIndex(dev_it->in_index);
142  }
143  }
144  void setCardOut(QComboBox* combo, QString val)
145  {
146  auto dev_it = ossia::find_if(devices, [&](const PortAudioCard& d) {
147  return d.raw_name == val && d.outputChan > 0;
148  });
149  if(dev_it != devices.end())
150  {
151  combo->setCurrentIndex(dev_it->out_index);
152  }
153  }
154 
155  void updateSampleRates(
156  QComboBox* rate, const PortAudioCard& input, const PortAudioCard& output)
157  {
158  PortAudioScope scope;
159  rate->clear();
160  for(int sr : {44100, 48000, 88200, 96000, 192000})
161  {
162  PaStreamParameters iParams{}, oParams{};
163  iParams.device = input.dev_idx;
164  iParams.channelCount = input.inputChan;
165  iParams.sampleFormat = paFloat32;
166  iParams.suggestedLatency = 0.02;
167 
168  oParams.device = output.dev_idx;
169  oParams.channelCount = output.outputChan;
170  oParams.sampleFormat = paFloat32;
171  oParams.suggestedLatency = 0.02;
172 
173  if(auto err = Pa_IsFormatSupported(nullptr, /*&iParams, */ &oParams, sr);
174  err == paFormatIsSupported)
175  {
176  rate->addItem(QString::number(sr));
177  }
178  else
179  {
180  qDebug() << "MME: samplerate errpr " << err << Pa_GetErrorText(err);
181  }
182  }
183  }
184 
185  QWidget* make_settings(
187  score::SettingsCommandDispatcher& m_disp, QWidget* parent) override
188  {
189  auto w = new QWidget{parent};
190  auto lay = new QFormLayout{w};
191 
192  auto card_in = new QComboBox{w};
193  auto card_out = new QComboBox{w};
194 
195  auto rate = new QComboBox{w};
196  // auto buffersize = new QComboBox{w};
197 
198  auto updateRates = [=] {
199  updateSampleRates(
200  rate, devices[card_in->itemData(card_in->currentIndex()).toInt()],
201  devices[card_out->itemData(card_in->currentIndex()).toInt()]);
202  };
203 
204  // Disabled case
205  card_in->addItem(devices.front().pretty_name, 0);
206  card_out->addItem(devices.front().pretty_name, 0);
207  devices.front().in_index = 0;
208  devices.front().out_index = 0;
209 
210  // Normal devices
211  for(std::size_t i = 1; i < devices.size(); i++)
212  {
213  auto& card = devices[i];
214 
215  if(card.inputChan > 0)
216  {
217  card_in->addItem(card.pretty_name, (int)i);
218  card.in_index = card_in->count() - 1;
219  }
220  if(card.outputChan > 0)
221  {
222  card_out->addItem(card.pretty_name, (int)i);
223  card.out_index = card_out->count() - 1;
224  }
225  }
226 
227  using Model = Audio::Settings::Model;
228 
229  {
230  lay->addRow(QObject::tr("Input device"), card_in);
231 
232  auto update_dev = [=, &m, &m_disp](const PortAudioCard& dev) {
233  if(dev.raw_name != m.getCardIn())
234  {
235  m_disp.submitDeferredCommand<Audio::Settings::SetModelCardIn>(m, dev.raw_name);
236  m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultIn>(
237  m, dev.inputChan);
238  if(dev.hostapi != PaHostApiTypeId::paMME)
239  {
240  if(dev.out_index != -1 && dev.out_index != card_out->currentIndex())
241  card_out->setCurrentIndex(dev.out_index);
242  }
243  }
244  };
245 
246  QObject::connect(
247  card_in, SignalUtils::QComboBox_currentIndexChanged_int(), &v, [=](int i) {
248  update_dev(devices[card_in->itemData(i).toInt()]);
249  updateRates();
250  });
251 
252  if(m.getCardIn().isEmpty())
253  {
254  auto default_in = Pa_GetDefaultInputDevice();
255 
256  for(auto& v : devices)
257  {
258  if(v.dev_idx == default_in)
259  {
260  update_dev(v);
261  break;
262  }
263  }
264  }
265  else
266  {
267  setCardIn(card_in, m.getCardIn());
268  }
269 
270  updateRates();
271  }
272 
273  {
274  lay->addRow(QObject::tr("Output device"), card_out);
275 
276  auto update_dev = [=, &m, &m_disp](const PortAudioCard& dev) {
277  if(dev.raw_name != m.getCardOut())
278  {
279  m_disp.submitDeferredCommand<Audio::Settings::SetModelCardOut>(
280  m, dev.raw_name);
281  m_disp.submitDeferredCommand<Audio::Settings::SetModelDefaultOut>(
282  m, dev.outputChan);
283  if(dev.hostapi != PaHostApiTypeId::paMME)
284  {
285  if(dev.in_index != -1 && dev.in_index != card_in->currentIndex())
286  card_in->setCurrentIndex(dev.in_index);
287  }
288  }
289  };
290 
291  QObject::connect(
292  card_out, SignalUtils::QComboBox_currentIndexChanged_int(), &v, [=](int i) {
293  update_dev(devices[card_out->itemData(i).toInt()]);
294  updateRates();
295  });
296 
297  if(m.getCardOut().isEmpty())
298  {
299  auto default_out = Pa_GetDefaultOutputDevice();
300  for(auto& v : devices)
301  {
302  if(v.dev_idx == default_out)
303  {
304  update_dev(v);
305  break;
306  }
307  }
308  }
309  else
310  {
311  setCardOut(card_out, m.getCardOut());
312  }
313 
314  updateRates();
315  }
316 
317  {
318  lay->addRow(QObject::tr("Sample rate"), rate);
319 
320  updateRates();
321  }
322  /*
323  {
324  lay->addRow(QObject::tr("Buffer size"), buffersize);
325 
326  updateRates();
327  }
328 */
329  con(m, &Model::changed, w, [=, &m] {
330  setCardIn(card_in, m.getCardIn());
331  setCardOut(card_out, m.getCardOut());
332  });
333  return w;
334  }
335 };
336 #endif
337 }
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