プログラミングと音楽:C#WPFでVSTiシンセサイザーを理解して記述します。 パート1

音楽のクリエイティビティに携わっている私は、コンピューターでアレンジと記録を作成します 。あらゆる種類のVSTプラグインとツールを使用します 。 私は認めることを恥ずかしく思います-シンセサイザーで音がどのように合成されるか理解できませんでした。 プログラミングにより、自分でシンセサイザーを作成し、サウンドを作成するプロセスを「実行」できました。







VSTプラグイン/ツールの作成方法を段階的に説明する複数の記事を計画しています:オシレーター、周波数フィルター、さまざまなエフェクト、モジュレーションパラメーターのプログラミング。 練習に重点が置かれ 、これがどのように機能するかを簡単な言語でプログラマーに説明します。 理論(厳しい結論と証拠)をバイパスします(当然、記事や書籍へのリンクがあります)。







通常、プラグインはC ++(クロスプラットフォーム、アルゴリズムを効率的に実装する機能)で記述されていますが、私はより適切な言語を選択することにしました-C#; プログラミングの技術的な詳細ではなく、シンセサイザー自体とアルゴリズムの学習に焦点を当てます。 美しいインターフェイスを作成するために、WPFを使用しました。 .NETアーキテクチャを使用する機能により、 VSTラッパーライブラリが有効になりました NET







以下は面白いシンセサイザーを入手した私のシンプルなシンセサイザーのレビュービデオです。









準備ができていれば、難しい道があります。猫へようこそ。









記事のサイクル



  1. C#WPFでVSTiシンセサイザーを理解して記述します
  2. ADSR信号エンベロープ
  3. バタワース周波数フィルター
  4. 遅延、歪み、およびパラメーター変調


目次



  1. 音合成の神秘的な世界
  2. デジタルサウンド
  3. VST SDK
  4. WDL-OLおよびJUCE
  5. VST .NET
  6. VST .NET用のアドオン
  7. WPF UI
  8. UIストリーム
  9. Syntage Syntax Architectureの概要
  10. プラグイン/ツールを作成するプロジェクトを設定します
  11. コードのデバッグ
  12. シンプルなオシレーターを書く
  13. 参照資料




音合成の神秘的な世界



私は本当に音楽が好きで、さまざまなスタイルを聴き、さまざまな楽器を演奏し、そしてもちろん、アレンジを作曲して記録します。 録音プログラムでシンセサイザーエミュレーターを使用し始めたとき(そして今)、私は常に多くのプリセットを調べて適切なサウンドを探しました。







1つのシンセサイザーのプリセットを調べると、幼少期の電子シンセサイザーの「期待される」サウンド(漫画Flying Shipの音楽)と、ドラム、サウンド、ノイズ、音声の模倣の両方を見つけることができます。 そして、これらはすべて同じパラメーターノブを備えた1つのシンセサイザーによって行われます。 私は理解していましたが、これは常に驚きました。すべての音は、すべてのノブの特定の設定の本質です。







最近、私は最終的に音がどのように作成されるか(より正確には合成される )、どのようにしてノブをひねる必要があるのか​​、信号がエフェクトからどのように変更されるのかを視覚的におよび耳で把握することにしました。 そしてもちろん、自分で好きなスタイルをコピーするために、自分で音を「巻き上げる」ことを学ぶ(少なくとも基本を理解する)こと。 私は1つの引用に従うことにしました:







「教えてください-そして、私は忘れて、見せてください-そして、私はそれを覚えて、それをさせてください-そして、私は理解します。」

孔子



もちろん、すべてを一列に並べる必要はありません(自転車はどこにあるのでしょうか?)、しかし今、私は知識を得て、最も重要なこと-それをあなたと共有します。







目的 :理論を掘り下げることなく、実際にプログラミングの観点からプロセスの説明を強調するシンプルなシンセサイザーを作成します。







シンセサイザーでは:









いくつかの記事ですべてのコンポーネントを検討する予定です。 このセクションでは、発振器のプログラミングについて説明します。







C#でプログラムします。 UIは、WPFまたはWindowsフォームのいずれかで作成できます。または、グラフィカルシェルをまったく使用せずに作成できます。 さらに、WPFの選択-エンコードに十分な速さの美しいグラフィック、マイナス-Windowsのみ。 他のOSの所有者-落胆しないでください、結局のところ、目標はシンセサイザーを理解することです(そして美しいUIを接着することではありません)、さらに、私が実証するコードはすぐにC ++に転送できます







VST SDKWDL-OLおよびJUCEの章で、 VSTの概念、その内部実装について説明します。 深刻なプラグインの開発に適したアドオンライブラリについて。 VST .NETの章では、このライブラリ、その短所、アドイン、UIプログラミングについて説明します。







