From a143bef7a57c3d7dbd6121a94826ea782cf38bf4 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 17 Jul 2011 10:58:19 +0100 Subject: [PATCH] Cleanup, need to get non-UI stuff working first --- juce-dssi.cpp | 1680 ++++++++++---------- .../audio/plugin_client/LV2/juce_LV2_Wrapper.cpp | 249 +-- 2 files changed, 845 insertions(+), 1084 deletions(-) rewrite juce-dssi.cpp (66%) diff --git a/juce-dssi.cpp b/juce-dssi.cpp dissimilarity index 66% index bae69b9..afe4a0e 100644 --- a/juce-dssi.cpp +++ b/juce-dssi.cpp @@ -1,858 +1,822 @@ - /* - DSSI Plugin wrapper. - - Recall to : disable XInitThreads in - doPlatformSpecificInitialisation/doPlatformSpecificShutdown with - also the signal redirection stuff, and the X11 io error stuff (this - is done in juce-git since 2010/10/28). - - Recall also to export only a few symbols, in order to avoid - potential clashes with the host symbols. The best is to use a linker - script: - - g++ -shared -Wl,version-script=linux_dssi_symbols.map - - with linux_dssi_symbols.map: - { - global: - ladspa_descriptor; - dssi_descriptor; - dssi_gui_main; - local: *; - }; - - And build your sources with -fvisibility=hidden. The best is also to - check that nm -D plugin.so does not list a ton of exported symbols - that may clash, such as zlib functions / png stuff or md5_init .. - Stuff from libstdc++ should be safe, I think. - - - IMPORTANT: make sure that the .so file used for the VST plugin and - the DSSI are not symlinks to the same physical file, or that will - cause issues (if a host allows VST and DSSI to be loaded at the same - time, such as renoise, then in that case both vst and dssi will - share the same juce environment, but they both use a different - message thread, and they both call ShutdownJuce_GUI() without caring - for each other so horrible crashes happen). - - */ - - #include - #if defined(TARGET_LINUX) - #include - #include - #include - #undef KeyPress - #endif - - #include "dssi.h" - #include "ladspa.h" - #include - #include "JucePluginCharacteristics.h" - #include - - #define USING_LIBLO - #ifdef USING_LIBLO - #include - #else - #define OSCPKT_OSTREAM_OUTPUT - #include "oscpkt/oscpkt.hh" - #include "oscpkt/udp.hh" - #endif - - /* conveniency functions for debugging ..*/ - inline std::ostream &operator<<(std::ostream &os, const juce::String &s) { - os << s.toUTF8(); - return os; - } - - - BEGIN_JUCE_NAMESPACE - extern Display* display; - extern bool juce_postMessageToSystemQueue (void* message); - END_JUCE_NAMESPACE - class DssiSharedMessageThread : public Thread - { - public: - DssiSharedMessageThread() - : Thread (JUCE_T("DssiMessageThread")), - initialised (false) - { - startThread (7); - while (! initialised) - sleep (1); - } - - ~DssiSharedMessageThread() - { - signalThreadShouldExit(); - JUCEApplication::quit(); - waitForThreadToExit (5000); - clearSingletonInstance(); - } - - void run() - { - initialiseJuce_GUI(); - initialised = true; - MessageManager::getInstance()->setCurrentThreadAsMessageThread(); - - while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) - { - } - } - - juce_DeclareSingleton (DssiSharedMessageThread, false) - - private: - bool initialised; - }; - - juce_ImplementSingleton (DssiSharedMessageThread); - - - - - - - - class JuceDSSIWrapper; - - class DssiEditorCompWrapper : public DocumentWindow - { - JuceDSSIWrapper* wrapper; - - public: - DssiEditorCompWrapper (JuceDSSIWrapper* const wrapper_, - AudioProcessorEditor* const editor, const String &title, - int xpos, int ypos) - : DocumentWindow(title, Colours::white, DocumentWindow::allButtons, false) - { - wrapper = wrapper_; - setOpaque (true); - setTitleBarHeight(0); - setUsingNativeTitleBar(true); - editor->setOpaque (true); - setDropShadowEnabled(false); - setContentComponent(editor, true, true); - - if (xpos != -10000 && ypos != -10000) { - setTopLeftPosition(xpos, ypos-20 /* position bug? */ ); - } else setCentreRelative(.5f, .5f); - - Component::addToDesktop(getDesktopWindowStyleFlags()); - setVisible(true); - } - - AudioProcessorEditor* getEditorComp() const - { - return dynamic_cast (getContentComponent()); - } - - BorderSize getBorderThickness() const { return BorderSize(0); } - BorderSize getContentComponentBorder() const { return BorderSize(0); } - - void closeButtonPressed(); - - juce_UseDebuggingNewOperator - }; - - - struct OscAction : public juce::Message { - String msg, arg; - }; - #ifdef USING_LIBLO - class DssiMinimalOscServer : public Thread { - lo_server serv; - MessageListener *listener; - public: - DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {} - ~DssiMinimalOscServer() { - jassert(!isThreadRunning()); - stopServer(); - } - void setListener(MessageListener *l) { listener = l; } - void sendMessageTo(const String &osc_url, const String &message_path, - const String &arg1 = String::empty, const String &arg2 = String::empty) { - char *url_hostname = lo_url_get_hostname(osc_url.toUTF8()); - char *url_port = lo_url_get_port(osc_url.toUTF8()); - char *url_path = lo_url_get_path(osc_url.toUTF8()); - - String path; - path << url_path << message_path; path = path.replace("//","/"); - lo_address hostaddr = lo_address_new(url_hostname, url_port); - - if (!arg1.isNotEmpty()) { - lo_send(hostaddr, path.toUTF8(), ""); - } else if (!arg2.isNotEmpty()) { - lo_send(hostaddr, path.toUTF8(), "s", arg1.toUTF8()); - } else { - lo_send(hostaddr, path.toUTF8(), "ss", arg1.toUTF8(), arg2.toUTF8()); - } - lo_address_free(hostaddr); - free(url_hostname); - free(url_port); - free(url_path); - } - void startServer() { - if (isThreadRunning()) return; - serv = lo_server_new(NULL, NULL); - lo_server_add_method(serv, NULL, NULL, &osc_callback, this); - startThread(); - } - void stopServer() { - if (!isThreadRunning()) return; - signalThreadShouldExit(); - stopThread(1500); - lo_server_free(serv); - } - String getOscUrl() { - char *s = lo_server_get_url(serv); - String url; url << s; free(s); - return url; - } - void run() { - while (!threadShouldExit()) { - lo_server_recv_noblock(serv, 50); - } - } - static int osc_callback(const char *path, const char *types, - lo_arg **argv, int argc, lo_message , void *user_data) { - OscAction *a = new OscAction; - a->msg << path; - if (argc > 0 && types[0] == 's') { - a->arg << &argv[0]->s; - } - ((DssiMinimalOscServer*)user_data)->listener->postMessage(a); - return 0; - } - }; - #else - // ad-hoc server that implements only what we need for dssi.. - class DssiMinimalOscServer : public Thread { - oscpkt::UdpSocket serv; - MessageListener *listener; - public: - DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {} - void setListener(MessageListener *l) { listener = l; } - void sendMessageTo(const String &osc_url, const String &message_path, - const String &arg1 = String::empty, const String &arg2 = String::empty) { - oscpkt::Url url(osc_url.toUTF8()); - if (!url.isOk()) return; - - oscpkt::PacketWriter pw; - oscpkt::Message msg; - std::string path = url.path; - if (!path.empty() && path[path.size()-1] == '/') path.resize(path.size()-1); - path += message_path.toUTF8(); - - msg.init(path); - if (arg1.isNotEmpty()) msg.pushStr(arg1.toUTF8()); - if (arg2.isNotEmpty()) msg.pushStr(arg2.toUTF8()); - pw.addMessage(msg); - - oscpkt::UdpSocket sock; sock.connectTo(url.hostname, url.port); - bool ok = sock.sendPacket(pw.packetData(), pw.packetSize()); - if (!ok) { - cerr << "Could not send " << msg.addressPattern() << " message to " - << url.hostname << ":" << url.port << " '" << sock.errorMessage() << "'\n"; - } - } - void startServer() { - if (isThreadRunning()) return; - serv.bindTo(0 /* any port */); - if (!serv.isOk()) { - cerr << "cannot start osc server: " << serv.errorMessage() << "\n"; return; - } - startThread(); - } - void stopServer() { - if (!isThreadRunning()) return; - signalThreadShouldExit(); - stopThread(1500); - serv.close(); - } - String getOscUrl() { - String s; s << ("osc.udp://" + serv.localHostNameWithPort() + "/").c_str(); - return s; - } - void run() { - while (!threadShouldExit() && serv.isOk()) { - if (serv.receiveNextPacket(50 /* timeout, in ms */)) { - oscpkt::PacketReader pr(serv.packetData(), serv.packetSize()); - if (pr.isOk()) { - oscpkt::Message *msg; - while ((msg = pr.popMessage())) { - OscAction *a = new OscAction; - a->msg << msg->addressPattern().c_str(); - if (msg->arg().nbArgRemaining() && msg->arg().isStr()) { - std::string s; msg->arg().popStr(s); - a->arg << s.c_str(); - } - listener->postMessage(a); - } - } - } - } - } - }; - #endif - - - - - - - static Array activePlugins; - - extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); - - static LADSPA_Descriptor *ladspa_desc = 0; - static DSSI_Descriptor *dssi_desc = 0; - - struct JuceDSSIWrapper : public Timer, public MessageListener { - - static LADSPA_Descriptor *getLadspaDescriptor() { - if (!ladspa_desc) initialiseDescriptors(); - return ladspa_desc; - } - - static DSSI_Descriptor *getDssiDescriptor() { - if (!dssi_desc) initialiseDescriptors(); - return dssi_desc; - } - - static void initialiseDescriptors(); - - static void destroyDescriptors() { - if (ladspa_desc) { - for (size_t i=0; i < ladspa_desc->PortCount; ++i) { - free((void*)ladspa_desc->PortNames[i]); - } - delete[] ladspa_desc->PortDescriptors; - delete[] ladspa_desc->PortNames; - delete[] ladspa_desc->PortRangeHints; - delete ladspa_desc; - ladspa_desc = 0; - } - if (dssi_desc) { - delete dssi_desc; - dssi_desc = 0; - } - } - - static void callbackCleanup(LADSPA_Handle instance) { - { - MessageManagerLock mmLock; - delete (JuceDSSIWrapper*)instance; - } - if (activePlugins.size() == 0) { - DssiSharedMessageThread::deleteInstance(); - shutdownJuce_GUI(); - } - } - - static LADSPA_Handle callbackInstantiate(const LADSPA_Descriptor *, - unsigned long s_rate) { - if (activePlugins.size() == 0) { - DssiSharedMessageThread::getInstance(); - } - MessageManagerLock mmLock; - return new JuceDSSIWrapper(s_rate); - } - - - static void callbackConnectPort(LADSPA_Handle instance, unsigned long port, - LADSPA_Data * data) { - MessageManagerLock mmLock; - ((JuceDSSIWrapper*)instance)->connectPort(port, data); - } - - static void callbackActivate(LADSPA_Handle instance) { - MessageManagerLock mmLock; - ((JuceDSSIWrapper*)instance)->activate(); - } - - static void callbackDeactivate(LADSPA_Handle instance) { - MessageManagerLock mmLock; - ((JuceDSSIWrapper*)instance)->deactivate(); - } - - static void callbackRunAsEffect(LADSPA_Handle instance, - unsigned long sample_count) { - ((JuceDSSIWrapper*)instance)->run(sample_count, 0, 0); - } - - static void callbackRun(LADSPA_Handle instance, unsigned long sample_count, - snd_seq_event_t *events, unsigned long event_count) { - ((JuceDSSIWrapper*)instance)->run(sample_count, events, event_count); - } - - static char* callbackConfigure(LADSPA_Handle instance, - const char *key, const char *value) { - MessageManagerLock mmLock; - return ((JuceDSSIWrapper*)instance)->configure(key, value); - } - - static const DSSI_Program_Descriptor *callbackGetProgram(LADSPA_Handle instance, - unsigned long index) { - MessageManagerLock mmLock; - return ((JuceDSSIWrapper*)instance)->getProgram(index); - } - - static void callbackSelectProgram(LADSPA_Handle instance, - unsigned long bank, - unsigned long program) { - MessageManagerLock mmLock; - return ((JuceDSSIWrapper*)instance)->selectProgram(bank, program); - } - - private: - double sample_rate; - MidiBuffer midi_buffer; - - float *output_port[JucePlugin_MaxNumOutputChannels]; - - #define UNSET_PARAMETER_VALUE 1e10 - Array param_port; - Array param_saved; // value of the parameters saved at previous callback, to detect param value change - - AudioProcessor *filter; - - DssiEditorCompWrapper* editorComp; - bool shouldDeleteEditor; - bool hasShutdown; - - int x_editor, y_editor; - - DssiMinimalOscServer osc_server; // used only for comminucation with the gui - String gui_osc_url; - - public: - JuceDSSIWrapper(unsigned long s_rate) { - editorComp = 0; - hasShutdown = false; - shouldDeleteEditor = false; - x_editor = y_editor = -10000; - osc_server.setListener(this); - - for (int c=0; c < JucePlugin_MaxNumOutputChannels; ++c) output_port[c] = 0; - sample_rate = s_rate; - filter = createPluginFilter(); - param_port.insertMultiple(0, 0, filter->getNumParameters()); - param_saved.insertMultiple(0, UNSET_PARAMETER_VALUE, filter->getNumParameters()); - activePlugins.add (this); - startTimer (1000); - } - - ~JuceDSSIWrapper() { - osc_server.stopServer(); - stopTimer(); - deleteEditor(false); - hasShutdown = true; - deleteAndZero(filter); - jassert (activePlugins.contains (this)); - activePlugins.removeValue (this); - } - - AudioProcessor *getFilter() { return filter; } - - void connectPort(unsigned long port, LADSPA_Data *data) { - if (port < JucePlugin_MaxNumOutputChannels) { - output_port[port] = data; - } else { - int param = (int)port - JucePlugin_MaxNumOutputChannels; - if (param < param_port.size()) { - param_port.set(param, (float*)data); - param_saved.set(param, UNSET_PARAMETER_VALUE); - } - } - } - - void activate() { - unsigned block_size = 512; - filter->setNonRealtime(false); - filter->setPlayConfigDetails (0, JucePlugin_MaxNumOutputChannels, - sample_rate, block_size); - filter->prepareToPlay(sample_rate, block_size); - updateParameters(); - midi_buffer.clear(); - snd_midi_event_new(sizeof midi_parser_buffer, &midi_parser); - } - - void deactivate() { - filter->releaseResources(); - midi_buffer.clear(); - snd_midi_event_free(midi_parser); - } - - DSSI_Program_Descriptor latest_program_descriptor; - std::string latest_program_descriptor_name; - const DSSI_Program_Descriptor *getProgram(unsigned long index) { - if (index < (unsigned long)filter->getNumPrograms()) { - latest_program_descriptor.Bank = 0; - latest_program_descriptor.Program = index; - latest_program_descriptor_name = filter->getProgramName((int)index).toUTF8(); - latest_program_descriptor.Name = latest_program_descriptor_name.c_str(); - return &latest_program_descriptor; - } - return 0; - } - - void selectProgram(unsigned long bank, unsigned long program) { - if (bank == 0) filter->setCurrentProgram((int)program); - updateParameters(); - } - - // update the port values from the plugin parameter values - void updateParameters() { - for (int i=0; i < param_port.size(); ++i) { - if (param_port[i]) { - float v = filter->getParameter(i); - *param_port[i] = v; - param_saved.set(i,v); - } - } - } - - void run(unsigned long sample_count, snd_seq_event_t *events, unsigned long event_count) { - /* handle incoming midi events */ - if (event_count) { - for (size_t i=0; i < event_count; ++i) { - const int num_bytes = snd_midi_event_decode(midi_parser, midi_parser_buffer, sizeof midi_parser_buffer, &events[i]); - snd_midi_event_reset_decode(midi_parser); - if (num_bytes) { - midi_buffer.addEvent(midi_parser_buffer, num_bytes, events[i].time.tick); - } - } - } - - /* handle parameter changes initiated by the host */ - for (int i=0; i < param_port.size(); ++i) { - if (param_port[i]) { - if (param_saved[i] != *param_port[i]) { - filter->setParameter(i, *param_port[i]); - } - } - } - - { - const ScopedLock sl (filter->getCallbackLock()); - if (filter->isSuspended()) { - for (int i = 0; i < JucePlugin_MaxNumOutputChannels; ++i) - zeromem (output_port[i], sizeof (float) * sample_count); - } else { - AudioSampleBuffer chans (output_port, JucePlugin_MaxNumOutputChannels, sample_count); - filter->processBlock (chans, midi_buffer); - } - } - - /* read back parameter values */ - updateParameters(); - - if (!midi_buffer.isEmpty()) { midi_buffer.clear(); } - } - - struct FakeGuiConnectMessage : public Message { - String arg; - FakeGuiConnectMessage(const String &s) { arg = s; } - ~FakeGuiConnectMessage() throw() {} - }; - - char *configure(const char *key, const char *value) { - if (strcmp(key, "guiVisible") == 0) { - postMessage(new FakeGuiConnectMessage(String(value))); - } - return 0; - } - - void handleMessage(const Message &msg) { - const FakeGuiConnectMessage *fmsg; - if ((fmsg = dynamic_cast(&msg))) { - bool show = fmsg->arg.isNotEmpty(); - if (show) { - StringArray arg; arg.addTokens(fmsg->arg, JUCE_T("|"), JUCE_T("")); - if (arg.size() == 2) { - gui_osc_url = arg[0]; - String window_title = arg[1]; - - /* only 1 gui will be opened at once, request for new guis will automatically close the older ones */ - deleteEditor (true); - createEditorComp(window_title); - } - } else { - deleteEditor (true); - } - } - - const OscAction *osc; - if ((osc = dynamic_cast(&msg))) { - if (osc->msg == "/internal_gui_hide") { - deleteEditor(true); - } else if (osc->msg == "/exiting") { - deleteEditor(true); - gui_osc_url = String::empty; - } - } - } - - static AudioProcessor *initialiseAndCreateFilter() { - initialiseJuce_GUI(); - AudioProcessor* filter = createPluginFilter(); - return filter; - } - - void createEditorComp(const String &title); - void deleteEditor (bool canDeleteLaterIfModal); - - void notifyRemoteProcess(bool b) { - if (b) { - osc_server.startServer(); - osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), osc_server.getOscUrl()); - } else { - osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), JUCE_T("")); - osc_server.stopServer(); - } - } - - void timerCallback() { - if (shouldDeleteEditor) { - shouldDeleteEditor = false; - deleteEditor (true); - } - if (osc_server.isThreadRunning() && gui_osc_url.isNotEmpty()) { - // perdiodically ping the gui process, so that it now it has not been abandonned as an orphan process... - notifyRemoteProcess(editorComp !=0 ); - } - } - - snd_midi_event_t* midi_parser; - uint8_t midi_parser_buffer[16384]; - - }; // end of class JuceDSSIWrapper - - void DssiEditorCompWrapper::closeButtonPressed() { - wrapper->deleteEditor(true); - } - - void JuceDSSIWrapper::createEditorComp(const String &title) { - if (hasShutdown || filter == 0) - return; - - if (editorComp == 0) { - AudioProcessorEditor* const ed = filter->createEditorIfNeeded(); - if (ed) { - editorComp = new DssiEditorCompWrapper(this, ed, title, x_editor, y_editor); - notifyRemoteProcess(true); - } - } - shouldDeleteEditor = false; - } - - void JuceDSSIWrapper::deleteEditor (bool canDeleteLaterIfModal) - { - PopupMenu::dismissAllActiveMenus(); - - if (editorComp != 0) { - Component* const modalComponent = Component::getCurrentlyModalComponent(); - if (modalComponent != 0) { - modalComponent->exitModalState (0); - - if (canDeleteLaterIfModal) { - shouldDeleteEditor = true; - return; - } - } - - filter->editorBeingDeleted (editorComp->getEditorComp()); - x_editor = editorComp->getX(); - y_editor = editorComp->getY(); - deleteAndZero (editorComp); - - notifyRemoteProcess(false); - - // there's some kind of component currently modal, but the host - // is trying to delete our plugin. You should try to avoid this happening.. - jassert (Component::getCurrentlyModalComponent() == 0); - } - } - - void JuceDSSIWrapper::initialiseDescriptors() { - initialiseJuce_GUI(); - AudioProcessor *plugin = createPluginFilter(); - - char **port_names; - LADSPA_PortDescriptor *port_descriptors; - LADSPA_PortRangeHint *port_range_hints; - - ladspa_desc = new LADSPA_Descriptor; assert(ladspa_desc); - ladspa_desc->UniqueID = JucePlugin_VSTUniqueID; // not used by dssi hosts anyway.. - ladspa_desc->Label = "Main"; // must not contain white spaces - ladspa_desc->Properties = LADSPA_PROPERTY_REALTIME; //LADSPA_PROPERTY_HARD_RT_CAPABLE; - ladspa_desc->Name = JucePlugin_Name " DSSI Synth"; - ladspa_desc->Maker = JucePlugin_Manufacturer; - ladspa_desc->Copyright = "Copyright (c) " JucePlugin_Manufacturer " 2010"; - ladspa_desc->PortCount = JucePlugin_MaxNumOutputChannels + plugin->getNumParameters(); - - - port_descriptors = new LADSPA_PortDescriptor[ladspa_desc->PortCount]; - memset(port_descriptors, 0, sizeof(LADSPA_PortDescriptor)*ladspa_desc->PortCount); - ladspa_desc->PortDescriptors = port_descriptors; - - port_range_hints = new LADSPA_PortRangeHint[ladspa_desc->PortCount]; - memset(port_range_hints, 0, sizeof(LADSPA_PortRangeHint)*ladspa_desc->PortCount); - ladspa_desc->PortRangeHints = port_range_hints; - - port_names = new char *[ladspa_desc->PortCount]; - ladspa_desc->PortNames = port_names; - - unsigned long port = 0; - for (int channel=0; channel < JucePlugin_MaxNumOutputChannels; ++channel, ++port) { - char s[100]; snprintf(s, 100, "Output%d", channel+1); - port_names[port] = strdup(s); - - port_descriptors[port] = LADSPA_PORT_OUTPUT|LADSPA_PORT_AUDIO; - port_range_hints[port].HintDescriptor = 0; - } - for (int param=0; param < plugin->getNumParameters(); ++param, ++port) { - port_names[port] = strdup(plugin->getParameterName(param).toUTF8()); - port_descriptors[port] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; - port_range_hints[port].HintDescriptor = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE; - port_range_hints[port].LowerBound = 0; - port_range_hints[port].UpperBound = 1; - } - jassert(port == ladspa_desc->PortCount); - - ladspa_desc->activate = &callbackActivate; - ladspa_desc->cleanup = &callbackCleanup; - ladspa_desc->connect_port = &callbackConnectPort; - ladspa_desc->deactivate = &callbackDeactivate; - ladspa_desc->instantiate = &callbackInstantiate; - ladspa_desc->run = &callbackRunAsEffect; - ladspa_desc->run_adding = NULL; - ladspa_desc->set_run_adding_gain = NULL; - - - dssi_desc = new DSSI_Descriptor; - dssi_desc->DSSI_API_Version = 1; - dssi_desc->LADSPA_Plugin = ladspa_desc; - dssi_desc->configure = &callbackConfigure; - dssi_desc->get_program = callbackGetProgram; - dssi_desc->get_midi_controller_for_port = NULL; - dssi_desc->select_program = callbackSelectProgram; - dssi_desc->run_synth = &callbackRun; - dssi_desc->run_synth_adding = NULL; - dssi_desc->run_multiple_synths = NULL; - dssi_desc->run_multiple_synths_adding = NULL; - - delete plugin; - shutdownJuce_GUI(); - } - - __attribute__((destructor)) void dssi_destructor() - { - jassert(activePlugins.size() == 0); - JuceDSSIWrapper::destroyDescriptors(); - } - - extern "C" __attribute__ ((visibility("default"))) const LADSPA_Descriptor *ladspa_descriptor(unsigned long index) - { - return (index == 0 ? JuceDSSIWrapper::getLadspaDescriptor() : 0); - } - - extern "C" __attribute__ ((visibility("default"))) const DSSI_Descriptor *dssi_descriptor(unsigned long index) - { - return (index == 0 ? JuceDSSIWrapper::getDssiDescriptor() : 0); - } - - /* ---------- the fake gui process starts below ---------- */ - - struct FakeExternalGUI : public MessageListener, public Timer { - String window_title; - String host_osc_url; - String plugin_osc_url; - DssiMinimalOscServer osc_server; - juce::Time time_last_ping; - - FakeExternalGUI() { osc_server.setListener(this); startTimer(1000); } - ~FakeExternalGUI() { osc_server.stopServer(); } - - // notify the plugin via the host, using the '/configure' callback - void show(bool do_show) { - String conf; - if (do_show) { - conf << osc_server.getOscUrl() << "|" << window_title; - } - osc_server.sendMessageTo(host_osc_url, "/configure", "guiVisible", conf); - if (!do_show && plugin_osc_url.isNotEmpty()) - osc_server.sendMessageTo(plugin_osc_url, "/internal_gui_hide", "0"); - } - - void quit() { - MessageManager::getInstance()->stopDispatchLoop(); - } - - void init(const char *host_osc_url_, const char *plugin_so_name, - const char *label, const char *friendlyname) { - (void)plugin_so_name; - host_osc_url << host_osc_url_; - window_title << label << " - " << friendlyname; - osc_server.startServer(); - osc_server.sendMessageTo(host_osc_url, "/update", osc_server.getOscUrl() + "dssi"); - } - - void handleMessage(const Message &msg) { - const OscAction *osc; - if ((osc = dynamic_cast(&msg))) { - if (osc->msg == "/dssi/hide") show(false); - else if (osc->msg == "/dssi/show") show(true); - else if (osc->msg == "/dssi/quit") quit(); - else if (osc->msg == "/internal_gui_status") { - plugin_osc_url = osc->arg; - time_last_ping = juce::Time::getCurrentTime(); - if (!plugin_osc_url.isNotEmpty()) quit(); - } - } - } - - void timerCallback() { - juce::Time t = juce::Time::getCurrentTime(); - if (plugin_osc_url.isNotEmpty() && (t-time_last_ping ).inMilliseconds() > 5000) { - /* no ping for 5 seconds, the fake gui process kills itself.. */ - quit(); - } - } - - void exiting() { - osc_server.sendMessageTo(host_osc_url, "/exiting"); - if (plugin_osc_url) osc_server.sendMessageTo(plugin_osc_url, "/exiting"); - } - }; - - FakeExternalGUI *fake = 0; - - void handle_sigterm(int) { - static int count = 0; - if (count++ == 0) { - fake->quit(); - } else exit(1); - } - - - extern "C" __attribute__ ((visibility("default"))) int dssi_gui_main(const char *osc_host_url, const char *plugin_so_name, - const char *label, const char *friendlyname) { - initialiseJuce_GUI(); - signal(SIGTERM, &handle_sigterm); - - fake = new FakeExternalGUI(); - fake->init(osc_host_url, plugin_so_name, label, friendlyname); - - MessageManager::getInstance()->runDispatchLoop(); - //fake->run_(); - fake->exiting(); - deleteAndZero(fake); - shutdownJuce_GUI(); - return 0; - } - - +/* + */ + +#include +#if defined(TARGET_LINUX) +#include +#include +#include +#undef KeyPress +#endif + +#include "dssi.h" +#include "ladspa.h" +#include +#include "JucePluginCharacteristics.h" +#include + +#define USING_LIBLO +#ifdef USING_LIBLO +#include +#else +#define OSCPKT_OSTREAM_OUTPUT +#include "oscpkt/oscpkt.hh" +#include "oscpkt/udp.hh" +#endif + +/* conveniency functions for debugging ..*/ +inline std::ostream &operator<<(std::ostream &os, const juce::String &s) { + os << s.toUTF8(); + return os; +} + + +BEGIN_JUCE_NAMESPACE + extern Display* display; +extern bool juce_postMessageToSystemQueue (void* message); +END_JUCE_NAMESPACE + class DssiSharedMessageThread : public Thread +{ +public: + DssiSharedMessageThread() + : Thread (JUCE_T("DssiMessageThread")), + initialised (false) + { + startThread (7); + while (! initialised) + sleep (1); + } + + ~DssiSharedMessageThread() + { + signalThreadShouldExit(); + JUCEApplication::quit(); + waitForThreadToExit (5000); + clearSingletonInstance(); + } + + void run() + { + initialiseJuce_GUI(); + initialised = true; + MessageManager::getInstance()->setCurrentThreadAsMessageThread(); + + while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) + { + } + } + + juce_DeclareSingleton (DssiSharedMessageThread, false) + + private: + bool initialised; +}; + +juce_ImplementSingleton (DssiSharedMessageThread); + + + + + + + +class JuceDSSIWrapper; + +class DssiEditorCompWrapper : public DocumentWindow +{ + JuceDSSIWrapper* wrapper; + +public: + DssiEditorCompWrapper (JuceDSSIWrapper* const wrapper_, + AudioProcessorEditor* const editor, const String &title, + int xpos, int ypos) + : DocumentWindow(title, Colours::white, DocumentWindow::allButtons, false) + { + wrapper = wrapper_; + setOpaque (true); + setTitleBarHeight(0); + setUsingNativeTitleBar(true); + editor->setOpaque (true); + setDropShadowEnabled(false); + setContentComponent(editor, true, true); + + if (xpos != -10000 && ypos != -10000) { + setTopLeftPosition(xpos, ypos-20 /* position bug? */ ); + } else setCentreRelative(.5f, .5f); + + Component::addToDesktop(getDesktopWindowStyleFlags()); + setVisible(true); + } + + AudioProcessorEditor* getEditorComp() const + { + return dynamic_cast (getContentComponent()); + } + + BorderSize getBorderThickness() const { return BorderSize(0); } + BorderSize getContentComponentBorder() const { return BorderSize(0); } + + void closeButtonPressed(); + + juce_UseDebuggingNewOperator; +}; + + +struct OscAction : public juce::Message { + String msg, arg; +}; +#ifdef USING_LIBLO +class DssiMinimalOscServer : public Thread { + lo_server serv; + MessageListener *listener; +public: + DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {} + ~DssiMinimalOscServer() { + jassert(!isThreadRunning()); + stopServer(); + } + void setListener(MessageListener *l) { listener = l; } + void sendMessageTo(const String &osc_url, const String &message_path, + const String &arg1 = String::empty, const String &arg2 = String::empty) { + char *url_hostname = lo_url_get_hostname(osc_url.toUTF8()); + char *url_port = lo_url_get_port(osc_url.toUTF8()); + char *url_path = lo_url_get_path(osc_url.toUTF8()); + + String path; + path << url_path << message_path; path = path.replace("//","/"); + lo_address hostaddr = lo_address_new(url_hostname, url_port); + + if (!arg1.isNotEmpty()) { + lo_send(hostaddr, path.toUTF8(), ""); + } else if (!arg2.isNotEmpty()) { + lo_send(hostaddr, path.toUTF8(), "s", arg1.toUTF8()); + } else { + lo_send(hostaddr, path.toUTF8(), "ss", arg1.toUTF8(), arg2.toUTF8()); + } + lo_address_free(hostaddr); + free(url_hostname); + free(url_port); + free(url_path); + } + void startServer() { + if (isThreadRunning()) return; + serv = lo_server_new(NULL, NULL); + lo_server_add_method(serv, NULL, NULL, &osc_callback, this); + startThread(); + } + void stopServer() { + if (!isThreadRunning()) return; + signalThreadShouldExit(); + stopThread(1500); + lo_server_free(serv); + } + String getOscUrl() { + char *s = lo_server_get_url(serv); + String url; url << s; free(s); + return url; + } + void run() { + while (!threadShouldExit()) { + lo_server_recv_noblock(serv, 50); + } + } + static int osc_callback(const char *path, const char *types, + lo_arg **argv, int argc, lo_message , void *user_data) { + OscAction *a = new OscAction; + a->msg << path; + if (argc > 0 && types[0] == 's') { + a->arg << &argv[0]->s; + } + ((DssiMinimalOscServer*)user_data)->listener->postMessage(a); + return 0; + } +}; +#else +// ad-hoc server that implements only what we need for dssi.. +class DssiMinimalOscServer : public Thread { + oscpkt::UdpSocket serv; + MessageListener *listener; +public: + DssiMinimalOscServer() : Thread(JUCE_T("osc")), listener(0) {} + void setListener(MessageListener *l) { listener = l; } + void sendMessageTo(const String &osc_url, const String &message_path, + const String &arg1 = String::empty, const String &arg2 = String::empty) { + oscpkt::Url url(osc_url.toUTF8()); + if (!url.isOk()) return; + + oscpkt::PacketWriter pw; + oscpkt::Message msg; + std::string path = url.path; + if (!path.empty() && path[path.size()-1] == '/') path.resize(path.size()-1); + path += message_path.toUTF8(); + + msg.init(path); + if (arg1.isNotEmpty()) msg.pushStr(arg1.toUTF8()); + if (arg2.isNotEmpty()) msg.pushStr(arg2.toUTF8()); + pw.addMessage(msg); + + oscpkt::UdpSocket sock; sock.connectTo(url.hostname, url.port); + bool ok = sock.sendPacket(pw.packetData(), pw.packetSize()); + if (!ok) { + cerr << "Could not send " << msg.addressPattern() << " message to " + << url.hostname << ":" << url.port << " '" << sock.errorMessage() << "'\n"; + } + } + void startServer() { + if (isThreadRunning()) return; + serv.bindTo(0 /* any port */); + if (!serv.isOk()) { + cerr << "cannot start osc server: " << serv.errorMessage() << "\n"; return; + } + startThread(); + } + void stopServer() { + if (!isThreadRunning()) return; + signalThreadShouldExit(); + stopThread(1500); + serv.close(); + } + String getOscUrl() { + String s; s << ("osc.udp://" + serv.localHostNameWithPort() + "/").c_str(); + return s; + } + void run() { + while (!threadShouldExit() && serv.isOk()) { + if (serv.receiveNextPacket(50 /* timeout, in ms */)) { + oscpkt::PacketReader pr(serv.packetData(), serv.packetSize()); + if (pr.isOk()) { + oscpkt::Message *msg; + while ((msg = pr.popMessage())) { + OscAction *a = new OscAction; + a->msg << msg->addressPattern().c_str(); + if (msg->arg().nbArgRemaining() && msg->arg().isStr()) { + std::string s; msg->arg().popStr(s); + a->arg << s.c_str(); + } + listener->postMessage(a); + } + } + } + } + } +}; +#endif + + + + + + +static Array activePlugins; + +extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); + +static LADSPA_Descriptor *ladspa_desc = 0; +static DSSI_Descriptor *dssi_desc = 0; + +struct JuceDSSIWrapper : public Timer, public MessageListener { + + static LADSPA_Descriptor *getLadspaDescriptor() { + if (!ladspa_desc) initialiseDescriptors(); + return ladspa_desc; + } + + static DSSI_Descriptor *getDssiDescriptor() { + if (!dssi_desc) initialiseDescriptors(); + return dssi_desc; + } + + static void initialiseDescriptors(); + + static void destroyDescriptors() { + if (ladspa_desc) { + for (size_t i=0; i < ladspa_desc->PortCount; ++i) { + free((void*)ladspa_desc->PortNames[i]); + } + delete[] ladspa_desc->PortDescriptors; + delete[] ladspa_desc->PortNames; + delete[] ladspa_desc->PortRangeHints; + delete ladspa_desc; + ladspa_desc = 0; + } + if (dssi_desc) { + delete dssi_desc; + dssi_desc = 0; + } + } + + static void callbackCleanup(LADSPA_Handle instance) { + { + MessageManagerLock mmLock; + delete (JuceDSSIWrapper*)instance; + } + if (activePlugins.size() == 0) { + DssiSharedMessageThread::deleteInstance(); + shutdownJuce_GUI(); + } + } + + static LADSPA_Handle callbackInstantiate(const LADSPA_Descriptor *, + unsigned long s_rate) { + if (activePlugins.size() == 0) { + DssiSharedMessageThread::getInstance(); + } + MessageManagerLock mmLock; + return new JuceDSSIWrapper(s_rate); + } + + + static void callbackConnectPort(LADSPA_Handle instance, unsigned long port, + LADSPA_Data * data) { + MessageManagerLock mmLock; + ((JuceDSSIWrapper*)instance)->connectPort(port, data); + } + + static void callbackActivate(LADSPA_Handle instance) { + MessageManagerLock mmLock; + ((JuceDSSIWrapper*)instance)->activate(); + } + + static void callbackDeactivate(LADSPA_Handle instance) { + MessageManagerLock mmLock; + ((JuceDSSIWrapper*)instance)->deactivate(); + } + + static void callbackRunAsEffect(LADSPA_Handle instance, + unsigned long sample_count) { + ((JuceDSSIWrapper*)instance)->run(sample_count, 0, 0); + } + + static void callbackRun(LADSPA_Handle instance, unsigned long sample_count, + snd_seq_event_t *events, unsigned long event_count) { + ((JuceDSSIWrapper*)instance)->run(sample_count, events, event_count); + } + + static char* callbackConfigure(LADSPA_Handle instance, + const char *key, const char *value) { + MessageManagerLock mmLock; + return ((JuceDSSIWrapper*)instance)->configure(key, value); + } + + static const DSSI_Program_Descriptor *callbackGetProgram(LADSPA_Handle instance, + unsigned long index) { + MessageManagerLock mmLock; + return ((JuceDSSIWrapper*)instance)->getProgram(index); + } + + static void callbackSelectProgram(LADSPA_Handle instance, + unsigned long bank, + unsigned long program) { + MessageManagerLock mmLock; + return ((JuceDSSIWrapper*)instance)->selectProgram(bank, program); + } + +private: + double sample_rate; + MidiBuffer midi_buffer; + + float *output_port[JucePlugin_MaxNumOutputChannels]; + +#define UNSET_PARAMETER_VALUE 1e10 + Array param_port; + Array param_saved; // value of the parameters saved at previous callback, to detect param value change + + AudioProcessor *filter; + + DssiEditorCompWrapper* editorComp; + bool shouldDeleteEditor; + bool hasShutdown; + + int x_editor, y_editor; + + DssiMinimalOscServer osc_server; // used only for comminucation with the gui + String gui_osc_url; + +public: + JuceDSSIWrapper(unsigned long s_rate) { + editorComp = 0; + hasShutdown = false; + shouldDeleteEditor = false; + x_editor = y_editor = -10000; + osc_server.setListener(this); + + for (int c=0; c < JucePlugin_MaxNumOutputChannels; ++c) output_port[c] = 0; + sample_rate = s_rate; + filter = createPluginFilter(); + param_port.insertMultiple(0, 0, filter->getNumParameters()); + param_saved.insertMultiple(0, UNSET_PARAMETER_VALUE, filter->getNumParameters()); + activePlugins.add (this); + startTimer (1000); + } + + ~JuceDSSIWrapper() { + osc_server.stopServer(); + stopTimer(); + deleteEditor(false); + hasShutdown = true; + deleteAndZero(filter); + jassert (activePlugins.contains (this)); + activePlugins.removeValue (this); + } + + AudioProcessor *getFilter() { return filter; } + + void connectPort(unsigned long port, LADSPA_Data *data) { + if (port < JucePlugin_MaxNumOutputChannels) { + output_port[port] = data; + } else { + int param = (int)port - JucePlugin_MaxNumOutputChannels; + if (param < param_port.size()) { + param_port.set(param, (float*)data); + param_saved.set(param, UNSET_PARAMETER_VALUE); + } + } + } + + void activate() { + unsigned block_size = 512; + filter->setNonRealtime(false); + filter->setPlayConfigDetails (0, JucePlugin_MaxNumOutputChannels, + sample_rate, block_size); + filter->prepareToPlay(sample_rate, block_size); + updateParameters(); + midi_buffer.clear(); + snd_midi_event_new(sizeof midi_parser_buffer, &midi_parser); + } + + void deactivate() { + filter->releaseResources(); + midi_buffer.clear(); + snd_midi_event_free(midi_parser); + } + + DSSI_Program_Descriptor latest_program_descriptor; + std::string latest_program_descriptor_name; + const DSSI_Program_Descriptor *getProgram(unsigned long index) { + if (index < (unsigned long)filter->getNumPrograms()) { + latest_program_descriptor.Bank = 0; + latest_program_descriptor.Program = index; + latest_program_descriptor_name = filter->getProgramName((int)index).toUTF8(); + latest_program_descriptor.Name = latest_program_descriptor_name.c_str(); + return &latest_program_descriptor; + } + return 0; + } + + void selectProgram(unsigned long bank, unsigned long program) { + if (bank == 0) filter->setCurrentProgram((int)program); + updateParameters(); + } + + // update the port values from the plugin parameter values + void updateParameters() { + for (int i=0; i < param_port.size(); ++i) { + if (param_port[i]) { + float v = filter->getParameter(i); + *param_port[i] = v; + param_saved.set(i,v); + } + } + } + + void run(unsigned long sample_count, snd_seq_event_t *events, unsigned long event_count) { + /* handle incoming midi events */ + if (event_count) { + for (size_t i=0; i < event_count; ++i) { + const int num_bytes = snd_midi_event_decode(midi_parser, midi_parser_buffer, sizeof midi_parser_buffer, &events[i]); + snd_midi_event_reset_decode(midi_parser); + if (num_bytes) { + midi_buffer.addEvent(midi_parser_buffer, num_bytes, events[i].time.tick); + } + } + } + + /* handle parameter changes initiated by the host */ + for (int i=0; i < param_port.size(); ++i) { + if (param_port[i]) { + if (param_saved[i] != *param_port[i]) { + filter->setParameter(i, *param_port[i]); + } + } + } + + { + const ScopedLock sl (filter->getCallbackLock()); + if (filter->isSuspended()) { + for (int i = 0; i < JucePlugin_MaxNumOutputChannels; ++i) + zeromem (output_port[i], sizeof (float) * sample_count); + } else { + AudioSampleBuffer chans (output_port, JucePlugin_MaxNumOutputChannels, sample_count); + filter->processBlock (chans, midi_buffer); + } + } + + /* read back parameter values */ + updateParameters(); + + if (!midi_buffer.isEmpty()) { midi_buffer.clear(); } + } + + struct FakeGuiConnectMessage : public Message { + String arg; + FakeGuiConnectMessage(const String &s) { arg = s; } + ~FakeGuiConnectMessage() throw() {} + }; + + char *configure(const char *key, const char *value) { + if (strcmp(key, "guiVisible") == 0) { + postMessage(new FakeGuiConnectMessage(String(value))); + } + return 0; + } + + void handleMessage(const Message &msg) { + const FakeGuiConnectMessage *fmsg; + if ((fmsg = dynamic_cast(&msg))) { + bool show = fmsg->arg.isNotEmpty(); + if (show) { + StringArray arg; arg.addTokens(fmsg->arg, JUCE_T("|"), JUCE_T("")); + if (arg.size() == 2) { + gui_osc_url = arg[0]; + String window_title = arg[1]; + + /* only 1 gui will be opened at once, request for new guis will automatically close the older ones */ + deleteEditor (true); + createEditorComp(window_title); + } + } else { + deleteEditor (true); + } + } + + const OscAction *osc; + if ((osc = dynamic_cast(&msg))) { + if (osc->msg == "/internal_gui_hide") { + deleteEditor(true); + } else if (osc->msg == "/exiting") { + deleteEditor(true); + gui_osc_url = String::empty; + } + } + } + + static AudioProcessor *initialiseAndCreateFilter() { + initialiseJuce_GUI(); + AudioProcessor* filter = createPluginFilter(); + return filter; + } + + void createEditorComp(const String &title); + void deleteEditor (bool canDeleteLaterIfModal); + + void notifyRemoteProcess(bool b) { + if (b) { + osc_server.startServer(); + osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), osc_server.getOscUrl()); + } else { + osc_server.sendMessageTo(gui_osc_url, JUCE_T("/internal_gui_status"), JUCE_T("")); + osc_server.stopServer(); + } + } + + void timerCallback() { + if (shouldDeleteEditor) { + shouldDeleteEditor = false; + deleteEditor (true); + } + if (osc_server.isThreadRunning() && gui_osc_url.isNotEmpty()) { + // perdiodically ping the gui process, so that it now it has not been abandonned as an orphan process... + notifyRemoteProcess(editorComp !=0 ); + } + } + + snd_midi_event_t* midi_parser; + uint8_t midi_parser_buffer[16384]; + +}; // end of class JuceDSSIWrapper + +void DssiEditorCompWrapper::closeButtonPressed() { + wrapper->deleteEditor(true); +} + +void JuceDSSIWrapper::createEditorComp(const String &title) { + if (hasShutdown || filter == 0) + return; + + if (editorComp == 0) { + AudioProcessorEditor* const ed = filter->createEditorIfNeeded(); + if (ed) { + editorComp = new DssiEditorCompWrapper(this, ed, title, x_editor, y_editor); + notifyRemoteProcess(true); + } + } + shouldDeleteEditor = false; +} + +void JuceDSSIWrapper::deleteEditor (bool canDeleteLaterIfModal) +{ + PopupMenu::dismissAllActiveMenus(); + + if (editorComp != 0) { + Component* const modalComponent = Component::getCurrentlyModalComponent(); + if (modalComponent != 0) { + modalComponent->exitModalState (0); + + if (canDeleteLaterIfModal) { + shouldDeleteEditor = true; + return; + } + } + + filter->editorBeingDeleted (editorComp->getEditorComp()); + x_editor = editorComp->getX(); + y_editor = editorComp->getY(); + deleteAndZero (editorComp); + + notifyRemoteProcess(false); + + // there's some kind of component currently modal, but the host + // is trying to delete our plugin. You should try to avoid this happening.. + jassert (Component::getCurrentlyModalComponent() == 0); + } +} + +void JuceDSSIWrapper::initialiseDescriptors() { + initialiseJuce_GUI(); + AudioProcessor *plugin = createPluginFilter(); + + char **port_names; + LADSPA_PortDescriptor *port_descriptors; + LADSPA_PortRangeHint *port_range_hints; + + ladspa_desc = new LADSPA_Descriptor; assert(ladspa_desc); + ladspa_desc->UniqueID = JucePlugin_VSTUniqueID; // not used by dssi hosts anyway.. + ladspa_desc->Label = "Main"; // must not contain white spaces + ladspa_desc->Properties = LADSPA_PROPERTY_REALTIME; //LADSPA_PROPERTY_HARD_RT_CAPABLE; + ladspa_desc->Name = JucePlugin_Name " DSSI Synth"; + ladspa_desc->Maker = JucePlugin_Manufacturer; + ladspa_desc->Copyright = "Copyright (c) " JucePlugin_Manufacturer " 2010"; + ladspa_desc->PortCount = JucePlugin_MaxNumOutputChannels + plugin->getNumParameters(); + + + port_descriptors = new LADSPA_PortDescriptor[ladspa_desc->PortCount]; + memset(port_descriptors, 0, sizeof(LADSPA_PortDescriptor)*ladspa_desc->PortCount); + ladspa_desc->PortDescriptors = port_descriptors; + + port_range_hints = new LADSPA_PortRangeHint[ladspa_desc->PortCount]; + memset(port_range_hints, 0, sizeof(LADSPA_PortRangeHint)*ladspa_desc->PortCount); + ladspa_desc->PortRangeHints = port_range_hints; + + port_names = new char *[ladspa_desc->PortCount]; + ladspa_desc->PortNames = port_names; + + unsigned long port = 0; + for (int channel=0; channel < JucePlugin_MaxNumOutputChannels; ++channel, ++port) { + char s[100]; snprintf(s, 100, "Output%d", channel+1); + port_names[port] = strdup(s); + + port_descriptors[port] = LADSPA_PORT_OUTPUT|LADSPA_PORT_AUDIO; + port_range_hints[port].HintDescriptor = 0; + } + for (int param=0; param < plugin->getNumParameters(); ++param, ++port) { + port_names[port] = strdup(plugin->getParameterName(param).toUTF8()); + port_descriptors[port] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; + port_range_hints[port].HintDescriptor = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE; + port_range_hints[port].LowerBound = 0; + port_range_hints[port].UpperBound = 1; + } + jassert(port == ladspa_desc->PortCount); + + ladspa_desc->activate = &callbackActivate; + ladspa_desc->cleanup = &callbackCleanup; + ladspa_desc->connect_port = &callbackConnectPort; + ladspa_desc->deactivate = &callbackDeactivate; + ladspa_desc->instantiate = &callbackInstantiate; + ladspa_desc->run = &callbackRunAsEffect; + ladspa_desc->run_adding = NULL; + ladspa_desc->set_run_adding_gain = NULL; + + + dssi_desc = new DSSI_Descriptor; + dssi_desc->DSSI_API_Version = 1; + dssi_desc->LADSPA_Plugin = ladspa_desc; + dssi_desc->configure = &callbackConfigure; + dssi_desc->get_program = callbackGetProgram; + dssi_desc->get_midi_controller_for_port = NULL; + dssi_desc->select_program = callbackSelectProgram; + dssi_desc->run_synth = &callbackRun; + dssi_desc->run_synth_adding = NULL; + dssi_desc->run_multiple_synths = NULL; + dssi_desc->run_multiple_synths_adding = NULL; + + delete plugin; + shutdownJuce_GUI(); +} + +__attribute__((destructor)) void dssi_destructor() +{ + jassert(activePlugins.size() == 0); + JuceDSSIWrapper::destroyDescriptors(); +} + +extern "C" __attribute__ ((visibility("default"))) const LADSPA_Descriptor *ladspa_descriptor(unsigned long index) +{ + return (index == 0 ? JuceDSSIWrapper::getLadspaDescriptor() : 0); +} + +extern "C" __attribute__ ((visibility("default"))) const DSSI_Descriptor *dssi_descriptor(unsigned long index) +{ + return (index == 0 ? JuceDSSIWrapper::getDssiDescriptor() : 0); +} + +/* ---------- the fake gui process starts below ---------- */ + +struct FakeExternalGUI : public MessageListener, public Timer { + String window_title; + String host_osc_url; + String plugin_osc_url; + DssiMinimalOscServer osc_server; + juce::Time time_last_ping; + + FakeExternalGUI() { osc_server.setListener(this); startTimer(1000); } + ~FakeExternalGUI() { osc_server.stopServer(); } + + // notify the plugin via the host, using the '/configure' callback + void show(bool do_show) { + String conf; + if (do_show) { + conf << osc_server.getOscUrl() << "|" << window_title; + } + osc_server.sendMessageTo(host_osc_url, "/configure", "guiVisible", conf); + if (!do_show && plugin_osc_url.isNotEmpty()) + osc_server.sendMessageTo(plugin_osc_url, "/internal_gui_hide", "0"); + } + + void quit() { + MessageManager::getInstance()->stopDispatchLoop(); + } + + void init(const char *host_osc_url_, const char *plugin_so_name, + const char *label, const char *friendlyname) { + (void)plugin_so_name; + host_osc_url << host_osc_url_; + window_title << label << " - " << friendlyname; + osc_server.startServer(); + osc_server.sendMessageTo(host_osc_url, "/update", osc_server.getOscUrl() + "dssi"); + } + + void handleMessage(const Message &msg) { + const OscAction *osc; + if ((osc = dynamic_cast(&msg))) { + if (osc->msg == "/dssi/hide") show(false); + else if (osc->msg == "/dssi/show") show(true); + else if (osc->msg == "/dssi/quit") quit(); + else if (osc->msg == "/internal_gui_status") { + plugin_osc_url = osc->arg; + time_last_ping = juce::Time::getCurrentTime(); + if (!plugin_osc_url.isNotEmpty()) quit(); + } + } + } + + void timerCallback() { + juce::Time t = juce::Time::getCurrentTime(); + if (plugin_osc_url.isNotEmpty() && (t-time_last_ping ).inMilliseconds() > 5000) { + /* no ping for 5 seconds, the fake gui process kills itself.. */ + quit(); + } + } + + void exiting() { + osc_server.sendMessageTo(host_osc_url, "/exiting"); + if (plugin_osc_url) osc_server.sendMessageTo(plugin_osc_url, "/exiting"); + } +}; + +FakeExternalGUI *fake = 0; + +void handle_sigterm(int) { + static int count = 0; + if (count++ == 0) { + fake->quit(); + } else exit(1); +} + + +extern "C" __attribute__ ((visibility("default"))) int dssi_gui_main(const char *osc_host_url, const char *plugin_so_name, + const char *label, const char *friendlyname) { + initialiseJuce_GUI(); + signal(SIGTERM, &handle_sigterm); + + fake = new FakeExternalGUI(); + fake->init(osc_host_url, plugin_so_name, label, friendlyname); + + MessageManager::getInstance()->runDispatchLoop(); + //fake->run_(); + fake->exiting(); + deleteAndZero(fake); + shutdownJuce_GUI(); + return 0; +} + + diff --git a/juce/source/src/audio/plugin_client/LV2/juce_LV2_Wrapper.cpp b/juce/source/src/audio/plugin_client/LV2/juce_LV2_Wrapper.cpp index 571e425..43d3e4c 100644 --- a/juce/source/src/audio/plugin_client/LV2/juce_LV2_Wrapper.cpp +++ b/juce/source/src/audio/plugin_client/LV2/juce_LV2_Wrapper.cpp @@ -18,16 +18,6 @@ #include "../juce_PluginHeaders.h" #include "../juce_PluginHostType.h" -static bool recursionCheck = false; - -BEGIN_JUCE_NAMESPACE - extern void juce_callAnyTimersSynchronously(); -END_JUCE_NAMESPACE - -extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); - -static Array activePlugins; - //============================================================================== // Same as juce_lv2_gen.cpp String get_uri() @@ -45,24 +35,23 @@ String get_external_ui_uri() return String("urn:" JucePlugin_Manufacturer ":" JucePlugin_Name ":JUCE-External-UI").replace(" ", "_"); } +static Array activePlugins; + +extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); + //============================================================================== // Create a new JUCE LV2 Plugin -class JuceLV2Wrapper : private Timer, public AudioProcessorListener +class JuceLV2Wrapper { public: JuceLV2Wrapper(const LV2_Descriptor* descriptor_, double sample_rate_, const LV2_Feature* const* features) : chunkMemoryTime (0), - editor (nullptr), numInChans (JucePlugin_MaxNumInputChannels), numOutChans (JucePlugin_MaxNumOutputChannels), isProcessing (false), hasShutdown (false), firstProcessCallback (true), - shouldDeleteEditor (false), descriptor (descriptor_), - ui_descriptor (nullptr), - ui_controller (nullptr), - ui_write_func (nullptr), sample_rate (sample_rate_), buffer_size (512), midi_uri_id (0), @@ -71,7 +60,6 @@ public: printf("JuceLV2Wrapper()\n"); filter = createPluginFilter(); filter->setPlayConfigDetails(numInChans, numOutChans, 0, 0); - filter->addListener (this); // Port count #if JucePlugin_WantsMidiInput @@ -121,19 +109,11 @@ public: JUCE_AUTORELEASEPOOL { - #if JUCE_LINUX - MessageManagerLock mmLock; - #endif - stopTimer(); - deleteEditor (false); - hasShutdown = true; delete filter; filter = 0; - jassert (editor == 0); - channels.free(); deleteTempChannels(); @@ -249,7 +229,7 @@ public: #endif } - // Change if buffer size changed + // Check if buffer size changed if (buffer_size != sample_count) { buffer_size = sample_count; filter->setPlayConfigDetails(numInChans, numOutChans, sample_rate, buffer_size); @@ -343,216 +323,27 @@ public: } } - //============================================================================== - static void do_ui_external_run(lv2_external_ui* _this_) - { - printf("do_ui_external_run()\n"); - //doIdleCallback(); - - } - - static void do_ui_external_show(lv2_external_ui* _this_) - { - printf("do_ui_external_show()\n"); - //editorComp->setVisible(true); - - } - - static void do_ui_external_hide(lv2_external_ui* _this_) - { - printf("do_ui_external_hide()\n"); - //editorComp->setVisible(false); - } - - void do_ui_instantiate(const char* UiURI, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features) - { - ui_write_func = write_function; - ui_controller = controller; - - initialiseJuce_GUI(); - - //checkWhetherMessageThreadIsCorrect(); - //const MessageManagerLock mmLock; - //jassert (! recursionCheck); - - //startTimer (1000 / 4); // performs misc housekeeping chores - - deleteEditor (true); - createEditor(); - - if (get_external_ui_uri().compare(UiURI) == 0) - { - printf("External UI starting...\n"); - - ui_external_host = nullptr; - - uint16_t i = 0; - while(features[i]) { - if (strcmp(features[i]->URI, LV2_EXTERNAL_UI_URI) == 0) { - ui_external_host = (lv2_external_ui_host*)features[i]->data; - printf("Got external UI host data\n"); - break; - } - i++; - } - - ui_external_widget.run = do_ui_external_run; - ui_external_widget.show = do_ui_external_show; - ui_external_widget.hide = do_ui_external_hide; - - *widget = &ui_external_widget; - - printf("External UI initiated\n"); - } - else // JUCE UI - { - printf("JUCE UI starting...\n"); - *widget = editor; - } - - } - - void do_ui_cleanup() - { - // TODO - } - - void do_ui_port_event(uint32_t port_index, float value) + void do_cleanup() { - //int index = - //if (port_index < filter->getNumParameters()) - // filter->setParameter(port_index, value); } // TODO - set/get chunk //============================================================================== - // JUCE Stuff - void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) - { - //int ctrl_pad = 0; - //if (ui_controller && ui_write_func) - // ui_write_func(ui_controller, ctrl_pad + index, sizeof(float), 0, &newValue); - } - - void audioProcessorChanged (AudioProcessor*) - { - //updateDisplay(); - } - - void timerCallback() - { - if (shouldDeleteEditor) - { - shouldDeleteEditor = false; - deleteEditor (true); - } - - if (chunkMemoryTime > 0 - && chunkMemoryTime < JUCE_NAMESPACE::Time::getApproximateMillisecondCounter() - 2000 - && ! recursionCheck) - { - chunkMemoryTime = 0; - chunkMemory.setSize (0); - } - } - - void doIdleCallback() - { - // (wavelab calls this on a separate thread and causes a deadlock).. - if (MessageManager::getInstance()->isThisTheMessageThread() - && ! recursionCheck) - { - recursionCheck = true; - - JUCE_AUTORELEASEPOOL - juce_callAnyTimersSynchronously(); - - for (int i = ComponentPeer::getNumPeers(); --i >= 0;) - ComponentPeer::getPeer (i)->performAnyPendingRepaintsNow(); - - recursionCheck = false; - } - } - - void createEditor() - { - printf("createEditor()\n"); - - if (hasShutdown || filter == nullptr) - return; - - if (editor == nullptr) - { - printf("HERE 001\n"); - editor = filter->createEditorIfNeeded(); - printf("HERE 002\n"); - - if (editor != nullptr) - { - printf("HERE 003\n"); - editor->setOpaque (true); - editor->setVisible (true); - printf("HERE 005\n"); - } - printf("HERE 006\n"); - } - - shouldDeleteEditor = false; - printf("HERE 007\n"); - } - - void deleteEditor (bool canDeleteLaterIfModal) - { - JUCE_AUTORELEASEPOOL - PopupMenu::dismissAllActiveMenus(); - - jassert (! recursionCheck); - recursionCheck = true; - - if (editor != nullptr) - { - Component* const modalComponent = Component::getCurrentlyModalComponent(); - if (modalComponent != nullptr) - { - modalComponent->exitModalState (0); - - if (canDeleteLaterIfModal) - { - shouldDeleteEditor = true; - recursionCheck = false; - return; - } - } - - filter->editorBeingDeleted (editor); - - // there's some kind of component currently modal, but the host - // is trying to delete our plugin. You should try to avoid this happening.. - jassert (Component::getCurrentlyModalComponent() == nullptr); - } - - recursionCheck = false; - } + // JUCE Stuff, TODO //============================================================================== private: AudioProcessor* filter; JUCE_NAMESPACE::MemoryBlock chunkMemory; JUCE_NAMESPACE::uint32 chunkMemoryTime; - AudioProcessorEditor* editor; MidiBuffer midiEvents; int numInChans, numOutChans; - bool isProcessing, hasShutdown, firstProcessCallback, shouldDeleteEditor; + bool isProcessing, hasShutdown, firstProcessCallback; HeapBlock channels; Array tempChannels; // see note in do_run() const LV2_Descriptor* descriptor; - const LV2UI_Descriptor* ui_descriptor; - LV2UI_Controller ui_controller; - LV2UI_Write_Function ui_write_func; - lv2_external_ui ui_external_widget; - lv2_external_ui_host* ui_external_host; double sample_rate; int buffer_size; @@ -591,38 +382,39 @@ LV2_Handle juce_lv2_instantiate(const LV2_Descriptor* descriptor, double sample_ void juce_lv2_connect_port(LV2_Handle instance, uint32_t port, void* data_location) { - if (!instance) return; JuceLV2Wrapper* wrapper = (JuceLV2Wrapper*)instance; wrapper->do_connect_port(port, data_location); } void juce_lv2_activate(LV2_Handle instance) { - if (!instance) return; JuceLV2Wrapper* wrapper = (JuceLV2Wrapper*)instance; wrapper->do_activate(); } void juce_lv2_run(LV2_Handle instance, uint32_t sample_count) { - if (!instance) return; JuceLV2Wrapper* wrapper = (JuceLV2Wrapper*)instance; wrapper->do_run(sample_count); } void juce_lv2_deactivate(LV2_Handle instance) { - if (!instance) return; JuceLV2Wrapper* wrapper = (JuceLV2Wrapper*)instance; wrapper->do_deactivate(); } void juce_lv2_cleanup(LV2_Handle instance) { - if (!instance) return; JuceLV2Wrapper* wrapper = (JuceLV2Wrapper*)instance; - //free((void*)wrapper->descriptor->URI); - //delete wrapper->descriptor; + wrapper->do_cleanup(); + + //if (wrapper->descriptor) + //{ + // free((void*)wrapper->descriptor->URI); + // delete wrapper->descriptor; + //} + delete wrapper; } @@ -632,6 +424,8 @@ const void* juce_lv2_extension_data(const char* uri) return nullptr; } +//============================================================================== +#if 0 LV2UI_Handle juce_lv2ui_instantiate(const LV2UI_Descriptor* descriptor, const char* plugin_uri, const char* bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features) { printf("ui_instantiate()\n"); @@ -666,6 +460,7 @@ void juce_lv2ui_port_event(LV2UI_Handle instance, uint32_t port_index, uint32_t float value = *(float*)buffer; wrapper->do_ui_port_event(port_index, value); } +#endif //============================================================================== // Create new LV2 objects @@ -683,6 +478,7 @@ LV2_Descriptor* getNewLv2Plugin() return Lv2Plugin; } +#if 0 LV2UI_Descriptor* getNewLv2UI(bool external) { LV2UI_Descriptor* Lv2UI = new LV2UI_Descriptor; @@ -693,6 +489,7 @@ LV2UI_Descriptor* getNewLv2UI(bool external) Lv2UI->extension_data = juce_lv2_extension_data; return Lv2UI; } +#endif //============================================================================== // Mac startup code.. @@ -723,7 +520,7 @@ LV2UI_Descriptor* getNewLv2UI(bool external) extern "C" __attribute__ ((visibility("default"))) const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) { // 0 -> External UI; 1 -> JUCE UI - return (index <= 1) ? getNewLv2UI((index == 0)) : nullptr; + return nullptr; //(index <= 1) ? getNewLv2UI((index == 0)) : nullptr; } // don't put initialiseJuce_GUI or shutdownJuce_GUI in these... it will crash! -- 2.11.4.GIT