A JUCE module for embedding Flutter UIs in audio plugins (VST3, AU) and standalone apps.
Status: Experimental — macOS and Windows working.
Example plugin running as a VST3 in REAPER — Flutter UI with real-time audio analysis
Drop a FlutterComponent into your JUCE plugin editor and get a full Flutter UI rendered at 60fps. Works on macOS (Metal/Impeller) and Windows (ANGLE/D3D), in standalone apps and DAW hosts (tested in REAPER).
#include <juce_flutter/juce_flutter.h>
class MyEditor : public juce::AudioProcessorEditor {
juce_flutter::FlutterComponent flutter;
public:
MyEditor(AudioProcessor& p) : AudioProcessorEditor(p) {
flutter.loadFromPluginBundle();
addAndMakeVisible(flutter);
setSize(800, 600);
}
void resized() override { flutter.setBounds(getLocalBounds()); }
};On each platform, Flutter is hosted in a child window that tracks the JUCE component's position:
macOS Windows
JUCE Component (host window) JUCE Component (host window)
└─ NSPanel (borderless) └─ Child HWND
└─ FlutterViewController └─ FlutterDesktopViewController
└─ Metal/Impeller └─ ANGLE/D3D
On macOS, Flutter's Metal/Impeller renderer requires being the contentViewController of an NSWindow, so we create a borderless NSPanel and attach it as a child of the host window. On Windows, a child HWND hosts the FlutterDesktopViewController via standard SetParent() + WS_CHILD.
Engine and view controller creation is deferred until the component is on-screen — creating them during construction blocks the host's run loop. See docs/ARCHITECTURE.md for details.
- macOS: macOS 13+ (arm64)
- Windows: Windows 10+, Visual Studio 2022 with C++ desktop workload
- JUCE 7+ or 8+
- Flutter SDK with desktop support enabled
- CMake 3.22+
The example plugin includes real-time audio analysis (RMS, peak, crest factor, 32-band spectrum, waveform) computed in C++ and visualized in Flutter.
git clone https://github.com/trsonic/juce_flutter.git
cd juce_fluttercd example
./build.sh run standalone # Build + run standalone
./build.sh run reaper # Build + install VST3 + launch REAPER
./build.sh build # Build only
./build.sh install # Install VST3 to ~/Library/Audio/Plug-Ins/VST3/
./build.sh clean # Clean build directorycd example
.\build_win.ps1 run standalone # Build + run standalone
.\build_win.ps1 build # Build only
.\build_win.ps1 install # Install VST3 to %LOCALAPPDATA%\Programs\Common Files\VST3\
.\build_win.ps1 clean # Clean build directoryBoth scripts handle the full pipeline: build the Flutter UI, compile the C++ plugin, and embed all Flutter assets into the app/VST3 bundle.
If you prefer to run CMake directly, build the Flutter UI first:
# macOS
cd example/flutter_ui && flutter build macos --release && cd ../..
# Windows
cd example\flutter_ui && flutter build windows --release && cd ..\..Then configure and build:
# macOS
cd example
cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DFLUTTER_ENGINE_PATH="/path/to/FlutterMacOS.framework"
cmake --build build -j8# Windows
cd example
cmake -B build_win -G "Visual Studio 17 2022" -A x64 `
-DFLUTTER_ENGINE_PATH="C:\flutter\bin\cache\artifacts\engine\windows-x64-release"
cmake --build build_win --config Release -jAfter building, you need to embed Flutter into the bundles. See the build scripts for the exact copy steps, or docs/ARCHITECTURE.md for the expected bundle layouts.
include(FetchContent)
FetchContent_Declare(JUCE
GIT_REPOSITORY https://github.com/juce-framework/JUCE.git
GIT_TAG 8.0.12
GIT_SHALLOW TRUE)
FetchContent_Declare(juce_flutter
GIT_REPOSITORY https://github.com/trsonic/juce_flutter.git
GIT_TAG main)
FetchContent_MakeAvailable(JUCE juce_flutter)
target_link_libraries(MyPlugin PRIVATE juce_flutter)add_subdirectory(path/to/juce_flutter)
target_link_libraries(MyPlugin PRIVATE juce_flutter)You also need to link the Flutter engine. The setup differs per platform:
macOS — link FlutterMacOS.framework and set rpaths:
set(FLUTTER_FW_DIR "/path/to/dir/containing/FlutterMacOS.framework")
target_compile_options(MyPlugin PRIVATE "-F${FLUTTER_FW_DIR}")
target_link_libraries(MyPlugin PRIVATE "-F${FLUTTER_FW_DIR}" "-framework FlutterMacOS")
set_target_properties(MyPlugin_Standalone PROPERTIES
BUILD_RPATH "@executable_path/../Frameworks")
set_target_properties(MyPlugin_VST3 PROPERTIES
BUILD_RPATH "@loader_path/../Frameworks")Windows — link flutter_windows.dll.lib with delay-load and build the C++ client wrapper:
set(FLUTTER_ENGINE_PATH "C:/flutter/bin/cache/artifacts/engine/windows-x64-release")
target_include_directories(MyPlugin PUBLIC "${FLUTTER_ENGINE_PATH}")
target_link_libraries(MyPlugin_VST3 PRIVATE
"${FLUTTER_ENGINE_PATH}/flutter_windows.dll.lib" delayimp.lib)
target_link_options(MyPlugin_VST3 PRIVATE "/DELAYLOAD:flutter_windows.dll")See example/CMakeLists.txt for the full setup including the C++ client wrapper.
Send data from C++ to Flutter:
auto* bridge = flutter.getBridge();
bridge->sendParameterUpdate("gain", 0.75f);
bridge->invoke("spectrum", spectrumData);Receive in Dart:
const channel = MethodChannel('com.jahlion.juce_flutter/bridge');
channel.setMethodCallHandler((call) async {
if (call.method == 'parameterChanged') {
final id = call.arguments['id'];
final value = call.arguments['value'];
// Update UI...
}
});On macOS the bridge uses ObjC FlutterMethodChannel. On Windows it uses the C++ flutter::MethodChannel<> wrapper with flutter::EncodableValue.
juce_flutter/
├── juce_flutter.h # Module header
├── juce_flutter.mm # Module implementation (macOS, ObjC++)
├── juce_flutter.cpp # Module implementation (Windows)
├── juce_flutter.moduledesc # JUCE module descriptor
└── flutter/
├── FlutterComponent.h/cpp # Drop-in juce::Component (cross-platform)
├── FlutterRenderer.h # Abstract renderer interface
├── FlutterRenderer_mac.mm # macOS: child NSPanel + Metal/Impeller
├── FlutterRenderer_win.cpp # Windows: child HWND + ANGLE/D3D
├── FlutterBridge.h/cpp # Bridge base (cross-platform)
├── FlutterBridge_mac.mm # macOS: ObjC FlutterMethodChannel
├── FlutterBridge_win.cpp # Windows: C++ flutter::MethodChannel
├── FlutterBundleResolver.mm # macOS: bundle/asset path resolution
└── FlutterBundleResolver_win.cpp # Windows: bundle/asset path resolution
- Child window approach: The Flutter panel is a separate window, so it floats above the host. This works well but means Flutter content can't be clipped by the host window bounds.
- No custom engine builds: Uses stock Flutter engine from the SDK. The low-level C Embedder API (
FlutterEngineRun) is not exported from the standard macOS framework. - Code signing (macOS): Embedded frameworks must be re-signed after copying into plugin bundles.
- Input handling (Windows): Basic mouse/keyboard works automatically via child HWND. Focus management in some DAW hosts and high-DPI scaling may need tweaking.
| Approach | Result | Notes |
|---|---|---|
| NSView subview | Transparent | Metal needs contentViewController |
| Child NSPanel | 60fps | Works in standalone + REAPER VST3 |
| CGWindowListCreateImage | Requires permission | Screen Recording TCC |
| NSView cacheDisplayInRect | Black output | Can't capture Metal layers |
| C Embedder API | Not exported | Requires custom engine build |
MIT — see LICENSE.