シンセサイザーロジックのプログラミングは、「 シンプルオシレーターの作成」の章から始まります。 VSTプラグインの作成の技術的側面に興味がない場合は、実際には合成について(そして何もコーディングしないで)読みたいだけです。この章をすぐに歓迎します。







私が書いたシンセサイザーのソースコードはGitHubで入手できます









デジタルサウンド



実際、私たちの究極の目標は、コンピューターでサウンドを作成することです。 「音の理論」のハブに関する記事を(少なくとも流に)読んでください 。コンピュータ上の音の表現、概念、用語に関する基本的な知識が記載されています。







コンピューター上の非圧縮形式のサウンドファイルは、サンプルの配列です。 プラグインは最終的に入力でシードの配列を受け入れて処理します(精度に応じて、浮動小数点数または倍精度数になります。整数を使用することもできます)。 なぜ単一のサンプルではなく配列と言ったのですか? これにより、 音が全体として処理されることを強調したかったのです。 イコライゼーションを行う必要がある場合、他のサンプルに関する情報がなければ1つのサンプルだけで操作することはできません。







もちろん、あなたが処理しているものを知ることは重要ではないタスクがあります-それらは特定のサンプルを考慮しています。 たとえば、タスクはボリュームレベルを2倍に増やすことです。 各サンプルを個別に処理することができ、残りについて知る必要はありません。







サンプルを-1〜1の浮動小数点数として処理します。通常、「サンプル値」を言わないために、「振幅」と言うことができます。 一部のサンプルの振幅が1より大きいか-1より小さい場合、 クリッピングが発生するため、これを回避する必要があります。









VST SDK



VST(Virtual Studio Technology)は、サウンド処理プログラム用のプラグインを作成できるテクノロジーです。 現在、シンセサイザー、エフェクト、サウンドアナライザー、バーチャルインストゥルメントなど、さまざまな問題を解決する多数のプラグインがあります。







VSTプラグインを作成するために、Steinberg(一部の人はCubaseを知っています)は、C ++で記述されたVST SDKをリリースしました。 テクノロジー(または、彼らが言う「プラグイン形式」)VSTに加えて、RTS、AAX、 それらの何千もの他のものがあります 。 人気が高く、多数のプラグインとツールがあるため、VSTを選択しました(ただし、最も有名なプラグインはさまざまな形式で提供されています)。







現在、VST SDK 3.6.6の現在のバージョンですが、多くは引き続きバージョン2.4を使用しています。 歴史的に、バージョン2.4のサポートなしでDAWを見つけることは困難であり、すべてがバージョン3.0以降をサポートするわけではありません。







VST SDKは、 公式Webサイトからダウンロードできます。

将来的には、VST 2.4のラッパーであるVST.NETライブラリを使用します。







プラグインを真剣に開発するつもりで、SDKの最新バージョンを使用したい場合は、ドキュメントと例を個別に学習できます(すべては公式サイトからダウンロードできます)。







次に、プラグインとそのDAWとの相互作用を一般的に理解するために、VST SDK 2.4の原理を簡単に説明します。







Windows VSTでは、プラグインバージョン2.4は動的DLLライブラリとして表示されます。

DLLをロードするホストプログラムに名前を付けます。 通常、他のプログラムとは別にプラグインを起動するための音楽編集プログラム( DAW )または単純なシェルです(たとえば、非常に多くの場合、.dllを使用する仮想楽器では、プラグインには.exeファイルが付属しており、プラグインをピアノ、シンセサイザーとして個別に読み込みます)。







ダウンロードしたVST SDKのフォルダー内のソース「VST3 SDK \ pluginterfaces \ vst2.x」で、追加の関数、列挙、および構造を見つけることができます。







ライブラリは、次のシグネチャで関数をエクスポートする必要があります。







EXPORT void* VSTPluginMain(audioMasterCallback hostCallback)
      
      





この関数はコールバックポインターを使用して、プラグインが必要な情報をホストから受信できるようにします。







 VstIntPtr (VSTCALLBACK *audioMasterCallback) (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt)
      
      





すべてが十分に「低い」レベルで行われます-ホストがそれから何を望んでいるかを理解するために、opcodeパラメーターを通してコマンド番号を渡す必要があります。 ハードコアCエンコーダーをリストするすべてのオペコードは、AudioMasterOpcodesX列挙にあります。 残りのパラメーターは同様の方法で使用されます。







VSTPluginMainは、本質的にはプラグインであるAEffect構造体へのポインターを返す必要があります。これには、プラグインに関する情報と、ホストが呼び出す関数へのポインターが含まれています。







