Играйте в синусоиду до бесконечности, пока не скажете остановиться в Яве

Я пытаюсь сделать кусок кода, который может воспроизводить синусоидальную волну, пока не получит приказ остановиться. Я хочу иметь возможность воспроизводить частоту MIDI-ноты. Библиотека MIDI в java чувствует себя немного неаккуратно (когда я говорю ей, чтобы играть, есть небольшая задержка между нажатием клавиши и игрой ноты).

Я видел пример, когда вы генерируете синусоидальную волну определенной длины, а затем воспроизводите ее, давая SourceDataLine байтовый массив. Это сработало, но байтовый массив может быть только таким длинным, и он в конечном итоге перестанет играть.

Моя следующая идея состояла в том, чтобы постоянно записывать один байт в строку, вычислять следующую строку и продолжать. Вот мой код:

int i = 0;
int sampleRate = 8000;
int freq = 440;

while (true) {
    double samplingInterval = (double) (sampleRate / freq);
    double angle = (2.0 * Math.PI * i) / samplingInterval;
    byte toPlay = (byte) (Math.sin(angle) * 127);
    line.write(new byte[] {toPlay}, 0, 1);
    i++;
}

Я надеялся, что это даст мне постоянный выход с частотой 440 Гц, но это дало мне эту ошибку:

java.lang.IllegalArgumentException: illegal request to write non-integral number of frames (1 bytes, frameSize = 2 bytes)

Если нет, есть ли способ ускорить MIDI-библиотеку в Java или я только что сделал глупую ошибку? Заранее спасибо.

2 ответа

Решение

Кадр аудио представляет собой один или несколько одновременно синхронизированных аудиосэмплов. В вашем случае стерео (frames ==2). В потоке они обычно чередуются (например, L, R, L, R....).

Вы можете исправить это, написав каждый образец дважды.

В то время как Java (или любой другой язык JITd со сборкой мусора Stop-the-World) никогда не является выбором реализации для аудио программного обеспечения с низкой задержкой, я подозреваю, что обнаруживаемая вами "небрежность" на самом деле является длительным периодом аудио буфера: настройка по умолчанию за буферный период может составить существенную долю секунды.

Для этой конкретной идеи вы можете использовать

while (true) {
    double samplingInterval = (double) (sampleRate / freq);
    double angle = (2.0 * Math.PI * i) / samplingInterval;
    byte toPlay = (byte) (Math.sin(angle) * 127);
    byte[] data=new byte[1024];
    for(int j=0; j<data.length; j++) data[j]=toPlay;
    line.write(data, 0, data.length);
    i++;
}

или длины 4 будет достаточно.

Другие вопросы по тегам