Here are 33 things I wish were clearly labelled “on the box” when starting out with JUCE. I started this blog post years ago when I started learning and updated it along the way.
There’s a lot of helpful info out there scattered all over the JUCE forum, the Audio Programmer Discord and beyond.
It can be hard to know how to prioritize information when getting started. What is critical? What is nice to know? What are the agreed upon best practices vs. just someone’s esoteric way of doing things?
This assumes you have some basics covered, including:
- Basic C++ principles, such as “don’t profile performance in Debug mode.”
- Basic audio principles, such as “no memory allocation in processBlock.”
Here they are, ordered from n00b to Pro:
The JUCE forum is key
The JUCE docs can be light on examples and gotchas. Historically, the community hasn’t been able to contribute to these docs, so they sometimes aren’t beginner friendly.
Instead, cozy up to the forum and expect to spend hours there. It has community examples of pretty much everything you want to do and 95% of problems you’ll run into getting started. The search is your new best friend.
The tutorials are very good too. They describe mental models and concepts the docs gloss over.
Build and run the Standalone to save time
The standalone is the fastest plugin format to iterate on, in part because it doesn’t have to be started up within a host.
Even if you don’t plan on delivering a standalone, it’s trivial to add a Standalone
JUCE target. In CMake, it’s as easy as adding it to FORMATS
(see pamplejuce as an example). Building this doesn’t require any plugin APIs and is the quickest way to compile and iterate, especially when working on UI.
Bonus tip: To ensure your audio/midi device info and plugin state are saved, be sure to gracefully quit the standalone (close the plugin window, don’t hit stop in the DAW). Thanks The Him!
Save your AudioPluginHost setup
If you are using JUCE’s AudioPluginHost as a test host (like I do!), you can save your “patch” with everything all wired up and all your parameters in place.
File > Save
the AudioPluginHost settings as a .filtergraph
. When your IDE loads the host next time, it will autoload the last saved “patch”.
Bonus points: add an EQ and other metering you might use regularly to the chain.
DBG
will add dropouts and glitches in Debug
DBG
works with strings and allocates. It’s likely you’ll hear issues if you fire too many of them in the audio thread.
Can’t have your cake and eat it too!
New to Xcode? Turn on the console
If you are in Xcode wondering where your DBG
output is:
View > Debug Area > Activate Console
DBG
in your DAW
There are times when you need to debug in a DAW context. You won’t be able to see output from DBG
unless your IDE attaches to your DAW.
In Xcode, “Debug Executable” needs to be selected.
In Clion, you can add a custom “configuration” which points to your DAW.
Debugging in the DAW on Apple Silicon is now more difficult. It requires disabling SIP and manually attaching to a process called AUHostingServiceXPC_arrow
. This process name may change in the future.
AU not showing up in MacOS?
AU plugins only show up in DAWs when they are in one the subfolders (Components
, VST3
, etc) of /Library/Audio/Plugins
and ~/Library/Audio/Plugins
. They must be located there to be recognized.
If they still aren’t scanning, give the AU registrar that manages them a kick:
killall -9 AudioComponentRegistrar
Bonus tip: Anthony Nichols says “set your version number to 0 for debug builds and your plugin will be auto scanned every time.”
AUv3 won’t be registered on macOS until you run the standalone
The AUv3 gets registered when you use the Finder to launch the standalone app which contains the AUv3 appex bundle (i.e. double-click on the standalone app’s icon in the Finder).
Fabian
Make sure you are actually running the latest version of your plugin
Before you spend hours trying to figure out why that latest fix or feature isn’t showing up: Check if your plugin actually got updated.
This can happen for myriad reasons, including:
- You are using CMAKE but didn’t tell JUCE
COPY_PLUGIN_AFTER_BUILD TRUE
injuce_add_plugin
- You might have your DAW open with the synth open while you are building a new version, so the copy fails.
- On some platforms (windows?) you might not have correct permissions for the target folder, so the copy might be failing.
What I do: stick a JUCE label
somewhere with the timestamp and build type:
label.setText (__DATE__ " " __TIME__ " " CMAKE_BUILD_TYPE, dontSendNotification);
Including the build type requires exposing the definition via target_compile_definitions
in your CMakelists.txt:
CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}"
Assume your UI will be the most time consuming thing
This is more of an expectation setter than a practical tip. The hard part of building a real plugin is dealing with UI.
Use melatonin inspector to inspect your component hierarchy and positioning. Or pluginGUIMagic to handle all UI for you.
If your goal is to create a professional looking product, this is where most of your invested time will be.
Use CLion for consistency in dev across platforms
Not a sponsor! But CLion is my best JUCE friend for these reasons:
- Gives me a consistent dev experience on both macOS and Windows
- Syncs IDE settings between my computers and platforms
- Has built in CMake support that makes life easy, picks up changes to CMakeLists.txt
- Has built in Catch2 support and UI for tests
It’s generally a nicer experience than trying to juggle Xcode on Mac and VS Code on windows!
LookAndFeels come before components that use them in member list
The order matters, specifically on destruction.
Members are deleted in the reverse order they are listed in the class. So, you want the look and feel to be deleted after the component using them. In this case, customLookAndFeel
should be declared before myOtherComponent assuming the latter uses the look and feel.
class MyComponent < public juce::Component
{
private:
CustomLookAndFeel customLookAndFeel;
MyOtherComponent myOtherComponent;
}
Pixels in JUCE are logical
When specifying heights and widths in JUCE, you are specifying the number of logical pixels.
They are device and resolution independent. Don’t get them confused with the number of physical pixels in your screen!
TargetConditionals.h file not found on macOS
This can happen after updating Xcode on a CMake project. To resolve, rm -rf
your build directory completely.
fatal error: 'TargetConditionals.h' file not found
#include <TargetConditionals.h> // (needed to find out what platform we're using)
Enable repaint debugging
This macro will flashes any repainted area with random colors.
This is a good first line of defense way to understand jank/glitch problems with component painting. However, it’s only so helpful…
(Put JUCE_ENABLE_REPAINT_DEBUGGING=1
in target_compile_definitions
in your CMakeLists.txt)
If you are on macOS, you might also be interested in Quartz Debug which lets you do similar.
Edit any value live
For fine tuning UI code, JUCE_LIVE_CONSTANT
is your friend.
For example, I find it really helpful to help me tune my shadow blur radii and opacity:
shadows.setRadius (JUCE_LIVE_CONSTANT(blurRadius));
shadows.setColor (colors[i].withAlpha (JUCE_LIVE_CONSTANT (0.7f)));
On recompile, you’ll get a lil 1990s vibes UI which will let you live edit the values.
Your plugin isn’t ready to ship until it passes pluginval
If you haven’t used pluginval yet, be prepared to fix 5 bugs you didn’t know you had.
See my post to get started.
If your plugin UI asserts on re-open, check your listeners
If you are having trouble tracking down the culprit, look for all those addListener
and addChangeListener
calls and make sure you pop their remove
counterparts in the destructor.
Draw with floats, use int for component bounds
Components in JUCE can technically have sub-pixel position/size when actually drawn to scale, transformed, etc.
This means their position and or size may not always be exact integers.
However, component’s bounds are always specified as integer-perfect pixels. Read more and see my post on component drawing.
Someday we’ll get float component bounds in JUCE! I believe.
Use Melatonin inspector to inspect your component hierarchy
If you are guessing at why your component isn’t showing up, what size it might be, etc — you are wasting your time!
I made a tool inspired by Web Inspector / Figma that lets you quickly zoom around, inspect and play with component properties, sizes, and more.
Better JUCE type debugging in your IDE
Jim Credland open sourced an lldb script and natvis config which improves usability when debugging JUCE types such as juce::String
and juce::Identifier
. I added some additional types like juce::var
and juce::ValueTree
.
I also have a lldb script that visualizes the contents of AudioBlocks as sparklines in your IDE.
Use clang-format to stop wasting time formatting code
See my example in Pamplejuce which is derived from Adam Wilson’s in this post. Read how to set up clang-format in CLion.
When you hit the JUCE leak detector, press continue
to find all the leaks
*** Leaked objects detected: 18 instance(s) of class AudioProcessorParameterWithID
JUCE Assertion failure in juce_LeakedObjectDetector.h:92
You hit that first jassert
and think: “why am I randomly leaking this one object?”
Often there are more object types leaking. For example, the first leaked objects reported might be members of a later (or the last) leaked object.
So keep hitting continue
in your debugger to let the program keep going. Hat tip.
Get comfy building your own widgets
If you have a clear picture how your UI should look and behave, in many cases it will take less time to “steal” from the stock widgets (i.e. read the code, grab relevant parts) to build exactly what you need than it is to contort the stock widgets to your will.
The JUCE widgets have a lot of legacy cruft. The widget library hasn’t received much maintenance or attention over the years. It can be tough to create a specific modern UI. It can’t do shadows properly (but Melatonin Blur can!) It wasn’t build for rounded corners.
Your desired customizations could be supported out of the box (for example via setColour
or setBorder
). But my experience is that endless edge cases will drive you to stop bothering with customization.
Start simple: If you want to show some text and don’t need editing, don’t use a Label
. Instead use a very simple g.drawText
call inside of a paint
, etc.
An exception to this rule might be the TextEditor
— this has very sophisticated multi-line text rendering and selection logic. Not a fun wheel to reinvent.
Don’t bother writing LookAndFeels for your custom widgets
LookAndFeels
exist so that JUCE can offers customizable widgets. It enables many different kinds of presentation for a “widget” (such as a Label). This is useful strategy for a framework.
But you aren’t making a framework. If you are, it’s probably just for internal use. So you don’t need to provide an elaborate customization API for your “widget”.
Start simple. Keep all your drawing stuff in paint
. You’ll thank yourself later.
Use a better macro for variable debugging
I wrote this little helper which I find nicer than DBG
because you can quickly inspect multiple variables:
/* D is a little helper to print variables
*
* D(myFloat); // myFloat: 1.89
* D(myFloat, myBoolean) // myFloat: 1.89, myBoolean: 0
*/
#if (DEBUG)
#define D(...) makePrintable (#__VA_ARGS__, __VA_ARGS__)
template <typename Arg1>
void makePrintable (const char* name, Arg1&& arg1)
{
std::cout << name << ": " << arg1 << std::endl;
}
template <typename Arg1, typename... Args>
void makePrintable (const char* names, Arg1&& arg1, Args&&... args)
{
const char* comma = std::strchr (names + 1, ',');
std::cout.write (names, comma - names) << ": " << arg1;
makePrintable (comma, args...);
}
#else
#define D(...) // strings allocate, so no-op in Release
#endif
Statically link the Windows runtime
This should probably be the default in JUCE again. It was changed to dynamic because of an issue long since fixed. Dynamic can cause problems, so ensure static in CMake with:
if (WIN32)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE INTERNAL "")
endif ()
Don’t be scared off by the “Debug” present in the snippet, it snippet selects all configurations, regardless of Debug information.
You can inspect what dlls your binary dynamically links to with the developer command prompt:
dumpbin /dependents <your binary path>
For best performance, treat drawing like you would treat the audio thread
This just means it’s best practice to avoid allocation in paint calls when possible. It’s not always possible. I suggest having juce::Path
or juce::Image
or juce::ColourGradient
objects as member variables so they are allocated once on open, not re-allocated in your paint calls.
If you really wanna dig deep into inspecting component performance, see my module that helps you do just that.
Beware dereferencing tons of atomics per buffer
If you are a calling a bunch of things like gain->load()
per-sample in a tight loop in processBlock
you will pay a performance penalty, see this link.
This only becomes a problem if you are effectively dereferencing atomics hundreds of times in an inner loop. Otherwise don’t worry about it.
You can suspend audio processing
Call suspendProcessing(true)
when you need to do some heavy lifting (like heavy duty preset switching, convolution reallocation) and your callbacks will return silence. Thanks Kunz.
Avoid static memory and singletons in plugins
static
memory can create issues when there are multiple instances of your plugin in a DAW. This is because they could be run within the same process. Or they could be in different processes. You can’t know. You won’t know. It depends on the DAW.
Singletons are static memory too, so also should also be avoided in plugins. In standalone apps, they are fine. In plugins, the process/thread ambiguity makes it hard to reason about how many copies of the singleton you might have. For example, DAWs can decide to sandbox plugins into their own process (for example in Logic on an M1 or AUv3 generally), in which case you would have one singleton for each instance of your plugin.
thread_local
won’t help you either, at least not for audio effects. Some hosts may run an entire channel’s signal chain on one thread. People might have two instances of your plugin on the channel.
Personally, almost every time I tried to use a singleton it was the wrong tool for the job, or I ran into issues.
The solution is boring — manually pass state/data structures down through your components, and to organize that data hierarchy so that classes get the minimum amount of state they need to function.
Don’t take my word for it, take Dave Rowland’s.
parameterChanged
can happen on the audio thread
This one is important. Most people learn the hard way from their app having trouble as it’s unfortunately not properly documented.
Only do trivial things in these parameter callbacks. Yes, both parameterChanged
and parameterValueChanged
. Treat them like you are on the audio thread. Because they can be called from any thread, including the message thread or the audio thread. It depends on the DAW.
Avoid calling repaint
and avoid doing any heavy lifting for your audio stuff.
You also might want to avoid Async updater and Component::postCommandMessage
as posting to the message thread technically isn’t lock free. It’s still the default when using the apvts, so it seems functional enough, but I would vote to stay away from it.
There are a few popular patterns in the community. One is to use a juce::Timer
(they are cheap and fast, you can easily have hundreds of them) to check for a boolean.
class MyComponent : public juce::Component, juce::Timer, juce::AudioProcessorParameter::Listener
{
public:
MyComponent()
{
startTimerHz (60);
}
void timerCallback() override
{
if (recalculate)
repaint();
}
void paint (juce::Graphics& g) override
{
// paint here
}
void parameterValueChanged (int /*parameterIndex*/, float /*newValue*/) override
{
// this boolean will be picked up on next timer run
if (!recalculate)
{
recalculate = true;
}
}
private:
std::atomic<bool> recalculate = false;
};
On JUCE 7.0.6 and above you can use VBlankAttachment
instead of the timer to ensure your component is repainted on the very next frame. That’s what I do.
Leave your tip in the comments
And/or check out the JUCE forum thread for more tips!
Leave a Reply