Br0kenB1ts - Yan's blog2024-02-21T07:19:52-08:00https://www.pongasoft.com/blog/yanYan PujanteThe power of emscripten ports2024-02-19T00:00:00-08:00https://www.pongasoft.com/blog/yan/webassembly/2024/02/19/the-power-of-emscripten-ports<h3>Introduction</h3>
<p>After spending quite a bit of time <a href="https://github.com/emscripten-core/emscripten/pulls?q=is%3Amerged+is%3Apr+author%3Aypujante">enhancing the ports system in emscripten</a>, I wanted to share my experience about why I think emscripten ports are a really powerful concept.</p>
<h3>What is emscripten?</h3>
<p><a href="https://emscripten.org/">Emscripten</a> is a <em>complete compiler toolchain to WebAssembly</em>. In a nutshell, it lets you compile C and C++ code for the <a href="https://webassembly.org/">WebAssembly</a> platform including targetting code that runs in your browser.</p>
<h3>What is a WebAssembly port?</h3>
<p>A WebAssembly port is a port of some code to make it run in the context of the web platform (both client and server).</p>
<p>Let’s say you have the following C function <code>int add(int a, int b) { return a+b; }</code>. You can easily compile this code to WebAssembly using emscripten (<code>emcc</code>) and there is no real porting necessary.</p>
<p>What if your code is now the <a href="https://www.glfw.org/"><span class="caps">GLFW</span></a> library? The library is a C <span class="caps">API</span> and comes with implementations for various (desktop) targets (ex: Windows, macOS, etc…). But if you compile this C code with emscripten, it is not going to result in a binary that you can actually use in a Web context. It needs to be ported to the WebAssembly platform.</p>
<p>There is a limited built-in port of <span class="caps">GLFW</span> (written 100% in Javascript). And I released a more comprehensive (free and open source) port written in C++ (with as little Javascript as necessary): <a href="https://github.com/pongasoft/emscripten-glfw">emscripten-glfw</a>.</p>
<p>Both of these implementations are (WebAssembly) ports in the sense that they let you write your C or C++ code against the <span class="caps">GLFW</span> <span class="caps">API</span> and it will work in a browser context. For example you can check this live <a href="https://pongasoft.github.io/emscripten-glfw/test/demo/main.html">demo</a>.</p>
<p>Emscripten has a good <a href="https://emscripten.org/docs/porting/index.html">documentation</a> about what it means to port your code to WebAssembly.</p>
<h3>What is an emscripten port?</h3>
<p>Emscripten also defines the concept of ports which can be a bit confusing. Per the <a href="https://emscripten.org/docs/compiling/Building-Projects.html#emscripten-ports">documentation</a>, <em>Emscripten Ports is a collection of useful libraries, ported to Emscripten</em>. I think a better definition would be: <em>Emscripten Ports is a collection of useful libraries, ported to WebAssembly and easily usable with emscripten</em>.</p>
<p>What this means, is that there is a very easy (= very powerful) way to use a WebAssembly port that was made by somebody else: by simply using the <code>emcc</code> option <code>--use-port</code> you <strong>declare</strong> that you want to use a port and emscripten takes care of 2 things:</p>
<ol>
<li>Fetch the code of the WebAssembly port</li>
<li>Compile it and make it available to you (including headers)</li>
</ol>
<p>For example:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">emcc --use-port=sdl2 main.c -o /tmp/main.html</code></pre></figure><p>By simply using the option <code>--use-port=sdl2</code>, emscripten will fetch (= download) the WebAssembly port from its source, then compile it with the right set of flags and options for the Web platform and make all the SDL2 header files available for your code.</p>
<p>This is where the power lives: you don’t have to worry about where the port is located, how to compile it, how to add the headers to the list of include directories, etc… It just works like magic.</p>
<p>If you have ever used java/gradle, it is very much like <strong>declaring</strong> a dependency: the system takes care of locating it, downloading it and making it available in your classpath.</p>
<p>At this moment, if you want to use <a href="https://www.raylib.com/">raylib</a> there is a WebAssembly port available, meaning raylib can be compiled for the Web/HTML5 platform. The instructions to do so are <a href="https://github.com/raysan5/raylib/wiki/Working-for-Web-(HTML5)">there</a>. It is quite complicated (including cloning the repo, following all the instructions, etc…). If/when there is an emscripten port, it will be as simple as <code>--use-port=contrib.raylib</code>.</p>
<h3>The port file</h3>
<p>The way the <em>magic</em> works is by having a port file which is a python script containing the code necessary to fetch and compile the code. In other words, it is a <em>recipe</em> to use a WebAssembly port. The syntax is a bit clunky and use some internal APIs, but it does the job.</p>
<p>When you write <code>--use-port=sdl2</code>, really what happens is that the code in <code>EMSDK_ROOT/tools/ports/sdl2.py</code> gets used.</p>
<p>Since my contributions in emscripten 3.1.54:</p>
<ul>
<li>ports can have options<br />
<figure class="highlight"><pre><code class="language-text" data-lang="text"># declare which formats sdl2_image should be compiled with
emcc --use-port=sdl2_image:formats=png,jpg main.c -o /tmp/main.html</code></pre></figure></li>
<li>ports can be contributed by a wider audience (contrib ports)<br />
<figure class="highlight"><pre><code class="language-text" data-lang="text"># a contrib port is not officially maintained by emscripten
emcc --use-port=contrib.glfw3 main.c -o /tmp/main.html</code></pre></figure></li>
<li>ports can be external<br />
<figure class="highlight"><pre><code class="language-text" data-lang="text"># a local port
emcc --use-port=/path/to/port.py main.c -o /tmp/main.html</code></pre></figure></li>
</ul>
<p>Because port files can be entirely external, it is fairly easy to create one and test it without having to clone emscripten and submit a pull request (PR). If the port is successful, it can eventually be submitted to the contrib folder (which requires cloning/PR).</p>
<p>You can also copy a built-in emscripten port file locally and use the local copy if you want to make sure you always use the same version of a port as emscripten can be updated whether you want it or not (version locking).</p>
<h3>Final thoughts</h3>
<p>I really do beleive that emscripten ports are truly powerful and I hope I was able to convince you :). If you want to use my own port, check the <a href="https://github.com/pongasoft/emscripten-glfw">emscripten-glfw</a> project for a modern, very compliant implementation of GLFW3.</p>How to share data between RT and UI in VST32020-12-12T00:00:00-08:00https://www.pongasoft.com/blog/yan/vst/2020/12/12/VST3-sharing-technique<h3>Introduction</h3>
<p>This post describes a way to share (large amounts of) data between the UI layer and the RT (real time) layer of a VST3 plugin. Although it is <strong>not</strong> recommended to actually share data (messaging is the preferred approach in VST3), there are use cases where using pure messaging is very expensive.</p>
<p>For example, in my <a href="https://pongasoft.com/vst/SAM-SPL64.html"><span class="caps">SAM</span>-<span class="caps">SPL</span> 64</a> <span class="caps">VST</span> plugin, the user can load a sample to work with (which can be arbitrarily large). The UI layer needs the sample because it renders it as a waveform (including an editing tab where you can zoom in and out of the waveform). The RT layer needs the sample because it plays it. Using messaging requires copying the sample multiple times (at the very minimum UI → messaging, messaging → RT) and also keeping 2 distinct copies.</p>
<p><img src="https://pongasoft.com/images/vst/SAM-SPL64/SAM-SPL64-Edit.jpg" width="100%"></p>
<h3>High level description</h3>
<p>The main idea is to use a pair of <code>std::shared_ptr</code> pointing to the <em>same</em> (immutable) object when sharing. When one side updates its <code>std::shared_ptr</code> then they are pointing to different objects and a (very lightweight) message is sent to the other side so that it can update its own pointer to the new one: they now point to the same object again.</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">// start
- UI shared_ptr -> nullptr
- RT shared_ptr -> nullptr
// new object (A) in RT
- UI shared_ptr -> nullptr
- RT shared_ptr -> A | version 1
- RT sends "version 1" message to UI
- UI receives "version 1" notification and copies RT shared_ptr
- UI shared_ptr -> A | version 1
// new object (B) in UI
- RT shared_ptr -> A | version 1
- UI shared_ptr -> B | version 2
- UI sends "version 2" message to RT
- RT receives "version 2" notification and copies UI shared_ptr
- A is destroyed as a result (assuming the rest of the code is not still pointing to it)
- RT shared_ptr -> B | version 2 </code></pre></figure><div class="info">As can be seen in this protocol:<br />
* Only lightweight messages are exchanged (“version” is just a number)<br />
* No object copy actually happens, we only copy <code>shared_ptr</code></div>
<h3>Implementation details</h3>
<h4>The <code>SharedObjectMgr</code></h4>
<p>The pair of <code>std::shared_ptr</code> is encapsulated in a class <a href="https://github.com/pongasoft/vst-sam-spl-64/blob/master/src/cpp/SharedObjectMgr.h">SharedObjectMgr</a> along with a version so that we make sure that we don’t update to the wrong version (messaging is asynchronous so we can’t know for sure the order in which events will happen).</p>
<p>The class uses a <code>SpinLock</code> to ensure thread safety. Locking is very lightweight and the critical section only modifies shared pointers and version values which should never be an issue in practice. Also due to the notification mechanism, locking should happen only when something changes (in my case it is only when the user loads a sample, or uses sampling, which is very rare).</p>
<p>There are 2 primary methods on each side, prefixed by which side <strong>should</strong> call them:</p>
<ul>
<li><code>uiSetObject</code> and <code>uiAdjustObjectFromRT</code> for the UI side</li>
<li><code>rtSetObject</code> and <code>rtAdjustObjectFromUI</code> for the RT side</li>
</ul>
<p>Example of usage from RT:</p>
<ul>
<li>RT has a new <code>ObjectType</code> to share with UI</li>
<li>RT calls <code>mgr.rtSetObject</code> and gets a version [<code>v</code>]</li>
<li>RT sends <code>v</code> to UI via messaging (<code>RTJmbOutParam.broadcast(v)</code> with Jamba)</li>
<li>UI receives <code>v</code> via messaging (simple callback with Jamba)</li>
<li>UI calls <code>mgr.uiAdjustObjectFromRT(v)</code> and gets the new <code>ObjectType</code></li>
</ul>
<p>Usage from UI follows the exact same pattern:</p>
<ul>
<li>UI has a new <code>ObjectType</code> to share with RT</li>
<li>UI calls <code>mgr.guiSetObject</code> and gets a version [<code>v</code>]</li>
<li>UI sends <code>v</code> to RT via messaging (<code>GUIJmbParam.broadcast(v)</code> with Jamba)</li>
<li>RT receives <code>v</code> via messaging (<code>RTJmbInParam.pop()</code> with Jamba)</li>
<li>RT calls <code>mgr.rtAdjustObjectFromUI(v)</code> and gets the new <code>ObjectType</code></li>
</ul>
<div class="info">On the RT side, it is recommended to extract and use the pointer directly instead of the <code>std::shared_ptr</code> because <code>std::shared_ptr</code> uses locks internally, and the object will never be deleted unless RT acts on it (<code>mgr.rtSetObject</code> or <code>mgr.rtAdjustObjectFromUI(v)</code>): each side manages its own (shared) pointer separately.</div>
<h4>Sharing the <code>SharedObjectMgr</code></h4>
<p>In order to share the manager itself, RT creates it and send it to the UI via messaging:</p>
<ul>
<li>serializing the pointer <code>reinterpret_cast<uint64>(ptr)</code> on the RT side</li>
<li>deserializing it on the UI side <code>reinterpret_cast<SharedObjectMgr *>(ser)</code></li>
</ul>
<p>In order for the host to keep RT and UI in the same process, thus making sharing possible, the plugin must be declared <strong>not</strong> distributable (the flag <code>Steinberg::Vst::kDistributable</code> <strong>must not</strong> be set).</p>
<div class="warning">
<p>There are a couple of details to pay attention to:</p>
<p>1. When using the editor to work on the <span class="caps">GUI</span>, the RT is not created or executed, and as a result the manager is never sent to the UI<br />
2. It is hard to predict (and guarantee) the order in which the plugin will get initialized and when messages are actually exchanged, meaning the UI may need the manager prior to receiving it from RT.</p>
<p>It is thus recommended to create one in the <span class="caps">GUI</span> and discard it (and transfer its state) when the one from RT is received.</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// Example on how to handle it in the UI</span>
<span class="c1">// With the following definitions</span>
<span class="c1">// using SharedSampleBuffersMgr32 = SharedObjectMgr<SampleBuffers<Sample32>, int64>;</span>
<span class="c1">// mutable std::unique_ptr<SharedSampleBuffersMgr32> fGUIOnlyMgr{};</span>
<span class="c1">// the code always call getSharedMgr() which does the right thing</span>
<span class="n">SharedSampleBuffersMgr32</span> <span class="o">*</span><span class="n">SampleMgr</span><span class="o">::</span><span class="n">getSharedMgr</span><span class="p">()</span> <span class="k">const</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">sharedMgr</span> <span class="o">=</span> <span class="o">*</span><span class="n">fState</span><span class="o">-></span><span class="n">fSharedSampleBuffersMgrPtr</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">sharedMgr</span><span class="p">)</span>
<span class="k">return</span> <span class="n">sharedMgr</span><span class="p">;</span>
<span class="c1">// case when we have not received the manager yet</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">fGUIOnlyMgr</span><span class="p">)</span>
<span class="n">fGUIOnlyMgr</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o"><</span><span class="n">SharedSampleBuffersMgr32</span><span class="o">></span><span class="p">();</span>
<span class="k">return</span> <span class="n">fGUIOnlyMgr</span><span class="p">.</span><span class="n">get</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// the callback gets registered to handle the manager from RT</span>
<span class="kt">void</span> <span class="n">SampleMgr</span><span class="o">::</span><span class="n">registerParameters</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="n">registerCallback</span><span class="o"><</span><span class="n">SharedSampleBuffersMgr32</span> <span class="o">*></span><span class="p">(</span><span class="n">fParams</span><span class="o">-></span><span class="n">fSharedSampleBuffersMgrPtr</span><span class="p">,</span>
<span class="p">[</span><span class="k">this</span><span class="p">]</span> <span class="p">(</span><span class="n">GUIJmbParam</span><span class="o"><</span><span class="n">SharedSampleBuffersMgr32</span> <span class="o">*></span> <span class="o">&</span><span class="n">iParam</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">mgr</span> <span class="o">=</span> <span class="o">*</span><span class="n">iParam</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">fGUIOnlyMgr</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">uiBuffers</span> <span class="o">=</span> <span class="n">fGUIOnlyMgr</span><span class="o">-></span><span class="n">uiGetObject</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="n">uiBuffers</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// there was a buffer that we need to transfer to the real time</span>
<span class="k">auto</span> <span class="n">version</span> <span class="o">=</span> <span class="n">mgr</span><span class="o">-></span><span class="n">uiSetObject</span><span class="p">(</span><span class="n">uiBuffers</span><span class="p">);</span>
<span class="c1">// we tell RT</span>
<span class="n">fGUINewSampleMessage</span><span class="p">.</span><span class="n">broadcast</span><span class="p">(</span><span class="n">version</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fGUIOnlyMgr</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure></div>
<h3>Using <a href="https://jamba.dev">Jamba</a></h3>
<p>This section will show how to do it with Jamba since a lot of facilities are readily available.</p>
<h4>Parameters</h4>
<p>We need 3 parameters: one for the manager (pointer) and 2 for the version (one in each direction)</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// declaration</span>
<span class="n">JmbParam</span><span class="o"><</span><span class="n">int64</span><span class="o">></span> <span class="n">fUIToRTVersion</span><span class="p">;</span> <span class="c1">// used by UI to communicate new version to RT</span>
<span class="n">JmbParam</span><span class="o"><</span><span class="n">int64</span><span class="o">></span> <span class="n">fRTToUIVersion</span><span class="p">;</span> <span class="c1">// used by RT to communicate new version to UI</span>
<span class="n">JmbParam</span><span class="o"><</span><span class="n">SharedObjectMgr</span><span class="o"><</span><span class="n">MyType</span><span class="o">></span> <span class="o">*></span> <span class="n">fSharedObjectMgrPtr</span><span class="p">;</span> <span class="c1">// the shared mgr</span>
<span class="c1">// definition</span>
<span class="n">fUIToRTVersion</span> <span class="o">=</span>
<span class="n">jmb</span><span class="o"><</span><span class="n">Int64ParamSerializer</span><span class="o">></span><span class="p">(</span><span class="n">EParamIDs</span><span class="o">::</span><span class="n">kUIToRTVersion</span><span class="p">,</span> <span class="n">STR16</span> <span class="p">(</span><span class="s">"UI Version (msg)"</span><span class="p">))</span>
<span class="p">.</span><span class="n">guiOwned</span><span class="p">()</span>
<span class="p">.</span><span class="n">shared</span><span class="p">()</span>
<span class="p">.</span><span class="n">transient</span><span class="p">()</span>
<span class="p">.</span><span class="n">add</span><span class="p">();</span>
<span class="n">fRTToUIVersion</span> <span class="o">=</span>
<span class="n">jmb</span><span class="o"><</span><span class="n">Int64ParamSerializer</span><span class="o">></span><span class="p">(</span><span class="n">EParamIDs</span><span class="o">::</span><span class="n">kRTToUIVersion</span><span class="p">,</span> <span class="n">STR16</span> <span class="p">(</span><span class="s">"RT Version (msg)"</span><span class="p">))</span>
<span class="p">.</span><span class="n">rtOwned</span><span class="p">()</span>
<span class="p">.</span><span class="n">shared</span><span class="p">()</span>
<span class="p">.</span><span class="n">transient</span><span class="p">()</span>
<span class="p">.</span><span class="n">add</span><span class="p">();</span>
<span class="n">fSharedObjectMgrPtr</span> <span class="o">=</span>
<span class="n">jmb</span><span class="o"><</span><span class="n">PointerSerializer</span><span class="o"><</span><span class="n">SharedObjectMgr</span><span class="o"><</span><span class="n">MyType</span><span class="o">>></span><span class="p">(</span><span class="n">EParamIDs</span><span class="o">::</span><span class="n">kSharedObjectMgrPtr</span><span class="p">,</span> <span class="n">STR16</span> <span class="p">(</span><span class="s">"Shared Mgr (ptr)"</span><span class="p">))</span>
<span class="p">.</span><span class="n">transient</span><span class="p">()</span>
<span class="p">.</span><span class="n">rtOwned</span><span class="p">()</span>
<span class="p">.</span><span class="n">shared</span><span class="p">()</span>
<span class="p">.</span><span class="n">add</span><span class="p">();</span></code></pre></figure><h4>Serializing the pointer</h4>
<p>Implement the <code>IParamSerializer</code> and serialize the pointer into a <code>uint64</code>.</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">T</span><span class="p">></span>
<span class="k">class</span> <span class="nc">PointerSerializer</span> <span class="o">:</span> <span class="k">public</span> <span class="n">IParamSerializer</span><span class="o"><</span><span class="n">T</span> <span class="o">*></span>
<span class="p">{</span>
<span class="nl">public:</span>
<span class="k">using</span> <span class="n">ParamType</span> <span class="o">=</span> <span class="n">T</span> <span class="o">*</span><span class="p">;</span>
<span class="k">static_assert</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">ParamType</span><span class="p">)</span> <span class="o"><=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">uint64</span><span class="p">),</span> <span class="s">"Making sure that a pointer will fit"</span><span class="p">);</span>
<span class="n">tresult</span> <span class="n">readFromStream</span><span class="p">(</span><span class="n">IBStreamer</span> <span class="o">&</span><span class="n">iStreamer</span><span class="p">,</span> <span class="n">ParamType</span> <span class="o">&</span><span class="n">oValue</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span>
<span class="p">{</span>
<span class="n">uint64</span> <span class="n">ptr</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">res</span> <span class="o">=</span> <span class="n">IBStreamHelper</span><span class="o">::</span><span class="n">readInt64u</span><span class="p">(</span><span class="n">iStreamer</span><span class="p">,</span> <span class="n">ptr</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">res</span> <span class="o">!=</span> <span class="n">kResultOk</span><span class="p">)</span>
<span class="k">return</span> <span class="n">res</span><span class="p">;</span>
<span class="n">oValue</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o"><</span><span class="n">ParamType</span><span class="o">></span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
<span class="k">return</span> <span class="n">res</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">tresult</span> <span class="n">writeToStream</span><span class="p">(</span><span class="n">ParamType</span> <span class="k">const</span> <span class="o">&</span><span class="n">iValue</span><span class="p">,</span> <span class="n">IBStreamer</span> <span class="o">&</span><span class="n">oStreamer</span><span class="p">)</span> <span class="k">const</span> <span class="k">override</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">oStreamer</span><span class="p">.</span><span class="n">writeInt64u</span><span class="p">(</span><span class="k">reinterpret_cast</span><span class="o"><</span><span class="n">uint64</span><span class="o">></span><span class="p">(</span><span class="n">iValue</span><span class="p">)))</span>
<span class="k">return</span> <span class="n">kResultOk</span><span class="p">;</span>
<span class="k">else</span>
<span class="k">return</span> <span class="n">kResultFalse</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">};</span></code></pre></figure><h4>RT Side</h4>
<p>On the RT side, we have the 3 parameters + the manager itself (which needs to be shared with the UI).</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// definition (PluginRTState)</span>
<span class="n">RTJmbInParam</span><span class="o"><</span><span class="n">int64</span><span class="o">></span> <span class="n">fUIToRTVersion</span><span class="p">;</span>
<span class="n">RTJmbOutParam</span><span class="o"><</span><span class="n">int64</span><span class="o">></span> <span class="n">fRTToUIVersion</span><span class="p">;</span>
<span class="n">RTJmbOutParam</span><span class="o"><</span><span class="n">SharedObjectMgr</span><span class="o"><</span><span class="n">MyType</span><span class="o">></span> <span class="o">*></span> <span class="n">fSharedObjectMgrPtr</span><span class="p">;</span>
<span class="n">SharedObjectMgr</span><span class="o"><</span><span class="n">MyType</span><span class="o">></span> <span class="n">fSharedMgr</span><span class="p">{};</span>
<span class="c1">// Usage | sending the shared pointer to the UI</span>
<span class="n">tresult</span> <span class="n">SampleSplitterProcessor</span><span class="o">::</span><span class="n">setupProcessing</span><span class="p">(</span><span class="n">ProcessSetup</span> <span class="o">&</span><span class="n">setup</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="c1">// sending the shared pointer to the UI</span>
<span class="n">fState</span><span class="p">.</span><span class="n">fSharedObjectMgrPtr</span><span class="p">.</span><span class="n">broadcast</span><span class="p">(</span><span class="o">&</span><span class="n">fState</span><span class="p">.</span><span class="n">fSharedMgr</span><span class="p">);</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="c1">// Usage | receiving a MyType from UI</span>
<span class="n">tresult</span> <span class="n">SampleSplitterProcessor</span><span class="o">::</span><span class="n">processInputs</span><span class="p">(</span><span class="n">ProcessData</span> <span class="o">&</span><span class="n">data</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="k">auto</span> <span class="n">version</span> <span class="o">=</span> <span class="n">fState</span><span class="p">.</span><span class="n">fUIToRTVersion</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="n">version</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">myObject</span> <span class="o">=</span> <span class="n">fState</span><span class="p">.</span><span class="n">fSharedMgr</span><span class="p">.</span><span class="n">rtAdjustObjectFromUI</span><span class="p">(</span><span class="o">*</span><span class="n">version</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">myObject</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// store myObject.get() somewhere and use it in the rest of the code</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="c1">// Usage | updating MyType on the RT side</span>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">SampleType</span><span class="p">></span>
<span class="n">tresult</span> <span class="n">SampleSplitterProcessor</span><span class="o">::</span><span class="n">genericProcessInputs</span><span class="p">(</span><span class="n">ProcessData</span> <span class="o">&</span><span class="n">data</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="k">auto</span> <span class="n">myObject</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_shared</span><span class="o"><</span><span class="n">MyType</span><span class="o">></span><span class="p">(...);</span>
<span class="c1">// we store it in the mgr</span>
<span class="k">auto</span> <span class="n">version</span> <span class="o">=</span> <span class="n">fState</span><span class="p">.</span><span class="n">fSharedMgr</span><span class="p">.</span><span class="n">rtSetObject</span><span class="p">(</span><span class="n">myObject</span><span class="p">);</span>
<span class="c1">// and notify the UI of the new sample</span>
<span class="n">fState</span><span class="p">.</span><span class="n">fRTToUIVersion</span><span class="p">.</span><span class="n">broadcast</span><span class="p">(</span><span class="n">version</span><span class="p">);</span>
<span class="c1">// store myObject.get() somewhere and use it in the rest of the code</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure><h4>UI Side</h4>
<p>On the UI side, we have the 3 parameters (and potentially a UI version of the manager, see warning section above to why).</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// definition</span>
<span class="n">GUIJmbParam</span><span class="o"><</span><span class="n">int64</span><span class="o">></span> <span class="n">fUIToRTVersion</span><span class="p">;</span>
<span class="n">GUIJmbParam</span><span class="o"><</span><span class="n">SharedObjectMgr</span><span class="o"><</span><span class="n">MyType</span><span class="o">></span> <span class="o">*></span> <span class="n">fSharedObjectMgrPtr</span><span class="p">;</span>
<span class="c1">// no need to define fRTToUIVersion as we can use the one in Parameters instead</span>
<span class="c1">// retrieving manager</span>
<span class="n">SharedObjectMgr</span><span class="o"><</span><span class="n">MyType</span><span class="o">></span> <span class="o">*</span><span class="n">getSharedMgr</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// ... see above for more complicated implementation accounting for</span>
<span class="c1">// no RT or non deterministic messaging </span>
<span class="k">return</span> <span class="o">*</span><span class="n">fSharedObjectMgrPtr</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Usage | updating MyType on the UI side (from some view, controller, or listener)</span>
<span class="kt">void</span> <span class="nf">myFunction</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// ....</span>
<span class="k">auto</span> <span class="n">myObject</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_shared</span><span class="o"><</span><span class="n">MyType</span><span class="o">></span><span class="p">(...);</span>
<span class="c1">// we store it in the mgr</span>
<span class="k">auto</span> <span class="n">version</span> <span class="o">=</span> <span class="n">getSharedMgr</span><span class="p">()</span><span class="o">-></span><span class="n">uiSetObject</span><span class="p">(</span><span class="n">myObject</span><span class="p">);</span>
<span class="c1">// we tell RT</span>
<span class="n">fUIToRTVersion</span><span class="p">.</span><span class="n">broadcast</span><span class="p">(</span><span class="n">version</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Usage | receiving a MyType from RT</span>
<span class="kt">void</span> <span class="n">XXXController</span><span class="o">::</span><span class="n">registerParameters</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="n">registerCallback</span><span class="o"><</span><span class="n">int64</span><span class="o">></span><span class="p">(</span><span class="n">fParams</span><span class="o">-></span><span class="n">fRTToUIVersion</span><span class="p">,</span>
<span class="p">[</span><span class="k">this</span><span class="p">]</span> <span class="p">(</span><span class="n">GUIJmbParam</span><span class="o"><</span><span class="n">int64</span><span class="o">></span> <span class="o">&</span><span class="n">iParam</span><span class="p">)</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">version</span> <span class="o">=</span> <span class="o">*</span><span class="n">iParam</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">myObject</span> <span class="o">=</span> <span class="n">getSharedMgr</span><span class="p">()</span><span class="o">-></span><span class="n">uiAdjustObjectFromRT</span><span class="p">(</span><span class="n">version</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">myObject</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure><h4>Making the plugin <strong>not</strong> distributable</h4>
<p>You can simply call the <a href="https://pongasoft.github.io/jamba/docs/5.1.1/html/classpongasoft_1_1_v_s_t_1_1_jamba_plugin_factory.html#aea5d08ecadfa454e476f47b4bdb982b1"><code>JambaPluginFactory::GetNonDistributableVST3PluginFactory</code></a> <span class="caps">API</span> to make the plugin non distributable.</p>
<h3>Conclusion</h3>
<p>Implementing such a technique is definitely quite involved and complicated with multiple edge cases to deal with (like no RT when running the editor, messaging order, thread safety, etc…). In any case, I would not recommend using it unless you really want to share large amounts of data. If messaging can do the trick, just stick to it!</p>
<p>The latest version of <a href="https://github.com/pongasoft/vst-sam-spl-64"><span class="caps">SAM</span>-<span class="caps">SPL</span> 64</a> implements this pattern/protocol and is the source of inspiration for this blog post.</p>
<p>This solution works quite well for my use case because what is shared only changes on user action (user loads a new sample, user uses sampling, user reloads the plugin).</p>
<div class="warning">
<p>One of the issue in this design is the fact that the critical section under lock may end up deleting an object which could affect RT. It is a non issue in my use case because of the fact that it can only happen on user action, not while the plugin is being used for rendering the sound. One improvement would be to have “delete” only happen on the UI side outside of the critical section under lock.</p>
</div>Announcing Jamba - VST2/3 framework2018-08-29T00:00:00-07:00https://www.pongasoft.com/blog/yan/vst/2018/08/29/Announcing-Jamba<h3>Introduction</h3>
<p>After working on the <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/12/VST-development-notes/">VST3 development notes</a> series of post and implementing another <a href="https://pongasoft.com/vst/VAC-6V.html"><span class="caps">VST</span> plugin</a>, I extracted this learning experience into a new lightweight framework called Jamba. It is now available on <a href="https://github.com/pongasoft/jamba">github</a>.</p>
<h3>Details</h3>
<p>Starting from scratch developing a <span class="caps">VST</span> plugin is quite a task (as was reflected in the previous series of posts). The Jamba framework offers a set of wrapper classes, makefiles, concepts, abstractions and utilities to help with the bootstrapping process. You are still writing a <span class="caps">VST</span> plugin with the VST3 <span class="caps">SDK</span> (you can actually pretty much override any feature of the framework to bypass it).</p>
<p>The framework takes care of some of the biggest pain points like saving and restoring the state <strong>in a thread safe fashion</strong>.</p>
<p>The <a href="https://jamba.dev">Jamba</a> website is the main site for Jamba.</p>
<p>The Jamba framework also provides a simple command line executable which creates a fully functionning (Jamba enabled) plugin: check <a href="https://jamba.dev/quickstart/">Quick Starting Guide</a>.</p>
<h3>Features</h3>
<p>Building the Jamba framework started from a set of questions that needed to be answered in order to build the plugins that I was writing. This led to the decision and design of the framework. Here are some of the questions:</p>
<h4>How do I create a VST3 plugin that depends on the <span class="caps">SDK</span>?</h4>
<p>Even the latest 3.6.10 version of the VST3 <span class="caps">SDK</span>, which provides instructions for the first time on how to build your own plugin, has it backward with your plugin being built part of the <span class="caps">SDK</span>.</p>
<p>Jamba solution is to include <code>jamba.cmake</code> in your project (and having installed the <span class="caps">SDK</span> of course), and everything is taken care for you with the dependencies in the proper direction, meaning your plugin depends on the <span class="caps">SDK</span> not the other way around. In other words, your plugin is a self contained project that can be easily built by others.</p>
<h4>What code do I need to change if I copy/paste an example?</h4>
<p>The VST3 <span class="caps">SDK</span> comes with a set of examples and it is pretty hard and messy to copy/paste one of them to create your own plugin since it is hard to know what needs to be changed (for example, you need to generate UUIDs…). There is also a lot of boilerplate code.</p>
<p>Jamba offers a simple command line executable which creates a fully functionning (Jamba enabled) plugin: check <a href="https://jamba.dev/quickstart/">Quick Starting Guide</a>.</p>
<div class="info">The plugin generated is ready to be built including those unique UUIDs. The only thing that cannot be generated is the 4 letter code for VST2 plugins since they need to be registered on the Steinberg website…</div>
<h4>How do I save and restore the state?</h4>
<div class="warning">Even the <code>again</code> sample code provided with the VST3 <span class="caps">SDK</span> is <strong>not</strong> thread safe (check <a href="https://github.com/steinbergmedia/vst3_public_sdk/blob/master/samples/vst/again/source/again.cpp#L385">getState</a> => <code>fGain</code>, <code>fGainReduction</code> and <code>bBypass</code> are being accessed from the <span class="caps">GUI</span> thread (<code>getState</code> is called by the <span class="caps">GUI</span> thread), yet they are owned and modified by the processor code).</div>
<p>Jamba takes care of the state management entirely and does it <strong>in a thread safe fashion</strong> by using the <code>...::LockFree::SingleElementQueue</code> and <code>...::LockFree::AtomicValue</code> concepts. It also takes care of versioning the state so that it is easier to upgrade later on.</p>
<h4>How to I map my <span class="caps">VST</span> parameter to a <<em>Type</em>> ?</h4>
<p><span class="caps">VST</span> parameters are represented by a double in the <code>[0.0, 1.0]</code> range.</p>
<p>For example, there are many ways to <em>map</em> a value in this range to a boolean (the C way: 0 is <code>false</code>, anything else is <code>true</code> / the “fair” way: <code>[0.0, 0.5[</code> is <code>false</code>, <code>[0.5, 1.0]</code> is <code>true</code>, etc…).</p>
<p>What matters is that the <em>mapping</em> should be consistent throughout the code.</p>
<p>Jamba offers <em>typed</em> parameters (a <em>converter</em> is provided at creation time) so that the code doesn’t deal with this conversion and only deal with the <em>Type</em> (ex: <code>RTVstParam<bool></code>).</p>
<h4>How do I stop duplicating <span class="caps">VST</span> related code?</h4>
<p>In the <span class="caps">VST</span> world, the <span class="caps">GUI</span> is the one registering the <span class="caps">VST</span> parameters with the <span class="caps">SDK</span>. The Real Time (RT) / processing code is also using them. The <code>Processor::getState</code> methods saves the state and the <code>Controller::setComponentState</code> needs to read this state in the same order to initialize the values. There is a lot of duplication and redundancy which can lead to bugs.</p>
<p>Jamba puts the parameters front and center: you <em>declare</em> them <strong>once</strong> and they are reused throughout the code and Jamba takes care of registering them and storing them.</p>
<p>Check <a href="https://github.com/pongasoft/jamba-sample-gain/blob/v1.0.1/src/cpp/JSGainPlugin.h">JSGainParameters</a> for an example of parameter declaration.</p>
<h4>How do I store a user input text field?</h4>
<p>In my A/B switch plugin, the user can input the name of the A and/or B source. This obviously needs to be saved so that it is restored when the plugin is later on reopened.</p>
<p>Jamba introduces a new kind of parameter called Jamba parameters which extend the “<em>double in the <code>[0.0, 1.0]</code> range</em>” restriction of <span class="caps">VST</span> parameters to any type (providing a <em>serializer</em>). In this case, the text field is a <code>JmbParam<UTF8String></code> and Jamba will save it automatically. Jamba also introduces a TextField view that will manage it.</p>
<p>Check <a href="https://github.com/pongasoft/jamba-sample-gain">Jamba Sample Gain</a> plugin for an example of using such a parameter.</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="p">...</span>
<span class="n">JmbParam</span><span class="o"><</span><span class="n">UTF8String</span><span class="o">></span> <span class="n">fInputTextParam</span><span class="p">;</span> <span class="c1">// the input text field</span>
<span class="p">...</span>
<span class="c1">// the free form input text - this param WILL be saved in its owner (GUI) state</span>
<span class="n">fInputTextParam</span> <span class="o">=</span>
<span class="n">jmb</span><span class="o"><</span><span class="n">UTF8StringSerializer</span><span class="o">></span><span class="p">(</span><span class="n">EJSGainParamID</span><span class="o">::</span><span class="n">kInputText</span><span class="p">,</span> <span class="n">STR16</span><span class="p">(</span><span class="s">"Input Text"</span><span class="p">))</span>
<span class="p">.</span><span class="n">guiOwned</span><span class="p">()</span>
<span class="p">.</span><span class="n">defaultValue</span><span class="p">(</span><span class="s">"Hello from GUI"</span><span class="p">)</span>
<span class="p">.</span><span class="n">add</span><span class="p">();</span></code></pre></figure><h4>How do I exchange data between the RT and <span class="caps">GUI</span>?</h4>
<p>In my <span class="caps">VAC</span>-6V plugin, the RT is the one keeping track of the history of peak values that are rendered in the UI. This is quite a challenging problem (thread safe, messaging, timer, view that may or may not be created, etc…).</p>
<p>Jamba makes this problem very simple by using a Jamba parameter and declaring it <code>shared</code>. The RT code simply calls <code>broadcast</code> on it with the new value. The <span class="caps">GUI</span> view simply uses this parameter as it would a normal <span class="caps">VST</span> parameter.</p>
<p>Check <a href="https://github.com/pongasoft/jamba-sample-gain">Jamba Sample Gain</a> plugin for an example of using such a parameter.</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// In RT code (JSGainProcessor.cpp)</span>
<span class="n">Stats</span> <span class="n">stats</span><span class="p">{};</span>
<span class="n">stats</span><span class="p">.</span><span class="n">fSampleRate</span> <span class="o">=</span> <span class="n">processSetup</span><span class="p">.</span><span class="n">sampleRate</span><span class="p">;</span>
<span class="n">stats</span><span class="p">.</span><span class="n">fMaxSinceReset</span> <span class="o">=</span> <span class="n">fState</span><span class="p">.</span><span class="n">fMaxSinceReset</span><span class="p">;</span>
<span class="n">stats</span><span class="p">.</span><span class="n">fResetTime</span> <span class="o">=</span> <span class="n">Clock</span><span class="o">::</span><span class="n">getCurrentTimeMillis</span><span class="p">();</span>
<span class="n">fState</span><span class="p">.</span><span class="n">fStats</span><span class="p">.</span><span class="n">broadcast</span><span class="p">(</span><span class="n">stats</span><span class="p">);</span> <span class="c1">// send to GUI</span>
<span class="c1">// In GUI code JSGainStatsView.cpp</span>
<span class="n">std</span><span class="o">::</span><span class="n">ostringstream</span> <span class="n">s</span><span class="p">;</span>
<span class="n">s</span> <span class="o"><<</span> <span class="s">"Rate="</span> <span class="o"><<</span> <span class="n">fStatsParam</span><span class="o">-></span><span class="n">fSampleRate</span>
<span class="o"><<</span> <span class="s">"| Max="</span> <span class="o"><<</span> <span class="n">toDbString</span><span class="p">(</span><span class="n">fStatsParam</span><span class="o">-></span><span class="n">fMaxSinceReset</span><span class="p">)</span>
<span class="o"><<</span> <span class="s">"| Dur.="</span> <span class="o"><<</span> <span class="n">computeDurationString</span><span class="p">(</span><span class="n">Clock</span><span class="o">::</span><span class="n">getCurrentTimeMillis</span><span class="p">()</span> <span class="o">-</span>
<span class="n">fStatsParam</span><span class="o">-></span><span class="n">fResetTime</span><span class="p">);</span>
<span class="p">...</span>
<span class="n">rdc</span><span class="p">.</span><span class="n">drawString</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">str</span><span class="p">(),</span> <span class="p">...);</span></code></pre></figure><h4>How do I exchange data between the <span class="caps">GUI</span> and RT?</h4>
<p>Although a bit more rare than the other way around, Jamba uses the same mechanism for communicating from the <span class="caps">GUI</span> to RT: a <code>shared</code> Jamba parameter (this time <code>guiOwned</code>).</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// In GUI code JSGainSendMessageView.cpp</span>
<span class="n">UIMessage</span> <span class="n">msg</span><span class="p">{};</span>
<span class="k">auto</span> <span class="k">const</span> <span class="o">&</span><span class="n">text</span> <span class="o">=</span> <span class="n">fState</span><span class="o">-></span><span class="n">fInputText</span><span class="p">.</span><span class="n">getValue</span><span class="p">();</span> <span class="c1">// note the use of the JmbParam<UTF8String> param</span>
<span class="n">text</span><span class="p">.</span><span class="n">copy</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">fText</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">fText</span><span class="p">)</span> <span class="o">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">fText</span><span class="p">[</span><span class="mi">0</span><span class="p">]));</span>
<span class="n">fState</span><span class="o">-></span><span class="n">fUIMessage</span><span class="p">.</span><span class="n">broadcast</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span> <span class="c1">// send to RT</span>
<span class="c1">// In RT code (JSGainProcessor.cpp)</span>
<span class="c1">// Detect the fact that the GUI has sent a message to the RT.</span>
<span class="k">auto</span> <span class="n">uiMessage</span> <span class="o">=</span> <span class="n">fState</span><span class="p">.</span><span class="n">fUIMessage</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="n">uiMessage</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">DLOG_F</span><span class="p">(</span><span class="n">INFO</span><span class="p">,</span> <span class="s">"Received message from UI <%s> / timestamp = %lld"</span><span class="p">,</span>
<span class="n">uiMessage</span><span class="o">-></span><span class="n">fText</span><span class="p">,</span> <span class="n">uiMessage</span><span class="o">-></span><span class="n">fTimestamp</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure><h4>How do I write a custom view?</h4>
<p>The views are the main UI components (a label, a knob, a slider, etc…). Jamba allows you to write custom views very easily including adding them to the Editor so that they can be used like native components.</p>
<p>Check <a href="https://github.com/pongasoft/jamba-sample-gain">Jamba Sample Gain</a> plugin for several examples on how to write a custiom view.</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">JSGainStatsView</span> <span class="o">:</span> <span class="k">public</span> <span class="n">PluginCustomView</span><span class="o"><</span><span class="n">JSGainGUIState</span><span class="o">></span><span class="p">,</span>
<span class="k">public</span> <span class="n">ITimerCallback</span>
<span class="p">{</span>
<span class="nl">public:</span>
<span class="c1">// Constructor</span>
<span class="k">explicit</span> <span class="n">JSGainStatsView</span><span class="p">(</span><span class="k">const</span> <span class="n">CRect</span> <span class="o">&</span><span class="n">iSize</span><span class="p">)</span> <span class="o">:</span> <span class="n">PluginCustomView</span><span class="p">(</span><span class="n">iSize</span><span class="p">)</span>
<span class="p">{}</span>
<span class="k">const</span> <span class="n">CColor</span> <span class="o">&</span><span class="n">getTextColor</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">fTextColor</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">void</span> <span class="nf">setTextColor</span><span class="p">(</span><span class="k">const</span> <span class="n">CColor</span> <span class="o">&</span><span class="n">iColor</span><span class="p">)</span> <span class="p">{</span> <span class="n">fTextColor</span> <span class="o">=</span> <span class="n">iColor</span><span class="p">;</span> <span class="p">}</span>
<span class="n">FontPtr</span> <span class="n">getFont</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">fFont</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">void</span> <span class="nf">setFont</span><span class="p">(</span><span class="n">FontPtr</span> <span class="n">iFont</span><span class="p">)</span> <span class="p">{</span> <span class="n">fFont</span> <span class="o">=</span> <span class="n">iFont</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">void</span> <span class="n">registerParameters</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
<span class="kt">void</span> <span class="n">draw</span><span class="p">(</span><span class="n">CDrawContext</span> <span class="o">*</span><span class="n">iContext</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
<span class="p">...</span>
<span class="k">protected</span><span class="o">:</span>
<span class="n">CColor</span> <span class="n">fTextColor</span><span class="p">{};</span>
<span class="n">FontSPtr</span> <span class="n">fFont</span><span class="p">{</span><span class="nb">nullptr</span><span class="p">};</span>
<span class="n">GUIJmbParam</span><span class="o"><</span><span class="n">Stats</span><span class="o">></span> <span class="n">fStatsParam</span><span class="p">{};</span>
<span class="k">public</span><span class="o">:</span>
<span class="k">class</span> <span class="nc">Creator</span> <span class="o">:</span> <span class="k">public</span> <span class="n">CustomViewCreator</span><span class="o"><</span><span class="n">JSGainStatsView</span><span class="p">,</span> <span class="n">CustomView</span><span class="o">></span>
<span class="p">{</span>
<span class="nl">public:</span>
<span class="k">explicit</span> <span class="n">Creator</span><span class="p">(</span><span class="kt">char</span> <span class="k">const</span> <span class="o">*</span><span class="n">iViewName</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">,</span>
<span class="kt">char</span> <span class="k">const</span> <span class="o">*</span><span class="n">iDisplayName</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="k">noexcept</span> <span class="o">:</span>
<span class="n">CustomViewCreator</span><span class="p">(</span><span class="n">iViewName</span><span class="p">,</span> <span class="n">iDisplayName</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">registerColorAttribute</span><span class="p">(</span><span class="s">"text-color"</span><span class="p">,</span>
<span class="o">&</span><span class="n">JSGainStatsView</span><span class="o">::</span><span class="n">getTextColor</span><span class="p">,</span>
<span class="o">&</span><span class="n">JSGainStatsView</span><span class="o">::</span><span class="n">setTextColor</span><span class="p">);</span>
<span class="n">registerFontAttribute</span><span class="p">(</span><span class="s">"font"</span><span class="p">,</span>
<span class="o">&</span><span class="n">JSGainStatsView</span><span class="o">::</span><span class="n">getFont</span><span class="p">,</span>
<span class="o">&</span><span class="n">JSGainStatsView</span><span class="o">::</span><span class="n">setFont</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="p">};</span></code></pre></figure><h4>How do I use multiple parameters to render a view?</h4>
<p>As an example, the <span class="caps">VAC</span>-6V plugin displays peak values (one parameter) with a color depending on another parameter (soft clipping level).</p>
<p>Jamba makes all parameters accessible to any custom view, and by default (by calling <code>registerXX(xxx, bool iSubscribeToChanges = true)</code>) the view is notified when the parameter changes which makes it trivial to implement a view depending on multiple parameters.</p>
<p>Check <a href="https://github.com/pongasoft/jamba-sample-gain/blob/master/src/cpp/GUI/LinkedSliderView.cpp">LinkedSliderView</a> custom view for an example.</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// calback when fLinkedSlider or fLink changes</span>
<span class="kt">void</span> <span class="n">GenericLinkedSliderView</span><span class="o">::</span><span class="n">onParameterChange</span><span class="p">(</span><span class="n">ParamID</span> <span class="n">iParamID</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">fLinkedSlider</span><span class="p">.</span><span class="n">getParamID</span><span class="p">()</span> <span class="o">==</span> <span class="n">iParamID</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">fLink</span> <span class="o">&&</span> <span class="n">fSlider</span> <span class="o">!=</span> <span class="n">fLinkedSlider</span><span class="p">)</span>
<span class="n">fSlider</span><span class="p">.</span><span class="n">copyValueFrom</span><span class="p">(</span><span class="n">fLinkedSlider</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">fLink</span><span class="p">.</span><span class="n">getParamID</span><span class="p">()</span> <span class="o">==</span> <span class="n">iParamID</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">fLink</span> <span class="o">&&</span> <span class="n">fSlider</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span> <span class="o"><</span> <span class="n">fLinkedSlider</span><span class="p">.</span><span class="n">getValue</span><span class="p">())</span>
<span class="n">fSlider</span><span class="p">.</span><span class="n">copyValueFrom</span><span class="p">(</span><span class="n">fLinkedSlider</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure><h4>How do I use 4 frames for a toggle?</h4>
<p>The VST3 <span class="caps">SDK</span> comes with a toggle that uses only 2 frames.</p>
<p>Jamba introduces:</p>
<ul>
<li>Jamba – Toggle Button (on/off)</li>
<li>Jamba – Momentary Button (on when pressed)</li>
</ul>
<p>which are 2 views handling either 2 or 4 frames (the extra 2 frames deal with the state: pressed/not pressed).</p>
<h4>How do I write unit tests for my plugin?</h4>
<p>When you are not familiar with cmake and C++ development, coming up with a solution to write unit tests is a bit challenging and quite time consuming.</p>
<p>Although there are many ways to accomplish this, <code>jamba.cmake</code> offers an integration with Google Test as a starting point. When following the <a href="https://jamba.dev/quickstart/">Quick Starting Guide</a>, the plugin comes with a single unit test example so that it is easy to add more tests without having to deal with the details.</p>
<p>You can obviously use your own test framework if you wish to.</p>
<h4>How do I build/test/edit/validate/install my plugin?</h4>
<p>Although the tools are part of the VST3 <span class="caps">SDK</span>, it is very manual and time consuming to figure out how to compile and run them with the right set of arguments.</p>
<p>By including <code>jamba.cmake</code> (<code>jamba_dev_scripts(${target})</code>), Jamba generates a convenient shell/bat script :</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">./jamba.sh -h
Usage: jamba.sh [-hdrn] <command>
-h : usage help
-d : use Debug build config (default)
-r : use Release build config (Debug if not defined)
-n : dry run (prints what it is going to do)
Commands:
---- VST Commands ----
clean : clean all builds
build : build the VST plugin
edit : start the GUI editor (Debug only)
install : install the VST plugin in their default location
test : run the unit tests
validate : run the VST3 validator
---- Audio Unit Commands ----
build-au : build the Audio Unit wrapper plugin
install-au : install the Audio Unit plugin in its default location
---- Generic Commands ----
archive : generate the zip file containing the plugin(s) and README/License
prod : run test/validate/archive (default to Release, override with -d)
---- CMake target ----
<target> : invoke cmake with the provided target</code></pre></figure><h4>How do I build for macOS and Windows 10?</h4>
<p>Jamba comes with support for both macOS and Windows 10 out of the box and you can write your code on one platform and compile/build on the other as well (as long as the source of truth is the <code>CMakeLists.txt</code>). The files that are important are:</p>
<ul>
<li><code>CMakeLists.txt</code> which contains the list of sources (you need to add your own source files in the <code>vst_sources</code> list). <a href="https://github.com/pongasoft/jamba-sample-gain/blob/v1.1.0/CMakeLists.txt#L33">Example</a>.</li>
<li><code>CMakeLists.txt</code> which contains the list of resources (graphics) for the UI (<code>jamba_add_vst3_resource</code>). Note that Jamba automatically takes care of handling the resources for macOS and Windows 10 (which is different). <a href="https://github.com/pongasoft/jamba-sample-gain/blob/v1.1.0/CMakeLists.txt#L64">Example</a>.</li>
</ul>
<h4>How do I wrap my plugin as an Audio Unit?</h4>
<p>The VST3 <span class="caps">SDK</span> provides a wrapper to generate an audio unit from the VST3 plugin. Using it out of the box is quite challenging (good luck hunting down the “Core Audio <span class="caps">SDK</span>” referenced in the comments…). Jamba (new in 2.1.0) encapsulates all the details for you. By following the <a href="https://jamba.dev/quickstart/">Quick Starting Guide</a> you will have a plugin configured to support Audio Unit immediately.</p>
<p><em>Last edited: 2020/01/04</em></p>VST (3.6.9) Development Notes - Part 42018-03-24T00:00:00-07:00https://www.pongasoft.com/blog/yan/vst/2018/03/24/VST-development-notes-part4<p>In <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/17/VST-development-notes-part3/">Part 3</a>, we created a fully functioning VST2/VST3 plugin which was capable of switching between 2 inputs. This post features some notes about the remaining of the implementation to be fully code complete (without a pretty UI).</p>
<div class="warning">Check <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/12/VST-development-notes/">Part 1</a> for platform requirements and assumptions.</div>
<h3>Setup</h3>
<p>This project is available on github: <a href="https://github.com/pongasoft/vst-ab-switch">vst-ab-switch</a>. Make sure you checkout the <code>blog-part4-369</code> tag in order to follow along with this part (or <a href="https://github.com/pongasoft/vst-ab-switch/tree/blog-part4-369">browse</a> the code directly on github)</p>
<div class="info">As mentioned in Part 1, I am now using CLion exclusively but it should be fairly trivial to generate the Xcode project or using <code>cmake</code> directly as explained in Part 1.</div>
<p>All the source files are located under <code>src/cpp</code></p>
<h3>Creating a UI only text edit field</h3>
<p>The plugin allows you to give a name to each input. This field is purely UI (the processor does not care about it). But it still needs to be saved as part of the state so that the names are preserved. It turned out to be a much more complicated task that anticipated. Adding a “Text Field” view in the editor (class <code>CTextEdit</code>) and equivalent parameter in the controller like we did for the Audio Switch control actually does not work! A parameter <strong>must</strong> represent a value (between 0 and 1) not a pure string.</p>
<p>In order to achieve this, here are the steps I ended up following (check the source code for details):</p>
<ul>
<li>using the editor, added a “Text Field” view (class <code>CTextField</code>) for each input</li>
<li>assign the view to a tag so that we can get a hold of it in the code</li>
<li>changed the controller to inherit from <code>VSTGUI::VST3EditorDelegate</code> in order to override the <code>verifyView</code> method which is the place where we can get a hold of the views</li>
<li>implemented the <code>getState</code> and <code>setState</code> method which deal with the UI state only (<code>setComponentState</code> deals with the processor state!) in order to save/restore the text.</li>
</ul>
<p>Although it does make sense in hindsight, the views are actually not created when the plugin is loaded. The processor and controller classes are instantiated and initialized (via the various <code>set*State</code> methods) when the plugin gets loaded. The views are actually only created when the UI portion of the plugin is displayed (which usually happens when double clicking on the loaded plugin in a <span class="caps">DAW</span>). And when the UI portion is closed they get destroyed!</p>
<p>This is the reason why I ended up creating a <code>StringTextEdit</code> class whose lifespan matches the controller (the controller owns them): this class is in charge of keeping the actual text of the field. When a view is created (when the UI is opened) it gets assigned to this class (<code>assignTextEdit</code>) at which point the view gets initialized with the text value and a couple of listeners are registered:</p>
<ul>
<li><code>valueChanged</code> so that it gets notified if the user modifies the string</li>
<li><code>viewWillDelete</code> so that it gets notified when the view is closed (and can deregister the listeners)</li>
</ul>
<p>As you can see this is a lot more involved than simply adding a parameter!</p>
<div class="warning"><em>Rant</em>: string handling is a big mess in the <span class="caps">SDK</span> with types like <code>String</code>, <code>ConstString</code>, <code>UTF8String</code>, <code>char8</code>, <code>tchar</code>, <code>STR16</code> (macro), <code>USTRING</code> (macro), and much more… Here is an example: <code>CTextEdit.getText()</code> returns a <code>UTF8String</code> string, but the method <code>IBStream.writeStringUtf8</code> expects a <code>const tchar* ptr</code> which are not compatible…</div>
<h3>Soften/Cross fade feature</h3>
<p>Adding this feature was not too hard: from a UI point of view it is just another On/Off button and is tied to a “standard” parameter. From the processor point of view, it requires to keep track of the previous state of the switch since cross fading happens <strong>only</strong> when transitioning from one input to the next.</p>
<p>The actual implementation is simply doing a linear interpolation between the 2 inputs for the duration of the frame. I reused the concept of templated code (introduced in the <code>again</code> sample coming with the <span class="caps">SDK</span>) and enhanced it a little bit to avoid dealing with <code>void **</code> pointers… Templates can be pretty powerful!</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// Use of template to retrieve the proper buffer</span>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">SampleType</span><span class="p">></span>
<span class="kr">inline</span> <span class="n">SampleType</span><span class="o">**</span> <span class="nf">getBuffer</span><span class="p">(</span><span class="n">AudioBusBuffers</span> <span class="o">&</span><span class="n">buffer</span><span class="p">);</span>
<span class="c1">// specialization for Sample32</span>
<span class="k">template</span><span class="o"><</span><span class="p">></span>
<span class="kr">inline</span> <span class="n">Sample32</span><span class="o">**</span> <span class="nf">getBuffer</span><span class="p">(</span><span class="n">AudioBusBuffers</span> <span class="o">&</span><span class="n">buffer</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">buffer</span><span class="p">.</span><span class="n">channelBuffers32</span><span class="p">;</span> <span class="p">}</span>
<span class="c1">// specialization for Sample64</span>
<span class="k">template</span><span class="o"><</span><span class="p">></span>
<span class="kr">inline</span> <span class="n">Sample64</span><span class="o">**</span> <span class="nf">getBuffer</span><span class="p">(</span><span class="n">AudioBusBuffers</span> <span class="o">&</span><span class="n">buffer</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">buffer</span><span class="p">.</span><span class="n">channelBuffers64</span><span class="p">;</span> <span class="p">}</span>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">SampleType</span><span class="p">></span>
<span class="n">tresult</span> <span class="nf">linearCrossFade</span><span class="p">(</span><span class="n">AudioBusBuffers</span> <span class="o">&</span><span class="n">audioBufferIn1</span><span class="p">,</span>
<span class="n">AudioBusBuffers</span> <span class="o">&</span><span class="n">audioBufferIn2</span><span class="p">,</span>
<span class="n">AudioBusBuffers</span> <span class="o">&</span><span class="n">audioBufferOut</span><span class="p">,</span>
<span class="n">int32</span> <span class="n">numSamples</span><span class="p">)</span>
<span class="p">{</span>
<span class="p">...</span>
<span class="n">SampleType</span><span class="o">**</span> <span class="n">in1</span> <span class="o">=</span> <span class="n">getBuffer</span><span class="o"><</span><span class="n">SampleType</span><span class="o">></span><span class="p">(</span><span class="n">audioBufferIn1</span><span class="p">);</span>
<span class="n">SampleType</span><span class="o">**</span> <span class="n">in2</span> <span class="o">=</span> <span class="n">getBuffer</span><span class="o"><</span><span class="n">SampleType</span><span class="o">></span><span class="p">(</span><span class="n">audioBufferIn2</span><span class="p">);</span>
<span class="n">SampleType</span><span class="o">**</span> <span class="n">out</span> <span class="o">=</span> <span class="n">getBuffer</span><span class="o"><</span><span class="n">SampleType</span><span class="o">></span><span class="p">(</span><span class="n">audioBufferOut</span><span class="p">);</span>
<span class="p">...</span>
<span class="p">}</span></code></pre></figure><h3>Adding a processor controlled parameter</h3>
<p>The plugin has an <span class="caps">LED</span> light which shows whether there is sound or not going on, or in other words a very simplistic VU meter. The <code>again</code> sample provided with the <span class="caps">SDK</span> has a full VU meter so it was easy to follow the steps which are not too hard.</p>
<ul>
<li>added another On/Off button in the editor</li>
<li>this component gets registered as a parameter in the controller but this time with a flag of <code>ParameterInfo::kIsReadOnly</code> since it is only modified by the code not the user</li>
<li>the processor determines if there is sound or not in the frame being rendered and communicates the result to the UI using the <code>data.outputParameterChanges</code> concept. Note that the value is communicated <strong>only</strong> when it changes!</li>
<li>since this is a transient and dynamic/computed value it does not get saved with the state of the plugin</li>
</ul>
<p>Check the code for more details.</p>
<h3>Conclusion</h3>
<p>At this stage, the plugin pretty much reimplements all the features of the rack extension (minus CV handling which does not exist in <span class="caps">VST</span>). The next step is to make the UI look decent ;)</p>
<p><em>Last edited: 2018/03/24</em></p>VST (3.6.9) Development Notes - Part 32018-03-17T00:00:00-07:00https://www.pongasoft.com/blog/yan/vst/2018/03/17/VST-development-notes-part3<p>In <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/14/VST-development-notes-part2/">Part 2</a>, we created a fully functioning VST2/VST3 plugin whose sole action was to remove 3dB of headroom to the input signal (no UI). Part 3 focuses on adding a UI with a control to actually do the primary job of the A/B switch: switching between input A and input B.</p>
<div class="warning">Check <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/12/VST-development-notes/">Part 1</a> for platform requirements and assumptions.</div>
<h3>Setup</h3>
<p>This project is available on github: <a href="https://github.com/pongasoft/vst-ab-switch">vst-ab-switch</a>. Make sure you checkout the <code>blog-part3-369</code> tag in order to follow along with this part (or <a href="https://github.com/pongasoft/vst-ab-switch/tree/blog-part3-369">browse</a> the code directly on github)</p>
<div class="info">As mentioned in Part 1, I am now using CLion exclusively but it should be fairly trivial to generate the Xcode project or using <code>cmake</code> directly as explained in Part 1.</div>
<p>All the source files are located under <code>src/cpp</code></p>
<h3>Creating the UI</h3>
<p>As described in Part 2, using the <code>editorhost</code> app (or deploying the plugin in an actual <span class="caps">DAW</span>), you can right click on the background to open the editor. The documentation that comes with the <span class="caps">SDK</span> describes the editor a little bit.</p>
<figure class="image"><img src="https://www.pongasoft.com/blog/yan/resource/images/2018-03-17/editorapp.jpg" alt="editorapp" border="0" width="100%" style=""/><figcaption>This is what the editor will look like at the end of this exercise</figcaption></figure>
<h4>Changing the background color</h4>
<p>The top right area shows the parameters that you can edit for the “view” that is selected in the top left area (called Editor). To change the background color (of the only current view), simply click on the <code>background-color</code> selection box. I selected <code>~BlueCColor~</code> to make it different from the default.</p>
<h4>Adding simple text</h4>
<p>The bottom right section has multiple tabs. The first one is called <code>Views</code> and lists all the views that the editor knows about. Each view corresponds to a C++ class (feel free to look at the source code of the <span class="caps">SDK</span> to see what each class does, although the comments/documentation is quite limited). You then drag and drop a view from this list into the editor. I dragged the one called <code>Label</code> (C++ class being <code>CTextLabel</code>) and changed some properties like the title, background color, etc… to create the <em>A/B Switch</em> title.</p>
<p>If you want to change the font to one that is not listed, you need to go into the <code>Fonts</code> tab in the bottom right section and add the font there. It will then appear in the dropdown selection.</p>
<div class="warning">From what I could understand, when adding a font, it simply adds the name of the font in the xml file, not the actual font. It is very unclear to me what would happen in the event the font does not exist on a user machine. That is why I don’t think it is a good idea to add a font and would recommend that the text that appears in the UI of the plugin be part of the background image (scaling/HiDPI is handled!).</div>
<h4>Adding the switch/toggle</h4>
<p>To add the toggle, I used the <code>OnOff Button</code> view (<code>COnOffButton</code>) and resized it to 50×50. An on/off button is a view that has 2 states which can get toggled by clicking on it.</p>
<p>In order for the button to have an image that will change when the toggle is clicked, you need to:</p>
<ul>
<li>create one image that contains the 2 frames on top of each other. Or in other words, the image is twice as tall (see the file <code>resource/Control_AudioSwitch.png</code>).</li>
</ul>
<figure class="image"><img src="https://github.com/pongasoft/vst-ab-switch/raw/blog-part3-369/resource/Control_AudioSwitch.png" alt="Control_AudioSwitch.png" border="0" style=""/><figcaption>The image contains 2 frames </figcaption></figure>
<ul>
<li>on the <code>Bitmaps</code> tab of the bottom right panel, click the <code>+</code> and select the image</li>
<li>now in the parameters area, you can now select the image you just added, in the dropdown of the <code>bitmap</code> entry.</li>
</ul>
<div class="warning">I spent a huge amount of time trying to figure out why this was not working at first. I actually found a bug in the <span class="caps">SDK</span> (see <a href="https://sdk.steinberg.net/viewtopic.php?f=5&t=533">forum</a>): the filename of the image is parsed to determine the scale (used for HiDPI) and so my image (originally) called <code>Control_AudioSwitch_50x100.png</code> ended up being treated as a 50x scale.</div>
<p>If you exit the <em>Editing</em> mode (which you can do by clicking on the <code>x</code> next to <code>Editing</code> in the menu bar), you can now click on the image and it changes to say <code>In A</code> or <code>In B</code>.</p>
<h4>Saving</h4>
<p>Once you are done, you save the new UI by clicking <code>File/Save As...</code>. The file needs to be saved under the <code>resource</code> folder and to match what the C++ code expects (see <code>ABSwitchController</code> constructor): it should be called <code>ABSwitch.uidesc</code>. The image(s) also need to be under the <code>resource</code> folder.</p>
<div class="warning">You may need to edit the file generated to remove the absolute path that gets saved for the bitmap (see <code>ABSwitch.uidesc</code>) checked in.</div>
<p>The file contains a <code><custom>...</custom></code> section which represents the state of the editor and can be safely removed (although it is probably good to keep it while you are working on the UI).</p>
<h4>Bonus: automatic scaling</h4>
<p>Based on the helloworld sample that comes with the <span class="caps">SDK</span> (and the bug I found), I realized that if you generate an image called <code>Control_AudioSwitch_2x.png</code> and add it to the <code>Bitmaps</code> section, the UI will automatically use it on HiDPI screen like retina displays!</p>
<h3>Adding a parameter to the code</h3>
<p>Now that we have a UI, we need to tie the UI to the code so that toggling the switch actually does something.</p>
<h4>Declaring the parameter in the controller</h4>
<p>The controller (<code>ABSwitchController</code>) declares the parameter we just created in the <code>initialize</code> method:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// the toggle that switches between A and B input</span>
<span class="n">parameters</span><span class="p">.</span><span class="n">addParameter</span><span class="p">(</span><span class="n">STR16</span> <span class="p">(</span><span class="s">"Audio Switch"</span><span class="p">),</span> <span class="c1">// title</span>
<span class="n">STR16</span> <span class="p">(</span><span class="s">"A/B"</span><span class="p">),</span> <span class="c1">// units</span>
<span class="mi">1</span><span class="p">,</span> <span class="c1">// stepCount => 1 means toggle</span>
<span class="mi">0</span><span class="p">,</span> <span class="c1">// defaultNormalizedValue => we start with A (0)</span>
<span class="n">Vst</span><span class="o">::</span><span class="n">ParameterInfo</span><span class="o">::</span><span class="n">kCanAutomate</span><span class="p">,</span> <span class="c1">// flags</span>
<span class="n">ABSwitchParamID</span><span class="o">::</span><span class="n">kAudioSwitch</span><span class="p">,</span> <span class="c1">// tag</span>
<span class="mi">0</span><span class="p">,</span> <span class="c1">// unitID => not using units at this stage</span>
<span class="n">STR16</span> <span class="p">(</span><span class="s">"Switch"</span><span class="p">));</span> <span class="c1">// shortTitle</span></code></pre></figure><ul>
<li><code>parameters</code> is a field that comes from the <code>EditController</code> class that the controller inherits from and is used to declare/register a parameter</li>
<li>a <code>stepCount</code> of 1 indicates it is a toggle</li>
<li>the <code>defaultNormalizedValue</code> specifies the original value of the parameter, which we set to 0 (which represents Input A)</li>
<li>the <code>tag</code> (defined in <code>ABSwitchCIDs.h</code>) is very important as it is the value that ties all the pieces together (the value comes from an enum and is set to <code>1000</code>). Note that it is unclear to me what the actual allowed range for this tag is. Can it start at 0 for example? The helloworld sample code uses 500 and 1000… so I used a similar value.</li>
<li>I have no idea how <em>short</em> the short title is supposed to be (could not find an obvious explanation)</li>
</ul>
<p>In order for the controller to be able to restore its state (for example after loading a project which contains this plugin), the <code>setComponentState</code> method needs to be implemented:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">tresult</span> <span class="n">ABSwitchController</span><span class="o">::</span><span class="n">setComponentState</span><span class="p">(</span><span class="n">IBStream</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// we receive the current state of the component (processor part)</span>
<span class="k">if</span><span class="p">(</span><span class="n">state</span> <span class="o">==</span> <span class="nb">nullptr</span><span class="p">)</span>
<span class="k">return</span> <span class="n">kResultFalse</span><span class="p">;</span>
<span class="c1">// using helper to read the stream</span>
<span class="n">IBStreamer</span> <span class="n">streamer</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">kLittleEndian</span><span class="p">);</span>
<span class="c1">// ABSwitchParamID::kAudioSwitch</span>
<span class="kt">float</span> <span class="n">savedParam1</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">streamer</span><span class="p">.</span><span class="n">readFloat</span><span class="p">(</span><span class="n">savedParam1</span><span class="p">))</span>
<span class="k">return</span> <span class="n">kResultFalse</span><span class="p">;</span>
<span class="n">setParamNormalized</span><span class="p">(</span><span class="n">ABSwitchParamID</span><span class="o">::</span><span class="n">kAudioSwitch</span><span class="p">,</span> <span class="n">savedParam1</span><span class="p">);</span>
<span class="k">return</span> <span class="n">kResultOk</span><span class="p">;</span></code></pre></figure><p>The state is provided as an <code>IBStream</code> and using the helper class <code>IBStreamer</code> you can read what was previously saved (see processor). Note how the value read from the stream is assigned to the proper parameter by using the <code>setParamNormalized</code> and tag previously used during registration!</p>
<h4>Adding the parameter to the UI</h4>
<p>In the UI editor, you now add a tag in the <code>Tags</code> tab of the bottom right area. I called it <code>Param_AudioSwitch</code> with a value of 1000 (which is the tag defined in the controller!).</p>
<p>Then the on/off button view needs to be edited to select this tag in the <code>control-tag</code> entry.</p>
<p>Don’t forget to save the UI.</p>
<h4>Handling the parameter in the processor</h4>
<p>Quite surprisingly, you don’t register/declare the parameter in the processor. You just <em>use</em> it. That being said, the parameter is usually represented by an actual concept in the processor code. In this case, the field <code>fSwitchState</code> (whose type is backed up by an enum class for clarity) represents which input is currently used.</p>
<h5>Persistence</h5>
<p>Similarly to the controller, you need to implement a couple of methods to read and write the parameter to handle persistence (for example, loading/saving a project).</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// called to restore the state in the processor</span>
<span class="n">tresult</span> <span class="n">ABSwitchProcessor</span><span class="o">::</span><span class="n">setState</span><span class="p">(</span><span class="n">IBStream</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">state</span> <span class="o">==</span> <span class="nb">nullptr</span><span class="p">)</span>
<span class="k">return</span> <span class="n">kResultFalse</span><span class="p">;</span>
<span class="n">IBStreamer</span> <span class="n">streamer</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">kLittleEndian</span><span class="p">);</span>
<span class="c1">// ABSwitchParamID::kAudioSwitch</span>
<span class="kt">float</span> <span class="n">savedParam1</span> <span class="o">=</span> <span class="mf">0.</span><span class="n">f</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">streamer</span><span class="p">.</span><span class="n">readFloat</span><span class="p">(</span><span class="n">savedParam1</span><span class="p">))</span>
<span class="k">return</span> <span class="n">kResultFalse</span><span class="p">;</span>
<span class="n">fSwitchState</span> <span class="o">=</span> <span class="n">ESwitchStateFromValue</span><span class="p">(</span><span class="n">savedParam1</span><span class="p">);</span>
<span class="k">return</span> <span class="n">kResultOk</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure><p>This method is very similar to the one implemented in the controller with the difference that it saves the parameter value in the local <code>fSwitchState</code> field.</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="c1">// called to save the state</span>
<span class="n">tresult</span> <span class="n">ABSwitchProcessor</span><span class="o">::</span><span class="n">setState</span><span class="p">(</span><span class="n">IBStream</span> <span class="o">*</span><span class="n">state</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">IBStreamer</span> <span class="n">streamer</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">kLittleEndian</span><span class="p">);</span>
<span class="n">streamer</span><span class="p">.</span><span class="n">writeFloat</span><span class="p">(</span><span class="n">fSwitchState</span> <span class="o">==</span> <span class="n">ESwitchState</span><span class="o">::</span><span class="n">kA</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">);</span>
<span class="k">return</span> <span class="n">kResultOk</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure><p>This method is the one that generates the stream in the first place. In this case the streamer is used to write the content of the local <code>fSwitchState</code> field to the stream.</p>
<h5>Updates</h5>
<p>Finally, the last remaining piece of the puzzle is how the processor gets notified of changes in the UI (or through automation). Every time the <code>process</code> method is called, the <code>ProcessData</code> argument contains an entry referencing all the parameters that have changed since the last call to <code>process</code> (for clarity I have extracted this step into a separate method).</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">void</span> <span class="n">ABSwitchProcessor</span><span class="o">::</span><span class="n">processParameters</span><span class="p">(</span><span class="n">IParameterChanges</span> <span class="o">&</span><span class="n">inputParameterChanges</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">int32</span> <span class="n">numParamsChanged</span> <span class="o">=</span> <span class="n">inputParameterChanges</span><span class="p">.</span><span class="n">getParameterCount</span><span class="p">();</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">numParamsChanged</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">IParamValueQueue</span> <span class="o">*</span><span class="n">paramQueue</span> <span class="o">=</span> <span class="n">inputParameterChanges</span><span class="p">.</span><span class="n">getParameterData</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">paramQueue</span> <span class="o">!=</span> <span class="nb">nullptr</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">ParamValue</span> <span class="n">value</span><span class="p">;</span>
<span class="n">int32</span> <span class="n">sampleOffset</span><span class="p">;</span>
<span class="n">int32</span> <span class="n">numPoints</span> <span class="o">=</span> <span class="n">paramQueue</span><span class="o">-></span><span class="n">getPointCount</span><span class="p">();</span>
<span class="c1">// we read the "last" point (ignoring multiple changes for now)</span>
<span class="k">if</span><span class="p">(</span><span class="n">paramQueue</span><span class="o">-></span><span class="n">getPoint</span><span class="p">(</span><span class="n">numPoints</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="n">sampleOffset</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="o">==</span> <span class="n">kResultOk</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">switch</span><span class="p">(</span><span class="n">paramQueue</span><span class="o">-></span><span class="n">getParameterId</span><span class="p">())</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">kAudioSwitch</span><span class="p">:</span>
<span class="n">fSwitchState</span> <span class="o">=</span> <span class="n">ESwitchStateFromValue</span><span class="p">(</span><span class="n">value</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="nl">default:</span>
<span class="c1">// shouldn't happen?</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure><ul>
<li>the outer loop iterates over every change (in this case there should be at most 1)</li>
<li>the code is a little bit complicated because you do not get just a single value but potentially multiple values: every call to <code>process</code> handles several samples and the parameter may actually change at different points in time during this window. The <em>“Parameters and Automation”</em> section in the <span class="caps">VST</span> <span class="caps">SDK</span> documentation is actually pretty good at describing what could happen (check the diagram at the bottom of the page!).</li>
<li>for our case, we are simplifying and simply taking the last known value in the interval for our value</li>
<li>note how the <code>switch</code> statement determines which parameter we are talking about</li>
</ul>
<div class="info">As you can see there is a lack of consistency: in the UI editor and the controller, it is called <em>tag</em>. Here it is called <em>parameterId</em>. But it is the same thing!</div>
<h5>Use</h5>
<p>Now that we know how the parameter gets persisted and updated, we can simply use it. After all this, the actual value is stored in the <code>fSwitchState</code> field, ready to be used in the rest of the code.</p>
<p>In the <code>initialize</code> method we register another stereo audio input (B):</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="p">...</span>
<span class="c1">// 2 ins (A and B) => 1 out</span>
<span class="n">addAudioInput</span><span class="p">(</span><span class="n">STR16</span> <span class="p">(</span><span class="s">"Stereo In A"</span><span class="p">),</span> <span class="n">SpeakerArr</span><span class="o">::</span><span class="n">kStereo</span><span class="p">);</span>
<span class="n">addAudioInput</span><span class="p">(</span><span class="n">STR16</span> <span class="p">(</span><span class="s">"Stereo In B"</span><span class="p">),</span> <span class="n">SpeakerArr</span><span class="o">::</span><span class="n">kStereo</span><span class="p">);</span>
<span class="n">addAudioOutput</span><span class="p">(</span><span class="n">STR16</span> <span class="p">(</span><span class="s">"Stereo Out"</span><span class="p">),</span> <span class="n">SpeakerArr</span><span class="o">::</span><span class="n">kStereo</span><span class="p">);</span>
<span class="p">...</span></code></pre></figure><p>The logic is now almost trivial!</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="p">...</span>
<span class="kt">int</span> <span class="n">inputIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// this is where the "magic" happens => determine which input we use (A or B)</span>
<span class="k">if</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">numInputs</span> <span class="o">></span> <span class="mi">1</span><span class="p">)</span>
<span class="n">inputIndex</span> <span class="o">=</span> <span class="n">fSwitchState</span> <span class="o">==</span> <span class="n">ESwitchState</span><span class="o">::</span><span class="n">kA</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">AudioBusBuffers</span> <span class="o">&</span><span class="n">stereoInput</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">inputs</span><span class="p">[</span><span class="n">inputIndex</span><span class="p">];</span>
<span class="n">AudioBusBuffers</span> <span class="o">&</span><span class="n">stereoOutput</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">outputs</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="p">...</span>
<span class="c1">// simply copy the samples</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">out</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">in</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">sampleFramesSize</span><span class="p">);</span>
<span class="p">...</span></code></pre></figure><p>The <code>fSwitchState</code> field is used to determine which stereo input we should use. The rest of the logic simply copies the input samples to the output stereo pair (removed the gain section from Part 2) using <code>memcpy</code> for each channel (left and right).</p>
<h3>Conclusion</h3>
<p>At this stage we have a fully working A/B switch. It’s not pretty, it doesn’t have all the bells and whistles of the Rack Extension but the core part of the plugin is doing what it is supposed to do. The final version should be very similar to the Rack Extension (minus CV handling which is not available in the <span class="caps">VST</span> world :( ).</p>
<h3>Next</h3>
<p>Check out <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/24/VST-development-notes-part4/">Part 4</a> for some more notes and comments regarding the remainder of the implementation.</p>
<p><em>Last edited: 2018/03/24</em></p>VST (3.6.9) Development Notes - Part 22018-03-14T00:00:00-07:00https://www.pongasoft.com/blog/yan/vst/2018/03/14/VST-development-notes-part2<p>In <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/12/VST-development-notes/">Part 1</a>, I described my first steps in installing the VST3 <span class="caps">SDK</span> and compiling a basic plugin in its own environment. Part2 focuses on the content of a plugin. For this exercise, I am reimplementing the <a href="https://pongasoft.com/rack-extensions/ABSwitch.html">A/B Audio Switch</a> rack extension since the logic is fairly simple while still touching many areas of plugin development.</p>
<div class="warning">Check <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/12/VST-development-notes/">Part 1</a> for platform requirements and assumptions.</div>
<h3>Setup</h3>
<p>This project is available on github: <a href="https://github.com/pongasoft/vst-ab-switch">vst-ab-switch</a>. Make sure you checkout the <code>blog-part2-369</code> tag in order to follow along with this part (or <a href="https://github.com/pongasoft/vst-ab-switch/tree/blog-part2-369">browse</a> the code directly on github).</p>
<div class="info">As mentioned in Part 1, I am now using CLion exclusively but it should be fairly trivial to generate the Xcode project or using <code>cmake</code> directly as explained in Part 1.</div>
<p>All the source files are located under <code>src/cpp</code></p>
<h3>Processor and Controller</h3>
<p>As described in the documentation, a plugin is made up of 2 main concepts:</p>
<ul>
<li>the processor in charge of doing all the actual audio processing (deal with audio samples, midi events, etc…)</li>
<li>the controller in charge of the UI (vu meters, knobs, mouse events, etc…)</li>
</ul>
<p>Each concept is represented by a main class, in this case <code>ABSwitchProcessor</code> and <code>ABSwitchController</code> which inherit/implement the required interfaces/classes.</p>
<h3>Main entry point(s)</h3>
<p>When the host/<span class="caps">DAW</span> loads the plugin, it needs to get a handle to the classes. This is a bit convoluted and frankly not explained at all in the documentation. So this is what I could gather from looking at the examples and <span class="caps">SDK</span> source code.</p>
<h4>Class IDs</h4>
<p>Each class defines a unique ID (actually each class in the <span class="caps">SDK</span> has a unique ID as well). The file <code>ABSwitchCIDs.h</code> declares the unique ID for the 2 main classes:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">static</span> <span class="k">const</span> <span class="o">::</span><span class="n">Steinberg</span><span class="o">::</span><span class="n">FUID</span> <span class="nf">ABSwitchProcessorUID</span><span class="p">(</span><span class="mh">0x8d605466</span><span class="p">,</span> <span class="mh">0x25154967</span><span class="p">,</span> <span class="mh">0x85ddbb25</span><span class="p">,</span> <span class="mh">0x8ac01235</span><span class="p">);</span>
<span class="k">static</span> <span class="k">const</span> <span class="o">::</span><span class="n">Steinberg</span><span class="o">::</span><span class="n">FUID</span> <span class="nf">ABSwitchControllerUID</span><span class="p">(</span><span class="mh">0x82aea4a3</span><span class="p">,</span> <span class="mh">0x5b4e4a5f</span><span class="p">,</span> <span class="mh">0xa3d68b1a</span><span class="p">,</span> <span class="mh">0x8a1b69c5</span><span class="p">);</span></code></pre></figure><p>The helloworld plugin that comes with the <span class="caps">SDK</span> specifies: <em>“you can use <span class="caps">GUID</span> creator tools like <a href="https://www.guidgenerator.com/">www.guidgenerator.com</a>”</em> in order to generate those <span class="caps">IDS</span>. I generated these using java <code>java.lang.UUID.randomUUID()</code> (see <a href="https://docs.oracle.com/javase/9/docs/api/java/util/UUID.html">javadoc</a>), but feel free to use the website suggested.</p>
<h4>VST2 entry point</h4>
<p>The VST3 <span class="caps">SDK</span> contains a helper wrapper to wrap a VST3 plugin into a VST2 plugin. The main entry point for VST2 is defined in <code>ABSwitchVST2.cpp</code>:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="o">::</span><span class="n">AudioEffect</span> <span class="o">*</span><span class="nf">createEffectInstance</span><span class="p">(</span><span class="n">audioMasterCallback</span> <span class="n">audioMaster</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">Steinberg</span><span class="o">::</span><span class="n">Vst</span><span class="o">::</span><span class="n">Vst2Wrapper</span><span class="o">::</span><span class="n">create</span><span class="p">(</span><span class="n">GetPluginFactory</span><span class="p">(),</span>
<span class="n">pongasoft</span><span class="o">::</span><span class="n">VST</span><span class="o">::</span><span class="n">ABSwitchProcessorUID</span><span class="p">,</span>
<span class="err">'</span><span class="n">TBDx</span><span class="err">'</span><span class="p">,</span>
<span class="n">audioMaster</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure><p>It simply uses the ID of the processor. Note that the VST2 unique ID is set to <code>TBDx</code> as I have not registered one with Steinberg yet…</p>
<div class="info">If you are familiar with VST2, the main entry point is actually the function<br />
<br />
<code>VST_EXPORT AEffect* VSTPluginMain(audioMasterCallback audioMaster);</code><br />
<br />
but the VST3 wrapper classes define this method which internally calls <code>createEffectInstance</code></div>
<h4>VST3 entry point</h4>
<p>For VST3, the main entry point is actually the following C-style export function:<br />
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">IPluginFactory</span><span class="o">*</span> <span class="n">PLUGIN_API</span> <span class="n">GetPluginFactory</span><span class="p">()</span></code></pre></figure></p>
<div class="info">This information is in the VST3 documentation but quite buried… It is on the <span class="caps">VST</span> 3 <span class="caps">API</span> / <span class="caps">VST</span>-MA page (selected in the sidebar) under the section Plug-ins / Module Factory.</div>
<p>Once the host gets access to the factory, it can then call it back to instantiate the processor and controller.</p>
<p>Thankfully the implementation of this method, the factory itself and the class registration is all done via a set of macros (see file <code>ABSwitchVST3.cpp</code>):</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">BEGIN_FACTORY_DEF</span> <span class="p">(</span><span class="s">"pongasoft"</span><span class="p">,</span>
<span class="s">"https://www.pongasoft.com"</span><span class="p">,</span>
<span class="s">"mailto:support@pongasoft.com"</span><span class="p">)</span>
<span class="c1">// ABSwitchProcessor processor</span>
<span class="n">DEF_CLASS2</span> <span class="p">(</span><span class="n">INLINE_UID_FROM_FUID</span><span class="p">(</span><span class="o">::</span><span class="n">pongasoft</span><span class="o">::</span><span class="n">VST</span><span class="o">::</span><span class="n">ABSwitchProcessorUID</span><span class="p">),</span>
<span class="n">PClassInfo</span><span class="o">::</span><span class="n">kManyInstances</span><span class="p">,</span> <span class="c1">// cardinality</span>
<span class="n">kVstAudioEffectClass</span><span class="p">,</span> <span class="c1">// the component category (do not changed this)</span>
<span class="n">stringPluginName</span><span class="p">,</span> <span class="c1">// here the Plug-in name (to be changed)</span>
<span class="n">Vst</span><span class="o">::</span><span class="n">kDistributable</span><span class="p">,</span> <span class="c1">// means that component and controller could be distributed on different computers</span>
<span class="s">"Fx"</span><span class="p">,</span> <span class="c1">// Subcategory for this Plug-in (to be changed)</span>
<span class="n">FULL_VERSION_STR</span><span class="p">,</span> <span class="c1">// Plug-in version (to be changed)</span>
<span class="n">kVstVersionString</span><span class="p">,</span> <span class="c1">// the VST 3 SDK version (do not changed this, use always this define)</span>
<span class="n">pongasoft</span><span class="o">::</span><span class="n">VST</span><span class="o">::</span><span class="n">ABSwitchProcessor</span><span class="o">::</span><span class="n">createInstance</span><span class="p">)</span> <span class="c1">// function pointer called when this component should be instantiated</span>
<span class="c1">// ABSwitchController controller</span>
<span class="n">DEF_CLASS2</span> <span class="p">(</span><span class="n">INLINE_UID_FROM_FUID</span><span class="p">(</span><span class="o">::</span><span class="n">pongasoft</span><span class="o">::</span><span class="n">VST</span><span class="o">::</span><span class="n">ABSwitchControllerUID</span><span class="p">),</span>
<span class="n">PClassInfo</span><span class="o">::</span><span class="n">kManyInstances</span><span class="p">,</span> <span class="c1">// cardinality</span>
<span class="n">kVstComponentControllerClass</span><span class="p">,</span><span class="c1">// the Controller category (do not changed this)</span>
<span class="n">stringPluginName</span>
<span class="s">"Controller"</span><span class="p">,</span> <span class="c1">// controller name (could be the same than component name)</span>
<span class="mi">0</span><span class="p">,</span> <span class="c1">// not used here</span>
<span class="s">""</span><span class="p">,</span> <span class="c1">// not used here</span>
<span class="n">FULL_VERSION_STR</span><span class="p">,</span> <span class="c1">// Plug-in version (to be changed)</span>
<span class="n">kVstVersionString</span><span class="p">,</span> <span class="c1">// the VST 3 SDK version (do not changed this, use always this define)</span>
<span class="n">pongasoft</span><span class="o">::</span><span class="n">VST</span><span class="o">::</span><span class="n">ABSwitchController</span><span class="o">::</span><span class="n">createInstance</span><span class="p">)</span><span class="c1">// function pointer called when this component should be instantiated</span>
<span class="n">END_FACTORY</span>
<span class="c1">// from ABSwitchProcessor.h</span>
<span class="k">class</span> <span class="nc">ABSwitchProcessor</span> <span class="o">:</span> <span class="k">public</span> <span class="n">AudioEffect</span> <span class="p">{</span>
<span class="nl">public:</span>
<span class="c1">// .....</span>
<span class="k">static</span> <span class="n">FUnknown</span> <span class="o">*</span><span class="n">createInstance</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span> <span class="cm">/*context*/</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="n">IAudioProcessor</span> <span class="o">*</span><span class="p">)</span> <span class="k">new</span> <span class="n">ABSwitchProcessor</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// .....</span>
<span class="p">}</span>
<span class="c1">// from ABSwitchController.h</span>
<span class="k">class</span> <span class="nc">ABSwitchController</span> <span class="o">:</span> <span class="k">public</span> <span class="n">EditController</span> <span class="p">{</span>
<span class="nl">public:</span>
<span class="c1">// .....</span>
<span class="k">static</span> <span class="n">FUnknown</span> <span class="o">*</span><span class="n">createInstance</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span> <span class="cm">/*context*/</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="n">IEditController</span> <span class="o">*</span><span class="p">)</span> <span class="k">new</span> <span class="n">ABSwitchController</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// .....</span>
<span class="p">}</span></code></pre></figure><p>The macro uses the IDs previously created as well as the factory methods (<code>createInstance</code>) from the processor and controller.</p>
<h4>Attaching the controller to the processor</h4>
<p>Finally, the controller gets attached to the processor via its ID. See file <code>ABSwitchProcessor.cpp</code>:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">ABSwitchProcessor</span><span class="o">::</span><span class="n">ABSwitchProcessor</span><span class="p">()</span> <span class="o">:</span> <span class="n">AudioEffect</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">setControllerClass</span><span class="p">(</span><span class="n">ABSwitchControllerUID</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure><h3>The processor</h3>
<p>The processor (defined in <code>ABSwitchProcessor.h</code>) needs to implement both <code>IComponent</code> and <code>IAudioProcessor</code> as per the documentation. The <span class="caps">SDK</span> class <code>AudioEffect</code> implements the basic methods and is used as a starting point for the processor.</p>
<h4>Initializing the processor</h4>
<p>During the initialization phase of the processor, we define the inputs and outputs of the plugin (see file <code>ABSwitchProcessor.cpp</code>):</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">tresult</span> <span class="n">PLUGIN_API</span> <span class="n">ABSwitchProcessor</span><span class="o">::</span><span class="n">initialize</span><span class="p">(</span><span class="n">FUnknown</span> <span class="o">*</span><span class="n">context</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">tresult</span> <span class="n">result</span> <span class="o">=</span> <span class="n">AudioEffect</span><span class="o">::</span><span class="n">initialize</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">result</span> <span class="o">!=</span> <span class="n">kResultOk</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">//---create Audio In/Out buses------</span>
<span class="n">addAudioInput</span><span class="p">(</span><span class="n">STR16</span> <span class="p">(</span><span class="s">"Stereo In"</span><span class="p">),</span> <span class="n">SpeakerArr</span><span class="o">::</span><span class="n">kStereo</span><span class="p">);</span>
<span class="n">addAudioOutput</span><span class="p">(</span><span class="n">STR16</span> <span class="p">(</span><span class="s">"Stereo Out"</span><span class="p">),</span> <span class="n">SpeakerArr</span><span class="o">::</span><span class="n">kStereo</span><span class="p">);</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure><div class="info">In order for the processor to declare that it can handle 64 bits processing, the method <code>canProcessSampleSize</code> needs to be overridden (see file <code>ABSwitchProcessor.cpp</code>).</div>
<h4>The actual processing</h4>
<p>The actual audio processing takes place in this method (see file <code>ABSwitchProcessor.cpp</code>):</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">tresult</span> <span class="n">PLUGIN_API</span> <span class="n">ABSwitchProcessor</span><span class="o">::</span><span class="n">process</span><span class="p">(</span><span class="n">ProcessData</span> <span class="o">&</span><span class="n">data</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ... see source code</span>
<span class="p">}</span></code></pre></figure><div class="info">The code is mostly copied from the <em>again</em> sample provided with the <span class="caps">SDK</span>.</div>
<p>A few points:</p>
<ul>
<li>all the info necessary is provided in the <code>data</code> parameter</li>
<li><code>data.inputs[0]</code> refers to the Stereo In pair defined during initialization</li>
<li><code>data.outputs[0]</code> refers to the Stereo Out pair defined during initialization</li>
<li><code>in[0]</code> represents the first channel of the Stereo In pair (the left channel). It is an array of samples of size <code>data.numSamples</code>. <code>in[1]</code> represents the other channel (right).</li>
<li><code>out[0]</code> represents the first channel of the Stereo Out pair (the left channel). It is an array of samples of size <code>data.numSamples</code>. <code>out[1]</code> represents the other channel (right).</li>
<li>the <em>type</em> of a sample (ex: <code>in[0][0]</code> is the first sample of the left input pair) is determined by <code>data.symbolicSampleSize</code>. Note how the code uses a templated function (defined in <code>ABSwitchProcess.h</code>) so as not to repeat the code for 32 or 64 bits.</li>
</ul>
<div class="info">The code is generic and doesn’t care how many channels are defined for input or output: it simply multiplies every input sample by 0.5 to generate an output sample.</div>
<h3>The controller</h3>
<p>The controller (defined in <code>ABSwitchController.h</code>) needs to implement <code>IEditController</code> as per the documentation. The <span class="caps">SDK</span> class <code>EditController</code> implements the basic methods and is used as a starting point for this plugin.</p>
<h4>Creating the main view</h4>
<p>The controller main method is quite simple and returns an instance of <code>VSTGUI::VST3Editor</code>:</p>
<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="n">IPlugView</span> <span class="o">*</span><span class="n">ABSwitchController</span><span class="o">::</span><span class="n">createView</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">name</span> <span class="o">&&</span> <span class="n">strcmp</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">ViewType</span><span class="o">::</span><span class="n">kEditor</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">VSTGUI</span><span class="o">::</span><span class="n">VST3Editor</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="s">"view"</span><span class="p">,</span> <span class="n">fXmlFile</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure><p>This class will read the provided xml file to build the <span class="caps">GUI</span> (it is empty so it will render a black square…). This class has an editing mode which allows you to create the UI on the fly with an editor (by right clicking in the UI)! The documentation (section <span class="caps">VSTGUI</span> 4 / Inline UI Editing for VST3 (<span class="caps">WYSIWYG</span>)) explains the various steps to make it happen (seems complicated), but in my experience it is already built-in in the makefiles so it is quite easy:</p>
<figure class="highlight"><pre><code class="language-cmake" data-lang="cmake"><span class="c1"># from AddVST3Library.cmake</span>
<span class="nb">target_compile_definitions</span><span class="p">(</span><span class="si">${</span><span class="nv">target</span><span class="si">}</span> PUBLIC $<$<CONFIG:Debug>:VSTGUI_LIVE_EDITING=1><span class="p">)</span></code></pre></figure><p>As long as you compile in <code>Debug</code> mode the editor is enabled. Simple.</p>
<p>Although you can start your <span class="caps">DAW</span> to load the plugin, the <span class="caps">SDK</span> comes with a simple <code>editorhost</code> application (similar to the <code>validator</code> application (see Part 1)) which lets you load the plugin and edit it:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># you may need to compile this app separately</span>
./vst3-sdk.369/Debug/bin/editorhost.app/Contents/MacOS/editorhost ./vst-ab-switch/Debug/VST3/pongasoft_ABSwitch.vst3</code></pre></figure><h3>What we have so far</h3>
<p>At this stage we have a fully functioning VST3 and VST2 plugin which simply removes 3dB from the input signal (so we can verify that the plugin gets called properly) and has a blank UI with editing built-in.</p>
<h3>Next</h3>
<p>Now that we have a fully functioning plugin which does something trivial, let’s build the UI and add the toggle to switch between input A and input B. This is the point of <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/17/VST-development-notes-part3/">Part 3</a>.</p>
<p><em>Last edited: 2018/03/17</em></p>VST (3.6.9) Development Notes - Part 12018-03-12T00:00:00-07:00https://www.pongasoft.com/blog/yan/vst/2018/03/12/VST-development-notes<p>As I am learning about <span class="caps">VST</span> development I wanted to share my notes, first as a way to document my own understanding for the future and second as a way to help others following a similar path.</p>
<div class="warning">This is not meant to be generic for all platforms and is only focusing on macOS.</div>
<h2>Configuration</h2>
<p>At the time of this writing (March 2018) I am using:</p>
<ul>
<li>Hardware: MacBook Pro (Mid 2014) / 2.8Ghz Intel Core i7 / 16GB</li>
<li>macOS High Sierra 10.13.3</li>
<li>Xcode 9.2</li>
<li>CLion 2017.3.3</li>
</ul>
<h2>Notes</h2>
<h3>Install</h3>
<p>Version <span class="caps">VST</span> 3 / 369 (vstsdk369_01_03_2018_build_132) downloaded from <a href="https://download.steinberg.net/sdk_downloads/vstsdk369_01_03_2018_build_132.zip">Steinberg</a> (shasum 256 => <code>7c6c2a5f0bcbf8a7a0d6a42b782f0d3c00ec8eafa4226bbf2f5554e8cd764964</code>).</p>
<p>After unpacking, run</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">./copy_vst2_to_vst3_sdk.sh</code></pre></figure><p>to make sure that we can build VST2 wrappers as well (VST3 is very poorly supported, ex: Maschine by Native Instruments or Reason by Propellerhead do not support it)</p>
<h3>Documentation</h3>
<p>Documentation starts with <code>VST3_SDK/index.html</code> and is not really beginner friendly.</p>
<h3>Building the <span class="caps">SDK</span> and examples</h3>
<p>The VST3 <span class="caps">SDK</span> uses <code>cmake</code> as its built tool. This lets you build the project on various platforms as well as use Xcode or Visual Studio (altough I do not recommend this approach if you want to build cross plaform). The section <em>“How to use cmake for Building <span class="caps">VST</span> 3 Plug-in”</em> describes the various steps.</p>
<div class="info">New in version 3.6.9, the ability to build your own plugins. There is actually a brand new documentation page explaining how to do that.</div>
<p>I investigated 2 ways of building the <span class="caps">SDK</span> and examples: one with Xcode and one with CLion/Makefiles</p>
<h4>Building the <span class="caps">SDK</span>, samples and helloworld (with Xcode)</h4>
<h5>Generate Xcode project</h5>
<ul>
<li>Run <code>cmake-gui</code> (version 3.9.4 installed from <a href="https://cmake.org/download/">CMake download</a>) from anywhere.</li>
<li>In the <code>Where is the source code:</code> field, point to the <code>VST3_SDK</code> folder.</li>
<li>In the <code>Where to build the binaries:</code> field, point to any folder you want</li>
<li>Click <code>Configure</code> (and approve creation of the output folder if it does not exist).</li>
<li>Select <code>Xcode</code> for the <em>generator</em> for this project.</li>
<li>Once the configuration completes, the Name/Value section will have some default values. Check the <code>SMTG_CREATE_VST2_VERSION</code> option because we want to build VST2 plugins as well. Note how the <code>SMTG_MYPLUGINS_SRC_PATH</code> is pointing to the <code>my_plugins</code> folder contained with the distribution, but <strong>oustide</strong> the actual <span class="caps">SDK</span>. More on this later.</li>
<li>Click <code>Generate</code></li>
</ul>
<h5>Build the again example</h5>
<div class="info">All this is relative to the <code>Where to build the binaries:</code> folder previously set.</div>
<ul>
<li>Under the output folder previously selected there is now a Xcode project called <code>vstsdk</code>. Double click to open in Xcode.</li>
<li>In the <code>Scheme</code> selection dropdown (on the right of the buttons that look like play & stop), select <code>again / My Mac</code></li>
<li>Now select menu <code>Product/Build</code> to build <code>again</code> plugin</li>
<li>The result of the build will be stored under <code>VST3/Debug/again.vst3</code></li>
<li>As a side effect of this build, some libraries were generated (under <code>lib/Debug</code>) and a validator tool (<code>bin/Debug/validator</code>)</li>
<li>Run the validator tool:</li>
</ul>
<figure class="highlight"><pre><code class="language-text" data-lang="text">./bin/Debug/validator VST3/Debug/again.vst3
...
* Scanning classes...
Factory Info:
vendor = Steinberg Media Technologies
url = http://www.steinberg.net
email = mailto:info@steinberg.de
Class Info 0:
name = AGain VST3
category = Audio Module Class
cid = 84E8DE5F92554F5396FAE4133C935A18
Class Info 1:
name = AGain VST3Controller
category = Component Controller Class
cid = D39D5B65D7AF42FA843F4AC841EB04F0
Class Info 2:
name = AGain SideChain VST3
category = Audio Module Class
cid = 41347FD6FED64094AFBB12B7DBA1D441
...
-------------------------------------------------------------
Result: 78 tests passed, 0 tests failed
-------------------------------------------------------------</code></pre></figure><div class="info">Note that the <code>validator</code> tool is actually run automatically part of the build (it is an option in the cmake build file which is ON by default).</div>
<ul>
<li>Copy the plugin as a VST2 plugin for local testing<br />
<figure class="highlight"><pre><code class="language-text" data-lang="text">cp -r VST3/Debug/again.vst3 ~/Library/Audio/Plug-Ins/VST/again.vst</code></pre></figure></li>
</ul>
<div class="info">Note that since 3.6.9, a link into the VST3 folder is automatically created as part of the build (variable <code>SMTG_VST3_TARGET_PATH</code> in <code>cmake-gui</code>). </div>
<ul>
<li>Open a <span class="caps">DAW</span> application (Maschine in my example) and assign the plugin to a sound (the plugin is under <code>Steinberg Media Technologies</code>)</li>
</ul>
<h5>Build the helloworld example</h5>
<p>This is easy and just a matter of selecting another Scheme: <code>helloworldWithVSTGUI > Mac</code> under the drowdown and building it.</p>
<div class="warning">This example is <strong>not</strong> a VST2 plugin!</div>
<h4>Building the <span class="caps">SDK</span>, samples and helloworld (on the <span class="caps">CLI</span>)</h4>
<p>The reason why I do not like to use Xcode (or Visual Studio) is that the source of truth is the <code>CMakeLists.txt</code> file. If you generate an Xcode project, then the Xcode project becomes the source of truth: if you add source files to the project or resources (like images), then they only get added to the Xcode project. So you then would have to backport your changes to the <code>CMakeLists.txt</code> in order to compile on a different platform.</p>
<div class="warning"><span class="caps">SDK</span> 3.6.9 is actually broken when it comes to command line (see <a href="https://sdk.steinberg.net/viewtopic.php?f=4&t=532">post</a>). A workaround is to disable the validator to run automatically.</div>
<h5>Generate the project</h5>
<p>The first step is to generate the project (this assumes that the sdk has been installed under <code>/Applications/VST_SDK.369</code>):</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nb">mkdir</span> <span class="nt">-p</span> /tmp/vst369.sdk/Debug
<span class="nb">cd</span> /tmp/vst369.sdk/Debug
<span class="c"># enable VST2 and disable automatic run of validator (workaround)</span>
cmake <span class="nt">-DSMTG_RUN_VST_VALIDATOR</span><span class="o">=</span>OFF <span class="nt">-DSMTG_CREATE_VST2_VERSION</span><span class="o">=</span>ON /Applications/VST_SDK.369/VST3_SDK</code></pre></figure><h5>Build the again sample</h5>
<p>After running the previous command, you can run this commands to generate the <code>again</code> sample:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">cmake <span class="nt">--build</span> <span class="nb">.</span> <span class="nt">--target</span> again</code></pre></figure><p>Since the validator was disabled, let’s build it first:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">cmake <span class="nt">--build</span> <span class="nb">.</span> <span class="nt">--target</span> validator</code></pre></figure><p>And run it to verify that the <code>again</code> sample was built properly:</p>
<figure class="highlight"><pre><code class="language-bash" data-lang="bash">./bin/validator VST3/again.vst3
<span class="k">*</span> Loading module...
/tmp/vst369.sdk/Debug/VST3/again.vst3
<span class="k">*</span> Scanning classes...
Factory Info:
vendor <span class="o">=</span> Steinberg Media Technologies
url <span class="o">=</span> http://www.steinberg.net
email <span class="o">=</span> mailto:info@steinberg.de
Class Info 0:
name <span class="o">=</span> AGain VST3
category <span class="o">=</span> Audio Module Class
cid <span class="o">=</span> 84E8DE5F92554F5396FAE4133C935A18
Class Info 1:
name <span class="o">=</span> AGain VST3Controller
category <span class="o">=</span> Component Controller Class
cid <span class="o">=</span> D39D5B65D7AF42FA843F4AC841EB04F0
Class Info 2:
name <span class="o">=</span> AGain SideChain VST3
category <span class="o">=</span> Audio Module Class
cid <span class="o">=</span> 41347FD6FED64094AFBB12B7DBA1D441
...
<span class="nt">-------------------------------------------------------------</span>
Result: 78 tests passed, 0 tests failed
<span class="nt">-------------------------------------------------------------</span></code></pre></figure><div class="warning">The <span class="caps">SDK</span> is also broken when it comes to building a VST2 plugin on the command line! The reason being that the C function <code>VSTPluginMain</code> is <strong>not</strong> exported properly as proven by this output</div>
<figure class="highlight"><pre><code class="language-text" data-lang="text">mrswatson64 --display-info -p VST3/again.vst3
- 00000000 000000 MrsWatson version 0.9.8 initialized, build 20150122
- 00000000 000000 Plugin 'VST3/again.vst3' is of type VST2.x
- 00000000 000000 Opening VST2.x plugin 'VST3/again.vst3'
E 00000000 000005 Couldn't get a pointer to plugin's main()
E 00000000 000005 Could not load VST2.x plugin '/Volumes/Vault/tmp/blog/VST3/again.vst3'
E 00000000 000005 Plugin 'VST3/again.vst3' could not be opened
E 00000000 000005 Plugin 'VST3/again.vst3' could not be added to the chain
E 00000000 000005 Plugin chain could not be constructed, exiting</code></pre></figure><p>In order to fix this issue you need to modify the <span class="caps">SDK</span> :(</p>
<ul>
<li>add a file <code>public.sdk/source/main/macexport_vst2.exp</code> with the following content (exporting the VST3 specific symbols + the VST2 specific one to make the plugin work either as a VST3 or VST2 plugin)<br />
<figure class="highlight"><pre><code class="language-text" data-lang="text">_GetPluginFactory
_bundleEntry
_bundleExit
_VSTPluginMain</code></pre></figure></li>
</ul>
<ul>
<li>change the <code>again/CMakeLists.txt</code> section to use it</li>
</ul>
<figure class="highlight"><pre><code class="language-cmake" data-lang="cmake"><span class="nb">if</span> <span class="p">(</span>SMTG_CREATE_VST2_VERSION<span class="p">)</span>
<span class="nb">message</span><span class="p">(</span>STATUS <span class="s2">"SMTG_CREATE_VST2_VERSION is set for </span><span class="si">${</span><span class="nv">target</span><span class="si">}</span><span class="s2">. A VST 2 version of the plug-in will be created (just rename the generated file from .vst3 to .vst)."</span><span class="p">)</span>
<span class="nb">if</span><span class="p">(</span>MAC<span class="p">)</span>
<span class="c1"># fix missing VSTPluginMain symbol when also building VST 2 version</span>
<span class="nf">smtg_set_exported_symbols</span><span class="p">(</span><span class="si">${</span><span class="nv">target</span><span class="si">}</span> <span class="s2">"</span><span class="si">${</span><span class="nv">SDK_ROOT</span><span class="si">}</span><span class="s2">/public.sdk/source/main/macexport_vst2.exp"</span><span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>
<span class="nb">if</span> <span class="p">(</span>WIN<span class="p">)</span>
<span class="nb">add_definitions</span><span class="p">(</span>-D_CRT_SECURE_NO_WARNINGS<span class="p">)</span>
<span class="nb">endif</span><span class="p">()</span>
<span class="nb">endif</span><span class="p">()</span></code></pre></figure><p>Then you need to regenerate the Makfiles (first <code>cmake</code> command) and then rebuild <code>again</code>.</p>
<h3>Building your own <span class="caps">VST</span> plugin(s)</h3>
<p>I spent a lot of time trying to figure out how to build my own <span class="caps">VST</span> plugin since I started with 3.6.8 which did not provide any means or instructions. This has changed with 3.6.9 which now provides an example plugins folder (<code>my_plugins</code>) and the necessary CMake changes in order to build an <em>outside the <span class="caps">SDK</span></em> project.</p>
<p>Although this is a serious improvement since 3.6.8, I am still not thrilled with the end result:</p>
<ul>
<li>instead of having your project depends on the <span class="caps">SDK</span>, it is the other way around: you create a project and then you tell the <span class="caps">SDK</span> (via <code>SMTG_VST3_TARGET_PATH</code>) where your project is located. This makes it very hard to work with CLion for example, because the root of the project is always the <span class="caps">SDK</span> not your project (you are still building vstsdk, not MyPlugin).</li>
<li>the build location of your project is hardcoded to be <code>build</code> in the source tree of your project</li>
<li>also note that none of the <em>helloworld</em> examples provided use VST2 (you can use the <code>again</code> sample for that).</li>
<li>the <span class="caps">SDK</span> is actually broken in a couple of places when not using Xcode (validator and VST2)</li>
</ul>
<p>For this reason I created a project which addresses the shorcomings of the <span class="caps">SDK</span> and is a fully self contained project: the idea is that the <code>CMakeLists.txt</code> of the project <em>includes</em> (a modified version of) the one coming with the <span class="caps">SDK</span> and <em>relies</em> on its location (<code>VST3_SDK_ROOT</code>). The full source code is available on github <a href="https://github.com/pongasoft/vst3-again-sample">vst3-again-sample</a></p>
<div class="info">Note that this project includes:<br />
<br />
* the proper fix for VST2 so there is no need to patch the <span class="caps">SDK</span><br />
* fix to the validator issue</div>
<p>It is probably not the best solution but that is the best I could do. It does achieve the fact that it is a self contained, properly version controlled project, which depends on the <span class="caps">SDK</span>. I just wished Steinberg was distributing the <span class="caps">SDK</span> with a native/tested/bullet proof way to do this.</p>
<h3>Using CLion</h3>
<p><a href="https://www.jetbrains.com/clion/">Clion</a> can actually work directly with CMake thus making the process more straightforward and cleaner as you are only dealing with the original project: CLion shows the original project and use CMake directly, so as a result if you add a resource to the project it will be added to the CMake world.</p>
<p>Using the <code>vst3-again-sample</code> as an example</p>
<ul>
<li>simply open the project with CLion. Initially, CLion tries to build the project and it will fail because it does not know where the <span class="caps">SDK</span> root is located</li>
<li>under the menu <code>Preferences / Build,Execution,Deployment / CMake</code> add the <code>CMake options</code>: <br />
<figure class="highlight"><pre><code class="language-text" data-lang="text">-DVST3_SDK_ROOT=/Applications/VST_SDK.369/VST3_SDK -DSMTG_CREATE_VST2_VERSION=ON</code></pre></figure></li>
<li>Note that you can change the <code>Generation path:</code> so that it is outside the source folder (which I personally prefer)</li>
<li>Select the <code>again | Debug</code> configuration (similar to Xcode scheme) and select the menu item <code>Run / Build</code></li>
<li>The outcome will be under <code>VST3</code> (relative to <code>Generation Path</code>)</li>
</ul>
<p>Testing with <code>validator</code> and deploying to a <span class="caps">DAW</span> are the same as the Xcode section with the difference that there is no <code>Debug</code> subfolders (if you use the default <code>Generation Path</code>, the <code>debug</code> concept is part of the directory name).</p>
<div class="info">Note that the <code>validator</code> tool is actually run automatically part of the build (it is an option in the cmake build file which is ON by default).</div>
<h3>Next</h3>
<p>Now that we know how to build any <span class="caps">VST</span> 3 plugin (as well as <span class="caps">VST</span> 2) while depending on the <span class="caps">SDK</span>, it is time to build your own. This is the point of <a href="https://www.pongasoft.com/blog/yan/vst/2018/03/14/VST-development-notes-part2/">Part 2</a>.</p>
<p><em>Last edited: 2018/03/14</em></p>How to control CueMix FX volume with MIDI2014-04-24T00:00:00-07:00https://www.pongasoft.com/blog/yan/music/2014/04/24/how-to-control-CueMixFX-volume-with-MIDI<p>I have a <span class="caps">MOTU</span> 828mk3 hardware interface hooked up to my computer sitting in a rack a few feet away from me. Controlling the volume knob is not very practical because of its location (and size… I wished <span class="caps">MOTU</span> would design a bigger volume button!). I also have several <span class="caps">MIDI</span> controllers connected to my computer and I wanted to use one of those to control the volume. The issue is that out of the box, <span class="caps">MOTU</span> only supports a very small set of <span class="caps">MIDI</span> controllers (all Mackie branded). I was still able to make it work using <a href="http://en.wikipedia.org/wiki/Open_Sound_Control"><span class="caps">OSC</span></a> and here is how I did it.</p>
<h2>The final setup</h2>
<p>Let’s start with the final big picture:</p>
<p><img src="https://www.pongasoft.com/blog/yan/resource/images/2014-04-24/final-setup.png" alt="final setup" border="0" width="100%" style=""/></p>
<p>The various pieces:</p>
<ul>
<li>my hardware <span class="caps">MIDI</span> controller is a Maschine <span class="caps">MKII</span> and I use the big knob in the master section (including push for mute!)</li>
<li>the computer runs <a href="http://www.osculator.net/">OSCulator</a> which is the glue that converts from <span class="caps">MIDI</span> to <span class="caps">OSC</span> and vice-versa</li>
<li>the computer also runs CueMix FX which is the software that comes with the <span class="caps">MOTU</span> hardware interface (unfortunately this software must be running as well in order for the system to work)</li>
<li>my hardware interface is a <span class="caps">MOTU</span> 828mk3</li>
</ul>
<div class="warning"><img src="https://www.pongasoft.com/blog/yan/resource/warning_48x48.png" alt="warning" border="0" width="21" height="21" />I would strongly suggest you experiment without any sound being played by your interface. Making mistakes in the setup could result in the volume jumping to max which is probably not very good for your speakers or ears…</div>
<h2>Starting with OSCulator</h2>
<p>After you install OSCulator, simply turn the knob/fader on you hardware <span class="caps">MIDI</span> controller and it will automatically show up in the OSCulator interface. In the case of the Maschine controller I ended up with the following 2 <span class="caps">MIDI</span> messages:</p>
<ul>
<li><code>/midi/cc101/1</code>: which is the <span class="caps">MIDI</span> message sent when I turn the knob => this will control the volume</li>
<li><code>/midi/cc102/1</code>: which is the <span class="caps">MIDI</span> message sent when I push the knob => this will control mute/unmute</li>
</ul>
<h2>Setting up CueMix FX</h2>
<p>In CueMix FX, select the menu <strong>Control Surfaces/Configure <span class="caps">OSC</span> Devices</strong>. Then click the little <code>+</code> icon and in the <strong>Endpoint:</strong> section select the OSCulator entry (make sure to <b>not</b> select the <span class="caps">MOTU</span> endpoint otherwise you create a loop). In the <strong>Layout Specification</strong>, there should be an entry already pre-populated (<strong>TouchOSC-iPad</strong>). If not you can simply click <strong>Add</strong> and load the file that you can get from the <a href="http://www.motu.com/download/download_matching_downloads.html?download_id=221"><span class="caps">MOTU</span> website</a> .</p>
<h2>Setting up the wiring</h2>
<p>Now the only thing that needs to be done is configure OSCulator to translate the <span class="caps">MIDI</span> messages into <span class="caps">OSC</span> messages and vice versa. The <span class="caps">OSC</span> message that you are interested in is <code>/dev/0/0/mon</code> which controls the monitor level. This is what my configuration looks like:</p>
<h3>OSCulator routing parameters</h3>
<p><img src="https://www.pongasoft.com/blog/yan/resource/images/2014-04-24/OSC-routing-parameters.png" alt="parameters" border="0" width="100%" style=""/></p>
<ul>
<li>the first rewrite address is used when the <span class="caps">MIDI</span> controller sends a value (note that OSCulator automatically converts the 0-127 <span class="caps">MIDI</span> value to 0.0-1.0 float value required for <span class="caps">OSC</span>): it is sent as the first parameter of the <span class="caps">OSC</span> <code>/dev/0/0/mon</code> message</li>
<li>the second rewrite address sends the (float) value 0 (which turns down the volume to 0) and is used for mute</li>
<li>the third rewrite address sends the content of variable 0 (which restores the volume to its prior level) and is used for mute</li>
</ul>
<h3>OSCulator routing</h3>
<p><img src="https://www.pongasoft.com/blog/yan/resource/images/2014-04-24/OSCulator-routing.png" alt="routing" border="0" width="100%" style=""/></p>
<ul>
<li>The first entry shows that on receiving the message <code>/dev/0/0/mon</code> (which is a message sent by the <span class="caps">MOTU</span> interface if you tweak the knob directly on the hardware interface or in the CueMix FX application!), then the message is passed along to the <span class="caps">MIDI</span> controller (<span class="caps">MIDI</span> event CC 101) as well as stored in <code>variable(0)</code> (to implement mute). Note that the advantage of the knob I selected on the Maschine controller is that it is an infinitely turning knob so its value can be changed easily.</li>
<li>The second entry shows that on receiving the message <code>/midi/cc101/1</code> (which happens when the knob on the <span class="caps">MIDI</span> controller is turned), then an <span class="caps">OSC</span> message is routed to CueMIX FX as well as stored in <code>variable(0)</code> (to implement mute).</li>
<li>The last entry shows that on receiving the message <code>/midi/cc102/1</code> (which happens when the knob on the <span class="caps">MIDI</span> controller is pushed), an <span class="caps">OSC</span> message is routed to CueMIX FX with a value 0.0 or <code>variable(0)</code> depending on the input value (hi/lo). This achieves mute.</li>
</ul>
<h2>Conclusion</h2>
<p>I have achieved what I wanted to do in the first place. I wished I did not have to run 2 pieces of software in order to do this. During my experiment I have noticed that if the CueMix FX interface is not visible for a while, then it stops responding to <span class="caps">OSC</span> messages. Bringing it back visible instantly fixes the issue. I am still investigating what the problem could be.</p>How to fix IncompatibleClassChangerError with groovy on jdk72013-03-31T00:00:00-07:00https://www.pongasoft.com/blog/yan/java/2013/03/31/how-to-fix-incompatibleclasschangeerror-with-groovy-on-jdk7<p>While working on having the latest version of <a href="http://linkedin.github.com/glu/docs/latest/html/index.html">glu</a> compile with jdk1.6 and run with either jdk1.6 or jdk1.7 I ran into the following error raised by groovy:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">Caused by: java.lang.IncompatibleClassChangeError: the number of constructors
during runtime and compile time for java.lang.RuntimeException do not match.
Expected 4 but got 5 </code></pre></figure><div class="info"><img src="https://www.pongasoft.com/blog/yan/resource/info_48x48.png" alt="info_48x48.png" border="0" width="21" height="21" />The code in this blog has been updated on 2013/05/13 to include a fix for the fact that the workaround would not <em>stick</em> due to weak references.</div>
<h2>The issue</h2>
<p>The issue gets triggered when running any groovy code compiled with jdk1.6 and running with jdk1.7 whenever you are throwing an exception that has been written in groovy (meaning your exception is defined in a <code>.groovy</code> file).</p>
<h2>The source of the problem</h2>
<p>This exception is triggered by the following section in the <a href="https://github.com/groovy/groovy-core/blob/GROOVY_2_0_7/src/main/groovy/lang/MetaClassImpl.java#L1405">groovy source code</a></p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="kd">public</span> <span class="kt">int</span> <span class="nf">selectConstructorAndTransformArguments</span><span class="o">(</span><span class="kt">int</span> <span class="n">numberOfConstructors</span><span class="o">,</span> <span class="n">Object</span><span class="o">[]</span> <span class="n">arguments</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//TODO: that is just a quick prototype, not the real thing!</span>
<span class="k">if</span> <span class="o">(</span><span class="n">numberOfConstructors</span> <span class="o">!=</span> <span class="n">constructors</span><span class="o">.</span><span class="na">size</span><span class="o">())</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IncompatibleClassChangeError</span><span class="o">(</span><span class="s2">"the number of constructors during runtime and compile time for "</span> <span class="o">+</span>
<span class="k">this</span><span class="o">.</span><span class="na">theClass</span><span class="o">.</span><span class="na">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s2">" do not match. Expected "</span> <span class="o">+</span> <span class="n">numberOfConstructors</span> <span class="o">+</span> <span class="s2">" but got "</span> <span class="o">+</span> <span class="n">constructors</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
<span class="o">}</span>
<span class="c1">// ... clipped remainder of the method...</span>
<span class="o">}</span></code></pre></figure><p>This is pretty frustrating as the comment seems to indicate that this was added as a quick prototype. Also the <code>numberOfConstructors</code> variable is not used in the remainder of the method!</p>
<p>In java 1.7, there is one more constructor that has been added to the <code>java.lang.Exception</code> class (a first in many years!). So at runtime, the previous code simply fails and throws <code>IncompatibleClassChangeError</code>.</p>
<h2>The solution(s)</h2>
<h3>Compile and run under jdk1.7</h3>
<p>If you don’t need the backward compatibility, you can always (re)compile your code with jdk1.7 and run with jdk1.7.</p>
<p>The problem here is that if you use any groovy library compiled with jdk1.6 (or earlier), then you will have the same problem.</p>
<h3>Changing your groovy exceptions into java exceptions</h3>
<p>Doing a quick search on the Internet, I realized I was not the only one and found this <a href="http://blog.proxerd.pl/article/how-to-fix-incompatibleclasschangeerror-for-your-groovy-projects-running-on-jdk7">solution</a>.</p>
<p>This solution does indeed work and if you can change all your exceptions that were groovy exceptions into java exceptions then the problem does go away.</p>
<p>The problem is that in my case, it was a lot of changes in several different projects.</p>
<p>Also you have the same issue as in the previous section: if you use any groovy library compiled with jdk1.6 (or earlier), then you will have the same problem.</p>
<h3>Removing the “extra” constructor at runtime</h3>
<p>I came up with a different approach which <em>simply</em> removes the extra constructor added by jdk1.7 at runtime. The reasoning behind this is, since I am compiling with jdk1.6 there is no way I can use this extra constructor in my code since it did not exist!</p>
<p>So here is what I did:</p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="kd">static</span> <span class="kt">void</span> <span class="nf">installWorkaround</span><span class="o">()</span>
<span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">getJavaVersionAsDouble</span><span class="o">()</span> <span class="o">>=</span> <span class="mf">1.7</span><span class="o">)</span>
<span class="o">{</span>
<span class="kt">def</span> <span class="n">metaClass</span> <span class="o">=</span> <span class="n">Exception</span><span class="o">.</span><span class="na">metaClass</span>
<span class="k">if</span><span class="o">(</span><span class="n">metaClass</span><span class="o">.</span><span class="na">hasProperty</span><span class="o">(</span><span class="s2">"__gluExceptionJdk17Workaround"</span><span class="o">))</span>
<span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span> <span class="s2">"Running with jdk1.7: groovy Exception workaround already installed"</span>
<span class="o">}</span>
<span class="k">else</span>
<span class="o">{</span>
<span class="kt">def</span> <span class="n">field</span> <span class="o">=</span> <span class="n">MetaClassImpl</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getDeclaredField</span><span class="o">(</span><span class="s1">'constructors'</span><span class="o">)</span>
<span class="n">field</span><span class="o">.</span><span class="na">setAccessible</span><span class="o">(</span><span class="kc">true</span><span class="o">)</span>
<span class="kt">def</span> <span class="n">constructors</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">metaClass</span><span class="o">)</span>
<span class="kt">int</span> <span class="n">jdk7ConstructorIdx</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>
<span class="n">constructors</span><span class="o">.</span><span class="na">array</span><span class="o">.</span><span class="na">eachWithIndex</span> <span class="o">{</span> <span class="n">elt</span><span class="o">,</span> <span class="n">idx</span> <span class="o">-></span>
<span class="k">if</span><span class="o">(</span><span class="n">elt</span><span class="o">.</span><span class="na">cachedConstructor</span><span class="o">.</span><span class="na">parameterTypes</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="mi">4</span><span class="o">)</span>
<span class="n">jdk7ConstructorIdx</span> <span class="o">=</span> <span class="n">idx</span>
<span class="o">}</span>
<span class="k">if</span><span class="o">(</span><span class="n">jdk7ConstructorIdx</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">)</span>
<span class="o">{</span>
<span class="n">constructors</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">jdk7ConstructorIdx</span><span class="o">)</span>
<span class="c1">// this has 2 purposes:</span>
<span class="c1">// 1) allowing to check the workaround</span>
<span class="c1">// 2) making sure that the metaclass is no longer a weak reference, hence is not garbage collected!</span>
<span class="n">metaClass</span><span class="o">.</span><span class="na">__gluExceptionJdk17Workaround</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">log</span><span class="o">.</span><span class="na">info</span> <span class="s2">"Running with jdk1.7: installed groovy Exception workaround"</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// making sure the workaround is in place...</span>
<span class="k">if</span><span class="o">(!</span><span class="k">new</span> <span class="n">Exception</span><span class="o">().</span><span class="na">__gluExceptionJdk17Workaround</span><span class="o">)</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s2">"groovy exception workaround not set properly!!!"</span><span class="o">)</span>
<span class="o">}</span>
<span class="k">else</span>
<span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">debug</span> <span class="s2">"Running with jdk1.6: non workaround necessary"</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure><p>This code modifies the metaclass of the <code>java.lang.Exception</code> class to remove the constructor added by jdk1.7. Note that this code is pretty safe to call many times but is recommended to be called as early as possible when you start your groovy application.</p>
<p>From my own testing this works very well and the original <code>IncompatibleClassChangeError</code> is no longer thrown. I have asked for feedback on the <a href="http://groovy.329449.n5.nabble.com/jdk7-and-IncompatibleClassChangeError-for-exception-class-td5714582.html">groovy forum</a> in regards to this solution but have not received any as of this writing.</p>
<p>Note that I am using groovy version 2.0.7 (and this problem remains on the (groovy) trunk as of this writing). I have not tried this solution with any other version of groovy.</p>
<h2>Reference</h2>
<ul>
<li><a href="#" onclick="toggleShowHide('gist-5281627-ExceptionJdk17Workaround');return false;"><code>ExceptionJdk17Workaround</code> full source code</a> from github.<br />
<div id="gist-5281627-ExceptionJdk17Workaround" class="hidden"><script src="https://gist.github.com/ypujante/5281627.js"></script></div></li>
</ul>How to enable log4j output during testing with gradle and IDEA2013-03-11T00:00:00-07:00https://www.pongasoft.com/blog/yan/java/2013/03/11/how-to-enable-log4j-output-during-testing-gradle-idea<p>This post comes from my frustration with the following error messages when running tests:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">log4j:WARN No appenders could be found for logger (<your class name goes here>).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.</code></pre></figure><h2>Background</h2>
<p>For logging purposes, I use log4j.</p>
<p>Most of the services I write, and that are started with a command line interface, are using the <code style="white-space:nowrap;">-Dlog4j.configuration=yyy</code> configuration property to point to the proper log4j configuration. So it works.</p>
<p>The issue is that when you run tests for the same code that uses log4j, the tests are started differently, whether it is in your <span class="caps">IDE</span> (in my case <span class="caps">IDEA</span>) or using the build framework (in my case <code>gradle</code>): they do not have this property provided. So you end up with this log4j warning message. The big issue though is not really the warning message, it is the fact that for some odd reason, log4j just swallows any single output moving forward, which I am not entirely sure is the smartest way to handle it.</p>
<h2>Solution</h2>
<p>Here is what I did to resolve the issue (I am sure there are other ways, but this one works and is pretty simple):</p>
<h3>Create a log4j project</h3>
<p>In your multi-project build (<code>gradle</code>), create a brand new project:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">log4j-test-config/
src/
main/
resources/
log4j.xml
build.gradle</code></pre></figure><p>This is the content of <code>log4j.xml</code>:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="UTF-8" ?></span>
<span class="cp"><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"></span>
<span class="nt"><log4j:configuration</span> <span class="na">xmlns:log4j=</span><span class="s">"http://jakarta.apache.org/log4j/"</span><span class="nt">></span>
<span class="nt"><appender</span> <span class="na">name=</span><span class="s">"ConsoleAppender"</span> <span class="na">class=</span><span class="s">"org.apache.log4j.ConsoleAppender"</span><span class="nt">></span>
<span class="nt"><layout</span> <span class="na">class=</span><span class="s">"org.apache.log4j.PatternLayout"</span><span class="nt">></span>
<span class="nt"><param</span> <span class="na">name=</span><span class="s">"ConversionPattern"</span> <span class="na">value=</span><span class="s">"%d{yyyy/MM/dd HH:mm:ss.SSS} %p [%c{1}] %m%n"</span> <span class="nt">/></span>
<span class="nt"></layout></span>
<span class="nt"></appender></span>
<span class="nt"><root></span>
<span class="nt"><priority</span> <span class="na">value=</span><span class="s">"warn"</span><span class="nt">/></span>
<span class="nt"><appender-ref</span> <span class="na">ref=</span><span class="s">"ConsoleAppender"</span><span class="nt">/></span>
<span class="nt"></root></span>
<span class="nt"></log4j:configuration></span></code></pre></figure><p>and this is the content of <code>build.gradle</code></p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'java'</span>
<span class="n">dependencies</span> <span class="o">{</span>
<span class="n">runtime</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">slf4jLog4j</span>
<span class="n">runtime</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">log4j</span>
<span class="o">}</span></code></pre></figure><h3>Add a test dependency in your other projects</h3>
<p>Thanks to <code>gradle</code> support for multi-project build, in any project in which you have tests, simply add a (<code>testRuntime</code>) dependency to the <code>log4j-test-config</code> project:</p>
<figure class="highlight"><pre><code class="language-groovy" data-lang="groovy"><span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'groovy'</span>
<span class="n">dependencies</span> <span class="o">{</span>
<span class="n">compile</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">linkedinUtilsCore</span>
<span class="n">compile</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">slf4j</span>
<span class="n">compile</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">zookeeper</span>
<span class="n">testCompile</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">groovyTest</span>
<span class="n">testCompile</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">linkedinUtilsGroovy</span>
<span class="n">testCompile</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">junit</span>
<span class="n">testRuntime</span> <span class="n">spec</span><span class="o">.</span><span class="na">external</span><span class="o">.</span><span class="na">slf4jLog4j</span>
<span class="n">testRuntime</span> <span class="nf">project</span><span class="o">(</span><span class="s1">':log4j-test-config'</span><span class="o">)</span> <span class="c1">// this is where the "magic" happens!</span>
<span class="o">}</span></code></pre></figure><h3>Running tests</h3>
<p>Now when you run your tests with <code>gradle</code>, it will automatically pick up the jar file generated by the <code>log4j-test-config</code> project and add it to the classpath => the file <code>log4j.xml</code> will be on the classpath and log4j will be happy.</p>
<p>If you issue <code>gradle cleanIdea idea</code> then gradle will rebuild the <span class="caps">IDEA</span> project and add the proper dependency within the ide itself => when you run your test directly in <span class="caps">IDEA</span>, then the same happens and you get log4j output!</p>
<div class="info"><img src="https://www.pongasoft.com/blog/yan/resource/info_48x48.png" alt="info_48x48.png" border="0" width="21" height="21" />You can simply change <code>warn</code> in the <code>log4j.xml</code> file with any other value during testing/debugging and it will automatically be picked up by <span class="caps">IDEA</span> or gradle…</div>