DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
DSParkPlugin.h
Go to the documentation of this file.
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
58#include "../Core/AudioSpec.h"
59#include "../Core/AudioBuffer.h"
60#include "../Core/DenormalGuard.h"
61
62#include <array>
63#include <concepts>
64#include <cstdint>
65#include <cstdio>
66#include <cstring>
67#include <type_traits>
68#include <vector>
69
70namespace dspark::plugin {
71
72// -- Descriptor ---------------------------------------------------------------
73
83enum class Category
84{
85 Fx,
87};
88
98{
99 const char* name = "DSPark Plugin";
100 const char* vendor = "DSPark";
101 const char* url = "";
102 const char* email = "";
103 const char* productId = "com.dspark.plugin";
104 const char* version = "1.0.0";
106};
107
108// -- Parameters ---------------------------------------------------------------
109
115struct Param
116{
117 const char* id = "";
118 const char* name = "";
119 float minValue = 0.0f;
120 float maxValue = 1.0f;
121 float defValue = 0.0f;
122 const char* unit = "";
123 int steps = 0;
124};
125
127constexpr Param param(const char* id, const char* name,
128 float minValue, float maxValue, float defValue,
129 const char* unit) noexcept
130{
131 return Param { id, name, minValue, maxValue, defValue, unit, 0 };
132}
133
135constexpr Param toggle(const char* id, const char* name, bool defaultOn) noexcept
136{
137 return Param { id, name, 0.0f, 1.0f, defaultOn ? 1.0f : 0.0f, "", 1 };
138}
139
141template <typename... Ps>
142constexpr std::array<Param, sizeof...(Ps)> params(Ps... ps) noexcept
143{
144 return { ps... };
145}
146
147// -- Normalisation ------------------------------------------------------------
148
150constexpr double toNormalized(const Param& p, double plain) noexcept
151{
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);
156}
157
159constexpr double toPlain(const Param& p, double normalized) noexcept
160{
161 double n = normalized < 0.0 ? 0.0 : (normalized > 1.0 ? 1.0 : normalized);
162 if (p.steps > 0)
163 {
164 // Snap to the nearest of steps+1 positions.
165 const double scaled = n * p.steps + 0.5;
166 const double idx = static_cast<double>(static_cast<long long>(scaled));
167 n = idx / p.steps;
168 n = n < 0.0 ? 0.0 : (n > 1.0 ? 1.0 : n);
169 }
170 return p.minValue + n * (static_cast<double>(p.maxValue) - p.minValue);
171}
172
177inline int parseToggleText(const char* text) noexcept
178{
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;
184 return -1;
185}
186
188inline void formatValue(const Param& p, double plain, char* out, int outSize) noexcept
189{
190 if (p.steps == 1)
191 {
192 std::snprintf(out, static_cast<size_t>(outSize), "%s",
193 plain >= 0.5 * (p.minValue + p.maxValue) ? "On" : "Off");
194 return;
195 }
196 if (p.unit && p.unit[0] != '\0')
197 std::snprintf(out, static_cast<size_t>(outSize), "%.2f %s", plain, p.unit);
198 else
199 std::snprintf(out, static_cast<size_t>(outSize), "%.2f", plain);
200}
201
202// -- Stable hashing (parameter ids, class UIDs) -------------------------------
203
205constexpr uint32_t hash32(const char* s) noexcept
206{
207 uint32_t h = 2166136261u;
208 while (*s != '\0')
209 {
210 h ^= static_cast<uint8_t>(*s++);
211 h *= 16777619u;
212 }
213 return h;
214}
215
217constexpr uint64_t hash64(const char* s, uint64_t salt) noexcept
218{
219 uint64_t h = 14695981039346656037ull ^ salt;
220 while (*s != '\0')
221 {
222 h ^= static_cast<uint8_t>(*s++);
223 h *= 1099511628211ull;
224 }
225 return h;
226}
227
234template <typename P>
235constexpr bool paramIdsUnique() noexcept
236{
237 constexpr size_t n = P::parameters.size();
238 for (size_t i = 0; i < n; ++i)
239 {
240 const uint32_t h = hash32(P::parameters[i].id);
241 if (h == 0x5052474Du || h == 0x42595053u) return false; // 'PRGM'/'BYPS'
242 for (size_t j = i + 1; j < n; ++j)
243 if (hash32(P::parameters[j].id) == h) return false;
244 }
245 return true;
246}
247
252constexpr std::array<uint8_t, 16> makeUid(const char* productId, uint64_t salt) noexcept
253{
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)
258 {
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));
261 }
262 return uid;
263}
264
265// -- Capability detection -----------------------------------------------------
266
267template <typename P>
268concept HasReset = requires(P p) { p.reset(); };
269
270template <typename P>
271concept HasLatency = requires(const P p) { { p.getLatency() } -> std::convertible_to<int>; };
272
273template <typename P>
274concept HasTail = requires(const P p) { { p.getTailSeconds() } -> std::convertible_to<double>; };
275
276template <typename P>
277concept HasGetState = requires(const P p) {
278 { p.getState() } -> std::convertible_to<std::vector<uint8_t>>;
279};
280
281template <typename P>
282concept HasSetState = requires(P p, const uint8_t* d, size_t n) {
283 { p.setState(d, n) } -> std::convertible_to<bool>;
284};
285
290template <typename P>
291concept HasEditor = requires { P::hasEditor; } && P::hasEditor;
292
311template <typename P>
312concept HasSidechain = requires(P p, AudioBufferView<float> io,
313 AudioBufferView<float> sc) {
314 p.processBlock(io, sc);
315};
316
317// -- Transport (host tempo & timeline) ------------------------------------------
318
330{
331 double tempoBpm = 120.0;
332 double ppqPosition = 0.0;
333 double barStartPpq = 0.0;
336 double loopStartPpq = 0.0;
337 double loopEndPpq = 0.0;
338 bool playing = false;
339 bool recording = false;
340 bool looping = false;
341 bool tempoValid = false;
342 bool positionValid = false;
343 bool timeSigValid = false;
344 bool loopValid = false;
345
347 [[nodiscard]] double secondsPerBeat() const noexcept
348 {
349 return 60.0 / (tempoBpm > 1.0 ? tempoBpm : 120.0);
350 }
351
353 [[nodiscard]] double samplesPerBeat(double sampleRate) const noexcept
354 {
355 return secondsPerBeat() * sampleRate;
356 }
357};
358
365template <typename P>
366concept HasTransport = requires(P p, const TransportInfo& t) {
367 p.setTransport(t);
368};
369
370// -- MIDI ------------------------------------------------------------------------
371
387{
388 enum class Type : uint8_t
389 {
390 NoteOn,
391 NoteOff,
392 PitchBend,
396 };
397
401 float value = 0.0f;
402 int sampleOffset = 0;
403};
404
412template <typename P>
413concept HasMidi = requires(P p, const MidiEvent& e) {
414 p.handleMidiEvent(e);
415};
416
417// -- Offline rendering ------------------------------------------------------------
418
427template <typename P>
428concept HasOfflineMode = requires(P p, bool offline) {
429 p.setOfflineRendering(offline);
430};
431
432// -- Channel support ----------------------------------------------------------------
433
444{
446 StereoOnly,
447 MonoOnly
448};
449
450template <typename P>
451concept HasChannelSupport = requires {
452 { P::channels } -> std::convertible_to<ChannelSupport>;
453};
454
456template <typename P>
458{
459 if constexpr (HasChannelSupport<P>)
460 return P::channels;
461 else
463}
464
466template <typename P>
467constexpr bool supportsChannelCount(int numChannels) noexcept
468{
469 constexpr ChannelSupport s = channelSupportOf<P>();
470 if (numChannels == 1) return s != ChannelSupport::StereoOnly;
471 if (numChannels == 2) return s != ChannelSupport::MonoOnly;
472 return false;
473}
474
476template <typename P>
477constexpr int defaultChannelCount() noexcept
478{
479 return channelSupportOf<P>() == ChannelSupport::MonoOnly ? 1 : 2;
480}
481
482// -- Factory presets ----------------------------------------------------------------
483
489template <size_t NumValues>
491{
492 const char* name = "";
493 std::array<float, NumValues> values {};
494};
495
498template <typename... Vs>
499constexpr PresetDef<sizeof...(Vs)> preset(const char* name, Vs... values) noexcept
500{
501 return PresetDef<sizeof...(Vs)> { name, { static_cast<float>(values)... } };
502}
503
507template <typename First, typename... Rest>
508constexpr std::array<First, 1 + sizeof...(Rest)> presets(First first, Rest... rest) noexcept
509{
510 static_assert((std::is_same_v<First, Rest> && ...),
511 "every preset must list one value per parameter");
512 return { first, rest... };
513}
514
522template <typename P>
523concept HasFactoryPresets = requires {
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>;
527};
528
530template <typename P>
531constexpr int factoryPresetCountOf() noexcept
532{
533 if constexpr (HasFactoryPresets<P>)
534 {
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());
539 }
540 else
541 return 0;
542}
543
546template <typename P>
547constexpr double presetNormalized(int presetIndex, size_t paramIndex) noexcept
548{
549 if constexpr (HasFactoryPresets<P>)
550 return toNormalized(P::parameters[paramIndex],
551 P::factoryPresets[static_cast<size_t>(presetIndex)]
552 .values[paramIndex]);
553 else
554 {
555 (void) presetIndex;
556 (void) paramIndex;
557 return 0.0;
558 }
559}
560
561// -- Sample-accurate automation -------------------------------------------------------
562
571template <typename P>
572concept HasSampleAccurateOptOut = requires {
573 { P::sampleAccurateAutomation } -> std::convertible_to<bool>;
574};
575
576template <typename P>
577constexpr bool sampleAccurateOf() noexcept
578{
579 if constexpr (HasSampleAccurateOptOut<P>)
580 return P::sampleAccurateAutomation;
581 else
582 return true;
583}
584
587inline constexpr int kAutomationQuantum = 32;
588
591inline constexpr int kMaxBlockEvents = 256;
592
600{
601 enum class Kind : uint8_t { Param, Bypass, Midi };
602 int32_t offset = 0;
604 uint32_t paramId = 0;
605 double value = 0.0;
607};
608
611inline void sortBlockEvents(BlockEvent* events, int count) noexcept
612{
613 for (int i = 1; i < count; ++i)
614 {
615 BlockEvent key = events[i];
616 int j = i - 1;
617 while (j >= 0 && events[j].offset > key.offset)
618 {
619 events[j + 1] = events[j];
620 --j;
621 }
622 events[j + 1] = key;
623 }
624}
625
626// -- Editor contract (used by plugin/webview/DSParkWebViewEditor.h) -------------
627
630{
631 int width = 480;
632 int height = 320;
633};
634
641enum class EditorResize
642{
643 Fixed,
644 Free,
646};
647
650template <typename P>
651concept HasEditorHtml = requires {
652 { P::editorHtml() } -> std::convertible_to<const char*>;
653};
654
656template <typename P>
657concept HasEditorSize = requires {
658 { P::editorSize.width } -> std::convertible_to<int>;
659 { P::editorSize.height } -> std::convertible_to<int>;
660};
661
663template <typename P>
664constexpr EditorSize editorSizeOf() noexcept
665{
666 if constexpr (HasEditorSize<P>)
667 return EditorSize { static_cast<int>(P::editorSize.width),
668 static_cast<int>(P::editorSize.height) };
669 else
670 return EditorSize {};
671}
672
675template <typename P>
676concept HasEditorResizable = requires { P::editorResizable; } && P::editorResizable;
677
680template <typename P>
681concept HasEditorResize = requires {
682 { P::editorResize } -> std::convertible_to<EditorResize>;
683};
684
686template <typename P>
687constexpr EditorResize editorResizeOf() noexcept
688{
689 if constexpr (HasEditorResize<P>)
690 return P::editorResize;
691 else if constexpr (HasEditorResizable<P>)
692 return EditorResize::Free;
693 else
694 return EditorResize::Fixed;
695}
696
698inline constexpr double kEditorMinSizeFactor = 0.5;
699inline constexpr double kEditorMaxSizeFactor = 3.0;
700
709template <typename P>
710constexpr void constrainEditorSize(double& width, double& height, double scale) noexcept
711{
712 const EditorSize logical = editorSizeOf<P>();
713 constexpr EditorResize mode = editorResizeOf<P>();
714 if constexpr (mode == EditorResize::Fixed)
715 {
716 width = logical.width * scale;
717 height = logical.height * scale;
718 }
719 else
720 {
721 const double minW = logical.width * scale * kEditorMinSizeFactor;
722 const double maxW = logical.width * scale * kEditorMaxSizeFactor;
723 const double minH = logical.height * scale * kEditorMinSizeFactor;
724 const double maxH = logical.height * scale * kEditorMaxSizeFactor;
725 width = width < minW ? minW : (width > maxW ? maxW : width);
726 height = height < minH ? minH : (height > maxH ? maxH : height);
727 if constexpr (mode == EditorResize::KeepAspect)
728 {
729 const double ratio = static_cast<double>(logical.width) / logical.height;
730 double fitW = width;
731 if (height * ratio < fitW) fitW = height * ratio; // inside-fit
732 fitW = fitW < minW ? minW : (fitW > maxW ? maxW : fitW);
733 width = fitW;
734 height = fitW / ratio;
735 }
736 }
737}
738
741template <typename P>
742concept HasEditorDebug = requires { P::editorDebug; } && P::editorDebug;
743
748template <typename P>
749concept HasEditorDevFile = requires {
750 { P::editorDevFile() } -> std::convertible_to<const char*>;
751};
752
753// -- PluginBase: the whole contract, visible in one place ----------------------
754
788template <typename Derived>
790{
797 void reset() noexcept {}
798
807 [[nodiscard]] int getLatency() const noexcept { return 0; }
808
815 [[nodiscard]] double getTailSeconds() const noexcept { return 0.0; }
816
824 [[nodiscard]] std::vector<uint8_t> getState() const { return {}; }
825
827 bool setState(const uint8_t*, size_t) { return false; }
828
837 void setTransport(const TransportInfo&) noexcept {}
838
847 void setOfflineRendering(bool) noexcept {}
848
855 static constexpr bool hasEditor = false;
856};
857
858// -- Wrapper-side state serialisation ------------------------------------------
859//
860// Every backend stores plugin state in ONE container format so presets stay
861// portable across formats and tolerant across versions (same philosophy as
862// Core/StateBlob): a magic/version header, the parameter table keyed by
863// hash32(id) - unknown ids are skipped, missing ids keep defaults - and an
864// optional opaque user-state section appended verbatim.
865
866inline constexpr uint32_t kStateMagic = 0x4453504Bu; // "DSPK"
867inline constexpr uint32_t kStateVersion = 1u;
868
871inline constexpr uint32_t kProgramStateId = 0x5052474Du;
872
877inline constexpr uint32_t kBypassStateId = 0x42595053u;
878
879template <typename P>
880inline std::vector<uint8_t> buildState(const P& user, const double* normalized,
881 size_t numParams, int programIndex = -1,
882 int bypassState = -1)
883{
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));
889 };
890 auto pushValue = [&push32](std::vector<uint8_t>& v, double value) {
891 uint64_t bits = 0;
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));
896 };
897 std::vector<uint8_t> blob;
898 push32(blob, kStateMagic);
899 push32(blob, kStateVersion);
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)
903 {
904 push32(blob, hash32(P::parameters[i].id));
905 pushValue(blob, normalized[i]);
906 }
907 if (programIndex >= 0)
908 {
909 // The active factory preset rides along as one more entry so hosts
910 // restore the program selection with the session.
911 push32(blob, kProgramStateId);
912 pushValue(blob, static_cast<double>(programIndex));
913 }
914 if (bypassState >= 0)
915 {
916 push32(blob, kBypassStateId);
917 pushValue(blob, bypassState > 0 ? 1.0 : 0.0);
918 }
919 if constexpr (HasGetState<P>)
920 {
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());
924 }
925 else
926 push32(blob, 0u);
927 return blob;
928}
929
934template <typename P>
935inline bool applyState(P& user, const uint8_t* data, size_t size, double* normalized,
936 int* programIndex = nullptr, int* bypassState = nullptr)
937{
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;
944 pos += 4;
945 return true;
946 };
947 size_t pos = 0;
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;
952
953 constexpr size_t numParams = P::parameters.size();
954 for (uint32_t i = 0; i < count; ++i)
955 {
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);
959 double v = 0.0;
960 std::memcpy(&v, &bits, sizeof(v));
961 // A corrupt blob can carry any bit pattern: a NaN would survive both
962 // range checks below (every comparison is false) and poison the
963 // parameter shadow, so non-finite entries keep the default instead.
964 if (v != v) continue;
965 if (id == kProgramStateId)
966 {
967 if (programIndex != nullptr && v >= 0.0)
968 *programIndex = static_cast<int>(v + 0.5);
969 continue;
970 }
971 if (id == kBypassStateId)
972 {
973 if (bypassState != nullptr)
974 *bypassState = v >= 0.5 ? 1 : 0;
975 continue;
976 }
977 for (size_t k = 0; k < numParams; ++k)
978 if (hash32(P::parameters[k].id) == id)
979 {
980 normalized[k] = v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v);
981 break;
982 }
983 // Unknown ids are skipped: forward-compatible by construction.
984 }
985
986 uint32_t userSize = 0;
987 // Subtraction form: pos <= size is guaranteed after read32, and on
988 // 32-bit size_t targets (WASM) the addition form `pos + userSize` could
989 // wrap around and admit an out-of-bounds user section.
990 if (read32(pos, userSize) && userSize > 0 && userSize <= size - pos)
991 {
992 if constexpr (HasSetState<P>)
993 user.setState(data + pos, userSize);
994 }
995 return true;
996}
997
998} // namespace dspark::plugin
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).