AEffect構造の主なフィールド:









プラグインを使用する場合、ユーザーが設定されたすべてのノブとスイッチを保存できると非常に便利です。 さらに自動化するためにさらにクールに! たとえば、 有名な立ち上がり効果を作成したい場合-イコライザーのカットオフパラメーター(カットオフ周波数)を経時的に変更する必要があります。







ホストがプラグインのパラメーターを管理するために、AEffectには対応する機能があります:ホストはパラメーターの総数を要求し、特定のパラメーターの値を見つけて設定し、パラメーターの名前とその説明を見つけ、表示された値を取得できます。







ホストは、プラグインのパラメーターのロジックを気にしません。 ホストのタスクは、パラメーターを保存、ロード、自動化することです。 ホストがパラメーターを0から1の浮動小数点数として認識し、プラグインに必要に応じて解釈させるのは非常に便利です(これはほとんどのDAWが非公式に行ったことです)。







プリセット(VST SDK-プログラムに関して)は、すべてのプラグインパラメーターの特定の値のコレクションです。 ホストは、パラメータと同様に、プリセット番号を変更/切り替え/選択し、名前を確認できます。 銀行-プリセットのコレクション。 バンクは論理的にDAWにのみ存在し、VST SDKにはプリセットとプログラムのみがあります。







AEffect構造の概念を理解したら、簡単なDLLプラグインをスケッチしてコンパイルできます。

そして、さらに高いレベルに進みます。









WDL-OLおよびJUCE



ベアVST SDKでの開発が悪いのはなぜですか?









WDL-OLが登場します。 これは、クロスプラットフォームプラグインを作成するためのC ++ライブラリです。 サポートされている形式は、VST、VST3、Audiounit、RTAS、AAXです。 ライブラリの利便性は、(プロジェクトが正しく設定されている場合)1つのコードを記述し、コンパイルするときにさまざまな形式でプラグインを取得できることです。







WDL-OLの使用方法は、 Martin Finkeのブログ「Music&Programming」詳しく説明されています。 ロシア語へのhabr記事の翻訳もあります。







WDL-OLは、VST SDKの開発ネガの最初の3つのポイントを少なくとも解決します。 必要なのは、プロジェクト(ブログの最初の記事)を正しく構成し、IPlugクラスを継承することです。







 class MySuperPuperPlugin : public IPlug { public: explicit MyFirstPlugin(IPlugItanceInfo instanceInfo); virtual ~MyFirstPlugin() override; void ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames) override; };
      
      





これで、明確な良心をもって、プラグインの「コア」であるProcessDoubleReplacing関数を実装できます。 IPlugクラスは、すべての心配事を処理しました。 勉強すれば、それが(VST形式で)AEffect構造のラッパーであることをすぐに理解できます。 ホストコールバックとホスト関数は、わかりやすい名前と適切なパラメーターリストを持つ便利な仮想関数に変わりました。







WDL-OLには既にUIを作成するためのツールがあります。 しかし、私にとっては、これはすべて非常に苦労して行われます。UIはコードで組み立てられ、すべてのリソースは.rcファイルなどに記述されなければなりません。







WDL-OLの他に、 JUCEライブラリについても学びました。 JUCEはWDL-OLに似ており、VST SDKでの開発の主張されているすべての欠点を解決します。 とりわけ、既にUIエディターとオーディオデータを操作するためのクラスの束があります。 個人的には使用していませんので、少なくともwikiでそれについて読むことをお勧めします







本格的なプラグインを作成する場合は、WDL-OLまたはJUCEライブラリの使用を真剣に考えます。 彼らはあなたのために全体のルーチンを行いますが、あなたはまだ効果的なアルゴリズムとクロスプラットフォームを実装するためのC ++言語のすべての力を持っています-これは多数のDAWの世界では重要ではありません。









VST .NET



WDL-OLとJUCEが私を喜ばせなかったのはなぜですか?







  1. 私の仕事は、シンセサイザーのプログラミング方法、オーディオ処理、エフェクトを理解することであり、最大数のフォーマットとプラットフォーム用にプラグインを組み立てる方法ではありません。 ここでは「技術的なプログラミング」が背景にフェードインしています(もちろん、これは悪いコードを書いてOOPを使用しない理由ではありません)。
  2. 私はC#に甘やかされています。 繰り返しになりますが、この言語は、同じC ++とは異なり、いくつかの技術的な点について考える必要がありません。
  3. 視覚的な機能の点で、WPFテクノロジが好きです。


