编程低通滤波器

时间:2012-04-18 08:26:39

标签: audio filter emulation

我已经用Java编写了一个Sega Master System仿真器(尽管这个问题不是特定于Java的)并且已经完成了除SN76489声音芯片之外的所有事情。这个芯片如何发出声音很容易 - 我遇到的问题是将其转换为可通过PC /笔记本电脑/ JVM运行的任何形式播放的形式。

我已经确定了以下步骤;

由于SN76489以大约221khz的采样率运行 - 这意味着它输出的波频率高达110khz(尽管在实践中我怀疑任何事情都不会这么高)。因此,我需要在对其进行下采样之前实现低通滤波器。

然后我想将它下采样到44.1khz,所以我可以通过音频线输出它(在我的例子中是Java源数据线)。

为此,我需要将低通滤波器设置为22.05khz,但问题是我不知道(从数学上讲)低通滤波器实际上是如何工作的。我需要这个才能写一个。

目前,我的声音芯片创建了0.2秒的缓冲区,如上所述,以221khz存储样本。根据我的理解,我可以下采样 - 但是如果我在没有首先应用低通滤波器的情况下进行下采样,我理解我可能会在生成的声音流中出现混叠毛刺。

任何人都可以推荐最简单的数学思维算法 - 我意识到由于涉及的变量,低通永远不会“精确”,但我只需要一个对我的大脑来说足够沉重的声音解释(其中现在还没有真正处理过波浪处理才能理解。

如果有帮助,具体内容如下: SN76489同时输出三个方波和一个噪声槽。将它们相加在一起,然后输出到混频器/放大器 - 链路中的这个阶段是我想要在下采样然后放大波形之前运行低通滤波器的地方。人们可以给予我的任何帮助非常感谢。我意识到需要背景阅读,但我想知道“我需要阅读什么”。非常感谢。

更新:我最终想出了一个更简单的方法 - 尽管还不是很好。 SN76489通过从寄存器值生成每个音调通道来工作 - 输出极性1,值递减,依此类推 - 直到值为0,然后重置该值并将极性切换为-1,依此类推。然后将该值乘以一个体积,得到该样本的最终幅度,并与其他通道求和。

我现在只是阻止一个寄存器值,它会产生一个高于我所需要的奈奎斯特限制的方波。这给我留下了更好的信号,但它仍然有一些嗡嗡声/砰砰声 - 不知道为什么最大可能的频率应该是18,473Hz。这种爆裂/嗡嗡声是否是因为当芯片将频道从一个频率切换到另一个频率时,它不允许当前的波形完全完成?例如,芯片输出1111,然后是00 - 而不是全四个零并切换到新的频率 - 这可能会导致混叠吗?

2 个答案:

答案 0 :(得分:2)

编辑:我在下方添加了一个过滤器实现,可以回答您提出的问题。然而,以如此高的采样率实现具有信号的高阶滤波器将每秒消耗数百万次操作。如果您首先对芯片的输出进行频谱分析可能是最好的。如果没有高于几kHz的声能,则抗混叠滤波器会浪费处理资源。即使存在高达中等高频的能量,也可能值得先对信号进行抽取,然后在应用第二阶段抽取之前进行滤波。另外,您可能还希望将其降至比44.1 kHz低得多的速率。对于主系统仿真器,你可能没有8或10 kHz的采样率(我们在这里不是说高保真)。但无论如何,要回答有关如何使用您指定的采样率和截止值实现低通滤波器的问题。 。 。

好的,首先设计一个低通滤波器。 matlab抽取函数对我来说听起来不错,因此我们将为此示例复制该方法。文档说明如下

  

抽取矢量y的长度比输入短r倍   矢量x。默认情况下,decimate采用八阶低通   切比雪夫I型滤波器的截止频率为0.8 *(Fs / 2)/ r。它   在正向和反向方向上过滤输入序列   消除所有相位失真,有效地使滤波器顺序加倍。

Cheby滤波器是一个不错的选择,因为它们比Butterworth设计具有更陡的抑制性,但代价是通带波纹很小。我们不能在实时系统中对两个方向进行IIR过滤,但这应该可以满足您的需要。我们可以使用以下Matlab代码制作滤波器系数。 。 。 。

sr = 221e3;
srDesired = 44.1e3;
order = 8;
passBandRipple = 1; %//dB

Wp = 0.8 * (srDesired/2) / (sr/2);

[b,a] = cheby1 (order, passBandRipple, Wp, 'low');

freqz(b,a,[],sr, 'half');

sos = tf2sos(b,a)

这为我们提供了一个8阶IIR滤波器,其响应如下。这看起来像我们想要的。相位响应对于该应用来说并不重要。截止频率为0.8 * 22.050 kHz,因为您希望接近奈奎斯特极限的信号在抽取之前得到很好的衰减。

enter image description here

最后的tf2sos命令将我们刚刚创建的过滤器转换为可以使用级联双二阶滤波器部分实现的二阶部分。该命令的输出如下。 。 。

第A节

  

b = 1.98795003258633e-07,3.97711540624783e-07,1.98854354149782e-07,
  a = 1 -1.81843900641769,0.835282840946310

第B节

  

b = 1,2.02501937393162,1.02534004997240,
  a = 1,-1.77945624664044,0.860871442492022

第C节

  

b = 1,1.9938921206706,0.999702296645625,
  a = 1,-1.73415546937221,0.907015729252152

第D节

  

b = 1,1.97498006006623,0.975285456788754,
  a = 1,-1.72600279649390,0.966884508765457

现在,您可以在滤波器级联中为每个双二阶段使用这些滤波器系数。您可以使用类似以下示例中的代码实现此过滤器。它是C代码,但你应该能够很容易地将它转换为java。请注意,下面的代码中没有a0系数。上面的第二个顺序部分被正确归一化,因此a0总是等于1.只需将其保留。

//Make the following vars private members of your filter class
// b0, b1, b2, a1, a2 calculated above
// m1, m2 are the memory locations
// dn is the de-denormal coeff (=1.0e-20f) 

void processBiquad(const float* in, float* out, unsigned length)
{
    for(unsigned i = 0; i < length; ++i)
    {
        register float w = in[i] - a1*m1 - a2*m2 + dn;
        out[i] = b1*m1 + b2*m2 + b0*w;
        m2 = m1; m1 = w;
    }
    dn = -dn;
}

您应该为此过滤器创建一个类,然后实例化4个单独的类(每个过滤器1个),将a和b值设置为上面指定的值。接下来,将一个阶段的输入挂钩到下一个阶段的输出,以便为您提供级联。

答案 1 :(得分:1)

http://en.wikipedia.org/wiki/Butterworth_filter

C代码生成器:http://www-users.cs.york.ac.uk/~fisher/mkfilter/(应该可以很容易地翻译成Java)