オーディオプラグインの作成、パート13

シリーズのすべての投稿:

パート1.紹介とセットアップ

パート2.コードの学習

パート3. VSTおよびAU

パート4.デジタル歪み

パート5.プリセットとGUI

パート6.信号合成

パート7. MIDIメッセージの受信

パート8.仮想キーボード

パート9.封筒

パート10. GUIの改善

パート11.フィルター

パート12.低周波発振器

パート13.再設計

パート14.ポリフォニー1

パート15.ポリフォニー2

パート16.アンチエイリアス






古典的な減算方式シンセサイザーのすべてのコンポーネントはすでに作成されています。次に、構造を明確に理解したので、再設計を行います。



プラグインは、 SpaceBass (Kasmichi Bass)と呼ばれるポリフォニックシンセサイザーになります。







プラグインには2つのオシレーターがあり、そのサウンドはハンドルを使用してミックスできます。 左に回す-最初のオシレーターのみが鳴り、右に回すと2番目のオシレーターが鳴ります。 真ん中の位置は、両方のオシレーターのサウンドを同じ比率で提供します。 pitch modノブは、対応するオシレーターのトーンの変調レベルを低周波オシレーターで調整します。 ボリュームとフィルターエンベロープは既にわかっています。 「LFO amount」ノブがフィルターセクションに表示されました。 LFOがカットオフ周波数にどのように影響するかを制御します。



この段階でも、見た目は良いです。 進行状況を評価しましょう。 すでに行ったことは次のとおりです。







そして、これから行われることのリストです。







あなたが見ることができるように、ほとんどはすでに行われています!

計画は次のとおりです。まず、Photoshopでグラフィックデザインがどのように作成されるかを見てください。 次に、主な部分はポリフォニーであり、プラグインの構造をわずかに変更します。 最後に、トーン変調を追加します。



これらのタスクを3つの投稿に分割します。 これはデザインに関するものであり、他の2つはポリフォニーに関するものです。これはかなり面倒な部分だからです。



新しいデザイン





ハンドルは、わずかに変更されたボスのようなレスリーサンフォードのノブです 。 私はそれを小さくし、ノッチを広げ、影をわずかに変えました。 変更されたバージョンは、ここからダウンロードできますこのガイドを使って新しいキーボードを作りました。



Photoshopを入手する場所と、Photoshopですべてを描画する方法については詳しく説明しませんが、レイヤーの構造と作業の一般的な原則について説明します。 アーカイブされたTIFをダウンロードして解凍し、PhotoshopまたはGIMPで開きます。 構造を掘り下げて、どのように作成されたかを理解できるように、構造をそのままにしておこうとしました。 テキストを変更する場合は、ロゴ用のTusjと署名用のAvenir 85 Heavyの 2つのフォントが必要になります。 残念ながら、2番目のものは無料ではなくなりましたが、たとえば、代わりにHelveticaを使用できます。 システムにフォントをダウンロードしてインストールします。



スマートオブジェクトとベクターシェイプ





Photoshopでレイヤーの構造を調べると、ほとんどのオブジェクトがベクターシェイプとスマートオブジェクトであることがわかります 。 次の場合にスマートオブジェクトを使用することを強くお勧めします。







最初の項目は、ノブと波形スイッチに関連しています。 それらはいくつかの場所に現れ、それらのインスタンスは同一に見えます。 ( Vを押して) 移動ツールを選択し、MacでCmdまたはWindowsでCtrlを押しながら、波形スイッチをクリックします。 レイヤーパレットで、 シェイプと呼ばれるレイヤー強調表示されます。 プレビューをダブルクリックして、スマートオブジェクトを開きます。 波形がベクトルオブジェクトであることが明らかになります。







ベクトルオブジェクトの利点は、 損失なく可能なあらゆる方法で回転、スケーリング、および変更できることです。 そのため、たとえば、ピクセル密度の高い画面用の新しいバージョンのインターフェイスが必要な場合は、これらの画像をスケーリングするだけで済みます。 はい、JKnobManからペンをエクスポートする必要がありますが、すべてのペンは同一のスマートオブジェクトであるため、交換は非常に簡単です。



キーボードレイヤーグループを分析してみましょう。 内部には4オクターブがあります。 オクターブはスマートオブジェクトでもあり、1オクターブで行われたすべての変更は他のオクターブに影響します。 たとえば、 Octave 4を開いてパレットを確認します。







すべての黒のキーは1つのスマートオブジェクトを指し、白もキーを指します。 レイヤーパレットの黒いキーをダブルクリックします。 3つのベクトル形式で構成されることが明らかになります。







