7

我已经用 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——而不是完整的四个零并切换到新的频率——这可能会导致混叠,不是吗?

4

2 回答 2

2

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

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

抽取向量 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,因为您希望接近奈奎斯特极限的信号在抽取前得到很好的衰减。

在此处输入图像描述

最后的 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 个单独的类(每个过滤器 1 个),将 a 和 b 值设置为上面指定的值。接下来,将一个阶段的输入连接到下一个阶段的输出,为您提供级联。

于 2012-04-18T09:27:41.513 回答
1

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

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

于 2012-04-18T08:37:03.920 回答