【问题标题】:Bandlimited waveform generation [closed]带限波形生成[关闭]
【发布时间】:2010-09-15 13:48:19
【问题描述】:

我正在编写一个软件合成器,需要以 44.1 kHz 采样率实时生成带限、无混叠波形。锯齿波现在就可以了,因为我可以通过将两个锯齿混合在一起产生脉冲波,一个倒相和相移。

到目前为止,我已经尝试了以下方法:

  1. 在启动时预计算不同带限频率的一个周期完美带限波形样本,然后播放混合在一起的两个最接近的波形样本。我猜可以工作,但感觉不是很优雅。需要大量样本,否则会听到它们之间的“间隙”。插值和混合也相当消耗 CPU。

  2. 集成一系列 DC 补偿 sinc 脉冲以获得锯齿波。听起来不错,除了如果您没有完全正确地获得直流补偿(我发现这真的很棘手),波会从零漂移。可以通过在积分器中增加一点泄漏来减少 DC 问题,但这样会丢失低频。

所以,我的问题是:通常的做法是什么?任何建议的解决方案都必须在 CPU 方面高效,因为它必须实时完成,同时针对多个声音。

【问题讨论】:

  • 我知道这是在一年前被问到的,但是对于其他偶然发现这个问题的人,我建议在KVR 搜索友好且能干的DSP and Plug-in Development 论坛

标签: audio signal-processing synthesizer


【解决方案1】:

生成带限波形的一种快速方法是使用带限步长 (BLEP)。您自己生成带限步骤:

并将其存储在波表中,然后用带限步长替换每个转换,以创建如下所示的波形:

通过Band-Limited Sound Synthesis查看演练。

由于这个 BLEP 是非因果的(意味着它延伸到未来),为了生成实时波形,最好使用最小相位频带限制步骤,称为 MinBLEP,它具有相同的频率频谱,但只延伸到过去:

MinBLEP 将这个想法更进一步,并 取一个窗口的 sinc,执行一个 最小相位重建,然后 整合结果并将其存储在 桌子。现在制作一个振荡器 只需在每个位置插入一个 MinBLEP 波形的不连续性。因此对于 插入 MinBLEP 的方波 波形反转的地方,对于锯 挥手插入一个 MinBLEP,其中 值反转,但您生成 正常斜坡。

【讨论】:

    【解决方案2】:

    有很多方法可以生成带限波形。您最终将像往常一样以计算成本与质量进行交易。

    我建议你在这里看看这个网站:

    http://www.musicdsp.org/

    查看存档!它充满了好材料。我刚刚搜索了关键字“bandlimited”。如果您至少忙碌一周,弹出的材料。

    顺便说一句 - 不知道这是否是您要找的,但几年前我确实减少了别名(例如不是真正的频带限制)波形生成。我刚刚计算了最后一个样本位置和当前样本位置之间的积分。对于传统的合成器波形,如果您在奇点处拆分积分间隔(例如,当锯齿重置时),您可以很容易地做到这一点。 CPU 负载很低,质量可以满足我的需要。

    我也遇到过同样的漂移问题,但是在积分上应用截止频率非常低的高通可以消除这种影响。无论如何,真正的模拟合成器不会进入亚赫兹区域,因此您不会错过太多。

    【讨论】:

    • "phase += (sampleRate/(float TableSize)/frequency;" 不编译。应该是 "(sampleRate/(float TableSize))/frequency;" 吗?
    • 好链接!充满了很多漂亮的小sn-ps
    【解决方案3】:

    这是我在 Nils 的想法的启发下提出的。将其粘贴在这里以防对其他人有用。我只是使用最后一个样本的相位变化作为内核大小(或截止值)对锯齿波进行分析框过滤。它工作得相当好,在最高音符处有一些听得见的混叠,但对于正常使用来说,它听起来很棒。

    为了进一步减少混叠,可以稍微增加内核大小,例如使其 2*phaseChange 听起来也不错,尽管您会损失一些最高频率。

    另外,这是我在浏览 SP 以查找类似主题时发现的另一个不错的 DSP 资源:The Synthesis ToolKit in C++ (STK)。它是一个类库,有很多有用的 DSP 工具。它甚至可以使用带限波形发生器。他们使用的方法是像我在第一篇文章中描述的那样集成 sinc(尽管我猜他们做得比我好......)。

    float getSaw(float phaseChange)
    {
        static float phase = 0.0f;
        phase = fmod(phase + phaseChange, 1.0f);
        return getBoxFilteredSaw(phase, phaseChange);
    }
    
    float getPulse(float phaseChange, float pulseWidth)
    {
        static float phase = 0.0f;
        phase = fmod(phase + phaseChange, 1.0f);
        return getBoxFilteredSaw(phase, phaseChange) - getBoxFilteredSaw(fmod(phase + pulseWidth, 1.0f), phaseChange);
    }
    
    float getBoxFilteredSaw(float phase, float kernelSize)
    {
        float a, b;
    
        // Check if kernel is longer that one cycle
        if (kernelSize >= 1.0f) {
            return 0.0f;
        }
    
        // Remap phase and kernelSize from [0.0, 1.0] to [-1.0, 1.0]
        kernelSize *= 2.0f;
        phase = phase * 2.0f - 1.0f;
    
        if (phase + kernelSize > 1.0f)
        {
            // Kernel wraps around edge of [-1.0, 1.0]
            a = phase;
            b = phase + kernelSize - 2.0f;
        }
        else
        {
            // Kernel fits nicely in [-1.0, 1.0]
            a = phase;
            b = phase + kernelSize;
        }
    
        // Integrate and divide with kernelSize
        return (b * b - a * a) / (2.0f * kernelSize);
    }
    

    【讨论】:

      【解决方案4】:

      blit 的 DC 偏移 - 可以通过简单的高通滤波器来减少! - 很像一个真正的模拟电路,他们使用隔直电容!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-08
        • 2011-11-16
        • 2013-03-30
        • 1970-01-01
        • 2015-11-22
        • 1970-01-01
        • 1970-01-01
        • 2019-05-28
        相关资源
        最近更新 更多