0

我尝试制作一个合成器,它可以工作,我可以用它们播放音乐。但是我制作的第一个合成器有延迟,你无法播放快速的歌曲。所以我再次尝试使用sourceDataline.flush()方法来加速它。好吧,它在某种程度上解决了它,但延迟太多了。我也尝试降低采样率,但延迟太多了。

编辑:原来你可以评论keyStateInterface.setFlush(false); 它改善延迟的行但是你仍然不能播放快速的歌曲

这是代码:

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

public class SoundLine implements Runnable{
    KeyStateInterface keyStateInterface;
    public SoundLine(KeyStateInterface arg){
        keyStateInterface=arg;
    }

    @Override
    public void run() {
        AudioFormat audioFormat = new AudioFormat(44100,8,1,true,false);
        try {
            SourceDataLine sourceDataLine = AudioSystem.getSourceDataLine(audioFormat);
            sourceDataLine.open(audioFormat);
            sourceDataLine.start();

            SynthMain synthMain = new SynthMain();

            int v = 0;
            while (true) {
                int bytesAvailable = sourceDataLine.available();

                if (bytesAvailable > 0) {
                    int sampling = 256/(64);
                    byte[] bytes = new byte[sampling];

                    for (int i = 0; i < sampling; i++) {

                        //bytes[i] = (byte) (Math.sin(angle) * 127f);
                        float t = (float) (synthMain.makeSound((double)v,44100,keyStateInterface)* 127f);
                        bytes[i] = (byte) (t);
                        v += 1;
                    }
                    if(keyStateInterface.getFlush()){
                        sourceDataLine.flush();
                    }
                    sourceDataLine.write(bytes, 0, sampling);
                    //if(!keyStateInterface.isCacheKeysSame())sourceDataLine.flush();

                    //System.out.println(bytesWritten);
                } else {
                    Thread.sleep(1);
                }

                //System.out.println(bytesAvailable);
                //System.out.println();
                //if((System.currentTimeMillis()-mil)%50==0)freq+=0.5;
            }
        }catch (Exception e){


        }
    }
}

public class SynthMain {
    double[] noteFrequency = {
            466.1637615181,
            493.8833012561,
            523.2511306012,
            554.3652619537,
            587.3295358348,
            622.2539674442,
            659.2551138257,
            698.4564628660,
            739.9888454233,
            783.9908719635,
            830.6093951599,
            880.0000000000,
            932.3275230362,
            987.7666025122,
            1046.5022612024,
            1108.7305239075,
            1174.6590716696,
            1244.5079348883,
            1318.5102276515,
            1396.9129257320,
            1479.9776908465,
            1567.9817439270,
            1661.2187903198,
            1760.0000000000,
            1864.6550460724,
            1975.5332050245,
            2093.0045224048,
            2217.4610478150,
            2349.3181433393,
            2489.0158697766,
            2637.0204553030,
            2793.8258514640,
            2959.9553816931,
            3135.9634878540,
            3322.4375806396,
            3520.0000000000,
            3729.3100921447,
    };
    boolean[] keys = new boolean[noteFrequency.length];
    public double makeSound(double dTime,double SampleRate,KeyStateInterface keyStateInterface){
        if(keyStateInterface.getSizeOfMidiKey()>0){
            keyStateInterface.setFlush(true);
            for(int i=0;i<keyStateInterface.getSizeOfMidiKey();i++) {
                KeyRequest keyRequest = keyStateInterface.popMidiKey();
                if(keyRequest.getCommand()==-112){
                    if(keyRequest.getVelocity()>0)keys[keyRequest.getArg1()] = true;
                    if(keyRequest.getVelocity()<1)keys[keyRequest.getArg1()] = false;
                    System.out.println(keyRequest.getVelocity());
                }
            }
        }else{
            keyStateInterface.setFlush(false);
        }
        //System.out.println("makeSound");
        double a = 0.0;
        for(int i=0;i<keys.length;i++){
            if(keys[i]){
                a+=Oscillate(dTime,noteFrequency[i],(int)SampleRate);
            }
        }
        return a*0.4;
    }
    public double Oscillate(double dTime,double dFreq,int sampleRate){
        double period = (double)sampleRate / dFreq;
        return Math.sin(2.0 * Math.PI * (int)dTime / period);
    }
}
import java.util.ArrayList;
import java.util.Stack;

public class KeyState implements KeyStateInterface{
    boolean isFlush;
    ArrayList<KeyRequest> keyRequest = new ArrayList<KeyRequest>();
    ArrayList<KeyRequest> midiKeyRequest = new ArrayList<KeyRequest>();

    @Override
    public void pushKey(int keyCode, boolean press) {
        keyRequest.add(new KeyRequest(KeyRequest.KEY,keyCode,press));
    }

    @Override
    public void pushMidiKey(int command, int arg1, int velocity) {
        midiKeyRequest.add(new KeyRequest(KeyRequest.MIDI_KEY,command,arg1,velocity));
    }

    @Override
    public KeyRequest popKey() {
        KeyRequest t = keyRequest.get(keyRequest.size());
        return t;
    }

    @Override
    public KeyRequest popMidiKey() {
        KeyRequest t = midiKeyRequest.get(keyRequest.size());
        midiKeyRequest.remove(keyRequest.size());
        return t;
    }

    @Override
    public int getSizeOfKey() {
        return keyRequest.size();
    }

    @Override
    public int getSizeOfMidiKey() {
        return midiKeyRequest.size();
    }

    @Override
    public boolean getFlush() {
        boolean v = isFlush;
        isFlush = false;
        return v;
    }

    @Override
    public void setFlush(boolean arg) {
        isFlush=arg;
    }
}
4

1 回答 1

0

我没有深入研究您的代码,但也许以下信息会很有用。

SourceDataLine.write() 方法在内部使用阻塞队列。它只会在可以处理数据的情况下以最快的速度前进。因此,无需在填充和运输之前测试可用容量bytes

我会给 SDL 线程 10 的优先级,因为它的大部分时间都花在了阻塞状态。

另外,我会让线路保持畅通。我首先从Praxis Live的 Neil Smith 那里得到了这个建议。不断重建它需要付出一定的代价。在我看来,您正在为每 4 个字节的音频数据创建一个新的 SDL。那将是非常低效的。我怀疑在 256 到 8K 范围内的某个地方在一条开放的线路上运送会是一个更好的选择,但我没有确凿的事实来支持这一观点。Neil 写过让所有传输数组的大小相同(例如,合成器生成的数据数组与 SDL 写入的大小相同)。

我用java做了一个实时的theremin,其中延迟包括读取鼠标点击和位置的任务,然后将其发送到生成音频数据的合成器。我不会声称我的延迟降低到允许“在口袋里”开始和停止音符的精度,但它仍然非常好。我怀疑我可能会进一步优化。

我认为尼尔(前面提到过)有更好的结果。早在 2011 年,他就谈到实现 5 毫秒或更短的延迟。

于 2021-08-16T21:27:35.043 回答