Compiling two plugins: one as "synth" and another as MIDI effect

I am developing a MIDI effect plugin. I found an issue with Ableton and some other DAWs that they do not support MIDI effects. So to get around this issue, I have both MIDI input, MIDI output, and a dead (empty) audio output (i.e. in JUCE Plugin Characteristic “Plugin is a Synth”). Though unfortunately, Logic cannot access MIDI output when the plugin has audio output, and/or is considered a synth/instrument.

I need to have two versions of my effect available to my users, one MIDI effect plugin version, and one “Instrument”/“Synth” version. How would one go about compiling two, almost identical versions of their JUCE plugin with simply the “Plugin is a Synth” switched?

With cmake I believe you would just need 2 different targets and you could change the slightly different settings for each target.

Something like this would work I think. I just copied the cmake audio plugin example and duplicated it. Changed the second target name to AudioExample2. You could use CMake variables to reduce the duplication

# Example Audio Plugin CMakeLists.txt

cmake_minimum_required(VERSION 3.15)

project(AUDIO_PLUGIN_EXAMPLE VERSION 0.0.1)


add_subdirectory(JUCE)                    # If you've put JUCE in a subdirectory called JUCE



juce_add_plugin(AudioPluginExample
    # VERSION ...                               # Set this if the plugin version is different to the project version
    # ICON_BIG ...                              # ICON_* arguments specify a path to an image file to use as an icon for the Standalone
    # ICON_SMALL ...
    # COMPANY_NAME ...                          # Specify the name of the plugin's author
    IS_SYNTH TRUE                      # Is this a synth or an effect?
    NEEDS_MIDI_INPUT TRUE          # Does the plugin need midi input?
    # NEEDS_MIDI_OUTPUT TRUE/FALSE              # Does the plugin need midi output?
    IS_MIDI_EFFECT FALSE                 # Is this plugin a MIDI effect?
    # EDITOR_WANTS_KEYBOARD_FOCUS TRUE/FALSE    # Does the editor need keyboard focus?
    COPY_PLUGIN_AFTER_BUILD TRUE                # Should the plugin be installed to a default location after building?
    PLUGIN_MANUFACTURER_CODE Juce               # A four-character manufacturer id with at least one upper-case character
    PLUGIN_CODE Dem0                            # A unique four-character plugin id with at least one upper-case character
    FORMATS  VST3                               # The formats to build. Other valid formats are: AAX Unity VST AU AUv3
    PRODUCT_NAME "Audio Plugin Example")        # The name of the final executable, which can differ from the target name


target_sources(AudioPluginExample PRIVATE
    PluginEditor.cpp
    PluginProcessor.cpp)

target_compile_definitions(AudioPluginExample
    PUBLIC
    # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them.
    JUCE_WEB_BROWSER=0  # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call
    JUCE_USE_CURL=0     # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call
    JUCE_VST3_CAN_REPLACE_VST2=0)


target_link_libraries(AudioPluginExample PRIVATE
    # AudioPluginData           # If we'd created a binary data target, we'd link to it here
    juce::juce_audio_utils)

juce_add_plugin(AudioPluginExample2
        # VERSION ...                               # Set this if the plugin version is different to the project version
        # ICON_BIG ...                              # ICON_* arguments specify a path to an image file to use as an icon for the Standalone
        # ICON_SMALL ...
        # COMPANY_NAME ...                          # Specify the name of the plugin's author
        IS_SYNTH FALSE                       # Is this a synth or an effect?
        NEEDS_MIDI_INPUT TRUE             # Does the plugin need midi input?
        NEEDS_MIDI_OUTPUT TRUE           # Does the plugin need midi output?
        IS_MIDI_EFFECT TRUE                 # Is this plugin a MIDI effect?
        # EDITOR_WANTS_KEYBOARD_FOCUS TRUE/FALSE    # Does the editor need keyboard focus?
        COPY_PLUGIN_AFTER_BUILD TRUE                # Should the plugin be installed to a default location after building?
        PLUGIN_MANUFACTURER_CODE Juce               # A four-character manufacturer id with at least one upper-case character
        PLUGIN_CODE Dem0                            # A unique four-character plugin id with at least one upper-case character
        FORMATS  VST3                               # The formats to build. Other valid formats are: AAX Unity VST AU AUv3
        PRODUCT_NAME "Audio Plugin Example")        # The name of the final executable, which can differ from the target name

