Started with UI stuff again, slowly
[juce-lv2.git] / juce-dssi.cpp
blobafe4a0e01c7201391dd5586fa1fa474cd6e59ac4
1 /*
2 */
4 #include <fstream>
5 #if defined(TARGET_LINUX)
6 #include <X11/Xlib.h>
7 #include <X11/Xutil.h>
8 #include <X11/Xatom.h>
9 #undef KeyPress
10 #endif
12 #include "dssi.h"
13 #include "ladspa.h"
14 #include <alsa/asoundlib.h>
15 #include "JucePluginCharacteristics.h"
16 #include <juce.h>
18 #define USING_LIBLO
19 #ifdef USING_LIBLO
20 #include <lo/lo.h>
21 #else
22 #define OSCPKT_OSTREAM_OUTPUT
23 #include "oscpkt/oscpkt.hh"
24 #include "oscpkt/udp.hh"
25 #endif
27 /* conveniency functions for debugging ..*/
28 inline std::ostream &operator<<(std::ostream &os, const juce::String &s) {
29 os << s.toUTF8();
30 return os;
34 BEGIN_JUCE_NAMESPACE
35 extern Display* display;
36 extern bool juce_postMessageToSystemQueue (void* message);
37 END_JUCE_NAMESPACE
38 class DssiSharedMessageThread : public Thread
40 public:
41 DssiSharedMessageThread()
42 : Thread (JUCE_T("DssiMessageThread")),
43 initialised (false)
45 startThread (7);
46 while (! initialised)
47 sleep (1);
50 ~DssiSharedMessageThread()
52 signalThreadShouldExit();
53 JUCEApplication::quit();
54 waitForThreadToExit (5000);
55 clearSingletonInstance();
58 void run()
60 initialiseJuce_GUI();
61 initialised = true;
62 MessageManager::getInstance()->setCurrentThreadAsMessageThread();
64 while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250))
69 juce_DeclareSingleton (DssiSharedMessageThread, false)
71 private:
72 bool initialised;
75 juce_ImplementSingleton (DssiSharedMessageThread);
83 class JuceDSSIWrapper;
85 class DssiEditorCompWrapper : public DocumentWindow
87 JuceDSSIWrapper* wrapper;
89 public:
90 DssiEditorCompWrapper (JuceDSSIWrapper* const wrapper_,
91 AudioProcessorEditor* const editor, const String &title,
92 int xpos, int ypos)
93 : DocumentWindow(title, Colours::white, DocumentWindow::allButtons, false)
95 wrapper = wrapper_;
96 setOpaque (true);
97 setTitleBarHeight(0);
98 setUsingNativeTitleBar(true);
99 editor->setOpaque (true);
100 setDropShadowEnabled(false);
101 setContentComponent(editor, true, true);
103 if (xpos != -10000 && ypos != -10000) {
104 setTopLeftPosition(xpos, ypos-20 /* position bug? */ );
105 } else setCentreRelative(.5f, .5f);
107 Component::addToDesktop(getDesktopWindowStyleFlags());
108 setVisible(true);
111 AudioProcessorEditor* getEditorComp() const
113 return dynamic_cast <AudioProcessorEditor*> (getContentComponent());
116 BorderSize getBorderThickness() const { return BorderSize(0); }
117 BorderSize getContentComponentBorder() const { return BorderSize(0); }
119 void closeButtonPressed();
121 juce_UseDebuggingNewOperator;
125 struct OscAction : public juce::Message {
126 String msg, arg;
128 #ifdef USING_LIBLO
129 class DssiMinimalOscServer : public Thread {
130 lo_server serv;
131 MessageListener *listener;
132 public:
133 DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {}
134 ~DssiMinimalOscServer() {
135 jassert(!isThreadRunning());
136 stopServer();
138 void setListener(MessageListener *l) { listener = l; }
139 void sendMessageTo(const String &osc_url, const String &message_path,
140 const String &arg1 = String::empty, const String &arg2 = String::empty) {
141 char *url_hostname = lo_url_get_hostname(osc_url.toUTF8());
142 char *url_port = lo_url_get_port(osc_url.toUTF8());
143 char *url_path = lo_url_get_path(osc_url.toUTF8());
145 String path;
146 path << url_path << message_path; path = path.replace("//","/");
147 lo_address hostaddr = lo_address_new(url_hostname, url_port);
149 if (!arg1.isNotEmpty()) {
150 lo_send(hostaddr, path.toUTF8(), "");
151 } else if (!arg2.isNotEmpty()) {
152 lo_send(hostaddr, path.toUTF8(), "s", arg1.toUTF8());
153 } else {
154 lo_send(hostaddr, path.toUTF8(), "ss", arg1.toUTF8(), arg2.toUTF8());
156 lo_address_free(hostaddr);
157 free(url_hostname);
158 free(url_port);
159 free(url_path);
161 void startServer() {
162 if (isThreadRunning()) return;
163 serv = lo_server_new(NULL, NULL);
164 lo_server_add_method(serv, NULL, NULL, &osc_callback, this);
165 startThread();
167 void stopServer() {
168 if (!isThreadRunning()) return;
169 signalThreadShouldExit();
170 stopThread(1500);
171 lo_server_free(serv);
173 String getOscUrl() {
174 char *s = lo_server_get_url(serv);
175 String url; url << s; free(s);
176 return url;
178 void run() {
179 while (!threadShouldExit()) {
180 lo_server_recv_noblock(serv, 50);
183 static int osc_callback(const char *path, const char *types,
184 lo_arg **argv, int argc, lo_message , void *user_data) {
185 OscAction *a = new OscAction;
186 a->msg << path;
187 if (argc > 0 && types[0] == 's') {
188 a->arg << &argv[0]->s;
190 ((DssiMinimalOscServer*)user_data)->listener->postMessage(a);
191 return 0;
194 #else
195 // ad-hoc server that implements only what we need for dssi..
196 class DssiMinimalOscServer : public Thread {
197 oscpkt::UdpSocket serv;
198 MessageListener *listener;
199 public:
200 DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {}
201 void setListener(MessageListener *l) { listener = l; }
202 void sendMessageTo(const String &osc_url, const String &message_path,
203 const String &arg1 = String::empty, const String &arg2 = String::empty) {
204 oscpkt::Url url(osc_url.toUTF8());
205 if (!url.isOk()) return;
207 oscpkt::PacketWriter pw;
208 oscpkt::Message msg;
209 std::string path = url.path;
210 if (!path.empty() && path[path.size()-1] == '/') path.resize(path.size()-1);
211 path += message_path.toUTF8();
213 msg.init(path);
214 if (arg1.isNotEmpty()) msg.pushStr(arg1.toUTF8());
215 if (arg2.isNotEmpty()) msg.pushStr(arg2.toUTF8());
216 pw.addMessage(msg);
218 oscpkt::UdpSocket sock; sock.connectTo(url.hostname, url.port);
219 bool ok = sock.sendPacket(pw.packetData(), pw.packetSize());
220 if (!ok) {
221 cerr << "Could not send " << msg.addressPattern() << " message to "
222 << url.hostname << ":" << url.port << " '" << sock.errorMessage() << "'\n";
225 void startServer() {
226 if (isThreadRunning()) return;
227 serv.bindTo(0 /* any port */);
228 if (!serv.isOk()) {
229 cerr << "cannot start osc server: " << serv.errorMessage() << "\n"; return;
231 startThread();
233 void stopServer() {
234 if (!isThreadRunning()) return;
235 signalThreadShouldExit();
236 stopThread(1500);
237 serv.close();
239 String getOscUrl() {
240 String s; s << ("osc.udp://" + serv.localHostNameWithPort() + "/").c_str();
241 return s;
243 void run() {
244 while (!threadShouldExit() && serv.isOk()) {
245 if (serv.receiveNextPacket(50 /* timeout, in ms */)) {
246 oscpkt::PacketReader pr(serv.packetData(), serv.packetSize());
247 if (pr.isOk()) {
248 oscpkt::Message *msg;
249 while ((msg = pr.popMessage())) {
250 OscAction *a = new OscAction;
251 a->msg << msg->addressPattern().c_str();
252 if (msg->arg().nbArgRemaining() && msg->arg().isStr()) {
253 std::string s; msg->arg().popStr(s);
254 a->arg << s.c_str();
256 listener->postMessage(a);
263 #endif
270 static Array<void*> activePlugins;
272 extern AudioProcessor* JUCE_CALLTYPE createPluginFilter();
274 static LADSPA_Descriptor *ladspa_desc = 0;
275 static DSSI_Descriptor *dssi_desc = 0;
277 struct JuceDSSIWrapper : public Timer, public MessageListener {
279 static LADSPA_Descriptor *getLadspaDescriptor() {
280 if (!ladspa_desc) initialiseDescriptors();
281 return ladspa_desc;
284 static DSSI_Descriptor *getDssiDescriptor() {
285 if (!dssi_desc) initialiseDescriptors();
286 return dssi_desc;
289 static void initialiseDescriptors();
291 static void destroyDescriptors() {
292 if (ladspa_desc) {
293 for (size_t i=0; i < ladspa_desc->PortCount; ++i) {
294 free((void*)ladspa_desc->PortNames[i]);
296 delete[] ladspa_desc->PortDescriptors;
297 delete[] ladspa_desc->PortNames;
298 delete[] ladspa_desc->PortRangeHints;
299 delete ladspa_desc;
300 ladspa_desc = 0;
302 if (dssi_desc) {
303 delete dssi_desc;
304 dssi_desc = 0;
308 static void callbackCleanup(LADSPA_Handle instance) {
310 MessageManagerLock mmLock;
311 delete (JuceDSSIWrapper*)instance;
313 if (activePlugins.size() == 0) {
314 DssiSharedMessageThread::deleteInstance();
315 shutdownJuce_GUI();
319 static LADSPA_Handle callbackInstantiate(const LADSPA_Descriptor *,
320 unsigned long s_rate) {
321 if (activePlugins.size() == 0) {
322 DssiSharedMessageThread::getInstance();
324 MessageManagerLock mmLock;
325 return new JuceDSSIWrapper(s_rate);
329 static void callbackConnectPort(LADSPA_Handle instance, unsigned long port,
330 LADSPA_Data * data) {
331 MessageManagerLock mmLock;
332 ((JuceDSSIWrapper*)instance)->connectPort(port, data);
335 static void callbackActivate(LADSPA_Handle instance) {
336 MessageManagerLock mmLock;
337 ((JuceDSSIWrapper*)instance)->activate();
340 static void callbackDeactivate(LADSPA_Handle instance) {
341 MessageManagerLock mmLock;
342 ((JuceDSSIWrapper*)instance)->deactivate();
345 static void callbackRunAsEffect(LADSPA_Handle instance,
346 unsigned long sample_count) {
347 ((JuceDSSIWrapper*)instance)->run(sample_count, 0, 0);
350 static void callbackRun(LADSPA_Handle instance, unsigned long sample_count,
351 snd_seq_event_t *events, unsigned long event_count) {
352 ((JuceDSSIWrapper*)instance)->run(sample_count, events, event_count);
355 static char* callbackConfigure(LADSPA_Handle instance,
356 const char *key, const char *value) {
357 MessageManagerLock mmLock;
358 return ((JuceDSSIWrapper*)instance)->configure(key, value);
361 static const DSSI_Program_Descriptor *callbackGetProgram(LADSPA_Handle instance,
362 unsigned long index) {
363 MessageManagerLock mmLock;
364 return ((JuceDSSIWrapper*)instance)->getProgram(index);
367 static void callbackSelectProgram(LADSPA_Handle instance,
368 unsigned long bank,
369 unsigned long program) {
370 MessageManagerLock mmLock;
371 return ((JuceDSSIWrapper*)instance)->selectProgram(bank, program);
374 private:
375 double sample_rate;
376 MidiBuffer midi_buffer;
378 float *output_port[JucePlugin_MaxNumOutputChannels];
380 #define UNSET_PARAMETER_VALUE 1e10
381 Array<float*> param_port;
382 Array<float> param_saved; // value of the parameters saved at previous callback, to detect param value change
384 AudioProcessor *filter;
386 DssiEditorCompWrapper* editorComp;
387 bool shouldDeleteEditor;
388 bool hasShutdown;
390 int x_editor, y_editor;
392 DssiMinimalOscServer osc_server; // used only for comminucation with the gui
393 String gui_osc_url;
395 public:
396 JuceDSSIWrapper(unsigned long s_rate) {
397 editorComp = 0;
398 hasShutdown = false;
399 shouldDeleteEditor = false;
400 x_editor = y_editor = -10000;
401 osc_server.setListener(this);
403 for (int c=0; c < JucePlugin_MaxNumOutputChannels; ++c) output_port[c] = 0;
404 sample_rate = s_rate;
405 filter = createPluginFilter();
406 param_port.insertMultiple(0, 0, filter->getNumParameters());
407 param_saved.insertMultiple(0, UNSET_PARAMETER_VALUE, filter->getNumParameters());
408 activePlugins.add (this);
409 startTimer (1000);
412 ~JuceDSSIWrapper() {
413 osc_server.stopServer();
414 stopTimer();
415 deleteEditor(false);
416 hasShutdown = true;
417 deleteAndZero(filter);
418 jassert (activePlugins.contains (this));
419 activePlugins.removeValue (this);
422 AudioProcessor *getFilter() { return filter; }
424 void connectPort(unsigned long port, LADSPA_Data *data) {
425 if (port < JucePlugin_MaxNumOutputChannels) {
426 output_port[port] = data;
427 } else {
428 int param = (int)port - JucePlugin_MaxNumOutputChannels;
429 if (param < param_port.size()) {
430 param_port.set(param, (float*)data);
431 param_saved.set(param, UNSET_PARAMETER_VALUE);
436 void activate() {
437 unsigned block_size = 512;
438 filter->setNonRealtime(false);
439 filter->setPlayConfigDetails (0, JucePlugin_MaxNumOutputChannels,
440 sample_rate, block_size);
441 filter->prepareToPlay(sample_rate, block_size);
442 updateParameters();
443 midi_buffer.clear();
444 snd_midi_event_new(sizeof midi_parser_buffer, &midi_parser);
447 void deactivate() {
448 filter->releaseResources();
449 midi_buffer.clear();
450 snd_midi_event_free(midi_parser);
453 DSSI_Program_Descriptor latest_program_descriptor;
454 std::string latest_program_descriptor_name;
455 const DSSI_Program_Descriptor *getProgram(unsigned long index) {
456 if (index < (unsigned long)filter->getNumPrograms()) {
457 latest_program_descriptor.Bank = 0;
458 latest_program_descriptor.Program = index;
459 latest_program_descriptor_name = filter->getProgramName((int)index).toUTF8();
460 latest_program_descriptor.Name = latest_program_descriptor_name.c_str();
461 return &latest_program_descriptor;
463 return 0;
466 void selectProgram(unsigned long bank, unsigned long program) {
467 if (bank == 0) filter->setCurrentProgram((int)program);
468 updateParameters();
471 // update the port values from the plugin parameter values
472 void updateParameters() {
473 for (int i=0; i < param_port.size(); ++i) {
474 if (param_port[i]) {
475 float v = filter->getParameter(i);
476 *param_port[i] = v;
477 param_saved.set(i,v);
482 void run(unsigned long sample_count, snd_seq_event_t *events, unsigned long event_count) {
483 /* handle incoming midi events */
484 if (event_count) {
485 for (size_t i=0; i < event_count; ++i) {
486 const int num_bytes = snd_midi_event_decode(midi_parser, midi_parser_buffer, sizeof midi_parser_buffer, &events[i]);
487 snd_midi_event_reset_decode(midi_parser);
488 if (num_bytes) {
489 midi_buffer.addEvent(midi_parser_buffer, num_bytes, events[i].time.tick);
494 /* handle parameter changes initiated by the host */
495 for (int i=0; i < param_port.size(); ++i) {
496 if (param_port[i]) {
497 if (param_saved[i] != *param_port[i]) {
498 filter->setParameter(i, *param_port[i]);
504 const ScopedLock sl (filter->getCallbackLock());
505 if (filter->isSuspended()) {
506 for (int i = 0; i < JucePlugin_MaxNumOutputChannels; ++i)
507 zeromem (output_port[i], sizeof (float) * sample_count);
508 } else {
509 AudioSampleBuffer chans (output_port, JucePlugin_MaxNumOutputChannels, sample_count);
510 filter->processBlock (chans, midi_buffer);
514 /* read back parameter values */
515 updateParameters();
517 if (!midi_buffer.isEmpty()) { midi_buffer.clear(); }
520 struct FakeGuiConnectMessage : public Message {
521 String arg;
522 FakeGuiConnectMessage(const String &s) { arg = s; }
523 ~FakeGuiConnectMessage() throw() {}
526 char *configure(const char *key, const char *value) {
527 if (strcmp(key, "guiVisible") == 0) {
528 postMessage(new FakeGuiConnectMessage(String(value)));
530 return 0;
533 void handleMessage(const Message &msg) {
534 const FakeGuiConnectMessage *fmsg;
535 if ((fmsg = dynamic_cast<const FakeGuiConnectMessage*>(&msg))) {
536 bool show = fmsg->arg.isNotEmpty();
537 if (show) {
538 StringArray arg; arg.addTokens(fmsg->arg, JUCE_T("|"), JUCE_T(""));
539 if (arg.size() == 2) {
540 gui_osc_url = arg[0];
541 String window_title = arg[1];
543 /* only 1 gui will be opened at once, request for new guis will automatically close the older ones */
544 deleteEditor (true);
545 createEditorComp(window_title);
547 } else {
548 deleteEditor (true);
552 const OscAction *osc;
553 if ((osc = dynamic_cast<const OscAction*>(&msg))) {
554 if (osc->msg == "/internal_gui_hide") {
555 deleteEditor(true);
556 } else if (osc->msg == "/exiting") {
557 deleteEditor(true);
558 gui_osc_url = String::empty;
563 static AudioProcessor *initialiseAndCreateFilter() {
564 initialiseJuce_GUI();
565 AudioProcessor* filter = createPluginFilter();
566 return filter;
569 void createEditorComp(const String &title);
570 void deleteEditor (bool canDeleteLaterIfModal);
572 void notifyRemoteProcess(bool b) {
573 if (b) {
574 osc_server.startServer();
575 osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), osc_server.getOscUrl());
576 } else {
577 osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), JUCE_T(""));
578 osc_server.stopServer();
582 void timerCallback() {
583 if (shouldDeleteEditor) {
584 shouldDeleteEditor = false;
585 deleteEditor (true);
587 if (osc_server.isThreadRunning() && gui_osc_url.isNotEmpty()) {
588 // perdiodically ping the gui process, so that it now it has not been abandonned as an orphan process...
589 notifyRemoteProcess(editorComp !=0 );
593 snd_midi_event_t* midi_parser;
594 uint8_t midi_parser_buffer[16384];
596 }; // end of class JuceDSSIWrapper
598 void DssiEditorCompWrapper::closeButtonPressed() {
599 wrapper->deleteEditor(true);
602 void JuceDSSIWrapper::createEditorComp(const String &title) {
603 if (hasShutdown || filter == 0)
604 return;
606 if (editorComp == 0) {
607 AudioProcessorEditor* const ed = filter->createEditorIfNeeded();
608 if (ed) {
609 editorComp = new DssiEditorCompWrapper(this, ed, title, x_editor, y_editor);
610 notifyRemoteProcess(true);
613 shouldDeleteEditor = false;
616 void JuceDSSIWrapper::deleteEditor (bool canDeleteLaterIfModal)
618 PopupMenu::dismissAllActiveMenus();
620 if (editorComp != 0) {
621 Component* const modalComponent = Component::getCurrentlyModalComponent();
622 if (modalComponent != 0) {
623 modalComponent->exitModalState (0);
625 if (canDeleteLaterIfModal) {
626 shouldDeleteEditor = true;
627 return;
631 filter->editorBeingDeleted (editorComp->getEditorComp());
632 x_editor = editorComp->getX();
633 y_editor = editorComp->getY();
634 deleteAndZero (editorComp);
636 notifyRemoteProcess(false);
638 // there's some kind of component currently modal, but the host
639 // is trying to delete our plugin. You should try to avoid this happening..
640 jassert (Component::getCurrentlyModalComponent() == 0);
644 void JuceDSSIWrapper::initialiseDescriptors() {
645 initialiseJuce_GUI();
646 AudioProcessor *plugin = createPluginFilter();
648 char **port_names;
649 LADSPA_PortDescriptor *port_descriptors;
650 LADSPA_PortRangeHint *port_range_hints;
652 ladspa_desc = new LADSPA_Descriptor; assert(ladspa_desc);
653 ladspa_desc->UniqueID = JucePlugin_VSTUniqueID; // not used by dssi hosts anyway..
654 ladspa_desc->Label = "Main"; // must not contain white spaces
655 ladspa_desc->Properties = LADSPA_PROPERTY_REALTIME; //LADSPA_PROPERTY_HARD_RT_CAPABLE;
656 ladspa_desc->Name = JucePlugin_Name " DSSI Synth";
657 ladspa_desc->Maker = JucePlugin_Manufacturer;
658 ladspa_desc->Copyright = "Copyright (c) " JucePlugin_Manufacturer " 2010";
659 ladspa_desc->PortCount = JucePlugin_MaxNumOutputChannels + plugin->getNumParameters();
662 port_descriptors = new LADSPA_PortDescriptor[ladspa_desc->PortCount];
663 memset(port_descriptors, 0, sizeof(LADSPA_PortDescriptor)*ladspa_desc->PortCount);
664 ladspa_desc->PortDescriptors = port_descriptors;
666 port_range_hints = new LADSPA_PortRangeHint[ladspa_desc->PortCount];
667 memset(port_range_hints, 0, sizeof(LADSPA_PortRangeHint)*ladspa_desc->PortCount);
668 ladspa_desc->PortRangeHints = port_range_hints;
670 port_names = new char *[ladspa_desc->PortCount];
671 ladspa_desc->PortNames = port_names;
673 unsigned long port = 0;
674 for (int channel=0; channel < JucePlugin_MaxNumOutputChannels; ++channel, ++port) {
675 char s[100]; snprintf(s, 100, "Output%d", channel+1);
676 port_names[port] = strdup(s);
678 port_descriptors[port] = LADSPA_PORT_OUTPUT|LADSPA_PORT_AUDIO;
679 port_range_hints[port].HintDescriptor = 0;
681 for (int param=0; param < plugin->getNumParameters(); ++param, ++port) {
682 port_names[port] = strdup(plugin->getParameterName(param).toUTF8());
683 port_descriptors[port] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
684 port_range_hints[port].HintDescriptor = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE;
685 port_range_hints[port].LowerBound = 0;
686 port_range_hints[port].UpperBound = 1;
688 jassert(port == ladspa_desc->PortCount);
690 ladspa_desc->activate = &callbackActivate;
691 ladspa_desc->cleanup = &callbackCleanup;
692 ladspa_desc->connect_port = &callbackConnectPort;
693 ladspa_desc->deactivate = &callbackDeactivate;
694 ladspa_desc->instantiate = &callbackInstantiate;
695 ladspa_desc->run = &callbackRunAsEffect;
696 ladspa_desc->run_adding = NULL;
697 ladspa_desc->set_run_adding_gain = NULL;
700 dssi_desc = new DSSI_Descriptor;
701 dssi_desc->DSSI_API_Version = 1;
702 dssi_desc->LADSPA_Plugin = ladspa_desc;
703 dssi_desc->configure = &callbackConfigure;
704 dssi_desc->get_program = callbackGetProgram;
705 dssi_desc->get_midi_controller_for_port = NULL;
706 dssi_desc->select_program = callbackSelectProgram;
707 dssi_desc->run_synth = &callbackRun;
708 dssi_desc->run_synth_adding = NULL;
709 dssi_desc->run_multiple_synths = NULL;
710 dssi_desc->run_multiple_synths_adding = NULL;
712 delete plugin;
713 shutdownJuce_GUI();
716 __attribute__((destructor)) void dssi_destructor()
718 jassert(activePlugins.size() == 0);
719 JuceDSSIWrapper::destroyDescriptors();
722 extern "C" __attribute__ ((visibility("default"))) const LADSPA_Descriptor *ladspa_descriptor(unsigned long index)
724 return (index == 0 ? JuceDSSIWrapper::getLadspaDescriptor() : 0);
727 extern "C" __attribute__ ((visibility("default"))) const DSSI_Descriptor *dssi_descriptor(unsigned long index)
729 return (index == 0 ? JuceDSSIWrapper::getDssiDescriptor() : 0);
732 /* ---------- the fake gui process starts below ---------- */
734 struct FakeExternalGUI : public MessageListener, public Timer {
735 String window_title;
736 String host_osc_url;
737 String plugin_osc_url;
738 DssiMinimalOscServer osc_server;
739 juce::Time time_last_ping;
741 FakeExternalGUI() { osc_server.setListener(this); startTimer(1000); }
742 ~FakeExternalGUI() { osc_server.stopServer(); }
744 // notify the plugin via the host, using the '/configure' callback
745 void show(bool do_show) {
746 String conf;
747 if (do_show) {
748 conf << osc_server.getOscUrl() << "|" << window_title;
750 osc_server.sendMessageTo(host_osc_url, "/configure", "guiVisible", conf);
751 if (!do_show && plugin_osc_url.isNotEmpty())
752 osc_server.sendMessageTo(plugin_osc_url, "/internal_gui_hide", "0");
755 void quit() {
756 MessageManager::getInstance()->stopDispatchLoop();
759 void init(const char *host_osc_url_, const char *plugin_so_name,
760 const char *label, const char *friendlyname) {
761 (void)plugin_so_name;
762 host_osc_url << host_osc_url_;
763 window_title << label << " - " << friendlyname;
764 osc_server.startServer();
765 osc_server.sendMessageTo(host_osc_url, "/update", osc_server.getOscUrl() + "dssi");
768 void handleMessage(const Message &msg) {
769 const OscAction *osc;
770 if ((osc = dynamic_cast<const OscAction*>(&msg))) {
771 if (osc->msg == "/dssi/hide") show(false);
772 else if (osc->msg == "/dssi/show") show(true);
773 else if (osc->msg == "/dssi/quit") quit();
774 else if (osc->msg == "/internal_gui_status") {
775 plugin_osc_url = osc->arg;
776 time_last_ping = juce::Time::getCurrentTime();
777 if (!plugin_osc_url.isNotEmpty()) quit();
782 void timerCallback() {
783 juce::Time t = juce::Time::getCurrentTime();
784 if (plugin_osc_url.isNotEmpty() && (t-time_last_ping ).inMilliseconds() > 5000) {
785 /* no ping for 5 seconds, the fake gui process kills itself.. */
786 quit();
790 void exiting() {
791 osc_server.sendMessageTo(host_osc_url, "/exiting");
792 if (plugin_osc_url) osc_server.sendMessageTo(plugin_osc_url, "/exiting");
796 FakeExternalGUI *fake = 0;
798 void handle_sigterm(int) {
799 static int count = 0;
800 if (count++ == 0) {
801 fake->quit();
802 } else exit(1);
806 extern "C" __attribute__ ((visibility("default"))) int dssi_gui_main(const char *osc_host_url, const char *plugin_so_name,
807 const char *label, const char *friendlyname) {
808 initialiseJuce_GUI();
809 signal(SIGTERM, &handle_sigterm);
811 fake = new FakeExternalGUI();
812 fake->init(osc_host_url, plugin_so_name, label, friendlyname);
814 MessageManager::getInstance()->runDispatchLoop();
815 //fake->run_();
816 fake->exiting();
817 deleteAndZero(fake);
818 shutdownJuce_GUI();
819 return 0;