我正在嵌入式平台上实现许多音频效果。从硬件中,我获得了需要一些按摩的格式的样本输入缓冲区1,然后是过程本身,然后是进一步的按摩。会有几种不同类型的效果,但现在您可以认为实际的
process
-函数作为模板参数 2 给出。
我想提取并按摩出来,这样就没有(源)代码重复,而且也不必复制到单独的缓冲区。此外,虽然大多数效果将在浮入浮出上运行,但我希望有一个选项可以直接处理整数。因此,使用一种尚未神话的语法,理想情况下它看起来像这样:
class Effect1
{
void process(AudioBuffers<float, float> buffers)
{
// A simple case, in and out buffers are processed in lockstep
for (auto && [in, out] : buffers)
{
// Note that "in" is massaged lazily, i.e. on this line
// to a float, and out is clamped and converted to int
// on assign
out = doSomething(in);
}
}
};
class Effect2
{
void process(AudioBuffers<uint32_t, int32_t> buffers)
{
// A completely silly effect, just to demonstrate random access
// and no conversion
std::size_t k = buffers.out.size() / 2;
for (std::size_t i = 0; i < buffers.in.size(); ++i)
{
buffers.out[k] = buffers.in[i]; // No need for conversion, we're just shuffling samples
k = k + 1 < buffers.out.size() ? k + 1 : 0;
}
}
};
...
template<typename InputType, typename OutputType>
AudioBuffers
{
// Note that the constructor arguments are always the types as provided
// by hardware, so they don't depend on the template arguments
AudioBuffers(std::array<const uint32_t, kBufferLength> &in, std::array<int32_t, kBufferLength> &out)
{
...
}
// Implementation. Here we know the requested InputType and OutputType,
// so we can insert the necessary massaging when constructing iterators...
...
}
...
template<class Effect>
void process(std::array<const uint32_t, kBufferLength> &in, std::array<int32_t, kBufferLength> &out, Effect &effect)
{
// Use CTAD to determine the correct instantiation/specialization of AudioBuffers which does the necessary massaging, as determined by the class Effect.
effect.process({in, out});
}
我当然可以以“暴力”方式实现 AudioBuffer 模板:
begin
和 end
返回这些迭代器,以及用于随机访问的 operator[]
begin
中提供 end
和 AudioBuffers
,返回这些迭代器的 zip(我必须实现/导入其实现,因为 std::zip
是 C++23)。但是,我感觉我想要的是“几乎”部分 std::ranges 和视图的重新实现。我认为我可以使用std::views::transform
来包装输入缓冲区。但是,我不知道如何包装输出,以便使用
std::ranges
中的元素进行分配时完成额外的处理步骤(和类型转换)。最后,我的问题:我可以通过组合 std
或
std::ranges
中已有的元素来构建上述功能吗?如果没有,有什么可以简化迭代器的实现,除了取消引用/分配之外,这将完全是样板文件?还有其他建议或框架挑战吗?脚注:具体来说,输入是24位int,只是符号位没有扩展到第24位以上,输出需要钳位到24位int范围并从float转换为int。关于上面的“仅对样本进行洗牌”示例,输出的第 24 位以上的位没有任何效果,一切正常。
std::variant<Effect1, Effect2, ...>
visit
ed。
namespace rv = std::ranges::views;
std::vector<int> in_vals = { 1,2,3, 5 };
std::vector<int> out_vals(in_vals.size());
for (auto& [in, out] : rv::zip(in_vals, out_vals)) { // << no
out = do_something(in);
}
您可以通过执行类似的操作来创建参考包装器的视图
namespace rv = std::ranges::views;
auto to_references(auto rng) {
return rng | rv::transform(
[](auto& v) {
return std::ref(v);
}
);
}
int do_something(int v) {
return 2 * v;
}
int main() {
std::vector<int> in_vals = { 1,2,3, 5 };
std::vector<int> out_vals(in_vals.size());
for (auto [in, out] : rv::zip(in_vals, to_references(rv::all(out_vals)))){
out.get() = do_something(in);
}
for (auto v : out_vals) {
std::println("{}", v);
}
}
我认为这可行,但基本上明白这不是惯用的。范围的目的不是就地修改容器。您希望以函数式风格使用范围函数,其中输入范围进入,输出范围退出。例如,
namespace r = std::ranges;
namespace rv = std::ranges::views;
int do_something(int v) {
return 2 * v;
}
int main() {
std::vector<int> in_vals = { 1,2,3, 5 };
auto out_vals = in_vals | rv::transform(do_something) | r::to<std::vector<int>>();
for (auto v : out_vals) {
std::println("{}", v);
}
}