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