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

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

📚 Source Page

このチュートリアルでは、いくつかのオーディオ/MIDIプラグインの例を詳細に説明し、プラグイン開発の可能性を探ります。

レベル: 中級
プラットフォーム: Windows, macOS, Linux, iOS
プラグインフォーマット: VST, VST3, AU, AAX, Standalone
クラス: MidiBuffer, SortedSet, AudioParameterFloat, Synthesiser, MidiBuffer, MidiMessage, AudioProcessorValueTreeState, GenericAudioProcessorEditor

はじめに

このチュートリアルには、いくつかのデモプロジェクトが付属しています。これらのプロジェクトへのダウンロードリンクは、チュートリアルの関連セクションで提供されています。

これらの各セクションでこの手順についてサポートが必要な場合は、チュートリアル:Projucer Part 1: Projucerを始めようを参照してください。

デモプロジェクト

このチュートリアルで提供されるデモプロジェクトは、オーディオ/MIDIプラグインのいくつかの異なる例を示しています。要約すると、これらのプラグインは以下の通りです:

すべてのプロジェクトで共通のGenericAudioProcessorEditorクラスを使用して、プラグインの例全体でGUIコンポーネントをレイアウトします。

ヒント

ここで紹介するコードは、JUCEサンプルのPlugInSamplesとほぼ同様です。

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

このセクションのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルをProjucerで開いてください。

ヒント

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

アルペジエーターは、オーディオ処理のないMIDIプラグインで、DAWのソフトウェアインストゥルメントまたはMIDIトラックに挿入して、入力MIDIシグナルを変更できます。

Arpeggiator plugin window
Arpeggiator plugin window

アルペジエーターの実装

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プラグインを作成しているため、オーディオバスなしでプラグインを初期化します。また、アルペジエーターの速度用の単一パラメータを追加します:

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(); // [10]
  • [6]:MIDIプラグインを扱っていることを確認するために、オーディオバッファにオーディオチャンネルがないことをアサートします。
  • [7]:それでもオーディオバッファからブロック内のサンプル数を取得します。
  • [8]:ユーザーインターフェースの速度パラメータとサンプルレートに従って、サンプル数でノートの持続時間を計算します。
  • [9]:MidiBuffer内の各イベントについて、イベントが「Note On」の場合はノートをSortedSetに追加し、イベントが「Note Off」の場合はノートを削除します。
  • [10]:次に、次のステップで単一のノートをバッファに1つずつ追加するためにMidiBufferを空にします。
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]
}
  • [11]:現在の時間に現在のブロック内のサンプル数を加えたものがノートの持続時間より大きいかどうかを確認します。そうであれば、現在のブロックの終わりまでにノートの遷移に達することを意味するため、MidiBufferを変更する処理を行います。そうでなければ、MIDI状態をそのまま維持します。
  • [12]:現在のオーディオブロック内でノートの遷移が発生するサンプルオフセットを計算します。
  • [13]:前のノートがまだ再生中の場合、lastNoteValue変数は0より大きく、正しいサンプルオフセットでノートの再生を停止するために「Note Off」イベントを送信する必要があります。その後、lastNoteValue変数をリセットします。
  • [14]:SortedSetにシャッフルして再生するノートがある場合、前のノート番号を保存し、次のノート番号を取得した後、セット内の最初のノートを再生するために「Note On」イベントを送信します。
  • [15]:最後に、ノートの遷移に達するかどうかに関係なく、ノートの持続時間に対する現在の時間を追跡します。

ノイズゲートプラグイン

このセクションのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルをProjucerで開いてください。

ノイズゲートは、DAWトラックにインサートとして配置されたときに、特定のサイドチェーンしきい値以下の入力サウンドをフィルタリングするオーディオプラグインです。

Noise gate plugin window
Noise gate plugin window

ノイズゲートの実装

NoiseGateクラスでは、ノイズゲートの動作を実装するためにいくつかのプライベートメンバー変数を定義しています:

private:
//==============================================================================
juce::AudioParameterFloat* threshold;
juce::AudioParameterFloat* alpha;
int sampleCountDown;

float lowPassCoeff;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NoiseGate)
};

クラスコンストラクタでは、入力、出力、サイドチェーン用の3つのステレオバスでプラグインを初期化します[1]。また、thresholdとalphaの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));
}

thresholdパラメータは、ノイズゲートが入力信号に作用するパワーレベルを決定します。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]
}
  • [3]:ローパス係数は、サイドチェーン信号とalphaパラメータから計算され、ゲーティング動作を決定します。
  • [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;
}
}
  • [5]:まず、後続のステップで別々に処理するために、サイドチェーンバッファをメインIOバッファから分離します。
  • [6]:次に、thresholdとalphaパラメータのコピーを取得します。
  • [7]:外側のループはオーディオバッファブロック内の個々のサンプルを処理し、内側のループはインターリーブ方式でチャンネルを処理します。これにより、単一のサンプル処理で各チャンネルに同じ状態を維持できます。
  • [8]:サイドチェーンの各チャンネルについて、信号を加算し、サイドチェーンチャンネル数で割って信号をモノにサムします。
  • [9]:次に、式y[i] = ((1 - alpha) * sidechain) + (alpha * y[i - 1])を使用して、alphaパラメータとサイドチェーンモノ信号からローパス係数を計算します。
  • [10]:この係数がしきい値以上の場合、サンプルカウントダウンをサンプルレートにリセットします。
  • [11]:各入力チャンネルについて、カウントダウンがゼロでない場合は入力バッファサンプルを出力バッファにコピーします。そうでなければ、ゼロサンプルを書き込んで出力信号をミュートします。
  • [12]:処理される各サンプルについてサンプルカウントダウン値をデクリメントします。
