【问题标题】:How to implement the Karplus-Strong algorithm in Java?如何在 Java 中实现 Karplus-Strong 算法?
【发布时间】:2020-04-18 23:31:10
【问题描述】:

我找遍了整个地方,但似乎找不到使用 Karplus-Strong 算法的简单 Java 程序的示例,这很奇怪,因为我认为它应该是一个经典的编码练习。等式是 y[n] = x[n] + 0.5 * (y[n-N] + y[n-(N+1)])。它应该提供模拟吉他弦被弹拨的声波。到目前为止,我有以下代码:

import javax.sound.sampled.*;

public class Main {

    public static void main(String args[]) throws LineUnavailableException {
        int rate = 44100;
        byte[] buffer = new byte[1];
        AudioFormat af = new AudioFormat(rate, 8, 1, false, true);
        SourceDataLine sdl = AudioSystem.getSourceDataLine(af);
        sdl.open();
        sdl.start();

        for(int i = 1; i < rate; i++) {
            buffer[0] = (byte) (...);
            sdl.write(buffer, 0, 1);
        }   
    }   

}

如何使用该等式创建可以插入代码的字节数组?

【问题讨论】:

  • 我可以先看看你自己的实现吗?

标签: java algorithm audio simulation


【解决方案1】:

您将需要一个用于计算声波值的 PCM 值数组,以及一个用于保存要写入SourceDataLine 的值的字节数组。

PCM 数组的大小设置为您正在创建的波形的周期。因此,如果您想制作 A 440,则周期(基于 44100fps 的采样率)将是 100(440 有点尖锐)。

第一步是用随机数填充 PCM 数组(浮点数就足够了,范围在 -1 到 1 之间)。 然后循环执行以下两步(从第二步开始):

  • 根据您引用的公式计算下一组 PCM 值。
  • 根据您的音频格式将 PCM 缓冲区值转换为字节,并将其附加到将写入 SourceDataLine 的字节数组中。

SourceDataLine 的字节缓冲区已满时,写入缓冲区并开始重新填充它以进行下一次写入操作。

有一个article here 也描述了算法的一些改进。将 PCM 转换为每个音频格式的字节的详细信息已在其他帖子中介绍。

以下是一个快速而肮脏的实现。该代码仅播放 200-pcm 音符。显然,人们想要重写它以使其适用于其他笔记。但它确实显示了算法的实际作用,并且确实发挥了作用。

public class KarplusStrongTone {

    float[] pcmArray;
    SourceDataLine sdl; 
    int period = 200;
    int sdlIdx = 0; 
    byte sdlBuffer[] = new byte[4000];


    public static void main(String[] args) throws UnsupportedAudioFileException, 
    IOException, InterruptedException, LineUnavailableException {

        KarplusStrongTone kst = new KarplusStrongTone();
        kst.initializePCMArray();
        kst.makeOutputLine();
        kst.play();
    }   

    private void initializePCMArray()
    {
        pcmArray = new float[period];
        for (int i = 0; i < period; i++) pcmArray[i] = (float)(Math.random() * 2 - 1);
    }

    private void makeOutputLine() throws LineUnavailableException {

        AudioFormat audioFmt = new AudioFormat(
                AudioFormat.Encoding.PCM_SIGNED, 
                44100, 16, 1, 2, 44100, false);

        Info info = new DataLine.Info(SourceDataLine.class, audioFmt);
        sdl = (SourceDataLine)AudioSystem.getLine(info);
        sdl.open();
        sdl.start();    
    }


    private void play()
    {
        int countIterations = 0;
        float localMax = 1;

        while (localMax > 0.00001f)
        {
            localMax = 0;
            for (int i = period - 1; i > 0; i--)
            {
                pcmArray[i] = (pcmArray[i] + pcmArray[i-1])/2;
                localMax = Math.max(Math.abs(pcmArray[i]), localMax);
            }
            pcmArray[0] = pcmArray[0]/2;

            countIterations++; // just curious how long while runs

            ship(pcmArray);

        }       
        System.out.println("Iterations = " + countIterations);
    }

    private void ship(float[] pcm)
    {
        for (int i = 0; i < period; i++)
        {
            int audioVal = (int)(pcm[i] * 32767);
            sdlBuffer[sdlIdx + i * 2] = (byte)audioVal;
            sdlBuffer[sdlIdx + (i * 2) + 1] = (byte)(audioVal >> 8);
        }
        sdlIdx += (period * 2);

        if (sdlIdx == 4000)
        {
            sdl.write(sdlBuffer, 0, 4000);
            sdlIdx = 0;
        }
    }
}

【讨论】:

  • 我知道该怎么做,这是方程式,我遇到了麻烦。我试过:buffer[0] = (byte) (0.5*(randomArray[i-1]+randomArray[i]));但我只听到静电声。
  • 当您平均初始期间的相邻 PCM 值(在您的 randomArray[] 中)时,经过多次迭代,整个值数组接近 0。根据定义,没有 randomArray[-1] 元素,因此在计算算法中使用值 0。结果 buff[] 成为下一个 randomArray[]。这有帮助吗?换句话说,计算的“过滤”动作是一个简单的2个元素的盒子过滤器。\
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-02
  • 2011-02-12
  • 1970-01-01
  • 2020-10-13
  • 2012-10-22
  • 2019-12-25
  • 2022-12-03
相关资源
最近更新 更多