Skip to content

Architecture ​

JUCE is a stack of layered modules with two runtimes on top: an event-driven GUI on the message thread, and a real-time audio pipeline on the audio thread. Most of what you write plugs into one of these two and passes data safely to the other.

The layer cake ​

Each module declares its dependencies in its main header, so the framework forms a hierarchy with no cycles.[1] Simplified, it looks like this (the interactive module map has every module and edge):

Arrows point from a module to what it depends on. Some things to notice:

  • juce_core depends on nothing.[1:1] It is JUCE's replacement for much of the C++ standard library, plus OS services.
  • juce_events is the hinge. Anything asynchronous (GUI, timers, device change notifications, OSC) needs its message loop.
  • The audio stack does not need the GUI. juce_audio_basics, juce_audio_devices, juce_audio_formats, and juce_dsp never touch juce_graphics. You can build a headless audio tool or server.
  • Plug-in code is split in two. juce_audio_processors_headless holds the AudioProcessor interface, parameters, graph, and format hosting with no GUI dependency. juce_audio_processors adds editors and plug-in scanning UI on top. (The split arrived in JUCE 8.0.11, so older tutorials only mention juce_audio_processors.)[2]
  • juce_audio_utils sits at the top because it glues everything together: device selectors, waveform thumbnails, keyboard widgets, and players.

The two runtimes ​

Message threadAudio thread
Who drives itMessageManager running the OS event loopThe audio driver or plug-in host, every few milliseconds
What runs thereGUI, timers, file dialogs, most listenersprocessBlock(), getNextAudioBlock(), audioDeviceIOCallbackWithContext()
RulesMay block briefly; must not freezeNo locks that can wait, no allocation, no file or network I/O, no GUI calls
Talking to the other sideWrite atomics or push to a FIFORead atomics or pop from a FIFO; never call into Components

The thread rules in this table are general real-time audio practice rather than quotations from JUCE, although JUCE's own APIs are built around them (for example getRawParameterValue(), described as something "a realtime process can read", and the lock-free AbstractFifo).[3] Getting this boundary right is the most important skill in JUCE programming. See Core concepts → Threads for the tools JUCE gives you.

How audio reaches your code ​

There are three common entry points. They share the same idea: something outside your code owns the clock and calls you with a buffer to fill.

1. A standalone audio app ​

AudioAppComponent (in juce_audio_utils) packages this for you. It is a Component and an AudioSource, with its own AudioDeviceManager and AudioSourcePlayer.[4]

2. A plug-in ​

The host owns the clock. A format wrapper in juce_audio_plugin_client translates the host's calls (VST3's process(), AU's render callback, and so on) into AudioProcessor::processBlock(). See Anatomy of a plug-in.

3. A plug-in host or modular app ​

AudioProcessorGraph connects many AudioProcessors (your own or loaded third-party plug-ins) into one. An AudioProcessorPlayer then feeds it from an audio device. This is how extras/AudioPluginHost works.[5]

How the GUI reaches your code ​

  • A top-level Component (usually a DocumentWindow) gets a ComponentPeer, the platform-specific native window.
  • Child components are lightweight: they have no native window. JUCE hit-tests and clips them itself, so the same UI renders identically everywhere.
  • Drawing goes through Graphics, which targets a rendering backend: CoreGraphics, Direct2D, the software renderer, or OpenGL.[6]

How an app starts ​

For a plug-in there is no main(): the host loads your binary and the wrapper calls createPluginFilter(), a function you provide that returns your AudioProcessor.[7]

Where the tools fit ​

See Build systems for details.

Sources ​


  1. The BEGIN_JUCE_MODULE_DECLARATION block in each module's main header, for example juce_audio_devices.h (dependencies: juce_audio_basics, juce_events); format in docs/JUCE Module Format.md. The diagram edges are a simplification: the full generated graph is the module map, built from site/data/modules.json. ↩︎ ↩︎

  2. CHANGE_LIST.md, Version 8.0.11: "Added a new juce_audio_processors_headless module". Its declared dependencies are in juce_audio_processors_headless.h. ↩︎

  3. juce_AudioProcessorValueTreeState.h (getRawParameterValue); juce_AbstractFifo.h ("lock-free FIFO", "single-reader, single-writer"). ↩︎

  4. juce_AudioAppComponent.h: class AudioAppComponent : public Component, public AudioSource. ↩︎

  5. extras/AudioPluginHost/Source/UI/GraphEditorPanel.cpp uses AudioProcessorGraph. ↩︎

  6. Rendering back ends in modules/juce_graphics/contexts and native (CoreGraphics, Direct2D, LowLevelGraphicsSoftwareRenderer); OpenGL is the juce_opengl module. ↩︎

  7. Declared in juce_audio_processors.h as juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter(); and called from juce_CreatePluginFilter.h. ↩︎

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