DISTRHO Plugin Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

930 lines
31KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2021 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. #include "DistrhoUIInternal.hpp"
  17. #include "../extra/String.hpp"
  18. #include "lv2/atom.h"
  19. #include "lv2/atom-util.h"
  20. #include "lv2/data-access.h"
  21. #include "lv2/instance-access.h"
  22. #include "lv2/midi.h"
  23. #include "lv2/options.h"
  24. #include "lv2/parameters.h"
  25. #include "lv2/patch.h"
  26. #include "lv2/ui.h"
  27. #include "lv2/urid.h"
  28. #include "lv2/lv2_kxstudio_properties.h"
  29. #include "lv2/lv2_programs.h"
  30. #ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX
  31. # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
  32. #endif
  33. START_NAMESPACE_DISTRHO
  34. typedef struct _LV2_Atom_MidiEvent {
  35. LV2_Atom atom; /**< Atom header. */
  36. uint8_t data[3]; /**< MIDI data (body). */
  37. } LV2_Atom_MidiEvent;
  38. #if ! DISTRHO_PLUGIN_WANT_STATE
  39. static constexpr const setStateFunc setStateCallback = nullptr;
  40. #endif
  41. #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
  42. static constexpr const sendNoteFunc sendNoteCallback = nullptr;
  43. #endif
  44. // -----------------------------------------------------------------------
  45. template <class LV2F>
  46. static const LV2F* getLv2Feature(const LV2_Feature* const* features, const char* const uri)
  47. {
  48. for (int i=0; features[i] != nullptr; ++i)
  49. {
  50. if (std::strcmp(features[i]->URI, uri) == 0)
  51. return (const LV2F*)features[i]->data;
  52. }
  53. return nullptr;
  54. }
  55. class UiLv2
  56. {
  57. public:
  58. UiLv2(const char* const bundlePath,
  59. const intptr_t winId,
  60. const LV2_Options_Option* options,
  61. const LV2_URID_Map* const uridMap,
  62. const LV2_Feature* const* const features,
  63. const LV2UI_Controller controller,
  64. const LV2UI_Write_Function writeFunc,
  65. LV2UI_Widget* const widget,
  66. void* const dspPtr,
  67. const float sampleRate,
  68. const float scaleFactor,
  69. const uint32_t bgColor,
  70. const uint32_t fgColor,
  71. const char* const appClassName)
  72. : fUridMap(uridMap),
  73. fUridUnmap(getLv2Feature<LV2_URID_Unmap>(features, LV2_URID__unmap)),
  74. fUiPortMap(getLv2Feature<LV2UI_Port_Map>(features, LV2_UI__portMap)),
  75. fUiRequestValue(getLv2Feature<LV2UI_Request_Value>(features, LV2_UI__requestValue)),
  76. fUiTouch(getLv2Feature<LV2UI_Touch>(features, LV2_UI__touch)),
  77. fController(controller),
  78. fWriteFunction(writeFunc),
  79. fURIDs(uridMap),
  80. fBypassParameterIndex(fUiPortMap != nullptr ? fUiPortMap->port_index(fUiPortMap->handle, ParameterDesignationSymbols::bypass_lv2)
  81. : LV2UI_INVALID_PORT_INDEX),
  82. fWinIdWasNull(winId == 0),
  83. fUI(this, winId, sampleRate,
  84. editParameterCallback,
  85. setParameterCallback,
  86. setStateCallback,
  87. sendNoteCallback,
  88. nullptr, // resize is very messy, hosts can do it without extensions
  89. fileRequestCallback,
  90. bundlePath, dspPtr, scaleFactor, bgColor, fgColor, appClassName)
  91. {
  92. if (widget != nullptr)
  93. *widget = (LV2UI_Widget)fUI.getNativeWindowHandle();
  94. #if DISTRHO_PLUGIN_WANT_STATE
  95. // tell the DSP we're ready to receive msgs
  96. setState("__dpf_ui_data__", "");
  97. #endif
  98. if (winId != 0)
  99. return;
  100. // if winId == 0 then options must not be null
  101. DISTRHO_SAFE_ASSERT_RETURN(options != nullptr,);
  102. #ifndef __EMSCRIPTEN__
  103. const LV2_URID uridWindowTitle = uridMap->map(uridMap->handle, LV2_UI__windowTitle);
  104. const LV2_URID uridTransientWinId = uridMap->map(uridMap->handle, LV2_KXSTUDIO_PROPERTIES__TransientWindowId);
  105. const char* windowTitle = nullptr;
  106. for (int i=0; options[i].key != 0; ++i)
  107. {
  108. if (options[i].key == uridTransientWinId)
  109. {
  110. if (options[i].type == fURIDs.atomLong)
  111. {
  112. if (const int64_t transientWinId = *(const int64_t*)options[i].value)
  113. fUI.setWindowTransientWinId(static_cast<intptr_t>(transientWinId));
  114. }
  115. else
  116. d_stderr("Host provides transientWinId but has wrong value type");
  117. }
  118. else if (options[i].key == uridWindowTitle)
  119. {
  120. if (options[i].type == fURIDs.atomString)
  121. {
  122. windowTitle = (const char*)options[i].value;
  123. }
  124. else
  125. d_stderr("Host provides windowTitle but has wrong value type");
  126. }
  127. }
  128. if (windowTitle == nullptr)
  129. windowTitle = DISTRHO_PLUGIN_NAME;
  130. fUI.setWindowTitle(windowTitle);
  131. #endif
  132. }
  133. // -------------------------------------------------------------------
  134. void lv2ui_port_event(const uint32_t rindex, const uint32_t bufferSize, const uint32_t format, const void* const buffer)
  135. {
  136. if (format == 0)
  137. {
  138. const uint32_t parameterOffset = fUI.getParameterOffset();
  139. if (rindex < parameterOffset)
  140. return;
  141. DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),)
  142. float value = *(const float*)buffer;
  143. if (rindex == fBypassParameterIndex)
  144. value = 1.0f - value;
  145. fUI.parameterChanged(rindex-parameterOffset, value);
  146. }
  147. #if DISTRHO_PLUGIN_WANT_STATE
  148. else if (format == fURIDs.atomEventTransfer)
  149. {
  150. const LV2_Atom* const atom = (const LV2_Atom*)buffer;
  151. if (atom->type == fURIDs.dpfKeyValue)
  152. {
  153. const char* const key = (const char*)LV2_ATOM_BODY_CONST(atom);
  154. const char* const value = key+(std::strlen(key)+1);
  155. fUI.stateChanged(key, value);
  156. }
  157. else if (atom->type == fURIDs.atomObject && fUridUnmap != nullptr)
  158. {
  159. const LV2_Atom_Object* const obj = (const LV2_Atom_Object*)atom;
  160. const LV2_Atom* property = nullptr;
  161. const LV2_Atom* atomvalue = nullptr;
  162. lv2_atom_object_get(obj, fURIDs.patchProperty, &property, fURIDs.patchValue, &atomvalue, 0);
  163. DISTRHO_SAFE_ASSERT_RETURN(property != nullptr,);
  164. DISTRHO_SAFE_ASSERT_RETURN(atomvalue != nullptr,);
  165. DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID,);
  166. DISTRHO_SAFE_ASSERT_RETURN(atomvalue->type == fURIDs.atomPath || atomvalue->type == fURIDs.atomString,);
  167. if (property != nullptr && property->type == fURIDs.atomURID &&
  168. atomvalue != nullptr && (atomvalue->type == fURIDs.atomPath || atomvalue->type == fURIDs.atomString))
  169. {
  170. const LV2_URID dpf_lv2_urid = ((const LV2_Atom_URID*)property)->body;
  171. DISTRHO_SAFE_ASSERT_RETURN(dpf_lv2_urid != 0,);
  172. const char* const dpf_lv2_key = fUridUnmap->unmap(fUridUnmap->handle, dpf_lv2_urid);
  173. DISTRHO_SAFE_ASSERT_RETURN(dpf_lv2_key != nullptr,);
  174. /*constexpr*/ const size_t reqLen = std::strlen(DISTRHO_PLUGIN_URI "#");
  175. DISTRHO_SAFE_ASSERT_RETURN(std::strlen(dpf_lv2_key) > reqLen,);
  176. const char* const key = dpf_lv2_key + reqLen;
  177. const char* const value = (const char*)LV2_ATOM_BODY_CONST(atomvalue);
  178. fUI.stateChanged(key, value);
  179. }
  180. }
  181. else if (atom->type == fURIDs.midiEvent)
  182. {
  183. // ignore
  184. }
  185. else
  186. {
  187. d_stdout("DPF :: received atom not handled :: %s",
  188. fUridUnmap != nullptr ? fUridUnmap->unmap(fUridUnmap->handle, atom->type) : "(null)");
  189. }
  190. }
  191. #endif
  192. }
  193. // -------------------------------------------------------------------
  194. int lv2ui_idle()
  195. {
  196. if (fWinIdWasNull)
  197. return (fUI.plugin_idle() && fUI.isVisible()) ? 0 : 1;
  198. return fUI.plugin_idle() ? 0 : 1;
  199. }
  200. int lv2ui_show()
  201. {
  202. return fUI.setWindowVisible(true) ? 0 : 1;
  203. }
  204. int lv2ui_hide()
  205. {
  206. return fUI.setWindowVisible(false) ? 0 : 1;
  207. }
  208. // -------------------------------------------------------------------
  209. uint32_t lv2_get_options(LV2_Options_Option* const /*options*/)
  210. {
  211. // currently unused
  212. return LV2_OPTIONS_ERR_UNKNOWN;
  213. }
  214. uint32_t lv2_set_options(const LV2_Options_Option* const options)
  215. {
  216. for (int i=0; options[i].key != 0; ++i)
  217. {
  218. if (options[i].key == fURIDs.paramSampleRate)
  219. {
  220. if (options[i].type == fURIDs.atomFloat)
  221. {
  222. const float sampleRate = *(const float*)options[i].value;
  223. fUI.setSampleRate(sampleRate, true);
  224. continue;
  225. }
  226. else
  227. {
  228. d_stderr("Host changed UI sample-rate but with wrong value type");
  229. continue;
  230. }
  231. }
  232. }
  233. return LV2_OPTIONS_SUCCESS;
  234. }
  235. // -------------------------------------------------------------------
  236. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  237. void lv2ui_select_program(const uint32_t bank, const uint32_t program)
  238. {
  239. const uint32_t realProgram = bank * 128 + program;
  240. fUI.programLoaded(realProgram);
  241. }
  242. #endif
  243. // -------------------------------------------------------------------
  244. private:
  245. // LV2 features
  246. const LV2_URID_Map* const fUridMap;
  247. const LV2_URID_Unmap* const fUridUnmap;
  248. const LV2UI_Port_Map* const fUiPortMap;
  249. const LV2UI_Request_Value* const fUiRequestValue;
  250. const LV2UI_Touch* const fUiTouch;
  251. // LV2 UI stuff
  252. const LV2UI_Controller fController;
  253. const LV2UI_Write_Function fWriteFunction;
  254. // LV2 URIDs
  255. const struct URIDs {
  256. const LV2_URID_Map* _uridMap;
  257. const LV2_URID dpfKeyValue;
  258. const LV2_URID atomEventTransfer;
  259. const LV2_URID atomFloat;
  260. const LV2_URID atomLong;
  261. const LV2_URID atomObject;
  262. const LV2_URID atomPath;
  263. const LV2_URID atomString;
  264. const LV2_URID atomURID;
  265. const LV2_URID midiEvent;
  266. const LV2_URID paramSampleRate;
  267. const LV2_URID patchProperty;
  268. const LV2_URID patchSet;
  269. const LV2_URID patchValue;
  270. URIDs(const LV2_URID_Map* const uridMap)
  271. : _uridMap(uridMap),
  272. dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")),
  273. atomEventTransfer(map(LV2_ATOM__eventTransfer)),
  274. atomFloat(map(LV2_ATOM__Float)),
  275. atomLong(map(LV2_ATOM__Long)),
  276. atomObject(map(LV2_ATOM__Object)),
  277. atomPath(map(LV2_ATOM__Path)),
  278. atomString(map(LV2_ATOM__String)),
  279. atomURID(map(LV2_ATOM__URID)),
  280. midiEvent(map(LV2_MIDI__MidiEvent)),
  281. paramSampleRate(map(LV2_PARAMETERS__sampleRate)),
  282. patchProperty(map(LV2_PATCH__property)),
  283. patchSet(map(LV2_PATCH__Set)),
  284. patchValue(map(LV2_PATCH__value)) {}
  285. inline LV2_URID map(const char* const uri) const
  286. {
  287. return _uridMap->map(_uridMap->handle, uri);
  288. }
  289. } fURIDs;
  290. // index of bypass parameter, if present
  291. const uint32_t fBypassParameterIndex;
  292. // using ui:showInterface if true
  293. const bool fWinIdWasNull;
  294. // Plugin UI (after LV2 stuff so the UI can call into us during its constructor)
  295. UIExporter fUI;
  296. // ----------------------------------------------------------------------------------------------------------------
  297. // DPF callbacks
  298. void editParameterValue(const uint32_t rindex, const bool started)
  299. {
  300. if (fUiTouch != nullptr && fUiTouch->touch != nullptr)
  301. fUiTouch->touch(fUiTouch->handle, rindex, started);
  302. }
  303. static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started)
  304. {
  305. static_cast<UiLv2*>(ptr)->editParameterValue(rindex, started);
  306. }
  307. void setParameterValue(const uint32_t rindex, float value)
  308. {
  309. DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
  310. if (rindex == fBypassParameterIndex)
  311. value = 1.0f - value;
  312. fWriteFunction(fController, rindex, sizeof(float), 0, &value);
  313. }
  314. static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value)
  315. {
  316. static_cast<UiLv2*>(ptr)->setParameterValue(rindex, value);
  317. }
  318. #if DISTRHO_PLUGIN_WANT_STATE
  319. void setState(const char* const key, const char* const value)
  320. {
  321. DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
  322. const uint32_t eventInPortIndex = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS;
  323. // join key and value
  324. String tmpStr;
  325. tmpStr += key;
  326. tmpStr += "\xff";
  327. tmpStr += value;
  328. tmpStr[std::strlen(key)] = '\0';
  329. // set msg size (key + separator + value + null terminator)
  330. const uint32_t msgSize = static_cast<uint32_t>(tmpStr.length()) + 1U;
  331. // reserve atom space
  332. const uint32_t atomSize = sizeof(LV2_Atom) + msgSize;
  333. char* const atomBuf = (char*)malloc(atomSize);
  334. DISTRHO_SAFE_ASSERT_RETURN(atomBuf != nullptr,);
  335. std::memset(atomBuf, 0, atomSize);
  336. // set atom info
  337. LV2_Atom* const atom = (LV2_Atom*)atomBuf;
  338. atom->size = msgSize;
  339. atom->type = fURIDs.dpfKeyValue;
  340. // set atom data
  341. std::memcpy(atomBuf + sizeof(LV2_Atom), tmpStr.buffer(), msgSize);
  342. // send to DSP side
  343. fWriteFunction(fController, eventInPortIndex, atomSize, fURIDs.atomEventTransfer, atom);
  344. // free atom space
  345. free(atomBuf);
  346. }
  347. static void setStateCallback(void* const ptr, const char* const key, const char* const value)
  348. {
  349. static_cast<UiLv2*>(ptr)->setState(key, value);
  350. }
  351. #endif
  352. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  353. void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
  354. {
  355. DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
  356. if (channel > 0xF)
  357. return;
  358. const uint32_t eventInPortIndex = DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS;
  359. LV2_Atom_MidiEvent atomMidiEvent;
  360. atomMidiEvent.atom.size = 3;
  361. atomMidiEvent.atom.type = fURIDs.midiEvent;
  362. atomMidiEvent.data[0] = channel + (velocity != 0 ? 0x90 : 0x80);
  363. atomMidiEvent.data[1] = note;
  364. atomMidiEvent.data[2] = velocity;
  365. // send to DSP side
  366. fWriteFunction(fController, eventInPortIndex, lv2_atom_total_size(&atomMidiEvent.atom),
  367. fURIDs.atomEventTransfer, &atomMidiEvent);
  368. }
  369. static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity)
  370. {
  371. static_cast<UiLv2*>(ptr)->sendNote(channel, note, velocity);
  372. }
  373. #endif
  374. bool fileRequest(const char* const key)
  375. {
  376. d_stdout("UI file request %s %p", key, fUiRequestValue);
  377. if (fUiRequestValue == nullptr)
  378. return false;
  379. String dpf_lv2_key(DISTRHO_PLUGIN_URI "#");
  380. dpf_lv2_key += key;
  381. const int r = fUiRequestValue->request(fUiRequestValue->handle,
  382. fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()),
  383. fURIDs.atomPath,
  384. nullptr);
  385. d_stdout("UI file request %s %p => %s %i", key, fUiRequestValue, dpf_lv2_key.buffer(), r);
  386. return r == LV2UI_REQUEST_VALUE_SUCCESS;
  387. }
  388. static bool fileRequestCallback(void* ptr, const char* key)
  389. {
  390. return static_cast<UiLv2*>(ptr)->fileRequest(key);
  391. }
  392. };
  393. // -----------------------------------------------------------------------
  394. static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*,
  395. const char* const uri,
  396. const char* const bundlePath,
  397. const LV2UI_Write_Function writeFunction,
  398. const LV2UI_Controller controller,
  399. LV2UI_Widget* const widget,
  400. const LV2_Feature* const* const features)
  401. {
  402. if (uri == nullptr || std::strcmp(uri, DISTRHO_PLUGIN_URI) != 0)
  403. {
  404. d_stderr("Invalid plugin URI");
  405. return nullptr;
  406. }
  407. const LV2_Options_Option* options = nullptr;
  408. const LV2_URID_Map* uridMap = nullptr;
  409. void* parentId = nullptr;
  410. void* instance = nullptr;
  411. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  412. struct LV2_DirectAccess_Interface {
  413. void* (*get_instance_pointer)(LV2_Handle handle);
  414. };
  415. const LV2_Extension_Data_Feature* extData = nullptr;
  416. #endif
  417. for (int i=0; features[i] != nullptr; ++i)
  418. {
  419. /**/ if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0)
  420. options = (const LV2_Options_Option*)features[i]->data;
  421. else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0)
  422. uridMap = (const LV2_URID_Map*)features[i]->data;
  423. else if (std::strcmp(features[i]->URI, LV2_UI__parent) == 0)
  424. parentId = features[i]->data;
  425. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  426. else if (std::strcmp(features[i]->URI, LV2_DATA_ACCESS_URI) == 0)
  427. extData = (const LV2_Extension_Data_Feature*)features[i]->data;
  428. else if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0)
  429. instance = features[i]->data;
  430. #endif
  431. }
  432. if (options == nullptr && parentId == nullptr)
  433. {
  434. d_stderr("Options feature missing (needed for show-interface), cannot continue!");
  435. return nullptr;
  436. }
  437. if (uridMap == nullptr)
  438. {
  439. d_stderr("URID Map feature missing, cannot continue!");
  440. return nullptr;
  441. }
  442. if (parentId == nullptr)
  443. {
  444. d_stdout("Parent Window Id missing, host should be using ui:showInterface...");
  445. }
  446. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  447. if (extData == nullptr || instance == nullptr)
  448. {
  449. d_stderr("Data or instance access missing, cannot continue!");
  450. return nullptr;
  451. }
  452. if (const LV2_DirectAccess_Interface* const directAccess = (const LV2_DirectAccess_Interface*)extData->data_access(DISTRHO_PLUGIN_LV2_STATE_PREFIX "direct-access"))
  453. instance = directAccess->get_instance_pointer(instance);
  454. else
  455. instance = nullptr;
  456. if (instance == nullptr)
  457. {
  458. d_stderr("Failed to get direct access, cannot continue!");
  459. return nullptr;
  460. }
  461. #endif
  462. const intptr_t winId = (intptr_t)parentId;
  463. float sampleRate = 0.0f;
  464. float scaleFactor = 0.0f;
  465. uint32_t bgColor = 0;
  466. uint32_t fgColor = 0xffffffff;
  467. const char* appClassName = nullptr;
  468. if (options != nullptr)
  469. {
  470. const LV2_URID uridAtomInt = uridMap->map(uridMap->handle, LV2_ATOM__Int);
  471. const LV2_URID uridAtomFloat = uridMap->map(uridMap->handle, LV2_ATOM__Float);
  472. const LV2_URID uridAtomString = uridMap->map(uridMap->handle, LV2_ATOM__String);
  473. const LV2_URID uridSampleRate = uridMap->map(uridMap->handle, LV2_PARAMETERS__sampleRate);
  474. const LV2_URID uridBgColor = uridMap->map(uridMap->handle, LV2_UI__backgroundColor);
  475. const LV2_URID uridFgColor = uridMap->map(uridMap->handle, LV2_UI__foregroundColor);
  476. #ifndef DISTRHO_OS_MAC
  477. const LV2_URID uridScaleFactor = uridMap->map(uridMap->handle, LV2_UI__scaleFactor);
  478. #endif
  479. const LV2_URID uridClassName = uridMap->map(uridMap->handle, "urn:distrho:className");
  480. for (int i=0; options[i].key != 0; ++i)
  481. {
  482. /**/ if (options[i].key == uridSampleRate)
  483. {
  484. if (options[i].type == uridAtomFloat)
  485. sampleRate = *(const float*)options[i].value;
  486. else
  487. d_stderr("Host provides UI sample-rate but has wrong value type");
  488. }
  489. else if (options[i].key == uridBgColor)
  490. {
  491. if (options[i].type == uridAtomInt)
  492. bgColor = (uint32_t)*(const int32_t*)options[i].value;
  493. else
  494. d_stderr("Host provides UI background color but has wrong value type");
  495. }
  496. else if (options[i].key == uridFgColor)
  497. {
  498. if (options[i].type == uridAtomInt)
  499. fgColor = (uint32_t)*(const int32_t*)options[i].value;
  500. else
  501. d_stderr("Host provides UI foreground color but has wrong value type");
  502. }
  503. #ifndef DISTRHO_OS_MAC
  504. else if (options[i].key == uridScaleFactor)
  505. {
  506. if (options[i].type == uridAtomFloat)
  507. scaleFactor = *(const float*)options[i].value;
  508. else
  509. d_stderr("Host provides UI scale factor but has wrong value type");
  510. }
  511. #endif
  512. else if (options[i].key == uridClassName)
  513. {
  514. if (options[i].type == uridAtomString)
  515. appClassName = (const char*)options[i].value;
  516. else
  517. d_stderr("Host provides UI scale factor but has wrong value type");
  518. }
  519. }
  520. }
  521. if (sampleRate < 1.0)
  522. {
  523. d_stdout("WARNING: this host does not send sample-rate information for LV2 UIs, using 44100 as fallback (this could be wrong)");
  524. sampleRate = 44100.0;
  525. }
  526. return new UiLv2(bundlePath, winId, options, uridMap, features,
  527. controller, writeFunction, widget, instance,
  528. sampleRate, scaleFactor, bgColor, fgColor, appClassName);
  529. }
  530. #define uiPtr ((UiLv2*)ui)
  531. static void lv2ui_cleanup(LV2UI_Handle ui)
  532. {
  533. delete uiPtr;
  534. }
  535. static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
  536. {
  537. uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer);
  538. }
  539. // -----------------------------------------------------------------------
  540. static int lv2ui_idle(LV2UI_Handle ui)
  541. {
  542. return uiPtr->lv2ui_idle();
  543. }
  544. static int lv2ui_show(LV2UI_Handle ui)
  545. {
  546. return uiPtr->lv2ui_show();
  547. }
  548. static int lv2ui_hide(LV2UI_Handle ui)
  549. {
  550. return uiPtr->lv2ui_hide();
  551. }
  552. // -----------------------------------------------------------------------
  553. static uint32_t lv2_get_options(LV2UI_Handle ui, LV2_Options_Option* options)
  554. {
  555. return uiPtr->lv2_get_options(options);
  556. }
  557. static uint32_t lv2_set_options(LV2UI_Handle ui, const LV2_Options_Option* options)
  558. {
  559. return uiPtr->lv2_set_options(options);
  560. }
  561. // -----------------------------------------------------------------------
  562. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  563. static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program)
  564. {
  565. uiPtr->lv2ui_select_program(bank, program);
  566. }
  567. #endif
  568. // -----------------------------------------------------------------------
  569. static const void* lv2ui_extension_data(const char* uri)
  570. {
  571. static const LV2_Options_Interface options = { lv2_get_options, lv2_set_options };
  572. static const LV2UI_Idle_Interface uiIdle = { lv2ui_idle };
  573. static const LV2UI_Show_Interface uiShow = { lv2ui_show, lv2ui_hide };
  574. if (std::strcmp(uri, LV2_OPTIONS__interface) == 0)
  575. return &options;
  576. if (std::strcmp(uri, LV2_UI__idleInterface) == 0)
  577. return &uiIdle;
  578. if (std::strcmp(uri, LV2_UI__showInterface) == 0)
  579. return &uiShow;
  580. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  581. static const LV2_Programs_UI_Interface uiPrograms = { lv2ui_select_program };
  582. if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0)
  583. return &uiPrograms;
  584. #endif
  585. return nullptr;
  586. }
  587. #undef instancePtr
  588. // -----------------------------------------------------------------------
  589. static const LV2UI_Descriptor sLv2UiDescriptor = {
  590. DISTRHO_UI_URI,
  591. lv2ui_instantiate,
  592. lv2ui_cleanup,
  593. lv2ui_port_event,
  594. lv2ui_extension_data
  595. };
  596. // -----------------------------------------------------------------------
  597. END_NAMESPACE_DISTRHO
  598. DISTRHO_PLUGIN_EXPORT
  599. const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
  600. {
  601. USE_NAMESPACE_DISTRHO
  602. return (index == 0) ? &sLv2UiDescriptor : nullptr;
  603. }
  604. #if defined(__MOD_DEVICES__) && defined(__EMSCRIPTEN__)
  605. #include <emscripten/html5.h>
  606. #include <string>
  607. typedef void (*_custom_param_set)(uint32_t port_index, float value);
  608. typedef void (*_custom_patch_set)(const char* uri, const char* value);
  609. struct ModguiHandle {
  610. LV2UI_Handle handle;
  611. long loop_id;
  612. _custom_param_set param_set;
  613. _custom_patch_set patch_set;
  614. };
  615. enum URIs {
  616. kUriNull,
  617. kUriAtomEventTransfer,
  618. kUriDpfKeyValue,
  619. };
  620. static std::vector<std::string> kURIs;
  621. static LV2_URID lv2_urid_map(LV2_URID_Map_Handle, const char* const uri)
  622. {
  623. for (size_t i=0, size=kURIs.size(); i<size; ++i)
  624. {
  625. if (kURIs[i] == uri)
  626. return i;
  627. }
  628. kURIs.push_back(uri);
  629. return kURIs.size() - 1u;
  630. }
  631. static const char* lv2_urid_unmap(LV2_URID_Map_Handle, const LV2_URID urid)
  632. {
  633. return kURIs[urid].c_str();
  634. }
  635. static void lv2ui_write_function(LV2UI_Controller controller,
  636. uint32_t port_index,
  637. uint32_t buffer_size,
  638. uint32_t port_protocol,
  639. const void* buffer)
  640. {
  641. DISTRHO_SAFE_ASSERT_RETURN(buffer_size >= 1,);
  642. // d_stdout("lv2ui_write_function %p %u %u %u %p", controller, port_index, buffer_size, port_protocol, buffer);
  643. ModguiHandle* const mhandle = static_cast<ModguiHandle*>(controller);
  644. switch (port_protocol)
  645. {
  646. case kUriNull:
  647. mhandle->param_set(port_index, *static_cast<const float*>(buffer));
  648. break;
  649. case kUriAtomEventTransfer:
  650. if (const LV2_Atom* const atom = static_cast<const LV2_Atom*>(buffer))
  651. {
  652. // d_stdout("lv2ui_write_function %u %u:%s", atom->size, atom->type, kURIs[atom->type].c_str());
  653. // if (kURIs[atom->type] == "urn:distrho:KeyValueState")
  654. {
  655. const char* const key = (const char*)(atom + 1);
  656. const char* const value = key + (std::strlen(key) + 1U);
  657. // d_stdout("lv2ui_write_function %s %s", key, value);
  658. String urikey;
  659. urikey = DISTRHO_PLUGIN_URI "#";
  660. urikey += key;
  661. mhandle->patch_set(urikey, value);
  662. }
  663. }
  664. break;
  665. }
  666. }
  667. static void app_idle(void* const handle)
  668. {
  669. static_cast<UiLv2*>(handle)->lv2ui_idle();
  670. }
  671. DISTRHO_PLUGIN_EXPORT
  672. LV2UI_Handle modgui_init(const char* const className, _custom_param_set param_set, _custom_patch_set patch_set)
  673. {
  674. d_stdout("init \"%s\"", className);
  675. DISTRHO_SAFE_ASSERT_RETURN(className != nullptr, nullptr);
  676. static LV2_URID_Map uridMap = { nullptr, lv2_urid_map };
  677. static LV2_URID_Unmap uridUnmap = { nullptr, lv2_urid_unmap };
  678. // known first URIDs, matching URIs
  679. if (kURIs.empty())
  680. {
  681. kURIs.push_back("");
  682. kURIs.push_back("http://lv2plug.in/ns/ext/atom#eventTransfer");
  683. kURIs.push_back(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState");
  684. }
  685. static float sampleRateValue = 48000.f;
  686. static LV2_Options_Option options[3] = {
  687. {
  688. LV2_OPTIONS_INSTANCE,
  689. 0,
  690. uridMap.map(uridMap.handle, LV2_PARAMETERS__sampleRate),
  691. sizeof(float),
  692. uridMap.map(uridMap.handle, LV2_ATOM__Float),
  693. &sampleRateValue
  694. },
  695. {
  696. LV2_OPTIONS_INSTANCE,
  697. 0,
  698. uridMap.map(uridMap.handle, "urn:distrho:className"),
  699. std::strlen(className) + 1,
  700. uridMap.map(uridMap.handle, LV2_ATOM__String),
  701. className
  702. },
  703. {}
  704. };
  705. static const LV2_Feature optionsFt = { LV2_OPTIONS__options, static_cast<void*>(options) };
  706. static const LV2_Feature uridMapFt = { LV2_URID__map, static_cast<void*>(&uridMap) };
  707. static const LV2_Feature uridUnmapFt = { LV2_URID__unmap, static_cast<void*>(&uridUnmap) };
  708. static const LV2_Feature* features[] = {
  709. &optionsFt,
  710. &uridMapFt,
  711. &uridUnmapFt,
  712. nullptr
  713. };
  714. ModguiHandle* const mhandle = new ModguiHandle;
  715. mhandle->handle = nullptr;
  716. mhandle->loop_id = 0;
  717. mhandle->param_set = param_set;
  718. mhandle->patch_set = patch_set;
  719. LV2UI_Widget widget;
  720. const LV2UI_Handle handle = lv2ui_instantiate(&sLv2UiDescriptor,
  721. DISTRHO_PLUGIN_URI,
  722. "", // bundlePath
  723. lv2ui_write_function,
  724. mhandle,
  725. &widget,
  726. features);
  727. mhandle->handle = handle;
  728. static_cast<UiLv2*>(handle)->lv2ui_show();
  729. mhandle->loop_id = emscripten_set_interval(app_idle, 1000.0/60, handle);
  730. return mhandle;
  731. }
  732. DISTRHO_PLUGIN_EXPORT
  733. void modgui_param_set(const LV2UI_Handle handle, const uint32_t index, const float value)
  734. {
  735. lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle, index, sizeof(float), kUriNull, &value);
  736. }
  737. DISTRHO_PLUGIN_EXPORT
  738. void modgui_patch_set(const LV2UI_Handle handle, const char* const uri, const char* const value)
  739. {
  740. static const constexpr uint32_t URI_PREFIX_LEN = sizeof(DISTRHO_PLUGIN_URI);
  741. DISTRHO_SAFE_ASSERT_RETURN(std::strncmp(uri, DISTRHO_PLUGIN_URI "#", URI_PREFIX_LEN) == 0,);
  742. const uint32_t keySize = std::strlen(uri + URI_PREFIX_LEN) + 1;
  743. const uint32_t valueSize = std::strlen(value) + 1;
  744. const uint32_t atomSize = sizeof(LV2_Atom) + keySize + valueSize;
  745. LV2_Atom* const atom = static_cast<LV2_Atom*>(std::malloc(atomSize));
  746. atom->size = atomSize;
  747. atom->type = kUriDpfKeyValue;
  748. std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)), uri + URI_PREFIX_LEN, keySize);
  749. std::memcpy(static_cast<uint8_t*>(static_cast<void*>(atom + 1)) + keySize, value, valueSize);
  750. lv2ui_port_event(static_cast<ModguiHandle*>(handle)->handle,
  751. DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS, // events input port
  752. atomSize, kUriAtomEventTransfer, atom);
  753. std::free(atom);
  754. }
  755. DISTRHO_PLUGIN_EXPORT
  756. void modgui_cleanup(const LV2UI_Handle handle)
  757. {
  758. d_stdout("cleanup");
  759. ModguiHandle* const mhandle = static_cast<ModguiHandle*>(handle);
  760. if (mhandle->loop_id != 0)
  761. emscripten_clear_interval(mhandle->loop_id);
  762. lv2ui_cleanup(mhandle->handle);
  763. delete mhandle;
  764. }
  765. #endif
  766. // -----------------------------------------------------------------------