11#include "IPlugWasmDSP.h"
14#include <unordered_map>
15#include <emscripten.h>
16#include <emscripten/bind.h>
19using namespace emscripten;
23static std::unordered_map<int, IPlugWasmDSP*> sInstances;
24static std::atomic<int> sNextInstanceId{1};
29 auto it = sInstances.find(instanceId);
30 return (it != sInstances.end()) ? it->second :
nullptr;
33IPlugWasmDSP::IPlugWasmDSP(
const InstanceInfo& info,
const Config& config)
38 int nInputs = MaxNChannels(ERoute::kInput);
39 int nOutputs = MaxNChannels(ERoute::kOutput);
41 SetChannelConnections(ERoute::kInput, 0, nInputs,
true);
42 SetChannelConnections(ERoute::kOutput, 0, nOutputs,
true);
45IPlugWasmDSP::~IPlugWasmDSP()
50 sInstances.erase(mInstanceId);
56 mInstanceId = instanceId;
59 sInstances[instanceId] =
this;
65 DBGMSG(
"IPlugWasmDSP::Init(%d, %d) instance=%d\n", sampleRate, blockSize, mInstanceId);
67 SetSampleRate(sampleRate);
68 SetBlockSize(blockSize);
77 SetChannelConnections(ERoute::kOutput, 0,
MaxNChannels(ERoute::kOutput),
true);
79 AttachBuffers(ERoute::kOutput, 0,
NChannelsConnected(ERoute::kOutput), outputs, nFrames);
88 ProcessBuffers(0.0f, nFrames);
95 while (mParamChangeFromProcessor.ElementsAvailable())
98 mParamChangeFromProcessor.Pop(p);
99 SendParameterValueFromDelegate(p.idx, p.value,
false);
103 while (mMidiMsgsFromProcessor.ElementsAvailable())
106 mMidiMsgsFromProcessor.Pop(msg);
107 SendMidiMsgFromDelegate(msg);
113void IPlugWasmDSP::OnParamMessage(
int paramIdx,
double value)
120void IPlugWasmDSP::OnMidiMessage(
int status,
int data1,
int data2)
122 IMidiMsg msg = {0, (uint8_t)status, (uint8_t)data1, (uint8_t)data2};
126void IPlugWasmDSP::OnSysexMessage(
const uint8_t* pData,
int size)
128 ISysEx sysex = {0, pData, size};
132void IPlugWasmDSP::OnArbitraryMessage(
int msgTag,
int ctrlTag,
int dataSize,
const void* pData)
134 OnMessage(msgTag, ctrlTag, dataSize, pData);
140 mMidiMsgsFromProcessor.Push(msg);
148 var instances = Module._instancePorts;
149 if (instances && instances[$0]) {
150 var data =
new Uint8Array($2);
151 data.set(HEAPU8.subarray($1, $1 + $2));
152 instances[$0].postMessage({
157 }, mInstanceId,
reinterpret_cast<intptr_t
>(msg.mData), msg.mSize);
161void IPlugWasmDSP::SendControlValueFromDelegate(
int ctrlTag,
double normalizedValue)
164 bool usedSAB = EM_ASM_INT({
165 var processors = Module._instanceProcessors;
166 if (processors && processors[$0] && processors[$0].sabBuffer) {
167 var proc = processors[$0];
169 var ptr = Module._malloc(4);
170 Module.HEAPF32[ptr >> 2] = $2;
171 var result = proc._writeSABMessage(0, $1, 0, ptr, 4);
173 return result ? 1 : 0;
176 }, mInstanceId, ctrlTag, normalizedValue);
181 var instances = Module._instancePorts;
182 if (instances && instances[$0]) {
183 instances[$0].postMessage({
189 }, mInstanceId, ctrlTag, normalizedValue);
193void IPlugWasmDSP::SendControlMsgFromDelegate(
int ctrlTag,
int msgTag,
int dataSize,
const void* pData)
196 bool usedSAB =
false;
197 if (dataSize > 0 && pData)
199 usedSAB = EM_ASM_INT({
200 var processors = Module._instanceProcessors;
201 if (processors && processors[$0] && processors[$0].sabBuffer) {
202 return processors[$0]._writeSABMessage(1, $1, $2, $3, $4) ? 1 : 0;
205 }, mInstanceId, ctrlTag, msgTag,
reinterpret_cast<intptr_t
>(pData), dataSize);
211 if (dataSize > 0 && pData)
214 var instances = Module._instancePorts;
215 if (instances && instances[$0]) {
216 var data =
new Uint8Array($4);
217 data.set(HEAPU8.subarray($3, $3 + $4));
218 instances[$0].postMessage({
225 }, mInstanceId, ctrlTag, msgTag,
reinterpret_cast<intptr_t
>(pData), dataSize);
230 var instances = Module._instancePorts;
231 if (instances && instances[$0]) {
232 instances[$0].postMessage({
239 }, mInstanceId, ctrlTag, msgTag);
244void IPlugWasmDSP::SendParameterValueFromDelegate(
int paramIdx,
double value,
bool normalized)
247 var instances = Module._instancePorts;
248 if (instances && instances[$0]) {
249 instances[$0].postMessage({
255 }, mInstanceId, paramIdx, value);
258void IPlugWasmDSP::SendMidiMsgFromDelegate(
const IMidiMsg& msg)
261 var instances = Module._instancePorts;
262 if (instances && instances[$0]) {
263 instances[$0].postMessage({
270 }, mInstanceId, msg.mStatus, msg.mData1, msg.mData2);
273void IPlugWasmDSP::SendArbitraryMsgFromDelegate(
int msgTag,
int dataSize,
const void* pData)
276 bool usedSAB =
false;
277 if (dataSize > 0 && pData)
279 usedSAB = EM_ASM_INT({
280 var processors = Module._instanceProcessors;
281 if (processors && processors[$0] && processors[$0].sabBuffer) {
282 return processors[$0]._writeSABMessage(2, 0, $1, $2, $3) ? 1 : 0;
285 }, mInstanceId, msgTag,
reinterpret_cast<intptr_t
>(pData), dataSize);
291 if (dataSize > 0 && pData)
294 var instances = Module._instancePorts;
295 if (instances && instances[$0]) {
296 var data =
new Uint8Array($3);
297 data.set(HEAPU8.subarray($2, $2 + $3));
298 instances[$0].postMessage({
304 }, mInstanceId, msgTag,
reinterpret_cast<intptr_t
>(pData), dataSize);
309 var instances = Module._instancePorts;
310 if (instances && instances[$0]) {
311 instances[$0].postMessage({
317 }, mInstanceId, msgTag);
331static int _createInstance()
333 IPlugWasmDSP* pInstance = iplug::MakePlug(iplug::InstanceInfo());
334 if (!pInstance)
return 0;
336 int instanceId = sNextInstanceId.fetch_add(1);
341 if (!Module._instancePorts) Module._instancePorts = {};
342 if (!Module._instanceProcessors) Module._instanceProcessors = {};
349static void _destroyInstance(
int instanceId)
356 if (Module._instancePorts)
delete Module._instancePorts[$0];
357 if (Module._instanceProcessors)
delete Module._instanceProcessors[$0];
364static void _init(
int instanceId,
int sampleRate,
int blockSize)
367 if (pInstance) pInstance->
Init(sampleRate, blockSize);
370static void _processBlock(
int instanceId, uintptr_t inputPtrs, uintptr_t outputPtrs,
int nFrames)
375 sample** inputs =
reinterpret_cast<sample**
>(inputPtrs);
376 sample** outputs =
reinterpret_cast<sample**
>(outputPtrs);
381static void _onParam(
int instanceId,
int paramIdx,
double value)
384 if (pInstance) pInstance->OnParamMessage(paramIdx, value);
387static void _onMidi(
int instanceId,
int status,
int data1,
int data2)
390 if (pInstance) pInstance->OnMidiMessage(status, data1, data2);
393static void _onSysex(
int instanceId, uintptr_t pData,
int size)
398 const uint8_t* pDataPtr =
reinterpret_cast<const uint8_t*
>(pData);
399 pInstance->OnSysexMessage(pDataPtr, size);
403static void _onArbitraryMsg(
int instanceId,
int msgTag,
int ctrlTag,
int dataSize, uintptr_t pData)
408 const void* pDataPtr =
reinterpret_cast<const void*
>(pData);
409 pInstance->OnArbitraryMessage(msgTag, ctrlTag, dataSize, pDataPtr);
413static void _onIdleTick(
int instanceId)
419static int _getNumInputChannels(
int instanceId)
425static int _getNumOutputChannels(
int instanceId)
431static bool _isInstrument(
int instanceId)
437static int _getNumParams(
int instanceId)
440 return pInstance ? pInstance->NParams() : 0;
443static double _getParamDefault(
int instanceId,
int paramIdx)
446 if (pInstance && paramIdx >= 0 && paramIdx < pInstance->NParams())
447 return pInstance->GetParam(paramIdx)->GetDefault(
true);
451static std::string _getParamName(
int instanceId,
int paramIdx)
454 if (pInstance && paramIdx >= 0 && paramIdx < pInstance->NParams())
455 return pInstance->GetParam(paramIdx)->GetName();
459static std::string _getParamLabel(
int instanceId,
int paramIdx)
462 if (pInstance && paramIdx >= 0 && paramIdx < pInstance->NParams())
463 return pInstance->GetParam(paramIdx)->GetLabel();
467static double _getParamMin(
int instanceId,
int paramIdx)
470 if (pInstance && paramIdx >= 0 && paramIdx < pInstance->NParams())
471 return pInstance->GetParam(paramIdx)->GetMin();
475static double _getParamMax(
int instanceId,
int paramIdx)
478 if (pInstance && paramIdx >= 0 && paramIdx < pInstance->NParams())
479 return pInstance->GetParam(paramIdx)->GetMax();
483static double _getParamStep(
int instanceId,
int paramIdx)
486 if (pInstance && paramIdx >= 0 && paramIdx < pInstance->NParams())
487 return pInstance->GetParam(paramIdx)->GetStep();
491static double _getParamValue(
int instanceId,
int paramIdx)
494 if (pInstance && paramIdx >= 0 && paramIdx < pInstance->NParams())
495 return pInstance->GetParam(paramIdx)->Value();
499static std::string _getPluginName(
int instanceId)
505static std::string _getPluginInfoJSON(
int instanceId)
508 if (!pInstance)
return "{}";
510 std::string json =
"{";
511 json +=
"\"instanceId\":" + std::to_string(instanceId) +
",";
512 json +=
"\"name\":\"" + std::string(pInstance->
GetPluginName()) +
"\",";
515 json +=
"\"isInstrument\":" + std::string(pInstance->
IsPlugInstrument() ?
"true" :
"false") +
",";
516 json +=
"\"params\":[";
518 int nParams = pInstance->NParams();
519 for (
int i = 0; i < nParams; i++)
521 IParam* pParam = pInstance->GetParam(i);
522 if (i > 0) json +=
",";
524 json +=
"\"idx\":" + std::to_string(i) +
",";
525 json +=
"\"name\":\"" + std::string(pParam->
GetName()) +
"\",";
526 json +=
"\"label\":\"" + std::string(pParam->
GetLabel()) +
"\",";
527 json +=
"\"min\":" + std::to_string(pParam->
GetMin()) +
",";
528 json +=
"\"max\":" + std::to_string(pParam->
GetMax()) +
",";
529 json +=
"\"default\":" + std::to_string(pParam->
GetDefault()) +
",";
530 json +=
"\"step\":" + std::to_string(pParam->
GetStep()) +
",";
531 json +=
"\"value\":" + std::to_string(pParam->
Value());
540 function(
"createInstance", &_createInstance);
541 function(
"destroyInstance", &_destroyInstance);
542 function(
"init", &_init);
543 function(
"processBlock", &_processBlock);
544 function(
"onParam", &_onParam);
545 function(
"onMidi", &_onMidi);
546 function(
"onSysex", &_onSysex);
547 function(
"onArbitraryMsg", &_onArbitraryMsg);
548 function(
"onIdleTick", &_onIdleTick);
549 function(
"getNumInputChannels", &_getNumInputChannels);
550 function(
"getNumOutputChannels", &_getNumOutputChannels);
551 function(
"isInstrument", &_isInstrument);
552 function(
"getNumParams", &_getNumParams);
553 function(
"getParamDefault", &_getParamDefault);
554 function(
"getParamName", &_getParamName);
555 function(
"getParamLabel", &_getParamLabel);
556 function(
"getParamMin", &_getParamMin);
557 function(
"getParamMax", &_getParamMax);
558 function(
"getParamStep", &_getParamStep);
559 function(
"getParamValue", &_getParamValue);
560 function(
"getPluginName", &_getPluginName);
561 function(
"getPluginInfoJSON", &_getPluginInfoJSON);
double GetStep() const
Returns the parameter's step size.
double GetDefault(bool normalized=false) const
Returns the parameter's default value.
double GetMin() const
Returns the parameter's minimum value.
const char * GetLabel() const
Returns the parameter's label.
const char * GetName() const
Returns the parameter's name.
double Value() const
Gets a readable value of the parameter.
double GetMax() const
Returns the parameter's maximum value.
The base class of an IPlug plug-in, which interacts with the different plug-in APIs.
virtual void OnIdle()
Override this method to get an "idle"" call on the main thread.
void SetParameterValue(int paramIdx, double normalizedValue)
SetParameterValue is called from the UI in the middle of a parameter change gesture (possibly via del...
The base class for IPlug Audio Processing.
virtual void ProcessMidiMsg(const IMidiMsg &msg)
Override this method to handle incoming MIDI messages.
bool IsInstrument() const
int NChannelsConnected(ERoute direction) const
virtual void ProcessSysEx(const ISysEx &msg)
Override this method to handle incoming MIDI System Exclusive (SysEx) messages.
int MaxNChannels(ERoute direction) const
virtual void OnReset()
Override this method in your plug-in class to do something prior to playback etc.
Hybrid DSP class - AudioWorklet processor for split DSP/UI builds.
void Init(int sampleRate, int blockSize)
Initialize the DSP processor.
bool SendSysEx(const ISysEx &msg) override
Send a single MIDI System Exclusive (SysEx) message // TODO: info about what thread should this be ca...
void SetInstanceId(int instanceId)
Set the instance ID (called after construction by createInstance binding)
int GetNumOutputChannels() const
Get the number of output channels.
void OnIdleTick()
Called on idle tick to flush queued messages to UI.
bool SendMidiMsg(const IMidiMsg &msg) override
Send a single MIDI message // TODO: info about what thread should this be called on or not called on!
int GetNumInputChannels() const
Get the number of input channels.
bool IsPlugInstrument() const
Check if plugin is an instrument (synth)
void ProcessBlock(sample **inputs, sample **outputs, int nFrames) override
Process a block of audio.
const char * GetPluginName() const
Encapsulates a MIDI message and provides helper functions.
A struct for dealing with SysEx messages.
In certain cases we need to queue parameter changes for transferral between threads.