使用 java Audiosystem 为什么滑音频率上升得太高
Why glissando frequency goes up too high using java Audiosystem
我尝试创建从开始音符到结束音符的滑音(平滑的音高上升)(下面的 java 代码)。我像这样从起始音符频率线性上升到停止音符频率
for (i = 0; i < b1.length; i++) {
instantFrequency = startFrequency + (i * deltaFreq / nrOfSamples);
b1[i] = (byte) (127 * Math.sin(2 * Math.PI * instantFrequency * i / sampleRate));
}
在the resulting audio fragment中,滑音结尾的音高明显高于停音符。是我的数学有问题还是有听力学原因导致这个上升正弦波似乎过冲?非常感谢任何想法!
public static void main(String[] args) throws IOException {
int sampleRate = 44100;
int sampleSizeInBits = 8;
int nrOfChannels = 1;
byte[] sine220 = createTimedSine(220, sampleRate, 0.5);
byte[] gliss220to440 = createTimedGlissando(220, 440, sampleRate, 4);
byte[] sine440 = createTimedSine(440, sampleRate, 2);
byte[] fullWave = concatenate(sine220, gliss220to440, sine440);
AudioInputStream stream = new AudioInputStream(new ByteArrayInputStream(fullWave),
new AudioFormat(sampleRate, sampleSizeInBits, nrOfChannels, true, false), fullWave.length);
File fileOut = new File(path, filename);
Type wavType = AudioFileFormat.Type.WAVE;
try {
AudioSystem.write(stream, wavType, fileOut);
} catch (IOException e) {
System.out.println("Error writing output file '" + filename + "': " + e.getMessage());
}
}
public static byte[] createTimedSine(float frequency, int samplingRate, double duration) {
int nrOfSamples = (int) Math.round(duration * samplingRate);
return (createSampledSine(nrOfSamples, frequency, samplingRate));
}
public static byte[] createSampledSine(int nrOfSamples, float frequency, int sampleRate) {
byte[] b1 = new byte[nrOfSamples];
int i;
for (i = 0; i < b1.length; i++) {
b1[i] = (byte) (127 * Math.sin(2 * Math.PI * frequency * i / sampleRate));
}
System.out.println("Freq of sine: " + frequency);
return b1;
}
public static byte[] createTimedGlissando(float startFrequency, float stopFrequency, int samplingRate,
double duration) {
int nrOfSamples = (int) Math.round(duration * samplingRate);
return (createGlissando(nrOfSamples, startFrequency, stopFrequency, samplingRate));
}
public static byte[] createGlissando(int nrOfSamples, float startFrequency, float stopFrequency, int sampleRate) {
byte[] b1 = new byte[nrOfSamples];
float deltaFreq = (stopFrequency - startFrequency);
float instantFrequency = 0;
int i;
for (i = 0; i < b1.length; i++) {
instantFrequency = startFrequency + (i * deltaFreq / nrOfSamples);
b1[i] = (byte) (127 * Math.sin(2 * Math.PI * instantFrequency * i / sampleRate));
}
System.out.println("Start freq glissando :" + startFrequency);
System.out.println("Stop freq glissando :" + instantFrequency);
return b1;
}
static byte[] concatenate(byte[] a, byte[] b, byte[] c) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(a);
outputStream.write(b);
outputStream.write(c);
byte d[] = outputStream.toByteArray();
return d;
}
控制台输出:
Freq of sine: 220.0
Start freq glissando :220.0
Stop freq glissando :439.9975
Freq of sine: 440.0
出现这个问题是因为每一帧的相邻间距太宽了。 instantFrequency
的计算很好,但是通过将其乘以 i
得出的值是可疑的。当你从i到i+1时,前进的距离如下:
distance = ((n+1) * instantFrequency[n+1]) - (n * instantFrequency[n])
这大于所需的增量值,该值应等于新的 instantFrequency
值,例如:
distance = ((n+1) * instantFrequency[n]) - (n * instantFrequency[n])
下面的代码帮我解决了这个困扰我好几个小时的问题。只有在睡过头之后,我才能得到上面简洁的解释(在编辑中添加)。
这里有一个更简单的案例来说明这个问题。由于问题发生在sin函数计算之前,所以我排除了它们以及trig计算之后的所有操作。
public class CuriousSeries {
public static void main(String[] args) {
double aa = 1; // analogous to your 220
double bb = 2; // analogous to your 440
double delta = bb - aa;
int steps = 10;
double[] travelVals = new double[steps + 1];
// trip aa
for (int i = 0; i <= 10; i++) {
travelVals[i] = aa * i;
System.out.println("aa trip. travelVals[" + i + "] = " + travelVals[i]);
}
// trip ab
for (int i = 0; i <= 10; i++) {
double instantFreq = aa + (i / 10.0) * delta;
travelVals[i] = instantFreq * i;
System.out.println("ab trip. travelVals[" + i + "] = " + travelVals[i]);
}
// trip bb
for (int i = 0; i <= 10; i++) {
travelVals[i] = bb * i;
System.out.println("bb trip. travelVals[" + i + "] = " + travelVals[i]);
}
// trip cc
travelVals[0] = 0;
for (int i = 1; i <= 10; i++) {
double travelIncrement = aa + (i / 10.0) * delta;
travelVals[i] = travelVals[i-1] + travelIncrement;
System.out.println("cc trip. travelVals[" + i + "] = " + travelVals[i]);
}
}
}
让我们将 aa
视为类似于 220 Hz,而 bb
类似于 440 Hz。在每个部分中,我们从 0 开始到位置 10。我们前进的数量的计算与您的计算类似。对于“固定利率”,我们只需将步骤的值乘以 i
(行程 aa 和 bb)。在 trip ab 中,我使用了与您类似的计算。它的问题是最后的步骤太大了。如果您检查输出行,您可以看到这一点:
ab trip. travelSum[9] = 17.099999999999998
ab trip. travelSum[10] = 20.0
“步”移动的距离接近 3,而不是所需的 2!
在最后一个示例中,trip cc,travelIncrement
的计算与 instantFrequency
相同。但在这种情况下,增量只是简单地添加到之前的位置。
事实上,出于音频合成的目的(当通过计算创建波形时),使用加法来最小化 cpu 成本是有意义的。按照这些思路,我通常会做一些更像下面的事情,尽可能多地从内部循环中删除计算:
double cursor = 0;
double prevCursor = 0;
double pitchIncrement = 2 * Math.PI * frequency / sampleRate;
for (int i = 0; i < n; i++) {
cursor = prevCursor + pitchIncrement;
audioVal[i] = Math.sin(cursor);
prevCursor = cursor;
}
我尝试创建从开始音符到结束音符的滑音(平滑的音高上升)(下面的 java 代码)。我像这样从起始音符频率线性上升到停止音符频率
for (i = 0; i < b1.length; i++) {
instantFrequency = startFrequency + (i * deltaFreq / nrOfSamples);
b1[i] = (byte) (127 * Math.sin(2 * Math.PI * instantFrequency * i / sampleRate));
}
在the resulting audio fragment中,滑音结尾的音高明显高于停音符。是我的数学有问题还是有听力学原因导致这个上升正弦波似乎过冲?非常感谢任何想法!
public static void main(String[] args) throws IOException {
int sampleRate = 44100;
int sampleSizeInBits = 8;
int nrOfChannels = 1;
byte[] sine220 = createTimedSine(220, sampleRate, 0.5);
byte[] gliss220to440 = createTimedGlissando(220, 440, sampleRate, 4);
byte[] sine440 = createTimedSine(440, sampleRate, 2);
byte[] fullWave = concatenate(sine220, gliss220to440, sine440);
AudioInputStream stream = new AudioInputStream(new ByteArrayInputStream(fullWave),
new AudioFormat(sampleRate, sampleSizeInBits, nrOfChannels, true, false), fullWave.length);
File fileOut = new File(path, filename);
Type wavType = AudioFileFormat.Type.WAVE;
try {
AudioSystem.write(stream, wavType, fileOut);
} catch (IOException e) {
System.out.println("Error writing output file '" + filename + "': " + e.getMessage());
}
}
public static byte[] createTimedSine(float frequency, int samplingRate, double duration) {
int nrOfSamples = (int) Math.round(duration * samplingRate);
return (createSampledSine(nrOfSamples, frequency, samplingRate));
}
public static byte[] createSampledSine(int nrOfSamples, float frequency, int sampleRate) {
byte[] b1 = new byte[nrOfSamples];
int i;
for (i = 0; i < b1.length; i++) {
b1[i] = (byte) (127 * Math.sin(2 * Math.PI * frequency * i / sampleRate));
}
System.out.println("Freq of sine: " + frequency);
return b1;
}
public static byte[] createTimedGlissando(float startFrequency, float stopFrequency, int samplingRate,
double duration) {
int nrOfSamples = (int) Math.round(duration * samplingRate);
return (createGlissando(nrOfSamples, startFrequency, stopFrequency, samplingRate));
}
public static byte[] createGlissando(int nrOfSamples, float startFrequency, float stopFrequency, int sampleRate) {
byte[] b1 = new byte[nrOfSamples];
float deltaFreq = (stopFrequency - startFrequency);
float instantFrequency = 0;
int i;
for (i = 0; i < b1.length; i++) {
instantFrequency = startFrequency + (i * deltaFreq / nrOfSamples);
b1[i] = (byte) (127 * Math.sin(2 * Math.PI * instantFrequency * i / sampleRate));
}
System.out.println("Start freq glissando :" + startFrequency);
System.out.println("Stop freq glissando :" + instantFrequency);
return b1;
}
static byte[] concatenate(byte[] a, byte[] b, byte[] c) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(a);
outputStream.write(b);
outputStream.write(c);
byte d[] = outputStream.toByteArray();
return d;
}
控制台输出:
Freq of sine: 220.0
Start freq glissando :220.0
Stop freq glissando :439.9975
Freq of sine: 440.0
出现这个问题是因为每一帧的相邻间距太宽了。 instantFrequency
的计算很好,但是通过将其乘以 i
得出的值是可疑的。当你从i到i+1时,前进的距离如下:
distance = ((n+1) * instantFrequency[n+1]) - (n * instantFrequency[n])
这大于所需的增量值,该值应等于新的 instantFrequency
值,例如:
distance = ((n+1) * instantFrequency[n]) - (n * instantFrequency[n])
下面的代码帮我解决了这个困扰我好几个小时的问题。只有在睡过头之后,我才能得到上面简洁的解释(在编辑中添加)。
这里有一个更简单的案例来说明这个问题。由于问题发生在sin函数计算之前,所以我排除了它们以及trig计算之后的所有操作。
public class CuriousSeries {
public static void main(String[] args) {
double aa = 1; // analogous to your 220
double bb = 2; // analogous to your 440
double delta = bb - aa;
int steps = 10;
double[] travelVals = new double[steps + 1];
// trip aa
for (int i = 0; i <= 10; i++) {
travelVals[i] = aa * i;
System.out.println("aa trip. travelVals[" + i + "] = " + travelVals[i]);
}
// trip ab
for (int i = 0; i <= 10; i++) {
double instantFreq = aa + (i / 10.0) * delta;
travelVals[i] = instantFreq * i;
System.out.println("ab trip. travelVals[" + i + "] = " + travelVals[i]);
}
// trip bb
for (int i = 0; i <= 10; i++) {
travelVals[i] = bb * i;
System.out.println("bb trip. travelVals[" + i + "] = " + travelVals[i]);
}
// trip cc
travelVals[0] = 0;
for (int i = 1; i <= 10; i++) {
double travelIncrement = aa + (i / 10.0) * delta;
travelVals[i] = travelVals[i-1] + travelIncrement;
System.out.println("cc trip. travelVals[" + i + "] = " + travelVals[i]);
}
}
}
让我们将 aa
视为类似于 220 Hz,而 bb
类似于 440 Hz。在每个部分中,我们从 0 开始到位置 10。我们前进的数量的计算与您的计算类似。对于“固定利率”,我们只需将步骤的值乘以 i
(行程 aa 和 bb)。在 trip ab 中,我使用了与您类似的计算。它的问题是最后的步骤太大了。如果您检查输出行,您可以看到这一点:
ab trip. travelSum[9] = 17.099999999999998
ab trip. travelSum[10] = 20.0
“步”移动的距离接近 3,而不是所需的 2!
在最后一个示例中,trip cc,travelIncrement
的计算与 instantFrequency
相同。但在这种情况下,增量只是简单地添加到之前的位置。
事实上,出于音频合成的目的(当通过计算创建波形时),使用加法来最小化 cpu 成本是有意义的。按照这些思路,我通常会做一些更像下面的事情,尽可能多地从内部循环中删除计算:
double cursor = 0;
double prevCursor = 0;
double pitchIncrement = 2 * Math.PI * frequency / sampleRate;
for (int i = 0; i < n; i++) {
cursor = prevCursor + pitchIncrement;
audioVal[i] = Math.sin(cursor);
prevCursor = cursor;
}