【问题标题】:Why glissando frequency goes up too high using java Audiosystem为什么使用 java Audiosystem 滑音频率上升太高
【发布时间】:2021-01-25 09:04:20
【问题描述】:

我尝试创建一个从开始音符到结束音符的滑音(平滑的音高上升)(下面的 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

【问题讨论】:

  • 很好奇它精确地上升到 660 赫兹(八度和五度,听它,E 高于 A 440)。这可能是一个线索。

标签: java audio trigonometry frequency pitch


【解决方案1】:

问题的出现是因为每一帧的相邻间距太宽。 instantFrequency 的计算结果很好,但是通过将其乘以 i 得到一个值是可疑的。从ii+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(行程 aabb)。在行程 ab 我使用与您类似的计算。它的问题是最后的步骤太大了。如果您检查输出行,您可以看到这一点:

ab trip. travelSum[9] = 17.099999999999998
ab trip. travelSum[10] = 20.0

“步”行进的距离接近 3,而不是期望的 2!

在最后一个示例中,trip cctravelIncrement 的计算与 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;
}

【讨论】:

  • 非常感谢!使用 delta-phase 还可以解决连接不同部分时的瞬时点击,这是我列表中的下一个,太棒了!所以最后,不希望的额外相位增加来自每次切换到更高的频率,其中波已经进一步逐相,并且使用 delta-phase 通过确保相位差是精确的来解决这个问题。另外,感谢您从内部循环中删除尽可能多的计算!
  • 现在我们拥有了重现传奇 VHX 简介的所有工具,万岁 youtube.com/watch?v=Sfg9DVwOd9w
  • 您离按照经典 Yamaha DX-7 或当前 Native Instruments FM8 的路线创建 FM 合成器(使用相位调制)不远了!
猜你喜欢
  • 1970-01-01
  • 2016-01-15
  • 2021-10-26
  • 1970-01-01
  • 2014-10-21
  • 2019-10-08
  • 2011-06-10
  • 2013-09-30
相关资源
最近更新 更多