1

我正在使用 NAudio 在 C# 中编写一个合成器。我试图让它在频率之间平滑滑动。但我有一种感觉,我对所涉及的数学不太了解。在切换到正确的下一个音高之前,它会以高音疯狂滑动。

从一个音高滑到另一个音高的数学正确方法是什么?

这是代码:

公共覆盖 int Read(float[] buffer, int offset, int sampleCount) { int sampleRate = WaveFormat.SampleRate;

        for (int n = 0; n < sampleCount; n++)
        {
            if (nextFrequencyQueue.Count > 0)
            {                    
                nextFrequency = nextFrequencyQueue.Dequeue();
            }

            if (nextFrequency > 0 && Frequency != nextFrequency)
            {
                if (Frequency == 0) //special case for first note
                {
                    Frequency = nextFrequency;
                }
                else //slide up or down to next frequency
                {
                    if (Frequency < nextFrequency)
                    {
                        Frequency = Clamp(Frequency + frequencyStep, nextFrequency, Frequency);
                    }
                    if (Frequency > nextFrequency)
                    {
                        Frequency = Clamp(Frequency - frequencyStep, Frequency, nextFrequency);
                    }
                }
            }

            buffer[n + offset] = (float)(Amplitude * Math.Sin(2 * Math.PI * time * Frequency));
            try
            {
                time += (double)1 / (double)sampleRate;
            }
            catch
            {
                time = 0;
            }
        }
        return sampleCount;
    }
4

3 回答 3

2

您正在使用绝对时间来确定波函数,因此当您稍微改变频率时,如果您以该新频率开始运行,下一个样本将会是什么。

我不知道已建立的最佳方法,但一种可能足够好的简单方法是计算相位 (φ = t mod 1/f old ) 并调整 t 以保持新频率下的相位 (t = φ/f)。

更平滑的方法是保留一阶导数。这更加困难,因为与波本身不同,一阶导数的幅度随频率而变化,这意味着保持相位是不够的。无论如何,考虑到您正在平稳地改变频率,这种增加的复杂性几乎可以肯定是矫枉过正。

于 2012-12-26T04:21:50.567 回答
2

一种方法是使用波表。你在一个数组中构造一个完整的正弦波周期,然后在你的 Read 函数中你可以简单地查找它。您阅读的每个样本,您都会根据所需的输出频率计算出一个数量。然后,当您想滑行到一个新频率时,您计算新的增量以查找表,然后不是直接去那里,而是逐步调整增量以在设定的时间段内移动到新值('glide ' 或滑音时间)。

于 2012-12-26T08:36:45.210 回答
2
  Frequency = Clamp(Frequency + frequencyStep, nextFrequency, Frequency);

人耳不是这样工作的,它是高度非线性的。自然是对数的。中间 C 的频率为 261.626 Hz。下一个音符 C# 与前一个音符的关系是 Math.Pow(2, 1/12.0) 或大约 1.0594631。所以 C# 是 277.183 Hz,增量为 15.557 Hz。

刻度上的下一个 C 具有两倍的频率,即 523.252 Hz。之后的 C# 是 554.366 Hz,增量为 31.084 Hz。注意增量是如何翻倍的。因此,您的代码片段中的 frequencyStep 不应该是加法,而应该是乘法

  buffer[n + offset] = (float)(Amplitude * Math.Sin(2 * Math.PI * time * Frequency));

这也是一个问题。您计算的样本不会从一个频率平滑过渡到下一个频率。当“频率”改变时有一个步骤。您必须对“时间”应用偏移量,以便它在采样时间“时间 - 1”产生完全相同的采样值,与之前使用频率值计算的值相同。这些步骤会产生带有许多谐波的高频伪影,这些谐波对人耳来说非常明显。

背景信息可在此Wikipedia 文章中找到。它将有助于可视化您生成的波形,您将很容易诊断出阶跃问题。我将复制 Wiki 图像:

在此处输入图像描述

于 2012-12-26T12:35:38.103 回答