target_sources(AudioPluginExample2 PRIVATE
        PluginEditor.cpp
        PluginProcessor.cpp)

target_compile_definitions(AudioPluginExample2
        PUBLIC
        # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them.
        JUCE_WEB_BROWSER=0  # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call
        JUCE_USE_CURL=0     # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call
        JUCE_VST3_CAN_REPLACE_VST2=0)

target_link_libraries(AudioPluginExample2 PRIVATE
        # AudioPluginData           # If we'd created a binary data target, we'd link to it here
        juce::juce_audio_utils)

Woah, this is crazy cool! Is there any materials that I should look at for beginning compiling JUCE plugins with CMake?

There is the juce CMake API docs. Then there is also the juce CMake examples with examples for plugins, console, and GUI applications. Honestly juce CMake api does most everything for you, you just have to add your sources and link the libraries and you are good to go. Its not that hard to get up and running

For CMake in general I found this youtube series helpful.

This modern cmake page was helpful as well, and helped me get started using googletest to do unit testing

You could simplify that snippet I posted above quite a bit using a CMake variable for your source files that way you wouldnt have to duplicate those. Could do the same for any settings that were the same across each plugin. Cmake is very powerful and I really enjoy using it. Takes a bit to get used to essentially programming the build instead of using a gui like the projucer.

Rather than duplicating the bulk of the plugin setup, I’d recommend writing a CMake function containing all the common configuration, perhaps passing any differing characteristics as plugin arguments:

cmake_minimum_required(VERSION 3.15)

project(AUDIO_PLUGIN_EXAMPLE VERSION 0.0.1)

function(my_add_custom_plugin_target target_name is_synth)
    juce_add_plugin("${target_name}"
        IS_SYNTH "${is_synth}"
        # other settings...
        PLUGIN_MANUFACTURER_CODE Juce
        PLUGIN_CODE Dem0
        FORMATS AU VST3 Standalone
        PRODUCT_NAME "Audio Plugin Example")

    target_sources("${target_name}" PRIVATE
        PluginEditor.cpp
        PluginProcessor.cpp)

    target_compile_definitions("${target_name}"
        PUBLIC
        JUCE_WEB_BROWSER=0
        JUCE_USE_CURL=0
        JUCE_VST3_CAN_REPLACE_VST2=0)

    target_link_libraries("${target_name}" PRIVATE
        juce::juce_audio_utils)
endfunction()

my_add_custom_target(synth_plugin TRUE)
my_add_custom_target(midi_plugin FALSE)
5 Likes

This looks fantastic! Thank you!

So when compiling with CMake, is the project configuration set in the Projucer file not used? Does all things like “Plugin Characteristics” and the sorts have to be defined in the juce_add_plugin definition in the CMakeLists file?

If you use cmake you dont use the projucer at all anymore. You dont need a .jucer file. The root CMakeLists.txt file essentially replaces the .jucer file.

Anything that was once done with the projucer would instead be done using a command in the CMakeLists.txt file and the JUCE CMake API. The API docs provide a run down of the kinds of settings that can be supplied. If you see a setting in the projucer, you can generally search the juce CMake api docs and find a corresponding setting there

Okay gotcha!

So then all the sources have to listed out manually, and/or using special CMake commands to include all .cpp files in subdirectories, right? I have many source files in subfolders so it’d be a pain to do manually. My guess is there some CMake function to grab all .cpp files recursively in a directory.

