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