58#include "../Core/AudioSpec.h"
59#include "../Core/AudioBuffer.h"
60#include "../Core/DenormalGuard.h"
99 const char*
name =
"DSPark Plugin";
128 float minValue,
float maxValue,
float defValue,
129 const char* unit)
noexcept
131 return Param { id, name, minValue, maxValue, defValue, unit, 0 };
135constexpr Param toggle(
const char*
id,
const char* name,
bool defaultOn)
noexcept
137 return Param { id, name, 0.0f, 1.0f, defaultOn ? 1.0f : 0.0f,
"", 1 };
141template <
typename... Ps>
142constexpr std::array<
Param,
sizeof...(Ps)>
params(Ps... ps)
noexcept
152 const double range =
static_cast<double>(p.maxValue) - p.
minValue;
153 if (range <= 0.0)
return 0.0;
154 double n = (plain - p.minValue) / range;
155 return n < 0.0 ? 0.0 : (n > 1.0 ? 1.0 : n);
161 double n = normalized < 0.0 ? 0.0 : (normalized > 1.0 ? 1.0 : normalized);
165 const double scaled = n * p.steps + 0.5;
166 const double idx =
static_cast<double>(
static_cast<long long>(scaled));
168 n = n < 0.0 ? 0.0 : (n > 1.0 ? 1.0 : n);
170 return p.minValue + n * (
static_cast<double>(p.maxValue) - p.minValue);
179 auto lower = [](
char c) {
return c >=
'A' && c <= 'Z' ? static_cast<char>(c + 32) : c; };
180 if (text ==
nullptr)
return -1;
181 if (lower(text[0]) ==
'o' && lower(text[1]) ==
'n' && text[2] ==
'\0')
return 1;
182 if (lower(text[0]) ==
'o' && lower(text[1]) ==
'f' && lower(text[2]) ==
'f'
183 && text[3] ==
'\0')
return 0;
192 std::snprintf(out,
static_cast<size_t>(outSize),
"%s",
193 plain >= 0.5 * (p.minValue + p.maxValue) ?
"On" :
"Off");
196 if (p.unit && p.unit[0] !=
'\0')
197 std::snprintf(out,
static_cast<size_t>(outSize),
"%.2f %s", plain, p.unit);
199 std::snprintf(out,
static_cast<size_t>(outSize),
"%.2f", plain);
205constexpr uint32_t
hash32(
const char* s)
noexcept
207 uint32_t h = 2166136261u;
210 h ^=
static_cast<uint8_t
>(*s++);
217constexpr uint64_t
hash64(
const char* s, uint64_t salt)
noexcept
219 uint64_t h = 14695981039346656037ull ^ salt;
222 h ^=
static_cast<uint8_t
>(*s++);
223 h *= 1099511628211ull;
237 constexpr size_t n = P::parameters.size();
238 for (
size_t i = 0; i < n; ++i)
240 const uint32_t h =
hash32(P::parameters[i].
id);
241 if (h == 0x5052474Du || h == 0x42595053u)
return false;
242 for (
size_t j = i + 1; j < n; ++j)
243 if (
hash32(P::parameters[j].
id) == h)
return false;
252constexpr std::array<uint8_t, 16>
makeUid(
const char* productId, uint64_t salt)
noexcept
254 const uint64_t a =
hash64(productId, salt);
255 const uint64_t b =
hash64(productId, salt ^ 0x9E3779B97F4A7C15ull);
256 std::array<uint8_t, 16> uid {};
257 for (
int i = 0; i < 8; ++i)
259 uid[
static_cast<size_t>(i)] =
static_cast<uint8_t
>(a >> (8 * i));
260 uid[
static_cast<size_t>(i + 8)] =
static_cast<uint8_t
>(b >> (8 * i));
271concept HasLatency =
requires(
const P p) { { p.getLatency() } -> std::convertible_to<int>; };
274concept HasTail =
requires(
const P p) { { p.getTailSeconds() } -> std::convertible_to<double>; };
278 { p.getState() } -> std::convertible_to<std::vector<uint8_t>>;
283 { p.setState(d, n) } -> std::convertible_to<bool>;
291concept HasEditor =
requires { P::hasEditor; } && P::hasEditor;
313 AudioBufferView<float> sc) {
314 p.processBlock(io, sc);
413concept HasMidi =
requires(P p,
const MidiEvent& e) {
414 p.handleMidiEvent(e);
429 p.setOfflineRendering(offline);
452 { P::channels } -> std::convertible_to<ChannelSupport>;
489template <
size_t NumValues>
498template <
typename... Vs>
501 return PresetDef<
sizeof...(Vs)> { name, {
static_cast<float>(values)... } };
507template <
typename First,
typename... Rest>
508constexpr std::array<First, 1 +
sizeof...(Rest)>
presets(First first, Rest... rest)
noexcept
510 static_assert((std::is_same_v<First, Rest> && ...),
511 "every preset must list one value per parameter");
512 return { first, rest... };
524 { P::factoryPresets.size() } -> std::convertible_to<size_t>;
525 { P::factoryPresets[0].name } -> std::convertible_to<const char*>;
526 { P::factoryPresets[0].values.size() } -> std::convertible_to<size_t>;
535 static_assert(P::factoryPresets[0].values.size() == P::parameters.size(),
536 "factoryPresets must list one PLAIN value per parameter, "
537 "in parameter-table order");
538 return static_cast<int>(P::factoryPresets.size());
551 P::factoryPresets[
static_cast<size_t>(presetIndex)]
552 .values[paramIndex]);
573 { P::sampleAccurateAutomation } -> std::convertible_to<bool>;
580 return P::sampleAccurateAutomation;
613 for (
int i = 1; i < count; ++i)
617 while (j >= 0 && events[j].offset > key.
offset)
619 events[j + 1] = events[j];
652 { P::editorHtml() } -> std::convertible_to<const char*>;
658 { P::editorSize.width } -> std::convertible_to<int>;
659 { P::editorSize.height } -> std::convertible_to<int>;
667 return EditorSize {
static_cast<int>(P::editorSize.width),
668 static_cast<int>(P::editorSize.height) };
682 { P::editorResize } -> std::convertible_to<EditorResize>;
690 return P::editorResize;
716 width = logical.
width * scale;
717 height = logical.
height * scale;
725 width = width < minW ? minW : (width > maxW ? maxW : width);
726 height = height < minH ? minH : (height > maxH ? maxH : height);
729 const double ratio =
static_cast<double>(logical.
width) / logical.
height;
731 if (height * ratio < fitW) fitW = height * ratio;
732 fitW = fitW < minW ? minW : (fitW > maxW ? maxW : fitW);
734 height = fitW / ratio;
750 { P::editorDevFile() } -> std::convertible_to<const char*>;
788template <
typename Derived>
824 [[nodiscard]] std::vector<uint8_t>
getState()
const {
return {}; }
827 bool setState(
const uint8_t*,
size_t) {
return false; }
880inline std::vector<uint8_t>
buildState(
const P& user,
const double* normalized,
881 size_t numParams,
int programIndex = -1,
882 int bypassState = -1)
884 auto push32 = [](std::vector<uint8_t>& v, uint32_t x) {
885 v.push_back(
static_cast<uint8_t
>(x));
886 v.push_back(
static_cast<uint8_t
>(x >> 8));
887 v.push_back(
static_cast<uint8_t
>(x >> 16));
888 v.push_back(
static_cast<uint8_t
>(x >> 24));
890 auto pushValue = [&push32](std::vector<uint8_t>& v,
double value) {
892 static_assert(
sizeof(bits) ==
sizeof(value));
893 std::memcpy(&bits, &value,
sizeof(bits));
894 push32(v,
static_cast<uint32_t
>(bits));
895 push32(v,
static_cast<uint32_t
>(bits >> 32));
897 std::vector<uint8_t> blob;
900 push32(blob,
static_cast<uint32_t
>(numParams) + (programIndex >= 0 ? 1u : 0u)
901 + (bypassState >= 0 ? 1u : 0u));
902 for (
size_t i = 0; i < numParams; ++i)
904 push32(blob,
hash32(P::parameters[i].
id));
905 pushValue(blob, normalized[i]);
907 if (programIndex >= 0)
912 pushValue(blob,
static_cast<double>(programIndex));
914 if (bypassState >= 0)
917 pushValue(blob, bypassState > 0 ? 1.0 : 0.0);
921 const std::vector<uint8_t> userBlob = user.getState();
922 push32(blob,
static_cast<uint32_t
>(userBlob.size()));
923 blob.insert(blob.end(), userBlob.begin(), userBlob.end());
935inline bool applyState(P& user,
const uint8_t* data,
size_t size,
double* normalized,
936 int* programIndex =
nullptr,
int* bypassState =
nullptr)
938 auto read32 = [&](
size_t& pos, uint32_t& out) {
939 if (pos + 4 > size)
return false;
940 out =
static_cast<uint32_t
>(data[pos])
941 |
static_cast<uint32_t
>(data[pos + 1]) << 8
942 |
static_cast<uint32_t
>(data[pos + 2]) << 16
943 |
static_cast<uint32_t
>(data[pos + 3]) << 24;
948 uint32_t magic = 0, version = 0, count = 0;
949 if (!read32(pos, magic) || magic !=
kStateMagic)
return false;
950 if (!read32(pos, version) || version == 0)
return false;
951 if (!read32(pos, count))
return false;
953 constexpr size_t numParams = P::parameters.size();
954 for (uint32_t i = 0; i < count; ++i)
956 uint32_t
id = 0, lo = 0, hi = 0;
957 if (!read32(pos,
id) || !read32(pos, lo) || !read32(pos, hi))
return false;
958 const uint64_t bits =
static_cast<uint64_t
>(lo) | (
static_cast<uint64_t
>(hi) << 32);
960 std::memcpy(&v, &bits,
sizeof(v));
964 if (v != v)
continue;
967 if (programIndex !=
nullptr && v >= 0.0)
968 *programIndex =
static_cast<int>(v + 0.5);
973 if (bypassState !=
nullptr)
974 *bypassState = v >= 0.5 ? 1 : 0;
977 for (
size_t k = 0; k < numParams; ++k)
978 if (
hash32(P::parameters[k].
id) == id)
980 normalized[k] = v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v);
986 uint32_t userSize = 0;
990 if (read32(pos, userSize) && userSize > 0 && userSize <= size - pos)
993 user.setState(data + pos, userSize);
Optional static constexpr bool editorDebug = true; - enables the browser DevTools in the editor (WebV...
Optional static const char* editorDevFile() - absolute path of an HTML file to load INSTEAD of editor...
static const char* editorHtml() - the editor page (HTML/CSS/JS), usually a raw string literal....
Optional static constexpr bool editorResizable = true; - shorthand for editorResize = EditorResize::F...
Optional static constexpr EditorResize editorResize = ...; - full resize policy; takes precedence ove...
Optional static constexpr EditorSize editorSize { w, h };.
Custom-editor switch. With hasEditor = false (or absent) hosts show their generic parameter UI....
Factory-preset capability: a static constexpr auto factoryPresets table built with presets()....
MIDI capability: void handleMidiEvent(const MidiEvent&) noexcept. Its presence grows a note/event inp...
Render-mode capability: void setOfflineRendering(bool) noexcept. The host flips it to true for non-re...
Sample-accurate opt-out: static constexpr bool sampleAccurateAutomation = false;. By default the wrap...
Sidechain capability. Implement the two-buffer process - the same shape DSPark's own dynamics take:
Transport capability: void setTransport(const TransportInfo&) noexcept. Called on the audio thread,...
constexpr bool supportsChannelCount(int numChannels) noexcept
True when P accepts a bus width of numChannels (1 or 2).
constexpr std::array< Param, sizeof...(Ps)> params(Ps... ps) noexcept
Builds the parameter table (use inside static constexpr auto).
constexpr double kEditorMinSizeFactor
Resize bounds: hosts may shrink to half and grow to 3x the declared size.
EditorResize
How the host may resize the editor window. Declare static constexpr EditorResize editorResize = ....
@ Fixed
The window is exactly editorSize; hosts cannot drag-resize it.
@ KeepAspect
Drag-resizable, locked to the declared width:height ratio.
@ Free
Drag-resizable between 0.5x and 3x the declared size.
constexpr bool paramIdsUnique() noexcept
Compile-time guarantee that every parameter id hashes uniquely and collides with neither reserved sta...
constexpr ChannelSupport channelSupportOf() noexcept
The declared channel support of P (default: mono+stereo).
constexpr uint32_t kProgramStateId
Reserved entry id persisting the active factory-preset index ('PRGM'); never a user parameter....
constexpr uint32_t kStateVersion
Category
Plugin category (reflected into each format's class metadata).
@ Instrument
Audio generator (MIDI in -> audio out, no audio input).
@ Fx
Audio effect (audio in -> audio out).
std::vector< uint8_t > buildState(const P &user, const double *normalized, size_t numParams, int programIndex=-1, int bypassState=-1)
constexpr Param toggle(const char *id, const char *name, bool defaultOn) noexcept
On/off parameter helper.
constexpr bool sampleAccurateOf() noexcept
constexpr uint64_t hash64(const char *s, uint64_t salt) noexcept
FNV-1a 64-bit with a salt (two runs build a 128-bit class UID).
constexpr Param param(const char *id, const char *name, float minValue, float maxValue, float defValue, const char *unit) noexcept
Continuous parameter helper.
constexpr std::array< uint8_t, 16 > makeUid(const char *productId, uint64_t salt) noexcept
Deterministic 16-byte class UID from the stable productId. Each backend salts differently so VST3/CLA...
constexpr double toNormalized(const Param &p, double plain) noexcept
Plain [min, max] -> normalized [0, 1] (steps snap on the way back).
constexpr double toPlain(const Param &p, double normalized) noexcept
Normalized [0, 1] -> plain [min, max], snapped for stepped params.
int parseToggleText(const char *text) noexcept
Case-insensitive "On"/"Off" recognition - the inverse of the toggle display below....
constexpr double presetNormalized(int presetIndex, size_t paramIndex) noexcept
Normalized value of parameter paramIndex in factory preset presetIndex (bounds already validated by t...
constexpr int kAutomationQuantum
Sub-block grain: automation/event splits snap to this many frames (bounds the per-call overhead; ~0....
constexpr uint32_t hash32(const char *s) noexcept
FNV-1a 32-bit over a C string - the per-parameter host id.
constexpr EditorResize editorResizeOf() noexcept
The effective resize policy for plugin class P.
constexpr uint32_t kStateMagic
constexpr void 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 t...
constexpr double kEditorMaxSizeFactor
constexpr int kMaxBlockEvents
Hard cap of timestamped events handled per block; the (very unlikely) excess applies at the position ...
constexpr PresetDef< sizeof...(Vs)> preset(const char *name, Vs... values) noexcept
Builds one factory preset: preset("Warm", 6.0f, 0.8f, ...) - one PLAIN value per parameter,...
constexpr EditorSize editorSizeOf() noexcept
The declared (logical) editor size, or the 480x320 default.
constexpr int factoryPresetCountOf() noexcept
Number of factory presets of P (0 when none declared).
constexpr int defaultChannelCount() noexcept
The width every backend starts in before the host negotiates.
void formatValue(const Param &p, double plain, char *out, int outSize) noexcept
Formats a plain value for host display ("3.5 dB", "On", "440 Hz").
void sortBlockEvents(BlockEvent *events, int count) noexcept
Stable insertion sort by offset (tiny N, allocation-free - audio-thread safe; equal offsets keep arri...
ChannelSupport
Bus widths a plugin offers to the host. The DSPark contract is channel-agnostic by construction (prep...
@ MonoOnly
Strictly 1->1 (rare; metering/utility plugins).
@ StereoOnly
Strictly 2->2 (inherently stereo DSP).
@ MonoAndStereo
Negotiates 1->1 or 2->2 with the host (default).
constexpr uint32_t kBypassStateId
Reserved entry id persisting the host bypass ('BYPS' - the same value every backend uses as the bypas...
constexpr std::array< First, 1+sizeof...(Rest)> presets(First first, Rest... rest) noexcept
Builds the factory preset table (all presets must cover the same parameter count - enforced here; mat...
bool 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 progra...
One timestamped in-block event, normalised across formats: a parameter point, a bypass point,...
uint32_t paramId
hash32 id (Kind::Param only).
int32_t offset
Frame position within the block.
double value
Normalized value / bypass >= 0.5.
MidiEvent midi
Kind::Midi payload.
Static identity of a plugin. All fields are plain literals so the whole descriptor can live in a stat...
Editor window size in logical pixels (physical = logical x host scale).
One incoming MIDI-ish event, normalised across formats.
@ NoteOn
note = key, value = velocity 0..1.
@ PolyPressure
note = key, value = pressure 0..1.
@ NoteOff
note = key, value = release velocity 0..1.
@ ControlChange
note = controller number, value = 0..1.
@ ChannelPressure
value = pressure 0..1 (note unused).
@ PitchBend
value = bend -1..+1 (note unused).
float value
Velocity / CC / pressure 0..1; bend -1..+1.
int sampleOffset
Frames into the NEXT processBlock call.
uint8_t note
Key 0..127, or controller number for CC.
One automatable parameter. Plain values run [min, max]; hosts see the normalized [0,...
const char * name
Display name.
const char * unit
Display unit ("dB", "Hz", "%", ...).
int steps
0 continuous, 1 toggle, N discrete.
Optional convenience base that makes EVERY contract method visible and overridable from one place - I...
void setOfflineRendering(bool) noexcept
void setTransport(const TransportInfo &) noexcept
static constexpr bool hasEditor
double getTailSeconds() const noexcept
std::vector< uint8_t > getState() const
bool setState(const uint8_t *, size_t)
- restore side. Return false on a foreign blob.
int getLatency() const noexcept
One factory preset: a name plus a PLAIN value for every parameter, in parameter-table order....
std::array< float, NumValues > values
Host transport snapshot, delivered once per audio block.
bool playing
Transport is rolling.
double secondsPerBeat() const noexcept
Seconds per quarter note at the current tempo.
double tempoBpm
Current tempo (when tempoValid).
double loopStartPpq
Cycle start (when loopValid).
double samplesPerBeat(double sampleRate) const noexcept
Samples per quarter note - the basis for tempo-synced delays/LFOs.
bool timeSigValid
Time signature came from the host.
bool tempoValid
tempoBpm came from the host.
bool positionValid
ppqPosition/barStartPpq came from the host.
double ppqPosition
Musical position of frame 0, in quarter notes.
double loopEndPpq
Cycle end (when loopValid).
double barStartPpq
Quarter-note position of the current bar start.
bool loopValid
Loop points came from the host.
int timeSigNumerator
e.g. 3 in 3/4 (when timeSigValid).
bool looping
Cycle/loop mode is engaged.
bool recording
Transport is recording.
int timeSigDenominator
e.g. 4 in 3/4 (when timeSigValid).