I believe you can use GLOBs, but that generally is frowned upon. Some IDEs allow you to add sources to a cmake target whenever you add a new file and what not (CLion is tightly integrated with CMake, and its what I like to use, although its not free). Im not sure how most people handle it, Ive just been manually adding the files to my CMake target as I go along. I dont know if CMake provides any way to recursively add .cpp files automatically besides using globs (which again is frowned upon. Reasons why can be found by searching CMake and glob in google)

I’m having big troubles trying to get the JUCE directory added to CMake. I have my JUCE installation in a directory outside of my project directory. Is there a way I can do add_subdirectory to the JUCE path that’s outside the project directory?

I figured it out using this StackOverflow post. One has to set a JUCE build directory as a second argument to add_subdirectory.

When I set the juce_add_plugin options IS_SYNTH to FALSE, and IS_MIDI_EFFECT to TRUE, my “MIDI Effect” target shows up (and works) in Ableton and Bitwig as an Audio effect! How is one suppose to output a pure MIDI Effect plugin via CMake?

This may be an issue with the hosts themselves - I know Live doesn’t support third-party midi-effects natively. The closest you can get is to create a plugin with midi ins and outs, and to route the midi out into a different track. There are official instructions here.

It’s not an issue with the hosts, I’m sure of it. Ableton actually throws an error message when trying to load a MIDI effect plugin (instead of loading it as an audio effect), and Bitwig has my plugin listed as “Audio FX” instead of “Note FX” in the plugin manager. I played around with all possible VST/AU Category options, but those have no effect on it being a MIDI effect, nor it showing as Note FX in Bitwig.

I checked all the preprocessor directives in my plugin processor with a new plugin processor file from JUCE 6, and they are all in the right places.

I believe its a bug with JUCE’s CMake module. When setting IS_MIDI_EFFECT TRUE in juce_add_plugin and JucePlugin_IsMidiEffect=1 in target_compile_definitions, Cmake complains about redefinition of preprocessor JucePlugin_IsMidiEffect. Even though I explicitly set IS_MIDI_EFFECT TRUE, one of the defines declared for JucePlugin_IsMidiEffect is set to 0. Even when I set IS_MIDI_EFFECT FALSE, its 0.

Here is the full error:

In file included from <built-in>:400<command line>:
:39:9: <command line>:warning39: :9: warning'JucePlugin_IsMidiEffect':  macro redefined
      'JucePlugin_IsMidiEffect'[-Wmacro-redefined] macro
 redefined
      [-Wmacro-redefined]
#define JucePlugin_IsMidiEffect 1#define JucePlugin_IsMidiEffect 1

        ^        ^

<command line>:<command line>38::38:99::  notenote: : previousprevious  definitiondefinition  isis  herehere

#define JucePlugin_IsMidiEffect 0#define JucePlugin_IsMidiEffect 0

        ^        ^

It shows by the line numbers that the define setting it to 1 is after the one setting it to 0, so why is it still not becoming a MIDI Effect? Is the first define the define compilers use?

When compiling with Projucer or CMake, I cannot get my plugin to compile as a pure MIDI effect anymore. Can others concur? Did JUCE 6 break something in regards to compiling VST 2 plugins as MIDI effects?

I’m struggling to reproduce the issue you’re seeing here. To test, I’ve copied the AudioPlugin CMake example to a new folder, and replaced the CMakeLists with the following contents:

cmake_minimum_required(VERSION 3.15)

project(AUDIO_PLUGIN_EXAMPLE VERSION 0.0.1)

add_subdirectory(../juce-dev JUCE)

juce_set_vst2_sdk_path("<path to SDK>")

