Skip to content

Commit

Permalink
refactor: refactor audio_buffer to follow the API design in P1386 (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ghost-LZW committed May 4, 2023
1 parent a64cb84 commit 1678260
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 65 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This is an implementation of the standard audio API for C++ proposed in P1386.

The latest published revision of this proposal can always be found at [https://wg21.link/p1386]([https://wg21.link/p1386).
The latest published revision of this proposal can always be found at [https://wg21.link/p1386](https://wg21.link/p1386).

## Disclaimer

Expand All @@ -15,6 +15,17 @@ Personally, I think this proposal wouldn't accept recently, but I really like an

Basically, this repo's custom backend is a P1386-like SLD3 C++ wrapper. So, may not send pr to upstream. Because it is no longer a header-only library. (except the proposal owner accepts that)

Unlike the [original repo](https://github.com/stdcpp-audio/libstdaudio), I decide to flollow the API design presented in P1386 try my best.
So you can use it since you already read P1386.

Here are some stuffs different with the [original repo](https://github.com/stdcpp-audio/libstdaudio).

1. use mdspan for contiguous data view, and `std::span` vector for distant view.

2. add multidimensional subscript operation since it supported by compiler.

3. access operator's argument is `(channel, frame)` instead of `(frame, channel)`, which follow P1386.

## Repository structure

`include` contains the `audio` header, which is the only header users of the library should include. It also contains the header files of the different classes and functions, prefixed with `__audio_`. Please refer to these header files for a documentation of the API as implemented here. (We plan to set up proper documentation soon.)
Expand Down
2 changes: 1 addition & 1 deletion examples/level_meter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ int main() {

for (int frame = 0; frame < in.size_frames(); ++frame) {
for (int channel = 0; channel < in.size_channels(); ++channel) {
float abs_value = std::abs(in(frame, channel));
float abs_value = std::abs(in(channel, frame));

if (abs_value > max_abs_value)
max_abs_value.store(abs_value);
Expand Down
2 changes: 1 addition & 1 deletion examples/melody.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ int main() {
auto next_sample = synth.get_next_sample();

for (int channel = 0; channel < out.size_channels(); ++channel)
out(frame, channel) = next_sample;
out(channel, frame) = next_sample;
}
});

Expand Down
2 changes: 1 addition & 1 deletion examples/sine_wave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ int main() {
phase = std::fmod(phase + delta, 2.0f * static_cast<float>(M_PI));

for (int channel = 0; channel < out.size_channels(); ++channel)
out(frame, channel) = 0.2f * next_sample;
out(channel, frame) = 0.2f * next_sample;
}
});

Expand Down
2 changes: 1 addition & 1 deletion examples/white_noise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ int main() {

for (int frame = 0; frame < out.size_frames(); ++frame)
for (int channel = 0; channel < out.size_channels(); ++channel)
out(frame, channel) = white_noise(gen);
out(channel, frame) = white_noise(gen);
});

device->start();
Expand Down
106 changes: 72 additions & 34 deletions include/experimental/__p1386/audio_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <cassert>
#include <chrono>
#include <optional>
#include <type_traits>
#include <variant>

#if __cpp_lib_mdspan >= 202207L
#include <mdspan>
Expand All @@ -30,50 +32,64 @@ inline constexpr contiguous_deinterleaved_t contiguous_deinterleaved;
struct ptr_to_ptr_deinterleaved_t {};
inline constexpr ptr_to_ptr_deinterleaved_t ptr_to_ptr_deinterleaved;

template <typename _SampleType> class audio_buffer {
template <typename SampleType> class audio_buffer {
public:
using sample_type = _SampleType;
using sample_type = SampleType;
using index_type = size_t;
using contiguous_view_type =
mdspan<sample_type, dextents<index_type, 2>, layout_stride>;
using distant_view_type = std::vector<std::span<sample_type>>;

audio_buffer(sample_type *data, index_type num_frames,
index_type num_channels, contiguous_interleaved_t)
: _num_frames(num_frames), _num_channels(num_channels),
_stride(_num_channels), _is_contiguous(true) {
assert(num_channels <= _max_num_channels);
for (auto i = 0; i < _num_channels; ++i) {
_channels[i] = data + i;
}
_is_contiguous(true) {
_data_view = mdspan(
data, typename contiguous_view_type::mapping_type{
dextents<index_type, 2>{num_channels, num_frames},
std::array<index_type, 2>{1, num_channels}}); // layout_left
}

audio_buffer(sample_type *data, index_type num_frames,
index_type num_channels, contiguous_deinterleaved_t)
: _num_frames(num_frames), _num_channels(num_channels), _stride(1),
: _num_frames(num_frames), _num_channels(num_channels),
_is_contiguous(true) {
assert(num_channels <= _max_num_channels);
for (auto i = 0; i < _num_channels; ++i) {
_channels[i] = data + (i * _num_frames);
}
_data_view = mdspan(
data, typename contiguous_view_type::mapping_type{
dextents<index_type, 2>{num_channels, num_frames},
std::array<index_type, 2>{num_frames, 1}}); // layout_right
}

audio_buffer(sample_type **data, index_type num_frames,
index_type num_channels, ptr_to_ptr_deinterleaved_t)
: _num_frames(num_frames), _num_channels(num_channels), _stride(1),
: _num_frames(num_frames), _num_channels(num_channels),
_is_contiguous(false) {
assert(num_channels <= _max_num_channels);
copy(data, data + _num_channels, _channels.begin());
distant_view_type view;
view.reserve(_num_channels);
for (int i = 0; i < _num_channels; i++) {
view.emplace_back(data[i], num_frames);
}
_data_view = view;
}

sample_type *data() const noexcept {
return _is_contiguous ? _channels[0] : nullptr;
return _is_contiguous ? get<contiguous_view_type>(_data_view).data_handle()
: nullptr;
}

bool is_contiguous() const noexcept { return _is_contiguous; }

bool frames_are_contiguous() const noexcept {
return _stride == _num_channels;
return is_contiguous()
? get<contiguous_view_type>(_data_view).stride(0) == 1
: false;
}

bool channels_are_contiguous() const noexcept { return _stride == 1; }
bool channels_are_contiguous() const noexcept {
return is_contiguous()
? get<contiguous_view_type>(_data_view).stride(1) == 1
: false;
}

index_type size_frames() const noexcept { return _num_frames; }

Expand All @@ -83,44 +99,66 @@ template <typename _SampleType> class audio_buffer {
return _num_channels * _num_frames;
}

sample_type &operator()(index_type frame, index_type channel) noexcept {
// TODO: enable this only if AUDIO_USE_PAREN_OPERATOR defined.
sample_type &operator()(index_type channel, index_type frame) noexcept {
return const_cast<sample_type &>(
as_const(*this).operator()(frame, channel));
as_const(*this).operator()(channel, frame));
}

const sample_type &operator()(index_type frame,
index_type channel) const noexcept {
return _channels[channel][frame * _stride];
const sample_type &operator()(index_type channel,
index_type frame) const noexcept {
return std::visit(
[&channel, &frame](auto &&v) -> const sample_type & {
if constexpr (std::is_same_v<std::decay_t<decltype(v)>,
contiguous_view_type>) {
#if __cpp_multidimensional_subscript >= 202110L
return v[channel, frame];
#else
return v(channel, frame);
#endif
} else {
return v[channel][frame];
}
},
_data_view);
}

#if __cpp_multidimensional_subscript >= 202110L
sample_type &operator[](index_type frame, index_type channel) noexcept {
sample_type &operator[](index_type channel, index_type frame) noexcept {
return const_cast<sample_type &>(
as_const(*this).operator()(frame, channel));
as_const(*this).operator()(channel, frame));
}

const sample_type &operator[](index_type frame,
index_type channel) const noexcept {
return _channels[channel][frame * _stride];
const sample_type &operator[](index_type channel,
index_type frame) const noexcept {
return std::visit(
[&channel, &frame](auto &&v) -> const sample_type & {
if constexpr (std::is_same_v<std::decay_t<decltype(v)>,
contiguous_view_type>) {
return v[channel, frame];
} else {
return v[channel][frame];
}
},
_data_view);
}
#endif

private:
bool _is_contiguous = false;
index_type _num_frames = 0;
index_type _num_channels = 0;
index_type _stride = 0;
constexpr static size_t _max_num_channels = 16;
std::array<sample_type *, _max_num_channels> _channels = {};

std::variant<contiguous_view_type, distant_view_type> _data_view;
};

// TODO: this is currently macOS specific!
using audio_clock_t = chrono::steady_clock;

template <typename _SampleType> struct audio_device_io {
std::optional<audio_buffer<_SampleType>> input_buffer;
template <typename SampleType> struct audio_device_io {
std::optional<audio_buffer<SampleType>> input_buffer;
std::optional<chrono::time_point<audio_clock_t>> input_time;
std::optional<audio_buffer<_SampleType>> output_buffer;
std::optional<audio_buffer<SampleType>> output_buffer;
std::optional<chrono::time_point<audio_clock_t>> output_time;
};

Expand Down
52 changes: 26 additions & 26 deletions test/audio_buffer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ TEST_CASE("Interleaved contiguous buffer") {
SECTION("Element read (const)") {
const auto &cbuffer = buffer;
CHECK(cbuffer(0, 0) == 0);
CHECK(cbuffer(1, 0) == 2);
CHECK(cbuffer(2, 0) == 4);
CHECK(cbuffer(0, 1) == 1);
CHECK(cbuffer(0, 1) == 2);
CHECK(cbuffer(0, 2) == 4);
CHECK(cbuffer(1, 0) == 1);
CHECK(cbuffer(1, 1) == 3);
CHECK(cbuffer(2, 1) == 5);
CHECK(cbuffer(1, 2) == 5);
}

SECTION("Element write") {
buffer(0, 0) = 6;
buffer(1, 0) = 7;
buffer(2, 0) = 8;
buffer(0, 1) = 9;
buffer(0, 1) = 7;
buffer(0, 2) = 8;
buffer(1, 0) = 9;
buffer(1, 1) = 10;
buffer(2, 1) = 11;
buffer(1, 2) = 11;

CHECK(data == std::array<float, 6>{6, 9, 7, 10, 8, 11});
}
Expand Down Expand Up @@ -98,20 +98,20 @@ TEST_CASE("Deinterleaved contiguous buffer") {
SECTION("Element read (const)") {
const auto &cbuffer = buffer;
CHECK(cbuffer(0, 0) == 0);
CHECK(cbuffer(1, 0) == 1);
CHECK(cbuffer(2, 0) == 2);
CHECK(cbuffer(0, 1) == 3);
CHECK(cbuffer(0, 1) == 1);
CHECK(cbuffer(0, 2) == 2);
CHECK(cbuffer(1, 0) == 3);
CHECK(cbuffer(1, 1) == 4);
CHECK(cbuffer(2, 1) == 5);
CHECK(cbuffer(1, 2) == 5);
}

SECTION("Element write") {
buffer(0, 0) = 6;
buffer(1, 0) = 7;
buffer(2, 0) = 8;
buffer(0, 1) = 9;
buffer(0, 1) = 7;
buffer(0, 2) = 8;
buffer(1, 0) = 9;
buffer(1, 1) = 10;
buffer(2, 1) = 11;
buffer(1, 2) = 11;

CHECK(data == std::array<float, 6>{6, 7, 8, 9, 10, 11});
}
Expand All @@ -132,8 +132,8 @@ TEST_CASE("Deinterleaved pointer-to-pointer buffer") {
CHECK(!buffer.frames_are_contiguous());
}

SECTION("channels_are_contiguous returns true") {
CHECK(buffer.channels_are_contiguous());
SECTION("channels_are_contiguous returns false") {
CHECK(!buffer.channels_are_contiguous());
}

SECTION("size_channels returns correct value") {
Expand All @@ -151,20 +151,20 @@ TEST_CASE("Deinterleaved pointer-to-pointer buffer") {
SECTION("Element read (const)") {
const auto &cbuffer = buffer;
CHECK(cbuffer(0, 0) == 0);
CHECK(cbuffer(1, 0) == 1);
CHECK(cbuffer(2, 0) == 2);
CHECK(cbuffer(0, 1) == 3);
CHECK(cbuffer(0, 1) == 1);
CHECK(cbuffer(0, 2) == 2);
CHECK(cbuffer(1, 0) == 3);
CHECK(cbuffer(1, 1) == 4);
CHECK(cbuffer(2, 1) == 5);
CHECK(cbuffer(1, 2) == 5);
}

SECTION("Element write") {
buffer(0, 0) = 6;
buffer(1, 0) = 7;
buffer(2, 0) = 8;
buffer(0, 1) = 9;
buffer(0, 1) = 7;
buffer(0, 2) = 8;
buffer(1, 0) = 9;
buffer(1, 1) = 10;
buffer(2, 1) = 11;
buffer(1, 2) = 11;

CHECK(left == std::array<float, 3>{6, 7, 8});
CHECK(right == std::array<float, 3>{9, 10, 11});
Expand Down

0 comments on commit 1678260

Please sign in to comment.