パート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がカットオフ周波数にどのように影響するかを制御します。
この段階でも、見た目は良いです。 進行状況を評価しましょう。 すでに行ったことは次のとおりです。
- MIDIレシーバー
- 仮想キーボード
- 発振器
- ボリュームエンベロープ
- マルチモードフィルター
- フィルターエンベロープ
- LFO
そして、これから行われることのリストです。
- 新しいデザイン
- ポリフォニー
- オシレーターサウンドのミキシング(簡単です)
- オシレータートーン変調
あなたが見ることができるように、ほとんどはすでに行われています!
計画は次のとおりです。まず、Photoshopでグラフィックデザインがどのように作成されるかを見てください。 次に、主な部分はポリフォニーであり、プラグインの構造をわずかに変更します。 最後に、トーン変調を追加します。
これらのタスクを3つの投稿に分割します。 これはデザインに関するものであり、他の2つはポリフォニーに関するものです。これはかなり面倒な部分だからです。
新しいデザイン
ハンドルは、わずかに変更されたボスのようなレスリーサンフォードのノブです 。 私はそれを小さくし、ノッチを広げ、影をわずかに変えました。 変更されたバージョンは、ここからダウンロードできます 。 このガイドを使って新しいキーボードを作りました。
Photoshopを入手する場所と、Photoshopですべてを描画する方法については詳しく説明しませんが、レイヤーの構造と作業の一般的な原則について説明します。 アーカイブされたTIFをダウンロードして解凍し、PhotoshopまたはGIMPで開きます。 構造を掘り下げて、どのように作成されたかを理解できるように、構造をそのままにしておこうとしました。 テキストを変更する場合は、ロゴ用のTusjと署名用のAvenir 85 Heavyの 2つのフォントが必要になります。 残念ながら、2番目のものは無料ではなくなりましたが、たとえば、代わりにHelveticaを使用できます。 システムにフォントをダウンロードしてインストールします。
スマートオブジェクトとベクターシェイプ
Photoshopでレイヤーの構造を調べると、ほとんどのオブジェクトがベクターシェイプとスマートオブジェクトであることがわかります 。 次の場合にスマートオブジェクトを使用することを強くお勧めします。
- 複数の場所で同じコンポーネントが必要な場合
- オブジェクトのグループにエフェクトを適用する場合(およびCS6よりも新しいバージョンのPhotoshopを使用する場合)
- 情報を失うことなくオブジェクトを回転させたいが、そのオブジェクトを回転させて戻したい
最初の項目は、ノブと波形スイッチに関連しています。 それらはいくつかの場所に現れ、それらのインスタンスは同一に見えます。 ( Vを押して) 移動ツールを選択し、MacでCmdまたはWindowsでCtrlを押しながら、波形スイッチをクリックします。 レイヤーパレットで、 シェイプと呼ばれるレイヤーが強調表示されます。 プレビューをダブルクリックして、スマートオブジェクトを開きます。 波形がベクトルオブジェクトであることが明らかになります。
ベクトルオブジェクトの利点は、 損失なく可能なあらゆる方法で回転、スケーリング、および変更できることです。 そのため、たとえば、ピクセル密度の高い画面用の新しいバージョンのインターフェイスが必要な場合は、これらの画像をスケーリングするだけで済みます。 はい、JKnobManからペンをエクスポートする必要がありますが、すべてのペンは同一のスマートオブジェクトであるため、交換は非常に簡単です。
キーボードレイヤーグループを分析してみましょう。 内部には4オクターブがあります。 オクターブはスマートオブジェクトでもあり、1オクターブで行われたすべての変更は他のオクターブに影響します。 たとえば、 Octave 4を開いてパレットを確認します。
すべての黒のキーは1つのスマートオブジェクトを指し、白もキーを指します。 レイヤーパレットの黒いキーをダブルクリックします。 3つのベクトル形式で構成されることが明らかになります。
今、黒いキーの角を少し滑らかにしたいと想像してください。 コピーをリベットした場合、今すぐすべてをレーキする必要があります。 しかし、このアプローチでは、すべて1つのキーを編集することになります。
Photoshopを使用して、いくつかの原則に従います。
- 可能な限り、非破壊的に作業します。 スマートフィルターを使用し、ラスターオブジェクトをスケーリングせず、レイヤーのラスター化を回避します。 何かをラスタライズすると、オブジェクトの元の構造に関するすべての情報が失われます。 それでもラスタライズする必要がある場合は、必要に応じて最初にベクターフォームのバックアップコピーを作成し、そこに戻ります。
- スマートオブジェクトを使用したコピーは避けてください。 何かをコピーする前に、自問してください。コピーを変更しますか? そうでない場合は、ダムコピーではなく、スマートオブジェクトが必要です。
- たとえば、 ペンツールを使用して、角丸長方形などのオブジェクトを編集します。 このツールを使用すると、曲線などの場所を保存できます。
独自の仮想キーボードを作成するための便利なポイント:
- 押されていないキーを描くとき、押されたとき、それらは通常より暗く見えるはずであることを覚えておいてください。 黒のキーが押されていない状態で文字通り黒の場合、「より多く押された」と表現することは困難です。
- 白いキーはすべて黒いキーと同じ幅です。
- 押されたフォームDoおよびFa(CおよびF)では、MiおよびC(EおよびB)と同じように-これは同じ画像です。 最初のシャープと2番目のフラットのオフセットが同じであることを確認してください。同じでない場合、レイヤーとギャップが表示されます。
- キーストロークを描くときは、半透明のオーバーレイを使用しないでください。 マスクレイヤーを使用して、押された各キーが画像領域のみを変更するようにします。
IKeyboardControl
を使用した影や外部グローなどの効果は、正しく表示できません。 -
IKeyboardControl
の最高キーはUpです。 そのため、上部に黒いキーのない白いキーが必要です。 非破壊的に作業する場合、それは作業になりません。
別の小さなヒント:シンセのロゴには、文字Sが3回出現します。 このフォントの文字は不規則なテクスチャを持っているため、3つのまったく同じ文字Sは完全に不自然に見えます。 ロゴの見栄えを良くするには、マスク付きの追加レイヤーを使用します。これにより、複数の場所で同じ文字のテクスチャをわずかに変更できます。
グラフィックスのエクスポート
インターフェイスコンポーネントは、プラグインコードから参照するために、個別の.pngとしてエクスポートする必要があります。 手動でエクスポートするか、次の6つのファイルをダウンロードできます。
- 背景画像には、静的な部分と、すべてのキーが押されていない仮想キーボードが含まれています
- 波形およびフィルターモードスイッチ
- ペン
-
IKeyboardControl
6IKeyboardControl
押された白いキー - そして黒
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
を削除します 。 次回は書き直します。
できた!
プラグインを起動して、新しいインターフェースをすべてお楽しみください! 確かに、音はまだ機能していません-次回はそれを行います。 シンセサイザーをポリフォニックにする必要があります!
コードはここからダウンロードできます 。
元の投稿 。