今、黒いキーの角を少し滑らかにしたいと想像してください。 コピーをリベットした場合、今すぐすべてをレーキする必要があります。 しかし、このアプローチでは、すべて1つのキーを編集することになります。



Photoshopを使用して、いくつかの原則に従います。







独自の仮想キーボードを作成するための便利なポイント:







別の小さなヒント:シンセのロゴには、文字Sが3回出現します。 このフォントの文字は不規則なテクスチャを持っているため、3つのまったく同じ文字Sは完全に不自然に見えます。 ロゴの見栄えを良くするには、マスク付きの追加レイヤーを使用します。これにより、複数の場所で同じ文字のテクスチャをわずかに変更できます。



グラフィックスのエクスポート





インターフェイスコンポーネントは、プラグインコードから参照するために、個別の.pngとしてエクスポートする必要があります。 手動でエクスポートするか、次の6つのファイルをダウンロードできます。







GUIの実装





いつものように、古き良き超便利な複製スクリプトを使用して合成プロジェクトをコピーします。 IPlugExamplesフォルダーから実行します。



cd ~/plugin-development/wdl-ol/IPlugExamples/





./duplicate.py Synthesis/ SpaceBass YourName







以前の投稿で開発を行っていない場合は、プロジェクトダウンロードして解凍し、 duplicate



スクリプトを実行duplicate



ます。



6つの画像すべてをプロジェクトリソースフォルダーにコピーし、不要になったknob_small.pngを削除します。



同じファイル名を使用しているため、 resource.hをわずかに変更するだけですKNOB_SMALL_ID



およびKNOB_SMALL_FN



削除しKNOB_SMALL_FN



。 ファイルヘッダーは次のようになります。



 // Unique IDs for each image resource. #define BG_ID 101 #define WHITE_KEY_ID 102 #define BLACK_KEY_ID 103 #define WAVEFORM_ID 104 #define KNOB_ID 105 #define FILTERMODE_ID 106 // Image resource locations for this plug. #define BG_FN "resources/img/bg.png" #define WHITE_KEY_FN "resources/img/whitekey.png" #define BLACK_KEY_FN "resources/img/blackkey.png" #define WAVEFORM_FN "resources/img/waveform.png" #define KNOB_FN "resources/img/knob.png" #define FILTERMODE_FN "resources/img/filtermode.png"
      
      







インターフェイスが少し大きくなりました:



 // GUI default dimensions #define GUI_WIDTH 571 #define GUI_HEIGHT 500
      
      







別のリソースファイルSpaceBass.rcを編集する必要があります。



 #include "resource.h" BG_ID PNG BG_FN WHITE_KEY_ID PNG WHITE_KEY_FN BLACK_KEY_ID PNG BLACK_KEY_FN WAVEFORM_ID PNG WAVEFORM_FN KNOB_ID PNG KNOB_FN FILTERMODE_ID PNG FILTERMODE_FN
      
      







それでは、 Oscillator



クラスを少し変更しましょう。 Oscillator::OscillatorMode



として外部からアクセスできるように、 enum OscillatorMode



をクラス内で移動します。 Filter



EnvelopeGenerator



でも同じことを行いましたが、ここでは「対称性のために」行います。

public



が最初になるようにpublic



セクションとprivate



セクションを並べ替えます。 そして、 enum OscillatorMode



をこのセクションの上に移動します。



 class Oscillator { public: enum OscillatorMode { OSCILLATOR_MODE_SINE = 0, OSCILLATOR_MODE_SAW, OSCILLATOR_MODE_SQUARE, OSCILLATOR_MODE_TRIANGLE, kNumOscillatorModes }; void setMode(OscillatorMode mode); void setFrequency(double frequency); void setSampleRate(double sampleRate); void generate(double* buffer, int nFrames); inline void setMuted(bool muted) { isMuted = muted; } double nextSample(); Oscillator() : mOscillatorMode(OSCILLATOR_MODE_SINE), mPI(2*acos(0.0)), twoPI(2 * mPI), isMuted(true), mFrequency(440.0), mPhase(0.0), mSampleRate(44100.0) { updateIncrement(); }; private: OscillatorMode mOscillatorMode; const double mPI; const double twoPI; bool isMuted; double mFrequency; double mPhase; double mSampleRate; double mPhaseIncrement; void updateIncrement(); };
      
      







次に、GUIコードに直接進みましょう。 SpaceBass.hから始めましょう。 private



関数をいくつか追加します。



 void CreateParams(); void CreateGraphics();
      
      







