DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
DSParkPlugin.h File Reference

Format-agnostic plugin layer: write ONE class, ship every format. More...

#include "../Core/AudioSpec.h"
#include "../Core/AudioBuffer.h"
#include "../Core/DenormalGuard.h"
#include <array>
#include <concepts>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <type_traits>
#include <vector>
Include dependency graph for DSParkPlugin.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

struct  dspark::plugin::Descriptor
 Static identity of a plugin. All fields are plain literals so the whole descriptor can live in a static constexpr of the user class. More...
 
struct  dspark::plugin::Param
 One automatable parameter. Plain values run [min, max]; hosts see the normalized [0, 1] projection. steps == 0 means continuous, steps == 1 a toggle, steps == N an N+1-position discrete control. More...
 
struct  dspark::plugin::TransportInfo
 Host transport snapshot, delivered once per audio block. More...
 
struct  dspark::plugin::MidiEvent
 One incoming MIDI-ish event, normalised across formats. More...
 
struct  dspark::plugin::PresetDef< NumValues >
 One factory preset: a name plus a PLAIN value for every parameter, in parameter-table order. Build the table with preset()/presets() so the value count is checked at compile time against the parameter count. More...
 
struct  dspark::plugin::BlockEvent
 One timestamped in-block event, normalised across formats: a parameter point, a bypass point, or a MIDI event. The backends collect them per block, sort them, and either split processing at quantum boundaries (sample-accurate mode) or apply them all up front. More...
 
struct  dspark::plugin::EditorSize
 Editor window size in logical pixels (physical = logical x host scale). More...
 
struct  dspark::plugin::PluginBase< Derived >
 Optional convenience base that makes EVERY contract method visible and overridable from one place - IDE-discoverable, without the virtuals. More...
 

Namespaces

namespace  dspark
 Main namespace for the DSPark framework.
 
namespace  dspark::plugin
 

Concepts

concept  dspark::plugin::HasReset
 
concept  dspark::plugin::HasLatency
 
concept  dspark::plugin::HasTail
 
concept  dspark::plugin::HasGetState
 
concept  dspark::plugin::HasSetState
 
concept  dspark::plugin::HasEditor
 Custom-editor switch. With hasEditor = false (or absent) hosts show their generic parameter UI. With hasEditor = true AND plugin/webview/DSParkWebViewEditor.h included BEFORE the format headers, the WebView editor layer serves editorHtml() inside the host window.
 
concept  dspark::plugin::HasSidechain
 Sidechain capability. Implement the two-buffer process - the same shape DSPark's own dynamics take:
 
concept  dspark::plugin::HasTransport
 Transport capability: void setTransport(const TransportInfo&) noexcept. Called on the audio thread, before processBlock, whenever the host supplied transport data for the block (no call means "same as last block"). This is how tempo-synced delays, LFOs and gates follow the song.
 
concept  dspark::plugin::HasMidi
 MIDI capability: void handleMidiEvent(const MidiEvent&) noexcept. Its presence grows a note/event input in every format (VST3 event bus, CLAP note port, AU music-device selectors) - which is also why PluginBase deliberately ships no default for it. Required for Category::Instrument, optional for MIDI-driven effects. Audio thread; allocation-free.
 
concept  dspark::plugin::HasOfflineMode
 Render-mode capability: void setOfflineRendering(bool) noexcept. The host flips it to true for non-realtime bounces (VST3 kOffline setup, the CLAP render extension, AU OfflineRender) - the moment to switch to more expensive algorithms (higher oversampling, longer lookahead). Called outside the audio thread, before processing (re)starts. Default: assume realtime.
 
concept  dspark::plugin::HasChannelSupport
 
concept  dspark::plugin::HasFactoryPresets
 Factory-preset capability: a static constexpr auto factoryPresets table built with presets(). The backends publish it natively - a VST3 program list, CLAP preset-load + preset-discovery, AU factory presets - so the host's own preset browser offers them. No PluginBase default on purpose: the table's presence changes what hosts display.
 
