OSSIA
Open Scenario System for Interactive Application
alsa_protocol.hpp
1 #pragma once
2 #if __has_include(<alsa/asoundlib.h>) && !defined(__EMSCRIPTEN__)
3 
4 #include <ossia/audio/audio_engine.hpp>
5 #include <ossia/dataflow/float_to_sample.hpp>
6 #include <ossia/detail/dylib_loader.hpp>
8 #include <ossia/detail/pod_vector.hpp>
9 #include <ossia/detail/thread.hpp>
10 
11 #include <alsa/asoundlib.h>
12 
13 #include <atomic>
14 #include <string_view>
15 #include <thread>
16 #include <vector>
17 
18 #define OSSIA_AUDIO_ALSA 1
19 
20 namespace ossia
21 {
22 #define snd_alloca(ptr, lib, type) \
23  do \
24  { \
25  *ptr = (snd_##type##_t*)alloca(lib.type##_sizeof()); \
26  memset(*ptr, 0, lib.type##_sizeof()); \
27  } while(0)
28 class libasound
29 {
30 public:
31  decltype(&::snd_pcm_open) pcm_open{};
32  decltype(&::snd_pcm_hw_params_any) pcm_hw_params_any{};
33  decltype(&::snd_pcm_hw_params_sizeof) pcm_hw_params_sizeof{};
34  decltype(&::snd_pcm_hw_params_set_access) pcm_hw_params_set_access{};
35  decltype(&::snd_pcm_hw_params_get_format) pcm_hw_params_get_format{};
36  decltype(&::snd_pcm_hw_params_set_format) pcm_hw_params_set_format{};
37  decltype(&::snd_pcm_hw_params_set_channels) pcm_hw_params_set_channels{};
38  decltype(&::snd_pcm_hw_params_set_period_size) pcm_hw_params_set_period_size{};
39  decltype(&::snd_pcm_hw_params_set_buffer_size) pcm_hw_params_set_buffer_size{};
40  decltype(&::snd_pcm_hw_params_set_periods) pcm_hw_params_set_periods{};
41  decltype(&::snd_pcm_hw_params_set_periods_near) pcm_hw_params_set_periods_near{};
42  decltype(&::snd_pcm_hw_params_set_rate_near) pcm_hw_params_set_rate_near{};
43  decltype(&::snd_pcm_hw_params_get_channels) pcm_hw_params_get_channels{};
44  decltype(&::snd_pcm_hw_params_get_channels_min) pcm_hw_params_get_channels_min{};
45  decltype(&::snd_pcm_hw_params_get_channels_max) pcm_hw_params_get_channels_max{};
46  decltype(&::snd_pcm_hw_params_get_rate) pcm_hw_params_get_rate{};
47  decltype(&::snd_pcm_hw_params_get_rate_min) pcm_hw_params_get_rate_min{};
48  decltype(&::snd_pcm_hw_params_get_rate_max) pcm_hw_params_get_rate_max{};
49  decltype(&::snd_pcm_hw_params_get_buffer_size) pcm_hw_params_get_buffer_size{};
50  decltype(&::snd_pcm_hw_params_get_buffer_size_min) pcm_hw_params_get_buffer_size_min{};
51  decltype(&::snd_pcm_hw_params_get_buffer_size_max) pcm_hw_params_get_buffer_size_max{};
52  decltype(&::snd_pcm_hw_params_get_period_size) pcm_hw_params_get_period_size{};
53  decltype(&::snd_pcm_hw_params_get_period_size_min) pcm_hw_params_get_period_size_min{};
54  decltype(&::snd_pcm_hw_params_get_period_size_max) pcm_hw_params_get_period_size_max{};
55  decltype(&::snd_pcm_hw_params_get_periods) pcm_hw_params_get_periods{};
56  decltype(&::snd_pcm_hw_params) pcm_hw_params{};
57  decltype(&::snd_pcm_sw_params_current) pcm_sw_params_current{};
58  decltype(&::snd_pcm_sw_params_sizeof) pcm_sw_params_sizeof{};
59  decltype(&::snd_pcm_sw_params_set_start_threshold) pcm_sw_params_set_start_threshold{};
60  decltype(&::snd_pcm_sw_params_set_avail_min) pcm_sw_params_set_avail_min{};
61  decltype(&::snd_pcm_sw_params) pcm_sw_params{};
62  decltype(&::snd_pcm_name) pcm_name{};
63  decltype(&::snd_pcm_state) pcm_state{};
64  decltype(&::snd_pcm_state_name) pcm_state_name{};
65  decltype(&::snd_pcm_writei) pcm_writei{};
66  decltype(&::snd_pcm_writen) pcm_writen{};
67  decltype(&::snd_pcm_prepare) pcm_prepare{};
68  decltype(&::snd_pcm_start) pcm_start{};
69  decltype(&::snd_pcm_recover) pcm_recover{};
70  decltype(&::snd_pcm_drain) pcm_drain{};
71  decltype(&::snd_pcm_close) pcm_close{};
72  decltype(&::snd_strerror) strerror{};
73  decltype(&::snd_device_name_hint) device_name_hint{};
74  decltype(&::snd_device_name_get_hint) device_name_get_hint{};
75  decltype(&::snd_device_name_free_hint) device_name_free_hint{};
76 
77  static const libasound& instance()
78  {
79  static const libasound self;
80  return self;
81  }
82 
83 private:
84  dylib_loader library;
85 
86  libasound()
87  : library("libasound.so.2")
88  {
89  // in terms of regex:
90  // decltype\‍(&::([a-z_]+)\‍) [a-z_]+{};
91  // \1 = library.symbol<decltype(&::\1)>("\1");
92 
93  pcm_open = library.symbol<decltype(&::snd_pcm_open)>("snd_pcm_open");
94  pcm_hw_params_any
95  = library.symbol<decltype(&::snd_pcm_hw_params_any)>("snd_pcm_hw_params_any");
96  pcm_hw_params_sizeof = library.symbol<decltype(&::snd_pcm_hw_params_sizeof)>(
97  "snd_pcm_hw_params_sizeof");
98  pcm_hw_params_set_access = library.symbol<decltype(&::snd_pcm_hw_params_set_access)>(
99  "snd_pcm_hw_params_set_access");
100  pcm_hw_params_get_format = library.symbol<decltype(&::snd_pcm_hw_params_get_format)>(
101  "snd_pcm_hw_params_get_format");
102  pcm_hw_params_set_format = library.symbol<decltype(&::snd_pcm_hw_params_set_format)>(
103  "snd_pcm_hw_params_set_format");
104  pcm_hw_params_set_channels
105  = library.symbol<decltype(&::snd_pcm_hw_params_set_channels)>(
106  "snd_pcm_hw_params_set_channels");
107  pcm_hw_params_set_period_size
108  = library.symbol<decltype(&::snd_pcm_hw_params_set_period_size)>(
109  "snd_pcm_hw_params_set_period_size");
110  pcm_hw_params_set_buffer_size
111  = library.symbol<decltype(&::snd_pcm_hw_params_set_buffer_size)>(
112  "snd_pcm_hw_params_set_buffer_size");
113  pcm_hw_params_set_periods
114  = library.symbol<decltype(&::snd_pcm_hw_params_set_periods)>(
115  "snd_pcm_hw_params_set_periods");
116  pcm_hw_params_set_periods_near
117  = library.symbol<decltype(&::snd_pcm_hw_params_set_periods_near)>(
118  "snd_pcm_hw_params_set_periods_near");
119  pcm_hw_params_set_rate_near
120  = library.symbol<decltype(&::snd_pcm_hw_params_set_rate_near)>(
121  "snd_pcm_hw_params_set_rate_near");
122  pcm_hw_params_get_channels
123  = library.symbol<decltype(&::snd_pcm_hw_params_get_channels)>(
124  "snd_pcm_hw_params_get_channels");
125  pcm_hw_params_get_channels_min
126  = library.symbol<decltype(&::snd_pcm_hw_params_get_channels_min)>(
127  "snd_pcm_hw_params_get_channels_min");
128  pcm_hw_params_get_channels_max
129  = library.symbol<decltype(&::snd_pcm_hw_params_get_channels_max)>(
130  "snd_pcm_hw_params_get_channels_max");
131  pcm_hw_params_get_rate = library.symbol<decltype(&::snd_pcm_hw_params_get_rate)>(
132  "snd_pcm_hw_params_get_rate");
133  pcm_hw_params_get_rate_min
134  = library.symbol<decltype(&::snd_pcm_hw_params_get_rate_min)>(
135  "snd_pcm_hw_params_get_rate_min");
136  pcm_hw_params_get_rate_max
137  = library.symbol<decltype(&::snd_pcm_hw_params_get_rate_max)>(
138  "snd_pcm_hw_params_get_rate_max");
139  pcm_hw_params_get_buffer_size
140  = library.symbol<decltype(&::snd_pcm_hw_params_get_buffer_size)>(
141  "snd_pcm_hw_params_get_buffer_size");
142  pcm_hw_params_get_buffer_size_min
143  = library.symbol<decltype(&::snd_pcm_hw_params_get_buffer_size_min)>(
144  "snd_pcm_hw_params_get_buffer_size_min");
145  pcm_hw_params_get_buffer_size_max
146  = library.symbol<decltype(&::snd_pcm_hw_params_get_buffer_size_max)>(
147  "snd_pcm_hw_params_get_buffer_size_max");
148  pcm_hw_params_get_period_size
149  = library.symbol<decltype(&::snd_pcm_hw_params_get_period_size)>(
150  "snd_pcm_hw_params_get_period_size");
151  pcm_hw_params_get_period_size_min
152  = library.symbol<decltype(&::snd_pcm_hw_params_get_period_size_min)>(
153  "snd_pcm_hw_params_get_period_size_min");
154  pcm_hw_params_get_period_size_max
155  = library.symbol<decltype(&::snd_pcm_hw_params_get_period_size_max)>(
156  "snd_pcm_hw_params_get_period_size_max");
157  pcm_hw_params_get_periods
158  = library.symbol<decltype(&::snd_pcm_hw_params_get_periods)>(
159  "snd_pcm_hw_params_get_periods");
160  pcm_hw_params = library.symbol<decltype(&::snd_pcm_hw_params)>("snd_pcm_hw_params");
161  pcm_sw_params_current = library.symbol<decltype(&::snd_pcm_sw_params_current)>(
162  "snd_pcm_sw_params_current");
163  pcm_sw_params_sizeof = library.symbol<decltype(&::snd_pcm_sw_params_sizeof)>(
164  "snd_pcm_sw_params_sizeof");
165  pcm_sw_params_set_start_threshold
166  = library.symbol<decltype(&::snd_pcm_sw_params_set_start_threshold)>(
167  "snd_pcm_sw_params_set_start_threshold");
168  pcm_sw_params_set_avail_min
169  = library.symbol<decltype(&::snd_pcm_sw_params_set_avail_min)>(
170  "snd_pcm_sw_params_set_avail_min");
171  pcm_sw_params = library.symbol<decltype(&::snd_pcm_sw_params)>("snd_pcm_sw_params");
172  pcm_name = library.symbol<decltype(&::snd_pcm_name)>("snd_pcm_name");
173  pcm_state = library.symbol<decltype(&::snd_pcm_state)>("snd_pcm_state");
174  pcm_state_name
175  = library.symbol<decltype(&::snd_pcm_state_name)>("snd_pcm_state_name");
176  pcm_writei = library.symbol<decltype(&::snd_pcm_writei)>("snd_pcm_writei");
177  pcm_writen = library.symbol<decltype(&::snd_pcm_writen)>("snd_pcm_writen");
178  pcm_prepare = library.symbol<decltype(&::snd_pcm_prepare)>("snd_pcm_prepare");
179  pcm_start = library.symbol<decltype(&::snd_pcm_start)>("snd_pcm_start");
180  pcm_recover = library.symbol<decltype(&::snd_pcm_recover)>("snd_pcm_recover");
181  pcm_drain = library.symbol<decltype(&::snd_pcm_drain)>("snd_pcm_drain");
182  pcm_close = library.symbol<decltype(&::snd_pcm_close)>("snd_pcm_close");
183  strerror = library.symbol<decltype(&::snd_strerror)>("snd_strerror");
184  device_name_hint
185  = library.symbol<decltype(&::snd_device_name_hint)>("snd_device_name_hint");
186  device_name_get_hint = library.symbol<decltype(&::snd_device_name_get_hint)>(
187  "snd_device_name_get_hint");
188  device_name_free_hint = library.symbol<decltype(&::snd_device_name_free_hint)>(
189  "snd_device_name_free_hint");
190 
191  // in terms of regex:
192  // decltype\‍(&::([a-z_]+)\‍) [a-z_]+{};
193  // assert(\1);
194  assert(pcm_open);
195  assert(pcm_hw_params_any);
196  assert(pcm_hw_params_sizeof);
197  assert(pcm_hw_params_set_access);
198  assert(pcm_hw_params_get_format);
199  assert(pcm_hw_params_set_format);
200  assert(pcm_hw_params_set_channels);
201  assert(pcm_hw_params_set_period_size);
202  assert(pcm_hw_params_set_buffer_size);
203  assert(pcm_hw_params_set_periods);
204  assert(pcm_hw_params_set_periods_near);
205  assert(pcm_hw_params_set_rate_near);
206  assert(pcm_hw_params_get_channels);
207  assert(pcm_hw_params_get_channels_min);
208  assert(pcm_hw_params_get_channels_max);
209  assert(pcm_hw_params_get_rate);
210  assert(pcm_hw_params_get_rate_min);
211  assert(pcm_hw_params_get_rate_max);
212  assert(pcm_hw_params_get_buffer_size);
213  assert(pcm_hw_params_get_buffer_size_min);
214  assert(pcm_hw_params_get_buffer_size_max);
215  assert(pcm_hw_params_get_period_size);
216  assert(pcm_hw_params_get_period_size_min);
217  assert(pcm_hw_params_get_period_size_max);
218  assert(pcm_hw_params_get_periods);
219  assert(pcm_hw_params);
220  assert(pcm_sw_params_current);
221  assert(pcm_sw_params_sizeof);
222  assert(pcm_sw_params_set_start_threshold);
223  assert(pcm_sw_params_set_avail_min);
224  assert(pcm_sw_params);
225  assert(pcm_name);
226  assert(pcm_state);
227  assert(pcm_state_name);
228  assert(pcm_writei);
229  assert(pcm_writen);
230  assert(pcm_prepare);
231  assert(pcm_start);
232  assert(pcm_recover);
233  assert(pcm_drain);
234  assert(pcm_close);
235  assert(strerror);
236  assert(device_name_hint);
237  assert(device_name_get_hint);
238  assert(device_name_free_hint);
239  }
240 };
241 
242 struct exception : std::runtime_error
243 {
244  template <typename... Args>
245  exception(fmt::format_string<Args...> format, Args&&... args)
246  : std::runtime_error{fmt::format(format, std::forward<Args>(args)...)}
247  {
248  }
249 };
250 
251 template <auto Format>
252 static constexpr void
253 snd_interleave(const float* const* in, char* out, int channels, int bs)
254 {
255  switch(Format)
256  {
257  case SND_PCM_FORMAT_S16_LE:
258  return ossia::interleave<int16_t, 16, 2>(
259  in, reinterpret_cast<int16_t*>(out), channels, bs);
260  case SND_PCM_FORMAT_S24_LE:
261  return ossia::interleave<int32_t, 24, 4>(
262  in, reinterpret_cast<int32_t*>(out), channels, bs);
263  case SND_PCM_FORMAT_S24_3LE:
264  return ossia::interleave<int32_t, 24, 3>(
265  in, reinterpret_cast<int32_t*>(out), channels, bs);
266  case SND_PCM_FORMAT_S32_LE:
267  return ossia::interleave<int32_t, 32, 4>(
268  in, reinterpret_cast<int32_t*>(out), channels, bs);
269  case SND_PCM_FORMAT_FLOAT_LE:
270  return ossia::interleave<float, 32, 4>(
271  in, reinterpret_cast<float*>(out), channels, bs);
272  default:
273  return;
274  }
275 }
276 
277 template <auto Format>
278 static constexpr void
279 snd_convert(const float* const* in, char* out, int channels, int bs)
280 {
281  switch(Format)
282  {
283  case SND_PCM_FORMAT_S16_LE:
284  return ossia::convert<int16_t, 16>(
285  in, reinterpret_cast<int16_t*>(out), channels, bs);
286  case SND_PCM_FORMAT_S24_LE:
287  return ossia::convert<int32_t, 24>(
288  in, reinterpret_cast<int32_t*>(out), channels, bs);
289  case SND_PCM_FORMAT_S24_3LE:
290  return ossia::convert<int32_t, 24>(
291  in, reinterpret_cast<int32_t*>(out), channels, bs);
292  case SND_PCM_FORMAT_S32_LE:
293  return ossia::convert<int32_t, 32>(
294  in, reinterpret_cast<int32_t*>(out), channels, bs);
295  case SND_PCM_FORMAT_FLOAT_LE:
296  return ossia::convert<float, 32>(in, reinterpret_cast<float*>(out), channels, bs);
297  default:
298  return;
299  }
300 }
301 
302 template <auto Format>
303 static constexpr int snd_bytes_per_sample()
304 {
305  switch(Format)
306  {
307  case SND_PCM_FORMAT_S16_LE:
308  return 2;
309  case SND_PCM_FORMAT_S24_3LE:
310  return 3;
311  case SND_PCM_FORMAT_S24_LE:
312  return 4;
313  case SND_PCM_FORMAT_S32_LE:
314  return 4;
315  case SND_PCM_FORMAT_FLOAT_LE:
316  return 4;
317  default:
318  return 4;
319  }
320 }
321 
322 class alsa_engine final : public audio_engine
323 {
324 public:
325  alsa_engine(
326  std::string /* card_in */, std::string card_out, int inputs, int outputs, int rate,
327  int bs)
328  {
329  const auto& snd = libasound::instance();
330 
331  {
332  if(int ret = snd.pcm_open(&m_client, card_out.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
333  ret < 0)
334  throw ossia::exception(
335  "alsa_engine: error when opening device '{}': {}", card_out,
336  snd.strerror(ret));
337 
338  snd_pcm_hw_params_t* hwparams;
339  snd_alloca(&hwparams, snd, pcm_hw_params);
340  snd.pcm_hw_params_any(m_client, hwparams);
341 
342  // snd_pcm_sw_params_t *swparams;
343  // snd_pcm_sw_params_alloca(&swparams);
344 
345  auto access = SND_PCM_ACCESS_RW_NONINTERLEAVED;
346  if(int ret = snd.pcm_hw_params_set_access(m_client, hwparams, access); ret < 0)
347  {
348  ossia::logger().error(
349  "alsa_engine: can't set noninterleaved mode: {}", snd.strerror(ret));
350 
351  access = SND_PCM_ACCESS_RW_INTERLEAVED;
352  }
353 
354  auto format = SND_PCM_FORMAT_FLOAT_LE;
355  if(int ret = snd.pcm_hw_params_set_format(m_client, hwparams, format); ret < 0)
356  {
357  format = SND_PCM_FORMAT_S32_LE;
358  if(int ret = snd.pcm_hw_params_set_format(m_client, hwparams, format); ret < 0)
359  {
360  format = SND_PCM_FORMAT_S24_LE;
361  if(int ret = snd.pcm_hw_params_set_format(m_client, hwparams, format); ret < 0)
362  {
363  format = SND_PCM_FORMAT_S16_LE;
364  if(int ret = snd.pcm_hw_params_set_format(m_client, hwparams, format);
365  ret < 0)
366  {
367  snd.pcm_hw_params_get_format(hwparams, &format);
368 
369  ossia::logger().error(
370  "alsa_engine: can't set format: {} => got {}", snd.strerror(ret),
371  (int)format);
372  }
373  }
374  }
375  }
376 
377  if(int ret = snd.pcm_hw_params_set_channels(m_client, hwparams, outputs); ret < 0)
378  ossia::logger().error(
379  "alsa_engine: can't set channels number. {}", snd.strerror(ret));
380 
381  if(int ret = snd.pcm_hw_params_set_period_size(m_client, hwparams, bs, 0); ret < 0)
382  ossia::logger().error(
383  "alsa_engine: can't set period size. {}", snd.strerror(ret));
384 
385  unsigned int pnear = 2;
386  if(int ret = snd.pcm_hw_params_set_periods_near(m_client, hwparams, &pnear, 0);
387  ret < 0)
388  ossia::logger().error("alsa_engine: can't set periods. {}", snd.strerror(ret));
389 
390  // if (int ret = snd.pcm_hw_params_set_buffer_size(m_client, hwparams, 2
391  // * bs);
392  // ret < 0)
393  // ossia::logger().error("alsa_engine: can't set buffer size. {}",
394  // snd.strerror(ret));
395 
396  unsigned int urate = rate;
397  if(int ret = snd.pcm_hw_params_set_rate_near(m_client, hwparams, &urate, 0);
398  ret < 0)
399  ossia::logger().error("alsa_engine: can't set rate. {}", snd.strerror(ret));
400 
401  if(int ret = snd.pcm_hw_params(m_client, hwparams); ret < 0)
402  ossia::logger().error("alsa_engine: snd_pcm_hw_params: {}", snd.strerror(ret));
403 
404  this->effective_inputs = 0;
405  {
406  unsigned int tmp{};
407  snd.pcm_hw_params_get_channels(hwparams, &tmp);
408  this->effective_outputs = tmp;
409  }
410 
411  {
412  unsigned int tmp{};
413  snd.pcm_hw_params_get_rate(hwparams, &tmp, 0);
414  this->effective_sample_rate = tmp;
415  }
416 
417  {
418  snd_pcm_uframes_t tmp{};
419  snd.pcm_hw_params_get_period_size(hwparams, &tmp, 0);
420  this->effective_buffer_size = tmp;
421  }
422  {
423  snd_pcm_uframes_t tmp_bufsize{};
424  snd.pcm_hw_params_get_buffer_size(hwparams, &tmp_bufsize);
425  unsigned int tmp_periods{};
426  snd.pcm_hw_params_get_periods(hwparams, &tmp_periods, 0);
427 
428  ossia::logger().error("Device: {}\n", card_out);
429  ossia::logger().error("Expected: {} : {} : {}\n", outputs, rate, bs);
430  ossia::logger().error(
431  "Got: {} : {} : {} => {} ; {}\n", effective_outputs, effective_sample_rate,
432  effective_buffer_size, tmp_bufsize, tmp_periods);
433  }
434 
435  if(access == SND_PCM_ACCESS_RW_NONINTERLEAVED)
436  {
437  switch(format)
438  {
439  case SND_PCM_FORMAT_S16_LE:
440  m_thread = std::thread([this] {
441  init_thread();
442  run_thread_deinterleaved<SND_PCM_FORMAT_S16_LE>();
443  });
444  m_activated = true;
445  break;
446  case SND_PCM_FORMAT_S24_LE:
447  m_thread = std::thread([this] {
448  init_thread();
449  run_thread_deinterleaved<SND_PCM_FORMAT_S24_LE>();
450  });
451  m_activated = true;
452  break;
453  case SND_PCM_FORMAT_S24_3LE:
454  m_thread = std::thread([this] {
455  init_thread();
456  run_thread_deinterleaved<SND_PCM_FORMAT_S24_LE>();
457  });
458  m_activated = true;
459  break;
460  case SND_PCM_FORMAT_S32_LE:
461  m_thread = std::thread([this] {
462  init_thread();
463  run_thread_deinterleaved<SND_PCM_FORMAT_S32_LE>();
464  });
465  m_activated = true;
466  break;
467  case SND_PCM_FORMAT_FLOAT_LE:
468  m_thread = std::thread([this] {
469  init_thread();
470  run_thread_deinterleaved<SND_PCM_FORMAT_FLOAT_LE>();
471  });
472  m_activated = true;
473  break;
474  default:
475  m_activated = false;
476  break;
477  }
478  }
479  else
480  {
481  switch(format)
482  {
483  case SND_PCM_FORMAT_S16_LE:
484  m_thread = std::thread([this] {
485  init_thread();
486  run_thread_interleaved<SND_PCM_FORMAT_S16_LE>();
487  });
488  m_activated = true;
489  break;
490  case SND_PCM_FORMAT_S24_LE:
491  m_thread = std::thread([this] {
492  init_thread();
493  run_thread_interleaved<SND_PCM_FORMAT_S24_LE>();
494  });
495  m_activated = true;
496  break;
497  case SND_PCM_FORMAT_S24_3LE:
498  m_thread = std::thread([this] {
499  init_thread();
500  run_thread_interleaved<SND_PCM_FORMAT_S24_LE>();
501  });
502  m_activated = true;
503  break;
504  case SND_PCM_FORMAT_S32_LE:
505  m_thread = std::thread([this] {
506  init_thread();
507  run_thread_interleaved<SND_PCM_FORMAT_S32_LE>();
508  });
509  m_activated = true;
510  break;
511  case SND_PCM_FORMAT_FLOAT_LE:
512  m_thread = std::thread([this] {
513  init_thread();
514  run_thread_interleaved<SND_PCM_FORMAT_FLOAT_LE>();
515  });
516  m_activated = true;
517  break;
518  default:
519  m_activated = false;
520  break;
521  }
522  }
523  }
524  }
525 
526  ~alsa_engine() override
527  {
528  stop();
529 
530  if(m_client)
531  {
532  m_stop_token = true;
533  assert(m_thread.joinable());
534  m_thread.join();
535 
536  snd.pcm_drain(m_client);
537  snd.pcm_close(m_client);
538  }
539  }
540 
541  bool running() const override
542  {
543  if(!m_client)
544  return false;
545  return m_activated;
546  }
547 
548 private:
549  void init_thread()
550  {
551  ossia::set_thread_name("ossia audio 0");
552  ossia::set_thread_pinned(thread_type::Audio, 0);
553  }
554 
555  void clear_buffers()
556  {
557  ossia::fill(this->m_temp_buffer, 0.f);
558  ossia::fill(this->m_deinterleaved_buffer, 0.f);
559  }
560 
561  void abort(int ret)
562  {
563  ossia::logger().error("alsa_engine::submit: {}", snd.strerror(ret));
564  m_activated = false;
565  m_stop_token = true;
566  this->stop_processing = true;
567  };
568 
569  bool submit_interleaved(char* data, int sample_size_in_bytes)
570  {
571  int samples = this->effective_buffer_size;
572  const int channels = this->effective_outputs;
573  while(samples > 0)
574  {
575  int ret = snd.pcm_writei(m_client, data, samples);
576 
577  if(ret == -EPIPE || ret == -ESTRPIPE)
578  {
579  ossia::logger().error("alsa_engine: snd_pcm_writei: buffer underrun.");
580  if(int ret = snd.pcm_prepare(m_client); ret < 0)
581  {
582  abort(ret);
583  return false;
584  }
585  }
586  else if(ret == -EAGAIN)
587  {
588  continue;
589  }
590  else if(ret < 0)
591  {
592  // ossia::logger().error("alsa_engine: snd_pcm_writei: {}",
593  // snd.strerror(ret));
594  return true;
595  abort(ret);
596  return false;
597  }
598  else
599  {
600  data += ret * channels * sample_size_in_bytes;
601  samples -= ret;
602  ret = snd.pcm_start(m_client);
603 
604  if(ret < 0)
605  ossia::logger().error("alsa_engine: snd_pcm_start: {}", snd.strerror(ret));
606  }
607  }
608  return true;
609  }
610 
611  bool submit_deinterleaved(void** data, int sample_size_in_bytes)
612  {
613  int samples = this->effective_buffer_size;
614  const int channels = this->effective_outputs;
615  while(samples > 0)
616  {
617  int ret = snd.pcm_writen(m_client, data, samples);
618 
619  if(ret == -EPIPE || ret == -ESTRPIPE)
620  {
621  ossia::logger().error("alsa_engine: snd_pcm_writei: buffer underrun.");
622  if(int ret = snd.pcm_prepare(m_client); ret < 0)
623  {
624  abort(ret);
625  return false;
626  }
627  }
628  else if(ret == -EAGAIN)
629  {
630  continue;
631  }
632  else if(ret < 0)
633  {
634  // ossia::logger().error("alsa_engine: snd_pcm_writei: {}",
635  // snd.strerror(ret));
636  return true;
637  abort(ret);
638  return false;
639  }
640  else
641  {
642  auto data_c = reinterpret_cast<char**>(data);
643  for(int c = 0; c < channels; ++c)
644  {
645  data_c[c] += ret * sample_size_in_bytes;
646  }
647 
648  samples -= ret;
649  snd.pcm_start(m_client);
650  }
651  }
652  return true;
653  }
654 
655  template <auto Format>
656  void run_thread_interleaved()
657  {
658  constexpr int bytes_per_sample = snd_bytes_per_sample<Format>();
659  m_temp_buffer.resize(
660  this->effective_outputs * this->effective_buffer_size * bytes_per_sample);
661  m_deinterleaved_buffer.resize(this->effective_outputs * this->effective_buffer_size);
662 
663  float** score_outs = (float**)alloca(sizeof(float*) * this->effective_outputs);
664  for(int c = 0; c < this->effective_outputs; c++)
665  score_outs[c] = m_deinterleaved_buffer.data() + c * this->effective_buffer_size;
666 
667  m_start_time = clk::now();
668  m_last_time = m_start_time;
669  while(!this->m_stop_token)
670  {
671  process(nullptr, score_outs, this->effective_buffer_size);
672 
673  snd_interleave<Format>(
674  score_outs, m_temp_buffer.data(), this->effective_outputs,
675  this->effective_buffer_size);
676 
677  if(!submit_interleaved(m_temp_buffer.data(), bytes_per_sample))
678  break;
679  }
680  }
681 
682  template <auto Format>
683  void run_thread_deinterleaved()
684  {
685  constexpr int bytes_per_sample = snd_bytes_per_sample<Format>();
686  // In the float case we skip the temp buffer
687  if constexpr(Format != SND_PCM_FORMAT_FLOAT_LE)
688  {
689  m_temp_buffer.resize(
690  this->effective_outputs * this->effective_buffer_size * bytes_per_sample);
691  }
692  m_deinterleaved_buffer.resize(this->effective_outputs * this->effective_buffer_size);
693 
694  float** score_outs = (float**)alloca(sizeof(float*) * this->effective_outputs);
695  void** deinterleaved_outs = (void**)alloca(sizeof(void*) * this->effective_outputs);
696 
697  for(int c = 0; c < this->effective_outputs; c++)
698  {
699  score_outs[c] = m_deinterleaved_buffer.data() + c * this->effective_buffer_size;
700  }
701 
702  m_start_time = clk::now();
703  m_last_time = m_start_time;
704  while(!this->m_stop_token)
705  {
706  process(nullptr, score_outs, this->effective_buffer_size);
707 
708  if constexpr(Format != SND_PCM_FORMAT_FLOAT_LE)
709  {
710  // Convert into the temp buffer if necessary
711  snd_convert<Format>(
712  score_outs, m_temp_buffer.data(), this->effective_outputs,
713  this->effective_buffer_size);
714  }
715 
716  for(int c = 0; c < this->effective_outputs; c++)
717  {
718  if constexpr(Format != SND_PCM_FORMAT_FLOAT_LE)
719  deinterleaved_outs[c]
720  = m_deinterleaved_buffer.data() + c * this->effective_buffer_size;
721  else
722  deinterleaved_outs[c] = score_outs[c];
723  }
724 
725  // Submit
726  if(!submit_deinterleaved(deinterleaved_outs, bytes_per_sample))
727  break;
728  }
729  }
730 
731  void process(float**, float** float_output, uint64_t nframes)
732  {
733  auto& self = *static_cast<alsa_engine*>(this);
734  self.tick_start();
735 
736  const auto inputs = this->effective_inputs;
737  const auto outputs = this->effective_outputs;
738  if(self.stop_processing)
739  {
740  self.tick_clear();
741  clear_buffers();
742  return;
743  }
744 
745  using namespace std::chrono;
746  ossia::audio_tick_state ts{
747  nullptr,
748  float_output,
749  (int)inputs,
750  (int)outputs,
751  nframes,
752  duration_cast<nanoseconds>(m_last_time - m_start_time).count() / 1e9};
753  self.audio_tick(ts);
754 
755  self.tick_end();
756  m_last_time = clk::now();
757  }
758 
759  using clk = std::chrono::steady_clock;
760 
761  const libasound& snd{libasound::instance()};
762 
763  snd_pcm_t* m_client{};
764  std::thread m_thread;
765  ossia::pod_vector<char> m_temp_buffer;
766  ossia::float_vector m_deinterleaved_buffer;
767 
768  clk::time_point m_start_time{};
769  clk::time_point m_last_time{};
770 
771  std::atomic_bool m_stop_token{};
772  bool m_activated = false;
773 };
774 
775 }
776 
777 #endif
Definition: git_info.h:7
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition: context.cpp:104