実際にプラグインの実装をしてみて、一つづつプログラムを確認していきたいと思います。
今回は、土台となるアプリ作成だね
前回に引き続き、エフェクトを数珠つなぎにするためのアプリを作成していきます。今回は、主に音声処理の中身であるPluginProcessor.cppのプログラムをしていくことになります。
進めているチュートリアルページは次のページになります。
こんな人の役に立つかも
・JUCEフレームワークに入門したい人
・VST、AUプラグイン開発の最初の一歩を踏み出したい人
・JUCEのチュートリアルをやっている人
基本的なカスケードプラグイン
まずは、エフェクト処理をいくつもカスケード処理させるための土台となる、音がスルーされるようなプラグインをビルドするのが目的です。
バスレイアウトの設定など
「PluginProcessor.cpp」のコンストラクタを次のように書き換えました。
バスレイアウトの項目でもやったように、音声入出力の初期化を行った後、「AudioProcessorGraph」の初期化も行うようにします。
CascadingPluginAudioProcessor::CascadingPluginAudioProcessor()
//音声入出力の初期化
を追加しました。
: AudioProcessor(BusesProperties().withInput("Input", juce::AudioChannelSet::stereo(), true)
.withOutput("Output", juce::AudioChannelSet::stereo(), true)),
//AudioProcessorGraphの初期化(newする)しました。
mainProcessor(new juce::AudioProcessorGraph())
{
}
バスレイアウトを初期化しましたので、「isBusesLayoutSupported」コールバックにも、バスレイアウトの受け入れ条件を記載します。
「PluginProcessor.cpp」の「isBusesLayoutSupported」を次のように書き換えました。今回のチュートリアルでは、「入力モノラル、出力モノラル」の場合と、「入力ステレオ、出力ステレオ」の場合をサポートするようにプログラミングしています。
bool CascadingPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
//追加プログラムです。書き換え
しました。
//①Inputがdisabledまたは、Outputがdisabledはサポートしません。
if (layouts.getMainInputChannelSet() == juce::AudioChannelSet::disabled()
|| layouts.getMainOutputChannelSet() == juce::AudioChannelSet::disabled())
return false;
//②OutOutがモノラル以外かつステレオ以外のときはサポートしません。(モノラルとステレオのみサポートします。)
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
return false;
//③最後に、入力と出力が同じであることを判定しています。
return layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet();
}
①では、入力と出力がdisableの場合はサポートしていません。
②では、ステレオとモノラルの出力のみをサポートするようにしています。出力で判定をしています。
③では、入力と出力が一致した場合、Trueを返しています。そのため、②では出力のみの判定としています。
条件全体を見ると、「入出力がモノラル」のとき、または「入出力がステレオ」のとき、バスレイアウトをサポートするようなコールバック関数としています。
prepareToPlay関数
「PluginProcessor.cpp」の「prepareToPlay」関数をプログラミングです。この時点でinitializeGraphはまだ定義されていません。
void CascadingPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
//以下のプログラムを追加しました。
mainProcessor->setPlayConfigDetails(getMainBusNumInputChannels(),
getMainBusNumOutputChannels(),
sampleRate, samplesPerBlock);
mainProcessor->prepareToPlay(sampleRate, samplesPerBlock);
//後程定義します。
initialiseGraph();
}
AudioProcessorPraphクラスのオブジェクト、mainProcessorに、「setPlayConfigDetails」関数で、音声の入出力とサンプルレート、オーディオバッファ(1ブロック分)のサンプル数を設定しています。
次に、mainProcessorのpreparedToPlayにサンプルレートとオーディオバッファのサンプル数を渡しています。
最後に、後程実装を行う「initialiseGraph」関数を呼び出します。
イメージとして、mainProcessorというチャンネルストリップのようにいくつかの音声処理をカスケードするための新しいプロセッサを今までのオーディオ処理の中に作成する、みたいなイメージでしょうか。
releaseResoueces関数
「PluginProcessor.cpp」の「releaseResources」関数に以下の通り追記しました。mainProcessorを開放します。明示的な記載が必要なようですね。
void CascadingPluginAudioProcessor::releaseResources()
{
mainProcessor->releaseResources();
}
processBlock関数
「PluginProcessor.cpp」の「processBlock」を以下の通り追記しました。
updateGraphはこの時点でまだ未定義で、チュートリアルの後の方に出てきましたので、ひとまずコメントアウトしてビルドできるようにしました。
void CascadingPluginAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
//①音声のチャンネル数分繰り返しを行い、オーディオバッファを初期化しています。
for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
buffer.clear(i, 0, buffer.getNumSamples());
//↓②UpdateGraphはもう少し後に出てくるので、現状はコメントアウトしました。
//updateGraph();
//③mainProcessorのprocessBlockへデータを渡します。
mainProcessor->processBlock(buffer, midiMessages);
}
①の部分でオーディオバッファをクリアしています。for文の条件については、調べましたが、よくわからないので、今後オーディオバッファ回りを勉強していく過程でわかってくる、予定です 笑
clear関数の引数は、(チャンネル、バッファスタート位置、バッファ終了位置)なので、今回はバッファ全体をクリアしていることになります。
②のUpdateGraphは、後に実装される関数なので、今回はコメントアウトしないとビルドできませんでしたので、コメントアウトしました。Graphは、チャンネルストリップのようにエフェクト(オーディオ処理)を連結させていく概念で、途中に連結されるエフェクトが変更されたときにそれを反映させるための処理ということです。次に実装するinitializeGraphのUpdateバージョンです。
privateなメンバ関数
「PluginProcessor.h」内のprivateなメンバ関数に「initialiseGraph」と「connextAudioNodes」「AudioProcessorGraph」を追加しました。
private:
//privateなメソッドとして以下の3関数を追加しました。
//①mainProcessor内へのエフェクト(オーディオ処理)のノードを初期化します。
void initialiseGraph()
{
//プロセッサのクリア
mainProcessor->clear();
//音声入出力ノードとmidi入出力ノードを追加します。
audioInputNode = mainProcessor->addNode(std::make_unique<AudioGraphIOProcessor>(AudioGraphIOProcessor::audioInputNode));
audioOutputNode = mainProcessor->addNode(std::make_unique<AudioGraphIOProcessor>(AudioGraphIOProcessor::audioOutputNode));
midiInputNode = mainProcessor->addNode(std::make_unique<AudioGraphIOProcessor>(AudioGraphIOProcessor::midiInputNode));
midiOutputNode = mainProcessor->addNode(std::make_unique<AudioGraphIOProcessor>(AudioGraphIOProcessor::midiOutputNode));
//ノード連結処理を別関数で定義します。
connectAudioNodes();
connectMidiNodes();
}
//②mainProcessor内にオーディオ処理ノードを連結します。
void connectAudioNodes()
{
for (int channel = 0; channel < 2; ++channel)
mainProcessor->addConnection({ { audioInputNode->nodeID, channel },
{ audioOutputNode->nodeID, channel } });
}
//③midiの処理ノードを連結します。
void connectMidiNodes()
{
mainProcessor->addConnection({ { midiInputNode->nodeID, juce::AudioProcessorGraph::midiChannelIndex },
{ midiOutputNode->nodeID, juce::AudioProcessorGraph::midiChannelIndex } });
}
//...以下略...
①では、mainProcessorをクリアして、音声入出力ノードと、MIDI入出力ノードを作成します。そして、ノード接続を行う「connectAudioNodes」と「connectMidiNodes」関数を呼び出します。
②では、「connectAudioNodes」関数を定義します。チャンネル数分の繰り返しを行い、「addConection」関数で特定のチャンネル(channnel変数)のaudioInputNodeのIDとaudioOutputNodeのIDへと紐づけています。
③の、「connectMidiNodes」関数も②と同じで、MIDIの入出力ノードを紐づけています。
ここでは、コネクションを入力から出力へ直接つなげましたので、エフェクト処理はなく、音をスルーさせるだけの設定となっています。
検証
一度、ここまでの流れでプラグインをビルドしてみました。AudioProcessorGraphクラスを利用した、音声をそのままスルーするプラグインとして動作するはずですので、エラーなくビルドでき、動作するかを試してみました。
何も起こらないプラグインですが、基本となる形みたいですね。