concept  dspark::plugin::HasSampleAccurateOptOut
 Sample-accurate opt-out: static constexpr bool sampleAccurateAutomation = false;. By default the wrappers split each audio block at automation points (snapped to kAutomationQuantum frames) so fast curves land where the host drew them instead of stepping once per block. Opt out when your plugin has a high fixed cost per processBlock CALL and block-rate automation is acceptable.
 
concept  dspark::plugin::HasEditorHtml
 static const char* editorHtml() - the editor page (HTML/CSS/JS), usually a raw string literal. Required when hasEditor is true.
 
concept  dspark::plugin::HasEditorSize
 Optional static constexpr EditorSize editorSize { w, h };.
 
concept  dspark::plugin::HasEditorResizable
 Optional static constexpr bool editorResizable = true; - shorthand for editorResize = EditorResize::Free (default: fixed size).
 
concept  dspark::plugin::HasEditorResize
 Optional static constexpr EditorResize editorResize = ...; - full resize policy; takes precedence over editorResizable.
 
concept  dspark::plugin::HasEditorDebug
 Optional static constexpr bool editorDebug = true; - enables the browser DevTools in the editor (WebView2; development builds only).
 
concept  dspark::plugin::HasEditorDevFile
 Optional static const char* editorDevFile() - absolute path of an HTML file to load INSTEAD of editorHtml() while developing: edit the file, reopen the editor, no recompile. Falls back to editorHtml() when the file is missing, so the same build still works elsewhere. Strip from releases.
 

Enumerations

