プラグインの例
このチュートリアルでは、オーディオ/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() 関数では、以下のようにいくつかの変数を初期化して、その後の処理に備える:
voidprepareToPlay(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() 関数の中で以下のように実際の処理を行う:
voidprocessBlock(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(constauto metadata: midi)// [9]
{
constauto msg= metadata.getMessage();
if(msg.isNoteOn()) notes.add(msg.getNoteNumber());
elseif(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() 関数では、入力チャンネル数が出力チャンネル数と同じで、入力バスが有効になっていることを確認します:
boolisBusesLayoutSupported(const BusesLayout& layouts)constoverride
{
// 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() 関数では、以下のようにいくつかの変数を初期化して、その後の処理に備える:
voidprepareToPlay(double,int)override
{
lowPassCoeff=0.0f;// [3]
sampleCountDown=0;// [4]
}
- サイドチェイン信号とαパラメータからローパス係数を計算し、ゲーティング動作を決定する。
- サンプル・カウントダウンは、ゲーティングが発生する前に、サンプル・レートに対して残っているサンプル数を追跡できます。
次に、processBlock() 関数で以下のように実際の処理を行います:
voidprocessBlock(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;
}
}
- まず、サイドチェインバッファをメインのI/Oバッファから分離し、後続のステップで別々に処理する。
- 次に、しきい値とアルファパラメータのコピーを取得する。
- 外側のループはオーディオバッファブロック内の個々のサンプルを処理し、内側のループはチャンネルをインターリーブ処理します。これにより、1つのサンプル処理で各チャンネルの状態を同じに保つことができます。
- サイドチェインの各チャンネルについて、信号を加算し、サイドチェインのチャンネル数で割って、信号をモノラルに合計します。
- 次に、y[i] = ((1 - alpha) * sidechain) + (alpha * y[i - 1]) という式を使って、alphaパラメータとサイド チェイン・モノ信号からローパス係数を計算します。
- この係数がスレッショルド以上の場合、サンプルカウントダウンをサンプルレートにリセットする。
- 各入力チャンネルについて、カウントダウンがゼロでない場合、入力バッファのサンプルを出力バッファにコピーします。そうでない場合、ゼロサンプルを書き込むことで出力信号をミュートします。
- 各サンプルが処理されるたびに、サンプルカウントダウンの値をデクリメントします。
ここに示した実装は、通常ノイズゲートをプログラムする方法ではない。世の中にはもっと効率的で優れたアルゴリズムがあります。
マルチアウト・シンセ・プラグイン
このセクション のデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP。プロジェクトを解凍し、Projucerで最初のヘッダーファイルを開きます。
Projucerのプロジェクト設定の "Plugin Characteristics" フィールドで、"Plugin MIDI Input" オプションを有効にしてください。
マルチアウト・シンセは、オーディオファイルのサンプルを基に最大5つのシンセサイザー音色を生成し、最大16のマルチ出力に信号を出力するソフトウェア・インストゥルメント・プラグインです。

マルチアウト・シンセのインプリメンテーション
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]:
voidloadNewSample(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);
}
- まず、サンプルのバイナリ・データからMemoryInputStream を作成し、そのストリームを "ogg" フォーマットとしてファイルを読み込むように変換する。
- 先に作成したストリーム・リーダーを使ってSamplerSound オブジェクトを宣言し、BigInteger を使ってMIDIノートの範囲を制約する。
- シンセ配列内のすべてのSynthesiser オブジェクトについて、新しいものをロードする前に、現在ロードされているSynthesiserSound をクリアする。
- 次に、新しく作成したSamplerSound をスマートポインターに代入し、サウンドへの参照を保持します。
- 最後に、全てのSynthesizer オブジェクトに対して、新しいサウンドをSamplerSound オブジェクトとしてロードします。
必要以上にバスが追加されたり削除されたりしないように、AudioProcessor クラスから以下の2つの関数をオーバーライドします:
boolcanAddBus(bool isInput)constoverride{return(! isInput&&getBusCount(false)< maxMidiChannel);}
boolcanRemoveBus(bool isInput)constoverride{return(! isInput&&getBusCount(false)>1);}
これにより、インプットバスの追加や削除、アウトプットバスの16チャンネルを超える追加や完全な削除を防ぐことができます。
prepareToPlay() 関数では、setCurrentPlaybackSampleRate() 関数を呼び出すことで、シンセ配列内のすべてのSynthesizer オブジェクトのサンプルレートを設定し、後続の処理の準備をする:
voidprepareToPlay(double newSampleRate,int samplesPerBlock)override
{
juce::ignoreUnused(samplesPerBlock);
for(auto midiChannel=0; midiChannel< maxMidiChannel;++midiChannel)
synth[midiChannel]->setCurrentPlaybackSampleRate(newSampleRate);
}
次に、processBlock() 関数の中で以下のように実際の処理を行う:
voidprocessBlock(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]
}
}
- まず、出力バスの数を求める。
- すべての出力バス(したがって、すべてのSynthesiser インスタンス)に対して、不要なオーディオバスバッファをフィルタリングし、その後に定義されたプライベートヘルパー関数を呼び出すことによって、シンセシスのmidiチャンネルに対応しないmidiメッセージをフィルタリングする。
- その後、対応するSynthesiser オブジェクト上で直接
renderNextBlock()関数を呼び出すことで、正しいオーディオバスバッファとMIDIチャンネルバッファを供給してサウンドを生成できます。
MIDIチャンネルをフィルタリングするヘルパー関数は、後述するように実装されている:
static juce::MidiBufferfilterMidiMessagesForChannel(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]
}
- すべてのMidiチャンネルバッファについて、Midiメッセージのチャンネルが、探している出力バスのMidiチャンネルと一致するかどうかをチェックし、一致すれば、MidiMessage を新しく作成したMidiBuffer に追加する。
- すべてのMIDIチャンネルのすべてのMIDIメッセー ジを繰り返しチェックした後、選択したチャンネルのMIDIメッセージだけを含むバッファを返します。
サラウンド・プラグイン
このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP。プロジェクトを解凍し、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]を確認する:
boolisBusesLayoutSupported(const BusesLayout& layouts)constoverride
{
return((! layouts.getMainInputChannelSet().isDiscreteLayout())// [1]
&&(! layouts.getMainOutputChannelSet().isDiscreteLayout())
&&(layouts.getMainInputChannelSet()== layouts.getMainOutputChannelSet())// [2]
&&(! layouts.getMainInputChannelSet().isDisabled()));// [3]
}
prepareToPlay() 関数では、以下のようにいくつかの変数を初期化して、その後の処理に備える:
voidprepareToPlay(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);
}
- まず、ユーザーによってクリックされたチャンネルのインデックスを指定する一時変数をリセットします。
- 次に、ブロックを処理する前にサンプル・レートを保存し、後でこの一時変数をインクリメントして、サンプル・オフセットで位相を追跡します。
- アクティブなチャンネルと係数アレイのサイズを、現在アクティブなチャンネル数に変更します。
- reset()関数が数カ所で呼び出され、後で定義するようにアクティブ・チャネル配列がクリアされます。
- 最後に、GUIスレッドへの非同期更新をトリガし、後でコールバックを処理します。
reset() 関数は、ブロック処理が終了した後のreleaseResources() 関数でも呼び出されます:
reset() 関数は、以下のように各チャンネルの値を0にすることで実装される:
voidreset()override
{
for(auto& channel: channelActive)
channel=0;
}
GUIの非同期更新に関しては、AudioProcessorEditor のupdateGUI() 関数を呼び出してコールバックを処理します:
voidhandleAsyncUpdate()override
{
if(auto* editor=getActiveEditor())
if(auto* surroundEditor=dynamic_cast<SurroundEditor*>(editor))
surroundEditor->updateGUI();
}
AudioProcessor はSurroundEditor クラスで定義されたChannelClickListenerクラスを継承しているので、その仮想関数をオーバーライドする必要があります。channelButtonClicked() コールバック関数は、ユーザがチャンネルボタンをクリックしたときにトリガーされます。押されたチャンネルのインデックスを提供し、サンプルオフセット変数をリセットします:
voidchannelButtonClicked(int channelIndex)override
{
channelClicked= channelIndex;
sampleOffset=0;
}
isChannelActive() ヘルパー関数は、アクティブなチャンネル配列にまだ処理すべきサンプルがあるかどうかをチェックすることで、指定したチャンネルがアクティブかどうかを返します:
boolisChannelActive(int channelIndex)override
{
return channelActive[channelIndex]>0;
}
次に、processBlock() 関数の中で以下のように実際の処理を行う:
voidprocessBlock(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]
}
- オーディオバッファ内の各チャンネルについて、アクティブなチャンネルのカウントダウンサンプルとアルファ係数の値の参照を取得する。
- 次に、バッファブロックの各サンプルについて、チャンネルの入力サンプル値を取得し、alpha[i] = ((1 - x) * sample) + (x * alpha[i - 1]) という式を使ってアルファ係数を計算します。
- アルファ係数がある閾値(この場合は0.1)以上の場合、そのチャンネルのカウントダウン サンプルをサンプルレートの半分に設定します。
- また、カウントダウンのサンプル数から現在のブロックのサンプル数を差し引く。
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++));
}
}
次に、サンプルオフセットとブロック内のサンプル数から2つのうち最小の数を取ることによって、充填する出力サンプル数を計算します。
次に、クリックしたチャンネル・インデックスが有効かどうかをチェックし、正しいチャンネル・バッファの書き込みポインタを取得します。
最後に、A4周波数をサンプル・レートで割って正弦波の周波数を計算します。次に、サンプルを埋めるごとに、適切な周波 数と位相オフセットを持つ正弦波を生成します。サンプル・オフセット変数は、次のサンプルの割り当て後にインクリメントします。
概要
このチュートリアルでは、オーディオ/MIDIプラグインの例をいくつか見てきました。特に
- 面白い音楽パターンを作るためのシンプルなアルペジエーターを作る
- 不要なノイズをフィルタリングするノイズゲートの作成
- 複数の出力を持つシンセサイザーを作成しました
- チャンネル数を拡張するためのサラウンド対応プラグインを構築