1 /*
2  * DISTRHO Plugin Framework (DPF)
3  * Copyright (C) 2012-2016 Filipe Coelho <falktx@falktx.com>
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
6  * or without fee is hereby granted, provided that the above copyright notice and this
7  * permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include "DistrhoUIInternal.hpp"
18 
19 #include "../extra/String.hpp"
20 
21 #include "lv2/atom.h"
22 #include "lv2/atom-util.h"
23 #include "lv2/data-access.h"
24 #include "lv2/instance-access.h"
25 #include "lv2/midi.h"
26 #include "lv2/options.h"
27 #include "lv2/parameters.h"
28 #include "lv2/ui.h"
29 #include "lv2/urid.h"
30 #include "lv2/lv2_kxstudio_properties.h"
31 #include "lv2/lv2_programs.h"
32 
33 #ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX
34 # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
35 #endif
36 
37 START_NAMESPACE_DISTRHO
38 
39 typedef struct _LV2_Atom_MidiEvent {
40     LV2_Atom atom;    /**< Atom header. */
41     uint8_t  data[3]; /**< MIDI data (body). */
42 } LV2_Atom_MidiEvent;
43 
44 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
45 static const sendNoteFunc sendNoteCallback = nullptr;
46 #endif
47 
48 // -----------------------------------------------------------------------
49 
50 class UiLv2
51 {
52 public:
53     UiLv2(const char* const bundlePath, const intptr_t winId,
54           const LV2_Options_Option* options, const LV2_URID_Map* const uridMap, const LV2UI_Resize* const uiResz, const LV2UI_Touch* uiTouch,
55           const LV2UI_Controller controller, const LV2UI_Write_Function writeFunc,
56           const float scaleFactor, LV2UI_Widget* const widget, void* const dspPtr)
57         : fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, scaleFactor, dspPtr, bundlePath),
58           fUridMap(uridMap),
59           fUiResize(uiResz),
60           fUiTouch(uiTouch),
61           fController(controller),
62           fWriteFunction(writeFunc),
63           fEventTransferURID(uridMap->map(uridMap->handle, LV2_ATOM__eventTransfer)),
64           fMidiEventURID(uridMap->map(uridMap->handle, LV2_MIDI__MidiEvent)),
65           fKeyValueURID(uridMap->map(uridMap->handle, DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")),
66           fWinIdWasNull(winId == 0)
67     {
68         if (fUiResize != nullptr && winId != 0)
69             fUiResize->ui_resize(fUiResize->handle, fUI.getWidth(), fUI.getHeight());
70 
71         if (widget != nullptr)
72             *widget = (LV2UI_Widget)fUI.getWindowId();
73 
74 #if DISTRHO_PLUGIN_WANT_STATE
75         // tell the DSP we're ready to receive msgs
76         setState("__dpf_ui_data__", "");
77 #endif
78 
79         if (winId != 0)
80             return;
81 
82         // if winId == 0 then options must not be null
83         DISTRHO_SAFE_ASSERT_RETURN(options != nullptr,);
84 
85         const LV2_URID uridWindowTitle(uridMap->map(uridMap->handle, LV2_UI__windowTitle));
86         const LV2_URID uridTransientWinId(uridMap->map(uridMap->handle, LV2_KXSTUDIO_PROPERTIES__TransientWindowId));
87 
88         bool hasTitle = false;
89 
90         for (int i=0; options[i].key != 0; ++i)
log_pre_callback(void)91         {
92             if (options[i].key == uridTransientWinId)
93             {
94                 if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Long))
95                 {
96                     if (const int64_t transientWinId = *(const int64_t*)options[i].value)
97                         fUI.setWindowTransientWinId(static_cast<intptr_t>(transientWinId));
98                 }
99                 else
100                     d_stderr("Host provides transientWinId but has wrong value type");
101             }
102             else if (options[i].key == uridWindowTitle)
103             {
104                 if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__String))
105                 {
106                     if (const char* const windowTitle = (const char*)options[i].value)
107                     {
108                         hasTitle = true;
109                         fUI.setWindowTitle(windowTitle);
110                     }
111                 }
112                 else
113                     d_stderr("Host provides windowTitle but has wrong value type");
114             }
115         }
116 
117         if (! hasTitle)
main(int argc,char * argv[])118             fUI.setWindowTitle(DISTRHO_PLUGIN_NAME);
119     }
120 
121     // -------------------------------------------------------------------
122 
123     void lv2ui_port_event(const uint32_t rindex, const uint32_t bufferSize, const uint32_t format, const void* const buffer)
124     {
125         if (format == 0)
126         {
127             const uint32_t parameterOffset(fUI.getParameterOffset());
128 
129             if (rindex < parameterOffset)
130                 return;
131 
132             DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),)
133 
134             const float value(*(const float*)buffer);
135             fUI.parameterChanged(rindex-parameterOffset, value);
136         }
137 #if DISTRHO_PLUGIN_WANT_STATE
138         else if (format == fEventTransferURID)
139         {
140             const LV2_Atom* const atom((const LV2_Atom*)buffer);
141 
142             DISTRHO_SAFE_ASSERT_RETURN(atom->type == fKeyValueURID,);
143 
144             const char* const key   = (const char*)LV2_ATOM_BODY_CONST(atom);
145             const char* const value = key+(std::strlen(key)+1);
146 
147             fUI.stateChanged(key, value);
148         }
149 #endif
150     }
151 
152     // -------------------------------------------------------------------
153 
154     int lv2ui_idle()
155     {
156         if (fWinIdWasNull)
157             return (fUI.idle() && fUI.isVisible()) ? 0 : 1;
158 
159         return fUI.idle() ? 0 : 1;
160     }
161 
162     int lv2ui_show()
163     {
164         return fUI.setWindowVisible(true) ? 0 : 1;
165     }
166 
167     int lv2ui_hide()
168     {
169         return fUI.setWindowVisible(false) ? 0 : 1;
170     }
171 
172     int lv2ui_resize(uint width, uint height)
173     {
174         fUI.setWindowSize(width, height, true);
175         return 0;
176     }
177 
178     // -------------------------------------------------------------------
179 
180     uint32_t lv2_get_options(LV2_Options_Option* const /*options*/)
181     {
182         // currently unused
183         return LV2_OPTIONS_ERR_UNKNOWN;
184     }
185 
186     uint32_t lv2_set_options(const LV2_Options_Option* const options)
187     {
188         for (int i=0; options[i].key != 0; ++i)
189         {
190             if (options[i].key == fUridMap->map(fUridMap->handle, LV2_PARAMETERS__sampleRate))
191             {
192                 if (options[i].type == fUridMap->map(fUridMap->handle, LV2_ATOM__Float))
193                 {
194                     const float sampleRate(*(const float*)options[i].value);
195                     fUI.setSampleRate(sampleRate);
196                     continue;
197                 }
198                 else
199                 {
200                     d_stderr("Host changed UI sample-rate but with wrong value type");
201                     continue;
202                 }
203             }
204         }
205 
206         return LV2_OPTIONS_SUCCESS;
207     }
208 
209     // -------------------------------------------------------------------
210 
211 #if DISTRHO_PLUGIN_WANT_PROGRAMS
212     void lv2ui_select_program(const uint32_t bank, const uint32_t program)
213     {
214         const uint32_t realProgram(bank * 128 + program);
215 
216         fUI.programLoaded(realProgram);
217     }
218 #endif
219 
220     // -------------------------------------------------------------------
221 
222 protected:
223     void editParameterValue(const uint32_t rindex, const bool started)
224     {
225         if (fUiTouch != nullptr && fUiTouch->touch != nullptr)
226             fUiTouch->touch(fUiTouch->handle, rindex, started);
227     }
228 
229     void setParameterValue(const uint32_t rindex, const float value)
230     {
231         DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
232 
233         fWriteFunction(fController, rindex, sizeof(float), 0, &value);
234     }
235 
236     void setState(const char* const key, const char* const value)
237     {
238         DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
239 
240         const uint32_t eventInPortIndex(DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS);
241 
242         // join key and value
243         String tmpStr;
244         tmpStr += key;
245         tmpStr += "\xff";
246         tmpStr += value;
247 
248         tmpStr[std::strlen(key)] = '\0';
249 
250         // set msg size (key + separator + value + null terminator)
251         const size_t msgSize(tmpStr.length()+1);
252 
253         // reserve atom space
254         const size_t atomSize(sizeof(LV2_Atom) + msgSize);
255         char         atomBuf[atomSize];
256         std::memset(atomBuf, 0, atomSize);
257 
258         // set atom info
259         LV2_Atom* const atom((LV2_Atom*)atomBuf);
260         atom->size = msgSize;
261         atom->type = fKeyValueURID;
262 
263         // set atom data
264         std::memcpy(atomBuf + sizeof(LV2_Atom), tmpStr.buffer(), msgSize);
265 
266         // send to DSP side
267         fWriteFunction(fController, eventInPortIndex, atomSize, fEventTransferURID, atom);
268     }
269 
270 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
271     void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
272     {
273         DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
274 
275         if (channel > 0xF)
276             return;
277 
278         const uint32_t eventInPortIndex(DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS);
279 
280         LV2_Atom_MidiEvent atomMidiEvent;
281         atomMidiEvent.atom.size = 3;
282         atomMidiEvent.atom.type = fMidiEventURID;
283 
284         atomMidiEvent.data[0] = channel + (velocity != 0 ? 0x90 : 0x80);
285         atomMidiEvent.data[1] = note;
286         atomMidiEvent.data[2] = velocity;
287 
288         // send to DSP side
289         fWriteFunction(fController, eventInPortIndex, lv2_atom_total_size(&atomMidiEvent.atom), fEventTransferURID, &atomMidiEvent);
290     }
291 #endif
292 
293     void setSize(const uint width, const uint height)
294     {
295         fUI.setWindowSize(width, height);
296 
297         if (fUiResize != nullptr && ! fWinIdWasNull)
298             fUiResize->ui_resize(fUiResize->handle, width, height);
299     }
300 
301 private:
302     UIExporter fUI;
303 
304     // LV2 features
305     const LV2_URID_Map* const fUridMap;
306     const LV2UI_Resize* const fUiResize;
307     const LV2UI_Touch*  const fUiTouch;
308 
309     // LV2 UI stuff
310     const LV2UI_Controller     fController;
311     const LV2UI_Write_Function fWriteFunction;
312 
313     // Need to save this
314     const LV2_URID fEventTransferURID;
315     const LV2_URID fMidiEventURID;
316     const LV2_URID fKeyValueURID;
317 
318     // using ui:showInterface if true
319     bool fWinIdWasNull;
320 
321     // -------------------------------------------------------------------
322     // Callbacks
323 
324     #define uiPtr ((UiLv2*)ptr)
325 
326     static void editParameterCallback(void* ptr, uint32_t rindex, bool started)
327     {
328         uiPtr->editParameterValue(rindex, started);
329     }
330 
331     static void setParameterCallback(void* ptr, uint32_t rindex, float value)
332     {
333         uiPtr->setParameterValue(rindex, value);
334     }
335 
336     static void setStateCallback(void* ptr, const char* key, const char* value)
337     {
338         uiPtr->setState(key, value);
339     }
340 
341 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
342     static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity)
343     {
344         uiPtr->sendNote(channel, note, velocity);
345     }
346 #endif
347 
348     static void setSizeCallback(void* ptr, uint width, uint height)
349     {
350         uiPtr->setSize(width, height);
351     }
352 
353     #undef uiPtr
354 };
355 
356 // -----------------------------------------------------------------------
357 
358 static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char* uri, const char* bundlePath,
359                                       LV2UI_Write_Function writeFunction, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features)
360 {
361     if (uri == nullptr || std::strcmp(uri, DISTRHO_PLUGIN_URI) != 0)
362     {
363         d_stderr("Invalid plugin URI");
364         return nullptr;
365     }
366 
367     const LV2_Options_Option* options = nullptr;
368     const LV2_URID_Map*       uridMap = nullptr;
369     const LV2UI_Resize*      uiResize = nullptr;
370     const LV2UI_Touch*       uiTouch  = nullptr;
371     void*                    parentId = nullptr;
372     void*                    instance = nullptr;
373 
374 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
375     struct LV2_DirectAccess_Interface {
376         void* (*get_instance_pointer)(LV2_Handle handle);
377     };
378     const LV2_Extension_Data_Feature* extData = nullptr;
379 #endif
380 
381     for (int i=0; features[i] != nullptr; ++i)
382     {
383         if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0)
384             options = (const LV2_Options_Option*)features[i]->data;
385         else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0)
386             uridMap = (const LV2_URID_Map*)features[i]->data;
387         else if (std::strcmp(features[i]->URI, LV2_UI__resize) == 0)
388             uiResize = (const LV2UI_Resize*)features[i]->data;
389         else if (std::strcmp(features[i]->URI, LV2_UI__parent) == 0)
390             parentId = features[i]->data;
391         else if (std::strcmp(features[i]->URI, LV2_UI__touch) == 0)
392             uiTouch = (const LV2UI_Touch*)features[i]->data;
393 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
394         else if (std::strcmp(features[i]->URI, LV2_DATA_ACCESS_URI) == 0)
395             extData = (const LV2_Extension_Data_Feature*)features[i]->data;
396         else if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0)
397             instance = features[i]->data;
398 #endif
399     }
400 
401     if (options == nullptr && parentId == nullptr)
402     {
403         d_stderr("Options feature missing (needed for show-interface), cannot continue!");
404         return nullptr;
405     }
406 
407     if (uridMap == nullptr)
408     {
409         d_stderr("URID Map feature missing, cannot continue!");
410         return nullptr;
411     }
412 
413     if (parentId == nullptr)
414     {
415         d_stdout("Parent Window Id missing, host should be using ui:showInterface...");
416     }
417 
418 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
419     if (extData == nullptr || instance == nullptr)
420     {
421         d_stderr("Data or instance access missing, cannot continue!");
422         return nullptr;
423     }
424 
425     if (const LV2_DirectAccess_Interface* const directAccess = (const LV2_DirectAccess_Interface*)extData->data_access(DISTRHO_PLUGIN_LV2_STATE_PREFIX "direct-access"))
426         instance = directAccess->get_instance_pointer(instance);
427     else
428         instance = nullptr;
429 
430     if (instance == nullptr)
431     {
432         d_stderr("Failed to get direct access, cannot continue!");
433         return nullptr;
434     }
435 #endif
436 
437     float scaleFactor = 1.0f;
438     const intptr_t winId((intptr_t)parentId);
439 
440     if (options != nullptr)
441     {
442         const LV2_URID uridAtomFloat(uridMap->map(uridMap->handle, LV2_ATOM__Float));
443         const LV2_URID uridSampleRate(uridMap->map(uridMap->handle, LV2_PARAMETERS__sampleRate));
444         const LV2_URID uridScaleFactor(uridMap->map(uridMap->handle, LV2_UI__scaleFactor));
445 
446         for (int i=0; options[i].key != 0; ++i)
447         {
448             /**/ if (options[i].key == uridSampleRate)
449             {
450                 if (options[i].type == uridAtomFloat)
451                     d_lastUiSampleRate = *(const float*)options[i].value;
452                 else
453                     d_stderr("Host provides UI sample-rate but has wrong value type");
454             }
455             else if (options[i].key == uridScaleFactor)
456             {
457                 if (options[i].type == uridAtomFloat)
458                     scaleFactor = *(const float*)options[i].value;
parse_psql_options(int argc,char * argv[],struct adhoc_opts * options)459                 else
460                     d_stderr("Host provides UI scale factor but has wrong value type");
461             }
462         }
463     }
464 
465     if (d_lastUiSampleRate < 1.0)
466     {
467         d_stdout("WARNING: this host does not send sample-rate information for LV2 UIs, using 44100 as fallback (this could be wrong)");
468         d_lastUiSampleRate = 44100.0;
469     }
470 
471     return new UiLv2(bundlePath, winId, options, uridMap, uiResize, uiTouch, controller, writeFunction, scaleFactor, widget, instance);
472 }
473 
474 #define uiPtr ((UiLv2*)ui)
475 
476 static void lv2ui_cleanup(LV2UI_Handle ui)
477 {
478     delete uiPtr;
479 }
480 
481 static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
482 {
483     uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer);
484 }
485 
486 // -----------------------------------------------------------------------
487 
488 static int lv2ui_idle(LV2UI_Handle ui)
489 {
490     return uiPtr->lv2ui_idle();
491 }
492 
493 static int lv2ui_show(LV2UI_Handle ui)
494 {
495     return uiPtr->lv2ui_show();
496 }
497 
498 static int lv2ui_hide(LV2UI_Handle ui)
499 {
500     return uiPtr->lv2ui_hide();
501 }
502 
503 static int lv2ui_resize(LV2UI_Handle ui, int width, int height)
504 {
505     DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, 1);
506     DISTRHO_SAFE_ASSERT_RETURN(width > 0, 1);
507     DISTRHO_SAFE_ASSERT_RETURN(height > 0, 1);
508 
509     return 1; // This needs more testing
510     //return uiPtr->lv2ui_resize(width, height);
511 }
512 
513 // -----------------------------------------------------------------------
514 
515 static uint32_t lv2_get_options(LV2UI_Handle ui, LV2_Options_Option* options)
516 {
517     return uiPtr->lv2_get_options(options);
518 }
519 
520 static uint32_t lv2_set_options(LV2UI_Handle ui, const LV2_Options_Option* options)
521 {
522     return uiPtr->lv2_set_options(options);
523 }
524 
525 // -----------------------------------------------------------------------
526 
527 #if DISTRHO_PLUGIN_WANT_PROGRAMS
528 static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program)
529 {
530     uiPtr->lv2ui_select_program(bank, program);
531 }
532 #endif
533 
534 // -----------------------------------------------------------------------
535 
536 static const void* lv2ui_extension_data(const char* uri)
537 {
538     static const LV2_Options_Interface options = { lv2_get_options, lv2_set_options };
539     static const LV2UI_Idle_Interface  uiIdle  = { lv2ui_idle };
540     static const LV2UI_Show_Interface  uiShow  = { lv2ui_show, lv2ui_hide };
541     static const LV2UI_Resize          uiResz  = { nullptr, lv2ui_resize };
542 
543     if (std::strcmp(uri, LV2_OPTIONS__interface) == 0)
544         return &options;
545     if (std::strcmp(uri, LV2_UI__idleInterface) == 0)
546         return &uiIdle;
547     if (std::strcmp(uri, LV2_UI__showInterface) == 0)
548         return &uiShow;
549     if (std::strcmp(uri, LV2_UI__resize) == 0)
550         return &uiResz;
551 
552 #if DISTRHO_PLUGIN_WANT_PROGRAMS
553     static const LV2_Programs_UI_Interface uiPrograms = { lv2ui_select_program };
554 
555     if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0)
556         return &uiPrograms;
557 #endif
558 
559     return nullptr;
560 }
561 
562 #undef instancePtr
563 
564 // -----------------------------------------------------------------------
565 
566 static const LV2UI_Descriptor sLv2UiDescriptor = {
567     DISTRHO_UI_URI,
568     lv2ui_instantiate,
569     lv2ui_cleanup,
570     lv2ui_port_event,
571     lv2ui_extension_data
572 };
573 
574 // -----------------------------------------------------------------------
575 
576 END_NAMESPACE_DISTRHO
577 
578 DISTRHO_PLUGIN_EXPORT
579 const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
580 {
581     USE_NAMESPACE_DISTRHO
582     return (index == 0) ? &sLv2UiDescriptor : nullptr;
583 }
584 
585 // -----------------------------------------------------------------------
586