├── VST_Compatible_Logo_Steinberg.png ├── flutter_vst3 ├── VST_Compatible_Logo_Steinberg.png ├── lib │ ├── flutter_vst3.dart │ └── src │ │ ├── flutter_vst3_parameters.dart │ │ ├── flutter_vst3_bridge.dart │ │ └── flutter_vst3_callbacks.dart ├── pubspec.yaml ├── native │ ├── templates │ │ ├── plugin_ids.h.template │ │ ├── plugin_factory.cpp.template │ │ ├── plugin_controller.cpp.template │ │ ├── plugin_processor.cpp.template │ │ ├── plugin_processor_aot.cpp.template │ │ └── plugin_processor_native.cpp.template │ ├── include │ │ ├── plugin_ids.h │ │ └── dart_vst3_bridge.h │ └── src │ │ ├── factory.cpp │ │ ├── plugin_controller.cpp │ │ ├── plugin_view.cpp │ │ ├── dart_vst3_bridge.cpp │ │ └── plugin_processor.cpp ├── CHANGELOG.md ├── example │ └── README.md ├── LICENSE ├── CONTRIBUTING.md ├── README.md ├── create_plugin_guide.md └── scripts │ └── generate_plugin.dart ├── .vscode └── settings.json ├── dart_vst_graph ├── lib │ ├── dart_vst_graph.dart │ └── src │ │ └── bindings.dart ├── pubspec.yaml ├── test │ └── graph_test.dart └── native │ ├── CMakeLists.txt │ └── include │ └── dvh_graph.h ├── dart_vst_host ├── lib │ ├── dart_vst_host.dart │ └── src │ │ ├── bindings.dart │ │ └── host.dart ├── pubspec.yaml ├── example │ └── main.dart └── native │ ├── include │ └── dart_vst_host.h │ └── CMakeLists.txt ├── vsts ├── echo │ ├── macos │ │ └── Flutter │ │ │ ├── GeneratedPluginRegistrant.swift │ │ │ └── ephemeral │ │ │ ├── Flutter-Generated.xcconfig │ │ │ └── flutter_export_environment.sh │ ├── .gitignore │ ├── pubspec.yaml │ ├── lib │ │ ├── src │ │ │ ├── echo_bridge.dart │ │ │ ├── echo_vst3_processor.dart │ │ │ ├── echo_parameters.dart │ │ │ ├── echo_processor.dart │ │ │ └── echo_plugin.dart │ │ ├── echo_parameters.dart │ │ └── echo_processor_exe.dart │ ├── CMakeLists.txt │ ├── plugin_metadata.json │ └── test │ │ └── echo_test.dart └── flutter_reverb │ ├── lib │ ├── flutter_reverb.dart │ ├── flutter_reverb_parameters.dart │ ├── flutter_reverb_processor_exe.dart │ └── src │ │ └── reverb_processor.dart │ ├── .gitignore │ ├── pubspec.yaml │ ├── CMakeLists.txt │ ├── plugin_metadata.json │ └── test │ └── reverb_test.dart ├── .gitignore ├── validate_echo.sh ├── validate_flutter_reverb.sh ├── .devcontainer ├── devcontainer.json ├── Dockerfile └── README.md ├── setup.sh ├── CLAUDE.md └── Makefile /VST_Compatible_Logo_Steinberg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MelbourneDeveloper/flutter_vst3/HEAD/VST_Compatible_Logo_Steinberg.png -------------------------------------------------------------------------------- /flutter_vst3/VST_Compatible_Logo_Steinberg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MelbourneDeveloper/flutter_vst3/HEAD/flutter_vst3/VST_Compatible_Logo_Steinberg.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "titleBar.activeBackground": "#0b0240", 4 | "activityBar.background": "#0b0240", 5 | } 6 | } -------------------------------------------------------------------------------- /dart_vst_graph/lib/dart_vst_graph.dart: -------------------------------------------------------------------------------- 1 | /// Export the public graph API. Consumers should import this file to 2 | /// access VstGraph without worrying about bindings. 3 | 4 | export 'src/bindings.dart' show VstGraph; -------------------------------------------------------------------------------- /dart_vst_host/lib/dart_vst_host.dart: -------------------------------------------------------------------------------- 1 | /// Export the public host API. Consumers should import this file to 2 | /// access VstHost and VstPlugin without worrying about the FFI 3 | /// bindings. 4 | 5 | export 'src/host.dart'; -------------------------------------------------------------------------------- /dart_vst_graph/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_vst_graph 2 | description: Dart FFI bindings for multi‑VST graph host 3 | version: 0.1.0 4 | environment: 5 | sdk: '>=3.3.0 <4.0.0' 6 | dependencies: 7 | ffi: ^2.1.0 8 | dev_dependencies: 9 | test: ^1.24.0 -------------------------------------------------------------------------------- /vsts/echo/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | 9 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build products 2 | build/ 3 | .dart_tool/ 4 | .idea/ 5 | *.iml 6 | *.pyc 7 | __pycache__/ 8 | node_modules/ 9 | *.dll 10 | *.so 11 | *.dylib 12 | *.zip 13 | .DS_Store 14 | 15 | vst3sdk/ 16 | 17 | *.wav 18 | 19 | vst_plugins/ 20 | -------------------------------------------------------------------------------- /vsts/echo/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Build directories 3 | build/ 4 | 5 | # Dart generated files 6 | .dart_tool/ 7 | .packages 8 | pubspec.lock 9 | 10 | # macOS 11 | .DS_Store 12 | 13 | # IDE 14 | .vscode/ 15 | .idea/ 16 | *.swp 17 | *.swo 18 | 19 | generated/ -------------------------------------------------------------------------------- /vsts/flutter_reverb/lib/flutter_reverb.dart: -------------------------------------------------------------------------------- 1 | /// Flutter Reverb - VST3 Reverb Plugin 2 | /// 3 | /// A VST3 reverb plugin implementation in pure Dart. 4 | library flutter_reverb; 5 | 6 | export 'flutter_reverb_parameters.dart'; 7 | export 'src/reverb_processor.dart'; -------------------------------------------------------------------------------- /validate_echo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Build the validator 4 | # cd vst3sdk 5 | # cmake -B build_all . 6 | # cd build_all 7 | # make validator 8 | 9 | # Run validator on echo plugin 10 | vst3sdk/build_all/bin/Debug/validator vsts/echo/build/VST3/Release/echo.vst3 -------------------------------------------------------------------------------- /vsts/flutter_reverb/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Build directories 3 | build/ 4 | 5 | # Dart generated files 6 | .dart_tool/ 7 | .packages 8 | pubspec.lock 9 | 10 | # macOS 11 | .DS_Store 12 | 13 | # IDE 14 | .vscode/ 15 | .idea/ 16 | *.swp 17 | *.swo 18 | 19 | generated/ -------------------------------------------------------------------------------- /dart_vst_host/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_vst_host 2 | description: Minimal VST3 host via FFI wrapping Steinberg hosting helpers 3 | version: 0.1.0 4 | environment: 5 | sdk: '>=3.0.0 <4.0.0' 6 | dependencies: 7 | ffi: ^2.1.0 8 | meta: ^1.12.0 9 | dev_dependencies: 10 | test: ^1.24.0 -------------------------------------------------------------------------------- /vsts/echo/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: echo 2 | description: A simple echo VST3 plugin with obvious delay effect 3 | version: 1.0.0 4 | publish_to: 'none' 5 | 6 | environment: 7 | sdk: '>=3.0.0 <4.0.0' 8 | flutter: ">=3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_vst3: 14 | path: ../../flutter_vst3 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | test: ^1.24.0 20 | flutter_lints: ^4.0.0 21 | 22 | flutter: 23 | uses-material-design: true -------------------------------------------------------------------------------- /vsts/flutter_reverb/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_reverb 2 | description: A unified Flutter VST3 reverb plugin with complete UI and audio processing 3 | version: 1.0.0 4 | publish_to: 'none' 5 | 6 | environment: 7 | sdk: '>=3.0.0 <4.0.0' 8 | flutter: ">=3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_vst3: 14 | path: ../../flutter_vst3 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | flutter_lints: ^4.0.0 20 | test: ^1.24.0 21 | 22 | flutter: 23 | uses-material-design: true -------------------------------------------------------------------------------- /vsts/echo/macos/Flutter/ephemeral/Flutter-Generated.xcconfig: -------------------------------------------------------------------------------- 1 | // This is a generated file; do not edit or check into version control. 2 | FLUTTER_ROOT=/opt/homebrew/Caskroom/flutter/3.27.2/flutter 3 | FLUTTER_APPLICATION_PATH=/Users/christianfindlay/Documents/Code/vst_project_package/vsts/echo 4 | COCOAPODS_PARALLEL_CODE_SIGN=true 5 | FLUTTER_BUILD_DIR=build 6 | FLUTTER_BUILD_NAME=1.0.0 7 | FLUTTER_BUILD_NUMBER=1.0.0 8 | DART_OBFUSCATION=false 9 | TRACK_WIDGET_CREATION=true 10 | TREE_SHAKE_ICONS=false 11 | PACKAGE_CONFIG=.dart_tool/package_config.json 12 | -------------------------------------------------------------------------------- /flutter_vst3/lib/flutter_vst3.dart: -------------------------------------------------------------------------------- 1 | /// flutter_vst3 - Framework for VST® 3 Plugin Development 2 | /// 3 | /// This package provides the complete framework for building VST® 3 plugins 4 | /// with Flutter UI and pure Dart audio processing. It bridges Dart/Flutter 5 | /// code with the Steinberg VST® 3 SDK C++ infrastructure. 6 | /// 7 | /// VST® is a trademark of Steinberg Media Technologies GmbH. 8 | library flutter_vst3; 9 | 10 | export 'src/flutter_vst3_bridge.dart'; 11 | export 'src/flutter_vst3_callbacks.dart'; 12 | export 'src/flutter_vst3_parameters.dart'; -------------------------------------------------------------------------------- /validate_flutter_reverb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to validate the Flutter Reverb VST3 plugin 4 | 5 | echo "Validating Flutter Reverb VST3 plugin..." 6 | 7 | # Check if VST3 validator exists 8 | if [ ! -f "vst3sdk/build_all/bin/Debug/validator" ]; then 9 | echo "ERROR: VST3 validator not found at vst3sdk/build_all/bin/Debug/validator" 10 | echo "Please build the VST3 SDK validator first." 11 | exit 1 12 | fi 13 | 14 | # Run the validator on the Flutter Reverb plugin 15 | vst3sdk/build_all/bin/Debug/validator vsts/flutter_reverb/build/VST3/Release/flutter_reverb.vst3 -------------------------------------------------------------------------------- /vsts/echo/macos/Flutter/ephemeral/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/opt/homebrew/Caskroom/flutter/3.27.2/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/christianfindlay/Documents/Code/vst_project_package/vsts/echo" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "FLUTTER_BUILD_NAME=1.0.0" 8 | export "FLUTTER_BUILD_NUMBER=1.0.0" 9 | export "DART_OBFUSCATION=false" 10 | export "TRACK_WIDGET_CREATION=true" 11 | export "TREE_SHAKE_ICONS=false" 12 | export "PACKAGE_CONFIG=.dart_tool/package_config.json" 13 | -------------------------------------------------------------------------------- /flutter_vst3/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_vst3 2 | description: Flutter/Dart framework for building VST® 3 plugins with Flutter UI and pure Dart audio processing. Provides the interface between Dart audio processing code and the Steinberg VST® 3 SDK C++ infrastructure. 3 | version: 0.1.2 4 | repository: https://github.com/MelbourneDeveloper/flutter_vst3 5 | homepage: https://github.com/MelbourneDeveloper/flutter_vst3 6 | topics: 7 | - vst3 8 | - audio 9 | - plugins 10 | - flutter 11 | - music 12 | platforms: 13 | macos: 14 | windows: 15 | linux: 16 | 17 | environment: 18 | sdk: '>=3.0.0 <4.0.0' 19 | 20 | dependencies: 21 | ffi: ^2.1.0 22 | 23 | dev_dependencies: 24 | test: ^1.24.0 25 | lints: ^4.0.0 -------------------------------------------------------------------------------- /flutter_vst3/native/templates/plugin_ids.h.template: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // Auto-generated plugin IDs and parameters for {{PLUGIN_NAME}} 3 | // This file is generated automatically by dart_vst3_bridge at build time. 4 | // DO NOT EDIT - any changes will be overwritten. 5 | 6 | #pragma once 7 | 8 | #include "pluginterfaces/base/funknown.h" 9 | #include "pluginterfaces/vst/vsttypes.h" 10 | 11 | using namespace Steinberg; 12 | 13 | // Plugin UIDs (generated from plugin name and company) 14 | static const FUID k{{PLUGIN_CLASS_NAME}}ProcessorUID({{PROCESSOR_UID}}); 15 | static const FUID k{{PLUGIN_CLASS_NAME}}ControllerUID({{CONTROLLER_UID}}); 16 | 17 | // Parameter IDs 18 | enum { 19 | {{PARAMETER_ENUMS}} 20 | {{PARAM_COUNT_NAME}} = {{PARAM_COUNT}} 21 | }; -------------------------------------------------------------------------------- /vsts/echo/lib/src/echo_bridge.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_vst3/flutter_vst3.dart'; 2 | import 'echo_vst3_processor.dart'; 3 | 4 | /// FFI bindings for the echo VST3 bridge - FAILS HARD if callbacks not registered 5 | class EchoBridge { 6 | static EchoVST3Processor? _processor; 7 | 8 | /// Initialize the bridge and register callbacks 9 | static void initialize() { 10 | // Create the processor 11 | _processor = EchoVST3Processor(); 12 | 13 | // Register with the global VST3 bridge 14 | VST3Bridge.registerProcessor(_processor!); 15 | 16 | print('Echo bridge initialized with callbacks registered'); 17 | } 18 | 19 | /// Get the registered processor (for testing) 20 | static EchoVST3Processor? getProcessor() => _processor; 21 | } -------------------------------------------------------------------------------- /flutter_vst3/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.2 2 | 3 | * Corrections 4 | 5 | ## 0.1.1 6 | 7 | * Fix image URL 8 | 9 | ## 0.1.0 10 | 11 | * Initial release of flutter_vst3 framework 12 | * Complete framework for building VST® 3 plugins with Flutter UI and Dart audio processing 13 | * Auto-generation of all C++ VST® 3 boilerplate code from Dart parameter definitions 14 | * Native performance with compilation to machine code (no Dart runtime) 15 | * Cross-platform support for macOS, Windows, and Linux 16 | * 3-way parameter binding system (DAW ↔ Flutter UI ↔ Audio Parameters) 17 | * VST3Processor base class for implementing audio processing in pure Dart 18 | * VST3Bridge for Flutter UI ↔ VST® host communication 19 | * Complete step-by-step plugin creation guide 20 | * Compatible with Steinberg VST® 3 SDK -------------------------------------------------------------------------------- /vsts/echo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(echo_plugin 3 | VERSION 1.0.0 4 | LANGUAGES CXX) 5 | 6 | # Build the Echo VST® 3 plugin using flutter_vst3 7 | # This plugin uses pure Dart audio processing with Flutter UI via FFI bridge 8 | 9 | set(CMAKE_CXX_STANDARD 17) 10 | 11 | # Set build type and debug flags 12 | if(NOT CMAKE_BUILD_TYPE) 13 | set(CMAKE_BUILD_TYPE Release) 14 | endif() 15 | 16 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 17 | add_compile_definitions(_DEBUG) 18 | else() 19 | add_compile_definitions(NDEBUG RELEASE) 20 | endif() 21 | 22 | # Include the flutter_vst3 CMake helper 23 | include(../../flutter_vst3/native/cmake/VST3Bridge.cmake) 24 | 25 | # Create the VST® 3 plugin using auto-generated code from Dart definitions 26 | # The bridge will auto-generate all C++ files from lib/echo_parameters.dart 27 | # Metadata is read from plugin_metadata.json by the generator 28 | add_dart_vst3_plugin(echo echo_parameters.dart) -------------------------------------------------------------------------------- /flutter_vst3/native/include/plugin_ids.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // 3 | // Definition of unique identifiers for the Dart VST host plug‑in. The 4 | // processor and controller GUIDs must be globally unique to avoid 5 | // clashes with other plug‑ins. Adjust these values when creating a 6 | // new plug‑in product. 7 | 8 | #pragma once 9 | #include "pluginterfaces/base/fplatform.h" 10 | #include "pluginterfaces/base/funknown.h" 11 | #include "pluginterfaces/vst/vsttypes.h" 12 | 13 | // com.yourorg.DartVstHost 14 | static const Steinberg::FUID kProcessorUID (0xA1B2C3D4, 0x00000001, 0x00000002, 0x00000003); 15 | static const Steinberg::FUID kControllerUID(0xA1B2C3D4, 0x00000004, 0x00000005, 0x00000006); 16 | 17 | // Parameter identifiers for the host plug‑in. Currently only one 18 | // parameter controlling the output gain of the main mixer. When you 19 | // add more exposed parameters to the graph they should be listed here. 20 | enum Params : Steinberg::Vst::ParamID { 21 | kParamOutputGain = 0, 22 | }; -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VST Development Environment", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "context": "." 6 | }, 7 | "workspaceFolder": "/workspace", 8 | "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", 9 | "remoteEnv": { 10 | "VST3_SDK_DIR": "/workspace/vst3sdk", 11 | "PATH": "/usr/lib/dart/bin:/opt/flutter/bin:${containerEnv:PATH}" 12 | }, 13 | "customizations": { 14 | "vscode": { 15 | "extensions": [ 16 | "Dart-Code.dart-code", 17 | "Dart-Code.flutter", 18 | "ms-vscode.cpptools", 19 | "ms-vscode.cmake-tools", 20 | "ms-vscode.makefile-tools" 21 | ], 22 | "settings": { 23 | "dart.flutterSdkPath": "/opt/flutter", 24 | "dart.sdkPath": "/usr/lib/dart", 25 | "cmake.configureOnOpen": false 26 | } 27 | } 28 | }, 29 | "postCreateCommand": "./setup.sh", 30 | "remoteUser": "vscode", 31 | "forwardPorts": [3000, 8080], 32 | "features": { 33 | "ghcr.io/devcontainers/features/git:1": {} 34 | } 35 | } -------------------------------------------------------------------------------- /vsts/flutter_reverb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(flutter_reverb_plugin 3 | VERSION 1.0.0 4 | LANGUAGES CXX) 5 | 6 | # Build the Flutter Reverb VST® 3 plugin using flutter_vst3 7 | # This plugin uses pure Dart audio processing with Flutter UI via FFI bridge 8 | 9 | set(CMAKE_CXX_STANDARD 17) 10 | 11 | # Set build type and debug flags 12 | if(NOT CMAKE_BUILD_TYPE) 13 | set(CMAKE_BUILD_TYPE Release) 14 | endif() 15 | 16 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 17 | add_compile_definitions(_DEBUG) 18 | else() 19 | add_compile_definitions(NDEBUG RELEASE) 20 | endif() 21 | 22 | # Include the flutter_vst3 CMake helper 23 | include(../../flutter_vst3/native/cmake/VST3Bridge.cmake) 24 | 25 | # Create the VST® 3 plugin using auto-generated code from Dart definitions 26 | # The bridge will auto-generate all C++ files from lib/src/reverb_parameters.dart 27 | add_dart_vst3_plugin(flutter_reverb lib/src/flutter_reverb_parameters.dart 28 | BUNDLE_IDENTIFIER "com.yourcompany.vst3.flutterreverb" 29 | COMPANY_NAME "Your Company" 30 | PLUGIN_NAME "Flutter Reverb" 31 | ) -------------------------------------------------------------------------------- /dart_vst_graph/test/graph_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:test/test.dart'; 3 | import 'package:dart_vst_graph/dart_vst_graph.dart'; 4 | 5 | void main() { 6 | // Determine if the native library is present in the working 7 | // directory. On CI or developer machines the library should be 8 | // built into libdart_vst_host.{so,dylib,dll} alongside the tests. 9 | final libFile = Platform.isWindows 10 | ? File('dart_vst_host.dll') 11 | : Platform.isMacOS 12 | ? File('libdart_vst_host.dylib') 13 | : File('libdart_vst_host.so'); 14 | 15 | if (!libFile.existsSync()) { 16 | throw Exception('Native library ${libFile.path} not found! Build the native library first.'); 17 | } 18 | 19 | late VstGraph graph; 20 | setUp(() { 21 | final absolutePath = Directory.current.path + Platform.pathSeparator + libFile.path; 22 | graph = VstGraph(sampleRate: 48000, maxBlock: 512, dylibPath: absolutePath); 23 | }); 24 | tearDown(() { 25 | graph.dispose(); 26 | }); 27 | 28 | test('gain node parameters', () { 29 | final id = graph.addGain(0.0); 30 | // The GainNode has exactly one parameter at index 0 31 | expect(graph.setParam(id, 0, 0.5), isTrue); 32 | }); 33 | } -------------------------------------------------------------------------------- /flutter_vst3/native/src/factory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // 3 | // Factory implementation for the Dart VST host plug‑in. Registers 4 | // processor and controller classes with Steinberg’s module factory. 5 | 6 | #include "plugin_ids.h" 7 | #include "public.sdk/source/main/pluginfactory.h" 8 | #include "pluginterfaces/vst/ivstaudioprocessor.h" 9 | #include "pluginterfaces/vst/ivsteditcontroller.h" 10 | 11 | #define FULL_VERSION_STR "1.0.0" 12 | 13 | using namespace Steinberg; 14 | 15 | extern FUnknown* createDvhProcessor(void*); 16 | extern FUnknown* createDvhController(void*); 17 | 18 | bool InitModule() { return true; } 19 | bool DeinitModule() { return true; } 20 | 21 | BEGIN_FACTORY_DEF("YourOrg","https://your.org","support@your.org") 22 | 23 | DEF_CLASS2(INLINE_UID_FROM_FUID(kProcessorUID), 24 | PClassInfo::kManyInstances, kVstAudioEffectClass, "DartVstHost", 25 | Vst::kDistributable | Vst::kSimpleModeSupported, 26 | "Instrument|Fx", FULL_VERSION_STR, kVstVersionString, createDvhProcessor) 27 | 28 | DEF_CLASS2(INLINE_UID_FROM_FUID(kControllerUID), 29 | PClassInfo::kManyInstances, kVstComponentControllerClass, "DartVstHostController", 30 | 0, "", FULL_VERSION_STR, kVstVersionString, createDvhController) 31 | 32 | END_FACTORY -------------------------------------------------------------------------------- /vsts/echo/plugin_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginName": "Echo", 3 | "vendor": "CF", 4 | "version": "1.0.0", 5 | "category": "Fx|Delay", 6 | "bundleIdentifier": "com.cf.echo", 7 | "companyWeb": "https://cf.com", 8 | "companyEmail": "info@cf.com", 9 | "parameters": [ 10 | { 11 | "id": 0, 12 | "name": "delayTime", 13 | "displayName": "Delay Time", 14 | "description": "Controls the delay time in milliseconds (0ms to 1000ms)", 15 | "defaultValue": 0.5, 16 | "units": "ms" 17 | }, 18 | { 19 | "id": 1, 20 | "name": "feedback", 21 | "displayName": "Feedback", 22 | "description": "Controls the feedback amount (0% = single echo, 100% = infinite)", 23 | "defaultValue": 0.3, 24 | "units": "%" 25 | }, 26 | { 27 | "id": 2, 28 | "name": "mix", 29 | "displayName": "Mix", 30 | "description": "Controls the wet/dry mix (0% = dry only, 100% = wet only)", 31 | "defaultValue": 0.5, 32 | "units": "%" 33 | }, 34 | { 35 | "id": 3, 36 | "name": "bypass", 37 | "displayName": "Bypass", 38 | "description": "Bypasses the echo effect when enabled", 39 | "defaultValue": 0.0, 40 | "units": "" 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /flutter_vst3/native/src/plugin_controller.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // 3 | // Controller class for the Dart VST host plug‑in. Exposes parameters 4 | // to the host and manages the state of the user interface. Only one 5 | // parameter controlling the output gain is currently provided. 6 | 7 | #include "plugin_ids.h" 8 | #include "public.sdk/source/vst/vsteditcontroller.h" 9 | #include "public.sdk/source/vst/vstparameters.h" 10 | #include "pluginterfaces/base/ustring.h" 11 | 12 | using namespace Steinberg; 13 | using namespace Steinberg::Vst; 14 | 15 | class DvhController : public EditController { 16 | public: 17 | tresult PLUGIN_API initialize(FUnknown* ctx) override { 18 | tresult r = EditController::initialize(ctx); 19 | if (r != kResultTrue) return r; 20 | 21 | // Create a normalized parameter for output gain. Normalized 22 | // 0.0 -> -60dB, 1.0 -> 0dB. The unit string is in decibels. 23 | RangeParameter* outGain = new RangeParameter(USTRING("Output Gain"), kParamOutputGain, USTRING("dB"), 0, 1, 0.5); 24 | parameters.addParameter(outGain); 25 | return kResultTrue; 26 | } 27 | 28 | }; 29 | 30 | // Factory function for the controller 31 | FUnknown* createDvhController(void*) { 32 | return (IEditController*)new DvhController(); 33 | } -------------------------------------------------------------------------------- /vsts/flutter_reverb/plugin_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginName": "Flutter Reverb", 3 | "vendor": "Flutter VST3", 4 | "version": "1.0.0", 5 | "category": "kFxReverb", 6 | "bundleIdentifier": "com.flutter.vst3.reverb", 7 | "companyWeb": "https://flutter.dev", 8 | "companyEmail": "noreply@flutter.dev", 9 | "parameters": [ 10 | { 11 | "id": 0, 12 | "name": "roomSize", 13 | "displayName": "Room Size", 14 | "description": "Controls the size of the reverb space (0% = small room, 100% = large hall)", 15 | "defaultValue": 0.5, 16 | "units": "%" 17 | }, 18 | { 19 | "id": 1, 20 | "name": "damping", 21 | "displayName": "Damping", 22 | "description": "Controls high frequency absorption (0% = bright, 100% = dark)", 23 | "defaultValue": 0.5, 24 | "units": "%" 25 | }, 26 | { 27 | "id": 2, 28 | "name": "wetLevel", 29 | "displayName": "Wet Level", 30 | "description": "Controls the level of reverb signal mixed with the input", 31 | "defaultValue": 0.3, 32 | "units": "%" 33 | }, 34 | { 35 | "id": 3, 36 | "name": "dryLevel", 37 | "displayName": "Dry Level", 38 | "description": "Controls the level of direct (unprocessed) signal", 39 | "defaultValue": 0.7, 40 | "units": "%" 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /flutter_vst3/example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_vst3 Example 2 | 3 | For comprehensive examples of VST® 3 plugin development with flutter_vst3, please see the complete plugin implementations in the main repository: 4 | 5 | - [Flutter Reverb Plugin](https://github.com/MelbourneDeveloper/flutter_vst3/tree/main/vsts/flutter_reverb) - A full reverb plugin with Flutter UI 6 | - [Echo Plugin](https://github.com/MelbourneDeveloper/flutter_vst3/tree/main/vsts/echo) - A delay/echo plugin with custom controls 7 | 8 | ## Quick Start 9 | 10 | Follow the [Complete Step-by-Step Plugin Creation Guide](../create_plugin_guide.md) to create your first VST® 3 plugin. 11 | 12 | ## Basic Plugin Structure 13 | 14 | ```dart 15 | import 'package:flutter_vst3/flutter_vst3.dart'; 16 | 17 | class MyProcessor extends VST3Processor { 18 | @override 19 | void initialize(double sampleRate, int maxBlockSize) { 20 | // Initialize your audio processing 21 | } 22 | 23 | @override 24 | void processStereo(List inputL, List inputR, 25 | List outputL, List outputR) { 26 | // Your audio processing logic here 27 | for (int i = 0; i < inputL.length; i++) { 28 | outputL[i] = inputL[i]; // Pass through for now 29 | outputR[i] = inputR[i]; 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | *VST® is a registered trademark of Steinberg Media Technologies GmbH, registered in Europe and other countries.* -------------------------------------------------------------------------------- /vsts/echo/lib/src/echo_vst3_processor.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_vst3/flutter_vst3.dart'; 2 | import 'echo_processor.dart'; 3 | import 'echo_parameters.dart'; 4 | 5 | /// VST3 processor adapter for the Echo plugin 6 | /// This connects the pure Dart echo processor to the VST3 bridge 7 | class EchoVST3Processor extends VST3Processor { 8 | EchoProcessor? _echoProcessor; 9 | final EchoParameters _parameters = EchoParameters(); 10 | 11 | @override 12 | void initialize(double sampleRate, int maxBlockSize) { 13 | _echoProcessor = EchoProcessor(); 14 | _echoProcessor!.initialize(sampleRate, maxBlockSize); 15 | } 16 | 17 | @override 18 | void processStereo(List inputL, List inputR, 19 | List outputL, List outputR) { 20 | _echoProcessor?.processStereo(inputL, inputR, outputL, outputR, _parameters); 21 | } 22 | 23 | @override 24 | void setParameter(int paramId, double normalizedValue) { 25 | _parameters.setParameter(paramId, normalizedValue); 26 | } 27 | 28 | @override 29 | double getParameter(int paramId) { 30 | return _parameters.getParameter(paramId); 31 | } 32 | 33 | @override 34 | int getParameterCount() { 35 | return EchoParameters.numParameters; 36 | } 37 | 38 | @override 39 | void reset() { 40 | _echoProcessor?.reset(); 41 | } 42 | 43 | @override 44 | void dispose() { 45 | _echoProcessor?.dispose(); 46 | _echoProcessor = null; 47 | } 48 | } -------------------------------------------------------------------------------- /flutter_vst3/lib/src/flutter_vst3_parameters.dart: -------------------------------------------------------------------------------- 1 | /// VST3 Parameter utilities and types 2 | /// 3 | /// This file provides common parameter handling utilities that Dart VST3 4 | /// plugins can use for consistent parameter management. 5 | 6 | /// Common parameter information structure 7 | class VST3ParameterInfo { 8 | final int id; 9 | final String name; 10 | final String shortName; 11 | final String units; 12 | final double minValue; 13 | final double maxValue; 14 | final double defaultNormalized; 15 | final bool canAutomate; 16 | 17 | const VST3ParameterInfo({ 18 | required this.id, 19 | required this.name, 20 | required this.shortName, 21 | required this.units, 22 | this.minValue = 0.0, 23 | this.maxValue = 1.0, 24 | required this.defaultNormalized, 25 | this.canAutomate = true, 26 | }); 27 | } 28 | 29 | /// Base class for parameter management in VST3 plugins 30 | abstract class VST3ParameterHandler { 31 | /// Get parameter information by index 32 | VST3ParameterInfo getParameterInfo(int index); 33 | 34 | /// Get total number of parameters 35 | int get parameterCount; 36 | 37 | /// Convert normalized value (0.0-1.0) to display string 38 | String parameterToString(int paramId, double normalizedValue); 39 | 40 | /// Convert display string to normalized value (0.0-1.0) 41 | double stringToParameter(int paramId, String text); 42 | 43 | /// Get default normalized value for parameter 44 | double getDefaultValue(int paramId); 45 | } -------------------------------------------------------------------------------- /dart_vst_host/example/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | import 'package:dart_vst_host/dart_vst_host.dart'; 4 | 5 | /// Example command line program that loads a VST3 plug‑in, sends a 6 | /// note and processes one block of audio. Run this with the path to 7 | /// a .vst3 bundle as the last argument. For example: 8 | /// dart example/main.dart /path/to/MyPlugin.vst3 9 | void main(List args) async { 10 | if (args.isEmpty) { 11 | stderr.writeln('Run: dart example/main.dart /path/to/Plugin.vst3'); 12 | exit(64); 13 | } 14 | 15 | final pluginPath = args.last; 16 | final host = VstHost.create(sampleRate: 48000, maxBlock: 512); 17 | final plug = host.load(pluginPath); 18 | 19 | if (!plug.resume(sampleRate: 48000, maxBlock: 512)) { 20 | stderr.writeln('resume failed'); 21 | exit(1); 22 | } 23 | 24 | final n = 512; 25 | final inL = Float32List(n); 26 | final inR = Float32List(n); 27 | final outL = Float32List(n); 28 | final outR = Float32List(n); 29 | 30 | // Play a middle C with velocity 0.8 on channel 0 31 | plug.noteOn(0, 60, 0.8); 32 | 33 | final ok = plug.processStereoF32(inL, inR, outL, outR); 34 | stdout.writeln('process ok: $ok, first sample L=${outL[0]}'); 35 | 36 | final count = plug.paramCount(); 37 | for (var i = 0; i < count && i < 8; i++) { 38 | final pi = plug.paramInfoAt(i); 39 | stdout.writeln('param $i id=${pi.id} "${pi.title}" [${pi.units}] = ${plug.getParamNormalized(pi.id)}'); 40 | } 41 | 42 | plug.suspend(); 43 | plug.unload(); 44 | host.dispose(); 45 | } -------------------------------------------------------------------------------- /flutter_vst3/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2025, Christian Findlay 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /flutter_vst3/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to flutter_vst3 2 | 3 | We welcome contributions from anyone interested in audio plugin development! Whether you're a seasoned audio developer or someone passionate about music and technology, your contributions are valuable. 4 | 5 | ## Getting Started 6 | 7 | 1. Fork the repository 8 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`) 9 | 3. Make your changes 10 | 4. Test your changes thoroughly 11 | 5. Commit your changes (`git commit -m 'Add amazing feature'`) 12 | 6. Push to the branch (`git push origin feature/amazing-feature`) 13 | 7. Open a Pull Request 14 | 15 | ## Development with AI 16 | 17 | We encourage contributors to leverage modern AI development tools like [Claude Code](https://claude.ai/code) to accelerate development, especially if you're new to audio programming. Quality AI agents can help bridge knowledge gaps and enable more developers to contribute to audio software. 18 | 19 | ## Areas for Contribution 20 | 21 | - **Audio DSP algorithms** - New effects and processors 22 | - **Flutter UI components** - Better plugin interfaces 23 | - **Documentation** - Tutorials, examples, and guides 24 | - **Platform support** - Improvements for Windows, macOS, Linux 25 | - **Bug fixes** - Always appreciated! 26 | 27 | ## Code Style 28 | 29 | Follow the existing code patterns in the project. Refer to the [CLAUDE.md](../CLAUDE.md) file for coding guidelines. 30 | 31 | ## Questions? 32 | 33 | Open an issue to discuss ideas or ask questions. We're here to help! 34 | 35 | *VST® is a registered trademark of Steinberg Media Technologies GmbH, registered in Europe and other countries.* -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | # Avoid prompts from apt 4 | ENV DEBIAN_FRONTEND=noninteractive 5 | 6 | # Install system dependencies 7 | RUN apt-get update && apt-get install -y \ 8 | build-essential \ 9 | cmake \ 10 | git \ 11 | curl \ 12 | wget \ 13 | unzip \ 14 | pkg-config \ 15 | libasound2-dev \ 16 | libx11-dev \ 17 | libxrandr-dev \ 18 | libxinerama-dev \ 19 | libxcursor-dev \ 20 | libfreetype6-dev \ 21 | sudo \ 22 | && rm -rf /var/lib/apt/lists/* 23 | 24 | # Install Node.js and npm 25 | RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \ 26 | apt-get install -y nodejs && \ 27 | rm -rf /var/lib/apt/lists/* 28 | 29 | # Install Dart SDK 30 | RUN ARCH=$(dpkg --print-architecture) && \ 31 | wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/dart.gpg && \ 32 | echo "deb [signed-by=/usr/share/keyrings/dart.gpg arch=${ARCH}] https://storage.googleapis.com/download.dartlang.org/linux/debian stable main" | tee /etc/apt/sources.list.d/dart_stable.list && \ 33 | apt-get update && \ 34 | apt-get install -y dart && \ 35 | rm -rf /var/lib/apt/lists/* 36 | 37 | # Set user for development first 38 | RUN useradd -m -s /bin/bash vscode && \ 39 | echo "vscode ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 40 | 41 | # Install Flutter and fix ownership 42 | RUN git clone https://github.com/flutter/flutter.git -b stable /opt/flutter && \ 43 | chown -R vscode:vscode /opt/flutter && \ 44 | sudo -u vscode /opt/flutter/bin/flutter doctor 45 | 46 | # Add Dart and Flutter to PATH 47 | ENV PATH="/usr/lib/dart/bin:/opt/flutter/bin:${PATH}" 48 | 49 | # Create workspace directory 50 | WORKDIR /workspace 51 | 52 | # Set VST3_SDK_DIR environment variable 53 | ENV VST3_SDK_DIR=/workspace/vst3sdk 54 | 55 | # Create workspace directory 56 | WORKDIR /workspace 57 | 58 | # Install Claude Code globally 59 | RUN npm install -g @anthropic-ai/claude-code 60 | 61 | USER vscode 62 | WORKDIR /workspace -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo 'Setting up development environment...' 6 | 7 | # Check and install dependencies on macOS 8 | if [ "$(uname)" = "Darwin" ]; then 9 | echo 'Checking dependencies for macOS...' 10 | 11 | # Check if Homebrew is installed 12 | if ! command -v brew &> /dev/null; then 13 | echo 'Installing Homebrew...' 14 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 15 | fi 16 | 17 | # Check and install cmake 18 | if ! command -v cmake &> /dev/null; then 19 | echo 'Installing cmake...' 20 | brew install cmake 21 | else 22 | echo 'cmake already installed' 23 | fi 24 | 25 | # Check and install make (part of Xcode command line tools) 26 | if ! command -v make &> /dev/null; then 27 | echo 'Installing Xcode command line tools...' 28 | xcode-select --install 29 | echo 'Please complete the Xcode command line tools installation and run this script again.' 30 | exit 1 31 | else 32 | echo 'make already installed' 33 | fi 34 | fi 35 | 36 | # Download and extract VST3 SDK if not present 37 | if [ ! -d vst3sdk ]; then 38 | echo 'Downloading VST3 SDK...' 39 | curl -L -o vst3sdk.zip https://www.steinberg.net/vst3sdk 40 | unzip -q vst3sdk.zip 41 | if [ -d VST_SDK/vst3sdk ]; then 42 | mv VST_SDK/vst3sdk . 43 | rm -rf VST_SDK 44 | fi 45 | rm -f vst3sdk.zip 46 | echo 'VST3 SDK downloaded and extracted.' 47 | else 48 | echo 'VST3 SDK already exists, skipping download.' 49 | fi 50 | 51 | # Set VST3_SDK_DIR environment variable 52 | export VST3_SDK_DIR="$(pwd)/vst3sdk" 53 | 54 | # Build native library 55 | echo 'Building native library...' 56 | mkdir -p native/build 57 | cd native/build 58 | cmake .. 59 | make 60 | 61 | # Copy library to project root 62 | if [ "$(uname)" = "Darwin" ]; then 63 | cp libdart_vst_host.dylib ../../ 64 | elif [ "$(uname)" = "Linux" ]; then 65 | cp libdart_vst_host.so ../../ 66 | else 67 | cp libdart_vst_host.dll ../../ 68 | fi 69 | 70 | cd ../.. 71 | 72 | echo 'Setup complete! Native library built and ready for development.' -------------------------------------------------------------------------------- /flutter_vst3/native/templates/plugin_factory.cpp.template: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // Auto-generated VST3 factory for {{PLUGIN_NAME}} 3 | // This file is generated automatically by dart_vst3_bridge at build time. 4 | // DO NOT EDIT - any changes will be overwritten. 5 | 6 | #include "{{PLUGIN_ID}}_ids.h" 7 | #include "pluginterfaces/vst/ivstcomponent.h" 8 | #include "pluginterfaces/vst/ivstaudioprocessor.h" 9 | #include "pluginterfaces/vst/ivsteditcontroller.h" 10 | #include "public.sdk/source/main/pluginfactory.h" 11 | 12 | #define stringCompanyName "{{COMPANY_NAME}}" 13 | #define stringCompanyWeb "{{PLUGIN_URL}}" 14 | #define stringCompanyEmail "{{PLUGIN_EMAIL}}" 15 | 16 | using namespace Steinberg::Vst; 17 | 18 | // Forward declarations - these are implemented in generated processor/controller files 19 | namespace Steinberg { 20 | namespace Vst { 21 | namespace {{PLUGIN_CLASS_NAME}} { 22 | FUnknown* createProcessorInstance(void*); 23 | FUnknown* createControllerInstance(void*); 24 | } 25 | } 26 | } 27 | 28 | //------------------------------------------------------------------------ 29 | // VST Plug-in Entry 30 | //------------------------------------------------------------------------ 31 | BEGIN_FACTORY_DEF (stringCompanyName, stringCompanyWeb, stringCompanyEmail) 32 | 33 | // Define the VST3 plugin processor 34 | DEF_CLASS2 (INLINE_UID_FROM_FUID(k{{PLUGIN_CLASS_NAME}}ProcessorUID), 35 | PClassInfo::kManyInstances, 36 | kVstAudioEffectClass, 37 | "{{PLUGIN_NAME}}", 38 | Vst::kDistributable, 39 | "{{PLUGIN_CATEGORY}}", 40 | "{{PLUGIN_VERSION}}", 41 | kVstVersionString, 42 | Steinberg::Vst::{{PLUGIN_CLASS_NAME}}::createProcessorInstance) 43 | 44 | // Define the VST3 plugin controller 45 | DEF_CLASS2 (INLINE_UID_FROM_FUID(k{{PLUGIN_CLASS_NAME}}ControllerUID), 46 | PClassInfo::kManyInstances, 47 | kVstComponentControllerClass, 48 | "{{PLUGIN_NAME}} Controller", 49 | 0, 50 | "", 51 | "{{PLUGIN_VERSION}}", 52 | kVstVersionString, 53 | Steinberg::Vst::{{PLUGIN_CLASS_NAME}}::createControllerInstance) 54 | 55 | END_FACTORY -------------------------------------------------------------------------------- /vsts/echo/lib/echo_parameters.dart: -------------------------------------------------------------------------------- 1 | /// Echo parameters for the VST3 plugin 2 | class EchoParameters { 3 | static const int kDelayTimeParam = 0; 4 | static const int kFeedbackParam = 1; 5 | static const int kMixParam = 2; 6 | static const int kBypassParam = 3; 7 | 8 | /// Controls the delay time in milliseconds (0ms to 1000ms) 9 | double delayTime = 0.5; 10 | 11 | /// Controls the feedback amount (0% = single echo, 100% = infinite) 12 | double feedback = 0.3; 13 | 14 | /// Controls the wet/dry mix (0% = dry only, 100% = wet only) 15 | double mix = 0.5; 16 | 17 | /// Bypasses the echo effect when enabled 18 | double bypass = 0.0; 19 | 20 | /// Get parameter value by ID 21 | double getParameter(int paramId) { 22 | return switch (paramId) { 23 | kDelayTimeParam => delayTime, 24 | kFeedbackParam => feedback, 25 | kMixParam => mix, 26 | kBypassParam => bypass, 27 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 28 | }; 29 | } 30 | 31 | /// Set parameter value by ID 32 | void setParameter(int paramId, double value) { 33 | final clampedValue = value.clamp(0.0, 1.0); 34 | switch (paramId) { 35 | case kDelayTimeParam: 36 | delayTime = clampedValue; 37 | break; 38 | case kFeedbackParam: 39 | feedback = clampedValue; 40 | break; 41 | case kMixParam: 42 | mix = clampedValue; 43 | break; 44 | case kBypassParam: 45 | bypass = clampedValue; 46 | break; 47 | default: 48 | throw ArgumentError('Unknown parameter ID: $paramId'); 49 | } 50 | } 51 | 52 | /// Get parameter name by ID 53 | String getParameterName(int paramId) { 54 | return switch (paramId) { 55 | kDelayTimeParam => 'Delay Time', 56 | kFeedbackParam => 'Feedback', 57 | kMixParam => 'Mix', 58 | kBypassParam => 'Bypass', 59 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 60 | }; 61 | } 62 | 63 | /// Get parameter units by ID 64 | String getParameterUnits(int paramId) { 65 | return switch (paramId) { 66 | kDelayTimeParam => 'ms', 67 | kFeedbackParam => '%', 68 | kMixParam => '%', 69 | kBypassParam => '', 70 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 71 | }; 72 | } 73 | 74 | /// Get number of parameters 75 | static const int numParameters = 4; 76 | } -------------------------------------------------------------------------------- /vsts/echo/lib/src/echo_parameters.dart: -------------------------------------------------------------------------------- 1 | /// Echo parameters for the VST3 plugin 2 | class EchoParameters { 3 | static const int kDelayTimeParam = 0; 4 | static const int kFeedbackParam = 1; 5 | static const int kMixParam = 2; 6 | static const int kBypassParam = 3; 7 | 8 | /// Controls the delay time in milliseconds (0ms to 1000ms) 9 | double delayTime = 0.5; 10 | 11 | /// Controls the feedback amount (0% = single echo, 100% = infinite) 12 | double feedback = 0.3; 13 | 14 | /// Controls the wet/dry mix (0% = dry only, 100% = wet only) 15 | double mix = 0.5; 16 | 17 | /// Bypasses the echo effect when enabled 18 | double bypass = 0.0; 19 | 20 | /// Get parameter value by ID 21 | double getParameter(int paramId) { 22 | return switch (paramId) { 23 | kDelayTimeParam => delayTime, 24 | kFeedbackParam => feedback, 25 | kMixParam => mix, 26 | kBypassParam => bypass, 27 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 28 | }; 29 | } 30 | 31 | /// Set parameter value by ID 32 | void setParameter(int paramId, double value) { 33 | final clampedValue = value.clamp(0.0, 1.0); 34 | switch (paramId) { 35 | case kDelayTimeParam: 36 | delayTime = clampedValue; 37 | break; 38 | case kFeedbackParam: 39 | feedback = clampedValue; 40 | break; 41 | case kMixParam: 42 | mix = clampedValue; 43 | break; 44 | case kBypassParam: 45 | bypass = clampedValue; 46 | break; 47 | default: 48 | throw ArgumentError('Unknown parameter ID: $paramId'); 49 | } 50 | } 51 | 52 | /// Get parameter name by ID 53 | String getParameterName(int paramId) { 54 | return switch (paramId) { 55 | kDelayTimeParam => 'Delay Time', 56 | kFeedbackParam => 'Feedback', 57 | kMixParam => 'Mix', 58 | kBypassParam => 'Bypass', 59 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 60 | }; 61 | } 62 | 63 | /// Get parameter units by ID 64 | String getParameterUnits(int paramId) { 65 | return switch (paramId) { 66 | kDelayTimeParam => 'ms', 67 | kFeedbackParam => '%', 68 | kMixParam => '%', 69 | kBypassParam => '', 70 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 71 | }; 72 | } 73 | 74 | /// Get number of parameters 75 | static const int numParameters = 4; 76 | } -------------------------------------------------------------------------------- /vsts/flutter_reverb/lib/flutter_reverb_parameters.dart: -------------------------------------------------------------------------------- 1 | /// Reverb parameters for the VST3 plugin 2 | class ReverbParameters { 3 | static const int kRoomSizeParam = 0; 4 | static const int kDampingParam = 1; 5 | static const int kWetLevelParam = 2; 6 | static const int kDryLevelParam = 3; 7 | 8 | /// Controls the size of the reverb space (0% = small room, 100% = large hall) 9 | double roomSize = 0.5; 10 | 11 | /// Controls high frequency absorption (0% = bright, 100% = dark) 12 | double damping = 0.5; 13 | 14 | /// Controls the level of reverb signal mixed with the input 15 | double wetLevel = 0.3; 16 | 17 | /// Controls the level of direct (unprocessed) signal 18 | double dryLevel = 0.7; 19 | 20 | /// Get parameter value by ID 21 | double getParameter(int paramId) { 22 | return switch (paramId) { 23 | kRoomSizeParam => roomSize, 24 | kDampingParam => damping, 25 | kWetLevelParam => wetLevel, 26 | kDryLevelParam => dryLevel, 27 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 28 | }; 29 | } 30 | 31 | /// Set parameter value by ID 32 | void setParameter(int paramId, double value) { 33 | final clampedValue = value.clamp(0.0, 1.0); 34 | switch (paramId) { 35 | case kRoomSizeParam: 36 | roomSize = clampedValue; 37 | break; 38 | case kDampingParam: 39 | damping = clampedValue; 40 | break; 41 | case kWetLevelParam: 42 | wetLevel = clampedValue; 43 | break; 44 | case kDryLevelParam: 45 | dryLevel = clampedValue; 46 | break; 47 | default: 48 | throw ArgumentError('Unknown parameter ID: $paramId'); 49 | } 50 | } 51 | 52 | /// Get parameter name by ID 53 | String getParameterName(int paramId) { 54 | return switch (paramId) { 55 | kRoomSizeParam => 'Room Size', 56 | kDampingParam => 'Damping', 57 | kWetLevelParam => 'Wet Level', 58 | kDryLevelParam => 'Dry Level', 59 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 60 | }; 61 | } 62 | 63 | /// Get parameter units by ID 64 | String getParameterUnits(int paramId) { 65 | return switch (paramId) { 66 | kRoomSizeParam => '%', 67 | kDampingParam => '%', 68 | kWetLevelParam => '%', 69 | kDryLevelParam => '%', 70 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 71 | }; 72 | } 73 | 74 | /// Get number of parameters 75 | static const int numParameters = 4; 76 | } -------------------------------------------------------------------------------- /.devcontainer/README.md: -------------------------------------------------------------------------------- 1 | # VST Development Container 2 | 3 | This dev container provides a complete development environment for the VST project with all dependencies pre-installed. 4 | 5 | ## What's Included 6 | 7 | - **Ubuntu 22.04** base image 8 | - **CMake 3.22+** for building C++ components 9 | - **Dart SDK** for Dart development 10 | - **Flutter SDK** for Flutter UI development 11 | - **VST3 SDK directory** prepared (you need to provide the SDK) 12 | - **Build tools** (GCC, Make, etc.) 13 | - **Audio development libraries** (ALSA, X11, etc.) 14 | - **Node.js and npm** for package management 15 | - **Claude Code** pre-installed globally 16 | 17 | ## Usage 18 | 19 | ### With VS Code 20 | 21 | 1. Install the **Dev Containers** extension in VS Code 22 | 2. Open this project folder in VS Code 23 | 3. Click "Reopen in Container" when prompted (or use Command Palette: "Dev Containers: Reopen in Container") 24 | 4. Wait for the container to build (first time takes ~5-10 minutes) 25 | 5. Once ready, you can use the terminal to run: 26 | ```bash 27 | make build # Build everything 28 | make test # Run tests 29 | make help # See all available commands 30 | ``` 31 | 32 | ### Environment Variables 33 | 34 | The container automatically sets: 35 | - `VST3_SDK_DIR=/opt/vst3sdk` - Points to the pre-installed Steinberg VST3 SDK 36 | - `PATH` includes Dart and Flutter binaries 37 | 38 | ### Verification 39 | 40 | After the container starts, run: 41 | ```bash 42 | make check-env 43 | ``` 44 | 45 | This should show all dependencies as available. 46 | 47 | ## Container Features 48 | 49 | - **Persistent workspace**: Your code changes are preserved 50 | - **Pre-configured extensions**: Dart, Flutter, C++, CMake support 51 | - **Port forwarding**: Ports 3000 and 8080 forwarded for development servers 52 | - **Git integration**: Git is available and configured 53 | 54 | ## Troubleshooting 55 | 56 | If the container fails to build: 57 | 1. Ensure Docker is running 58 | 2. Ensure you have the VST3 SDK available (see VST3 SDK Setup below) 59 | 3. Try rebuilding: Command Palette → "Dev Containers: Rebuild Container" 60 | 61 | ## VST3 SDK Setup 62 | 63 | The VST3 SDK is not automatically downloaded due to Steinberg's licensing requirements. You need to: 64 | 65 | 1. Download the VST3 SDK from [Steinberg's Developer Portal](https://www.steinberg.net/developers/) 66 | 2. Extract it and place the `vst3sdk` folder in your project root 67 | 3. The container will automatically mount it to `/opt/vst3sdk` 68 | 69 | Alternatively, you can modify the devcontainer to mount your SDK from elsewhere on your system. -------------------------------------------------------------------------------- /vsts/flutter_reverb/lib/flutter_reverb_processor_exe.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | import 'src/reverb_processor.dart'; 4 | 5 | const CMD_INIT = 0x01; 6 | const CMD_PROCESS = 0x02; 7 | const CMD_SET_PARAM = 0x03; 8 | const CMD_TERMINATE = 0xFF; 9 | 10 | void main() async { 11 | final processor = ReverbProcessor(); 12 | 13 | // CRITICAL: Set binary mode (only if stdin is a terminal) 14 | try { 15 | stdin.lineMode = false; 16 | } catch (e) { 17 | // Ignore error if stdin is not a terminal (e.g., when piped) 18 | } 19 | 20 | // Main event loop 21 | await for (final bytes in stdin) { 22 | if (bytes.isEmpty) continue; 23 | 24 | final buffer = ByteData.view(Uint8List.fromList(bytes).buffer); 25 | final command = buffer.getUint8(0); 26 | 27 | switch (command) { 28 | case CMD_INIT: 29 | final sampleRate = buffer.getFloat64(1, Endian.little); 30 | processor.initialize(sampleRate, 512); 31 | stdout.add([CMD_INIT]); // ACK 32 | await stdout.flush(); 33 | break; 34 | 35 | case CMD_PROCESS: 36 | final numSamples = buffer.getInt32(1, Endian.little); 37 | // Read interleaved stereo 38 | final audioData = Float32List(numSamples * 2); 39 | for (int i = 0; i < numSamples * 2; i++) { 40 | audioData[i] = buffer.getFloat32(5 + i * 4, Endian.little); 41 | } 42 | 43 | // Split to L/R 44 | final inputL = List.generate(numSamples, 45 | (i) => audioData[i * 2].toDouble()); 46 | final inputR = List.generate(numSamples, 47 | (i) => audioData[i * 2 + 1].toDouble()); 48 | 49 | final outputL = List.filled(numSamples, 0.0); 50 | final outputR = List.filled(numSamples, 0.0); 51 | 52 | // PROCESS WITH REVERB DART CODE! 53 | processor.processStereo(inputL, inputR, outputL, outputR); 54 | 55 | // Send back interleaved 56 | final response = ByteData(1 + numSamples * 8); 57 | response.setUint8(0, CMD_PROCESS); 58 | for (int i = 0; i < numSamples; i++) { 59 | response.setFloat32(1 + i * 8, outputL[i], Endian.little); 60 | response.setFloat32(1 + i * 8 + 4, outputR[i], Endian.little); 61 | } 62 | 63 | stdout.add(response.buffer.asUint8List()); 64 | await stdout.flush(); 65 | break; 66 | 67 | case CMD_SET_PARAM: 68 | final paramId = buffer.getInt32(1, Endian.little); 69 | final value = buffer.getFloat64(5, Endian.little); 70 | processor.setParameter(paramId, value); 71 | stdout.add([CMD_SET_PARAM]); // ACK 72 | await stdout.flush(); 73 | break; 74 | 75 | case CMD_TERMINATE: 76 | exit(0); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /dart_vst_host/native/include/dart_vst_host.h: -------------------------------------------------------------------------------- 1 | // VST3 Host interface for Dart 2 | // Generated for cross-platform Dart VST hosting 3 | 4 | #pragma once 5 | #include 6 | 7 | #ifdef _WIN32 8 | #ifdef DART_VST_HOST_EXPORTS 9 | #define DVH_API __declspec(dllexport) 10 | #else 11 | #define DVH_API __declspec(dllimport) 12 | #endif 13 | #else 14 | #define DVH_API __attribute__((visibility("default"))) 15 | #endif 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | typedef void* DVH_Host; 22 | typedef void* DVH_Plugin; 23 | 24 | // Create a VST3 host. Provide sample rate and max block size. 25 | DVH_API DVH_Host dvh_create_host(double sample_rate, int32_t max_block); 26 | // Destroy a previously created host. 27 | DVH_API void dvh_destroy_host(DVH_Host host); 28 | 29 | // Load a plugin from module path. Optional class UID filters which class to instantiate. 30 | DVH_API DVH_Plugin dvh_load_plugin(DVH_Host host, const char* module_path_utf8, const char* class_uid_or_null); 31 | // Unload a previously loaded plugin. 32 | DVH_API void dvh_unload_plugin(DVH_Plugin p); 33 | 34 | // Resume processing on a plugin. Must be called after loading before processing. 35 | DVH_API int32_t dvh_resume(DVH_Plugin p, double sample_rate, int32_t max_block); 36 | // Suspend processing on a plugin. 37 | DVH_API int32_t dvh_suspend(DVH_Plugin p); 38 | 39 | // Process stereo audio. Input pointers must be valid arrays of length num_frames. Output will be written in-place. 40 | DVH_API int32_t dvh_process_stereo_f32(DVH_Plugin p, 41 | const float* inL, const float* inR, 42 | float* outL, float* outR, 43 | int32_t num_frames); 44 | 45 | // Send a NoteOn to the plugin. Channel and pitch follow MIDI convention. Velocity in [0,1]. 46 | DVH_API int32_t dvh_note_on(DVH_Plugin p, int32_t channel, int32_t note, float velocity); 47 | // Send a NoteOff to the plugin. 48 | DVH_API int32_t dvh_note_off(DVH_Plugin p, int32_t channel, int32_t note, float velocity); 49 | 50 | // Query number of parameters for a plugin. Returns 0 if no controller present. 51 | DVH_API int32_t dvh_param_count(DVH_Plugin p); 52 | // Query parameter info by index. Fills id, title and units buffers. Returns 1 on success. 53 | DVH_API int32_t dvh_param_info(DVH_Plugin p, int32_t index, 54 | int32_t* id_out, 55 | char* title_utf8, int32_t title_cap, 56 | char* units_utf8, int32_t units_cap); 57 | 58 | // Get a parameter value normalized [0,1] by ID. 59 | DVH_API float dvh_get_param_normalized(DVH_Plugin p, int32_t param_id); 60 | // Set a parameter normalized value. Returns 1 on success. 61 | DVH_API int32_t dvh_set_param_normalized(DVH_Plugin p, int32_t param_id, float normalized); 62 | 63 | #ifdef __cplusplus 64 | } 65 | #endif -------------------------------------------------------------------------------- /vsts/echo/lib/echo_processor_exe.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | import 'src/echo_processor.dart'; 4 | import 'src/echo_parameters.dart'; 5 | 6 | const CMD_INIT = 0x01; 7 | const CMD_PROCESS = 0x02; 8 | const CMD_SET_PARAM = 0x03; 9 | const CMD_TERMINATE = 0xFF; 10 | 11 | void main() async { 12 | final processor = EchoProcessor(); 13 | final parameters = EchoParameters(); 14 | 15 | // CRITICAL: Set binary mode (only if stdin is a terminal) 16 | try { 17 | stdin.lineMode = false; 18 | } catch (e) { 19 | // Ignore error if stdin is not a terminal (e.g., when piped) 20 | } 21 | 22 | // Main event loop 23 | await for (final bytes in stdin) { 24 | if (bytes.isEmpty) continue; 25 | 26 | final buffer = ByteData.view(Uint8List.fromList(bytes).buffer); 27 | final command = buffer.getUint8(0); 28 | 29 | switch (command) { 30 | case CMD_INIT: 31 | final sampleRate = buffer.getFloat64(1, Endian.little); 32 | processor.initialize(sampleRate, 512); 33 | stdout.add([CMD_INIT]); // ACK 34 | await stdout.flush(); 35 | break; 36 | 37 | case CMD_PROCESS: 38 | final numSamples = buffer.getInt32(1, Endian.little); 39 | // Read interleaved stereo 40 | final audioData = Float32List(numSamples * 2); 41 | for (int i = 0; i < numSamples * 2; i++) { 42 | audioData[i] = buffer.getFloat32(5 + i * 4, Endian.little); 43 | } 44 | 45 | // Split to L/R 46 | final inputL = List.generate(numSamples, 47 | (i) => audioData[i * 2].toDouble()); 48 | final inputR = List.generate(numSamples, 49 | (i) => audioData[i * 2 + 1].toDouble()); 50 | 51 | final outputL = List.filled(numSamples, 0.0); 52 | final outputR = List.filled(numSamples, 0.0); 53 | 54 | // PROCESS WITH YOUR DART CODE! 55 | processor.processStereo(inputL, inputR, outputL, outputR, parameters); 56 | 57 | // Send back interleaved 58 | final response = ByteData(1 + numSamples * 8); 59 | response.setUint8(0, CMD_PROCESS); 60 | for (int i = 0; i < numSamples; i++) { 61 | response.setFloat32(1 + i * 8, outputL[i], Endian.little); 62 | response.setFloat32(1 + i * 8 + 4, outputR[i], Endian.little); 63 | } 64 | 65 | stdout.add(response.buffer.asUint8List()); 66 | await stdout.flush(); 67 | break; 68 | 69 | case CMD_SET_PARAM: 70 | final paramId = buffer.getInt32(1, Endian.little); 71 | final value = buffer.getFloat64(5, Endian.little); 72 | parameters.setParameter(paramId, value); 73 | stdout.add([CMD_SET_PARAM]); // ACK 74 | await stdout.flush(); 75 | break; 76 | 77 | case CMD_TERMINATE: 78 | exit(0); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /flutter_vst3/native/src/plugin_view.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // 3 | // Minimal view implementation that launches an external Flutter UI 4 | // application. This plug‑in uses an external window to avoid 5 | // embedding Flutter directly into the VST host for now. When the 6 | // view is attached, the external process is started; when removed it 7 | // simply retains its state. Resize support is limited. 8 | 9 | #include "public.sdk/source/vst/utility/uid.h" 10 | #include "public.sdk/source/vst/vsteditcontroller.h" 11 | #include "pluginterfaces/vst/ivsteditcontroller.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #if defined(_WIN32) 19 | #include 20 | #endif 21 | 22 | namespace Steinberg::Vst { 23 | 24 | class DummyView : public IPlugView, public FObject { 25 | public: 26 | DummyView() {} 27 | ~DummyView() override {} 28 | 29 | tresult PLUGIN_API isPlatformTypeSupported(FIDString type) override { 30 | #if defined(_WIN32) 31 | if (strcmp(type, kPlatformTypeHWND) == 0) return kResultTrue; 32 | #elif defined(__APPLE__) 33 | if (strcmp(type, kPlatformTypeNSView) == 0) return kResultTrue; 34 | #else 35 | if (strcmp(type, kPlatformTypeX11EmbedWindowID) == 0) return kResultTrue; 36 | #endif 37 | return kInvalidArgument; 38 | } 39 | 40 | tresult PLUGIN_API attached(void* parent, FIDString) override { 41 | parent_ = parent; 42 | launchFlutter(); 43 | return kResultTrue; 44 | } 45 | tresult PLUGIN_API removed() override { 46 | parent_ = nullptr; 47 | return kResultTrue; 48 | } 49 | tresult PLUGIN_API onSize(ViewRect*) override { return kResultTrue; } 50 | tresult PLUGIN_API getSize(ViewRect* r) override { 51 | if (!r) return kInvalidArgument; 52 | r->left = 0; 53 | r->top = 0; 54 | r->right = 600; 55 | r->bottom = 420; 56 | return kResultTrue; 57 | } 58 | tresult PLUGIN_API setFrame(IPlugFrame*) override { return kResultTrue; } 59 | tresult PLUGIN_API canResize() override { return kResultTrue; } 60 | tresult PLUGIN_API checkSizeConstraint(ViewRect*) override { return kResultTrue; } 61 | 62 | REFCOUNT_METHODS(FObject) 63 | tresult PLUGIN_API queryInterface(const TUID iid, void** obj) override { 64 | QUERY_INTERFACE(iid, obj, IPlugView::iid, IPlugView) 65 | return FObject::queryInterface(iid, obj); 66 | } 67 | 68 | private: 69 | void* parent_{nullptr}; 70 | std::atomic launched_{false}; 71 | 72 | void launchFlutter() { 73 | if (launched_.exchange(true)) return; 74 | #if defined(_WIN32) 75 | std::wstring exe = L"flutter_ui\\build\\windows\\runner\\Release\\flutter_ui.exe"; 76 | STARTUPINFO si{}; 77 | PROCESS_INFORMATION pi{}; 78 | CreateProcessW(exe.c_str(), NULL, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi); 79 | CloseHandle(pi.hProcess); 80 | CloseHandle(pi.hThread); 81 | #elif defined(__APPLE__) 82 | system("open flutter_ui/build/macos/Build/Products/Release/flutter_ui.app"); 83 | #else 84 | if (!fork()) { 85 | execl("flutter_ui/build/linux/x64/release/bundle/flutter_ui", "flutter_ui", (char*)NULL); 86 | _exit(0); 87 | } 88 | #endif 89 | } 90 | }; 91 | 92 | } // namespace Steinberg::Vst -------------------------------------------------------------------------------- /flutter_vst3/native/include/dart_vst3_bridge.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // 3 | // Generic C bridge for calling Dart VST3 processors from C++ 4 | // This provides a universal interface between VST3 C++ code and 5 | // pure Dart implementations via FFI callbacks. 6 | 7 | #pragma once 8 | #include 9 | 10 | #ifdef _WIN32 11 | # ifdef DART_VST_HOST_EXPORTS 12 | # define DART_VST3_API __declspec(dllexport) 13 | # else 14 | # define DART_VST3_API __declspec(dllimport) 15 | # endif 16 | #else 17 | # define DART_VST3_API __attribute__((visibility("default"))) 18 | #endif 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | // Function pointer types for Dart callbacks 25 | typedef void (*DartInitializeProcessorFn)(double sample_rate, int32_t max_block_size); 26 | typedef void (*DartProcessAudioFn)(const float* input_l, const float* input_r, 27 | float* output_l, float* output_r, 28 | int32_t num_samples); 29 | typedef void (*DartSetParameterFn)(int32_t param_id, double normalized_value); 30 | typedef double (*DartGetParameterFn)(int32_t param_id); 31 | typedef int32_t (*DartGetParameterCountFn)(void); 32 | typedef void (*DartResetFn)(void); 33 | typedef void (*DartDisposeFn)(void); 34 | 35 | // Structure holding all Dart callback functions 36 | typedef struct { 37 | DartInitializeProcessorFn initialize_processor; 38 | DartProcessAudioFn process_audio; 39 | DartSetParameterFn set_parameter; 40 | DartGetParameterFn get_parameter; 41 | DartGetParameterCountFn get_parameter_count; 42 | DartResetFn reset; 43 | DartDisposeFn dispose; 44 | } DartVST3Callbacks; 45 | 46 | // Per-plugin instance management 47 | typedef struct DartVST3Instance DartVST3Instance; 48 | 49 | // Create a new plugin instance 50 | DART_VST3_API DartVST3Instance* dart_vst3_create_instance(const char* plugin_id); 51 | 52 | // Destroy a plugin instance 53 | DART_VST3_API int32_t dart_vst3_destroy_instance(DartVST3Instance* instance); 54 | 55 | // Register Dart callback functions for a specific plugin instance 56 | DART_VST3_API int32_t dart_vst3_register_callbacks(DartVST3Instance* instance, 57 | const DartVST3Callbacks* callbacks); 58 | 59 | // Initialize the Dart processor 60 | DART_VST3_API int32_t dart_vst3_initialize(DartVST3Instance* instance, 61 | double sample_rate, int32_t max_block_size); 62 | 63 | // Process stereo audio through Dart processor 64 | DART_VST3_API int32_t dart_vst3_process_stereo(DartVST3Instance* instance, 65 | const float* input_l, const float* input_r, 66 | float* output_l, float* output_r, 67 | int32_t num_samples); 68 | 69 | // Set/get parameter values 70 | DART_VST3_API int32_t dart_vst3_set_parameter(DartVST3Instance* instance, 71 | int32_t param_id, double normalized_value); 72 | DART_VST3_API double dart_vst3_get_parameter(DartVST3Instance* instance, int32_t param_id); 73 | DART_VST3_API int32_t dart_vst3_get_parameter_count(DartVST3Instance* instance); 74 | 75 | // Reset processor state 76 | DART_VST3_API int32_t dart_vst3_reset(DartVST3Instance* instance); 77 | 78 | // Dispose resources 79 | DART_VST3_API int32_t dart_vst3_dispose(DartVST3Instance* instance); 80 | 81 | #ifdef __cplusplus 82 | } 83 | #endif -------------------------------------------------------------------------------- /flutter_vst3/native/templates/plugin_controller.cpp.template: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // Auto-generated VST3 controller for {{PLUGIN_NAME}} 3 | // This file is generated automatically by dart_vst3_bridge at build time. 4 | // DO NOT EDIT - any changes will be overwritten. 5 | 6 | #include "{{PLUGIN_ID}}_ids.h" 7 | #include "pluginterfaces/base/ibstream.h" 8 | #include "pluginterfaces/base/ustring.h" 9 | #include "pluginterfaces/vst/ivstmidicontrollers.h" 10 | #include "public.sdk/source/vst/vsteditcontroller.h" 11 | 12 | using namespace Steinberg; 13 | using namespace Steinberg::Vst; 14 | 15 | // Controller for the {{PLUGIN_NAME}} plugin 16 | class {{PLUGIN_CLASS_NAME}}Controller : public EditController { 17 | public: 18 | {{PLUGIN_CLASS_NAME}}Controller() = default; 19 | 20 | tresult PLUGIN_API initialize(FUnknown* context) override { 21 | tresult result = EditController::initialize(context); 22 | if (result != kResultTrue) return result; 23 | 24 | // Add parameters to controller 25 | {{PARAMETER_CONTROLLER_INIT}} 26 | 27 | return kResultTrue; 28 | } 29 | 30 | tresult PLUGIN_API setComponentState(IBStream* state) override { 31 | if (!state) return kResultFalse; 32 | 33 | // Read parameter values from processor state 34 | for (int32 i = 0; i < {{PARAMETER_COUNT}}; ++i) { 35 | double value = 0.0; 36 | int32 bytesRead = 0; 37 | if (state->read(&value, sizeof(value), &bytesRead) == kResultTrue) { 38 | setParamNormalized(i, value); 39 | } 40 | } 41 | 42 | return kResultTrue; 43 | } 44 | 45 | tresult PLUGIN_API setState(IBStream* state) override { 46 | return setComponentState(state); 47 | } 48 | 49 | tresult PLUGIN_API getState(IBStream* state) override { 50 | if (!state) return kResultFalse; 51 | 52 | // Write current parameter values 53 | for (int32 i = 0; i < {{PARAMETER_COUNT}}; ++i) { 54 | double value = getParamNormalized(i); 55 | int32 bytesWritten = 0; 56 | state->write(&value, sizeof(value), &bytesWritten); 57 | } 58 | 59 | return kResultTrue; 60 | } 61 | 62 | // Convert normalized parameter values to display strings 63 | tresult PLUGIN_API getParamStringByValue(ParamID id, ParamValue valueNormalized, String128 string) override { 64 | {{PARAMETER_TO_STRING}} 65 | return EditController::getParamStringByValue(id, valueNormalized, string); 66 | } 67 | 68 | // Convert display strings to normalized parameter values 69 | tresult PLUGIN_API getParamValueByString(ParamID id, TChar* string, ParamValue& valueNormalized) override { 70 | {{STRING_TO_PARAMETER}} 71 | return EditController::getParamValueByString(id, string, valueNormalized); 72 | } 73 | 74 | // Create view (UI) for the plugin - integrates with Flutter UI 75 | IPlugView* PLUGIN_API createView(FIDString name) override { 76 | if (strcmp(name, ViewType::kEditor) == 0) { 77 | // TODO: Return Flutter-based UI view 78 | // For now, return nullptr to use generic host UI 79 | return nullptr; 80 | } 81 | return nullptr; 82 | } 83 | }; 84 | 85 | // Factory functions in proper namespace 86 | namespace Steinberg { 87 | namespace Vst { 88 | namespace {{PLUGIN_CLASS_NAME}} { 89 | FUnknown* createControllerInstance(void*) { 90 | return (IEditController*)new {{PLUGIN_CLASS_NAME}}Controller(); 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /flutter_vst3/README.md: -------------------------------------------------------------------------------- 1 | # flutter_vst3 2 | 3 | Flutter/Dart framework for building VST® 3 plugins with Flutter UI and pure Dart audio processing. 4 | 5 | VST Compatible 6 | 7 | *VST® is a registered trademark of Steinberg Media Technologies GmbH, registered in Europe and other countries.* 8 | 9 | ## Overview 10 | 11 | `flutter_vst3` is a complete framework that enables you to build professional VST® 3 audio plugins using Flutter for the UI and Dart for real-time audio processing. The framework auto-generates all C++ VST® 3 boilerplate code - you write only Dart and Flutter. 12 | 13 | **For complete architecture documentation and examples, see the [main project README](https://github.com/MelbourneDeveloper/flutter_vst3).** 14 | 15 | ## Features 16 | 17 | - ✅ **Flutter UI** - Build beautiful, reactive plugin interfaces 18 | - ✅ **Pure Dart DSP** - Write audio processing in familiar Dart syntax 19 | - ✅ **Auto-Generated C++** - Never write VST® 3 boilerplate 20 | - ✅ **Native Performance** - Compiles to machine code, no runtime 21 | - ✅ **3-Way Parameter Binding** - DAW ↔ Flutter UI ↔ Parameters stay in sync 22 | - ✅ **Cross-Platform** - macOS, Windows, Linux support 23 | 24 | ## Quick Start 25 | 26 | 📖 **[Complete Step-by-Step Plugin Creation Guide](create_plugin_guide.md)** 27 | 28 | The guide covers everything you need to build your first VST® 3 plugin: 29 | - Project setup and dependencies 30 | - Parameter definition with auto-generated C++ 31 | - Building and testing your plugin 32 | - Installation to system VST® 3 directory 33 | 34 | ## API Reference 35 | 36 | ### VST3Processor 37 | 38 | Base class for all Dart VST® 3 processors: 39 | 40 | ```dart 41 | abstract class VST3Processor { 42 | void initialize(double sampleRate, int maxBlockSize); 43 | void processStereo(List inputL, List inputR, 44 | List outputL, List outputR); 45 | void setParameter(int paramId, double normalizedValue); 46 | double getParameter(int paramId); 47 | int getParameterCount(); 48 | void reset(); 49 | void dispose(); 50 | } 51 | ``` 52 | 53 | ### VST3Bridge 54 | 55 | Main bridge for Flutter UI ↔ VST® host communication: 56 | 57 | ```dart 58 | class VST3Bridge { 59 | // Register your processor 60 | static void registerProcessor(VST3Processor processor); 61 | 62 | // Initialize processor (called from C++ layer) 63 | static void initializeProcessor(double sampleRate, int maxBlockSize); 64 | 65 | // Process audio (called from C++ layer) 66 | static void processStereoCallback(/* FFI parameters */); 67 | } 68 | ``` 69 | 70 | ## Examples 71 | 72 | See the complete example plugins in the main repository: 73 | - [Flutter Reverb](https://github.com/MelbourneDeveloper/flutter_vst3/tree/main/vsts/flutter_reverb) - Full reverb with Flutter UI 74 | - [Echo Plugin](https://github.com/MelbourneDeveloper/flutter_vst3/tree/main/vsts/echo) - Delay/echo with custom knobs 75 | 76 | ## Requirements 77 | 78 | - Dart SDK 3.0+ 79 | - Flutter SDK 3.0+ 80 | - CMake 3.20+ 81 | - Steinberg VST® 3 SDK 82 | - C++17 compiler 83 | 84 | ## Legal Notice 85 | 86 | This framework is not affiliated with Steinberg Media Technologies GmbH. 87 | VST® is a trademark of Steinberg Media Technologies GmbH. 88 | 89 | Users must comply with the Steinberg VST® 3 SDK License Agreement when distributing VST® 3 plugins. 90 | See: https://steinbergmedia.github.io/vst3_dev_portal/pages/VST+3+Licensing/Index.html 91 | 92 | ## License 93 | 94 | This project is licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /vsts/echo/lib/src/echo_processor.dart: -------------------------------------------------------------------------------- 1 | import 'echo_parameters.dart'; 2 | 3 | /// Simple echo/delay effect processor that FAILS HARD without parameters 4 | class EchoProcessor { 5 | static const int _delayBufferSize = 132300; // ~3 seconds at 44.1kHz 6 | 7 | final List _delayBufferL = List.filled(_delayBufferSize, 0.0); 8 | final List _delayBufferR = List.filled(_delayBufferSize, 0.0); 9 | int _writeIndex = 0; 10 | 11 | double _sampleRate = 44100.0; 12 | bool _initialized = false; 13 | 14 | /// Initialize the echo processor 15 | void initialize(double sampleRate, int maxBlockSize) { 16 | _sampleRate = sampleRate; 17 | _initialized = true; 18 | } 19 | 20 | /// Process stereo audio block with parameters-driven echo that FAILS HARD 21 | void processStereo(List inputL, List inputR, 22 | List outputL, List outputR, 23 | EchoParameters parameters) { 24 | if (!_initialized) { 25 | throw StateError('ECHO PROCESSOR FAILURE: Not initialized! Cannot process audio without initialization!'); 26 | } 27 | 28 | if (inputL.length != inputR.length || outputL.length != inputL.length || outputR.length != inputL.length) { 29 | throw ArgumentError('ECHO PROCESSOR FAILURE: Buffer length mismatch! All buffers must have the same length!'); 30 | } 31 | 32 | // Convert normalized parameters to actual values 33 | final delayTime = parameters.delayTime; // 0.0 to 1.0 -> 0ms to 1000ms 34 | final feedback = parameters.feedback; // 0.0 to 1.0 -> 0% to 100% 35 | final mix = parameters.mix; // 0.0 to 1.0 -> 0% to 100% 36 | final bypass = parameters.bypass; // 0.0 = active, 1.0 = bypassed 37 | 38 | // If bypassed, pass through clean signal 39 | if (bypass > 0.5) { 40 | for (int i = 0; i < inputL.length; i++) { 41 | outputL[i] = inputL[i]; 42 | outputR[i] = inputR[i]; 43 | } 44 | return; 45 | } 46 | 47 | // Calculate delay in samples (scale from 0-1 to 10ms-500ms for musical range) 48 | final delayMs = 10.0 + (delayTime * 490.0); // 10ms to 500ms range 49 | final delayInSamples = (delayMs * _sampleRate / 1000.0).round().clamp(1, _delayBufferSize - 1); 50 | 51 | // Scale feedback for musical control 52 | final feedbackAmount = feedback * 0.85; // Max 85% for stable feedback 53 | 54 | // Scale wet/dry mix for natural blend 55 | final wetLevel = mix; 56 | final dryLevel = (1.0 - mix); 57 | 58 | for (int i = 0; i < inputL.length; i++) { 59 | // Calculate read index for delay 60 | final readIndex = (_writeIndex - delayInSamples + _delayBufferSize) % _delayBufferSize; 61 | 62 | // Get delayed samples 63 | final delayedL = _delayBufferL[readIndex]; 64 | final delayedR = _delayBufferR[readIndex]; 65 | 66 | // Apply feedback with saturation 67 | final saturatedFeedbackL = _softSaturate(delayedL * feedbackAmount); 68 | final saturatedFeedbackR = _softSaturate(delayedR * feedbackAmount); 69 | 70 | // Write input + feedback to delay buffer 71 | _delayBufferL[_writeIndex] = inputL[i] + saturatedFeedbackL; 72 | _delayBufferR[_writeIndex] = inputR[i] + saturatedFeedbackR; 73 | 74 | // Clean wet signal with slight stereo width 75 | final wetL = (delayedL * wetLevel) + (delayedR * wetLevel * 0.1); 76 | final wetR = (delayedR * wetLevel) + (delayedL * wetLevel * 0.1); 77 | 78 | // Mix dry and wet signals 79 | final finalL = (inputL[i] * dryLevel) + wetL; 80 | final finalR = (inputR[i] * dryLevel) + wetR; 81 | 82 | outputL[i] = finalL; 83 | outputR[i] = finalR; 84 | 85 | // Advance write index 86 | _writeIndex = (_writeIndex + 1) % _delayBufferSize; 87 | } 88 | } 89 | 90 | /// Soft saturation for controlled feedback limiting 91 | double _softSaturate(double x) { 92 | return x > 1.0 ? 1.0 - (1.0 / (x + 1.0)) : 93 | x < -1.0 ? -1.0 + (1.0 / (-x + 1.0)) : x; 94 | } 95 | 96 | /// Reset delay buffers 97 | void reset() { 98 | _delayBufferL.fillRange(0, _delayBufferSize, 0.0); 99 | _delayBufferR.fillRange(0, _delayBufferSize, 0.0); 100 | _writeIndex = 0; 101 | } 102 | 103 | /// Dispose resources 104 | void dispose() { 105 | _initialized = false; 106 | reset(); 107 | } 108 | } -------------------------------------------------------------------------------- /dart_vst_host/native/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(dart_vst_host LANGUAGES CXX) 3 | 4 | # Build the VST host library for loading and controlling VST3 plugins. 5 | # When consuming from Dart you will need to load 6 | # libdart_vst_host.{so,dylib,dll} from your application. 7 | 8 | set(CMAKE_CXX_STANDARD 17) 9 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 10 | 11 | # Use the properly downloaded VST3 SDK 12 | if(NOT DEFINED ENV{VST3_SDK_DIR}) 13 | set(VST3_SDK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../vst3sdk) 14 | else() 15 | set(VST3_SDK_DIR $ENV{VST3_SDK_DIR}) 16 | endif() 17 | 18 | if(NOT EXISTS ${VST3_SDK_DIR}/CMakeLists.txt) 19 | message(FATAL_ERROR "VST3 SDK not found at ${VST3_SDK_DIR}. Download it first.") 20 | endif() 21 | 22 | # Manually include VST3 sources - SDK requires CMake 3.25+ but we have 3.22 23 | file(GLOB_RECURSE VST3_BASE_SOURCES 24 | ${VST3_SDK_DIR}/base/source/*.cpp 25 | ${VST3_SDK_DIR}/pluginterfaces/base/*.cpp 26 | ${VST3_SDK_DIR}/pluginterfaces/vst/*.cpp 27 | ${VST3_SDK_DIR}/pluginterfaces/gui/*.cpp 28 | ) 29 | 30 | # Add core VST SDK sources - avoid duplicates 31 | file(GLOB VST3_SDK_SOURCES 32 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstbus.cpp 33 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstcomponent.cpp 34 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstcomponentbase.cpp 35 | ${VST3_SDK_DIR}/public.sdk/source/vst/vsteditcontroller.cpp 36 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstinitiids.cpp 37 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstnoteexpressiontypes.cpp 38 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstparameters.cpp 39 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstpresetfile.cpp 40 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstrepresentation.cpp 41 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/hostclasses.cpp 42 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/pluginterfacesupport.cpp 43 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module.cpp 44 | ${VST3_SDK_DIR}/public.sdk/source/common/pluginview.cpp 45 | ${VST3_SDK_DIR}/public.sdk/source/common/commoniids.cpp 46 | ${VST3_SDK_DIR}/public.sdk/source/vst/utility/stringconvert.cpp 47 | ${VST3_SDK_DIR}/public.sdk/source/vst/utility/sampleaccurate.cpp 48 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/eventlist.cpp 49 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/parameterchanges.cpp 50 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/plugprovider.cpp 51 | ${VST3_SDK_DIR}/public.sdk/source/common/updatehandler.cpp 52 | ${VST3_SDK_DIR}/base/thread/source/flock.cpp 53 | ${VST3_SDK_DIR}/base/thread/source/fcondition.cpp 54 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/connectionproxy.cpp 55 | ${VST3_SDK_DIR}/public.sdk/source/common/commonstringconvert.cpp 56 | ) 57 | 58 | include_directories( 59 | ${VST3_SDK_DIR} 60 | ${VST3_SDK_DIR}/pluginterfaces 61 | ${VST3_SDK_DIR}/public.sdk/source 62 | ${VST3_SDK_DIR}/base/thread/include 63 | ${CMAKE_CURRENT_SOURCE_DIR}/include 64 | ) 65 | 66 | # List source files. This library provides VST3 plugin hosting functionality. 67 | add_library(dart_vst_host SHARED 68 | src/dart_vst_host.cpp 69 | ${VST3_BASE_SOURCES} 70 | ${VST3_SDK_SOURCES} 71 | ) 72 | 73 | # Add platform-specific module files 74 | if(APPLE) 75 | target_sources(dart_vst_host PRIVATE 76 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module_mac.mm 77 | ${VST3_SDK_DIR}/public.sdk/source/common/threadchecker_mac.mm 78 | ) 79 | elseif(UNIX) 80 | target_sources(dart_vst_host PRIVATE 81 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module_linux.cpp 82 | ${VST3_SDK_DIR}/public.sdk/source/common/threadchecker_linux.cpp 83 | ) 84 | elseif(WIN32) 85 | target_sources(dart_vst_host PRIVATE 86 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module_win32.cpp 87 | ${VST3_SDK_DIR}/public.sdk/source/common/threadchecker_win32.cpp 88 | ) 89 | endif() 90 | 91 | target_compile_definitions(dart_vst_host PRIVATE 92 | DART_VST_HOST_EXPORTS 93 | RELEASE=1 94 | ) 95 | 96 | if(APPLE) 97 | find_library(COCOA_FRAMEWORK Cocoa) 98 | find_library(CARBON_FRAMEWORK Carbon) 99 | find_library(COREFOUNDATION_FRAMEWORK CoreFoundation) 100 | find_library(AUDIOTOOLBOX_FRAMEWORK AudioToolbox) 101 | target_link_libraries(dart_vst_host 102 | ${COCOA_FRAMEWORK} 103 | ${CARBON_FRAMEWORK} 104 | ${COREFOUNDATION_FRAMEWORK} 105 | ${AUDIOTOOLBOX_FRAMEWORK} 106 | ) 107 | 108 | # Enable Objective-C++ for .mm files with ARC 109 | set_source_files_properties( 110 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module_mac.mm 111 | ${VST3_SDK_DIR}/public.sdk/source/common/threadchecker_mac.mm 112 | PROPERTIES COMPILE_FLAGS "-x objective-c++ -fobjc-arc" 113 | ) 114 | endif() -------------------------------------------------------------------------------- /vsts/echo/test/echo_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import '../lib/src/echo_processor.dart'; 3 | import '../lib/src/echo_parameters.dart'; 4 | import '../lib/src/echo_plugin.dart'; 5 | 6 | void main() { 7 | group('EchoProcessor', () { 8 | test('initializes correctly', () { 9 | final processor = EchoProcessor(); 10 | processor.initialize(44100.0, 512); 11 | 12 | expect(processor, isNotNull); 13 | }); 14 | 15 | test('processes stereo audio without crashing', () { 16 | final processor = EchoProcessor(); 17 | processor.initialize(44100.0, 512); 18 | final parameters = EchoParameters(); 19 | parameters.mix = 0.8; // High mix for obvious effect 20 | parameters.feedback = 0.7; // High feedback 21 | parameters.delayTime = 0.3; // Medium delay 22 | 23 | final inputL = List.filled(128, 0.5); 24 | final inputR = List.filled(128, 0.3); 25 | final outputL = List.filled(128, 0.0); 26 | final outputR = List.filled(128, 0.0); 27 | 28 | processor.processStereo(inputL, inputR, outputL, outputR, parameters); 29 | 30 | // Should produce some output (not all zeros due to dry signal) 31 | expect(outputL.any((sample) => sample != 0.0), isTrue); 32 | expect(outputR.any((sample) => sample != 0.0), isTrue); 33 | }); 34 | 35 | test('produces obvious echo effect', () { 36 | final processor = EchoProcessor(); 37 | processor.initialize(44100.0, 512); 38 | final parameters = EchoParameters(); 39 | parameters.mix = 0.9; // EXTREME mix for testing 40 | parameters.feedback = 0.8; // High feedback 41 | parameters.delayTime = 0.2; // Fast delay 42 | 43 | // Process silence first to clear buffers 44 | final silence = List.filled(128, 0.0); 45 | final output = List.filled(128, 0.0); 46 | processor.processStereo(silence, silence, output, output, parameters); 47 | 48 | // Now process a short impulse 49 | final impulse = List.filled(128, 0.0); 50 | impulse[0] = 1.0; // Single sample impulse 51 | final impulseOutput = List.filled(128, 0.0); 52 | 53 | processor.processStereo(impulse, impulse, impulseOutput, impulseOutput, parameters); 54 | 55 | // Should have dry signal at start 56 | expect(impulseOutput[0], greaterThan(0.0)); 57 | }); 58 | 59 | test('can be reset', () { 60 | final processor = EchoProcessor(); 61 | processor.initialize(44100.0, 512); 62 | 63 | processor.reset(); 64 | 65 | // After reset, should still work 66 | final parameters = EchoParameters(); 67 | parameters.mix = 0.5; // Moderate settings 68 | final input = List.filled(128, 0.1); 69 | final output = List.filled(128, 0.0); 70 | processor.processStereo(input, input, output, output, parameters); 71 | 72 | expect(output.any((sample) => sample != 0.0), isTrue); 73 | }); 74 | }); 75 | 76 | group('DartEchoPlugin', () { 77 | test('initializes correctly', () { 78 | final plugin = DartEchoPlugin(); 79 | expect(plugin.initialize(), isTrue); 80 | }); 81 | 82 | test('has correct plugin info', () { 83 | final plugin = DartEchoPlugin(); 84 | final info = plugin.pluginInfo; 85 | 86 | expect(info['name'], equals('Echo')); 87 | expect(info['vendor'], equals('CF')); 88 | expect(info['inputs'], equals(2)); 89 | expect(info['outputs'], equals(2)); 90 | expect(info['parameters'], equals(4)); // Now has 4 parameters 91 | }); 92 | 93 | test('can process audio without crashing', () { 94 | final plugin = DartEchoPlugin(); 95 | plugin.initialize(); 96 | plugin.setupProcessing(44100.0, 512); 97 | plugin.setActive(true); 98 | 99 | final inputs = [ 100 | List.filled(128, 0.5), 101 | List.filled(128, 0.3) 102 | ]; 103 | final outputs = [ 104 | List.filled(128, 0.0), 105 | List.filled(128, 0.0) 106 | ]; 107 | 108 | plugin.processAudio(inputs, outputs); 109 | 110 | // Should produce some output 111 | expect(outputs[0].any((sample) => sample != 0.0), isTrue); 112 | expect(outputs[1].any((sample) => sample != 0.0), isTrue); 113 | }); 114 | }); 115 | 116 | group('DartEchoFactory', () { 117 | test('creates plugin instances', () { 118 | final plugin = DartEchoFactory.createInstance(); 119 | expect(plugin, isA()); 120 | }); 121 | 122 | test('provides class info', () { 123 | final info = DartEchoFactory.getClassInfo(); 124 | expect(info['name'], equals('Echo')); 125 | expect(info['classId'], equals('DartEcho')); 126 | }); 127 | }); 128 | } -------------------------------------------------------------------------------- /flutter_vst3/lib/src/flutter_vst3_bridge.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi' as ffi; 2 | 3 | /// Abstract interface that all Dart VST3 processors must implement 4 | abstract class VST3Processor { 5 | /// Initialize the processor with sample rate and block size 6 | void initialize(double sampleRate, int maxBlockSize); 7 | 8 | /// Process stereo audio 9 | void processStereo(List inputL, List inputR, 10 | List outputL, List outputR); 11 | 12 | /// Set parameter value (normalized 0.0-1.0) 13 | void setParameter(int paramId, double normalizedValue); 14 | 15 | /// Get parameter value (normalized 0.0-1.0) 16 | double getParameter(int paramId); 17 | 18 | /// Get total number of parameters 19 | int getParameterCount(); 20 | 21 | /// Reset processor state 22 | void reset(); 23 | 24 | /// Dispose resources 25 | void dispose(); 26 | } 27 | 28 | /// Callback function signatures for FFI 29 | typedef DartInitializeProcessorNative = ffi.Void Function(ffi.Double, ffi.Int32); 30 | typedef DartProcessAudioNative = ffi.Void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Int32); 31 | typedef DartSetParameterNative = ffi.Void Function(ffi.Int32, ffi.Double); 32 | typedef DartGetParameterNative = ffi.Double Function(ffi.Int32); 33 | typedef DartGetParameterCountNative = ffi.Int32 Function(); 34 | typedef DartResetNative = ffi.Void Function(); 35 | typedef DartDisposeNative = ffi.Void Function(); 36 | 37 | /// Main FFI bridge between Dart VST3 plugins and C++ VST3 infrastructure 38 | class VST3Bridge { 39 | static VST3Processor? _processor; 40 | 41 | /// Register a Dart VST3 processor with the bridge 42 | /// This must be called before the VST3 plugin can process audio 43 | static void registerProcessor(VST3Processor processor) { 44 | _processor = processor; 45 | print('Dart VST3 processor registered locally (callbacks not yet implemented in FFI layer)'); 46 | } 47 | 48 | /// Initialize the processor 49 | /// Called from C++ when VST3 plugin is initialized 50 | static void initializeProcessor(double sampleRate, int maxBlockSize) { 51 | if (_processor == null) { 52 | throw StateError('CRITICAL VST3 BRIDGE FAILURE: Cannot initialize - no processor registered! Call VST3Bridge.registerProcessor() before using the plugin!'); 53 | } 54 | _processor!.initialize(sampleRate, maxBlockSize); 55 | } 56 | 57 | /// Process stereo audio block 58 | /// Called from C++ VST3 processor during audio processing 59 | static void processAudio(ffi.Pointer inputL, 60 | ffi.Pointer inputR, 61 | ffi.Pointer outputL, 62 | ffi.Pointer outputR, 63 | int numSamples) { 64 | if (_processor == null) { 65 | throw StateError('CRITICAL VST3 BRIDGE FAILURE: No processor registered! Audio processing CANNOT continue without a Dart processor! Call VST3Bridge.registerProcessor() first!'); 66 | } 67 | 68 | // Convert C pointers to Dart lists 69 | final inL = inputL.asTypedList(numSamples); 70 | final inR = inputR.asTypedList(numSamples); 71 | final outL = outputL.asTypedList(numSamples); 72 | final outR = outputR.asTypedList(numSamples); 73 | 74 | // Convert float arrays to double arrays for Dart processing 75 | final dartInL = List.generate(numSamples, (i) => inL[i].toDouble()); 76 | final dartInR = List.generate(numSamples, (i) => inR[i].toDouble()); 77 | final dartOutL = List.filled(numSamples, 0.0); 78 | final dartOutR = List.filled(numSamples, 0.0); 79 | 80 | // Process through Dart processor 81 | _processor!.processStereo(dartInL, dartInR, dartOutL, dartOutR); 82 | 83 | // Convert back to C float arrays 84 | for (int i = 0; i < numSamples; i++) { 85 | outL[i] = dartOutL[i]; 86 | outR[i] = dartOutR[i]; 87 | } 88 | } 89 | 90 | /// Set parameter value 91 | /// Called from C++ when VST3 parameter changes 92 | static void setParameter(int paramId, double normalizedValue) { 93 | _processor?.setParameter(paramId, normalizedValue); 94 | } 95 | 96 | /// Get parameter value 97 | /// Called from C++ when VST3 needs current parameter value 98 | static double getParameter(int paramId) { 99 | return _processor?.getParameter(paramId) ?? 0.0; 100 | } 101 | 102 | /// Get parameter count 103 | static int getParameterCount() { 104 | return _processor?.getParameterCount() ?? 0; 105 | } 106 | 107 | /// Reset processor state 108 | /// Called from C++ when VST3 is activated/deactivated 109 | static void reset() { 110 | _processor?.reset(); 111 | } 112 | 113 | /// Dispose resources 114 | static void dispose() { 115 | _processor?.dispose(); 116 | _processor = null; 117 | } 118 | } -------------------------------------------------------------------------------- /vsts/flutter_reverb/test/reverb_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import '../lib/flutter_reverb_parameters.dart'; 3 | import '../lib/src/reverb_processor.dart'; 4 | 5 | /// Test suite for Flutter Reverb VST3 plugin components 6 | void main() { 7 | print('=== Flutter Reverb Test Suite ===\n'); 8 | 9 | _testReverbParameters(); 10 | _testReverbProcessor(); 11 | 12 | print('🎉 All tests passed! Flutter Reverb components are working correctly.'); 13 | } 14 | 15 | void _testReverbParameters() { 16 | print('Testing ReverbParameters...'); 17 | 18 | final params = ReverbParameters(); 19 | 20 | // Test default values 21 | assert(params.roomSize == 0.5, 'Default room size should be 0.5'); 22 | assert(params.damping == 0.5, 'Default damping should be 0.5'); 23 | assert(params.wetLevel == 0.3, 'Default wet level should be 0.3'); 24 | assert(params.dryLevel == 0.7, 'Default dry level should be 0.7'); 25 | 26 | // Test parameter setting and getting 27 | params.setParameter(ReverbParameters.kRoomSizeParam, 0.8); 28 | assert(params.getParameter(ReverbParameters.kRoomSizeParam) == 0.8, 'Room size should be 0.8'); 29 | 30 | // Test clamping 31 | params.setParameter(ReverbParameters.kWetLevelParam, 1.5); 32 | assert(params.getParameter(ReverbParameters.kWetLevelParam) == 1.0, 'Wet level should be clamped to 1.0'); 33 | 34 | params.setParameter(ReverbParameters.kDryLevelParam, -0.5); 35 | assert(params.getParameter(ReverbParameters.kDryLevelParam) == 0.0, 'Dry level should be clamped to 0.0'); 36 | 37 | // Test parameter names 38 | assert(params.getParameterName(ReverbParameters.kRoomSizeParam) == 'Room Size', 'Room size name should match'); 39 | assert(params.getParameterName(ReverbParameters.kDampingParam) == 'Damping', 'Damping name should match'); 40 | 41 | // Test parameter units 42 | assert(params.getParameterUnits(ReverbParameters.kWetLevelParam) == '%', 'Wet level units should be %'); 43 | 44 | // Test parameter constants 45 | assert(ReverbParameters.kRoomSizeParam == 0, 'Room size param ID should be 0'); 46 | assert(ReverbParameters.kDampingParam == 1, 'Damping param ID should be 1'); 47 | assert(ReverbParameters.kWetLevelParam == 2, 'Wet level param ID should be 2'); 48 | assert(ReverbParameters.kDryLevelParam == 3, 'Dry level param ID should be 3'); 49 | assert(ReverbParameters.numParameters == 4, 'Should have 4 parameters'); 50 | 51 | print('✓ ReverbParameters tests passed\n'); 52 | } 53 | 54 | void _testReverbProcessor() { 55 | print('Testing ReverbProcessor...'); 56 | 57 | final processor = ReverbProcessor(); 58 | processor.initialize(44100.0, 512); 59 | 60 | // Test parameter setting 61 | processor.setParameter(ReverbParameters.kRoomSizeParam, 0.7); 62 | assert(processor.getParameter(ReverbParameters.kRoomSizeParam) == 0.7, 'Processor should store parameter values'); 63 | 64 | // Test different parameter values 65 | processor.setParameter(ReverbParameters.kDampingParam, 0.9); 66 | processor.setParameter(ReverbParameters.kWetLevelParam, 0.4); 67 | processor.setParameter(ReverbParameters.kDryLevelParam, 0.6); 68 | 69 | assert(processor.getParameter(ReverbParameters.kDampingParam) == 0.9, 'Damping should be set'); 70 | assert(processor.getParameter(ReverbParameters.kWetLevelParam) == 0.4, 'Wet level should be set'); 71 | assert(processor.getParameter(ReverbParameters.kDryLevelParam) == 0.6, 'Dry level should be set'); 72 | 73 | // Test audio processing 74 | const numSamples = 100; 75 | final inputL = List.generate(numSamples, (i) => math.sin(i * 0.1) * 0.5); 76 | final inputR = List.generate(numSamples, (i) => math.cos(i * 0.1) * 0.5); 77 | final outputL = List.filled(numSamples, 0.0); 78 | final outputR = List.filled(numSamples, 0.0); 79 | 80 | processor.processStereo(inputL, inputR, outputL, outputR); 81 | 82 | // Verify output is not just zeros (reverb should produce some output) 83 | final hasNonZeroL = outputL.any((sample) => sample.abs() > 0.001); 84 | final hasNonZeroR = outputR.any((sample) => sample.abs() > 0.001); 85 | assert(hasNonZeroL, 'Processor should produce non-zero left output'); 86 | assert(hasNonZeroR, 'Processor should produce non-zero right output'); 87 | 88 | // Test silence processing (should still work) 89 | final silentL = List.filled(numSamples, 0.0); 90 | final silentR = List.filled(numSamples, 0.0); 91 | final outputSilentL = List.filled(numSamples, 0.0); 92 | final outputSilentR = List.filled(numSamples, 0.0); 93 | 94 | processor.processStereo(silentL, silentR, outputSilentL, outputSilentR); 95 | // With silence, output should be very quiet but might not be exactly zero due to reverb tail 96 | 97 | // Test reset 98 | processor.reset(); 99 | 100 | // Test disposal 101 | processor.dispose(); 102 | 103 | print('✓ ReverbProcessor tests passed\n'); 104 | } -------------------------------------------------------------------------------- /vsts/echo/lib/src/echo_plugin.dart: -------------------------------------------------------------------------------- 1 | import 'echo_processor.dart'; 2 | import 'echo_parameters.dart'; 3 | 4 | /// Simple Echo Plugin with natural delay effect and FAIL HARD validation 5 | class DartEchoPlugin { 6 | 7 | EchoProcessor? _processor; 8 | final EchoParameters _parameters = EchoParameters(); 9 | bool _isActive = false; 10 | 11 | /// Initialize the plugin 12 | bool initialize() { 13 | _processor = EchoProcessor(); 14 | return true; 15 | } 16 | 17 | /// Set up audio processing parameters 18 | void setupProcessing(double sampleRate, int maxBlockSize) { 19 | _processor?.initialize(sampleRate, maxBlockSize); 20 | } 21 | 22 | /// Activate/deactivate the plugin 23 | void setActive(bool active) { 24 | if (active && !_isActive) { 25 | _processor?.reset(); 26 | } 27 | _isActive = active; 28 | } 29 | 30 | /// Get plugin information from metadata JSON 31 | Map get pluginInfo { 32 | return { 33 | 'name': 'Echo', 34 | 'vendor': 'CF', 35 | 'version': '1.0.0', 36 | 'category': 'Fx|Delay', 37 | 'type': 'effect', 38 | 'inputs': 2, 39 | 'outputs': 2, 40 | 'parameters': 4, 41 | 'canProcessReplacing': true, 42 | 'hasEditor': false, 43 | }; 44 | } 45 | 46 | /// Set parameter value by index 47 | void setParameter(int index, double value) { 48 | switch (index) { 49 | case 0: 50 | _parameters.delayTime = value; 51 | break; 52 | case 1: 53 | _parameters.feedback = value; 54 | break; 55 | case 2: 56 | _parameters.mix = value; 57 | break; 58 | case 3: 59 | _parameters.bypass = value; 60 | break; 61 | } 62 | } 63 | 64 | /// Get parameter value by index 65 | double getParameter(int index) { 66 | return switch (index) { 67 | 0 => _parameters.delayTime, 68 | 1 => _parameters.feedback, 69 | 2 => _parameters.mix, 70 | 3 => _parameters.bypass, 71 | _ => 0.0, 72 | }; 73 | } 74 | 75 | /// Get parameter info by index 76 | Map getParameterInfo(int index) { 77 | return switch (index) { 78 | 0 => { 79 | 'name': 'delayTime', 80 | 'displayName': 'Delay Time', 81 | 'description': 'Controls the delay time in milliseconds (0ms to 500ms)', 82 | 'defaultValue': 0.5, 83 | 'units': 'ms', 84 | }, 85 | 1 => { 86 | 'name': 'feedback', 87 | 'displayName': 'Feedback', 88 | 'description': 'Controls the feedback amount (0% = single echo, 85% = max stable)', 89 | 'defaultValue': 0.3, 90 | 'units': '%', 91 | }, 92 | 2 => { 93 | 'name': 'mix', 94 | 'displayName': 'Mix', 95 | 'description': 'Controls the wet/dry mix (0% = dry only, 100% = wet only)', 96 | 'defaultValue': 0.5, 97 | 'units': '%', 98 | }, 99 | 3 => { 100 | 'name': 'bypass', 101 | 'displayName': 'Bypass', 102 | 'description': 'Bypasses the echo effect when enabled', 103 | 'defaultValue': 0.0, 104 | 'units': '', 105 | }, 106 | _ => {}, 107 | }; 108 | } 109 | 110 | /// Process audio block 111 | void processAudio(List> inputs, List> outputs) { 112 | if (!_isActive || inputs.isEmpty || outputs.isEmpty) { 113 | return; 114 | } 115 | 116 | // Ensure we have stereo inputs and outputs 117 | final inputL = inputs.isNotEmpty ? inputs[0] : []; 118 | final inputR = inputs.length > 1 ? inputs[1] : inputL; 119 | 120 | if (outputs.isEmpty) return; 121 | final outputL = outputs[0]; 122 | final outputR = outputs.length > 1 ? outputs[1] : outputL; 123 | 124 | if (inputL.isEmpty || outputL.isEmpty) return; 125 | 126 | // Process the audio through the echo effect with parameters 127 | if (_processor == null) { 128 | throw StateError('CRITICAL PLUGIN FAILURE: EchoProcessor not initialized!'); 129 | } 130 | 131 | // Use current parameter values (not hardcoded defaults) 132 | 133 | _processor!.processStereo(inputL, inputR, outputL, outputR, _parameters); 134 | } 135 | 136 | /// Dispose resources 137 | void dispose() { 138 | _isActive = false; 139 | _processor?.dispose(); 140 | } 141 | } 142 | 143 | /// Factory for creating echo plugin instances 144 | class DartEchoFactory { 145 | /// Create a new plugin instance 146 | static DartEchoPlugin createInstance() { 147 | return DartEchoPlugin(); 148 | } 149 | 150 | /// Get plugin class information from metadata JSON 151 | static Map getClassInfo() { 152 | return { 153 | 'name': 'Echo', 154 | 'vendor': 'CF', 155 | 'version': '1.0.0', 156 | 'category': 'Fx|Delay', 157 | 'classId': 'DartEcho', 158 | 'controllerId': 'DartEchoController', 159 | }; 160 | } 161 | } -------------------------------------------------------------------------------- /dart_vst_graph/native/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(dart_vst_graph LANGUAGES CXX) 3 | 4 | # Build the VST audio graph library for connecting and routing VST plugins. 5 | # When consuming from Dart you will need to load 6 | # libdart_vst_graph.{so,dylib,dll} from your application. 7 | 8 | set(CMAKE_CXX_STANDARD 17) 9 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 10 | 11 | # Use the properly downloaded VST3 SDK 12 | if(NOT DEFINED ENV{VST3_SDK_DIR}) 13 | set(VST3_SDK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../vst3sdk) 14 | else() 15 | set(VST3_SDK_DIR $ENV{VST3_SDK_DIR}) 16 | endif() 17 | 18 | if(NOT EXISTS ${VST3_SDK_DIR}/CMakeLists.txt) 19 | message(FATAL_ERROR "VST3 SDK not found at ${VST3_SDK_DIR}. Download it first.") 20 | endif() 21 | 22 | # Manually include VST3 sources - SDK requires CMake 3.25+ but we have 3.22 23 | file(GLOB_RECURSE VST3_BASE_SOURCES 24 | ${VST3_SDK_DIR}/base/source/*.cpp 25 | ${VST3_SDK_DIR}/pluginterfaces/base/*.cpp 26 | ${VST3_SDK_DIR}/pluginterfaces/vst/*.cpp 27 | ${VST3_SDK_DIR}/pluginterfaces/gui/*.cpp 28 | ) 29 | 30 | # Add core VST SDK sources for hosting 31 | file(GLOB VST3_SDK_SOURCES 32 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstbus.cpp 33 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstcomponent.cpp 34 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstcomponentbase.cpp 35 | ${VST3_SDK_DIR}/public.sdk/source/vst/vsteditcontroller.cpp 36 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstinitiids.cpp 37 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstnoteexpressiontypes.cpp 38 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstparameters.cpp 39 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstpresetfile.cpp 40 | ${VST3_SDK_DIR}/public.sdk/source/vst/vstrepresentation.cpp 41 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/hostclasses.cpp 42 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/pluginterfacesupport.cpp 43 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module.cpp 44 | ${VST3_SDK_DIR}/public.sdk/source/common/pluginview.cpp 45 | ${VST3_SDK_DIR}/public.sdk/source/common/commoniids.cpp 46 | ${VST3_SDK_DIR}/public.sdk/source/vst/utility/stringconvert.cpp 47 | ${VST3_SDK_DIR}/public.sdk/source/vst/utility/sampleaccurate.cpp 48 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/eventlist.cpp 49 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/parameterchanges.cpp 50 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/plugprovider.cpp 51 | ${VST3_SDK_DIR}/public.sdk/source/common/updatehandler.cpp 52 | ${VST3_SDK_DIR}/base/thread/source/flock.cpp 53 | ${VST3_SDK_DIR}/base/thread/source/fcondition.cpp 54 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/connectionproxy.cpp 55 | ${VST3_SDK_DIR}/public.sdk/source/common/commonstringconvert.cpp 56 | ) 57 | 58 | include_directories( 59 | ${VST3_SDK_DIR} 60 | ${VST3_SDK_DIR}/pluginterfaces 61 | ${VST3_SDK_DIR}/public.sdk/source 62 | ${VST3_SDK_DIR}/base/thread/include 63 | ${CMAKE_CURRENT_SOURCE_DIR}/include 64 | ${CMAKE_CURRENT_SOURCE_DIR}/../../dart_vst_host/native/include 65 | ) 66 | 67 | # List source files. This library provides audio graph functionality. 68 | add_library(dart_vst_graph SHARED 69 | src/graph.cpp 70 | ${VST3_BASE_SOURCES} 71 | ${VST3_SDK_SOURCES} 72 | ) 73 | 74 | # Add platform-specific module files 75 | if(APPLE) 76 | target_sources(dart_vst_graph PRIVATE 77 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module_mac.mm 78 | ${VST3_SDK_DIR}/public.sdk/source/common/threadchecker_mac.mm 79 | ) 80 | elseif(UNIX) 81 | target_sources(dart_vst_graph PRIVATE 82 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module_linux.cpp 83 | ${VST3_SDK_DIR}/public.sdk/source/common/threadchecker_linux.cpp 84 | ) 85 | elseif(WIN32) 86 | target_sources(dart_vst_graph PRIVATE 87 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module_win32.cpp 88 | ${VST3_SDK_DIR}/public.sdk/source/common/threadchecker_win32.cpp 89 | ) 90 | endif() 91 | 92 | target_compile_definitions(dart_vst_graph PRIVATE 93 | DART_VST_HOST_EXPORTS 94 | RELEASE=1 95 | ) 96 | 97 | # Link dart_vst_host library 98 | set(DART_VST_HOST_LIB "${CMAKE_CURRENT_SOURCE_DIR}/../../dart_vst_host/native/build/libdart_vst_host.dylib") 99 | if(EXISTS ${DART_VST_HOST_LIB}) 100 | target_link_libraries(dart_vst_graph ${DART_VST_HOST_LIB}) 101 | endif() 102 | 103 | if(APPLE) 104 | find_library(COCOA_FRAMEWORK Cocoa) 105 | find_library(CARBON_FRAMEWORK Carbon) 106 | find_library(COREFOUNDATION_FRAMEWORK CoreFoundation) 107 | find_library(AUDIOTOOLBOX_FRAMEWORK AudioToolbox) 108 | target_link_libraries(dart_vst_graph 109 | ${COCOA_FRAMEWORK} 110 | ${CARBON_FRAMEWORK} 111 | ${COREFOUNDATION_FRAMEWORK} 112 | ${AUDIOTOOLBOX_FRAMEWORK} 113 | ) 114 | 115 | # Enable Objective-C++ for .mm files with ARC 116 | set_source_files_properties( 117 | ${VST3_SDK_DIR}/public.sdk/source/vst/hosting/module_mac.mm 118 | ${VST3_SDK_DIR}/public.sdk/source/common/threadchecker_mac.mm 119 | PROPERTIES COMPILE_FLAGS "-x objective-c++ -fobjc-arc" 120 | ) 121 | endif() -------------------------------------------------------------------------------- /dart_vst_host/lib/src/bindings.dart: -------------------------------------------------------------------------------- 1 | /// Dart FFI bindings to the native Dart VST host library. This 2 | /// translation mirrors the C API defined in dart_vst_host.h. It 3 | /// provides low‑level functions for creating a host, loading VST 4 | /// plug‑ins and processing audio. Higher level classes are defined 5 | /// in host.dart which wrap these bindings in a safer API. 6 | 7 | import 'dart:ffi'; 8 | import 'dart:io'; 9 | import 'package:ffi/ffi.dart'; 10 | 11 | // Type definitions matching the C API signatures. Each typedef 12 | // corresponds to a C function pointer. See dart_vst_host.h for 13 | // documentation on each function. 14 | typedef _HostCreateC = Pointer Function(Double, Int32); 15 | typedef _HostDestroyC = Void Function(Pointer); 16 | 17 | typedef _LoadC = Pointer Function(Pointer, Pointer, Pointer); 18 | typedef _UnloadC = Void Function(Pointer); 19 | 20 | typedef _ResumeC = Int32 Function(Pointer, Double, Int32); 21 | typedef _SuspendC = Int32 Function(Pointer); 22 | 23 | typedef _ProcessStereoC = Int32 Function( 24 | Pointer, 25 | Pointer, Pointer, 26 | Pointer, Pointer, 27 | Int32); 28 | 29 | typedef _NoteC = Int32 Function(Pointer, Int32, Int32, Float); 30 | 31 | typedef _ParamCountC = Int32 Function(Pointer); 32 | typedef _ParamInfoC = Int32 Function(Pointer, Int32, Pointer, Pointer, Int32, Pointer, Int32); 33 | typedef _GetParamC = Float Function(Pointer, Int32); 34 | typedef _SetParamC = Int32 Function(Pointer, Int32, Float); 35 | 36 | /// Wrapper around the dynamic library providing access to the C 37 | /// functions. Users generally should not use this directly; instead 38 | /// use the VstHost and VstPlugin classes in host.dart which manage 39 | /// resources safely. 40 | class NativeBindings { 41 | final DynamicLibrary lib; 42 | 43 | NativeBindings(this.lib); 44 | 45 | late final Pointer Function(double, int) dvhCreateHost = 46 | lib.lookupFunction<_HostCreateC, Pointer Function(double, int)>('dvh_create_host'); 47 | 48 | late final void Function(Pointer) dvhDestroyHost = 49 | lib.lookupFunction<_HostDestroyC, void Function(Pointer)>('dvh_destroy_host'); 50 | 51 | late final Pointer Function(Pointer, Pointer, Pointer) dvhLoadPlugin = 52 | lib.lookupFunction<_LoadC, Pointer Function(Pointer, Pointer, Pointer)>('dvh_load_plugin'); 53 | 54 | late final void Function(Pointer) dvhUnloadPlugin = 55 | lib.lookupFunction<_UnloadC, void Function(Pointer)>('dvh_unload_plugin'); 56 | 57 | late final int Function(Pointer, double, int) dvhResume = 58 | lib.lookupFunction<_ResumeC, int Function(Pointer, double, int)>('dvh_resume'); 59 | 60 | late final int Function(Pointer) dvhSuspend = 61 | lib.lookupFunction<_SuspendC, int Function(Pointer)>('dvh_suspend'); 62 | 63 | late final int Function(Pointer, Pointer, Pointer, Pointer, Pointer, int) dvhProcessStereoF32 = 64 | lib.lookupFunction<_ProcessStereoC, int Function(Pointer, Pointer, Pointer, Pointer, Pointer, int)>('dvh_process_stereo_f32'); 65 | 66 | late final int Function(Pointer, int, int, double) dvhNoteOn = 67 | lib.lookupFunction<_NoteC, int Function(Pointer, int, int, double)>('dvh_note_on'); 68 | 69 | late final int Function(Pointer, int, int, double) dvhNoteOff = 70 | lib.lookupFunction<_NoteC, int Function(Pointer, int, int, double)>('dvh_note_off'); 71 | 72 | late final int Function(Pointer) dvhParamCount = 73 | lib.lookupFunction<_ParamCountC, int Function(Pointer)>('dvh_param_count'); 74 | 75 | late final int Function(Pointer, int, Pointer, Pointer, int, Pointer, int) dvhParamInfo = 76 | lib.lookupFunction<_ParamInfoC, int Function(Pointer, int, Pointer, Pointer, int, Pointer, int)>('dvh_param_info'); 77 | 78 | late final double Function(Pointer, int) dvhGetParam = 79 | lib.lookupFunction<_GetParamC, double Function(Pointer, int)>('dvh_get_param_normalized'); 80 | 81 | late final int Function(Pointer, int, double) dvhSetParam = 82 | lib.lookupFunction<_SetParamC, int Function(Pointer, int, double)>('dvh_set_param_normalized'); 83 | } 84 | 85 | /// Load the native library. The optional [path] may be used to point 86 | /// directly at libdart_vst_host.{so,dylib,dll}. On platforms where 87 | /// dynamic library lookup is provided by the process/executable, this 88 | /// falls back accordingly. 89 | DynamicLibrary loadDvh({String? path}) { 90 | if (path != null) return DynamicLibrary.open(path); 91 | if (Platform.isMacOS) return DynamicLibrary.open('libdart_vst_host.dylib'); 92 | if (Platform.isLinux) return DynamicLibrary.open('libdart_vst_host.so'); 93 | if (Platform.isWindows) return DynamicLibrary.open('dart_vst_host.dll'); 94 | // Use process/executable fallback when available 95 | try { 96 | return DynamicLibrary.process(); 97 | } catch (_) {} 98 | try { 99 | return DynamicLibrary.executable(); 100 | } catch (_) {} 101 | throw UnsupportedError('Unable to locate native dart_vst_host library'); 102 | } -------------------------------------------------------------------------------- /flutter_vst3/lib/src/flutter_vst3_callbacks.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi' as ffi; 2 | import 'dart:io'; 3 | import 'package:ffi/ffi.dart'; 4 | import 'flutter_vst3_bridge.dart'; 5 | 6 | /// C function type definitions for FFI callbacks 7 | typedef InitializeProcessorC = ffi.Void Function(ffi.Double sampleRate, ffi.Int32 maxBlockSize); 8 | typedef InitializeProcessorDart = void Function(double sampleRate, int maxBlockSize); 9 | 10 | typedef ProcessAudioC = ffi.Void Function(ffi.Pointer inputL, 11 | ffi.Pointer inputR, 12 | ffi.Pointer outputL, 13 | ffi.Pointer outputR, 14 | ffi.Int32 numSamples); 15 | typedef ProcessAudioDart = void Function(ffi.Pointer inputL, 16 | ffi.Pointer inputR, 17 | ffi.Pointer outputL, 18 | ffi.Pointer outputR, 19 | int numSamples); 20 | 21 | typedef SetParameterC = ffi.Void Function(ffi.Int32 paramId, ffi.Double normalizedValue); 22 | typedef SetParameterDart = void Function(int paramId, double normalizedValue); 23 | 24 | typedef GetParameterC = ffi.Double Function(ffi.Int32 paramId); 25 | typedef GetParameterDart = double Function(int paramId); 26 | 27 | typedef GetParameterCountC = ffi.Int32 Function(); 28 | typedef GetParameterCountDart = int Function(); 29 | 30 | typedef ResetC = ffi.Void Function(); 31 | typedef ResetDart = void Function(); 32 | 33 | typedef DisposeC = ffi.Void Function(); 34 | typedef DisposeDart = void Function(); 35 | 36 | // C++ function bindings 37 | typedef CreateInstanceC = ffi.Pointer Function(ffi.Pointer); 38 | typedef CreateInstanceDart = ffi.Pointer Function(ffi.Pointer); 39 | 40 | typedef RegisterCallbacksC = ffi.Int32 Function(ffi.Pointer, ffi.Pointer); 41 | typedef RegisterCallbacksDart = int Function(ffi.Pointer, ffi.Pointer); 42 | 43 | // Struct matching the C++ DartVST3Callbacks structure 44 | final class DartVST3Callbacks extends ffi.Struct { 45 | external ffi.Pointer> initializeProcessor; 46 | external ffi.Pointer> processAudio; 47 | external ffi.Pointer> setParameter; 48 | external ffi.Pointer> getParameter; 49 | external ffi.Pointer> getParameterCount; 50 | external ffi.Pointer> reset; 51 | external ffi.Pointer> dispose; 52 | } 53 | 54 | /// Register Dart callbacks with C++ layer 55 | /// This MUST be called before the VST3 plugin can use the Dart processor 56 | void registerVST3Callbacks({required String pluginId}) { 57 | try { 58 | // Load the native library 59 | late ffi.DynamicLibrary lib; 60 | if (Platform.isMacOS) { 61 | lib = ffi.DynamicLibrary.open('libdart_vst_host.dylib'); 62 | } else if (Platform.isLinux) { 63 | lib = ffi.DynamicLibrary.open('libdart_vst_host.so'); 64 | } else if (Platform.isWindows) { 65 | lib = ffi.DynamicLibrary.open('dart_vst_host.dll'); 66 | } else { 67 | throw UnsupportedError('Unsupported platform for VST3 callbacks'); 68 | } 69 | 70 | // Get C++ function pointers 71 | final createInstance = lib.lookupFunction( 72 | 'dart_vst3_create_instance'); 73 | final registerCallbacks = lib.lookupFunction( 74 | 'dart_vst3_register_callbacks'); 75 | 76 | // Create plugin instance 77 | final pluginIdPtr = pluginId.toNativeUtf8(); 78 | final instance = createInstance(pluginIdPtr); 79 | 80 | if (instance == ffi.nullptr) { 81 | throw StateError('CRITICAL VST3 BRIDGE FAILURE: Failed to create plugin instance for $pluginId'); 82 | } 83 | 84 | // Create callback structure and populate with Dart function pointers 85 | final callbacks = calloc(); 86 | callbacks.ref.initializeProcessor = ffi.Pointer.fromFunction( 87 | VST3Bridge.initializeProcessor); 88 | callbacks.ref.processAudio = ffi.Pointer.fromFunction( 89 | VST3Bridge.processAudio); 90 | callbacks.ref.setParameter = ffi.Pointer.fromFunction( 91 | VST3Bridge.setParameter); 92 | callbacks.ref.getParameter = ffi.Pointer.fromFunction( 93 | VST3Bridge.getParameter, 0.0); 94 | callbacks.ref.getParameterCount = ffi.Pointer.fromFunction( 95 | VST3Bridge.getParameterCount, 0); 96 | callbacks.ref.reset = ffi.Pointer.fromFunction( 97 | VST3Bridge.reset); 98 | callbacks.ref.dispose = ffi.Pointer.fromFunction( 99 | VST3Bridge.dispose); 100 | 101 | // Register callbacks with C++ bridge 102 | final result = registerCallbacks(instance, callbacks); 103 | 104 | if (result == 0) { 105 | calloc.free(callbacks); 106 | throw StateError('CRITICAL VST3 BRIDGE FAILURE: Failed to register callbacks for $pluginId'); 107 | } 108 | 109 | // Cleanup 110 | calloc.free(callbacks); 111 | print('VST3 callbacks successfully registered for $pluginId'); 112 | 113 | } catch (e, stackTrace) { 114 | print('FATAL VST3 BRIDGE ERROR: Failed to register callbacks for $pluginId'); 115 | print('Error: $e'); 116 | print('Stack trace: $stackTrace'); 117 | throw StateError('VST3 bridge initialization failed: $e'); 118 | } 119 | } -------------------------------------------------------------------------------- /flutter_vst3/native/src/dart_vst3_bridge.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // 3 | // Implementation of the generic C bridge for calling Dart VST3 processors 4 | // from C++. This manages callback functions per plugin instance and provides 5 | // a universal C API that any VST3 processor can use. 6 | 7 | #include "dart_vst3_bridge.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // Per-instance data structure 17 | struct DartVST3Instance { 18 | std::string plugin_id; 19 | DartVST3Callbacks callbacks; 20 | bool callbacks_registered; 21 | std::mutex mutex; 22 | 23 | DartVST3Instance(const std::string& id) 24 | : plugin_id(id), callbacks{0}, callbacks_registered(false) {} 25 | }; 26 | 27 | // Global instance registry 28 | static std::unordered_map> g_instances; 29 | static std::mutex g_instances_mutex; 30 | 31 | extern "C" { 32 | 33 | DartVST3Instance* dart_vst3_create_instance(const char* plugin_id) { 34 | std::lock_guard lock(g_instances_mutex); 35 | 36 | if (!plugin_id) return nullptr; 37 | 38 | auto instance = std::make_unique(plugin_id); 39 | auto* raw_ptr = instance.get(); 40 | g_instances[raw_ptr] = std::move(instance); 41 | 42 | return raw_ptr; 43 | } 44 | 45 | int32_t dart_vst3_destroy_instance(DartVST3Instance* instance) { 46 | std::lock_guard lock(g_instances_mutex); 47 | 48 | if (!instance) return 0; 49 | 50 | auto it = g_instances.find(instance); 51 | if (it != g_instances.end()) { 52 | g_instances.erase(it); 53 | return 1; 54 | } 55 | 56 | return 0; 57 | } 58 | 59 | int32_t dart_vst3_register_callbacks(DartVST3Instance* instance, 60 | const DartVST3Callbacks* callbacks) { 61 | if (!instance || !callbacks) return 0; 62 | 63 | std::lock_guard lock(instance->mutex); 64 | 65 | // Copy all callback function pointers 66 | instance->callbacks = *callbacks; 67 | instance->callbacks_registered = true; 68 | 69 | return 1; 70 | } 71 | 72 | int32_t dart_vst3_initialize(DartVST3Instance* instance, 73 | double sample_rate, int32_t max_block_size) { 74 | if (!instance) return 0; 75 | 76 | std::lock_guard lock(instance->mutex); 77 | 78 | if (!instance->callbacks_registered || !instance->callbacks.initialize_processor) { 79 | return 0; 80 | } 81 | 82 | instance->callbacks.initialize_processor(sample_rate, max_block_size); 83 | return 1; 84 | } 85 | 86 | int32_t dart_vst3_process_stereo(DartVST3Instance* instance, 87 | const float* input_l, const float* input_r, 88 | float* output_l, float* output_r, 89 | int32_t num_samples) { 90 | if (!instance) return 0; 91 | 92 | std::lock_guard lock(instance->mutex); 93 | 94 | if (!instance->callbacks_registered || !instance->callbacks.process_audio) { 95 | // NO FALLBACKS! FAIL HARD! CRASH THE ENTIRE PLUGIN! 96 | fprintf(stderr, "CRITICAL VST3 BRIDGE FAILURE: No Dart callbacks registered! Plugin ID: %s\n", instance->plugin_id.c_str()); 97 | fprintf(stderr, "CALLBACKS_REGISTERED: %d\n", instance->callbacks_registered); 98 | fprintf(stderr, "PROCESS_AUDIO CALLBACK: %p\n", (void*)instance->callbacks.process_audio); 99 | fflush(stderr); 100 | abort(); // KILL THE PLUGIN HARD! 101 | } 102 | 103 | instance->callbacks.process_audio(input_l, input_r, output_l, output_r, num_samples); 104 | return 1; 105 | } 106 | 107 | int32_t dart_vst3_set_parameter(DartVST3Instance* instance, 108 | int32_t param_id, double normalized_value) { 109 | if (!instance) return 0; 110 | 111 | std::lock_guard lock(instance->mutex); 112 | 113 | if (!instance->callbacks_registered || !instance->callbacks.set_parameter) { 114 | return 0; 115 | } 116 | 117 | instance->callbacks.set_parameter(param_id, normalized_value); 118 | return 1; 119 | } 120 | 121 | double dart_vst3_get_parameter(DartVST3Instance* instance, int32_t param_id) { 122 | if (!instance) return 0.0; 123 | 124 | std::lock_guard lock(instance->mutex); 125 | 126 | if (!instance->callbacks_registered || !instance->callbacks.get_parameter) { 127 | return 0.0; 128 | } 129 | 130 | return instance->callbacks.get_parameter(param_id); 131 | } 132 | 133 | int32_t dart_vst3_get_parameter_count(DartVST3Instance* instance) { 134 | if (!instance) return 0; 135 | 136 | std::lock_guard lock(instance->mutex); 137 | 138 | if (!instance->callbacks_registered || !instance->callbacks.get_parameter_count) { 139 | return 0; 140 | } 141 | 142 | return instance->callbacks.get_parameter_count(); 143 | } 144 | 145 | int32_t dart_vst3_reset(DartVST3Instance* instance) { 146 | if (!instance) return 0; 147 | 148 | std::lock_guard lock(instance->mutex); 149 | 150 | if (!instance->callbacks_registered || !instance->callbacks.reset) { 151 | return 0; 152 | } 153 | 154 | instance->callbacks.reset(); 155 | return 1; 156 | } 157 | 158 | int32_t dart_vst3_dispose(DartVST3Instance* instance) { 159 | if (!instance) return 0; 160 | 161 | std::lock_guard lock(instance->mutex); 162 | 163 | if (!instance->callbacks_registered || !instance->callbacks.dispose) { 164 | return 0; 165 | } 166 | 167 | instance->callbacks.dispose(); 168 | return 1; 169 | } 170 | 171 | } // extern "C" -------------------------------------------------------------------------------- /flutter_vst3/native/src/plugin_processor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // 3 | // Implementation of the audio processor for the Dart VST host plug‑in. 4 | // This component wraps the Graph API and exposes it as a VST3 5 | // processor. It forwards incoming audio and MIDI to the graph and 6 | // outputs the processed result. Automation of the exposed gain 7 | // parameter is mapped to the graph’s gain node. 8 | 9 | #include "plugin_ids.h" 10 | #include "pluginterfaces/base/ibstream.h" 11 | #include "pluginterfaces/base/ipluginbase.h" 12 | #include "pluginterfaces/vst/ivstaudioprocessor.h" 13 | #include "pluginterfaces/vst/ivstevents.h" 14 | #include "pluginterfaces/vst/ivstparameterchanges.h" 15 | #include "public.sdk/source/vst/vstaudioeffect.h" 16 | 17 | #include "dvh_graph.h" 18 | 19 | #define FULL_VERSION_STR "1.0.0" 20 | 21 | using namespace Steinberg; 22 | using namespace Steinberg::Vst; 23 | 24 | // The processor derives from AudioEffect and holds an instance of the 25 | // graph. It wires up buses and responds to parameter and event 26 | // messages from the host. 27 | class DvhProcessor : public AudioEffect { 28 | public: 29 | DvhProcessor() { setControllerClass(kControllerUID); } 30 | ~DvhProcessor() override { 31 | if (graph_) dvh_graph_destroy(graph_); 32 | } 33 | 34 | tresult PLUGIN_API initialize(FUnknown* ctx) override { 35 | tresult r = AudioEffect::initialize(ctx); 36 | if (r != kResultTrue) return r; 37 | 38 | // Add stereo audio inputs and outputs. Use auxiliary type for 39 | // inputs to allow both main input and sidechain. Only the main 40 | // output is required. 41 | addAudioInput(STR16("Main In"), SpeakerArr::kStereo, kAux); 42 | addAudioInput(STR16("Sidechain"), SpeakerArr::kStereo, kAux); 43 | addAudioOutput(STR16("Main Out"), SpeakerArr::kStereo, kMain); 44 | 45 | // MIDI input for note events 46 | addEventInput(STR16("MIDI In"), 16); 47 | 48 | // Create the graph with a default sample rate and block size. These 49 | // will be updated in setupProcessing(). 50 | graph_ = dvh_graph_create(48000.0, 1024); 51 | // Construct a minimal internal graph: input -> mixer -> gain -> output 52 | int32_t inNode = -1, outNode = -1, mix = -1, gain = -1; 53 | dvh_graph_add_split(graph_, &inNode); 54 | dvh_graph_add_mixer(graph_, 3, &mix); 55 | dvh_graph_add_gain(graph_, 0.0f, &gain); 56 | dvh_graph_add_split(graph_, &outNode); 57 | 58 | dvh_graph_connect(graph_, inNode, 0, mix, 0); 59 | dvh_graph_connect(graph_, mix, 0, gain, 0); 60 | dvh_graph_connect(graph_, gain, 0, outNode, 0); 61 | dvh_graph_set_io_nodes(graph_, inNode, outNode); 62 | // Remember the index of the gain node (2) for automation later 63 | gainNode_ = gain; 64 | return r; 65 | } 66 | 67 | tresult PLUGIN_API setBusArrangements(SpeakerArrangement* inputs, int32 numIns, 68 | SpeakerArrangement* outputs, int32 numOuts) override { 69 | // We only support one stereo output 70 | if (numOuts != 1 || outputs[0] != SpeakerArr::kStereo) return kResultFalse; 71 | return kResultTrue; 72 | } 73 | 74 | tresult PLUGIN_API setupProcessing(ProcessSetup& s) override { 75 | setup_ = s; 76 | // Recreate the graph with the actual sample rate and maximum block 77 | // size when changed. The current implementation does not rebuild 78 | // nodes on the fly but this could be extended. 79 | return kResultTrue; 80 | } 81 | 82 | tresult PLUGIN_API setActive(TBool state) override { 83 | return AudioEffect::setActive(state); 84 | } 85 | 86 | tresult PLUGIN_API process(ProcessData& data) override { 87 | if (!graph_) return kResultFalse; 88 | 89 | // Apply parameter changes from automation. Only one parameter for 90 | // gain is implemented. Iterate through all incoming changes and 91 | // update the graph accordingly. 92 | if (data.inputParameterChanges) { 93 | int32 listCount = data.inputParameterChanges->getParameterCount(); 94 | for (int32 i = 0; i < listCount; ++i) { 95 | IParamValueQueue* q = data.inputParameterChanges->getParameterData(i); 96 | if (!q) continue; 97 | int32 index; 98 | ParamValue v; 99 | int32 sampleOffset; 100 | // Use the last point in the queue as the effective value 101 | if (q->getPoint(q->getPointCount() - 1, sampleOffset, v) == kResultTrue) { 102 | if (q->getParameterId() == kParamOutputGain) { 103 | dvh_graph_set_param(graph_, gainNode_, 0, (float)v); 104 | } 105 | } 106 | } 107 | } 108 | 109 | // Dispatch MIDI events 110 | if (data.inputEvents) { 111 | int32 n = data.inputEvents->getEventCount(); 112 | for (int32 i = 0; i < n; ++i) { 113 | Event e; 114 | if (data.inputEvents->getEvent(i, e) != kResultTrue) continue; 115 | if (e.type == Event::kNoteOnEvent) dvh_graph_note_on(graph_, -1, e.noteOn.channel, e.noteOn.pitch, e.noteOn.velocity); 116 | if (e.type == Event::kNoteOffEvent) dvh_graph_note_off(graph_, -1, e.noteOff.channel, e.noteOff.pitch, e.noteOff.velocity); 117 | } 118 | } 119 | 120 | // Determine pointers to input and output channel buffers. 121 | const float* inL = nullptr; 122 | const float* inR = nullptr; 123 | if (data.numInputs > 0) { 124 | auto in0 = &data.inputs[0]; 125 | if (in0->numChannels >= 2) { 126 | inL = in0->channelBuffers32[0]; 127 | inR = in0->channelBuffers32[1]; 128 | } 129 | } 130 | float* outL = nullptr; 131 | float* outR = nullptr; 132 | if (data.numOutputs > 0) { 133 | auto out0 = &data.outputs[0]; 134 | if (out0->numChannels >= 2) { 135 | outL = out0->channelBuffers32[0]; 136 | outR = out0->channelBuffers32[1]; 137 | } 138 | } 139 | if (!outL || !outR) return kResultFalse; 140 | // Provide zero input if no buffers are connected (e.g. instrument) 141 | if (!inL || !inR) { 142 | static float zeros[4096] = {0}; 143 | inL = inR = zeros; 144 | } 145 | 146 | if (dvh_graph_process_stereo(graph_, inL, inR, outL, outR, data.numSamples) != 1) 147 | return kResultFalse; 148 | 149 | return kResultTrue; 150 | } 151 | 152 | private: 153 | DVH_Graph graph_{nullptr}; 154 | ProcessSetup setup_{}; 155 | int32_t gainNode_ = -1; 156 | }; 157 | 158 | // Factory function for the processor 159 | FUnknown* createDvhProcessor(void*) { 160 | return (IAudioProcessor*)new DvhProcessor(); 161 | } -------------------------------------------------------------------------------- /vsts/flutter_reverb/lib/src/reverb_processor.dart: -------------------------------------------------------------------------------- 1 | import '../flutter_reverb_parameters.dart'; 2 | 3 | /// Comb filter implementation for reverb 4 | class CombFilter { 5 | final List _buffer; 6 | int _bufferIndex = 0; 7 | double _feedback = 0.5; 8 | double _dampening = 0.5; 9 | double _filterStore = 0.0; 10 | 11 | CombFilter(int bufferSize) : _buffer = List.filled(bufferSize, 0.0); 12 | 13 | double process(double input) { 14 | final output = _buffer[_bufferIndex]; 15 | _filterStore = (output * (1.0 - _dampening)) + (_filterStore * _dampening); 16 | _buffer[_bufferIndex] = input + (_filterStore * _feedback); 17 | 18 | _bufferIndex = (_bufferIndex + 1) % _buffer.length; 19 | return output; 20 | } 21 | 22 | void setFeedback(double feedback) => _feedback = feedback; 23 | void setDampening(double dampening) => _dampening = dampening; 24 | 25 | void reset() { 26 | _buffer.fillRange(0, _buffer.length, 0.0); 27 | _bufferIndex = 0; 28 | _filterStore = 0.0; 29 | } 30 | } 31 | 32 | /// Allpass filter implementation for reverb 33 | class AllpassFilter { 34 | final List _buffer; 35 | int _bufferIndex = 0; 36 | final double _feedback = 0.5; 37 | 38 | AllpassFilter(int bufferSize) : _buffer = List.filled(bufferSize, 0.0); 39 | 40 | double process(double input) { 41 | final bufout = _buffer[_bufferIndex]; 42 | final output = -input + bufout; 43 | _buffer[_bufferIndex] = input + (bufout * _feedback); 44 | 45 | _bufferIndex = (_bufferIndex + 1) % _buffer.length; 46 | return output; 47 | } 48 | 49 | void reset() { 50 | _buffer.fillRange(0, _buffer.length, 0.0); 51 | _bufferIndex = 0; 52 | } 53 | } 54 | 55 | /// Pure Dart reverb processor using Freeverb algorithm 56 | class ReverbProcessor { 57 | static const int _kNumCombs = 8; 58 | static const int _kNumAllpass = 4; 59 | 60 | // Tuning values for 44.1kHz 61 | static const List _combTuning = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617]; 62 | static const List _allpassTuning = [556, 441, 341, 225]; 63 | 64 | final List _combFiltersL = []; 65 | final List _combFiltersR = []; 66 | final List _allpassFiltersL = []; 67 | final List _allpassFiltersR = []; 68 | 69 | final ReverbParameters _parameters = ReverbParameters(); 70 | double _sampleRate = 44100.0; 71 | bool _initialized = false; 72 | 73 | /// Initialize the reverb processor 74 | void initialize(double sampleRate, int maxBlockSize) { 75 | _sampleRate = sampleRate; 76 | _initializeFilters(); 77 | _initialized = true; 78 | } 79 | 80 | void _initializeFilters() { 81 | final scale = _sampleRate / 44100.0; 82 | 83 | _combFiltersL.clear(); 84 | _combFiltersR.clear(); 85 | _allpassFiltersL.clear(); 86 | _allpassFiltersR.clear(); 87 | 88 | // Initialize comb filters 89 | for (int i = 0; i < _kNumCombs; i++) { 90 | final bufferSizeL = (_combTuning[i] * scale).round(); 91 | final bufferSizeR = ((_combTuning[i] + 23) * scale).round(); 92 | 93 | _combFiltersL.add(CombFilter(bufferSizeL)); 94 | _combFiltersR.add(CombFilter(bufferSizeR)); 95 | } 96 | 97 | // Initialize allpass filters 98 | for (int i = 0; i < _kNumAllpass; i++) { 99 | final bufferSizeL = (_allpassTuning[i] * scale).round(); 100 | final bufferSizeR = ((_allpassTuning[i] + 23) * scale).round(); 101 | 102 | _allpassFiltersL.add(AllpassFilter(bufferSizeL)); 103 | _allpassFiltersR.add(AllpassFilter(bufferSizeR)); 104 | } 105 | 106 | _updateCombFilterParameters(); 107 | } 108 | 109 | void _updateCombFilterParameters() { 110 | final feedback = _parameters.roomSize * 0.28 + 0.7; 111 | 112 | for (final filter in _combFiltersL) { 113 | filter.setFeedback(feedback); 114 | filter.setDampening(_parameters.damping); 115 | } 116 | 117 | for (final filter in _combFiltersR) { 118 | filter.setFeedback(feedback); 119 | filter.setDampening(_parameters.damping); 120 | } 121 | } 122 | 123 | /// Set parameter value 124 | void setParameter(int paramId, double value) { 125 | _parameters.setParameter(paramId, value); 126 | 127 | if (_initialized && (paramId == ReverbParameters.kRoomSizeParam || 128 | paramId == ReverbParameters.kDampingParam)) { 129 | _updateCombFilterParameters(); 130 | } 131 | } 132 | 133 | /// Get parameter value 134 | double getParameter(int paramId) { 135 | return _parameters.getParameter(paramId); 136 | } 137 | 138 | /// Process stereo audio block 139 | void processStereo(List inputL, List inputR, 140 | List outputL, List outputR) { 141 | if (!_initialized || inputL.length != inputR.length || 142 | outputL.length != inputL.length || outputR.length != inputL.length) { 143 | return; 144 | } 145 | 146 | for (int i = 0; i < inputL.length; i++) { 147 | // Mix to mono for reverb input 148 | final monoIn = (inputL[i] + inputR[i]) * 0.5; 149 | 150 | // Process through comb filters 151 | double combOutL = 0.0; 152 | double combOutR = 0.0; 153 | 154 | for (int j = 0; j < _kNumCombs; j++) { 155 | combOutL += _combFiltersL[j].process(monoIn); 156 | combOutR += _combFiltersR[j].process(monoIn); 157 | } 158 | 159 | // Process through allpass filters 160 | for (int j = 0; j < _kNumAllpass; j++) { 161 | combOutL = _allpassFiltersL[j].process(combOutL); 162 | combOutR = _allpassFiltersR[j].process(combOutR); 163 | } 164 | 165 | // Mix dry and wet signals 166 | outputL[i] = inputL[i] * _parameters.dryLevel + combOutL * _parameters.wetLevel; 167 | outputR[i] = inputR[i] * _parameters.dryLevel + combOutR * _parameters.wetLevel; 168 | } 169 | } 170 | 171 | /// Reset all filters 172 | void reset() { 173 | for (final filter in _combFiltersL) { 174 | filter.reset(); 175 | } 176 | for (final filter in _combFiltersR) { 177 | filter.reset(); 178 | } 179 | for (final filter in _allpassFiltersL) { 180 | filter.reset(); 181 | } 182 | for (final filter in _allpassFiltersR) { 183 | filter.reset(); 184 | } 185 | } 186 | 187 | /// Dispose resources 188 | void dispose() { 189 | _initialized = false; 190 | _combFiltersL.clear(); 191 | _combFiltersR.clear(); 192 | _allpassFiltersL.clear(); 193 | _allpassFiltersR.clear(); 194 | } 195 | } -------------------------------------------------------------------------------- /flutter_vst3/create_plugin_guide.md: -------------------------------------------------------------------------------- 1 | # Creating a New flutter_vst3 Plugin - Step by Step Guide 2 | 3 | VST Compatible 4 | 5 | This guide shows you how to create a new VST® 3 plugin using flutter_vst3 framework with pure Dart and Flutter - **no C++ required**. 6 | 7 | *VST® is a trademark of Steinberg Media Technologies GmbH, registered in Europe and other countries.* 8 | 9 | ## Prerequisites 10 | 11 | - Dart SDK 3.0+ 12 | - CMake 3.20+ 13 | - VST3 SDK (run `./setup.sh` if not already done) 14 | 15 | ## Step 1: Create Plugin Directory 16 | 17 | ```bash 18 | mkdir vsts/my_plugin 19 | cd vsts/my_plugin 20 | ``` 21 | 22 | ## Step 2: Create pubspec.yaml 23 | 24 | ```yaml 25 | name: my_plugin 26 | description: My awesome VST3 plugin 27 | version: 1.0.0 28 | publish_to: 'none' 29 | 30 | environment: 31 | sdk: '>=3.0.0 <4.0.0' 32 | 33 | dependencies: 34 | flutter_vst3: 35 | path: ../../flutter_vst3 36 | 37 | dev_dependencies: 38 | test: ^1.24.0 39 | ``` 40 | 41 | ## Step 3: Create Directory Structure 42 | 43 | ```bash 44 | mkdir -p lib 45 | mkdir -p test 46 | ``` 47 | 48 | **Important**: The parameters file goes in `lib/` root, not in a `src/` subdirectory. 49 | 50 | ## Step 4: Define Plugin Parameters 51 | 52 | Create `lib/my_plugin_parameters.dart` (in lib root, not src/): 53 | 54 | ```dart 55 | /// Parameters for my VST3 plugin 56 | class MyPluginParameters { 57 | static const int kGainParam = 0; 58 | static const int kFreqParam = 1; 59 | 60 | /// Controls the output gain (0% = silence, 100% = full) 61 | double gain = 0.5; 62 | 63 | /// Controls the filter frequency (0% = 20Hz, 100% = 20kHz) 64 | double frequency = 0.5; 65 | 66 | /// Get parameter value by ID 67 | double getParameter(int paramId) { 68 | return switch (paramId) { 69 | kGainParam => gain, 70 | kFreqParam => frequency, 71 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 72 | }; 73 | } 74 | 75 | /// Set parameter value by ID 76 | void setParameter(int paramId, double value) { 77 | final clampedValue = value.clamp(0.0, 1.0); 78 | switch (paramId) { 79 | case kGainParam: 80 | gain = clampedValue; 81 | break; 82 | case kFreqParam: 83 | frequency = clampedValue; 84 | break; 85 | default: 86 | throw ArgumentError('Unknown parameter ID: $paramId'); 87 | } 88 | } 89 | 90 | /// Get parameter name by ID 91 | String getParameterName(int paramId) { 92 | return switch (paramId) { 93 | kGainParam => 'Gain', 94 | kFreqParam => 'Frequency', 95 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 96 | }; 97 | } 98 | 99 | /// Get parameter units by ID 100 | String getParameterUnits(int paramId) { 101 | return switch (paramId) { 102 | kGainParam => '%', 103 | kFreqParam => 'Hz', 104 | _ => throw ArgumentError('Unknown parameter ID: $paramId'), 105 | }; 106 | } 107 | 108 | /// Get number of parameters 109 | static const int numParameters = 2; 110 | } 111 | ``` 112 | 113 | ## Step 5: Create Plugin Metadata 114 | 115 | Create `plugin_metadata.json`: 116 | 117 | ```json 118 | { 119 | "pluginName": "My Plugin", 120 | "vendor": "Your Company", 121 | "version": "1.0.0", 122 | "category": "kFx", 123 | "bundleIdentifier": "com.yourcompany.vst3.myplugin", 124 | "companyWeb": "https://yourwebsite.com", 125 | "companyEmail": "you@yourcompany.com" 126 | } 127 | ``` 128 | 129 | ## Step 6: Create CMakeLists.txt 130 | 131 | ```cmake 132 | cmake_minimum_required(VERSION 3.20) 133 | project(my_plugin 134 | VERSION 1.0.0 135 | LANGUAGES CXX) 136 | 137 | set(CMAKE_CXX_STANDARD 17) 138 | 139 | if(NOT CMAKE_BUILD_TYPE) 140 | set(CMAKE_BUILD_TYPE Release) 141 | endif() 142 | 143 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 144 | add_compile_definitions(_DEBUG) 145 | else() 146 | add_compile_definitions(NDEBUG RELEASE) 147 | endif() 148 | 149 | # Include the flutter_vst3 bridge helper 150 | include(../../flutter_vst3/native/cmake/VST3Bridge.cmake) 151 | 152 | # Create the VST® 3 plugin using auto-generated code from Dart definitions 153 | add_dart_vst3_plugin(my_plugin my_plugin_parameters.dart) 154 | ``` 155 | 156 | ## Step 7: Install Dependencies 157 | 158 | ```bash 159 | dart pub get 160 | ``` 161 | 162 | ## Step 8: Create Basic Test 163 | 164 | Create `test/my_plugin_test.dart`: 165 | 166 | ```dart 167 | import 'package:test/test.dart'; 168 | import '../lib/my_plugin_parameters.dart'; 169 | 170 | void main() { 171 | group('MyPluginParameters', () { 172 | late MyPluginParameters params; 173 | 174 | setUp(() { 175 | params = MyPluginParameters(); 176 | }); 177 | 178 | test('should initialize with default values', () { 179 | expect(params.gain, equals(0.5)); 180 | expect(params.frequency, equals(0.5)); 181 | }); 182 | 183 | test('should set and get parameters correctly', () { 184 | params.setParameter(MyPluginParameters.kGainParam, 0.8); 185 | expect(params.getParameter(MyPluginParameters.kGainParam), equals(0.8)); 186 | }); 187 | 188 | test('should clamp parameter values', () { 189 | params.setParameter(MyPluginParameters.kGainParam, 1.5); 190 | expect(params.gain, equals(1.0)); 191 | 192 | params.setParameter(MyPluginParameters.kGainParam, -0.5); 193 | expect(params.gain, equals(0.0)); 194 | }); 195 | 196 | test('should return correct parameter names', () { 197 | expect(params.getParameterName(MyPluginParameters.kGainParam), equals('Gain')); 198 | expect(params.getParameterName(MyPluginParameters.kFreqParam), equals('Frequency')); 199 | }); 200 | }); 201 | } 202 | ``` 203 | 204 | ## Step 9: Build the Plugin 205 | 206 | ```bash 207 | mkdir build && cd build 208 | cmake .. 209 | make 210 | ``` 211 | 212 | The `.vst3` plugin bundle will be created in the build directory. 213 | 214 | ## Step 10: Test Your Plugin 215 | 216 | ```bash 217 | # Run Dart tests 218 | dart test 219 | 220 | # Validate the VST3 plugin (if you have VST3 validator) 221 | # validator my_plugin.vst3 222 | ``` 223 | 224 | ## What Happens Behind the Scenes 225 | 226 | 1. **Auto-Generation**: The CMake system reads your `my_plugin_parameters.dart` file and generates all necessary C++ boilerplate 227 | 2. **Generated Files**: Creates controller, processor, and factory C++ files in `build/generated/` 228 | 3. **VST3 Bundle**: Packages everything into a proper `.vst3` bundle with metadata 229 | 4. **Zero C++**: You never touch C++ - everything is generated from your Dart definitions 230 | 231 | ## Key Points 232 | 233 | - ✅ **Parameter comments become VST3 parameter descriptions** 234 | - ✅ **All C++ is auto-generated from Dart** 235 | - ✅ **Standard VST3 plugin structure is created automatically** 236 | - ✅ **Follow existing naming patterns**: `*_parameters.dart` 237 | - ✅ **Use descriptive parameter comments with ranges and units** 238 | 239 | ## Installation 240 | 241 | ```bash 242 | # Install to system VST3 folder 243 | make install 244 | ``` 245 | 246 | Your plugin is now ready to use in any VST® 3 host! -------------------------------------------------------------------------------- /dart_vst_graph/native/include/dvh_graph.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // 3 | // This header defines the C API for the dynamic graph used by the 4 | // Dart VST host. The graph allows multiple VST3 plug‑ins to be hosted 5 | // simultaneously, with arbitrary connections between nodes to support 6 | // mixing, splitting and gain adjustment. All functions use C 7 | // linkage so they can be called from Dart via FFI. 8 | 9 | #pragma once 10 | #include 11 | 12 | #ifdef _WIN32 13 | # ifdef DART_VST_HOST_EXPORTS 14 | # define DVH_API __declspec(dllexport) 15 | # else 16 | # define DVH_API __declspec(dllimport) 17 | # endif 18 | #else 19 | # define DVH_API __attribute__((visibility("default"))) 20 | #endif 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | // Opaque handles for the host, individual plug‑in wrappers and the 27 | // graph itself. These pointers are managed internally by the native 28 | // library. Clients must call the appropriate destroy functions to 29 | // release memory. 30 | typedef void* DVH_Host; 31 | typedef void* DVH_Plugin; 32 | typedef void* DVH_Graph; 33 | 34 | // Simple transport information structure. This can be expanded in 35 | // future versions to include more DAW state such as loop points, 36 | // bar/beat positions, etc. All fields are in host (double or int) 37 | // domain and should be filled out by the plug‑in before calling 38 | // dvh_graph_set_transport(). 39 | typedef struct { 40 | double tempo; 41 | int32_t timeSigNum; 42 | int32_t timeSigDen; 43 | double ppqPosition; 44 | int32_t playing; // 0 = stopped, 1 = playing 45 | } DVH_Transport; 46 | 47 | // Create a new graph at the specified sample rate and maximum block 48 | // size. The graph owns its own internal host used for VST loading 49 | // (shared with other graphs). Returns a handle to the graph or 50 | // nullptr on failure. 51 | DVH_API DVH_Graph dvh_graph_create(double sample_rate, int32_t max_block); 52 | 53 | // Destroy a graph created by dvh_graph_create(). After calling this 54 | // the handle becomes invalid and must not be used again. 55 | DVH_API void dvh_graph_destroy(DVH_Graph g); 56 | 57 | // Remove all nodes and connections from the graph. Does not destroy 58 | // the graph itself. Returns 1 on success. 59 | DVH_API int32_t dvh_graph_clear(DVH_Graph g); 60 | 61 | // Add a VST3 plug‑in to the graph. The module_path_utf8 must point 62 | // to a .vst3 bundle on disk. class_uid_or_null is an optional 63 | // UID string specifying which class within the module to instantiate; 64 | // if null the first "Audio Module Class" is used. On success the 65 | // new node’s ID is written to out_node_id and 1 is returned. On 66 | // failure 0 is returned and out_node_id is untouched. 67 | DVH_API int32_t dvh_graph_add_vst(DVH_Graph g, 68 | const char* module_path_utf8, 69 | const char* class_uid_or_null, 70 | int32_t* out_node_id); 71 | 72 | // Add a mixer node with the given number of inputs. Each input 73 | // represents a stereo bus. The mixer sums all connected inputs with 74 | // per‑input gains (initially 0dB) and outputs a single stereo bus. 75 | // Returns 1 on success and writes the new node ID to out_node_id. 76 | DVH_API int32_t dvh_graph_add_mixer(DVH_Graph g, int32_t num_inputs, int32_t* out_node_id); 77 | 78 | // Add a splitter node which simply forwards its input stereo bus to 79 | // its output. Useful as an IO placeholder. Returns 1 on success. 80 | DVH_API int32_t dvh_graph_add_split(DVH_Graph g, int32_t* out_node_id); 81 | 82 | // Add a gain node with an initial gain in dB. The gain node exposes a 83 | // single parameter (ID 0) representing a normalized gain value 84 | // mapped from ‑60dB (0.0) to 0dB (1.0). Returns 1 on success. 85 | DVH_API int32_t dvh_graph_add_gain(DVH_Graph g, float gain_db, int32_t* out_node_id); 86 | 87 | // Connect the output of src_node to the input of dst_node. The bus 88 | // indices are reserved for future multi‑bus support and must be zero 89 | // for now. Returns 1 on success. 90 | DVH_API int32_t dvh_graph_connect(DVH_Graph g, 91 | int32_t src_node, int32_t src_bus, 92 | int32_t dst_node, int32_t dst_bus); 93 | 94 | // Disconnect the output of src_node from the input of dst_node. The 95 | // bus indices must match a previous call to dvh_graph_connect(). 96 | // Returns 1 on success. 97 | DVH_API int32_t dvh_graph_disconnect(DVH_Graph g, 98 | int32_t src_node, int32_t src_bus, 99 | int32_t dst_node, int32_t dst_bus); 100 | 101 | // Specify which nodes act as the global input and output for the 102 | // graph. If either is set to ‑1 it is ignored, allowing the graph to 103 | // operate without a physical input or output. Returns 1 on success. 104 | DVH_API int32_t dvh_graph_set_io_nodes(DVH_Graph g, int32_t input_node_or_minus1, int32_t output_node_or_minus1); 105 | 106 | // Send a note on or off event to a specific node. If node_or_minus1 107 | // is ‑1 the event is broadcast to all nodes. Returns 1 on success. 108 | DVH_API int32_t dvh_graph_note_on(DVH_Graph g, int32_t node_or_minus1, int32_t ch, int32_t note, float vel); 109 | DVH_API int32_t dvh_graph_note_off(DVH_Graph g, int32_t node_or_minus1, int32_t ch, int32_t note, float vel); 110 | 111 | // Query the number of parameters available on a node. Returns zero if 112 | // the node has no parameters or an invalid ID is supplied. 113 | DVH_API int32_t dvh_graph_param_count(DVH_Graph g, int32_t node_id); 114 | 115 | // Retrieve information about a parameter on a node. The title and 116 | // units strings must have sufficient capacity as given by title_cap 117 | // and units_cap. On success returns 1 and writes the parameter ID, 118 | // title and units; on failure returns 0. 119 | DVH_API int32_t dvh_graph_param_info(DVH_Graph g, int32_t node_id, int32_t index, 120 | int32_t* id_out, 121 | char* title_utf8, int32_t title_cap, 122 | char* units_utf8, int32_t units_cap); 123 | 124 | // Get or set a parameter’s normalized value on a node. The normalized 125 | // value is a float between 0.0 and 1.0. Returns the current value 126 | // from dvh_graph_get_param() or 1/0 for dvh_graph_set_param(). 127 | DVH_API float dvh_graph_get_param(DVH_Graph g, int32_t node_id, int32_t param_id); 128 | DVH_API int32_t dvh_graph_set_param(DVH_Graph g, int32_t node_id, int32_t param_id, float normalized); 129 | 130 | // Update the graph’s transport state. The native graph does not yet 131 | // perform tempo‑synchronized processing but this information is 132 | // preserved for future use. Returns 1 on success. 133 | DVH_API int32_t dvh_graph_set_transport(DVH_Graph g, DVH_Transport t); 134 | 135 | // Query the latency introduced by the graph in samples. At present 136 | // latency compensation is not implemented and this always returns 0. 137 | DVH_API int32_t dvh_graph_latency(DVH_Graph g); 138 | 139 | // Process a block of audio through the graph. The input and output 140 | // buffers must have at least num_frames samples. Returns 1 on 141 | // success; on failure the contents of outL/outR are undefined. 142 | DVH_API int32_t dvh_graph_process_stereo(DVH_Graph g, 143 | const float* inL, const float* inR, 144 | float* outL, float* outR, 145 | int32_t num_frames); 146 | 147 | #ifdef __cplusplus 148 | } 149 | #endif -------------------------------------------------------------------------------- /flutter_vst3/native/templates/plugin_processor.cpp.template: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // Auto-generated VST3 processor for {{PLUGIN_NAME}} 3 | // This file is generated automatically by dart_vst3_bridge at build time. 4 | // DO NOT EDIT - any changes will be overwritten. 5 | 6 | #include "{{PLUGIN_ID}}_ids.h" 7 | #include "pluginterfaces/vst/ivstparameterchanges.h" 8 | #include "pluginterfaces/vst/ivstprocesscontext.h" 9 | #include "pluginterfaces/base/ibstream.h" 10 | #include "public.sdk/source/vst/vstaudioeffect.h" 11 | #include "base/source/fstreamer.h" 12 | #include "dart_vst3_bridge.h" 13 | #include 14 | #include 15 | 16 | using namespace Steinberg; 17 | using namespace Steinberg::Vst; 18 | 19 | // Processor for the {{PLUGIN_NAME}} plugin 20 | class {{PLUGIN_CLASS_NAME}}Processor : public AudioEffect { 21 | public: 22 | {{PLUGIN_CLASS_NAME}}Processor(); 23 | virtual ~{{PLUGIN_CLASS_NAME}}Processor() = default; 24 | 25 | tresult PLUGIN_API initialize(FUnknown* context) override; 26 | tresult PLUGIN_API terminate() override; 27 | tresult PLUGIN_API setActive(TBool state) override; 28 | tresult PLUGIN_API process(ProcessData& data) override; 29 | tresult PLUGIN_API setupProcessing(ProcessSetup& setup) override; 30 | tresult PLUGIN_API setBusArrangements(SpeakerArrangement* inputs, int32 numIns, 31 | SpeakerArrangement* outputs, int32 numOuts) override; 32 | tresult PLUGIN_API setState(IBStream* state) override; 33 | tresult PLUGIN_API getState(IBStream* state) override; 34 | tresult PLUGIN_API getControllerClassId(TUID classId) override; 35 | 36 | private: 37 | {{PARAMETER_VARIABLES}} 38 | double sampleRate = 44100.0; 39 | 40 | // Dart VST3 bridge instance 41 | DartVST3Instance* dartInstance = nullptr; 42 | }; 43 | 44 | {{PLUGIN_CLASS_NAME}}Processor::{{PLUGIN_CLASS_NAME}}Processor() { 45 | setControllerClass(k{{PLUGIN_CLASS_NAME}}ControllerUID); 46 | 47 | // CRITICAL ARCHITECTURE PROBLEM: This VST3 plugin requires Dart runtime! 48 | // The current architecture assumes the VST3 is loaded by a Dart application, 49 | // but DAWs like Ableton don't have Dart runtime. 50 | // 51 | // TODO: Either embed Dart VM in this plugin OR redesign the bridge architecture 52 | // For now, create instance but expect it to fail during audio processing 53 | dartInstance = dart_vst3_create_instance("{{PLUGIN_ID}}"); 54 | 55 | if (dartInstance == nullptr) { 56 | // Log the issue but don't crash during construction 57 | fprintf(stderr, "WARNING: Failed to create Dart VST3 instance for '{{PLUGIN_ID}}'\n"); 58 | fprintf(stderr, "This plugin requires Dart runtime support which is missing!\n"); 59 | fflush(stderr); 60 | } 61 | {{PARAMETER_DEFAULTS}} 62 | } 63 | 64 | tresult {{PLUGIN_CLASS_NAME}}Processor::initialize(FUnknown* context) { 65 | tresult result = AudioEffect::initialize(context); 66 | if (result != kResultTrue) return result; 67 | 68 | // Configure audio buses - stereo in/out by default 69 | addAudioInput(STR16("Stereo In"), SpeakerArr::kStereo); 70 | addAudioOutput(STR16("Stereo Out"), SpeakerArr::kStereo); 71 | 72 | return kResultTrue; 73 | } 74 | 75 | tresult {{PLUGIN_CLASS_NAME}}Processor::terminate() { 76 | if (dartInstance) { 77 | dart_vst3_dispose(dartInstance); 78 | dart_vst3_destroy_instance(dartInstance); 79 | dartInstance = nullptr; 80 | } 81 | return AudioEffect::terminate(); 82 | } 83 | 84 | tresult {{PLUGIN_CLASS_NAME}}Processor::setActive(TBool state) { 85 | if (state) { 86 | // Plugin activated - initialize Dart processor 87 | if (dartInstance) { 88 | dart_vst3_initialize(dartInstance, sampleRate, 512); 89 | } 90 | } else { 91 | // Plugin deactivated - reset Dart processor 92 | if (dartInstance) { 93 | dart_vst3_reset(dartInstance); 94 | } 95 | } 96 | return AudioEffect::setActive(state); 97 | } 98 | 99 | tresult {{PLUGIN_CLASS_NAME}}Processor::setupProcessing(ProcessSetup& setup) { 100 | sampleRate = setup.sampleRate; 101 | return AudioEffect::setupProcessing(setup); 102 | } 103 | 104 | tresult {{PLUGIN_CLASS_NAME}}Processor::setBusArrangements(SpeakerArrangement* inputs, int32 numIns, 105 | SpeakerArrangement* outputs, int32 numOuts) { 106 | // Only accept stereo input/output 107 | if (numIns == 1 && numOuts == 1 && inputs[0] == SpeakerArr::kStereo && outputs[0] == SpeakerArr::kStereo) { 108 | return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts); 109 | } 110 | return kResultFalse; 111 | } 112 | 113 | tresult {{PLUGIN_CLASS_NAME}}Processor::process(ProcessData& data) { 114 | // Process parameter changes 115 | if (data.inputParameterChanges) { 116 | int32 numParamsChanged = data.inputParameterChanges->getParameterCount(); 117 | for (int32 i = 0; i < numParamsChanged; i++) { 118 | IParamValueQueue* paramQueue = data.inputParameterChanges->getParameterData(i); 119 | if (paramQueue) { 120 | ParamValue value; 121 | int32 sampleOffset; 122 | int32 numPoints = paramQueue->getPointCount(); 123 | 124 | if (paramQueue->getPoint(numPoints - 1, sampleOffset, value) == kResultTrue) { 125 | // Forward parameter changes to Dart processor 126 | if (dartInstance) { 127 | dart_vst3_set_parameter(dartInstance, paramQueue->getParameterId(), value); 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | // Process audio 135 | if (data.numInputs == 0 || data.numOutputs == 0) { 136 | return kResultOk; 137 | } 138 | 139 | AudioBusBuffers* input = &data.inputs[0]; 140 | AudioBusBuffers* output = &data.outputs[0]; 141 | 142 | int32 numChannels = input->numChannels; 143 | int32 sampleFrames = data.numSamples; 144 | 145 | if (numChannels > 0 && sampleFrames > 0) { 146 | if (dartInstance && numChannels >= 2) { 147 | // Process stereo audio through Dart 148 | Sample32* inputL = input->channelBuffers32[0]; 149 | Sample32* inputR = input->channelBuffers32[1]; 150 | Sample32* outputL = output->channelBuffers32[0]; 151 | Sample32* outputR = output->channelBuffers32[1]; 152 | 153 | dart_vst3_process_stereo(dartInstance, inputL, inputR, outputL, outputR, sampleFrames); 154 | } else { 155 | // NO FALLBACKS! FAIL HARD! 156 | throw std::runtime_error("VST3 PROCESSOR CRITICAL FAILURE: Dart instance is null or audio is not stereo! Plugin cannot function without proper Dart processor!"); 157 | } 158 | } 159 | 160 | return kResultOk; 161 | } 162 | 163 | tresult {{PLUGIN_CLASS_NAME}}Processor::setState(IBStream* state) { 164 | if (!state) return kResultFalse; 165 | 166 | // Read parameter values 167 | {{PARAMETER_STATE_READ}} 168 | 169 | return kResultOk; 170 | } 171 | 172 | tresult {{PLUGIN_CLASS_NAME}}Processor::getState(IBStream* state) { 173 | if (!state) return kResultFalse; 174 | 175 | // Write parameter values 176 | {{PARAMETER_STATE_WRITE}} 177 | 178 | return kResultOk; 179 | } 180 | 181 | tresult {{PLUGIN_CLASS_NAME}}Processor::getControllerClassId(TUID classId) { 182 | memcpy(classId, k{{PLUGIN_CLASS_NAME}}ControllerUID, sizeof(TUID)); 183 | return kResultTrue; 184 | } 185 | 186 | // Factory functions in proper namespace 187 | namespace Steinberg { 188 | namespace Vst { 189 | namespace {{PLUGIN_CLASS_NAME}} { 190 | FUnknown* createProcessorInstance(void*) { 191 | return (IAudioProcessor*)new {{PLUGIN_CLASS_NAME}}Processor(); 192 | } 193 | } 194 | } 195 | } -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Code Rules 6 | - NO PLACEHOLDERS. NO FALLBACKS. FAIL HARD!!! If the code is not implemented, THROW AN ERROR!!! Fail LOUDLY by throwing an exception. 7 | - NO DUPLICATION. Move files, code elements instead of copying them. Search for elements before adding them. 8 | - FP style. No interfaces, classes, or mutable state. Pure functions with no side effects. 9 | - Tests must FAIL HARD. Don't add allowances and print warnings. Just FAIL! 10 | - Keep functions under 20 lines long. 11 | - Do not use Git commands unless explicitly requested 12 | - Keep files under 400 LOC, even tests 13 | 14 | ## Dart Rules 15 | - NEVER use the late keyword 16 | - Document all public functions with Dart /// doc, especially the important ones 17 | - Don't use if statements. Use pattern matching or ternaries instead. 18 | - Only native machine code. No AOT or Dart runtime! 19 | 20 | 21 | ## Project Overview 22 | 23 | This a Steinberg VST® 3 toolkit for Dart and Flutter. Use this toolkit to implement VST® plugins and VST® hosts. This toolkit enables anyone to create VST® 3 plugins with pure Dart and Flutter. 24 | 25 | *VST® is a trademark of Steinberg Media Technologies GmbH.* 26 | 27 | The main project is flutter_vst3. Use this to create your own VST® 3 plugins with Flutter and Dart. There is also VST3 host and audio graph system written in Dart with native C++ components. The project enables loading VST3 plugins into a customizable audio graph that can be controlled from Dart and Flutter applications. 28 | 29 | Download Steinberg SDK here: 30 | https://www.steinberg.net/vst3sdk 31 | curl -L -o vst3sdk.zip https://www.steinberg.net/vst3sdk 32 | 33 | ### Core Components 34 | 35 | 1. **dart_vst_host** - High-level Dart bindings for VST® 3 plugin hosting with RAII resource management 36 | 2. **dart_vst_graph** - Audio graph system allowing connection of VST® plugins, mixers, splitters, and gain nodes 37 | 3. **flutter_vst3** - Complete framework for building VST® 3 plugins with Flutter UI and Dart audio processing (auto-generates ALL C++ from Dart parameter definitions) 38 | 4. **flutter_ui** - Desktop Flutter application providing a GUI for the VST host 39 | 5. **native/** - C++ implementation of VST3 host and audio graph using Steinberg VST3 SDK 40 | 6. **vsts/** - Individual VST plugin packages, each builds its own .vst3 plugin (flutter_reverb, echo) 41 | 42 | ### Architecture 43 | 44 | The system uses FFI to bridge Dart and C++. The native library (`libdart_vst_host.dylib/so/dll`) contains both VST host functionality and the audio graph implementation. Dart packages provide high-level APIs that manage native resource lifetimes using RAII patterns. 45 | 46 | The audio graph supports: 47 | - VST3 plugin nodes 48 | - Mixer nodes (multiple stereo inputs → single stereo output) 49 | - Splitter nodes (single stereo input → multiple stereo outputs) 50 | - Gain nodes with dB control 51 | - Arbitrary connections between compatible nodes 52 | 53 | ## Build Requirements 54 | 55 | ### Prerequisites 56 | - **VST3_SDK_DIR environment variable** must point to Steinberg VST3 SDK root (or use bundled `vst3sdk/` directory) 57 | - CMake 3.20+ 58 | - C++17 compiler 59 | - Dart SDK 3.0+ 60 | - Flutter (for UI component) 61 | 62 | ### Build Commands 63 | 64 | **Setup (First time):** 65 | ```bash 66 | ./setup.sh # Downloads Steinberg VST® 3 SDK and builds native library automatically 67 | ``` 68 | 69 | **Primary build targets using Makefile:** 70 | ```bash 71 | # Build Flutter Dart Reverb VST3 plugin (default target) 72 | make 73 | 74 | # Build specific plugins 75 | make reverb-vst # Build Flutter Reverb VST3 76 | make echo-vst # Build Echo VST3 77 | 78 | # Install to system VST folder 79 | make install 80 | 81 | # Build native library (required for all Dart components) 82 | make native 83 | 84 | # Clean and rebuild 85 | make clean reverb-vst 86 | ``` 87 | 88 | **Manual Native Library Build:** 89 | ```bash 90 | cd native/ 91 | mkdir build && cd build 92 | cmake .. 93 | make 94 | # Copies libdart_vst_host.dylib to project root for Dart tests 95 | cp libdart_vst_host.dylib ../../ 96 | ``` 97 | 98 | **VST3 Plugins (auto-generated from Dart parameter definitions):** 99 | ```bash 100 | # Build flutter_reverb plugin 101 | cd vsts/flutter_reverb/ 102 | mkdir build && cd build 103 | cmake .. 104 | make 105 | # Output: flutter_reverb.vst3 bundle 106 | 107 | # Build echo plugin 108 | cd vsts/echo/ 109 | mkdir build && cd build 110 | cmake .. 111 | make 112 | # Output: echo.vst3 bundle 113 | ``` 114 | 115 | **Dart Packages:** 116 | ```bash 117 | # Install dependencies for all packages 118 | make dart-deps 119 | 120 | # Or manually: 121 | cd dart_vst_host/ && dart pub get 122 | cd dart_vst_graph/ && dart pub get 123 | cd dart_vst3_bridge/ && dart pub get 124 | ``` 125 | 126 | **Flutter UI:** 127 | ```bash 128 | make flutter-deps 129 | cd flutter_ui/ 130 | flutter run 131 | ``` 132 | 133 | ## Testing 134 | 135 | **Run all tests:** 136 | ```bash 137 | make test 138 | ``` 139 | 140 | **Run individual package tests:** 141 | ```bash 142 | make test-host # dart_vst_host tests 143 | make test-graph # dart_vst_graph tests 144 | 145 | # Manual test runs: 146 | cd dart_vst_host/ && dart test 147 | cd dart_vst_graph/ && dart test 148 | cd vsts/flutter_reverb/ && dart test 149 | cd vsts/echo/ && dart test 150 | ``` 151 | 152 | **Important:** Tests require the native library to be built and present in the working directory. The test framework will fail with a clear error message if `libdart_vst_host.dylib` is missing. 153 | 154 | ## VST® 3 Plugin Development (Zero C++ Required) 155 | 156 | The flutter_vst3 framework now auto-generates ALL C++ boilerplate from Dart parameter definitions: 157 | 158 | 1. **Define parameters in Dart** with doc comments: 159 | ```dart 160 | class ReverbParameters { 161 | /// Controls the size of the reverb space (0% = small room, 100% = large hall) 162 | double roomSize = 0.5; 163 | 164 | /// Controls high frequency absorption (0% = bright, 100% = dark) 165 | double damping = 0.5; 166 | } 167 | ``` 168 | 169 | 2. **CMake auto-generates C++ files**: 170 | ```cmake 171 | add_dart_vst3_plugin(flutter_reverb reverb_parameters.dart 172 | BUNDLE_IDENTIFIER "com.yourcompany.vst3.flutterreverb" 173 | COMPANY_NAME "Your Company" 174 | PLUGIN_NAME "Flutter Reverb" 175 | ) 176 | ``` 177 | 178 | 3. **Generated files** (completely hidden): 179 | - `generated/flutter_reverb_controller.cpp` 180 | - `generated/flutter_reverb_processor.cpp` 181 | - `generated/flutter_reverb_factory.cpp` 182 | - `include/flutter_reverb_ids.h` 183 | 184 | ## Key Files 185 | 186 | - `native/include/dart_vst_host.h` - C API for VST hosting 187 | - `native/include/dvh_graph.h` - C API for audio graph 188 | - `dart_vst_host/lib/src/host.dart` - High-level VST host wrapper 189 | - `dart_vst_graph/lib/src/bindings.dart` - FFI bindings and VstGraph class 190 | - `dart_vst3_bridge/native/cmake/VST3Bridge.cmake` - Shared CMake functions for plugin builds 191 | - `vsts/*/CMakeLists.txt` - Individual plugin build configurations 192 | - `flutter_ui/lib/main.dart` - Flutter application entry point 193 | - `Makefile` - Primary build system with all targets 194 | - `setup.sh` - Environment setup script 195 | 196 | ## Development Workflow 197 | 198 | 1. Run `./setup.sh` for first-time setup (downloads SDK, builds native library) 199 | 2. Use `make` to build Flutter Dart Reverb VST3 plugin 200 | 3. Use `make test` to run all tests and verify FFI bindings 201 | 4. Use Flutter UI (`make run-flutter`) for interactive testing 202 | 5. Build individual VST plugins in their respective `vsts/` directories 203 | 6. Each plugin package is self-contained and builds its own .vst3 bundle 204 | 7. Tests will fail loudly if native dependencies are missing 205 | 206 | ## Platform-Specific Notes 207 | 208 | - **macOS:** Outputs `.dylib` and `.vst3` bundle 209 | - **Linux:** Outputs `.so` library 210 | - **Windows:** Outputs `.dll` library 211 | - All platforms require VST3 SDK and appropriate build tools 212 | - Use `make install` to install VST3 plugins to system folders automatically 213 | 214 | ## Plugin Validation 215 | 216 | ```bash 217 | # Validate built plugins 218 | ./validate_echo.sh # Runs VST3 validator on echo plugin 219 | ``` -------------------------------------------------------------------------------- /flutter_vst3/native/templates/plugin_processor_aot.cpp.template: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 2 | // Auto-generated VST3 processor for {{PLUGIN_NAME}} using AOT-compiled Dart code 3 | // This file is generated automatically by dart_vst3_bridge at build time. 4 | // DO NOT EDIT - any changes will be overwritten. 5 | 6 | #include "{{PLUGIN_ID}}_ids.h" 7 | #include "pluginterfaces/vst/ivstparameterchanges.h" 8 | #include "pluginterfaces/vst/ivstprocesscontext.h" 9 | #include "pluginterfaces/base/ibstream.h" 10 | #include "public.sdk/source/vst/vstaudioeffect.h" 11 | #include "base/source/fstreamer.h" 12 | // Using native AOT-compiled Dart processor - NO FFI BRIDGE! 13 | extern "C" { 14 | void {{PLUGIN_ID}}_native_initialize(double sample_rate, int max_block_size); 15 | void {{PLUGIN_ID}}_native_process_stereo(float* inputL, float* inputR, float* outputL, float* outputR, int samples); 16 | void {{PLUGIN_ID}}_native_set_parameter(int param_id, double value); 17 | double {{PLUGIN_ID}}_native_get_parameter(int param_id); 18 | void {{PLUGIN_ID}}_native_reset(); 19 | void {{PLUGIN_ID}}_native_dispose(); 20 | } 21 | #include 22 | #include 23 | 24 | using namespace Steinberg; 25 | using namespace Steinberg::Vst; 26 | 27 | // Processor for the {{PLUGIN_NAME}} plugin 28 | class {{PLUGIN_CLASS_NAME}}Processor : public AudioEffect { 29 | public: 30 | {{PLUGIN_CLASS_NAME}}Processor(); 31 | virtual ~{{PLUGIN_CLASS_NAME}}Processor() = default; 32 | 33 | tresult PLUGIN_API initialize(FUnknown* context) override; 34 | tresult PLUGIN_API terminate() override; 35 | tresult PLUGIN_API setActive(TBool state) override; 36 | tresult PLUGIN_API process(ProcessData& data) override; 37 | tresult PLUGIN_API setupProcessing(ProcessSetup& setup) override; 38 | tresult PLUGIN_API setBusArrangements(SpeakerArrangement* inputs, int32 numIns, 39 | SpeakerArrangement* outputs, int32 numOuts) override; 40 | tresult PLUGIN_API setState(IBStream* state) override; 41 | tresult PLUGIN_API getState(IBStream* state) override; 42 | tresult PLUGIN_API getControllerClassId(TUID classId) override; 43 | 44 | private: 45 | {{PARAMETER_VARIABLES}} 46 | double sampleRate = 44100.0; 47 | 48 | // Native AOT processor - NO MORE DART RUNTIME DEPENDENCY! 49 | bool nativeProcessorInitialized = false; 50 | }; 51 | 52 | {{PLUGIN_CLASS_NAME}}Processor::{{PLUGIN_CLASS_NAME}}Processor() { 53 | setControllerClass(k{{PLUGIN_CLASS_NAME}}ControllerUID); 54 | 55 | // ARCHITECTURE FIXED: Using AOT-compiled native Dart code - NO RUNTIME DEPENDENCY! 56 | nativeProcessorInitialized = false; 57 | {{PARAMETER_DEFAULTS}} 58 | } 59 | 60 | tresult {{PLUGIN_CLASS_NAME}}Processor::initialize(FUnknown* context) { 61 | tresult result = AudioEffect::initialize(context); 62 | if (result != kResultTrue) return result; 63 | 64 | // Configure audio buses - stereo in/out by default 65 | addAudioInput(STR16("Stereo In"), SpeakerArr::kStereo); 66 | addAudioOutput(STR16("Stereo Out"), SpeakerArr::kStereo); 67 | 68 | return kResultTrue; 69 | } 70 | 71 | tresult {{PLUGIN_CLASS_NAME}}Processor::terminate() { 72 | if (nativeProcessorInitialized) { 73 | {{PLUGIN_ID}}_native_dispose(); 74 | nativeProcessorInitialized = false; 75 | } 76 | return AudioEffect::terminate(); 77 | } 78 | 79 | tresult {{PLUGIN_CLASS_NAME}}Processor::setActive(TBool state) { 80 | if (state) { 81 | // Plugin activated - TRY to initialize native AOT processor and FAIL HARD if it doesn't work 82 | try { 83 | {{PLUGIN_ID}}_native_initialize(sampleRate, 512); 84 | nativeProcessorInitialized = true; 85 | } catch (const std::exception& e) { 86 | // FAIL HARD! CRASH THE WHOLE DAW! 87 | fprintf(stderr, "{{PLUGIN_ID}} CRITICAL FAILURE: AOT processor initialization failed: %s\n", e.what()); 88 | fprintf(stderr, "THIS PLUGIN REQUIRES WORKING AOT COMPILATION!\n"); 89 | fflush(stderr); 90 | abort(); // CRASH THE DAW - NO SILENT FAILURES! 91 | } 92 | } else { 93 | // Plugin deactivated - reset native processor 94 | if (nativeProcessorInitialized) { 95 | try { 96 | {{PLUGIN_ID}}_native_reset(); 97 | } catch (...) { 98 | // Even cleanup failures should crash 99 | abort(); 100 | } 101 | } 102 | } 103 | return AudioEffect::setActive(state); 104 | } 105 | 106 | tresult {{PLUGIN_CLASS_NAME}}Processor::setupProcessing(ProcessSetup& setup) { 107 | sampleRate = setup.sampleRate; 108 | return AudioEffect::setupProcessing(setup); 109 | } 110 | 111 | tresult {{PLUGIN_CLASS_NAME}}Processor::setBusArrangements(SpeakerArrangement* inputs, int32 numIns, 112 | SpeakerArrangement* outputs, int32 numOuts) { 113 | // Only accept stereo input/output 114 | if (numIns == 1 && numOuts == 1 && inputs[0] == SpeakerArr::kStereo && outputs[0] == SpeakerArr::kStereo) { 115 | return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts); 116 | } 117 | return kResultFalse; 118 | } 119 | 120 | tresult {{PLUGIN_CLASS_NAME}}Processor::process(ProcessData& data) { 121 | // Process parameter changes 122 | if (data.inputParameterChanges) { 123 | int32 numParamsChanged = data.inputParameterChanges->getParameterCount(); 124 | for (int32 i = 0; i < numParamsChanged; i++) { 125 | IParamValueQueue* paramQueue = data.inputParameterChanges->getParameterData(i); 126 | if (paramQueue) { 127 | ParamValue value; 128 | int32 sampleOffset; 129 | int32 numPoints = paramQueue->getPointCount(); 130 | 131 | if (paramQueue->getPoint(numPoints - 1, sampleOffset, value) == kResultTrue) { 132 | // Forward parameter changes to native AOT processor 133 | if (nativeProcessorInitialized) { 134 | {{PLUGIN_ID}}_native_set_parameter(paramQueue->getParameterId(), value); 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | // Process audio 142 | if (data.numInputs == 0 || data.numOutputs == 0) { 143 | return kResultOk; 144 | } 145 | 146 | AudioBusBuffers* input = &data.inputs[0]; 147 | AudioBusBuffers* output = &data.outputs[0]; 148 | 149 | int32 numChannels = input->numChannels; 150 | int32 sampleFrames = data.numSamples; 151 | 152 | if (numChannels > 0 && sampleFrames > 0) { 153 | if (nativeProcessorInitialized && numChannels >= 2) { 154 | // Process stereo audio through AOT-compiled native Dart code - ZERO FFI OVERHEAD! 155 | Sample32* inputL = input->channelBuffers32[0]; 156 | Sample32* inputR = input->channelBuffers32[1]; 157 | Sample32* outputL = output->channelBuffers32[0]; 158 | Sample32* outputR = output->channelBuffers32[1]; 159 | 160 | try { 161 | {{PLUGIN_ID}}_native_process_stereo(inputL, inputR, outputL, outputR, sampleFrames); 162 | } catch (const std::exception& e) { 163 | // FAIL HARD! CRASH THE WHOLE DAW! 164 | fprintf(stderr, "{{PLUGIN_ID}} CRITICAL FAILURE: Audio processing failed: %s\n", e.what()); 165 | fflush(stderr); 166 | abort(); // CRASH THE DAW - NO SILENT FAILURES! 167 | } 168 | } else { 169 | // NO FALLBACKS! FAIL HARD! CRASH THE DAW! 170 | fprintf(stderr, "VST3 PROCESSOR CRITICAL FAILURE: Native AOT processor not initialized or audio is not stereo!\n"); 171 | fprintf(stderr, "nativeProcessorInitialized: %s, numChannels: %d\n", 172 | nativeProcessorInitialized ? "true" : "false", numChannels); 173 | fflush(stderr); 174 | abort(); // CRASH THE WHOLE DAW! 175 | } 176 | } 177 | 178 | return kResultOk; 179 | } 180 | 181 | tresult {{PLUGIN_CLASS_NAME}}Processor::setState(IBStream* state) { 182 | if (!state) return kResultFalse; 183 | 184 | // Read parameter values 185 | {{PARAMETER_STATE_READ}} 186 | 187 | return kResultOk; 188 | } 189 | 190 | tresult {{PLUGIN_CLASS_NAME}}Processor::getState(IBStream* state) { 191 | if (!state) return kResultFalse; 192 | 193 | // Write parameter values 194 | {{PARAMETER_STATE_WRITE}} 195 | 196 | return kResultOk; 197 | } 198 | 199 | tresult {{PLUGIN_CLASS_NAME}}Processor::getControllerClassId(TUID classId) { 200 | memcpy(classId, k{{PLUGIN_CLASS_NAME}}ControllerUID, sizeof(TUID)); 201 | return kResultTrue; 202 | } 203 | 204 | // Factory functions in proper namespace 205 | namespace Steinberg { 206 | namespace Vst { 207 | namespace {{PLUGIN_CLASS_NAME}} { 208 | FUnknown* createProcessorInstance(void*) { 209 | return (IAudioProcessor*)new {{PLUGIN_CLASS_NAME}}Processor(); 210 | } 211 | } 212 | } 213 | } -------------------------------------------------------------------------------- /dart_vst_graph/lib/src/bindings.dart: -------------------------------------------------------------------------------- 1 | /// FFI bindings to the graph API defined in dvh_graph.h. This file 2 | /// exposes a low level Dart interface to the native audio graph which 3 | /// allows adding nodes, connecting them and processing audio. See 4 | /// VstGraph below for a higher level wrapper. 5 | 6 | import 'dart:ffi'; 7 | import 'dart:io'; 8 | import 'dart:typed_data'; 9 | import 'package:ffi/ffi.dart'; 10 | 11 | typedef _GraphCreateC = Pointer Function(Double, Int32); 12 | typedef _GraphDestroyC = Void Function(Pointer); 13 | typedef _GraphClearC = Int32 Function(Pointer); 14 | typedef _AddVstC = Int32 Function(Pointer, Pointer, Pointer, Pointer); 15 | typedef _AddMixerC = Int32 Function(Pointer, Int32, Pointer); 16 | typedef _AddSplitC = Int32 Function(Pointer, Pointer); 17 | typedef _AddGainC = Int32 Function(Pointer, Float, Pointer); 18 | typedef _ConnC = Int32 Function(Pointer, Int32, Int32, Int32, Int32); 19 | typedef _SetIO = Int32 Function(Pointer, Int32, Int32); 20 | typedef _NoteC = Int32 Function(Pointer, Int32, Int32, Int32, Float); 21 | typedef _ParamCountC = Int32 Function(Pointer, Int32); 22 | typedef _ParamInfoC = Int32 Function(Pointer, Int32, Int32, Pointer, Pointer, Int32, Pointer, Int32); 23 | typedef _GetParamC = Float Function(Pointer, Int32, Int32); 24 | typedef _SetParamC = Int32 Function(Pointer, Int32, Int32, Float); 25 | typedef _LatencyC = Int32 Function(Pointer); 26 | typedef _ProcessC = Int32 Function(Pointer, Pointer, Pointer, Pointer, Pointer, Int32); 27 | 28 | class GraphBindings { 29 | final DynamicLibrary lib; 30 | GraphBindings(this.lib); 31 | 32 | late final Pointer Function(double, int) create = 33 | lib.lookupFunction<_GraphCreateC, Pointer Function(double, int)>('dvh_graph_create'); 34 | late final void Function(Pointer) destroy = 35 | lib.lookupFunction<_GraphDestroyC, void Function(Pointer)>('dvh_graph_destroy'); 36 | late final int Function(Pointer) clear = 37 | lib.lookupFunction<_GraphClearC, int Function(Pointer)>('dvh_graph_clear'); 38 | 39 | late final int Function(Pointer, Pointer, Pointer, Pointer) addVst = 40 | lib.lookupFunction<_AddVstC, int Function(Pointer, Pointer, Pointer, Pointer)>('dvh_graph_add_vst'); 41 | late final int Function(Pointer, int, Pointer) addMixer = 42 | lib.lookupFunction<_AddMixerC, int Function(Pointer, int, Pointer)>('dvh_graph_add_mixer'); 43 | late final int Function(Pointer, Pointer) addSplit = 44 | lib.lookupFunction<_AddSplitC, int Function(Pointer, Pointer)>('dvh_graph_add_split'); 45 | late final int Function(Pointer, double, Pointer) addGain = 46 | lib.lookupFunction<_AddGainC, int Function(Pointer, double, Pointer)>('dvh_graph_add_gain'); 47 | 48 | late final int Function(Pointer, int, int, int, int) connect = 49 | lib.lookupFunction<_ConnC, int Function(Pointer, int, int, int, int)>('dvh_graph_connect'); 50 | late final int Function(Pointer, int, int, int, int) disconnect = 51 | lib.lookupFunction<_ConnC, int Function(Pointer, int, int, int, int)>('dvh_graph_disconnect'); 52 | late final int Function(Pointer, int, int) setIO = 53 | lib.lookupFunction<_SetIO, int Function(Pointer, int, int)>('dvh_graph_set_io_nodes'); 54 | 55 | late final int Function(Pointer, int, int, int, double) noteOn = 56 | lib.lookupFunction<_NoteC, int Function(Pointer, int, int, int, double)>('dvh_graph_note_on'); 57 | late final int Function(Pointer, int, int, int, double) noteOff = 58 | lib.lookupFunction<_NoteC, int Function(Pointer, int, int, int, double)>('dvh_graph_note_off'); 59 | 60 | late final int Function(Pointer, int) paramCount = 61 | lib.lookupFunction<_ParamCountC, int Function(Pointer, int)>('dvh_graph_param_count'); 62 | late final int Function(Pointer, int, int, Pointer, Pointer, int, Pointer, int) paramInfo = 63 | lib.lookupFunction<_ParamInfoC, int Function(Pointer, int, int, Pointer, Pointer, int, Pointer, int)>('dvh_graph_param_info'); 64 | 65 | late final double Function(Pointer, int, int) getParam = 66 | lib.lookupFunction<_GetParamC, double Function(Pointer, int, int)>('dvh_graph_get_param'); 67 | late final int Function(Pointer, int, int, double) setParam = 68 | lib.lookupFunction<_SetParamC, int Function(Pointer, int, int, double)>('dvh_graph_set_param'); 69 | 70 | late final int Function(Pointer) latency = 71 | lib.lookupFunction<_LatencyC, int Function(Pointer)>('dvh_graph_latency'); 72 | late final int Function(Pointer, Pointer, Pointer, Pointer, Pointer, int) process = 73 | lib.lookupFunction<_ProcessC, int Function(Pointer, Pointer, Pointer, Pointer, Pointer, int)>('dvh_graph_process_stereo'); 74 | } 75 | 76 | /// Helper to load the native library. See dart_vst_host.loadDvh() 77 | /// for details on path selection. This wrapper is duplicated here to 78 | /// avoid depending on dart_vst_host from dart_vst_graph. 79 | DynamicLibrary _openLib({String? path}) { 80 | if (path != null) return DynamicLibrary.open(path); 81 | if (Platform.isMacOS) return DynamicLibrary.open('libdart_vst_host.dylib'); 82 | if (Platform.isLinux) return DynamicLibrary.open('libdart_vst_host.so'); 83 | if (Platform.isWindows) return DynamicLibrary.open('dart_vst_host.dll'); 84 | throw UnsupportedError('Unsupported platform'); 85 | } 86 | 87 | /// High level API wrapping GraphBindings. Manages lifetime and 88 | /// provides convenience methods for graph construction. 89 | class VstGraph { 90 | final GraphBindings _b; 91 | final Pointer handle; 92 | VstGraph._(this._b, this.handle); 93 | factory VstGraph({required double sampleRate, required int maxBlock, String? dylibPath}) { 94 | final b = GraphBindings(_openLib(path: dylibPath)); 95 | final h = b.create(sampleRate, maxBlock); 96 | if (h == nullptr) throw StateError('graph create failed'); 97 | return VstGraph._(b, h); 98 | } 99 | void dispose() => _b.destroy(handle); 100 | 101 | /// Add a VST3 plug‑in to the graph. Returns the new node ID on 102 | /// success or throws on failure. 103 | int addVst(String path, {String? classUid}) { 104 | final p = path.toNativeUtf8(); 105 | final u = classUid == null ? nullptr : classUid.toNativeUtf8(); 106 | final id = malloc(); 107 | try { 108 | final ok = _b.addVst(handle, p, u, id) == 1; 109 | if (!ok) throw StateError('addVst failed'); 110 | return id.value; 111 | } finally { 112 | malloc.free(p); 113 | if (u != nullptr) malloc.free(u); 114 | malloc.free(id); 115 | } 116 | } 117 | 118 | /// Add a mixer with [inputs] stereo buses. Returns the node ID. 119 | int addMixer(int inputs) { 120 | final id = malloc(); 121 | try { 122 | if (_b.addMixer(handle, inputs, id) != 1) throw StateError('addMixer failed'); 123 | return id.value; 124 | } finally { 125 | malloc.free(id); 126 | } 127 | } 128 | /// Add a splitter node. Returns the node ID. 129 | int addSplit() { 130 | final id = malloc(); 131 | try { 132 | if (_b.addSplit(handle, id) != 1) throw StateError('addSplit failed'); 133 | return id.value; 134 | } finally { 135 | malloc.free(id); 136 | } 137 | } 138 | /// Add a gain node with initial dB value. Returns the node ID. 139 | int addGain(double db) { 140 | final id = malloc(); 141 | try { 142 | if (_b.addGain(handle, db, id) != 1) throw StateError('addGain failed'); 143 | return id.value; 144 | } finally { 145 | malloc.free(id); 146 | } 147 | } 148 | 149 | /// Connect source node [src] to destination [dst]. Only a single 150 | /// stereo bus per node is supported currently. Returns true on 151 | /// success. 152 | bool connect(int src, int dst) => _b.connect(handle, src, 0, dst, 0) == 1; 153 | 154 | /// Set which nodes serve as the graph’s input and output. Returns 155 | /// true on success. 156 | bool setIO({required int inputNode, required int outputNode}) => _b.setIO(handle, inputNode, outputNode) == 1; 157 | 158 | /// Set a parameter on a node. Returns true on success. 159 | bool setParam(int node, int paramId, double v) => _b.setParam(handle, node, paramId, v) == 1; 160 | 161 | /// Process a block of audio. The length of the output buffers must 162 | /// match the input length. This method is primarily intended for 163 | /// testing; real‑time processing in a plug‑in should use the native 164 | /// graph directly. Returns true on success. 165 | bool process(Float32List inL, Float32List inR, Float32List outL, Float32List outR) { 166 | if (inL.length != inR.length || inL.length != outL.length || inL.length != outR.length) { 167 | throw ArgumentError('Buffers must have same length'); 168 | } 169 | final n = inL.length; 170 | final pInL = malloc(n); 171 | final pInR = malloc(n); 172 | final pOutL = malloc(n); 173 | final pOutR = malloc(n); 174 | try { 175 | pInL.asTypedList(n).setAll(0, inL); 176 | pInR.asTypedList(n).setAll(0, inR); 177 | final ok = _b.process(handle, pInL, pInR, pOutL, pOutR, n) == 1; 178 | if (!ok) return false; 179 | outL.setAll(0, pOutL.asTypedList(n)); 180 | outR.setAll(0, pOutR.asTypedList(n)); 181 | return true; 182 | } finally { 183 | malloc.free(pInL); 184 | malloc.free(pInR); 185 | malloc.free(pOutL); 186 | malloc.free(pOutR); 187 | } 188 | } 189 | } -------------------------------------------------------------------------------- /dart_vst_host/lib/src/host.dart: -------------------------------------------------------------------------------- 1 | /// High level wrappers over the native VST host bindings. These 2 | /// classes manage resources using RAII and provide idiomatic Dart 3 | /// APIs for loading plug‑ins, controlling parameters and processing 4 | /// audio. 5 | 6 | import 'dart:ffi'; 7 | import 'dart:io'; 8 | import 'dart:typed_data'; 9 | 10 | import 'package:ffi/ffi.dart'; 11 | 12 | import 'bindings.dart'; 13 | 14 | /// Represents a running host context. A host owns its VST plug‑ins 15 | /// and must be disposed when no longer needed. 16 | class VstHost { 17 | final NativeBindings _b; 18 | final Pointer handle; 19 | 20 | VstHost._(this._b, this.handle); 21 | 22 | /// Create a new host at the given sample rate and maximum block 23 | /// size. Optionally specify [dylibPath] to load the native 24 | /// library from a custom location. Throws StateError on failure. 25 | static VstHost create({required double sampleRate, required int maxBlock, String? dylibPath}) { 26 | final b = NativeBindings(loadDvh(path: dylibPath)); 27 | final h = b.dvhCreateHost(sampleRate, maxBlock); 28 | if (h == nullptr) { 29 | throw StateError('Failed to create host'); 30 | } 31 | return VstHost._(b, h); 32 | } 33 | 34 | /// Release resources associated with this host. After calling 35 | /// dispose(), the host handle is invalid and should not be used. 36 | void dispose() { 37 | _b.dvhDestroyHost(handle); 38 | } 39 | 40 | /// Load a VST plug‑in from [modulePath]. Optionally specify 41 | /// [classUid] to select a specific class from a multi‑class module. 42 | /// Returns a VstPlugin on success; throws StateError on failure. 43 | VstPlugin load(String modulePath, {String? classUid}) { 44 | print('🔍 DIAGNOSTIC: Attempting to load VST plugin from: $modulePath'); 45 | print('🔍 DIAGNOSTIC: classUid: ${classUid ?? "null"}'); 46 | 47 | // Check if path exists (could be file or directory for VST3 bundles) 48 | final fileEntity = FileSystemEntity.typeSync(modulePath); 49 | if (fileEntity == FileSystemEntityType.notFound) { 50 | print('❌ DIAGNOSTIC: Path does not exist: $modulePath'); 51 | throw StateError('VST plugin not found: $modulePath'); 52 | } 53 | print('🔍 DIAGNOSTIC: Path exists, type: $fileEntity'); 54 | 55 | // For .vst3 bundles, check for the actual shared library 56 | if (modulePath.endsWith('.vst3')) { 57 | print('🔍 DIAGNOSTIC: VST3 bundle detected, checking for shared library...'); 58 | final vst3Dir = Directory(modulePath); 59 | if (!vst3Dir.existsSync()) { 60 | print('❌ DIAGNOSTIC: VST3 bundle directory does not exist'); 61 | throw StateError('VST3 bundle not found: $modulePath'); 62 | } 63 | 64 | // Check for architecture-specific libraries 65 | final archPaths = [ 66 | '$modulePath/Contents/aarch64-linux', 67 | '$modulePath/Contents/arm64-linux', 68 | '$modulePath/Contents/x86_64-linux', 69 | '$modulePath/Contents/Linux', 70 | '$modulePath/Contents/linux' 71 | ]; 72 | 73 | print('🔍 DIAGNOSTIC: Searching for shared libraries in VST3 bundle...'); 74 | for (final archPath in archPaths) { 75 | if (Directory(archPath).existsSync()) { 76 | print('📁 DIAGNOSTIC: Found architecture directory: $archPath'); 77 | final files = Directory(archPath).listSync(); 78 | for (final f in files) { 79 | if (f.path.endsWith('.so')) { 80 | print('📄 DIAGNOSTIC: Found .so file: ${f.path}'); 81 | 82 | // Check architecture of the .so file using readelf 83 | try { 84 | final result = Process.runSync('readelf', ['-h', f.path]); 85 | if (result.exitCode == 0) { 86 | final output = result.stdout.toString(); 87 | print('🔍 DIAGNOSTIC: Library architecture info:'); 88 | final lines = output.split('\n'); 89 | for (final line in lines) { 90 | if (line.contains('Machine:') || line.contains('Class:')) { 91 | print(' $line'); 92 | } 93 | } 94 | } 95 | } catch (e) { 96 | print('⚠️ DIAGNOSTIC: Could not run readelf: $e'); 97 | } 98 | 99 | // Try to detect if it's x86_64 or ARM 100 | final isX86 = archPath.contains('x86_64'); 101 | final isArm = archPath.contains('aarch64') || archPath.contains('arm64'); 102 | 103 | if (isX86) { 104 | print('⚠️ DIAGNOSTIC: This appears to be an x86_64 binary'); 105 | print('⚠️ DIAGNOSTIC: Current system architecture: ${Platform.version.contains('arm') ? 'ARM' : 'Unknown'}'); 106 | } else if (isArm) { 107 | print('✅ DIAGNOSTIC: This appears to be an ARM binary'); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | print('🔍 DIAGNOSTIC: Calling native dvhLoadPlugin...'); 116 | final p = modulePath.toNativeUtf8(); 117 | final uid = classUid == null ? nullptr : classUid.toNativeUtf8(); 118 | final h = _b.dvhLoadPlugin(handle, p, uid); 119 | malloc.free(p); 120 | if (uid != nullptr) malloc.free(uid); 121 | 122 | if (h == nullptr) { 123 | print('❌ DIAGNOSTIC: dvhLoadPlugin returned nullptr'); 124 | print('❌ DIAGNOSTIC: Possible causes:'); 125 | print(' 1. VST plugin architecture mismatch (x86_64 plugin on ARM system)'); 126 | print(' 2. Missing dependencies (VST3 SDK not properly linked)'); 127 | print(' 3. Invalid VST3 bundle structure'); 128 | print(' 4. Plugin requires specific host features not implemented'); 129 | throw StateError('Failed to load plugin from $modulePath - check diagnostics above'); 130 | } 131 | 132 | print('✅ DIAGNOSTIC: Plugin loaded successfully, handle: $h'); 133 | return VstPlugin._(_b, h); 134 | } 135 | } 136 | 137 | /// Information about a plug‑in parameter. The [id] can be passed 138 | /// to getParamNormalized() and setParamNormalized(). 139 | class ParamInfo { 140 | final int id; 141 | final String title; 142 | final String units; 143 | ParamInfo(this.id, this.title, this.units); 144 | } 145 | 146 | /// Represents a loaded VST plug‑in. Provides methods for 147 | /// starting/stopping processing, handling MIDI events and 148 | /// manipulating parameters. Instances must be unloaded when no 149 | /// longer needed. 150 | class VstPlugin { 151 | final NativeBindings _b; 152 | final Pointer handle; 153 | VstPlugin._(this._b, this.handle); 154 | 155 | /// Activate the plug‑in with the given sample rate and block size. 156 | bool resume({required double sampleRate, required int maxBlock}) => 157 | _b.dvhResume(handle, sampleRate, maxBlock) == 1; 158 | 159 | /// Deactivate processing. Returns true on success. 160 | bool suspend() => _b.dvhSuspend(handle) == 1; 161 | 162 | /// Release this plug‑in from the host. After calling unload() the 163 | /// handle is invalid. Further calls on this instance will throw. 164 | void unload() => _b.dvhUnloadPlugin(handle); 165 | 166 | /// Number of parameters exposed by this plug‑in. 167 | int paramCount() => _b.dvhParamCount(handle); 168 | 169 | /// Get information about a parameter by index. Throws StateError if 170 | /// index is out of range or retrieval fails. 171 | ParamInfo paramInfoAt(int index) { 172 | final id = malloc(); 173 | final title = malloc.allocate(256); 174 | final units = malloc.allocate(64); 175 | try { 176 | final ok = _b.dvhParamInfo(handle, index, id, title, 256, units, 64) == 1; 177 | if (!ok) throw StateError('param info failed'); 178 | return ParamInfo(id.value, title.toDartString(), units.toDartString()); 179 | } finally { 180 | malloc.free(id); 181 | malloc.free(title); 182 | malloc.free(units); 183 | } 184 | } 185 | 186 | /// Get the normalized value of a parameter by ID. 187 | double getParamNormalized(int paramId) => _b.dvhGetParam(handle, paramId); 188 | 189 | /// Set the normalized value of a parameter by ID. Returns true on 190 | /// success. 191 | bool setParamNormalized(int paramId, double value) => 192 | _b.dvhSetParam(handle, paramId, value) == 1; 193 | 194 | /// Send a MIDI note on event. Channel is zero‑based. 195 | bool noteOn(int channel, int note, double velocity) => 196 | _b.dvhNoteOn(handle, channel, note, velocity) == 1; 197 | 198 | /// Send a MIDI note off event. 199 | bool noteOff(int channel, int note, double velocity) => 200 | _b.dvhNoteOff(handle, channel, note, velocity) == 1; 201 | 202 | /// Process a block of stereo audio. The input and output lists must 203 | /// all have the same length. Returns true on success. 204 | bool processStereoF32(Float32List inL, Float32List inR, Float32List outL, Float32List outR) { 205 | if (inL.length != inR.length || inL.length != outL.length || inL.length != outR.length) { 206 | throw ArgumentError('All buffers must have same length'); 207 | } 208 | final n = inL.length; 209 | final pInL = malloc(n); 210 | final pInR = malloc(n); 211 | final pOutL = malloc(n); 212 | final pOutR = malloc(n); 213 | try { 214 | pInL.asTypedList(n).setAll(0, inL); 215 | pInR.asTypedList(n).setAll(0, inR); 216 | final ok = _b.dvhProcessStereoF32(handle, pInL, pInR, pOutL, pOutR, n) == 1; 217 | if (!ok) return false; 218 | outL.setAll(0, pOutL.asTypedList(n)); 219 | outR.setAll(0, pOutR.asTypedList(n)); 220 | return true; 221 | } finally { 222 | malloc.free(pInL); 223 | malloc.free(pInR); 224 | malloc.free(pOutL); 225 | malloc.free(pOutR); 226 | } 227 | } 228 | } -------------------------------------------------------------------------------- /flutter_vst3/scripts/generate_plugin.dart: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dart 2 | 3 | import 'dart:convert'; 4 | import 'dart:io'; 5 | 6 | void main(List args) { 7 | if (args.length < 3 || args.length > 4) { 8 | print('USAGE: generate_plugin.dart [build_dir]'); 9 | exit(1); 10 | } 11 | 12 | final pluginDir = args[0]; 13 | final pluginName = args[1]; 14 | final targetName = args[2]; 15 | final buildDir = args.length == 4 ? args[3] : null; 16 | 17 | try { 18 | generatePluginFiles(pluginDir, pluginName, targetName, buildDir); 19 | print('SUCCESS: Generated VST3 plugin files'); 20 | } catch (e) { 21 | print('CRITICAL ERROR: $e'); 22 | exit(1); 23 | } 24 | } 25 | 26 | void generatePluginFiles(String pluginDir, String pluginName, String targetName, String? buildDir) { 27 | // Read metadata JSON 28 | final metadataFile = File('$pluginDir/plugin_metadata.json'); 29 | if (!metadataFile.existsSync()) { 30 | throw Exception('${metadataFile.path} not found!'); 31 | } 32 | 33 | final metadata = jsonDecode(metadataFile.readAsStringSync()) as Map; 34 | 35 | // Validate required fields 36 | final required = ['pluginName', 'vendor', 'version', 'category', 'bundleIdentifier', 'companyWeb', 'companyEmail']; 37 | for (final field in required) { 38 | if (!metadata.containsKey(field)) { 39 | throw Exception('Required field "$field" missing from plugin_metadata.json'); 40 | } 41 | } 42 | 43 | final parameters = (metadata['parameters'] as List? ?? []) 44 | .cast>(); 45 | 46 | // Generate files 47 | final genDir = buildDir != null 48 | ? Directory('$buildDir/generated') 49 | : Directory('$pluginDir/generated'); 50 | 51 | genDir.createSync(recursive: true); 52 | 53 | final pluginClass = _toPascalCase(targetName); 54 | 55 | // Find templates directory 56 | final scriptDir = Directory.fromUri(Platform.script).parent.path; 57 | final templateDir = '$scriptDir/../native/templates'; 58 | 59 | // Generate C++ code snippets 60 | final cppSnippets = _generateCppSnippets(parameters, metadata, pluginName); 61 | 62 | // Base replacements 63 | final replacements = { 64 | '{{PLUGIN_NAME}}': metadata['pluginName'], 65 | '{{PLUGIN_NAME_UPPER}}': pluginName.toUpperCase(), 66 | '{{PLUGIN_NAME_CAMEL}}': pluginClass, 67 | '{{PLUGIN_CLASS_NAME}}': pluginClass, 68 | '{{PLUGIN_ID}}': pluginName, 69 | '{{COMPANY_NAME}}': metadata['vendor'], 70 | '{{PLUGIN_URL}}': metadata['companyWeb'], 71 | '{{PLUGIN_EMAIL}}': metadata['companyEmail'], 72 | '{{PLUGIN_CATEGORY}}': metadata['category'], 73 | '{{PLUGIN_VERSION}}': metadata['version'], 74 | }; 75 | 76 | // Add generated C++ code 77 | replacements.addAll(cppSnippets); 78 | 79 | // Generate files from templates - using AOT template 80 | final templates = [ 81 | ('plugin_controller.cpp.template', '${targetName}_controller.cpp'), 82 | ('plugin_processor_aot.cpp.template', '${targetName}_processor.cpp'), 83 | ('plugin_factory.cpp.template', '${targetName}_factory.cpp'), 84 | ('plugin_processor_native.cpp.template', '${targetName}_processor_native.cpp') 85 | ]; 86 | 87 | for (final template in templates) { 88 | final templateFile = File('$templateDir/${template.$1}'); 89 | final outputFile = File('${genDir.path}/${template.$2}'); 90 | 91 | if (!templateFile.existsSync()) { 92 | print('WARNING: Template missing: ${templateFile.path}'); 93 | continue; 94 | } 95 | 96 | var content = templateFile.readAsStringSync(); 97 | 98 | // Replace all placeholders 99 | for (final replacement in replacements.entries) { 100 | content = content.replaceAll(replacement.key, replacement.value); 101 | } 102 | 103 | // Check for unresolved placeholders 104 | if (content.contains('{{')) { 105 | final unresolved = RegExp(r'\{\{[^}]+\}\}').allMatches(content).map((m) => m.group(0)).toList(); 106 | throw Exception('Unresolved placeholders in ${template.$2}: $unresolved'); 107 | } 108 | 109 | outputFile.writeAsStringSync(content); 110 | print('Generated: ${template.$2}'); 111 | } 112 | 113 | // Generate IDs header 114 | _generateIdsHeader(genDir, targetName, pluginClass, parameters, metadata); 115 | 116 | // Generate CMake metadata 117 | _generateCMakeMetadata(genDir, metadata, parameters); 118 | 119 | print('Generated ${parameters.length} parameter(s)'); 120 | } 121 | 122 | Map _generateCppSnippets(List> parameters, Map metadata, String pluginName) { 123 | if (parameters.isEmpty) { 124 | return { 125 | '{{PARAMETER_COUNT}}': '0', 126 | '{{PARAMETER_VARIABLES}}': ' // No parameters', 127 | '{{PARAMETER_DEFAULTS}}': ' // No parameters to initialize', 128 | '{{PARAMETER_CONTROLLER_INIT}}': ' // No parameters to add', 129 | '{{PARAMETER_STATE_SAVE}}': ' // No parameters to save', 130 | '{{PARAMETER_STATE_LOAD}}': ' // No parameters to load', 131 | '{{GET_PARAMETER_INFO}}': ' return kResultFalse;', 132 | '{{NORMALIZE_PARAMETER}}': ' return normalizedValue;', 133 | '{{DENORMALIZE_PARAMETER}}': ' return plainValue;', 134 | '{{STRING_TO_PARAMETER}}': ' return false;', 135 | '{{PARAMETER_TO_STRING}}': ' return false;', 136 | }; 137 | } 138 | 139 | // Generate parameter variables 140 | final paramVars = parameters.map((p) => ' double ${p['name']} = ${p['defaultValue']};').join('\n'); 141 | 142 | // Generate parameter initialization 143 | final paramDefaults = parameters.map((p) => ' ${p['name']} = ${p['defaultValue']};').join('\n'); 144 | 145 | // Generate controller parameter registration 146 | final controllerInit = parameters.map((p) { 147 | final title = (p['name'] as String).replaceAll('_', ' ').split(' ').map((word) => word.isEmpty ? '' : word[0].toUpperCase() + word.substring(1)).join(' '); 148 | return ' parameters.addParameter(STR16("$title"), STR16("${p['units']}"), 0, ${p['defaultValue']}, ParameterInfo::kCanAutomate, ${p['id']});'; 149 | }).join('\n'); 150 | 151 | // Generate parameter info getter 152 | final paramInfoCases = parameters.map((p) { 153 | final title = (p['name'] as String).replaceAll('_', ' ').split(' ').map((word) => word.isEmpty ? '' : word[0].toUpperCase() + word.substring(1)).join(' '); 154 | return ''' case ${p['id']}: 155 | info.id = ${p['id']}; 156 | info.title = STR16("$title"); 157 | info.shortTitle = STR16("$title"); 158 | info.units = STR16("${p['units']}"); 159 | info.stepCount = 0; 160 | info.defaultNormalizedValue = ${p['defaultValue']}; 161 | info.flags = ParameterInfo::kCanAutomate; 162 | return kResultTrue;'''; 163 | }).join('\n'); 164 | 165 | // Generate state read/write 166 | final stateRead = parameters.map((p) => ''' // Read ${p['name']} 167 | if (state->read(&${p['name']}, sizeof(${p['name']}), &bytesRead) != kResultTrue) return kResultFalse;''').join('\n'); 168 | 169 | final stateWrite = parameters.map((p) => ''' // Write ${p['name']} 170 | if (state->write(&${p['name']}, sizeof(${p['name']}), &bytesWritten) != kResultTrue) return kResultFalse;''').join('\n'); 171 | 172 | return { 173 | '{{PARAMETER_COUNT}}': parameters.length.toString(), 174 | '{{PARAMETER_VARIABLES}}': paramVars, 175 | '{{PARAMETER_DEFAULTS}}': paramDefaults, 176 | '{{PARAMETER_CONTROLLER_INIT}}': controllerInit, 177 | '{{GET_PARAMETER_INFO}}': paramInfoCases + '\n return kResultFalse;', 178 | '{{NORMALIZE_PARAMETER}}': ' return normalizedValue; // Identity for now', 179 | '{{DENORMALIZE_PARAMETER}}': ' return plainValue; // Identity for now', 180 | '{{STRING_TO_PARAMETER}}': ' return false; // Not implemented', 181 | '{{PARAMETER_TO_STRING}}': ' return false; // Not implemented', 182 | '{{PARAMETER_STATE_READ}}': parameters.isEmpty ? ' // No parameters to read' : ' int32 bytesRead = 0;\n$stateRead', 183 | '{{PARAMETER_STATE_WRITE}}': parameters.isEmpty ? ' // No parameters to write' : ' int32 bytesWritten = 0;\n$stateWrite', 184 | }; 185 | } 186 | 187 | void _generateIdsHeader(Directory genDir, String targetName, String pluginClass, List> parameters, Map metadata) { 188 | final content = '''#pragma once 189 | #include "pluginterfaces/base/funknown.h" 190 | 191 | using namespace Steinberg; 192 | 193 | // Parameter IDs for ${metadata['pluginName']} 194 | enum ${pluginClass}Parameters { 195 | ${parameters.map((p) => ' k${_toPascalCase(p['name'] as String)}Param = ${p['id']},').join('\n')} 196 | kNumParameters = ${parameters.length} 197 | }; 198 | 199 | // Plugin UIDs as proper FUID objects (generate proper GUIDs in production) 200 | static const FUID k${pluginClass}ProcessorUID(0xF9D0C991, 0x074C8404, 0x4D825FC5, 0x21E8F92B); 201 | static const FUID k${pluginClass}ControllerUID(0xA0115732, 0x16F06596, 0x4B9846B6, 0x007933D0); 202 | '''; 203 | 204 | File('${genDir.path}/${targetName}_ids.h').writeAsStringSync(content); 205 | print('Generated: ${targetName}_ids.h'); 206 | } 207 | 208 | void _generateCMakeMetadata(Directory genDir, Map metadata, List> parameters) { 209 | final content = '''set(JSON_PLUGIN_NAME "${metadata['pluginName']}") 210 | set(JSON_VENDOR "${metadata['vendor']}") 211 | set(JSON_VERSION "${metadata['version']}") 212 | set(JSON_CATEGORY "${metadata['category']}") 213 | set(JSON_BUNDLE_ID "${metadata['bundleIdentifier']}") 214 | set(JSON_WEB "${metadata['companyWeb']}") 215 | set(JSON_EMAIL "${metadata['companyEmail']}") 216 | set(PARAM_COUNT "${parameters.length}") 217 | '''; 218 | 219 | File('${genDir.path}/metadata.cmake').writeAsStringSync(content); 220 | } 221 | 222 | String _toPascalCase(String input) { 223 | return input.split('_').map((word) => word.isEmpty ? '' : word[0].toUpperCase() + word.substring(1)).join(''); 224 | } -------------------------------------------------------------------------------- /flutter_vst3/native/templates/plugin_processor_native.cpp.template: -------------------------------------------------------------------------------- 1 | // WORKING NATIVE PROCESSOR - NO AOT BULLSHIT! 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef _WIN32 15 | #include 16 | #else 17 | #include 18 | #include 19 | #endif 20 | 21 | constexpr uint8_t CMD_INIT = 0x01; 22 | constexpr uint8_t CMD_PROCESS = 0x02; 23 | constexpr uint8_t CMD_SET_PARAM = 0x03; 24 | constexpr uint8_t CMD_TERMINATE = 0xFF; 25 | 26 | // Forward declaration to get address for dladdr 27 | static void dummy_function() {} 28 | 29 | class {{PLUGIN_CLASS_NAME}}NativeProcessor { 30 | private: 31 | FILE* to_dart = nullptr; 32 | FILE* from_dart = nullptr; 33 | bool initialized = false; 34 | std::mutex io_mutex; 35 | std::string dart_exe_path; 36 | 37 | #ifdef _WIN32 38 | HANDLE hProcess = NULL; 39 | #else 40 | pid_t child_pid = -1; 41 | #endif 42 | 43 | std::string getExecutablePath() { 44 | // Use dladdr to get the path of the current shared object (VST3 binary) 45 | Dl_info dl_info; 46 | if (dladdr((void*)&dummy_function, &dl_info) != 0) { 47 | char* path_copy = strdup(dl_info.dli_fname); 48 | char* dir = dirname(path_copy); 49 | std::string dart_path = std::string(dir) + "/{{PLUGIN_ID}}_processor"; 50 | 51 | // fprintf(stderr, "{{PLUGIN_NAME_UPPER}}: VST3 binary at: %s\n", dl_info.dli_fname); 52 | // fprintf(stderr, "{{PLUGIN_NAME_UPPER}}: Looking for Dart executable at: %s\n", dart_path.c_str()); 53 | 54 | free(path_copy); 55 | return dart_path; 56 | } 57 | throw std::runtime_error("FAILED TO GET VST3 BINARY PATH!"); 58 | } 59 | 60 | public: 61 | void initialize(double sampleRate, int maxBlockSize) { 62 | if (initialized) return; 63 | 64 | // Get path to Dart executable relative to VST3 binary 65 | dart_exe_path = getExecutablePath(); 66 | 67 | // SPAWN THE DART EXECUTABLE 68 | #ifdef _WIN32 69 | // Windows implementation 70 | SECURITY_ATTRIBUTES sa; 71 | sa.nLength = sizeof(SECURITY_ATTRIBUTES); 72 | sa.bInheritHandle = TRUE; 73 | sa.lpSecurityDescriptor = NULL; 74 | 75 | HANDLE stdin_r, stdin_w, stdout_r, stdout_w; 76 | CreatePipe(&stdin_r, &stdin_w, &sa, 0); 77 | CreatePipe(&stdout_r, &stdout_w, &sa, 0); 78 | SetHandleInformation(stdin_w, HANDLE_FLAG_INHERIT, 0); 79 | SetHandleInformation(stdout_r, HANDLE_FLAG_INHERIT, 0); 80 | 81 | STARTUPINFO si = {0}; 82 | si.cb = sizeof(STARTUPINFO); 83 | si.hStdInput = stdin_r; 84 | si.hStdOutput = stdout_w; 85 | si.hStdError = stdout_w; 86 | si.dwFlags = STARTF_USESTDHANDLES; 87 | 88 | PROCESS_INFORMATION pi; 89 | if (!CreateProcess("{{PLUGIN_ID}}_processor.exe", NULL, NULL, NULL, 90 | TRUE, 0, NULL, NULL, &si, &pi)) { 91 | throw std::runtime_error("FAILED TO START {{PLUGIN_ID}}_processor.exe!"); 92 | } 93 | 94 | hProcess = pi.hProcess; 95 | CloseHandle(pi.hThread); 96 | CloseHandle(stdin_r); 97 | CloseHandle(stdout_w); 98 | 99 | to_dart = _fdopen(_open_osfhandle((intptr_t)stdin_w, 0), "wb"); 100 | from_dart = _fdopen(_open_osfhandle((intptr_t)stdout_r, 0), "rb"); 101 | #else 102 | // Unix implementation with bidirectional pipe 103 | int to_child[2], from_child[2]; 104 | if (pipe(to_child) == -1 || pipe(from_child) == -1) { 105 | throw std::runtime_error("FAILED TO CREATE PIPES!"); 106 | } 107 | 108 | child_pid = fork(); 109 | if (child_pid == -1) { 110 | throw std::runtime_error("FAILED TO FORK!"); 111 | } 112 | 113 | if (child_pid == 0) { 114 | // Child process 115 | dup2(to_child[0], STDIN_FILENO); 116 | dup2(from_child[1], STDOUT_FILENO); 117 | close(to_child[0]); 118 | close(to_child[1]); 119 | close(from_child[0]); 120 | close(from_child[1]); 121 | 122 | // fprintf(stderr, "{{PLUGIN_NAME_UPPER}}: Child process executing: %s\n", dart_exe_path.c_str()); 123 | // fflush(stderr); 124 | execl(dart_exe_path.c_str(), "{{PLUGIN_ID}}_processor", nullptr); 125 | // fprintf(stderr, "{{PLUGIN_NAME_UPPER}}: exec failed!\n"); 126 | // fflush(stderr); 127 | exit(1); // exec failed 128 | } 129 | 130 | // Parent process 131 | close(to_child[0]); 132 | close(from_child[1]); 133 | to_dart = fdopen(to_child[1], "wb"); 134 | from_dart = fdopen(from_child[0], "rb"); 135 | #endif 136 | 137 | // Send init command 138 | uint8_t init_msg[9]; 139 | init_msg[0] = CMD_INIT; 140 | memcpy(&init_msg[1], &sampleRate, sizeof(double)); 141 | 142 | fwrite(init_msg, 1, 9, to_dart); 143 | fflush(to_dart); 144 | 145 | // Wait for ACK 146 | uint8_t ack; 147 | if (fread(&ack, 1, 1, from_dart) != 1 || ack != CMD_INIT) { 148 | throw std::runtime_error("DART PROCESS FAILED TO INITIALIZE!"); 149 | } 150 | 151 | initialized = true; 152 | } 153 | 154 | void processStereo(float* inputL, float* inputR, 155 | float* outputL, float* outputR, int numSamples) { 156 | if (!initialized) { 157 | throw std::runtime_error("NOT INITIALIZED!"); 158 | } 159 | 160 | std::lock_guard lock(io_mutex); 161 | 162 | // Create message 163 | size_t msg_size = 5 + numSamples * 8; 164 | std::vector message(msg_size); 165 | 166 | message[0] = CMD_PROCESS; 167 | memcpy(&message[1], &numSamples, sizeof(int32_t)); 168 | 169 | // Interleave audio 170 | float* data = (float*)&message[5]; 171 | for (int i = 0; i < numSamples; i++) { 172 | data[i * 2] = inputL[i]; 173 | data[i * 2 + 1] = inputR[i]; 174 | } 175 | 176 | // Send to Dart 177 | if (fwrite(message.data(), 1, msg_size, to_dart) != msg_size) { 178 | throw std::runtime_error("FAILED TO SEND AUDIO TO DART!"); 179 | } 180 | fflush(to_dart); 181 | 182 | // Read response 183 | uint8_t response_cmd; 184 | if (fread(&response_cmd, 1, 1, from_dart) != 1 || response_cmd != CMD_PROCESS) { 185 | throw std::runtime_error("INVALID RESPONSE FROM DART!"); 186 | } 187 | 188 | // Read processed audio 189 | std::vector processed(numSamples * 2); 190 | if (fread(processed.data(), sizeof(float), numSamples * 2, from_dart) != numSamples * 2) { 191 | throw std::runtime_error("FAILED TO READ PROCESSED AUDIO!"); 192 | } 193 | 194 | // De-interleave 195 | for (int i = 0; i < numSamples; i++) { 196 | outputL[i] = processed[i * 2]; 197 | outputR[i] = processed[i * 2 + 1]; 198 | } 199 | } 200 | 201 | void setParameter(int paramId, double value) { 202 | if (!initialized) return; 203 | 204 | uint8_t msg[13]; 205 | msg[0] = CMD_SET_PARAM; 206 | memcpy(&msg[1], ¶mId, sizeof(int32_t)); 207 | memcpy(&msg[5], &value, sizeof(double)); 208 | 209 | fwrite(msg, 1, 13, to_dart); 210 | fflush(to_dart); 211 | 212 | uint8_t ack; 213 | fread(&ack, 1, 1, from_dart); 214 | } 215 | 216 | void reset() { 217 | // Implemented if needed 218 | } 219 | 220 | void dispose() { 221 | if (!initialized) return; 222 | 223 | uint8_t term = CMD_TERMINATE; 224 | fwrite(&term, 1, 1, to_dart); 225 | fflush(to_dart); 226 | 227 | fclose(to_dart); 228 | fclose(from_dart); 229 | 230 | #ifdef _WIN32 231 | WaitForSingleObject(hProcess, 5000); 232 | CloseHandle(hProcess); 233 | #else 234 | int status; 235 | waitpid(child_pid, &status, 0); 236 | #endif 237 | 238 | initialized = false; 239 | } 240 | 241 | ~{{PLUGIN_CLASS_NAME}}NativeProcessor() { 242 | dispose(); 243 | } 244 | }; 245 | 246 | // Global instance 247 | static {{PLUGIN_CLASS_NAME}}NativeProcessor* g_processor = nullptr; 248 | 249 | // C interface 250 | extern "C" { 251 | void {{PLUGIN_ID}}_native_initialize(double sample_rate, int max_block_size) { 252 | try { 253 | if (!g_processor) g_processor = new {{PLUGIN_CLASS_NAME}}NativeProcessor(); 254 | g_processor->initialize(sample_rate, max_block_size); 255 | } catch (const std::exception& e) { 256 | fprintf(stderr, "{{PLUGIN_NAME_UPPER}} NATIVE FAILURE: %s\n", e.what()); 257 | abort(); // FAIL HARD! 258 | } 259 | } 260 | 261 | void {{PLUGIN_ID}}_native_process_stereo(float* inputL, float* inputR, 262 | float* outputL, float* outputR, int samples) { 263 | try { 264 | if (!g_processor) throw std::runtime_error("NOT INITIALIZED!"); 265 | g_processor->processStereo(inputL, inputR, outputL, outputR, samples); 266 | } catch (const std::exception& e) { 267 | fprintf(stderr, "{{PLUGIN_NAME_UPPER}} NATIVE FAILURE: %s\n", e.what()); 268 | abort(); // FAIL HARD! 269 | } 270 | } 271 | 272 | void {{PLUGIN_ID}}_native_set_parameter(int param_id, double value) { 273 | if (g_processor) g_processor->setParameter(param_id, value); 274 | } 275 | 276 | double {{PLUGIN_ID}}_native_get_parameter(int param_id) { 277 | return 0.0; // Implement if needed 278 | } 279 | 280 | void {{PLUGIN_ID}}_native_reset() { 281 | if (g_processor) g_processor->reset(); 282 | } 283 | 284 | void {{PLUGIN_ID}}_native_dispose() { 285 | if (g_processor) { 286 | g_processor->dispose(); 287 | delete g_processor; 288 | g_processor = nullptr; 289 | } 290 | } 291 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # flutter_vst3 Toolkit Makefile 2 | # Builds VST® 3 plugins with Flutter UI and pure Dart audio processing 3 | 4 | .PHONY: all build test clean clean-native clean-plugin help dart-deps flutter-deps reverb-vst install reverb reverb-build-only echo echo-vst echo-build-only echo-deps 5 | 6 | # Default target - build the Flutter Reverb VST® 3 plugin 7 | all: reverb 8 | 9 | # Build all components (host, graph, and reverb VST) 10 | build: native plugin dart-deps flutter-deps reverb-vst 11 | 12 | # Build the Flutter Reverb VST® 3 plugin 13 | reverb: reverb-deps 14 | @echo "Building Flutter Reverb VST® 3 plugin..." 15 | @mkdir -p vsts/flutter_reverb/build 16 | @cd vsts/flutter_reverb/build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j$(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) 17 | @echo "✅ VST® 3 plugin built: vsts/flutter_reverb/build/VST3/Release/flutter_reverb.vst3" 18 | 19 | # Alias for reverb 20 | reverb-vst: reverb 21 | 22 | # Build reverb VST® 3 without installing (explicit build-only target) 23 | reverb-build-only: reverb 24 | 25 | # Build the Echo VST® 3 plugin 26 | echo: echo-deps 27 | @echo "Building Echo VST® 3 plugin..." 28 | @mkdir -p vsts/echo/build 29 | @cd vsts/echo/build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j$(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) 30 | @echo "✅ VST® 3 plugin built: vsts/echo/build/VST3/Release/echo.vst3" 31 | 32 | # Alias for echo 33 | echo-vst: echo 34 | 35 | # Build echo VST® 3 without installing (explicit build-only target) 36 | echo-build-only: echo 37 | 38 | # Run all tests 39 | test: build 40 | @echo "Running flutter_vst3 tests..." 41 | cd flutter_vst3 && dart test || true 42 | @echo "Running flutter_reverb tests..." 43 | cd vsts/flutter_reverb && dart test || true 44 | @echo "Running echo tests..." 45 | cd vsts/echo && dart test || true 46 | @echo "Running dart_vst_host tests..." 47 | cd dart_vst_host && dart test 48 | @echo "Running dart_vst_graph tests..." 49 | cd dart_vst_graph && dart test 50 | 51 | # Build native libraries (required for all Dart components) 52 | native: clean-native 53 | @echo "Building native libraries..." 54 | @if [ -z "$(VST3_SDK_DIR)" ]; then \ 55 | if [ -d "vst3sdk" ]; then \ 56 | export VST3_SDK_DIR="$(shell pwd)/vst3sdk"; \ 57 | echo "Using VST3 SDK from $(shell pwd)/vst3sdk"; \ 58 | elif [ -d "/workspace/vst3sdk" ]; then \ 59 | export VST3_SDK_DIR="/workspace/vst3sdk"; \ 60 | echo "Using VST3 SDK from /workspace/vst3sdk"; \ 61 | else \ 62 | echo "Error: VST3_SDK_DIR environment variable not set"; \ 63 | echo "Please set it to the root of Steinberg VST3 SDK or run setup.sh first"; \ 64 | exit 1; \ 65 | fi; \ 66 | fi 67 | @echo "Building dart_vst_host library..." 68 | @if [ -d "/workspace" ]; then \ 69 | mkdir -p /workspace/dart_vst_host/native/build; \ 70 | cd /workspace/dart_vst_host/native/build && VST3_SDK_DIR=$${VST3_SDK_DIR:-/workspace/vst3sdk} cmake .. && make; \ 71 | cp /workspace/dart_vst_host/native/build/libdart_vst_host.* /workspace/ 2>/dev/null || true; \ 72 | cp /workspace/dart_vst_host/native/build/libdart_vst_host.* /workspace/dart_vst_host/ 2>/dev/null || true; \ 73 | else \ 74 | mkdir -p dart_vst_host/native/build; \ 75 | cd dart_vst_host/native/build && VST3_SDK_DIR=$${VST3_SDK_DIR:-$(shell pwd)/vst3sdk} cmake .. && make; \ 76 | cp dart_vst_host/native/build/libdart_vst_host.* . 2>/dev/null || true; \ 77 | cp dart_vst_host/native/build/libdart_vst_host.* dart_vst_host/ 2>/dev/null || true; \ 78 | fi 79 | @echo "Building dart_vst_graph library..." 80 | @if [ -d "/workspace" ]; then \ 81 | mkdir -p /workspace/dart_vst_graph/native/build; \ 82 | cd /workspace/dart_vst_graph/native/build && VST3_SDK_DIR=$${VST3_SDK_DIR:-/workspace/vst3sdk} cmake .. && make; \ 83 | cp /workspace/dart_vst_graph/native/build/libdart_vst_graph.* /workspace/ 2>/dev/null || true; \ 84 | cp /workspace/dart_vst_graph/native/build/libdart_vst_graph.* /workspace/dart_vst_graph/ 2>/dev/null || true; \ 85 | else \ 86 | mkdir -p dart_vst_graph/native/build; \ 87 | cd dart_vst_graph/native/build && VST3_SDK_DIR=$${VST3_SDK_DIR:-$(shell pwd)/vst3sdk} cmake .. && make; \ 88 | cp dart_vst_graph/native/build/libdart_vst_graph.* . 2>/dev/null || true; \ 89 | cp dart_vst_graph/native/build/libdart_vst_graph.* dart_vst_graph/ 2>/dev/null || true; \ 90 | fi 91 | @echo "Native libraries built and copied to required locations" 92 | 93 | # Build all VST® 3 plugins 94 | plugin: native clean-plugin 95 | @echo "Building all VST® 3 plugins..." 96 | @for plugin in vsts/*/; do \ 97 | if [ -f "$$plugin/CMakeLists.txt" ]; then \ 98 | echo "Building $$plugin"; \ 99 | cd "$$plugin" && mkdir -p build && cd build && cmake .. && make && cd ../../../; \ 100 | fi; \ 101 | done 102 | 103 | # Install Dart dependencies for all packages 104 | dart-deps: 105 | @echo "Installing flutter_vst3 dependencies..." 106 | @if [ -d "/workspace" ]; then \ 107 | dart pub get --directory=/workspace/flutter_vst3; \ 108 | dart pub get --directory=/workspace/dart_vst_host; \ 109 | dart pub get --directory=/workspace/dart_vst_graph; \ 110 | else \ 111 | dart pub get --directory=flutter_vst3; \ 112 | dart pub get --directory=dart_vst_host; \ 113 | dart pub get --directory=dart_vst_graph; \ 114 | fi 115 | 116 | # Install reverb plugin dependencies 117 | reverb-deps: 118 | @echo "Installing Flutter Reverb dependencies..." 119 | @if [ -d "/workspace" ]; then \ 120 | dart pub get --directory=/workspace/flutter_vst3; \ 121 | dart pub get --directory=/workspace/vsts/flutter_reverb; \ 122 | else \ 123 | dart pub get --directory=flutter_vst3; \ 124 | dart pub get --directory=vsts/flutter_reverb; \ 125 | fi 126 | 127 | # Install echo plugin dependencies 128 | echo-deps: 129 | @echo "Installing Echo plugin dependencies..." 130 | @if [ -d "/workspace" ]; then \ 131 | dart pub get --directory=/workspace/flutter_vst3; \ 132 | dart pub get --directory=/workspace/vsts/echo; \ 133 | else \ 134 | dart pub get --directory=flutter_vst3; \ 135 | dart pub get --directory=vsts/echo; \ 136 | fi 137 | 138 | # Install Flutter dependencies 139 | flutter-deps: 140 | @echo "Installing Flutter dependencies..." 141 | @if [ -d "/workspace" ]; then \ 142 | flutter pub get --directory=/workspace/flutter_ui; \ 143 | else \ 144 | flutter pub get --directory=flutter_ui; \ 145 | fi 146 | 147 | # Install VST® 3 plugin to system location (macOS/Linux) 148 | install: reverb-vst 149 | @echo "Installing flutter_reverb.vst3..." 150 | @if [ "$$(uname)" = "Darwin" ]; then \ 151 | mkdir -p ~/Library/Audio/Plug-Ins/VST3/; \ 152 | cp -r vsts/flutter_reverb/build/VST3/Release/flutter_reverb.vst3 ~/Library/Audio/Plug-Ins/VST3/; \ 153 | echo "✅ Installed to ~/Library/Audio/Plug-Ins/VST3/"; \ 154 | elif [ "$$(uname)" = "Linux" ]; then \ 155 | mkdir -p ~/.vst3/; \ 156 | cp -r vsts/flutter_reverb/build/VST3/Release/flutter_reverb.vst3 ~/.vst3/; \ 157 | echo "✅ Installed to ~/.vst3/"; \ 158 | else \ 159 | echo "⚠️ Manual installation required on this platform"; \ 160 | fi 161 | 162 | # Clean all build artifacts 163 | clean: clean-native clean-plugin 164 | @echo "Removing native libraries from all locations..." 165 | rm -f libdart_vst_host.* 166 | rm -f *.dylib *.so *.dll 167 | rm -f dart_vst_host/libdart_vst_host.* 168 | rm -f dart_vst_graph/libdart_vst_graph.* 169 | 170 | # Clean native library builds 171 | clean-native: 172 | @echo "Cleaning native builds..." 173 | @if [ -d "/workspace" ]; then \ 174 | rm -rf /workspace/dart_vst_host/native/build; \ 175 | rm -rf /workspace/dart_vst_graph/native/build; \ 176 | else \ 177 | rm -rf dart_vst_host/native/build; \ 178 | rm -rf dart_vst_graph/native/build; \ 179 | fi 180 | 181 | # Clean plugin builds 182 | clean-plugin: 183 | @echo "Cleaning all VST plugin builds..." 184 | @for plugin in vsts/*/; do \ 185 | if [ -d "$$plugin/build" ]; then \ 186 | echo "Cleaning $$plugin"; \ 187 | rm -rf "$$plugin/build"; \ 188 | fi; \ 189 | done 190 | 191 | # Run Flutter app 192 | run-flutter: flutter-deps 193 | cd flutter_ui && flutter run 194 | 195 | # Run dart_vst_host tests only 196 | test-host: native dart-deps 197 | cd dart_vst_host && dart test 198 | 199 | # Run dart_vst_graph tests only 200 | test-graph: native dart-deps 201 | cd dart_vst_graph && dart test 202 | 203 | # Check environment 204 | check-env: 205 | @echo "Checking environment..." 206 | @test -n "$(VST3_SDK_DIR)" && echo "✅ VST3_SDK_DIR = $(VST3_SDK_DIR)" || echo "❌ VST3_SDK_DIR not set" 207 | @command -v cmake >/dev/null 2>&1 && echo "✅ CMake available" || echo "❌ CMake not found" 208 | @command -v dart >/dev/null 2>&1 && echo "✅ Dart available" || echo "❌ Dart not found" 209 | @command -v flutter >/dev/null 2>&1 && echo "✅ Flutter available" || echo "❌ Flutter not found" 210 | 211 | # Help 212 | help: 213 | @echo "Dart VST3 Toolkit Build System" 214 | @echo "===============================" 215 | @echo "" 216 | @echo "🎯 PRIMARY TARGET:" 217 | @echo " all (default) - Build Flutter Dart Reverb VST3 plugin" 218 | @echo "" 219 | @echo "🎛️ PLUGIN TARGETS:" 220 | @echo " reverb-vst - Build Flutter Dart Reverb VST3 plugin" 221 | @echo " reverb-build-only - Build Flutter Dart Reverb VST3 plugin (no install)" 222 | @echo " reverb-deps - Install reverb plugin dependencies only" 223 | @echo " echo-vst - Build Echo VST3 plugin" 224 | @echo " echo-build-only - Build Echo VST3 plugin (no install)" 225 | @echo " echo-deps - Install echo plugin dependencies only" 226 | @echo " install - Build and install VST3 plugin to system" 227 | @echo "" 228 | @echo "🏗️ BUILD TARGETS:" 229 | @echo " build - Build all components (host, graph, reverb)" 230 | @echo " native - Build native library with VST3 bridge" 231 | @echo " plugin - Build generic VST3 plugin (old)" 232 | @echo "" 233 | @echo "📦 DEPENDENCY TARGETS:" 234 | @echo " dart-deps - Install all Dart package dependencies" 235 | @echo " flutter-deps - Install Flutter UI dependencies" 236 | @echo "" 237 | @echo "🧪 TESTING TARGETS:" 238 | @echo " test - Run all tests (bridge, reverb, host, graph)" 239 | @echo " test-host - Run dart_vst_host tests only" 240 | @echo " test-graph - Run dart_vst_graph tests only" 241 | @echo "" 242 | @echo "🧹 CLEANUP TARGETS:" 243 | @echo " clean - Clean all build artifacts" 244 | @echo " clean-native - Clean native library build only" 245 | @echo " clean-plugin - Clean plugin build only" 246 | @echo "" 247 | @echo "🔧 UTILITY TARGETS:" 248 | @echo " run-flutter - Run Flutter UI application" 249 | @echo " check-env - Check build environment setup" 250 | @echo " help - Show this help message" 251 | @echo "" 252 | @echo "📋 EXAMPLES:" 253 | @echo " make # Build FlutterDartReverb.vst3" 254 | @echo " make clean reverb-vst # Clean build and rebuild reverb VST" 255 | @echo " make install # Build and install to DAW plugins folder" 256 | @echo "" 257 | @echo "🔧 PREREQUISITES:" 258 | @echo " • Set VST3_SDK_DIR environment variable (or use bundled SDK)" 259 | @echo " • Install CMake 3.20+, Dart SDK 3.0+, and Flutter" 260 | @echo "" 261 | @echo "📁 PACKAGES:" 262 | @echo " • dart_vst3_bridge/ - FFI bridge for any Dart VST3 plugin" 263 | @echo " • vsts/flutter_reverb/ - Pure Dart reverb VST3 implementation" 264 | @echo " • vsts/echo/ - Pure Dart echo VST3 implementation" 265 | @echo " • dart_vst_host/ - VST3 hosting for Dart applications" 266 | @echo " • dart_vst_graph/ - Audio graph system with VST routing" --------------------------------------------------------------------------------