ライブラリページはvstnet.codeplex.comで 、ソースコード、バイナリ、ドキュメントがあります。 私が理解しているように、図書館は進行中です ほぼ終了し、得点 凍結(まれにしか使用されない一部の機能は実装されていません。数年はリポジトリの変更はありません)。







ライブラリは、3つの主要なアセンブリで構成されています。







  1. Jacobi.Vst.Core.dll-ホストとプラグインの動作、オーディオの補助クラス、イベント、MIDIを決定するインターフェイスが含まれています。 ほとんどは、VST SDKのネイティブ構造、定義、および列挙のラッパーです。
  2. Jacobi.Vst.Framework.dll-Jacobi.Vst.Coreからインターフェースを実装するプラグインの基本クラスが含まれており、プラグインの開発を加速し、すべてをゼロから作成することはできません。 高レベルのホストとプラグインの相互作用のためのクラス、パラメーターとプログラムのさまざまなマネージャー、MIDIメッセージはUIで動作します。
  3. Jacobi.Vst.Interop.dll-VST SDK上のマネージC ++ラッパー。ホストをロードされた.NETアセンブリ(プラグイン)に接続できます。


ホストが単純な動的DLLを期待している場合、どのように.NETを作成できますか? 方法は次のとおりです。実際、ホストはアセンブリをロードせず、コンパイル済みのJacobi.Vst.Interop DLLをロードします。Jacobi.Vst.InteropDLLはすでに.NET内でプラグインをロードしています。







次のトリックを使用します。プラグインを開発し、出力で.NETアセンブリMyPlugin.dllを取得するとしましょう。 ホストにMyPlugin.dllの代わりにJacobi.Vst.Interop.dllをロードさせる必要があります。そうすると、プラグインがロードされます。 問題は、Jacobi.Vst.Interop.dllがlibのロード元をどのように知るかです。 多くの解決策があります。 開発者は、libラッパーにlibと同じ名前を付けるオプションを選択し、.NETアセンブリを「my_name.vstdll」として検索します。







それはすべて次のように動作します







  1. MyPlugin.dllをコンパイルして取得しました
  2. MyPlugin.dllの名前をMyPlugin.vstdllに変更します
  3. 近くのJacobi.Vst.Interop.dllをコピーします
  4. Jacobi.Vst.Interop.dllの名前をMyPlugin.dllに変更します
  5. これで、ホストはMyPlugin.dll(つまりJacobi.Vst.Interopラッパー)をロードし、彼女は自分の名前が「MyPlugin」であることを知って、アセンブリMyPlugin.vstdllをロードします。


libをロードする場合、IVstPluginCommandStubインターフェイスを実装するクラスが含まれている必要があります。







 public interface IVstPluginCommandStub : IVstPluginCommands24 { VstPluginInfo GetPluginInfo(IVstHostCommandStub hostCmdStub); Configuration PluginConfiguration { get; set; } }
      
      





VstPluginInfoには、プラグインに関する基本情報(バージョン、プラグインの一意のID、パラメーターとプログラムの数、処理されたチャネルの数)が含まれています。 呼び出し側のJacobi.Vst.InteropラッパーにはPluginConfigurationが必要です。







次に、IVstPluginCommandStubは、ホストによって呼び出されるメソッドを含むIVstPluginCommands24インターフェイスを実装します。これには、サンプルの配列(バッファー)の処理、パラメーター、プログラム(プリセット)、MIDIメッセージなどの処理が含まれます。







Jacobi.Vst.Frameworkには、IVstPluginCommandStubを実装する既製の便利なクラスStdPluginCommandStubが含まれています。 必要なことは、StdPluginCommandStubから継承し、CreatePluginInstance()メソッドを実装することだけです。このメソッドは、IVstPluginを実装するプラグインクラスのオブジェクト(インスタンス)を返します。







 public class PluginCommandStub : StdPluginCommandStub { protected override IVstPlugin CreatePluginInstance() { return new MyPluginController(); } }
      
      





繰り返しますが、既製の便利なVstPluginWithInterfaceManagerBaseクラスがあります。







 public abstract class VstPluginWithInterfaceManagerBase : PluginInterfaceManagerBase, IVstPlugin, IExtensible, IDisposable { protected VstPluginWithInterfaceManagerBase(string name, VstProductInfo productInfo, VstPluginCategory category, VstPluginCapabilities capabilities, int initialDelay, int pluginID); public VstPluginCapabilities Capabilities { get; } public VstPluginCategory Category { get; } public IVstHost Host { get; } public int InitialDelay { get; } public string Name { get; } public int PluginID { get; } public VstProductInfo ProductInfo { get; } public event EventHandler Opened; public virtual void Open(IVstHost host); public virtual void Resume(); public virtual void Suspend(); protected override void Dispose(bool disposing); protected virtual void OnOpened(); }
      
      





