为什么使用Java Audiosystem的glissando频率会过高

问题描述

我尝试创建从起始音符到结束音符的glissando(平滑的音高上升)(下面的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,4);
        byte[] sine440 = createTimedSine(440,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,double duration) {
        int nrOfSamples = (int) Math.round(duration * samplingRate);

        return (createGlissando(nrOfSamples,startFrequency,stopFrequency,samplingRate));
    }

    public static byte[] createGlissando(int nrOfSamples,float startFrequency,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 )。在旅行 ab 中,我使用与您相似的计算方法。问题在于最后的步骤太大。如果检查输出行,您会看到以下信息:

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

“台阶”所经过的距离接近3,而不是所需的2 !!

在上一个示例中,行程为 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;
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...