如何在Java中实现Karplus-Strong算法?
How to implement the Karplus-Strong algorithm in Java?
我找遍了所有地方,似乎找不到使用 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);
}
}
}
我如何使用等式创建可以插入我的代码的字节数组?
您将需要一个用于计算声波值的 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;
}
}
}
我找遍了所有地方,似乎找不到使用 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);
}
}
}
我如何使用等式创建可以插入我的代码的字节数组?
您将需要一个用于计算声波值的 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;
}
}
}