ライブラリのソースコードを見ると、オーディオ、パラメーター、MIDIなどを操作するためのプラグインのコンポーネントを記述するインターフェイスが表示されます。 :







 IVstPluginAudioProcessor IVstPluginParameters IVstPluginPrograms IVstHostAutomation IVstMidiProcessor
      
      





VstPluginWithInterfaceManagerBaseクラスには、これらのインターフェイスを返す仮想メソッドが含まれています。







 protected virtual IVstPluginAudioPrecisionProcessor CreateAudioPrecisionProcessor(IVstPluginAudioPrecisionProcessor instance); protected virtual IVstPluginAudioProcessor CreateAudioProcessor(IVstPluginAudioProcessor instance); protected virtual IVstPluginBypass CreateBypass(IVstPluginBypass instance); protected virtual IVstPluginConnections CreateConnections(IVstPluginConnections instance); protected virtual IVstPluginEditor CreateEditor(IVstPluginEditor instance); protected virtual IVstMidiProcessor CreateMidiProcessor(IVstMidiProcessor instance); protected virtual IVstPluginMidiPrograms CreateMidiPrograms(IVstPluginMidiPrograms instance); protected virtual IVstPluginMidiSource CreateMidiSource(IVstPluginMidiSource instance); protected virtual IVstPluginParameters CreateParameters(IVstPluginParameters instance); protected virtual IVstPluginPersistence CreatePersistence(IVstPluginPersistence instance); protected virtual IVstPluginProcess CreateProcess(IVstPluginProcess instance); protected virtual IVstPluginPrograms CreatePrograms(IVstPluginPrograms instance);
      
      





これらのメソッドは、カスタムコンポーネントクラスにロジックを実装するためにオーバーロードする必要があります。 たとえば、サンプルを処理する場合、IVstPluginAudioProcessorを実装するクラスを作成し、CreateAudioProcessorメソッドで返す必要があります。







 public class MyPlugin : VstPluginWithInterfaceManagerBase { ... protected override IVstPluginAudioProcessor CreateAudioProcessor(IVstPluginAudioProcessor instance) { return new MyAudioProcessor(); } ... } ... public class MyAudioProcessor : VstPluginAudioProcessorBase //      { public override void Process(VstAudioBuffer[] inChannels, VstAudioBuffer[] outChannels) { //   } }
      
      





さまざまな既製のコンポーネントクラスを使用して、プラグインのロジックのプログラミングに集中できます。 ただし、Jacobi.Vst.Coreからのインターフェイスのみに基づいて、必要に応じて自分ですべてを実装することを気にする人はいません。







すでにコーディングしている人のために-ボリュームを6 dB下げるプラグインの例を提供します(このためには、サンプルに0.5を掛ける必要があります- サウンドに関する記事を読んでください )。







