C++ Logo

sg13

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
with:

#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
possible
  // 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),
latency_filter::max(5ms));
  /* 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
DirectX
  assert(!c.is_definite());
  assert_throws(c.get_device());
  assert(count(c.drivers()) == 2);
  assert(count(c.devices()) == 4);

  c.refine(driver::asio);

  assert(!c.is_definite());
  assert_throws(c.get_device());
  assert(count(c.drivers()) == 1);
  assert(count(c.devices()) == 2);

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

  assert(c.is_definite());
  assert_no_throw(c.get_device());
  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
choices
  // 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