DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
DSParkClap.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
53#define DSPARK_PLUGIN_CLAP_INCLUDED 1
54
55#include "../DSParkPlugin.h"
56
57#include "clap/clap.h"
58
59#include <algorithm>
60#include <atomic>
61#include <cstdint>
62#include <cstdlib>
63#include <cstring>
64#include <new>
65#include <vector>
66
68
69inline constexpr uint32_t kBypassParamId = 0x42595053u; // matches the VST3 backend
70inline constexpr int kBypassRampSamples = 256;
71
72template <typename P>
73struct Plugin
74{
75 static constexpr size_t kNumParams = P::parameters.size();
76 static constexpr bool kIsInstrument =
77 P::descriptor.category == Category::Instrument;
78 static constexpr int kNumPresets = factoryPresetCountOf<P>();
79 static_assert(!(kIsInstrument && HasSidechain<P>),
80 "an Instrument has no audio inputs; remove the sidechain "
81 "processBlock or use Category::Fx");
82 static_assert(!kIsInstrument || HasMidi<P>,
83 "an Instrument needs handleMidiEvent (see HasMidi): it has "
84 "no audio input to process");
85 static_assert(paramIdsUnique<P>(),
86 "two parameter ids share a hash32 (or collide with the "
87 "reserved PRGM/BYPS state ids): automation and state would "
88 "cross-wire. Rename one id.");
89
90 clap_plugin_t plugin {}; // the C-facing object (plugin_data = this)
91 const clap_host_t* host = nullptr;
92 const clap_host_latency_t* hostLatency = nullptr;
93 const clap_host_params_t* hostParams = nullptr;
94 const clap_host_preset_load_t* hostPresetLoad = nullptr;
95
96 P user {};
97 double sampleRate = 48000.0;
98 uint32_t maxFrames = 0;
99 bool prepared = false;
106 std::atomic<int> cachedLatency { 0 };
107 int currentChannels = defaultChannelCount<P>(); // selected ports config
108 bool offlineRender = false; // last render-extension setting
109 std::atomic<bool> latencyDirty { false }; // audio -> main thread notify
110
111 std::atomic<double> shadow[kNumParams == 0 ? 1 : kNumParams] {};
112 std::atomic<bool> bypass { false };
113 std::atomic<int> currentProgram { 0 }; // last factory preset loaded
114 float bypassMix = 0.0f;
115 std::vector<float> dryL, dryR;
116 std::vector<float> silence; // stand-in sidechain when not connected
117 DryDelay dryDelay; // latency-aligned dry path of the bypass
118
119#if defined(DSPARK_PLUGIN_WEBVIEW)
120 // --- editor state + UI -> host event queue (single producer: main thread;
121 // single consumer: process() on audio or flush() on main - never both).
122 webview_ui::Editor<P> guiEditor;
123 bool guiActive = false;
124 double guiScale = 1.0;
125 int guiWidth = 0, guiHeight = 0;
126 bool guiEditActive[kNumParams == 0 ? 1 : kNumParams] {};
127#if defined(__linux__)
128 // GTK is driven from the host's run loop: a timer-support tick pumps it.
129 const clap_host_timer_support_t* hostTimer = nullptr;
130 clap_id guiTimerId = CLAP_INVALID_ID;
131#endif
132
133 enum : uint8_t { kUiGestureBegin = 0, kUiValue = 1, kUiGestureEnd = 2 };
134 struct UiEvent
135 {
136 uint32_t paramId;
137 uint8_t kind;
138 double value;
139 };
140 static constexpr uint32_t kUiQueueSize = 256; // power of two
141 UiEvent uiQueue[kUiQueueSize] {};
142 std::atomic<uint32_t> uiHead { 0 }, uiTail { 0 };
143#endif
144
145 explicit Plugin(const clap_host_t* h) noexcept : host(h)
146 {
147 for (size_t i = 0; i < kNumParams; ++i)
148 shadow[i].store(toNormalized(P::parameters[i], P::parameters[i].defValue),
149 std::memory_order_relaxed);
150 }
151
152 static Plugin* self(const clap_plugin_t* p) noexcept
153 {
154 return static_cast<Plugin*>(p->plugin_data);
155 }
156
157 void applyNormalized(int index, double normalized) noexcept
158 {
159 // Host values are untrusted doubles: a NaN would pass both range
160 // clamps, poison the shadow and reach the user's setter. Ignore it.
161 if (normalized != normalized) return;
162 const auto& spec = P::parameters[static_cast<size_t>(index)];
163 shadow[static_cast<size_t>(index)].store(normalized, std::memory_order_relaxed);
164 user.setParameter(index, static_cast<float>(toPlain(spec, normalized)));
165 }
166
167 static int indexOfParamId(uint32_t id) noexcept
168 {
169 for (size_t i = 0; i < kNumParams; ++i)
170 if (hash32(P::parameters[i].id) == id) return static_cast<int>(i);
171 return -1;
172 }
173
175 void applyFactoryPresetIdx(int idx) noexcept
176 {
177 if constexpr (kNumPresets > 0)
178 {
179 if (idx < 0 || idx >= kNumPresets) return;
180 for (size_t i = 0; i < kNumParams; ++i)
181 applyNormalized(static_cast<int>(i), presetNormalized<P>(idx, i));
182 currentProgram.store(idx, std::memory_order_relaxed);
183 }
184 else
185 (void) idx;
186 }
187
191 void refreshLatency() noexcept
192 {
193 if constexpr (HasLatency<P>)
194 {
195 const int now = user.getLatency();
196 if (prepared && now != cachedLatency.load(std::memory_order_relaxed))
197 {
198 cachedLatency.store(now, std::memory_order_relaxed);
199 latencyDirty.store(true, std::memory_order_release);
200 if (host != nullptr && host->request_callback != nullptr)
201 host->request_callback(host);
202 }
203 }
204 }
205
209 void collectEvent(const clap_event_header_t* ev, uint32_t numSamples,
210 BlockEvent* events, int& count) noexcept
211 {
212 if (ev == nullptr || ev->space_id != CLAP_CORE_EVENT_SPACE_ID) return;
213 BlockEvent out {};
214 out.offset = static_cast<int32_t>(ev->time);
215 if (numSamples > 0 && out.offset >= static_cast<int32_t>(numSamples))
216 out.offset = static_cast<int32_t>(numSamples) - 1;
217 if (out.offset < 0) out.offset = 0;
218
219 if (ev->type == CLAP_EVENT_PARAM_VALUE)
220 {
221 const auto* pv = reinterpret_cast<const clap_event_param_value_t*>(ev);
222 if (pv->param_id == kBypassParamId)
223 {
224 out.kind = BlockEvent::Kind::Bypass;
225 out.value = pv->value;
226 }
227 else
228 {
229 const int idx = indexOfParamId(pv->param_id);
230 if (idx < 0) return;
231 out.kind = BlockEvent::Kind::Param;
232 out.paramId = pv->param_id;
233 out.value = toNormalized(P::parameters[static_cast<size_t>(idx)],
234 pv->value);
235 }
236 }
237 else if constexpr (HasMidi<P>)
238 {
239 if (ev->type == CLAP_EVENT_NOTE_ON || ev->type == CLAP_EVENT_NOTE_OFF)
240 {
241 const auto* note = reinterpret_cast<const clap_event_note_t*>(ev);
242 out.kind = BlockEvent::Kind::Midi;
243 out.midi.type = ev->type == CLAP_EVENT_NOTE_ON
245 out.midi.channel = static_cast<uint8_t>(
246 note->channel >= 0 ? note->channel & 0x0F : 0);
247 out.midi.note = static_cast<uint8_t>(
248 note->key >= 0 ? note->key & 0x7F : 0);
249 out.midi.value = static_cast<float>(note->velocity);
250 }
251 else if (ev->type == CLAP_EVENT_MIDI)
252 {
253 const auto* midi = reinterpret_cast<const clap_event_midi_t*>(ev);
254 const uint8_t status = midi->data[0] & 0xF0u;
255 const uint8_t channel = midi->data[0] & 0x0Fu;
256 const uint8_t d1 = midi->data[1] & 0x7Fu;
257 const uint8_t d2 = midi->data[2] & 0x7Fu;
258 out.kind = BlockEvent::Kind::Midi;
259 out.midi.channel = channel;
260 switch (status)
261 {
262 case 0x90: // wire convention: velocity 0 means note off
263 out.midi.type = d2 > 0 ? MidiEvent::Type::NoteOn
265 out.midi.note = d1;
266 out.midi.value = static_cast<float>(d2) / 127.0f;
267 break;
268 case 0x80:
269 out.midi.type = MidiEvent::Type::NoteOff;
270 out.midi.note = d1;
271 out.midi.value = static_cast<float>(d2) / 127.0f;
272 break;
273 case 0xA0:
274 out.midi.type = MidiEvent::Type::PolyPressure;
275 out.midi.note = d1;
276 out.midi.value = static_cast<float>(d2) / 127.0f;
277 break;
278 case 0xB0:
279 out.midi.type = MidiEvent::Type::ControlChange;
280 out.midi.note = d1;
281 out.midi.value = static_cast<float>(d2) / 127.0f;
282 break;
283 case 0xD0:
284 out.midi.type = MidiEvent::Type::ChannelPressure;
285 out.midi.value = static_cast<float>(d1) / 127.0f;
286 break;
287 case 0xE0:
288 out.midi.type = MidiEvent::Type::PitchBend;
289 out.midi.value = (static_cast<float>((d2 << 7) | d1) - 8192.0f)
290 / 8192.0f;
291 break;
292 default:
293 return;
294 }
295 }
296 else
297 return;
298 }
299 else
300 return;
301
302 if (count < kMaxBlockEvents) events[count++] = out;
303 else events[kMaxBlockEvents - 1] = out;
304 }
305
306 void collectEvents(const clap_input_events_t* in, uint32_t numSamples,
307 BlockEvent* events, int& count) noexcept
308 {
309 if (in == nullptr) return;
310 const uint32_t n = in->size(in);
311 for (uint32_t i = 0; i < n; ++i)
312 collectEvent(in->get(in, i), numSamples, events, count);
313 }
314
317 bool applyBlockEvent(const BlockEvent& ev, int blockStart) noexcept
318 {
319 switch (ev.kind)
320 {
322 bypass.store(ev.value >= 0.5, std::memory_order_relaxed);
323 return false;
325 if constexpr (HasMidi<P>)
326 {
327 MidiEvent midi = ev.midi;
328 midi.sampleOffset = ev.offset - blockStart;
329 if (midi.sampleOffset < 0) midi.sampleOffset = 0;
330 user.handleMidiEvent(midi);
331 }
332 return false;
334 default:
335 if (const int idx = indexOfParamId(ev.paramId); idx >= 0)
336 {
337 applyNormalized(idx, ev.value);
338 return true;
339 }
340 return false;
341 }
342 }
343
345 void forwardTransport(const clap_event_transport_t* t) noexcept
346 {
347 if constexpr (HasTransport<P>)
348 {
349 if (t == nullptr) return;
350 constexpr double kBeatFactor = static_cast<double>(CLAP_BEATTIME_FACTOR);
351 TransportInfo info {};
352 info.playing = (t->flags & CLAP_TRANSPORT_IS_PLAYING) != 0;
353 info.recording = (t->flags & CLAP_TRANSPORT_IS_RECORDING) != 0;
354 info.looping = (t->flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE) != 0;
355 if ((t->flags & CLAP_TRANSPORT_HAS_TEMPO) != 0)
356 {
357 info.tempoBpm = t->tempo;
358 info.tempoValid = true;
359 }
360 if ((t->flags & CLAP_TRANSPORT_HAS_BEATS_TIMELINE) != 0)
361 {
362 info.ppqPosition = static_cast<double>(t->song_pos_beats) / kBeatFactor;
363 info.barStartPpq = static_cast<double>(t->bar_start) / kBeatFactor;
364 info.positionValid = true;
365 info.loopStartPpq =
366 static_cast<double>(t->loop_start_beats) / kBeatFactor;
367 info.loopEndPpq = static_cast<double>(t->loop_end_beats) / kBeatFactor;
368 info.loopValid = true;
369 }
370 if ((t->flags & CLAP_TRANSPORT_HAS_TIME_SIGNATURE) != 0)
371 {
372 info.timeSigNumerator = t->tsig_num;
373 info.timeSigDenominator = t->tsig_denom;
374 info.timeSigValid = true;
375 }
376 user.setTransport(info);
377 }
378 else
379 (void) t;
380 }
381
382#if defined(DSPARK_PLUGIN_WEBVIEW)
383 // --- UI -> host parameter events ---------------------------------------------
384
385 void pushUiEvent(uint32_t paramId, uint8_t kind, double value) noexcept
386 {
387 const uint32_t tail = uiTail.load(std::memory_order_relaxed);
388 const uint32_t next = (tail + 1) & (kUiQueueSize - 1);
389 if (next == uiHead.load(std::memory_order_acquire)) return; // full: drop
390 uiQueue[tail] = UiEvent { paramId, kind, value };
391 uiTail.store(next, std::memory_order_release);
392 }
393
394 void requestUiFlush() noexcept
395 {
396 // Host schedules process()/flush(), which drains the queue below.
397 if (hostParams != nullptr && host != nullptr)
398 hostParams->request_flush(host);
399 }
400
401 void drainUiEvents(const clap_output_events_t* out) noexcept
402 {
403 if (out == nullptr) return;
404 uint32_t head = uiHead.load(std::memory_order_relaxed);
405 const uint32_t tail = uiTail.load(std::memory_order_acquire);
406 while (head != tail)
407 {
408 const UiEvent& e = uiQueue[head];
409 if (e.kind == kUiValue)
410 {
411 clap_event_param_value_t ev {};
412 ev.header.size = sizeof(ev);
413 ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
414 ev.header.type = CLAP_EVENT_PARAM_VALUE;
415 ev.param_id = e.paramId;
416 ev.note_id = -1;
417 ev.port_index = -1;
418 ev.channel = -1;
419 ev.key = -1;
420 ev.value = e.value;
421 out->try_push(out, &ev.header);
422 }
423 else
424 {
425 clap_event_param_gesture_t ev {};
426 ev.header.size = sizeof(ev);
427 ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
428 ev.header.type = static_cast<uint16_t>(
429 e.kind == kUiGestureBegin ? CLAP_EVENT_PARAM_GESTURE_BEGIN
430 : CLAP_EVENT_PARAM_GESTURE_END);
431 ev.param_id = e.paramId;
432 out->try_push(out, &ev.header);
433 }
434 head = (head + 1) & (kUiQueueSize - 1);
435 }
436 uiHead.store(head, std::memory_order_release);
437 }
438#endif // DSPARK_PLUGIN_WEBVIEW
439
440 // --- clap_plugin_t callbacks ------------------------------------------------
441
442 static bool sInit(const clap_plugin_t* p) noexcept
443 {
444 auto* s = self(p);
445 if (s->host != nullptr)
446 {
447 s->hostParams = static_cast<const clap_host_params_t*>(
448 s->host->get_extension(s->host, CLAP_EXT_PARAMS));
449 s->hostLatency = static_cast<const clap_host_latency_t*>(
450 s->host->get_extension(s->host, CLAP_EXT_LATENCY));
451 if constexpr (kNumPresets > 0)
452 s->hostPresetLoad = static_cast<const clap_host_preset_load_t*>(
453 s->host->get_extension(s->host, CLAP_EXT_PRESET_LOAD));
454 }
455 return true;
456 }
457
458 static void sDestroy(const clap_plugin_t* p) noexcept { delete self(p); }
459
460 static bool sActivate(const clap_plugin_t* p, double sr,
461 uint32_t, uint32_t maxFrames) noexcept
462 {
463 auto* s = self(p);
464 s->sampleRate = sr;
465 s->maxFrames = maxFrames;
466 dspark::AudioSpec spec { sr, static_cast<int>(maxFrames), s->currentChannels };
467 s->user.prepare(spec);
468 if constexpr (HasOfflineMode<P>)
469 s->user.setOfflineRendering(s->offlineRender);
470 for (size_t i = 0; i < kNumParams; ++i)
471 s->user.setParameter(static_cast<int>(i),
472 static_cast<float>(toPlain(P::parameters[i],
473 s->shadow[i].load(std::memory_order_relaxed))));
474 s->dryL.assign(maxFrames, 0.0f);
475 s->dryR.assign(maxFrames, 0.0f);
476 if constexpr (HasSidechain<P>)
477 s->silence.assign(maxFrames, 0.0f);
478 s->bypassMix = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
479 if constexpr (HasLatency<P>)
480 {
481 const int latency = s->user.getLatency();
482 s->cachedLatency.store(latency, std::memory_order_relaxed);
483 s->dryDelay.prepare(std::max(2 * latency, kDryDelayMinCapacity),
484 static_cast<int>(maxFrames));
485 }
486 s->prepared = true;
487 return true;
488 }
489
490 static void sDeactivate(const clap_plugin_t*) noexcept {}
491 static bool sStartProcessing(const clap_plugin_t*) noexcept { return true; }
492 static void sStopProcessing(const clap_plugin_t*) noexcept {}
493
494 static void sReset(const clap_plugin_t* p) noexcept
495 {
496 auto* s = self(p);
497 if constexpr (HasReset<P>)
498 s->user.reset();
499 s->dryDelay.reset();
500 s->bypassMix = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
501 }
502
503 static clap_process_status sProcess(const clap_plugin_t* p,
504 const clap_process_t* process) noexcept
505 {
506 auto* s = self(p);
507
508 // FTZ/DAZ for the whole callback: DSPark processors guard their own
509 // hot loops, this covers user DSP in hosts that do not set it.
510 dspark::DenormalGuard denormalGuard;
511
512#if defined(DSPARK_PLUGIN_WEBVIEW)
513 s->drainUiEvents(process->out_events);
514#endif
515
516 const uint32_t n = process->frames_count;
517
519 int eventCount = 0;
520 s->collectEvents(process->in_events, n, events, eventCount);
521 sortBlockEvents(events, eventCount);
522
523 bool paramsChanged = false;
524
525 const bool canRender = n > 0 && s->prepared
526 && process->audio_outputs_count >= 1
527 && process->audio_outputs[0].data32 != nullptr
528 && process->audio_outputs[0].channel_count >= 1;
529 if (!canRender)
530 {
531 for (int i = 0; i < eventCount; ++i)
532 paramsChanged |= s->applyBlockEvent(events[i], events[i].offset);
533 if (paramsChanged) s->refreshLatency();
534 return CLAP_PROCESS_CONTINUE;
535 }
536
537 s->forwardTransport(process->transport);
538
539 auto& outPort = process->audio_outputs[0];
540 float** out = outPort.data32;
541 const uint32_t width = static_cast<uint32_t>(s->currentChannels);
542 uint32_t nCh = outPort.channel_count < width
543 ? outPort.channel_count : width;
544 while (nCh > 0 && out[nCh - 1] == nullptr) --nCh; // degenerate hosts
545 if (nCh < 1) return CLAP_PROCESS_CONTINUE;
546
547 const bool haveIn = !kIsInstrument && process->audio_inputs_count >= 1
548 && process->audio_inputs[0].data32 != nullptr
549 && process->audio_inputs[0].channel_count >= 1;
550 float** in = haveIn ? process->audio_inputs[0].data32 : nullptr;
551 const uint32_t inCh = haveIn ? process->audio_inputs[0].channel_count : 0;
552
553 // Sidechain: input port 1, pre-allocated silence when not routed.
554 float* scHost[2] = { nullptr, nullptr };
555 if constexpr (HasSidechain<P>)
556 {
557 if (process->audio_inputs_count >= 2
558 && process->audio_inputs[1].data32 != nullptr)
559 {
560 const auto& scPort = process->audio_inputs[1];
561 const uint32_t scCh = scPort.channel_count < 2
562 ? scPort.channel_count : 2;
563 for (uint32_t ch = 0; ch < scCh; ++ch)
564 scHost[ch] = scPort.data32[ch];
565 if (scCh == 1)
566 scHost[1] = scPort.data32[0]; // mono key feeds both ears
567 }
568 }
569
570 // A block longer than the activated maximum runs as consecutive
571 // chunks of at most that size: the user DSP never sees more frames
572 // than it prepared for.
573 const int total = static_cast<int>(n);
574 const int chunkMax = static_cast<int>(s->dryL.size());
575 if (chunkMax < 1) return CLAP_PROCESS_CONTINUE;
576 float* dry[2] = { s->dryL.data(), s->dryR.data() };
577 int evIdx = 0;
578
579 for (int c0 = 0; c0 < total; c0 += chunkMax)
580 {
581 const int len = total - c0 < chunkMax ? total - c0 : chunkMax;
582 const int cEnd = c0 + len;
583
584 // Dry copy for the bypass blend; instruments start cleared (voices
585 // ADD) and bypass toward silence (their dry vectors stay zero).
586 // Output channels past a narrower input port repeat its last one.
587 const size_t bytes = sizeof(float) * static_cast<size_t>(len);
588 for (uint32_t ch = 0; ch < nCh; ++ch)
589 {
590 if (kIsInstrument)
591 {
592 std::memset(out[ch] + c0, 0, bytes);
593 continue;
594 }
595 const float* src = out[ch];
596 if (haveIn)
597 {
598 const float* x = in[ch < inCh ? ch : inCh - 1];
599 if (x != nullptr) src = x;
600 }
601 std::memcpy(dry[ch], src + c0, bytes);
602 if (out[ch] != src)
603 std::memcpy(out[ch] + c0, src + c0, bytes);
604 }
605 if constexpr (HasLatency<P> && !kIsInstrument)
606 s->dryDelay.process(dry, static_cast<int>(nCh), len,
607 s->cachedLatency.load(std::memory_order_relaxed));
608
609 float* scChunk[2] = { nullptr, nullptr };
610 if constexpr (HasSidechain<P>)
611 for (int ch = 0; ch < 2; ++ch)
612 scChunk[ch] = scHost[ch] != nullptr ? scHost[ch] + c0
613 : s->silence.data();
614
615 // Sub-block processing at quantum-aligned event positions (the
616 // sample-accurate default); opted out, the chunk's events apply
617 // up front.
618 auto processSegment = [&](int start, int length) noexcept {
619 float* sub[2] = { out[0] + start,
620 nCh > 1 ? out[1] + start : out[0] + start };
621 dspark::AudioBufferView<float> view(sub, static_cast<int>(nCh), length);
622 if constexpr (HasSidechain<P>)
623 {
624 // The key view mirrors the main width (mono main, mono key).
625 float* scSub[2] = { scChunk[0] + (start - c0),
626 scChunk[1] + (start - c0) };
628 static_cast<int>(nCh), length);
629 s->user.processBlock(view, scView);
630 }
631 else
632 s->user.processBlock(view);
633 };
634
635 if (!sampleAccurateOf<P>())
636 {
637 for (; evIdx < eventCount && events[evIdx].offset < cEnd; ++evIdx)
638 paramsChanged |= s->applyBlockEvent(events[evIdx], c0);
639 processSegment(c0, len);
640 }
641 else
642 {
643 int pos = c0;
644 while (pos < cEnd)
645 {
646 while (evIdx < eventCount
647 && (events[evIdx].offset / kAutomationQuantum)
648 * kAutomationQuantum <= pos)
649 paramsChanged |= s->applyBlockEvent(events[evIdx++], pos);
650 int next = cEnd;
651 if (evIdx < eventCount)
652 {
653 const int snapped = (events[evIdx].offset / kAutomationQuantum)
655 if (snapped < next) next = snapped;
656 }
657 if (next <= pos) next = pos + kAutomationQuantum < cEnd
658 ? pos + kAutomationQuantum : cEnd;
659 processSegment(pos, next - pos);
660 pos = next;
661 }
662 }
663
664 // Soft bypass toward the (latency-aligned) dry signal.
665 const float target = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
666 if (s->bypassMix != target || target > 0.0f)
667 {
668 const float step = 1.0f / static_cast<float>(kBypassRampSamples);
669 float mix = s->bypassMix;
670 for (int i = 0; i < len; ++i)
671 {
672 mix += (target > mix) ? step : ((target < mix) ? -step : 0.0f);
673 mix = mix < 0.0f ? 0.0f : (mix > 1.0f ? 1.0f : mix);
674 for (uint32_t ch = 0; ch < nCh; ++ch)
675 out[ch][c0 + i] += (dry[ch][i] - out[ch][c0 + i]) * mix;
676 }
677 s->bypassMix = mix;
678 }
679 }
680 for (; evIdx < eventCount; ++evIdx) // safety: events at block end
681 paramsChanged |= s->applyBlockEvent(events[evIdx], total);
682
683 if (paramsChanged) s->refreshLatency();
684 return CLAP_PROCESS_CONTINUE;
685 }
686
687 static const void* sGetExtension(const clap_plugin_t*, const char* id) noexcept
688 {
689 if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0) return &kAudioPorts;
690 if (std::strcmp(id, CLAP_EXT_PARAMS) == 0) return &kParams;
691 if (std::strcmp(id, CLAP_EXT_STATE) == 0) return &kState;
692 if (std::strcmp(id, CLAP_EXT_LATENCY) == 0) return &kLatency;
693 if (std::strcmp(id, CLAP_EXT_TAIL) == 0) return &kTail;
694 if (channelSupportOf<P>() == ChannelSupport::MonoAndStereo)
695 {
696 if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS_CONFIG) == 0)
697 return &kPortsConfig;
698 if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO) == 0
699 || std::strcmp(id, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO_COMPAT) == 0)
700 return &kPortsConfigInfo;
701 }
702 if constexpr (HasMidi<P>)
703 if (std::strcmp(id, CLAP_EXT_NOTE_PORTS) == 0) return &kNotePorts;
704 if constexpr (HasOfflineMode<P>)
705 if (std::strcmp(id, CLAP_EXT_RENDER) == 0) return &kRender;
706 if constexpr (kNumPresets > 0)
707 if (std::strcmp(id, CLAP_EXT_PRESET_LOAD) == 0
708 || std::strcmp(id, CLAP_EXT_PRESET_LOAD_COMPAT) == 0)
709 return &kPresetLoad;
710#if defined(DSPARK_PLUGIN_WEBVIEW)
711 if constexpr (HasEditor<P>)
712 {
713 if (webview_ui::Editor<P>::available() && std::strcmp(id, CLAP_EXT_GUI) == 0)
714 return &kGui;
715#if defined(__linux__)
717 && std::strcmp(id, CLAP_EXT_TIMER_SUPPORT) == 0)
718 return &kTimer;
719#endif
720 }
721#endif
722 return nullptr;
723 }
724
725 static void sOnMainThread(const clap_plugin_t* p) noexcept
726 {
727 // Deferred latency notification (the audio thread only flags it).
728 auto* s = self(p);
729 if (s->latencyDirty.exchange(false, std::memory_order_acq_rel)
730 && s->hostLatency != nullptr && s->hostLatency->changed != nullptr)
731 s->hostLatency->changed(s->host);
732 }
733
734 // --- ext: audio ports ---------------------------------------------------------
735
736 static uint32_t sPortCount(const clap_plugin_t*, bool isInput) noexcept
737 {
738 if (isInput)
739 {
740 if (kIsInstrument) return 0;
741 return HasSidechain<P> ? 2 : 1;
742 }
743 return 1;
744 }
745
746 static bool sPortGet(const clap_plugin_t* p, uint32_t index, bool isInput,
747 clap_audio_port_info_t* info) noexcept
748 {
749 if (info == nullptr || index >= sPortCount(p, isInput)) return false;
750 const int width = self(p)->currentChannels;
751 std::memset(info, 0, sizeof(*info));
752 if (isInput && index == 1) // the sidechain: routable, never the main pair
753 {
754 info->id = 2;
755 std::snprintf(info->name, sizeof(info->name), "Sidechain");
756 info->flags = 0;
757 info->channel_count = static_cast<uint32_t>(width);
758 info->port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
759 info->in_place_pair = CLAP_INVALID_ID;
760 return true;
761 }
762 info->id = isInput ? 0 : 1;
763 std::snprintf(info->name, sizeof(info->name), "%s",
764 isInput ? "Input" : "Output");
765 info->flags = CLAP_AUDIO_PORT_IS_MAIN;
766 info->channel_count = static_cast<uint32_t>(width);
767 info->port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
768 info->in_place_pair = kIsInstrument ? CLAP_INVALID_ID
769 : (isInput ? 1u : 0u);
770 return true;
771 }
772
773 inline static const clap_plugin_audio_ports_t kAudioPorts = { &sPortCount, &sPortGet };
774
775 // --- ext: audio ports config (mono / stereo selection) --------------------------
776
777 static uint32_t sConfigCount(const clap_plugin_t*) noexcept { return 2; }
778
779 static void fillConfig(clap_audio_ports_config_t* config, int width) noexcept
780 {
781 std::memset(config, 0, sizeof(*config));
782 config->id = static_cast<clap_id>(width);
783 std::snprintf(config->name, sizeof(config->name), "%s",
784 width == 1 ? "Mono" : "Stereo");
785 config->input_port_count = sPortCount(nullptr, true);
786 config->output_port_count = 1;
787 config->has_main_input = !kIsInstrument;
788 config->main_input_channel_count = static_cast<uint32_t>(width);
789 config->main_input_port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
790 config->has_main_output = true;
791 config->main_output_channel_count = static_cast<uint32_t>(width);
792 config->main_output_port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
793 }
794
795 static bool sConfigGet(const clap_plugin_t*, uint32_t index,
796 clap_audio_ports_config_t* config) noexcept
797 {
798 if (config == nullptr || index >= 2) return false;
799 fillConfig(config, index == 0 ? 1 : 2);
800 return true;
801 }
802
803 static bool sConfigSelect(const clap_plugin_t* p, clap_id configId) noexcept
804 {
805 const int width = static_cast<int>(configId);
806 if (!supportsChannelCount<P>(width)) return false;
807 self(p)->currentChannels = width;
808 return true;
809 }
810
811 inline static const clap_plugin_audio_ports_config_t kPortsConfig = {
813 };
814
815 static clap_id sConfigCurrent(const clap_plugin_t* p) noexcept
816 {
817 return static_cast<clap_id>(self(p)->currentChannels);
818 }
819
820 static bool sConfigInfoGet(const clap_plugin_t* p, clap_id configId,
821 uint32_t portIndex, bool isInput,
822 clap_audio_port_info_t* info) noexcept
823 {
824 const int width = static_cast<int>(configId);
825 if (info == nullptr || !supportsChannelCount<P>(width)
826 || portIndex >= sPortCount(p, isInput))
827 return false;
828 // Same shape as sPortGet, with the width of the asked-about config.
829 std::memset(info, 0, sizeof(*info));
830 if (isInput && portIndex == 1)
831 {
832 info->id = 2;
833 std::snprintf(info->name, sizeof(info->name), "Sidechain");
834 info->channel_count = static_cast<uint32_t>(width);
835 info->port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
836 info->in_place_pair = CLAP_INVALID_ID;
837 return true;
838 }
839 info->id = isInput ? 0 : 1;
840 std::snprintf(info->name, sizeof(info->name), "%s",
841 isInput ? "Input" : "Output");
842 info->flags = CLAP_AUDIO_PORT_IS_MAIN;
843 info->channel_count = static_cast<uint32_t>(width);
844 info->port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
845 info->in_place_pair = kIsInstrument ? CLAP_INVALID_ID
846 : (isInput ? 1u : 0u);
847 return true;
848 }
849
850 inline static const clap_plugin_audio_ports_config_info_t kPortsConfigInfo = {
852 };
853
854 // --- ext: note ports (HasMidi) ----------------------------------------------------
855
856 static uint32_t sNotePortCount(const clap_plugin_t*, bool isInput) noexcept
857 {
858 return (HasMidi<P> && isInput) ? 1 : 0;
859 }
860
861 static bool sNotePortGet(const clap_plugin_t*, uint32_t index, bool isInput,
862 clap_note_port_info_t* info) noexcept
863 {
864 if (info == nullptr || !isInput || index != 0 || !HasMidi<P>) return false;
865 std::memset(info, 0, sizeof(*info));
866 info->id = 10;
867 info->supported_dialects = CLAP_NOTE_DIALECT_CLAP | CLAP_NOTE_DIALECT_MIDI;
868 info->preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
869 std::snprintf(info->name, sizeof(info->name), "MIDI In");
870 return true;
871 }
872
873 inline static const clap_plugin_note_ports_t kNotePorts = {
875 };
876
877 // --- ext: render (HasOfflineMode) ---------------------------------------------------
878
879 static bool sRenderHardRealtime(const clap_plugin_t*) noexcept { return false; }
880
881 static bool sRenderSet(const clap_plugin_t* p, clap_plugin_render_mode mode) noexcept
882 {
883 auto* s = self(p);
884 s->offlineRender = mode == CLAP_RENDER_OFFLINE;
885 if constexpr (HasOfflineMode<P>)
886 s->user.setOfflineRendering(s->offlineRender);
887 return true;
888 }
889
890 inline static const clap_plugin_render_t kRender = {
892 };
893
894 // --- ext: preset load (factory presets; the DSO is the container) -------------------
895
896 static bool sPresetFromLocation(const clap_plugin_t* p, uint32_t locationKind,
897 const char* location, const char* loadKey) noexcept
898 {
899 if constexpr (kNumPresets > 0)
900 {
901 auto* s = self(p);
902 if (locationKind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN
903 || loadKey == nullptr)
904 return false;
905 const int idx = std::atoi(loadKey);
906 if (idx < 0 || idx >= kNumPresets) return false;
907 s->applyFactoryPresetIdx(idx);
908 s->refreshLatency();
909 if (s->hostParams != nullptr && s->hostParams->rescan != nullptr)
910 s->hostParams->rescan(s->host, CLAP_PARAM_RESCAN_VALUES);
911 if (s->hostPresetLoad != nullptr && s->hostPresetLoad->loaded != nullptr)
912 s->hostPresetLoad->loaded(s->host, locationKind, location, loadKey);
913 return true;
914 }
915 else
916 {
917 (void) p;
918 (void) locationKind;
919 (void) location;
920 (void) loadKey;
921 return false;
922 }
923 }
924
925 inline static const clap_plugin_preset_load_t kPresetLoad = {
927 };
928
929 // --- ext: params ----------------------------------------------------------------
930
931 static uint32_t sParamCount(const clap_plugin_t*) noexcept
932 {
933 return static_cast<uint32_t>(kNumParams) + 1; // + Bypass
934 }
935
936 static bool sParamInfo(const clap_plugin_t*, uint32_t index,
937 clap_param_info_t* info) noexcept
938 {
939 if (info == nullptr || index > kNumParams) return false;
940 std::memset(info, 0, sizeof(*info));
941 if (index == kNumParams)
942 {
943 info->id = kBypassParamId;
944 std::snprintf(info->name, sizeof(info->name), "Bypass");
945 info->flags = CLAP_PARAM_IS_AUTOMATABLE | CLAP_PARAM_IS_STEPPED
946 | CLAP_PARAM_IS_BYPASS;
947 info->min_value = 0.0;
948 info->max_value = 1.0;
949 info->default_value = 0.0;
950 return true;
951 }
952 const auto& spec = P::parameters[index];
953 info->id = hash32(spec.id);
954 std::snprintf(info->name, sizeof(info->name), "%s", spec.name);
955 info->flags = CLAP_PARAM_IS_AUTOMATABLE;
956 if (spec.steps > 0) info->flags |= CLAP_PARAM_IS_STEPPED;
957 if (spec.labels != nullptr) info->flags |= CLAP_PARAM_IS_ENUM;
958 info->min_value = spec.minValue;
959 info->max_value = spec.maxValue;
960 info->default_value = spec.defValue;
961 return true;
962 }
963
964 static bool sParamValue(const clap_plugin_t* p, clap_id id, double* out) noexcept
965 {
966 auto* s = self(p);
967 if (out == nullptr) return false;
968 if (id == kBypassParamId)
969 {
970 *out = s->bypass.load(std::memory_order_relaxed) ? 1.0 : 0.0;
971 return true;
972 }
973 const int idx = indexOfParamId(id);
974 if (idx < 0) return false;
975 *out = toPlain(P::parameters[static_cast<size_t>(idx)],
976 s->shadow[static_cast<size_t>(idx)].load(std::memory_order_relaxed));
977 return true;
978 }
979
980 static bool sParamValueToText(const clap_plugin_t*, clap_id id, double value,
981 char* out, uint32_t size) noexcept
982 {
983 if (out == nullptr || size == 0) return false;
984 if (id == kBypassParamId)
985 {
986 std::snprintf(out, size, "%s", value >= 0.5 ? "On" : "Off");
987 return true;
988 }
989 const int idx = indexOfParamId(id);
990 if (idx < 0) return false;
991 formatValue(P::parameters[static_cast<size_t>(idx)], value, out,
992 static_cast<int>(size));
993 return true;
994 }
995
996 static bool sParamTextToValue(const clap_plugin_t*, clap_id id,
997 const char* text, double* out) noexcept
998 {
999 if (text == nullptr || out == nullptr) return false;
1000 const int toggle = parseToggleText(text);
1001 if (id == kBypassParamId)
1002 {
1003 *out = toggle >= 0 ? toggle
1004 : (std::strtod(text, nullptr) >= 0.5 ? 1.0 : 0.0);
1005 return true;
1006 }
1007 const int idx = indexOfParamId(id);
1008 if (idx < 0) return false;
1009 // Labels, On/Off or a number, clamped and snapped; "nan" and other
1010 // garbage are refused rather than passed through to the host.
1011 return parseValue(P::parameters[static_cast<size_t>(idx)], text, *out);
1012 }
1013
1014 static void sParamFlush(const clap_plugin_t* p, const clap_input_events_t* in,
1015 const clap_output_events_t* out) noexcept
1016 {
1017 auto* s = self(p);
1019 int eventCount = 0;
1020 s->collectEvents(in, 0, events, eventCount);
1021 sortBlockEvents(events, eventCount);
1022 bool paramsChanged = false;
1023 for (int i = 0; i < eventCount; ++i)
1024 paramsChanged |= s->applyBlockEvent(events[i], events[i].offset);
1025 if (paramsChanged) s->refreshLatency();
1026#if defined(DSPARK_PLUGIN_WEBVIEW)
1027 s->drainUiEvents(out);
1028#else
1029 (void) out;
1030#endif
1031 }
1032
1033 inline static const clap_plugin_params_t kParams = {
1036 };
1037
1038 // --- ext: state -------------------------------------------------------------------
1039
1040 static bool sStateSave(const clap_plugin_t* p, const clap_ostream_t* stream) noexcept
1041 {
1042 auto* s = self(p);
1043 double norm[kNumParams == 0 ? 1 : kNumParams];
1044 for (size_t i = 0; i < kNumParams; ++i)
1045 norm[i] = s->shadow[i].load(std::memory_order_relaxed);
1046 const std::vector<uint8_t> blob = buildState(
1047 s->user, norm, kNumParams,
1048 kNumPresets > 0 ? s->currentProgram.load(std::memory_order_relaxed) : -1,
1049 s->bypass.load(std::memory_order_relaxed) ? 1 : 0);
1050 size_t pos = 0;
1051 while (pos < blob.size())
1052 {
1053 const int64_t put = stream->write(stream, blob.data() + pos,
1054 blob.size() - pos);
1055 if (put <= 0) return false;
1056 pos += static_cast<size_t>(put);
1057 }
1058 return true;
1059 }
1060
1061 static bool sStateLoad(const clap_plugin_t* p, const clap_istream_t* stream) noexcept
1062 {
1063 auto* s = self(p);
1064 std::vector<uint8_t> blob;
1065 uint8_t chunk[4096];
1066 for (;;)
1067 {
1068 const int64_t got = stream->read(stream, chunk, sizeof(chunk));
1069 if (got < 0) return false;
1070 if (got == 0) break;
1071 blob.insert(blob.end(), chunk, chunk + got);
1072 if (blob.size() > (16u << 20)) return false;
1073 }
1074 double norm[kNumParams == 0 ? 1 : kNumParams];
1075 for (size_t i = 0; i < kNumParams; ++i)
1076 norm[i] = s->shadow[i].load(std::memory_order_relaxed);
1077 int program = -1;
1078 int bypassState = -1;
1079 if (!applyState(s->user, blob.data(), blob.size(), norm, &program,
1080 &bypassState))
1081 return false;
1082 for (size_t i = 0; i < kNumParams; ++i)
1083 s->applyNormalized(static_cast<int>(i), norm[i]);
1084 if (kNumPresets > 0 && program >= 0 && program < kNumPresets)
1085 s->currentProgram.store(program, std::memory_order_relaxed);
1086 if (bypassState >= 0)
1087 s->bypass.store(bypassState != 0, std::memory_order_relaxed);
1088 s->refreshLatency(); // restored state may imply a new lookahead
1089 return true;
1090 }
1091
1092 inline static const clap_plugin_state_t kState = { &sStateSave, &sStateLoad };
1093
1094 // --- ext: latency / tail --------------------------------------------------------
1095
1096 static uint32_t sLatencyGet(const clap_plugin_t* p) noexcept
1097 {
1098 return static_cast<uint32_t>(
1099 self(p)->cachedLatency.load(std::memory_order_relaxed));
1100 }
1101
1102 inline static const clap_plugin_latency_t kLatency = { &sLatencyGet };
1103
1104 static uint32_t sTailGet(const clap_plugin_t* p) noexcept
1105 {
1106 auto* s = self(p);
1107 if constexpr (HasTail<P>)
1108 {
1109 const double seconds = s->user.getTailSeconds();
1110 if (!(seconds > 0.0)) return 0; // negative/NaN -> no tail
1111 const double samples = seconds * s->sampleRate;
1112 // CLAP treats anything >= INT32_MAX as an infinite tail; the cap
1113 // also avoids the double->uint32 overflow (UB).
1114 if (samples >= 4294967295.0) return 0xFFFFFFFFu;
1115 return static_cast<uint32_t>(samples);
1116 }
1117 else
1118 {
1119 (void) s;
1120 return 0;
1121 }
1122 }
1123
1124 inline static const clap_plugin_tail_t kTail = { &sTailGet };
1125
1126#if defined(DSPARK_PLUGIN_WEBVIEW)
1127 // --- ext: gui (WebView editor layer) ----------------------------------------------
1128 //
1129 // CLAP hands the parent window AFTER create(), so create() only arms the
1130 // state and set_parent() builds the actual web engine. All [main-thread].
1131
1132 static const char* clapWindowApi() noexcept
1133 {
1134#if defined(_WIN32)
1135 return CLAP_WINDOW_API_WIN32;
1136#elif defined(__APPLE__)
1137 return CLAP_WINDOW_API_COCOA;
1138#else
1139 return CLAP_WINDOW_API_X11;
1140#endif
1141 }
1142
1143 static void cbGuiSetParam(void* context, int index, double plain) noexcept
1144 {
1145 auto* s = static_cast<Plugin*>(context);
1146 const Param& spec = P::parameters[static_cast<size_t>(index)];
1147 const double normalized = toNormalized(spec, plain);
1148 s->applyNormalized(index, normalized);
1149 const double snapped = toPlain(spec, normalized);
1150 const uint32_t id = hash32(spec.id);
1151 if (s->guiEditActive[static_cast<size_t>(index)])
1152 s->pushUiEvent(id, kUiValue, snapped);
1153 else
1154 {
1155 // Edits outside an explicit gesture still get one (host undo).
1156 s->pushUiEvent(id, kUiGestureBegin, 0.0);
1157 s->pushUiEvent(id, kUiValue, snapped);
1158 s->pushUiEvent(id, kUiGestureEnd, 0.0);
1159 }
1160 s->requestUiFlush();
1161 }
1162
1163 static void cbGuiBeginEdit(void* context, int index) noexcept
1164 {
1165 auto* s = static_cast<Plugin*>(context);
1166 s->guiEditActive[static_cast<size_t>(index)] = true;
1167 s->pushUiEvent(hash32(P::parameters[static_cast<size_t>(index)].id),
1168 kUiGestureBegin, 0.0);
1169 s->requestUiFlush();
1170 }
1171
1172 static void cbGuiEndEdit(void* context, int index) noexcept
1173 {
1174 auto* s = static_cast<Plugin*>(context);
1175 s->guiEditActive[static_cast<size_t>(index)] = false;
1176 s->pushUiEvent(hash32(P::parameters[static_cast<size_t>(index)].id),
1177 kUiGestureEnd, 0.0);
1178 s->requestUiFlush();
1179 }
1180
1181 static bool sGuiIsApiSupported(const clap_plugin_t*, const char* api,
1182 bool isFloating) noexcept
1183 {
1184 return !isFloating && api != nullptr && std::strcmp(api, clapWindowApi()) == 0
1186 }
1187
1188 static bool sGuiGetPreferredApi(const clap_plugin_t*, const char** api,
1189 bool* isFloating) noexcept
1190 {
1191 if (api == nullptr || isFloating == nullptr) return false;
1192 *api = clapWindowApi();
1193 *isFloating = false;
1195 }
1196
1197 static bool sGuiCreate(const clap_plugin_t* p, const char* api,
1198 bool isFloating) noexcept
1199 {
1200 auto* s = self(p);
1201 if (!sGuiIsApiSupported(p, api, isFloating)) return false;
1202#if defined(__linux__)
1203 // GTK breathes only when pumped from the host's run loop; without
1204 // timer-support the page would freeze - fall back to generic UI.
1205 s->hostTimer = s->host != nullptr
1206 ? static_cast<const clap_host_timer_support_t*>(
1207 s->host->get_extension(s->host, CLAP_EXT_TIMER_SUPPORT))
1208 : nullptr;
1209 if (s->hostTimer == nullptr || s->hostTimer->register_timer == nullptr
1210 || !s->hostTimer->register_timer(s->host, 33, &s->guiTimerId))
1211 {
1212 webview_ui::debugLog("clap gui create: no host timer-support -> no editor");
1213 s->hostTimer = nullptr;
1214 s->guiTimerId = CLAP_INVALID_ID;
1215 return false;
1216 }
1217#endif
1218 const EditorSize logical = editorSizeOf<P>();
1219 s->guiWidth = toBoundedInt(logical.width * s->guiScale + 0.5,
1220 1, static_cast<int>(kEditorMaxPixels));
1221 s->guiHeight = toBoundedInt(logical.height * s->guiScale + 0.5,
1222 1, static_cast<int>(kEditorMaxPixels));
1223 s->guiActive = true;
1224 webview_ui::debugLog("clap gui create %dx%d scale=%.2f",
1225 s->guiWidth, s->guiHeight, s->guiScale);
1226 return true;
1227 }
1228
1229 static void sGuiDestroy(const clap_plugin_t* p) noexcept
1230 {
1231 auto* s = self(p);
1232#if defined(__linux__)
1233 if (s->hostTimer != nullptr && s->guiTimerId != CLAP_INVALID_ID)
1234 s->hostTimer->unregister_timer(s->host, s->guiTimerId);
1235 s->hostTimer = nullptr;
1236 s->guiTimerId = CLAP_INVALID_ID;
1237#endif
1238 s->guiEditor.destroy();
1239 s->guiActive = false;
1240 }
1241
1242 static bool sGuiSetScale(const clap_plugin_t* p, double scale) noexcept
1243 {
1244#if defined(__APPLE__)
1245 (void) p; (void) scale;
1246 return false; // cocoa uses logical sizes; scaling is the OS's job
1247#else
1248 auto* s = self(p);
1249 if (!(scale > 0.0)) return false; // NaN included
1250 // Only the physical-size negotiation changes; the page itself never
1251 // zooms (the web engine applies the window DPI to CSS pixels itself).
1252 const double previous = s->guiScale;
1253 s->guiScale = scale;
1254 s->guiWidth = toBoundedInt(s->guiWidth * (scale / previous) + 0.5,
1255 1, static_cast<int>(kEditorMaxPixels));
1256 s->guiHeight = toBoundedInt(s->guiHeight * (scale / previous) + 0.5,
1257 1, static_cast<int>(kEditorMaxPixels));
1258 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1259 return true;
1260#endif
1261 }
1262
1263 static bool sGuiGetSize(const clap_plugin_t* p, uint32_t* width,
1264 uint32_t* height) noexcept
1265 {
1266 auto* s = self(p);
1267 if (width == nullptr || height == nullptr || !s->guiActive) return false;
1268 *width = static_cast<uint32_t>(s->guiWidth);
1269 *height = static_cast<uint32_t>(s->guiHeight);
1270 return true;
1271 }
1272
1273 static bool sGuiCanResize(const clap_plugin_t*) noexcept
1274 {
1275 return editorResizeOf<P>() != EditorResize::Fixed;
1276 }
1277
1278 static bool sGuiGetResizeHints(const clap_plugin_t*,
1279 clap_gui_resize_hints_t* hints) noexcept
1280 {
1281 if (hints == nullptr) return false;
1282 constexpr EditorResize mode = editorResizeOf<P>();
1283 const EditorSize logical = editorSizeOf<P>();
1284 hints->can_resize_horizontally = mode != EditorResize::Fixed;
1285 hints->can_resize_vertically = mode != EditorResize::Fixed;
1286 hints->preserve_aspect_ratio = mode == EditorResize::KeepAspect;
1287 hints->aspect_ratio_width = static_cast<uint32_t>(logical.width);
1288 hints->aspect_ratio_height = static_cast<uint32_t>(logical.height);
1289 return true;
1290 }
1291
1292 static bool sGuiAdjustSize(const clap_plugin_t* p, uint32_t* width,
1293 uint32_t* height) noexcept
1294 {
1295 auto* s = self(p);
1296 if (width == nullptr || height == nullptr) return false;
1297 double w = *width;
1298 double h = *height;
1299 constrainEditorSize<P>(w, h, s->guiScale);
1300 *width = static_cast<uint32_t>(
1301 toBoundedInt(w + 0.5, 1, static_cast<int>(kEditorMaxPixels)));
1302 *height = static_cast<uint32_t>(
1303 toBoundedInt(h + 0.5, 1, static_cast<int>(kEditorMaxPixels)));
1304 return true;
1305 }
1306
1307 static bool sGuiSetSize(const clap_plugin_t* p, uint32_t width,
1308 uint32_t height) noexcept
1309 {
1310 auto* s = self(p);
1311 if (!s->guiActive) return false;
1312 // Clamp here too: not every host runs the size through adjust_size.
1313 double w = width;
1314 double h = height;
1315 constrainEditorSize<P>(w, h, s->guiScale);
1316 webview_ui::debugLog("clap gui set_size %ux%u -> %.0fx%.0f", width, height, w, h);
1317 s->guiWidth = toBoundedInt(w + 0.5, 1, static_cast<int>(kEditorMaxPixels));
1318 s->guiHeight = toBoundedInt(h + 0.5, 1, static_cast<int>(kEditorMaxPixels));
1319 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1320 return true;
1321 }
1322
1323 static bool sGuiSetParent(const clap_plugin_t* p,
1324 const clap_window_t* window) noexcept
1325 {
1326 auto* s = self(p);
1327 if (window == nullptr || !s->guiActive) return false;
1328#if defined(__linux__)
1329 // The X11 window id rides the union's dedicated field.
1330 void* parentHandle = reinterpret_cast<void*>(
1331 static_cast<std::uintptr_t>(window->x11));
1332#else
1333 void* parentHandle = window->ptr;
1334#endif
1335 webview_ui::debugLog("clap gui set_parent %p negotiated=%dx%d",
1336 parentHandle, s->guiWidth, s->guiHeight);
1337 const webview_ui::HostCallbacks callbacks {
1338 s, &cbGuiSetParam, &cbGuiBeginEdit, &cbGuiEndEdit
1339 };
1340 if (!s->guiEditor.create(parentHandle, s->shadow, callbacks))
1341 return false;
1342 // Fill whatever box the host actually built (call order differs
1343 // between hosts); fall back to the negotiated size otherwise.
1344 int parentW = 0, parentH = 0;
1345 if (s->guiEditor.queryParentSize(parentW, parentH))
1346 {
1347 s->guiWidth = parentW;
1348 s->guiHeight = parentH;
1349 }
1350 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1351 return true;
1352 }
1353
1354 static bool sGuiSetTransient(const clap_plugin_t*, const clap_window_t*) noexcept
1355 {
1356 return false; // embedded only; no floating window mode
1357 }
1358
1359 static void sGuiSuggestTitle(const clap_plugin_t*, const char*) noexcept {}
1360
1361 static bool sGuiShow(const clap_plugin_t* p) noexcept
1362 {
1363 self(p)->guiEditor.setVisible(true);
1364 return true;
1365 }
1366
1367 static bool sGuiHide(const clap_plugin_t* p) noexcept
1368 {
1369 self(p)->guiEditor.setVisible(false);
1370 return true;
1371 }
1372
1373 inline static const clap_plugin_gui_t kGui = {
1374 &sGuiIsApiSupported, &sGuiGetPreferredApi, &sGuiCreate, &sGuiDestroy,
1375 &sGuiSetScale, &sGuiGetSize, &sGuiCanResize, &sGuiGetResizeHints,
1376 &sGuiAdjustSize, &sGuiSetSize, &sGuiSetParent, &sGuiSetTransient,
1377 &sGuiSuggestTitle, &sGuiShow, &sGuiHide
1378 };
1379
1380#if defined(__linux__)
1381
1382 // --- ext: timer-support (drives the GTK main context for the editor) -----------
1383
1384 static void sOnTimer(const clap_plugin_t* p, clap_id) noexcept
1385 {
1386 self(p)->guiEditor.pump();
1387 }
1388
1389 inline static const clap_plugin_timer_support_t kTimer = { &sOnTimer };
1390
1391#endif // __linux__
1392#endif // DSPARK_PLUGIN_WEBVIEW
1393
1394 // --- descriptor & factory ----------------------------------------------------------
1395
1396 static const clap_plugin_descriptor_t* descriptor() noexcept
1397 {
1398 static const char* features[] = {
1399 kIsInstrument ? CLAP_PLUGIN_FEATURE_INSTRUMENT
1400 : CLAP_PLUGIN_FEATURE_AUDIO_EFFECT,
1401 channelSupportOf<P>() == ChannelSupport::MonoOnly
1402 ? CLAP_PLUGIN_FEATURE_MONO : CLAP_PLUGIN_FEATURE_STEREO,
1403 nullptr
1404 };
1405 static const clap_plugin_descriptor_t desc = {
1406 CLAP_VERSION_INIT,
1407 P::descriptor.productId,
1408 P::descriptor.name,
1409 P::descriptor.vendor,
1410 P::descriptor.url,
1411 "", // manual_url
1412 P::descriptor.url, // support_url
1413 P::descriptor.version,
1414 "Built with DSPark", // description
1415 features
1416 };
1417 return &desc;
1418 }
1419
1420 static const clap_plugin_t* create(const clap_host_t* host) noexcept
1421 {
1422 auto* s = new (std::nothrow) Plugin(host);
1423 if (s == nullptr) return nullptr;
1424 s->plugin.desc = descriptor();
1425 s->plugin.plugin_data = s;
1426 s->plugin.init = &sInit;
1427 s->plugin.destroy = &sDestroy;
1428 s->plugin.activate = &sActivate;
1429 s->plugin.deactivate = &sDeactivate;
1430 s->plugin.start_processing = &sStartProcessing;
1431 s->plugin.stop_processing = &sStopProcessing;
1432 s->plugin.reset = &sReset;
1433 s->plugin.process = &sProcess;
1434 s->plugin.get_extension = &sGetExtension;
1435 s->plugin.on_main_thread = &sOnMainThread;
1436 return &s->plugin;
1437 }
1438};
1439
1440template <typename P>
1442{
1443 static uint32_t sCount(const clap_plugin_factory_t*) noexcept { return 1; }
1444
1445 static const clap_plugin_descriptor_t* sDescriptor(const clap_plugin_factory_t*,
1446 uint32_t index) noexcept
1447 {
1448 return index == 0 ? Plugin<P>::descriptor() : nullptr;
1449 }
1450
1451 static const clap_plugin_t* sCreate(const clap_plugin_factory_t*,
1452 const clap_host_t* host,
1453 const char* pluginId) noexcept
1454 {
1455 if (pluginId == nullptr
1456 || std::strcmp(pluginId, P::descriptor.productId) != 0)
1457 return nullptr;
1458 return Plugin<P>::create(host);
1459 }
1460
1461 inline static const clap_plugin_factory_t kFactory = {
1463 };
1464
1465 // --- preset discovery (factory presets live inside the plugin DSO) -------------
1466 //
1467 // The provider declares ONE location of kind PLUGIN and reports each
1468 // factory preset with its table index as the load key; preset-load's
1469 // from_location() applies it. Hosts with a preset browser (e.g. Bitwig)
1470 // index these without instantiating the plugin.
1471
1472 static constexpr int kNumPresets = factoryPresetCountOf<P>();
1473
1474 static const char* providerId() noexcept
1475 {
1476 // Magic static: the discovery factory contract is [thread-safe].
1477 static const std::array<char, 256> id = [] {
1478 std::array<char, 256> buf {};
1479 std::snprintf(buf.data(), buf.size(), "%s.presets",
1480 P::descriptor.productId);
1481 return buf;
1482 }();
1483 return id.data();
1484 }
1485
1486 static const clap_preset_discovery_provider_descriptor_t* providerDescriptor() noexcept
1487 {
1488 static const clap_preset_discovery_provider_descriptor_t desc = {
1489 CLAP_VERSION_INIT, providerId(), "DSPark Factory Presets",
1490 P::descriptor.vendor
1491 };
1492 return &desc;
1493 }
1494
1496 {
1497 clap_preset_discovery_provider_t provider {};
1498 const clap_preset_discovery_indexer_t* indexer = nullptr;
1499 };
1500
1501 static bool sProviderInit(const clap_preset_discovery_provider_t* provider) noexcept
1502 {
1503 const auto* state = static_cast<const ProviderState*>(provider->provider_data);
1504 if (state->indexer == nullptr || state->indexer->declare_location == nullptr)
1505 return false;
1506 clap_preset_discovery_location_t location {};
1507 location.flags = CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT;
1508 location.name = "Factory Presets";
1509 location.kind = CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN;
1510 location.location = nullptr;
1511 return state->indexer->declare_location(state->indexer, &location);
1512 }
1513
1514 static void sProviderDestroy(const clap_preset_discovery_provider_t* provider) noexcept
1515 {
1516 delete static_cast<ProviderState*>(provider->provider_data);
1517 }
1518
1519 static bool sProviderGetMetadata(const clap_preset_discovery_provider_t*,
1520 uint32_t locationKind, const char*,
1521 const clap_preset_discovery_metadata_receiver_t*
1522 receiver) noexcept
1523 {
1524 if constexpr (kNumPresets > 0)
1525 {
1526 if (locationKind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN
1527 || receiver == nullptr)
1528 return false;
1529 const clap_universal_plugin_id_t pluginId {
1530 "clap", P::descriptor.productId
1531 };
1532 for (int i = 0; i < kNumPresets; ++i)
1533 {
1534 char key[16];
1535 std::snprintf(key, sizeof(key), "%d", i);
1536 if (!receiver->begin_preset(receiver,
1537 P::factoryPresets[static_cast<size_t>(i)].name, key))
1538 return true; // the indexer asked to stop; not an error
1539 receiver->add_plugin_id(receiver, &pluginId);
1540 receiver->set_flags(receiver,
1541 CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT);
1542 }
1543 return true;
1544 }
1545 else
1546 {
1547 (void) locationKind;
1548 (void) receiver;
1549 return false;
1550 }
1551 }
1552
1553 static const void* sProviderGetExtension(const clap_preset_discovery_provider_t*,
1554 const char*) noexcept
1555 { return nullptr; }
1556
1557 static uint32_t sDiscoveryCount(const clap_preset_discovery_factory_t*) noexcept
1558 { return kNumPresets > 0 ? 1u : 0u; }
1559
1560 static const clap_preset_discovery_provider_descriptor_t* sDiscoveryDescriptor(
1561 const clap_preset_discovery_factory_t*, uint32_t index) noexcept
1562 {
1563 return (kNumPresets > 0 && index == 0) ? providerDescriptor() : nullptr;
1564 }
1565
1566 static const clap_preset_discovery_provider_t* sDiscoveryCreate(
1567 const clap_preset_discovery_factory_t*,
1568 const clap_preset_discovery_indexer_t* indexer,
1569 const char* providerIdAsked) noexcept
1570 {
1571 if (kNumPresets == 0 || indexer == nullptr || providerIdAsked == nullptr
1572 || std::strcmp(providerIdAsked, providerId()) != 0)
1573 return nullptr;
1574 auto* state = new (std::nothrow) ProviderState();
1575 if (state == nullptr) return nullptr;
1576 state->indexer = indexer;
1577 state->provider.desc = providerDescriptor();
1578 state->provider.provider_data = state;
1579 state->provider.init = &sProviderInit;
1580 state->provider.destroy = &sProviderDestroy;
1581 state->provider.get_metadata = &sProviderGetMetadata;
1582 state->provider.get_extension = &sProviderGetExtension;
1583 return &state->provider;
1584 }
1585
1586 inline static const clap_preset_discovery_factory_t kDiscoveryFactory = {
1588 };
1589
1590 static bool sEntryInit(const char*) noexcept { return true; }
1591 static void sEntryDeinit() noexcept {}
1592
1593 static const void* sEntryGetFactory(const char* factoryId) noexcept
1594 {
1595 if (factoryId == nullptr) return nullptr;
1596 if (std::strcmp(factoryId, CLAP_PLUGIN_FACTORY_ID) == 0)
1597 return &kFactory;
1598 if (kNumPresets > 0
1599 && (std::strcmp(factoryId, CLAP_PRESET_DISCOVERY_FACTORY_ID) == 0
1600 || std::strcmp(factoryId, CLAP_PRESET_DISCOVERY_FACTORY_ID_COMPAT) == 0))
1601 return &kDiscoveryFactory;
1602 return nullptr;
1603 }
1604};
1605
1606} // namespace dspark::plugin::clap_backend
1607
1613#define DSPARK_CLAP_PLUGIN(PluginClass) \
1614 extern "C" CLAP_EXPORT const clap_plugin_entry_t clap_entry = { \
1615 CLAP_VERSION_INIT, \
1616 &dspark::plugin::clap_backend::Factory<PluginClass>::sEntryInit, \
1617 &dspark::plugin::clap_backend::Factory<PluginClass>::sEntryDeinit, \
1618 &dspark::plugin::clap_backend::Factory<PluginClass>::sEntryGetFactory \
1619 };
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
The dry signal of the wrappers' soft bypass, delayed by the plugin's latency.
One embedded WebView editor instance for plugin class P. Owned by the format backends (the VST3 IPlug...
Custom-editor switch. With hasEditor = false (or absent) hosts show their generic parameter UI....
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...
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 int kBypassRampSamples
Definition DSParkClap.h:70
constexpr uint32_t kBypassParamId
Definition DSParkClap.h:69
void debugLog(const char *format,...) noexcept
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.
@ Instrument
Audio generator (MIDI in -> audio out, no audio input).
constexpr double kEditorMaxPixels
Pixel ceiling for a negotiated editor size.
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 int kDryDelayMinCapacity
Ring headroom of the bypass dry path: latencies up to this (or twice the latency at activation,...
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 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 int kMaxBlockEvents
Hard cap of timestamped events handled per block; the (very unlikely) excess applies at the position ...
void formatValue(const Param &p, double plain, char *out, int outSize) noexcept
Formats a plain value for host display ("3.5 dB", "On", "Saw", "4").
void sortBlockEvents(BlockEvent *events, int count) noexcept
Stable insertion sort by offset (tiny N, allocation-free - audio-thread safe; equal offsets keep arri...
@ MonoOnly
Strictly 1->1 (rare; metering/utility plugins).
@ MonoAndStereo
Negotiates 1->1 or 2->2 with the host (default).
constexpr int toBoundedInt(double value, int lo, int hi) noexcept
A floating value from outside converted to an integer, with the range settled BEFORE the conversion.
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...
bool parseValue(const Param &p, const char *text, double &plain) noexcept
Host text -> plain value, the inverse of formatValue(): a choice's label (any case),...
Describes the audio environment for a DSP processor.
Definition AudioSpec.h:37
One timestamped in-block event, normalised across formats: a parameter point, a bypass point,...
int32_t offset
Frame position within the block.
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).
int sampleOffset
Frames into the NEXT processBlock call.
One automatable parameter. Plain values run [min, max]; hosts see the normalized [0,...
const char * id
Stable text id (state + automation identity).
Host transport snapshot, delivered once per audio block.
bool playing
Transport is rolling.
const clap_preset_discovery_indexer_t * indexer
static bool sProviderInit(const clap_preset_discovery_provider_t *provider) noexcept
static const clap_preset_discovery_provider_descriptor_t * providerDescriptor() noexcept
static const char * providerId() noexcept
static const void * sEntryGetFactory(const char *factoryId) noexcept
static const clap_preset_discovery_provider_t * sDiscoveryCreate(const clap_preset_discovery_factory_t *, const clap_preset_discovery_indexer_t *indexer, const char *providerIdAsked) noexcept
static void sEntryDeinit() noexcept
static uint32_t sDiscoveryCount(const clap_preset_discovery_factory_t *) noexcept
static uint32_t sCount(const clap_plugin_factory_t *) noexcept
static bool sProviderGetMetadata(const clap_preset_discovery_provider_t *, uint32_t locationKind, const char *, const clap_preset_discovery_metadata_receiver_t *receiver) noexcept
static void sProviderDestroy(const clap_preset_discovery_provider_t *provider) noexcept
static bool sEntryInit(const char *) noexcept
static const clap_preset_discovery_provider_descriptor_t * sDiscoveryDescriptor(const clap_preset_discovery_factory_t *, uint32_t index) noexcept
static const clap_plugin_factory_t kFactory
static const void * sProviderGetExtension(const clap_preset_discovery_provider_t *, const char *) noexcept
static const clap_preset_discovery_factory_t kDiscoveryFactory
static const clap_plugin_t * sCreate(const clap_plugin_factory_t *, const clap_host_t *host, const char *pluginId) noexcept
static const clap_plugin_descriptor_t * sDescriptor(const clap_plugin_factory_t *, uint32_t index) noexcept
static uint32_t sConfigCount(const clap_plugin_t *) noexcept
Definition DSParkClap.h:777
static void sStopProcessing(const clap_plugin_t *) noexcept
Definition DSParkClap.h:492
const clap_host_params_t * hostParams
Definition DSParkClap.h:93
static void sReset(const clap_plugin_t *p) noexcept
Definition DSParkClap.h:494
static const clap_plugin_audio_ports_t kAudioPorts
Definition DSParkClap.h:773
static constexpr int kNumPresets
Definition DSParkClap.h:78
static bool sConfigSelect(const clap_plugin_t *p, clap_id configId) noexcept
Definition DSParkClap.h:803
static const clap_plugin_state_t kState
static Plugin * self(const clap_plugin_t *p) noexcept
Definition DSParkClap.h:152
void collectEvent(const clap_event_header_t *ev, uint32_t numSamples, BlockEvent *events, int &count) noexcept
Definition DSParkClap.h:209
const clap_host_preset_load_t * hostPresetLoad
Definition DSParkClap.h:94
static uint32_t sParamCount(const clap_plugin_t *) noexcept
Definition DSParkClap.h:931
static bool sParamValue(const clap_plugin_t *p, clap_id id, double *out) noexcept
Definition DSParkClap.h:964
static uint32_t sNotePortCount(const clap_plugin_t *, bool isInput) noexcept
Definition DSParkClap.h:856
static bool sConfigInfoGet(const clap_plugin_t *p, clap_id configId, uint32_t portIndex, bool isInput, clap_audio_port_info_t *info) noexcept
Definition DSParkClap.h:820
static clap_process_status sProcess(const clap_plugin_t *p, const clap_process_t *process) noexcept
Definition DSParkClap.h:503
void forwardTransport(const clap_event_transport_t *t) noexcept
Definition DSParkClap.h:345
static bool sPortGet(const clap_plugin_t *p, uint32_t index, bool isInput, clap_audio_port_info_t *info) noexcept
Definition DSParkClap.h:746
static const clap_plugin_latency_t kLatency
static bool sActivate(const clap_plugin_t *p, double sr, uint32_t, uint32_t maxFrames) noexcept
Definition DSParkClap.h:460
Plugin(const clap_host_t *h) noexcept
Definition DSParkClap.h:145
static void sOnMainThread(const clap_plugin_t *p) noexcept
Definition DSParkClap.h:725
static bool sRenderSet(const clap_plugin_t *p, clap_plugin_render_mode mode) noexcept
Definition DSParkClap.h:881
static constexpr bool kIsInstrument
Definition DSParkClap.h:76
void applyFactoryPresetIdx(int idx) noexcept
Definition DSParkClap.h:175
static uint32_t sTailGet(const clap_plugin_t *p) noexcept
static bool sNotePortGet(const clap_plugin_t *, uint32_t index, bool isInput, clap_note_port_info_t *info) noexcept
Definition DSParkClap.h:861
static clap_id sConfigCurrent(const clap_plugin_t *p) noexcept
Definition DSParkClap.h:815
static constexpr size_t kNumParams
Definition DSParkClap.h:75
const clap_host_latency_t * hostLatency
Definition DSParkClap.h:92
std::atomic< double > shadow[kNumParams==0 ? 1 :kNumParams]
Definition DSParkClap.h:111
static bool sStateSave(const clap_plugin_t *p, const clap_ostream_t *stream) noexcept
static const clap_plugin_audio_ports_config_t kPortsConfig
Definition DSParkClap.h:811
static void sDestroy(const clap_plugin_t *p) noexcept
Definition DSParkClap.h:458
static bool sParamTextToValue(const clap_plugin_t *, clap_id id, const char *text, double *out) noexcept
Definition DSParkClap.h:996
void applyNormalized(int index, double normalized) noexcept
Definition DSParkClap.h:157
static void sDeactivate(const clap_plugin_t *) noexcept
Definition DSParkClap.h:490
static void sParamFlush(const clap_plugin_t *p, const clap_input_events_t *in, const clap_output_events_t *out) noexcept
static const clap_plugin_render_t kRender
Definition DSParkClap.h:890
static const clap_plugin_audio_ports_config_info_t kPortsConfigInfo
Definition DSParkClap.h:850
bool applyBlockEvent(const BlockEvent &ev, int blockStart) noexcept
Definition DSParkClap.h:317
static bool sRenderHardRealtime(const clap_plugin_t *) noexcept
Definition DSParkClap.h:879
static const void * sGetExtension(const clap_plugin_t *, const char *id) noexcept
Definition DSParkClap.h:687
static bool sPresetFromLocation(const clap_plugin_t *p, uint32_t locationKind, const char *location, const char *loadKey) noexcept
Definition DSParkClap.h:896
static bool sParamInfo(const clap_plugin_t *, uint32_t index, clap_param_info_t *info) noexcept
Definition DSParkClap.h:936
static const clap_plugin_descriptor_t * descriptor() noexcept
static bool sStartProcessing(const clap_plugin_t *) noexcept
Definition DSParkClap.h:491
static bool sInit(const clap_plugin_t *p) noexcept
Definition DSParkClap.h:442
static const clap_plugin_t * create(const clap_host_t *host) noexcept
static int indexOfParamId(uint32_t id) noexcept
Definition DSParkClap.h:167
void collectEvents(const clap_input_events_t *in, uint32_t numSamples, BlockEvent *events, int &count) noexcept
Definition DSParkClap.h:306
static uint32_t sLatencyGet(const clap_plugin_t *p) noexcept
static const clap_plugin_preset_load_t kPresetLoad
Definition DSParkClap.h:925
static const clap_plugin_params_t kParams
static const clap_plugin_tail_t kTail
static bool sStateLoad(const clap_plugin_t *p, const clap_istream_t *stream) noexcept
static void fillConfig(clap_audio_ports_config_t *config, int width) noexcept
Definition DSParkClap.h:779
static bool sParamValueToText(const clap_plugin_t *, clap_id id, double value, char *out, uint32_t size) noexcept
Definition DSParkClap.h:980
static bool sConfigGet(const clap_plugin_t *, uint32_t index, clap_audio_ports_config_t *config) noexcept
Definition DSParkClap.h:795
static uint32_t sPortCount(const clap_plugin_t *, bool isInput) noexcept
Definition DSParkClap.h:736
static const clap_plugin_note_ports_t kNotePorts
Definition DSParkClap.h:873