したがって、インターフェイスコードでコンストラクタを圧倒することはありません。 そこにいる間に、不要になった不要なdouble mFrequency



を削除しdouble mFrequency





enum EParams



EParamsの前にenum EParams



で定数を追加します。



 const double parameterStep = 0.001;
      
      







このパラメーターは、ユーザーがインターフェイスノブを回す精度を決定します。 各ペンで使用されるため、ハンドルのパラメーターを使用して個別の行に特定の値を書き込むのではなく、ここで1つの定数を作成することをお勧めします。

プラグインの新しいバージョンには、より多くのオプションがあります。 EParams



編集:



 enum EParams { // Oscillator Section: mOsc1Waveform = 0, mOsc1PitchMod, mOsc2Waveform, mOsc2PitchMod, mOscMix, // Filter Section: mFilterMode, mFilterCutoff, mFilterResonance, mFilterLfoAmount, mFilterEnvAmount, // LFO: mLFOWaveform, mLFOFrequency, // Volume Envelope: mVolumeEnvAttack, mVolumeEnvDecay, mVolumeEnvSustain, mVolumeEnvRelease, // Filter Envelope: mFilterEnvAttack, mFilterEnvDecay, mFilterEnvSustain, mFilterEnvRelease, kNumParams };
      
      







また、 ELayout



も、仮想キーボードの場所が変更されたためです。



 enum ELayout { kWidth = GUI_WIDTH, kHeight = GUI_HEIGHT, kKeybX = 62, kKeybY = 425 };
      
      







一箇所にすべてのパラメーター





InitDouble()



およびnew IKnobMultiControl



への呼び出しをスタンプする代わりに、GUI情報を格納するための特別なデータ構造を作成することをおnew IKnobMultiControl



します。

EParams



下に次のstruct



作成します。



 typedef struct { const char* name; const int x; const int y; const double defaultVal; const double minVal; const double maxVal; } parameterProperties_struct;
      
      







パラメータ名、プラグインウィンドウ内のコントロールの座標、およびデフォルト値/最小値/最大値(パラメータのタイプがdouble



)が保存されます。 スイッチの場合、 default/min/maxVal



は必要ありません。 静的型付けのため、これは余分なひねりになります。

以下では、パラメータデータを(ほぼ)保存するデータ構造を作成します。 パラメーターごとに、1つのparameterProperties_struct



が必要です。つまり、サイズkNumParams



配列が必要kNumParams







 const parameterProperties_struct parameterProperties[kNumParams] =
      
      







この行の下に実際の値を挿入します。 Filter Modeなどのenum



型のパラメーターについては、 default/min/maxVals



が初期化されていないことに注意してください。



 { {.name="Osc 1 Waveform", .x=30, .y=75}, {.name="Osc 1 Pitch Mod", .x=69, .y=61, .defaultVal=0.0, .minVal=0.0, .maxVal=1.0}, {.name="Osc 2 Waveform", .x=203, .y=75}, {.name="Osc 2 Pitch Mod", .x=242, .y=61, .defaultVal=0.0, .minVal=0.0, .maxVal=1.0}, {.name="Osc Mix", .x=130, .y=61, .defaultVal=0.5, .minVal=0.0, .maxVal=1.0}, {.name="Filter Mode", .x=30, .y=188}, {.name="Filter Cutoff", .x=69, .y=174, .defaultVal=0.99, .minVal=0.0, .maxVal=0.99}, {.name="Filter Resonance", .x=124, .y=174, .defaultVal=0.0, .minVal=0.0, .maxVal=1.0}, {.name="Filter LFO Amount", .x=179, .y=174, .defaultVal=0.0, .minVal=0.0, .maxVal=1.0}, {.name="Filter Envelope Amount", .x=234, .y=174, .defaultVal=0.0, .minVal=-1.0, .maxVal=1.0}, {.name="LFO Waveform", .x=30, .y=298}, {.name="LFO Frequency", .x=69, .y=284, .defaultVal=6.0, .minVal=0.01, .maxVal=30.0}, {.name="Volume Env Attack", .x=323, .y=61, .defaultVal=0.01, .minVal=0.01, .maxVal=10.0}, {.name="Volume Env Decay", .x=378, .y=61, .defaultVal=0.5, .minVal=0.01, .maxVal=15.0}, {.name="Volume Env Sustain", .x=433, .y=61, .defaultVal=0.1, .minVal=0.001, .maxVal=1.0}, {.name="Volume Env Release", .x=488, .y=61, .defaultVal=1.0, .minVal=0.01, .maxVal=15.0}, {.name="Filter Env Attack", .x=323, .y=174, .defaultVal=0.01, .minVal=0.01, .maxVal=10.0}, {.name="Filter Env Decay", .x=378, .y=174, .defaultVal=0.5, .minVal=0.01, .maxVal=15.0}, {.name="Filter Env Sustain", .x=433, .y=174, .defaultVal=0.1, .minVal=0.001, .maxVal=1.0}, {.name="Filter Env Release", .x=488, .y=174, .defaultVal=1.0, .minVal=0.01, .maxVal=15.0} };
      
      







大規模な仕掛け。 中括弧{}



を使用した同様の構文は、「 複合リテラル 」と呼ばれるC / C ++の比較的新しい手法で、基本的な考え方は、この方法で構造と配列を初期化できることです。 外部括弧はparameterProperties[]



配列を初期化しparameterProperties[]



;それらはコンマで区切られた複合リテラルのリストを含み、それぞれが1つのparameterProperties_struct



を初期化しparameterProperties_struct



。 最初のリテラルを例として使用して、これを見てみましょう。



 {.name="Osc 1 Waveform", .x=30, .y=75}
      
      







古い学校のアプローチはこれを書くことです:



 parameterProperties_struct* osc1Waveform_prop = parameterProperties[mOsc1Waveform]; osc1Waveform_prop->name = "Osc 1 Waveform"; osc1Waveform_prop->x = 30; osc1Waveform_prop->y = 75;
      
      







これは、各パラメーターに対して行う必要があります!

struct



構造の複合リテラルへの「クラシック」アプローチは次のとおりです。



 {"Osc 1 Waveform", 30, 75}
      
      







非常に簡潔ですが、エラーが発生しやすい。 struct



の上部に何かを追加したり、要素の順序を変更すると、問題が発生します。 指定するイニシャライザを使用することをお勧めしますが、さらに入力する必要があります。 この巨大なフレーズは、単に構文.membername=



を使用してstruct



要素にアクセスできることを意味します。 最終的な形式では、これはJSONまたはRubyのハッシュに少し似ています



 {.name="Osc 1 Waveform", .x=30, .y=75}
      
      







パラメータを作成する





CreateGraphics



CreateGraphics



2つのメンバーCreateParams



を追加しました。 これで、コンストラクタは非常にシンプルに見えます。



 SpaceBass::SpaceBass(IPlugInstanceInfo instanceInfo) : IPLUG_CTOR(kNumParams, kNumPrograms, instanceInfo), lastVirtualKeyboardNoteNumber(virtualKeyboardMinimumNoteNumber - 1) { TRACE; CreateParams(); CreateGraphics(); CreatePresets(); mMIDIReceiver.noteOn.Connect(this, &SpaceBass::onNoteOn); mMIDIReceiver.noteOff.Connect(this, &SpaceBass::onNoteOff); mEnvelopeGenerator.beganEnvelopeCycle.Connect(this, &SpaceBass::onBeganEnvelopeCycle); mEnvelopeGenerator.finishedEnvelopeCycle.Connect(this, &SpaceBass::onFinishedEnvelopeCycle); }
      
      







すべてが透明ですよね? ここでGetParam()



pGraphics



GetParam()



代わりに、すべてを取り出しました。

CreateParams



書きましょう!



 void SpaceBass::CreateParams() { for (int i = 0; i < kNumParams; i++) { IParam* param = GetParam(i); const parameterProperties_struct& properties = parameterProperties[i]; switch (i) { // Enum Parameters: case mOsc1Waveform: case mOsc2Waveform: param->InitEnum(properties.name, Oscillator::OSCILLATOR_MODE_SAW, Oscillator::kNumOscillatorModes); // For VST3: param->SetDisplayText(0, properties.name); break; case mLFOWaveform: param->InitEnum(properties.name, Oscillator::OSCILLATOR_MODE_TRIANGLE, Oscillator::kNumOscillatorModes); // For VST3: param->SetDisplayText(0, properties.name); break; case mFilterMode: param->InitEnum(properties.name, Filter::FILTER_MODE_LOWPASS, Filter::kNumFilterModes); break; // Double Parameters: default: param->InitDouble(properties.name, properties.defaultVal, properties.minVal, properties.maxVal, parameterStep); break; } }
      
      







すべてのパラメーターを反復処理します。 最初に、作成したデータ構造から必要なプロパティを取得し、次にswitch



使用して異なるenum



switch



初期化します。 LFOの場合、三角波形がデフォルトで選択されます。これは、最も頻繁に使用されるからです。 16個すべてのペンに対して1つの式しか使用しないことに注意してください!

一部のペンでは、非線形動作を指定する方が適切です。 たとえば、オクターブ単位の音とその周波数の数学的関係により、カットオフ周波数を対数的に変更することをお勧めします。 SetShape



の最後に適切なSetShape



呼び出しを追加します。



  GetParam(mFilterCutoff)->SetShape(2); GetParam(mVolumeEnvAttack)->SetShape(3); GetParam(mFilterEnvAttack)->SetShape(3); GetParam(mVolumeEnvDecay)->SetShape(3); GetParam(mFilterEnvDecay)->SetShape(3); GetParam(mVolumeEnvSustain)->SetShape(2); GetParam(mFilterEnvSustain)->SetShape(2); GetParam(mVolumeEnvRelease)->SetShape(3); GetParam(mFilterEnvRelease)->SetShape(3);
      
      







最後に、各パラメーターについて、 OnParamChange



1回呼び出す必要があります。これにより、プラグインが最初に呼び出されたときに、内部変数に正しい値が設定されます。



  for (int i = 0; i < kNumParams; i++) { OnParamChange(i); } }
      
      







内部パラメーターが完成したら、それらのコントロールを追加します。 これはCreateGraphics



の本体で行われCreateGraphics



。 まず、背景画像を追加します。



 void SpaceBass::CreateGraphics() { IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight); pGraphics->AttachBackground(BG_ID, BG_FN);
      
      







次にキーボード:



  IBitmap whiteKeyImage = pGraphics->LoadIBitmap(WHITE_KEY_ID, WHITE_KEY_FN, 6); IBitmap blackKeyImage = pGraphics->LoadIBitmap(BLACK_KEY_ID, BLACK_KEY_FN); // C# D# F# G# A# int keyCoordinates[12] = { 0, 10, 17, 30, 35, 52, 61, 68, 79, 85, 97, 102 }; mVirtualKeyboard = new IKeyboardControl(this, kKeybX, kKeybY, virtualKeyboardMinimumNoteNumber, /* octaves: */ 4, &whiteKeyImage, &blackKeyImage, keyCoordinates); pGraphics->AttachControl(mVirtualKeyboard);
      
      







キーボードで変更された唯一のものは、オクターブの数(現在は4 keyCoordinates



)とkeyCoordinates



です。 新しいキーは幅が広いため、押されたキーが適切な座標で表示されるように、それらの間のステップを調整する必要があります。

次に、ノブとスイッチの画像をアップロードします。



  IBitmap waveformBitmap = pGraphics->LoadIBitmap(WAVEFORM_ID, WAVEFORM_FN, 4); IBitmap filterModeBitmap = pGraphics->LoadIBitmap(FILTERMODE_ID, FILTERMODE_FN, 3); IBitmap knobBitmap = pGraphics->LoadIBitmap(KNOB_ID, KNOB_FN, 64);
      
      







いつものように、ここでは.pngをロードし、それぞれにいくつのフレームがあるかをシステムに伝えます。

主な部分は、すべてのパラメーターを反復処理して適切なコントロールを作成することです。



  for (int i = 0; i < kNumParams; i++) { const parameterProperties_struct& properties = parameterProperties[i]; IControl* control; IBitmap* graphic; switch (i) { // Switches: case mOsc1Waveform: case mOsc2Waveform: case mLFOWaveform: graphic = &waveformBitmap; control = new ISwitchControl(this, properties.x, properties.y, i, graphic); break; case mFilterMode: graphic = &filterModeBitmap; control = new ISwitchControl(this, properties.x, properties.y, i, graphic); break; // Knobs: default: graphic = &knobBitmap; control = new IKnobMultiControl(this, properties.x, properties.y, i, graphic); break; } pGraphics->AttachControl(control); }
      
      







ここでは、まず現在のパラメーターのプロパティを見つけてから、特定の場合にswitch



を使用します。 また、ここでは、 waveform.pngの代わりに、 filterMode.pngが mFilterModeパラメーターに使用されます。 繰り返しますが、 デフォルトはハンドルのコードです。ハンドルはほとんどの場合コントロールの中にあるためです。

AttachGraphics



を呼び出して、関数本体を完成させます。



  AttachGraphics(pGraphics); }
      
      







最後に、 OnParamChange()



からswitch



を削除します 。 次回は書き直します。



できた!





プラグインを起動して、新しいインターフェースをすべてお楽しみください! 確かに、音はまだ機能していません-次回はそれを行います。 シンセサイザーをポリフォニックにする必要があります!



コードはここからダウンロードできます

元の投稿



All Articles