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 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
20 # error DSSI UIs do not support direct access!
21 #endif
22
23 #include "../extra/Sleep.hpp"
24
25 #include <lo/lo.h>
26
27 START_NAMESPACE_DISTRHO
28
29 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
30 static const sendNoteFunc sendNoteCallback = nullptr;
31 #endif
32
33 // -----------------------------------------------------------------------
34
35 struct OscData {
36 lo_address addr;
37 const char* path;
38 lo_server server;
39
OscDataOscData40 OscData()
41 : addr(nullptr),
42 path(nullptr),
43 server(nullptr) {}
44
idleOscData45 void idle() const
46 {
47 if (server == nullptr)
48 return;
49
50 while (lo_server_recv_noblock(server, 0) != 0) {}
51 }
52
send_configureOscData53 void send_configure(const char* const key, const char* const value) const
54 {
55 char targetPath[std::strlen(path)+11];
56 std::strcpy(targetPath, path);
57 std::strcat(targetPath, "/configure");
58 lo_send(addr, targetPath, "ss", key, value);
59 }
60
send_controlOscData61 void send_control(const int32_t index, const float value) const
62 {
63 char targetPath[std::strlen(path)+9];
64 std::strcpy(targetPath, path);
65 std::strcat(targetPath, "/control");
66 lo_send(addr, targetPath, "if", index, value);
67 }
68
send_midiOscData69 void send_midi(uchar data[4]) const
70 {
71 char targetPath[std::strlen(path)+6];
72 std::strcpy(targetPath, path);
73 std::strcat(targetPath, "/midi");
74 lo_send(addr, targetPath, "m", data);
75 }
76
send_updateOscData77 void send_update(const char* const url) const
78 {
79 char targetPath[std::strlen(path)+8];
80 std::strcpy(targetPath, path);
81 std::strcat(targetPath, "/update");
82 lo_send(addr, targetPath, "s", url);
83 }
84
send_exitingOscData85 void send_exiting() const
86 {
87 char targetPath[std::strlen(path)+9];
88 std::strcpy(targetPath, path);
89 std::strcat(targetPath, "/exiting");
90 lo_send(addr, targetPath, "");
91 }
92 };
93
94 // -----------------------------------------------------------------------
95
96 class UIDssi
97 {
98 public:
UIDssi(const OscData & oscData,const char * const uiTitle)99 UIDssi(const OscData& oscData, const char* const uiTitle)
100 : fUI(this, 0, nullptr, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback),
101 fHostClosed(false),
102 fOscData(oscData)
103 {
104 fUI.setWindowTitle(uiTitle);
105 }
106
~UIDssi()107 ~UIDssi()
108 {
109 if (fOscData.server != nullptr && ! fHostClosed)
110 fOscData.send_exiting();
111 }
112
exec()113 void exec()
114 {
115 for (;;)
116 {
117 fOscData.idle();
118
119 if (fHostClosed || ! fUI.idle())
120 break;
121
122 d_msleep(30);
123 }
124 }
125
126 // -------------------------------------------------------------------
127
128 #if DISTRHO_PLUGIN_WANT_STATE
dssiui_configure(const char * key,const char * value)129 void dssiui_configure(const char* key, const char* value)
130 {
131 fUI.stateChanged(key, value);
132 }
133 #endif
134
dssiui_control(ulong index,float value)135 void dssiui_control(ulong index, float value)
136 {
137 fUI.parameterChanged(index, value);
138 }
139
140 #if DISTRHO_PLUGIN_WANT_PROGRAMS
dssiui_program(ulong bank,ulong program)141 void dssiui_program(ulong bank, ulong program)
142 {
143 fUI.programLoaded(bank * 128 + program);
144 }
145 #endif
146
dssiui_samplerate(const double sampleRate)147 void dssiui_samplerate(const double sampleRate)
148 {
149 fUI.setSampleRate(sampleRate, true);
150 }
151
dssiui_show(const bool focus=false)152 void dssiui_show(const bool focus = false)
153 {
154 fUI.setWindowVisible(true);
155
156 if (focus)
157 fUI.focus();
158 }
159
dssiui_hide()160 void dssiui_hide()
161 {
162 fUI.setWindowVisible(false);
163 }
164
dssiui_quit()165 void dssiui_quit()
166 {
167 fHostClosed = true;
168 fUI.quit();
169 }
170
171 // -------------------------------------------------------------------
172
173 protected:
setParameterValue(const uint32_t rindex,const float value)174 void setParameterValue(const uint32_t rindex, const float value)
175 {
176 if (fOscData.server == nullptr)
177 return;
178
179 fOscData.send_control(rindex, value);
180 }
181
setState(const char * const key,const char * const value)182 void setState(const char* const key, const char* const value)
183 {
184 if (fOscData.server == nullptr)
185 return;
186
187 fOscData.send_configure(key, value);
188 }
189
190 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
sendNote(const uint8_t channel,const uint8_t note,const uint8_t velocity)191 void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
192 {
193 if (fOscData.server == nullptr)
194 return;
195 if (channel > 0xF)
196 return;
197
198 uint8_t mdata[4] = {
199 0,
200 static_cast<uint8_t>(channel + (velocity != 0 ? 0x90 : 0x80)),
201 note,
202 velocity
203 };
204 fOscData.send_midi(mdata);
205 }
206 #endif
207
setSize(const uint width,const uint height)208 void setSize(const uint width, const uint height)
209 {
210 fUI.setWindowSize(width, height);
211 }
212
213 private:
214 UIExporter fUI;
215 bool fHostClosed;
216
217 const OscData& fOscData;
218
219 // -------------------------------------------------------------------
220 // Callbacks
221
222 #define uiPtr ((UIDssi*)ptr)
223
setParameterCallback(void * ptr,uint32_t rindex,float value)224 static void setParameterCallback(void* ptr, uint32_t rindex, float value)
225 {
226 uiPtr->setParameterValue(rindex, value);
227 }
228
setStateCallback(void * ptr,const char * key,const char * value)229 static void setStateCallback(void* ptr, const char* key, const char* value)
230 {
231 uiPtr->setState(key, value);
232 }
233
234 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
sendNoteCallback(void * ptr,uint8_t channel,uint8_t note,uint8_t velocity)235 static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity)
236 {
237 uiPtr->sendNote(channel, note, velocity);
238 }
239 #endif
240
setSizeCallback(void * ptr,uint width,uint height)241 static void setSizeCallback(void* ptr, uint width, uint height)
242 {
243 uiPtr->setSize(width, height);
244 }
245
246 #undef uiPtr
247 };
248
249 // -----------------------------------------------------------------------
250
251 static OscData gOscData;
252 static const char* gUiTitle = nullptr;
253 static UIDssi* globalUI = nullptr;
254
initUiIfNeeded()255 static void initUiIfNeeded()
256 {
257 if (globalUI != nullptr)
258 return;
259
260 if (d_lastUiSampleRate == 0.0)
261 d_lastUiSampleRate = 44100.0;
262
263 globalUI = new UIDssi(gOscData, gUiTitle);
264 }
265
266 // -----------------------------------------------------------------------
267
osc_debug_handler(const char * path,const char *,lo_arg **,int,lo_message,void *)268 int osc_debug_handler(const char* path, const char*, lo_arg**, int, lo_message, void*)
269 {
270 d_debug("osc_debug_handler(\"%s\")", path);
271 return 0;
272
273 #ifndef DEBUG
274 // unused
275 (void)path;
276 #endif
277 }
278
osc_error_handler(int num,const char * msg,const char * path)279 void osc_error_handler(int num, const char* msg, const char* path)
280 {
281 d_stderr("osc_error_handler(%i, \"%s\", \"%s\")", num, msg, path);
282 }
283
284 #if DISTRHO_PLUGIN_WANT_STATE
osc_configure_handler(const char *,const char *,lo_arg ** argv,int,lo_message,void *)285 int osc_configure_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
286 {
287 const char* const key = &argv[0]->s;
288 const char* const value = &argv[1]->s;
289 d_debug("osc_configure_handler(\"%s\", \"%s\")", key, value);
290
291 initUiIfNeeded();
292
293 globalUI->dssiui_configure(key, value);
294
295 return 0;
296 }
297 #endif
298
osc_control_handler(const char *,const char *,lo_arg ** argv,int,lo_message,void *)299 int osc_control_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
300 {
301 const int32_t rindex = argv[0]->i;
302 const float value = argv[1]->f;
303 d_debug("osc_control_handler(%i, %f)", rindex, value);
304
305 int32_t index = rindex - DISTRHO_PLUGIN_NUM_INPUTS - DISTRHO_PLUGIN_NUM_OUTPUTS;
306
307 // latency
308 #if DISTRHO_PLUGIN_WANT_LATENCY
309 index -= 1;
310 #endif
311
312 if (index < 0)
313 return 0;
314
315 initUiIfNeeded();
316
317 globalUI->dssiui_control(index, value);
318
319 return 0;
320 }
321
322 #if DISTRHO_PLUGIN_WANT_PROGRAMS
osc_program_handler(const char *,const char *,lo_arg ** argv,int,lo_message,void *)323 int osc_program_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
324 {
325 const int32_t bank = argv[0]->i;
326 const int32_t program = argv[1]->f;
327 d_debug("osc_program_handler(%i, %i)", bank, program);
328
329 initUiIfNeeded();
330
331 globalUI->dssiui_program(bank, program);
332
333 return 0;
334 }
335 #endif
336
osc_sample_rate_handler(const char *,const char *,lo_arg ** argv,int,lo_message,void *)337 int osc_sample_rate_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
338 {
339 const int32_t sampleRate = argv[0]->i;
340 d_debug("osc_sample_rate_handler(%i)", sampleRate);
341
342 d_lastUiSampleRate = sampleRate;
343
344 if (globalUI != nullptr)
345 globalUI->dssiui_samplerate(sampleRate);
346
347 return 0;
348 }
349
osc_show_handler(const char *,const char *,lo_arg **,int,lo_message,void *)350 int osc_show_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
351 {
352 d_debug("osc_show_handler()");
353
354 initUiIfNeeded();
355
356 globalUI->dssiui_show();
357
358 return 0;
359 }
360
osc_hide_handler(const char *,const char *,lo_arg **,int,lo_message,void *)361 int osc_hide_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
362 {
363 d_debug("osc_hide_handler()");
364
365 if (globalUI != nullptr)
366 globalUI->dssiui_hide();
367
368 return 0;
369 }
370
osc_quit_handler(const char *,const char *,lo_arg **,int,lo_message,void *)371 int osc_quit_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
372 {
373 d_debug("osc_quit_handler()");
374
375 if (globalUI != nullptr)
376 globalUI->dssiui_quit();
377
378 return 0;
379 }
380
381 END_NAMESPACE_DISTRHO
382
383 // -----------------------------------------------------------------------
384
main(int argc,char * argv[])385 int main(int argc, char* argv[])
386 {
387 USE_NAMESPACE_DISTRHO
388
389 // dummy test mode
390 if (argc == 1)
391 {
392 gUiTitle = "DSSI UI Test";
393
394 initUiIfNeeded();
395 globalUI->dssiui_show(true);
396 globalUI->exec();
397
398 delete globalUI;
399 globalUI = nullptr;
400
401 return 0;
402 }
403
404 if (argc != 5)
405 {
406 fprintf(stderr, "Usage: %s <osc-url> <plugin-dll> <plugin-label> <instance-name>\n", argv[0]);
407 return 1;
408 }
409
410 const char* oscUrl = argv[1];
411 const char* uiTitle = argv[4];
412
413 char* const oscHost = lo_url_get_hostname(oscUrl);
414 char* const oscPort = lo_url_get_port(oscUrl);
415 char* const oscPath = lo_url_get_path(oscUrl);
416 size_t oscPathSize = strlen(oscPath);
417 lo_address oscAddr = lo_address_new(oscHost, oscPort);
418 lo_server oscServer = lo_server_new_with_proto(nullptr, LO_UDP, osc_error_handler);
419
420 char* const oscServerPath = lo_server_get_url(oscServer);
421
422 char pluginPath[strlen(oscServerPath)+oscPathSize];
423 strcpy(pluginPath, oscServerPath);
424 strcat(pluginPath, oscPath+1);
425
426 #if DISTRHO_PLUGIN_WANT_STATE
427 char oscPathConfigure[oscPathSize+11];
428 strcpy(oscPathConfigure, oscPath);
429 strcat(oscPathConfigure, "/configure");
430 lo_server_add_method(oscServer, oscPathConfigure, "ss", osc_configure_handler, nullptr);
431 #endif
432
433 char oscPathControl[oscPathSize+9];
434 strcpy(oscPathControl, oscPath);
435 strcat(oscPathControl, "/control");
436 lo_server_add_method(oscServer, oscPathControl, "if", osc_control_handler, nullptr);
437
438 d_stdout("oscServerPath: \"%s\"", oscServerPath);
439 d_stdout("pluginPath: \"%s\"", pluginPath);
440 d_stdout("oscPathControl: \"%s\"", oscPathControl);
441
442 #if DISTRHO_PLUGIN_WANT_PROGRAMS
443 char oscPathProgram[oscPathSize+9];
444 strcpy(oscPathProgram, oscPath);
445 strcat(oscPathProgram, "/program");
446 lo_server_add_method(oscServer, oscPathProgram, "ii", osc_program_handler, nullptr);
447 #endif
448
449 char oscPathSampleRate[oscPathSize+13];
450 strcpy(oscPathSampleRate, oscPath);
451 strcat(oscPathSampleRate, "/sample-rate");
452 lo_server_add_method(oscServer, oscPathSampleRate, "i", osc_sample_rate_handler, nullptr);
453
454 char oscPathShow[oscPathSize+6];
455 strcpy(oscPathShow, oscPath);
456 strcat(oscPathShow, "/show");
457 lo_server_add_method(oscServer, oscPathShow, "", osc_show_handler, nullptr);
458
459 char oscPathHide[oscPathSize+6];
460 strcpy(oscPathHide, oscPath);
461 strcat(oscPathHide, "/hide");
462 lo_server_add_method(oscServer, oscPathHide, "", osc_hide_handler, nullptr);
463
464 char oscPathQuit[oscPathSize+6];
465 strcpy(oscPathQuit, oscPath);
466 strcat(oscPathQuit, "/quit");
467 lo_server_add_method(oscServer, oscPathQuit, "", osc_quit_handler, nullptr);
468
469 lo_server_add_method(oscServer, nullptr, nullptr, osc_debug_handler, nullptr);
470
471 gUiTitle = uiTitle;
472
473 gOscData.addr = oscAddr;
474 gOscData.path = oscPath;
475 gOscData.server = oscServer;
476 gOscData.send_update(pluginPath);
477
478 // wait for init
479 for (int i=0; i < 100; ++i)
480 {
481 lo_server_recv(oscServer);
482
483 if (d_lastUiSampleRate != 0.0 || globalUI != nullptr)
484 break;
485
486 d_msleep(50);
487 }
488
489 int ret = 1;
490
491 if (d_lastUiSampleRate != 0.0 || globalUI != nullptr)
492 {
493 initUiIfNeeded();
494
495 globalUI->exec();
496
497 delete globalUI;
498 globalUI = nullptr;
499
500 ret = 0;
501 }
502
503 #if DISTRHO_PLUGIN_WANT_STATE
504 lo_server_del_method(oscServer, oscPathConfigure, "ss");
505 #endif
506 lo_server_del_method(oscServer, oscPathControl, "if");
507 #if DISTRHO_PLUGIN_WANT_PROGRAMS
508 lo_server_del_method(oscServer, oscPathProgram, "ii");
509 #endif
510 lo_server_del_method(oscServer, oscPathSampleRate, "i");
511 lo_server_del_method(oscServer, oscPathShow, "");
512 lo_server_del_method(oscServer, oscPathHide, "");
513 lo_server_del_method(oscServer, oscPathQuit, "");
514 lo_server_del_method(oscServer, nullptr, nullptr);
515
516 std::free(oscServerPath);
517 std::free(oscHost);
518 std::free(oscPort);
519 std::free(oscPath);
520
521 lo_address_free(oscAddr);
522 lo_server_free(oscServer);
523
524 return ret;
525 }
526