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