我正在编写软件合成器,需要以44.1 kHz采样率实时生成带限,无别名波形。 Sawtooth波形现在可以做,因为我可以通过将两个锯齿混合在一起产生脉冲波,一个锯齿波被反转并且相移。

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

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

  2. 整合一系列直流补偿sinc脉冲以获得锯齿波。听起来不错,除非你没有得到完全正确的DC补偿(我发现它非常棘手),波浪会从零偏离。通过向积分器添加一点泄漏可以减少DC问题,但随后会丢失低频。

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

有帮助吗?

解决方案

有很多方法可以实现带限波形的生成。您将最终将计算成本与质量进行交易。

我建议您在这里查看此网站:

http://www.musicdsp.org/

查看存档!它充满了好材料。我刚刚对关键字“bandlimited”进行了搜索。弹出的材料应该保持忙碌至少一周。

Btw - 不知道这是不是你要找的东西,但几年前我做了别名减少(例如不是真正的带限)波形。我刚刚计算了最后一个和当前样本位置之间的积分。对于传统的合成波形,如果将积分区间分割为奇点(例如,当锯齿得到它的复位时),你可以很容易地做到这一点。 CPU负载很低,质量可以满足我的需求。

我有相同的漂移问题,但在积分上应用具有非常低截止频率的高通消除了这种效应。真正的模拟合成器无论如何都不会进入subhertz区域,所以你不会错过太多。

其他提示

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

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

请参阅 Band-Limited Sound Synthesis 的演练。

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

  

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

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

为了减少混叠,内核大小可以增加一点,使得2 * phaseChange例如听起来也不错,尽管你会失去一些最高频率。

此外,这是我在浏览类似主题的SP时发现的另一个优秀的DSP资源: C ++(STK)中的Synthesis ToolKit 。它是一个拥有大量有用的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);
}

使用简单的高通滤波器可以减少blit的DC偏移! - 就像真正的模拟电路,他们使用直流阻塞帽!

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top