11#define PDEBUG(...) Ase::debug ("alsa", "PCM: " __VA_ARGS__)
12#define MDEBUG(...) Ase::debug ("alsa", "MIDI: " __VA_ARGS__)
14#define ALSAQUIET(expr) ({ silence_error_handler++; auto __ret_ = (expr); silence_error_handler--; __ret_; })
15#define WITH_MIDI_POLL 0
17#define alsa_alloca0(struc) ({ struc##_t *ptr = (struc##_t*) alloca (struc##_sizeof()); memset (ptr, 0, struc##_sizeof()); ptr; })
18#define return_error(reason, aerror, ERRMEMB) do { \
19 const Error __ase_error = ase_error_from_errno (-aerror, Ase::Error::ERRMEMB); \
20 Ase::debug ("alsa", "%s: %s: %s", alsadev_, reason, \
21 ase_error_blurb (__ase_error)); \
52#if __has_include(<alsa/asoundlib.h>)
53#include <alsa/asoundlib.h>
56static_assert (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__,
"endianess unimplemented");
63chars2string (
const char *s)
69cxfree (
char *mallocedstring,
const char *fallback =
"")
73 const String string = mallocedstring;
74 free (mallocedstring);
83 std::string::size_type l = 0;
85 for (std::string::size_type i = input.
find (from, 0); i != std::string::npos; l = i + 1, i = input.
find (from, l))
87 target.
append (input, l, i - l);
97ase_handle_alsa_error (
const char *file,
int line,
const char *function,
int err,
const char *fmt, ...)
99 if (silence_error_handler != 0)
101 constexpr const int MAXBUFFER = 8 * 1024;
102 char buffer[MAXBUFFER + 2] = { 0, }, *b = buffer, *e = b + MAXBUFFER;
105 const int plen =
vsnprintf (b, e - b, fmt, argv);
109 if (file && line > 0 && function)
111 else if (file && line > 0)
113 else if (file && function)
115 else if (file || function)
117 Ase::debug (
"alsa",
"Error: %s", s);
120static snd_output_t *snd_output =
nullptr;
125 static const bool ASE_USED initialized = [] {
126 snd_lib_error_set_handler (ase_handle_alsa_error);
127 return snd_output_stdio_attach (&snd_output, stderr, 0);
132mixer_elem_get_channels (snd_mixer_elem_t *
const mel,
133 const std::function<
int (snd_mixer_elem_t*, snd_mixer_selem_channel_id_t)> &mixer_has_channel,
138 for (snd_mixer_selem_channel_id_t i = SND_MIXER_SCHN_MONO; i <= SND_MIXER_SCHN_LAST;
139 i = snd_mixer_selem_channel_id_t (i + 1))
140 if (mixer_has_channel (mel, i))
141 channels.push_back (i);
148 snd_mixer_t *mixer =
nullptr;
149 snd_mixer_selem_regopt regopt = { .ver = 1, .abstract = SND_MIXER_SABSTRACT_NONE, .device = card_hw.
c_str() };
150 snd_mixer_selem_id_t *selem_id =
nullptr;
152 int bseen = 0, iseen = 0, oseen = 0, maxin = 0, maxout = 0;
153 int err = snd_mixer_open (&mixer, 0);
154 if (err < 0)
goto error_out;
155 err = snd_mixer_selem_register (mixer, ®opt,
nullptr);
156 if (err < 0)
goto error_out;
157 err = snd_mixer_load (mixer);
158 if (err < 0)
goto error_out;
159 err = snd_mixer_selem_id_malloc (&selem_id);
160 if (err < 0)
goto error_out;
161 PDEBUG (
"CARD(%s): %s [%s]", card_hw, long_name, mixer_name);
162 PDEBUG (
"M-------- %-6s %2u %s %s - %s",
"MIXER", snd_mixer_get_count (mixer), card_hw, mixer_name, long_name);
163 for (snd_mixer_elem_t *mel = snd_mixer_first_elem (mixer); mel; mel = snd_mixer_elem_next (mel))
165 if (snd_mixer_elem_get_type (mel) != SND_MIXER_ELEM_SIMPLE)
168 const snd_mixer_selem_channel_id_t c0 = SND_MIXER_SCHN_MONO;
169 const bool cv = snd_mixer_selem_has_capture_volume (mel);
170 const bool pv = snd_mixer_selem_has_playback_volume (mel);
171 const bool S = snd_mixer_selem_has_common_switch (mel) || snd_mixer_selem_has_playback_switch (mel) || snd_mixer_selem_has_capture_switch (mel);
172 const bool e = snd_mixer_selem_is_enumerated (mel);
173 const char *tname = pv ? cv ?
"INOUT" :
"OUT" : cv ?
"IN" : S ?
"SWITCH" : e ?
"ENUM" :
"-";
174 const bool a = snd_mixer_selem_is_active (mel);
175 const bool j = snd_mixer_selem_has_playback_volume_joined (mel) || snd_mixer_selem_has_capture_volume_joined (mel);
176 const bool m = snd_mixer_selem_has_playback_switch (mel) && snd_mixer_selem_get_playback_switch (mel, c0, &v) == 0 && !v;
177 const bool M = m && snd_mixer_selem_has_playback_switch_joined (mel);
178 const bool c = snd_mixer_selem_has_capture_switch (mel) && snd_mixer_selem_get_capture_switch (mel, c0, &v) == 0 && v;
179 const bool C = c && snd_mixer_selem_has_capture_switch_joined (mel);
180 const bool x = snd_mixer_selem_has_capture_switch_exclusive (mel);
181 const auto p = mixer_elem_get_channels (mel, snd_mixer_selem_has_playback_channel, pv);
182 const auto d = mixer_elem_get_channels (mel, snd_mixer_selem_has_capture_channel, cv);
183 maxout =
std::max (maxout,
int (p.size()));
184 maxin =
std::max (maxin,
int (d.size()));
193 for (
const auto ch : p)
194 if ((ch == 0 || !snd_mixer_selem_has_playback_volume_joined (mel)) &&
195 0 == snd_mixer_selem_get_playback_volume (mel, ch, &l))
197 for (
const auto ch : d)
198 if ((ch == 0 || !snd_mixer_selem_has_capture_volume_joined (mel)) &&
199 0 == snd_mixer_selem_get_capture_volume (mel, ch, &l))
201 val = val.
empty() ?
"" :
": " + val;
202 PDEBUG (
"-%c%c%c%c%c%c%c%c %-6s %2u %s%s",
203 cv ?
'r' :
'-', pv ?
'w' :
'-', a ?
'-' :
'i',
205 M ?
'M' : m ?
'm' :
'-',
207 C ?
'C' : c ?
'c' :
'-',
210 snd_mixer_selem_get_name (mel),
214 hints += (hints.empty() ?
"" :
", ") +
String (
"surround");
215 if (oseen == 1 && bseen + iseen == 1)
216 hints += (hints.empty() ?
"" :
", ") +
String (
"headset");
217 if (oseen + bseen == 0 && iseen >= 1)
218 hints += (hints.empty() ?
"" :
", ") +
String (
"recorder");
220 hints += (hints.empty() ?
"" :
", ") +
String (
"multi-track");
222 PDEBUG (
"(%s)", hints);
225 snd_mixer_close (mixer);
227 snd_mixer_selem_id_free (selem_id);
232list_alsa_drivers (Driver::EntryVec &entries)
236 bool seen_plughw =
false;
237 void **nhints =
nullptr;
238 if (ALSAQUIET (snd_device_name_hint (-1,
"pcm", &nhints)) >= 0)
241 for (
void **hint = nhints; *hint; hint++)
243 name = cxfree (snd_device_name_get_hint (*hint,
"NAME"));
244 desc = cxfree (snd_device_name_get_hint (*hint,
"DESC"));
245 ioid = cxfree (snd_device_name_get_hint (*hint,
"IOID"),
"Duplex");
246 seen_plughw = seen_plughw ||
strncmp (name.c_str(),
"plughw:", 7) == 0;
249 PDEBUG (
"DISCOVER: %s - %s - %s", name, ioid, substitute_string (
"\n",
" ", desc));
252 entry.device_name = desc;
253 entry.device_info =
"Routing via the PulseAudio sound system";
254 entry.notice =
"Note: PulseAudio routing is not realtime capable";
255 entry.readonly =
"Input" == ioid;
256 entry.writeonly =
"Output" == ioid;
257 entry.priority = Driver::PULSE;
258 entries.push_back (entry);
261 snd_device_name_free_hint (nhints);
264 snd_ctl_card_info_t *cinfo = alsa_alloca0 (snd_ctl_card_info);
266 while (snd_card_next (&cindex) == 0 && cindex >= 0)
268 snd_ctl_card_info_clear (cinfo);
269 snd_ctl_t *chandle =
nullptr;
271 if (snd_ctl_open (&chandle, card_hw.c_str(), SND_CTL_NONBLOCK) < 0 || !chandle)
273 if (snd_ctl_card_info (chandle, cinfo) < 0)
275 snd_ctl_close (chandle);
278 const String card_id = chars2string (snd_ctl_card_info_get_id (cinfo));
279 const String card_driver = chars2string (snd_ctl_card_info_get_driver (cinfo));
280 const String card_name = chars2string (snd_ctl_card_info_get_name (cinfo));
281 const String card_longname = chars2string (snd_ctl_card_info_get_longname (cinfo));
282 const String card_mixername = chars2string (snd_ctl_card_info_get_mixername (cinfo));
284 const String mixer_keywords = mixer_info (card_hw, card_mixername, card_longname);
287 snd_pcm_info_t *wpi = alsa_alloca0 (snd_pcm_info);
288 snd_pcm_info_t *rpi = alsa_alloca0 (snd_pcm_info);
290 while (snd_ctl_pcm_next_device (chandle, &dindex) == 0 && dindex >= 0)
292 snd_pcm_info_set_device (wpi, dindex);
293 snd_pcm_info_set_subdevice (wpi, 0);
294 snd_pcm_info_set_stream (wpi, SND_PCM_STREAM_PLAYBACK);
295 const bool writable = snd_ctl_pcm_info (chandle, wpi) == 0;
296 snd_pcm_info_set_device (rpi, dindex);
297 snd_pcm_info_set_subdevice (rpi, 0);
298 snd_pcm_info_set_stream (rpi, SND_PCM_STREAM_CAPTURE);
299 const bool readable = snd_ctl_pcm_info (chandle, rpi) == 0;
300 const auto pcmclass = snd_pcm_info_get_class (writable ? wpi : rpi);
301 if (!writable && !readable)
303 const int total_playback_subdevices = writable ? snd_pcm_info_get_subdevices_count (wpi) : 0;
304 const int avail_playback_subdevices = writable ? snd_pcm_info_get_subdevices_avail (wpi) : 0;
306 if (total_playback_subdevices && total_playback_subdevices != avail_playback_subdevices)
307 wdevs =
string_format (
"%u*playback (%u busy)", total_playback_subdevices, total_playback_subdevices - avail_playback_subdevices);
308 else if (total_playback_subdevices)
309 wdevs =
string_format (
"%u*playback", total_playback_subdevices);
310 const int total_capture_subdevices = readable ? snd_pcm_info_get_subdevices_count (rpi) : 0;
311 const int avail_capture_subdevices = readable ? snd_pcm_info_get_subdevices_avail (rpi) : 0;
312 if (total_capture_subdevices && total_capture_subdevices != avail_capture_subdevices)
313 rdevs =
string_format (
"%u*capture (%u busy)", total_capture_subdevices, total_capture_subdevices - avail_capture_subdevices);
314 else if (total_capture_subdevices)
315 rdevs =
string_format (
"%u*capture", total_capture_subdevices);
316 const String joiner = !wdevs.
empty() && !rdevs.empty() ?
" + " :
"";
318 entry.devid =
string_format (
"hw:CARD=%s,DEV=%u", card_id, dindex);
319 const bool is_usb =
string_startswith (chars2string (snd_pcm_info_get_id (writable ? wpi : rpi)),
"USB Audio");
320 entry.device_name = chars2string (snd_pcm_info_get_name (writable ? wpi : rpi));
321 entry.device_name +=
" - " + card_name;
322 entry.hints = mixer_keywords;
323 if (card_name != card_mixername && !card_mixername.empty())
324 entry.device_name +=
" [" + card_mixername +
"]";
325 if (pcmclass == SND_PCM_CLASS_GENERIC)
326 entry.capabilities = readable && writable ?
"Full-Duplex Audio" : readable ?
"Audio Input" :
"Audio Output";
328 entry.capabilities = readable && writable ?
"Full-Duplex Modem" : readable ?
"Modem Input" :
"Modem Output";
329 entry.capabilities +=
", streams: " + wdevs + joiner + rdevs;
331 entry.device_info = card_longname;
332 entry.readonly = !writable;
333 entry.writeonly = !readable;
335 entry.priority = (is_usb ? Driver::ALSA_USB : Driver::ALSA_KERN) + Driver::WCARD * cindex + Driver::WDEV * dindex;
336 entry.priority &=
string_option_check (mixer_options,
"surround") ? ~Driver::SURROUND : ~0;
338 entry.priority &=
string_option_check (mixer_options,
"recorder") ? ~Driver::RECORDER : ~0;
339 entries.push_back (entry);
340 PDEBUG (
"DISCOVER: %s - %s", entry.devid, entry.device_name);
342 snd_ctl_close (chandle);
347class AlsaPcmDriver :
public PcmDriver {
348 snd_pcm_t *read_handle_ =
nullptr;
349 snd_pcm_t *write_handle_ =
nullptr;
351 uint n_channels_ = 0;
353 int period_size_ = 0;
354 int16 *period_buffer_ =
nullptr;
355 uint read_write_count_ = 0;
358 explicit AlsaPcmDriver (
const String &driver,
const String &devid) : PcmDriver (driver, devid) {}
360 create (
const String &devid)
368 snd_pcm_close (read_handle_);
370 snd_pcm_close (write_handle_);
371 delete[] period_buffer_;
374 pcm_n_channels ()
const override
379 pcm_mix_freq ()
const override
384 pcm_block_length ()
const override
392 PDEBUG (
"CLOSE: %s: r=%d w=%d", alsadev_, !!read_handle_, !!write_handle_);
395 snd_pcm_drop (read_handle_);
396 snd_pcm_close (read_handle_);
397 read_handle_ =
nullptr;
401 snd_pcm_nonblock (write_handle_, 0);
402 snd_pcm_drain (write_handle_);
403 snd_pcm_close (write_handle_);
404 write_handle_ =
nullptr;
406 delete[] period_buffer_;
407 period_buffer_ =
nullptr;
408 flags_ &= ~size_t (Flags::OPENED | Flags::READABLE | Flags::WRITABLE);
412 open (IODir iodir,
const PcmDriverConfig &config)
override
414 Error error =
open (devid_, iodir, config);
415 if (error != Error::NONE && strncmp (
"hw:", devid_.c_str(), 3) == 0)
416 error =
open (
"plug" + devid_, iodir, config);
420 open (
const String &alsadev, IODir iodir,
const PcmDriverConfig &config)
426 const bool require_readable = iodir == READONLY || iodir == READWRITE;
427 const bool require_writable = iodir == WRITEONLY || iodir == READWRITE;
428 flags_ |= Flags::READABLE * require_readable;
429 flags_ |= Flags::WRITABLE * require_writable;
430 n_channels_ = config.n_channels;
432 if (!aerror && require_readable)
433 aerror = snd_pcm_open (&read_handle_, alsadev_.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
434 if (!aerror && require_writable)
435 aerror = snd_pcm_open (&write_handle_, alsadev_.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
437 const uint period_size = config.block_length;
438 Error error = !aerror ? Error::NONE : ase_error_from_errno (-aerror, Error::FILE_OPEN_FAILED);
439 uint rh_freq = config.mix_freq, rh_n_periods = 2, rh_period_size = period_size;
440 if (!aerror && read_handle_)
441 error = alsa_device_setup (read_handle_, config.latency_ms, &rh_freq, &rh_n_periods, &rh_period_size);
442 uint wh_freq = config.mix_freq, wh_n_periods = 2, wh_period_size = period_size;
443 if (!aerror && write_handle_)
444 error = alsa_device_setup (write_handle_, config.latency_ms, &wh_freq, &wh_n_periods, &wh_period_size);
446 if (!error && read_handle_ && write_handle_)
448 const bool linked = snd_pcm_link (read_handle_, write_handle_) == 0;
449 if (rh_freq != wh_freq || rh_n_periods != wh_n_periods || rh_period_size != wh_period_size || !linked)
450 error = Error::DEVICES_MISMATCH;
451 PDEBUG (
"OPEN: %s: %s: %f==%f && %d*%d==%d*%d && linked==%d", alsadev_,
452 error != 0 ?
"MISMATCH" :
"LINKED", rh_freq, wh_freq, rh_n_periods, rh_period_size, wh_n_periods, wh_period_size, linked);
454 mix_freq_ = read_handle_ ? rh_freq : wh_freq;
455 n_periods_ = read_handle_ ? rh_n_periods : wh_n_periods;
456 period_size_ = read_handle_ ? rh_period_size : wh_period_size;
457 if (!error && (!read_handle_ || !write_handle_))
458 PDEBUG (
"OPEN: %s: %s: mix=%.1fHz n=%d period=%d", alsadev_,
459 read_handle_ ?
"READONLY" :
"WRITEONLY", mix_freq_, n_periods_, period_size_);
461 aerror = snd_pcm_prepare (read_handle_ ? read_handle_ : write_handle_);
462 error = !aerror ? Error::NONE : ase_error_from_errno (-aerror, Error::FILE_OPEN_FAILED);
467 period_buffer_ =
new int16[period_size_ * n_channels_];
468 flags_ |= Flags::OPENED;
473 snd_pcm_close (read_handle_);
474 read_handle_ =
nullptr;
476 snd_pcm_close (write_handle_);
477 write_handle_ =
nullptr;
479 PDEBUG (
"OPEN: %s: opening readable=%d writable=%d: %s", alsadev_, readable(), writable(), ase_error_blurb (error));
485 alsa_device_setup (snd_pcm_t *phandle, uint latency_ms, uint *mix_freq, uint *n_periodsp, uint *period_sizep)
488 if (
int aerror = snd_pcm_nonblock (phandle, 0); aerror < 0)
489 return_error (
"snd_pcm_nonblock", aerror, FILE_OPEN_FAILED);
491 snd_pcm_hw_params_t *hparams = alsa_alloca0 (snd_pcm_hw_params);
492 if (
int aerror = snd_pcm_hw_params_any (phandle, hparams); aerror < 0)
493 return_error (
"snd_pcm_hw_params_any", aerror, FILE_OPEN_FAILED);
494 if (
int aerror = snd_pcm_hw_params_set_channels (phandle, hparams, n_channels_); aerror < 0)
495 return_error (
"snd_pcm_hw_params_set_channels", aerror, DEVICE_CHANNELS);
496 if (
int aerror = snd_pcm_hw_params_set_access (phandle, hparams, SND_PCM_ACCESS_RW_INTERLEAVED); aerror < 0)
497 return_error (
"snd_pcm_hw_params_set_access", aerror, DEVICE_FORMAT);
498 if (
int aerror = snd_pcm_hw_params_set_format (phandle, hparams, SND_PCM_FORMAT_S16_LE); aerror < 0)
499 return_error (
"snd_pcm_hw_params_set_format", aerror, DEVICE_FORMAT);
501 uint rate = *mix_freq;
502 if (
int aerror = snd_pcm_hw_params_set_rate (phandle, hparams, rate, 0); aerror < 0)
503 return_error (
"snd_pcm_hw_params_set_rate", aerror, DEVICE_FREQUENCY);
504 if (rate != *mix_freq)
505 return_error (
"snd_pcm_hw_params_set_rate", -EINVAL, DEVICE_FREQUENCY);
506 PDEBUG (
"SETUP: %s: rate: %d", alsadev_, rate);
508 snd_pcm_uframes_t period_min = 2, period_max = 1048576;
509 snd_pcm_hw_params_get_period_size_min (hparams, &period_min,
nullptr);
510 snd_pcm_hw_params_get_period_size_max (hparams, &period_max,
nullptr);
511 const snd_pcm_uframes_t latency_frames = rate * latency_ms / 1000;
512 snd_pcm_uframes_t period_size = 32;
513 if (alsadev_ ==
"pulse")
514 period_size =
MAX (period_size, 384);
515 while (period_size + 16 <= latency_frames / 3)
517 period_size =
CLAMP (period_size, period_min, period_max);
518 period_size =
MIN (period_size, *period_sizep);
520 if (
int aerror = snd_pcm_hw_params_set_period_size_near (phandle, hparams, &period_size, &dir); aerror < 0)
521 return_error (
"snd_pcm_hw_params_set_period_size_near", aerror, DEVICE_LATENCY);
522 PDEBUG (
"SETUP: %s: period_size: %d (dir=%+d, min=%d max=%d)", alsadev_,
523 period_size, dir, period_min, period_max);
525 const uint want_nperiods = latency_ms == 0 ? 2 :
CLAMP (latency_frames / period_size, 2, 1023) + 1;
526 uint nperiods = want_nperiods;
527 if (
int aerror = snd_pcm_hw_params_set_periods_near (phandle, hparams, &nperiods,
nullptr); aerror < 0)
528 return_error (
"snd_pcm_hw_params_set_periods", aerror, DEVICE_LATENCY);
529 PDEBUG (
"SETUP: %s: n_periods: %d (requested: %d)", alsadev_, nperiods, want_nperiods);
530 if (
int aerror = snd_pcm_hw_params (phandle, hparams); aerror < 0)
531 return_error (
"snd_pcm_hw_params", aerror, FILE_OPEN_FAILED);
533 snd_pcm_uframes_t buffer_size_min = 0, buffer_size_max = 0, buffer_size = 0;
534 if (
int aerror = snd_pcm_hw_params_get_buffer_size (hparams, &buffer_size); aerror < 0)
535 return_error (
"snd_pcm_hw_params_get_buffer_size", aerror, DEVICE_BUFFER);
536 if (
int aerror = snd_pcm_hw_params_get_buffer_size_min (hparams, &buffer_size_min); aerror < 0)
537 return_error (
"snd_pcm_hw_params_get_buffer_size_min", aerror, DEVICE_BUFFER);
538 if (
int aerror = snd_pcm_hw_params_get_buffer_size_max (hparams, &buffer_size_max); aerror < 0)
539 return_error (
"snd_pcm_hw_params_get_buffer_size_max", aerror, DEVICE_BUFFER);
540 PDEBUG (
"SETUP: %s: buffer_size: %d (min=%d, max=%d)", alsadev_, buffer_size, buffer_size_min, buffer_size_max);
542 snd_pcm_sw_params_t *sparams = alsa_alloca0 (snd_pcm_sw_params);
543 if (
int aerror = snd_pcm_sw_params_current (phandle, sparams); aerror < 0)
544 return_error (
"snd_pcm_sw_params_current", aerror, FILE_OPEN_FAILED);
545 if (
int aerror = snd_pcm_sw_params_set_start_threshold (phandle, sparams, (buffer_size / period_size) * period_size); aerror < 0)
546 return_error (
"snd_pcm_sw_params_set_start_threshold", aerror, DEVICE_BUFFER);
547 snd_pcm_uframes_t availmin = 0;
548 if (
int aerror = snd_pcm_sw_params_set_avail_min (phandle, sparams, period_size); aerror < 0)
549 return_error (
"snd_pcm_sw_params_set_avail_min", aerror, DEVICE_LATENCY);
550 if (
int aerror = snd_pcm_sw_params_get_avail_min (sparams, &availmin); aerror < 0)
551 return_error (
"snd_pcm_sw_params_get_avail_min", aerror, DEVICE_LATENCY);
552 PDEBUG (
"SETUP: %s: avail_min: %d", alsadev_, availmin);
553 if (
int aerror = snd_pcm_sw_params_set_stop_threshold (phandle, sparams, LONG_MAX); aerror < 0)
554 return_error (
"snd_pcm_sw_params_set_stop_threshold", aerror, DEVICE_BUFFER);
555 snd_pcm_uframes_t stopthreshold = 0;
556 if (
int aerror = snd_pcm_sw_params_get_stop_threshold (sparams, &stopthreshold); aerror < 0)
557 return_error (
"snd_pcm_sw_params_get_stop_threshold", aerror, DEVICE_BUFFER);
558 PDEBUG (
"SETUP: %s: stop_threshold: %d", alsadev_, stopthreshold);
559 if (
int aerror = snd_pcm_sw_params_set_silence_threshold (phandle, sparams, 0); aerror < 0)
560 return_error (
"snd_pcm_sw_params_set_silence_threshold", aerror, DEVICE_BUFFER);
561 if (
int aerror = snd_pcm_sw_params_set_silence_size (phandle, sparams, LONG_MAX); aerror < 0)
562 return_error (
"snd_pcm_sw_params_set_silence_size", aerror, DEVICE_BUFFER);
563 if (
int aerror = snd_pcm_sw_params (phandle, sparams); aerror < 0)
564 return_error (
"snd_pcm_sw_params", aerror, FILE_OPEN_FAILED);
567 *n_periodsp = nperiods;
568 *period_sizep = period_size;
569 PDEBUG (
"SETUP: %s: OPEN: r=%d w=%d n_channels=%d sample_freq=%d nperiods=%u period=%u (%u) bufsz=%u",
570 alsadev_, phandle == read_handle_, phandle == write_handle_,
571 n_channels_, *mix_freq, *n_periodsp, *period_sizep,
572 nperiods * period_size, buffer_size);
579 silence_error_handler++;
580 PDEBUG (
"RETRIGGER: %s: retriggering device (r=%s w=%s)...",
581 alsadev_, !read_handle_ ?
"<CLOSED>" : snd_pcm_state_name (snd_pcm_state (read_handle_)),
582 !write_handle_ ?
"<CLOSED>" : snd_pcm_state_name (snd_pcm_state (write_handle_)));
583 snd_pcm_prepare (read_handle_ ? read_handle_ : write_handle_);
586 snd_pcm_drop (read_handle_);
588 snd_pcm_drain (write_handle_);
590 int aerror = snd_pcm_prepare (read_handle_ ? read_handle_ : write_handle_);
592 printerr (
"ALSA: %s: failed to prepare for io: %s\n", __func__, snd_strerror (-aerror));
596 const size_t needed_zeros = period_size_ / 2;
597 assert_return (needed_zeros <= AUDIO_BLOCK_FLOAT_ZEROS_SIZE);
599 for (
size_t i = 0; i < n_periods_; i++)
603 n = snd_pcm_writei (write_handle_, zeros, period_size_);
604 while (n == -EAGAIN);
608 silence_error_handler--;
611 pcm_check_io (int64 *timeoutp)
override
615 snd_pcm_state_t
ws = SND_PCM_STATE_DISCONNECTED, rs = SND_PCM_STATE_DISCONNECTED;
616 snd_pcm_status_t *
stat = alsa_alloca0 (snd_pcm_status);
619 snd_pcm_status (read_handle_, stat);
620 rs = snd_pcm_state (read_handle_);
622 int rn = snd_pcm_status_get_avail (stat);
625 snd_pcm_status (write_handle_, stat);
626 ws = snd_pcm_state (write_handle_);
628 int wn = snd_pcm_status_get_avail (stat);
629 printerr (
"ALSA: check_io: read=%4u/%4u (%s) write=%4u/%4u (%s) block=%u: %s\n",
630 rn, period_size_ * n_periods_, snd_pcm_state_name (rs),
631 wn, period_size_ * n_periods_, snd_pcm_state_name (ws),
632 period_size_, rn >= period_size_ ?
"true" :
"false");
635 int n_frames_avail = snd_pcm_avail_update (read_handle_ ? read_handle_ : write_handle_);
636 if (n_frames_avail < 0 ||
637 (n_frames_avail == 0 &&
638 snd_pcm_state (read_handle_ ? read_handle_ : write_handle_) != SND_PCM_STATE_RUNNING))
640 if (n_frames_avail < period_size_)
643 snd_pcm_hwsync (read_handle_ ? read_handle_ : write_handle_);
644 n_frames_avail = snd_pcm_avail_update (read_handle_ ? read_handle_ : write_handle_);
645 n_frames_avail =
MAX (n_frames_avail, 0);
648 if (n_frames_avail >= period_size_)
651 const uint diff_frames = period_size_ - n_frames_avail;
652 *timeoutp = diff_frames * 1000 / mix_freq_;
656 pcm_latency (uint *rlatency, uint *wlatency)
const override
658 snd_pcm_sframes_t rdelay, wdelay;
659 if (!read_handle_ || snd_pcm_delay (read_handle_, &rdelay) < 0)
661 if (!write_handle_ || snd_pcm_delay (write_handle_, &wdelay) < 0)
663 const int buffer_length = n_periods_ * period_size_;
665 *rlatency =
CLAMP (rdelay, 0, buffer_length);
666 *wlatency =
CLAMP (wdelay, 0, buffer_length);
669 pcm_read (
size_t n,
float *values)
override
672 float *dest = values;
673 size_t n_left = period_size_;
674 const size_t n_values = n_left * n_channels_;
676 read_write_count_ += 1;
679 ssize_t n_frames = snd_pcm_readi (read_handle_, period_buffer_, n_left);
682 PDEBUG (
"READ: %s: read() error: %s", alsadev_, snd_strerror (n_frames));
683 silence_error_handler++;
684 snd_pcm_prepare (read_handle_);
685 silence_error_handler--;
687 const size_t frame_size = n_channels_ *
sizeof (period_buffer_[0]);
688 memset (period_buffer_, 0, n_frames * frame_size);
692 convert_samples (n_frames * n_channels_,
const_cast<const int16_t*
> (period_buffer_), dest, __BYTE_ORDER__);
693 dest += n_frames * n_channels_;
702 pcm_write (
size_t n,
const float *values)
override
705 if (read_handle_ && read_write_count_ < 1)
707 silence_error_handler++;
708 snd_pcm_forward (read_handle_, period_size_);
709 silence_error_handler--;
710 read_write_count_ += 1;
712 read_write_count_ -= 1;
713 const float *floats = values;
714 size_t n_left = period_size_;
717 convert_clip_samples (n_left * n_channels_, floats, period_buffer_, __BYTE_ORDER__);
718 floats += n_left * n_channels_;
720 n = snd_pcm_writei (write_handle_, period_buffer_, n_left);
723 PDEBUG (
"WRITE: %s: write() error: %s", alsadev_, snd_strerror (n));
724 silence_error_handler++;
725 snd_pcm_prepare (write_handle_);
726 silence_error_handler--;
734static const String alsa_pcm_driverid = PcmDriver::register_driver (
"alsa",
735 AlsaPcmDriver::create,
736 [] (Driver::EntryVec &entries) {
737 list_alsa_drivers (entries);
741class AlsaSeqMidiDriver :
public MidiDriver {
742 using PortSubscribe =
std::unique_ptr<snd_seq_port_subscribe_t,
decltype (&snd_seq_port_subscribe_free)>;
743 snd_seq_t *seq_ =
nullptr;
744 int queue_ = -1, iport_ = -1, total_fds_ = 0;
745 snd_midi_event_t *evparser_ =
nullptr;
747 bool mdebug_ =
false;
749 make_port_subscribe (snd_seq_port_subscribe_t *other =
nullptr)
751 snd_seq_port_subscribe_t *subs =
nullptr;
752 snd_seq_port_subscribe_malloc (&subs);
754 return {
nullptr, snd_seq_port_subscribe_free };
756 snd_seq_port_subscribe_copy (subs, other);
757 return { subs, snd_seq_port_subscribe_free };
761 create (
const String &devid)
767 AlsaSeqMidiDriver (
const String &driver,
const String &devid) :
768 MidiDriver (driver, devid), subs_ { nullptr, nullptr }
782 aerror = snd_seq_open (&seq_,
"default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
784 aerror = snd_seq_set_client_name (seq_, myname.
c_str());
786 queue_ = snd_seq_alloc_named_queue (seq_, (myname +
" SeqQueue").c_str());
787 snd_seq_queue_tempo_t *qtempo = alsa_alloca0 (snd_seq_queue_tempo);
788 snd_seq_queue_tempo_set_tempo (qtempo, 60 * 1000000 / 480);
789 snd_seq_queue_tempo_set_ppq (qtempo, 1920);
791 aerror = snd_seq_set_queue_tempo (seq_, queue_, qtempo);
793 snd_seq_start_queue (seq_, queue_,
nullptr);
795 aerror = snd_seq_drain_output (seq_);
797 MDEBUG (
"SndSeq: %s: initialization failed: %s", myname, snd_strerror (-aerror));
799 MDEBUG (
"SndSeq: %s: queue started: %.5f", myname, queue_now());
800 return ase_error_from_errno (-aerror, Error::FILE_OPEN_FAILED);
806 auto is_identifier_char = [] (
int ch) {
807 return ( (ch >=
'A' && ch <=
'Z') ||
808 (ch >=
'a' && ch <=
'z') ||
809 (ch >=
'0' && ch <=
'9') ||
810 ch ==
'_' || ch ==
'$' );
812 for (
size_t i = 0; i <
string.size() &&
string[i]; ++i)
813 if (is_identifier_char (
string[i]))
814 normalized +=
string[i];
815 else if (normalized.
size() && normalized[normalized.
size() - 1] !=
'-')
820 make_devid (
int card, uint type,
const std::string &clientname,
int client, uint caps)
822 if (0 == (type & SND_SEQ_PORT_TYPE_MIDI_GENERIC))
825 if ((type & SND_SEQ_PORT_TYPE_SYNTHESIZER) && (type & SND_SEQ_PORT_TYPE_HARDWARE))
827 else if ((type & SND_SEQ_PORT_TYPE_SYNTHESIZER) && (type & SND_SEQ_PORT_TYPE_SOFTWARE))
828 devid =
"softsynth:";
829 else if (type & SND_SEQ_PORT_TYPE_SYNTHESIZER)
831 else if (type & SND_SEQ_PORT_TYPE_APPLICATION)
833 else if (type & SND_SEQ_PORT_TYPE_HARDWARE)
835 else if (type & SND_SEQ_PORT_TYPE_SOFTWARE)
842 snd_ctl_t *chandle =
nullptr;
844 if (snd_ctl_open (&chandle, sbuf.c_str(), SND_CTL_NONBLOCK) >= 0 && chandle)
846 snd_ctl_card_info_t *cinfo = alsa_alloca0 (snd_ctl_card_info);
847 if (snd_ctl_card_info (chandle, cinfo) >= 0)
849 const char *cid = snd_ctl_card_info_get_id (cinfo);
853 snd_ctl_close (chandle);
858 else if (!clientname.
empty())
866 enumerate (Driver::EntryVec *entries, snd_seq_port_info_t *sinfo =
nullptr,
const std::string &selector =
"", uint need_caps = 0)
869 snd_seq_client_info_t *cinfo = alsa_alloca0 (snd_seq_client_info);
870 snd_seq_client_info_set_client (cinfo, -1);
871 while (snd_seq_query_next_client (seq_, cinfo) == 0)
873 const int client = snd_seq_client_info_get_client (cinfo);
876 snd_seq_port_info_t *pinfo = alsa_alloca0 (snd_seq_port_info);
877 snd_seq_port_info_set_client (pinfo, client);
878 snd_seq_port_info_set_port (pinfo, -1);
879 while (snd_seq_query_next_port (seq_, pinfo) == 0)
881 const uint tmask = SND_SEQ_PORT_TYPE_MIDI_GENERIC |
882 SND_SEQ_PORT_TYPE_SYNTHESIZER |
883 SND_SEQ_PORT_TYPE_APPLICATION;
884 const int type = snd_seq_port_info_get_type (pinfo);
885 if (0 == (type & tmask))
887 const uint cmask = SND_SEQ_PORT_CAP_READ |
888 SND_SEQ_PORT_CAP_WRITE |
889 SND_SEQ_PORT_CAP_DUPLEX;
890 const int caps = snd_seq_port_info_get_capability (pinfo);
891 if (0 == (caps & cmask) || need_caps != (need_caps & caps))
893 const int card = snd_seq_client_info_get_card (cinfo);
894 const std::string clientname = snd_seq_client_info_get_name (cinfo);
895 std::string devportid = make_devid (card, type, clientname, client, caps);
896 if (devportid.
empty())
898 const int cport = snd_seq_port_info_get_port (pinfo);
906 if (snd_card_get_longname (card, &str) == 0 && str)
910 if (snd_card_get_name (card, &str) == 0 && str)
915 const bool is_usb = longname.
find (
" at usb-") != std::string::npos;
916 const bool is_kern = snd_seq_client_info_get_type (cinfo) == SND_SEQ_KERNEL_CLIENT;
917 const bool is_thru = is_kern && clientname ==
"Midi Through";
920 entry.devid = devportid;
921 entry.device_name =
string_capitalize (snd_seq_port_info_get_name (pinfo), 1,
false);
922 if (!string_startswith (entry.device_name, devname))
923 entry.device_name = devname +
" " + entry.device_name;
924 if (!cardname.
empty())
925 entry.device_name +=
" - " + cardname;
926 if (caps & SND_SEQ_PORT_CAP_DUPLEX)
927 entry.capabilities =
"Full-Duplex MIDI";
928 else if ((caps & SND_SEQ_PORT_CAP_READ) && (caps & SND_SEQ_PORT_CAP_WRITE))
929 entry.capabilities =
"MIDI In-Out";
930 else if (caps & SND_SEQ_PORT_CAP_READ)
931 entry.capabilities =
"MIDI Output";
932 else if (caps & SND_SEQ_PORT_CAP_WRITE)
933 entry.capabilities =
"MIDI Input";
934 if (!string_startswith (longname, cardname +
" at "))
935 entry.device_info = longname;
936 if (type & SND_SEQ_PORT_TYPE_APPLICATION || !is_kern)
937 entry.notice =
"Note: MIDI device is provided by an application";
938 entry.readonly = (caps & SND_SEQ_PORT_CAP_READ) && !(caps & SND_SEQ_PORT_CAP_WRITE);
939 entry.writeonly = (caps & SND_SEQ_PORT_CAP_WRITE) && !(caps & SND_SEQ_PORT_CAP_READ);
940 entry.priority = is_thru ? Driver::MIDI_THRU :
941 is_usb ? Driver::ALSA_USB :
942 is_kern ? Driver::ALSA_KERN :
944 entry.priority += Driver::WCARD *
MAX (0, card);
945 entry.priority += Driver::WDEV * client;
946 entry.priority += Driver::WSUB * cport;
947 entries->push_back (entry);
948 MDEBUG (
"DISCOVER: %s - %s", entry.devid, entry.device_name);
950 const bool match = selector == devportid;
953 snd_seq_port_info_copy (sinfo, pinfo);
961 list_drivers (Driver::EntryVec &entries)
963 AlsaSeqMidiDriver smd (
"?",
"");
964 if (smd.initialize (
program_alias() +
" Probing") == Error::NONE)
965 smd.enumerate (&entries);
968 open (IODir iodir)
override
975 PortSubscribe psub = make_port_subscribe();
978 Error error = seq_ ? Error::NONE : initialize (myname);
979 if (error != Error::NONE)
983 const bool require_readable = iodir == READONLY || iodir == READWRITE;
984 const bool require_writable = iodir == WRITEONLY || iodir == READWRITE;
985 const uint caps = require_writable * (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ) +
986 require_readable * (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE);
987 snd_seq_port_info_t *pinfo = alsa_alloca0 (snd_seq_port_info);
988 const bool match_devid = enumerate (
nullptr, pinfo, devid_, caps);
990 return Error::DEVICE_NOT_AVAILABLE;
991 flags_ |= Flags::READABLE * require_readable;
992 flags_ |= Flags::WRITABLE * require_writable;
994 snd_seq_port_info_t *minfo = alsa_alloca0 (snd_seq_port_info);
995 snd_seq_port_info_set_port (minfo, 0);
996 snd_seq_port_info_set_port_specified (minfo,
true);
997 snd_seq_port_info_set_name (minfo, (myname +
" LSP-0").c_str());
998 snd_seq_port_info_set_type (minfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
999 const int intracaps = SND_SEQ_PORT_CAP_NO_EXPORT | SND_SEQ_PORT_CAP_WRITE;
1000 snd_seq_port_info_set_capability (minfo, intracaps);
1001 snd_seq_port_info_set_midi_channels (minfo, 16);
1002 snd_seq_port_info_set_timestamping (minfo,
true);
1003 snd_seq_port_info_set_timestamp_real (minfo,
true);
1004 snd_seq_port_info_set_timestamp_queue (minfo, queue_);
1005 int aerror = snd_seq_create_port (seq_, minfo);
1008 iport_ = snd_seq_port_info_get_port (minfo);
1013 snd_seq_addr_t qaddr = {};
1014 qaddr.client = snd_seq_port_info_get_client (pinfo);
1015 qaddr.port = snd_seq_port_info_get_port (pinfo);
1016 snd_seq_port_subscribe_set_sender (&*psub, &qaddr);
1017 qaddr.client = snd_seq_client_id (seq_);
1018 qaddr.port = iport_;
1019 snd_seq_port_subscribe_set_dest (&*psub, &qaddr);
1020 snd_seq_port_subscribe_set_queue (&*psub, queue_);
1021 snd_seq_port_subscribe_set_time_update (&*psub,
true);
1022 snd_seq_port_subscribe_set_time_real (&*psub,
true);
1024 aerror = snd_seq_subscribe_port (seq_, &*psub);
1027 subs_ = make_port_subscribe (&*psub);
1033 snd_seq_drain_output (seq_);
1035 aerror = snd_midi_event_new (1024, &evparser_);
1038 snd_midi_event_init (evparser_);
1039 snd_midi_event_no_status (evparser_,
true);
1045 total_fds_ = snd_seq_poll_descriptors_count (seq_, POLLIN);
1048 struct pollfd *pfds = (
struct pollfd*) alloca (total_fds_ *
sizeof (
struct pollfd));
1049 if (snd_seq_poll_descriptors (seq_, pfds, total_fds_, POLLIN) > 0)
1052 AseTrans *trans = ase_trans_open ();
1053 static_assert (
sizeof (
struct pollfd) ==
sizeof (GPollFD));
1054 AseJob *job = ase_job_add_poll (pollin_func, (
void*)
this, pollfree_func, total_fds_, (
const GPollFD*) pfds);
1055 ase_trans_add (trans, job);
1056 ase_trans_commit (trans);
1062 flags_ |= Flags::OPENED;
1064 error = !aerror ? Error::NONE : ase_error_from_errno (-aerror, Error::FILE_OPEN_FAILED);
1065 MDEBUG (
"SndSeq: %s: opening readable=%d writable=%d: %s", devid_, readable(), writable(), ase_error_blurb (error));
1066 if (error != Error::NONE)
1079 AseTrans *trans = ase_trans_open ();
1080 AseJob *job = ase_job_remove_poll (pollin_func, (
void*)
this);
1081 ase_trans_add (trans, job);
1082 ase_trans_commit (trans);
1087 snd_midi_event_free (evparser_);
1088 evparser_ =
nullptr;
1092 snd_seq_unsubscribe_port (seq_, &*subs_);
1097 snd_seq_delete_port (seq_, iport_);
1102 snd_seq_free_queue (seq_, queue_);
1107 snd_seq_close (seq_);
1116 MDEBUG (
"SndSeq: %s: CLOSE: r=%d w=%d", devid_, readable(), writable());
1117 flags_ &= ~size_t (Flags::OPENED | Flags::READABLE | Flags::WRITABLE);
1122 pollfree_func (
void *data)
1130 union {
uint64_t u64[16];
char c[1]; } stbuf = {};
1131 assert_return (snd_seq_queue_status_sizeof() <=
sizeof (stbuf), NAN);
1132 snd_seq_queue_status_t *
stat = (snd_seq_queue_status_t*) &stbuf;
1133 int aerror = snd_seq_get_queue_status (seq_, queue_, stat);
1136 const snd_seq_real_time_t *rt = snd_seq_queue_status_get_real_time (stat);
1137 return rt->tv_sec + 1e-9 * rt->tv_nsec;
1142 has_events ()
override
1145 const bool pull_fifo =
true;
1146 return snd_seq_event_input_pending (seq_, pull_fifo) > 0;
1149 fetch_events (MidiEventOutput &estream,
double samplerate)
override
1152 const size_t old_size = estream.size();
1154 snd_seq_event_t *ev =
nullptr;
1155 const double now = queue_now();
1156 const auto mkid = [] (
uint note,
uint channel) {
1157 return (channel + 1) * 128 + note;
1159 bool must_sort =
false;
1160 const auto add = [&] (MidiEventOutput &estream,
const snd_seq_event_t *ev,
const MidiEvent &event) {
1161 const double t = ev->time.time.tv_sec + 1e-9 * ev->time.time.tv_nsec;
1162 const double diff = t - now;
1163 int64_t frames = diff * samplerate;
1164 if (event.type == event.NOTE_OFF)
1166 const auto last_frame = estream.last_frame();
1167 frames =
std::max (frames, last_frame);
1170 must_sort |= estream.append_unsorted (frame_delay, event);
1173 while (r = snd_seq_event_input (seq_, &ev), r >= 0)
1176 case SND_SEQ_EVENT_NOTEON:
1178 make_note_on (ev->data.note.channel, ev->data.note.note,
1179 ev->data.note.velocity * (1.0 / 127.0), 0,
1180 mkid (ev->data.note.note, ev->data.note.channel)));
1182 case SND_SEQ_EVENT_NOTEOFF:
1184 make_note_off (ev->data.note.channel, ev->data.note.note,
1185 ev->data.note.velocity * (1.0 / 127.0), 0,
1186 mkid (ev->data.note.note, ev->data.note.channel)));
1188 case SND_SEQ_EVENT_KEYPRESS:
1190 make_aftertouch (ev->data.note.channel, ev->data.note.note,
1191 ev->data.note.velocity * (1.0 / 127.0), 0,
1192 mkid (ev->data.note.note, ev->data.note.channel)));
1194 case SND_SEQ_EVENT_CONTROLLER:
1196 make_control8 (ev->data.control.channel, ev->data.control.param,
1197 ev->data.control.value));
1199 case SND_SEQ_EVENT_PGMCHANGE:
1201 make_program (ev->data.control.channel, ev->data.control.value));
1203 case SND_SEQ_EVENT_CHANPRESS:
1205 make_pressure (ev->data.control.channel, ev->data.control.value * (1.0 / 127.0)));
1207 case SND_SEQ_EVENT_PITCHBEND:
1209 make_pitch_bend (ev->data.control.channel,
1210 ev->data.control.value *
1211 (ev->data.control.value < 0 ? 1.0 / 8192.0 : 1.0 / 8191.0)));
1213 case SND_SEQ_EVENT_SYSEX:
1214 MDEBUG (
"%+4d ch=%-2u SYSEX: %s",
1215 int (samplerate * (ev->time.time.tv_sec + 1e-9 * ev->time.time.tv_nsec - now)),
1216 ev->data.control.channel, hex_str (ev->data.ext.len, (
const uint8*) ev->data.ext.ptr));
1218 case SND_SEQ_EVENT_CLOCK:
1221 case SND_SEQ_EVENT_CONTROL14:
1222 case SND_SEQ_EVENT_NONREGPARAM:
1223 case SND_SEQ_EVENT_REGPARAM:
1224 case SND_SEQ_EVENT_NOTE:
1226 MDEBUG (
"%+4d ch=%-2u SND_SEQ_EVENT_... %u",
1227 int (samplerate * (ev->time.time.tv_sec + 1e-9 * ev->time.time.tv_nsec - now)),
1228 ev->data.control.channel, ev->type);
1232 if (r < 0 && r != -EAGAIN)
1233 MDEBUG (
"SndSeq: %s: snd_seq_event_input: %s", devid_, snd_strerror (r));
1235 for (
size_t i = old_size; i < estream.
size(); i++)
1236 MDEBUG (
"%s", (estream.begin() + i)->to_string());
1238 estream.ensure_order();
1239 return estream.size() - old_size;
1243static const String alsa_seqmidi_driverid = MidiDriver::register_driver (
"alsa",
1244 AlsaSeqMidiDriver::create,
1245 AlsaSeqMidiDriver::list_drivers);
1251 for (
uint i = 0; i < 16 && i < len; i++)
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
#define MIN(a, b)
Yield minimum of a and b.
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
#define MAX(a, b)
Yield maximum of a and b.
String normalize(const String &path)
Convert path to normal form.
The Anklang C++ API namespace.
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
bool debug_key_enabled(const char *conditional)
Check if conditional is enabled by $ASE_DEBUG.
String string_join(const String &junctor, const StringS &strvec)
int16_t int16
A 16-bit signed integer.
float const_float_zeros[AUDIO_BLOCK_FLOAT_ZEROS_SIZE]
Block of const floats allof value 0.
uint8_t uint8
An 8-bit unsigned integer.
bool string_option_check(const String &optionlist, const String &feature)
Check if an option is set/unset in an options list string.
String string_capitalize(const String &str, size_t maxn, bool rest_tolower)
Capitalize words, so the first letter is upper case, the rest lower case.
Error
Enum representing Error states.
String string_from_int(int64 value)
Convert a 64bit signed integer into a string.
StringS string_split_any(const String &string, const String &splitchars, size_t maxn)
String program_alias()
Retrieve the program name as used for logging or debug messages.
std::string String
Convenience alias for std::string.
uint32_t uint
Provide 'uint' as convenience type.
String string_from_type(Type value)
Create a string from a templated argument value, such as bool, int, double.
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.