チュートリアル:プラグインの例
このチュートリアルでは、オーディオ/MIDIのプラグインの例をいくつか詳しく説明し、プラグイン開発のオープンな可能性を探ります。
レベル:中級
プラットフォーム:Windows, macOS, Linux, iOS
プラグイン形式:VST, VST3, AU, AAX, Standalone
クラス:MidiBuffer, SortedSet, AudioParameterFloat, Synthesiser, MidiBuffer, MidiMessage, AudioProcessorValueTreeState, GenericAudioProcessorEditor,
はじめる
このチュートリアルにはいくつかのデモプロジェクトがあります。 これらのプロジェクトのダウンロード・リンクは、チュートリアルの関連セクションに記載されています。
各セクションのこのステップでヘルプが必要な場合は、 Projucerパート1:Projucerを始める を参照してください
デモプロジェクト
このチュートリアルで提供されるデモプロジェクトは、オーディオ/MIDIプラグインのいくつかの異なる例を示しています。 要約すると、これらのプラグインは
- シンプルなアルペジエーターで面白い音楽パターンを作る。アルペジエータープラグイン へジャンプ。
- ノイズゲートで不要なノイズをフィルタリング。ノイズゲートプラグイン へジャンプ
- チャンネル数をマルチアウト・シンセサイザーに拡張する。マルチアウトシンセプラグイン へジャンプします。
- チャンネル数をサラウンド対応プラグインに拡張する。サラウンドプラグイン へジャンプします。
私たちは、すべてのプロジェクトに共通する GenericAudioProcessorEditor クラスを使用して、プラグイン例全体のGUIコンポーネントをレイアウトしています。
ここで紹介するコードは、JUCE Examples の PlugInSamples と大まかに似ています。
アルペジエーター・プラグイン
このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP プロジェクトを解凍し、Projucerで最初のヘッダーファイルを開きます。
Projucerのプロジェクト設定の "Plugin Characteristics" フィールドで "MIDI Effect Plugin" オプションを有効にしてください。
アルペジエーターは、オーディオプロセッシングのないMIDIプラグインで、ソフトウェア・インストゥルメントやDAWのMIDIトラックに挿入して、入力されるMIDI信号を変更することができます。
アルペジエーターの実装
Arpeggiator
クラスでは、アルペジエーターの動作を実装するために、以下のようにいくつかのプライベート・メンバー変数を定義した:
private:
//==============================================================================
juce::AudioParameterFloat* speed;
int currentNote, lastNoteValue;
int time;
float rate;
juce::SortedSet<int> notes;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Arpeggiator)
};
その中に、あるソートルールに従ってユニークなint変数のセットを保持する SortedSet オブジェクトがある。これによって、MIDIノートを効率的に並べ替えて、希望する音楽パターンを作り出すことができる。
クラスのコンストラクタでは、MIDIプラグインを作成するため、オーディオバスなしでプラグインを初期化します。また、ここに示すように、アルペジエーターのスピードのパラメーターを1つ追加します:
Arpeggiator()
: AudioProcessor (BusesProperties()) // add no audio buses at all
{
addParameter (speed = new juce::AudioParameterFloat ("speed", "Arpeggiator Speed", 0.0, 1.0, 0.5));
}
prepareToPlay()
関数では、以下のようにいくつかの変数を初期化して、その後の処理に備える:
void prepareToPlay (double sampleRate, int) override
{
notes.clear(); // [1]
currentNote = 0; // [2]
lastNoteValue = -1; // [3]
time = 0; // [4]
rate = static_cast<float> (sampleRate); // [5]
}
- まず、MIDIノート番号の SortedSet を空にします。
- currentNote変数は、音符の SortedSet の現在のインデックスを一時的に保持する。
- lastNoteValue変数は、ノートを止めることができるように、前のインデックスを一時的に保持する。
- time変数は、バッファサイズとサンプルレートに対するノートの持続時間を保持する。
- rate は、現在のサンプルレートを float 変数に格納します。
次に、processBlock()
関数の中で以下のように実際の処理を行う:
void processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi) override
{
// the audio buffer in a midi effect will have zero channels!
jassert (buffer.getNumChannels() == 0); // [6]
// however we use the buffer to get timing information
auto numSamples = buffer.getNumSamples(); // [7]
// get note duration
auto noteDuration = static_cast<int> (std::ceil (rate * 0.25f * (0.1f + (1.0f - (*speed))))); // [8]
for (const auto metadata : midi) // [9]
{
const auto msg = metadata.getMessage();
if (msg.isNoteOn()) notes.add (msg.getNoteNumber());
else if (msg.isNoteOff()) notes.removeValue (msg.getNoteNumber());
}
midi.clear();
- MIDIプラグインを扱っていることを確認するために、オーディオバッファにオーディオチャンネルがないことを表明する。
- オーディオバッファからブロック内のサンプル数を取得する。
- ユーザーインターフェースのスピードパラメーターとサンプルレートに従って、サンプル数で音符の長さを計算します。
- MidiBuffer 内のすべてのイベントに対して、イベントが "Note On" の場合はノートを SortedSet に追加し、イベントが "Note Off" の場合はノートを削除します。
- 次に、MidiBuffer を空にして、次のステップでノートを1つずつバッファに戻します。
if ((time + numSamples) >= noteDuration) // [11]
{
auto offset = juce::jmax (0, juce::jmin ((int) (noteDuration - time), numSamples - 1)); // [12]
if (lastNoteValue > 0) // [13]
{
midi.addEvent (juce::MidiMessage::noteOff (1, lastNoteValue), offset);
lastNoteValue = -1;
}
if (notes.size() > 0) // [14]
{
currentNote = (currentNote + 1) % notes.size();
lastNoteValue = notes[currentNote];
midi.addEvent (juce::MidiMessage::noteOn (1, lastNoteValue, (juce::uint8) 127), offset);
}
}
time = (time + numSamples) % noteDuration; // [15]
}
- 現在のブロックのサンプル数を加算した現在の時間が、ノートの継続時間より大きいかどうかをチェックする。もしそうであれば、現在のブロックの終わりには音符の遷移に達するので、MidiBuffer を変更します。 そうでなければ、MIDIステートをそのまま維持する。
- 現在のオーデ ィオブロック内でノートトランジションが発生するサンプルオフセットを計算する。
- 前のノートがまだ再生されている場合、変数lastNoteValueは0より大きいので、正しいサンプルオフセットでノートの再生を停止するために "Note Off "イベントを送信する必要があります。次に、lastNoteValue変数をリセットします。
- SortedSetにシャッフルして再生するノートがある場合、前のノート番号を保存し、次のノート番号を取得した後、セットの最初のノートを再生するために "Note On "イベントを送信します。
- 最後に、ノート遷移に到達したかどうかにかかわらず、ノート持続時間に対する現在の時間を追跡します。
ノイズゲートプラグイン
このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP。 プロジェクトを解凍し、Projucerで最初のヘッダーファイルを開きます。
ノイズゲートはオーディオプラグインで、DAWのトラックにインサートとして置かれたときに、あるサイドチェインスレッショルド以下の入力音をフィルタリングします。
ノイズゲートの実装
NoiseGate
クラスでは、ノイズ・ゲートの動作を実装するために、以下のようにいくつかのプライベート・メンバー変数を定義しました:
private:
//==============================================================================
juce::AudioParameterFloat* threshold;
juce::AudioParameterFloat* alpha;
int sampleCountDown;
float lowPassCoeff;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NoiseGate)
};
クラスのコンストラクタで、プラグインを入力、出力、サイドチェイン用の3つのステレオ・バスで初期化します [1]。 また、ここに示すように、スレッショルドとアルファの2つのパラメータを追加します[2]:
NoiseGate()
: AudioProcessor (BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo()) // [1]
.withOutput ("Output", juce::AudioChannelSet::stereo())
.withInput ("Sidechain", juce::AudioChannelSet::stereo()))
{
addParameter (threshold = new juce::AudioParameterFloat ("threshold", "Threshold", 0.0f, 1.0f, 0.5f)); // [2]
addParameter (alpha = new juce::AudioParameterFloat ("alpha", "Alpha", 0.0f, 1.0f, 0.8f));
}
スレッショルドパラメーターは、ノイズゲートが入力信号に作用するパワーレベルを決定する。alpha パラメーターは、サイドチェイン信号のフィルタリングを制御します。
isBusesLayoutSupported()
関数では、入力チャンネル数が出力チャンネル数と同じで、入力バスが有効になっていることを確認します:
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
// the sidechain can take any layout, the main bus needs to be the same on the input and output
return layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet()
&& ! layouts.getMainInputChannelSet().isDisabled();
}
prepareToPlay()
関数では、以下のようにいくつかの変数を初期化して、その後の処理に備える:
void prepareToPlay (double, int) override
{
lowPassCoeff = 0.0f; // [3]
sampleCountDown = 0; // [4]
}
- サイドチェイン信号とαパラメータからローパス係数を計算し、ゲーティング動作を決定する。
- サンプル・カウントダウンは、ゲーティングが発生する前に、サンプル・レートに対して残っているサンプル数を追跡することができます。
次に、processBlock()
関数で以下のように実際の処理を行います:
void processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer&) override
{
auto mainInputOutput = getBusBuffer (buffer, true, 0); // [5]
auto sideChainInput = getBusBuffer (buffer, true, 1);
auto alphaCopy = alpha->get(); // [6]
auto thresholdCopy = threshold->get();
for (auto j = 0; j < buffer.getNumSamples(); ++j) // [7]
{
auto mixedSamples = 0.0f;
for (auto i = 0; i < sideChainInput.getNumChannels(); ++i) // [8]
mixedSamples += sideChainInput.getReadPointer (i) [j];
mixedSamples /= static_cast<float> (sideChainInput.getNumChannels());
lowPassCoeff = (alphaCopy * lowPassCoeff) + ((1.0f - alphaCopy) * mixedSamples); // [9]
if (lowPassCoeff >= thresholdCopy) // [10]
sampleCountDown = (int) getSampleRate();
// very in-effective way of doing this
for (auto i = 0; i < mainInputOutput.getNumChannels(); ++i) // [11]
*mainInputOutput.getWritePointer (i, j) = sampleCountDown > 0 ? *mainInputOutput.getReadPointer (i, j)
: 0.0f;
if (sampleCountDown > 0) // [12]
--sampleCountDown;
}
}
- まず、サイドチェーンバッファをメインのIOバッファから分離し、後続のステップで別々に処理する。
- 次に、しきい値とアルファパラメータのコピーを取得する。
- 外側のループはオーディオバッファブロック内の個々のサンプルを処理し、内側のループはチャンネルをインターリーブ処理します。これにより、1つのサンプル処理で各チャンネルの状態を同じに保つことができます。
- サイドチェーンの各チャンネルについて、信号を加算し、サイドチェーンのチャンネル数で割って、信号をモノラルに合計します。
- 次に、y[i] = ((1 - alpha) * sidechain) + (alpha * y[i - 1]) という式を使って、alphaパラメータとサイドチェイン・モノ信号からローパス係数を計算します。
- この係 数がスレッショルド以上の場合、サンプルカウントダウンをサンプルレートにリセットする。
- 各入力チャンネルについて、カウントダウンがゼロでない場合、入力バッファのサンプルを出力バッファにコピーします。そうでない場合、ゼロサンプルを書き込むことで出力信号をミュートします。
- 各サンプルが処理されるたびに、サンプルカウントダウンの値をデクリメントします。
ここに示した実装は、通常ノイズゲートをプログラムする方法ではない。世の中にはもっと効率的で優れたアルゴリズムがあります。
マルチアウト・シンセ・プラグイン
このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP。プロジェクトを解凍し、Projucerで最初のヘッダーファイルを開きます。
Projucerのプロジェクト設定の "Plugin Characteristics" フィールドで、"Plugin MIDI Input" オプションを有効にしてください。
マルチアウト・シンセは、オーディオファイルのサンプルを基に最大5つのシンセサイザー音色を生成し、最大16のマルチ出力に信号を出力するソフトウェア・インストゥルメント・プラグインです。