No need for special LV2 PluginCharacteristcs
[juce-lv2.git] / juce-dssi.cpp
blob8c6dba77a373eb78426f56ef4e71e0a13ff9e530
1 /*
2 DSSI Plugin wrapper.
4 Recall to : disable XInitThreads in
5 doPlatformSpecificInitialisation/doPlatformSpecificShutdown with
6 also the signal redirection stuff, and the X11 io error stuff (this
7 is done in juce-git since 2010/10/28).
9 Recall also to export only a few symbols, in order to avoid
10 potential clashes with the host symbols. The best is to use a linker
11 script:
13 g++ -shared -Wl,version-script=linux_dssi_symbols.map
15 with linux_dssi_symbols.map:
17 global:
18 ladspa_descriptor;
19 dssi_descriptor;
20 dssi_gui_main;
21 local: *;
24 And build your sources with -fvisibility=hidden. The best is also to
25 check that nm -D plugin.so does not list a ton of exported symbols
26 that may clash, such as zlib functions / png stuff or md5_init ..
27 Stuff from libstdc++ should be safe, I think.
30 IMPORTANT: make sure that the .so file used for the VST plugin and
31 the DSSI are not symlinks to the same physical file, or that will
32 cause issues (if a host allows VST and DSSI to be loaded at the same
33 time, such as renoise, then in that case both vst and dssi will
34 share the same juce environment, but they both use a different
35 message thread, and they both call ShutdownJuce_GUI() without caring
36 for each other so horrible crashes happen).
40 #include <fstream>
41 #if defined(TARGET_LINUX)
42 #include <X11/Xlib.h>
43 #include <X11/Xutil.h>
44 #include <X11/Xatom.h>
45 #undef KeyPress
46 #endif
48 #include "dssi.h"
49 #include "ladspa.h"
50 #include <alsa/asoundlib.h>
51 #include "JucePluginCharacteristics.h"
52 #include <juce.h>
54 //#define USING_LIBLO
55 #ifdef USING_LIBLO
56 #include <lo/lo.h>
57 #else
58 #define OSCPKT_OSTREAM_OUTPUT
59 #include "oscpkt/oscpkt.hh"
60 #include "oscpkt/udp.hh"
61 #endif
63 /* conveniency functions for debugging ..*/
64 inline std::ostream &operator<<(std::ostream &os, const juce::String &s) {
65 os << s.toUTF8();
66 return os;
70 BEGIN_JUCE_NAMESPACE
71 extern Display* display;
72 extern bool juce_postMessageToSystemQueue (void* message);
73 END_JUCE_NAMESPACE
74 class DssiSharedMessageThread : public Thread
76 public:
77 DssiSharedMessageThread()
78 : Thread (JUCE_T("DssiMessageThread")),
79 initialised (false)
81 startThread (7);
82 while (! initialised)
83 sleep (1);
86 ~DssiSharedMessageThread()
88 signalThreadShouldExit();
89 JUCEApplication::quit();
90 waitForThreadToExit (5000);
91 clearSingletonInstance();
94 void run()
96 initialiseJuce_GUI();
97 initialised = true;
98 MessageManager::getInstance()->setCurrentThreadAsMessageThread();
100 while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250))
105 juce_DeclareSingleton (DssiSharedMessageThread, false)
107 private:
108 bool initialised;
111 juce_ImplementSingleton (DssiSharedMessageThread);
113 class JuceDSSIWrapper;
115 class DssiEditorCompWrapper : public DocumentWindow
117 JuceDSSIWrapper* wrapper;
119 public:
120 DssiEditorCompWrapper (JuceDSSIWrapper* const wrapper_,
121 AudioProcessorEditor* const editor, const String &title,
122 int xpos, int ypos)
123 : DocumentWindow(title, Colours::white, DocumentWindow::allButtons, false)
125 wrapper = wrapper_;
126 setOpaque (true);
127 setTitleBarHeight(0);
128 setUsingNativeTitleBar(true);
129 editor->setOpaque (true);
130 setDropShadowEnabled(false);
131 setContentComponent(editor, true, true);
133 if (xpos != -10000 && ypos != -10000) {
134 setTopLeftPosition(xpos, ypos-20 /* position bug? */ );
135 } else setCentreRelative(.5f, .5f);
137 Component::addToDesktop(getDesktopWindowStyleFlags());
138 setVisible(true);
141 AudioProcessorEditor* getEditorComp() const
143 return dynamic_cast <AudioProcessorEditor*> (getContentComponent());
146 BorderSize getBorderThickness() const { return BorderSize(0); }
147 BorderSize getContentComponentBorder() const { return BorderSize(0); }
149 void closeButtonPressed();
151 juce_UseDebuggingNewOperator
155 struct OscAction : public juce::Message {
156 String msg, arg;
158 #ifdef USING_LIBLO
159 class DssiMinimalOscServer : public Thread {
160 lo_server serv;
161 MessageListener *listener;
162 public:
163 DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {}
164 ~DssiMinimalOscServer() {
165 jassert(!isThreadRunning());
166 stopServer();
168 void setListener(MessageListener *l) { listener = l; }
169 void sendMessageTo(const String &osc_url, const String &message_path,
170 const String &arg1 = String::empty, const String &arg2 = String::empty) {
171 char *url_hostname = lo_url_get_hostname(osc_url.toUTF8());
172 char *url_port = lo_url_get_port(osc_url.toUTF8());
173 char *url_path = lo_url_get_path(osc_url.toUTF8());
175 String path;
176 path << url_path << message_path; path = path.replace("//","/");
177 lo_address hostaddr = lo_address_new(url_hostname, url_port);
179 if (!arg1.isNotEmpty()) {
180 lo_send(hostaddr, path.toUTF8(), "");
181 } else if (!arg2.isNotEmpty()) {
182 lo_send(hostaddr, path.toUTF8(), "s", arg1.toUTF8());
183 } else {
184 lo_send(hostaddr, path.toUTF8(), "ss", arg1.toUTF8(), arg2.toUTF8());
186 lo_address_free(hostaddr);
187 free(url_hostname);
188 free(url_port);
189 free(url_path);
191 void startServer() {
192 if (isThreadRunning()) return;
193 serv = lo_server_new(NULL, NULL);
194 lo_server_add_method(serv, NULL, NULL, &osc_callback, this);
195 startThread();
197 void stopServer() {
198 if (!isThreadRunning()) return;
199 signalThreadShouldExit();
200 stopThread(1500);
201 lo_server_free(serv);
203 String getOscUrl() {
204 char *s = lo_server_get_url(serv);
205 String url; url << s; free(s);
206 return url;
208 void run() {
209 while (!threadShouldExit()) {
210 lo_server_recv_noblock(serv, 50);
213 static int osc_callback(const char *path, const char *types,
214 lo_arg **argv, int argc, lo_message , void *user_data) {
215 OscAction *a = new OscAction;
216 a->msg << path;
217 if (argc > 0 && types[0] == 's') {
218 a->arg << &argv[0]->s;
220 ((DssiMinimalOscServer*)user_data)->listener->postMessage(a);
221 return 0;
224 #else
225 // ad-hoc server that implements only what we need for dssi..
226 class DssiMinimalOscServer : public Thread {
227 oscpkt::UdpSocket serv;
228 MessageListener *listener;
229 public:
230 DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {}
231 void setListener(MessageListener *l) { listener = l; }
232 void sendMessageTo(const String &osc_url, const String &message_path,
233 const String &arg1 = String::empty, const String &arg2 = String::empty) {
234 oscpkt::Url url(osc_url.toUTF8());
235 if (!url.isOk()) return;
237 oscpkt::PacketWriter pw;
238 oscpkt::Message msg;
239 std::string path = url.path;
240 if (!path.empty() && path[path.size()-1] == '/') path.resize(path.size()-1);
241 path += message_path.toUTF8();
243 msg.init(path);
244 if (arg1.isNotEmpty()) msg.pushStr(arg1.toUTF8());
245 if (arg2.isNotEmpty()) msg.pushStr(arg2.toUTF8());
246 pw.addMessage(msg);
248 oscpkt::UdpSocket sock; sock.connectTo(url.hostname, url.port);
249 bool ok = sock.sendPacket(pw.packetData(), pw.packetSize());
250 if (!ok) {
251 cerr << "Could not send " << msg.addressPattern() << " message to "
252 << url.hostname << ":" << url.port << " '" << sock.errorMessage() << "'\n";
255 void startServer() {
256 if (isThreadRunning()) return;
257 serv.bindTo(0 /* any port */);
258 if (!serv.isOk()) {
259 cerr << "cannot start osc server: " << serv.errorMessage() << "\n"; return;
261 startThread();
263 void stopServer() {
264 if (!isThreadRunning()) return;
265 signalThreadShouldExit();
266 stopThread(1500);
267 serv.close();
269 String getOscUrl() {
270 String s; s << ("osc.udp://" + serv.localHostNameWithPort() + "/").c_str();
271 return s;
273 void run() {
274 while (!threadShouldExit() && serv.isOk()) {
275 if (serv.receiveNextPacket(50 /* timeout, in ms */)) {
276 oscpkt::PacketReader pr(serv.packetData(), serv.packetSize());
277 if (pr.isOk()) {
278 oscpkt::Message *msg;
279 while ((msg = pr.popMessage())) {
280 OscAction *a = new OscAction;
281 a->msg << msg->addressPattern().c_str();
282 if (msg->arg().nbArgRemaining() && msg->arg().isStr()) {
283 std::string s; msg->arg().popStr(s);
284 a->arg << s.c_str();
286 listener->postMessage(a);
293 #endif
296 static Array<void*> activePlugins;
298 extern AudioProcessor* JUCE_CALLTYPE createPluginFilter();
300 static LADSPA_Descriptor *ladspa_desc = 0;
301 static DSSI_Descriptor *dssi_desc = 0;
303 struct JuceDSSIWrapper : public Timer, public MessageListener {
305 static LADSPA_Descriptor *getLadspaDescriptor() {
306 if (!ladspa_desc) initialiseDescriptors();
307 return ladspa_desc;
310 static DSSI_Descriptor *getDssiDescriptor() {
311 if (!dssi_desc) initialiseDescriptors();
312 return dssi_desc;
315 static void initialiseDescriptors();
317 static void destroyDescriptors() {
318 if (ladspa_desc) {
319 for (size_t i=0; i < ladspa_desc->PortCount; ++i) {
320 free((void*)ladspa_desc->PortNames[i]);
322 delete[] ladspa_desc->PortDescriptors;
323 delete[] ladspa_desc->PortNames;
324 delete[] ladspa_desc->PortRangeHints;
325 delete ladspa_desc;
326 ladspa_desc = 0;
328 if (dssi_desc) {
329 delete dssi_desc;
330 dssi_desc = 0;
334 static void callbackCleanup(LADSPA_Handle instance) {
336 MessageManagerLock mmLock;
337 delete (JuceDSSIWrapper*)instance;
339 if (activePlugins.size() == 0) {
340 DssiSharedMessageThread::deleteInstance();
341 shutdownJuce_GUI();
345 static LADSPA_Handle callbackInstantiate(const LADSPA_Descriptor *,
346 unsigned long s_rate) {
347 if (activePlugins.size() == 0) {
348 DssiSharedMessageThread::getInstance();
350 MessageManagerLock mmLock;
351 return new JuceDSSIWrapper(s_rate);
355 static void callbackConnectPort(LADSPA_Handle instance, unsigned long port,
356 LADSPA_Data * data) {
357 MessageManagerLock mmLock;
358 ((JuceDSSIWrapper*)instance)->connectPort(port, data);
361 static void callbackActivate(LADSPA_Handle instance) {
362 MessageManagerLock mmLock;
363 ((JuceDSSIWrapper*)instance)->activate();
366 static void callbackDeactivate(LADSPA_Handle instance) {
367 MessageManagerLock mmLock;
368 ((JuceDSSIWrapper*)instance)->deactivate();
371 static void callbackRunAsEffect(LADSPA_Handle instance,
372 unsigned long sample_count) {
373 ((JuceDSSIWrapper*)instance)->run(sample_count, 0, 0);
376 static void callbackRun(LADSPA_Handle instance, unsigned long sample_count,
377 snd_seq_event_t *events, unsigned long event_count) {
378 ((JuceDSSIWrapper*)instance)->run(sample_count, events, event_count);
381 static char* callbackConfigure(LADSPA_Handle instance,
382 const char *key, const char *value) {
383 MessageManagerLock mmLock;
384 return ((JuceDSSIWrapper*)instance)->configure(key, value);
387 static const DSSI_Program_Descriptor *callbackGetProgram(LADSPA_Handle instance,
388 unsigned long index) {
389 MessageManagerLock mmLock;
390 return ((JuceDSSIWrapper*)instance)->getProgram(index);
393 static void callbackSelectProgram(LADSPA_Handle instance,
394 unsigned long bank,
395 unsigned long program) {
396 MessageManagerLock mmLock;
397 return ((JuceDSSIWrapper*)instance)->selectProgram(bank, program);
400 private:
401 double sample_rate;
402 MidiBuffer midi_buffer;
404 float *output_port[JucePlugin_MaxNumOutputChannels];
406 #define UNSET_PARAMETER_VALUE 1e10
407 Array<float*> param_port;
408 Array<float> param_saved; // value of the parameters saved at previous callback, to detect param value change
410 AudioProcessor *filter;
412 DssiEditorCompWrapper* editorComp;
413 bool shouldDeleteEditor;
414 bool hasShutdown;
416 int x_editor, y_editor;
418 DssiMinimalOscServer osc_server; // used only for comminucation with the gui
419 String gui_osc_url;
421 public:
422 JuceDSSIWrapper(unsigned long s_rate) {
423 editorComp = 0;
424 hasShutdown = false;
425 shouldDeleteEditor = false;
426 x_editor = y_editor = -10000;
427 osc_server.setListener(this);
429 for (int c=0; c < JucePlugin_MaxNumOutputChannels; ++c) output_port[c] = 0;
430 sample_rate = s_rate;
431 filter = createPluginFilter();
432 param_port.insertMultiple(0, 0, filter->getNumParameters());
433 param_saved.insertMultiple(0, UNSET_PARAMETER_VALUE, filter->getNumParameters());
434 activePlugins.add (this);
435 startTimer (1000);
438 ~JuceDSSIWrapper() {
439 osc_server.stopServer();
440 stopTimer();
441 deleteEditor(false);
442 hasShutdown = true;
443 deleteAndZero(filter);
444 jassert (activePlugins.contains (this));
445 activePlugins.removeValue (this);
448 AudioProcessor *getFilter() { return filter; }
450 void connectPort(unsigned long port, LADSPA_Data *data) {
451 if (port < JucePlugin_MaxNumOutputChannels) {
452 output_port[port] = data;
453 } else {
454 int param = (int)port - JucePlugin_MaxNumOutputChannels;
455 if (param < param_port.size()) {
456 param_port.set(param, (float*)data);
457 param_saved.set(param, UNSET_PARAMETER_VALUE);
462 void activate() {
463 unsigned block_size = 512;
464 filter->setNonRealtime(false);
465 filter->setPlayConfigDetails (0, JucePlugin_MaxNumOutputChannels,
466 sample_rate, block_size);
467 filter->prepareToPlay(sample_rate, block_size);
468 updateParameters();
469 midi_buffer.clear();
470 snd_midi_event_new(sizeof midi_parser_buffer, &midi_parser);
473 void deactivate() {
474 filter->releaseResources();
475 midi_buffer.clear();
476 snd_midi_event_free(midi_parser);
479 DSSI_Program_Descriptor latest_program_descriptor;
480 std::string latest_program_descriptor_name;
481 const DSSI_Program_Descriptor *getProgram(unsigned long index) {
482 if (index < (unsigned long)filter->getNumPrograms()) {
483 latest_program_descriptor.Bank = 0;
484 latest_program_descriptor.Program = index;
485 latest_program_descriptor_name = filter->getProgramName((int)index).toUTF8();
486 latest_program_descriptor.Name = latest_program_descriptor_name.c_str();
487 return &latest_program_descriptor;
489 return 0;
492 void selectProgram(unsigned long bank, unsigned long program) {
493 if (bank == 0) filter->setCurrentProgram((int)program);
494 updateParameters();
497 // update the port values from the plugin parameter values
498 void updateParameters() {
499 for (int i=0; i < param_port.size(); ++i) {
500 if (param_port[i]) {
501 float v = filter->getParameter(i);
502 *param_port[i] = v;
503 param_saved.set(i,v);
508 void run(unsigned long sample_count, snd_seq_event_t *events, unsigned long event_count) {
509 /* handle incoming midi events */
510 if (event_count) {
511 for (size_t i=0; i < event_count; ++i) {
512 const int num_bytes = snd_midi_event_decode(midi_parser, midi_parser_buffer, sizeof midi_parser_buffer, &events[i]);
513 snd_midi_event_reset_decode(midi_parser);
514 if (num_bytes) {
515 midi_buffer.addEvent(midi_parser_buffer, num_bytes, events[i].time.tick);
520 /* handle parameter changes initiated by the host */
521 for (int i=0; i < param_port.size(); ++i) {
522 if (param_port[i]) {
523 if (param_saved[i] != *param_port[i]) {
524 filter->setParameter(i, *param_port[i]);
530 const ScopedLock sl (filter->getCallbackLock());
531 if (filter->isSuspended()) {
532 for (int i = 0; i < JucePlugin_MaxNumOutputChannels; ++i)
533 zeromem (output_port[i], sizeof (float) * sample_count);
534 } else {
535 AudioSampleBuffer chans (output_port, JucePlugin_MaxNumOutputChannels, sample_count);
536 filter->processBlock (chans, midi_buffer);
540 /* read back parameter values */
541 updateParameters();
543 if (!midi_buffer.isEmpty()) { midi_buffer.clear(); }
546 struct FakeGuiConnectMessage : public Message {
547 String arg;
548 FakeGuiConnectMessage(const String &s) { arg = s; }
549 ~FakeGuiConnectMessage() throw() {}
552 char *configure(const char *key, const char *value) {
553 if (strcmp(key, "guiVisible") == 0) {
554 postMessage(new FakeGuiConnectMessage(String(value)));
556 return 0;
559 void handleMessage(const Message &msg) {
560 const FakeGuiConnectMessage *fmsg;
561 if ((fmsg = dynamic_cast<const FakeGuiConnectMessage*>(&msg))) {
562 bool show = fmsg->arg.isNotEmpty();
563 if (show) {
564 StringArray arg; arg.addTokens(fmsg->arg, JUCE_T("|"), JUCE_T(""));
565 if (arg.size() == 2) {
566 gui_osc_url = arg[0];
567 String window_title = arg[1];
569 /* only 1 gui will be opened at once, request for new guis will automatically close the older ones */
570 deleteEditor (true);
571 createEditorComp(window_title);
573 } else {
574 deleteEditor (true);
578 const OscAction *osc;
579 if ((osc = dynamic_cast<const OscAction*>(&msg))) {
580 if (osc->msg == "/internal_gui_hide") {
581 deleteEditor(true);
582 } else if (osc->msg == "/exiting") {
583 deleteEditor(true);
584 gui_osc_url = String::empty;
589 static AudioProcessor *initialiseAndCreateFilter() {
590 initialiseJuce_GUI();
591 AudioProcessor* filter = createPluginFilter();
592 return filter;
595 void createEditorComp(const String &title);
596 void deleteEditor (bool canDeleteLaterIfModal);
598 void notifyRemoteProcess(bool b) {
599 if (b) {
600 osc_server.startServer();
601 osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), osc_server.getOscUrl());
602 } else {
603 osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), JUCE_T(""));
604 osc_server.stopServer();
608 void timerCallback() {
609 if (shouldDeleteEditor) {
610 shouldDeleteEditor = false;
611 deleteEditor (true);
613 if (osc_server.isThreadRunning() && gui_osc_url.isNotEmpty()) {
614 // perdiodically ping the gui process, so that it now it has not been abandonned as an orphan process...
615 notifyRemoteProcess(editorComp !=0 );
619 snd_midi_event_t* midi_parser;
620 uint8_t midi_parser_buffer[16384];
622 }; // end of class JuceDSSIWrapper
624 void DssiEditorCompWrapper::closeButtonPressed() {
625 wrapper->deleteEditor(true);
628 void JuceDSSIWrapper::createEditorComp(const String &title) {
629 if (hasShutdown || filter == 0)
630 return;
632 if (editorComp == 0) {
633 AudioProcessorEditor* const ed = filter->createEditorIfNeeded();
634 if (ed) {
635 editorComp = new DssiEditorCompWrapper(this, ed, title, x_editor, y_editor);
636 notifyRemoteProcess(true);
639 shouldDeleteEditor = false;
642 void JuceDSSIWrapper::deleteEditor (bool canDeleteLaterIfModal)
644 PopupMenu::dismissAllActiveMenus();
646 if (editorComp != 0) {
647 Component* const modalComponent = Component::getCurrentlyModalComponent();
648 if (modalComponent != 0) {
649 modalComponent->exitModalState (0);
651 if (canDeleteLaterIfModal) {
652 shouldDeleteEditor = true;
653 return;
657 filter->editorBeingDeleted (editorComp->getEditorComp());
658 x_editor = editorComp->getX();
659 y_editor = editorComp->getY();
660 deleteAndZero (editorComp);
662 notifyRemoteProcess(false);
664 // there's some kind of component currently modal, but the host
665 // is trying to delete our plugin. You should try to avoid this happening..
666 jassert (Component::getCurrentlyModalComponent() == 0);
670 void JuceDSSIWrapper::initialiseDescriptors() {
671 initialiseJuce_GUI();
672 AudioProcessor *plugin = createPluginFilter();
674 char **port_names;
675 LADSPA_PortDescriptor *port_descriptors;
676 LADSPA_PortRangeHint *port_range_hints;
678 ladspa_desc = new LADSPA_Descriptor; assert(ladspa_desc);
679 ladspa_desc->UniqueID = JucePlugin_VSTUniqueID; // not used by dssi hosts anyway..
680 ladspa_desc->Label = "Main"; // must not contain white spaces
681 ladspa_desc->Properties = LADSPA_PROPERTY_REALTIME; //LADSPA_PROPERTY_HARD_RT_CAPABLE;
682 ladspa_desc->Name = JucePlugin_Name " DSSI Synth";
683 ladspa_desc->Maker = JucePlugin_Manufacturer;
684 ladspa_desc->Copyright = "Copyright (c) " JucePlugin_Manufacturer " 2010";
685 ladspa_desc->PortCount = JucePlugin_MaxNumOutputChannels + plugin->getNumParameters();
688 port_descriptors = new LADSPA_PortDescriptor[ladspa_desc->PortCount];
689 memset(port_descriptors, 0, sizeof(LADSPA_PortDescriptor)*ladspa_desc->PortCount);
690 ladspa_desc->PortDescriptors = port_descriptors;
692 port_range_hints = new LADSPA_PortRangeHint[ladspa_desc->PortCount];
693 memset(port_range_hints, 0, sizeof(LADSPA_PortRangeHint)*ladspa_desc->PortCount);
694 ladspa_desc->PortRangeHints = port_range_hints;
696 port_names = new char *[ladspa_desc->PortCount];
697 ladspa_desc->PortNames = port_names;
699 unsigned long port = 0;
700 for (int channel=0; channel < JucePlugin_MaxNumOutputChannels; ++channel, ++port) {
701 char s[100]; snprintf(s, 100, "Output%d", channel+1);
702 port_names[port] = strdup(s);
704 port_descriptors[port] = LADSPA_PORT_OUTPUT|LADSPA_PORT_AUDIO;
705 port_range_hints[port].HintDescriptor = 0;
707 for (int param=0; param < plugin->getNumParameters(); ++param, ++port) {
708 port_names[port] = strdup(plugin->getParameterName(param).toUTF8());
709 port_descriptors[port] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
710 port_range_hints[port].HintDescriptor = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE;
711 port_range_hints[port].LowerBound = 0;
712 port_range_hints[port].UpperBound = 1;
714 jassert(port == ladspa_desc->PortCount);
716 ladspa_desc->activate = &callbackActivate;
717 ladspa_desc->cleanup = &callbackCleanup;
718 ladspa_desc->connect_port = &callbackConnectPort;
719 ladspa_desc->deactivate = &callbackDeactivate;
720 ladspa_desc->instantiate = &callbackInstantiate;
721 ladspa_desc->run = &callbackRunAsEffect;
722 ladspa_desc->run_adding = NULL;
723 ladspa_desc->set_run_adding_gain = NULL;
726 dssi_desc = new DSSI_Descriptor;
727 dssi_desc->DSSI_API_Version = 1;
728 dssi_desc->LADSPA_Plugin = ladspa_desc;
729 dssi_desc->configure = &callbackConfigure;
730 dssi_desc->get_program = callbackGetProgram;
731 dssi_desc->get_midi_controller_for_port = NULL;
732 dssi_desc->select_program = callbackSelectProgram;
733 dssi_desc->run_synth = &callbackRun;
734 dssi_desc->run_synth_adding = NULL;
735 dssi_desc->run_multiple_synths = NULL;
736 dssi_desc->run_multiple_synths_adding = NULL;
738 delete plugin;
739 shutdownJuce_GUI();
742 __attribute__((destructor)) void dssi_destructor()
744 jassert(activePlugins.size() == 0);
745 JuceDSSIWrapper::destroyDescriptors();
748 extern "C" __attribute__ ((visibility("default"))) const LADSPA_Descriptor *ladspa_descriptor(unsigned long index)
750 return (index == 0 ? JuceDSSIWrapper::getLadspaDescriptor() : 0);
753 extern "C" __attribute__ ((visibility("default"))) const DSSI_Descriptor *dssi_descriptor(unsigned long index)
755 return (index == 0 ? JuceDSSIWrapper::getDssiDescriptor() : 0);
758 /* ---------- the fake gui process starts below ---------- */
760 struct FakeExternalGUI : public MessageListener, public Timer {
761 String window_title;
762 String host_osc_url;
763 String plugin_osc_url;
764 DssiMinimalOscServer osc_server;
765 juce::Time time_last_ping;
767 FakeExternalGUI() { osc_server.setListener(this); startTimer(1000); }
768 ~FakeExternalGUI() { osc_server.stopServer(); }
770 // notify the plugin via the host, using the '/configure' callback
771 void show(bool do_show) {
772 String conf;
773 if (do_show) {
774 conf << osc_server.getOscUrl() << "|" << window_title;
776 osc_server.sendMessageTo(host_osc_url, "/configure", "guiVisible", conf);
777 if (!do_show && plugin_osc_url.isNotEmpty())
778 osc_server.sendMessageTo(plugin_osc_url, "/internal_gui_hide", "0");
781 void quit() {
782 MessageManager::getInstance()->stopDispatchLoop();
785 void init(const char *host_osc_url_, const char *plugin_so_name,
786 const char *label, const char *friendlyname) {
787 (void)plugin_so_name;
788 host_osc_url << host_osc_url_;
789 window_title << label << " - " << friendlyname;
790 osc_server.startServer();
791 osc_server.sendMessageTo(host_osc_url, "/update", osc_server.getOscUrl() + "dssi");
794 void handleMessage(const Message &msg) {
795 const OscAction *osc;
796 if ((osc = dynamic_cast<const OscAction*>(&msg))) {
797 if (osc->msg == "/dssi/hide") show(false);
798 else if (osc->msg == "/dssi/show") show(true);
799 else if (osc->msg == "/dssi/quit") quit();
800 else if (osc->msg == "/internal_gui_status") {
801 plugin_osc_url = osc->arg;
802 time_last_ping = juce::Time::getCurrentTime();
803 if (!plugin_osc_url.isNotEmpty()) quit();
808 void timerCallback() {
809 juce::Time t = juce::Time::getCurrentTime();
810 if (plugin_osc_url.isNotEmpty() && (t-time_last_ping ).inMilliseconds() > 5000) {
811 /* no ping for 5 seconds, the fake gui process kills itself.. */
812 quit();
816 void exiting() {
817 osc_server.sendMessageTo(host_osc_url, "/exiting");
818 if (plugin_osc_url) osc_server.sendMessageTo(plugin_osc_url, "/exiting");
822 FakeExternalGUI *fake = 0;
824 void handle_sigterm(int) {
825 static int count = 0;
826 if (count++ == 0) {
827 fake->quit();
828 } else exit(1);
832 extern "C" __attribute__ ((visibility("default"))) int dssi_gui_main(const char *osc_host_url, const char *plugin_so_name,
833 const char *label, const char *friendlyname) {
834 initialiseJuce_GUI();
835 signal(SIGTERM, &handle_sigterm);
837 fake = new FakeExternalGUI();
838 fake->init(osc_host_url, plugin_so_name, label, friendlyname);
840 MessageManager::getInstance()->runDispatchLoop();
841 //fake->run_();
842 fake->exiting();
843 deleteAndZero(fake);
844 shutdownJuce_GUI();
845 return 0;