私は、既存の音楽ソフトウェアプロジェクト内で、リアルタイムのオーディオキャプチャと分析のためのシステムの実装に取り組んできました。このシステムの目的は、ユーザーが録音ボタンを押したとき(または指定されたカウントイン期間の後)にオーディオのキャプチャを開始し、ユーザーが歌ったり演奏したりする音を決定し、これらの音を五線に記すことです。私の方法の要点は、1つのスレッドを使用してオーディオデータのチャンクをキャプチャしてキューに入れ、別のスレッドを使用してデータをキューから削除して分析を実行することです。
このスキームはうまく機能しますが、オーディオキャプチャの開始からMIDIバッキングインストゥルメントの再生までのレイテンシーを定量化するのに問題があります。オーディオキャプチャは、MIDI機器の再生が始まる前に開始され、ユーザーはおそらく自分のパフォーマンスをMIDI機器と同期させることになります。したがって、バッキングMIDI機器の再生を開始する前にキャプチャされたオーディオデータを無視し、それ以降に収集されたオーディオデータのみを分析する必要があります。
バッキングトラックの再生は、かなり長い間配置され、他の誰かによって維持されているコードの本体によって処理されるため、可能な限りプログラム全体をリファクタリングすることは避けたいと思います。オーディオキャプチャは、TimerオブジェクトとTimerTaskを拡張するクラスで制御されます。このクラスのインスタンスは、Notateと呼ばれる扱いにくい(〜25k行)クラスで作成されます。ちなみに、Notateはバッキングトラックの再生を処理するオブジェクトのタブも保持しています。タイマーの.scheduleAtFixedRate()メソッドは、オーディオキャプチャの期間を制御するために使用され、TimerTaskは、キュー(ArrayBlockingQueue)で.notify()を呼び出すことによって開始するようにキャプチャスレッドに通知します。
これら2つのプロセスの初期化間のタイムギャップを計算するための私の戦略は、キャプチャが開始する直前に取得したタイムスタンプ(ミリ秒単位)を、再生開始時に取得したタイムスタンプから差し引くことでした。これは、.startのときと定義しています。 ()メソッドは、MIDIバッキングトラックを担当するJavaSequencerオブジェクトで呼び出されます。次に、その結果を使用して、この間隔(n)の間にキャプチャされたと予想されるオーディオサンプルの数を決定し、キャプチャされたオーディオデータの配列の最初のn * 2バイトを無視します(16をキャプチャしているためn * 2-ビットサンプル、データはバイト配列として保存されます…サンプルあたり2バイト)。
ただし、この方法では正確な結果が得られません。計算されたオフセットは常に私が予想するよりも小さいので、指定された位置で分析を開始した後、オーディオデータに重要な(そして残念ながら変化する)量の「空の」スペースが残ります。これにより、プログラムは、ユーザーがバッキングMIDI楽器と一緒に演奏を開始していないときに収集されたオーディオデータの分析を試み、ユーザーの音楽のパッセージの開始時に休符を効果的に追加し、リズム値を台無しにします。以降のすべてのノートについて計算されます。
以下は、私のオーディオキャプチャスレッドのコードです。これは、キャプチャされたオーディオデータの配列のレイテンシと対応する位置オフセットも決定します。レイテンシーを決定するための私の方法が正しく機能していない理由について誰かが洞察を提供できますか?
public class CaptureThread extends Thread
{
public void run()
{
//number of bytes to capture before putting data in the queue.
//determined via the sample rate, tempo, and # of "beats" in 1 "measure"
int bytesToCapture = (int) ((SAMPLE_RATE * 2.) / (score.getTempo()
/ score.getMetre()[0] / 60.));
//temporary buffer - will be added to ByteArrayOutputStream upon filling.
byte tempBuffer[] = new byte[target.getBufferSize() / 5];
int limit = (int) (bytesToCapture / tempBuffer.length);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(bytesToCapture);
int bytesRead;
try
{ //Loop until stopCapture is set.
while (!stopCapture)
{ //first, wait for notification from TimerTask
synchronized (thisCapture)
{
thisCapture.wait();
}
if (!processingStarted)
{ //the time at which audio capture begins
startTime = System.currentTimeMillis();
}
//start the TargetDataLine, from which audio data is read
target.start();
//collect 1 captureInterval's worth of data
for (int n = 0; n < limit; n++)
{
bytesRead = target.read(tempBuffer, 0, tempBuffer.length);
if (bytesRead > 0)
{ //Append data to output stream.
outputStream.write(tempBuffer, 0, bytesRead);
}
}
if (!processingStarted)
{
long difference = (midiSynth.getPlaybackStartTime()
+ score.getCountInTime() * 1000 - startTime);
positionOffset = (int) ((difference / 1000.)
* SAMPLE_RATE * 2.);
if (positionOffset % 2 != 0)
{ //1 sample = 2 bytes, so positionOffset must be even
positionOffset += 1;
}
}
if (outputStream.size() > 0)
{ //package data collected in the output stream into a byte array
byte[] capturedAudioData = outputStream.toByteArray();
//add captured data to the queue for processing
processingQueue.add(capturedAudioData);
synchronized (processingQueue)
{
try
{ //notify the analysis thread that data is in the queue
processingQueue.notify();
} catch (Exception e)
{
//handle the error
}
}
outputStream.reset(); //reset the output stream
}
}
} catch (Exception e)
{
//handle error
}
}
}
Mixerオブジェクトを使用して、マイクからのデータを受け入れるTargetDataLineとMIDI機器からの再生を処理するLineを同期することを検討しています。次に、再生を処理するラインを見つけます...何かアイデアはありますか?