为什么这个以编程方式生成的音乐和弦听起来不正确?
Why does this programmatically generated musical chord not sound correct?
我有以下 class 生成包含声音数据的缓冲区:
package musicbox.example;
import javax.sound.sampled.LineUnavailableException;
import musicbox.engine.SoundPlayer;
public class CChordTest {
private static final int SAMPLE_RATE = 1024 * 64;
private static final double PI2 = 2 * Math.PI;
/*
* Note frequencies in Hz.
*/
private static final double C4 = 261.626;
private static final double E4 = 329.628;
private static final double G4 = 391.995;
/**
* Returns buffer containing audio information representing the C chord
* played for the specified duration.
*
* @param duration The duration in milliseconds.
* @return Array of bytes representing the audio information.
*/
private static byte[] generateSoundBuffer(int duration) {
double durationInSeconds = duration / 1000.0;
int samples = (int) durationInSeconds * SAMPLE_RATE;
byte[] out = new byte[samples];
for (int i = 0; i < samples; i++) {
double value = 0.0;
double t = (i * durationInSeconds) / samples;
value += Math.sin(t * C4 * PI2); // C note
value += Math.sin(t * E4 * PI2); // E note
value += Math.sin(t * G4 * PI2); // G note
out[i] = (byte) (value * Byte.MAX_VALUE);
}
return out;
}
public static void main(String... args) throws LineUnavailableException {
SoundPlayer player = new SoundPlayer(SAMPLE_RATE);
player.play(generateSoundBuffer(1000));
}
}
也许我在这里误解了一些物理或数学,但似乎每个正弦波应该代表每个音符(C、E 和 G)的声音,并且通过将三个正弦波相加,我应该听到一些东西类似于我在键盘上同时弹奏这三个音符。然而,我所听到的与此相去甚远。
值得一提的是,如果我注释掉任何两个正弦波并保留第三个,我确实会听到与该正弦波对应的(正确)音符。
有人能发现我做错了什么吗?
要合并音频信号,您需要对其样本进行平均,而不是求和。
在转换为字节之前将值除以 3。
你没有说它听起来不正确的方式,添加三个 sin 值,这样你将得到一个范围从 -3.0 到 3.0 的信号,所以当你应用 *Byte.MAX_VALUE,这就是为什么平均可能对你有用,添加是正确的,你只需要在之后缩放结果以防止削波并除以正弦波的数量是最简单的方法。但是,如果您开始动态更改正弦波的数量并尝试使用相同的策略,您将不会获得预期的结果,您必须在信号最响亮时缩放信号。请记住,真实音频不会处于最大振幅,因此如果您合成的音频不是,您不必担心它,另外,我们感知音量的方式是对数的,因此半振幅的信号是不同的-3dB 的幅度非常接近我们能听到的最小幅度变化。
我有以下 class 生成包含声音数据的缓冲区:
package musicbox.example;
import javax.sound.sampled.LineUnavailableException;
import musicbox.engine.SoundPlayer;
public class CChordTest {
private static final int SAMPLE_RATE = 1024 * 64;
private static final double PI2 = 2 * Math.PI;
/*
* Note frequencies in Hz.
*/
private static final double C4 = 261.626;
private static final double E4 = 329.628;
private static final double G4 = 391.995;
/**
* Returns buffer containing audio information representing the C chord
* played for the specified duration.
*
* @param duration The duration in milliseconds.
* @return Array of bytes representing the audio information.
*/
private static byte[] generateSoundBuffer(int duration) {
double durationInSeconds = duration / 1000.0;
int samples = (int) durationInSeconds * SAMPLE_RATE;
byte[] out = new byte[samples];
for (int i = 0; i < samples; i++) {
double value = 0.0;
double t = (i * durationInSeconds) / samples;
value += Math.sin(t * C4 * PI2); // C note
value += Math.sin(t * E4 * PI2); // E note
value += Math.sin(t * G4 * PI2); // G note
out[i] = (byte) (value * Byte.MAX_VALUE);
}
return out;
}
public static void main(String... args) throws LineUnavailableException {
SoundPlayer player = new SoundPlayer(SAMPLE_RATE);
player.play(generateSoundBuffer(1000));
}
}
也许我在这里误解了一些物理或数学,但似乎每个正弦波应该代表每个音符(C、E 和 G)的声音,并且通过将三个正弦波相加,我应该听到一些东西类似于我在键盘上同时弹奏这三个音符。然而,我所听到的与此相去甚远。
值得一提的是,如果我注释掉任何两个正弦波并保留第三个,我确实会听到与该正弦波对应的(正确)音符。
有人能发现我做错了什么吗?
要合并音频信号,您需要对其样本进行平均,而不是求和。
在转换为字节之前将值除以 3。
你没有说它听起来不正确的方式,添加三个 sin 值,这样你将得到一个范围从 -3.0 到 3.0 的信号,所以当你应用 *Byte.MAX_VALUE,这就是为什么平均可能对你有用,添加是正确的,你只需要在之后缩放结果以防止削波并除以正弦波的数量是最简单的方法。但是,如果您开始动态更改正弦波的数量并尝试使用相同的策略,您将不会获得预期的结果,您必须在信号最响亮时缩放信号。请记住,真实音频不会处于最大振幅,因此如果您合成的音频不是,您不必担心它,另外,我们感知音量的方式是对数的,因此半振幅的信号是不同的-3dB 的幅度非常接近我们能听到的最小幅度变化。