簡體   English   中英

為什么使用 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));
        }

生成的音頻片段中,滑音結尾的音調明顯高於停音符。 是我的數學有問題還是有聽力學原因導致這個上升正弦波似乎過沖? 非常感謝任何想法!

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;
    }

控制台 output:

Freq of sine: 220.0
Start freq glissando :220.0
Stop freq glissando :439.9975
Freq of sine: 440.0

出現問題是因為每個幀的相鄰間距太寬。 instantFrequency的計算很好,但通過將它乘以i得出的值是可疑的。 當你 go 從ii+1時,前進的距離如下:

distance = ((n+1) * instantFrequency[n+1]) - (n * instantFrequency[n]) 

這大於所需的 delta 值,該值應等於新的instantFrequency值,例如:

distance = ((n+1) * instantFrequency[n]) - (n * instantFrequency[n]) 

下面的代碼幫我弄清楚了這個讓我疑惑了幾個小時的問題。 只有在睡過頭之后,我才能得到上面簡潔的解釋(在編輯中添加)。

這是一個更簡單的案例來說明這個問題。 由於問題發生在 sin function 計算之前,我排除了它們以及所有在 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 和 go 開始到 position 10。我們 go 向前的金額計算與您的計算類似。 對於“固定利率”,我們只需將步驟的值乘以i (行程aabb )。 在 trip ab中,我使用了與您類似的計算。 它的問題是最后的步驟太大了。 如果您檢查 output 行,您可以看到這一點:

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

“步”移動的距離接近 3,而不是所需的 2!

在最后一個示例 trip cc中, instantFrequency的計算與travelIncrement的計算相同。 但在這種情況下,增量只是簡單地添加到之前的 position。

事實上,出於音頻合成的目的(當計算創建 wave forms 時),使用加法來最小化 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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM