メインコンテンツまでスキップ

チュートリアル:プラグインの例

📚 Source Page

このチュートリアルでは、オーディオ/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 と大まかに似ています。

アルペジエーター・プラグイン

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIPZIP プロジェクトを解凍し、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]
}
  1. まず、MIDIノート番号の SortedSet を空にします。
  2. currentNote変数は、音符の SortedSet の現在のインデックスを一時的に保持する。
  3. lastNoteValue変数は、ノートを止めることができるように、前のインデックスを一時的に保持する。
  4. time変数は、バッファサイズとサンプルレートに対するノートの持続時間を保持する。
  5. 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();
  1. MIDIプラグインを扱っていることを確認するために、オーディオバッファにオーディオチャンネルがないことを表明する。
  2. オーディオバッファからブロック内のサンプル数を取得する。
  3. ユーザーインターフェースのスピードパラメーターとサンプルレートに従って、サンプル数で音符の長さを計算します。
  4. MidiBuffer 内のすべてのイベントに対して、イベントが "Note On" の場合はノートを SortedSet に追加し、イベントが "Note Off" の場合はノートを削除します。
  5. 次に、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]
}
  1. 現在のブロックのサンプル数を加算した現在の時間が、ノートの継続時間より大きいかどうかをチェックする。もしそうであれば、現在のブロックの終わりには音符の遷移に達するので、MidiBuffer を変更します。 そうでなければ、MIDIステートをそのまま維持する。
  2. 現在のオーディオブロック内でノートトランジションが発生するサンプルオフセットを計算する。
  3. 前のノートがまだ再生されている場合、変数lastNoteValueは0より大きいので、正しいサンプルオフセットでノートの再生を停止するために "Note Off "イベントを送信する必要があります。次に、lastNoteValue変数をリセットします。
  4. SortedSetにシャッフルして再生するノートがある場合、前のノート番号を保存し、次のノート番号を取得した後、セットの最初のノートを再生するために "Note On "イベントを送信します。
  5. 最後に、ノート遷移に到達したかどうかにかかわらず、ノート持続時間に対する現在の時間を追跡します。

ノイズゲートプラグイン

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIPZIP。 プロジェクトを解凍し、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]
}
  1. サイドチェイン信号とαパラメータからローパス係数を計算し、ゲーティング動作を決定する。
  2. サンプル・カウントダウンは、ゲーティングが発生する前に、サンプル・レートに対して残っているサンプル数を追跡することができます。

次に、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;
}
}
  1. まず、サイドチェーンバッファをメインのIOバッファから分離し、後続のステップで別々に処理する。
  2. 次に、しきい値とアルファパラメータのコピーを取得する。
  3. 外側のループはオーディオバッファブロック内の個々のサンプルを処理し、内側のループはチャンネルをインターリーブ処理します。これにより、1つのサンプル処理で各チャンネルの状態を同じに保つことができます。
  4. サイドチェーンの各チャンネルについて、信号を加算し、サイドチェーンのチャンネル数で割って、信号をモノラルに合計します。
  5. 次に、y[i] = ((1 - alpha) * sidechain) + (alpha * y[i - 1]) という式を使って、alphaパラメータとサイドチェイン・モノ信号からローパス係数を計算します。
  6. この係数がスレッショルド以上の場合、サンプルカウントダウンをサンプルレートにリセットする。
  7. 各入力チャンネルについて、カウントダウンがゼロでない場合、入力バッファのサンプルを出力バッファにコピーします。そうでない場合、ゼロサンプルを書き込むことで出力信号をミュートします。
  8. 各サンプルが処理されるたびに、サンプルカウントダウンの値をデクリメントします。
注記

ここに示した実装は、通常ノイズゲートをプログラムする方法ではない。世の中にはもっと効率的で優れたアルゴリズムがあります。

マルチアウト・シンセ・プラグイン

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIPZIP。プロジェクトを解凍し、Projucerで最初のヘッダーファイルを開きます。

注記

Projucerのプロジェクト設定の "Plugin Characteristics" フィールドで、"Plugin MIDI Input" オプションを有効にしてください。

マルチアウト・シンセは、オーディオファイルのサンプルを基に最大5つのシンセサイザー音色を生成し、最大16のマルチ出力に信号を出力するソフトウェア・インストゥルメント・プラグインです。

Multi-out synth plugin window
Multi-out synth plugin window

マルチアウト・シンセのインプリメンテーション

MultiOutSynth クラスでは、マルチアウト・シンセの動作を実装するために、以下のようにいくつかのプライベート・メンバー変数を定義した:

    //==============================================================================
juce::AudioFormatManager formatManager;
juce::OwnedArray<juce::Synthesiser> synth;
juce::SynthesiserSound::Ptr sound;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiOutSynth)
};

AudioFormatManager は、サンプル・サウンドを読み込むためのオーディオ・ファイル・フォーマットを登録します。 また、Synthesiser オブジェクトの配列があり、チャンネルごとに1つのシンセサイザーと、チュートリアルで使うサンプルサウンドへのスマートポインターを保持しています。

また、MIDIチャンネルの最大数やシンセのボイスの最大数など、便利な定数も宣言しています:

    enum
{
maxMidiChannel = 16,
maxNumberOfVoices = 5
};

クラスのコンストラクタでは、プラグインを16のステレオ出力バスで初期化しますが、ソフトウェア・インストゥルメント・プラグインを作成するため、入力バスはありません [1]。 また、".ogg "サンプル・ファイル[2]を読み込むために、AudioFormatManager オブジェクトに基本的なオーディオ・ファイル・フォーマットを登録します:

    MultiOutSynth()
: AudioProcessor (BusesProperties()
.withOutput ("Output #1", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output #2", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #3", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #4", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #5", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #6", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #7", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #8", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #9", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #10", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #11", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #12", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #13", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #14", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #15", juce::AudioChannelSet::stereo(), false)
.withOutput ("Output #16", juce::AudioChannelSet::stereo(), false)) // [1]
{
// initialize other stuff (not related to buses)
formatManager.registerBasicFormats(); // [2]

for (auto midiChannel = 0; midiChannel < maxMidiChannel; ++midiChannel) // [3]
{
synth.add (new juce::Synthesiser());

for (auto i = 0; i < maxNumberOfVoices; ++i)
synth[midiChannel]->addVoice (new juce::SamplerVoice()); // [4]
}

loadNewSample (juce::MemoryBlock (singing_ogg, singing_oggSize)); // [5]
}

MIDI/出力チャンネルごとに、新しい Synthesiser オブジェクトをインスタンス化して配列に追加し[3]、1つのシンセにつき5つの SamplerVoice オブジェクトを作成します[4]。また、以下に定義する loadNewSample() プライベート関数を使用して、サンプルファイルをバイナリデータとしてロードします[5]:

    void loadNewSample (const juce::MemoryBlock& sampleData)
{
auto soundBuffer = std::make_unique<juce::MemoryInputStream> (sampleData, false); // [6]
std::unique_ptr<juce::AudioFormatReader> formatReader (formatManager.findFormatForFileExtension ("ogg")->createReaderFor (soundBuffer.release(), true));

juce::BigInteger midiNotes;
midiNotes.setRange (0, 126, true);
juce::SynthesiserSound::Ptr newSound = new juce::SamplerSound ("Voice", *formatReader, midiNotes, 0x40, 0.0, 0.0, 10.0); // [7]

for (auto channel = 0; channel < maxMidiChannel; ++channel) // [8]
synth[channel]->removeSound (0);

sound = newSound; // [9]

for (auto channel = 0; channel < maxMidiChannel; ++channel) // [10]
synth[channel]->addSound (sound);
}
  1. まず、サンプルのバイナリ・データから MemoryInputStream を作成し、そのストリームを "ogg" フォーマットとしてファイルを読み込むように変換する。
  2. 先に作成したストリーム・リーダーを使って SamplerSound オブジェクトを宣言し、BigInteger を使ってMIDIノートの範囲を制約する。
  3. シンセ配列内のすべての Synthesiser オブジェクトについて、新しいものをロードする前に、現在ロードされている SynthesiserSound をクリアする。
  4. 次に、新しく作成した SamplerSound をスマートポインターに代入し、サウンドへの参照を保持します。
  5. 最後に、全ての Synthesizer オブジェクトに対して、新しいサウンドを SamplerSound オブジェクトとしてロードします。

必要以上にバスが追加されたり削除されたりしないように、AudioProcessor クラスから以下の2つの関数をオーバーライドします:

    bool canAddBus    (bool isInput) const override   { return (! isInput && getBusCount (false) < maxMidiChannel); }
bool canRemoveBus (bool isInput) const override { return (! isInput && getBusCount (false) > 1); }

これにより、インプットバスの追加や削除、アウトプットバスの16チャンネルを超える追加や完全な削除を防ぐことができます。

prepareToPlay() 関数では、setCurrentPlaybackSampleRate() 関数を呼び出すことで、シンセ配列内のすべての Synthesizer オブジェクトのサンプルレートを設定し、後続の処理の準備をする:

    void prepareToPlay (double newSampleRate, int samplesPerBlock) override
{
juce::ignoreUnused (samplesPerBlock);

for (auto midiChannel = 0; midiChannel < maxMidiChannel; ++midiChannel)
synth[midiChannel]->setCurrentPlaybackSampleRate (newSampleRate);
}

次に、processBlock() 関数の中で以下のように実際の処理を行う:

    void processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiBuffer) override
{
auto busCount = getBusCount (false); // [11]

for (auto busNr = 0; busNr < busCount; ++busNr) // [12]
{
auto midiChannelBuffer = filterMidiMessagesForChannel (midiBuffer, busNr + 1);
auto audioBusBuffer = getBusBuffer (buffer, false, busNr);

synth [busNr]->renderNextBlock (audioBusBuffer, midiChannelBuffer, 0, audioBusBuffer.getNumSamples()); // [13]
}
}
  1. まず、出力バスの数を求める。
  2. すべての出力バス(したがって、すべての Synthesiser インスタンス)に対して、不要なオーディオバスバッファをフィルタリングし、その後に定義されたプライベートヘルパー関数を呼び出すことによって、シンセシスのmidiチャンネルに対応しないmidiメッセージをフィルタリングする。
  3. その後、対応する Synthesiser オブジェクト上で直接 renderNextBlock() 関数を呼び出すことで、正しいオーディオバスバッファとMIDIチャンネルバッファを供給してサウンドを生成することができます。

MIDIチャンネルをフィルタリングするヘルパー関数は、後述するように実装されている:

    static juce::MidiBuffer filterMidiMessagesForChannel (const juce::MidiBuffer& input, int channel)
{
juce::MidiBuffer output;

for (auto metadata : input) // [14]
{
auto message = metadata.getMessage();

if (message.getChannel() == channel)
output.addEvent (message, metadata.samplePosition);
}

return output; // [15]
}
  1. すべてのMidiチャンネルバッファについて、Midiメッセージのチャンネルが、探している出力バスのMidiチャンネルと一致するかどうかをチェックし、一致すれば、 MidiMessage を新しく作成した MidiBuffer に追加する。
  2. すべてのMIDIチャンネルのすべてのMIDIメッセージを繰り返しチェックした後、選択したチャンネルのMIDIメッセージだけを含むバッファを返します。

サラウンド・プラグイン

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIPZIP。 プロジェクトを解凍し、Projucerで最初のヘッダーファイルを開きます。

サラウンド・ユーティリティは、サラウンド設定を含む各チャンネルの入力信号をモニターし、選択したチャンネルにpingサイン波を送ることができるプラグインです。

サラウンドプラグインウィンドウ
サラウンドプラグインウィンドウ

サラウンドの実装

SurroundProcessor クラスでは、サラウンド動作を実装するために、以下のようにいくつかのプライベート・メンバー変数を定義しました:

    juce::Array<int> channelActive;
juce::Array<float> alphaCoeffs;
int channelClicked;
int sampleOffset;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SurroundProcessor)
};

これらのうち、プラグインのアクティブ・チャンネルのサンプル数を記録する配列と、各チャンネルのアルファ係数を記録する配列がある。

クラスのコンストラクタでは、デフォルトで入力用と出力用にそれぞれ2つのステレオペアのバスでプラグインを初期化しますが、設定は現在使用されているバスレイアウトの設定に応じて変更されます。

    SurroundProcessor()
: AudioProcessor(BusesProperties().withInput ("Input", juce::AudioChannelSet::stereo())
.withOutput ("Output", juce::AudioChannelSet::stereo()))
{}

isBusesLayoutSupported() 関数では、以下のように、入出力チャンネルが離散チャンネルであること [1]、入力チャンネル数が出力チャンネル数と同じであること [2]、入力バスが有効であること [3]を確認する:

    bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
return ((! layouts.getMainInputChannelSet() .isDiscreteLayout()) // [1]
&& (! layouts.getMainOutputChannelSet().isDiscreteLayout())
&& (layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet()) // [2]
&& (! layouts.getMainInputChannelSet().isDisabled())); // [3]
}

prepareToPlay() 関数では、以下のようにいくつかの変数を初期化して、その後の処理に備える:

    void prepareToPlay (double sampleRate, int samplesPerBlock) override
{
channelClicked = 0; // [4]
sampleOffset = static_cast<int> (std::ceil (sampleRate)); // [5]

auto numChannels = getChannelCountOfBus (true, 0); // [6]
channelActive.resize (numChannels);
alphaCoeffs.resize (numChannels);
reset(); // [7]

triggerAsyncUpdate(); // [8]

juce::ignoreUnused (samplesPerBlock);
}
  1. まず、ユーザーによってクリックされたチャンネルのインデックスを指定する一時変数をリセットします。
  2. 次に、ブロックを処理する前にサンプル・レートを保存し、後でこの一時変数をインクリメントして、サンプル・オフセットで位相を追跡します。
  3. アクティブなチャンネルと係数アレイのサイズを、現在アクティブなチャンネル数に変更します。
  4. reset()関数が数カ所で呼び出され、後で定義するようにアクティブ・チャネル配列がクリアされます。
  5. 最後に、GUIスレッドへの非同期更新をトリガし、後でコールバックを処理します。

