//============================================================================= // MusE // Linux Music Editor // $Id: dssihost.cpp,v 1.15.2.16 2009/12/15 03:39:58 terminator356 Exp $ // // Copyright (C) 2002-2006 by Werner Schweer and others // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //============================================================================= #include "config.h" #ifdef DSSI_SUPPORT // Turn on debugging messages //#define DSSI_DEBUG // Support vst state saving/loading with vst chunks. Requires patches to DSSI and DSSI-vst! //#define DSSI_VST_CHUNK_SUPPORT #include #include #include #include #include //#include //#include #include #include #include #include "dssihost.h" #include "synth.h" #include "audio.h" #include "jackaudio.h" //#include "../driver/jackaudio.h" // p4.0.2 #include "midi.h" #include "midiport.h" #include "stringparam.h" #include "plugin.h" //#include "al/al.h" //#include "al/xml.h" #include "xml.h" #include "song.h" //#include "midictrl.h" //#include "ladspaplugin.h" #include "app.h" #include "globals.h" #include "globaldefs.h" //#include "al/dsp.h" #include "gconfig.h" /* static lo_server_thread serverThread; static char osc_path_tmp[1024]; static char* url; //--------------------------------------------------------- // oscError //--------------------------------------------------------- static void oscError(int num, const char *msg, const char *path) { fprintf(stderr, "MusE: liblo server error %d in path %s: %s\n", num, path, msg); } //--------------------------------------------------------- // oscDebugHandler //--------------------------------------------------------- static int oscDebugHandler(const char* path, const char* types, lo_arg** argv, int argc, void*, void*) { printf("MusE: got unhandled OSC message:\n path: <%s>\n", path); for (int i = 0; i < argc; i++) { printf(" arg %d '%c' ", i, types[i]); lo_arg_pp(lo_type(types[i]), argv[i]); printf("\n"); } return 1; } //--------------------------------------------------------- // oscUpdate //--------------------------------------------------------- int DssiSynthIF::oscUpdate(lo_arg **argv) { const char *url = (char *)&argv[0]->s; if (uiTarget) lo_address_free(uiTarget); char* host = lo_url_get_hostname(url); char* port = lo_url_get_port(url); uiTarget = lo_address_new(host, port); free(host); free(port); if (uiOscPath) free(uiOscPath); uiOscPath = lo_url_get_path(url); int pl = strlen(uiOscPath); if (uiOscControlPath) free(uiOscControlPath); uiOscControlPath = (char *)malloc(pl + 10); sprintf(uiOscControlPath, "%s/control", uiOscPath); if (uiOscConfigurePath) free(uiOscConfigurePath); uiOscConfigurePath = (char *)malloc(pl + 12); sprintf(uiOscConfigurePath, "%s/configure", uiOscPath); if (uiOscProgramPath) free(uiOscProgramPath); uiOscProgramPath = (char *)malloc(pl + 10); sprintf(uiOscProgramPath, "%s/program", uiOscPath); if (uiOscShowPath) free(uiOscShowPath); uiOscShowPath = (char *)malloc(pl + 10); sprintf(uiOscShowPath, "%s/show", uiOscPath); // At this point a more substantial host might also call // configure() on the UI to set any state that it had remembered // for the plugin instance. But we don't remember state for // plugin instances (see our own configure() implementation in // osc_configure_handler), and so we have nothing to send except // the optional project directory. #ifdef DSSI_DEBUG printf("DssiSynthIF::oscUpdate synth name:%s url:%s uiTarget:%p uiOscPath:%s uiOscConfigurePath:%s museProject:%s\n", synti->name().ascii(), url, uiTarget, uiOscPath, uiOscConfigurePath, museProject.ascii()); #endif //lo_send(uiTarget, uiOscConfigurePath, "ss", //DSSI_PROJECT_DIRECTORY_KEY, song->projectPath().toAscii().data()); lo_send(uiTarget, uiOscConfigurePath, "ss", DSSI_PROJECT_DIRECTORY_KEY, museProject.ascii()); #if 0 // Send current bank/program (-FIX- another race...) if (instance->pendingProgramChange < 0) { unsigned long bank = instance->currentBank; unsigned long program = instance->currentProgram; instance->uiNeedsProgramUpdate = 0; if (instance->uiTarget) { lo_send(instance->uiTarget, instance->ui_osc_program_path, "ii", bank, program); } } // Send control ports for (i = 0; i < instance->plugin->controlIns; i++) { int in = i + instance->firstControlIn; int port = pluginControlInPortNumbers[in]; lo_send(instance->uiTarget, instance->ui_osc_control_path, "if", port, pluginControlIns[in]); // Avoid overloading the GUI if there are lots and lots of ports if ((i+1) % 50 == 0) usleep(300000); } #endif return 0; } //--------------------------------------------------------- // oscMessageHandler //--------------------------------------------------------- int oscMessageHandler(const char* path, const char* types, lo_arg** argv, int argc, void* data, void* user_data) { const char* p = path; #ifdef DSSI_DEBUG if(argc) { printf("oscMessageHandler: path:%s argc:%d\n", path, argc); for(int i = 0; i < argc; ++i) { printf(" "); lo_arg_pp((lo_type)types[i], argv[i]); } printf("\n"); } else { printf("%s\n", path); printf("oscMessageHandler: no args, path:%s\n", path); } #endif if (strncmp(p, "/dssi/", 6)) return oscDebugHandler(path, types, argv, argc, data, user_data); p += 6; //p = strrchr(p, "/"); SynthIList* sl = song->syntis(); DssiSynthIF* instance = 0; SynthI* synti = 0; #ifdef DSSI_DEBUG fprintf(stderr, "oscMessageHandler: song->syntis() size:%d\n", sl->size()); #endif for(int retry = 0; retry < 5; ++retry) { #ifdef DSSI_DEBUG fprintf(stderr, "oscMessageHandler: search retry number:%d ...\n", retry); #endif //if(uiOscPath) // break; for(iSynthI si = sl->begin(); si != sl->end(); ++si) { #ifdef DSSI_DEBUG fprintf(stderr, "oscMessageHandler: searching for synth p:%s: checking instances:%s\n", p, (*si)->name().ascii()); #endif //int l = strlen((*si)->name().toAscii().data()); //if (!strncmp(p, (*si)->name().toAscii().data(), l)) { //int l = strlen((*si)->name().ascii()); const char* sub = strstr(p, (*si)->name().ascii()); //if(!strncmp(p, (*si)->name().ascii(), l)) if(sub != NULL) { synti = *si; instance = (DssiSynthIF*)(synti->sif()); //p += l; p = sub + strlen((*si)->name().ascii()); break; } } if(instance) break; sleep(1); } if(!instance) { fprintf(stderr, "oscMessageHandler: error: no instance\n"); return oscDebugHandler(path, types, argv, argc, data, user_data); } if (*p != '/' || *(p + 1) == 0) { fprintf(stderr, "oscMessageHandler: error: end or no /\n"); return oscDebugHandler(path, types, argv, argc, data, user_data); } ++p; #ifdef DSSI_DEBUG fprintf(stderr, "oscMessageHandler: method:%s\n", p); #endif if (!strcmp(p, "configure") && argc == 2 && !strcmp(types, "ss")) return instance->oscConfigure(argv); else if (!strcmp(p, "control") && argc == 2 && !strcmp(types, "if")) return instance->oscControl(argv); else if (!strcmp(p, "midi") && argc == 1 && !strcmp(types, "m")) return instance->oscMidi(argv); else if (!strcmp(p, "program") && argc == 2 && !strcmp(types, "ii")) return instance->oscProgram(argv); else if (!strcmp(p, "update") && argc == 1 && !strcmp(types, "s")) return instance->oscUpdate(argv); else if (!strcmp(p, "exiting") && argc == 0) return instance->oscExiting(argv); return oscDebugHandler(path, types, argv, argc, data, user_data); } */ //--------------------------------------------------------- // scanDSSILib //--------------------------------------------------------- static void scanDSSILib(QFileInfo& fi) // ddskrjo removed const for argument { //void* handle = dlopen(fi.filePath().toAscii().data(), RTLD_NOW); void* handle = dlopen(fi.filePath().toLatin1().constData(), RTLD_NOW); //void* handle = dlopen(fi.absFilePath().toLatin1().constData(), RTLD_NOW); if (handle == 0) { fprintf(stderr, "scanDSSILib: dlopen(%s) failed: %s\n", //fi.filePath().toAscii().data(), dlerror()); fi.filePath().toLatin1().constData(), dlerror()); //fi.absFilePath().toLatin1().constData(), dlerror()); return; } DSSI_Descriptor_Function dssi = (DSSI_Descriptor_Function)dlsym(handle, "dssi_descriptor"); if (!dssi) { /* const char *txt = dlerror(); if (txt) { fprintf(stderr, "Unable to find dssi_descriptor() function in plugin " "library file \"%s\": %s.\n" "Are you sure this is a DSSI plugin file?\n", //fi.filePath().toAscii().data(), fi.filePath().ascii(), //fi.absFilePath().toLatin1().constData(), txt); dlclose(handle); exit(1); } */ dlclose(handle); return; } else { const DSSI_Descriptor* descr; for (int i = 0;; ++i) { descr = dssi(i); if (descr == 0) break; #ifdef DSSI_DEBUG fprintf(stderr, "scanDSSILib: name:%s inPlaceBroken:%d\n", descr->LADSPA_Plugin->Name, LADSPA_IS_INPLACE_BROKEN(descr->LADSPA_Plugin->Properties)); #endif // Listing synths only while excluding effect plugins: // Do the exact opposite of what dssi-vst.cpp does for listing ladspa plugins. // That way we cover all bases - effect plugins and synths. // Non-synths will show up in the ladspa effect dialog, while synths will show up here... // There should be nothing left out... if(descr->run_synth || descr->run_synth_adding || descr->run_multiple_synths || descr->run_multiple_synths_adding) { const QString label(descr->LADSPA_Plugin->Label); // Make sure it doesn't already exist. std::vector::iterator is; for(is = synthis.begin(); is != synthis.end(); ++is) { Synth* s = *is; //#ifdef DSSI_DEBUG // fprintf(stderr, "scanDSSILib: name:%s listname:%s lib:%s listlib:%s\n", // label.toLatin1().constData(), s->name().toLatin1().constData(), fi.baseName(true).toLatin1().constData(), s->baseName().toLatin1().constData()); //#endif if(s->name() == label && s->baseName() == fi.completeBaseName()) break; } if(is != synthis.end()) continue; DssiSynth* s = new DssiSynth(fi, descr); if(debugMsg) { fprintf(stderr, "scanDSSILib: name:%s listname:%s lib:%s listlib:%s\n", label.toLatin1().constData(), s->name().toLatin1().constData(), fi.completeBaseName().toLatin1().constData(), s->baseName().toLatin1().constData()); int ai = 0, ao = 0, ci = 0, co = 0; for(unsigned long pt = 0; pt < descr->LADSPA_Plugin->PortCount; ++pt) { LADSPA_PortDescriptor pd = descr->LADSPA_Plugin->PortDescriptors[pt]; if(LADSPA_IS_PORT_INPUT(pd) && LADSPA_IS_PORT_AUDIO(pd)) ai++; else if(LADSPA_IS_PORT_OUTPUT(pd) && LADSPA_IS_PORT_AUDIO(pd)) ao++; else if(LADSPA_IS_PORT_INPUT(pd) && LADSPA_IS_PORT_CONTROL(pd)) ci++; else if(LADSPA_IS_PORT_OUTPUT(pd) && LADSPA_IS_PORT_CONTROL(pd)) co++; } fprintf(stderr, "audio ins:%d outs:%d control ins:%d outs:%d\n", ai, ao, ci, co); } synthis.push_back(s); } else { // NOTE: Just a test //QFileInfo ffi(fi); //plugins.add(&ffi, LADSPA_Descriptor_Function(NULL), descr->LADSPA_Plugin, false); //plugins.add(&ffi, descr, false); } } } dlclose(handle); } //--------------------------------------------------------- // scanVstDir //--------------------------------------------------------- static void scanDSSIDir(QString& s) // ddskrjo removed const for argument { if(debugMsg) //printf("scan DSSI plugin dir <%s>\n", s.toAscii().data()); printf("scanDSSIDir: scan DSSI plugin dir <%s>\n", s.toLatin1().constData()); #ifdef __APPLE__ QDir pluginDir(s, QString("*.dylib"), QDir::Unsorted, QDir::Files); #else QDir pluginDir(s, QString("*.so"), QDir::Unsorted, QDir::Files); #endif if(!pluginDir.exists()) return; //const QFileInfoList list = pluginDir.entryInfoList(); //for (int i = 0; i < list.size(); ++i) { //QFileInfo fi = list.at(i); //scanDSSILib(fi); //} QStringList list = pluginDir.entryList(); for(int i = 0; i < list.count(); ++i) { if(debugMsg) printf("scanDSSIDir: found %s\n", (s + QString("/") + list[i]).toLatin1().constData()); QFileInfo fi(s + QString("/") + list[i]); scanDSSILib(fi); } } //--------------------------------------------------------- // initDSSI //--------------------------------------------------------- void initDSSI() { const char* dssiPath = getenv("DSSI_PATH"); if (dssiPath == 0) dssiPath = "/usr/local/lib64/dssi:/usr/lib64/dssi:/usr/local/lib/dssi:/usr/lib/dssi"; //const char* ladspaPath = getenv("LADSPA_PATH"); //if (ladspaPath == 0) // ladspaPath = "/usr/local/lib64/ladspa:/usr/lib64/ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa"; const char* p = dssiPath; //QString pth = QString(dssiPath) + QString(":") + QString(ladspaPath); //const char* p = pth.toLatin1().constData(); while (*p != '\0') { const char* pe = p; while (*pe != ':' && *pe != '\0') pe++; int n = pe - p; if (n) { char* buffer = new char[n + 1]; strncpy(buffer, p, n); buffer[n] = '\0'; QString tmpStr(buffer); scanDSSIDir(tmpStr); delete[] buffer; } p = pe; if (*p == ':') p++; } // Create OSC thread //serverThread = lo_server_thread_new(0, oscError); //snprintf(osc_path_tmp, 31, "/dssi"); //char* tmp = lo_server_thread_get_url(serverThread); //url = (char *)malloc(strlen(tmp) + strlen(osc_path_tmp)); //sprintf(url, "%s%s", tmp, osc_path_tmp + 1); //free(tmp); //lo_server_thread_add_method(serverThread, 0, 0, oscMessageHandler, 0); //lo_server_thread_start(serverThread); } //--------------------------------------------------------- // DssiSynth // Synth.label = plug.Label // Synth.descr = plug.Name // Synth.maker = plug.maker // Synth.version = nil (no such field in ladspa, maybe try copyright instead) //--------------------------------------------------------- DssiSynth::DssiSynth(QFileInfo& fi, const DSSI_Descriptor* d) : // ddskrjo removed const from QFileInfo //Synth(fi, label, descr, maker, ver) Synth(fi, QString(d->LADSPA_Plugin->Label), QString(d->LADSPA_Plugin->Name), QString(d->LADSPA_Plugin->Maker), QString()) { df = 0; handle = 0; dssi = 0; _hasGui = false; const LADSPA_Descriptor* descr = d->LADSPA_Plugin; _portCount = descr->PortCount; //_portDescriptors = 0; //if(_portCount) // _portDescriptors = new LADSPA_PortDescriptor[_portCount]; _inports = 0; _outports = 0; _controlInPorts = 0; _controlOutPorts = 0; for(unsigned long k = 0; k < _portCount; ++k) { LADSPA_PortDescriptor pd = descr->PortDescriptors[k]; //_portDescriptors[k] = pd; if(pd & LADSPA_PORT_AUDIO) { if(pd & LADSPA_PORT_INPUT) ++_inports; else if(pd & LADSPA_PORT_OUTPUT) ++_outports; } else if(pd & LADSPA_PORT_CONTROL) { if(pd & LADSPA_PORT_INPUT) ++_controlInPorts; else if(pd & LADSPA_PORT_OUTPUT) ++_controlOutPorts; } } _inPlaceCapable = !LADSPA_IS_INPLACE_BROKEN(descr->Properties); // Blacklist vst plugins in-place configurable for now. if ((_inports != _outports) || (fi.completeBaseName() == QString("dssi-vst") && !config.vstInPlace)) _inPlaceCapable = false; } DssiSynth::~DssiSynth() { } //--------------------------------------------------------- // createSIF //--------------------------------------------------------- SynthIF* DssiSynth::createSIF(SynthI* synti) { if (_instances == 0) { //handle = dlopen(info.filePath().toAscii().data(), RTLD_NOW); handle = dlopen(info.filePath().toLatin1().constData(), RTLD_NOW); //handle = dlopen(info.absFilePath().toLatin1().constData(), RTLD_NOW); if (handle == 0) { fprintf(stderr, "DssiSynth::createSIF dlopen(%s) failed: %s\n", //info.filePath().toAscii().data(), dlerror()); info.filePath().toLatin1().constData(), dlerror()); //info.absFilePath().toLatin1().constData(), dlerror()); return 0; } df = (DSSI_Descriptor_Function)dlsym(handle, "dssi_descriptor"); if (!df) { const char *txt = dlerror(); fprintf(stderr, "Unable to find dssi_descriptor() function in plugin " "library file \"%s\": %s.\n" "Are you sure this is a DSSI plugin file?\n", //info.filePath().toAscii().data(), info.filePath().toLatin1().constData(), //info.absFilePath().toLatin1().constData(), txt ? txt : "?"); dlclose(handle); handle = 0; return 0; } for (int i = 0;; ++i) { dssi = df(i); if (dssi == 0) break; QString label(dssi->LADSPA_Plugin->Label); if (label == _name) break; } if(dssi != 0) { _inports = 0; _outports = 0; _controlInPorts = 0; _controlOutPorts = 0; pIdx.clear(); opIdx.clear(); iIdx.clear(); oIdx.clear(); rpIdx.clear(); iUsedIdx.clear(); midiCtl2PortMap.clear(); port2MidiCtlMap.clear(); //synti->_guiUpdateControls.clear(); const LADSPA_Descriptor* descr = dssi->LADSPA_Plugin; //#ifdef DSSI_DEBUG // printf("DssiSynth::createSIF ladspa plugin PortCount:%lu\n", d->PortCount); //#endif _portCount = descr->PortCount; for (unsigned long k = 0; k < _portCount; ++k) { LADSPA_PortDescriptor pd = descr->PortDescriptors[k]; #ifdef DSSI_DEBUG printf("DssiSynth::createSIF ladspa plugin Port:%ld Name:%s descriptor:%x\n", k, descr->PortNames[k], pd); #endif if (LADSPA_IS_PORT_AUDIO(pd)) { if (LADSPA_IS_PORT_INPUT(pd)) { ++_inports; iIdx.push_back(k); iUsedIdx.push_back(false); // Start out with all false. } else if (LADSPA_IS_PORT_OUTPUT(pd)) { ++_outports; oIdx.push_back(k); } rpIdx.push_back((unsigned long)-1); } else if (LADSPA_IS_PORT_CONTROL(pd)) { if (LADSPA_IS_PORT_INPUT(pd)) { rpIdx.push_back(_controlInPorts); ++_controlInPorts; pIdx.push_back(k); // Set to false at first. //synti->_guiUpdateControls.push_back(false); } else if (LADSPA_IS_PORT_OUTPUT(pd)) { rpIdx.push_back((unsigned long)-1); ++_controlOutPorts; opIdx.push_back(k); } } } _inPlaceCapable = !LADSPA_IS_INPLACE_BROKEN(descr->Properties); // Blacklist vst plugins in-place configurable for now. if((_inports != _outports) || (info.completeBaseName() == QString("dssi-vst") && !config.vstInPlace)) _inPlaceCapable = false; } } if (dssi == 0) { //fprintf(stderr, "cannot found DSSI synti %s\n", _name.toAscii().data()); fprintf(stderr, "cannot find DSSI synti %s\n", _name.toLatin1().constData()); dlclose(handle); handle = 0; df = 0; return 0; } DssiSynthIF* sif = new DssiSynthIF(synti); ++_instances; sif->init(this); //_plugin->incInstances(1); // static char oscUrl[1024]; //snprintf(oscUrl, 1024, "%s/%s", url, synti->name().toAscii().data()); //snprintf(oscUrl, 1024, "%s/%s", url, synti->name().toLatin1().constData()); // snprintf(oscUrl, 1024, "%s/%s/%s", url, info.baseName().toLatin1().constData(), synti->name().toLatin1().constData()); //QString guiPath(info.path() + "/" + info.baseName()); QString guiPath(info.path() + "/" + info.baseName()); QDir guiDir(guiPath, "*", QDir::Unsorted, QDir::Files); _hasGui = guiDir.exists(); //sif->initGui(); return sif; } //--------------------------------------------------------- // guiVisible //--------------------------------------------------------- bool DssiSynthIF::guiVisible() const { //return _guiVisible; #ifdef OSC_SUPPORT return _oscif.oscGuiVisible(); #endif return false; } //--------------------------------------------------------- // showGui //--------------------------------------------------------- void DssiSynthIF::showGui(bool v) { #ifdef OSC_SUPPORT #ifdef DSSI_DEBUG printf("DssiSynthIF::showGui(): v:%d visible:%d\n", v, guiVisible()); #endif _oscif.oscShowGui(v); #endif // OSC_SUPPORT /* if (v == guiVisible()) return; //if(guiPid == -1) if((guiQProc == 0) || (!guiQProc->isRunning())) { // We need an indicator that update was called - update must have been called to get new path etc... // If the process is not running this path is invalid, right? if(uiOscPath) free(uiOscPath); uiOscPath = 0; #ifdef DSSI_DEBUG printf("DssiSynthIF::showGui(): No QProcess or process not running. Starting gui...\n"); #endif initGui(); } //for (int i = 0; i < 5; ++i) { for (int i = 0; i < 10; ++i) { // Give it a wee bit more time? if (uiOscPath) break; sleep(1); } if (uiOscPath == 0) { printf("DssiSynthIF::showGui(): no uiOscPath. Error: Timeout - synth gui did not start within 10 seconds.\n"); return; } char uiOscGuiPath[strlen(uiOscPath)+6]; sprintf(uiOscGuiPath, "%s/%s", uiOscPath, v ? "show" : "hide"); #ifdef DSSI_DEBUG printf("DssiSynthIF::showGui(): Sending show/hide uiOscGuiPath:%s\n", uiOscGuiPath); #endif lo_send(uiTarget, uiOscGuiPath, ""); _guiVisible = v; */ } //--------------------------------------------------------- // receiveEvent //--------------------------------------------------------- //MidiEvent DssiSynthIF::receiveEvent() // { // return MidiEvent(); // } MidiPlayEvent DssiSynthIF::receiveEvent() { return MidiPlayEvent(); } //--------------------------------------------------------- // init //--------------------------------------------------------- bool DssiSynthIF::init(DssiSynth* s) { #ifdef DSSI_DEBUG printf("DssiSynthIF::init\n"); #endif synth = s; const DSSI_Descriptor* dssi = synth->dssi; const LADSPA_Descriptor* ld = dssi->LADSPA_Plugin; handle = ld->instantiate(ld, sampleRate); #ifdef OSC_SUPPORT _oscif.oscSetSynthIF(this); #endif queryPrograms(); int inports = synth->_inports; if(inports != 0) { audioInBuffers = new float*[inports]; for(int k = 0; k < inports; ++k) { //audioInBuffers[k] = new LADSPA_Data[segmentSize]; //posix_memalign((void**)(audioInBuffers + k), 16, sizeof(float) * segmentSize); posix_memalign((void**)&audioInBuffers[k], 16, sizeof(float) * segmentSize); memset(audioInBuffers[k], 0, sizeof(float) * segmentSize); ld->connect_port(handle, synth->iIdx[k], audioInBuffers[k]); } } int outports = synth->_outports; if(outports != 0) { audioOutBuffers = new float*[outports]; for(int k = 0; k < outports; ++k) { //audioOutBuffers[k] = new LADSPA_Data[segmentSize]; //posix_memalign((void**)(audioOutBuffers + k), 16, sizeof(float) * segmentSize); posix_memalign((void**)&audioOutBuffers[k], 16, sizeof(float) * segmentSize); memset(audioOutBuffers[k], 0, sizeof(float) * segmentSize); ld->connect_port(handle, synth->oIdx[k], audioOutBuffers[k]); //printf("DssiSynthIF::init output port name: %s\n", ld->PortNames[synth->oIdx[k]]); // out1, out2, out3 etc } } int controlPorts = synth->_controlInPorts; int controlOutPorts = synth->_controlOutPorts; if(controlPorts != 0) controls = new Port[controlPorts]; else controls = 0; if(controlOutPorts != 0) controlsOut = new Port[controlOutPorts]; else controlsOut = 0; synth->midiCtl2PortMap.clear(); synth->port2MidiCtlMap.clear(); synti->_guiUpdateControls.clear(); synti->_guiUpdateProgram = false; for (int k = 0; k < controlPorts; ++k) { int i = synth->pIdx[k]; //controls[k].val = ladspaDefaultValue(ld, i); ladspaDefaultValue(ld, i, &controls[k].val); // Set to false at first. synti->_guiUpdateControls.push_back(false); #ifdef DSSI_DEBUG printf("DssiSynthIF::init control port:%d port idx:%d name:%s\n", k, i, ld->PortNames[i]); #endif // This code is duplicated in ::getControllerInfo() // int ctlnum = DSSI_NONE; if(dssi->get_midi_controller_for_port) ctlnum = dssi->get_midi_controller_for_port(handle, i); // No controller number? Try to give it a unique one... if(ctlnum == DSSI_NONE) { // FIXME: Be more careful. Must make sure to pick numbers not already chosen or which WILL BE chosen. // Simple but flawed solution: Start them at 0x60000 + 0x2000 = 0x62000. Max NRPN number is 0x3fff. // TODO: Update: Actually we want to try to use CC Controller7 controllers if possible (or a choice) because what if // the user's controller hardware doesn't support RPN? // If CC Controller7 is chosen we must make sure to use only non-common numbers. An already limited range // of 127 now becomes narrower. See the cool document midi-controllers.txt in the DSSI source for a // nice roundup of numbers and how to choose them and how they relate to synths and DSSI synths etc. ! ctlnum = CTRL_NRPN14_OFFSET + 0x2000 + k; } else { int c = ctlnum; // Can be both CC and NRPN! Prefer CC over NRPN. if(DSSI_IS_CC(ctlnum)) { #ifdef DSSI_DEBUG printf("DssiSynthIF::init is CC control\n"); #endif ctlnum = DSSI_CC_NUMBER(c); #ifdef DSSI_DEBUG if(DSSI_IS_NRPN(ctlnum)) printf("DssiSynthIF::init is also NRPN control. Using CC.\n"); #endif } else if(DSSI_IS_NRPN(ctlnum)) { #ifdef DSSI_DEBUG printf("DssiSynthIF::init is NRPN control\n"); #endif ctlnum = DSSI_NRPN_NUMBER(c) + CTRL_NRPN14_OFFSET; } } #ifdef DSSI_DEBUG printf("DssiSynthIF::init inserting to midiCtl2PortMap: ctlnum:%d k:%d\n", ctlnum, k); #endif // We have a controller number! Insert it and the DSSI port number into both maps. synth->midiCtl2PortMap.insert(std::pair(ctlnum, k)); synth->port2MidiCtlMap.insert(std::pair(k, ctlnum)); ld->connect_port(handle, i, &controls[k].val); } for (int k = 0; k < controlOutPorts; ++k) { int i = synth->opIdx[k]; #ifdef DSSI_DEBUG printf("DssiSynthIF::init control output port:%d port idx:%d name:%s\n", k, i, ld->PortNames[i]); #endif // p3.3.39 Removed. /* //controls[k].val = ladspaDefaultValue(ld, i); ladspaDefaultValue(ld, i, &controlsOut[k].val); // This code is duplicated in ::getControllerInfo() // int ctlnum = DSSI_NONE; if(dssi->get_midi_controller_for_port) ctlnum = dssi->get_midi_controller_for_port(handle, i); // No controller number? Try to give it a unique one... if(ctlnum == DSSI_NONE) { // FIXME: Be more careful. Must make sure to pick numbers not already chosen or which WILL BE chosen. // Simple but flawed solution: Start them at 0x60000 + 0x3000 = 0x63000. Max NRPN number is 0x3fff. // TODO: CC etc. etc. ctlnum = CTRL_NRPN14_OFFSET + 0x3000 + k; } else { int c = ctlnum; // Can be both CC and NRPN! Prefer CC over NRPN. if(DSSI_IS_CC(ctlnum)) { #ifdef DSSI_DEBUG printf("DssiSynthIF::init is CC control\n"); #endif ctlnum = DSSI_CC_NUMBER(c); #ifdef DSSI_DEBUG if(DSSI_IS_NRPN(ctlnum)) printf("DssiSynthIF::init is also NRPN control. Using CC.\n"); #endif } else if(DSSI_IS_NRPN(ctlnum)) { #ifdef DSSI_DEBUG printf("DssiSynthIF::init is NRPN control\n"); #endif ctlnum = DSSI_NRPN_NUMBER(c) + CTRL_NRPN14_OFFSET; } } #ifdef DSSI_DEBUG printf("DssiSynthIF::init inserting to midiCtl2PortMap: ctlnum:%d k:%d\n", ctlnum, k); #endif // We have a controller number! Insert it and the DSSI port number into the map. // p3.3.39 Removed. Doesn't say whether it's in or out! Don't need this for now. //synth->midiCtl2PortMap.insert(std::pair(ctlnum, k)); */ // - Control outs are not handled but still must be connected to something. ld->connect_port(handle, i, &controlsOut[k].val); } // Set the latency to zero. //controls[controlPorts].val = 0.0; // Insert a controller for latency and the DSSI port number into the map. //synth->midiCtl2PortMap.insert(std::pair(CTRL_NRPN14_OFFSET + 0x2000, controlPorts)); // Connect the port. //ld->connect_port(handle, controlPorts, &controls[controlPorts].val); // Just a test. It works! We can instantiate a ladspa plugin for the synth. But it needs more work... //plugins.add(&synth->info, LADSPA_Descriptor_Function(NULL), ld, false); if (ld->activate) ld->activate(handle); // Set current configuration values. if(dssi->configure) { char *rv = dssi->configure(handle, DSSI_PROJECT_DIRECTORY_KEY, museProject.toLatin1().constData()); //song->projectPath() if(rv) { fprintf(stderr, "MusE: Warning: plugin doesn't like project directory: \"%s\"\n", rv); free(rv); } for(ciStringParamMap r = synti->_stringParamMap.begin(); r != synti->_stringParamMap.end(); ++r) { rv = 0; rv = dssi->configure(handle, r->first.c_str(), r->second.c_str()); if(rv) { fprintf(stderr, "MusE: Warning: plugin config key: %s value: %s \"%s\"\n", r->first.c_str(), r->second.c_str(), rv); free(rv); } } } // Set current program. if(dssi->select_program) dssi->select_program(handle, synti->_curBankL, synti->_curProgram); // // For stored initial control values, let SynthI::initInstance() take care of that via ::setParameter(). // return true; } //--------------------------------------------------------- // DssiSynthIF //--------------------------------------------------------- DssiSynthIF::DssiSynthIF(SynthI* s) : SynthIF(s) { #ifdef DSSI_DEBUG printf("DssiSynthIF::DssiSynthIF\n"); #endif synth = 0; handle = NULL; controls = 0; controlsOut = 0; //_curBank = 0; //_curProgram = 0; //#ifdef OSC_SUPPORT //_oscif.setSynthIF(this); //#endif //_guiVisible = false; //uiTarget = 0; //uiOscShowPath = 0; //uiOscControlPath = 0; //uiOscConfigurePath = 0; //uiOscProgramPath = 0; //uiOscPath = 0; //guiPid = -1; //guiQProc = 0; audioInBuffers = 0; audioOutBuffers = 0; } //--------------------------------------------------------- // ~DssiSynthIF //--------------------------------------------------------- DssiSynthIF::~DssiSynthIF() { #ifdef DSSI_DEBUG printf("DssiSynthIF::~DssiSynthIF\n"); #endif if(synth) { #ifdef DSSI_DEBUG printf("DssiSynthIF::~DssiSynthIF synth:%p\n", synth); #endif if(synth->dssi) { #ifdef DSSI_DEBUG printf("DssiSynthIF::~DssiSynthIF synth->dssi:%p\n", synth->dssi); #endif if(synth->dssi->LADSPA_Plugin) { #ifdef DSSI_DEBUG printf("DssiSynthIF::~DssiSynthIFsynth->dssi->LADSPA_Plugin:%p\n", synth->dssi->LADSPA_Plugin); #endif } } } if(synth && synth->dssi && synth->dssi->LADSPA_Plugin) { const DSSI_Descriptor* dssi = synth->dssi; const LADSPA_Descriptor* descr = dssi->LADSPA_Plugin; #ifdef DSSI_DEBUG printf("DssiSynthIF::~DssiSynthIF checking cleanup function exists\n"); #endif if(descr->cleanup) { #ifdef DSSI_DEBUG printf("DssiSynthIF::~DssiSynthIF calling cleanup function\n"); #endif descr->cleanup(handle); } } /* //if (guiPid != -1) // kill(guiPid, SIGHUP); if(guiQProc) { if(guiQProc->isRunning()) { #ifdef DSSI_DEBUG printf("DssiSynthIF::~DssiSynthIF killing guiQProc\n"); #endif guiQProc->kill(); } //delete guiQProc; } if(uiOscShowPath) free(uiOscShowPath); if(uiOscControlPath) free(uiOscControlPath); if(uiOscConfigurePath) free(uiOscConfigurePath); if(uiOscProgramPath) free(uiOscProgramPath); if(uiOscPath) free(uiOscPath); if(uiTarget) lo_address_free(uiTarget); */ if(audioInBuffers) { //for(int i = 0; i < synth->_inports; ++i) //{ // if(audioInBuffers[i]) // delete[] audioInBuffers[i]; //} for(unsigned long i = 0; i < synth->_inports; ++i) { if(audioInBuffers[i]) free(audioInBuffers[i]); } delete[] audioInBuffers; } if(audioOutBuffers) { //for(int i = 0; i < synth->_outports; ++i) //{ // if(audioOutBuffers[i]) // delete[] audioOutBuffers[i]; //} for(unsigned long i = 0; i < synth->_outports; ++i) { if(audioOutBuffers[i]) free(audioOutBuffers[i]); } delete[] audioOutBuffers; } if(controls) delete[] controls; if(controlsOut) delete[] controlsOut; } //--------------------------------------------------------- // getParameter //--------------------------------------------------------- float DssiSynthIF::getParameter(unsigned long n) const { if(n >= synth->_controlInPorts) { printf("DssiSynthIF::getParameter param number %ld out of range of ports:%ld\n", n, synth->_controlInPorts); return 0.0; } if(!controls) return 0.0; return controls[n].val; } //--------------------------------------------------------- // setParameter //--------------------------------------------------------- void DssiSynthIF::setParameter(unsigned long n, float v) { if(n >= synth->_controlInPorts) { printf("DssiSynthIF::setParameter param number %ld out of range of ports:%ld\n", n, synth->_controlInPorts); return; } if(!controls) return; controls[n].val = v; // Notify that changes are to be sent upon heartbeat. // TODO: No, at least not for now. So far, setParameter is only called during loading of stored params, // and we don't want this interfering with oscUpdate which also sends the values. //synti->_guiUpdateControls[n] = true; } //--------------------------------------------------------- // write //--------------------------------------------------------- //void DssiSynthIF::write(Xml&) const void DssiSynthIF::write(int level, Xml& xml) const { //bool vstsaved = false; #ifdef DSSI_VST_CHUNK_SUPPORT //--------------------------------------------- // dump current state of synth //--------------------------------------------- printf("dumping DSSI custom data! %d\n", synth->dssi->getCustomData); // this is only needed and supported if // we are talking to a VST plugin at the other end. std::string name = synth->dssi->LADSPA_Plugin->Name; if ((name.length()> 4) && name.substr(name.length() - 4) == " VST") { printf("is vst plugin, commencing data dump, apiversion=%d!\n", synth->dssi->DSSI_API_Version); unsigned long len = 0; void* p = 0; synth->dssi->getCustomData(handle,&p, &len); if (len) { xml.tag(level++, "midistate"); xml.nput(level++, "\n", len+7 /*VSTSAVE*/); xml.nput(level, ""); xml.nput("56 53 54 53 41 56 45 "); // embed a save marker "string 'VSTSAVE' for (long unsigned int i = 0; i < len; ++i) { if (i && (((i+7) % 16) == 0)) { xml.nput("\n"); xml.nput(level, ""); } xml.nput("%02x ", ((char*)(p))[i] & 0xff); } xml.nput("\n"); xml.tag(level--, "/event"); xml.etag(level--, "midistate"); //vstsaved = true; } } #else printf("support for vst chunks not compiled in!\n"); #endif /* // p3.3.39 Store the state of current program and bank and all input control values, but only if VSTSAVE above didn't do it already! // TODO: Not quite good enough, we would want to store all controls for EACH program, not just the current one. // Need to modify controls array to be inside a program array and act as a cache when the user changes a control on a particular program. if(!vstsaved) { if(synth->_controlInPorts) { // TODO: Hmm, what if these sizes change (platform etc.)? Hard code? Not good - need to store complete value. const int fs = sizeof(float); const int uls = sizeof(unsigned long); // Data length: Version major and minor bytes, bank + program, and controllers. const unsigned long len = 2 + 2 * uls + synth->_controlInPorts * fs; unsigned long prog = _curBank; unsigned long bnk = _curProgram; xml.tag(level++, "midistate"); xml.nput(level++, "\n", len+9); // "PARAMSAVE" length + data length. xml.nput(level, ""); xml.nput("50 41 52 41 4d 53 41 56 45 "); // Embed a save marker string "PARAMSAVE". unsigned long i = 9; // Store PARAMSAVE version major... char uc = DSSI_PARAMSAVE_VERSION_MAJOR; if(i && ((i % 16) == 0)) { xml.nput("\n"); xml.nput(level, ""); } xml.nput("%02x ", uc & 0xff); ++i; // Store PARAMSAVE version minor... uc = DSSI_PARAMSAVE_VERSION_MINOR; if(i && ((i % 16) == 0)) { xml.nput("\n"); xml.nput(level, ""); } xml.nput("%02x ", uc & 0xff); ++i; // Store bank... void* p = &bnk; for(int j = 0; j < uls; ++j) { if(i && ((i % 16) == 0)) { xml.nput("\n"); xml.nput(level, ""); } xml.nput("%02x ", ((char*)(p))[j] & 0xff); ++i; } // Store program... p = &prog; for(int j = 0; j < uls; ++j) { if(i && ((i % 16) == 0)) { xml.nput("\n"); xml.nput(level, ""); } xml.nput("%02x ", ((char*)(p))[j] & 0xff); ++i; } // Store controls... for(unsigned long c = 0; c < synth->_controlInPorts; ++c) { float v = controls[c].val; p = &v; for(int j = 0; j < fs; ++j) { if(i && ((i % 16) == 0)) { xml.nput("\n"); xml.nput(level, ""); } xml.nput("%02x ", ((char*)(p))[j] & 0xff); ++i; } } xml.nput("\n"); xml.tag(level--, "/event"); xml.etag(level--, "midistate"); } } */ // Store controls as parameters... for(unsigned long c = 0; c < synth->_controlInPorts; ++c) { float f = controls[c].val; xml.floatTag(level, "param", f); //xml.tag(level, "param name=\"%s\" val=\"%s\"/", name, r->first.c_str(), r->second.c_str()); } } //--------------------------------------------------------- // preProcessAlways //--------------------------------------------------------- void DssiSynthIF::preProcessAlways() { } //--------------------------------------------------------- // processEvent // Return true if event pointer filled. //-------------------------------------------------------- bool DssiSynthIF::processEvent(const MidiPlayEvent& e, snd_seq_event_t* event) { const DSSI_Descriptor* dssi = synth->dssi; int chn = e.channel(); int a = e.dataA(); int b = e.dataB(); //for sysex //QByteArray ba = QByteArray((const char*)e.data(), e.len()); //we must had 0xF0 at the beginning and 0xF7 at the end of e.data() //ba.push_front(0xF0); //ba.push_back(0xF7); //QByteArray ba(); ////ba.assign((const char*)e.data(), e.len()); ////ba.duplicate((const char*)e.data(), e.len()); ////ba.setRawData((const char*)e.data(), e.len()); //int len = e.len() + 2; int len = e.len(); char ca[len + 2]; ca[0] = 0xF0; memcpy(ca + 1, (const char*)e.data(), len); ca[len + 1] = 0xF7; len += 2; //snd_seq_event_t* event = &events[nevents]; event->queue = SND_SEQ_QUEUE_DIRECT; #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event type:%d chn:%d a:%d b:%d\n", e.type(), chn, a, b); #endif switch(e.type()) { case ME_NOTEON: #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is ME_NOTEON\n"); #endif if(b) snd_seq_ev_set_noteon(event, chn, a, b); else snd_seq_ev_set_noteoff(event, chn, a, 0); break; case ME_NOTEOFF: snd_seq_ev_set_noteoff(event, chn, a, 0); break; case ME_PROGRAM: { #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is ME_PROGRAM\n"); #endif int bank = (a >> 8) & 0xff; int prog = a & 0xff; //_curBank = bank; //_curProgram = prog; synti->_curBankH = 0; synti->_curBankL = bank; synti->_curProgram = prog; if(dssi->select_program) { dssi->select_program(handle, bank, prog); // Notify that changes are to be sent upon heartbeat. synti->_guiUpdateProgram = true; } // Event pointer not filled. Return false. return false; } break; case ME_CONTROLLER: { #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is ME_CONTROLLER\n"); #endif if((a == 0) || (a == 32)) return false; if(a == CTRL_PROGRAM) { #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is ME_CONTROLLER, dataA is CTRL_PROGRAM\n"); #endif int bank = (b >> 8) & 0xff; int prog = b & 0xff; //_curBank = bank; //_curProgram = prog; synti->_curBankH = 0; synti->_curBankL = bank; synti->_curProgram = prog; if(dssi->select_program) { dssi->select_program(handle, bank, prog); // Notify that changes are to be sent upon heartbeat. synti->_guiUpdateProgram = true; } // Event pointer not filled. Return false. return false; } if(a == CTRL_PITCH) { #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is ME_CONTROLLER, dataA is CTRL_PITCH\n"); #endif b &= 0x3fff; snd_seq_ev_set_pitchbend(event, chn, b); // Event pointer filled. Return true. return true; } const LADSPA_Descriptor* ld = dssi->LADSPA_Plugin; ciMidiCtl2LadspaPort ip = synth->midiCtl2PortMap.find(a); // Is it just a regular midi controller, not mapped to a LADSPA port (either by the plugin or by us)? // NOTE: There's no way to tell which of these controllers is supported by the plugin. // For example sustain footpedal or pitch bend may be supported, but not mapped to any LADSPA port. if(ip == synth->midiCtl2PortMap.end()) { // p3.3.39 Changed to return false because of crashes with unknown controllers when switching a midi track // among different dssi synths and regular synths etc. For example high RPN offset numbers (set by another // device selected into the midi port before selecting this synth) were passing through here when in fact // the particular synth had no such midi controllers. // ========================== No, that leaves out regular controllers like footpedal //#ifdef DSSI_DEBUG //fprintf(stderr, "DssiSynthIF::processEvent dataA:%d not found in map (not a ladspa controller). Ignoring.\n", a); //#endif //return false; //#ifdef DSSI_DEBUG //fprintf(stderr, "DssiSynthIF::processEvent dataA:%d not found in map (not a ladspa controller). Filling event as regular controller.\n", a); //#endif //snd_seq_ev_set_controller(event, chn, a, b); //return true; int ctlnum = a; //switch(midiControllerType(a)) if(midiControllerType(a) != MidiController::Controller7) return false; else { /* case MidiController::NRPN14: case MidiController::Controller14: case MidiController::Pitch: case MidiController::Program: case MidiController::RPN: case MidiController::RPN14: case MidiController::NRPN: #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent non-ladspa midi event controller unsupported. DataA:%d\n", a); #endif return false; */ //case MidiController::Controller7: #ifdef DSSI_DEBUG //fprintf(stderr, "DssiSynthIF::processEvent midi event is Controller7. Changing to DSSI_CC type. Current dataA:%d\n", a); fprintf(stderr, "DssiSynthIF::processEvent non-ladspa midi event is Controller7. Current dataA:%d\n", a); #endif //a = DSSI_CC(a); a &= 0x7f; ctlnum = DSSI_CC_NUMBER(ctlnum); // break; /* case MidiController::NRPN14: #ifdef DSSI_DEBUG // fprintf(stderr, "DssiSynthIF::processEvent midi event is NRPN. Changing to DSSI_NRPN type. Current dataA:%d\n", a); fprintf(stderr, "DssiSynthIF::processEvent non-ladspa midi event is NRPN. Current dataA:%d\n", a); #endif //a = DSSI_NRPN(a - CTRL_NRPN14_OFFSET); a &= 0x3fff; ctlnum = DSSI_NRPN_NUMBER(ctlnum); break; case MidiController::Controller14: a &= 0x7f; break; case MidiController::Pitch: // Should be caught above! #ifdef DSSI_DEBUG //fprintf(stderr, "DssiSynthIF::processEvent non-ladspa midi event is Pitch. DataA:%d\n", a); fprintf(stderr, "DssiSynthIF::processEvent Error! non-ladspa midi event is Pitch. Should have been caught already! DataA:%d\n", a); #endif //a &= 0x3fff; //snd_seq_ev_set_pitchbend(event, chn, b); // Event pointer filled. Return true. //return true; // Event pointer not filled. Return false. return false; case MidiController::Program: // Should be caught above! #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent Error! non-ladspa midi event is Program. Should have been caught already! DataA:%d\n", a); #endif return false; case MidiController::RPN: case MidiController::RPN14: case MidiController::NRPN: default: #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent non-ladspa midi event is RPN, RPN14, or NRPN type. DataA:%d\n", a); #endif break; */ } // Verify it's the same number. //if(ctlnum != a) //{ // #ifdef DSSI_DEBUG // printf("DssiSynthIF::processEvent Error! non-ladspa midi ctlnum:%d != event dataA:%d\n", ctlnum, a); // #endif // Event not filled. Return false. // TEMP: TODO: Turn on later //return false; //} // Fill the event. #ifdef DSSI_DEBUG printf("DssiSynthIF::processEvent non-ladspa filling midi event chn:%d dataA:%d dataB:%d\n", chn, a, b); #endif snd_seq_ev_set_controller(event, chn, a, b); return true; } //int num = ip->first; unsigned long k = ip->second; unsigned long i = synth->pIdx[k]; int ctlnum = DSSI_NONE; if(dssi->get_midi_controller_for_port) ctlnum = dssi->get_midi_controller_for_port(handle, i); // No midi controller for the ladspa port? Send to ladspa control. if(ctlnum == DSSI_NONE) { // Sanity check. if(k > synth->_controlInPorts) return false; // TODO: If necessary... choose non-existing numbers... //for(int k = 0; k < controlPorts; ++k) //{ // int i = synth->pIdx[k]; //} // Simple but flawed solution: Start them at 0x60000 + 0x2000 = 0x62000. Max NRPN number is 0x3fff. ctlnum = k + (CTRL_NRPN14_OFFSET + 0x2000); } // p3.3.39 else { #ifdef DSSI_DEBUG printf("DssiSynthIF::processEvent plugin requests DSSI-style ctlnum:%x(h) %d(d) be mapped to control port:%ld...\n", ctlnum, ctlnum, i); #endif int c = ctlnum; // Can be both CC and NRPN! Prefer CC over NRPN. if(DSSI_IS_CC(ctlnum)) { ctlnum = DSSI_CC_NUMBER(c); #ifdef DSSI_DEBUG printf("DssiSynthIF::processEvent is CC ctlnum:%d\n", ctlnum); #endif #ifdef DSSI_DEBUG if(DSSI_IS_NRPN(ctlnum)) printf("DssiSynthIF::processEvent is also NRPN control. Using CC.\n"); #endif } else if(DSSI_IS_NRPN(ctlnum)) { ctlnum = DSSI_NRPN_NUMBER(c) + CTRL_NRPN14_OFFSET; #ifdef DSSI_DEBUG printf("DssiSynthIF::processEvent is NRPN ctlnum:%x(h) %d(d)\n", ctlnum, ctlnum); #endif } } //{ float val = midi2LadspaValue(ld, i, ctlnum, b); #ifdef DSSI_DEBUG //fprintf(stderr, "DssiSynthIF::processEvent No midi controller for control port:%d port:%d dataA:%d Converting val from:%d to ladspa:%f\n", i, k, a, b, val); fprintf(stderr, "DssiSynthIF::processEvent control port:%ld port:%ld dataA:%d Converting val from:%d to ladspa:%f\n", i, k, a, b, val); #endif // Set the ladspa port value. controls[k].val = val; // FIXME: Testing - Works but is this safe in a RT process callback? Try hooking into gui heartbeat timer instead... //lo_send(uiTarget, uiOscControlPath, "if", i, val); // Notify that changes are to be sent upon heartbeat. synti->_guiUpdateControls[k] = true; // Since we absorbed the message as a ladspa control change, return false - the event is not filled. return false; //} // p3.3.39 Removed. // "Hosts should not deliver through run_synth any MIDI controller events that have already // been mapped to control port values." // D'oh! My mistake, did not understand that the mapping is only a *request* that the app map MIDI // controller events to a LADSPA port, and must do the conversion, not to actually *send* them via MIDI... /* else { switch(midiControllerType(a)) { case MidiController::Controller7: #ifdef DSSI_DEBUG //fprintf(stderr, "DssiSynthIF::processEvent midi event is Controller7. Changing to DSSI_CC type. Current dataA:%d\n", a); fprintf(stderr, "DssiSynthIF::processEvent midi event is Controller7. Current dataA:%d\n", a); #endif //a = DSSI_CC(a); a &= 0x7f; ctlnum = DSSI_CC_NUMBER(ctlnum); break; case MidiController::NRPN14: #ifdef DSSI_DEBUG // fprintf(stderr, "DssiSynthIF::processEvent midi event is NRPN. Changing to DSSI_NRPN type. Current dataA:%d\n", a); fprintf(stderr, "DssiSynthIF::processEvent midi event is NRPN. Current dataA:%d\n", a); #endif //a = DSSI_NRPN(a - CTRL_NRPN14_OFFSET); a &= 0x3fff; ctlnum = DSSI_NRPN_NUMBER(ctlnum); break; case MidiController::Controller14: a &= 0x7f; break; case MidiController::Pitch: #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is Pitch. DataA:%d\n", a); #endif a &= 0x3fff; break; case MidiController::Program: #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is Program. DataA:%d\n", a); #endif a &= 0x3fff; break; case MidiController::RPN: case MidiController::RPN14: case MidiController::NRPN: default: #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is RPN, RPN14, or NRPN type. DataA:%d\n", a); #endif break; } // Verify it's the same number. if(ctlnum != a) { #ifdef DSSI_DEBUG printf("DssiSynthIF::processEvent Error! ctlnum:%d != event dataA:%d\n", ctlnum, a); #endif // Event not filled. Return false. // TEMP: TODO: Turn on later //return false; } // Fill the event. // FIXME: Darn! We get to this point, but no change in sound (later). Nothing happens, at least with LTS - // which is the only one I found so far with midi controllers. // Tried with/without converting to DSSI_CC and DSSI_NRPN. What could be wrong here? #ifdef DSSI_DEBUG printf("DssiSynthIF::processEvent filling event chn:%d dataA:%d dataB:%d\n", chn, a, b); #endif snd_seq_ev_set_controller(event, chn, a, b); } */ } break; case ME_PITCHBEND: snd_seq_ev_set_pitchbend(event, chn, a); break; case ME_AFTERTOUCH: snd_seq_ev_set_chanpress(event, chn, a); break; case ME_SYSEX: #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is ME_SYSEX\n"); #endif if (QString((const char*)e.data()).startsWith("VSTSAVE")) { #ifdef DSSI_VST_CHUNK_SUPPORT printf("loading chunk from sysex %s!\n", e.data()+7); dssi->setCustomData(handle, e.data()+7 /* len of str*/,e.len()-7); #else printf("support for vst chunks not compiled in!\n"); #endif // Event not filled. return false; } /* // p3.3.39 Read the state of current bank and program and all input control values. // TODO: Needs to be better. See write(). else if (QString((const char*)e.data()).startsWith("PARAMSAVE")) { #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::processEvent midi event is ME_SYSEX PARAMSAVE\n"); #endif unsigned long dlen = e.len() - 9; // Minus "PARAMSAVE" if(dlen > 0) { //if(dlen < 2 * sizeof(unsigned long)) if(dlen < (2 + 2 * sizeof(unsigned long))) // Version major and minor bytes, bank and program. printf("DssiSynthIF::processEvent Error: PARAMSAVE data length does not include at least version major and minor, bank and program!\n"); else { // Not required, yet. //char vmaj = *((char*)(e.data() + 9)); // After "PARAMSAVE" //char vmin = *((char*)(e.data() + 10)); unsigned long* const ulp = (unsigned long*)(e.data() + 11); // After "PARAMSAVE" + version major and minor. // TODO: TODO: Set plugin bank and program. _curBank = ulp[0]; _curProgram = ulp[1]; dlen -= (2 + 2 * sizeof(unsigned long)); // After the version major and minor, bank and program. if(dlen > 0) { if((dlen % sizeof(float)) != 0) printf("DssiSynthIF::processEvent Error: PARAMSAVE float data length not integral multiple of float size!\n"); else { const unsigned long n = dlen / sizeof(float); if(n != synth->_controlInPorts) printf("DssiSynthIF::processEvent Warning: PARAMSAVE number of floats:%ld != number of controls:%ld\n", n, synth->_controlInPorts); // Point to location after "PARAMSAVE", version major and minor, bank and progam. float* const fp = (float*)(e.data() + 9 + 2 + 2 * sizeof(unsigned long)); for(unsigned long i = 0; i < synth->_controlInPorts && i < n; ++i) { const float v = fp[i]; controls[i].val = v; } } } } } // Event not filled. return false; } */ else { // NOTE: There is a limit on the size of a sysex. Got this: // "DssiSynthIF::processEvent midi event is ME_SYSEX" // "WARNING: MIDI event of type ? decoded to 367 bytes, discarding" // That might be ALSA doing that. snd_seq_ev_set_sysex(event, len, //(unsigned char*)ba.data()); (unsigned char*)ca); } break; default: if(debugMsg) fprintf(stderr, "DssiSynthIF::processEvent midi event unknown type:%d\n", e.type()); // Event not filled. return false; break; } return true; } //--------------------------------------------------------- // getData //--------------------------------------------------------- //void DssiSynthIF::getData(MidiEventList* el, unsigned pos, int ch, unsigned samples, float** data) iMPEvent DssiSynthIF::getData(MidiPort* /*mp*/, MPEventList* el, iMPEvent /*i*/, unsigned pos, int ports, unsigned n, float** buffer) { //#ifdef DSSI_DEBUG // fprintf(stderr, "DssiSynthIF::getData elsize:%d pos:%d ports:%d samples:%d processed already?:%d\n", el->size(), pos, ports, n, synti->processed()); //#endif //BEGIN: Process midi events // FIXME: Add 10(?) for good luck in case volatile size changes (increments) while we're processing. //unsigned long nevents = el->size(); unsigned long nevents = el->size() + synti->putFifo.getSize() + 10; /* while (!synti->putFifo.isEmpty()) { MidiEvent event = synti->putFifo.get(); printf("Dssi: FIFO\n"); } */ snd_seq_event_t events[nevents]; memset(events, 0, sizeof(events)); nevents = 0; //int curPos = pos; //unsigned endPos = pos + samples; unsigned endPos = pos + n; //int off = pos; int frameOffset = audio->getFrameOffset(); //iMidiEvent i = el->begin(); iMPEvent i = el->begin(); // Process event list events... for(; i != el->end(); ++i) { //if(i->time() >= endPos) // Doesn't work, at least here in muse-1. The event times are all // just slightly after the endPos, EVEN IF transport is stopped. // So it misses all the notes. if(i->time() >= (endPos + frameOffset)) // NOTE: frameOffset? Tested, examined printouts of times: Seems OK for playback. break; #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::getData eventlist event time:%d\n", i->time()); #endif // p3.3.39 Update hardware state so knobs and boxes are updated. Optimize to avoid re-setting existing values. // Same code as in MidiPort::sendEvent() if(synti->midiPort() != -1) { MidiPort* mp = &midiPorts[synti->midiPort()]; if(i->type() == ME_CONTROLLER) { int da = i->dataA(); int db = i->dataB(); db = mp->limitValToInstrCtlRange(da, db); if(!mp->setHwCtrlState(i->channel(), da, db)) continue; //mp->setHwCtrlState(i->channel(), da, db); } else if(i->type() == ME_PITCHBEND) { int da = mp->limitValToInstrCtlRange(CTRL_PITCH, i->dataA()); if(!mp->setHwCtrlState(i->channel(), CTRL_PITCH, da)) continue; //mp->setHwCtrlState(i->channel(), CTRL_PITCH, da); } else if(i->type() == ME_PROGRAM) { if(!mp->setHwCtrlState(i->channel(), CTRL_PROGRAM, i->dataA())) continue; //mp->setHwCtrlState(i->channel(), CTRL_PROGRAM, i->dataA()); } } if(processEvent(*i, &events[nevents])) ++nevents; } // Now process putEvent events... while(!synti->putFifo.isEmpty()) { MidiPlayEvent e = synti->putFifo.get(); #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::getData putFifo event time:%d\n", e.time()); #endif // Set to the current time. // FIXME: FIXME: Wrong - we should be setting some kind of linear realtime wallclock here, not song pos. e.setTime(pos); if(processEvent(e, &events[nevents])) ++nevents; } // Now process OSC gui input control fifo events. // It is probably more important that these are processed last so that they take precedence over all other // events because OSC + DSSI/DSSI-VST are fussy about receiving feedback via these control ports, from GUI changes. #ifdef OSC_SUPPORT unsigned long ctls = synth->_controlInPorts; for(unsigned long k = 0; k < ctls; ++k) { OscControlFifo* cfifo = _oscif.oscFifo(k); if(!cfifo) continue; // If there are 'events' in the fifo, get exactly one 'event' per control per process cycle... if(!cfifo->isEmpty()) { OscControlValue v = cfifo->get(); #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::getData OscControlFifo event input control number:%ld value:%f\n", k, v.value); #endif // Set the ladspa control port value. controls[k].val = v.value; // TODO: (From plugin module, adapt for synth if/when our own plugin gui is added to synths). // Need to update the automation value, otherwise the block above overwrites with the last automation value. ///if(_track) ///{ // Since we are now in the audio thread context, there's no need to send a message, // just modify directly. //audio->msgSetPluginCtrlVal(this, genACnum(_id, i), controls[i].val); /// _track->setPluginCtrlVal(k, v.value) ///} } } #endif /* // This is from MESS... Tried this here, didn't work, need to re-adapt, try again. int evTime = i->time(); if(evTime == 0) { printf("DssiSynthIF::getData - time is 0!\n"); //continue; evTime=frameOffset; // will cause frame to be zero, problem? } int frame = evTime - frameOffset; if(frame >= endPos) { printf("DssiSynthIF::getData frame > endPos!! frame = %d >= endPos %d, i->time() %d, frameOffset %d curPos=%d\n", frame, endPos, i->time(), frameOffset,curPos); continue; } if(frame > curPos) { if(frame < pos) printf("DssiSynthIF::getData should not happen: missed event %d\n", pos -frame); else { */ /* } curPos = frame; } */ // } el->erase(el->begin(), i); //END: Process midi events //BEGIN: Run the synth // All ports must be connected to something! // First, copy the given input buffers to our local input buffers. unsigned long np, k; //np = portsin > synth->_inports ? synth->_inports : portsin; //for(k = 0; k < np; ++k) // memcpy(audioInBuffers[k], inbuffer[k], sizeof(float) * n); //for(; k < portsin; ++k) // memset(audioInBuffers[k], 0, sizeof(float) * n); // Watch our limits. np = ports > synth->_outports ? synth->_outports : ports; const DSSI_Descriptor* dssi = synth->dssi; const LADSPA_Descriptor* descr = dssi->LADSPA_Plugin; k = 0; // Connect the given buffers directly to the ports, up to a max of synth ports. for(; k < np; ++k) descr->connect_port(handle, synth->oIdx[k], buffer[k]); // Connect the remaining ports to some local buffers (not used yet). for(; k < synth->_outports; ++k) descr->connect_port(handle, synth->oIdx[k], audioOutBuffers[k]); /* // // p3.3.39 Handle inputs... // //if((song->bounceTrack != this) && !noInRoute()) if(!((AudioTrack*)synti)->noInRoute()) { RouteList* irl = ((AudioTrack*)synti)->inRoutes(); iRoute i = irl->begin(); if(!i->track->isMidiTrack()) { //if(debugMsg) printf("DssiSynthIF::getData: Error: First route is a midi track route!\n"); } else { int ch = i->channel == -1 ? 0 : i->channel; int remch = i->remoteChannel == -1 ? 0 : i->remoteChannel; int chs = i->channels == -1 ? 0 : i->channels; // TODO: //if(ch >= synth->_inports) //iUsedIdx[ch] = true; //if(chs == 2) // iUsedIdx[ch + 1] = true; //((AudioTrack*)i->track)->copyData(framePos, channels, nframe, bp); ((AudioTrack*)i->track)->copyData(pos, ports, //(i->track->type() == Track::AUDIO_SOFTSYNTH && i->channel != -1) ? i->channel : 0, i->channel, i->channels, n, bp); } //unsigned pos, int ports, unsigned n, float** buffer ++i; for(; i != irl->end(); ++i) { if(i->track->isMidiTrack()) { //if(debugMsg) printf("DssiSynthIF::getData: Error: Route is a midi track route!\n"); continue; } //((AudioTrack*)i->track)->addData(framePos, channels, nframe, bp); ((AudioTrack*)i->track)->addData(framePos, channels, //(i->track->type() == Track::AUDIO_SOFTSYNTH && i->channel != -1) ? i->channel : 0, i->channel, i->channels, nframe, bp); } } */ //#ifdef DSSI_DEBUG //if(nevents) // fprintf(stderr, "DssiSynthIF::getData run nevents:%d\n", nevents); //#endif // Run the synth for one segment. This processes events and gets/fills our local buffers... if(synth->dssi->run_synth) { synth->dssi->run_synth(handle, n, events, nevents); // NOTE: Just a test //for(int m = 0; m < n; ++m) //{ // synth->dssi->run_synth(handle, 1, events, nevents); //} } else if (synth->dssi->run_multiple_synths) { snd_seq_event_t* ev = events; synth->dssi->run_multiple_synths(1, &handle, n, &ev, &nevents); } //END: Run the synth return i; } //--------------------------------------------------------- // putEvent //--------------------------------------------------------- //bool DssiSynthIF::putEvent(const MidiEvent& ev) bool DssiSynthIF::putEvent(const MidiPlayEvent& ev) { #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::putEvent midi event time:%d chn:%d a:%d b:%d\n", ev.time(), ev.channel(), ev.dataA(), ev.dataB()); #endif if (midiOutputTrace) ev.dump(); return synti->putFifo.put(ev); //return false; } //--------------------------------------------------------- // incInstances //--------------------------------------------------------- void DssiSynth::incInstances(int val) { _instances += val; if (_instances == 0) { if (handle) { #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynth::incInstances no more instances, closing library\n"); #endif dlclose(handle); } handle = 0; dssi = NULL; df = NULL; pIdx.clear(); opIdx.clear(); iIdx.clear(); oIdx.clear(); rpIdx.clear(); iUsedIdx.clear(); midiCtl2PortMap.clear(); port2MidiCtlMap.clear(); //synti->_guiUpdateControls.clear(); } } //--------------------------------------------------------- // initGui //--------------------------------------------------------- bool DssiSynthIF::initGui() { #ifdef OSC_SUPPORT return _oscif.oscInitGui(); #endif return true; /* // Are we already running? We don't want to allow another process do we... if((guiQProc != 0) && (guiQProc->isRunning())) return true; // // start gui // static char oscUrl[1024]; //snprintf(oscUrl, 1024, "%s/%s", url, synti->name().toAscii().data()); //snprintf(oscUrl, 1024, "%s/%s", url, synti->name().toLatin1().constData()); snprintf(oscUrl, 1024, "%s/%s/%s", url, synth->info.baseName().toLatin1().constData(), synti->name().toLatin1().constData()); //QString guiPath(info.path() + "/" + info.baseName()); QString guiPath(synth->info.dirPath() + "/" + synth->info.baseName()); QDir guiDir(guiPath, "*", QDir::Unsorted, QDir::Files); if (guiDir.exists()) { //const QFileInfoList list = guiDir.entryInfoList(); QStringList list = guiDir.entryList(); //for (int i = 0; i < list.size(); ++i) { for (unsigned int i = 0; i < list.count(); ++i) { //QFileInfo fi = list.at(i); QFileInfo fi(guiPath + QString("/") + list[i]); QString gui(fi.filePath()); if (gui.contains('_') == 0) continue; struct stat buf; //if (stat(gui.toAscii().data(), &buf)) { if (stat(gui.toLatin1().constData(), &buf)) { perror("stat failed"); continue; } #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::initGui %s %s %s %s\n", //fi.filePath().toAscii().data(), //fi.fileName().toAscii().data(), fi.filePath().toLatin1().constData(), //fi.fileName().toLatin1().constData(), oscUrl, synth->info.filePath().toLatin1().constData(), //name().toAscii().data(), synth->name().toLatin1().constData()); #endif if ((S_ISREG(buf.st_mode) || S_ISLNK(buf.st_mode)) && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { // Changed by T356. // fork + execlp were causing the processes to remain after closing gui, requiring manual kill. // Changed to QProcess, works OK now. //if((guiPid = fork()) == 0) { // No QProcess created yet? Do it now. Only once per SynthIF instance. Exists until parent destroyed. if(guiQProc == 0) guiQProc = new QProcess(muse); // Don't forget this, he he... guiQProc->clearArguments(); guiQProc->addArgument(fi.filePath()); //guiQProc->addArgument(fi.fileName()); // No conventional 'Arg0' here. guiQProc->addArgument(QString(oscUrl)); guiQProc->addArgument(synth->info.filePath()); guiQProc->addArgument(synth->name()); guiQProc->addArgument(QString("channel 1")); #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::initGui starting QProcess\n"); #endif if(guiQProc->start() == TRUE) { #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::initGui started QProcess\n"); #endif //guiPid = guiQProc->processIdentifier(); } else { // execlp( // fi.filePath().toAscii().data(), // fi.fileName().toAscii().data(), // fi.filePath().toLatin1().constData(), // fi.fileName().toLatin1().constData(), // oscUrl, // info.filePath().toAscii().data(), // name().toAscii().data(), // synth->info.filePath().toLatin1().constData(), // synth->name().toLatin1().constData(), // "channel 1", (void*)0); fprintf(stderr, "exec %s %s %s %s failed: %s\n", // fi.filePath().toAscii().data(), // fi.fileName().toAscii().data(), fi.filePath().toLatin1().constData(), fi.fileName().toLatin1().constData(), oscUrl, // name().toAscii().data(), synth->name().toLatin1().constData(), strerror(errno)); // It's Ok, Keep going. So nothing happens. So what. The timeout in showGui will just leave. // Maybe it's a 'busy' issue somewhere - allow to try again later + save work now. // exit(1); } #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::initGui after QProcess\n"); #endif } } } //synth->_hasGui = true; } else { printf("%s: no dir for dssi gui found: %s\n", //name().toAscii().data(), guiPath.toAscii().data()); synth->name().toLatin1().constData(), guiPath.toLatin1().constData()); //synth->_hasGui = false; } return true; */ } //--------------------------------------------------------- // guiHeartBeat //--------------------------------------------------------- void DssiSynthIF::guiHeartBeat() { #ifdef OSC_SUPPORT // Update the gui's program if needed. if(synti->_guiUpdateProgram) { _oscif.oscSendProgram(synti->_curProgram, synti->_curBankL); synti->_guiUpdateProgram = false; } // Update the gui's controls if needed. unsigned long ports = synth->_controlInPorts; if(ports > synti->_guiUpdateControls.size()) return; for(unsigned long i = 0; i < ports; ++i) { if(synti->_guiUpdateControls[i]) { unsigned long k = synth->pIdx[i]; _oscif.oscSendControl(k, controls[i].val); // Reset. synti->_guiUpdateControls[i] = false; } } #endif } #ifdef OSC_SUPPORT //--------------------------------------------------------- // oscUpdate //--------------------------------------------------------- int DssiSynthIF::oscUpdate() { // Send project directory. _oscif.oscSendConfigure(DSSI_PROJECT_DIRECTORY_KEY, museProject.toLatin1().constData()); // song->projectPath() // Send current string configuration parameters. //StringParamMap& map = synti->_stringParamMap; int i = 0; for(ciStringParamMap r = synti->_stringParamMap.begin(); r != synti->_stringParamMap.end(); ++r) { _oscif.oscSendConfigure(r->first.c_str(), r->second.c_str()); // Avoid overloading the GUI if there are lots and lots of params. if((i+1) % 50 == 0) usleep(300000); ++i; } // Send current bank and program. //unsigned long bank, prog; //synti->currentProg(&prog, &bank, 0); //_oscif.oscSendProgram(prog, bank); _oscif.oscSendProgram(synti->_curProgram, synti->_curBankL); // Send current control values. unsigned long ports = synth->_controlInPorts; for(unsigned long i = 0; i < ports; ++i) { unsigned long k = synth->pIdx[i]; _oscif.oscSendControl(k, controls[i].val); // Avoid overloading the GUI if there are lots and lots of ports. if((i+1) % 50 == 0) usleep(300000); } #if 0 /* Send current bank/program (-FIX- another race...) */ if (instance->pendingProgramChange < 0) { unsigned long bank = instance->currentBank; unsigned long program = instance->currentProgram; instance->uiNeedsProgramUpdate = 0; if (instance->uiTarget) { lo_send(instance->uiTarget, instance->ui_osc_program_path, "ii", bank, program); } } /* Send control ports */ for (i = 0; i < instance->plugin->controlIns; i++) { int in = i + instance->firstControlIn; int port = pluginControlInPortNumbers[in]; lo_send(instance->uiTarget, instance->ui_osc_control_path, "if", port, pluginControlIns[in]); /* Avoid overloading the GUI if there are lots and lots of ports */ if ((i+1) % 50 == 0) usleep(300000); } #endif return 0; } //--------------------------------------------------------- // oscProgram //--------------------------------------------------------- int DssiSynthIF::oscProgram(unsigned long program, unsigned long bank) { //int bank = argv[0]->i; //int program = argv[1]->i; int ch = 0; // TODO: ?? int port = synti->midiPort(); //_curBank = bank; //_curProgram = program; synti->_curBankH = 0; synti->_curBankL = bank; synti->_curProgram = program; bank &= 0xff; program &= 0xff; //MidiEvent event(0, ch, ME_CONTROLLER, CTRL_PROGRAM, (bank << 8) + program); if(port != -1) { //MidiPlayEvent event(0, port, ch, ME_CONTROLLER, CTRL_PROGRAM, (bank << 8) + program); MidiPlayEvent event(0, port, ch, ME_PROGRAM, (bank << 8) + program, 0); #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::oscProgram midi event chn:%d a:%d b:%d\n", event.channel(), event.dataA(), event.dataB()); #endif midiPorts[port].sendEvent(event); } //synti->playMidiEvent(&event); // TODO // //MidiDevice* md = dynamic_cast(synti); //if(md) // md->putEvent(event); // //synti->putEvent(event); return 0; } //--------------------------------------------------------- // oscControl //--------------------------------------------------------- int DssiSynthIF::oscControl(unsigned long port, float value) { //int port = argv[0]->i; //LADSPA_Data value = argv[1]->f; #ifdef DSSI_DEBUG printf("DssiSynthIF::oscControl received oscControl port:%ld val:%f\n", port, value); #endif //int controlPorts = synth->_controlInPorts; //if(port >= controlPorts) //if(port < 0 || port >= synth->rpIdx.size()) if(port >= synth->rpIdx.size()) { //fprintf(stderr, "DssiSynthIF::oscControl: port number:%d is out of range of number of ports:%d\n", port, controlPorts); fprintf(stderr, "DssiSynthIF::oscControl: port number:%ld is out of range of index list size:%zd\n", port, synth->rpIdx.size()); return 0; } // Convert from DSSI port number to control input port index. unsigned long cport = synth->rpIdx[port]; if((int)cport == -1) { fprintf(stderr, "DssiSynthIF::oscControl: port number:%ld is not a control input\n", port); return 0; } // p3.3.39 Set the DSSI control input port's value. // Observations: With a native DSSI synth like LessTrivialSynth, the native GUI's controls do not change the sound at all // ie. they don't update the DSSI control port values themselves. // Hence in response to the call to this oscControl, sent by the native GUI, it is required to that here. /// controls[cport].val = value; // DSSI-VST synths however, unlike DSSI synths, DO change their OWN sound in response to their gui controls. // AND this function is called. // Despite the descrepency we are STILL required to update the DSSI control port values here // because dssi-vst is WAITING FOR A RESPONSE. (A CHANGE in the control port value). // It will output something like "...4 events expected..." and count that number down as 4 actual control port value CHANGES // are done here in response. Normally it says "...0 events expected..." when MusE is the one doing the DSSI control changes. // TODO: (Done) May need FIFOs on each control(!) so that the control changes get sent one per process cycle. // Observed countdown not actually going to zero upon string of changes. // // NOTE: NOTE: This line in RemoteVSTServer::setParameter(int p, float v) in dssi-vst-server.cpp : // // " if (tv.tv_sec > m_lastGuiComms.tv_sec + 10) " // // explains an observation that after ten seconds, the server automatically clears the expected number to 0. // TODO: Now MusE should forget about all the VST fifo events past ten+ (?) seconds. Add event timestamps... // You can't send any 'new' values until either you a): send all the expected events or b): wait ten seconds. // (Because the server simply ignores the 'expected' messages.) // // Well, at least here are the fifos. Try this ... OscControlFifo* cfifo = _oscif.oscFifo(cport); if(cfifo) { OscControlValue cv; //cv.idx = cport; cv.value = value; if(cfifo->put(cv)) { fprintf(stderr, "DssiSynthIF::oscControl: fifo overflow: in control number:%ld\n", cport); } } //const DSSI_Descriptor* dssi = synth->dssi; //const LADSPA_Descriptor* ld = dssi->LADSPA_Plugin; ciMidiCtl2LadspaPort ip = synth->port2MidiCtlMap.find(cport); if(ip != synth->port2MidiCtlMap.end()) { // TODO: TODO: Update midi MusE's midi controller knobs, sliders, boxes etc with a call to the midi port's setHwCtrlState() etc. // But first we need a ladspa2MidiValue() function! ... // // //float val = ladspa2MidiValue(ld, i, ?, ?); } #if 0 int port = argv[0]->i; LADSPA_Data value = argv[1]->f; if (port < 0 || port > instance->plugin->descriptor->LADSPA_Plugin->PortCount) { fprintf(stderr, "MusE: OSC: %s port number (%d) is out of range\n", instance->friendly_name, port); return 0; } if (instance->pluginPortControlInNumbers[port] == -1) { fprintf(stderr, "MusE: OSC: %s port %d is not a control in\n", instance->friendly_name, port); return 0; } pluginControlIns[instance->pluginPortControlInNumbers[port]] = value; if (verbose) { printf("MusE: OSC: %s port %d = %f\n", instance->friendly_name, port, value); } #endif return 0; } /* //--------------------------------------------------------- // oscExiting //--------------------------------------------------------- int DssiSynthIF::oscExiting(lo_arg**) { //printf("not impl.: oscExiting\n"); // The gui is gone now, right? _guiVisible = false; //const DSSI_Descriptor* dssi = synth->dssi; //const LADSPA_Descriptor* ld = dssi->LADSPA_Plugin; //if(ld->deactivate) // ld->deactivate(handle); if (uiOscPath == 0) { printf("DssiSynthIF::oscExiting(): no uiOscPath\n"); return 1; } char uiOscGuiPath[strlen(uiOscPath)+6]; sprintf(uiOscGuiPath, "%s/%s", uiOscPath, "quit"); #ifdef DSSI_DEBUG printf("DssiSynthIF::oscExiting(): sending quit to uiOscGuiPath:%s\n", uiOscGuiPath); #endif lo_send(uiTarget, uiOscGuiPath, ""); #if 0 int i; if (verbose) { printf("MusE: OSC: got exiting notification for instance %d\n", instance->number); } if (instance->plugin) { // !!! No, this isn't safe -- plugins deactivated in this way // would still be included in a run_multiple_synths call unless // we re-jigged the instance array at the same time -- leave it // for now //if (instance->plugin->descriptor->LADSPA_Plugin->deactivate) { // instance->plugin->descriptor->LADSPA_Plugin->deactivate // (instanceHandles[instance->number]); // } // Leave this flag though, as we need it to determine when to exit instance->inactive = 1; } // Do we have any plugins left running? for (i = 0; i < instance_count; ++i) { if (!instances[i].inactive) return 0; } if (verbose) { printf("MusE: That was the last remaining plugin, exiting...\n"); } exiting = 1; #endif return 0; } */ //--------------------------------------------------------- // oscMidi //--------------------------------------------------------- int DssiSynthIF::oscMidi(int a, int b, int c) { //int a = argv[0]->m[1]; //int b = argv[0]->m[2]; //int c = argv[0]->m[3]; if (a == ME_NOTEOFF) { a = ME_NOTEON; c = 0; } int channel = 0; // TODO: ?? int port = synti->midiPort(); //MidiEvent event(0, channel, a, b, c); if(port != -1) { MidiPlayEvent event(0, port, channel, a, b, c); #ifdef DSSI_DEBUG fprintf(stderr, "DssiSynthIF::oscMidi midi event chn:%d a:%d b:%d\n", event.channel(), event.dataA(), event.dataB()); #endif midiPorts[port].sendEvent(event); } //synti->playMidiEvent(&event); // TODO // //MidiDevice* md = dynamic_cast(synti); //if(md) // md->putEvent(event); // //synti->putEvent(event); // return 0; } //--------------------------------------------------------- // oscConfigure //--------------------------------------------------------- int DssiSynthIF::oscConfigure(const char *key, const char *value) { //const char *key = (const char *)&argv[0]->s; //const char *value = (const char *)&argv[1]->s; // This is pretty much the simplest legal implementation of // configure in a DSSI host. // The host has the option to remember the set of (key,value) // pairs associated with a particular instance, so that if it // wants to restore the "same" instance on another occasion it can // just call configure() on it for each of those pairs and so // restore state without any input from a GUI. Any real-world GUI // host will probably want to do that. This host doesn't have any // concept of restoring an instance from one run to the next, so // we don't bother remembering these at all. #ifdef DSSI_DEBUG printf("DssiSynthIF::oscConfigure synth name:%s key:%s value:%s\n", synti->name().toLatin1().constData(), key, value); #endif // Add or modify the configuration map item. synti->_stringParamMap.set(key, value); if (!strncmp(key, DSSI_RESERVED_CONFIGURE_PREFIX, strlen(DSSI_RESERVED_CONFIGURE_PREFIX))) { fprintf(stderr, "MusE: OSC: UI for plugin '%s' attempted to use reserved configure key \"%s\", ignoring\n", //synti->name().toAscii().data(), key); synti->name().toLatin1().constData(), key); return 0; } if (!synth->dssi->configure) return 0; char* message = synth->dssi->configure(handle, key, value); if (message) { printf("MusE: on configure '%s' '%s', plugin '%s' returned error '%s'\n", //key, value, synti->name().toAscii().data(), message); key, value, synti->name().toLatin1().constData(), message); free(message); } // also call back on UIs for plugins other than the one // that requested this: // if (n != instance->number && instances[n].uiTarget) { // lo_send(instances[n].uiTarget, // instances[n].ui_osc_configure_path, "ss", key, value); // } // configure invalidates bank and program information, so // we should do this again now: queryPrograms(); return 0; } #endif // OSC_SUPPORT //--------------------------------------------------------- // queryPrograms //--------------------------------------------------------- void DssiSynthIF::queryPrograms() { for (std::vector::const_iterator i = programs.begin(); i != programs.end(); ++i) { free((void*)(i->Name)); } programs.clear(); //if (!(synth->dssi->get_program && synth->dssi->select_program)) if (!synth->dssi->get_program) return; for (int i = 0;; ++i) { const DSSI_Program_Descriptor* pd = synth->dssi->get_program(handle, i); if (pd == 0) break; DSSI_Program_Descriptor d; d.Name = strdup(pd->Name); d.Program = pd->Program; d.Bank = pd->Bank; programs.push_back(d); } } //--------------------------------------------------------- // getPatchName //--------------------------------------------------------- //QString DssiSynthIF::getPatchName(int, int prog) const char* DssiSynthIF::getPatchName(int /*chan*/, int prog, MType /*type*/, bool /*drum*/) { unsigned program = prog & 0x7f; int lbank = (prog >> 8) & 0xff; int hbank = (prog >> 16) & 0xff; if (lbank == 0xff) lbank = 0; if (hbank == 0xff) hbank = 0; unsigned bank = (hbank << 8) + lbank; for (std::vector::const_iterator i = programs.begin(); i != programs.end(); ++i) { if (i->Bank == bank && i->Program ==program) return i->Name; } return "?"; } //--------------------------------------------------------- // populatePatchPopup //--------------------------------------------------------- //void DssiSynthIF::populatePatchPopup(QMenu* menu, int) void DssiSynthIF::populatePatchPopup(QMenu* menu, int /*ch*/, MType /*type*/, bool /*drum*/) { // The plugin can change the programs, patches etc. // So make sure we're up to date by calling queryPrograms. queryPrograms(); menu->clear(); for (std::vector::const_iterator i = programs.begin(); i != programs.end(); ++i) { int bank = i->Bank; int prog = i->Program; int id = (bank << 16) + prog; QAction *act = menu->addAction(QString(i->Name)); act->setData(id); } } int DssiSynthIF::getControllerInfo(int id, const char** name, int* ctrl, int* min, int* max, int* initval) { int controlPorts = synth->_controlInPorts; if(id >= controlPorts) //if(id >= midiCtl2PortMap.size()) return 0; const DSSI_Descriptor* dssi = synth->dssi; const LADSPA_Descriptor* ld = dssi->LADSPA_Plugin; // Hmm, has a weird [] operator. Would it work? // For now just use duplicate code found in ::init() //iMidiCtl2LadspaPort ip = midiCtl2PortMap[id]; //int ctlnum = ip->first; //int k = ip->second; int i = synth->pIdx[id]; //int i = synth->pIdx[k]; //ladspaDefaultValue(ld, i, &controls[id].val); #ifdef DSSI_DEBUG printf("DssiSynthIF::getControllerInfo control port:%d port idx:%d name:%s\n", id, i, ld->PortNames[i]); #endif int ctlnum = DSSI_NONE; if(dssi->get_midi_controller_for_port) ctlnum = dssi->get_midi_controller_for_port(handle, i); // No controller number? Give it one. if(ctlnum == DSSI_NONE) { // TODO: If neccesary... choose non-existing numbers... //for(int k = 0; k < controlPorts; ++k) //{ // int i = synth->pIdx[k]; //} // Simple but flawed solution: Start them at 0x60000 + 0x2000 = 0x62000. Max NRPN number is 0x3fff. ctlnum = CTRL_NRPN14_OFFSET + 0x2000 + id; } else { #ifdef DSSI_DEBUG printf("DssiSynthIF::getControllerInfo ctlnum:%d\n", ctlnum); #endif int c = ctlnum; // Can be both CC and NRPN! Prefer CC over NRPN. if(DSSI_IS_CC(ctlnum)) { #ifdef DSSI_DEBUG printf("DssiSynthIF::getControllerInfo is CC control\n"); #endif ctlnum = DSSI_CC_NUMBER(c); #ifdef DSSI_DEBUG if(DSSI_IS_NRPN(ctlnum)) printf("DssiSynthIF::getControllerInfo is also NRPN control. Using CC.\n"); #endif } else if(DSSI_IS_NRPN(ctlnum)) { #ifdef DSSI_DEBUG printf("DssiSynthIF::getControllerInfo is NRPN control\n"); #endif ctlnum = DSSI_NRPN_NUMBER(c) + CTRL_NRPN14_OFFSET; } } int def = CTRL_VAL_UNKNOWN; if(ladspa2MidiControlValues(ld, i, ctlnum, min, max, &def)) *initval = def; else *initval = CTRL_VAL_UNKNOWN; #ifdef DSSI_DEBUG printf("DssiSynthIF::getControllerInfo passed ctlnum:%d min:%d max:%d initval:%d\n", ctlnum, *min, *max, *initval); #endif *ctrl = ctlnum; *name = ld->PortNames[i]; return ++id; /* // ...now create midi controllers for ports which did not define them ... for(int k = 0; k < controlPorts; ++k) { int i = synth->pIdx[k]; //controls[k].val = ladspaDefaultValue(ld, i); ladspaDefaultValue(ld, i, &controls[k].val); printf("DssiSynthIF::getControllerInfo #2 control port:%d port idx:%d name:%s\n", k, i, ld->PortNames[i]); if(!dssi->get_midi_controller_for_port || (dssi->get_midi_controller_for_port(handle, i) == DSSI_NONE)) { int ctlnum; //printf("DssiSynthIF::getControllerInfo #2 midi controller number:%d\n", ctlnum); printf("DssiSynthIF::getControllerInfo #2 creating MidiController number:%d\n", ctlnum); MidiController* mc = ladspa2MidiController(ld, i, ctlnum); // Add to MidiInstrument controller list. if(mc) { printf("DssiSynthIF::getControllerInfo #2 adding MidiController to instrument\n"); ((MidiInstrument*)synti)->controller()->add(mc); } } else { } } */ } int DssiSynthIF::channels() const { return synth->_outports > MAX_CHANNELS ? MAX_CHANNELS : synth->_outports; } int DssiSynthIF::totalOutChannels() const { return synth->_outports; } int DssiSynthIF::totalInChannels() const { return synth->_inports; } //-------------------------------- // Methods for PluginIBase: //-------------------------------- bool DssiSynthIF::on() const { return true; } // Synth is not part of a rack plugin chain. Always on. void DssiSynthIF::setOn(bool /*val*/) { } int DssiSynthIF::pluginID() { return (synth && synth->dssi) ? synth->dssi->LADSPA_Plugin->UniqueID : 0; } int DssiSynthIF::id() { return 0; } // Synth is not part of a rack plugin chain. Always 0. QString DssiSynthIF::pluginLabel() const { return (synth && synth->dssi) ? QString(synth->dssi->LADSPA_Plugin->Label) : QString(); } QString DssiSynthIF::name() const { return synti->name(); } AudioTrack* DssiSynthIF::track() { return (AudioTrack*)synti; } void DssiSynthIF::enableController(int i, bool v) { controls[i].enCtrl = v; } bool DssiSynthIF::controllerEnabled(int i) const { return controls[i].enCtrl; } bool DssiSynthIF::controllerEnabled2(int i) const { return controls[i].en2Ctrl; } void DssiSynthIF::updateControllers() { } void DssiSynthIF::writeConfiguration(int /*level*/, Xml& /*xml*/) { } bool DssiSynthIF::readConfiguration(Xml& /*xml*/, bool /*readPreset*/) { return false; } int DssiSynthIF::parameters() const { return synth ? synth->_controlInPorts : 0; } void DssiSynthIF::setParam(int i, double val) { setParameter(i, val); } double DssiSynthIF::param(int i) const { return getParameter(i); } const char* DssiSynthIF::paramName(int i) { return (synth && synth->dssi) ? synth->dssi->LADSPA_Plugin->PortNames[i] : 0; } //LADSPA_PortRangeHint DssiSynthIF::range(int i) { return (synth && synth->dssi) ? synth->dssi->LADSPA_Plugin->PortRangeHints[i] : 0; } LADSPA_PortRangeHint DssiSynthIF::range(int i) { return synth->dssi->LADSPA_Plugin->PortRangeHints[i]; } #else //DSSI_SUPPORT void initDSSI() {} #endif