53#define DSPARK_PLUGIN_CLAP_INCLUDED 1
55#include "../DSParkPlugin.h"
75 static constexpr size_t kNumParams = P::parameters.size();
80 "an Instrument has no audio inputs; remove the sidechain "
81 "processBlock or use Category::Fx");
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.");
91 const clap_host_t*
host =
nullptr;
119#if defined(DSPARK_PLUGIN_WEBVIEW)
123 bool guiActive =
false;
124 double guiScale = 1.0;
125 int guiWidth = 0, guiHeight = 0;
127#if defined(__linux__)
129 const clap_host_timer_support_t* hostTimer =
nullptr;
130 clap_id guiTimerId = CLAP_INVALID_ID;
133 enum : uint8_t { kUiGestureBegin = 0, kUiValue = 1, kUiGestureEnd = 2 };
140 static constexpr uint32_t kUiQueueSize = 256;
141 UiEvent uiQueue[kUiQueueSize] {};
142 std::atomic<uint32_t> uiHead { 0 }, uiTail { 0 };
149 std::memory_order_relaxed);
154 return static_cast<Plugin*
>(p->plugin_data);
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)));
170 if (
hash32(P::parameters[i].
id) ==
id)
return static_cast<int>(i);
195 const int now =
user.getLatency();
200 if (
host !=
nullptr &&
host->request_callback !=
nullptr)
212 if (ev ==
nullptr || ev->space_id != CLAP_CORE_EVENT_SPACE_ID)
return;
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;
219 if (ev->type == CLAP_EVENT_PARAM_VALUE)
221 const auto* pv =
reinterpret_cast<const clap_event_param_value_t*
>(ev);
225 out.value = pv->value;
232 out.paramId = pv->param_id;
233 out.value =
toNormalized(P::parameters[
static_cast<size_t>(idx)],
239 if (ev->type == CLAP_EVENT_NOTE_ON || ev->type == CLAP_EVENT_NOTE_OFF)
241 const auto* note =
reinterpret_cast<const clap_event_note_t*
>(ev);
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);
251 else if (ev->type == CLAP_EVENT_MIDI)
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;
259 out.midi.channel = channel;
266 out.midi.value =
static_cast<float>(d2) / 127.0f;
271 out.midi.value =
static_cast<float>(d2) / 127.0f;
276 out.midi.value =
static_cast<float>(d2) / 127.0f;
281 out.midi.value =
static_cast<float>(d2) / 127.0f;
285 out.midi.value =
static_cast<float>(d1) / 127.0f;
289 out.midi.value = (
static_cast<float>((d2 << 7) | d1) - 8192.0f)
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);
322 bypass.store(ev.value >= 0.5, std::memory_order_relaxed);
330 user.handleMidiEvent(midi);
349 if (t ==
nullptr)
return;
350 constexpr double kBeatFactor =
static_cast<double>(CLAP_BEATTIME_FACTOR);
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)
357 info.tempoBpm = t->tempo;
358 info.tempoValid =
true;
360 if ((t->flags & CLAP_TRANSPORT_HAS_BEATS_TIMELINE) != 0)
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;
366 static_cast<double>(t->loop_start_beats) / kBeatFactor;
367 info.loopEndPpq =
static_cast<double>(t->loop_end_beats) / kBeatFactor;
368 info.loopValid =
true;
370 if ((t->flags & CLAP_TRANSPORT_HAS_TIME_SIGNATURE) != 0)
372 info.timeSigNumerator = t->tsig_num;
373 info.timeSigDenominator = t->tsig_denom;
374 info.timeSigValid =
true;
376 user.setTransport(info);
382#if defined(DSPARK_PLUGIN_WEBVIEW)
385 void pushUiEvent(uint32_t paramId, uint8_t kind,
double value)
noexcept
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;
390 uiQueue[tail] = UiEvent { paramId, kind, value };
391 uiTail.store(next, std::memory_order_release);
394 void requestUiFlush() noexcept
401 void drainUiEvents(
const clap_output_events_t* out)
noexcept
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);
408 const UiEvent& e = uiQueue[head];
409 if (e.kind == kUiValue)
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;
421 out->try_push(out, &ev.header);
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);
434 head = (head + 1) & (kUiQueueSize - 1);
436 uiHead.store(head, std::memory_order_release);
442 static bool sInit(
const clap_plugin_t* p)
noexcept
445 if (s->host !=
nullptr)
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));
452 s->hostPresetLoad =
static_cast<const clap_host_preset_load_t*
>(
453 s->host->get_extension(s->host, CLAP_EXT_PRESET_LOAD));
458 static void sDestroy(
const clap_plugin_t* p)
noexcept {
delete self(p); }
460 static bool sActivate(
const clap_plugin_t* p,
double sr,
467 s->user.prepare(spec);
469 s->user.setOfflineRendering(s->offlineRender);
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))));
478 s->bypassMix = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
481 const int latency = s->user.getLatency();
482 s->cachedLatency.store(latency, std::memory_order_relaxed);
494 static void sReset(
const clap_plugin_t* p)
noexcept
500 s->bypassMix = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
503 static clap_process_status
sProcess(
const clap_plugin_t* p,
504 const clap_process_t* process)
noexcept
512#if defined(DSPARK_PLUGIN_WEBVIEW)
513 s->drainUiEvents(process->out_events);
516 const uint32_t n = process->frames_count;
520 s->collectEvents(process->in_events, n, events, eventCount);
523 bool paramsChanged =
false;
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;
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;
537 s->forwardTransport(process->transport);
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;
545 if (nCh < 1)
return CLAP_PROCESS_CONTINUE;
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;
554 float* scHost[2] = {
nullptr,
nullptr };
557 if (process->audio_inputs_count >= 2
558 && process->audio_inputs[1].data32 !=
nullptr)
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];
566 scHost[1] = scPort.data32[0];
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() };
579 for (
int c0 = 0; c0 < total; c0 += chunkMax)
581 const int len = total - c0 < chunkMax ? total - c0 : chunkMax;
582 const int cEnd = c0 + len;
587 const size_t bytes =
sizeof(float) *
static_cast<size_t>(len);
588 for (uint32_t ch = 0; ch < nCh; ++ch)
592 std::memset(out[ch] + c0, 0, bytes);
595 const float* src = out[ch];
598 const float* x = in[ch < inCh ? ch : inCh - 1];
599 if (x !=
nullptr) src = x;
601 std::memcpy(dry[ch], src + c0, bytes);
603 std::memcpy(out[ch] + c0, src + c0, bytes);
606 s->dryDelay.process(dry,
static_cast<int>(nCh), len,
607 s->cachedLatency.load(std::memory_order_relaxed));
609 float* scChunk[2] = {
nullptr,
nullptr };
611 for (
int ch = 0; ch < 2; ++ch)
612 scChunk[ch] = scHost[ch] !=
nullptr ? scHost[ch] + c0
618 auto processSegment = [&](
int start,
int length)
noexcept {
619 float* sub[2] = { out[0] + start,
620 nCh > 1 ? out[1] + start : out[0] + start };
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);
632 s->user.processBlock(view);
635 if (!sampleAccurateOf<P>())
637 for (; evIdx < eventCount && events[evIdx].
offset < cEnd; ++evIdx)
638 paramsChanged |= s->applyBlockEvent(events[evIdx], c0);
639 processSegment(c0, len);
646 while (evIdx < eventCount
649 paramsChanged |= s->applyBlockEvent(events[evIdx++], pos);
651 if (evIdx < eventCount)
655 if (snapped < next) next = snapped;
659 processSegment(pos, next - pos);
665 const float target = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
666 if (s->bypassMix != target || target > 0.0f)
669 float mix = s->bypassMix;
670 for (
int i = 0; i < len; ++i)
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;
680 for (; evIdx < eventCount; ++evIdx)
681 paramsChanged |= s->applyBlockEvent(events[evIdx], total);
683 if (paramsChanged) s->refreshLatency();
684 return CLAP_PROCESS_CONTINUE;
687 static const void*
sGetExtension(
const clap_plugin_t*,
const char*
id)
noexcept
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;
696 if (std::strcmp(
id, CLAP_EXT_AUDIO_PORTS_CONFIG) == 0)
698 if (std::strcmp(
id, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO) == 0
699 || std::strcmp(
id, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO_COMPAT) == 0)
703 if (std::strcmp(
id, CLAP_EXT_NOTE_PORTS) == 0)
return &
kNotePorts;
705 if (std::strcmp(
id, CLAP_EXT_RENDER) == 0)
return &
kRender;
707 if (std::strcmp(
id, CLAP_EXT_PRESET_LOAD) == 0
708 || std::strcmp(
id, CLAP_EXT_PRESET_LOAD_COMPAT) == 0)
710#if defined(DSPARK_PLUGIN_WEBVIEW)
715#if defined(__linux__)
717 && std::strcmp(
id, CLAP_EXT_TIMER_SUPPORT) == 0)
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);
736 static uint32_t
sPortCount(
const clap_plugin_t*,
bool isInput)
noexcept
746 static bool sPortGet(
const clap_plugin_t* p, uint32_t index,
bool isInput,
747 clap_audio_port_info_t* info)
noexcept
749 if (info ==
nullptr || index >=
sPortCount(p, isInput))
return false;
751 std::memset(info, 0,
sizeof(*info));
752 if (isInput && index == 1)
755 std::snprintf(info->name,
sizeof(info->name),
"Sidechain");
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;
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;
769 : (isInput ? 1u : 0u);
777 static uint32_t
sConfigCount(
const clap_plugin_t*)
noexcept {
return 2; }
779 static void fillConfig(clap_audio_ports_config_t* config,
int width)
noexcept
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;
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;
796 clap_audio_ports_config_t* config)
noexcept
798 if (config ==
nullptr || index >= 2)
return false;
803 static bool sConfigSelect(
const clap_plugin_t* p, clap_id configId)
noexcept
805 const int width =
static_cast<int>(configId);
806 if (!supportsChannelCount<P>(width))
return false;
821 uint32_t portIndex,
bool isInput,
822 clap_audio_port_info_t* info)
noexcept
824 const int width =
static_cast<int>(configId);
825 if (info ==
nullptr || !supportsChannelCount<P>(width)
829 std::memset(info, 0,
sizeof(*info));
830 if (isInput && portIndex == 1)
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;
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;
846 : (isInput ? 1u : 0u);
861 static bool sNotePortGet(
const clap_plugin_t*, uint32_t index,
bool isInput,
862 clap_note_port_info_t* info)
noexcept
864 if (info ==
nullptr || !isInput || index != 0 || !
HasMidi<P>)
return false;
865 std::memset(info, 0,
sizeof(*info));
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");
881 static bool sRenderSet(
const clap_plugin_t* p, clap_plugin_render_mode mode)
noexcept
884 s->offlineRender = mode == CLAP_RENDER_OFFLINE;
886 s->user.setOfflineRendering(s->offlineRender);
890 inline static const clap_plugin_render_t
kRender = {
897 const char* location,
const char* loadKey)
noexcept
902 if (locationKind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN
903 || loadKey ==
nullptr)
905 const int idx = std::atoi(loadKey);
907 s->applyFactoryPresetIdx(idx);
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);
937 clap_param_info_t* info)
noexcept
939 if (info ==
nullptr || index >
kNumParams)
return false;
940 std::memset(info, 0,
sizeof(*info));
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;
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;
964 static bool sParamValue(
const clap_plugin_t* p, clap_id
id,
double* out)
noexcept
967 if (out ==
nullptr)
return false;
970 *out = s->bypass.load(std::memory_order_relaxed) ? 1.0 : 0.0;
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));
981 char* out, uint32_t size)
noexcept
983 if (out ==
nullptr || size == 0)
return false;
986 std::snprintf(out, size,
"%s", value >= 0.5 ?
"On" :
"Off");
990 if (idx < 0)
return false;
991 formatValue(P::parameters[
static_cast<size_t>(idx)], value, out,
992 static_cast<int>(size));
997 const char* text,
double* out)
noexcept
999 if (text ==
nullptr || out ==
nullptr)
return false;
1004 : (std::strtod(text,
nullptr) >= 0.5 ? 1.0 : 0.0);
1008 if (idx < 0)
return false;
1011 return parseValue(P::parameters[
static_cast<size_t>(idx)], text, *out);
1014 static void sParamFlush(
const clap_plugin_t* p,
const clap_input_events_t* in,
1015 const clap_output_events_t* out)
noexcept
1020 s->collectEvents(in, 0, 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);
1033 inline static const clap_plugin_params_t
kParams = {
1040 static bool sStateSave(
const clap_plugin_t* p,
const clap_ostream_t* stream)
noexcept
1045 norm[i] = s->shadow[i].load(std::memory_order_relaxed);
1046 const std::vector<uint8_t> blob =
buildState(
1048 kNumPresets > 0 ? s->currentProgram.load(std::memory_order_relaxed) : -1,
1049 s->bypass.load(std::memory_order_relaxed) ? 1 : 0);
1051 while (pos < blob.size())
1053 const int64_t put = stream->write(stream, blob.data() + pos,
1055 if (put <= 0)
return false;
1056 pos +=
static_cast<size_t>(put);
1061 static bool sStateLoad(
const clap_plugin_t* p,
const clap_istream_t* stream)
noexcept
1064 std::vector<uint8_t> blob;
1065 uint8_t chunk[4096];
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;
1076 norm[i] = s->shadow[i].load(std::memory_order_relaxed);
1078 int bypassState = -1;
1079 if (!
applyState(s->user, blob.data(), blob.size(), norm, &program,
1083 s->applyNormalized(
static_cast<int>(i), norm[i]);
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();
1098 return static_cast<uint32_t
>(
1104 static uint32_t
sTailGet(
const clap_plugin_t* p)
noexcept
1109 const double seconds = s->user.getTailSeconds();
1110 if (!(seconds > 0.0))
return 0;
1111 const double samples = seconds * s->sampleRate;
1114 if (samples >= 4294967295.0)
return 0xFFFFFFFFu;
1115 return static_cast<uint32_t
>(samples);
1126#if defined(DSPARK_PLUGIN_WEBVIEW)
1132 static const char* clapWindowApi() noexcept
1135 return CLAP_WINDOW_API_WIN32;
1136#elif defined(__APPLE__)
1137 return CLAP_WINDOW_API_COCOA;
1139 return CLAP_WINDOW_API_X11;
1143 static void cbGuiSetParam(
void* context,
int index,
double plain)
noexcept
1145 auto* s =
static_cast<Plugin*
>(context);
1146 const Param& spec = P::parameters[
static_cast<size_t>(index)];
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);
1156 s->pushUiEvent(
id, kUiGestureBegin, 0.0);
1157 s->pushUiEvent(
id, kUiValue, snapped);
1158 s->pushUiEvent(
id, kUiGestureEnd, 0.0);
1160 s->requestUiFlush();
1163 static void cbGuiBeginEdit(
void* context,
int index)
noexcept
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();
1172 static void cbGuiEndEdit(
void* context,
int index)
noexcept
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();
1181 static bool sGuiIsApiSupported(
const clap_plugin_t*,
const char* api,
1182 bool isFloating)
noexcept
1184 return !isFloating && api !=
nullptr && std::strcmp(api, clapWindowApi()) == 0
1188 static bool sGuiGetPreferredApi(
const clap_plugin_t*,
const char** api,
1189 bool* isFloating)
noexcept
1191 if (api ==
nullptr || isFloating ==
nullptr)
return false;
1192 *api = clapWindowApi();
1193 *isFloating =
false;
1197 static bool sGuiCreate(
const clap_plugin_t* p,
const char* api,
1198 bool isFloating)
noexcept
1201 if (!sGuiIsApiSupported(p, api, isFloating))
return false;
1202#if defined(__linux__)
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))
1209 if (s->hostTimer ==
nullptr || s->hostTimer->register_timer ==
nullptr
1210 || !s->hostTimer->register_timer(s->host, 33, &s->guiTimerId))
1213 s->hostTimer =
nullptr;
1214 s->guiTimerId = CLAP_INVALID_ID;
1218 const EditorSize logical = editorSizeOf<P>();
1219 s->guiWidth =
toBoundedInt(logical.width * s->guiScale + 0.5,
1221 s->guiHeight =
toBoundedInt(logical.height * s->guiScale + 0.5,
1223 s->guiActive =
true;
1225 s->guiWidth, s->guiHeight, s->guiScale);
1229 static void sGuiDestroy(
const clap_plugin_t* p)
noexcept
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;
1238 s->guiEditor.destroy();
1239 s->guiActive =
false;
1242 static bool sGuiSetScale(
const clap_plugin_t* p,
double scale)
noexcept
1244#if defined(__APPLE__)
1245 (void) p; (void) scale;
1249 if (!(scale > 0.0))
return false;
1252 const double previous = s->guiScale;
1253 s->guiScale = scale;
1254 s->guiWidth =
toBoundedInt(s->guiWidth * (scale / previous) + 0.5,
1256 s->guiHeight =
toBoundedInt(s->guiHeight * (scale / previous) + 0.5,
1258 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1263 static bool sGuiGetSize(
const clap_plugin_t* p, uint32_t* width,
1264 uint32_t* height)
noexcept
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);
1273 static bool sGuiCanResize(
const clap_plugin_t*)
noexcept
1278 static bool sGuiGetResizeHints(
const clap_plugin_t*,
1279 clap_gui_resize_hints_t* hints)
noexcept
1281 if (hints ==
nullptr)
return false;
1283 const EditorSize logical = editorSizeOf<P>();
1287 hints->aspect_ratio_width =
static_cast<uint32_t
>(logical.width);
1288 hints->aspect_ratio_height =
static_cast<uint32_t
>(logical.height);
1292 static bool sGuiAdjustSize(
const clap_plugin_t* p, uint32_t* width,
1293 uint32_t* height)
noexcept
1296 if (width ==
nullptr || height ==
nullptr)
return false;
1299 constrainEditorSize<P>(w, h, s->guiScale);
1300 *width =
static_cast<uint32_t
>(
1302 *height =
static_cast<uint32_t
>(
1307 static bool sGuiSetSize(
const clap_plugin_t* p, uint32_t width,
1308 uint32_t height)
noexcept
1311 if (!s->guiActive)
return false;
1315 constrainEditorSize<P>(w, h, s->guiScale);
1319 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1323 static bool sGuiSetParent(
const clap_plugin_t* p,
1324 const clap_window_t* window)
noexcept
1327 if (window ==
nullptr || !s->guiActive)
return false;
1328#if defined(__linux__)
1330 void* parentHandle =
reinterpret_cast<void*
>(
1331 static_cast<std::uintptr_t
>(window->x11));
1333 void* parentHandle = window->ptr;
1336 parentHandle, s->guiWidth, s->guiHeight);
1337 const webview_ui::HostCallbacks callbacks {
1338 s, &cbGuiSetParam, &cbGuiBeginEdit, &cbGuiEndEdit
1340 if (!s->guiEditor.create(parentHandle, s->shadow, callbacks))
1344 int parentW = 0, parentH = 0;
1345 if (s->guiEditor.queryParentSize(parentW, parentH))
1347 s->guiWidth = parentW;
1348 s->guiHeight = parentH;
1350 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1354 static bool sGuiSetTransient(
const clap_plugin_t*,
const clap_window_t*)
noexcept
1359 static void sGuiSuggestTitle(
const clap_plugin_t*,
const char*)
noexcept {}
1361 static bool sGuiShow(
const clap_plugin_t* p)
noexcept
1363 self(p)->guiEditor.setVisible(
true);
1367 static bool sGuiHide(
const clap_plugin_t* p)
noexcept
1369 self(p)->guiEditor.setVisible(
false);
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
1380#if defined(__linux__)
1384 static void sOnTimer(
const clap_plugin_t* p, clap_id)
noexcept
1386 self(p)->guiEditor.pump();
1389 inline static const clap_plugin_timer_support_t kTimer = { &sOnTimer };
1398 static const char* features[] = {
1400 : CLAP_PLUGIN_FEATURE_AUDIO_EFFECT,
1402 ? CLAP_PLUGIN_FEATURE_MONO : CLAP_PLUGIN_FEATURE_STEREO,
1405 static const clap_plugin_descriptor_t desc = {
1407 P::descriptor.productId,
1409 P::descriptor.vendor,
1413 P::descriptor.version,
1414 "Built with DSPark",
1420 static const clap_plugin_t*
create(
const clap_host_t*
host)
noexcept
1423 if (s ==
nullptr)
return nullptr;
1425 s->plugin.plugin_data = s;
1426 s->plugin.init = &
sInit;
1432 s->plugin.reset = &
sReset;
1440template <
typename P>
1443 static uint32_t
sCount(
const clap_plugin_factory_t*)
noexcept {
return 1; }
1445 static const clap_plugin_descriptor_t*
sDescriptor(
const clap_plugin_factory_t*,
1446 uint32_t index)
noexcept
1451 static const clap_plugin_t*
sCreate(
const clap_plugin_factory_t*,
1452 const clap_host_t* host,
1453 const char* pluginId)
noexcept
1455 if (pluginId ==
nullptr
1456 || std::strcmp(pluginId, P::descriptor.productId) != 0)
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);
1488 static const clap_preset_discovery_provider_descriptor_t desc = {
1489 CLAP_VERSION_INIT,
providerId(),
"DSPark Factory Presets",
1490 P::descriptor.vendor
1498 const clap_preset_discovery_indexer_t*
indexer =
nullptr;
1501 static bool sProviderInit(
const clap_preset_discovery_provider_t* provider)
noexcept
1503 const auto* state =
static_cast<const ProviderState*
>(provider->provider_data);
1504 if (state->indexer ==
nullptr || state->indexer->declare_location ==
nullptr)
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);
1516 delete static_cast<ProviderState*
>(provider->provider_data);
1520 uint32_t locationKind,
const char*,
1521 const clap_preset_discovery_metadata_receiver_t*
1526 if (locationKind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN
1527 || receiver ==
nullptr)
1529 const clap_universal_plugin_id_t pluginId {
1530 "clap", P::descriptor.productId
1535 std::snprintf(key,
sizeof(key),
"%d", i);
1536 if (!receiver->begin_preset(receiver,
1537 P::factoryPresets[
static_cast<size_t>(i)].name, key))
1539 receiver->add_plugin_id(receiver, &pluginId);
1540 receiver->set_flags(receiver,
1541 CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT);
1547 (void) locationKind;
1554 const char*)
noexcept
1561 const clap_preset_discovery_factory_t*, uint32_t index)
noexcept
1567 const clap_preset_discovery_factory_t*,
1568 const clap_preset_discovery_indexer_t* indexer,
1569 const char* providerIdAsked)
noexcept
1571 if (
kNumPresets == 0 || indexer ==
nullptr || providerIdAsked ==
nullptr
1572 || std::strcmp(providerIdAsked,
providerId()) != 0)
1575 if (state ==
nullptr)
return nullptr;
1578 state->provider.provider_data = state;
1583 return &state->provider;
1595 if (factoryId ==
nullptr)
return nullptr;
1596 if (std::strcmp(factoryId, CLAP_PLUGIN_FACTORY_ID) == 0)
1599 && (std::strcmp(factoryId, CLAP_PRESET_DISCOVERY_FACTORY_ID) == 0
1600 || std::strcmp(factoryId, CLAP_PRESET_DISCOVERY_FACTORY_ID_COMPAT) == 0))
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 \
Non-owning view over audio channel data.
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...
static bool available() noexcept
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
constexpr uint32_t kBypassParamId
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.
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
clap_preset_discovery_provider_t provider
static bool sProviderInit(const clap_preset_discovery_provider_t *provider) noexcept
static const clap_preset_discovery_provider_descriptor_t * providerDescriptor() noexcept
static constexpr int kNumPresets
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
static void sStopProcessing(const clap_plugin_t *) noexcept
std::atomic< int > cachedLatency
const clap_host_params_t * hostParams
static void sReset(const clap_plugin_t *p) noexcept
std::atomic< int > currentProgram
static const clap_plugin_audio_ports_t kAudioPorts
static constexpr int kNumPresets
static bool sConfigSelect(const clap_plugin_t *p, clap_id configId) noexcept
static const clap_plugin_state_t kState
static Plugin * self(const clap_plugin_t *p) noexcept
void collectEvent(const clap_event_header_t *ev, uint32_t numSamples, BlockEvent *events, int &count) noexcept
const clap_host_preset_load_t * hostPresetLoad
static uint32_t sParamCount(const clap_plugin_t *) noexcept
static bool sParamValue(const clap_plugin_t *p, clap_id id, double *out) noexcept
static uint32_t sNotePortCount(const clap_plugin_t *, bool isInput) noexcept
static bool sConfigInfoGet(const clap_plugin_t *p, clap_id configId, uint32_t portIndex, bool isInput, clap_audio_port_info_t *info) noexcept
static clap_process_status sProcess(const clap_plugin_t *p, const clap_process_t *process) noexcept
void forwardTransport(const clap_event_transport_t *t) noexcept
static bool sPortGet(const clap_plugin_t *p, uint32_t index, bool isInput, clap_audio_port_info_t *info) noexcept
std::vector< float > dryR
static const clap_plugin_latency_t kLatency
static bool sActivate(const clap_plugin_t *p, double sr, uint32_t, uint32_t maxFrames) noexcept
Plugin(const clap_host_t *h) noexcept
static void sOnMainThread(const clap_plugin_t *p) noexcept
static bool sRenderSet(const clap_plugin_t *p, clap_plugin_render_mode mode) noexcept
static constexpr bool kIsInstrument
void applyFactoryPresetIdx(int idx) noexcept
std::vector< float > silence
static uint32_t sTailGet(const clap_plugin_t *p) noexcept
void refreshLatency() noexcept
static bool sNotePortGet(const clap_plugin_t *, uint32_t index, bool isInput, clap_note_port_info_t *info) noexcept
static clap_id sConfigCurrent(const clap_plugin_t *p) noexcept
static constexpr size_t kNumParams
const clap_host_latency_t * hostLatency
std::atomic< bool > bypass
std::atomic< double > shadow[kNumParams==0 ? 1 :kNumParams]
static bool sStateSave(const clap_plugin_t *p, const clap_ostream_t *stream) noexcept
static const clap_plugin_audio_ports_config_t kPortsConfig
static void sDestroy(const clap_plugin_t *p) noexcept
std::atomic< bool > latencyDirty
static bool sParamTextToValue(const clap_plugin_t *, clap_id id, const char *text, double *out) noexcept
std::vector< float > dryL
void applyNormalized(int index, double normalized) noexcept
static void sDeactivate(const clap_plugin_t *) noexcept
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
static const clap_plugin_audio_ports_config_info_t kPortsConfigInfo
bool applyBlockEvent(const BlockEvent &ev, int blockStart) noexcept
static bool sRenderHardRealtime(const clap_plugin_t *) noexcept
static const void * sGetExtension(const clap_plugin_t *, const char *id) noexcept
static bool sPresetFromLocation(const clap_plugin_t *p, uint32_t locationKind, const char *location, const char *loadKey) noexcept
static bool sParamInfo(const clap_plugin_t *, uint32_t index, clap_param_info_t *info) noexcept
static const clap_plugin_descriptor_t * descriptor() noexcept
static bool sStartProcessing(const clap_plugin_t *) noexcept
static bool sInit(const clap_plugin_t *p) noexcept
static const clap_plugin_t * create(const clap_host_t *host) noexcept
static int indexOfParamId(uint32_t id) noexcept
void collectEvents(const clap_input_events_t *in, uint32_t numSamples, BlockEvent *events, int &count) noexcept
static uint32_t sLatencyGet(const clap_plugin_t *p) noexcept
static const clap_plugin_preset_load_t kPresetLoad
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
static bool sParamValueToText(const clap_plugin_t *, clap_id id, double value, char *out, uint32_t size) noexcept
static bool sConfigGet(const clap_plugin_t *, uint32_t index, clap_audio_ports_config_t *config) noexcept
static uint32_t sPortCount(const clap_plugin_t *, bool isInput) noexcept
static const clap_plugin_note_ports_t kNotePorts