63#define DSPARK_PLUGIN_VST3_INCLUDED 1
65#include "../DSParkPlugin.h"
67#include "vst3_c_api.h"
77inline bool tuidEqual(
const Steinberg_TUID a,
const Steinberg_TUID b)
noexcept
79 return std::memcmp(a, b,
sizeof(Steinberg_TUID)) == 0;
86 for (; i < 127 && src[i] !=
'\0'; ++i)
87 dst[i] =
static_cast<Steinberg_char16
>(
static_cast<unsigned char>(src[i]));
91inline void copyAscii(
const char* src,
char* dst,
size_t cap)
noexcept
93 std::snprintf(dst, cap,
"%s", src ? src :
"");
97inline bool streamRead(Steinberg_IBStream* s,
void* dst, Steinberg_int32 bytes)
noexcept
99 auto* p =
static_cast<char*
>(dst);
102 Steinberg_int32 got = 0;
103 if (s->lpVtbl->read(s, p, bytes, &got) != Steinberg_kResultOk || got <= 0)
111inline bool streamWrite(Steinberg_IBStream* s,
const void* src, Steinberg_int32 bytes)
noexcept
113 auto* p =
static_cast<const char*
>(src);
116 Steinberg_int32 put = 0;
117 if (s->lpVtbl->write(s,
const_cast<char*
>(p), bytes, &put) != Steinberg_kResultOk
143 const uint32_t ctrl =
id & 0xFFu;
144 const uint32_t channel = (
id >> 8) & 0xFFu;
146 && (ctrl == 1 || ctrl == 64 || ctrl == 128 || ctrl == 129);
149inline constexpr uint32_t
midiProxyId(
int channel,
int controller)
noexcept
152 |
static_cast<uint32_t
>(controller);
159 ev.
channel =
static_cast<uint8_t
>((
id >> 8) & 0x0Fu);
160 ev.sampleOffset = offset;
161 const uint32_t ctrl =
id & 0xFFu;
165 ev.value =
static_cast<float>(normalized * 2.0 - 1.0);
167 else if (ctrl == 128)
170 ev.value =
static_cast<float>(normalized);
175 ev.note =
static_cast<uint8_t
>(ctrl);
176 ev.value =
static_cast<float>(normalized);
189 "an Instrument has no audio inputs; remove the sidechain "
190 "processBlock or use Category::Fx");
192 "an Instrument needs handleMidiEvent (see HasMidi): it has "
193 "no audio input to process");
194 static_assert(paramIdsUnique<P>(),
195 "two parameter ids share a hash32 (or collide with the "
196 "reserved PRGM/BYPS state ids): automation and state would "
197 "cross-wire. Rename one id.");
209 std::atomic<Steinberg_uint32>
refs { 1 };
212 Steinberg_Vst_ProcessSetup
setup { 0, 0, 0, 48000.0 };
229 std::atomic<Steinberg_Vst_IComponentHandler*>
handler {
nullptr };
241 std::memory_order_relaxed);
248 return reinterpret_cast<Plugin*
>(
static_cast<char*
>(iface)
249 -
static_cast<ptrdiff_t
>(lens) *
sizeof(
void*));
254 return reinterpret_cast<char*
>(
this) +
static_cast<ptrdiff_t
>(lens) *
sizeof(
void*);
259 Steinberg_tresult
query(
const Steinberg_TUID iid,
void** obj)
noexcept
261 if (obj ==
nullptr)
return Steinberg_kInvalidArgument;
263 if (
tuidEqual(iid, Steinberg_FUnknown_iid)
264 ||
tuidEqual(iid, Steinberg_IPluginBase_iid)
265 ||
tuidEqual(iid, Steinberg_Vst_IComponent_iid))
267 else if (
tuidEqual(iid, Steinberg_Vst_IAudioProcessor_iid))
269 else if (
tuidEqual(iid, Steinberg_Vst_IEditController_iid))
271 else if (
tuidEqual(iid, Steinberg_Vst_IProcessContextRequirements_iid))
277 if (*obj ==
nullptr)
return Steinberg_kNoInterface;
278 refs.fetch_add(1, std::memory_order_relaxed);
279 return Steinberg_kResultOk;
284 return refs.fetch_add(1, std::memory_order_relaxed) + 1;
289 const Steinberg_uint32 left =
refs.fetch_sub(1, std::memory_order_acq_rel) - 1;
290 if (left == 0)
delete this;
295 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sQuery(
void* self_,
296 const Steinberg_TUID iid,
301 static Steinberg_uint32 SMTG_STDMETHODCALLTYPE
sAddRef(
void* self_)
305 static Steinberg_uint32 SMTG_STDMETHODCALLTYPE
sRelease(
void* self_)
313 if (
hash32(P::parameters[i].
id) ==
id)
return static_cast<int>(i);
321 if (normalized != normalized)
return;
322 const auto& spec = P::parameters[
static_cast<size_t>(index)];
323 shadow[
static_cast<size_t>(index)].store(normalized, std::memory_order_relaxed);
324 user.setParameter(index,
static_cast<float>(
toPlain(spec, normalized)));
330 user.setParameter(
static_cast<int>(i),
331 static_cast<float>(
toPlain(P::parameters[i],
332 shadow[i].load(std::memory_order_relaxed))));
355 Steinberg_int32 flags = extraFlags;
358 const int now =
user.getLatency();
362 flags |= Steinberg_Vst_RestartFlags_kLatencyChanged;
366 if (
auto* h =
handler.load(std::memory_order_relaxed); h !=
nullptr)
367 h->lpVtbl->restartComponent(h, flags);
379 Steinberg_int32 numSamples,
382 if (changes ==
nullptr)
return;
383 const Steinberg_int32 queues = changes->lpVtbl->getParameterCount(changes);
384 for (Steinberg_int32 q = 0; q < queues; ++q)
386 Steinberg_Vst_IParamValueQueue* queue =
387 changes->lpVtbl->getParameterData(changes, q);
388 if (queue ==
nullptr)
continue;
389 const Steinberg_int32 points = queue->lpVtbl->getPointCount(queue);
390 if (points <= 0)
continue;
391 const Steinberg_Vst_ParamID
id = queue->lpVtbl->getParameterId(queue);
392 const Steinberg_int32 first = sampleAccurateOf<P>() ? 0 : points - 1;
393 for (Steinberg_int32 i = first; i < points; ++i)
395 Steinberg_int32 offset = 0;
396 Steinberg_Vst_ParamValue value = 0.0;
397 if (queue->lpVtbl->getPoint(queue, i, &offset, &value)
398 != Steinberg_kResultOk)
400 if (offset < 0) offset = 0;
401 if (numSamples > 0 && offset >= numSamples) offset = numSamples - 1;
416 Steinberg_int32 numSamples,
421 if (list ==
nullptr)
return;
422 const Steinberg_int32 n = list->lpVtbl->getEventCount(list);
423 for (Steinberg_int32 i = 0; i < n; ++i)
425 Steinberg_Vst_Event raw {};
426 if (list->lpVtbl->getEvent(list, i, &raw) != Steinberg_kResultOk)
429 if (raw.type == Steinberg_Vst_Event_EventTypes_kNoteOnEvent)
432 midi.channel =
static_cast<uint8_t
>(
433 raw.Steinberg_Vst_Event_noteOn.channel & 0x0F);
434 midi.note =
static_cast<uint8_t
>(
435 raw.Steinberg_Vst_Event_noteOn.pitch & 0x7F);
436 midi.value = raw.Steinberg_Vst_Event_noteOn.velocity;
438 else if (raw.type == Steinberg_Vst_Event_EventTypes_kNoteOffEvent)
441 midi.channel =
static_cast<uint8_t
>(
442 raw.Steinberg_Vst_Event_noteOff.channel & 0x0F);
443 midi.note =
static_cast<uint8_t
>(
444 raw.Steinberg_Vst_Event_noteOff.pitch & 0x7F);
445 midi.value = raw.Steinberg_Vst_Event_noteOff.velocity;
447 else if (raw.type == Steinberg_Vst_Event_EventTypes_kPolyPressureEvent)
450 midi.channel =
static_cast<uint8_t
>(
451 raw.Steinberg_Vst_Event_polyPressure.channel & 0x0F);
452 midi.note =
static_cast<uint8_t
>(
453 raw.Steinberg_Vst_Event_polyPressure.pitch & 0x7F);
454 midi.value = raw.Steinberg_Vst_Event_polyPressure.pressure;
458 Steinberg_int32 offset = raw.sampleOffset;
459 if (offset < 0) offset = 0;
460 if (numSamples > 0 && offset >= numSamples) offset = numSamples - 1;
483 bool& programChanged)
noexcept
488 bypass.store(ev.value >= 0.5, std::memory_order_relaxed);
496 user.handleMidiEvent(midi);
507 programChanged =
true;
517 ev.value, std::memory_order_relaxed);
519 ev.offset - blockStart);
521 user.handleMidiEvent(midi);
537 const int channel =
static_cast<int>((
id >> 8) & 0x0Fu);
538 const uint32_t ctrl =
id & 0xFFu;
539 const int slot = ctrl == 1 ? 0 : (ctrl == 64 ? 1 : (ctrl == 128 ? 2 : 3));
540 return channel * 4 + slot;
547 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sInitialize(
void*, Steinberg_FUnknown*)
548 {
return Steinberg_kResultOk; }
550 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sTerminate(
void*)
551 {
return Steinberg_kResultOk; }
554 {
return Steinberg_kResultFalse; }
556 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sSetIoMode(
void*, Steinberg_Vst_IoMode)
557 {
return Steinberg_kResultOk; }
568 Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir)
570 if (type == Steinberg_Vst_MediaTypes_kEvent)
571 return (
HasMidi<P> && dir == Steinberg_Vst_BusDirections_kInput) ? 1 : 0;
572 if (type != Steinberg_Vst_MediaTypes_kAudio)
return 0;
573 return dir == Steinberg_Vst_BusDirections_kInput ?
numInputBuses() : 1;
576 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sGetBusInfo(
void* self_,
577 Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir,
578 Steinberg_int32 index, Steinberg_Vst_BusInfo* bus)
580 if (bus ==
nullptr)
return Steinberg_kInvalidArgument;
581 if (type == Steinberg_Vst_MediaTypes_kEvent)
583 if (!
HasMidi<P> || dir != Steinberg_Vst_BusDirections_kInput
585 return Steinberg_kInvalidArgument;
586 bus->mediaType = Steinberg_Vst_MediaTypes_kEvent;
587 bus->direction = dir;
588 bus->channelCount = 16;
589 bus->busType = Steinberg_Vst_BusTypes_kMain;
590 bus->flags = Steinberg_Vst_BusInfo_BusFlags_kDefaultActive;
592 return Steinberg_kResultOk;
594 const Steinberg_int32 count =
595 dir == Steinberg_Vst_BusDirections_kInput ?
numInputBuses() : 1;
596 if (type != Steinberg_Vst_MediaTypes_kAudio || index < 0 || index >= count)
597 return Steinberg_kInvalidArgument;
598 bus->mediaType = Steinberg_Vst_MediaTypes_kAudio;
599 bus->direction = dir;
601 bus->busType = index == 0 ? Steinberg_Vst_BusTypes_kMain
602 : Steinberg_Vst_BusTypes_kAux;
603 bus->flags = Steinberg_Vst_BusInfo_BusFlags_kDefaultActive;
605 : (dir == Steinberg_Vst_BusDirections_kInput ?
"Input"
608 return Steinberg_kResultOk;
612 Steinberg_Vst_RoutingInfo*, Steinberg_Vst_RoutingInfo*)
613 {
return Steinberg_kNotImplemented; }
616 Steinberg_Vst_MediaType, Steinberg_Vst_BusDirection, Steinberg_int32,
618 {
return Steinberg_kResultOk; }
620 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sSetActive(
void* self_, Steinberg_TBool state)
625 const int maxBlock = p->setup.maxSamplesPerBlock > 0
626 ? p->setup.maxSamplesPerBlock : 512;
627 const double sr = p->setup.sampleRate > 0.0 ? p->setup.sampleRate : 48000.0;
629 p->user.prepare(spec);
631 p->user.setOfflineRendering(
632 p->setup.processMode == Steinberg_Vst_ProcessModes_kOffline);
633 p->applyAllShadows();
634 p->dryL.assign(
static_cast<size_t>(maxBlock), 0.0f);
635 p->dryR.assign(
static_cast<size_t>(maxBlock), 0.0f);
637 p->silence.assign(
static_cast<size_t>(maxBlock), 0.0f);
638 p->bypassMix = p->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
640 p->cachedLatency = p->user.getLatency();
643 return Steinberg_kResultOk;
647 Steinberg_IBStream* state)
650 if (state ==
nullptr)
return Steinberg_kInvalidArgument;
652 std::vector<uint8_t> blob;
656 Steinberg_int32 got = 0;
657 if (state->lpVtbl->read(state, chunk,
static_cast<Steinberg_int32
>(
sizeof(chunk)),
658 &got) != Steinberg_kResultOk || got <= 0)
660 blob.insert(blob.end(), chunk, chunk + got);
661 if (blob.size() > (16u << 20))
return Steinberg_kResultFalse;
665 norm[i] = p->shadow[i].load(std::memory_order_relaxed);
667 int bypassState = -1;
668 if (!
applyState(p->user, blob.data(), blob.size(), norm, &program,
670 return Steinberg_kResultFalse;
672 p->applyNormalized(
static_cast<int>(i), norm[i]);
674 p->currentProgram.store(program, std::memory_order_relaxed);
675 if (bypassState >= 0)
676 p->bypass.store(bypassState != 0, std::memory_order_relaxed);
678 return Steinberg_kResultOk;
682 Steinberg_IBStream* state)
685 if (state ==
nullptr)
return Steinberg_kInvalidArgument;
688 norm[i] = p->shadow[i].load(std::memory_order_relaxed);
691 kNumPresets > 0 ? p->currentProgram.load(std::memory_order_relaxed) : -1,
692 p->bypass.load(std::memory_order_relaxed) ? 1 : 0);
693 return streamWrite(state, blob.data(),
static_cast<Steinberg_int32
>(blob.size()))
694 ? Steinberg_kResultOk : Steinberg_kResultFalse;
698 &sQuery<0>, &sAddRef<0>, &sRelease<0>,
712 Steinberg_Vst_SpeakerArrangement* inputs, Steinberg_int32 numIns,
713 Steinberg_Vst_SpeakerArrangement* outputs, Steinberg_int32 numOuts)
716 || (numIns > 0 && inputs ==
nullptr) || outputs ==
nullptr)
717 return Steinberg_kResultFalse;
718 const Steinberg_Vst_SpeakerArrangement want = outputs[0];
720 if (want == Steinberg_Vst_SpeakerArr_kMono) channels = 1;
721 else if (want == Steinberg_Vst_SpeakerArr_kStereo) channels = 2;
722 if (channels == 0 || !supportsChannelCount<P>(channels))
723 return Steinberg_kResultFalse;
724 for (Steinberg_int32 i = 0; i < numIns; ++i)
725 if (inputs[i] != want)
726 return Steinberg_kResultFalse;
728 return Steinberg_kResultTrue;
732 Steinberg_Vst_BusDirection dir, Steinberg_int32 index,
733 Steinberg_Vst_SpeakerArrangement* arr)
735 const Steinberg_int32 count =
736 dir == Steinberg_Vst_BusDirections_kInput ?
numInputBuses() : 1;
737 if (index < 0 || index >= count || arr ==
nullptr)
738 return Steinberg_kInvalidArgument;
740 ? Steinberg_Vst_SpeakerArr_kMono : Steinberg_Vst_SpeakerArr_kStereo;
741 return Steinberg_kResultOk;
745 Steinberg_int32 size)
747 return size == Steinberg_Vst_SymbolicSampleSizes_kSample32
748 ? Steinberg_kResultTrue : Steinberg_kResultFalse;
757 Steinberg_Vst_ProcessSetup*
setup)
759 if (
setup ==
nullptr)
return Steinberg_kInvalidArgument;
761 return Steinberg_kResultOk;
764 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sSetProcessing(
void*, Steinberg_TBool)
765 {
return Steinberg_kResultOk; }
772 if (ctx ==
nullptr)
return;
774 const Steinberg_uint32 s = ctx->state;
775 t.
playing = (s & Steinberg_Vst_ProcessContext_StatesAndFlags_kPlaying) != 0;
776 t.recording = (s & Steinberg_Vst_ProcessContext_StatesAndFlags_kRecording) != 0;
777 t.looping = (s & Steinberg_Vst_ProcessContext_StatesAndFlags_kCycleActive) != 0;
778 if ((s & Steinberg_Vst_ProcessContext_StatesAndFlags_kTempoValid) != 0)
780 t.tempoBpm = ctx->tempo;
783 if ((s & Steinberg_Vst_ProcessContext_StatesAndFlags_kProjectTimeMusicValid) != 0)
785 t.ppqPosition = ctx->projectTimeMusic;
786 t.positionValid =
true;
788 if ((s & Steinberg_Vst_ProcessContext_StatesAndFlags_kBarPositionValid) != 0)
789 t.barStartPpq = ctx->barPositionMusic;
790 if ((s & Steinberg_Vst_ProcessContext_StatesAndFlags_kTimeSigValid) != 0)
792 t.timeSigNumerator = ctx->timeSigNumerator;
793 t.timeSigDenominator = ctx->timeSigDenominator;
794 t.timeSigValid =
true;
796 if ((s & Steinberg_Vst_ProcessContext_StatesAndFlags_kCycleValid) != 0)
798 t.loopStartPpq = ctx->cycleStartMusic;
799 t.loopEndPpq = ctx->cycleEndMusic;
802 user.setTransport(t);
808 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sProcess(
void* self_,
809 Steinberg_Vst_ProcessData* data)
812 if (data ==
nullptr)
return Steinberg_kInvalidArgument;
818 const Steinberg_int32 n = data->numSamples;
823 p->collectParameterChanges(data->inputParameterChanges, n, events, eventCount);
824 p->collectInputEvents(data->inputEvents, n, events, eventCount);
827 bool paramsChanged =
false;
828 bool programChanged =
false;
830 const bool canRender = n > 0
831 && data->symbolicSampleSize == Steinberg_Vst_SymbolicSampleSizes_kSample32
832 && data->numOutputs >= 1 && data->outputs !=
nullptr && p->prepared;
837 for (
int i = 0; i < eventCount; ++i)
838 paramsChanged |= p->applyBlockEvent(events[i], events[i].
offset,
840 if (paramsChanged || programChanged)
841 p->refreshLatency(programChanged
842 ? Steinberg_Vst_RestartFlags_kParamValuesChanged : 0);
843 if (n <= 0)
return Steinberg_kResultOk;
844 return data->symbolicSampleSize
845 == Steinberg_Vst_SymbolicSampleSizes_kSample32
846 ? Steinberg_kResultOk : Steinberg_kResultFalse;
849 p->forwardTransport(data->processContext);
851 auto& outBus = data->outputs[0];
852 float** out = outBus.Steinberg_Vst_AudioBusBuffers_channelBuffers32;
853 if (out ==
nullptr || outBus.numChannels < 1)
return Steinberg_kResultOk;
854 const int width = p->currentChannels;
855 int nCh = outBus.numChannels < width ? outBus.numChannels : width;
856 while (nCh > 0 && out[nCh - 1] ==
nullptr) --nCh;
857 if (nCh < 1)
return Steinberg_kResultOk;
860 && data->numInputs >= 1 && data->inputs !=
nullptr
861 && data->inputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32 !=
nullptr;
863 ? data->inputs[0].Steinberg_Vst_AudioBusBuffers_channelBuffers32 :
nullptr;
865 if (
static_cast<size_t>(n) > p->dryL.size())
866 return Steinberg_kResultOk;
872 float* dry[2] = { p->dryL.data(), p->dryR.data() };
873 const size_t bytes =
sizeof(float) *
static_cast<size_t>(n);
874 for (
int ch = 0; ch < nCh; ++ch)
878 std::memset(out[ch], 0, bytes);
881 const float* src = (haveIn && in[ch] !=
nullptr) ? in[ch] : out[ch];
882 std::memcpy(dry[ch], src, bytes);
884 std::memcpy(out[ch], src, bytes);
889 float* scPtrs[2] = {
nullptr,
nullptr };
892 if (
static_cast<size_t>(n) > p->silence.size())
893 return Steinberg_kResultOk;
894 scPtrs[0] = scPtrs[1] = p->silence.data();
895 if (data->numInputs >= 2 && data->inputs !=
nullptr
896 && data->inputs[1].Steinberg_Vst_AudioBusBuffers_channelBuffers32
899 const auto& scBus = data->inputs[1];
900 float** sc = scBus.Steinberg_Vst_AudioBusBuffers_channelBuffers32;
901 const int scCh = scBus.numChannels < 2 ? scBus.numChannels : 2;
902 for (
int ch = 0; ch < scCh; ++ch)
903 if (sc[ch] !=
nullptr)
905 if (scCh == 1 && sc[0] !=
nullptr)
913 auto processSegment = [&](
int start,
int length)
noexcept {
914 float* sub[2] = { out[0] + start,
915 nCh > 1 ? out[1] + start : out[0] + start };
920 float* scSub[2] = { scPtrs[0] + start, scPtrs[1] + start };
922 p->user.processBlock(view, scView);
925 p->user.processBlock(view);
929 if (!sampleAccurateOf<P>())
931 for (; evIdx < eventCount; ++evIdx)
932 paramsChanged |= p->applyBlockEvent(events[evIdx], 0, programChanged);
933 processSegment(0, n);
940 while (evIdx < eventCount
943 paramsChanged |= p->applyBlockEvent(events[evIdx++], pos,
946 if (evIdx < eventCount)
950 if (snapped < next) next = snapped;
954 processSegment(pos, next - pos);
957 for (; evIdx < eventCount; ++evIdx)
958 paramsChanged |= p->applyBlockEvent(events[evIdx], n, programChanged);
962 const float target = p->bypass.load(std::memory_order_relaxed) ? 1.0f : 0.0f;
963 if (p->bypassMix != target || target > 0.0f)
966 float mix = p->bypassMix;
967 for (Steinberg_int32 i = 0; i < n; ++i)
969 mix += (target > mix) ? step : ((target < mix) ? -step : 0.0f);
970 mix = mix < 0.0f ? 0.0f : (mix > 1.0f ? 1.0f : mix);
971 for (
int ch = 0; ch < nCh; ++ch)
972 out[ch][i] += (dry[ch][i] - out[ch][i]) * mix;
977 if (paramsChanged || programChanged)
978 p->refreshLatency(programChanged
979 ? Steinberg_Vst_RestartFlags_kParamValuesChanged : 0);
981 outBus.silenceFlags = 0;
982 return Steinberg_kResultOk;
990 const double seconds = p->user.getTailSeconds();
991 if (!(seconds > 0.0))
return 0;
992 const double samples = seconds
993 * (p->setup.sampleRate > 0 ? p->setup.sampleRate : 48000.0);
996 if (samples >= 4294967295.0)
return 0xFFFFFFFFu;
997 return static_cast<Steinberg_uint32
>(samples);
1007 &sQuery<1>, &sAddRef<1>, &sRelease<1>,
1017 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sCtrlInitialize(
void*, Steinberg_FUnknown*)
1018 {
return Steinberg_kResultOk; }
1021 {
return Steinberg_kResultOk; }
1024 Steinberg_IBStream* state)
1030 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sCtrlSetState(
void*, Steinberg_IBStream*)
1031 {
return Steinberg_kResultOk; }
1033 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sCtrlGetState(
void*, Steinberg_IBStream*)
1034 {
return Steinberg_kResultOk; }
1051 Steinberg_int32 index, Steinberg_Vst_ParameterInfo* info)
1055 return Steinberg_kInvalidArgument;
1056 std::memset(info, 0,
sizeof(*info));
1062 info->stepCount = 1;
1063 info->defaultNormalizedValue = 0.0;
1064 info->flags = Steinberg_Vst_ParameterInfo_ParameterFlags_kCanAutomate
1065 | Steinberg_Vst_ParameterInfo_ParameterFlags_kIsBypass;
1066 return Steinberg_kResultOk;
1074 info->defaultNormalizedValue = 0.0;
1075 info->unitId = Steinberg_Vst_kRootUnitId;
1076 info->flags = Steinberg_Vst_ParameterInfo_ParameterFlags_kCanAutomate
1077 | Steinberg_Vst_ParameterInfo_ParameterFlags_kIsList
1078 | Steinberg_Vst_ParameterInfo_ParameterFlags_kIsProgramChange;
1079 return Steinberg_kResultOk;
1086 const int channel = slot / 4;
1091 info->stepCount = 0;
1092 info->defaultNormalizedValue = ctrl == 129 ? 0.5 : 0.0;
1093 info->flags = Steinberg_Vst_ParameterInfo_ParameterFlags_kIsHidden;
1094 return Steinberg_kResultOk;
1096 const auto& spec = P::parameters[
static_cast<size_t>(index)];
1097 info->id =
hash32(spec.id);
1101 info->stepCount = spec.steps;
1102 info->defaultNormalizedValue =
toNormalized(spec, spec.defValue);
1103 info->flags = Steinberg_Vst_ParameterInfo_ParameterFlags_kCanAutomate;
1104 return Steinberg_kResultOk;
1108 Steinberg_Vst_ParamID
id, Steinberg_Vst_ParamValue normalized,
1109 Steinberg_Vst_String128 out)
1112 if (out ==
nullptr)
return Steinberg_kInvalidArgument;
1115 std::snprintf(text,
sizeof(text),
"%s", normalized >= 0.5 ?
"On" :
"Off");
1120 int idx =
static_cast<int>(normalized * (
kNumPresets - 1) + 0.5);
1122 std::snprintf(text,
sizeof(text),
"%s",
1123 P::factoryPresets[
static_cast<size_t>(idx)].name);
1127 std::snprintf(text,
sizeof(text),
"%.3f", normalized);
1131 if (idx < 0)
return Steinberg_kInvalidArgument;
1132 const auto& spec = P::parameters[
static_cast<size_t>(idx)];
1136 return Steinberg_kResultOk;
1140 Steinberg_Vst_ParamID
id, Steinberg_Vst_TChar*
string,
1141 Steinberg_Vst_ParamValue* normalized)
1144 if (
string ==
nullptr || normalized ==
nullptr)
return Steinberg_kInvalidArgument;
1147 for (; i < 63 &&
string[i] != 0; ++i)
1148 ascii[i] =
static_cast<char>(
string[i]);
1154 : (std::strtod(ascii,
nullptr) >= 0.5 ? 1.0 : 0.0);
1155 return Steinberg_kResultOk;
1161 for (
int presetIdx = 0; presetIdx <
kNumPresets; ++presetIdx)
1162 if (std::strcmp(ascii,
1163 P::factoryPresets[
static_cast<size_t>(presetIdx)].name)
1167 ?
static_cast<double>(presetIdx) / (
kNumPresets - 1)
1169 return Steinberg_kResultOk;
1172 const double v = std::strtod(ascii,
nullptr);
1174 return Steinberg_kResultOk;
1178 const double v = std::strtod(ascii,
nullptr);
1179 *normalized = v < 0.0 ? 0.0 : (v > 1.0 ? 1.0 : v);
1180 return Steinberg_kResultOk;
1183 if (idx < 0)
return Steinberg_kInvalidArgument;
1184 const Param& spec = P::parameters[
static_cast<size_t>(idx)];
1188 *normalized =
toNormalized(spec, std::strtod(ascii,
nullptr));
1189 return Steinberg_kResultOk;
1193 Steinberg_Vst_ParamID
id, Steinberg_Vst_ParamValue normalized)
1197 return static_cast<double>(
1198 static_cast<int>(normalized * (
kNumPresets - 1) + 0.5));
1201 return idx < 0 ? 0.0 :
toPlain(P::parameters[
static_cast<size_t>(idx)], normalized);
1205 Steinberg_Vst_ParamID
id, Steinberg_Vst_ParamValue plain)
1212 return idx < 0 ? 0.0 :
toNormalized(P::parameters[
static_cast<size_t>(idx)], plain);
1216 Steinberg_Vst_ParamID
id)
1220 return p->bypass.load(std::memory_order_relaxed) ? 1.0 : 0.0;
1223 ?
static_cast<double>(p->currentProgram.load(std::memory_order_relaxed))
1229 return p->midiProxyShadow[
static_cast<size_t>(
midiProxySlot(
id))]
1230 .load(std::memory_order_relaxed);
1233 return idx < 0 ? 0.0
1234 : p->shadow[
static_cast<size_t>(idx)].load(std::memory_order_relaxed);
1238 Steinberg_Vst_ParamID
id, Steinberg_Vst_ParamValue value)
1243 p->bypass.store(value >= 0.5, std::memory_order_relaxed);
1244 return Steinberg_kResultOk;
1250 p->applyFactoryPresetIdx(
1251 static_cast<int>(value * (
kNumPresets - 1) + 0.5));
1252 p->refreshLatency(Steinberg_Vst_RestartFlags_kParamValuesChanged);
1253 return Steinberg_kResultOk;
1262 .store(value, std::memory_order_relaxed);
1263 return Steinberg_kResultOk;
1267 if (idx < 0)
return Steinberg_kInvalidArgument;
1268 p->applyNormalized(idx, value);
1269 p->refreshLatency();
1270 return Steinberg_kResultOk;
1274 Steinberg_Vst_IComponentHandler*
handler)
1277 auto* old = p->handler.load(std::memory_order_relaxed);
1279 reinterpret_cast<Steinberg_FUnknown*
>(old)->lpVtbl->release(old);
1280 p->handler.store(
handler, std::memory_order_relaxed);
1282 reinterpret_cast<Steinberg_FUnknown*
>(
handler)->lpVtbl->addRef(
handler);
1283 return Steinberg_kResultOk;
1286 static Steinberg_IPlugView* SMTG_STDMETHODCALLTYPE
sCreateView(
void* self_,
1287 Steinberg_FIDString name);
1290 &sQuery<2>, &sAddRef<2>, &sRelease<2>,
1307 return Steinberg_Vst_IProcessContextRequirements_Flags_kNeedTempo
1308 | Steinberg_Vst_IProcessContextRequirements_Flags_kNeedTransportState
1309 | Steinberg_Vst_IProcessContextRequirements_Flags_kNeedProjectTimeMusic
1310 | Steinberg_Vst_IProcessContextRequirements_Flags_kNeedBarPositionMusic
1311 | Steinberg_Vst_IProcessContextRequirements_Flags_kNeedTimeSignature
1312 | Steinberg_Vst_IProcessContextRequirements_Flags_kNeedCycleMusic;
1318 &sQuery<3>, &sAddRef<3>, &sRelease<3>,
1327 static Steinberg_int32 SMTG_STDMETHODCALLTYPE
sGetUnitCount(
void*) {
return 1; }
1330 Steinberg_int32 unitIndex, Steinberg_Vst_UnitInfo* info)
1332 if (unitIndex != 0 || info ==
nullptr)
return Steinberg_kInvalidArgument;
1333 std::memset(info, 0,
sizeof(*info));
1334 info->id = Steinberg_Vst_kRootUnitId;
1335 info->parentUnitId = Steinberg_Vst_kNoParentUnitId;
1337 : Steinberg_Vst_kNoProgramListId;
1339 return Steinberg_kResultOk;
1348 Steinberg_int32 listIndex, Steinberg_Vst_ProgramListInfo* info)
1350 if (listIndex != 0 || info ==
nullptr ||
kNumPresets == 0)
1351 return Steinberg_kInvalidArgument;
1352 std::memset(info, 0,
sizeof(*info));
1356 return Steinberg_kResultOk;
1360 Steinberg_Vst_ProgramListID listId, Steinberg_int32 programIndex,
1361 Steinberg_Vst_String128 name)
1366 || programIndex < 0 || programIndex >=
kNumPresets)
1367 return Steinberg_kInvalidArgument;
1369 P::factoryPresets[
static_cast<size_t>(programIndex)].name, name);
1370 return Steinberg_kResultOk;
1375 (void) programIndex;
1377 return Steinberg_kInvalidArgument;
1382 Steinberg_Vst_ProgramListID, Steinberg_int32, Steinberg_Vst_CString,
1383 Steinberg_Vst_String128)
1384 {
return Steinberg_kResultFalse; }
1387 Steinberg_Vst_ProgramListID, Steinberg_int32)
1388 {
return Steinberg_kResultFalse; }
1391 Steinberg_Vst_ProgramListID, Steinberg_int32, Steinberg_int16,
1392 Steinberg_Vst_String128)
1393 {
return Steinberg_kResultFalse; }
1396 {
return Steinberg_Vst_kRootUnitId; }
1399 Steinberg_Vst_UnitID unitId)
1400 {
return unitId == Steinberg_Vst_kRootUnitId ? Steinberg_kResultOk
1401 : Steinberg_kInvalidArgument; }
1404 Steinberg_Vst_MediaType, Steinberg_Vst_BusDirection, Steinberg_int32,
1405 Steinberg_int32, Steinberg_Vst_UnitID* unitId)
1407 if (unitId ==
nullptr)
return Steinberg_kInvalidArgument;
1408 *unitId = Steinberg_Vst_kRootUnitId;
1409 return Steinberg_kResultOk;
1413 Steinberg_int32, Steinberg_int32,
struct Steinberg_IBStream*)
1414 {
return Steinberg_kNotImplemented; }
1416 inline static const Steinberg_Vst_IUnitInfoVtbl
kUnitVtbl = {
1417 &sQuery<4>, &sAddRef<4>, &sRelease<4>,
1430 Steinberg_int32 busIndex, Steinberg_int16 channel,
1431 Steinberg_Vst_CtrlNumber midiControllerNumber, Steinberg_Vst_ParamID*
id)
1433 if (!
HasMidi<P> ||
id ==
nullptr || busIndex != 0
1434 || channel < 0 || channel >= 16)
1435 return Steinberg_kResultFalse;
1436 const int ctrl = midiControllerNumber;
1437 if (ctrl != 1 && ctrl != 64 && ctrl != 128 && ctrl != 129)
1438 return Steinberg_kResultFalse;
1440 return Steinberg_kResultTrue;
1444 &sQuery<5>, &sAddRef<5>, &sRelease<5>,
1455#if defined(DSPARK_PLUGIN_WEBVIEW)
1462template <
typename P>
1465 static constexpr size_t kNumParams = Plugin<P>::kNumParams;
1469 const Steinberg_IPlugViewVtbl* viewVtbl;
1470 const Steinberg_IPlugViewContentScaleSupportVtbl* scaleVtbl;
1471#if defined(__linux__)
1472 const Steinberg_Linux_ITimerHandlerVtbl* timerVtbl;
1475 std::atomic<Steinberg_uint32> refs { 1 };
1477 Steinberg_IPlugFrame* frame =
nullptr;
1478 webview_ui::Editor<P> editor;
1482 bool correctingSize =
false;
1483 bool editActive[kNumParams == 0 ? 1 : kNumParams] {};
1484#if defined(__linux__)
1485 Steinberg_Linux_IRunLoop* runLoop =
nullptr;
1486 bool timerRegistered =
false;
1489 explicit View(Plugin<P>* plugin) noexcept : owner(plugin)
1491 viewVtbl = &kViewVtbl;
1492 scaleVtbl = &kScaleVtbl;
1493#if defined(__linux__)
1494 timerVtbl = &kTimerVtbl;
1496 owner->addRefImpl();
1497 const EditorSize logical = editorSizeOf<P>();
1498 width = logical.width;
1499 height = logical.height;
1504#if defined(__linux__)
1508 if (frame !=
nullptr)
1509 reinterpret_cast<Steinberg_FUnknown*
>(frame)->lpVtbl->release(frame);
1510 owner->releaseImpl();
1513#if defined(__linux__)
1517 void acquireRunLoop() noexcept
1519 if (frame ==
nullptr || runLoop !=
nullptr)
return;
1520 void* loop =
nullptr;
1521 if (
reinterpret_cast<Steinberg_FUnknown*
>(frame)->lpVtbl->queryInterface(
1522 frame, Steinberg_Linux_IRunLoop_iid, &loop) == Steinberg_kResultOk)
1523 runLoop =
static_cast<Steinberg_Linux_IRunLoop*
>(loop);
1526 void startTimer() noexcept
1529 if (runLoop !=
nullptr && !timerRegistered && editor.created())
1530 timerRegistered = runLoop->lpVtbl->registerTimer(runLoop,
1531 reinterpret_cast<Steinberg_Linux_ITimerHandler*
>(lensPtr(2)),
1532 33) == Steinberg_kResultOk;
1535 void stopTimer() noexcept
1537 if (runLoop !=
nullptr && timerRegistered)
1538 runLoop->lpVtbl->unregisterTimer(runLoop,
1539 reinterpret_cast<Steinberg_Linux_ITimerHandler*
>(lensPtr(2)));
1540 timerRegistered =
false;
1543 void releaseRunLoop() noexcept
1546 if (runLoop !=
nullptr)
1548 reinterpret_cast<Steinberg_FUnknown*
>(runLoop)->lpVtbl->release(runLoop);
1553 static void SMTG_STDMETHODCALLTYPE sOnTimer(
void* self_)
1555 fromLens(self_, 2)->editor.pump();
1560 static View* fromLens(
void* iface,
int lens)
noexcept
1562 return reinterpret_cast<View*
>(
static_cast<char*
>(iface)
1563 -
static_cast<ptrdiff_t
>(lens) *
sizeof(
void*));
1566 void* lensPtr(
int lens)
noexcept
1568 return reinterpret_cast<char*
>(
this) +
static_cast<ptrdiff_t
>(lens) *
sizeof(
void*);
1571 static const char* platformType() noexcept
1574 return Steinberg_kPlatformTypeHWND;
1575#elif defined(__APPLE__)
1576 return Steinberg_kPlatformTypeNSView;
1578 return Steinberg_kPlatformTypeX11EmbedWindowID;
1584 static void cbSetParam(
void* context,
int index,
double plain)
noexcept
1586 auto* view =
static_cast<View*
>(context);
1587 const Param& spec = P::parameters[
static_cast<size_t>(index)];
1589 view->owner->applyNormalized(index, normalized);
1590 if (
auto* handler = view->owner->handler.load(std::memory_order_relaxed))
1592 const Steinberg_Vst_ParamID
id =
hash32(spec.id);
1593 if (view->editActive[
static_cast<size_t>(index)])
1594 handler->lpVtbl->performEdit(handler,
id, normalized);
1598 handler->lpVtbl->beginEdit(handler,
id);
1599 handler->lpVtbl->performEdit(handler,
id, normalized);
1600 handler->lpVtbl->endEdit(handler,
id);
1605 static void cbBeginEdit(
void* context,
int index)
noexcept
1607 auto* view =
static_cast<View*
>(context);
1608 view->editActive[
static_cast<size_t>(index)] =
true;
1609 if (
auto* handler = view->owner->handler.load(std::memory_order_relaxed))
1610 handler->lpVtbl->beginEdit(handler,
1611 hash32(P::parameters[
static_cast<size_t>(index)].id));
1614 static void cbEndEdit(
void* context,
int index)
noexcept
1616 auto* view =
static_cast<View*
>(context);
1617 view->editActive[
static_cast<size_t>(index)] =
false;
1618 if (
auto* handler = view->owner->handler.load(std::memory_order_relaxed))
1619 handler->lpVtbl->endEdit(handler,
1620 hash32(P::parameters[
static_cast<size_t>(index)].id));
1625 Steinberg_tresult query(
const Steinberg_TUID iid,
void** obj)
noexcept
1627 if (obj ==
nullptr)
return Steinberg_kInvalidArgument;
1629 if (
tuidEqual(iid, Steinberg_FUnknown_iid)
1630 ||
tuidEqual(iid, Steinberg_IPlugView_iid))
1632 else if (
tuidEqual(iid, Steinberg_IPlugViewContentScaleSupport_iid))
1634#if defined(__linux__)
1635 else if (
tuidEqual(iid, Steinberg_Linux_ITimerHandler_iid))
1638 if (*obj ==
nullptr)
return Steinberg_kNoInterface;
1639 refs.fetch_add(1, std::memory_order_relaxed);
1640 return Steinberg_kResultOk;
1644 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sQuery(
void* self_,
1645 const Steinberg_TUID iid,
1647 {
return fromLens(self_, Lens)->query(iid, obj); }
1650 static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sAddRef(
void* self_)
1651 {
return fromLens(self_, Lens)->refs.fetch_add(1, std::memory_order_relaxed) + 1; }
1654 static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sRelease(
void* self_)
1656 auto* view = fromLens(self_, Lens);
1657 const Steinberg_uint32 left = view->refs.fetch_sub(1, std::memory_order_acq_rel) - 1;
1658 if (left == 0)
delete view;
1664 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sIsPlatformTypeSupported(
void*,
1665 Steinberg_FIDString type)
1667 return (webview_ui::Editor<P>::available() && type !=
nullptr
1668 && std::strcmp(type, platformType()) == 0)
1669 ? Steinberg_kResultTrue : Steinberg_kResultFalse;
1672 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sAttached(
void* self_,
void* parent,
1673 Steinberg_FIDString type)
1675 auto* view = fromLens(self_, 0);
1676 webview_ui::debugLog(
"vst3 attached(parent=%p type=%s) negotiated=%dx%d scale=%.2f",
1677 parent, type !=
nullptr ? type :
"?",
1678 view->width, view->height, view->scale);
1679 if (sIsPlatformTypeSupported(self_, type) != Steinberg_kResultTrue
1680 || parent ==
nullptr)
1681 return Steinberg_kResultFalse;
1682 const webview_ui::HostCallbacks callbacks {
1683 view, &cbSetParam, &cbBeginEdit, &cbEndEdit
1685 if (!view->editor.create(parent, view->owner->shadow, callbacks))
1686 return Steinberg_kResultFalse;
1690 int parentW = 0, parentH = 0;
1691 if (view->editor.queryParentSize(parentW, parentH))
1693 view->width = parentW;
1694 view->height = parentH;
1696 view->editor.setBounds(view->width, view->height);
1697 view->editor.setVisible(
true);
1698#if defined(__linux__)
1702 if (!view->timerRegistered)
1704 webview_ui::debugLog(
"vst3 attached: no usable IRunLoop -> no editor");
1705 view->editor.destroy();
1706 return Steinberg_kResultFalse;
1709 return Steinberg_kResultOk;
1712 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sRemoved(
void* self_)
1714 auto* view = fromLens(self_, 0);
1715#if defined(__linux__)
1718 view->editor.destroy();
1719 return Steinberg_kResultOk;
1722 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sOnWheel(
void*,
float)
1723 {
return Steinberg_kResultFalse; }
1725 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sOnKeyDown(
void*, Steinberg_char16,
1726 Steinberg_int16, Steinberg_int16)
1727 {
return Steinberg_kResultFalse; }
1729 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sOnKeyUp(
void*, Steinberg_char16,
1730 Steinberg_int16, Steinberg_int16)
1731 {
return Steinberg_kResultFalse; }
1733 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetSize(
void* self_,
1734 Steinberg_ViewRect* size)
1736 auto* view = fromLens(self_, 0);
1737 if (size ==
nullptr)
return Steinberg_kInvalidArgument;
1740 size->right = view->width;
1741 size->bottom = view->height;
1742 webview_ui::debugLog(
"vst3 getSize -> %dx%d", view->width, view->height);
1743 return Steinberg_kResultOk;
1746 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sOnSize(
void* self_,
1747 Steinberg_ViewRect* newSize)
1749 auto* view = fromLens(self_, 0);
1750 if (newSize ==
nullptr)
return Steinberg_kInvalidArgument;
1751 const double proposedW = newSize->right - newSize->left;
1752 const double proposedH = newSize->bottom - newSize->top;
1753 double w = proposedW;
1754 double h = proposedH;
1758 if (!view->correctingSize)
1759 constrainEditorSize<P>(w, h, view->scale);
1760 webview_ui::debugLog(
"vst3 onSize %.0fx%.0f -> %.0fx%.0f%s",
1761 proposedW, proposedH, w, h,
1762 view->correctingSize ?
" (correction round-trip)" :
"");
1763 view->width =
static_cast<int>(w + 0.5);
1764 view->height =
static_cast<int>(h + 0.5);
1765 view->editor.setBounds(view->width, view->height);
1766 if (!view->correctingSize && view->frame !=
nullptr
1767 && (view->width - proposedW > 1.0 || proposedW - view->width > 1.0
1768 || view->height - proposedH > 1.0 || proposedH - view->height > 1.0))
1770 view->correctingSize =
true;
1771 Steinberg_ViewRect corrected { 0, 0, view->width, view->height };
1772 view->frame->lpVtbl->resizeView(view->frame,
1773 reinterpret_cast<Steinberg_IPlugView*
>(view->lensPtr(0)), &corrected);
1774 view->correctingSize =
false;
1776 return Steinberg_kResultOk;
1779 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sOnFocus(
void*, Steinberg_TBool)
1780 {
return Steinberg_kResultOk; }
1782 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetFrame(
void* self_,
1783 Steinberg_IPlugFrame* frame)
1785 auto* view = fromLens(self_, 0);
1786#if defined(__linux__)
1787 view->releaseRunLoop();
1789 if (view->frame !=
nullptr)
1790 reinterpret_cast<Steinberg_FUnknown*
>(view->frame)
1791 ->lpVtbl->release(view->frame);
1792 view->frame = frame;
1793 if (view->frame !=
nullptr)
1794 reinterpret_cast<Steinberg_FUnknown*
>(view->frame)
1795 ->lpVtbl->addRef(view->frame);
1796#if defined(__linux__)
1797 if (view->editor.created())
1800 return Steinberg_kResultOk;
1803 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sCanResize(
void*)
1805 return editorResizeOf<P>() != EditorResize::Fixed ? Steinberg_kResultTrue
1806 : Steinberg_kResultFalse;
1809 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sCheckSizeConstraint(
void* self_,
1810 Steinberg_ViewRect* rect)
1812 auto* view = fromLens(self_, 0);
1813 if (rect ==
nullptr)
return Steinberg_kInvalidArgument;
1814 const double proposedW = rect->right - rect->left;
1815 const double proposedH = rect->bottom - rect->top;
1816 double w = proposedW;
1817 double h = proposedH;
1818 constrainEditorSize<P>(w, h, view->scale);
1819 rect->right = rect->left +
static_cast<Steinberg_int32
>(w + 0.5);
1820 rect->bottom = rect->top +
static_cast<Steinberg_int32
>(h + 0.5);
1821 webview_ui::debugLog(
"vst3 checkSizeConstraint %.0fx%.0f -> %.0fx%.0f",
1822 proposedW, proposedH, w, h);
1823 return Steinberg_kResultTrue;
1826 inline static const Steinberg_IPlugViewVtbl kViewVtbl = {
1827 &sQuery<0>, &sAddRef<0>, &sRelease<0>,
1828 &sIsPlatformTypeSupported, &sAttached, &sRemoved,
1829 &sOnWheel, &sOnKeyDown, &sOnKeyUp,
1830 &sGetSize, &sOnSize, &sOnFocus,
1831 &sSetFrame, &sCanResize, &sCheckSizeConstraint
1836 static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetContentScaleFactor(
void* self_,
1837 Steinberg_IPlugViewContentScaleSupport_ScaleFactor factor)
1839 auto* view = fromLens(self_, 1);
1840 if (!(factor > 0.0f))
return Steinberg_kInvalidArgument;
1841 const double previous = view->scale;
1842 view->scale = factor;
1843 webview_ui::debugLog(
"vst3 setContentScaleFactor %.2f (was %.2f)",
1844 view->scale, previous);
1847 view->width =
static_cast<int>(view->width * (view->scale / previous) + 0.5);
1848 view->height =
static_cast<int>(view->height * (view->scale / previous) + 0.5);
1849 if (view->editor.created() && view->frame !=
nullptr)
1853 Steinberg_ViewRect rect { 0, 0, view->width, view->height };
1854 view->frame->lpVtbl->resizeView(view->frame,
1855 reinterpret_cast<Steinberg_IPlugView*
>(view->lensPtr(0)), &rect);
1857 return Steinberg_kResultOk;
1860 inline static const Steinberg_IPlugViewContentScaleSupportVtbl kScaleVtbl = {
1861 &sQuery<1>, &sAddRef<1>, &sRelease<1>,
1862 &sSetContentScaleFactor
1865#if defined(__linux__)
1868 inline static const Steinberg_Linux_ITimerHandlerVtbl kTimerVtbl = {
1869 &sQuery<2>, &sAddRef<2>, &sRelease<2>, &sOnTimer
1876template <
typename P>
1878 Steinberg_FIDString name)
1882#if defined(DSPARK_PLUGIN_WEBVIEW)
1886 && std::strcmp(name,
"editor") == 0)
1888 auto* view =
new (std::nothrow) View<P>(fromLens(self_, 2));
1889 if (view !=
nullptr)
1890 return reinterpret_cast<Steinberg_IPlugView*
>(view->lensPtr(0));
1899template <
typename P>
1902 const Steinberg_IPluginFactory3Vtbl*
vtbl;
1908 return makeUid(P::descriptor.productId, 0x56535433ull);
1911 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sQuery(
void* self_,
1912 const Steinberg_TUID iid,
void** obj)
1914 if (obj ==
nullptr)
return Steinberg_kInvalidArgument;
1915 if (
tuidEqual(iid, Steinberg_FUnknown_iid)
1916 ||
tuidEqual(iid, Steinberg_IPluginFactory_iid)
1917 ||
tuidEqual(iid, Steinberg_IPluginFactory2_iid)
1918 ||
tuidEqual(iid, Steinberg_IPluginFactory3_iid))
1921 return Steinberg_kResultOk;
1924 return Steinberg_kNoInterface;
1927 static Steinberg_uint32 SMTG_STDMETHODCALLTYPE
sAddRef(
void*) {
return 100; }
1928 static Steinberg_uint32 SMTG_STDMETHODCALLTYPE
sRelease(
void*) {
return 100; }
1931 Steinberg_PFactoryInfo* info)
1933 if (info ==
nullptr)
return Steinberg_kInvalidArgument;
1934 std::memset(info, 0,
sizeof(*info));
1935 copyAscii(P::descriptor.vendor, info->vendor,
sizeof(info->vendor));
1936 copyAscii(P::descriptor.url, info->url,
sizeof(info->url));
1937 copyAscii(P::descriptor.email, info->email,
sizeof(info->email));
1938 info->flags = Steinberg_PFactoryInfo_FactoryFlags_kUnicode;
1939 return Steinberg_kResultOk;
1942 static Steinberg_int32 SMTG_STDMETHODCALLTYPE
sCountClasses(
void*) {
return 1; }
1947 std::memcpy(cid, uid.data(), 16);
1951 Steinberg_int32 index, Steinberg_PClassInfo* info)
1953 if (index != 0 || info ==
nullptr)
return Steinberg_kInvalidArgument;
1954 std::memset(info, 0,
sizeof(*info));
1956 info->cardinality = 0x7FFFFFFF;
1957 copyAscii(
"Audio Module Class", info->category,
sizeof(info->category));
1958 copyAscii(P::descriptor.name, info->name,
sizeof(info->name));
1959 return Steinberg_kResultOk;
1963 Steinberg_int32 index, Steinberg_PClassInfo2* info)
1965 if (index != 0 || info ==
nullptr)
return Steinberg_kInvalidArgument;
1966 std::memset(info, 0,
sizeof(*info));
1968 info->cardinality = 0x7FFFFFFF;
1969 copyAscii(
"Audio Module Class", info->category,
sizeof(info->category));
1970 copyAscii(P::descriptor.name, info->name,
sizeof(info->name));
1971 info->classFlags = 0;
1973 info->subCategories,
sizeof(info->subCategories));
1974 copyAscii(P::descriptor.vendor, info->vendor,
sizeof(info->vendor));
1975 copyAscii(P::descriptor.version, info->version,
sizeof(info->version));
1976 copyAscii(
"VST 3.7.9", info->sdkVersion,
sizeof(info->sdkVersion));
1977 return Steinberg_kResultOk;
1981 Steinberg_int32 index, Steinberg_PClassInfoW* info)
1983 if (index != 0 || info ==
nullptr)
return Steinberg_kInvalidArgument;
1984 std::memset(info, 0,
sizeof(*info));
1986 info->cardinality = 0x7FFFFFFF;
1987 copyAscii(
"Audio Module Class", info->category,
sizeof(info->category));
1989 info->classFlags = 0;
1991 info->subCategories,
sizeof(info->subCategories));
1995 return Steinberg_kResultOk;
1999 Steinberg_FIDString cid, Steinberg_FIDString iid,
void** obj)
2001 if (cid ==
nullptr || iid ==
nullptr || obj ==
nullptr)
2002 return Steinberg_kInvalidArgument;
2005 if (std::memcmp(cid, uid.data(), 16) != 0)
2006 return Steinberg_kNoInterface;
2008 auto* plugin =
new (std::nothrow)
Plugin<P>();
2009 if (plugin ==
nullptr)
return Steinberg_kResultFalse;
2011 Steinberg_TUID requested;
2012 std::memcpy(requested, iid,
sizeof(requested));
2013 const Steinberg_tresult r = plugin->query(requested, obj);
2014 plugin->releaseImpl();
2015 return r == Steinberg_kResultOk ? Steinberg_kResultOk : Steinberg_kNoInterface;
2018 static Steinberg_tresult SMTG_STDMETHODCALLTYPE
sSetHostContext(
void*, Steinberg_FUnknown*)
2019 {
return Steinberg_kResultOk; }
2021 inline static const Steinberg_IPluginFactory3Vtbl
kVtbl = {
2034#define DSPARK_VST3_EXPORT __declspec(dllexport)
2036#define DSPARK_VST3_EXPORT __attribute__((visibility("default")))
2043#define DSPARK_VST3_PLUGIN(PluginClass) \
2044 static dspark::plugin::vst3::Factory<PluginClass> gDsparkVst3Factory; \
2046 DSPARK_VST3_EXPORT Steinberg_IPluginFactory* SMTG_STDMETHODCALLTYPE \
2047 GetPluginFactory() \
2049 return reinterpret_cast<Steinberg_IPluginFactory*>(&gDsparkVst3Factory); \
2051 DSPARK_VST3_EXPORT bool InitDll() { return true; } \
2052 DSPARK_VST3_EXPORT bool ExitDll() { return true; } \
2053 DSPARK_VST3_EXPORT bool ModuleEntry(void*) { return true; } \
2054 DSPARK_VST3_EXPORT bool ModuleExit() { return true; } \
2055 DSPARK_VST3_EXPORT bool bundleEntry(void*) { return true; } \
2056 DSPARK_VST3_EXPORT bool bundleExit() { return true; } \
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...
Custom-editor switch. With hasEditor = false (or absent) hosts show their generic parameter UI....
Factory-preset capability: a static constexpr auto factoryPresets table built with presets()....
MIDI capability: void handleMidiEvent(const MidiEvent&) noexcept. Its presence grows a note/event inp...
Render-mode capability: void setOfflineRendering(bool) noexcept. The host flips it to true for non-re...
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 uint32_t midiProxyId(int channel, int controller) noexcept
bool streamRead(Steinberg_IBStream *s, void *dst, Steinberg_int32 bytes) noexcept
Reads the full requested range from an IBStream (hosts may chunk).
constexpr uint32_t kBypassParamId
constexpr uint32_t kMidiProxyBase
constexpr int kNumMidiProxies
bool tuidEqual(const Steinberg_TUID a, const Steinberg_TUID b) noexcept
bool streamWrite(Steinberg_IBStream *s, const void *src, Steinberg_int32 bytes) noexcept
constexpr Steinberg_int32 kPresetProgramListId
void asciiToString128(const char *src, Steinberg_Vst_String128 dst) noexcept
constexpr int kMidiProxyControllers[4]
constexpr bool isMidiProxyId(uint32_t id) noexcept
MidiEvent midiProxyToEvent(uint32_t id, double normalized, int offset) noexcept
Proxy parameter (normalized) -> the framework-neutral MidiEvent.
constexpr uint32_t kProgramParamId
constexpr int kBypassRampSamples
void copyAscii(const char *src, char *dst, size_t cap) noexcept
@ 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 std::array< uint8_t, 16 > makeUid(const char *productId, uint64_t salt) noexcept
Deterministic 16-byte class UID from the stable productId. Each backend salts differently so VST3/CLA...
constexpr double toNormalized(const Param &p, double plain) noexcept
Plain [min, max] -> normalized [0, 1] (steps snap on the way back).
constexpr double toPlain(const Param &p, double normalized) noexcept
Normalized [0, 1] -> plain [min, max], snapped for stepped params.
int parseToggleText(const char *text) noexcept
Case-insensitive "On"/"Off" recognition - the inverse of the toggle display below....
constexpr 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...
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.
Host transport snapshot, delivered once per audio block.
bool playing
Transport is rolling.
static std::array< uint8_t, 16 > classUid() noexcept
static Steinberg_int32 SMTG_STDMETHODCALLTYPE sCountClasses(void *)
static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sRelease(void *)
static void fillClassCommon(Steinberg_TUID cid) noexcept
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetHostContext(void *, Steinberg_FUnknown *)
static const Steinberg_IPluginFactory3Vtbl kVtbl
static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sAddRef(void *)
const Steinberg_IPluginFactory3Vtbl * vtbl
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sQuery(void *self_, const Steinberg_TUID iid, void **obj)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetClassInfoUnicode(void *, Steinberg_int32 index, Steinberg_PClassInfoW *info)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sCreateInstance(void *, Steinberg_FIDString cid, Steinberg_FIDString iid, void **obj)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetClassInfo(void *, Steinberg_int32 index, Steinberg_PClassInfo *info)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetClassInfo2(void *, Steinberg_int32 index, Steinberg_PClassInfo2 *info)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetFactoryInfo(void *, Steinberg_PFactoryInfo *info)
static Steinberg_int32 SMTG_STDMETHODCALLTYPE sGetProgramListCount(void *)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sTerminate(void *)
static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sGetLatencySamples(void *self_)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sComponentSetState(void *self_, Steinberg_IBStream *state)
static Steinberg_Vst_ParamValue SMTG_STDMETHODCALLTYPE sGetParamNormalized(void *self_, Steinberg_Vst_ParamID id)
std::atomic< double > shadow[kNumParams==0 ? 1 :kNumParams]
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetupProcessing(void *self_, Steinberg_Vst_ProcessSetup *setup)
static constexpr Steinberg_int32 kFirstProxyIndex
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetUnitByBus(void *, Steinberg_Vst_MediaType, Steinberg_Vst_BusDirection, Steinberg_int32, Steinberg_int32, Steinberg_Vst_UnitID *unitId)
const Steinberg_Vst_IComponentVtbl * componentVtbl
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sCtrlInitialize(void *, Steinberg_FUnknown *)
static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sRelease(void *self_)
const Steinberg_Vst_IProcessContextRequirementsVtbl * contextReqVtbl
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetRoutingInfo(void *, Steinberg_Vst_RoutingInfo *, Steinberg_Vst_RoutingInfo *)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetMidiControllerAssignment(void *, Steinberg_int32 busIndex, Steinberg_int16 channel, Steinberg_Vst_CtrlNumber midiControllerNumber, Steinberg_Vst_ParamID *id)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sCtrlSetState(void *, Steinberg_IBStream *)
static Steinberg_Vst_ParamValue SMTG_STDMETHODCALLTYPE sPlainParamToNormalized(void *, Steinberg_Vst_ParamID id, Steinberg_Vst_ParamValue plain)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sCtrlTerminate(void *)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sComponentGetState(void *self_, Steinberg_IBStream *state)
std::atomic< double > midiProxyShadow[HasMidi< P > ? kNumMidiProxies :1]
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sCtrlGetState(void *, Steinberg_IBStream *)
static constexpr Steinberg_int32 kBypassIndex
const Steinberg_Vst_IMidiMappingVtbl * midiMapVtbl
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sHasProgramPitchNames(void *, Steinberg_Vst_ProgramListID, Steinberg_int32)
static constexpr size_t kNumParams
static const Steinberg_Vst_IUnitInfoVtbl kUnitVtbl
void applyAllShadows() noexcept
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sCanProcessSampleSize(void *, Steinberg_int32 size)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetIoMode(void *, Steinberg_Vst_IoMode)
void applyFactoryPresetIdx(int idx) noexcept
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sQuery(void *self_, const Steinberg_TUID iid, void **obj)
static int indexOfParamId(Steinberg_Vst_ParamID id) noexcept
static Steinberg_int32 numInputBuses() noexcept
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetComponentHandler(void *self_, Steinberg_Vst_IComponentHandler *handler)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetProgramName(void *, Steinberg_Vst_ProgramListID listId, Steinberg_int32 programIndex, Steinberg_Vst_String128 name)
static const Steinberg_Vst_IComponentVtbl kComponentVtbl
void refreshLatency(Steinberg_int32 extraFlags=0) noexcept
bool applyBlockEvent(const BlockEvent &ev, int blockStart, bool &programChanged) noexcept
std::atomic< Steinberg_uint32 > refs
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetActive(void *self_, Steinberg_TBool state)
static Steinberg_int32 SMTG_STDMETHODCALLTYPE sGetParameterCount(void *)
Steinberg_tresult query(const Steinberg_TUID iid, void **obj) noexcept
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSelectUnit(void *, Steinberg_Vst_UnitID unitId)
static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sAddRef(void *self_)
const Steinberg_Vst_IUnitInfoVtbl * unitVtbl
Steinberg_uint32 addRefImpl() noexcept
static const Steinberg_Vst_IProcessContextRequirementsVtbl kContextReqVtbl
static Steinberg_int32 SMTG_STDMETHODCALLTYPE sGetUnitCount(void *)
void forwardTransport(const Steinberg_Vst_ProcessContext *ctx) noexcept
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetProgramListInfo(void *, Steinberg_int32 listIndex, Steinberg_Vst_ProgramListInfo *info)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetBusArrangement(void *self_, Steinberg_Vst_BusDirection dir, Steinberg_int32 index, Steinberg_Vst_SpeakerArrangement *arr)
std::vector< float > silence
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetUnitInfo(void *, Steinberg_int32 unitIndex, Steinberg_Vst_UnitInfo *info)
std::atomic< int > currentProgram
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetParamStringByValue(void *self_, Steinberg_Vst_ParamID id, Steinberg_Vst_ParamValue normalized, Steinberg_Vst_String128 out)
static const Steinberg_Vst_IAudioProcessorVtbl kProcessorVtbl
const Steinberg_Vst_IEditControllerVtbl * controllerVtbl
static const Steinberg_Vst_IMidiMappingVtbl kMidiMapVtbl
static const Steinberg_Vst_IEditControllerVtbl kControllerVtbl
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sActivateBus(void *, Steinberg_Vst_MediaType, Steinberg_Vst_BusDirection, Steinberg_int32, Steinberg_TBool)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetProgramPitchName(void *, Steinberg_Vst_ProgramListID, Steinberg_int32, Steinberg_int16, Steinberg_Vst_String128)
static constexpr Steinberg_int32 kProgramIndex
static constexpr int kNumPresets
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetBusInfo(void *self_, Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir, Steinberg_int32 index, Steinberg_Vst_BusInfo *bus)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetParamValueByString(void *self_, Steinberg_Vst_ParamID id, Steinberg_Vst_TChar *string, Steinberg_Vst_ParamValue *normalized)
static Steinberg_Vst_ParamValue SMTG_STDMETHODCALLTYPE sNormalizedParamToPlain(void *, Steinberg_Vst_ParamID id, Steinberg_Vst_ParamValue normalized)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetProgramInfo(void *, Steinberg_Vst_ProgramListID, Steinberg_int32, Steinberg_Vst_CString, Steinberg_Vst_String128)
std::vector< float > dryR
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetUnitProgramData(void *, Steinberg_int32, Steinberg_int32, struct Steinberg_IBStream *)
Steinberg_Vst_ProcessSetup setup
static Plugin * fromLens(void *iface, int lens) noexcept
std::vector< float > dryL
void collectParameterChanges(Steinberg_Vst_IParameterChanges *changes, Steinberg_int32 numSamples, BlockEvent *events, int &count) noexcept
void collectInputEvents(Steinberg_Vst_IEventList *list, Steinberg_int32 numSamples, BlockEvent *events, int &count) noexcept
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sProcess(void *self_, Steinberg_Vst_ProcessData *data)
std::atomic< bool > bypass
static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sGetTailSamples(void *self_)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetParamNormalized(void *self_, Steinberg_Vst_ParamID id, Steinberg_Vst_ParamValue value)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sInitialize(void *, Steinberg_FUnknown *)
static Steinberg_Vst_UnitID SMTG_STDMETHODCALLTYPE sGetSelectedUnit(void *)
Steinberg_uint32 releaseImpl() noexcept
void * lensPtr(int lens) noexcept
static Steinberg_IPlugView *SMTG_STDMETHODCALLTYPE sCreateView(void *self_, Steinberg_FIDString name)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetParameterInfo(void *self_, Steinberg_int32 index, Steinberg_Vst_ParameterInfo *info)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetComponentState(void *self_, Steinberg_IBStream *state)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sGetControllerClassId(void *, Steinberg_TUID)
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetProcessing(void *, Steinberg_TBool)
static constexpr bool kIsInstrument
static Steinberg_uint32 SMTG_STDMETHODCALLTYPE sGetProcessContextRequirements(void *)
const Steinberg_Vst_IAudioProcessorVtbl * processorVtbl
std::atomic< Steinberg_Vst_IComponentHandler * > handler
static Steinberg_int32 SMTG_STDMETHODCALLTYPE sGetBusCount(void *, Steinberg_Vst_MediaType type, Steinberg_Vst_BusDirection dir)
void applyNormalized(int index, double normalized) noexcept
static Steinberg_tresult SMTG_STDMETHODCALLTYPE sSetBusArrangements(void *self_, Steinberg_Vst_SpeakerArrangement *inputs, Steinberg_int32 numIns, Steinberg_Vst_SpeakerArrangement *outputs, Steinberg_int32 numOuts)
static int midiProxySlot(uint32_t id) noexcept