enum class  dspark::plugin::Category { dspark::plugin::Fx , dspark::plugin::Instrument }
 Plugin category (reflected into each format's class metadata). More...
 
enum class  dspark::plugin::ChannelSupport { dspark::plugin::MonoAndStereo , dspark::plugin::StereoOnly , dspark::plugin::MonoOnly }
 Bus widths a plugin offers to the host. The DSPark contract is channel-agnostic by construction (prepare() receives numChannels and processBlock honours io.numChannels()), so the DEFAULT is mono+stereo: the plugin appears on every track type. Declare static constexpr auto channels = dspark::plugin::ChannelSupport::...; only to restrict it (e.g. StereoOnly for M/S wideners). All buses of an instance run the same width - a sidechain follows the main pair. More...
 
enum class  dspark::plugin::EditorResize { dspark::plugin::Fixed , dspark::plugin::Free , dspark::plugin::KeepAspect }
 How the host may resize the editor window. Declare static constexpr EditorResize editorResize = ...; in the plugin class; without it the window is Fixed (or Free if the simpler editorResizable = true shorthand is present). More...
 

Functions

constexpr Param dspark::plugin::param (const char *id, const char *name, float minValue, float maxValue, float defValue, const char *unit) noexcept
 Continuous parameter helper.
 
constexpr Param dspark::plugin::toggle (const char *id, const char *name, bool defaultOn) noexcept
 On/off parameter helper.
 
template<typename... Ps>
constexpr std::array< Param, sizeof...(Ps)> dspark::plugin::params (Ps... ps) noexcept
 Builds the parameter table (use inside static constexpr auto).
 
constexpr double dspark::plugin::toNormalized (const Param &p, double plain) noexcept
 Plain [min, max] -> normalized [0, 1] (steps snap on the way back).
 
constexpr double dspark::plugin::toPlain (const Param &p, double normalized) noexcept
 Normalized [0, 1] -> plain [min, max], snapped for stepped params.
 
int dspark::plugin::parseToggleText (const char *text) noexcept
 Case-insensitive "On"/"Off" recognition - the inverse of the toggle display below. Returns 1 for On, 0 for Off, -1 for anything else. Hosts round-trip displayed strings through text-to-value (automation lanes, typed values), so toggles must parse their own output.
 
void dspark::plugin::formatValue (const Param &p, double plain, char *out, int outSize) noexcept
 Formats a plain value for host display ("3.5 dB", "On", "440 Hz").
 
constexpr uint32_t dspark::plugin::hash32 (const char *s) noexcept
 FNV-1a 32-bit over a C string - the per-parameter host id.
 
constexpr uint64_t dspark::plugin::hash64 (const char *s, uint64_t salt) noexcept
 FNV-1a 64-bit with a salt (two runs build a 128-bit class UID).
 
template<typename P >
constexpr bool dspark::plugin::paramIdsUnique () noexcept
 Compile-time guarantee that every parameter id hashes uniquely and collides with neither reserved state id ('PRGM', 'BYPS'). A collision would silently cross-wire automation and state between two parameters, so the backends refuse to build: static_assert(paramIdsUnique<MyPlugin>());.
 
constexpr std::array< uint8_t, 16 > dspark::plugin::makeUid (const char *productId, uint64_t salt) noexcept
 Deterministic 16-byte class UID from the stable productId. Each backend salts differently so VST3/CLAP/AU ids never collide.
 
template<typename P >
constexpr ChannelSupport dspark::plugin::channelSupportOf () noexcept
 The declared channel support of P (default: mono+stereo).
 
template<typename P >
constexpr bool dspark::plugin::supportsChannelCount (int numChannels) noexcept
 True when P accepts a bus width of numChannels (1 or 2).
 
template<typename P >
constexpr int dspark::plugin::defaultChannelCount () noexcept
 The width every backend starts in before the host negotiates.
 
template<typename... Vs>
constexpr PresetDef< sizeof...(Vs)> dspark::plugin::preset (const char *name, Vs... values) noexcept
 Builds one factory preset: preset("Warm", 6.0f, 0.8f, ...) - one PLAIN value per parameter, in table order.
 
template<typename First , typename... Rest>
constexpr std::array< First, 1+sizeof...(Rest)> dspark::plugin::presets (First first, Rest... rest) noexcept
 Builds the factory preset table (all presets must cover the same parameter count - enforced here; matched against the parameter table by the backends). Declare as static constexpr auto factoryPresets = ....
 
template<typename P >
constexpr int dspark::plugin::factoryPresetCountOf () noexcept
 Number of factory presets of P (0 when none declared).
 
template<typename P >
constexpr double dspark::plugin::presetNormalized (int presetIndex, size_t paramIndex) noexcept
 Normalized value of parameter paramIndex in factory preset presetIndex (bounds already validated by the caller).
 
template<typename P >
constexpr bool dspark::plugin::sampleAccurateOf () noexcept
 
void dspark::plugin::sortBlockEvents (BlockEvent *events, int count) noexcept
 Stable insertion sort by offset (tiny N, allocation-free - audio-thread safe; equal offsets keep arrival order).
 
template<typename P >
constexpr EditorSize dspark::plugin::editorSizeOf () noexcept
 The declared (logical) editor size, or the 480x320 default.
 
template<typename P >
constexpr EditorResize dspark::plugin::editorResizeOf () noexcept
 The effective resize policy for plugin class P.
 
template<typename P >
constexpr void dspark::plugin::constrainEditorSize (double &width, double &height, double scale) noexcept
 Applies the resize policy of P to a size the host proposes: Fixed pins it, Free clamps each axis to the 0.5x..3x window, KeepAspect fits the declared ratio INSIDE the proposal - never exceeding it on either axis. That last property is essential: hosts size the plugin area inside a window the user controls, so answering "larger" on any axis pushes the plugin outside its own window (clipped bottom in REAPER).
 
template<typename P >
std::vector< uint8_t > dspark::plugin::buildState (const P &user, const double *normalized, size_t numParams, int programIndex=-1, int bypassState=-1)
 
template<typename P >
bool dspark::plugin::applyState (P &user, const uint8_t *data, size_t size, double *normalized, int *programIndex=nullptr, int *bypassState=nullptr)
 Parses a state blob; fills normalized (defaults pre-loaded by the caller), reports a persisted program index / bypass through programIndex and bypassState (left untouched when absent) and forwards the user section. Returns false on a foreign blob.
 

Variables

constexpr int dspark::plugin::kAutomationQuantum = 32
 Sub-block grain: automation/event splits snap to this many frames (bounds the per-call overhead; ~0.7 ms at 48 kHz is inaudible).
 
constexpr int dspark::plugin::kMaxBlockEvents = 256
 Hard cap of timestamped events handled per block; the (very unlikely) excess applies at the position of the last captured event.
 
constexpr double dspark::plugin::kEditorMinSizeFactor = 0.5
 Resize bounds: hosts may shrink to half and grow to 3x the declared size.
 
constexpr double dspark::plugin::kEditorMaxSizeFactor = 3.0
 
constexpr uint32_t dspark::plugin::kStateMagic = 0x4453504Bu
 
constexpr uint32_t dspark::plugin::kStateVersion = 1u
 
constexpr uint32_t dspark::plugin::kProgramStateId = 0x5052474Du
 Reserved entry id persisting the active factory-preset index ('PRGM'); never a user parameter. Older builds skip it (unknown id).
 
constexpr uint32_t dspark::plugin::kBypassStateId = 0x42595053u
 Reserved entry id persisting the host bypass ('BYPS' - the same value every backend uses as the bypass ParamID). Hosts treat bypass as one more parameter, so it must survive a state round-trip like any other. Older builds skip it (unknown id).
 

Detailed Description

Format-agnostic plugin layer: write ONE class, ship every format.

The developer describes the plugin declaratively (a Descriptor and a constexpr parameter table) and implements the familiar DSPark contract (prepare / processBlock plus a setParameter switch). The format backends (plugin/vst3/..., later CLAP and AU) translate that single class into each plugin ABI - no external SDK to install, no base class to inherit from.

struct MyPlugin
{
static constexpr auto descriptor = dspark::plugin::Descriptor {
.name = "My Saturator",
.vendor = "My Company",
.url = "https://example.com",
.email = "mailto:dev@example.com",
.productId = "com.mycompany.mysaturator", // NEVER change after release
.version = "1.0.0",
};
static constexpr auto parameters = dspark::plugin::params(
dspark::plugin::param("drive", "Drive", -12.0f, 24.0f, 0.0f, "dB"),
dspark::plugin::param("mix", "Mix", 0.0f, 1.0f, 1.0f, ""));
void prepare(const dspark::AudioSpec& spec);
void setParameter(int index, float plainValue) noexcept; // any thread
void processBlock(dspark::AudioBufferView<float> io) noexcept;
// Optional (auto-detected): reset(), getLatency(), getTailSeconds(),
// getState(), setState(data, size).
};
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
constexpr std::array< Param, sizeof...(Ps)> params(Ps... ps) noexcept
Builds the parameter table (use inside static constexpr auto).
constexpr Param param(const char *id, const char *name, float minValue, float maxValue, float defValue, const char *unit) noexcept
Continuous parameter helper.
Describes the audio environment for a DSP processor.
Definition AudioSpec.h:35
Static identity of a plugin. All fields are plain literals so the whole descriptor can live in a stat...

Capability detection is by C++20 concepts: implement only what applies. The full optional menu (each one detected structurally, see the concepts below and docs/plugins.md):

  • reset / getLatency / getTailSeconds / getState / setState
  • processBlock(io, sidechain) -> a host-routable sidechain bus
  • handleMidiEvent(const MidiEvent&) -> note/CC/pitch-bend input
  • setTransport(const TransportInfo&) -> host tempo & timeline, per block
  • setOfflineRendering(bool) -> realtime vs bounce quality switch
  • channels (ChannelSupport) -> mono/stereo bus configurations
  • factoryPresets -> host-browsable factory programs
  • sampleAccurateAutomation -> opt out of sub-block automation
  • hasEditor + editorHtml() -> the WebView editor layer

State: the wrapper always serialises the parameter table itself (stable text ids hashed to 32 bits, version-tolerant); a user getState/setState blob - e.g. DSPark's StateBlob - rides along as an extra section.

Definition in file DSParkPlugin.h.