Audio 带限波形产生

Audio 带限波形产生,audio,signal-processing,synthesizer,Audio,Signal Processing,Synthesizer,我正在编写一个软件合成器,需要在44.1 kHz采样率下实时生成带限、无别名的波形。锯齿波现在就可以了,因为我可以通过混合两个锯齿波来产生一个脉冲波,一个是反转的,一个是相移的 到目前为止,我已经尝试了以下方法: 在启动时以不同的带宽限制频率预先计算一个周期的完全带宽限制波形样本,然后播放混合在一起的两个最接近的波形样本。我想可以,但感觉不是很优雅。需要大量样品,否则会听到它们之间的“差距”。插值和混合也是CPU密集型的 积分一列直流补偿sinc脉冲得到锯齿波。听起来不错,只是如果直流补偿不完全

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

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

  • 在启动时以不同的带宽限制频率预先计算一个周期的完全带宽限制波形样本,然后播放混合在一起的两个最接近的波形样本。我想可以,但感觉不是很优雅。需要大量样品,否则会听到它们之间的“差距”。插值和混合也是CPU密集型的

  • 积分一列直流补偿sinc脉冲得到锯齿波。听起来不错,只是如果直流补偿不完全正确,波会从零漂走(我发现这很棘手)。直流问题可以通过向积分器添加一点泄漏来减少,但随后会丢失低频


  • 所以,我的问题是:通常的做法是什么?任何建议的解决方案在CPU方面都必须是有效的,因为它必须是实时的,一次处理多个语音。

    有很多方法可以实现带限波形生成。你会像往常一样用计算成本和质量进行交易

    我建议您在此处查看此网站:

    查看档案!这里面全是好材料。我刚刚搜索了关键字“bandlimited”。弹出的材料,你应该保持忙碌至少一个星期

    顺便说一句,我不知道这是否是你想要的,但我在几年前做了alias reduced(例如,没有真正的频带限制)波形生成。我刚刚计算了上一个和当前样本位置之间的积分。对于传统的synth波形,如果在奇点处分割积分间隔(例如,当锯齿得到his重置时),就可以很容易地做到这一点。CPU负载低,质量可以满足我的需要


    我也有同样的漂移问题,但在积分上应用一个截止频率很低的高通,就消除了这种影响。真正的模拟合成器不会进入subhertz区域,所以你不会错过太多。

    这是我从Nils的想法中得到的灵感。将其粘贴到此处,以防对其他人有用。我简单地使用最后一个样本的相位变化作为内核大小(或截止值),通过解析的方法对锯齿波进行盒滤波。它工作得相当好,在最高的音符处有一些声音混叠,但对于正常使用来说,它听起来很棒

    为了进一步减少混叠,内核大小可以增加一点,例如2*phaseChange听起来也不错,不过会损失一些最高频率

    此外,在浏览SP以了解类似主题时,我还发现了另一个很好的DSP资源:。这是一个类库,有很多有用的DSP工具。它甚至有现成的带宽限制波形发生器。他们使用的方法是像我在第一篇文章中描述的那样集成sinc(尽管我想他们比我做得更好…)


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

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

    在看步行街

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

    小混混们把这个想法做得更进一步,做得更好 取一个带窗口的sinc,执行一个 最小相位重构 集成结果并将其存储在 桌子现在给你做一个振荡器 只需在每个位置插入一个小碎块 波形的不连续性。所以 一个方波你插入一个碎末 波形反转的位置,用于saw 波浪你插入一个小碎块在 值反转,但生成 正常斜坡


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

    我知道大约一年前有人问过这个问题,但是如果有人遇到这个问题,我建议在“phase+=(sampleRate/(float TableSize)/frequency;”上搜索友好且高度胜任的论坛;不编译。它应该是“(sampleRate/(float TableSize))/frequency;”?很好的链接!充满了漂亮的小片段
    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);
    }