ヒント

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

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

このセクションのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルを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があります。また、チャンネルごとに1つのシンセを保持するSynthesiserオブジェクトの配列と、チュートリアルで使用するサンプルサウンドへのスマートポインタがあります。

MIDIチャンネルの最大数とシンセボイスの最大数として、いくつかの便利な定数を列挙型として宣言します:

enum {
maxMidiChannel = 16,
maxNumberOfVoices = 5
};

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

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]、シンセごとに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);
}
  • [6]:まずサンプルバイナリデータからMemoryInputStreamを作成し、ストリームを「ogg」フォーマットとして読み取るように変換します。
  • [7]:先に作成したストリームリーダーでSamplerSoundオブジェクトを宣言し、BigIntegerを使用してMIDIノートの範囲を制約します。
  • [8]:シンセ配列内の各Synthesiserオブジェクトについて、新しいサウンドをロードする前に現在ロードされているSynthesiserSoundをクリアします。
  • [9]:次に、新しく作成されたSamplerSoundをスマートポインタに割り当て、サウンドへの参照を保持します。
  • [10]:最後に、各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()関数を呼び出して、シンセ配列内の各Synthesiserオブジェクトのサンプルレートを設定することで後続の処理に備えます:

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]
}
}
  • [11]:まず、出力バスの数を取得します。
  • [12]:各出力バス(したがって各Synthesiserインスタンス)について、不要なオーディオバスバッファをフィルタリングし、後で定義するプライベートヘルパー関数を呼び出してシンセサイザーのMIDIチャンネルに対応しないMIDIメッセージをフィルタリングします。
  • [13]:その後、正しいオーディオバスバッファとMIDIチャンネルバッファを提供して、対応するSynthesiserオブジェクトで直接renderNextBlock()関数を呼び出してサウンドを生成できます。

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]
}
  • [14]:各MIDIチャンネルバッファについて、MIDIメッセージチャンネルが探している出力バスのMIDIチャンネルと一致するかどうかを確認し、一致する場合は新しく作成されたMidiBufferMidiMessageを追加します。
  • [15]:すべてのMIDIチャンネルのすべてのMIDIメッセージを反復処理したら、選択したチャンネルのMIDIメッセージのみを含むバッファを返します。

サラウンドプラグイン

このセクションのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルをProjucerで開いてください。

サラウンドユーティリティは、サラウンド設定を含む個々のチャンネルの入力信号を監視し、選択したチャンネルにピングサイン波を送信できるプラグインです。

Surround plugin window
Surround plugin window

サラウンドの実装

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);
}
  • [4]:まず、ユーザーがクリックしたチャンネルインデックスを指定する一時変数をリセットします。
  • [5]:次に、ブロックを処理する前にサンプルレートを保存し、後でこの一時変数をインクリメントしてサンプルオフセットで位相を追跡します。
  • [6]:ブロックの現在アクティブなチャンネル数に合わせて、アクティブチャンネルと係数の配列をリサイズする必要があります。
  • [7]:reset()関数は、後で定義するようにアクティブチャンネル配列をクリアするために複数の場所で呼び出されます。
  • [8]:最後に、GUIスレッドへの非同期更新をトリガーし、後でコールバックを処理します。

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

void releaseResources() override { reset(); }

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]
}
  • [9]:オーディオバッファ内の各チャンネルについて、アクティブチャンネルのカウントダウンサンプルとアルファ係数値への参照を取得します。
  • [10]:次に、バッファブロック内の各サンプルについて、チャンネルの入力サンプル値を取得し、式alpha[i] = ((1 - x) * sample) + (x * alpha[i - 1])を使用してアルファ係数を計算します。この場合x = 0.8です。
  • [11]:アルファ係数がこの場合0.1という特定のしきい値以上の場合、その特定のチャンネルのカウントダウンサンプルをサンプルレートの半分に設定します。
  • [12]:また、カウントダウンのサンプル数から現在のブロック内のサンプル数を減算します。
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++));
}
}
  • [13]:次に、サンプルオフセットとブロック内のサンプル数の2つから小さい方の数を取って、埋める出力サンプル数を計算します。
  • [14]:次に、クリックされたチャンネルインデックスが有効かどうかを確認し、正しいチャンネルバッファの書き込みポインタを取得できます。
  • [15]:最後に、A4周波数をサンプルレートで割ってサイン波の周波数を計算します。次に、埋める各サンプルについて、適切な周波数と位相オフセットを使用してサイン波を生成し、次のサンプルのために割り当て後にインクリメントするサンプルオフセット変数を使用します。

まとめ

このチュートリアルでは、いくつかのオーディオ/MIDIプラグインの例を検討しました。特に以下のことを行いました:

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

関連項目