juce_add_plugin(AudioPluginExample
    IS_SYNTH                    TRUE
    NEEDS_MIDI_INPUT            TRUE
    NEEDS_MIDI_OUTPUT           TRUE
    IS_MIDI_EFFECT              TRUE
    EDITOR_WANTS_KEYBOARD_FOCUS TRUE
    PLUGIN_MANUFACTURER_CODE    Juce
    PLUGIN_CODE                 Dem0
    FORMATS                     AU VST VST3 Standalone
    PRODUCT_NAME                "MIDI Effect Example")

target_sources(AudioPluginExample PRIVATE
    PluginEditor.cpp
    PluginProcessor.cpp)

target_compile_definitions(AudioPluginExample
    PUBLIC
        JUCE_WEB_BROWSER=0
        JUCE_USE_CURL=0
        JUCE_VST3_CAN_REPLACE_VST2=0)

target_link_libraries(AudioPluginExample PRIVATE
    juce::juce_audio_utils
    juce::juce_recommended_config_flags
    juce::juce_recommended_lto_flags
    juce::juce_recommended_warning_flags)

The resulting project builds with no warnings, whether IS_SYNTH is set to TRUE or FALSE. When I build with --verbose, I can see that -DJucePlugin_IsMidiEffect=1 is passed to the compiler, without having to add it to target_compile_definitions. I’m using macOS 10.15.6, Xcode 11.6, and CMake 3.18.1. I can then load the VST2 plugin in Reaper and pass MIDI through it. Ableton will load the VST2 with no problems, but fails to load the VST3 (adding an unused output audio bus fixes that issue though).

Regarding categorisation in Bitwig, has this ever worked for you? In Reaper and Live, the plugin categories only seem to distinguish between instrument and non-instrument plugins, so it wouldn’t surprise me if that were the case in Bitwig too - perhaps only the built-in midi effects will appear as ‘note effects’. I’m not very familiar with Bitwig, though, so this is only a theory.

Can you share your CMakeLists (ideally) or describe how yours differs from the example I posted above? It would also be useful to know which compiler and CMake version you’re using.

Alright I copied your config for the AudioPlugin CMake example and this is my results:

When I load the VST2 plugin in Ableton, it loads as an instrument (with side-chaining capabilities, for some reason), not a MIDI effect. Yes, VST3 MIDI effect just doesn’t load in general. I now remember that my memory of Ableton not loading MIDI effects was indeed with VST3, not VST2.

Ableton will not even display the AU plugin as a plugin to choose from when IS_SYNTH and IS_MIDI_EFFECT set to TRUE. It will show up when IS_SYNTH is TRUE and IS_MIDI_EFFECT is FALSE (the plugin configured as an “Instrument”).

Yes, I found this can be done by simply setting IS_SYNTH to TRUE and IS_MIDI_EFFECT to FALSE. This is also true for VST2.

With your configuration, Bitwig considers the plugin as an instrument. If IS_SYNTH is set to FALSE and IS_MIDI_EFFECT is TRUE, it shows up as “Audio FX”. If IS_SYNTH is TRUE regardless, the plugin is shown as “Audio FX” and “Instrument”! That’s unfortunate these DAWs don’t classify the plugins like you’d expect.

Are you familiar if Logic works with and classifies properly MIDI Effect AU plugins? I know Logic cannot get MIDI output from AU plugins, and cannot load any VST plugin in general.

Logic does handle MIDI effect plugins as you’d expect, yes. They have their own category, and can be inserted in a special slot before an instrument plugin.

1 Like

Does JUCE make options such a IS_MIDI_EFFECT accessible as preprocessor definitions? (It doesn’t look like it). Or is that something we’d need to add into our CMakeLists.txt?

The preprocessor defs that are defined by the Projucer/JuceHeader for plugin projects should also be available in a CMake build. In the case of IS_MIDI_EFFECT, the corresponding preprocessor definition is JucePlugin_IsMidiEffect.

If you build with --verbose (e.g. cmake --build build-folder --target my_target_plugin --verbose) you should be able to see the full set of definitions that are passed (there’s a lot of them!).