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.

576 lines
18KB

  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. #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/ui.h"
  26. #include "lv2/urid.h"
  27. #include "lv2/lv2_kxstudio_properties.h"
  28. #include "lv2/lv2_programs.h"
  29. #ifndef DISTRHO_PLUGIN_LV2_STATE_PREFIX
  30. # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:"
  31. #endif
  32. START_NAMESPACE_DISTRHO
  33. typedef struct _LV2_Atom_MidiEvent {
  34. LV2_Atom atom; /**< Atom header. */
  35. uint8_t data[3]; /**< MIDI data (body). */
  36. } LV2_Atom_MidiEvent;
  37. #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
  38. static const sendNoteFunc sendNoteCallback = nullptr;
  39. #endif
  40. // -----------------------------------------------------------------------
  41. class UiLv2
  42. {
  43. public:
  44. UiLv2(const char* const bundlePath, const intptr_t winId,
  45. const LV2_Options_Option* options, const LV2_URID_Map* const uridMap, const LV2UI_Resize* const uiResz, const LV2UI_Touch* uiTouch,
  46. const LV2UI_Controller controller, const LV2UI_Write_Function writeFunc,
  47. LV2UI_Widget* const widget, void* const dspPtr)
  48. : fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, dspPtr, bundlePath),
  49. fUridMap(uridMap),
  50. fUiResize(uiResz),
  51. fUiTouch(uiTouch),
  52. fController(controller),
  53. fWriteFunction(writeFunc),
  54. fEventTransferURID(uridMap->map(uridMap->handle, LV2_ATOM__eventTransfer)),
  55. fMidiEventURID(uridMap->map(uridMap->handle, LV2_MIDI__MidiEvent)),
  56. fKeyValueURID(uridMap->map(uridMap->handle, DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")),
  57. fWinIdWasNull(winId == 0)
  58. {
  59. if (fUiResize != nullptr && winId != 0)
  60. fUiResize->ui_resize(fUiResize->handle, fUI.getWidth(), fUI.getHeight());
  61. if (widget != nullptr)
  62. *widget = (LV2UI_Widget)fUI.getWindowId();
  63. #if DISTRHO_PLUGIN_WANT_STATE
  64. // tell the DSP we're ready to receive msgs
  65. setState("__dpf_ui_data__", "");
  66. #endif
  67. if (winId != 0)
  68. return;
  69. // if winId == 0 then options must not be null
  70. DISTRHO_SAFE_ASSERT_RETURN(options != nullptr,);
  71. const LV2_URID uridWindowTitle(uridMap->map(uridMap->handle, LV2_UI__windowTitle));
  72. const LV2_URID uridTransientWinId(uridMap->map(uridMap->handle, LV2_KXSTUDIO_PROPERTIES__TransientWindowId));
  73. bool hasTitle = false;
  74. for (int i=0; options[i].key != 0; ++i)
  75. {
  76. if (options[i].key == uridTransientWinId)
  77. {
  78. if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Long))
  79. {
  80. if (const int64_t transientWinId = *(const int64_t*)options[i].value)
  81. fUI.setWindowTransientWinId(static_cast<intptr_t>(transientWinId));
  82. }
  83. else
  84. d_stderr("Host provides transientWinId but has wrong value type");
  85. }
  86. else if (options[i].key == uridWindowTitle)
  87. {
  88. if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__String))
  89. {
  90. if (const char* const windowTitle = (const char*)options[i].value)
  91. {
  92. hasTitle = true;
  93. fUI.setWindowTitle(windowTitle);
  94. }
  95. }
  96. else
  97. d_stderr("Host provides windowTitle but has wrong value type");
  98. }
  99. }
  100. if (! hasTitle)
  101. fUI.setWindowTitle(DISTRHO_PLUGIN_NAME);
  102. }
  103. // -------------------------------------------------------------------
  104. void lv2ui_port_event(const uint32_t rindex, const uint32_t bufferSize, const uint32_t format, const void* const buffer)
  105. {
  106. if (format == 0)
  107. {
  108. const uint32_t parameterOffset(fUI.getParameterOffset());
  109. if (rindex < parameterOffset)
  110. return;
  111. DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),)
  112. const float value(*(const float*)buffer);
  113. fUI.parameterChanged(rindex-parameterOffset, value);
  114. }
  115. #if DISTRHO_PLUGIN_WANT_STATE
  116. else if (format == fEventTransferURID)
  117. {
  118. const LV2_Atom* const atom((const LV2_Atom*)buffer);
  119. DISTRHO_SAFE_ASSERT_RETURN(atom->type == fKeyValueURID,);
  120. const char* const key = (const char*)LV2_ATOM_BODY_CONST(atom);
  121. const char* const value = key+(std::strlen(key)+1);
  122. fUI.stateChanged(key, value);
  123. }
  124. #endif
  125. }
  126. // -------------------------------------------------------------------
  127. int lv2ui_idle()
  128. {
  129. if (fWinIdWasNull)
  130. return (fUI.idle() && fUI.isVisible()) ? 0 : 1;
  131. return fUI.idle() ? 0 : 1;
  132. }
  133. int lv2ui_show()
  134. {
  135. return fUI.setWindowVisible(true) ? 0 : 1;
  136. }
  137. int lv2ui_hide()
  138. {
  139. return fUI.setWindowVisible(false) ? 0 : 1;
  140. }
  141. int lv2ui_resize(uint width, uint height)
  142. {
  143. fUI.setWindowSize(width, height, true);
  144. return 0;
  145. }
  146. // -------------------------------------------------------------------
  147. uint32_t lv2_get_options(LV2_Options_Option* const /*options*/)
  148. {
  149. // currently unused
  150. return LV2_OPTIONS_ERR_UNKNOWN;
  151. }
  152. uint32_t lv2_set_options(const LV2_Options_Option* const options)
  153. {
  154. for (int i=0; options[i].key != 0; ++i)
  155. {
  156. if (options[i].key == fUridMap->map(fUridMap->handle, LV2_PARAMETERS__sampleRate))
  157. {
  158. if (options[i].type == fUridMap->map(fUridMap->handle, LV2_ATOM__Float))
  159. {
  160. const float sampleRate(*(const float*)options[i].value);
  161. fUI.setSampleRate(sampleRate);
  162. continue;
  163. }
  164. else
  165. {
  166. d_stderr("Host changed UI sample-rate but with wrong value type");
  167. continue;
  168. }
  169. }
  170. }
  171. return LV2_OPTIONS_SUCCESS;
  172. }
  173. // -------------------------------------------------------------------
  174. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  175. void lv2ui_select_program(const uint32_t bank, const uint32_t program)
  176. {
  177. const uint32_t realProgram(bank * 128 + program);
  178. fUI.programLoaded(realProgram);
  179. }
  180. #endif
  181. // -------------------------------------------------------------------
  182. protected:
  183. void editParameterValue(const uint32_t rindex, const bool started)
  184. {
  185. if (fUiTouch != nullptr && fUiTouch->touch != nullptr)
  186. fUiTouch->touch(fUiTouch->handle, rindex, started);
  187. }
  188. void setParameterValue(const uint32_t rindex, const float value)
  189. {
  190. DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
  191. fWriteFunction(fController, rindex, sizeof(float), 0, &value);
  192. }
  193. void setState(const char* const key, const char* const value)
  194. {
  195. DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
  196. const uint32_t eventInPortIndex(DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS);
  197. // join key and value
  198. String tmpStr;
  199. tmpStr += key;
  200. tmpStr += "\xff";
  201. tmpStr += value;
  202. tmpStr[std::strlen(key)] = '\0';
  203. // set msg size (key + separator + value + null terminator)
  204. const size_t msgSize(tmpStr.length()+1);
  205. // reserve atom space
  206. const size_t atomSize(sizeof(LV2_Atom) + msgSize);
  207. char atomBuf[atomSize];
  208. std::memset(atomBuf, 0, atomSize);
  209. // set atom info
  210. LV2_Atom* const atom((LV2_Atom*)atomBuf);
  211. atom->size = msgSize;
  212. atom->type = fKeyValueURID;
  213. // set atom data
  214. std::memcpy(atomBuf + sizeof(LV2_Atom), tmpStr.buffer(), msgSize);
  215. // send to DSP side
  216. fWriteFunction(fController, eventInPortIndex, atomSize, fEventTransferURID, atom);
  217. }
  218. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  219. void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
  220. {
  221. DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,);
  222. if (channel > 0xF)
  223. return;
  224. const uint32_t eventInPortIndex(DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS);
  225. LV2_Atom_MidiEvent atomMidiEvent;
  226. atomMidiEvent.atom.size = 3;
  227. atomMidiEvent.atom.type = fMidiEventURID;
  228. atomMidiEvent.data[0] = channel + (velocity != 0 ? 0x90 : 0x80);
  229. atomMidiEvent.data[1] = note;
  230. atomMidiEvent.data[2] = velocity;
  231. // send to DSP side
  232. fWriteFunction(fController, eventInPortIndex, sizeof(LV2_Atom_MidiEvent), fEventTransferURID, &atomMidiEvent);
  233. }
  234. #endif
  235. void setSize(const uint width, const uint height)
  236. {
  237. fUI.setWindowSize(width, height);
  238. if (fUiResize != nullptr && ! fWinIdWasNull)
  239. fUiResize->ui_resize(fUiResize->handle, width, height);
  240. }
  241. private:
  242. UIExporter fUI;
  243. // LV2 features
  244. const LV2_URID_Map* const fUridMap;
  245. const LV2UI_Resize* const fUiResize;
  246. const LV2UI_Touch* const fUiTouch;
  247. // LV2 UI stuff
  248. const LV2UI_Controller fController;
  249. const LV2UI_Write_Function fWriteFunction;
  250. // Need to save this
  251. const LV2_URID fEventTransferURID;
  252. const LV2_URID fMidiEventURID;
  253. const LV2_URID fKeyValueURID;
  254. // using ui:showInterface if true
  255. bool fWinIdWasNull;
  256. // -------------------------------------------------------------------
  257. // Callbacks
  258. #define uiPtr ((UiLv2*)ptr)
  259. static void editParameterCallback(void* ptr, uint32_t rindex, bool started)
  260. {
  261. uiPtr->editParameterValue(rindex, started);
  262. }
  263. static void setParameterCallback(void* ptr, uint32_t rindex, float value)
  264. {
  265. uiPtr->setParameterValue(rindex, value);
  266. }
  267. static void setStateCallback(void* ptr, const char* key, const char* value)
  268. {
  269. uiPtr->setState(key, value);
  270. }
  271. #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
  272. static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity)
  273. {
  274. uiPtr->sendNote(channel, note, velocity);
  275. }
  276. #endif
  277. static void setSizeCallback(void* ptr, uint width, uint height)
  278. {
  279. uiPtr->setSize(width, height);
  280. }
  281. #undef uiPtr
  282. };
  283. // -----------------------------------------------------------------------
  284. static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char* uri, const char* bundlePath,
  285. LV2UI_Write_Function writeFunction, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features)
  286. {
  287. if (uri == nullptr || std::strcmp(uri, DISTRHO_PLUGIN_URI) != 0)
  288. {
  289. d_stderr("Invalid plugin URI");
  290. return nullptr;
  291. }
  292. const LV2_Options_Option* options = nullptr;
  293. const LV2_URID_Map* uridMap = nullptr;
  294. const LV2UI_Resize* uiResize = nullptr;
  295. const LV2UI_Touch* uiTouch = nullptr;
  296. void* parentId = nullptr;
  297. void* instance = nullptr;
  298. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  299. struct LV2_DirectAccess_Interface {
  300. void* (*get_instance_pointer)(LV2_Handle handle);
  301. };
  302. const LV2_Extension_Data_Feature* extData = nullptr;
  303. #endif
  304. for (int i=0; features[i] != nullptr; ++i)
  305. {
  306. if (std::strcmp(features[i]->URI, LV2_OPTIONS__options) == 0)
  307. options = (const LV2_Options_Option*)features[i]->data;
  308. else if (std::strcmp(features[i]->URI, LV2_URID__map) == 0)
  309. uridMap = (const LV2_URID_Map*)features[i]->data;
  310. else if (std::strcmp(features[i]->URI, LV2_UI__resize) == 0)
  311. uiResize = (const LV2UI_Resize*)features[i]->data;
  312. else if (std::strcmp(features[i]->URI, LV2_UI__parent) == 0)
  313. parentId = features[i]->data;
  314. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  315. else if (std::strcmp(features[i]->URI, LV2_DATA_ACCESS_URI) == 0)
  316. extData = (const LV2_Extension_Data_Feature*)features[i]->data;
  317. else if (std::strcmp(features[i]->URI, LV2_INSTANCE_ACCESS_URI) == 0)
  318. instance = features[i]->data;
  319. #endif
  320. }
  321. if (options == nullptr && parentId == nullptr)
  322. {
  323. d_stderr("Options feature missing (needed for show-interface), cannot continue!");
  324. return nullptr;
  325. }
  326. if (uridMap == nullptr)
  327. {
  328. d_stderr("URID Map feature missing, cannot continue!");
  329. return nullptr;
  330. }
  331. if (parentId == nullptr)
  332. {
  333. d_stdout("Parent Window Id missing, host should be using ui:showInterface...");
  334. }
  335. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  336. if (extData == nullptr || instance == nullptr)
  337. {
  338. d_stderr("Data or instance access missing, cannot continue!");
  339. return nullptr;
  340. }
  341. if (const LV2_DirectAccess_Interface* const directAccess = (const LV2_DirectAccess_Interface*)extData->data_access(DISTRHO_PLUGIN_LV2_STATE_PREFIX "direct-access"))
  342. instance = directAccess->get_instance_pointer(instance);
  343. else
  344. instance = nullptr;
  345. if (instance == nullptr)
  346. {
  347. d_stderr("Failed to get direct access, cannot continue!");
  348. return nullptr;
  349. }
  350. #endif
  351. const intptr_t winId((intptr_t)parentId);
  352. if (options != nullptr)
  353. {
  354. const LV2_URID uridSampleRate(uridMap->map(uridMap->handle, LV2_PARAMETERS__sampleRate));
  355. for (int i=0; options[i].key != 0; ++i)
  356. {
  357. if (options[i].key == uridSampleRate)
  358. {
  359. if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Float))
  360. d_lastUiSampleRate = *(const float*)options[i].value;
  361. else
  362. d_stderr("Host provides UI sample-rate but has wrong value type");
  363. break;
  364. }
  365. }
  366. }
  367. if (d_lastUiSampleRate < 1.0)
  368. {
  369. d_stdout("WARNING: this host does not send sample-rate information for LV2 UIs, using 44100 as fallback (this could be wrong)");
  370. d_lastUiSampleRate = 44100.0;
  371. }
  372. return new UiLv2(bundlePath, winId, options, uridMap, uiResize, uiTouch, controller, writeFunction, widget, instance);
  373. }
  374. #define uiPtr ((UiLv2*)ui)
  375. static void lv2ui_cleanup(LV2UI_Handle ui)
  376. {
  377. delete uiPtr;
  378. }
  379. static void lv2ui_port_event(LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer)
  380. {
  381. uiPtr->lv2ui_port_event(portIndex, bufferSize, format, buffer);
  382. }
  383. // -----------------------------------------------------------------------
  384. static int lv2ui_idle(LV2UI_Handle ui)
  385. {
  386. return uiPtr->lv2ui_idle();
  387. }
  388. static int lv2ui_show(LV2UI_Handle ui)
  389. {
  390. return uiPtr->lv2ui_show();
  391. }
  392. static int lv2ui_hide(LV2UI_Handle ui)
  393. {
  394. return uiPtr->lv2ui_hide();
  395. }
  396. static int lv2ui_resize(LV2UI_Handle ui, int width, int height)
  397. {
  398. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, 1);
  399. DISTRHO_SAFE_ASSERT_RETURN(width > 0, 1);
  400. DISTRHO_SAFE_ASSERT_RETURN(height > 0, 1);
  401. return 1; // This needs more testing
  402. //return uiPtr->lv2ui_resize(width, height);
  403. }
  404. // -----------------------------------------------------------------------
  405. static uint32_t lv2_get_options(LV2UI_Handle ui, LV2_Options_Option* options)
  406. {
  407. return uiPtr->lv2_get_options(options);
  408. }
  409. static uint32_t lv2_set_options(LV2UI_Handle ui, const LV2_Options_Option* options)
  410. {
  411. return uiPtr->lv2_set_options(options);
  412. }
  413. // -----------------------------------------------------------------------
  414. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  415. static void lv2ui_select_program(LV2UI_Handle ui, uint32_t bank, uint32_t program)
  416. {
  417. uiPtr->lv2ui_select_program(bank, program);
  418. }
  419. #endif
  420. // -----------------------------------------------------------------------
  421. static const void* lv2ui_extension_data(const char* uri)
  422. {
  423. static const LV2_Options_Interface options = { lv2_get_options, lv2_set_options };
  424. static const LV2UI_Idle_Interface uiIdle = { lv2ui_idle };
  425. static const LV2UI_Show_Interface uiShow = { lv2ui_show, lv2ui_hide };
  426. static const LV2UI_Resize uiResz = { nullptr, lv2ui_resize };
  427. if (std::strcmp(uri, LV2_OPTIONS__interface) == 0)
  428. return &options;
  429. if (std::strcmp(uri, LV2_UI__idleInterface) == 0)
  430. return &uiIdle;
  431. if (std::strcmp(uri, LV2_UI__showInterface) == 0)
  432. return &uiShow;
  433. if (std::strcmp(uri, LV2_UI__resize) == 0)
  434. return &uiResz;
  435. #if DISTRHO_PLUGIN_WANT_PROGRAMS
  436. static const LV2_Programs_UI_Interface uiPrograms = { lv2ui_select_program };
  437. if (std::strcmp(uri, LV2_PROGRAMS__UIInterface) == 0)
  438. return &uiPrograms;
  439. #endif
  440. return nullptr;
  441. }
  442. #undef instancePtr
  443. // -----------------------------------------------------------------------
  444. static const LV2UI_Descriptor sLv2UiDescriptor = {
  445. DISTRHO_UI_URI,
  446. lv2ui_instantiate,
  447. lv2ui_cleanup,
  448. lv2ui_port_event,
  449. lv2ui_extension_data
  450. };
  451. // -----------------------------------------------------------------------
  452. END_NAMESPACE_DISTRHO
  453. DISTRHO_PLUGIN_EXPORT
  454. const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index)
  455. {
  456. USE_NAMESPACE_DISTRHO
  457. return (index == 0) ? &sLv2UiDescriptor : nullptr;
  458. }
  459. // -----------------------------------------------------------------------