1 #include "SurgeLv2Ui.h"
2 #include "SurgeLv2Util.h"
3 #include "SurgeLv2Vstgui.h"
4 #include "SurgeLv2Wrapper.h"
5 #include "SurgeGUIEditor.h"
6 #include <cassert>
7 #include "DebugHelpers.h"
8 
9 #include "CScalableBitmap.h"
10 
SurgeLv2Ui(SurgeLv2Wrapper * instance,void * parentWindow,const LV2_URID_Map * uridMapper,const LV2UI_Resize * uiResizer,LV2UI_Write_Function writeFn,LV2UI_Controller controller,float uiScaleFactor)11 SurgeLv2Ui::SurgeLv2Ui(SurgeLv2Wrapper *instance, void *parentWindow,
12                        const LV2_URID_Map *uridMapper, const LV2UI_Resize *uiResizer,
13                        LV2UI_Write_Function writeFn, LV2UI_Controller controller,
14                        float uiScaleFactor)
15     : _editor(new SurgeGUIEditor(instance, instance->synthesizer(), this)), _instance(instance),
16       _writeFn(writeFn), _controller(controller), _uiScaleFactor(uiScaleFactor),
17       _uiInitialized(false)
18 {
19     instance->setEditor(this);
20 
21     if (uiResizer)
22     {
23         _editor->setZoomFactor(uiScaleFactor * 100);
24         _editor->setZoomCallback([this, uiResizer](SurgeGUIEditor *editor, bool resizeWindow) {
25             handleZoom(editor, uiResizer, resizeWindow);
26         });
27     }
28 
29     _editor->open(parentWindow);
30 
31     if (uiResizer)
32     {
33         VSTGUI::ERect *rect = nullptr;
34         _editor->getRect(&rect);
35         uiResizer->ui_resize(uiResizer->handle, (rect->right - rect->left) * uiScaleFactor,
36                              (rect->bottom - rect->top) * uiScaleFactor);
37     }
38 
39     _uiInitialized = true;
40 }
41 
~SurgeLv2Ui()42 SurgeLv2Ui::~SurgeLv2Ui()
43 {
44     _instance->setEditor(nullptr);
45     _editor->close();
46 }
47 
setParameterAutomated(int externalparam,float value)48 void SurgeLv2Ui::setParameterAutomated(int externalparam, float value)
49 {
50     _writeFn(_controller, externalparam, sizeof(float), 0, &value);
51 }
52 
53 #if LINUX
associateIdleRunLoop(const VSTGUI::SharedPointer<Lv2IdleRunLoop> & runLoop)54 void SurgeLv2Ui::associateIdleRunLoop(const VSTGUI::SharedPointer<Lv2IdleRunLoop> &runLoop)
55 {
56     _runLoop = runLoop;
57 }
58 #endif
59 
createDescriptor()60 LV2UI_Descriptor SurgeLv2Ui::createDescriptor()
61 {
62     LV2UI_Descriptor desc = {};
63     desc.URI = SURGE_UI_URI;
64     desc.instantiate = &instantiate;
65     desc.cleanup = &cleanup;
66     desc.port_event = &portEvent;
67     desc.extension_data = &extensionData;
68     return desc;
69 }
70 
instantiate(const LV2UI_Descriptor * descriptor,const char * plugin_uri,const char * bundle_path,LV2UI_Write_Function write_function,LV2UI_Controller controller,LV2UI_Widget * widget,const LV2_Feature * const * features)71 LV2UI_Handle SurgeLv2Ui::instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri,
72                                      const char *bundle_path, LV2UI_Write_Function write_function,
73                                      LV2UI_Controller controller, LV2UI_Widget *widget,
74                                      const LV2_Feature *const *features)
75 {
76     SurgeLv2Wrapper *instance =
77         (SurgeLv2Wrapper *)SurgeLv2::requireFeature(LV2_INSTANCE_ACCESS_URI, features);
78     void *parentWindow = (void *)SurgeLv2::findFeature(LV2_UI__parent, features);
79     auto *featureUridMap = (const LV2_URID_Map *)SurgeLv2::requireFeature(LV2_URID__map, features);
80     auto *featureResize = (const LV2UI_Resize *)SurgeLv2::findFeature(LV2_UI__resize, features);
81     auto *featureOptions =
82         (const LV2_Options_Option *)SurgeLv2::findFeature(LV2_OPTIONS__options, features);
83 
84     auto uriScaleFactor = featureUridMap->map(featureUridMap->handle, LV2_UI__scaleFactor);
85     float uiScaleFactor = 1.0f;
86     for (int i = 0; featureOptions[i].key != 0; ++i)
87     {
88         if (featureOptions[i].key == uriScaleFactor)
89         {
90             if (featureOptions[i].type ==
91                 featureUridMap->map(featureUridMap->handle, LV2_ATOM__Float))
92                 uiScaleFactor = *(const float *)featureOptions[i].value;
93             break;
94         }
95     }
96 
97     std::unique_ptr<SurgeLv2Ui> ui(new SurgeLv2Ui(instance, parentWindow, featureUridMap,
98                                                   featureResize, write_function, controller,
99                                                   uiScaleFactor));
100 
101     // From LV2 documentation: The UI points this at its main widget, which has
102     // the type defined by the UI type in the data file.
103     if (widget != nullptr)
104     {
105         *widget = ui->_editor->getFrame()->getPlatformFrame()->getPlatformRepresentation();
106     }
107 
108     return (LV2UI_Handle)ui.release();
109 }
110 
cleanup(LV2UI_Handle ui)111 void SurgeLv2Ui::cleanup(LV2UI_Handle ui)
112 {
113     SurgeLv2Ui *self = (SurgeLv2Ui *)ui;
114     delete self;
115 }
116 
portEvent(LV2UI_Handle ui,uint32_t port_index,uint32_t buffer_size,uint32_t format,const void * buffer)117 void SurgeLv2Ui::portEvent(LV2UI_Handle ui, uint32_t port_index, uint32_t buffer_size,
118                            uint32_t format, const void *buffer)
119 {
120     SurgeLv2Ui *self = (SurgeLv2Ui *)ui;
121     SurgeSynthesizer *s = self->_instance->synthesizer();
122 
123     if (format == 0 && port_index < n_total_params)
124     {
125         assert(buffer_size == sizeof(float));
126 
127         SurgeSynthesizer::ID did;
128         float value = *(const float *)buffer;
129         bool external = true;
130         if (s->fromDAWSideIndex(port_index, did))
131             s->setParameter01(did, value, external);
132     }
133 }
134 
extensionData(const char * uri)135 const void *SurgeLv2Ui::extensionData(const char *uri)
136 {
137     if (!strcmp(uri, LV2_UI__idleInterface))
138     {
139         static const LV2UI_Idle_Interface idle = {&uiIdle};
140         return &idle;
141     }
142     // if (!strcmp(uri, LV2_UI__resize)) {
143     //     static const LV2UI_Resize resize = { nullptr, &uiResize };
144     //     return &resize;
145     // }
146     return nullptr;
147 }
148 
uiIdle(LV2UI_Handle ui)149 int SurgeLv2Ui::uiIdle(LV2UI_Handle ui)
150 {
151     SurgeLv2Ui *self = (SurgeLv2Ui *)ui;
152     SurgeLv2Wrapper *instance = self->_instance;
153 
154     // HACK to get parameters written to host
155     // if a patch was loaded from storage, the plugin atomically sets a bit to
156     // indicate it. In this case, write the control value again using UI's write
157     // interface, in order to update the host side.
158     if (instance->_editorMustReloadPatch.exchange(false))
159     {
160         SurgeSynthesizer *s = instance->synthesizer();
161         for (unsigned int i = 0; i < n_total_params; i++)
162         {
163             SurgeSynthesizer::ID did;
164             if (s->fromDAWSideIndex(i, did))
165             {
166                 float value = s->getParameter01(did);
167                 self->setParameterAutomated(did.getDawSideIndex(), value);
168             }
169         }
170     }
171 
172 #if LINUX
173     self->_runLoop->execIdle();
174 #endif
175 
176     // TODO maybe handle the case of closed window here
177     return 0;
178 }
179 
handleZoom(SurgeGUIEditor * e,const LV2UI_Resize * resizer,bool resizeWindow)180 void SurgeLv2Ui::handleZoom(SurgeGUIEditor *e, const LV2UI_Resize *resizer, bool resizeWindow)
181 {
182     assert(e == _editor.get());
183 
184     float fzf = e->getZoomFactor() / 100.0;
185     int newW = e->getWindowSizeX() * fzf;
186     int newH = e->getWindowSizeY() * fzf;
187 
188     // identical implementation to VST2, except for here
189     if (resizer && resizeWindow && _uiInitialized)
190         resizer->ui_resize(resizer->handle, newW, newH);
191 
192     VSTGUI::CFrame *frame = e->getFrame();
193     if (frame)
194     {
195         frame->setZoom(fzf);
196         /*
197         ** cframe::setZoom uses prior size and integer math which means repeated resets drift
198         ** look at the "oroginWidth" math around in CFrame::setZoom. So rather than let those
199         ** drifts accumulate, just reset the size here since we know it
200         */
201         frame->setSize(newW, newH);
202 
203         setExtraScaleFactor(frame->getBackground(), e->getZoomFactor());
204 
205         frame->setDirty(true);
206         frame->invalid();
207     }
208 }
209 
setExtraScaleFactor(VSTGUI::CBitmap * bg,float zf)210 void SurgeLv2Ui::setExtraScaleFactor(VSTGUI::CBitmap *bg, float zf)
211 {
212     if (bg != NULL)
213     {
214         auto sbm = dynamic_cast<CScalableBitmap *>(bg); // dynamic casts are gross but better safe
215         if (sbm)
216         {
217             /*
218             ** VSTGUI has an error which is that the background bitmap doesn't get the frame
219             *transform
220             ** applied. Simply look at cviewcontainer::drawBackgroundRect. So we have to force the
221             *background
222             ** scale up using a backdoor API.
223             */
224             sbm->setExtraScaleFactor(zf);
225         }
226     }
227 }
228 
229 ///
230 #if LINUX
231 #include "vstgui/lib/platform/platform_x11.h"
232 #include "vstgui/lib/platform/linux/x11platform.h"
233 
234 namespace SurgeLv2
235 {
createRunLoop(void * ui)236 VSTGUI::SharedPointer<VSTGUI::X11::IRunLoop> createRunLoop(void *ui)
237 {
238     SurgeLv2Ui *self = (SurgeLv2Ui *)ui;
239     VSTGUI::SharedPointer<Lv2IdleRunLoop> runLoop(new Lv2IdleRunLoop);
240     self->associateIdleRunLoop(runLoop);
241     return runLoop;
242 }
243 } // namespace SurgeLv2
244 #endif // LINUX
245 
246 ///
247 LV2_SYMBOL_EXPORT
lv2ui_descriptor(uint32_t index)248 const LV2UI_Descriptor *lv2ui_descriptor(uint32_t index)
249 {
250 #if LINUX
251     VSTGUI::initializeSoHandle();
252 #endif
253 
254     if (index == 0)
255     {
256         static LV2UI_Descriptor desc = SurgeLv2Ui::createDescriptor();
257         return &desc;
258     }
259     return nullptr;
260 }
261