【问题标题】:Am I correctly implementing this basic low pass filter (Phrogz's filter!) in Java?我是否在 Java 中正确实现了这个基本的低通滤波器(Phrogz 的滤波器!)?
【发布时间】:2012-05-24 23:45:38
【问题描述】:

我在 java 中有一个基本的波发生器,但我需要一些东西来消除当波的幅度急剧变化时我得到的点击。即当我开始/停止播放波浪时,尤其是当我发出哔哔声时。

Phrogz's answer on SO 提供了一个非常好用且简单的函数,但我不确定我是否正确实现了它。

当我第一次尝试使用它时,我无法让它工作,但后来我似乎记得它工作得很好......从那以后我对我的代码进行了很多摆弄,现在它似乎没有再次工作得很好。

所以这是我最接近 SSCCE 的地方:

如果你玩这个,你会注意到当过滤器打开时(filter = true),波浪更安静,点击也稍微减少,但这似乎主要是由于音量降低。每次哔哔声仍有明显的“撞击”,这是我不想要的,我不记得以前在那里...

import javax.sound.sampled.*;


public class Oscillator{

    private static int SAMPLE_RATE = 22050;
    private static short MAX_AMPLITUDE = Short.MAX_VALUE;   
    private static AudioFormat af = null;
    private static SourceDataLine line = null;
    private int frequency = 440; //Hz
    private int numLoops = 1000;
    private int beep = 100;

    // set to true to apply low-pass filter
    private boolean  filter = true;
    // set the amount of "smoothing" here
    private int smoothing = 100;
    private double oldValue;

    public Oscillator(){

        prepareLine();

    }


    public static void main(String[] args) {
        System.out.println("Playing oscillator");
        Oscillator osc = new Oscillator();
        osc.play();
    }


    private void prepareLine(){


        af =  new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, SAMPLE_RATE, 16, 2, 4, SAMPLE_RATE, false);

        try {

            DataLine.Info info = new DataLine.Info(SourceDataLine.class, af);

            if (!AudioSystem.isLineSupported(info)) {
                System.out.println("Line does not support: " + af);
                System.exit(0);
            }
            line = (SourceDataLine) AudioSystem.getLine(info);
            line.open(af);
        }
        catch (Exception e) { 
            System.out.println(e.getMessage());
            System.exit(0);
        }
    }

    private void play() {

        System.out.println("play");

         int maxSize = (int) Math.round( (SAMPLE_RATE * af.getFrameSize())/ frequency );  
         byte[] samples = new byte[maxSize];

         line.start();

         double volume = 1;

         int count = 0;
         for (int i = 0; i < numLoops; i ++){


             if (count == beep) {
                 if(volume==1) volume = 0;
                 else volume = 1;
                 count = 0;
             }

             count ++;

             playWave(frequency, volume, samples);


         }

         line.drain();
         line.stop();
         line.close();
         System.exit(0);
    }  

    private void playWave(int frequency, double volLevel, byte[] samples) {

         double amplitude = volLevel * MAX_AMPLITUDE;

         int numSamplesInWave = (int) Math.round( ((double) SAMPLE_RATE)/frequency );

         int index = 0;

         for (int i = 0; i < numSamplesInWave; i++) {

             double theta = (double)i/numSamplesInWave;

             double wave = getWave(theta);

             int sample = (int) (wave * amplitude);


             if (filter) sample = applyLowPassFilter(sample);



             // left sample
             samples[index + 0] = (byte) (sample & 0xFF);        
             samples[index + 1] = (byte) ((sample >> 8) & 0xFF); 
             // right sample
             samples[index + 2] = (byte) (sample & 0xFF);
             samples[index + 3] = (byte) ((sample >> 8) & 0xFF);
             index += 4;
         }

         int offset = 0;

         while (offset < index){
             double increment =line.write(samples, offset, index-offset);
             offset += increment;
         }
    }

    private double getWave(double theta){

        double value = 0;

        theta = theta * 2 * Math.PI;

        value = getSin(theta);
        //value = getSqr(theta);

        return value;

    }

    private double getSin(double theta){
        return Math.sin(theta);
    }

    private int getSqr(double theta){
        if (theta <= Math.PI) return 1;
        else return 0;
    }

    // implementation of basic low-pass filter
    private int applyLowPassFilter(int sample){

        int newValue = sample;
        double filteredValue = oldValue + (newValue - oldValue) / smoothing;

        oldValue = filteredValue;
        return (int) filteredValue;
    }
}

相关方法在最后。如果有人对此进行测试,如果您有耳机,请注意音量!

所以要么:

  1. 它正在运行,我只是对这样一个简单的实现期望过高
  2. 我做错了什么,愚蠢而明显...

如果只是 1。我应该/如何摆脱突然幅度变化带来的刺耳节拍/敲击声/咔嗒声?

如果是 2. 好,应该是一个太长问题的简短答案。

【问题讨论】:

    标签: java signal-processing lowpass-filter


    【解决方案1】:

    低通滤波器不会消除突然幅度变化带来的咔嗒声。相反,您需要避免突然的幅度变化。

    您可以使用低通滤波器来过滤您的幅度电平。

    **Pseudo code**
    
    for i = 0 to numSamplesInWave-1 do
    begin
      theta = i / numSamplesInWave;
      wave = getWave(theta);
      currentAmplitude = applyLowPassFilter(TargetAmplitude);   
      Sample[i] = wave * currentAmplitude;
    end;
    

    使用上述低通滤波器可以平滑输入值。例如,当用户更改音量控制时。

    在其他情况下,创建某种信封可能更合适。例如,合成器通常使用ADSR envelopes 来平滑新语音/声音开始和停止时的幅度变化。

    【讨论】:

    • 嗯,好的,谢谢。之前我只是认为它在哔哔声/方波上做得更好......现在我的代码似乎主要是在减少音量。我将尝试将其应用于幅度以及研究实现 ADSR 包络。出于好奇,是否存在最大振幅变化,超过该变化您肯定会听到咔嗒声?
    • @kiman:为了避免点击,振幅应该平滑变化。小的、不连续的幅度变化仍然会产生咔嗒声。
    • @kiman:根据具体情况,1-10 毫秒内的平滑幅度变化可能足以避免明显的点击。
    猜你喜欢
    • 2015-05-18
    • 1970-01-01
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-29
    • 2023-04-10
    相关资源
    最近更新 更多