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