シンプルなプラグインの例
 using Jacobi.Vst.Core; using Jacobi.Vst.Framework; using Jacobi.Vst.Framework.Plugin; namespace Plugin { public class PluginCommandStub : StdPluginCommandStub { protected override IVstPlugin CreatePluginInstance() { return new MyPlugin(); } } public class MyPlugin : VstPluginWithInterfaceManagerBase { public MyPlugin() : base( "MyPlugin", new VstProductInfo("MyPlugin", "My Company", 1000), VstPluginCategory.Effect, VstPluginCapabilities.None, 0, new FourCharacterCode("TEST").ToInt32()) { } protected override IVstPluginAudioProcessor CreateAudioProcessor(IVstPluginAudioProcessor instance) { return new AudioProcessor(); } } public class AudioProcessor : VstPluginAudioProcessorBase { public AudioProcessor() : base(2, 2, 0) //     { } public override void Process(VstAudioBuffer[] inChannels, VstAudioBuffer[] outChannels) { for (int i = 0; i < inChannels.Length; ++i) { var inChannel = inChannels[i]; var outChannel = outChannels[i]; for (int j = 0; j < inChannel.SampleCount; ++j) { outChannel[j] = 0.5f * inChannel[j]; } } } } }
      
      







VST .NET用のアドオン



シンセをプログラミングしているときに、Jacobi.Vst.Frameworkのクラスを使用すると問題が発生しました。 主な問題は、パラメーターの使用とその自動化でした。







まず、値変更イベントの実装が好きではありませんでした。 次に、FL StudioとCubaseでプラグインをテストするときにバグがありました。 FL Studioは、VST SDKの特別な関数をeffGetParameterPropertiesオペコードとともに使用することなく、すべてのパラメーターを0〜1の浮動小数点数として扱います(関数はパラメーターに関する追加情報を取得するためにプラグインから呼び出されます)。 WDL-OLでは、実装は次のメモでコメントアウトされています。







effGetParameterPropertiesを実装してパラメーターをグループ化できますが、それをサポートするホストが見つかりません



もちろん、この関数はCubaseで呼び出されます(Cubaseは、VST SDKをリリースしたSteinbergの製品です)。







VST .NETでは、このコールバックはGetParameterProperties関数として実装され、VstParameterPropertiesクラスのオブジェクトを返します。 とにかく、Cubaseは私のパラメーターを誤って認識し、自動化しました。







最初に、ライブラリ自体に変更を加え、作成者に書面でソースをリポジトリに配置する許可を与えるか、GitHubでリポジトリを自分で作成しました。 しかし、まだ明確な答えが得られなかったため、 Syntage.Framework.dllにアドオンを追加することにしました







さらに、アドインは、WPFを使用する場合にUIを操作するための便利なクラスを実装します。







シンセサイザーのソースコードをダウンロードしてコンパイルします。







コードのコンパイル
  1. リポジトリのクローン/ダウンロード。
  2. デバッグでVisual Studioのソリューションを構築します。
  3. スタジオからシンセを開始するには、SimplyHostプロジェクトを使用する必要があります。
  4. プラグインファイルと依存ライブラリは「out \ vst \」フォルダーにあります。








アドインを使用するためのルールは簡単です。StdPluginCommandStubの代わりにSyntagePluginCommandStubを使用し、SyntagePluginからプラグインを継承します。









WPF UI



VSTプラグインには、グラフィカルインターフェイスは必要ありません。 UIのない​​多くのプラグインを見ました(そのうちの1つはmdaです )。 ほとんどのDAW(少なくともCubaseとFL Studio)は、生成するUIからパラメーターを制御する機能を提供します。













FL Studioのシンセサイザー用に自動生成されたUI







プラグインをUIで使用するには、まず、IVstPluginEditorを実装するクラスが必要です。 次に、プラグインクラス(SyntagePluginの子孫)のオーバーロードされたCreateEditor関数でそのインスタンスを返す必要があります。







WPFウィンドウを直接所有するPluginWpfUI <T>クラスを作成しました。 ここで、TはUserControlのタイプであり、UIの「メインフォーム」です。 PluginWpfUI <T>には、ロジックを実装するためにオーバーロードできる3つの仮想メソッドがあります。









Syntageシンセサイザーでは、スライダー、ノブ、ピアノキーボードなどのコントロールをいくつか作成しました。必要に応じてコピーして使用することができます。









UIスレッド



FL StudioとCubase 5でシンセサイザーをテストしましたが、他のDAWでも同じであると確信しています。プラグインUIは別のストリームで処理されます。 これは、オーディオおよびUIロジックが独立したストリームで処理されることを意味します。 これには、すべての問題、またはこのアプローチの結果 :別のストリームからのデータへのアクセス、重要なデータ、別のストリームからのUIへのアクセスが伴います...







問題の解決を容易にするために、基本的にコマンドキューであるUIThreadクラスを作成しました。 ある時点でUIに通知/変更/何かを行いたいが、現在のコードがUIスレッドで機能しない場合、必要な機能をキューに入れることができます。







 UIThread.Instance.InvokeUIAction(() => Control.Oscilloscope.Update());
      
      





ここでは、匿名メソッドがコマンドキューに配置され、必要なデータが更新されます。 ProcessIdleを呼び出すと、キューに蓄積されたすべてのコマンドが実行されます。







UIThreadはすべての問題を解決するわけではありません。 オシロスコープをプログラミングするとき、別のスレッドで処理されたサンプルの配列を使用してUIを更新する必要がありました。 ミューテックスを使用する必要がありました。









Syntage Syntax Architectureの概要



シンセサイザーを作成するとき、OOPが積極的に使用されました。 結果のアーキテクチャに精通し、私のコードを使用することをお勧めします。 あなたはそれをあなた自身の方法で行うことができますが、これらの記事ではあなたは私のビジョンに耐えなければなりません)













PluginCommandStubクラスは、PluginControllerクラスのオブジェクトを作成して返すためにのみ必要です。 PluginControllerはプラグインに関する情報を提供し、次のコンポーネントも作成および所有します。















オーディオデータを処理するために、IAudioChannelおよびIAudioStreamインターフェイスがあります。 IAudioChannelは、サンプルの配列/バッファへの直接アクセスを提供します(ダブル[]サンプル)。 IAudioStreamにはチャンネルの配列が含まれています。







提示されたインターフェイスには、すべてのサンプルとチャネルを「一括」処理する便利な方法が含まれています。チャネルとストリームの混合、各サンプルへのメソッドの個別適用などです。







IAudioChannelおよびIAudioStreamインターフェイスの場合、AudioChannelおよびAudioStream実装が記述されます。 ここで覚えておくべき重要なことは、関数の外部データである場合、AudioStreamとAudioChannelへのリンクを保存できないことです 。 一番下の行は、バッファーのサイズがプラグイン操作中に変更される可能性があり、バッファーが絶えず再利用されることです-絶えずメモリを再割り当てしてコピーすることは有益ではありません。 将来の参照のためにバッファを保存する必要がある場合 (理由はわかりません) -クリップボードにコピーします。







IAudioStreamProviderはオーディオストリームの所有者です。CreateAudioStream関数を使用してストリームを作成し、ReleaseAudioStream関数によって削除されるストリームを返すように要求できます。







各時点で、すべてのオーディオストリームとチャネルの長さ(サンプルアレイの長さ)は同じであり、技術的にはホストによって決定されます。 コードでは、IAudioChannelまたはIAudioStream自体(Lengthプロパティ)、および「所有者」のIAudioStreamProvider(CurrentStreamLenghtプロパティ)から取得できます。













AudioProcessorクラスはシンセサイザーの「コア」です-これにより、サウンド合成が行われます。 クラスはSyntageAudioProcessorの後継であり、SyntageAudioProcessorは次のインターフェイスを実装します。









サウンド合成は、オシレーターで単純な波を作成し、異なるオシレーターからのサウンドをミックスし、エフェクトで順次処理するという長い処理チェーンを経ます。 サウンドを作成および処理するためのロジックは、AudioProcessorのコンポーネントクラスに分割されました。 SyntageAudioProcessorComponentWithParameters<T> — AudioProcessor .



:









Routing.Process :













( - ). . , , , . . , LFO- ( ).







Oscillator, -.







, Parameter<T>, : EnumParameter, IntegerParameter, RealParameter . , Value T, float- RealValue — [0,1] ( UI ).









/



ついに! . C#, Visual Studio.

.NET Class Library, Jacobi.Vst.Core.dll Jacobi.Vst.Framework.dll, Syntage.Framework.dll.







( VST .NET ).







( Project → Properties → Build Events → Post-build event command line , On successful build ):







 if not exist "$(TargetDir)\vst\" mkdir "$(TargetDir)\vst\" copy "$(TargetDir)$(TargetFileName)" "$(TargetDir)\vst\$(TargetName).net.vstdll" copy "$(TargetDir)Syntage.Framework.dll" "$(TargetDir)\vst\Syntage.Framework.dll" copy "$(TargetDir)Jacobi.Vst.Interop.dll" "$(TargetDir)\vst\$(TargetName).dll" copy "$(TargetDir)Jacobi.Vst.Core.dll" "$(TargetDir)\vst\Jacobi.Vst.Core.dll" copy "$(TargetDir)Jacobi.Vst.Framework.dll" "$(TargetDir)\vst\Jacobi.Vst.Framework.dll"
      
      









Syntage SimplyHost. , ".vstdll" ( .exe ). — .







, . , DAW: FL Studio 12 Cubase 5. FL Studio , Visual Studio FL Studio ( Debug → Attach To Process ). , : .dll ( ); DAW.

















, " Syntage" — .







— ( , ) . . - ( , ). , . — , ( ).







— , .







"" ? , , , . :













: , , /, .







, .







, — "" . , , , () .







: .

. , 0 1. , . , , .







WaveGenerator, GetTableSample, ( 0 1).







— . , . NextDouble Random — , , . , , . [0,1] [-1,1].







 public static class WaveGenerator { public enum EOscillatorType { Sine, Triangle, Square, Saw, Noise } private static readonly Random _random = new Random(); public static double GetTableSample(EOscillatorType oscillatorType, double t) { switch (oscillatorType) { case EOscillatorType.Sine: return Math.Sin(DSPFunctions.Pi2 * t); case EOscillatorType.Triangle: if (t < 0.25) return 4 * t; if (t < 0.75) return 2 - 4 * t; return 4 * (t - 1); case EOscillatorType.Square: return (t < 0.5f) ? 1 : -1; case EOscillatorType.Saw: return 2 * t - 1; case EOscillatorType.Noise: return _random.NextDouble() * 2 - 1; default: throw new ArgumentOutOfRangeException(); } } }
      
      





, Oscillator, SyntageAudioProcessorComponentWithParameters<AudioProcessor>. , IGenerator,







 IAudioStream Generate();
      
      





IAudioStreamProvider ( AudioProcessor) , Generate .







:









:







 public class Oscillator : SyntageAudioProcessorComponentWithParameters<AudioProcessor>, IGenerator { private readonly IAudioStream _stream; // ,     private double _time; public EnumParameter<WaveGenerator.EOscillatorType> OscillatorType { get; private set; } public RealParameter Frequency { get; private set; } public Oscillator(AudioProcessor audioProcessor) : base(audioProcessor) { _stream = Processor.CreateAudioStream(); //   } public override IEnumerable<Parameter> CreateParameters(string parameterPrefix) { OscillatorType = new EnumParameter<WaveGenerator.EOscillatorType>(parameterPrefix + "Osc", "Oscillator Type", "Osc", false); Frequency = new FrequencyParameter(parameterPrefix + "Frq", "Oscillator Frequency", "Hz"); return new List<Parameter> { OscillatorType, Frequency }; } public IAudioStream Generate() { _stream.Clear(); //  ,    GenerateToneToStream(); //   return _stream; } }
      
      





GenerateToneToStream.







, :









, - . Generate() ( , ) — "". , , "". . .







0 [ ].







— . , timeDelta = 1/SampleRate. 44100 — 0.00002267573 .







, — _time timeDelta .







WaveGenerator.GetTableSample 0 1, 1 — . , — , .







.







: 440 . : 1/440 = 0.00227272727 .

44100 .

44150- , .

44150- 44150/44100 = 1.00113378685 .

, — 1.00113378685/0.00227272727 = 440.498866743.

— 0.498866743. WaveGenerator.GetTableSample.







, :













WaveGenerator.GenerateNextSample GenerateToneToStream.







 public static double GenerateNextSample(EOscillatorType oscillatorType, double frequency, double time) { var ph = time * frequency; ph -= (int)ph; //  frac    return GetTableSample(oscillatorType, ph); } ... private void GenerateToneToStream() { var count = Processor.CurrentStreamLenght; //     double timeDelta = 1.0 / Processor.SampleRate; //       //    ,       var leftChannel = _stream.Channels[0]; var rightChannel = _stream.Channels[1]; for (int i = 0; i < count; ++i) { // Frequency  OscillatorType    -     //    var frequency = DSPFunctions.GetNoteFrequency(Frequency.Value); var sample = WaveGenerator.GenerateNextSample(OscillatorType.Value, frequency, _time); leftChannel.Samples[i] = sample; rightChannel.Samples[i] = sample; _time += timeDelta; } }
      
      





, :









— .







AudioProcessor ( Generate) PluginController ( AudioProcessor).

Syntage. AudioProcessor , :









,
 public class PluginCommandStub : SyntagePluginCommandStub<PluginController> { protected override IVstPlugin CreatePluginInstance() { return new PluginController(); } } ... public class PluginController : SyntagePlugin { public AudioProcessor AudioProcessor { get; } public PluginController() : base( "MyPlugin", new VstProductInfo("MyPlugin", "TestCompany", 1000), VstPluginCategory.Synth, VstPluginCapabilities.None, 0, new FourCharacterCode("TEST").ToInt32()) { AudioProcessor = new AudioProcessor(this); ParametersManager.SetParameters(AudioProcessor.CreateParameters()); ParametersManager.CreateAndSetDefaultProgram(); } protected override IVstPluginAudioProcessor CreateAudioProcessor(IVstPluginAudioProcessor instance) { return AudioProcessor; } } ... public class AudioProcessor : SyntageAudioProcessor { private readonly AudioStream _mainStream; public readonly PluginController PluginController; public Oscillator Oscillator { get; } public AudioProcessor(PluginController pluginController) : base(0, 2, 0) //   ,      ,    - { _mainStream = (AudioStream)CreateAudioStream(); PluginController = pluginController; Oscillator = new Oscillator(this); } public override IEnumerable<Parameter> CreateParameters() { var parameters = new List<Parameter>(); parameters.AddRange(Oscillator.CreateParameters("O")); return parameters; } public override void Process(VstAudioBuffer[] inChannels, VstAudioBuffer[] outChannels) { base.Process(inChannels, outChannels); //   var stream = Oscillator.Generate(); //   stream  _mainStream _mainStream.Mix(stream, 1, _mainStream, 0); //   _mainStream.WriteToVstOut(outChannels); } }
      
      





ADSR- .







!







PS — , , diy- .









参照資料



  1. . , . ..
  2. -. . . .
  3. ., . — . .
  4. Martin Finke's Blog "Music & Programming" C++, WDL-OL.
  5. - Martin Finke's Blog
  6. ( - , ).



All Articles