Phi*_*987 7 audio emulation filter
我用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 - 而不是全四个零并切换到新的频率 - 这可能会导致混叠吗?
编辑:我在下面包含了一个过滤器实现,可以按要求回答您的问题。然而,以如此高的采样率实现具有信号的高阶滤波器每秒将消耗数百万次操作。最好先对芯片的输出进行频谱分析。如果没有高于几kHz的声能,那么抗混叠滤波器就会浪费处理资源。即使存在高达中高频的能量,也可能值得首先对信号进行抽取,然后在应用第二级抽取之前进行滤波。附带说明一下,您可能还希望降低到比 44.1 kHz 低得多的速率。对于主系统仿真器来说,您可能会喜欢 8 或 10 kHz 的采样率(我们这里讨论的不是高保真音响)。但无论如何,要回答有关如何使用您指定的采样率和截止值实现低通滤波器的问题。。。
好的,首先设计一个低通滤波器。matlab 抽取函数听起来不错,因此我们将在本示例中复制该方法。该文档说以下内容
抽取向量 y 的长度比输入向量 x 短 r 倍。默认情况下,抽取采用截止频率为 0.8*(Fs/2)/r 的八阶低通切比雪夫 I 型滤波器。它对正向和反向的输入序列进行滤波,以消除所有相位失真,从而有效地将滤波器阶数加倍。
切比滤波器是一个不错的选择,因为它们的抑制比巴特沃斯设计更陡峭,但代价是有一点通带纹波。我们无法在实时系统中进行双向 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)
Run Code Online (Sandbox Code Playgroud)
这为我们提供了一个 8 阶 IIR 滤波器,其响应如下。这看起来就是我们想要的。相位响应对于该应用并不重要。截止值为 0.8* 22.050 kHz,因为您希望接近奈奎斯特极限的信号在抽取之前得到很好的衰减。

最后的 tf2sos 命令将我们刚刚创建的滤波器转换为二阶部分,您可以使用级联双二阶滤波器部分来实现。该命令的输出如下。。。
b=1.98795003258633e-07, 3.97711540624783e-07, 1.98854354149782e-07,
a=1 -1.81843900641769, 0.835282840946310
b=1, 2.02501937393162, 1.02534004997240,
a=1, -1.77945624664044, 0.860871442492022
b=1, 1.99938921206706, 0.999702296645625,
a=1, -1.73415546937221, 0.907015729252152
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;
}
Run Code Online (Sandbox Code Playgroud)
您应该为此过滤器创建一个类,然后实例化 4 个单独的类(每个过滤器 1 个),将 a 和 b 值设置为上面指定的值。接下来,将一个阶段的输入连接到下一阶段的输出,以提供级联。