11

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

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

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

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

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

4

4 回答 4

8

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

在此处输入图像描述

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

在此处输入图像描述

请参阅Band-Limited Sound Synthesis 的演练

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

MinBLEP 更进一步,采用加窗 sinc,执行最小相位重建,然后整合结果并将其存储在表中。现在要制作一个振荡器,您只需在波形的每个不连续处插入一个 MinBLEP。因此,对于方波,您在波形反转的地方插入一个 MinBLEP,对于锯齿波,您在值反转的地方插入一个 MinBLEP,但您正常生成斜坡。

于 2010-04-28T19:10:14.233 回答
5

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

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

http://www.musicdsp.org/

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

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

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

于 2008-10-06T17:49:42.677 回答
2

这就是我在 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);
}
于 2008-10-06T19:29:26.360 回答
1

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

于 2016-10-08T22:22:00.263 回答