pluginval is a cross-platform audio plugin validator and tester application developed by Tracktion Corporation. It tests VST, VST3, AU (Audio Unit), LV2, and LADSPA plugins for compatibility and stability with host applications.
- Version: 1.0.4 (see
VERSIONfile) - License: GPLv3
- Framework: Built on JUCE (v8.0.x)
- Language: C++20
- Tests VST/VST2/VST3/AU/LV2/LADSPA plugins
- Cross-platform (macOS, Windows, Linux)
- GUI and headless (CLI) operation modes
- Validation runs in a separate process to prevent crashes from bringing down the app
- Real-time safety checking via rtcheck (macOS only currently)
- Integration with native validators (auval for AU, vstvalidator for VST3)
Follow these guidelines when working on this codebase:
-
Think first, then read: Before making changes, think through the problem and read relevant files in the codebase. Understand the existing code before proposing modifications.
-
Verify plans before major changes: Before making any major changes, check in with the user to verify the plan. Get confirmation before proceeding with significant modifications.
-
Provide high-level explanations: At every step, give a high-level explanation of what changes were made. Keep explanations concise and focused on the "what" and "why."
-
Keep changes simple: Make every task and code change as simple as possible. Avoid massive or complex changes. Every change should impact as little code as possible. Simplicity is paramount.
-
Maintain architecture documentation: Keep this documentation file updated to describe how the architecture of the app works. Update relevant sections when making architectural changes.
-
Never speculate about unread code: Never make claims about code you haven't opened. If the user references a specific file, you MUST read the file before answering. Investigate and read relevant files BEFORE answering questions about the codebase. Give grounded, hallucination-free answers based on actual file contents.
- Organisation:
<organisation> - Repository:
<repo>
For this project:
- Organisation:
Tracktion - Repository:
pluginval
Install the GitHub CLI:
brew install gh # macOS
# or
sudo apt install gh # Ubuntu/DebianAuthentication is handled via the GH_TOKEN environment variable (already configured).
-
List recent workflow runs:
gh run list -R <organisation>/<repo>
-
Find the most recent run for your branch from the output above.
-
View failed log details:
gh run view -R <organisation>/<repo> <run_id> --log-failed
Replace <run_id> with the ID from step 2.
pluginval/
├── Source/ # Main application source code
│ ├── Main.cpp # Application entry point
│ ├── MainComponent.cpp/h # GUI main window component
│ ├── Validator.cpp/h # Core validation orchestration
│ ├── PluginTests.cpp/h # Test framework and base classes
│ ├── CommandLine.cpp/h # CLI argument parsing
│ ├── CrashHandler.cpp/h # Crash reporting utilities
│ ├── TestUtilities.cpp/h # Helper functions for tests
│ ├── RTCheck.h # Real-time safety checking macros
│ ├── PluginvalLookAndFeel.h # Custom UI styling
│ ├── StrictnessInfoPopup.h # Strictness level info UI
│ ├── binarydata/ # Binary resources (icons)
│ ├── vst3validator/ # Embedded VST3 validator integration
│ │ ├── VST3ValidatorRunner.h
│ │ └── VST3ValidatorRunner.cpp
│ └── tests/ # Individual test implementations
│ ├── BasicTests.cpp # Core plugin tests (info, state, audio)
│ ├── BusTests.cpp # Audio bus configuration tests
│ ├── EditorTests.cpp # Plugin editor/GUI tests
│ ├── ParameterFuzzTests.cpp # Parameter fuzzing tests
│ ├── LocaleTest.cpp # Locale handling tests
│ └── ExtremeTests.cpp # Edge case tests
├── modules/
│ └── juce/ # JUCE framework (git submodule)
├── cmake/
│ ├── CPM.cmake # CMake Package Manager
│ └── GenerateBinaryHeader.cmake # Binary-to-C-header converter
├── tests/
│ ├── AddPluginvalTests.cmake # CMake module for CTest integration
│ ├── test_plugins/ # Test plugin files
│ ├── mac_tests/ # macOS-specific tests
│ └── windows_tests.bat # Windows test scripts
├── docs/ # Documentation
│ ├── Adding pluginval to CI.md
│ ├── Command line options.md
│ ├── Debugging a failed validation.md
│ └── Testing plugins with pluginval.md
├── CMakeLists.txt # Main build configuration
├── VERSION # Version number file
├── CHANGELIST.md # Release changelog
└── ROADMAP.md # Future development plans
- CMake 3.15+
- C++20 compatible compiler
- Git (for submodules)
# Initialize JUCE submodule
git submodule update --init
# Configure (Debug build)
cmake -B Builds/Debug -DCMAKE_BUILD_TYPE=Debug .
# Build
cmake --build Builds/Debug --config Debug| Option | Description | Default |
|---|---|---|
PLUGINVAL_FETCH_JUCE |
Fetch JUCE with pluginval | ON |
PLUGINVAL_VST3_VALIDATOR |
Build with embedded VST3 validator | ON |
WITH_ADDRESS_SANITIZER |
Enable AddressSanitizer | OFF |
WITH_THREAD_SANITIZER |
Enable ThreadSanitizer | OFF |
VST2_SDK_DIR |
Path to VST2 SDK (env var) | - |
VST2 SDK is not included. Set the environment variable before configuring:
VST2_SDK_DIR=/path/to/vst2sdk cmake -B Builds/Debug .- macOS: 10.11+ (deployment target), supports Apple Silicon via universal binary
- Windows: MSVC with static runtime linking
- Linux: Ubuntu 22.04+, statically links libstdc++
-
PluginValidatorApplication (
Main.cpp)- JUCE application entry point
- Handles both GUI and CLI modes
- Manages preferences and window lifecycle
-
Validator (
Validator.h/cpp)- Orchestrates validation passes
- Supports in-process and child-process validation
- Listener interface for progress callbacks
-
ValidationPass (
Validator.h)- Single async validation for one plugin
- Can run in separate process for crash isolation
-
PluginTests (
PluginTests.h/cpp)- UnitTest subclass that runs all registered tests
- Manages plugin loading and test execution
- Configurable via
Optionsstruct
-
PluginTest (
PluginTests.h)- Base class for individual tests
- Auto-registers via static instance pattern
- Defines requirements (thread, GUI needs)
Tests are self-registering. To find all tests, look for static instances:
static MyTest myTest; // Registers automaticallyStrictness Levels (1-10):
- Level 1-4: Basic tests, quick execution
- Level 5: Recommended minimum for host compatibility (default)
- Level 6+: Extended tests, parameter fuzzing, longer duration
- Level 10: Most thorough, includes real-time safety checks
Test Requirements:
struct Requirements {
Thread thread; // backgroundThread or messageThread
GUI gui; // noGUI or requiresGUI
};| File | Tests Included |
|---|---|
BasicTests.cpp |
PluginInfo, Programs, Editor, AudioProcessing, PluginState, Automation, auval, VST3validator |
BusTests.cpp |
Bus layout, channel configuration |
EditorTests.cpp |
Editor creation, resizing |
ParameterFuzzTests.cpp |
Random parameter value testing |
LocaleTest.cpp |
Locale handling verification |
ExtremeTests.cpp |
Edge cases, stress tests |
The VST3 validator (Steinberg's vstvalidator) is embedded into pluginval when built with PLUGINVAL_VST3_VALIDATOR=ON (the default). This provides single-file distribution while keeping vstvalidator completely isolated from pluginval's link dependencies.
Architecture:
- The VST3 SDK is fetched via CPM during CMake configure
- The SDK's own
validatortarget is built as a separate executable - A CMake script (
cmake/GenerateBinaryHeader.cmake) converts the compiled binary into a C byte array header VST3ValidatorRunner(Source/vst3validator/) extracts the embedded binary to a temp file on first use- When the
VST3validatortest runs, it spawns the extracted validator as a subprocess
Key files:
cmake/GenerateBinaryHeader.cmake— binary-to-C-header conversion scriptSource/vst3validator/VST3ValidatorRunner.h/cpp— extracts embedded binary, returnsjuce::File
Disabling embedded validator:
cmake -B Builds -DPLUGINVAL_VST3_VALIDATOR=OFF .- Create a subclass of
PluginTest:
struct MyNewTest : public PluginTest
{
MyNewTest()
: PluginTest ("My Test Name",
5, // strictness level (1-10)
{ Requirements::Thread::backgroundThread,
Requirements::GUI::noGUI })
{
}
void runTest (PluginTests& ut, juce::AudioPluginInstance& instance) override
{
// Use ut.expect(), ut.expectEquals(), ut.logMessage()
ut.logMessage ("Running my test...");
ut.expect (someCondition, "Test failed because...");
}
std::vector<TestDescription> getDescription (int strictnessLevel) const override
{
return { { name, "Description of what this test does" } };
}
};
// Register the test with a static instance
static MyNewTest myNewTest;- Add the source file to
CMakeLists.txtin theSourceFileslist.
Located in TestUtilities.h:
getNonBypassAutomatableParameters()- Get automatable paramsfillNoise()- Fill buffer with random audiocountNaNs(),countInfs(),countSubnormals()- Audio validationScopedEditorShower- RAII editor creation/destructioncallPrepareToPlayOnMessageThreadIfVST3()- VST3-safe lifecycleScopedAllocationDisabler- Detect allocations in audio thread
Use the RTC_REALTIME_CONTEXT_IF_ENABLED macro around processBlock calls:
{
RTC_REALTIME_CONTEXT_IF_ENABLED(ut.getOptions().realtimeCheck, blockNum)
instance.processBlock(ab, mb);
}- JUCE coding style (CamelCase for types, camelCase for variables)
- 4-space indentation
- Braces on same line for control structures
- Use JUCE types:
juce::String,juce::Array,juce::File, etc.
Use #pragma once (not traditional include guards)
Either use juce:: prefix or have using namespace juce; in cpp files (not headers)
- Tests may run on background or message thread (specify in Requirements)
- VST3 plugins require certain operations on message thread (use
*OnMessageThreadIfVST3helpers) - Use
juce::WaitableEventfor thread synchronization
ut.logMessage("Important message"); // Always shown
ut.logVerboseMessage("Detail message"); // Only with --verbose flagBasic usage:
./pluginval --strictness-level 5 /path/to/plugin.vst3Key options:
--validate [path]- Validate plugin at path--strictness-level [1-10]- Test thoroughness (default: 5)--skip-gui-tests- Skip GUI tests (for headless CI)--validate-in-process- Don't use child process (for debugging)--timeout-ms [ms]- Test timeout (default: 30000, -1 for none)--verbose- Enable verbose logging--output-dir [dir]- Directory for log files--sample-rates [list]- Comma-separated sample rates--block-sizes [list]- Comma-separated block sizes--rtcheck [disabled|enabled|relaxed]- Real-time safety checking
Environment variables can substitute CLI args:
--skip-gui-tests->SKIP_GUI_TESTS=1--timeout-ms 30000->TIMEOUT_MS=30000
Exit codes: 0 = success, 1 = failure
Use tests/AddPluginvalTests.cmake:
include(AddPluginvalTests)
add_pluginval_tests(MyPluginTarget
TEST_PREFIX "MyPlugin.pluginval"
LOG_DIR "${CMAKE_BINARY_DIR}/logs"
)- name: Download pluginval
run: |
curl -L "https://github.com/Tracktion/pluginval/releases/latest/download/pluginval_${{ runner.os }}.zip" -o pluginval.zip
unzip pluginval.zip
- name: Validate Plugin
run: |
./pluginval --strictness-level 5 --skip-gui-tests ./build/MyPlugin.vst3- JUCE (v8.0.x) - Audio application framework (git submodule)
- magic_enum (v0.9.7) - Enum reflection (fetched via CPM)
- rtcheck (optional, macOS) - Real-time safety checking (fetched via CPM)
- VST3 SDK (v3.7.x) - Steinberg VST3 SDK for embedded validator (fetched via CPM, optional)
- macOS: CoreAudio, AudioUnit frameworks
- Linux: ALSA, X11
- Windows: WASAPI, DirectSound
Debug unit tests run automatically in debug builds:
#if JUCE_DEBUG
juce::UnitTestRunner testRunner;
testRunner.runTestsInCategory ("pluginval");
#endifRun internal tests via CLI:
./pluginval --run-tests- Update
VERSIONfile - Update
CHANGELIST.md - Commit:
git commit -am "Version X.Y.Z" - Tag:
git tag -a vX.Y.Z -m "X.Y.Z release" - Push:
git push --tags
- All test classes are in
Source/tests/*.cpp - Search for
static.*Test.*Test;to find registrations - Each test subclasses
PluginTest
Main.cppcreatesValidatororCommandLineValidatorValidatorcreatesValidationPassfor each pluginValidationPassspawns child process or runs in-processPluginTests::runTest()iterates all registeredPluginTestinstances- Each
PluginTest::runTest()performs its specific validation
- All in
CMakeLists.txt - Source files listed in
SourceFilesvariable - JUCE modules linked via
target_link_libraries
#if JUCE_MAC
// macOS specific
#elif JUCE_WINDOWS
// Windows specific
#elif JUCE_LINUX
// Linux specific
#endif- Always test changes on multiple platforms when possible
- VST3 plugins have specific threading requirements - use the
*OnMessageThreadIfVST3helpers - Child process validation is the default and recommended for production use
- In-process validation (
--validate-in-process) is useful for debugging but a crashing plugin will crash pluginval - Real-time safety checking is only available on macOS currently (uses rtcheck library)