OSSIA
Open Scenario System for Interactive Application
portaudio_protocol.hpp
1 #pragma once
2 #include <ossia/detail/config.hpp>
3 
4 #if defined(OSSIA_ENABLE_PORTAUDIO)
5 #if __has_include(<portaudio.h>)
6 #include <ossia/audio/audio_engine.hpp>
7 #include <ossia/detail/thread.hpp>
8 
9 #include <portaudio.h>
10 
11 #include <iostream>
12 
13 #define OSSIA_AUDIO_PORTAUDIO 1
14 
15 namespace ossia
16 {
17 class portaudio_engine final : public audio_engine
18 {
19 public:
20  portaudio_engine(
21  std::string name, std::string card_in, std::string card_out, int inputs,
22  int outputs, int rate, int bs, PaHostApiTypeId hostApi)
23  {
24  if(Pa_Initialize() != paNoError)
25  throw std::runtime_error("Audio error");
26 
27  int card_in_idx = paNoDevice;
28  int card_out_idx = paNoDevice;
29 
30  auto hostApiIndex = Pa_HostApiTypeIdToHostApiIndex(hostApi);
31 
32  for(int i = 0; i < Pa_GetDeviceCount(); i++)
33  {
34  auto info = Pa_GetDeviceInfo(i);
35  if(info->hostApi != hostApiIndex && hostApiIndex != paInDevelopment)
36  continue;
37 
38  auto raw_name = info->name;
39  // std::cerr << " - device " << i << " has name: " << raw_name << "\n";
40  if(raw_name == card_in && info->maxInputChannels > 0)
41  {
42  // std::cerr << " its the input" << inputs << " " <<
43  // info->maxInputChannels << "\n";
44  card_in_idx = i;
45  }
46  if(raw_name == card_out && info->maxOutputChannels > 0)
47  {
48  // std::cerr << " its the output" << outputs << " " <<
49  // info->maxOutputChannels << "\n";
50  card_out_idx = i;
51  }
52  if(card_in_idx != paNoDevice && card_out_idx != paNoDevice)
53  break;
54  }
55 
56  auto devInInfo = Pa_GetDeviceInfo(card_in_idx);
57  if(!devInInfo)
58  {
59  std::cerr << "Audio error: no input device" << std::endl;
60  inputs = 0;
61  }
62  else
63  {
64  inputs = std::min(inputs, devInInfo->maxInputChannels);
65  }
66 
67  auto devOutInfo = Pa_GetDeviceInfo(card_out_idx);
68  if(!devOutInfo)
69  {
70  std::cerr << "Audio error: no output device" << std::endl;
71  outputs = 0;
72  }
73  else
74  {
75  outputs = std::min(outputs, devOutInfo->maxOutputChannels);
76  }
77 
78  PaStreamParameters inputParameters;
79  inputParameters.device = card_in_idx;
80  inputParameters.channelCount = inputs;
81  inputParameters.sampleFormat = paFloat32 | paNonInterleaved;
82  inputParameters.suggestedLatency = 0.01;
83  inputParameters.hostApiSpecificStreamInfo = nullptr;
84 
85  PaStreamParameters outputParameters;
86  outputParameters.device = card_out_idx;
87  outputParameters.channelCount = outputs;
88  outputParameters.sampleFormat = paFloat32 | paNonInterleaved;
89  outputParameters.suggestedLatency = 0.01;
90  outputParameters.hostApiSpecificStreamInfo = nullptr;
91 
92  PaStreamParameters* actualInput{};
93  if(card_in_idx != paNoDevice && inputs > 0)
94  actualInput = &inputParameters;
95  PaStreamParameters* actualOutput{};
96  if(card_out_idx != paNoDevice && outputs > 0)
97  actualOutput = &outputParameters;
98  /*
99  std::cerr << "input: \n"
100  << bool(actualInput) << " "
101  << card_in_idx<< " "
102  << inputs << "\n";
103  std::cerr << "output: \n"
104  << bool(actualOutput) << " "
105  << card_out_idx<< " "
106  << outputs << "\n";
107  */
108  PaStream* stream;
109  auto ec = Pa_OpenStream(
110  &stream, actualInput, actualOutput, rate,
111  bs, // paFramesPerBufferUnspecified,
112  paNoFlag, &PortAudioCallback, this);
113  m_stream.store(stream);
114  if(ec == PaErrorCode::paNoError)
115  {
116  ec = Pa_StartStream(stream);
117 
118  if(ec != PaErrorCode::paNoError)
119  {
120  std::cerr << "Error while starting audio stream: " << Pa_GetErrorText(ec)
121  << std::endl;
122  Pa_CloseStream(stream);
123  m_stream.store(nullptr);
124  }
125  else
126  {
127  auto info = Pa_GetStreamInfo(stream);
128  this->effective_sample_rate = info->sampleRate;
129  this->effective_buffer_size = bs;
130  this->effective_inputs = actualInput ? actualInput->channelCount : 0;
131  this->effective_outputs = actualOutput ? actualOutput->channelCount : 0;
132  }
133  }
134  else
135  {
136  std::cerr << "Error while opening audio stream: " << Pa_GetErrorText(ec)
137  << std::endl;
138  m_stream.store(nullptr);
139  }
140 
141  if(!m_stream)
142  {
143  Pa_Terminate();
144  throw std::runtime_error("Could not start PortAudio stream");
145  }
146  }
147 
148  bool running() const override
149  {
150  auto s = m_stream.load();
151  return s && Pa_IsStreamActive(m_stream);
152  }
153 
154  ~portaudio_engine() override
155  {
156  stop();
157 
158  if(auto stream = m_stream.load())
159  {
160  auto ec = Pa_StopStream(stream);
161  std::cerr << "=== stream stop ===\n";
162 
163  if(ec != PaErrorCode::paNoError)
164  {
165  std::cerr << "Error while stopping audio stream: " << Pa_GetErrorText(ec)
166  << std::endl;
167  }
168  }
169  Pa_Terminate();
170  }
171 
172 private:
173  static int clearBuffers(float** float_output, unsigned long nframes, int outs)
174  {
175  for(int i = 0; i < outs; i++)
176  {
177  auto chan = float_output[i];
178  for(std::size_t j = 0; j < nframes; j++)
179  chan[j] = 0.f;
180  }
181 
182  return paContinue;
183  }
184 
185  static int PortAudioCallback(
186  const void* input, void* output, unsigned long nframes,
187  const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags,
188  void* userData)
189  {
190  static const thread_local auto _ = [] {
191  ossia::set_thread_name("ossia audio 0");
192  ossia::set_thread_pinned(thread_type::Audio, 0);
193  return 0;
194  }();
195 
196  // auto t0 = std::chrono::steady_clock::now();
197  auto& self = *static_cast<portaudio_engine*>(userData);
198  self.tick_start();
199  auto clt = self.m_stream.load();
200 
201  if(self.stop_processing || !clt)
202  {
203  self.tick_clear();
204  return clearBuffers(((float**)output), nframes, self.effective_outputs);
205  }
206 
207  auto float_input = ((float* const*)input);
208  auto float_output = ((float**)output);
209 
210  ossia::audio_tick_state ts{
211  float_input, float_output, self.effective_inputs, self.effective_outputs,
212  nframes, timeInfo->currentTime};
213  self.audio_tick(ts);
214 
215  self.tick_end();
216 
217  // auto t1 = std::chrono::steady_clock::now();
218  //
219  // std::cerr << nframes << " => " <<
220  // std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count()
221  // << std::endl;
222  return paContinue;
223  }
224 
225  std::atomic<PaStream*> m_stream{};
226 };
227 }
228 
229 #endif
230 #endif
Definition: git_info.h:7
constexpr OSSIA_INLINE auto min(const T a, const U b) noexcept -> typename std::conditional<(sizeof(T) > sizeof(U)), T, U >::type
min function tailored for values
Definition: math.hpp:125