Loading...
Searching...
No Matches
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
11namespace Audio
12{
13#if __has_include(<pa_win_wmme.h>)
14
15class MMEFactory final : public AudioFactory
16{
17 SCORE_CONCRETE("f5950e60-dac3-4254-bfb7-b94c96c679aa")
18public:
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
STL namespace.
Used to access all the application-wide state and structures.
Definition ApplicationContext.hpp:24