修改指定值的范围“视图”

问题描述 投票:0回答:1

我正在嵌入式平台上实现许多音频效果。从硬件中,我获得了需要一些按摩的格式的样本输入缓冲区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 模板:

  1. 基本上为输入和输出实现迭代器,分别对解引用进行处理并在输入和输出上进行分配
  2. 将输入和输出缓冲区包装在一个类中,该类提供
    begin
    end
    返回这些迭代器,以及用于随机访问的
    operator[]
  3. begin
    中提供
    end
    AudioBuffers
    ,返回这些迭代器的 zip(我必须实现/导入其实现,因为
    std::zip
    是 C++23)。
  4. 可能需要更多样板,以使所有扣除正确进行

但是,我感觉我想要的是“几乎”部分 std::ranges 和视图的重新实现。我认为我可以使用std::views::transform来包装输入缓冲区。但是,我不知道如何包装输出,以便使用

std::ranges
中的元素进行分配时完成额外的处理步骤(和类型转换)。
最后,

我的问题

:我可以通过组合 std

std::ranges
中已有的元素来构建上述功能吗?如果没有,有什么可以简化迭代器的实现,除了取消引用/分配之外,这将完全是样板文件?还有其他建议或框架挑战吗?

脚注:

具体来说,输入是24位int,只是符号位没有扩展到第24位以上,输出需要钳位到24位int范围并从float转换为int。关于上面的“仅对样本进行洗牌”示例,输出的第 24 位以上的位没有任何效果,一切正常。
  1. 实际的实施将是
  2. std::variant<Effect1, Effect2, ...>
  3. ,即
    visit
    ed。
    
        
c++ iterator c++20 std-ranges
1个回答
0
投票

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); } }

© www.soinside.com 2019 - 2024. All rights reserved.