Blindly add a few stuff from VST
[juce-lv2.git] / juce-dssi.cpp
blobfb8864d2b9eecaedc20fb041e82532b57f3675e3
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);
119 class JuceDSSIWrapper;
121 class DssiEditorCompWrapper : public DocumentWindow
123 JuceDSSIWrapper* wrapper;
125 public:
126 DssiEditorCompWrapper (JuceDSSIWrapper* const wrapper_,
127 AudioProcessorEditor* const editor, const String &title,
128 int xpos, int ypos)
129 : DocumentWindow(title, Colours::white, DocumentWindow::allButtons, false)
131 wrapper = wrapper_;
132 setOpaque (true);
133 setTitleBarHeight(0);
134 setUsingNativeTitleBar(true);
135 editor->setOpaque (true);
136 setDropShadowEnabled(false);
137 setContentComponent(editor, true, true);
139 if (xpos != -10000 && ypos != -10000) {
140 setTopLeftPosition(xpos, ypos-20 /* position bug? */ );
141 } else setCentreRelative(.5f, .5f);
143 Component::addToDesktop(getDesktopWindowStyleFlags());
144 setVisible(true);
147 AudioProcessorEditor* getEditorComp() const
149 return dynamic_cast <AudioProcessorEditor*> (getContentComponent());
152 BorderSize getBorderThickness() const { return BorderSize(0); }
153 BorderSize getContentComponentBorder() const { return BorderSize(0); }
155 void closeButtonPressed();
157 juce_UseDebuggingNewOperator
161 struct OscAction : public juce::Message {
162 String msg, arg;
164 #ifdef USING_LIBLO
165 class DssiMinimalOscServer : public Thread {
166 lo_server serv;
167 MessageListener *listener;
168 public:
169 DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {}
170 ~DssiMinimalOscServer() {
171 jassert(!isThreadRunning());
172 stopServer();
174 void setListener(MessageListener *l) { listener = l; }
175 void sendMessageTo(const String &osc_url, const String &message_path,
176 const String &arg1 = String::empty, const String &arg2 = String::empty) {
177 char *url_hostname = lo_url_get_hostname(osc_url.toUTF8());
178 char *url_port = lo_url_get_port(osc_url.toUTF8());
179 char *url_path = lo_url_get_path(osc_url.toUTF8());
181 String path;
182 path << url_path << message_path; path = path.replace("//","/");
183 lo_address hostaddr = lo_address_new(url_hostname, url_port);
185 if (!arg1.isNotEmpty()) {
186 lo_send(hostaddr, path.toUTF8(), "");
187 } else if (!arg2.isNotEmpty()) {
188 lo_send(hostaddr, path.toUTF8(), "s", arg1.toUTF8());
189 } else {
190 lo_send(hostaddr, path.toUTF8(), "ss", arg1.toUTF8(), arg2.toUTF8());
192 lo_address_free(hostaddr);
193 free(url_hostname);
194 free(url_port);
195 free(url_path);
197 void startServer() {
198 if (isThreadRunning()) return;
199 serv = lo_server_new(NULL, NULL);
200 lo_server_add_method(serv, NULL, NULL, &osc_callback, this);
201 startThread();
203 void stopServer() {
204 if (!isThreadRunning()) return;
205 signalThreadShouldExit();
206 stopThread(1500);
207 lo_server_free(serv);
209 String getOscUrl() {
210 char *s = lo_server_get_url(serv);
211 String url; url << s; free(s);
212 return url;
214 void run() {
215 while (!threadShouldExit()) {
216 lo_server_recv_noblock(serv, 50);
219 static int osc_callback(const char *path, const char *types,
220 lo_arg **argv, int argc, lo_message , void *user_data) {
221 OscAction *a = new OscAction;
222 a->msg << path;
223 if (argc > 0 && types[0] == 's') {
224 a->arg << &argv[0]->s;
226 ((DssiMinimalOscServer*)user_data)->listener->postMessage(a);
227 return 0;
230 #else
231 // ad-hoc server that implements only what we need for dssi..
232 class DssiMinimalOscServer : public Thread {
233 oscpkt::UdpSocket serv;
234 MessageListener *listener;
235 public:
236 DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {}
237 void setListener(MessageListener *l) { listener = l; }
238 void sendMessageTo(const String &osc_url, const String &message_path,
239 const String &arg1 = String::empty, const String &arg2 = String::empty) {
240 oscpkt::Url url(osc_url.toUTF8());
241 if (!url.isOk()) return;
243 oscpkt::PacketWriter pw;
244 oscpkt::Message msg;
245 std::string path = url.path;
246 if (!path.empty() && path[path.size()-1] == '/') path.resize(path.size()-1);
247 path += message_path.toUTF8();
249 msg.init(path);
250 if (arg1.isNotEmpty()) msg.pushStr(arg1.toUTF8());
251 if (arg2.isNotEmpty()) msg.pushStr(arg2.toUTF8());
252 pw.addMessage(msg);
254 oscpkt::UdpSocket sock; sock.connectTo(url.hostname, url.port);
255 bool ok = sock.sendPacket(pw.packetData(), pw.packetSize());
256 if (!ok) {
257 cerr << "Could not send " << msg.addressPattern() << " message to "
258 << url.hostname << ":" << url.port << " '" << sock.errorMessage() << "'\n";
261 void startServer() {
262 if (isThreadRunning()) return;
263 serv.bindTo(0 /* any port */);
264 if (!serv.isOk()) {
265 cerr << "cannot start osc server: " << serv.errorMessage() << "\n"; return;
267 startThread();
269 void stopServer() {
270 if (!isThreadRunning()) return;
271 signalThreadShouldExit();
272 stopThread(1500);
273 serv.close();
275 String getOscUrl() {
276 String s; s << ("osc.udp://" + serv.localHostNameWithPort() + "/").c_str();
277 return s;
279 void run() {
280 while (!threadShouldExit() && serv.isOk()) {
281 if (serv.receiveNextPacket(50 /* timeout, in ms */)) {
282 oscpkt::PacketReader pr(serv.packetData(), serv.packetSize());
283 if (pr.isOk()) {
284 oscpkt::Message *msg;
285 while ((msg = pr.popMessage())) {
286 OscAction *a = new OscAction;
287 a->msg << msg->addressPattern().c_str();
288 if (msg->arg().nbArgRemaining() && msg->arg().isStr()) {
289 std::string s; msg->arg().popStr(s);
290 a->arg << s.c_str();
292 listener->postMessage(a);
299 #endif
306 static Array<void*> activePlugins;
308 extern AudioProcessor* JUCE_CALLTYPE createPluginFilter();
310 static LADSPA_Descriptor *ladspa_desc = 0;
311 static DSSI_Descriptor *dssi_desc = 0;
313 struct JuceDSSIWrapper : public Timer, public MessageListener {
315 static LADSPA_Descriptor *getLadspaDescriptor() {
316 if (!ladspa_desc) initialiseDescriptors();
317 return ladspa_desc;
320 static DSSI_Descriptor *getDssiDescriptor() {
321 if (!dssi_desc) initialiseDescriptors();
322 return dssi_desc;
325 static void initialiseDescriptors();
327 static void destroyDescriptors() {
328 if (ladspa_desc) {
329 for (size_t i=0; i < ladspa_desc->PortCount; ++i) {
330 free((void*)ladspa_desc->PortNames[i]);
332 delete[] ladspa_desc->PortDescriptors;
333 delete[] ladspa_desc->PortNames;
334 delete[] ladspa_desc->PortRangeHints;
335 delete ladspa_desc;
336 ladspa_desc = 0;
338 if (dssi_desc) {
339 delete dssi_desc;
340 dssi_desc = 0;
344 static void callbackCleanup(LADSPA_Handle instance) {
346 MessageManagerLock mmLock;
347 delete (JuceDSSIWrapper*)instance;
349 if (activePlugins.size() == 0) {
350 DssiSharedMessageThread::deleteInstance();
351 shutdownJuce_GUI();
355 static LADSPA_Handle callbackInstantiate(const LADSPA_Descriptor *,
356 unsigned long s_rate) {
357 if (activePlugins.size() == 0) {
358 DssiSharedMessageThread::getInstance();
360 MessageManagerLock mmLock;
361 return new JuceDSSIWrapper(s_rate);
365 static void callbackConnectPort(LADSPA_Handle instance, unsigned long port,
366 LADSPA_Data * data) {
367 MessageManagerLock mmLock;
368 ((JuceDSSIWrapper*)instance)->connectPort(port, data);
371 static void callbackActivate(LADSPA_Handle instance) {
372 MessageManagerLock mmLock;
373 ((JuceDSSIWrapper*)instance)->activate();
376 static void callbackDeactivate(LADSPA_Handle instance) {
377 MessageManagerLock mmLock;
378 ((JuceDSSIWrapper*)instance)->deactivate();
381 static void callbackRunAsEffect(LADSPA_Handle instance,
382 unsigned long sample_count) {
383 ((JuceDSSIWrapper*)instance)->run(sample_count, 0, 0);
386 static void callbackRun(LADSPA_Handle instance, unsigned long sample_count,
387 snd_seq_event_t *events, unsigned long event_count) {
388 ((JuceDSSIWrapper*)instance)->run(sample_count, events, event_count);
391 static char* callbackConfigure(LADSPA_Handle instance,
392 const char *key, const char *value) {
393 MessageManagerLock mmLock;
394 return ((JuceDSSIWrapper*)instance)->configure(key, value);
397 static const DSSI_Program_Descriptor *callbackGetProgram(LADSPA_Handle instance,
398 unsigned long index) {
399 MessageManagerLock mmLock;
400 return ((JuceDSSIWrapper*)instance)->getProgram(index);
403 static void callbackSelectProgram(LADSPA_Handle instance,
404 unsigned long bank,
405 unsigned long program) {
406 MessageManagerLock mmLock;
407 return ((JuceDSSIWrapper*)instance)->selectProgram(bank, program);
410 private:
411 double sample_rate;
412 MidiBuffer midi_buffer;
414 float *output_port[JucePlugin_MaxNumOutputChannels];
416 #define UNSET_PARAMETER_VALUE 1e10
417 Array<float*> param_port;
418 Array<float> param_saved; // value of the parameters saved at previous callback, to detect param value change
420 AudioProcessor *filter;
422 DssiEditorCompWrapper* editorComp;
423 bool shouldDeleteEditor;
424 bool hasShutdown;
426 int x_editor, y_editor;
428 DssiMinimalOscServer osc_server; // used only for comminucation with the gui
429 String gui_osc_url;
431 public:
432 JuceDSSIWrapper(unsigned long s_rate) {
433 editorComp = 0;
434 hasShutdown = false;
435 shouldDeleteEditor = false;
436 x_editor = y_editor = -10000;
437 osc_server.setListener(this);
439 for (int c=0; c < JucePlugin_MaxNumOutputChannels; ++c) output_port[c] = 0;
440 sample_rate = s_rate;
441 filter = createPluginFilter();
442 param_port.insertMultiple(0, 0, filter->getNumParameters());
443 param_saved.insertMultiple(0, UNSET_PARAMETER_VALUE, filter->getNumParameters());
444 activePlugins.add (this);
445 startTimer (1000);
448 ~JuceDSSIWrapper() {
449 osc_server.stopServer();
450 stopTimer();
451 deleteEditor(false);
452 hasShutdown = true;
453 deleteAndZero(filter);
454 jassert (activePlugins.contains (this));
455 activePlugins.removeValue (this);
458 AudioProcessor *getFilter() { return filter; }
460 void connectPort(unsigned long port, LADSPA_Data *data) {
461 if (port < JucePlugin_MaxNumOutputChannels) {
462 output_port[port] = data;
463 } else {
464 int param = (int)port - JucePlugin_MaxNumOutputChannels;
465 if (param < param_port.size()) {
466 param_port.set(param, (float*)data);
467 param_saved.set(param, UNSET_PARAMETER_VALUE);
472 void activate() {
473 unsigned block_size = 512;
474 filter->setNonRealtime(false);
475 filter->setPlayConfigDetails (0, JucePlugin_MaxNumOutputChannels,
476 sample_rate, block_size);
477 filter->prepareToPlay(sample_rate, block_size);
478 updateParameters();
479 midi_buffer.clear();
480 snd_midi_event_new(sizeof midi_parser_buffer, &midi_parser);
483 void deactivate() {
484 filter->releaseResources();
485 midi_buffer.clear();
486 snd_midi_event_free(midi_parser);
489 DSSI_Program_Descriptor latest_program_descriptor;
490 std::string latest_program_descriptor_name;
491 const DSSI_Program_Descriptor *getProgram(unsigned long index) {
492 if (index < (unsigned long)filter->getNumPrograms()) {
493 latest_program_descriptor.Bank = 0;
494 latest_program_descriptor.Program = index;
495 latest_program_descriptor_name = filter->getProgramName((int)index).toUTF8();
496 latest_program_descriptor.Name = latest_program_descriptor_name.c_str();
497 return &latest_program_descriptor;
499 return 0;
502 void selectProgram(unsigned long bank, unsigned long program) {
503 if (bank == 0) filter->setCurrentProgram((int)program);
504 updateParameters();
507 // update the port values from the plugin parameter values
508 void updateParameters() {
509 for (int i=0; i < param_port.size(); ++i) {
510 if (param_port[i]) {
511 float v = filter->getParameter(i);
512 *param_port[i] = v;
513 param_saved.set(i,v);
518 void run(unsigned long sample_count, snd_seq_event_t *events, unsigned long event_count) {
519 /* handle incoming midi events */
520 if (event_count) {
521 for (size_t i=0; i < event_count; ++i) {
522 const int num_bytes = snd_midi_event_decode(midi_parser, midi_parser_buffer, sizeof midi_parser_buffer, &events[i]);
523 snd_midi_event_reset_decode(midi_parser);
524 if (num_bytes) {
525 midi_buffer.addEvent(midi_parser_buffer, num_bytes, events[i].time.tick);
530 /* handle parameter changes initiated by the host */
531 for (int i=0; i < param_port.size(); ++i) {
532 if (param_port[i]) {
533 if (param_saved[i] != *param_port[i]) {
534 filter->setParameter(i, *param_port[i]);
540 const ScopedLock sl (filter->getCallbackLock());
541 if (filter->isSuspended()) {
542 for (int i = 0; i < JucePlugin_MaxNumOutputChannels; ++i)
543 zeromem (output_port[i], sizeof (float) * sample_count);
544 } else {
545 AudioSampleBuffer chans (output_port, JucePlugin_MaxNumOutputChannels, sample_count);
546 filter->processBlock (chans, midi_buffer);
550 /* read back parameter values */
551 updateParameters();
553 if (!midi_buffer.isEmpty()) { midi_buffer.clear(); }
556 struct FakeGuiConnectMessage : public Message {
557 String arg;
558 FakeGuiConnectMessage(const String &s) { arg = s; }
559 ~FakeGuiConnectMessage() throw() {}
562 char *configure(const char *key, const char *value) {
563 if (strcmp(key, "guiVisible") == 0) {
564 postMessage(new FakeGuiConnectMessage(String(value)));
566 return 0;
569 void handleMessage(const Message &msg) {
570 const FakeGuiConnectMessage *fmsg;
571 if ((fmsg = dynamic_cast<const FakeGuiConnectMessage*>(&msg))) {
572 bool show = fmsg->arg.isNotEmpty();
573 if (show) {
574 StringArray arg; arg.addTokens(fmsg->arg, JUCE_T("|"), JUCE_T(""));
575 if (arg.size() == 2) {
576 gui_osc_url = arg[0];
577 String window_title = arg[1];
579 /* only 1 gui will be opened at once, request for new guis will automatically close the older ones */
580 deleteEditor (true);
581 createEditorComp(window_title);
583 } else {
584 deleteEditor (true);
588 const OscAction *osc;
589 if ((osc = dynamic_cast<const OscAction*>(&msg))) {
590 if (osc->msg == "/internal_gui_hide") {
591 deleteEditor(true);
592 } else if (osc->msg == "/exiting") {
593 deleteEditor(true);
594 gui_osc_url = String::empty;
599 static AudioProcessor *initialiseAndCreateFilter() {
600 initialiseJuce_GUI();
601 AudioProcessor* filter = createPluginFilter();
602 return filter;
605 void createEditorComp(const String &title);
606 void deleteEditor (bool canDeleteLaterIfModal);
608 void notifyRemoteProcess(bool b) {
609 if (b) {
610 osc_server.startServer();
611 osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), osc_server.getOscUrl());
612 } else {
613 osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), JUCE_T(""));
614 osc_server.stopServer();
618 void timerCallback() {
619 if (shouldDeleteEditor) {
620 shouldDeleteEditor = false;
621 deleteEditor (true);
623 if (osc_server.isThreadRunning() && gui_osc_url.isNotEmpty()) {
624 // perdiodically ping the gui process, so that it now it has not been abandonned as an orphan process...
625 notifyRemoteProcess(editorComp !=0 );
629 snd_midi_event_t* midi_parser;
630 uint8_t midi_parser_buffer[16384];
632 }; // end of class JuceDSSIWrapper
634 void DssiEditorCompWrapper::closeButtonPressed() {
635 wrapper->deleteEditor(true);
638 void JuceDSSIWrapper::createEditorComp(const String &title) {
639 if (hasShutdown || filter == 0)
640 return;
642 if (editorComp == 0) {
643 AudioProcessorEditor* const ed = filter->createEditorIfNeeded();
644 if (ed) {
645 editorComp = new DssiEditorCompWrapper(this, ed, title, x_editor, y_editor);
646 notifyRemoteProcess(true);
649 shouldDeleteEditor = false;
652 void JuceDSSIWrapper::deleteEditor (bool canDeleteLaterIfModal)
654 PopupMenu::dismissAllActiveMenus();
656 if (editorComp != 0) {
657 Component* const modalComponent = Component::getCurrentlyModalComponent();
658 if (modalComponent != 0) {
659 modalComponent->exitModalState (0);
661 if (canDeleteLaterIfModal) {
662 shouldDeleteEditor = true;
663 return;
667 filter->editorBeingDeleted (editorComp->getEditorComp());
668 x_editor = editorComp->getX();
669 y_editor = editorComp->getY();
670 deleteAndZero (editorComp);
672 notifyRemoteProcess(false);
674 // there's some kind of component currently modal, but the host
675 // is trying to delete our plugin. You should try to avoid this happening..
676 jassert (Component::getCurrentlyModalComponent() == 0);
680 void JuceDSSIWrapper::initialiseDescriptors() {
681 initialiseJuce_GUI();
682 AudioProcessor *plugin = createPluginFilter();
684 char **port_names;
685 LADSPA_PortDescriptor *port_descriptors;
686 LADSPA_PortRangeHint *port_range_hints;
688 ladspa_desc = new LADSPA_Descriptor; assert(ladspa_desc);
689 ladspa_desc->UniqueID = JucePlugin_VSTUniqueID; // not used by dssi hosts anyway..
690 ladspa_desc->Label = "Main"; // must not contain white spaces
691 ladspa_desc->Properties = LADSPA_PROPERTY_REALTIME; //LADSPA_PROPERTY_HARD_RT_CAPABLE;
692 ladspa_desc->Name = JucePlugin_Name " DSSI Synth";
693 ladspa_desc->Maker = JucePlugin_Manufacturer;
694 ladspa_desc->Copyright = "Copyright (c) " JucePlugin_Manufacturer " 2010";
695 ladspa_desc->PortCount = JucePlugin_MaxNumOutputChannels + plugin->getNumParameters();
698 port_descriptors = new LADSPA_PortDescriptor[ladspa_desc->PortCount];
699 memset(port_descriptors, 0, sizeof(LADSPA_PortDescriptor)*ladspa_desc->PortCount);
700 ladspa_desc->PortDescriptors = port_descriptors;
702 port_range_hints = new LADSPA_PortRangeHint[ladspa_desc->PortCount];
703 memset(port_range_hints, 0, sizeof(LADSPA_PortRangeHint)*ladspa_desc->PortCount);
704 ladspa_desc->PortRangeHints = port_range_hints;
706 port_names = new char *[ladspa_desc->PortCount];
707 ladspa_desc->PortNames = port_names;
709 unsigned long port = 0;
710 for (int channel=0; channel < JucePlugin_MaxNumOutputChannels; ++channel, ++port) {
711 char s[100]; snprintf(s, 100, "Output%d", channel+1);
712 port_names[port] = strdup(s);
714 port_descriptors[port] = LADSPA_PORT_OUTPUT|LADSPA_PORT_AUDIO;
715 port_range_hints[port].HintDescriptor = 0;
717 for (int param=0; param < plugin->getNumParameters(); ++param, ++port) {
718 port_names[port] = strdup(plugin->getParameterName(param).toUTF8());
719 port_descriptors[port] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
720 port_range_hints[port].HintDescriptor = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE;
721 port_range_hints[port].LowerBound = 0;
722 port_range_hints[port].UpperBound = 1;
724 jassert(port == ladspa_desc->PortCount);
726 ladspa_desc->activate = &callbackActivate;
727 ladspa_desc->cleanup = &callbackCleanup;
728 ladspa_desc->connect_port = &callbackConnectPort;
729 ladspa_desc->deactivate = &callbackDeactivate;
730 ladspa_desc->instantiate = &callbackInstantiate;
731 ladspa_desc->run = &callbackRunAsEffect;
732 ladspa_desc->run_adding = NULL;
733 ladspa_desc->set_run_adding_gain = NULL;
736 dssi_desc = new DSSI_Descriptor;
737 dssi_desc->DSSI_API_Version = 1;
738 dssi_desc->LADSPA_Plugin = ladspa_desc;
739 dssi_desc->configure = &callbackConfigure;
740 dssi_desc->get_program = callbackGetProgram;
741 dssi_desc->get_midi_controller_for_port = NULL;
742 dssi_desc->select_program = callbackSelectProgram;
743 dssi_desc->run_synth = &callbackRun;
744 dssi_desc->run_synth_adding = NULL;
745 dssi_desc->run_multiple_synths = NULL;
746 dssi_desc->run_multiple_synths_adding = NULL;
748 delete plugin;
749 shutdownJuce_GUI();
752 __attribute__((destructor)) void dssi_destructor()
754 jassert(activePlugins.size() == 0);
755 JuceDSSIWrapper::destroyDescriptors();
758 extern "C" __attribute__ ((visibility("default"))) const LADSPA_Descriptor *ladspa_descriptor(unsigned long index)
760 return (index == 0 ? JuceDSSIWrapper::getLadspaDescriptor() : 0);
763 extern "C" __attribute__ ((visibility("default"))) const DSSI_Descriptor *dssi_descriptor(unsigned long index)
765 return (index == 0 ? JuceDSSIWrapper::getDssiDescriptor() : 0);
768 /* ---------- the fake gui process starts below ---------- */
770 struct FakeExternalGUI : public MessageListener, public Timer {
771 String window_title;
772 String host_osc_url;
773 String plugin_osc_url;
774 DssiMinimalOscServer osc_server;
775 juce::Time time_last_ping;
777 FakeExternalGUI() { osc_server.setListener(this); startTimer(1000); }
778 ~FakeExternalGUI() { osc_server.stopServer(); }
780 // notify the plugin via the host, using the '/configure' callback
781 void show(bool do_show) {
782 String conf;
783 if (do_show) {
784 conf << osc_server.getOscUrl() << "|" << window_title;
786 osc_server.sendMessageTo(host_osc_url, "/configure", "guiVisible", conf);
787 if (!do_show && plugin_osc_url.isNotEmpty())
788 osc_server.sendMessageTo(plugin_osc_url, "/internal_gui_hide", "0");
791 void quit() {
792 MessageManager::getInstance()->stopDispatchLoop();
795 void init(const char *host_osc_url_, const char *plugin_so_name,
796 const char *label, const char *friendlyname) {
797 (void)plugin_so_name;
798 host_osc_url << host_osc_url_;
799 window_title << label << " - " << friendlyname;
800 osc_server.startServer();
801 osc_server.sendMessageTo(host_osc_url, "/update", osc_server.getOscUrl() + "dssi");
804 void handleMessage(const Message &msg) {
805 const OscAction *osc;
806 if ((osc = dynamic_cast<const OscAction*>(&msg))) {
807 if (osc->msg == "/dssi/hide") show(false);
808 else if (osc->msg == "/dssi/show") show(true);
809 else if (osc->msg == "/dssi/quit") quit();
810 else if (osc->msg == "/internal_gui_status") {
811 plugin_osc_url = osc->arg;
812 time_last_ping = juce::Time::getCurrentTime();
813 if (!plugin_osc_url.isNotEmpty()) quit();
818 void timerCallback() {
819 juce::Time t = juce::Time::getCurrentTime();
820 if (plugin_osc_url.isNotEmpty() && (t-time_last_ping ).inMilliseconds() > 5000) {
821 /* no ping for 5 seconds, the fake gui process kills itself.. */
822 quit();
826 void exiting() {
827 osc_server.sendMessageTo(host_osc_url, "/exiting");
828 if (plugin_osc_url) osc_server.sendMessageTo(plugin_osc_url, "/exiting");
832 FakeExternalGUI *fake = 0;
834 void handle_sigterm(int) {
835 static int count = 0;
836 if (count++ == 0) {
837 fake->quit();
838 } else exit(1);
842 extern "C" __attribute__ ((visibility("default"))) int dssi_gui_main(const char *osc_host_url, const char *plugin_so_name,
843 const char *label, const char *friendlyname) {
844 initialiseJuce_GUI();
845 signal(SIGTERM, &handle_sigterm);
847 fake = new FakeExternalGUI();
848 fake->init(osc_host_url, plugin_so_name, label, friendlyname);
850 MessageManager::getInstance()->runDispatchLoop();
851 //fake->run_();
852 fake->exiting();
853 deleteAndZero(fake);
854 shutdownJuce_GUI();
855 return 0;