Skip to content

Core concepts ​

Five ideas carry most of JUCE: threads, components, state, listeners, and ownership. Learn these and the rest of the API reads as variations on a theme.

Threads ​

A JUCE program usually has:

  • The message thread. It exists once per process, runs the OS event loop via MessageManager, and is the only thread allowed to touch Components.[1]
  • The audio thread. It is owned by the audio driver or plug-in host and calls you every few milliseconds with a buffer. If it is late, the listener hears a click.
  • Any background threads you create (Thread, ThreadPool, TimeSliceThread) for file loading, analysis, or network work.

How long does the audio thread have? ​

The driver or host asks for one buffer at a time, and the next request arrives a fixed time later. That duration is the deadline for your whole processBlock(), including everything else in the signal chain.

Each audio callback must return before the next buffer is due, so the buffer duration is the deadline: samples ÷ sample rate. Larger buffers give more headroom and more latency. Computed from that formula, not measured.

Real-time rules for the audio thread ​

Inside processBlock() or getNextAudioBlock(), avoid anything whose running time is unbounded:

AvoidWhyInstead
new, malloc, growing a std::vector or juce::ArrayThe allocator may take a lock or ask the OSAllocate in prepareToPlay()
CriticalSection / std::mutex shared with the GUIThe GUI may hold it while it redrawsstd::atomic, AbstractFifo, or SpinLock with care
File, network, or console I/OCan block for milliseconds or secondsBackground thread plus a FIFO
Calling Component methodsNot thread-safe; asserts in debugPost to the message thread

Moving data between threads ​

A 250 Hz tone at 48 kHz whose gain rises from 0.2 to 1.0. Applied as a jump, the waveform has a discontinuity that is heard as a click (zipper noise when it repeats). A 20 ms linear ramp, as SmoothedValue produces, changes the level without one. Computed, not recorded.

Components ​

A JUCE GUI is a tree of Component objects. Each one is a rectangle that can draw itself, contain children, and respond to input.

cpp
class MainComponent : public juce::Component
{
public:
    MainComponent()
    {
        addAndMakeVisible (button);       // add a child and show it
        button.onClick = [this] { ++clicks; repaint(); };
        setSize (400, 300);
    }

    void paint (juce::Graphics& g) override   // draw yourself
    {
        g.fillAll (juce::Colours::black);
        g.setColour (juce::Colours::white);
        g.drawText ("Clicks: " + juce::String (clicks), getLocalBounds(), juce::Justification::centred);
    }

    void resized() override                    // lay out your children
    {
        button.setBounds (getLocalBounds().removeFromBottom (40).reduced (8));
    }

private:
    juce::TextButton button { "Click me" };
    int clicks = 0;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Key points:

  • paint() draws, resized() lays out. Never lay out in paint(). Call repaint() to request a redraw; it is coalesced and happens later.
  • Children are usually members, so their lifetime is tied to the parent's. The parent does not own children added with addAndMakeVisible().
  • Layout helpers: Rectangle::removeFromTop() and friends for simple slicing, and FlexBox and Grid for CSS-style layouts.[5]
  • Look and feel: built-in widgets (Slider, TextButton, ComboBox…) delegate their drawing to a LookAndFeel object. Subclass LookAndFeel_V4 to restyle a whole app without subclassing every widget.[6]
  • Windows: a top-level component lives in a DocumentWindow or ResizableWindow, which creates the native ComponentPeer.
  • Alternatives: since JUCE 8, a WebBrowserComponent can host an HTML/JS UI, with relay classes such as WebSliderRelay binding it to parameters.[7] juce_opengl lets you render a component with OpenGL.

State ​

JUCE favours one tree of data as the source of truth, observed by everything else.

Listeners and broadcasters ​

JUCE uses the observer pattern everywhere, with a consistent naming scheme:

SourceListener interfaceCallback
ButtonButton::Listener (or the onClick lambda)buttonClicked()
SliderSlider::Listener (or onValueChange)sliderValueChanged()
ValueTreeValueTree::ListenervalueTreePropertyChanged() …
ChangeBroadcasterChangeListenerchangeListenerCallback()
AudioProcessorAudioProcessorListeneraudioProcessorParameterChanged() …
Timer(you subclass it)timerCallback()

ListenerList is the underlying container. It is safe against listeners removing themselves during a callback. Always remove a listener in your destructor if the broadcaster may outlive you.

Ownership and memory ​

JUCE predates widespread C++11, so it has its own smart types. Modern JUCE interoperates with the standard library freely.

  • Owning containers: OwnedArray<T> deletes its elements; std::vector<std::unique_ptr<T>> is equivalent.
  • Shared ownership: ReferenceCountedObject with ReferenceCountedObject::Ptr. This is intrusive, so it is cheap and safe to pass as a raw pointer. ValueTree and Image share reference-counted data internally.[8:1]
  • Weak references: WeakReference<T>, and Component::SafePointer<T>, which becomes null when the component is deleted. Use it in async callbacks.
  • Leak detection: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ClassName) at the end of a class makes it non-copyable, and in debug builds reports any instances still alive at shutdown.
  • jassert: debug-only assertion that breaks into the debugger. JUCE uses thousands of them to flag misuse. When one fires, read the comment next to it.

Strings and text ​

juce::String is a reference-counted string held as UTF-8[9] with a rich API (upToFirstOccurrenceOf, fromFirstOccurrenceOf, formatted…). StringArray, Identifier (interned strings used as fast property keys in ValueTree), and StringRef round out the set. Convert to and from std::string with toStdString() and the String(std::string) constructor.

Sources ​


  1. juce_MessageManager.h (callAsync, message thread) and juce_Component.h. The "only thread allowed to touch Components" rule is the usual JUCE convention; the exact wording is in those headers' comments. ↩︎

  2. juce_AudioProcessorValueTreeState.h: std::atomic<float>* getRawParameterValue (StringRef parameterID) const noexcept. ↩︎

  3. juce_AbstractFifo.h: "lock-free FIFO", "single-reader, single-writer FIFO", "It doesn't actually hold any data itself". ↩︎

  4. juce_SmoothedValue.h. "Zipper noise" is standard audio terminology. ↩︎

  5. juce_Grid.h and juce_FlexBox.h in the same folder. ↩︎

  6. juce_LookAndFeel_V4.h. ↩︎

  7. juce_WebControlRelays.h (WebSliderRelay); CHANGE_LIST.md, Version 8.0.0: "Added support for WebView based UIs". ↩︎

  8. juce_ValueTree.h: "a lightweight reference to a shared data container", optional UndoManager on setters. ↩︎ ↩︎

  9. juce_String.h: "Using a reference-counted internal representation". ↩︎

Personal study notes on JUCE. Not affiliated with or endorsed by the JUCE team.