{"id":99,"date":"2023-09-23T16:33:06","date_gmt":"2023-09-23T23:33:06","guid":{"rendered":"https:\/\/rabiensoftware.com\/?p=99"},"modified":"2023-09-23T19:07:20","modified_gmt":"2023-09-24T02:07:20","slug":"creating-an-effect-plugin-with-gin","status":"publish","type":"post","link":"https:\/\/rabiensoftware.com\/index.php\/2023\/09\/23\/creating-an-effect-plugin-with-gin\/","title":{"rendered":"Creating an Effect plugin with Gin"},"content":{"rendered":"<h3>Introduction<\/h3>\n<p>Creating a plugin with Gin &amp; JUCE is a lot fast than with JUCE alone. A lot a boiler plate is handled for you, which lets you focus on getting your DSP working. This tutorial explains the code in the Gin example <a href=\"https:\/\/github.com\/FigBug\/Gin\/tree\/master\/examples\/Effect\">Effect<\/a> plugin which is a simple gain and pan plugin. A basic understanding of JUCE plugins is assumed. Start by forking and renaming the <a href=\"https:\/\/github.com\/figbug\/ginplugin\">GinPlugin<\/a> repo. Edit the <a href=\"https:\/\/github.com\/FigBug\/GinPlugin\/blob\/main\/CMakeLists.txt\">CMakeLists.txt<\/a> with you plugin name and ID and the <a href=\"https:\/\/github.com\/FigBug\/GinPlugin\/blob\/main\/ci\/build.sh\">build.sh<\/a> script with the name of your plugin. The repo is already setup to build a VST3, AU, LV2 plugin on Windows, macOS and Linux. Notarization on macOS is supported by adding your Apple developer account ID and signing keys as repository secrets.<\/p>\n<h3>First Steps<\/h3>\n<p>Subclass your processor from <strong>gin::Processor<\/strong> instead of <strong>juce::AudioProcessor<\/strong> and your editor from <strong>gin::ProcessorEditor<\/strong> instead of <strong>juce::AudioProcessorEditor<\/strong>.<\/p>\n<p>The <strong>gin::ProcessorOptions\u00a0<\/strong>class allows you to specify options for your plugin. By default it is filled with sensible defaults based on various JUCE #defines. At the end of your constructor you also need to call <strong>init()<\/strong>. A minimal plugin should look like this:<\/p>\n<pre class=\"p2\"><span class=\"s1\"><b>static<\/b><\/span> gin::ProcessorOptions <span class=\"s2\">getOpts<\/span>()\r\n{\r\n<span class=\"Apple-converted-space\">  \u00a0 <\/span>gin::ProcessorOptions opts;\r\n    \/\/ Fill in your custom options here\r\n<span class=\"Apple-converted-space\">  \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span> opts;\r\n}\r\n\r\nEffectAudioProcessor<span class=\"s3\">::<\/span><span class=\"s2\">EffectAudioProcessor<\/span><span class=\"s3\">()<\/span>\r\n<span class=\"Apple-converted-space\">  \u00a0 <\/span>: gin::Processor (<span class=\"s1\"><b>false<\/b><\/span>, getOpts())\r\n{\r\n    \/\/ call init and the end of your constructor\r\n<span class=\"Apple-converted-space\">  \u00a0 <\/span>init();\r\n}<\/pre>\n<p>Gin automatically handles loading and saving state, loading presets etc, all that is requires of your processor is to create the parameters and implement the <strong>processBlock<\/strong> function.<\/p>\n<h3>Adding Parameters<\/h3>\n<p>Gin supports internal parameters and external parameters. Internal parameters are not visible to the host, and should be used for things that have no need to be automated. They are also useful for ranges that might change in size, like number of filter types. External parameters are exposed to the host and can be optionally automatable or not.<\/p>\n<p>For the vol &amp; pan effect we will add 4 parameters, 2 internal (pan law &amp; polarity invert) and 2 external (vol &amp; pan)<\/p>\n<pre class=\"p1\"><span class=\"Apple-converted-space\"> \u00a0  <\/span>levelParam<span class=\"Apple-converted-space\">\u00a0 <\/span>= addExtParam (<span class=\"s1\">\"level\"<\/span>,<span class=\"Apple-converted-space\">\u00a0 <\/span><span class=\"s1\">\"Level\"<\/span>,<span class=\"Apple-converted-space\">\u00a0 <\/span>{}, <span class=\"s1\">\"dB\"<\/span>, {-<span class=\"s2\">100.0f<\/span>, <span class=\"s2\">5.0f<\/span>, <span class=\"s2\">0.0f<\/span>, <span class=\"s2\">5.0f<\/span>}, <span class=\"s2\">0.0f<\/span>, <span class=\"s2\">0.05f<\/span>);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>panParam<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>= addExtParam (<span class=\"s1\">\"pan\"<\/span>,<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\">\"Pan\"<\/span>,<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>{}, {}, <span class=\"Apple-converted-space\">\u00a0 <\/span>{-<span class=\"s2\">1.0f<\/span>, <span class=\"Apple-converted-space\">\u00a0 <\/span><span class=\"s2\">1.0f<\/span>, <span class=\"s2\">0.0f<\/span>, <span class=\"s2\">1.0<\/span>},<span class=\"Apple-converted-space\">\u00a0 <\/span><span class=\"s2\">0.0f<\/span>, <span class=\"s2\">0.05f<\/span>,<span class=\"Apple-converted-space\">\u00a0 <\/span>panTextFunction);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>modeParam <span class=\"Apple-converted-space\">\u00a0 <\/span>= addIntParam (<span class=\"s1\">\"mode\"<\/span>, <span class=\"Apple-converted-space\">\u00a0 <\/span><span class=\"s1\">\"Mode\"<\/span>, <span class=\"Apple-converted-space\">\u00a0 <\/span>{}, {}, <span class=\"Apple-converted-space\">\u00a0 <\/span>{ <span class=\"s2\">0.0f<\/span>, <span class=\"Apple-converted-space\">\u00a0 <\/span><span class=\"s2\">1.0f<\/span>, <span class=\"s2\">0.0f<\/span>, <span class=\"s2\">1.0<\/span>},<span class=\"Apple-converted-space\">\u00a0 <\/span><span class=\"s2\">0.0f<\/span>, <span class=\"s2\">0.0f<\/span>, <span class=\"Apple-converted-space\">\u00a0 <\/span>modeTextFunction);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>invertParam = addIntParam (<span class=\"s1\">\"invert\"<\/span>, <span class=\"s1\">\"Invert\"<\/span>, {}, {}, <span class=\"Apple-converted-space\">\u00a0 <\/span>{ <span class=\"s2\">0.0f<\/span>, <span class=\"Apple-converted-space\">\u00a0 <\/span><span class=\"s2\">1.0f<\/span>, <span class=\"s2\">0.0f<\/span>, <span class=\"s2\">1.0<\/span>},<span class=\"Apple-converted-space\">\u00a0 <\/span><span class=\"s2\">0.0f<\/span>, <span class=\"s2\">0.0f<\/span>, <span class=\"Apple-converted-space\">\u00a0 <\/span>onOffTextFunction);<\/pre>\n<p>For each parameter, provide and id, name, short name (optional), label (optional), range, default, smoothing time (optional) and value to text function (optional). A smoothing time prevents zipper noise when the user adjusts a parameter. Setting a smoothing time of 0 disables smoothing, leaving it up to your algorithm to provide smoothing itself.<\/p>\n<p>Gin parameters have up to 3 values associated with them. A value which is always between 0 and 1. A user value, which is displayed in the user interface and a processing value which is used by the processing algorithm. In some cases all these values may be in the 0 to 1 range, but for the level param they are all different. The user value is in dB and is in the range -100 db to +5 db and the processing value is the gain and is in the range 0 to ~3.1.<\/p>\n<p>To provide a processing value for a parameter, set the <strong>\u00a0gin::Parameter::conversionFunction<\/strong>.<\/p>\n<pre class=\"p1\">levelParam-&gt;conversionFunction = [] (<span class=\"s1\"><b>float<\/b><\/span> in) { <span class=\"s1\"><b>return<\/b><\/span> juce::Decibels::decibelsToGain (in); };<\/pre>\n<p>Text functions convert the user value into a string. If one isn&#8217;t provided a simple number with up to 3 decimal places is show. Text functions are used to convert mod and invert into strings and to add L, C or R to the pan value.<\/p>\n<pre class=\"p1\"><span class=\"s1\"><b>static<\/b><\/span> juce::String <span class=\"s2\">modeTextFunction<\/span> (<span class=\"s1\"><b>const<\/b><\/span> gin::Parameter&amp;, <span class=\"s1\"><b>float<\/b><\/span> v)\r\n{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>if<\/b><\/span> ((<span class=\"s1\"><b>int<\/b><\/span> (v)) == <span class=\"s3\">0<\/span>)\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span> <span class=\"s4\">\"Linear\"<\/span>;\r\n<b>    return<\/b> <span class=\"s4\">\"3dB\"<\/span><span class=\"s5\">;<\/span>\r\n}\r\n\r\n<span class=\"s1\"><b>static<\/b><\/span> juce::String <span class=\"s2\">onOffTextFunction<\/span> (<span class=\"s1\"><b>const<\/b><\/span> gin::Parameter&amp;, <span class=\"s1\"><b>float<\/b><\/span> v)\r\n{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>if<\/b><\/span> (<span class=\"s1\"><b>int<\/b><\/span> (v) == <span class=\"s3\">0<\/span>)\r\n<b>        return<\/b> <span class=\"s4\">\"On\"<\/span><span class=\"s5\">;<\/span>\r\n<b>    return<\/b> <span class=\"s4\">\"Off\"<\/span><span class=\"s5\">;<\/span>\r\n}\r\n\r\n<span class=\"s1\"><b>static<\/b><\/span> juce::String <span class=\"s2\">panTextFunction<\/span> (<span class=\"s1\"><b>const<\/b><\/span> gin::Parameter&amp;, <span class=\"s1\"><b>float<\/b><\/span> v)\r\n{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>if<\/b><\/span> (juce::String (v, <span class=\"s3\">2<\/span>) == <span class=\"s4\">\"0.00\"<\/span>)\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span> <span class=\"s4\">\"C\"<\/span>;\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>if<\/b><\/span> (v &lt; <span class=\"s3\">0<\/span>)\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span> juce::String (-v, <span class=\"s3\">2<\/span>) + <span class=\"s4\">\"L\"<\/span>;\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span> juce::String (v, <span class=\"s3\">2<\/span>) + <span class=\"s4\">\"R\"<\/span>;\r\n}<\/pre>\n<h3>DSP Code<\/h3>\n<p>The heart of the processing block is this simple function that converts a pan and volume into a left and right gain with two different pan laws.<\/p>\n<pre class=\"p1\"><span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> getGains = [] (<span class=\"s1\"><b>float<\/b><\/span> gain, <span class=\"s1\"><b>float<\/b><\/span> pan, <span class=\"s1\"><b>int<\/b><\/span> mode) -&gt; std::pair&lt;<span class=\"s1\"><b>float<\/b><\/span>, <span class=\"s1\"><b>float<\/b><\/span>&gt;\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>if<\/b><\/span> (mode == <span class=\"s2\">0<\/span>)\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>const<\/b><\/span> <span class=\"s1\"><b>float<\/b><\/span> pv = pan * gain;\r\n<span class=\"Apple-converted-space\">  \u00a0 \u00a0 \u00a0   \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span> { gain - pv, gain + pv };\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>else<\/b><\/span>\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>pan = (pan + <span class=\"s2\">1.0f<\/span>) * <span class=\"s2\">0.5f<\/span>;\r\n\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span>\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>gain * std::sin ((<span class=\"s2\">1.0f<\/span> - pan) * juce::MathConstants&lt;<span class=\"s1\"><b>float<\/b><\/span>&gt;::halfPi),\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>gain * std::sin (pan * juce::MathConstants&lt;<span class=\"s1\"><b>float<\/b><\/span>&gt;::halfPi)\r\n\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span>};\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>}\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>};<\/pre>\n<p>First we will get the parameters that won&#8217;t be smoothed and store them in variables. For the example, we will assume the user is ok with clicks if they change the pan law or polarity, but expect pan and volume to adjust without artifacts. Gin parameters can be accessed as ints or bools, which can save some casting.<\/p>\n<pre class=\"p1\"><span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>const<\/b><\/span> <span class=\"s1\"><b>auto<\/b><\/span> mode <span class=\"Apple-converted-space\">\u00a0 <\/span>= modeParam-&gt;getUserValueInt();\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>const<\/b><\/span> <span class=\"s1\"><b>auto<\/b><\/span> inv<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>= invertParam-&gt;getUserValueBool() ? -<span class=\"s2\">1.0f<\/span> : <span class=\"s2\">1.0f<\/span>;\r\n\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>const<\/b><\/span> <span class=\"s1\"><b>auto<\/b><\/span> numSamps = buffer.getNumSamples();\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> pos = <span class=\"s2\">0<\/span>;<\/pre>\n<p>If no parameters are currently changing, this function can be applied to the entire block with the same input parameters, otherwise they need to change for each sample. First, we check if any parameters are changing and work sample by sample.<\/p>\n<pre class=\"p1\"><span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>while<\/b><\/span> (isSmoothing() &amp;&amp; pos &lt; numSamps)\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> gain = levelParam-&gt;getProcValue (<span class=\"s2\">1<\/span>);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> pan = panParam-&gt;getProcValue (<span class=\"s2\">1<\/span>);\r\n\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> [left, right] = getGains (gain, pan, mode);\r\n\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>buffer.applyGain (<span class=\"s2\">0<\/span>, pos, <span class=\"s2\">1<\/span>, left<span class=\"Apple-converted-space\">\u00a0 <\/span>* inv);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>buffer.applyGain (<span class=\"s2\">1<\/span>, pos, <span class=\"s2\">1<\/span>, right * inv);\r\n<span class=\"Apple-converted-space\"> \u00a0 \u00a0 \u00a0 \u00a0<\/span>\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>pos++;\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>}<\/pre>\n<p>The function <strong>gin::Parameter::getProcValue(int stepSize)<\/strong> returns the next smoothed processing value for the parameter. The loop will continue until the parameters have reached their destination value or there is no more input to process. If there is still more input to process, it can be handled in a block for efficiency.<\/p>\n<pre class=\"p1\"><span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>if<\/b><\/span> (pos &lt; numSamps)\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> todo = numSamps - pos;<span class=\"Apple-converted-space\"> \u00a0 \u00a0  <\/span>\r\n\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> gain = levelParam-&gt;getProcValue (todo);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> pan = panParam-&gt;getProcValue (todo);\r\n<span class=\"Apple-converted-space\"> \u00a0 \u00a0 \u00a0 <\/span>\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> [left, right] = getGains (gain, pan, mode);<span class=\"Apple-converted-space\"> \u00a0  <\/span>\r\n\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>buffer.applyGain (<span class=\"s2\">0<\/span>, pos, todo, left<span class=\"Apple-converted-space\">\u00a0 <\/span>* inv);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span>buffer.applyGain (<span class=\"s2\">1<\/span>, pos, todo, right * inv);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>}<\/pre>\n<h3>Creating the Editor<\/h3>\n<p>If your plugin has UI (and it should), create it here. If you want your plugin to resize, wrap your editor in a <strong>gin::ScaledPluginEditor<\/strong>. Also create the plugin itself.<\/p>\n<pre class=\"p2\"><span class=\"s1\"><b>bool<\/b><\/span> EffectAudioProcessor<span class=\"s2\">::<\/span>hasEditor<span class=\"s2\">() <\/span><span class=\"s1\"><b>const<\/b><\/span>\r\n{\r\n<span class=\"s2\"><span class=\"Apple-converted-space\">  \u00a0 <\/span><\/span><b>return<\/b> <b>true<\/b><span class=\"s2\">;<\/span>\r\n}\r\n\r\njuce::AudioProcessorEditor* <span class=\"s3\">EffectAudioProcessor<\/span>::<span class=\"s3\">createEditor<\/span>()\r\n{\r\n<span class=\"Apple-converted-space\">  \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span> <span class=\"s1\"><b>new<\/b><\/span> EffectAudioProcessorEditor (*<span class=\"s1\"><b>this<\/b><\/span>);\r\n}\r\n\r\njuce::AudioProcessor* JUCE_CALLTYPE <span class=\"s3\">createPluginFilter<\/span>()\r\n{\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span><span class=\"s1\"><b>return<\/b><\/span> <span class=\"s1\"><b>new<\/b><\/span> EffectAudioProcessor();\r\n\r\n}<\/pre>\n<h3>UI Code<\/h3>\n<p>Gin uses a grid layout for components. When creating the UI first specify the size of the grid, in this case 6 x 1. And then layout the controls on the grid.<\/p>\n<pre class=\"p1\"><span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>setGridSize (<span class=\"s1\">6<\/span>, <span class=\"s1\">1<\/span>);\r\n<span class=\"Apple-converted-space\"> \u00a0 <\/span>\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>addControl (<span class=\"s2\"><b>new<\/b><\/span> gin::Select (p.modeParam), <span class=\"s1\">1<\/span>, <span class=\"s1\">0<\/span>);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>addControl (<span class=\"s2\"><b>new<\/b><\/span> gin::Knob (p.levelParam), <span class=\"s1\">2<\/span>, <span class=\"s1\">0<\/span>);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>addControl (<span class=\"s2\"><b>new<\/b><\/span> gin::Knob (p.panParam, <span class=\"s2\"><b>true<\/b><\/span>), <span class=\"s1\">3<\/span>, <span class=\"s1\">0<\/span>);\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 <\/span>addControl (<span class=\"s2\"><b>new<\/b><\/span> gin::Switch (p.invertParam), <span class=\"s1\">4<\/span>, <span class=\"s1\">0<\/span>);<\/pre>\n<p>Gin provides 3 basic controls. Select, based on a ComboBox. Knob, based on a rotary slider, and Switch, based on a toggle button. Add the to the UI with their grid position and Gin will handle updating them when their parameter changes and deleting them when the editor closes.<\/p>\n<p>The UI will look like this. Preset browser, preset load and save are all built in.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"784\" height=\"384\" class=\"alignnone wp-image-100 size-full\" src=\"https:\/\/rabiensoftware.com\/wp-content\/uploads\/2023\/09\/Pasted-7.png\" srcset=\"https:\/\/rabiensoftware.com\/wp-content\/uploads\/2023\/09\/Pasted-7.png 784w, https:\/\/rabiensoftware.com\/wp-content\/uploads\/2023\/09\/Pasted-7-300x147.png 300w, https:\/\/rabiensoftware.com\/wp-content\/uploads\/2023\/09\/Pasted-7-768x376.png 768w\" sizes=\"auto, (max-width: 784px) 100vw, 784px\" \/><\/p>\n<h3>Next Steps<\/h3>\n<p>If your plugin requires more state than just parameters, it can be saved in <strong>juce::ValueTree gin::Processor::state<\/strong>. Override the functions <strong>stateUpdated<\/strong> and <strong>updateState<\/strong> if you need to copy variables to \/ from the ValueTree.<\/p>\n<p>Create your own look and feel based on <strong>gin::CopperLookAndFeel\u00a0<\/strong>so that your plugins don&#8217;t look like my plugins.<\/p>\n<p>If the grid based layout is too restrictive, see the <strong>gin::Layout<\/strong> class to layout your components with json.<\/p>\n<p>If you have presets, add them to the plugin as binary resources. Then extract them in your constructor if the y don&#8217;t already exist.<\/p>\n<pre class=\"p1\"><span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>auto<\/b><\/span> sz = <span class=\"s2\">0<\/span>;\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>for<\/b><\/span> (<span class=\"s1\"><b>auto<\/b><\/span> i = <span class=\"s2\">0<\/span>; i &lt; <span class=\"s3\">BinaryData<\/span>::<span class=\"s4\">namedResourceListSize<\/span>; i++)\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>if<\/b><\/span> (<span class=\"s3\">juce<\/span>::<span class=\"s3\">String<\/span> (<span class=\"s3\">BinaryData<\/span>::<span class=\"s4\">originalFilenames<\/span>[i]).<span class=\"s4\">endsWith<\/span> (<span class=\"s5\">\".xml\"<\/span>))\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s1\"><b>if<\/b><\/span> (<span class=\"s1\"><b>auto<\/b><\/span> data = <span class=\"s3\">BinaryData<\/span>::<span class=\"s4\">getNamedResource<\/span> (<span class=\"s3\">BinaryData<\/span>::<span class=\"s4\">namedResourceList<\/span>[i], sz))\r\n<span class=\"Apple-converted-space\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span class=\"s4\">extractProgram<\/span> (<span class=\"s3\">BinaryData<\/span>::<span class=\"s4\">originalFilenames<\/span>[i], data, sz);<\/pre>\n<h3>Conclusion<\/h3>\n<p>You now have a full featured plugin with almost no code at all.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Creating a plugin with Gin &amp; JUCE is a lot fast than with JUCE alone. A lot a boiler plate is handled for you, which lets you focus on getting your DSP working. This tutorial explains the code in the Gin example Effect plugin which is a simple gain and pan plugin. A basic&hellip; <br \/> <a class=\"read-more\" href=\"https:\/\/rabiensoftware.com\/index.php\/2023\/09\/23\/creating-an-effect-plugin-with-gin\/\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":100,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,6,7],"tags":[5,4,3],"class_list":["post-99","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dsp","category-gin","category-programming","tag-dsp","tag-gin","tag-juce"],"_links":{"self":[{"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/posts\/99","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/comments?post=99"}],"version-history":[{"count":1,"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/posts\/99\/revisions"}],"predecessor-version":[{"id":101,"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/posts\/99\/revisions\/101"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/media\/100"}],"wp:attachment":[{"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/media?parent=99"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/categories?post=99"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rabiensoftware.com\/index.php\/wp-json\/wp\/v2\/tags?post=99"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}