iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
IPlugWasmUI.cpp
1/*
2 ==============================================================================
3
4 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5
6 See LICENSE.txt for more info.
7
8 ==============================================================================
9*/
10
11#include "IPlugWasmUI.h"
12
13#include <memory>
14
15#include <emscripten.h>
16#include <emscripten/bind.h>
17
18using namespace iplug;
19using namespace emscripten;
20
21#if defined OS_WEB && defined NO_IGRAPHICS
22void StartMainLoopTimer()
23{
24}
25#endif
26
27IPlugWasmUI::IPlugWasmUI(const InstanceInfo& info, const Config& config)
28: IPlugAPIBase(config, kAPIWEB)
29{
30 mControllerName.SetFormatted(32, "%s_Hybrid", GetPluginName());
31}
32
33void IPlugWasmUI::SendParameterValueFromUI(int paramIdx, double value)
34{
35 // Send parameter value to DSP via controller
36 EM_ASM({
37 if (typeof window.iPlugController !== 'undefined') {
38 window.iPlugController.setParam($0, $1);
39 } else {
40 console.warn('IPlugWasmUI: controller not ready');
41 }
42 }, paramIdx, value);
43
44 // Call super class to trigger OnParamChangeUI()
45 IPlugAPIBase::SendParameterValueFromUI(paramIdx, value);
46}
47
48void IPlugWasmUI::SendMidiMsgFromUI(const IMidiMsg& msg)
49{
50 EM_ASM({
51 if (typeof window.iPlugController !== 'undefined') {
52 window.iPlugController.sendMidi($0, $1, $2);
53 } else {
54 console.warn('IPlugWasmUI: controller not ready');
55 }
56 }, msg.mStatus, msg.mData1, msg.mData2);
57}
58
59void IPlugWasmUI::SendSysexMsgFromUI(const ISysEx& msg)
60{
61 EM_ASM({
62 if (typeof window.iPlugController !== 'undefined') {
63 var data = new Uint8Array($1);
64 data.set(HEAPU8.subarray($0, $0 + $1));
65 window.iPlugController.sendSysex(data);
66 } else {
67 console.warn('IPlugWasmUI: controller not ready');
68 }
69 }, reinterpret_cast<intptr_t>(msg.mData), msg.mSize);
70}
71
72void IPlugWasmUI::SendArbitraryMsgFromUI(int msgTag, int ctrlTag, int dataSize, const void* pData)
73{
74 if (dataSize > 0 && pData)
75 {
76 EM_ASM({
77 if (typeof window.iPlugController !== 'undefined') {
78 var data = new Uint8Array($2);
79 data.set(HEAPU8.subarray($3, $3 + $2));
80 window.iPlugController.sendArbitraryMsg($0, $1, data);
81 } else {
82 console.warn('IPlugWasmUI: controller not ready');
83 }
84 }, msgTag, ctrlTag, dataSize, reinterpret_cast<intptr_t>(pData));
85 }
86 else
87 {
88 EM_ASM({
89 if (typeof window.iPlugController !== 'undefined') {
90 window.iPlugController.sendArbitraryMsg($0, $1, null);
91 }
92 }, msgTag, ctrlTag);
93 }
94
95 IPlugAPIBase::SendArbitraryMsgFromUI(msgTag, ctrlTag, dataSize, pData);
96}
97
98void IPlugWasmUI::SendDSPIdleTick()
99{
100 EM_ASM({
101 if (typeof window.iPlugController !== 'undefined') {
102 window.iPlugController.sendIdleTick();
103 }
104 });
105}
106
107bool IPlugWasmUI::EditorResize(int width, int height)
108{
109 SetEditorSize(width, height);
110
111 EM_ASM({
112 if (typeof window.__iPlugWasmEditorResize === 'function') {
113 window.__iPlugWasmEditorResize($0, $1);
114 }
115 }, width, height);
116
117 return true;
118}
119
120// Global instance for Emscripten bindings
121extern std::unique_ptr<iplug::IPlugWasmUI> gPlug;
122
123// Parent-window dims that arrived before `gPlug` was constructed.
124// `iplug_fsready()` runs asynchronously after IDBFS syncs — so on the
125// first page load the JS bundle's initial ResizeObserver callback can
126// fire before `gPlug` exists. Without buffering, that resize is lost
127// and the canvas stays pinned at the plugin's default dimensions until
128// the parent element is resized again. Replayed by
129// `IPlugWasmUI_ApplyPendingParentWindowResize()`.
130static int gPendingParentWindowW = 0;
131static int gPendingParentWindowH = 0;
132
133extern "C" EMSCRIPTEN_KEEPALIVE void IPlugWasmUI_ApplyPendingParentWindowResize()
134{
135 if (gPlug && gPendingParentWindowW > 0 && gPendingParentWindowH > 0)
136 {
137 gPlug->OnParentWindowResize(gPendingParentWindowW, gPendingParentWindowH);
138 gPendingParentWindowW = 0;
139 gPendingParentWindowH = 0;
140 }
141}
142
143// Callback functions called by JavaScript controller when DSP sends messages
144static void _SendParameterValueFromDelegate(int paramIdx, double normalizedValue)
145{
146 gPlug->SendParameterValueFromDelegate(paramIdx, normalizedValue, true);
147}
148
149static void _SendControlValueFromDelegate(int ctrlTag, double normalizedValue)
150{
151 gPlug->SendControlValueFromDelegate(ctrlTag, normalizedValue);
152}
153
154static void _SendControlMsgFromDelegate(int ctrlTag, int msgTag, int dataSize, uintptr_t pData)
155{
156 const uint8_t* pDataPtr = reinterpret_cast<uint8_t*>(pData);
157 gPlug->SendControlMsgFromDelegate(ctrlTag, msgTag, dataSize, pDataPtr);
158}
159
160static void _SendArbitraryMsgFromDelegate(int msgTag, int dataSize, uintptr_t pData)
161{
162 const uint8_t* pDataPtr = reinterpret_cast<uint8_t*>(pData);
163 gPlug->SendArbitraryMsgFromDelegate(msgTag, dataSize, pDataPtr);
164}
165
166static void _SendMidiMsgFromDelegate(int status, int data1, int data2)
167{
168 IMidiMsg msg{0, (uint8_t)status, (uint8_t)data1, (uint8_t)data2};
169 gPlug->SendMidiMsgFromDelegate(msg);
170}
171
172static void _SendSysexMsgFromDelegate(int dataSize, uintptr_t pData)
173{
174 const uint8_t* pDataPtr = reinterpret_cast<uint8_t*>(pData);
175 ISysEx msg(0, pDataPtr, dataSize);
176 gPlug->SendSysexMsgFromDelegate(msg);
177}
178
179static void _StartIdleTimer()
180{
181 gPlug->CreateTimer();
182}
183
184static void _OnParentWindowResize(int width, int height)
185{
186 if (gPlug)
187 {
188 gPlug->OnParentWindowResize(width, height);
189 }
190 else
191 {
192 // gPlug not constructed yet — stash the dims so
193 // `iplug_fsready()` can replay them once the UI is open.
194 gPendingParentWindowW = width;
195 gPendingParentWindowH = height;
196 }
197}
198
199EMSCRIPTEN_BINDINGS(IPlugWasmUI) {
200 function("SPVFD", &_SendParameterValueFromDelegate);
201 function("SCVFD", &_SendControlValueFromDelegate);
202 function("SCMFD", &_SendControlMsgFromDelegate);
203 function("SAMFD", &_SendArbitraryMsgFromDelegate);
204 function("SMMFD", &_SendMidiMsgFromDelegate);
205 function("SSMFD", &_SendSysexMsgFromDelegate);
206 function("StartIdleTimer", &_StartIdleTimer);
207 function("OnParentWindowResize", &_OnParentWindowResize);
208}
The base class of an IPlug plug-in, which interacts with the different plug-in APIs.
Definition: IPlugAPIBase.h:43
Hybrid UI class - IGraphics side for split DSP/UI builds.
Definition: IPlugWasmUI.h:32
Encapsulates a MIDI message and provides helper functions.
Definition: IPlugMidi.h:31
A struct for dealing with SysEx messages.
Definition: IPlugMidi.h:539