reset() 関数は、ブロック処理が終了した後の releaseResources() 関数でも呼び出されます:

reset() 関数は、以下のように各チャンネルの値を0にすることで実装される:

    void reset() override
{
for (auto& channel : channelActive)
channel = 0;
}

GUIの非同期更新に関しては、AudioProcessorEditorupdateGUI() 関数を呼び出してコールバックを処理します:

    void handleAsyncUpdate() override
{
if (auto* editor = getActiveEditor())
if (auto* surroundEditor = dynamic_cast<SurroundEditor*> (editor))
surroundEditor->updateGUI();
}

AudioProcessorSurroundEditor クラスで定義された ChannelClickListener クラスを継承しているので、その仮想関数をオーバーライドする必要があります。 channelButtonClicked() コールバック関数は、ユーザがチャンネルボタンをクリックしたときにトリガーされます。押されたチャンネルのインデックスを提供し、サンプルオフセット変数をリセットします:

    void channelButtonClicked (int channelIndex) override
{
channelClicked = channelIndex;
sampleOffset = 0;
}

isChannelActive() ヘルパー関数は、アクティブなチャンネル配列にまだ処理すべきサンプルがあるかどうかをチェックすることで、指定したチャンネルがアクティブかどうかを返します:

    bool isChannelActive (int channelIndex) override
{
return channelActive[channelIndex] > 0;
}

次に、processBlock() 関数の中で以下のように実際の処理を行う:

    void processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer&) override
{
for (auto ch = 0; ch < buffer.getNumChannels(); ++ch) // [9]
{
auto& channelTime = channelActive.getReference (ch);
auto& alpha = alphaCoeffs.getReference (ch);

for (auto j = 0; j < buffer.getNumSamples(); ++j) // [10]
{
auto sample = buffer.getReadPointer (ch)[j];
alpha = (0.8f * alpha) + (0.2f * sample);

if (std::abs (alpha) >= 0.1f) // [11]
channelTime = static_cast<int> (getSampleRate() / 2.0);
}

channelTime = juce::jmax (0, channelTime - buffer.getNumSamples()); // [12]
}
  1. オーディオバッファ内の各チャンネルについて、アクティブなチャンネルのカウントダウンサンプルとアルファ係数の値の参照を取得する。
  2. 次に、バッファブロックの各サンプルについて、チャンネルの入力サンプル値を取得し、 alpha[i] = ((1 - x) * sample) + (x * alpha[i - 1]) という式を使ってアルファ係数を計算します。
  3. アルファ係数がある閾値(この場合は 0.1)以上の場合、そのチャンネルのカウントダウン サンプルをサンプルレートの半分に設定します。
  4. また、カウントダウンのサンプル数から現在のブロックのサンプル数を差し引く。
        auto fillSamples = juce::jmin (static_cast<int> (std::ceil (getSampleRate())) - sampleOffset,
buffer.getNumSamples()); // [13]

if (juce::isPositiveAndBelow (channelClicked, buffer.getNumChannels())) // [14]
{
auto* channelBuffer = buffer.getWritePointer (channelClicked);
auto freq = (float) (440.0 / getSampleRate());

for (auto i = 0; i < fillSamples; ++i) // [15]
channelBuffer[i] += std::sin (2.0f * juce::MathConstants<float>::pi * freq * static_cast<float> (sampleOffset++));
}
}
  1. 次に、サンプルオフセットとブロック内のサンプル数から2つのうち最小の数を取ることによって、充填する出力サンプル数を計算します。

  2. 次に、クリックしたチャンネル・インデックスが有効かどうかをチェックし、正しいチャンネル・バッファの書き込みポインタを取得します。

  3. 最後に、A4 周波数をサンプル・レートで割って正弦波の周波数を計算します。 次に、サンプルを埋めるごとに、適切な周波数と位相オフセットを持つ正弦波を生成します。 サンプル・オフセット変数は、次のサンプルの割り当て後にインクリメントします。

概要

このチュートリアルでは、オーディオ/MIDIプラグインの例をいくつか見てきました。特に

  • 面白い音楽パターンを作るためのシンプルなアルペジエーターを作る
  • 不要なノイズをフィルタリングするノイズゲートの作成
  • 複数の出力を持つシンセサイザーを作成しました
  • チャンネル数を拡張するためのサラウンド対応プラグインを構築

関連項目