编写低通滤波器

7
我用Java编写了一个世嘉主机模拟器(虽然这个问题与Java无关),除了SN76489声音芯片外,我已经完成了一切。这个芯片如何发出声音很容易,但我的问题是将其转换为可在电脑/笔记本电脑/JVM运行的设备上播放的形式。
我已经确定了以下步骤:
由于SN76489以大约221khz的采样率运行,这意味着它输出的波形的频率高达110khz(尽管实际上我怀疑任何东西都不会这么高)。因此,在我对其进行下采样之前,我需要先实现一个低通滤波器。
接下来,我想将其下采样到44.1khz,以便可以通过音频线输出它(在我的情况下,是Java Source Data Line)。
为此,我需要将低通滤波器设置为22.05khz,但问题在于我不知道(数学上说)低通滤波器实际上是如何工作的。我需要这样做才能编写一个。
目前,我的声音芯片创建一个0.2秒缓冲区,并在其中存储样本,采样率为221khz,如上所述。 我可以进行下采样,但如果没有先应用低通滤波器进行下采样,我了解到结果的声音流中可能会产生混叠故障。
有人可以为此推荐最简单的数学算法吗?我知道由于涉及到的变量,低通滤波器永远不是“精确”的,但我只需要一个足够简单的、为我的大脑(在这之前根本没有处理过波形处理)准备的合理解释。
如果有帮助的话,具体内容如下:
SN76489同时输出三个方波和一个噪声信道。它们被加在一起,输出到混频器/放大器 - 在该链的这个阶段,我想在下采样并放大波形之前运行低通滤波器。任何人能给我提供帮助将不胜感激。我意识到需要背景知识,但我想知道应该阅读什么。非常感谢。
更新:最终我想出了一种更简单的方法-尽管还没有完全实现。 SN76489通过从寄存器值生成每个音调信道来工作-输出1的极性,值递减,以此类推-直到值为0,然后重置该值并将极性切换为-1,以此类推。然后将该值乘以音量以获得该样本的最终振幅,并与其他通道相加。
我现在阻止产生超过我需要的奈奎斯特限制之上的方波寄存器值。这让我得到了一个更好的信号,但它仍然有一些嗡嗡声/爆音 - 不确定为什么,因为最大可能的频率应该是18,473Hz。这种爆鸣声是不是因为当芯片从一个频率切换到另一个频率时,它不允许当前的波形完全完成呢?举个例子,芯片输出1111,然后是00 - 而不是完整的四个零并切换到新的频率 - 这可能会导致混叠现象,对吗?
2个回答

2

编辑:我已经包含了一个下面的过滤器实现,回答了你提出的问题。然而,在如此高的采样率下实现一个高阶滤波器将会每秒消耗许多数百万次运算。最好先对芯片的输出进行频谱分析。如果没有超过几千赫兹的声能,那么抗混叠滤波器就是一种浪费处理资源的方法。即使有中等高频的声能,也可能值得先进行信号抽取,然后在应用第二个级别的抽取之前进行滤波。顺便说一句,您可能还想将抽样率降至远低于44.1 kHz。对于主系统仿真器,您可能只需要8 kHz或10 kHz的抽样率(我们不谈论高保真)。但无论如何,为了回答您关于如何使用指定的采样率和截止频率实现低通滤波器的问题...

首先,设计一个低通滤波器。Matlab 的 decimate 函数听起来不错,所以我们将复制这个方法做为例子。文档上说:

被抽取的向量 y 的长度是输入向量 x 的 r 倍短。默认情况下,decimate 使用一个带有截止频率为 0.8*(Fs/2)/r 的八阶低通 Chebyshev Type I 滤波器。它在正向和反向两个方向上过滤输入序列,以消除所有相位失真,有效地将滤波器阶数翻倍。

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,因为您希望在降采样之前将接近Nyquist极限的信号很好地衰减。
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.99938921206706, 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个单独的类(每个过滤器一个),将a和b值设置为上述指定值。接下来,将一个阶段的输入钩到下一个的输出,以获得级联效果。

这看起来很不错 - 我今晚下班后会试一试,非常感谢 :-) - PhilPotter1987
请看我在顶部的编辑,这可能是一种可以避免的过于粗暴的方法!让我知道你的进展如何。 - learnvst
请看我上面的更新 - 我很重视您的见解 :-) 谢谢。 - PhilPotter1987
我已经完成了这个,声音流现在几乎完美 :-) 不过我会记住你的帖子,因为当我开始制作我的megadrive模拟器时,我几乎肯定需要进行低通滤波。 - PhilPotter1987
好消息。在回答了你的问题之后,我才开始思考可能有更好的方法来完成你所做的事情。很高兴它已经运行起来了! - learnvst
显示剩余2条评论


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接