音声読み込みもバックグラウンドスレッドに一体化します。
JUCEのスレッドの使い方がわかっていいね~
JUCEプログラミング、Audioチュートリアルの「Looping audio using the AudioSampleBuffer class Advance」、「Accessing the path from the background thread」の項目から進めていきます。今回から、ダウンロードできるサンプルプログラムの02のほうに入るようです。私は、前回のプログラムに差分のようにして今回の機能をくみこんでいきました。
こんな人の役に立つかも
・JUCEで音声プログラミングを学びたい人
・JUCE、Audioチュートリアルを進めている人
・「Accessing the path from the background thread」をやっている人
今回のチュートリアル概要
今回のチュートリアルで、今まで音声の読み込みはメッセージスレッドのopenButtonClicked内で行っていたところを、バックグラウンドスレッドを利用して音声読み込みを行うように変更していきます。音声の読み込み自体も音声の容量が大きくなると、メッセージスレッドの処理待ちになってしまいますので、このようにバックグラウンドで読み込みをかけるのが良いみたいですね。
MainComponent.hの変更
Privateなメンバ関数とメンバ変数を追加しました。
private:
//...略...
//[1]今回追加する関数です。
void checkForPathToOpen();
juce::TextButton openButton;
juce::TextButton clearButton;
juce::AudioFormatManager formatManager;
juce::ReferenceCountedArray<ReferenceCountedBuffer> buffers;
ReferenceCountedBuffer::Ptr currentBuffer;
//[2]今回必要になる変数です。
juce::String chosenPath;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
[1]ではopenButtonClicked関数で音声を読み込んでいるところをこの関数に移植する予定です。この「checkForPathToOpen()」関数自体はバックグラウンドスレッドでよびだされる関数になります。
[2]は、openButtonClicked関数で取得した音声ファイルパスを格納するものです。openButtonClickedの中では単純に音声のファイルパスを取得するだけで、時間のかかる音声読み込みはバックグラウンドに投げよう、という魂胆ですね。
MainComponent.cppの変更
openButtonClicked関数
FileChooserクラスで音声ファイルの選択をするところまでは同様です。
選択したファイルが判明したら、そのファイルパスを変数に格納して終了となります。
void MainComponent::openButtonClicked()
{
juce::FileChooser chooser("Select a Wave file shorter than 2 seconds to play...",
{},
"*.wav");
if (chooser.browseForFileToOpen())
{
auto file = chooser.getResult();
/*std::unique_ptr<juce::AudioFormatReader> reader(formatManager.createReaderFor(file));
if (reader.get() != nullptr)
{
auto duration = (float)reader->lengthInSamples / reader->sampleRate;
if (duration < 2)
{
//currentBufferを設定する処理に変更されています。
//PtrのnewBufferにReferenceCountedBufferクラスのオブジェクトを生成します。
ReferenceCountedBuffer::Ptr newBuffer = new ReferenceCountedBuffer(file.getFileName(),
(int)reader->numChannels,
(int)reader->lengthInSamples);
//readerに与えるAudioSampleBufferをnreBufferから取得します。
reader->read(newBuffer->getAudioSampleBuffer(), 0, (int)reader->lengthInSamples, 0, true, true);
//currentBufferを設定します。
currentBuffer = newBuffer;
//buffersに追加します。
buffers.add(newBuffer);
}
else
{
// handle the error that the file is 2 seconds or longer..
}
}*/
//↑のコメントアウトの部分から、以下のプログラムに変更しました。
auto path = file.getFullPathName();
chosenPath.swapWith(path);
//[1]文字列のスワップです。
notify();
//[2]バックグラウンドスレッドの呼び出しです。
}
}
[1]では、JUCEのStringクラスのswapWith関数で、文字列のスワップをおこなっています。最初なにをしているのかよくわからなかった(juce::stringからjuce::stringなのでふつうに代入すればよいと思っていました。)のですが、VisualStudioにロールオーバーで出現する説明によると、とても速い操作のようで、メモリの確保やコピーが必要のなく完了するようです。やっていることは、privateなメンバ変数のchosenPathに文字列のアドレスを紐づけなおす、ような感じでしょうか。ここでchosenPathが選択した音声ファイルのパスになり、ローカル変数のpathがEmptyになるようです。
瞬間移動みたいな感じですね。
[2]は、notify関数でスレッドを呼び出します。run()関数を実行する、ということです。
run関数
スレッドを呼び出したときに実行される関数、runにcheckForPathOpen関数を追加しました。
void MainComponent::run()
{
while (!threadShouldExit())
{
checkForPathToOpen();//この関数を呼び出します。
checkForBuffersToFree();
wait(500);
DBG("buffers_size = " << buffers.size());
}
}
checkForPathToOpen関数
今回新たに追加される関数です。cppの一番最後に追加しました。
void MainComponent::checkForPathToOpen()
{
//[1]chosenPathの値をローカル変数pathToOpenにスワップします。
juce::String pathToOpen;
pathToOpen.swapWith(chosenPath);
//[2]pathToOpenが空でないときに実行します。
if (pathToOpen.isNotEmpty())
{
juce::File file(pathToOpen);
std::unique_ptr<juce::AudioFormatReader> reader(formatManager.createReaderFor(file));
if (reader.get() != nullptr)
{
auto duration = (float)reader->lengthInSamples / reader->sampleRate;
if (duration < 2)
{
ReferenceCountedBuffer::Ptr newBuffer = new ReferenceCountedBuffer(file.getFileName(),
(int)reader->numChannels,
(int)reader->lengthInSamples);
reader->read(newBuffer->getAudioSampleBuffer(), 0, (int)reader->lengthInSamples, 0, true, true);
currentBuffer = newBuffer;
buffers.add(newBuffer);
}
else
{
// handle the error that the file is 2 seconds or longer..
}
}
}
}
[1]では、privateなメンバ変数のchosenPathからこの関数のローカル変数pathToOpenへと中身がスワップされています。スワップすることで、chosenPathの中身がEmptyになる点がポイントです。このスレッドのrunは500msec毎に繰り返し実行されており、音声バッファの管理もしていますので、openButtonClicked関数でパスが設定されたタイミングのときに実行されるrunの時だけこの処理が実行されるべきです。openButtonClicked関数で呼び出されていない場合、ローカル変数の値は毎回関数終了時に破棄されますので、chosenPathがEmptyであれば500msec毎の実行時には、この処理は実行されないことになります。
[2]の条件のなかで、以前openButtonClicked関数で行っていた、buffers配列へ音声バッファを追加してたりという処理をそのまま行っています。
スレッドといっても、このクラスでは、一つしかスレッドが動作させられないみたいですね。
クラスで一個だけ追加できるスレッドのようなイメージ。