//=================================================================== // MusE // Linux Music Editor // // vst_native.cpp // (C) Copyright 2012-2013 Tim E. Real (terminator356 on users dot sourceforge dot net) // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; version 2 of // the License, or (at your option) any later version. // // 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //=================================================================== #include "config.h" #ifdef VST_NATIVE_SUPPORT #include #include #include #include #include #include #include #include #include #include "globals.h" #include "gconfig.h" #include "audio.h" #include "synth.h" #include "jackaudio.h" #include "midi.h" #include "xml.h" #include "plugin.h" #include "popupmenu.h" #include "pos.h" #include "tempo.h" #include "sync.h" #include "al/sig.h" #include "vst_native.h" #define OLD_PLUGIN_ENTRY_POINT "main" #define NEW_PLUGIN_ENTRY_POINT "VSTPluginMain" // Enable debugging messages //#define VST_NATIVE_DEBUG //#define VST_NATIVE_DEBUG_PROCESS namespace MusECore { extern JackAudioDevice* jackAudio; //----------------------------------------------------------------------------------------- // vstHostCallback // This must be a function, it cannot be a class method so we dispatch to various objects from here. //----------------------------------------------------------------------------------------- VstIntPtr VSTCALLBACK vstNativeHostCallback(AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) { // Is this callback for an actual instance? Hand-off to the instance if so. VSTPlugin* plugin; if(effect && effect->user) { plugin = (VSTPlugin*)(effect->user); return ((VstNativeSynthIF*)plugin)->hostCallback(opcode, index, value, ptr, opt); } // No instance found. So we are just scanning for plugins... #ifdef VST_NATIVE_DEBUG fprintf(stderr, "vstNativeHostCallback eff:%p opcode:%ld\n", effect, opcode); #endif switch (opcode) { case audioMasterAutomate: // index, value, returns 0 return 0; case audioMasterVersion: // vst version, currently 2 (0 for older) return 2300; case audioMasterCurrentId: // returns the unique id of a plug that's currently // loading return 0; case audioMasterIdle: // call application idle routine (this will // call effEditIdle for all open editors too) return 0; case audioMasterGetTime: // returns const VstTimeInfo* (or 0 if not supported) // should contain a mask indicating which fields are required // (see valid masks above), as some items may require extensive // conversions return 0; case audioMasterProcessEvents: // VstEvents* in return 0; case audioMasterIOChanged: // numInputs and/or numOutputs has changed return 0; case audioMasterSizeWindow: // index: width, value: height return 0; case audioMasterGetSampleRate: return MusEGlobal::sampleRate; case audioMasterGetBlockSize: return MusEGlobal::segmentSize; case audioMasterGetInputLatency: return 0; case audioMasterGetOutputLatency: return 0; case audioMasterGetCurrentProcessLevel: // returns: 0: not supported, // 1: currently in user thread (gui) // 2: currently in audio thread (where process is called) // 3: currently in 'sequencer' thread (midi, timer etc) // 4: currently offline processing and thus in user thread // other: not defined, but probably pre-empting user thread. return 0; case audioMasterGetAutomationState: // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write // offline return 0; case audioMasterOfflineStart: case audioMasterOfflineRead: // ptr points to offline structure, see below. return 0: error, 1 ok return 0; case audioMasterOfflineWrite: // same as read return 0; case audioMasterOfflineGetCurrentPass: case audioMasterOfflineGetCurrentMetaPass: return 0; case audioMasterGetVendorString: // fills with a string identifying the vendor (max 64 char) strcpy ((char*) ptr, "MusE"); return 1; case audioMasterGetProductString: // fills with a string with product name (max 64 char) strcpy ((char*) ptr, "NativeVST"); return 1; case audioMasterGetVendorVersion: // returns vendor-specific version return 2000; case audioMasterVendorSpecific: // no definition, vendor specific handling return 0; case audioMasterCanDo: // string in ptr, see below return 0; case audioMasterGetLanguage: // see enum return kVstLangEnglish; case audioMasterGetDirectory: // get plug directory, FSSpec on MAC, else char* return 0; case audioMasterUpdateDisplay: // something has changed, update 'multi-fx' display return 0; case audioMasterBeginEdit: // begin of automation session (when mouse down), parameter index in return 0; case audioMasterEndEdit: // end of automation session (when mouse up), parameter index in return 0; case audioMasterOpenFileSelector: // open a fileselector window with VstFileSelect* in return 0; case audioMasterCloseFileSelector: return 0; #ifdef VST_FORCE_DEPRECATED case audioMasterGetSpeakerArrangement: // (long)input in , output in return 0; case audioMasterPinConnected: // inquire if an input or output is beeing connected; // index enumerates input or output counting from zero: // value is 0 for input and != 0 otherwise. note: the // return value is 0 for such that older versions // will always return true. //return 1; return 0; // VST 2.0 opcodes... case audioMasterWantMidi: // is a filter which is currently ignored return 0; case audioMasterSetTime: // VstTimenfo* in , filter in , not supported return 0; case audioMasterTempoAt: // returns tempo (in bpm * 10000) at sample frame location passed in return 0; // TODO: case audioMasterGetNumAutomatableParameters: return 0; case audioMasterGetParameterQuantization: // returns the integer value for +1.0 representation, // or 1 if full single float precision is maintained // in automation. parameter index in (-1: all, any) //return 0; return 1; case audioMasterNeedIdle: // plug needs idle calls (outside its editor window) return 0; case audioMasterGetPreviousPlug: // input pin in (-1: first to come), returns cEffect* return 0; case audioMasterGetNextPlug: // output pin in (-1: first to come), returns cEffect* return 0; case audioMasterWillReplaceOrAccumulate: // returns: 0: not supported, 1: replace, 2: accumulate //return 0; return 1; case audioMasterSetOutputSampleRate: // for variable i/o, sample rate in return 0; case audioMasterSetIcon: // void* in , format not defined yet return 0; case audioMasterOpenWindow: // returns platform specific ptr return 0; case audioMasterCloseWindow: // close window, platform specific handle in return 0; #endif default: break; } if(MusEGlobal::debugMsg) fprintf(stderr, " unknown opcode\n"); return 0; } //--------------------------------------------------------- // loadPluginLib //--------------------------------------------------------- static void scanVstNativeLib(QFileInfo& fi) { void* handle = dlopen(fi.filePath().toAscii().constData(), RTLD_NOW); if (handle == NULL) { fprintf(stderr, "scanVstNativeLib: dlopen(%s) failed: %s\n", fi.filePath().toAscii().constData(), dlerror()); return; } char buffer[128]; QString effectName; QString vendorString; QString productString; int vendorVersion; std::vector::iterator is; int vst_version = 0; VstNativeSynth* new_synth = NULL; AEffect *(*getInstance)(audioMasterCallback); getInstance = (AEffect*(*)(audioMasterCallback))dlsym(handle, NEW_PLUGIN_ENTRY_POINT); if(!getInstance) { if(MusEGlobal::debugMsg) { fprintf(stderr, "VST 2.4 entrypoint \"" NEW_PLUGIN_ENTRY_POINT "\" not found in library %s, looking for \"" OLD_PLUGIN_ENTRY_POINT "\"\n", fi.filePath().toAscii().constData()); } getInstance = (AEffect*(*)(audioMasterCallback))dlsym(handle, OLD_PLUGIN_ENTRY_POINT); if(!getInstance) { fprintf(stderr, "ERROR: VST entrypoints \"" NEW_PLUGIN_ENTRY_POINT "\" or \"" OLD_PLUGIN_ENTRY_POINT "\" not found in library\n"); dlclose(handle); return; } else if(MusEGlobal::debugMsg) { fprintf(stderr, "VST entrypoint \"" OLD_PLUGIN_ENTRY_POINT "\" found\n"); } } else if(MusEGlobal::debugMsg) { fprintf(stderr, "VST entrypoint \"" NEW_PLUGIN_ENTRY_POINT "\" found\n"); } AEffect *plugin = getInstance(vstNativeHostCallback); if(!plugin) { fprintf(stderr, "ERROR: Failed to instantiate plugin in VST library \"%s\"\n", fi.filePath().toAscii().constData()); dlclose(handle); return; } else if(MusEGlobal::debugMsg) fprintf(stderr, "plugin instantiated\n"); if(plugin->magic != kEffectMagic) { fprintf(stderr, "Not a VST plugin in library \"%s\"\n", fi.filePath().toAscii().constData()); dlclose(handle); return; } else if(MusEGlobal::debugMsg) fprintf(stderr, "plugin is a VST\n"); if(!(plugin->flags & effFlagsHasEditor)) { if(MusEGlobal::debugMsg) fprintf(stderr, "Plugin has no GUI\n"); } else if(MusEGlobal::debugMsg) fprintf(stderr, "Plugin has a GUI\n"); if(!(plugin->flags & effFlagsCanReplacing)) fprintf(stderr, "Plugin does not support processReplacing\n"); else if(MusEGlobal::debugMsg) fprintf(stderr, "Plugin supports processReplacing\n"); plugin->dispatcher(plugin, effOpen, 0, 0, NULL, 0); buffer[0] = 0; plugin->dispatcher(plugin, effGetEffectName, 0, 0, buffer, 0); if(buffer[0]) effectName = QString(buffer); buffer[0] = 0; plugin->dispatcher(plugin, effGetVendorString, 0, 0, buffer, 0); if (buffer[0]) vendorString = QString(buffer); buffer[0] = 0; plugin->dispatcher(plugin, effGetProductString, 0, 0, buffer, 0); if (buffer[0]) productString = QString(buffer); vendorVersion = plugin->dispatcher(plugin, effGetVendorVersion, 0, 0, NULL, 0); // Some (older) plugins don't have any of these strings. We only have the filename to use. if(effectName.isEmpty()) effectName = fi.completeBaseName(); if(productString.isEmpty()) //productString = fi.completeBaseName(); productString = effectName; // Make sure it doesn't already exist. for(is = MusEGlobal::synthis.begin(); is != MusEGlobal::synthis.end(); ++is) if((*is)->name() == effectName && (*is)->baseName() == fi.completeBaseName()) goto _ending; // "2 = VST2.x, older versions return 0". Observed 2400 on all the ones tested so far. vst_version = plugin->dispatcher(plugin, effGetVstVersion, 0, 0, NULL, 0.0f); if(!((plugin->flags & effFlagsIsSynth) || (vst_version >= 2 && plugin->dispatcher(plugin, effCanDo, 0, 0,(void*) "receiveVstEvents", 0.0f) > 0))) { if(MusEGlobal::debugMsg) fprintf(stderr, "Plugin is not a synth\n"); goto _ending; } new_synth = new VstNativeSynth(fi, plugin, effectName, productString, vendorString, QString::number(vendorVersion)); if(MusEGlobal::debugMsg) fprintf(stderr, "scanVstNativeLib: adding vst synth plugin:%s name:%s effectName:%s vendorString:%s productString:%s vstver:%d\n", fi.filePath().toLatin1().constData(), fi.completeBaseName().toLatin1().constData(), effectName.toLatin1().constData(), vendorString.toLatin1().constData(), productString.toLatin1().constData(), vst_version ); MusEGlobal::synthis.push_back(new_synth); _ending: ; //plugin->dispatcher(plugin, effMainsChanged, 0, 0, NULL, 0); plugin->dispatcher(plugin, effClose, 0, 0, NULL, 0); dlclose(handle); } //--------------------------------------------------------- // scanVstDir //--------------------------------------------------------- static void scanVstNativeDir(const QString& s) { if (MusEGlobal::debugMsg) fprintf(stderr, "scan vst native plugin dir <%s>\n", s.toLatin1().constData()); QDir pluginDir(s, QString("*.so"), QDir::Unsorted, QDir::Files); if(!pluginDir.exists()) return; QStringList list = pluginDir.entryList(); int count = list.count(); for(int i = 0; i < count; ++i) { if(MusEGlobal::debugMsg) fprintf(stderr, "scanVstNativeDir: found %s\n", (s + QString("/") + list[i]).toLatin1().constData()); QFileInfo fi(s + QString("/") + list[i]); scanVstNativeLib(fi); } } //--------------------------------------------------------- // initVST_Native //--------------------------------------------------------- void initVST_Native() { std::string s; const char* vstPath = getenv("VST_NATIVE_PATH"); if (vstPath) { if (MusEGlobal::debugMsg) fprintf(stderr, "scan native vst: VST_NATIVE_PATH is: %s\n", vstPath); } else { if (MusEGlobal::debugMsg) fprintf(stderr, "scan native vst: VST_NATIVE_PATH not set\n"); } if(!vstPath) { vstPath = getenv("VST_PATH"); if (vstPath) { if (MusEGlobal::debugMsg) fprintf(stderr, "scan native vst: VST_PATH is: %s\n", vstPath); } else { if (MusEGlobal::debugMsg) fprintf(stderr, "scan native vst: VST_PATH not set\n"); const char* home = getenv("HOME"); s = std::string(home) + std::string("/vst:/usr/local/lib64/vst:/usr/local/lib/vst:/usr/lib64/vst:/usr/lib/vst"); vstPath = s.c_str(); if (MusEGlobal::debugMsg) fprintf(stderr, "scan native vst: defaulting to path: %s\n", vstPath); } } const char* p = vstPath; 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'; scanVstNativeDir(QString(buffer)); delete[] buffer; } p = pe; if (*p == ':') p++; } } //--------------------------------------------------------- // VstNativeSynth //--------------------------------------------------------- VstNativeSynth::VstNativeSynth(const QFileInfo& fi, AEffect* plugin, const QString& label, const QString& desc, const QString& maker, const QString& ver) : Synth(fi, label, desc, maker, ver) { _handle = NULL; _hasGui = plugin->flags & effFlagsHasEditor; _inports = plugin->numInputs; _outports = plugin->numOutputs; _controlInPorts = plugin->numParams; _inPlaceCapable = false; //(plugin->flags & effFlagsCanReplacing) && (_inports == _outports) && MusEGlobal::config.vstInPlace; #ifndef VST_VESTIGE_SUPPORT _hasChunks = plugin->flags & effFlagsProgramChunks; #else _hasChunks = false; #endif _flags = 0; _vst_version = 0; _vst_version = plugin->dispatcher(plugin, effGetVstVersion, 0, 0, NULL, 0.0f); // "2 = VST2.x, older versions return 0". Observed 2400 on all the ones tested so far. if(_vst_version >= 2) { if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"receiveVstEvents", 0.0f) > 0) _flags |= canReceiveVstEvents; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"sendVstEvents", 0.0f) > 0) _flags |= canSendVstEvents; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"sendVstMidiEvent", 0.0f) > 0) _flags |= canSendVstMidiEvents; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"sendVstTimeInfo", 0.0f) > 0) _flags |= canSendVstTimeInfo; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"receiveVstMidiEvent", 0.0f) > 0) _flags |= canReceiveVstMidiEvents; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"receiveVstTimeInfo", 0.0f) > 0) _flags |= canReceiveVstTimeInfo; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"offline", 0.0f) > 0) _flags |=canProcessOffline; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"plugAsChannelInsert", 0.0f) > 0) _flags |= canUseAsInsert; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"plugAsSend", 0.0f) > 0) _flags |= canUseAsSend; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"mixDryWet", 0.0f) > 0) _flags |= canMixDryWet; if(plugin->dispatcher(plugin, effCanDo, 0, 0, (void*)"midiProgramNames", 0.0f) > 0) _flags |= canMidiProgramNames; } } //--------------------------------------------------------- // incInstances //--------------------------------------------------------- void VstNativeSynth::incInstances(int val) { _instances += val; if(_instances == 0) { if(_handle) { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynth::incInstances no more instances, closing library\n"); #endif dlclose(_handle); } _handle = NULL; iIdx.clear(); oIdx.clear(); rpIdx.clear(); midiCtl2PortMap.clear(); port2MidiCtlMap.clear(); } } //--------------------------------------------------------- // instantiate //--------------------------------------------------------- AEffect* VstNativeSynth::instantiate(VstNativeSynthIF* sif) { int inst_num = _instances; inst_num++; QString n; n.setNum(inst_num); QString instanceName = baseName() + "-" + n; QByteArray ba = info.filePath().toLatin1(); const char* path = ba.constData(); void* hnd = _handle; int vst_version; if(hnd == NULL) { hnd = dlopen(path, RTLD_NOW); if(hnd == NULL) { fprintf(stderr, "dlopen(%s) failed: %s\n", path, dlerror()); return NULL; } } AEffect *(*getInstance)(audioMasterCallback); getInstance = (AEffect*(*)(audioMasterCallback))dlsym(hnd, NEW_PLUGIN_ENTRY_POINT); if(!getInstance) { if(MusEGlobal::debugMsg) { fprintf(stderr, "VST 2.4 entrypoint \"" NEW_PLUGIN_ENTRY_POINT "\" not found in library %s, looking for \"" OLD_PLUGIN_ENTRY_POINT "\"\n", path); } getInstance = (AEffect*(*)(audioMasterCallback))dlsym(hnd, OLD_PLUGIN_ENTRY_POINT); if(!getInstance) { fprintf(stderr, "ERROR: VST entrypoints \"" NEW_PLUGIN_ENTRY_POINT "\" or \"" OLD_PLUGIN_ENTRY_POINT "\" not found in library\n"); dlclose(hnd); return NULL; } else if(MusEGlobal::debugMsg) { fprintf(stderr, "VST entrypoint \"" OLD_PLUGIN_ENTRY_POINT "\" found\n"); } } else if(MusEGlobal::debugMsg) { fprintf(stderr, "VST entrypoint \"" NEW_PLUGIN_ENTRY_POINT "\" found\n"); } AEffect *plugin = getInstance(vstNativeHostCallback); if(!plugin) { fprintf(stderr, "ERROR: Failed to instantiate plugin in VST library \"%s\"\n", path); dlclose(hnd); return NULL; } else if(MusEGlobal::debugMsg) fprintf(stderr, "plugin instantiated\n"); if(plugin->magic != kEffectMagic) { fprintf(stderr, "Not a VST plugin in library \"%s\"\n", path); dlclose(hnd); return NULL; } else if(MusEGlobal::debugMsg) fprintf(stderr, "plugin is a VST\n"); if(!(plugin->flags & effFlagsHasEditor)) { if(MusEGlobal::debugMsg) fprintf(stderr, "Plugin has no GUI\n"); } else if(MusEGlobal::debugMsg) fprintf(stderr, "Plugin has a GUI\n"); if(!(plugin->flags & effFlagsCanReplacing)) fprintf(stderr, "Plugin does not support processReplacing\n"); else if(MusEGlobal::debugMsg) fprintf(stderr, "Plugin supports processReplacing\n"); plugin->user = sif; plugin->dispatcher(plugin, effOpen, 0, 0, NULL, 0); // "2 = VST2.x, older versions return 0". Observed 2400 on all the ones tested so far. vst_version = plugin->dispatcher(plugin, effGetVstVersion, 0, 0, NULL, 0.0f); if(!((plugin->flags & effFlagsIsSynth) || (vst_version >= 2 && plugin->dispatcher(plugin, effCanDo, 0, 0,(void*) "receiveVstEvents", 0.0f) > 0))) { if(MusEGlobal::debugMsg) fprintf(stderr, "Plugin is not a synth\n"); goto _error; } ++_instances; _handle = hnd; //plugin->dispatcher(plugin, effSetProgram, 0, 0, NULL, 0.0f); // REMOVE Tim. Or keep? return plugin; _error: //plugin->dispatcher(plugin, effMainsChanged, 0, 0, NULL, 0); plugin->dispatcher(plugin, effClose, 0, 0, NULL, 0); dlclose(hnd); return NULL; } //--------------------------------------------------------- // createSIF //--------------------------------------------------------- SynthIF* VstNativeSynth::createSIF(SynthI* s) { VstNativeSynthIF* sif = new VstNativeSynthIF(s); sif->init(this); return sif; } //--------------------------------------------------------- // VstNativeSynthIF //--------------------------------------------------------- VstNativeSynthIF::VstNativeSynthIF(SynthI* s) : SynthIF(s) { _guiVisible = false; _gw = NULL; _synth = NULL; _plugin = NULL; _active = false; _editor = NULL; _inProcess = false; _controls = NULL; // controlsOut = 0; _audioInBuffers = NULL; _audioInSilenceBuf = NULL; _audioOutBuffers = NULL; } VstNativeSynthIF::~VstNativeSynthIF() { // Just in case it wasn't removed or deactivate3 wasn't called etc... if(_plugin) fprintf(stderr, "ERROR: ~VstNativeSynthIF: _plugin is not NULL!\n"); if(_audioOutBuffers) { unsigned long op = _synth->outPorts(); for(unsigned long i = 0; i < op; ++i) { if(_audioOutBuffers[i]) free(_audioOutBuffers[i]); } delete[] _audioOutBuffers; } if(_audioInBuffers) { unsigned long ip = _synth->inPorts(); for(unsigned long i = 0; i < ip; ++i) { if(_audioInBuffers[i]) free(_audioInBuffers[i]); } delete[] _audioInBuffers; } if(_audioInSilenceBuf) free(_audioInSilenceBuf); if(_controls) delete[] _controls; if(_gw) delete[] _gw; } //--------------------------------------------------------- // init //--------------------------------------------------------- bool VstNativeSynthIF::init(Synth* s) { _synth = (VstNativeSynth*)s; _plugin = _synth->instantiate(this); if(!_plugin) return false; queryPrograms(); unsigned long outports = _synth->outPorts(); if(outports != 0) { _audioOutBuffers = new float*[outports]; for(unsigned long k = 0; k < outports; ++k) { int rv = posix_memalign((void**)&_audioOutBuffers[k], 16, sizeof(float) * MusEGlobal::segmentSize); if(rv != 0) { fprintf(stderr, "ERROR: VstNativeSynthIF::init: posix_memalign returned error:%d. Aborting!\n", rv); abort(); } if(MusEGlobal::config.useDenormalBias) { for(unsigned q = 0; q < MusEGlobal::segmentSize; ++q) _audioOutBuffers[k][q] = MusEGlobal::denormalBias; } else memset(_audioOutBuffers[k], 0, sizeof(float) * MusEGlobal::segmentSize); } } unsigned long inports = _synth->inPorts(); if(inports != 0) { _audioInBuffers = new float*[inports]; for(unsigned long k = 0; k < inports; ++k) { int rv = posix_memalign((void**)&_audioInBuffers[k], 16, sizeof(float) * MusEGlobal::segmentSize); if(rv != 0) { fprintf(stderr, "ERROR: VstNativeSynthIF::init: posix_memalign returned error:%d. Aborting!\n", rv); abort(); } if(MusEGlobal::config.useDenormalBias) { for(unsigned q = 0; q < MusEGlobal::segmentSize; ++q) _audioInBuffers[k][q] = MusEGlobal::denormalBias; } else memset(_audioInBuffers[k], 0, sizeof(float) * MusEGlobal::segmentSize); _iUsedIdx.push_back(false); // Start out with all false. } int rv = posix_memalign((void**)&_audioInSilenceBuf, 16, sizeof(float) * MusEGlobal::segmentSize); if(rv != 0) { fprintf(stderr, "ERROR: VstNativeSynthIF::init: posix_memalign returned error:%d. Aborting!\n", rv); abort(); } if(MusEGlobal::config.useDenormalBias) { for(unsigned q = 0; q < MusEGlobal::segmentSize; ++q) _audioInSilenceBuf[q] = MusEGlobal::denormalBias; } else memset(_audioInSilenceBuf, 0, sizeof(float) * MusEGlobal::segmentSize); } _controls = NULL; _gw = NULL; unsigned long controlPorts = _synth->inControls(); if(controlPorts != 0) { _controls = new Port[controlPorts]; _gw = new VstNativeGuiWidgets[controlPorts]; } for(unsigned long i = 0; i < controlPorts; ++i) { _gw[i].pressed = false; _controls[i].idx = i; //float val; // TODO //ladspaDefaultValue(ld, k, &val); // FIXME TODO float val = _plugin->getParameter(_plugin, i); // TODO _controls[i].val = val; _controls[i].tmpVal = val; _controls[i].enCtrl = true; // Support a special block for synth ladspa controllers. // Put the ID at a special block after plugins (far after). int id = genACnum(MAX_PLUGINS, i); const char* param_name = paramName(i); // TODO FIXME! ///float min, max; ///ladspaControlRange(ld, k, &min, &max); float min = 0.0, max = 1.0; CtrlList* cl; CtrlListList* cll = track()->controller(); iCtrlList icl = cll->find(id); if (icl == cll->end()) { cl = new CtrlList(id); cll->add(cl); cl->setCurVal(_controls[i].val); //cl->setCurVal(_plugin->getParameter(_plugin, i)); } else { cl = icl->second; _controls[i].val = cl->curVal(); #ifndef VST_VESTIGE_SUPPORT if(dispatch(effCanBeAutomated, i, 0, NULL, 0.0f) == 1) { #endif double v = cl->curVal(); if(v != _plugin->getParameter(_plugin, i)) _plugin->setParameter(_plugin, i, v); #ifndef VST_VESTIGE_SUPPORT } #ifdef VST_NATIVE_DEBUG else fprintf(stderr, "VstNativeSynthIF::init %s parameter:%lu cannot be automated\n", name().toLatin1().constData(), i); #endif #endif } cl->setRange(min, max); cl->setName(QString(param_name)); //cl->setValueType(ladspaCtrlValueType(ld, k)); cl->setValueType(ctrlValueType(i)); //cl->setMode(ladspaCtrlMode(ld, k)); cl->setMode(ctrlMode(i)); } activate(); doSelectProgram(synti->_curBankH, synti->_curBankL, synti->_curProgram); //doSelectProgram(synti->_curProgram); return true; } //--------------------------------------------------------- // resizeEditor //--------------------------------------------------------- bool VstNativeSynthIF::resizeEditor(int w, int h) { if(!_editor || w <= 0 || h <= 0) return false; _editor->resize(w, h); return true; } //--------------------------------------------------------- // hostCallback //--------------------------------------------------------- VstIntPtr VstNativeSynthIF::hostCallback(VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) { static VstTimeInfo _timeInfo; #ifdef VST_NATIVE_DEBUG if(opcode != audioMasterGetTime) fprintf(stderr, "VstNativeSynthIF::hostCallback %s opcode:%ld\n", name().toLatin1().constData(), opcode); #endif switch (opcode) { case audioMasterAutomate: // index, value, returns 0 ///_plugin->setParameter (_plugin, index, opt); guiControlChanged(index, opt); return 0; case audioMasterVersion: // vst version, currently 2 (0 for older) return 2300; case audioMasterCurrentId: // returns the unique id of a plug that's currently // loading ///return 0; return _plugin->uniqueID; case audioMasterIdle: // call application idle routine (this will // call effEditIdle for all open editors too) //_plugin->updateParamValues(false); //_plugin->dispatcher(_plugin, effEditIdle, 0, 0, NULL, 0.0f); ///idleEditor(); // REMOVE Tim. Or keep. return 0; case audioMasterGetTime: { // returns const VstTimeInfo* (or 0 if not supported) // should contain a mask indicating which fields are required // (see valid masks above), as some items may require extensive // conversions // FIXME TODO: Optimizations: This may be called many times in one process call // due to our multi-run slices. Some of the (costly) info will be redundant. // So try to add some flag to try to only call some or all of this once per cycle. #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::hostCallback master time: valid: nanos:%d ppqpos:%d tempo:%d bars:%d cyclepos:%d sig:%d smpte:%d clock:%d\n", (bool)(value & kVstNanosValid), (bool)(value & kVstPpqPosValid), (bool)(value & kVstTempoValid), (bool)(value & kVstBarsValid), (bool)(value & kVstCyclePosValid), (bool)(value & kVstTimeSigValid), (bool)(value & kVstSmpteValid), (bool)(value & kVstClockValid)); #endif memset(&_timeInfo, 0, sizeof(_timeInfo)); unsigned int curr_frame = MusEGlobal::audio->pos().frame(); _timeInfo.samplePos = (double)curr_frame; _timeInfo.sampleRate = (double)MusEGlobal::sampleRate; _timeInfo.flags = 0; Pos p(MusEGlobal::extSyncFlag.value() ? MusEGlobal::audio->tickPos() : curr_frame, MusEGlobal::extSyncFlag.value() ? true : false); if(value & kVstBarsValid) { int p_bar, p_beat, p_tick; p.mbt(&p_bar, &p_beat, &p_tick); _timeInfo.barStartPos = (double)Pos(p_bar, 0, 0).tick() / (double)MusEGlobal::config.division; _timeInfo.flags |= kVstBarsValid; } if(value & kVstTimeSigValid) { int z, n; AL::sigmap.timesig(p.tick(), z, n); #ifndef VST_VESTIGE_SUPPORT _timeInfo.timeSigNumerator = (long)z; _timeInfo.timeSigDenominator = (long)n; #else _timeInfo.timeSigNumerator = z; _timeInfo.timeSigDenominator = n; #endif _timeInfo.flags |= kVstTimeSigValid; } if(value & kVstPpqPosValid) { _timeInfo.ppqPos = (double)MusEGlobal::audio->tickPos() / (double)MusEGlobal::config.division; _timeInfo.flags |= kVstPpqPosValid; } if(value & kVstTempoValid) { double tempo = MusEGlobal::tempomap.tempo(p.tick()); _timeInfo.tempo = (60000000.0 / tempo) * double(MusEGlobal::tempomap.globalTempo())/100.0; _timeInfo.flags |= kVstTempoValid; } #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::hostCallback master time: sample pos:%f samplerate:%f sig num:%ld den:%ld tempo:%f\n", _timeInfo.samplePos, _timeInfo.sampleRate, _timeInfo.timeSigNumerator, _timeInfo.timeSigDenominator, _timeInfo.tempo); #endif if(MusEGlobal::audio->isPlaying()) _timeInfo.flags |= (kVstTransportPlaying | kVstTransportChanged); // TODO //if(MusEGlobal::audio->isRecording()) // _timeInfo.flags |= (kVstTransportRecording | kVstTransportChanged); return (long)&_timeInfo; } case audioMasterProcessEvents: // VstEvents* in return 0; // TODO: case audioMasterIOChanged: // numInputs and/or numOutputs has changed return 0; case audioMasterSizeWindow: // index: width, value: height if(resizeEditor(int(index), int(value))) return 1; // supported. return 0; case audioMasterGetSampleRate: //return 0; return MusEGlobal::sampleRate; case audioMasterGetBlockSize: //return 0; return MusEGlobal::segmentSize; case audioMasterGetInputLatency: return 0; case audioMasterGetOutputLatency: return 0; case audioMasterGetCurrentProcessLevel: // returns: 0: not supported, // 1: currently in user thread (gui) // 2: currently in audio thread (where process is called) // 3: currently in 'sequencer' thread (midi, timer etc) // 4: currently offline processing and thus in user thread // other: not defined, but probably pre-empting user thread. if(_inProcess) return 2; else return 1; case audioMasterGetAutomationState: // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write // offline return 1; // TODO: case audioMasterOfflineStart: case audioMasterOfflineRead: // ptr points to offline structure, see below. return 0: error, 1 ok return 0; case audioMasterOfflineWrite: // same as read return 0; case audioMasterOfflineGetCurrentPass: case audioMasterOfflineGetCurrentMetaPass: return 0; case audioMasterGetVendorString: // fills with a string identifying the vendor (max 64 char) strcpy ((char*) ptr, "MusE"); return 1; case audioMasterGetProductString: // fills with a string with product name (max 64 char) strcpy ((char*) ptr, "MusE Sequencer"); return 1; case audioMasterGetVendorVersion: // returns vendor-specific version return 2000; case audioMasterVendorSpecific: // no definition, vendor specific handling return 0; case audioMasterCanDo: // string in ptr, see below if(!strcmp((char*)ptr, "sendVstEvents") || !strcmp((char*)ptr, "receiveVstMidiEvent") || !strcmp((char*)ptr, "sendVstMidiEvent") || !strcmp((char*)ptr, "sendVstTimeInfo") || !strcmp((char*)ptr, "sizeWindow") || !strcmp((char*)ptr, "supplyIdle")) return 1; #if 0 //ifndef VST_VESTIGE_SUPPORT else if(!strcmp((char*)ptr, "openFileSelector") || !strcmp((char*)ptr, "closeFileSelector")) return 1; #endif return 0; case audioMasterGetLanguage: // see enum //return 0; return kVstLangEnglish; case audioMasterGetDirectory: // get plug directory, FSSpec on MAC, else char* return 0; case audioMasterUpdateDisplay: // something has changed, update 'multi-fx' display //_plugin->updateParamValues(false); //QApplication::processEvents(); // REMOVE Tim. Or keep. Commented in QTractor. _plugin->dispatcher(_plugin, effEditIdle, 0, 0, NULL, 0.0f); // ? return 0; case audioMasterBeginEdit: // begin of automation session (when mouse down), parameter index in guiAutomationBegin(index); return 1; case audioMasterEndEdit: // end of automation session (when mouse up), parameter index in guiAutomationEnd(index); return 1; #if 0 //ifndef VST_VESTIGE_SUPPORT case audioMasterOpenFileSelector: // open a fileselector window with VstFileSelect* in return 0; case audioMasterCloseFileSelector: return 0; #endif #ifdef VST_FORCE_DEPRECATED case audioMasterGetSpeakerArrangement: // (long)input in , output in return 0; case audioMasterPinConnected: // inquire if an input or output is beeing connected; // index enumerates input or output counting from zero: // value is 0 for input and != 0 otherwise. note: the // return value is 0 for such that older versions // will always return true. //return 1; return 0; // VST 2.0 opcodes... case audioMasterWantMidi: // is a filter which is currently ignored return 0; case audioMasterSetTime: // VstTimenfo* in , filter in , not supported return 0; case audioMasterTempoAt: // returns tempo (in bpm * 10000) at sample frame location passed in return 0; // TODO: case audioMasterGetNumAutomatableParameters: return 0; case audioMasterGetParameterQuantization: // returns the integer value for +1.0 representation, // or 1 if full single float precision is maintained // in automation. parameter index in (-1: all, any) //return 0; return 1; case audioMasterNeedIdle: // plug needs idle calls (outside its editor window) return 0; case audioMasterGetPreviousPlug: // input pin in (-1: first to come), returns cEffect* return 0; case audioMasterGetNextPlug: // output pin in (-1: first to come), returns cEffect* return 0; case audioMasterWillReplaceOrAccumulate: // returns: 0: not supported, 1: replace, 2: accumulate //return 0; return 1; case audioMasterSetOutputSampleRate: // for variable i/o, sample rate in return 0; case audioMasterSetIcon: // void* in , format not defined yet return 0; case audioMasterOpenWindow: // returns platform specific ptr return 0; case audioMasterCloseWindow: // close window, platform specific handle in return 0; #endif default: break; } return 0; } //--------------------------------------------------------- // idleEditor //--------------------------------------------------------- void VstNativeSynthIF::idleEditor() { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::idleEditor %p\n", this); #endif // REMOVE Tim. Or keep. //_plugin->dispatcher(_plugin, effEditIdle, 0, 0, NULL, 0.0f); //if(_editor) // _editor->update(); } //--------------------------------------------------------- // guiHeartBeat //--------------------------------------------------------- void VstNativeSynthIF::guiHeartBeat() { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::guiHeartBeat %p\n", this); #endif // REMOVE Tim. Or keep. if(_plugin && _active) { //#ifdef VST_FORCE_DEPRECATED // REMOVE Tim. Or keep //_plugin->dispatcher(_plugin, effIdle, 0, 0, NULL, 0.0f); //#endif if(_guiVisible) { _plugin->dispatcher(_plugin, effEditIdle, 0, 0, NULL, 0.0f); if(_editor) _editor->update(); } } } //--------------------------------------------------------- // nativeGuiVisible //--------------------------------------------------------- bool VstNativeSynthIF::nativeGuiVisible() const { return _guiVisible; } //--------------------------------------------------------- // guiVisible //--------------------------------------------------------- bool VstNativeSynthIF::guiVisible() const { return _gui && _gui->isVisible(); } //--------------------------------------------------------- // showGui //--------------------------------------------------------- void VstNativeSynthIF::showGui(bool v) { if (v) { if (_gui == 0) makeGui(); _gui->show(); } else { if (_gui) _gui->hide(); } } //--------------------------------------------------------- // showGui //--------------------------------------------------------- void VstNativeSynthIF::showNativeGui(bool v) { if(!(_plugin->flags & effFlagsHasEditor)) // || v == nativeGuiVisible()) return; if(v) { if(_editor) { if(!_editor->isVisible()) _editor->show(); _editor->raise(); _editor->activateWindow(); } else { Qt::WindowFlags wflags = Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint; _editor = new MusEGui::VstNativeEditor(NULL, wflags); _editor->open(this); } } else { if(_editor) { delete _editor; //_editor = NULL; // No - done in editorDeleted. } } _guiVisible = v; } //--------------------------------------------------------- // getGeometry //--------------------------------------------------------- void VstNativeSynthIF::getGeometry(int*x, int*y, int*w, int*h) const { if(!_gui) { *x=0;*y=0;*w=0;*h=0; return; } *x = _gui->x(); *y = _gui->y(); *w = _gui->width(); *h = _gui->height(); } //--------------------------------------------------------- // setGeometry //--------------------------------------------------------- void VstNativeSynthIF::setGeometry(int x, int y, int w, int h) { if(!_gui) return; _gui->setGeometry(x, y, w, h); } //--------------------------------------------------------- // editorOpened //--------------------------------------------------------- void VstNativeSynthIF::getNativeGeometry(int*x, int*y, int*w, int*h) const { if(!_editor) { *x=0;*y=0;*w=0;*h=0; return; } *x = _editor->x(); *y = _editor->y(); *w = _editor->width(); *h = _editor->height(); } //--------------------------------------------------------- // editorOpened //--------------------------------------------------------- void VstNativeSynthIF::setNativeGeometry(int x, int y, int w, int h) { if(!_editor) return; _editor->setGeometry(x, y, w, h); } //--------------------------------------------------------- // editorOpened //--------------------------------------------------------- void VstNativeSynthIF::editorOpened() { _guiVisible = true; } //--------------------------------------------------------- // editorClosed //--------------------------------------------------------- void VstNativeSynthIF::editorClosed() { _guiVisible = false; } //--------------------------------------------------------- // editorDeleted //--------------------------------------------------------- void VstNativeSynthIF::editorDeleted() { _editor = NULL; } //--------------------------------------------------------- // receiveEvent //--------------------------------------------------------- MidiPlayEvent VstNativeSynthIF::receiveEvent() { return MidiPlayEvent(); } //--------------------------------------------------------- // hasGui //--------------------------------------------------------- bool VstNativeSynthIF::hasNativeGui() const { return _plugin->flags & effFlagsHasEditor; } //--------------------------------------------------------- // channels //--------------------------------------------------------- int VstNativeSynthIF::channels() const { return _plugin->numOutputs > MAX_CHANNELS ? MAX_CHANNELS : _plugin->numOutputs ; } int VstNativeSynthIF::totalOutChannels() const { return _plugin->numOutputs; } int VstNativeSynthIF::totalInChannels() const { return _plugin->numInputs; } //--------------------------------------------------------- // deactivate3 //--------------------------------------------------------- void VstNativeSynthIF::deactivate3() { if(_editor) { delete _editor; _editor = NULL; _guiVisible = false; } deactivate(); if (_plugin) { _plugin->dispatcher (_plugin, effClose, 0, 0, NULL, 0); _plugin = NULL; } } //--------------------------------------------------------- // queryPrograms //--------------------------------------------------------- void VstNativeSynthIF::queryPrograms() { char buf[256]; programs.clear(); int num_progs = _plugin->numPrograms; int iOldIndex = dispatch(effGetProgram, 0, 0, NULL, 0.0f); bool need_restore = false; for(int prog = 0; prog < num_progs; ++prog) { buf[0] = 0; //#ifndef VST_VESTIGE_SUPPORT // value = category. -1 = regular linear. if(dispatch(effGetProgramNameIndexed, prog, -1, buf, 0.0f) == 0) { //#endif dispatch(effSetProgram, 0, prog, NULL, 0.0f); dispatch(effGetProgramName, 0, 0, buf, 0.0f); need_restore = true; //#ifndef VST_VESTIGE_SUPPORT } //#endif int bankH = (prog >> 14) & 0x7f; int bankL = (prog >> 7) & 0x7f; int patch = prog & 0x7f; VST_Program p; p.name = QString(buf); //p.program = prog & 0x7f; //p.bank = prog << 7; p.program = (bankH << 16) | (bankL << 8) | patch; programs.push_back(p); } // Restore current program. if(need_restore) // && num_progs > 0) { dispatch(effSetProgram, 0, iOldIndex, NULL, 0.0f); fprintf(stderr, "FIXME: VstNativeSynthIF::queryPrograms(): effGetProgramNameIndexed returned 0. Used ugly effSetProgram/effGetProgramName instead\n"); } } //--------------------------------------------------------- // doSelectProgram //--------------------------------------------------------- void VstNativeSynthIF::doSelectProgram(int bankH, int bankL, int prog) { if(!_plugin) return; #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::doSelectProgram bankH:%d bankL:%d prog:%d\n", bankH, bankL, prog); #endif if(bankH == 0xff) bankH = 0; if(bankL == 0xff) bankL = 0; if(prog == 0xff) prog = 0; int p = (bankH << 14) | (bankL << 7) | prog; if(p >= _plugin->numPrograms) { fprintf(stderr, "VstNativeSynthIF::doSelectProgram program:%d out of range\n", p); return; } //for (unsigned short i = 0; i < instances(); ++i) //{ // "host calls this before a new program (effSetProgram) is loaded" #ifndef VST_VESTIGE_SUPPORT //if(dispatch(effBeginSetProgram, 0, 0, NULL, 0.0f) == 1) // TESTED: Usually it did not acknowledge. So IGNORE it. dispatch(effBeginSetProgram, 0, 0, NULL, 0.0f); //{ #endif dispatch(effSetProgram, 0, p, NULL, 0.0f); //dispatch(effSetProgram, 0, prog, NULL, 0.0f); // "host calls this after the new program (effSetProgram) has been loaded" #ifndef VST_VESTIGE_SUPPORT dispatch(effEndSetProgram, 0, 0, NULL, 0.0f); //} //else // fprintf(stderr, "VstNativeSynthIF::doSelectProgram bankH:%d bankL:%d prog:%d Effect did not acknowledge effBeginSetProgram\n", bankH, bankL, prog); #endif //} // TODO: Is this true of VSTs? See the similar section in dssihost.cpp // REMOVE Tim. // "A plugin is permitted to re-write the values of its input control ports when select_program is called. // The host should re-read the input control port values and update its own records appropriately. // (This is the only circumstance in which a DSSI plugin is allowed to modify its own input ports.)" From dssi.h // Need to update the automation value, otherwise it overwrites later with the last automation value. if(id() != -1) { const unsigned long sic = _synth->inControls(); for(unsigned long k = 0; k < sic; ++k) { // We're in the audio thread context: no need to send a message, just modify directly. //synti->setPluginCtrlVal(genACnum(id(), k), _controls[k].val); //synti->setPluginCtrlVal(genACnum(id(), k), _plugin->getParameter(_plugin, k)); const float v = _plugin->getParameter(_plugin, k); _controls[k].val = v; synti->setPluginCtrlVal(genACnum(id(), k), v); } } // // Reset parameters default value... // TODO ? // AEffect *pVstEffect = vst_effect(0); // if (pVstEffect) { // const qtractorPlugin::Params& params = qtractorPlugin::params(); // qtractorPlugin::Params::ConstIterator param = params.constBegin(); // for ( ; param != params.constEnd(); ++param) { // qtractorPluginParam *pParam = param.value(); // float *pfValue = pParam->subject()->data(); // *pfValue = pVstEffect->getParameter(pVstEffect, pParam->index()); // pParam->setDefaultValue(*pfValue); // } // } } //--------------------------------------------------------- // getPatchName //--------------------------------------------------------- QString VstNativeSynthIF::getPatchName(int /*chan*/, int prog, bool /*drum*/) const { unsigned long program = prog & 0x7f; unsigned long lbank = (prog >> 8) & 0xff; unsigned long hbank = (prog >> 16) & 0xff; if (lbank == 0xff) lbank = 0; if (hbank == 0xff) hbank = 0; unsigned long p = (hbank << 16) | (lbank << 8) | program; unsigned long vp = (hbank << 14) | (lbank << 7) | program; //if((int)vp < _plugin->numPrograms) if(vp < programs.size()) { for(std::vector::const_iterator i = programs.begin(); i != programs.end(); ++i) { if(i->program == p) return i->name; } } return "?"; } //--------------------------------------------------------- // populatePatchPopup //--------------------------------------------------------- void VstNativeSynthIF::populatePatchPopup(MusEGui::PopupMenu* menu, int /*chan*/, 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 << 7) + prog; QAction *act = menu->addAction(i->name); //act->setData(id); act->setData(prog); } } //--------------------------------------------------------- // getParameter //--------------------------------------------------------- float VstNativeSynthIF::getParameter(unsigned long idx) const { if(idx >= _synth->inControls()) { fprintf(stderr, "VstNativeSynthIF::getParameter param number %lu out of range of ports:%lu\n", idx, _synth->inControls()); return 0.0; } return _plugin->getParameter(_plugin, idx); } //--------------------------------------------------------- // setParameter //--------------------------------------------------------- void VstNativeSynthIF::setParameter(unsigned long idx, float value) { addScheduledControlEvent(idx, value, MusEGlobal::audio->curFrame()); } //--------------------------------------------------------- // guiAutomationBegin //--------------------------------------------------------- void VstNativeSynthIF::guiAutomationBegin(unsigned long param_idx) { _gw[param_idx].pressed = true; AudioTrack* t = track(); int plug_id = id(); if(t && plug_id != -1) { plug_id = genACnum(plug_id, param_idx); //if(params[param].type == GuiParam::GUI_SLIDER) //{ //double val = ((Slider*)params[param].actuator)->value(); float val = param(param_idx); // FIXME TODO: //if (LADSPA_IS_HINT_LOGARITHMIC(params[param].hint)) // val = pow(10.0, val/20.0); //else if (LADSPA_IS_HINT_INTEGER(params[param].hint)) // val = rint(val); //plugin->setParam(param, val); //((DoubleLabel*)params[param].label)->setValue(val); //if(t) //{ t->startAutoRecord(plug_id, val); t->setPluginCtrlVal(plug_id, val); //} //} // else if(params[param].type == GuiParam::GUI_SWITCH) // { // float val = (float)((CheckBox*)params[param].actuator)->isChecked(); // plugin->setParam(param, val); // // //if(t) // //{ // t->startAutoRecord(plug_id, val); // t->setPluginCtrlVal(plug_id, val); // //} // } } enableController(param_idx, false); } //--------------------------------------------------------- // guiAutomationEnd //--------------------------------------------------------- void VstNativeSynthIF::guiAutomationEnd(unsigned long param_idx) { AutomationType at = AUTO_OFF; AudioTrack* t = track(); if(t) at = t->automationType(); int plug_id = id(); if(t && plug_id != -1) { plug_id = genACnum(plug_id, param_idx); //if(params[param].type == GuiParam::GUI_SLIDER) //{ //double val = ((Slider*)params[param].actuator)->value(); float val = param(param_idx); // FIXME TODO: //if (LADSPA_IS_HINT_LOGARITHMIC(params[param].hint)) // val = pow(10.0, val/20.0); //else if (LADSPA_IS_HINT_INTEGER(params[param].hint)) // val = rint(val); t->stopAutoRecord(plug_id, val); //} } // Special for switch - don't enable controller until transport stopped. if ((at == AUTO_OFF) || (at == AUTO_TOUCH)) // && (params[param].type != GuiParam::GUI_SWITCH || // FIXME TODO // !MusEGlobal::audio->isPlaying()) ) ) enableController(param_idx, true); _gw[param_idx].pressed = false; } //--------------------------------------------------------- // guiControlChanged //--------------------------------------------------------- int VstNativeSynthIF::guiControlChanged(unsigned long param_idx, float value) { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::guiControlChanged received oscControl port:%lu val:%f\n", param_idx, value); #endif if(param_idx >= _synth->inControls()) { fprintf(stderr, "VstNativeSynthIF::guiControlChanged: port number:%lu is out of range of index list size:%lu\n", param_idx, _synth->inControls()); return 0; } // Record automation: // Take care of this immediately rather than in the fifo processing. if(id() != -1) { unsigned long pid = genACnum(id(), param_idx); synti->recordAutomation(pid, value); } // Schedules a timed control change: ControlEvent ce; ce.unique = false; // Not used for native vst. ce.fromGui = true; // It came from the plugin's own GUI. ce.idx = param_idx; ce.value = value; // Don't use timestamp(), because it's circular, which is making it impossible to deal // with 'modulo' events which slip in 'under the wire' before processing the ring buffers. ce.frame = MusEGlobal::audio->curFrame(); if(_controlFifo.put(ce)) fprintf(stderr, "VstNativeSynthIF::guiControlChanged: fifo overflow: in control number:%lu\n", param_idx); enableController(param_idx, false); return 0; } //--------------------------------------------------------- // write //--------------------------------------------------------- void VstNativeSynthIF::write(int level, Xml& xml) const { #ifndef VST_VESTIGE_SUPPORT if(_synth->hasChunks()) { //--------------------------------------------- // dump current state of synth //--------------------------------------------- fprintf(stderr, "%s: commencing chunk data dump, plugin api version=%d\n", name().toLatin1().constData(), _synth->vstVersion()); unsigned long len = 0; void* p = 0; len = dispatch(effGetChunk, 0, 0, &p, 0.0); // index 0: is bank 1: is program if (len) { xml.tag(level++, "midistate version=\"%d\"", SYNTH_MIDI_STATE_SAVE_VERSION); xml.nput(level++, "\n", len+10); xml.nput(level, ""); xml.nput("%02x %02x ", (char)MUSE_SYNTH_SYSEX_MFG_ID, (char)VST_NATIVE_SYNTH_UNIQUE_ID); // Wrap in a proper header xml.nput("56 53 54 53 41 56 45 "); // embed a save marker "string 'VSTSAVE' xml.nput("%02x ", (char)0); // No flags yet, only uncompressed supported for now. TODO for (unsigned long int i = 0; i < len; ++i) { if (i && (((i+10) % 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"); } } #else fprintf(stderr, "support for vst chunks not compiled in!\n"); #endif //--------------------------------------------- // dump current state of synth //--------------------------------------------- int params = _plugin->numParams; for (int i = 0; i < params; ++i) { float f = _plugin->getParameter(_plugin, i); xml.floatTag(level, "param", f); } } //--------------------------------------------------------- // getData //--------------------------------------------------------- void VstNativeSynthIF::setVstEvent(VstMidiEvent* event, int a, int b, int c, int d) { event->type = kVstMidiType; event->byteSize = 24; event->deltaFrames = 0; event->flags = 0; event->detune = 0; event->noteLength = 0; event->noteOffset = 0; event->reserved1 = 0; event->reserved2 = 0; event->noteOffVelocity = 0; event->midiData[0] = a; event->midiData[1] = b; event->midiData[2] = c; event->midiData[3] = d; } //--------------------------------------------------------- // getData //--------------------------------------------------------- bool VstNativeSynthIF::processEvent(const MidiPlayEvent& e, VstMidiEvent* event) { int type = e.type(); int chn = e.channel(); int a = e.dataA(); int b = e.dataB(); #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event type:%d chn:%d a:%d b:%d\n", type, chn, a, b); #endif switch(type) { case ME_NOTEON: #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_NOTEON\n"); #endif setVstEvent(event, (type | chn) & 0xff, a & 0x7f, b & 0x7f); break; case ME_NOTEOFF: #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_NOTEOFF\n"); #endif setVstEvent(event, (type | chn) & 0xff, a & 0x7f, 0); break; case ME_PROGRAM: { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_PROGRAM\n"); #endif int bankH = (a >> 16) & 0xff; int bankL = (a >> 8) & 0xff; int prog = a & 0xff; synti->_curBankH = bankH; synti->_curBankL = bankL; synti->_curProgram = prog; doSelectProgram(bankH, bankL, prog); return false; // Event pointer not filled. Return false. } break; case ME_CONTROLLER: { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_CONTROLLER\n"); #endif if((a == 0) || (a == 32)) return false; if(a == CTRL_PROGRAM) { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_CONTROLLER, dataA is CTRL_PROGRAM\n"); #endif int bankH = (b >> 16) & 0xff; int bankL = (b >> 8) & 0xff; int prog = b & 0xff; synti->_curBankH = bankH; synti->_curBankL = bankL; synti->_curProgram = prog; doSelectProgram(bankH, bankL, prog); return false; // Event pointer not filled. Return false. } if(a == CTRL_PITCH) { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_CONTROLLER, dataA is CTRL_PITCH\n"); #endif int v = b + 8192; setVstEvent(event, (type | chn) & 0xff, v & 0x7f, (v >> 7) & 0x7f); return true; } if(a == CTRL_AFTERTOUCH) { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_CONTROLLER, dataA is CTRL_AFTERTOUCH\n"); #endif setVstEvent(event, (type | chn) & 0xff, b & 0x7f); return true; } if((a | 0xff) == CTRL_POLYAFTER) { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_CONTROLLER, dataA is CTRL_POLYAFTER\n"); #endif setVstEvent(event, (type | chn) & 0xff, a & 0x7f, b & 0x7f); return true; } #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_CONTROLLER, dataA is:%d\n", a); #endif // Regular controller. Pass it on. setVstEvent(event, (type | chn) & 0xff, a & 0x7f, b & 0x7f); return true; // REMOVE Tim. Or keep. TODO For native vsts? Or not... // // 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()) // { // int ctlnum = a; // if(midiControllerType(a) != MidiController::Controller7) // return false; // Event pointer not filled. Return false. // else // { // #ifdef VST_NATIVE_DEBUG // fprintf(stderr, "VstNativeSynthIF::processEvent non-ladspa midi event is Controller7. Current dataA:%d\n", a); // #endif // a &= 0x7f; // ctlnum = DSSI_CC_NUMBER(ctlnum); // } // // // Fill the event. // #ifdef VST_NATIVE_DEBUG // fprintf(stderr, "VstNativeSynthIF::processEvent non-ladspa filling midi event chn:%d dataA:%d dataB:%d\n", chn, a, b); // #endif // snd_seq_ev_clear(event); // event->queue = SND_SEQ_QUEUE_DIRECT; // snd_seq_ev_set_controller(event, chn, a, b); // return true; // } // // unsigned long k = ip->second; // unsigned long i = controls[k].idx; // 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; // // // Simple but flawed solution: Start them at 0x60000 + 0x2000 = 0x62000. Max NRPN number is 0x3fff. // ctlnum = k + (CTRL_NRPN14_OFFSET + 0x2000); // } // else // { // #ifdef VST_NATIVE_DEBUG // fprintf(stderr, "VstNativeSynthIF::processEvent plugin requests DSSI-style ctlnum:%x(h) %d(d) be mapped to control port:%lu...\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 VST_NATIVE_DEBUG // fprintf(stderr, "VstNativeSynthIF::processEvent is CC ctlnum:%d\n", ctlnum); // #endif // // #ifdef VST_NATIVE_DEBUG // if(DSSI_IS_NRPN(ctlnum)) // fprintf(stderr, "VstNativeSynthIF::processEvent is also NRPN control. Using CC.\n"); // #endif // } // else // if(DSSI_IS_NRPN(ctlnum)) // { // ctlnum = DSSI_NRPN_NUMBER(c) + CTRL_NRPN14_OFFSET; // // #ifdef VST_NATIVE_DEBUG // fprintf(stderr, "VstNativeSynthIF::processEvent is NRPN ctlnum:%x(h) %d(d)\n", ctlnum, ctlnum); // #endif // } // // } // // float val = midi2LadspaValue(ld, i, ctlnum, b); // // #ifdef VST_NATIVE_DEBUG // fprintf(stderr, "VstNativeSynthIF::processEvent control port:%lu port:%lu 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; // // // Need to update the automation value, otherwise it overwrites later with the last automation value. // if(id() != -1) // // We're in the audio thread context: no need to send a message, just modify directly. // synti->setPluginCtrlVal(genACnum(id(), k), val); // // // Since we absorbed the message as a ladspa control change, return false - the event is not filled. // return false; } break; case ME_PITCHBEND: { int v = a + 8192; setVstEvent(event, (type | chn) & 0xff, v & 0x7f, (v >> 7) & 0x7f); } break; case ME_AFTERTOUCH: setVstEvent(event, (type | chn) & 0xff, a & 0x7f); break; case ME_POLYAFTER: setVstEvent(event, (type | chn) & 0xff, a & 0x7f, b & 0x7f); break; case ME_SYSEX: { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::processEvent midi event is ME_SYSEX\n"); #endif const unsigned char* data = e.data(); if(e.len() >= 2) { if(data[0] == MUSE_SYNTH_SYSEX_MFG_ID) { if(data[1] == VST_NATIVE_SYNTH_UNIQUE_ID) { //if(e.len() >= 9) if(e.len() >= 10) { if (QString((const char*)(data + 2)).startsWith("VSTSAVE")) { if(_synth->hasChunks()) { #ifndef VST_VESTIGE_SUPPORT int chunk_flags = data[9]; if(chunk_flags & VST_NATIVE_CHUNK_FLAG_COMPRESSED) fprintf(stderr, "chunk flags:%x compressed chunks not supported yet.\n", chunk_flags); else { fprintf(stderr, "%s: loading chunk from sysex!\n", name().toLatin1().constData()); // 10 = 2 bytes header + "VSTSAVE" + 1 byte flags (compression etc) dispatch(effSetChunk, 0, e.len()-10, (void*)(data+10), 0.0); // index 0: is bank 1: is program } #else fprintf(stderr, "support for vst chunks not compiled in!\n"); #endif } // Event not filled. return false; } } } } } // DELETETHIS, 50 clean it up or fix it? /* // 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 VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::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. fprintf(stderr, "VstNativeSynthIF::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) fprintf(stderr, "VstNativeSynthIF::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) fprintf(stderr, "VstNativeSynthIF::processEvent Warning: PARAMSAVE number of floats:%lu != number of controls:%lu\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 { // FIXME TODO: Sysex support. return false; //int len = e.len(); //char ca[len + 2]; //ca[0] = 0xF0; // FIXME Stack overflow with big dumps. Use std::vector or something else. //memcpy(ca + 1, (const char*)e.data(), len); //ca[len + 1] = 0xF7; //len += 2; // NOTE: There is a limit on the size of a sysex. Got this: // "VstNativeSynthIF::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_clear(event); // event->queue = SND_SEQ_QUEUE_DIRECT; // snd_seq_ev_set_sysex(event, len, // (unsigned char*)ca); } } break; default: if(MusEGlobal::debugMsg) fprintf(stderr, "VstNativeSynthIF::processEvent midi event unknown type:%d\n", e.type()); // Event not filled. return false; break; } return true; } //--------------------------------------------------------- // getData // If ports is 0, just process controllers only, not audio (do not 'run'). //--------------------------------------------------------- iMPEvent VstNativeSynthIF::getData(MidiPort* /*mp*/, MPEventList* el, iMPEvent start_event, unsigned pos, int ports, unsigned nframes, float** buffer) { // We may not be using ev_buf_sz all at once - this will be just the maximum. const unsigned long ev_buf_sz = el->size() + synti->eventFifo.getSize(); VstMidiEvent events[ev_buf_sz]; char evbuf[sizeof(VstMidiEvent*) * ev_buf_sz + sizeof(VstEvents)]; VstEvents *vst_events = (VstEvents*)evbuf; vst_events->numEvents = 0; vst_events->reserved = 0; const int frameOffset = MusEGlobal::audio->getFrameOffset(); const unsigned long syncFrame = MusEGlobal::audio->curSyncFrame(); #ifdef VST_NATIVE_DEBUG_PROCESS fprintf(stderr, "VstNativeSynthIF::getData: pos:%u ports:%d nframes:%u syncFrame:%lu ev_buf_sz:%lu\n", pos, ports, nframes, syncFrame, ev_buf_sz); #endif const unsigned long nop = ((unsigned long) ports) > _synth->outPorts() ? _synth->outPorts() : ((unsigned long) ports); unsigned long sample = 0; // I read that some plugins do not like changing sample run length (compressors etc). Some are OK with it. // TODO: In order to support this effectively, must be user selectable, per-plugin. ENABLED for now. const bool usefixedrate = false; // For now, the fixed size is clamped to the audio buffer size. // TODO: We could later add slower processing over several cycles - // so that users can select a small audio period but a larger control period. const unsigned long min_per = (usefixedrate || MusEGlobal::config.minControlProcessPeriod > nframes) ? nframes : MusEGlobal::config.minControlProcessPeriod; const unsigned long min_per_mask = min_per-1; // min_per must be power of 2 AudioTrack* atrack = track(); const AutomationType at = atrack->automationType(); const bool no_auto = !MusEGlobal::automation || at == AUTO_OFF; const unsigned long in_ctrls = _synth->inControls(); CtrlListList* cll = atrack->controller(); ciCtrlList icl_first; const int plug_id = id(); if(plug_id != -1 && ports != 0) // Don't bother if not 'running'. icl_first = cll->lower_bound(genACnum(plug_id, 0)); // Inform the host callback we are in the audio thread. _inProcess = true; #ifdef VST_NATIVE_DEBUG_PROCESS fprintf(stderr, "VstNativeSynthIF::getData: Handling inputs...\n"); #endif // Handle inputs... if(ports != 0) // Don't bother if not 'running'. { if(!atrack->noInRoute()) { RouteList* irl = atrack->inRoutes(); iRoute i = irl->begin(); if(!i->track->isMidiTrack()) { const int ch = i->channel == -1 ? 0 : i->channel; const int remch = i->remoteChannel == -1 ? 0 : i->remoteChannel; const int chs = i->channels == -1 ? 0 : i->channels; if((unsigned)ch < _synth->inPorts() && (unsigned)(ch + chs) <= _synth->inPorts()) { const int h = remch + chs; for(int j = remch; j < h; ++j) _iUsedIdx[j] = true; ((AudioTrack*)i->track)->copyData(pos, chs, ch, -1, nframes, &_audioInBuffers[remch]); } } ++i; for(; i != irl->end(); ++i) { if(i->track->isMidiTrack()) continue; const int ch = i->channel == -1 ? 0 : i->channel; const int remch = i->remoteChannel == -1 ? 0 : i->remoteChannel; const int chs = i->channels == -1 ? 0 : i->channels; if((unsigned)ch < _synth->inPorts() && (unsigned)(ch + chs) <= _synth->inPorts()) { const bool u1 = _iUsedIdx[remch]; if(chs >= 2) { const bool u2 = _iUsedIdx[remch + 1]; if(u1 && u2) ((AudioTrack*)i->track)->addData(pos, chs, ch, -1, nframes, &_audioInBuffers[remch]); else if(!u1 && !u2) ((AudioTrack*)i->track)->copyData(pos, chs, ch, -1, nframes, &_audioInBuffers[remch]); else { if(u1) ((AudioTrack*)i->track)->addData(pos, 1, ch, 1, nframes, &_audioInBuffers[remch]); else ((AudioTrack*)i->track)->copyData(pos, 1, ch, 1, nframes, &_audioInBuffers[remch]); if(u2) ((AudioTrack*)i->track)->addData(pos, 1, ch + 1, 1, nframes, &_audioInBuffers[remch + 1]); else ((AudioTrack*)i->track)->copyData(pos, 1, ch + 1, 1, nframes, &_audioInBuffers[remch + 1]); } } else { if(u1) ((AudioTrack*)i->track)->addData(pos, 1, ch, -1, nframes, &_audioInBuffers[remch]); else ((AudioTrack*)i->track)->copyData(pos, 1, ch, -1, nframes, &_audioInBuffers[remch]); } const int h = remch + chs; for(int j = remch; j < h; ++j) _iUsedIdx[j] = true; } } } } #ifdef VST_NATIVE_DEBUG_PROCESS fprintf(stderr, "VstNativeSynthIF::getData: Processing automation control values...\n"); #endif int cur_slice = 0; while(sample < nframes) { unsigned long nsamp = nframes - sample; const unsigned long slice_frame = pos + sample; // // Process automation control values, while also determining the maximum acceptable // size of this run. Further processing, from FIFOs for example, can lower the size // from there, but this section determines where the next highest maximum frame // absolutely needs to be for smooth playback of the controller value stream... // if(ports != 0) // Don't bother if not 'running'. { ciCtrlList icl = icl_first; for(unsigned long k = 0; k < in_ctrls; ++k) { CtrlList* cl = (cll && plug_id != -1 && icl != cll->end()) ? icl->second : NULL; CtrlInterpolate& ci = _controls[k].interp; // Always refresh the interpolate struct at first, since things may have changed. // Or if the frame is outside of the interpolate range - and eStop is not true. // FIXME TODO: Be sure these comparisons are correct. if(cur_slice == 0 || (!ci.eStop && MusEGlobal::audio->isPlaying() && (slice_frame < (unsigned long)ci.sFrame || (ci.eFrame != -1 && slice_frame >= (unsigned long)ci.eFrame)) ) ) { if(cl && plug_id != -1 && (unsigned long)cl->id() == genACnum(plug_id, k)) { cl->getInterpolation(slice_frame, no_auto || !_controls[k].enCtrl, &ci); if(icl != cll->end()) ++icl; } else { // No matching controller, or end. Just copy the current value into the interpolator. // Keep the current icl iterator, because since they are sorted by frames, // if the IDs didn't match it means we can just let k catch up with icl. ci.sFrame = 0; ci.eFrame = -1; ci.sVal = _controls[k].val; ci.eVal = ci.sVal; ci.doInterp = false; ci.eStop = false; } } else { if(ci.eStop && ci.eFrame != -1 && slice_frame >= (unsigned long)ci.eFrame) // FIXME TODO: Get that comparison right. { // Clear the stop condition and set up the interp struct appropriately as an endless value. ci.sFrame = 0; //ci->eFrame; ci.eFrame = -1; ci.sVal = ci.eVal; ci.doInterp = false; ci.eStop = false; } if(cl && cll && icl != cll->end()) ++icl; } if(!usefixedrate && MusEGlobal::audio->isPlaying()) { unsigned long samps = nsamp; if(ci.eFrame != -1) samps = (unsigned long)ci.eFrame - slice_frame; if(!ci.doInterp && samps > min_per) { samps &= ~min_per_mask; if((samps & min_per_mask) != 0) samps += min_per; } else samps = min_per; if(samps < nsamp) nsamp = samps; } float new_val; if(ci.doInterp && cl) new_val = cl->interpolate(MusEGlobal::audio->isPlaying() ? slice_frame : pos, ci); else new_val = ci.sVal; if(_controls[k].val != new_val) { _controls[k].val = new_val; #ifndef VST_VESTIGE_SUPPORT if(dispatch(effCanBeAutomated, k, 0, NULL, 0.0f) == 1) { #endif if(_plugin->getParameter(_plugin, k) != new_val) _plugin->setParameter(_plugin, k, new_val); #ifndef VST_VESTIGE_SUPPORT } #ifdef VST_NATIVE_DEBUG else fprintf(stderr, "VstNativeSynthIF::getData %s parameter:%lu cannot be automated\n", name().toLatin1().constData(), k); #endif #endif } #ifdef VST_NATIVE_DEBUG_PROCESS fprintf(stderr, "VstNativeSynthIF::getData k:%lu sample:%lu frame:%lu ci.eFrame:%d nsamp:%lu \n", k, sample, frame, ci.eFrame, nsamp); #endif } } #ifdef VST_NATIVE_DEBUG_PROCESS fprintf(stderr, "VstNativeSynthIF::getData sample:%lu nsamp:%lu\n", sample, nsamp); #endif bool found = false; unsigned long frame = 0; unsigned long index = 0; unsigned long evframe; // Get all control ring buffer items valid for this time period... while(!_controlFifo.isEmpty()) { ControlEvent v = _controlFifo.peek(); // The events happened in the last period or even before that. Shift into this period with + n. This will sync with audio. // If the events happened even before current frame - n, make sure they are counted immediately as zero-frame. evframe = (syncFrame > v.frame + nframes) ? 0 : v.frame - syncFrame + nframes; #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::getData found:%d evframe:%lu frame:%lu event frame:%lu idx:%lu val:%f unique:%d\n", found, evframe, frame, v.frame, v.idx, v.value, v.unique); #endif // Protection. Observed this condition. Why? Supposed to be linear timestamps. if(found && evframe < frame) { fprintf(stderr, "VstNativeSynthIF::getData *** Error: evframe:%lu < frame:%lu event: frame:%lu idx:%lu val:%f unique:%d\n", evframe, frame, v.frame, v.idx, v.value, v.unique); // No choice but to ignore it. _controlFifo.remove(); // Done with the ring buffer's item. Remove it. continue; } if(evframe >= nframes // Next events are for a later period. || (!usefixedrate && !found && !v.unique && (evframe - sample >= nsamp)) // Next events are for a later run in this period. (Autom took prio.) || (found && !v.unique && (evframe - sample >= min_per)) // Eat up events within minimum slice - they're too close. || (usefixedrate && found && v.unique && v.idx == index)) // Special for dssi-vst: Fixed rate and must reply to all. break; _controlFifo.remove(); // Done with the ring buffer's item. Remove it. if(v.idx >= in_ctrls) // Sanity check. break; found = true; frame = evframe; index = v.idx; if(ports == 0) // Don't bother if not 'running'. { _controls[v.idx].val = v.value; // Might as well at least update these. // #ifndef VST_VESTIGE_SUPPORT // if(dispatch(effCanBeAutomated, v.idx, 0, NULL, 0.0f) == 1) // { // #endif // if(v.value != _plugin->getParameter(_plugin, v.idx)) // _plugin->setParameter(_plugin, v.idx, v.value); // #ifndef VST_VESTIGE_SUPPORT // } // #ifdef VST_NATIVE_DEBUG // else // fprintf(stderr, "VstNativeSynthIF::getData %s parameter:%lu cannot be automated\n", name().toLatin1().constData(), v.idx); // #endif // #endif } else { CtrlInterpolate* ci = &_controls[v.idx].interp; // Tell it to stop the current ramp at this frame, when it does stop, set this value: ci->eFrame = frame; ci->eVal = v.value; ci->eStop = true; } // Need to update the automation value, otherwise it overwrites later with the last automation value. if(plug_id != -1) synti->setPluginCtrlVal(genACnum(plug_id, v.idx), v.value); } if(found && !usefixedrate) // If a control FIFO item was found, takes priority over automation controller stream. nsamp = frame - sample; if(sample + nsamp > nframes) // Safety check. nsamp = nframes - sample; // TODO: Don't allow zero-length runs. This could/should be checked in the control loop instead. // Note this means it is still possible to get stuck in the top loop (at least for a while). if(nsamp != 0) { unsigned long nevents = 0; if(ports != 0) // Don't bother if not 'running'. { // Process event list events... for(; start_event != el->end(); ++start_event) { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::getData eventlist event time:%d pos:%u sample:%lu nsamp:%lu frameOffset:%d\n", start_event->time(), pos, sample, nsamp, frameOffset); #endif if(start_event->time() >= (pos + sample + nsamp + frameOffset)) // frameOffset? Test again... { #ifdef VST_NATIVE_DEBUG fprintf(stderr, " event is for future:%lu, breaking loop now\n", start_event->time() - frameOffset - pos - sample); #endif break; } // 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 = &MusEGlobal::midiPorts[synti->midiPort()]; if(start_event->type() == ME_CONTROLLER) { int da = start_event->dataA(); int db = start_event->dataB(); db = mp->limitValToInstrCtlRange(da, db); if(!mp->setHwCtrlState(start_event->channel(), da, db)) continue; } else if(start_event->type() == ME_PITCHBEND) { int da = mp->limitValToInstrCtlRange(CTRL_PITCH, start_event->dataA()); if(!mp->setHwCtrlState(start_event->channel(), CTRL_PITCH, da)) continue; } else if(start_event->type() == ME_AFTERTOUCH) { int da = mp->limitValToInstrCtlRange(CTRL_AFTERTOUCH, start_event->dataA()); if(!mp->setHwCtrlState(start_event->channel(), CTRL_AFTERTOUCH, da)) continue; } else if(start_event->type() == ME_POLYAFTER) { int ctl = (CTRL_POLYAFTER & ~0xff) | (start_event->dataA() & 0x7f); int db = mp->limitValToInstrCtlRange(ctl, start_event->dataB()); if(!mp->setHwCtrlState(start_event->channel(), ctl , db)) continue; } else if(start_event->type() == ME_PROGRAM) { if(!mp->setHwCtrlState(start_event->channel(), CTRL_PROGRAM, start_event->dataA())) continue; } } // Returns false if the event was not filled. It was handled, but some other way. if(processEvent(*start_event, &events[nevents])) { // Time-stamp the event. int ft = start_event->time() - frameOffset - pos - sample; if(ft < 0) ft = 0; if (ft >= int(nsamp)) { fprintf(stderr, "VstNativeSynthIF::getData: eventlist event time:%d out of range. pos:%d offset:%d ft:%d sample:%lu nsamp:%lu\n", start_event->time(), pos, frameOffset, ft, sample, nsamp); ft = nsamp - 1; } #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::getData eventlist: ft:%d current nevents:%lu\n", ft, nevents); #endif vst_events->events[nevents] = (VstEvent*)&events[nevents]; events[nevents].deltaFrames = ft; ++nevents; } } } // Now process putEvent events... while(!synti->eventFifo.isEmpty()) { MidiPlayEvent e = synti->eventFifo.peek(); #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::getData eventFifo event time:%d\n", e.time()); #endif if(e.time() >= (pos + sample + nsamp + frameOffset)) break; synti->eventFifo.remove(); // Done with ring buffer's event. Remove it. if(ports != 0) // Don't bother if not 'running'. { // Returns false if the event was not filled. It was handled, but some other way. if(processEvent(e, &events[nevents])) { // Time-stamp the event. int ft = e.time() - frameOffset - pos - sample; if(ft < 0) ft = 0; if (ft >= int(nsamp)) { fprintf(stderr, "VstNativeSynthIF::getData: eventFifo event time:%d out of range. pos:%d offset:%d ft:%d sample:%lu nsamp:%lu\n", e.time(), pos, frameOffset, ft, sample, nsamp); ft = nsamp - 1; } vst_events->events[nevents] = (VstEvent*)&events[nevents]; events[nevents].deltaFrames = ft; ++nevents; } } } #ifdef VST_NATIVE_DEBUG_PROCESS fprintf(stderr, "VstNativeSynthIF::getData: Connecting and running. sample:%lu nsamp:%lu nevents:%lu\n", sample, nsamp, nevents); #endif if(ports != 0) // Don't bother if not 'running'. { // Set the events pointer. if(nevents > 0) { vst_events->numEvents = nevents; dispatch(effProcessEvents, 0, 0, vst_events, 0.0f); } float* in_bufs[_synth->inPorts()]; float* out_bufs[_synth->outPorts()]; unsigned long k = 0; // Connect the given buffers directly to the ports, up to a max of synth ports. for(; k < nop; ++k) out_bufs[k] = buffer[k] + sample; // Connect the remaining ports to some local buffers (not used yet). const unsigned long op =_synth->outPorts(); for(; k < op; ++k) out_bufs[k] = _audioOutBuffers[k] + sample; // Connect all inputs either to some local buffers, or a silence buffer. const unsigned long ip =_synth->inPorts(); for(k = 0; k < ip; ++k) { if(_iUsedIdx[k]) { _iUsedIdx[k] = false; // Reset in_bufs[k] = _audioInBuffers[k] + sample; } else in_bufs[k] = _audioInSilenceBuf + sample; } // Run the synth for a period of time. This processes events and gets/fills our local buffers... if((_plugin->flags & effFlagsCanReplacing) && _plugin->processReplacing) { _plugin->processReplacing(_plugin, in_bufs, out_bufs, nsamp); } } sample += nsamp; } ++cur_slice; // Slice is done. Moving on to any next slice now... } // Inform the host callback we will be no longer in the audio thread. _inProcess = false; return start_event; } //--------------------------------------------------------- // putEvent //--------------------------------------------------------- bool VstNativeSynthIF::putEvent(const MidiPlayEvent& ev) { #ifdef VST_NATIVE_DEBUG fprintf(stderr, "VstNativeSynthIF::putEvent midi event time:%d chn:%d a:%d b:%d\n", ev.time(), ev.channel(), ev.dataA(), ev.dataB()); #endif if (MusEGlobal::midiOutputTrace) ev.dump(); return synti->eventFifo.put(ev); } //-------------------------------- // Methods for PluginIBase: //-------------------------------- unsigned long VstNativeSynthIF::pluginID() { return (_plugin) ? _plugin->uniqueID : 0; } int VstNativeSynthIF::id() { return MAX_PLUGINS; } // Set for special block reserved for synth. QString VstNativeSynthIF::pluginLabel() const { return _synth ? QString(_synth->name()) : QString(); } // FIXME Maybe wrong QString VstNativeSynthIF::lib() const { return _synth ? _synth->completeBaseName() : QString(); } QString VstNativeSynthIF::dirPath() const { return _synth ? _synth->absolutePath() : QString(); } QString VstNativeSynthIF::fileName() const { return _synth ? _synth->fileName() : QString(); } void VstNativeSynthIF::enableController(unsigned long i, bool v) { _controls[i].enCtrl = v; } bool VstNativeSynthIF::controllerEnabled(unsigned long i) const { return _controls[i].enCtrl;} void VstNativeSynthIF::enableAllControllers(bool v) { if(!_synth) return; const unsigned long sic = _synth->inControls(); for(unsigned long i = 0; i < sic; ++i) _controls[i].enCtrl = v; } void VstNativeSynthIF::updateControllers() { } void VstNativeSynthIF::activate() { //for (unsigned short i = 0; i < instances(); ++i) { // dispatch(i, effMainsChanged, 0, 1, NULL, 0.0f); dispatch(effMainsChanged, 0, 1, NULL, 0.0f); #ifndef VST_VESTIGE_SUPPORT //dispatch(i, effStartProcess, 0, 0, NULL, 0.0f); dispatch(effStartProcess, 0, 0, NULL, 0.0f); #endif //} // REMOVE Tim. Or keep? From PluginI::activate(). // if (initControlValues) { // for (unsigned long i = 0; i < controlPorts; ++i) { // controls[i].val = controls[i].tmpVal; // } // } // else { // // get initial control values from plugin // for (unsigned long i = 0; i < controlPorts; ++i) { // controls[i].tmpVal = controls[i].val; // } // } _active = true; } void VstNativeSynthIF::deactivate() { _active = false; //for (unsigned short i = 0; i < instances(); ++i) { #ifndef VST_VESTIGE_SUPPORT //dispatch(i, effStopProcess, 0, 0, NULL, 0.0f); dispatch(effStopProcess, 0, 0, NULL, 0.0f); #endif //dispatch(i, effMainsChanged, 0, 0, NULL, 0.0f); dispatch(effMainsChanged, 0, 0, NULL, 0.0f); //} } unsigned long VstNativeSynthIF::parameters() const { return _synth ? _synth->inControls() : 0; } unsigned long VstNativeSynthIF::parametersOut() const { return 0; } void VstNativeSynthIF::setParam(unsigned long i, float val) { setParameter(i, val); } float VstNativeSynthIF::param(unsigned long i) const { return getParameter(i); } float VstNativeSynthIF::paramOut(unsigned long) const { return 0.0; } const char* VstNativeSynthIF::paramName(unsigned long i) { if(!_plugin) return 0; static char buf[256]; buf[0] = 0; dispatch(effGetParamName, i, 0, buf, 0); return buf; } const char* VstNativeSynthIF::paramOutName(unsigned long) { return 0; } LADSPA_PortRangeHint VstNativeSynthIF::range(unsigned long /*i*/) { LADSPA_PortRangeHint h; // FIXME TODO: h.HintDescriptor = 0; h.LowerBound = 0.0; h.UpperBound = 1.0; return h; } LADSPA_PortRangeHint VstNativeSynthIF::rangeOut(unsigned long) { // There are no output controls. LADSPA_PortRangeHint h; h.HintDescriptor = 0; h.LowerBound = 0.0; h.UpperBound = 1.0; return h; } // FIXME TODO: CtrlValueType VstNativeSynthIF::ctrlValueType(unsigned long /*i*/) const { return VAL_LINEAR; } CtrlList::Mode VstNativeSynthIF::ctrlMode(unsigned long /*i*/) const { return CtrlList::INTERPOLATE; }; } // namespace MusECore #else // VST_NATIVE_SUPPORT namespace MusECore { void initVST_Native() {} } // namespace MusECore #endif