C++ Logo


Advanced search

[SG13] std::audio configuration API

From: Henry Högelow <henry_at_[hidden]>
Date: Mon, 26 Aug 2019 18:34:06 +0200
Hi all,

Guy and Timur had a talk in Berlin at C++ Meetup last Tuesday. They
explained how the standardisation process works in general and which
problems are still to solve for a proper std::audio api. They explicitly
asked for input regarding the audio hardware configuration/negotiation.

My name is Henry, I work in the audio domain with C++ for about 18 years
now and have quite some experience with Alsa, but was in touch with ASIO
and DirectX as well. So I thought I should maybe try to grab the basic
ideas of the Alsa negotiation and express it in C++. Here is what I came up

#include <stdlib.h>
#include <vector>
#include <list>
#include <bitset>
#include <chrono>
#include <assert.h>

namespace std
namespace audio
using namespace chrono_literals;

template <typename T>
struct iterable
  // todo

struct sample_format
  bool is_signed() const;
  bool is_unsigned() const;
  bool is_big_endian() const;
  bool is_little_endian() const;

  size_t width() const;
  size_t depth() const;

  bool is_integer() const;
  bool is_fixed_point() const;
  bool is_floating_point() const;

  size_t num_bits_pre_comma() const;
  size_t num_bits_post_comma() const;

  static sample_format SLE32;
  static sample_format SLE16;
  static sample_format SLE8;
  // more to come ...

struct direction
  bool is_capture() const;
  bool is_playback() const;

  static direction capture;
  static direction playback;
  static direction duplex;

struct interleaving
  bool is_interleaved() const;
  bool is_separate_channels() const;

  static interleaving interleaved;
  static interleaving separate;

struct api
  bool supports_pull() const;
  bool supports_push() const;

  static api pull;
  static api push;

struct channel_role
  explicit channel_role(const string&); // drivers might create custom roles

  bool is_left() const;
  bool is_right() const;
  bool is_center() const;
  bool is_mono() const;
  bool is_front() const;
  bool is_rear() const;
  bool is_sub() const;
  bool is_top() const;
  bool is_bottom() const;

  static channel_role front_left;
  static channel_role center;
  static channel_role front_right;
  static channel_role rear_left;
  static channel_role rear_right;
  static channel_role subwoofer;
  // more to come ...

using channel_layout = std::vector<channel_role>;

struct ownership
  bool is_exclusive() const;
  bool is_shared() const;

  static ownership exclusive;
  static ownership shared;

struct driver
  explicit driver(const string&);
  std::string name() const;
  uint32_t id() const;

  static driver asio;
  static driver directX;
  static driver alsa;
  static driver core_audio;
  // more to come ...

struct device
  explicit device(const string&);
  std::string name() const;
  uint32_t id() const;

using samplerate = uint32_t;

struct samplerate_filter
  static samplerate_filter exact(samplerate sr);
  static samplerate_filter min(samplerate sr);
  static samplerate_filter max(samplerate sr);

using buffersize = uint64_t;

struct buffersize_filter
  static buffersize_filter exact(buffersize size);
  static buffersize_filter min(buffersize size);
  static buffersize_filter max(buffersize size);

using latency = chrono::nanoseconds;

struct latency_filter
  static latency_filter exact(latency l);
  static latency_filter min(latency l);
  static latency_filter max(latency l);

struct audio_device
  template <typename CB> void attach_callback(CB&& cb);
  void start();
  void stop();

  // only const accessors here, you can not reconfigure a device
  // once you obtained it from configuration space
  driver driver() const;
  device device() const;
  buffersize buffersize() const;
  samplerate samplerate() const;
  latency latency() const;
  channel_layout channel_layout() const;
  // etc

struct configuration_space
  iterable<driver> drivers() const;
  iterable<device> devices() const;
  iterable<sample_format> formats() const;
  iterable<direction> directions() const;
  iterable<latency> latencies() const;
  iterable<samplerate> samplerates() const;
  iterable<buffersize> buffersizes() const;
  iterable<channel_layout> channel_layouts() const;

  template <typename... Filter>
  void refine(Filter... f);

  bool is_definite() const;
  // the space has been refined, so it contains only a single device now
  // and enough information to properly configure it

  audio_device get_device(); // will throw if !is_definite()

  static configuration_space default_playback();
  static configuration_space default_capture();

void how_to_use_it()
  configuration_space c;
  // c is not refined yet, so it contains every configuration that might be
  // on the current system

  c.refine(api::push, sample_format::SLE32, interleaving::interleaved);
  /* c is now bound to those drivers and devices, that support signed
32bits, little endian,
   * interleaved and that support audio to be pushed, instead of being
pulled via callbacks.
   * So calls to drivers(), devices(), directions(), etc will return
filtered lists that do only
   * contain combinations still available */

  c.refine(direction::duplex, latency_filter::min(3ms),
  /* c is now also bound to devices supporting 3ms <= latency <= 5ms,
offering input and output */

  // lets assume, we have two two devices left, both available via ASIO and
  assert(count(c.drivers()) == 2);
  assert(count(c.devices()) == 4);


  assert(count(c.drivers()) == 1);
  assert(count(c.devices()) == 2);

  c.refine(device("RME FireFace 400"));

  assert(count(c.drivers()) == 1);
  assert(count(c.devices()) == 1);

  auto device = c.get_device();
  // use device for audio processing

  // use another configuration_space for filling the fields of a UI dialog
  // if UX specifies like that, show only available items in menus, if
  // in other UI elements reduce the available options

Best, Henry
Henry Högelow
Freier Software-Architekt
Telefon: (+49) 151 22 80 40 28
E-Mail: henry_at_[hidden]
Internet: www.hoegelow.com

Received on 2019-08-26 11:36:30