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;
112#if defined(DSPARK_PLUGIN_WEBVIEW)
116 bool guiActive =
false;
117 double guiScale = 1.0;
118 int guiWidth = 0, guiHeight = 0;
120#if defined(__linux__)
122 const clap_host_timer_support_t* hostTimer =
nullptr;
123 clap_id guiTimerId = CLAP_INVALID_ID;
126 enum : uint8_t { kUiGestureBegin = 0, kUiValue = 1, kUiGestureEnd = 2 };
133 static constexpr uint32_t kUiQueueSize = 256;
134 UiEvent uiQueue[kUiQueueSize] {};
135 std::atomic<uint32_t> uiHead { 0 }, uiTail { 0 };
142 std::memory_order_relaxed);
147 return static_cast<Plugin*
>(p->plugin_data);
154 if (normalized != normalized)
return;
155 const auto& spec = P::parameters[
static_cast<size_t>(index)];
156 shadow[
static_cast<size_t>(index)].store(normalized, std::memory_order_relaxed);
157 user.setParameter(index,
static_cast<float>(
toPlain(spec, normalized)));
163 if (
hash32(P::parameters[i].
id) ==
id)
return static_cast<int>(i);
188 const int now =
user.getLatency();
193 if (
host !=
nullptr &&
host->request_callback !=
nullptr)
205 if (ev ==
nullptr || ev->space_id != CLAP_CORE_EVENT_SPACE_ID)
return;
207 out.
offset =
static_cast<int32_t
>(ev->time);
208 if (numSamples > 0 && out.offset >=
static_cast<int32_t
>(numSamples))
209 out.offset =
static_cast<int32_t
>(numSamples) - 1;
210 if (out.offset < 0) out.offset = 0;
212 if (ev->type == CLAP_EVENT_PARAM_VALUE)
214 const auto* pv =
reinterpret_cast<const clap_event_param_value_t*
>(ev);
218 out.value = pv->value;
225 out.paramId = pv->param_id;
226 out.value =
toNormalized(P::parameters[
static_cast<size_t>(idx)],
232 if (ev->type == CLAP_EVENT_NOTE_ON || ev->type == CLAP_EVENT_NOTE_OFF)
234 const auto* note =
reinterpret_cast<const clap_event_note_t*
>(ev);
236 out.midi.type = ev->type == CLAP_EVENT_NOTE_ON
238 out.midi.channel =
static_cast<uint8_t
>(
239 note->channel >= 0 ? note->channel & 0x0F : 0);
240 out.midi.note =
static_cast<uint8_t
>(
241 note->key >= 0 ? note->key & 0x7F : 0);
242 out.midi.value =
static_cast<float>(note->velocity);
244 else if (ev->type == CLAP_EVENT_MIDI)
246 const auto* midi =
reinterpret_cast<const clap_event_midi_t*
>(ev);
247 const uint8_t status = midi->data[0] & 0xF0u;
248 const uint8_t channel = midi->data[0] & 0x0Fu;
249 const uint8_t d1 = midi->data[1] & 0x7Fu;
250 const uint8_t d2 = midi->data[2] & 0x7Fu;
252 out.midi.channel = channel;
259 out.midi.value =
static_cast<float>(d2) / 127.0f;
264 out.midi.value =
static_cast<float>(d2) / 127.0f;
269 out.midi.value =
static_cast<float>(d2) / 127.0f;
274 out.midi.value =
static_cast<float>(d2) / 127.0f;
278 out.midi.value =
static_cast<float>(d1) / 127.0f;
282 out.midi.value = (
static_cast<float>((d2 << 7) | d1) - 8192.0f)
302 if (in ==
nullptr)
return;
303 const uint32_t n = in->size(in);
304 for (uint32_t i = 0; i < n; ++i)
305 collectEvent(in->get(in, i), numSamples, events, count);
315 bypass.store(ev.value >= 0.5, std::memory_order_relaxed);
323 user.handleMidiEvent(midi);
342 if (t ==
nullptr)
return;
343 constexpr double kBeatFactor =
static_cast<double>(CLAP_BEATTIME_FACTOR);
345 info.
playing = (t->flags & CLAP_TRANSPORT_IS_PLAYING) != 0;
346 info.recording = (t->flags & CLAP_TRANSPORT_IS_RECORDING) != 0;
347 info.looping = (t->flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE) != 0;
348 if ((t->flags & CLAP_TRANSPORT_HAS_TEMPO) != 0)
350 info.tempoBpm = t->tempo;
351 info.tempoValid =
true;
353 if ((t->flags & CLAP_TRANSPORT_HAS_BEATS_TIMELINE) != 0)
355 info.ppqPosition =
static_cast<double>(t->song_pos_beats) / kBeatFactor;
356 info.barStartPpq =
static_cast<double>(t->bar_start) / kBeatFactor;
357 info.positionValid =
true;
359 static_cast<double>(t->loop_start_beats) / kBeatFactor;
360 info.loopEndPpq =
static_cast<double>(t->loop_end_beats) / kBeatFactor;
361 info.loopValid =
true;
363 if ((t->flags & CLAP_TRANSPORT_HAS_TIME_SIGNATURE) != 0)
365 info.timeSigNumerator = t->tsig_num;
366 info.timeSigDenominator = t->tsig_denom;
367 info.timeSigValid =
true;
369 user.setTransport(info);
375#if defined(DSPARK_PLUGIN_WEBVIEW)
378 void pushUiEvent(uint32_t paramId, uint8_t kind,
double value)
noexcept
380 const uint32_t tail = uiTail.load(std::memory_order_relaxed);
381 const uint32_t next = (tail + 1) & (kUiQueueSize - 1);
382 if (next == uiHead.load(std::memory_order_acquire))
return;
383 uiQueue[tail] = UiEvent { paramId, kind, value };
384 uiTail.store(next, std::memory_order_release);
387 void requestUiFlush() noexcept
394 void drainUiEvents(
const clap_output_events_t* out)
noexcept
396 if (out ==
nullptr)
return;
397 uint32_t head = uiHead.load(std::memory_order_relaxed);
398 const uint32_t tail = uiTail.load(std::memory_order_acquire);
401 const UiEvent& e = uiQueue[head];
402 if (e.kind == kUiValue)
404 clap_event_param_value_t ev {};
405 ev.header.size =
sizeof(ev);
406 ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
407 ev.header.type = CLAP_EVENT_PARAM_VALUE;
408 ev.param_id = e.paramId;
414 out->try_push(out, &ev.header);
418 clap_event_param_gesture_t ev {};
419 ev.header.size =
sizeof(ev);
420 ev.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
421 ev.header.type =
static_cast<uint16_t
>(
422 e.kind == kUiGestureBegin ? CLAP_EVENT_PARAM_GESTURE_BEGIN
423 : CLAP_EVENT_PARAM_GESTURE_END);
424 ev.param_id = e.paramId;
425 out->try_push(out, &ev.header);
427 head = (head + 1) & (kUiQueueSize - 1);
429 uiHead.store(head, std::memory_order_release);
435 static bool sInit(
const clap_plugin_t* p)
noexcept
438 if (s->host !=
nullptr)
440 s->hostParams =
static_cast<const clap_host_params_t*
>(
441 s->host->get_extension(s->host, CLAP_EXT_PARAMS));
442 s->hostLatency =
static_cast<const clap_host_latency_t*
>(
443 s->host->get_extension(s->host, CLAP_EXT_LATENCY));
445 s->hostPresetLoad =
static_cast<const clap_host_preset_load_t*
>(
446 s->host->get_extension(s->host, CLAP_EXT_PRESET_LOAD));
451 static void sDestroy(
const clap_plugin_t* p)
noexcept {
delete self(p); }
453 static bool sActivate(
const clap_plugin_t* p,
double sr,
460 s->user.prepare(spec);
462 s->user.setOfflineRendering(s->offlineRender);
464 s->user.setParameter(
static_cast<int>(i),
465 static_cast<float>(
toPlain(P::parameters[i],
466 s->shadow[i].load(std::memory_order_relaxed))));
471 s->bypassMix = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
473 s->cachedLatency = s->user.getLatency();
482 static void sReset(
const clap_plugin_t* p)
noexcept
487 s->bypassMix = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
490 static clap_process_status
sProcess(
const clap_plugin_t* p,
491 const clap_process_t* process)
noexcept
499#if defined(DSPARK_PLUGIN_WEBVIEW)
500 s->drainUiEvents(process->out_events);
503 const uint32_t n = process->frames_count;
507 s->collectEvents(process->in_events, n, events, eventCount);
510 bool paramsChanged =
false;
512 const bool canRender = n > 0 && s->prepared
513 && process->audio_outputs_count >= 1
514 && process->audio_outputs[0].data32 !=
nullptr
515 && process->audio_outputs[0].channel_count >= 1;
518 for (
int i = 0; i < eventCount; ++i)
519 paramsChanged |= s->applyBlockEvent(events[i], events[i].
offset);
520 if (paramsChanged) s->refreshLatency();
521 return CLAP_PROCESS_CONTINUE;
524 s->forwardTransport(process->transport);
526 auto& outPort = process->audio_outputs[0];
527 float** out = outPort.data32;
528 const uint32_t width =
static_cast<uint32_t
>(s->currentChannels);
529 uint32_t nCh = outPort.channel_count < width
530 ? outPort.channel_count : width;
531 while (nCh > 0 && out[nCh - 1] ==
nullptr) --nCh;
532 if (nCh < 1)
return CLAP_PROCESS_CONTINUE;
534 const bool haveIn = !
kIsInstrument && process->audio_inputs_count >= 1
535 && process->audio_inputs[0].data32 !=
nullptr;
536 float** in = haveIn ? process->audio_inputs[0].data32 :
nullptr;
538 if (n > s->dryL.size())
return CLAP_PROCESS_CONTINUE;
542 float* dry[2] = { s->dryL.data(), s->dryR.data() };
543 const size_t bytes =
sizeof(float) * n;
544 for (uint32_t ch = 0; ch < nCh; ++ch)
548 std::memset(out[ch], 0, bytes);
551 const float* src = (haveIn && in[ch] !=
nullptr) ? in[ch] : out[ch];
552 std::memcpy(dry[ch], src, bytes);
554 std::memcpy(out[ch], src, bytes);
558 float* scPtrs[2] = {
nullptr,
nullptr };
561 if (n > s->silence.size())
return CLAP_PROCESS_CONTINUE;
562 scPtrs[0] = scPtrs[1] = s->silence.data();
563 if (process->audio_inputs_count >= 2
564 && process->audio_inputs[1].data32 !=
nullptr)
566 const auto& scPort = process->audio_inputs[1];
567 const uint32_t scCh = scPort.channel_count < 2
568 ? scPort.channel_count : 2;
569 for (uint32_t ch = 0; ch < scCh; ++ch)
570 if (scPort.data32[ch] !=
nullptr)
571 scPtrs[ch] = scPort.data32[ch];
572 if (scCh == 1 && scPort.data32[0] !=
nullptr)
573 scPtrs[1] = scPort.data32[0];
579 auto processSegment = [&](
int start,
int length)
noexcept {
580 float* sub[2] = { out[0] + start,
581 nCh > 1 ? out[1] + start : out[0] + start };
586 float* scSub[2] = { scPtrs[0] + start, scPtrs[1] + start };
588 static_cast<int>(nCh), length);
589 s->user.processBlock(view, scView);
592 s->user.processBlock(view);
595 const int total =
static_cast<int>(n);
597 if (!sampleAccurateOf<P>())
599 for (; evIdx < eventCount; ++evIdx)
600 paramsChanged |= s->applyBlockEvent(events[evIdx], 0);
601 processSegment(0, total);
608 while (evIdx < eventCount
611 paramsChanged |= s->applyBlockEvent(events[evIdx++], pos);
613 if (evIdx < eventCount)
617 if (snapped < next) next = snapped;
621 processSegment(pos, next - pos);
624 for (; evIdx < eventCount; ++evIdx)
625 paramsChanged |= s->applyBlockEvent(events[evIdx], total);
628 const float target = s->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
629 if (s->bypassMix != target || target > 0.0f)
632 float mix = s->bypassMix;
633 for (uint32_t i = 0; i < n; ++i)
635 mix += (target > mix) ? step : ((target < mix) ? -step : 0.0f);
636 mix = mix < 0.0f ? 0.0f : (mix > 1.0f ? 1.0f : mix);
637 for (uint32_t ch = 0; ch < nCh; ++ch)
638 out[ch][i] += (dry[ch][i] - out[ch][i]) * mix;
643 if (paramsChanged) s->refreshLatency();
644 return CLAP_PROCESS_CONTINUE;
647 static const void*
sGetExtension(
const clap_plugin_t*,
const char*
id)
noexcept
649 if (std::strcmp(
id, CLAP_EXT_AUDIO_PORTS) == 0)
return &
kAudioPorts;
650 if (std::strcmp(
id, CLAP_EXT_PARAMS) == 0)
return &
kParams;
651 if (std::strcmp(
id, CLAP_EXT_STATE) == 0)
return &
kState;
652 if (std::strcmp(
id, CLAP_EXT_LATENCY) == 0)
return &
kLatency;
653 if (std::strcmp(
id, CLAP_EXT_TAIL) == 0)
return &
kTail;
656 if (std::strcmp(
id, CLAP_EXT_AUDIO_PORTS_CONFIG) == 0)
658 if (std::strcmp(
id, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO) == 0
659 || std::strcmp(
id, CLAP_EXT_AUDIO_PORTS_CONFIG_INFO_COMPAT) == 0)
663 if (std::strcmp(
id, CLAP_EXT_NOTE_PORTS) == 0)
return &
kNotePorts;
665 if (std::strcmp(
id, CLAP_EXT_RENDER) == 0)
return &
kRender;
667 if (std::strcmp(
id, CLAP_EXT_PRESET_LOAD) == 0
668 || std::strcmp(
id, CLAP_EXT_PRESET_LOAD_COMPAT) == 0)
670#if defined(DSPARK_PLUGIN_WEBVIEW)
675#if defined(__linux__)
677 && std::strcmp(
id, CLAP_EXT_TIMER_SUPPORT) == 0)
689 if (s->latencyDirty.exchange(
false, std::memory_order_acq_rel)
690 && s->hostLatency !=
nullptr && s->hostLatency->changed !=
nullptr)
691 s->hostLatency->changed(s->host);
696 static uint32_t
sPortCount(
const clap_plugin_t*,
bool isInput)
noexcept
706 static bool sPortGet(
const clap_plugin_t* p, uint32_t index,
bool isInput,
707 clap_audio_port_info_t* info)
noexcept
709 if (info ==
nullptr || index >=
sPortCount(p, isInput))
return false;
711 std::memset(info, 0,
sizeof(*info));
712 if (isInput && index == 1)
715 std::snprintf(info->name,
sizeof(info->name),
"Sidechain");
717 info->channel_count =
static_cast<uint32_t
>(width);
718 info->port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
719 info->in_place_pair = CLAP_INVALID_ID;
722 info->id = isInput ? 0 : 1;
723 std::snprintf(info->name,
sizeof(info->name),
"%s",
724 isInput ?
"Input" :
"Output");
725 info->flags = CLAP_AUDIO_PORT_IS_MAIN;
726 info->channel_count =
static_cast<uint32_t
>(width);
727 info->port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
729 : (isInput ? 1u : 0u);
737 static uint32_t
sConfigCount(
const clap_plugin_t*)
noexcept {
return 2; }
739 static void fillConfig(clap_audio_ports_config_t* config,
int width)
noexcept
741 std::memset(config, 0,
sizeof(*config));
742 config->id =
static_cast<clap_id
>(width);
743 std::snprintf(config->name,
sizeof(config->name),
"%s",
744 width == 1 ?
"Mono" :
"Stereo");
745 config->input_port_count =
sPortCount(
nullptr,
true);
746 config->output_port_count = 1;
748 config->main_input_channel_count =
static_cast<uint32_t
>(width);
749 config->main_input_port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
750 config->has_main_output =
true;
751 config->main_output_channel_count =
static_cast<uint32_t
>(width);
752 config->main_output_port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
756 clap_audio_ports_config_t* config)
noexcept
758 if (config ==
nullptr || index >= 2)
return false;
763 static bool sConfigSelect(
const clap_plugin_t* p, clap_id configId)
noexcept
765 const int width =
static_cast<int>(configId);
766 if (!supportsChannelCount<P>(width))
return false;
781 uint32_t portIndex,
bool isInput,
782 clap_audio_port_info_t* info)
noexcept
784 const int width =
static_cast<int>(configId);
785 if (info ==
nullptr || !supportsChannelCount<P>(width)
789 std::memset(info, 0,
sizeof(*info));
790 if (isInput && portIndex == 1)
793 std::snprintf(info->name,
sizeof(info->name),
"Sidechain");
794 info->channel_count =
static_cast<uint32_t
>(width);
795 info->port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
796 info->in_place_pair = CLAP_INVALID_ID;
799 info->id = isInput ? 0 : 1;
800 std::snprintf(info->name,
sizeof(info->name),
"%s",
801 isInput ?
"Input" :
"Output");
802 info->flags = CLAP_AUDIO_PORT_IS_MAIN;
803 info->channel_count =
static_cast<uint32_t
>(width);
804 info->port_type = width == 1 ? CLAP_PORT_MONO : CLAP_PORT_STEREO;
806 : (isInput ? 1u : 0u);
821 static bool sNotePortGet(
const clap_plugin_t*, uint32_t index,
bool isInput,
822 clap_note_port_info_t* info)
noexcept
824 if (info ==
nullptr || !isInput || index != 0 || !
HasMidi<P>)
return false;
825 std::memset(info, 0,
sizeof(*info));
827 info->supported_dialects = CLAP_NOTE_DIALECT_CLAP | CLAP_NOTE_DIALECT_MIDI;
828 info->preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
829 std::snprintf(info->name,
sizeof(info->name),
"MIDI In");
841 static bool sRenderSet(
const clap_plugin_t* p, clap_plugin_render_mode mode)
noexcept
844 s->offlineRender = mode == CLAP_RENDER_OFFLINE;
846 s->user.setOfflineRendering(s->offlineRender);
850 inline static const clap_plugin_render_t
kRender = {
857 const char* location,
const char* loadKey)
noexcept
862 if (locationKind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN
863 || loadKey ==
nullptr)
865 const int idx = std::atoi(loadKey);
867 s->applyFactoryPresetIdx(idx);
869 if (s->hostParams !=
nullptr && s->hostParams->rescan !=
nullptr)
870 s->hostParams->rescan(s->host, CLAP_PARAM_RESCAN_VALUES);
871 if (s->hostPresetLoad !=
nullptr && s->hostPresetLoad->loaded !=
nullptr)
872 s->hostPresetLoad->loaded(s->host, locationKind, location, loadKey);
897 clap_param_info_t* info)
noexcept
899 if (info ==
nullptr || index >
kNumParams)
return false;
900 std::memset(info, 0,
sizeof(*info));
904 std::snprintf(info->name,
sizeof(info->name),
"Bypass");
905 info->flags = CLAP_PARAM_IS_AUTOMATABLE | CLAP_PARAM_IS_STEPPED
906 | CLAP_PARAM_IS_BYPASS;
907 info->min_value = 0.0;
908 info->max_value = 1.0;
909 info->default_value = 0.0;
912 const auto& spec = P::parameters[index];
913 info->id =
hash32(spec.id);
914 std::snprintf(info->name,
sizeof(info->name),
"%s", spec.name);
915 info->flags = CLAP_PARAM_IS_AUTOMATABLE;
916 if (spec.steps > 0) info->flags |= CLAP_PARAM_IS_STEPPED;
917 info->min_value = spec.minValue;
918 info->max_value = spec.maxValue;
919 info->default_value = spec.defValue;
923 static bool sParamValue(
const clap_plugin_t* p, clap_id
id,
double* out)
noexcept
926 if (out ==
nullptr)
return false;
929 *out = s->bypass.load(std::memory_order_relaxed) ? 1.0 : 0.0;
933 if (idx < 0)
return false;
934 *out =
toPlain(P::parameters[
static_cast<size_t>(idx)],
935 s->shadow[
static_cast<size_t>(idx)].load(std::memory_order_relaxed));
940 char* out, uint32_t size)
noexcept
942 if (out ==
nullptr || size == 0)
return false;
945 std::snprintf(out, size,
"%s", value >= 0.5 ?
"On" :
"Off");
949 if (idx < 0)
return false;
950 formatValue(P::parameters[
static_cast<size_t>(idx)], value, out,
951 static_cast<int>(size));
956 const char* text,
double* out)
noexcept
958 if (text ==
nullptr || out ==
nullptr)
return false;
963 : (std::strtod(text,
nullptr) >= 0.5 ? 1.0 : 0.0);
967 if (idx < 0)
return false;
968 const Param& spec = P::parameters[
static_cast<size_t>(idx)];
975 const double v = std::strtod(text,
nullptr);
976 *out = std::max(
static_cast<double>(spec.
minValue),
977 std::min(
static_cast<double>(spec.
maxValue), v));
982 static void sParamFlush(
const clap_plugin_t* p,
const clap_input_events_t* in,
983 const clap_output_events_t* out)
noexcept
988 s->collectEvents(in, 0, events, eventCount);
990 bool paramsChanged =
false;
991 for (
int i = 0; i < eventCount; ++i)
992 paramsChanged |= s->applyBlockEvent(events[i], events[i].
offset);
993 if (paramsChanged) s->refreshLatency();
994#if defined(DSPARK_PLUGIN_WEBVIEW)
995 s->drainUiEvents(out);
1001 inline static const clap_plugin_params_t
kParams = {
1008 static bool sStateSave(
const clap_plugin_t* p,
const clap_ostream_t* stream)
noexcept
1013 norm[i] = s->shadow[i].load(std::memory_order_relaxed);
1014 const std::vector<uint8_t> blob =
buildState(
1016 kNumPresets > 0 ? s->currentProgram.load(std::memory_order_relaxed) : -1,
1017 s->bypass.load(std::memory_order_relaxed) ? 1 : 0);
1019 while (pos < blob.size())
1021 const int64_t put = stream->write(stream, blob.data() + pos,
1023 if (put <= 0)
return false;
1024 pos +=
static_cast<size_t>(put);
1029 static bool sStateLoad(
const clap_plugin_t* p,
const clap_istream_t* stream)
noexcept
1032 std::vector<uint8_t> blob;
1033 uint8_t chunk[4096];
1036 const int64_t got = stream->read(stream, chunk,
sizeof(chunk));
1037 if (got < 0)
return false;
1038 if (got == 0)
break;
1039 blob.insert(blob.end(), chunk, chunk + got);
1040 if (blob.size() > (16u << 20))
return false;
1044 norm[i] = s->shadow[i].load(std::memory_order_relaxed);
1046 int bypassState = -1;
1047 if (!
applyState(s->user, blob.data(), blob.size(), norm, &program,
1051 s->applyNormalized(
static_cast<int>(i), norm[i]);
1053 s->currentProgram.store(program, std::memory_order_relaxed);
1054 if (bypassState >= 0)
1055 s->bypass.store(bypassState != 0, std::memory_order_relaxed);
1056 s->refreshLatency();
1071 static uint32_t
sTailGet(
const clap_plugin_t* p)
noexcept
1076 const double seconds = s->user.getTailSeconds();
1077 if (!(seconds > 0.0))
return 0;
1078 const double samples = seconds * s->sampleRate;
1081 if (samples >= 4294967295.0)
return 0xFFFFFFFFu;
1082 return static_cast<uint32_t
>(samples);
1093#if defined(DSPARK_PLUGIN_WEBVIEW)
1099 static const char* clapWindowApi() noexcept
1102 return CLAP_WINDOW_API_WIN32;
1103#elif defined(__APPLE__)
1104 return CLAP_WINDOW_API_COCOA;
1106 return CLAP_WINDOW_API_X11;
1110 static void cbGuiSetParam(
void* context,
int index,
double plain)
noexcept
1112 auto* s =
static_cast<Plugin*
>(context);
1113 const Param& spec = P::parameters[
static_cast<size_t>(index)];
1115 s->applyNormalized(index, normalized);
1116 const double snapped =
toPlain(spec, normalized);
1117 const uint32_t
id =
hash32(spec.
id);
1118 if (s->guiEditActive[
static_cast<size_t>(index)])
1119 s->pushUiEvent(
id, kUiValue, snapped);
1123 s->pushUiEvent(
id, kUiGestureBegin, 0.0);
1124 s->pushUiEvent(
id, kUiValue, snapped);
1125 s->pushUiEvent(
id, kUiGestureEnd, 0.0);
1127 s->requestUiFlush();
1130 static void cbGuiBeginEdit(
void* context,
int index)
noexcept
1132 auto* s =
static_cast<Plugin*
>(context);
1133 s->guiEditActive[
static_cast<size_t>(index)] =
true;
1134 s->pushUiEvent(
hash32(P::parameters[
static_cast<size_t>(index)].id),
1135 kUiGestureBegin, 0.0);
1136 s->requestUiFlush();
1139 static void cbGuiEndEdit(
void* context,
int index)
noexcept
1141 auto* s =
static_cast<Plugin*
>(context);
1142 s->guiEditActive[
static_cast<size_t>(index)] =
false;
1143 s->pushUiEvent(
hash32(P::parameters[
static_cast<size_t>(index)].id),
1144 kUiGestureEnd, 0.0);
1145 s->requestUiFlush();
1148 static bool sGuiIsApiSupported(
const clap_plugin_t*,
const char* api,
1149 bool isFloating)
noexcept
1151 return !isFloating && api !=
nullptr && std::strcmp(api, clapWindowApi()) == 0
1155 static bool sGuiGetPreferredApi(
const clap_plugin_t*,
const char** api,
1156 bool* isFloating)
noexcept
1158 if (api ==
nullptr || isFloating ==
nullptr)
return false;
1159 *api = clapWindowApi();
1160 *isFloating =
false;
1164 static bool sGuiCreate(
const clap_plugin_t* p,
const char* api,
1165 bool isFloating)
noexcept
1168 if (!sGuiIsApiSupported(p, api, isFloating))
return false;
1169#if defined(__linux__)
1172 s->hostTimer = s->host !=
nullptr
1173 ?
static_cast<const clap_host_timer_support_t*
>(
1174 s->host->get_extension(s->host, CLAP_EXT_TIMER_SUPPORT))
1176 if (s->hostTimer ==
nullptr || s->hostTimer->register_timer ==
nullptr
1177 || !s->hostTimer->register_timer(s->host, 33, &s->guiTimerId))
1180 s->hostTimer =
nullptr;
1181 s->guiTimerId = CLAP_INVALID_ID;
1185 const EditorSize logical = editorSizeOf<P>();
1186 s->guiWidth =
static_cast<int>(logical.width * s->guiScale + 0.5);
1187 s->guiHeight =
static_cast<int>(logical.height * s->guiScale + 0.5);
1188 s->guiActive =
true;
1190 s->guiWidth, s->guiHeight, s->guiScale);
1194 static void sGuiDestroy(
const clap_plugin_t* p)
noexcept
1197#if defined(__linux__)
1198 if (s->hostTimer !=
nullptr && s->guiTimerId != CLAP_INVALID_ID)
1199 s->hostTimer->unregister_timer(s->host, s->guiTimerId);
1200 s->hostTimer =
nullptr;
1201 s->guiTimerId = CLAP_INVALID_ID;
1203 s->guiEditor.destroy();
1204 s->guiActive =
false;
1207 static bool sGuiSetScale(
const clap_plugin_t* p,
double scale)
noexcept
1209#if defined(__APPLE__)
1210 (void) p; (void) scale;
1214 if (!(scale > 0.0))
return false;
1217 const double previous = s->guiScale;
1218 s->guiScale = scale;
1219 s->guiWidth =
static_cast<int>(s->guiWidth * (scale / previous) + 0.5);
1220 s->guiHeight =
static_cast<int>(s->guiHeight * (scale / previous) + 0.5);
1221 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1226 static bool sGuiGetSize(
const clap_plugin_t* p, uint32_t* width,
1227 uint32_t* height)
noexcept
1230 if (width ==
nullptr || height ==
nullptr || !s->guiActive)
return false;
1231 *width =
static_cast<uint32_t
>(s->guiWidth);
1232 *height =
static_cast<uint32_t
>(s->guiHeight);
1236 static bool sGuiCanResize(
const clap_plugin_t*)
noexcept
1241 static bool sGuiGetResizeHints(
const clap_plugin_t*,
1242 clap_gui_resize_hints_t* hints)
noexcept
1244 if (hints ==
nullptr)
return false;
1246 const EditorSize logical = editorSizeOf<P>();
1250 hints->aspect_ratio_width =
static_cast<uint32_t
>(logical.width);
1251 hints->aspect_ratio_height =
static_cast<uint32_t
>(logical.height);
1255 static bool sGuiAdjustSize(
const clap_plugin_t* p, uint32_t* width,
1256 uint32_t* height)
noexcept
1259 if (width ==
nullptr || height ==
nullptr)
return false;
1262 constrainEditorSize<P>(w, h, s->guiScale);
1263 *width =
static_cast<uint32_t
>(w + 0.5);
1264 *height =
static_cast<uint32_t
>(h + 0.5);
1268 static bool sGuiSetSize(
const clap_plugin_t* p, uint32_t width,
1269 uint32_t height)
noexcept
1272 if (!s->guiActive)
return false;
1276 constrainEditorSize<P>(w, h, s->guiScale);
1278 s->guiWidth =
static_cast<int>(w + 0.5);
1279 s->guiHeight =
static_cast<int>(h + 0.5);
1280 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1284 static bool sGuiSetParent(
const clap_plugin_t* p,
1285 const clap_window_t* window)
noexcept
1288 if (window ==
nullptr || !s->guiActive)
return false;
1289#if defined(__linux__)
1291 void* parentHandle =
reinterpret_cast<void*
>(
1292 static_cast<std::uintptr_t
>(window->x11));
1294 void* parentHandle = window->ptr;
1297 parentHandle, s->guiWidth, s->guiHeight);
1298 const webview_ui::HostCallbacks callbacks {
1299 s, &cbGuiSetParam, &cbGuiBeginEdit, &cbGuiEndEdit
1301 if (!s->guiEditor.create(parentHandle, s->shadow, callbacks))
1305 int parentW = 0, parentH = 0;
1306 if (s->guiEditor.queryParentSize(parentW, parentH))
1308 s->guiWidth = parentW;
1309 s->guiHeight = parentH;
1311 s->guiEditor.setBounds(s->guiWidth, s->guiHeight);
1315 static bool sGuiSetTransient(
const clap_plugin_t*,
const clap_window_t*)
noexcept
1320 static void sGuiSuggestTitle(
const clap_plugin_t*,
const char*)
noexcept {}
1322 static bool sGuiShow(
const clap_plugin_t* p)
noexcept
1324 self(p)->guiEditor.setVisible(
true);
1328 static bool sGuiHide(
const clap_plugin_t* p)
noexcept
1330 self(p)->guiEditor.setVisible(
false);
1334 inline static const clap_plugin_gui_t kGui = {
1335 &sGuiIsApiSupported, &sGuiGetPreferredApi, &sGuiCreate, &sGuiDestroy,
1336 &sGuiSetScale, &sGuiGetSize, &sGuiCanResize, &sGuiGetResizeHints,
1337 &sGuiAdjustSize, &sGuiSetSize, &sGuiSetParent, &sGuiSetTransient,
1338 &sGuiSuggestTitle, &sGuiShow, &sGuiHide
1341#if defined(__linux__)
1345 static void sOnTimer(
const clap_plugin_t* p, clap_id)
noexcept
1347 self(p)->guiEditor.pump();
1350 inline static const clap_plugin_timer_support_t kTimer = { &sOnTimer };
1359 static const char* features[] = {
1361 : CLAP_PLUGIN_FEATURE_AUDIO_EFFECT,
1363 ? CLAP_PLUGIN_FEATURE_MONO : CLAP_PLUGIN_FEATURE_STEREO,
1366 static const clap_plugin_descriptor_t desc = {
1368 P::descriptor.productId,
1370 P::descriptor.vendor,
1374 P::descriptor.version,
1375 "Built with DSPark",
1381 static const clap_plugin_t*
create(
const clap_host_t*
host)
noexcept
1384 if (s ==
nullptr)
return nullptr;
1386 s->plugin.plugin_data = s;
1387 s->plugin.init = &
sInit;
1393 s->plugin.reset = &
sReset;
1401template <
typename P>
1404 static uint32_t
sCount(
const clap_plugin_factory_t*)
noexcept {
return 1; }
1406 static const clap_plugin_descriptor_t*
sDescriptor(
const clap_plugin_factory_t*,
1407 uint32_t index)
noexcept
1412 static const clap_plugin_t*
sCreate(
const clap_plugin_factory_t*,
1413 const clap_host_t* host,
1414 const char* pluginId)
noexcept
1416 if (pluginId ==
nullptr
1417 || std::strcmp(pluginId, P::descriptor.productId) != 0)
1438 static const std::array<char, 256>
id = [] {
1439 std::array<char, 256> buf {};
1440 std::snprintf(buf.data(), buf.size(),
"%s.presets",
1441 P::descriptor.productId);
1449 static const clap_preset_discovery_provider_descriptor_t desc = {
1450 CLAP_VERSION_INIT,
providerId(),
"DSPark Factory Presets",
1451 P::descriptor.vendor
1459 const clap_preset_discovery_indexer_t*
indexer =
nullptr;
1462 static bool sProviderInit(
const clap_preset_discovery_provider_t* provider)
noexcept
1464 const auto* state =
static_cast<const ProviderState*
>(provider->provider_data);
1465 if (state->indexer ==
nullptr || state->indexer->declare_location ==
nullptr)
1467 clap_preset_discovery_location_t location {};
1468 location.flags = CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT;
1469 location.name =
"Factory Presets";
1470 location.kind = CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN;
1471 location.location =
nullptr;
1472 return state->
indexer->declare_location(state->indexer, &location);
1477 delete static_cast<ProviderState*
>(provider->provider_data);
1481 uint32_t locationKind,
const char*,
1482 const clap_preset_discovery_metadata_receiver_t*
1487 if (locationKind != CLAP_PRESET_DISCOVERY_LOCATION_PLUGIN
1488 || receiver ==
nullptr)
1490 const clap_universal_plugin_id_t pluginId {
1491 "clap", P::descriptor.productId
1496 std::snprintf(key,
sizeof(key),
"%d", i);
1497 if (!receiver->begin_preset(receiver,
1498 P::factoryPresets[
static_cast<size_t>(i)].name, key))
1500 receiver->add_plugin_id(receiver, &pluginId);
1501 receiver->set_flags(receiver,
1502 CLAP_PRESET_DISCOVERY_IS_FACTORY_CONTENT);
1508 (void) locationKind;
1515 const char*)
noexcept
1522 const clap_preset_discovery_factory_t*, uint32_t index)
noexcept
1528 const clap_preset_discovery_factory_t*,
1529 const clap_preset_discovery_indexer_t* indexer,
1530 const char* providerIdAsked)
noexcept
1532 if (
kNumPresets == 0 || indexer ==
nullptr || providerIdAsked ==
nullptr
1533 || std::strcmp(providerIdAsked,
providerId()) != 0)
1536 if (state ==
nullptr)
return nullptr;
1539 state->provider.provider_data = state;
1544 return &state->provider;
1556 if (factoryId ==
nullptr)
return nullptr;
1557 if (std::strcmp(factoryId, CLAP_PLUGIN_FACTORY_ID) == 0)
1560 && (std::strcmp(factoryId, CLAP_PRESET_DISCOVERY_FACTORY_ID) == 0
1561 || std::strcmp(factoryId, CLAP_PRESET_DISCOVERY_FACTORY_ID_COMPAT) == 0))
1574#define DSPARK_CLAP_PLUGIN(PluginClass) \
1575 extern "C" CLAP_EXPORT const clap_plugin_entry_t clap_entry = { \
1576 CLAP_VERSION_INIT, \
1577 &dspark::plugin::clap_backend::Factory<PluginClass>::sEntryInit, \
1578 &dspark::plugin::clap_backend::Factory<PluginClass>::sEntryDeinit, \
1579 &dspark::plugin::clap_backend::Factory<PluginClass>::sEntryGetFactory \
Non-owning view over audio channel data.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
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).
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 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", "440 Hz").
void sortBlockEvents(BlockEvent *events, int count) noexcept
Stable insertion sort by offset (tiny N, allocation-free - audio-thread safe; equal offsets keep arri...
@ MonoOnly
Strictly 1->1 (rare; metering/utility plugins).
@ MonoAndStereo
Negotiates 1->1 or 2->2 with the host (default).
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...
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,...
int steps
0 continuous, 1 toggle, N discrete.
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
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