diff options
author | Tim E. Real <termtech@rogers.com> | 2012-12-07 07:41:24 +0000 |
---|---|---|
committer | Tim E. Real <termtech@rogers.com> | 2012-12-07 07:41:24 +0000 |
commit | 0a919a7b36ee4b58e5ce3628a2d8b97bf751d2c4 (patch) | |
tree | 335fccddc229d2d0299095dfe46daae614188c79 /muse2/muse | |
parent | a9cef6554f73892b6c7ff6a7a44d8e72f06aa16f (diff) |
Feature: Native VST instruments support. PLEASE SEE ChangeLog.
Diffstat (limited to 'muse2/muse')
-rw-r--r-- | muse2/muse/CMakeLists.txt | 1 | ||||
-rw-r--r-- | muse2/muse/arranger/tlist.cpp | 14 | ||||
-rw-r--r-- | muse2/muse/audiotrack.cpp | 122 | ||||
-rw-r--r-- | muse2/muse/controlfifo.h | 4 | ||||
-rw-r--r-- | muse2/muse/dssihost.cpp | 64 | ||||
-rw-r--r-- | muse2/muse/dssihost.h | 19 | ||||
-rw-r--r-- | muse2/muse/globals.cpp | 1 | ||||
-rw-r--r-- | muse2/muse/globals.h | 1 | ||||
-rw-r--r-- | muse2/muse/helper.cpp | 8 | ||||
-rw-r--r-- | muse2/muse/main.cpp | 11 | ||||
-rw-r--r-- | muse2/muse/osc.cpp | 11 | ||||
-rw-r--r-- | muse2/muse/plugin.cpp | 2 | ||||
-rw-r--r-- | muse2/muse/plugin.h | 4 | ||||
-rw-r--r-- | muse2/muse/synth.cpp | 58 | ||||
-rw-r--r-- | muse2/muse/synth.h | 47 | ||||
-rw-r--r-- | muse2/muse/track.cpp | 18 | ||||
-rw-r--r-- | muse2/muse/vst_native.cpp | 2628 | ||||
-rw-r--r-- | muse2/muse/vst_native.h | 281 | ||||
-rw-r--r-- | muse2/muse/widgets/CMakeLists.txt | 2 | ||||
-rw-r--r-- | muse2/muse/widgets/vst_native_editor.cpp | 223 | ||||
-rw-r--r-- | muse2/muse/widgets/vst_native_editor.h | 91 |
21 files changed, 3463 insertions, 147 deletions
diff --git a/muse2/muse/CMakeLists.txt b/muse2/muse/CMakeLists.txt index 36645db4..574a84cf 100644 --- a/muse2/muse/CMakeLists.txt +++ b/muse2/muse/CMakeLists.txt @@ -138,6 +138,7 @@ file (GLOB core_source_files undo.cpp value.cpp vst.cpp + vst_native.cpp wave.cpp waveevent.cpp wavetrack.cpp diff --git a/muse2/muse/arranger/tlist.cpp b/muse2/muse/arranger/tlist.cpp index 3bc13934..31c2a7a3 100644 --- a/muse2/muse/arranger/tlist.cpp +++ b/muse2/muse/arranger/tlist.cpp @@ -1175,7 +1175,7 @@ void TList::oportPropertyPopupMenu(MusECore::Track* t, int x, int y) { if(t->type() == MusECore::Track::AUDIO_SOFTSYNTH) { - MusECore::SynthI* synth = (MusECore::SynthI*)t; + MusECore::SynthI* synth = static_cast<MusECore::SynthI*>(t); QMenu* p = new QMenu; QAction* gact = p->addAction(tr("show gui")); @@ -1191,7 +1191,7 @@ void TList::oportPropertyPopupMenu(MusECore::Track* t, int x, int y) // If it has a gui but we don't have OSC, disable the action. #ifndef OSC_SUPPORT #ifdef DSSI_SUPPORT - if(dynamic_cast<MusECore::DssiSynthIF*>(synth->sif())) + if(synth->synth() && synth->synth()->synthType() == MusECore::Synth::DSSI_SYNTH) { nact->setChecked(false); nact->setEnabled(false); @@ -1233,10 +1233,14 @@ void TList::oportPropertyPopupMenu(MusECore::Track* t, int x, int y) #ifndef OSC_SUPPORT #ifdef DSSI_SUPPORT MusECore::MidiDevice* dev = port->device(); - if(dev && dev->isSynti() && (dynamic_cast<MusECore::DssiSynthIF*>(((MusECore::SynthI*)dev)->sif()))) + if(dev && dev->isSynti()) { - nact->setChecked(false); - nact->setEnabled(false); + MusECore::SynthI* synth = static_cast<MusECore::SynthI*>(dev); + if(synth->synth() && synth->synth()->synthType() == MusECore::Synth::DSSI_SYNTH) + { + nact->setChecked(false); + nact->setEnabled(false); + } } #endif #endif diff --git a/muse2/muse/audiotrack.cpp b/muse2/muse/audiotrack.cpp index 08240f75..91b07063 100644 --- a/muse2/muse/audiotrack.cpp +++ b/muse2/muse/audiotrack.cpp @@ -38,6 +38,7 @@ #include "audiodev.h" #include "synth.h" #include "dssihost.h" +#include "vst_native.h" #include "app.h" #include "controlfifo.h" #include "fastlog.h" @@ -845,7 +846,7 @@ double AudioTrack::pluginCtrlVal(int ctlID) const } else { - if(ctlID < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + if(ctlID < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special synth controller block. { _efxPipe->controllersEnabled(ctlID, &en_1, &en_2); } @@ -853,20 +854,14 @@ double AudioTrack::pluginCtrlVal(int ctlID) const { if(type() == AUDIO_SOFTSYNTH) { -#ifdef DSSI_SUPPORT const SynthI* synth = static_cast<const SynthI*>(this); - if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + const SynthIF* sif = synth->sif(); + if(sif) { - SynthIF* sif = synth->sif(); - if(sif) - { - const DssiSynthIF* dssi_sif = static_cast<const DssiSynthIF*>(sif); - int in_ctrl_idx = ctlID & AC_PLUGIN_CTL_ID_MASK; - en_1 = dssi_sif->controllerEnabled(in_ctrl_idx); - en_2 = dssi_sif->controllerEnabled2(in_ctrl_idx); - } + int in_ctrl_idx = ctlID & AC_PLUGIN_CTL_ID_MASK; + en_1 = sif->controllerEnabled(in_ctrl_idx); + en_2 = sif->controllerEnabled2(in_ctrl_idx); } -#endif } } } @@ -905,25 +900,19 @@ bool AudioTrack::addScheduledControlEvent(int track_ctrl_id, float val, unsigned } else { - if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special synth controller block. return _efxPipe->addScheduledControlEvent(track_ctrl_id, val, frame); else { if(type() == AUDIO_SOFTSYNTH) { - #ifdef DSSI_SUPPORT const SynthI* synth = static_cast<const SynthI*>(this); - if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + SynthIF* sif = synth->sif(); + if(sif) { - SynthIF* sif = synth->sif(); - if(sif) - { - DssiSynthIF* dssi_sif = static_cast<DssiSynthIF*>(sif); - int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; - return dssi_sif->addScheduledControlEvent(in_ctrl_idx, val, frame); - } + int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; + return sif->addScheduledControlEvent(in_ctrl_idx, val, frame); } - #endif } } } @@ -949,25 +938,19 @@ void AudioTrack::enableController(int track_ctrl_id, bool en) } else { - if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special synth controller block. _efxPipe->enableController(track_ctrl_id, en); else { if(type() == AUDIO_SOFTSYNTH) { -#ifdef DSSI_SUPPORT - SynthI* synth = static_cast<SynthI*>(this); - if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + const SynthI* synth = static_cast<const SynthI*>(this); + SynthIF* sif = synth->sif(); + if(sif) { - SynthIF* sif = synth->sif(); - if(sif) - { - DssiSynthIF* dssi_sif = static_cast<DssiSynthIF*>(sif); - int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; - dssi_sif->enableController(in_ctrl_idx, en); - } + int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; + sif->enableController(in_ctrl_idx, en); } -#endif } } } @@ -996,7 +979,7 @@ void AudioTrack::controllersEnabled(int track_ctrl_id, bool* en1, bool* en2) con } else { - if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special dssi synth controller block. + if(track_ctrl_id < (int)genACnum(MAX_PLUGINS, 0)) // The beginning of the special synth controller block. { _efxPipe->controllersEnabled(track_ctrl_id, &en_1, &en_2); } @@ -1004,20 +987,14 @@ void AudioTrack::controllersEnabled(int track_ctrl_id, bool* en1, bool* en2) con { if(type() == AUDIO_SOFTSYNTH) { -#ifdef DSSI_SUPPORT const SynthI* synth = static_cast<const SynthI*>(this); - if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) + const SynthIF* sif = synth->sif(); + if(sif) { - SynthIF* sif = synth->sif(); - if(sif) - { - const DssiSynthIF* dssi_sif = static_cast<const DssiSynthIF*>(sif); - int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; - en_1 = dssi_sif->controllerEnabled(in_ctrl_idx); - en_2 = dssi_sif->controllerEnabled2(in_ctrl_idx); - } + int in_ctrl_idx = track_ctrl_id & AC_PLUGIN_CTL_ID_MASK; + en_1 = sif->controllerEnabled(in_ctrl_idx); + en_2 = sif->controllerEnabled2(in_ctrl_idx); } -#endif } } } @@ -1204,38 +1181,24 @@ bool AudioTrack::readProperties(Xml& xml, const QString& tag) // controls would all be set to zero. // But we will allow for the (unintended, useless) possibility of a controller // with no matching plugin control. - PluginIBase* p = 0; + const PluginIBase* p = 0; bool ctlfound = false; unsigned m = l->id() & AC_PLUGIN_CTL_ID_MASK; int n = (l->id() >> AC_PLUGIN_CTL_BASE_POW) - 1; if(n >= 0 && n < PipelineDepth) - { p = (*_efxPipe)[n]; - if(p && m < p->parameters()) - ctlfound = true; - } - // Support a special block for dssi synth ladspa controllers. + // Support a special block for synth controllers. else if(n == MAX_PLUGINS && type() == AUDIO_SOFTSYNTH) { - SynthI* synti = dynamic_cast < SynthI* > (this); - if(synti) - { - SynthIF* sif = synti->sif(); - if(sif) - { -#ifdef DSSI_SUPPORT - DssiSynthIF* dsif = dynamic_cast < DssiSynthIF* > (sif); - if(dsif) - { - p = dsif; - if(p && m < p->parameters()) - ctlfound = true; - } -#endif - } - } + const SynthI* synti = static_cast < SynthI* > (this); + const SynthIF* sif = synti->sif(); + if(sif) + p = static_cast < const PluginIBase* > (sif); } + if(p && m < p->parameters()) + ctlfound = true; + iCtrlList icl = _controller.find(l->id()); if (icl == _controller.end()) _controller.add(l); @@ -1382,22 +1345,13 @@ void AudioTrack::mapRackPluginsToControllers() const PluginIBase* p = 0; if(idx >= 0 && idx < PipelineDepth) p = (*_efxPipe)[idx]; - // Support a special block for dssi synth ladspa controllers. + // Support a special block for synth controllers. else if(idx == MAX_PLUGINS && type() == AUDIO_SOFTSYNTH) { - const SynthI* synti = dynamic_cast < const SynthI* > (this); - if(synti) - { - SynthIF* sif = synti->sif(); - if(sif) - { -#ifdef DSSI_SUPPORT - const DssiSynthIF* dsif = dynamic_cast < const DssiSynthIF* > (sif); - if(dsif) - p = dsif; -#endif - } - } + const SynthI* synti = static_cast < const SynthI* > (this); + SynthIF* sif = synti->sif(); + if(sif) + p = static_cast < const PluginIBase* > (sif); } // If there's no plugin at that rack position, or the param is out of range of diff --git a/muse2/muse/controlfifo.h b/muse2/muse/controlfifo.h index 6a35c8a5..6ee2cedb 100644 --- a/muse2/muse/controlfifo.h +++ b/muse2/muse/controlfifo.h @@ -36,7 +36,9 @@ struct ControlEvent // Unique: Whether the item must not be skipped, even if it has the same // (possibly rounded) frame and index as the previous item. This is mainly for // dssi-vst guis, they require acknowledgment of every message. - bool unique; + bool unique; + // Whether or not the event is from a synth or effect's own GUI. + bool fromGui; unsigned long idx; float value; unsigned long frame; diff --git a/muse2/muse/dssihost.cpp b/muse2/muse/dssihost.cpp index 70db2a47..f62d1434 100644 --- a/muse2/muse/dssihost.cpp +++ b/muse2/muse/dssihost.cpp @@ -157,7 +157,7 @@ static void scanDSSILib(QFileInfo& fi) // ddskrjo removed const for argument // scanVstDir //--------------------------------------------------------- -static void scanDSSIDir(QString& s) // ddskrjo removed const for argument +static void scanDSSIDir(const QString& s) { if(MusEGlobal::debugMsg) printf("scanDSSIDir: scan DSSI plugin dir <%s>\n", s.toLatin1().constData()); @@ -202,8 +202,7 @@ void initDSSI() char* buffer = new char[n + 1]; strncpy(buffer, p, n); buffer[n] = '\0'; - QString tmpStr(buffer); - scanDSSIDir(tmpStr); + scanDSSIDir(QString(buffer)); delete[] buffer; } p = pe; @@ -270,7 +269,8 @@ DssiSynth::DssiSynth(QFileInfo& fi, const DSSI_Descriptor* d) : // ddskrjo remov DssiSynth::~DssiSynth() { if(dssi) - delete dssi; + // delete dssi; + printf("DssiSynth::~DssiSynth Error: dssi descriptor is not NULL\n"); } //--------------------------------------------------------- @@ -650,9 +650,8 @@ bool DssiSynthIF::init(DssiSynth* s) } } } - - if (ld->activate) - ld->activate(handle); + + activate(); // Set current configuration values. if(dssi->configure) @@ -1988,6 +1987,7 @@ int DssiSynthIF::oscControl(unsigned long port, float value) // p4.0.21 ControlEvent ce; ce.unique = synth->_isDssiVst; // Special for messages from vst gui to host - requires processing every message. + ce.fromGui = true; // It came form the plugin's own GUI. ce.idx = cport; ce.value = value; @@ -2305,21 +2305,27 @@ int DssiSynthIF::totalInChannels() const return synth->_inports; } +void DssiSynthIF::deactivate3() +{ + deactivate(); +} + + //-------------------------------- // 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*/) { } +//bool DssiSynthIF::on() const { return true; } // Synth is not part of a rack plugin chain. Always on. +//void DssiSynthIF::setOn(bool /*val*/) { } unsigned long DssiSynthIF::pluginID() { return (synth && synth->dssi) ? synth->dssi->LADSPA_Plugin->UniqueID : 0; } int DssiSynthIF::id() { return MAX_PLUGINS; } // Set for special block reserved for dssi synth. p4.0.20 QString DssiSynthIF::pluginLabel() const { return (synth && synth->dssi) ? QString(synth->dssi->LADSPA_Plugin->Label) : QString(); } -QString DssiSynthIF::name() const { return synti->name(); } +//QString DssiSynthIF::name() const { return synti->name(); } QString DssiSynthIF::lib() const { return synth ? synth->completeBaseName() : QString(); } QString DssiSynthIF::dirPath() const { return synth ? synth->absolutePath() : QString(); } QString DssiSynthIF::fileName() const { return synth ? synth->fileName() : QString(); } -QString DssiSynthIF::titlePrefix() const { return QString(); } -MusECore::AudioTrack* DssiSynthIF::track() { return (MusECore::AudioTrack*)synti; } +//QString DssiSynthIF::titlePrefix() const { return QString(); } +//MusECore::AudioTrack* DssiSynthIF::track() { return (MusECore::AudioTrack*)synti; } void DssiSynthIF::enableController(unsigned long i, bool v) { controls[i].enCtrl = v; } bool DssiSynthIF::controllerEnabled(unsigned long i) const { return controls[i].enCtrl; } void DssiSynthIF::enable2Controller(unsigned long i, bool v) { controls[i].en2Ctrl = v; } @@ -2338,10 +2344,38 @@ void DssiSynthIF::enable2AllControllers(bool v) for(unsigned long i = 0; i < synth->_controlInPorts; ++i) controls[i].en2Ctrl = v; } +void DssiSynthIF::updateControllers() { } +void DssiSynthIF::activate() +{ + if(synth && synth->dssi && synth->dssi->LADSPA_Plugin && synth->dssi->LADSPA_Plugin->activate) + //for (int i = 0; i < instances; ++i) + // _plugin->activate(handle[i]); + synth->dssi->LADSPA_Plugin->activate(handle); + +// 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; +// } +// } +} +void DssiSynthIF::deactivate() +{ + if(!synth || !synth->dssi || !synth->dssi->LADSPA_Plugin ||!synth->dssi->LADSPA_Plugin->deactivate) + return; + //for (int i = 0; i < instances; ++i) + // synth->dssi->LADSPA_Plugin->deactivate(handle[i]); + synth->dssi->LADSPA_Plugin->deactivate(handle); +} -void DssiSynthIF::updateControllers() { } -void DssiSynthIF::writeConfiguration(int /*level*/, Xml& /*xml*/) { } -bool DssiSynthIF::readConfiguration(Xml& /*xml*/, bool /*readPreset*/) { return false; } +//void DssiSynthIF::writeConfiguration(int /*level*/, Xml& /*xml*/) { } +//bool DssiSynthIF::readConfiguration(Xml& /*xml*/, bool /*readPreset*/) { return false; } unsigned long DssiSynthIF::parameters() const { return synth ? synth->_controlInPorts : 0; } unsigned long DssiSynthIF::parametersOut() const { return synth ? synth->_controlOutPorts : 0; } diff --git a/muse2/muse/dssihost.h b/muse2/muse/dssihost.h index baa6adbe..99626c92 100644 --- a/muse2/muse/dssihost.h +++ b/muse2/muse/dssihost.h @@ -114,7 +114,7 @@ class DssiSynth : public Synth { // VSTi synthesizer instance //--------------------------------------------------------- -class DssiSynthIF : public SynthIF, public PluginIBase +class DssiSynthIF : public SynthIF { DssiSynth* synth; LADSPA_Handle handle; @@ -143,6 +143,8 @@ class DssiSynthIF : public SynthIF, public PluginIBase virtual ~DssiSynthIF(); + bool init(DssiSynth* s); + virtual DssiSynth* dssiSynth() { return synth; } virtual SynthI* dssiSynthI() { return synti; } @@ -169,7 +171,7 @@ class DssiSynthIF : public SynthIF, public PluginIBase virtual int totalOutChannels() const; virtual int totalInChannels() const; - virtual void deactivate3() {} + virtual void deactivate3(); virtual const char* getPatchName(int, int, bool); virtual void populatePatchPopup(MusEGui::PopupMenu*, int, bool); @@ -179,11 +181,8 @@ class DssiSynthIF : public SynthIF, public PluginIBase virtual float getParameter(unsigned long /*idx*/) const; virtual float getParameterOut(unsigned long n) const; virtual void setParameter(unsigned long /*idx*/, float /*value*/); - virtual int getControllerInfo(int, const char**, int*, int*, int*, int*); - bool init(DssiSynth* s); - #ifdef OSC_SUPPORT OscDssiIF& oscIF() { return _oscif; } int oscProgram(unsigned long prog, unsigned long bank); @@ -196,17 +195,13 @@ class DssiSynthIF : public SynthIF, public PluginIBase //------------------------- // Methods for PluginIBase: //------------------------- - bool on() const; - void setOn(bool val); + unsigned long pluginID(); int id(); QString pluginLabel() const; - QString name() const; QString lib() const; QString dirPath() const; QString fileName() const; - QString titlePrefix() const; - MusECore::AudioTrack* track(); void enableController(unsigned long i, bool v = true); bool controllerEnabled(unsigned long i) const; void enable2Controller(unsigned long i, bool v = true); @@ -214,8 +209,8 @@ class DssiSynthIF : public SynthIF, public PluginIBase void enableAllControllers(bool v = true); void enable2AllControllers(bool v = true); void updateControllers(); - void writeConfiguration(int level, Xml& xml); - bool readConfiguration(Xml& xml, bool readPreset=false); + void activate(); + void deactivate(); unsigned long parameters() const; unsigned long parametersOut() const; diff --git a/muse2/muse/globals.cpp b/muse2/muse/globals.cpp index b61cda6d..831558f4 100644 --- a/muse2/muse/globals.cpp +++ b/muse2/muse/globals.cpp @@ -111,6 +111,7 @@ int realTimePriority = 40; // 80 int midiRTPrioOverride = -1; bool loadPlugins = true; bool loadVST = true; +bool loadNativeVST = true; bool loadDSSI = true; bool usePythonBridge = false; bool useLASH = true; diff --git a/muse2/muse/globals.h b/muse2/muse/globals.h index da8221b9..3eb941a7 100644 --- a/muse2/muse/globals.h +++ b/muse2/muse/globals.h @@ -81,6 +81,7 @@ extern bool heavyDebugMsg; extern bool debugSync; extern bool loadPlugins; extern bool loadVST; +extern bool loadNativeVST; extern bool loadDSSI; extern bool usePythonBridge; extern bool useLASH; diff --git a/muse2/muse/helper.cpp b/muse2/muse/helper.cpp index 1c4db8c7..0d9b74bd 100644 --- a/muse2/muse/helper.cpp +++ b/muse2/muse/helper.cpp @@ -50,14 +50,6 @@ #include <QFileDialog> #include <QString> -#ifdef DSSI_SUPPORT -#include "dssihost.h" -#endif - -#ifdef VST_SUPPORT -#include "vst.h" -#endif - using std::set; namespace MusEGlobal { diff --git a/muse2/muse/main.cpp b/muse2/muse/main.cpp index ceb245ba..e233a28e 100644 --- a/muse2/muse/main.cpp +++ b/muse2/muse/main.cpp @@ -71,6 +71,7 @@ extern void initMidiController(); extern void initMetronome(); extern void initOSC(); extern void initVST(); +extern void initVST_Native(); extern void initPlugins(); extern void initDSSI(); extern void readConfiguration(); @@ -238,6 +239,9 @@ static void usage(const char* prog, const char* txt) #ifdef VST_SUPPORT fprintf(stderr, " -V Don't load VST plugins\n"); #endif +#ifdef VST_NATIVE_SUPPORT + fprintf(stderr, " -N Don't load Native VST plugins\n"); +#endif #ifdef DSSI_SUPPORT fprintf(stderr, " -I Don't load DSSI plugins\n"); #endif @@ -352,6 +356,9 @@ int main(int argc, char* argv[]) #ifdef VST_SUPPORT optstr += QString("V"); #endif +#ifdef VST_NATIVE_SUPPORT + optstr += QString("N"); +#endif #ifdef DSSI_SUPPORT optstr += QString("I"); #endif @@ -398,6 +405,7 @@ int main(int argc, char* argv[]) case 'Y': MusEGlobal::midiRTPrioOverride = atoi(optarg); break; case 'p': MusEGlobal::loadPlugins = false; break; case 'V': MusEGlobal::loadVST = false; break; + case 'N': MusEGlobal::loadNativeVST = false; break; case 'I': MusEGlobal::loadDSSI = false; break; case 'L': MusEGlobal::useLASH = false; break; case 'y': MusEGlobal::usePythonBridge = true; break; @@ -650,6 +658,9 @@ int main(int argc, char* argv[]) if (MusEGlobal::loadVST) MusECore::initVST(); + if (MusEGlobal::loadNativeVST) + MusECore::initVST_Native(); + if(MusEGlobal::loadDSSI) MusECore::initDSSI(); diff --git a/muse2/muse/osc.cpp b/muse2/muse/osc.cpp index 381e4acc..0f15083b 100644 --- a/muse2/muse/osc.cpp +++ b/muse2/muse/osc.cpp @@ -173,12 +173,11 @@ int oscMessageHandler(const char* path, const char* types, lo_arg** argv, const char* sub = strstr(p, ba.constData()); if(sub == NULL) continue; - - // TODO: Fix this dynamic cast - it may be a slowdown. - DssiSynthIF* instance = dynamic_cast<DssiSynthIF*>(synti->sif()); - if(!instance) - break; - + + if(!synti->sif() || !synti->synth() || synti->synth()->synthType() != MusECore::Synth::DSSI_SYNTH) + continue; + DssiSynthIF* instance = static_cast<DssiSynthIF*>(synti->sif()); + p = sub + ba.length(); if (*p != '/' || *(p + 1) == 0) { diff --git a/muse2/muse/plugin.cpp b/muse2/muse/plugin.cpp index 9dc2005e..0ca7524c 100644 --- a/muse2/muse/plugin.cpp +++ b/muse2/muse/plugin.cpp @@ -1575,6 +1575,7 @@ bool PluginIBase::addScheduledControlEvent(unsigned long i, float val, unsigned } ControlEvent ce; ce.unique = false; + ce.fromGui = false; ce.idx = i; ce.value = val; // Time-stamp the event. This does a possibly slightly slow call to gettimeofday via timestamp(). @@ -2800,6 +2801,7 @@ int PluginI::oscControl(unsigned long port, float value) */ ControlEvent ce; ce.unique = _plugin->_isDssiVst; // Special for messages from vst gui to host - requires processing every message. + ce.fromGui = true; // It came form the plugin's own GUI. ce.idx = cport; ce.value = value; // Time-stamp the event. This does a possibly slightly slow call to gettimeofday via timestamp(). diff --git a/muse2/muse/plugin.h b/muse2/muse/plugin.h index 25d434ca..822bafbd 100644 --- a/muse2/muse/plugin.h +++ b/muse2/muse/plugin.h @@ -274,6 +274,9 @@ class PluginIBase virtual void enable2AllControllers(bool v = true) = 0; virtual void updateControllers() = 0; + virtual void activate() = 0; + virtual void deactivate() = 0; + virtual void writeConfiguration(int level, Xml& xml) = 0; virtual bool readConfiguration(Xml& xml, bool readPreset=false) = 0; @@ -285,6 +288,7 @@ class PluginIBase virtual float paramOut(unsigned long i) const = 0; virtual const char* paramName(unsigned long i) = 0; virtual const char* paramOutName(unsigned long i) = 0; + // FIXME TODO: Either find a way to agnosticize these two ranges, or change them from ladspa ranges to a new MusE range class. virtual LADSPA_PortRangeHint range(unsigned long i) = 0; virtual LADSPA_PortRangeHint rangeOut(unsigned long i) = 0; diff --git a/muse2/muse/synth.cpp b/muse2/muse/synth.cpp index 840365ee..92c0a27d 100644 --- a/muse2/muse/synth.cpp +++ b/muse2/muse/synth.cpp @@ -61,7 +61,7 @@ namespace MusECore { extern void connectNodes(AudioTrack*, AudioTrack*); bool SynthI::_isVisible=false; -const char* synthTypes[] = { "METRONOME", "MESS", "DSSI", "VST", "UNKNOWN" }; +const char* synthTypes[] = { "METRONOME", "MESS", "DSSI", "VST", "VST_NATIVE", "UNKNOWN" }; QString synthType2String(Synth::Type type) { return QString(synthTypes[type]); } Synth::Type string2SynthType(const QString& type) @@ -74,6 +74,62 @@ Synth::Type string2SynthType(const QString& type) return Synth::SYNTH_TYPE_END; } +//-------------------------------- +// Methods for PluginIBase: +//-------------------------------- + +bool SynthIF::on() const { return true; } // Synth is not part of a rack plugin chain. Always on. +void SynthIF::setOn(bool /*val*/) { } +unsigned long SynthIF::pluginID() { return 0; } +int SynthIF::id() { return MAX_PLUGINS; } // Set for special block reserved for synth. +QString SynthIF::pluginLabel() const { return QString(); } +QString SynthIF::name() const { return synti->name(); } +QString SynthIF::lib() const { return QString(); } +QString SynthIF::dirPath() const { return QString(); } +QString SynthIF::fileName() const { return QString(); } +QString SynthIF::titlePrefix() const { return QString(); } +MusECore::AudioTrack* SynthIF::track() { return static_cast < MusECore::AudioTrack* > (synti); } +void SynthIF::enableController(unsigned long, bool) { } +bool SynthIF::controllerEnabled(unsigned long) const { return true;} +void SynthIF::enable2Controller(unsigned long, bool) { } +bool SynthIF::controllerEnabled2(unsigned long) const { return true; } +void SynthIF::enableAllControllers(bool) { } +void SynthIF::enable2AllControllers(bool) { } +void SynthIF::updateControllers() { } +void SynthIF::activate() { } +void SynthIF::deactivate() { } +void SynthIF::writeConfiguration(int /*level*/, Xml& /*xml*/) { } +bool SynthIF::readConfiguration(Xml& /*xml*/, bool /*readPreset*/) { return false; } +unsigned long SynthIF::parameters() const { return 0; } +unsigned long SynthIF::parametersOut() const { return 0; } +void SynthIF::setParam(unsigned long, float) { } +float SynthIF::param(unsigned long) const { return 0.0; } +float SynthIF::paramOut(unsigned long) const { return 0.0; } +const char* SynthIF::paramName(unsigned long) { return NULL; } +const char* SynthIF::paramOutName(unsigned long) { return NULL; } +LADSPA_PortRangeHint SynthIF::range(unsigned long) +{ + LADSPA_PortRangeHint h; + h.HintDescriptor = 0; + h.LowerBound = 0.0; + h.UpperBound = 1.0; + return h; +} +LADSPA_PortRangeHint SynthIF::rangeOut(unsigned long) +{ + LADSPA_PortRangeHint h; + h.HintDescriptor = 0; + h.LowerBound = 0.0; + h.UpperBound = 1.0; + return h; +} +CtrlValueType SynthIF::ctrlValueType(unsigned long) const { return VAL_LINEAR; } +CtrlList::Mode SynthIF::ctrlMode(unsigned long) const { return CtrlList::INTERPOLATE; }; + +//------------------------------------------------------------------------- + + + bool MessSynthIF::nativeGuiVisible() const { return _mess ? _mess->nativeGuiVisible() : false; diff --git a/muse2/muse/synth.h b/muse2/muse/synth.h index f5ecc9ab..c41d5535 100644 --- a/muse2/muse/synth.h +++ b/muse2/muse/synth.h @@ -34,6 +34,7 @@ #include "midiport.h" #include "track.h" #include "stringparam.h" +#include "plugin.h" #include <QFileInfo> @@ -67,7 +68,7 @@ class Synth { QString _version; public: - enum Type { METRO_SYNTH=0, MESS_SYNTH, DSSI_SYNTH, VST_SYNTH, SYNTH_TYPE_END }; + enum Type { METRO_SYNTH=0, MESS_SYNTH, DSSI_SYNTH, VST_SYNTH, VST_NATIVE_SYNTH, SYNTH_TYPE_END }; Synth(const QFileInfo& fi, QString label, QString descr, QString maker, QString ver); @@ -119,7 +120,7 @@ class MessSynth : public Synth { // 0xNN The synth's unique ID byte //--------------------------------------------------------- -class SynthIF { +class SynthIF : public PluginIBase { protected: SynthI* synti; @@ -159,6 +160,47 @@ class SynthIF { virtual float getParameter(unsigned long idx) const = 0; virtual void setParameter(unsigned long idx, float value) = 0; virtual int getControllerInfo(int id, const char** name, int* ctrl, int* min, int* max, int* initval) = 0; + + //------------------------- + // Methods for PluginIBase: + //------------------------- + + virtual bool on() const; + virtual void setOn(bool val); + virtual unsigned long pluginID(); + virtual int id(); + virtual QString pluginLabel() const; + virtual QString name() const; + virtual QString lib() const; + virtual QString dirPath() const; + virtual QString fileName() const; + virtual QString titlePrefix() const; + virtual MusECore::AudioTrack* track(); + virtual void enableController(unsigned long i, bool v = true); + virtual bool controllerEnabled(unsigned long i) const; + virtual void enable2Controller(unsigned long i, bool v = true); + virtual bool controllerEnabled2(unsigned long i) const; + virtual void enableAllControllers(bool v = true); + virtual void enable2AllControllers(bool v = true); + virtual void updateControllers(); + virtual void activate(); + virtual void deactivate(); + + virtual void writeConfiguration(int level, Xml& xml); + virtual bool readConfiguration(Xml& xml, bool readPreset=false); + + virtual unsigned long parameters() const; + virtual unsigned long parametersOut() const; + virtual void setParam(unsigned long i, float val); + virtual float param(unsigned long i) const; + virtual float paramOut(unsigned long i) const; + virtual const char* paramName(unsigned long i); + virtual const char* paramOutName(unsigned long i); + // FIXME TODO: Either find a way to agnosticize these two ranges, or change them from ladspa ranges to a new MusE range class. + virtual LADSPA_PortRangeHint range(unsigned long i); + virtual LADSPA_PortRangeHint rangeOut(unsigned long i); + virtual CtrlValueType ctrlValueType(unsigned long i) const; + virtual CtrlList::Mode ctrlMode(unsigned long i) const; }; //--------------------------------------------------------- @@ -207,6 +249,7 @@ class SynthI : public AudioTrack, public MidiDevice, friend class MessSynthIF; friend class DssiSynthIF; friend class VstSynthIF; + friend class VstNativeSynthIF; SynthI(); virtual ~SynthI(); diff --git a/muse2/muse/track.cpp b/muse2/muse/track.cpp index b18a9410..b17515fe 100644 --- a/muse2/muse/track.cpp +++ b/muse2/muse/track.cpp @@ -291,7 +291,7 @@ void Track::internal_assign(const Track& t, int flags) { // Do not call setName here. Audio Input and Output override it and try to set // Jack ports, which have not been initialized yet here. Must wait until - // .Audio Input and Output copy constructors or assign are called. + // Audio Input and Output copy constructors or assign are called. _name = s; break; } @@ -389,18 +389,10 @@ void Track::clearRecAutomation(bool clearList) if(type() == AUDIO_SOFTSYNTH) { -#ifdef DSSI_SUPPORT - SynthI* synth = static_cast<SynthI*>(this); - if(synth->synth() && synth->synth()->synthType() == Synth::DSSI_SYNTH) - { - SynthIF* sif = synth->sif(); - if(sif) - { - DssiSynthIF* dssi_sif = static_cast<DssiSynthIF*>(sif); - dssi_sif->enableAllControllers(true); - } - } -#endif + const SynthI* synth = static_cast<const SynthI*>(this); + SynthIF* sif = synth->sif(); + if(sif) + sif->enableAllControllers(true); } if(clearList) diff --git a/muse2/muse/vst_native.cpp b/muse2/muse/vst_native.cpp new file mode 100644 index 00000000..8febe142 --- /dev/null +++ b/muse2/muse/vst_native.cpp @@ -0,0 +1,2628 @@ +//=================================================================== +// MusE +// Linux Music Editor +// +// vst_native.cpp +// (C) Copyright 2012 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 <QDir> +#include <QMenu> + +#include <stdlib.h> +#include <stdio.h> +#include <dlfcn.h> +#include <cmath> +#include <set> +#include <jack/jack.h> + +#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 "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) +{ + VSTPlugin* plugin; + if(effect && effect->user) + { + plugin = (VSTPlugin*)(effect->user); + return ((VstNativeSynthIF*)plugin)->hostCallback(opcode, index, value, ptr, opt); + } + +#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 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 <true> such that older versions + // will always return true. + return 1; + + case audioMasterWantMidi: + // <value> is a filter which is currently ignored + return 0; + + case audioMasterGetTime: + // returns const VstTimeInfo* (or 0 if not supported) + // <value> 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 <ptr> + return 0; + + case audioMasterSetTime: + // VstTimenfo* in <ptr>, filter in <value>, not supported + return 0; + + case audioMasterTempoAt: + // returns tempo (in bpm * 10000) at sample frame location passed in <value> + return 0; + + 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 <value> (-1: all, any) + return 0; + + case audioMasterIOChanged: + // numInputs and/or numOutputs has changed + return 0; + + case audioMasterNeedIdle: + // plug needs idle calls (outside its editor window) + 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 audioMasterGetPreviousPlug: + // input pin in <value> (-1: first to come), returns cEffect* + return 0; + + case audioMasterGetNextPlug: + // output pin in <value> (-1: first to come), returns cEffect* + + case audioMasterWillReplaceOrAccumulate: + // returns: 0: not supported, 1: replace, 2: accumulate + 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 audioMasterSetOutputSampleRate: + // for variable i/o, sample rate in <opt> + return 0; + + case audioMasterGetSpeakerArrangement: + // (long)input in <value>, output in <ptr> + return 0; + + case audioMasterGetVendorString: + // fills <ptr> with a string identifying the vendor (max 64 char) + strcpy ((char*) ptr, "MusE"); + return 1; + + case audioMasterGetProductString: + // fills <ptr> 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 audioMasterSetIcon: + // void* in <ptr>, format not defined yet + return 0; + + case audioMasterCanDo: + // string in ptr, see below + return 0; + + case audioMasterGetLanguage: + // see enum + return kVstLangEnglish; + + case audioMasterOpenWindow: + // returns platform specific ptr + return 0; + + case audioMasterCloseWindow: + // close window, platform specific handle in <ptr> + return 0; + + 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 <index> + return 0; + + case audioMasterEndEdit: + // end of automation session (when mouse up), parameter index in <index> + return 0; + + case audioMasterOpenFileSelector: + // open a fileselector window with VstFileSelect* in <ptr> + return 0; + + case audioMasterCloseFileSelector: + return 0; + + 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; + std::vector<Synth*>::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); + + // 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(vst_version < 2 || !((plugin->flags & effFlagsIsSynth) || (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()); // TODO Version string? + + 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() + { + const char* vstPath = getenv("VST_NATIVE_PATH"); // FIXME TODO: What's the right path and env var? + if (vstPath == 0) + vstPath = "/usr/lib/vst:/usr/local/lib/vst"; + + 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; +#ifdef VST_SDK_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() +{ + 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->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(vst_version < 2 || !((plugin->flags & effFlagsIsSynth) || (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, effOpen, 0, 0, NULL, 0); + //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; + _synth = NULL; + _plugin = NULL; + _editor = NULL; + _inProcess = false; + _controls = NULL; +// controlsOut = 0; + _audioInBuffers = NULL; + _audioInSilenceBuf = NULL; + //_audioInSilenceBufs = 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(_audioInSilenceBufs) +// { +// for(unsigned long i = 0; i < _synth->inPorts(); ++i) +// { +// if(_audioInSilenceBufs[i]) +// free(_audioInSilenceBufs[i]); +// } +// delete[] _audioInSilenceBufs; +// } + + if(_controls) + delete[] _controls; +} + +//--------------------------------------------------------- +// init +//--------------------------------------------------------- + +bool VstNativeSynthIF::init(Synth* s) + { + _synth = (VstNativeSynth*)s; + _plugin = _synth->instantiate(); + if(!_plugin) + return false; + _plugin->user = this; + + queryPrograms(); + + unsigned long outports = _synth->outPorts(); + if(outports != 0) + { + _audioOutBuffers = new float*[outports]; + for(unsigned long k = 0; k < outports; ++k) + { + posix_memalign((void**)&_audioOutBuffers[k], 16, sizeof(float) * MusEGlobal::segmentSize); + memset(_audioOutBuffers[k], 0, sizeof(float) * MusEGlobal::segmentSize); + } + } + + unsigned long inports = _synth->inPorts(); + if(inports != 0) + { + _audioInBuffers = new float*[inports]; + //_audioInSilenceBufs = new float*[inports]; + for(unsigned long k = 0; k < inports; ++k) + { + posix_memalign((void**)&_audioInBuffers[k], 16, sizeof(float) * MusEGlobal::segmentSize); + memset(_audioInBuffers[k], 0, sizeof(float) * MusEGlobal::segmentSize); + //posix_memalign((void**)&_audioInSilenceBufs[k], 16, sizeof(float) * MusEGlobal::segmentSize); + //memset(_audioInSilenceBufs[k], 0, sizeof(float) * MusEGlobal::segmentSize); + _iUsedIdx.push_back(false); // Start out with all false. + } + + posix_memalign((void**)&_audioInSilenceBuf, 16, sizeof(float) * MusEGlobal::segmentSize); + memset(_audioInSilenceBuf, 0, sizeof(float) * MusEGlobal::segmentSize); + } + + int controlPorts = _synth->inControls(); + if(controlPorts != 0) + _controls = new Port[controlPorts]; + else + _controls = NULL; + + //_synth->midiCtl2PortMap.clear(); + //_synth->port2MidiCtlMap.clear(); + + for(unsigned long i = 0; i < _synth->inControls(); ++i) + { + _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; + _controls[i].en2Ctrl = 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 = ((MusECore::AudioTrack*)synti)->controller(); + iCtrlList icl = cll->find(id); + if (icl == cll->end()) + { + cl = new CtrlList(id); + cll->add(cl); + //cl->setCurVal(controls[cip].val); + cl->setCurVal(_plugin->getParameter(_plugin, i)); + } + else + { + cl = icl->second; + ///controls[cip].val = cl->curVal(); + //setParam(i, cl->curVal()); + if(dispatch(effCanBeAutomated, i, 0, NULL, 0.0f) == 1) + _plugin->setParameter(_plugin, i, cl->curVal()); +#ifdef VST_NATIVE_DEBUG + else + fprintf(stderr, "VstNativeSynthIF::init %s parameter:%lu cannot be automated\n", name().toLatin1().constData(), i); +#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; + //jack_position_t jack_pos; + //jack_transport_state_t tstate; + +#ifdef VST_NATIVE_DEBUG + 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; + + 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(); + return 0; + + case audioMasterGetTime: + // returns const VstTimeInfo* (or 0 if not supported) + // <value> should contain a mask indicating which fields are required + // (see valid masks above), as some items may require extensive + // conversions + +// FIXME: TODO: Change this to MusE tempo and sig, and even if Jack is used, +// the jackAudio pointer may be 0. Jack may not be running and dummy used instead. +#if 1 + return 0; +#else + memset(&_timeInfo, 0, sizeof(_timeInfo)); + + if (_plugin) { + tstate = jackAudio->transportQuery(&jack_pos); + + _timeInfo.samplePos = jack_pos.frame; + _timeInfo.sampleRate = jack_pos.frame_rate; + _timeInfo.flags = 0; + + if ((value & (kVstBarsValid|kVstTempoValid)) && (jack_pos.valid & JackPositionBBT)) { + _timeInfo.tempo = jack_pos.beats_per_minute; + _timeInfo.timeSigNumerator = (long) floor (jack_pos.beats_per_bar); + _timeInfo.timeSigDenominator = (long) floor (jack_pos.beat_type); + _timeInfo.flags |= (kVstBarsValid|kVstTempoValid); + } + if (tstate == JackTransportRolling) { + _timeInfo.flags |= kVstTransportPlaying; + } + } + else { + _timeInfo.samplePos = 0; + _timeInfo.sampleRate = MusEGlobal::sampleRate; + } + return (long)&_timeInfo; +#endif + + case audioMasterProcessEvents: + // VstEvents* in <ptr> + 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 <ptr> with a string identifying the vendor (max 64 char) + strcpy ((char*) ptr, "MusE"); + return 1; + + case audioMasterGetProductString: + // fills <ptr> 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 //ifdef VST_SDK_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 <index> + guiAutomationBegin(index); + return 1; + + case audioMasterEndEdit: + // end of automation session (when mouse up), parameter index in <index> + guiAutomationEnd(index); + return 1; + +#if 0 //ifdef VST_SDK_SUPPORT + case audioMasterOpenFileSelector: + // open a fileselector window with VstFileSelect* in <ptr> + return 0; + + case audioMasterCloseFileSelector: + return 0; +#endif + +#ifdef VST_NATIVE_FORCE_DEPRECATED + + case audioMasterGetSpeakerArrangement: + // (long)input in <value>, output in <ptr> + 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 <true> such that older versions + // will always return true. + //return 1; + return 0; + + // VST 2.0 opcodes... + case audioMasterWantMidi: + // <value> is a filter which is currently ignored + return 0; + + case audioMasterSetTime: + // VstTimenfo* in <ptr>, filter in <value>, not supported + return 0; + + case audioMasterTempoAt: + // returns tempo (in bpm * 10000) at sample frame location passed in <value> + 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 <value> (-1: all, any) + //return 0; + return 1; + + case audioMasterNeedIdle: + // plug needs idle calls (outside its editor window) + return 0; + + case audioMasterGetPreviousPlug: + // input pin in <value> (-1: first to come), returns cEffect* + return 0; + + case audioMasterGetNextPlug: + // output pin in <value> (-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 <opt> + return 0; + + case audioMasterSetIcon: + // void* in <ptr>, format not defined yet + return 0; + + case audioMasterOpenWindow: + // returns platform specific ptr + return 0; + + case audioMasterCloseWindow: + // close window, platform specific handle in <ptr> + return 0; +#endif + + + default: + break; + } + return 0; + } + +//--------------------------------------------------------- +// idleEditor +//--------------------------------------------------------- + +void VstNativeSynthIF::idleEditor() +{ +#ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::idleEditor %p\n", this); +#endif + + _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); + + // TODO TEST Test if these might be helpful, especially opaque event. + // _editor->setBackgroundRole(QPalette::NoRole); + // _editor->setAttribute(Qt::WA_NoSystemBackground); + // _editor->setAttribute(Qt::WA_StaticContents); + // // This is absolutely required for speed! Otherwise painfully slow because of full background + // // filling, even when requesting small udpdates! Background is drawn by us. + // _editor->setAttribute(Qt::WA_OpaquePaintEvent); + // //_editor->setFrameStyle(QFrame::Raised | QFrame::StyledPanel); + } + } + else + { + if(_editor) + { + delete _editor; + //_editor = NULL; // No - done in editorDeleted. + } + } + _guiVisible = v; +} + +//--------------------------------------------------------- +// 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; + 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; + +//#ifdef VST_SDK_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; +//#ifdef VST_SDK_SUPPORT + } +//#endif + + VST_Program p; + p.name = QString(buf); + //p.program = prog & 0x7f; + //p.bank = prog << 7; + p.program = prog; + 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" + //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); + //{ + 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" + 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); + //} + + // 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) + { + for(unsigned long k = 0; k < _synth->inControls(); ++k) + { + // We're in the audio thread context: no need to send a message, just modify directly. + synti->setPluginCtrlVal(genACnum(id(), k), _plugin->getParameter(_plugin, k)); + } + } + +// // 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 +//--------------------------------------------------------- + +const char* VstNativeSynthIF::getPatchName(int /*chan*/, int prog, bool /*drum*/) +{ + 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; + prog = (hbank << 14) + (lbank << 7) + program; + + if(prog < _plugin->numPrograms) + { + for(std::vector<VST_Program>::const_iterator i = programs.begin(); i != programs.end(); ++i) + { + if(i->program == program) + return i->name.toLatin1().constData(); + } + } + 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<VST_Program>::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) + { + //_plugin->setParameter(_plugin, idx, value); + addScheduledControlEvent(idx, value, MusEGlobal::audio->curFrame()); + } + +//--------------------------------------------------------- +// guiAutomationBegin +//--------------------------------------------------------- + +void VstNativeSynthIF::guiAutomationBegin(unsigned long param_idx) +{ + AutomationType at = AUTO_OFF; + MusECore::AudioTrack* t = track(); + if(t) + at = t->automationType(); + + // FIXME TODO: This stuff should probably be sent as control FIFO events. + // And probably not safe - accessing from gui and audio threads... + + if (at == AUTO_READ || at == AUTO_TOUCH || at == AUTO_WRITE) + enableController(param_idx, false); + + int plug_id = id(); + + if(plug_id == -1) + return; + + plug_id = MusECore::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->setPluginCtrlVal(plug_id, val); + t->startAutoRecord(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->setPluginCtrlVal(plug_id, val); +// t->startAutoRecord(plug_id, val); +// } +// } +} + +//--------------------------------------------------------- +// guiAutomationEnd +//--------------------------------------------------------- + +void VstNativeSynthIF::guiAutomationEnd(unsigned long param_idx) +{ + AutomationType at = AUTO_OFF; + MusECore::AudioTrack* t = track(); + if(t) + at = t->automationType(); + + // FIXME TODO: This stuff should probably be sent as control FIFO events. + // And probably not safe - accessing from gui and audio threads... + + // Special for switch - don't enable controller until transport stopped. + if ((at == AUTO_OFF) || + (at == AUTO_READ) || + (at == AUTO_TOUCH)) // && (params[param].type != GuiParam::GUI_SWITCH || // FIXME TODO + // !MusEGlobal::audio->isPlaying()) ) ) + enableController(param_idx, true); + + int plug_id = id(); + if(!t || plug_id == -1) + return; + plug_id = MusECore::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); + //} +} + +//--------------------------------------------------------- +// guiControlEventHandler +//--------------------------------------------------------- + +int VstNativeSynthIF::guiControlChanged(unsigned long param_idx, float value) +{ + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::guiControlChanged received oscControl port:%lu val:%f\n", port, 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; + } + + ControlEvent ce; + ce.unique = false; // Not used for native vst. + ce.fromGui = true; // It came form 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); + } + + // FIXME TODO: This stuff should probably be sent as control FIFO events. + // And probably not safe - accessing from gui and audio threads... + + // Record automation: + // Take care of this immediately rather than in the fifo processing. + if(id() != -1) + { + unsigned long pid = genACnum(id(), param_idx); + AutomationType at = synti->automationType(); + + if ((at == AUTO_WRITE) || + (at == AUTO_TOUCH && MusEGlobal::audio->isPlaying())) + enableController(param_idx, false); + + synti->recordAutomation(pid, value); + } + + return 0; +} + +//--------------------------------------------------------- +// write +//--------------------------------------------------------- + +void VstNativeSynthIF::write(int level, Xml& xml) const +{ +#ifdef VST_SDK_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++, "<event type=\"%d\"", Sysex); + // 10 = 2 bytes header + "VSTSAVE" + 1 byte flags (compression etc) + xml.nput(" datalen=\"%d\">\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 MusECore::MidiPlayEvent& e, VstMidiEvent* event) +{ + int type = e.type(); + int chn = e.channel(); + int a = e.dataA(); + int b = e.dataB(); + + 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; + + #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 MusECore::ME_NOTEON: + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::ME_NOTEON\n"); + #endif + setVstEvent(event, (type | chn) & 0xff, a & 0x7f, b & 0x7f); + break; + case MusECore::ME_NOTEOFF: + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::ME_NOTEOFF\n"); + #endif + setVstEvent(event, (type | chn) & 0xff, a & 0x7f, 0); + break; + case MusECore::ME_PROGRAM: + { + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::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 MusECore::ME_CONTROLLER: + { + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::ME_CONTROLLER\n"); + #endif + + if((a == 0) || (a == 32)) + return false; + + if(a == MusECore::CTRL_PROGRAM) + { + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::ME_CONTROLLER, dataA is MusECore::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 == MusECore::CTRL_PITCH) + { + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::ME_CONTROLLER, dataA is MusECore::CTRL_PITCH\n"); + #endif + int v = b + 8192; + setVstEvent(event, (type | chn) & 0xff, v & 0x7f, (v >> 7) & 0x7f); + return true; + } + + if(a == MusECore::CTRL_AFTERTOUCH) + { + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::ME_CONTROLLER, dataA is MusECore::CTRL_AFTERTOUCH\n"); + #endif + setVstEvent(event, (type | chn) & 0xff, b & 0x7f); + return true; + } + + if((a | 0xff) == MusECore::CTRL_POLYAFTER) + { + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::ME_CONTROLLER, dataA is MusECore::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 MusECore::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; +// +// MusECore::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(MusECore::midiControllerType(a) != MusECore::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 + (MusECore::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) + MusECore::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 MusECore::ME_PITCHBEND: + { + int v = a + 8192; + setVstEvent(event, (type | chn) & 0xff, v & 0x7f, (v >> 7) & 0x7f); + } + break; + case MusECore::ME_AFTERTOUCH: + setVstEvent(event, (type | chn) & 0xff, a & 0x7f); + break; + case MusECore::ME_POLYAFTER: + setVstEvent(event, (type | chn) & 0xff, a & 0x7f, b & 0x7f); + break; + case MusECore::ME_SYSEX: + { + #ifdef VST_NATIVE_DEBUG + fprintf(stderr, "VstNativeSynthIF::processEvent midi event is MusECore::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()) + { +#ifdef VST_SDK_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) + len = 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 MusECore::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; + + // NOTE: There is a limit on the size of a sysex. Got this: + // "VstNativeSynthIF::processEvent midi event is MusECore::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 +//--------------------------------------------------------- + +iMPEvent VstNativeSynthIF::getData(MidiPort* /*mp*/, MPEventList* el, iMPEvent start_event, unsigned pos, int ports, unsigned nframes, float** buffer) +{ + // We may not be using nevents all at once - this will be just the maximum. + unsigned long nevents = el->size() + synti->eventFifo.getSize(); + VstMidiEvent events[nevents]; + char evbuf[sizeof(VstMidiEvent*) * nevents + sizeof(VstEvents)]; + VstEvents *vst_events = (VstEvents*)evbuf; + vst_events->numEvents = 0; + vst_events->reserved = 0; + + int frameOffset = MusEGlobal::audio->getFrameOffset(); + unsigned long syncFrame = MusEGlobal::audio->curSyncFrame(); + + #ifdef VST_NATIVE_DEBUG_PROCESS + fprintf(stderr, "VstNativeSynthIF::getData: pos:%u ports:%d nframes:%u syncFrame:%lu nevents:%lu\n", pos, ports, nframes, syncFrame, nevents); + #endif + + unsigned long nop, k; + 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; + unsigned long fixedsize = nframes; + + // For now, the fixed size is clamped to the MusEGlobal::audio buffer size. + // TODO: We could later add slower processing over several cycles - + // so that users can select a small MusEGlobal::audio period but a larger control period. + if(fixedsize > nframes) + fixedsize = nframes; + + unsigned long min_per = MusEGlobal::config.minControlProcessPeriod; // Must be power of 2 ! + if(min_per > nframes) + min_per = nframes; + + // 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(!((MusECore::AudioTrack*)synti)->noInRoute()) + { + RouteList* irl = ((MusECore::AudioTrack*)synti)->inRoutes(); + iRoute i = irl->begin(); + if(!i->track->isMidiTrack()) + { + int ch = i->channel == -1 ? 0 : i->channel; + int remch = i->remoteChannel == -1 ? 0 : i->remoteChannel; + int chs = i->channels == -1 ? 0 : i->channels; + + if((unsigned)ch < _synth->inPorts() && (unsigned)(ch + chs) <= _synth->inPorts()) + { + int h = remch + chs; + for(int j = remch; j < h; ++j) + _iUsedIdx[j] = true; + + ((MusECore::AudioTrack*)i->track)->copyData(pos, chs, ch, -1, nframes, &_audioInBuffers[remch]); + } + } + + ++i; + for(; i != irl->end(); ++i) + { + if(i->track->isMidiTrack()) + continue; + + int ch = i->channel == -1 ? 0 : i->channel; + int remch = i->remoteChannel == -1 ? 0 : i->remoteChannel; + int chs = i->channels == -1 ? 0 : i->channels; + + if((unsigned)ch < _synth->inPorts() && (unsigned)(ch + chs) <= _synth->inPorts()) + { + bool u1 = _iUsedIdx[remch]; + if(chs >= 2) + { + bool u2 = _iUsedIdx[remch + 1]; + if(u1 && u2) + ((MusECore::AudioTrack*)i->track)->addData(pos, chs, ch, -1, nframes, &_audioInBuffers[remch]); + else + if(!u1 && !u2) + ((MusECore::AudioTrack*)i->track)->copyData(pos, chs, ch, -1, nframes, &_audioInBuffers[remch]); + else + { + if(u1) + ((MusECore::AudioTrack*)i->track)->addData(pos, 1, ch, 1, nframes, &_audioInBuffers[remch]); + else + ((MusECore::AudioTrack*)i->track)->copyData(pos, 1, ch, 1, nframes, &_audioInBuffers[remch]); + + if(u2) + ((MusECore::AudioTrack*)i->track)->addData(pos, 1, ch + 1, 1, nframes, &_audioInBuffers[remch + 1]); + else + ((MusECore::AudioTrack*)i->track)->copyData(pos, 1, ch + 1, 1, nframes, &_audioInBuffers[remch + 1]); + } + } + else + { + if(u1) + ((MusECore::AudioTrack*)i->track)->addData(pos, 1, ch, -1, nframes, &_audioInBuffers[remch]); + else + ((MusECore::AudioTrack*)i->track)->copyData(pos, 1, ch, -1, nframes, &_audioInBuffers[remch]); + } + + 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 + + while(sample < nframes) + { + unsigned long nsamp = usefixedrate ? fixedsize : nframes - 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(id() != -1) + { + unsigned long frame = pos + sample; + AutomationType at = AUTO_OFF; + at = synti->automationType(); + bool no_auto = !MusEGlobal::automation || at == AUTO_OFF; + AudioTrack* track = (static_cast<AudioTrack*>(synti)); + int nextFrame; + const unsigned long in_ctrls = _synth->inControls(); + for(unsigned long k = 0; k < in_ctrls; ++k) + { + //_controls[k].val = track->controller()->value(genACnum(id(), k), frame, + // no_auto || !_controls[k].enCtrl || !_controls[k].en2Ctrl, + // &nextFrame); + if(dispatch(effCanBeAutomated, k, 0, NULL, 0.0f) == 1) + _plugin->setParameter(_plugin, k, track->controller()->value(genACnum(id(), k), frame, + no_auto || !_controls[k].enCtrl || !_controls[k].en2Ctrl, + &nextFrame)); +#ifdef VST_NATIVE_DEBUG + else + fprintf(stderr, "VstNativeSynthIF::getData %s parameter:%lu cannot be automated\n", name().toLatin1().constData(), k); +#endif + +#ifdef VST_NATIVE_DEBUG_PROCESS + fprintf(stderr, "VstNativeSynthIF::getData k:%lu sample:%lu frame:%lu nextFrame:%d nsamp:%lu \n", k, sample, frame, nextFrame, nsamp); +#endif + if(MusEGlobal::audio->isPlaying() && !usefixedrate && nextFrame != -1) + { + // Returned value of nextFrame can be zero meaning caller replaces with some (constant) value. + unsigned long samps = (unsigned long)nextFrame; + if(samps > frame + min_per) + { + unsigned long diff = samps - frame; + unsigned long mask = min_per-1; // min_per must be power of 2 + samps = diff & ~mask; + if((diff & mask) != 0) + samps += min_per; + } + else + samps = min_per; + + if(samps < nsamp) + nsamp = samps; + } + } +#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 >= _synth->inControls()) // Sanity check. + break; + found = true; + frame = evframe; + index = v.idx; + // Set the ladspa control port value. + //controls[v.idx].val = v.value; + if(dispatch(effCanBeAutomated, v.idx, 0, NULL, 0.0f) == 1) + _plugin->setParameter(_plugin, v.idx, v.value); +#ifdef VST_NATIVE_DEBUG + else + fprintf(stderr, "VstNativeSynthIF::getData %s parameter:%lu cannot be automated\n", name().toLatin1().constData(), v.idx); +#endif + + // Need to update the automation value, otherwise it overwrites later with the last automation value. + if(id() != -1) + synti->setPluginCtrlVal(genACnum(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) + continue; + + nevents = 0; + // 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) + { + MusECore::MidiPort* mp = &MusEGlobal::midiPorts[synti->midiPort()]; + if(start_event->type() == MusECore::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() == MusECore::ME_PITCHBEND) + { + int da = mp->limitValToInstrCtlRange(MusECore::CTRL_PITCH, start_event->dataA()); + if(!mp->setHwCtrlState(start_event->channel(), MusECore::CTRL_PITCH, da)) + continue; + } + else if(start_event->type() == MusECore::ME_AFTERTOUCH) + { + int da = mp->limitValToInstrCtlRange(MusECore::CTRL_AFTERTOUCH, start_event->dataA()); + if(!mp->setHwCtrlState(start_event->channel(), MusECore::CTRL_AFTERTOUCH, da)) + continue; + } + else if(start_event->type() == MusECore::ME_POLYAFTER) + { + int ctl = (MusECore::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() == MusECore::ME_PROGRAM) + { + if(!mp->setHwCtrlState(start_event->channel(), MusECore::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()) + { + MusECore::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. + // 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 + + // 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()]; + + 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); + } +#if 0 //ifdef VST_NATIVE_FORCE_DEPRECATED + else + { + // TODO: This is not right, we don't accumulate data here. Depricated now, and frowned upon anyway... + if(_plugin->process) + _plugin->process(_plugin, in_bufs, out_bufs, nsamp); + } +#endif + + sample += nsamp; + } + + // Inform the host callback we will be no longer in the audio thread. + _inProcess = true; + + 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::enable2Controller(unsigned long i, bool v) { _controls[i].en2Ctrl = v; } +bool VstNativeSynthIF::controllerEnabled2(unsigned long i) const { return _controls[i].en2Ctrl; } +void VstNativeSynthIF::enableAllControllers(bool v) +{ + if(!_synth) + return; + for(unsigned long i = 0; i < _synth->inControls(); ++i) + _controls[i].enCtrl = v; +} +void VstNativeSynthIF::enable2AllControllers(bool v) +{ + if(!_synth) + return; + for(unsigned long i = 0; i < _synth->inControls(); ++i) + _controls[i].en2Ctrl = 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); +//#ifdef VST_SDK_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; +// } +// } +} +void VstNativeSynthIF::deactivate() +{ + //for (unsigned short i = 0; i < instances(); ++i) { +//#ifdef VST_SDK_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 + diff --git a/muse2/muse/vst_native.h b/muse2/muse/vst_native.h new file mode 100644 index 00000000..3b877cd8 --- /dev/null +++ b/muse2/muse/vst_native.h @@ -0,0 +1,281 @@ +//========================================================= +// MusE +// Linux Music Editor +// +// vst_native.h +// (C) Copyright 2012 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. +// +//========================================================= + +#ifndef __VST_NATIVE_H__ +#define __VST_NATIVE_H__ + +#include "config.h" + +// Make sure this number is unique among all the MESS synths and DSSI and VST host synths. +#define VST_NATIVE_SYNTH_UNIQUE_ID 9 +// Midistate sysex initialization command. +#define VST_NATIVE_INIT_DATA_CMD 1 + +#define VST_NATIVE_PARAMSAVE_VERSION_MAJOR 0 +#define VST_NATIVE_PARAMSAVE_VERSION_MINOR 1 + +#define VST_NATIVE_CHUNK_FLAG_COMPRESSED 1 + +#ifdef VST_NATIVE_SUPPORT + +class VstNativeSynthIF; + +typedef class VstNativeSynthIF VSTPlugin; + +#include "aeffectx.h" + +//#define VST_2_3_EXTENSIONS // TODO Detect this ? +#define VST_2_4_EXTENSIONS // TODO Detect this ? +#define VST_NATIVE_FORCE_DEPRECATED // TODO On or off? + +#ifndef VST_SDK_SUPPORT +#ifndef effCanBeAutomated +#define effCanBeAutomated 26 +#endif +#ifndef effGetProgramNameIndexed +#define effGetProgramNameIndexed 29 +#endif +#ifndef effBeginSetProgram +#define effBeginSetProgram 67 +#endif +#ifndef effEndSetProgram +#define effEndSetProgram 68 +#endif +#ifndef effStartProcess +#define effStartProcess 71 +#endif +#ifndef effStopProcess +#define effStopProcess 72 +#endif +#endif + +#if defined(VST_2_4_EXTENSIONS) +#ifdef VST_SDK_SUPPORT +typedef long VstInt32; +typedef long VstIntPtr; +#else +typedef int32_t VstInt32; +typedef intptr_t VstIntPtr; +#endif +#define VSTCALLBACK +#endif + +#include "vst_native_editor.h" +#include "synth.h" +#include "plugin.h" +#include "midictrl.h" + +#endif // VST_NATIVE_SUPPORT + +namespace MusEGui { +class PopupMenu; +} + +namespace MusECore { + +#ifdef VST_NATIVE_SUPPORT + +struct VstRect{ + short top; + short left; + short bottom; + short right; +}; + +struct VST_Program { + //unsigned long bank; + unsigned long program; + QString name; +}; + + +//--------------------------------------------------------- +// VstNativeSynth +//--------------------------------------------------------- + +class VstNativeSynth : public Synth { + enum VstPluginFlags + { + canSendVstEvents = 1 << 0, + canSendVstMidiEvents = 1 << 1, + canSendVstTimeInfo = 1 << 2, + canReceiveVstEvents = 1 << 3, + canReceiveVstMidiEvents = 1 << 4, + canReceiveVstTimeInfo = 1 << 5, + canProcessOffline = 1 << 6, + canUseAsInsert = 1 << 7, + canUseAsSend = 1 << 8, + canMixDryWet = 1 << 9, + canMidiProgramNames = 1 << 10 + }; + + void* _handle; + int _vst_version; + unsigned int _flags; + + unsigned long /*_portCount,*/ _inports, _outports, _controlInPorts; //, _controlOutPorts; + std::vector<unsigned long> iIdx; // Audio input index to port number. + std::vector<unsigned long> oIdx; // Audio output index to port number. + std::vector<unsigned long> rpIdx; // Port number to control input index. Item is -1 if it's not a control input. + MusECore::MidiCtl2LadspaPortMap midiCtl2PortMap; // Maps midi controller numbers to vst port numbers. + MusECore::MidiCtl2LadspaPortMap port2MidiCtlMap; // Maps vst port numbers to midi controller numbers. + bool _hasGui; + bool _inPlaceCapable; + bool _hasChunks; + + public: + VstNativeSynth(const QFileInfo& fi, AEffect* plugin, const QString& label, const QString& desc, const QString& maker, const QString& ver); + + virtual ~VstNativeSynth() {} + virtual Type synthType() const { return VST_NATIVE_SYNTH; } + virtual void incInstances(int val); + virtual AEffect* instantiate(); + virtual SynthIF* createSIF(SynthI*); + unsigned long inPorts() const { return _inports; } + unsigned long outPorts() const { return _outports; } + unsigned long inControls() const { return _controlInPorts; } + //unsigned long outControls() const { return _controlOutPorts; } + + int vstVersion() const { return _vst_version; } + bool hasChunks() const { return _hasChunks; } + const std::vector<unsigned long>* getRpIdx() { return &rpIdx; } + }; + +//--------------------------------------------------------- +// VstNativeSynthIF +// VSTi synthesizer instance +//--------------------------------------------------------- + +class VstNativeSynthIF : public SynthIF + { + friend class VstNativeSynth; + friend class MusEGui::VstNativeEditor; + + VstNativeSynth* _synth; + AEffect* _plugin; + MusEGui::VstNativeEditor* _editor; + bool _guiVisible; + bool _inProcess; // To inform the callback of the 'process level' - are we in the audio thread? + + Port* _controls; + float** _audioOutBuffers; + float** _audioInBuffers; + std::vector<unsigned long> _iUsedIdx; // During process, tells whether an audio input port was used by any input routes. + float* _audioInSilenceBuf; // Just all zeros all the time, so we don't have to clear for silence. + //float** _audioInSilenceBufs; // Just all zeros all the time, so we don't have to clear for silence. + + std::vector<VST_Program> programs; + void queryPrograms(); + void doSelectProgram(int bankH, int bankL, int prog); + bool processEvent(const MusECore::MidiPlayEvent&, VstMidiEvent*); + void setVstEvent(VstMidiEvent* event, int a = 0, int b = 0, int c = 0, int d = 0); + + void editorDeleted(); + void editorOpened(); + void editorClosed(); + + public: + VstNativeSynthIF(SynthI* s); + virtual ~VstNativeSynthIF(); + + virtual bool init(Synth*); + + AEffect* plugin() const { return _plugin; } + VstIntPtr hostCallback(VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt); + VstIntPtr dispatch(VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) const { + if(_plugin) return _plugin->dispatcher(_plugin, opcode, index, value, ptr, opt); return 0; } + void idleEditor(); + bool resizeEditor(int w, int h); + + virtual bool initGui() { return true; }; + virtual void guiHeartBeat() { } + virtual bool guiVisible() const; + virtual void showGui(bool); + virtual bool hasGui() const { return true; } + virtual bool nativeGuiVisible() const; + virtual void showNativeGui(bool v); + virtual bool hasNativeGui() const; + virtual void getGeometry(int*x, int*y, int*w, int*h) const { *x=0;*y=0;*w=0;*h=0; } + virtual void setGeometry(int, int, int, int) {} + virtual void getNativeGeometry(int*x, int*y, int*w, int*h) const { *x=0;*y=0;*w=0;*h=0; } + virtual void setNativeGeometry(int, int, int, int) {} + virtual void preProcessAlways() { }; + virtual iMPEvent getData(MidiPort*, MPEventList*, iMPEvent, unsigned pos, int ports, unsigned nframes, float** buffer) ; + virtual bool putEvent(const MidiPlayEvent& ev); + virtual MidiPlayEvent receiveEvent(); + virtual int eventsPending() const { return 0; } + virtual int channels() const; + virtual int totalOutChannels() const; + virtual int totalInChannels() const; + virtual void deactivate3(); + virtual const char* getPatchName(int chan, int prog, bool drum); + virtual void populatePatchPopup(MusEGui::PopupMenu* menu, int chan, bool drum); + virtual void write(int level, Xml& xml) const; + virtual float getParameter(unsigned long idx) const; + virtual void setParameter(unsigned long idx, float value); + virtual int getControllerInfo(int, const char**, int*, int*, int*, int*) { return 0; } + + virtual void guiAutomationBegin(unsigned long param_idx); + virtual void guiAutomationEnd(unsigned long param_idx); + virtual int guiControlChanged(unsigned long param_idx, float value); + + //------------------------- + // Methods for PluginIBase: + //------------------------- + unsigned long pluginID(); + int id(); + QString pluginLabel() const; + QString lib() const; + QString dirPath() const; + QString fileName() const; + void enableController(unsigned long i, bool v = true); + bool controllerEnabled(unsigned long i) const; + void enable2Controller(unsigned long i, bool v = true); + bool controllerEnabled2(unsigned long i) const; + void enableAllControllers(bool v = true); + void enable2AllControllers(bool v = true); + void updateControllers(); + void activate(); + void deactivate(); + + unsigned long parameters() const; + unsigned long parametersOut() const; + void setParam(unsigned long i, float val); + float param(unsigned long i) const; + float paramOut(unsigned long i) const; + const char* paramName(unsigned long i); + const char* paramOutName(unsigned long i); + LADSPA_PortRangeHint range(unsigned long i); + LADSPA_PortRangeHint rangeOut(unsigned long i); + CtrlValueType ctrlValueType(unsigned long i) const; + CtrlList::Mode ctrlMode(unsigned long i) const; + }; + +#endif // VST_NATIVE_SUPPORT + +extern void initVST_Native(); + +} // namespace MusECore + +#endif + diff --git a/muse2/muse/widgets/CMakeLists.txt b/muse2/muse/widgets/CMakeLists.txt index ed72e1a9..8c407525 100644 --- a/muse2/muse/widgets/CMakeLists.txt +++ b/muse2/muse/widgets/CMakeLists.txt @@ -105,6 +105,7 @@ QT4_WRAP_CPP (widget_mocs view.h vscale.h visibletracks.h + vst_native_editor.h warn_bad_timing.h ) @@ -231,6 +232,7 @@ file (GLOB widgets_source_files view.cpp vscale.cpp visibletracks.cpp + vst_native_editor.cpp warn_bad_timing.cpp ) diff --git a/muse2/muse/widgets/vst_native_editor.cpp b/muse2/muse/widgets/vst_native_editor.cpp new file mode 100644 index 00000000..8f739041 --- /dev/null +++ b/muse2/muse/widgets/vst_native_editor.cpp @@ -0,0 +1,223 @@ +//========================================================= +// MusE +// Linux Music Editor +// vst_native_editor.cpp +// (C) Copyright 2012 Tim E. Real (terminator356 on users dot sourceforge dot net) +// Some of the editor window coding was adapted from QTractor (by rncbc aka Rui Nuno Capela) +// +// 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 "vst_native_editor.h" +#include "vst_native.h" + +#include <QtGlobal> +#if defined(Q_WS_X11) +#include <QX11Info> +#endif + +namespace MusEGui { + +//--------------------------------------------------------------------- +// Helpers for editor widget. +//--------------------------------------------------------------------- + +#if defined(Q_WS_X11) + +static bool g_bXError = false; + +static int tempXErrorHandler ( Display *, XErrorEvent * ) +{ + g_bXError = true; + return 0; +} + +static XEventProc getXEventProc ( Display *pDisplay, Window w ) +{ + int iSize; + unsigned long iBytes, iCount; + unsigned char *pData; + XEventProc eventProc = NULL; + Atom aType, aName = XInternAtom(pDisplay, "_XEventProc", false); + + g_bXError = false; + XErrorHandler oldErrorHandler = XSetErrorHandler(tempXErrorHandler); + XGetWindowProperty(pDisplay, w, aName, 0, 1, false, + AnyPropertyType, &aType, &iSize, &iCount, &iBytes, &pData); + if (g_bXError == false && iCount == 1) + eventProc = (XEventProc) (pData); + XSetErrorHandler(oldErrorHandler); + + return eventProc; +} + +static Window getXChildWindow ( Display *pDisplay, Window w ) +{ + Window wRoot, wParent, *pwChildren; + unsigned int iChildren = 0; + + XQueryTree(pDisplay, w, &wRoot, &wParent, &pwChildren, &iChildren); + + return (iChildren > 0 ? pwChildren[0] : 0); +} + +#endif // Q_WS_X11 + +VstNativeEditor::VstNativeEditor(QWidget *parent, Qt::WindowFlags wflags) + : QWidget(parent, wflags), + #if defined(Q_WS_X11) + _display(QX11Info::display()), + _vstEditor(0), + _vstEventProc(0), + _buttonPress(false), + #endif + _sif(0) +{ + setAttribute(Qt::WA_DeleteOnClose); +} + +VstNativeEditor::~VstNativeEditor() +{ + if(_sif) + { + _sif->dispatch(effEditClose, 0, 0, NULL, 0.0f); + _sif->editorDeleted(); + _sif = NULL; + } +} + +//--------------------------------------------------------------------- +// open +//--------------------------------------------------------------------- + +void VstNativeEditor::open(MusECore::VstNativeSynthIF* sif) +{ + _sif = sif; + + // Start the proper (child) editor... + long value = 0; + void *ptr = (void *) winId(); +#if defined(Q_WS_X11) + value = (long) _display; +#endif + + MusECore::VstRect* pRect; + if(_sif->dispatch(effEditGetRect, 0, 0, &pRect, 0.0f)) + { + int w = pRect->right - pRect->left; + int h = pRect->bottom - pRect->top; + if (w > 0 && h > 0) + QWidget::setFixedSize(w, h); + } + + _sif->dispatch(effEditOpen, 0, value, ptr, 0.0f); + +#if defined(Q_WS_X11) + _vstEditor = getXChildWindow(_display, (Window) winId()); + if(_vstEditor) + _vstEventProc = getXEventProc(_display, _vstEditor); +#endif + + if(_sif->track()) + { + QString title = _sif->track()->name() + ":" + _sif->pluginLabel(); + setWindowTitle(title); + } + + //_sif->editorOpened(); + if(!isVisible()) + show(); + raise(); + activateWindow(); + _sif->idleEditor(); +} + +#if defined(Q_WS_X11) + +//--------------------------------------------------------------------- +// x11EventFilter +//--------------------------------------------------------------------- + +bool VstNativeEditor::x11EventFilter(XEvent *pEvent) +{ + if(_vstEventProc && pEvent->xany.window == _vstEditor) + { + // Avoid mouse tracking events... + switch (pEvent->xany.type) { + case ButtonPress: + _buttonPress = true; + break; + case ButtonRelease: + _buttonPress = false; + break; + case MotionNotify: + if(!_buttonPress) + return false; + // Fall thru... + default: + break; + } + // Process as intended... + (*_vstEventProc)(pEvent); + return true; + } + else + return false; +} + +#endif + +//--------------------------------------------------------------------- +// showEvent +//--------------------------------------------------------------------- + +void VstNativeEditor::showEvent(QShowEvent *pShowEvent) +{ + QWidget::showEvent(pShowEvent); + + if(_sif) + _sif->editorOpened(); +} + +//--------------------------------------------------------------------- +// closeEvent +//--------------------------------------------------------------------- + +void VstNativeEditor::closeEvent(QCloseEvent *pCloseEvent) +{ + if(_sif) + _sif->editorClosed(); + + QWidget::closeEvent(pCloseEvent); +} + +//--------------------------------------------------------------------- +// moveEvent +//--------------------------------------------------------------------- + +void VstNativeEditor::moveEvent(QMoveEvent *pMoveEvent) +{ + QWidget::moveEvent(pMoveEvent); +#if defined(Q_WS_X11) + if(_vstEditor) + { + XMoveWindow(_display, _vstEditor, 0, 0); + //QWidget::update(); // REMOVE Tim. Or keep? Commented in Qtractor. + } +#endif +} + +} // namespace MusEGui diff --git a/muse2/muse/widgets/vst_native_editor.h b/muse2/muse/widgets/vst_native_editor.h new file mode 100644 index 00000000..1f591275 --- /dev/null +++ b/muse2/muse/widgets/vst_native_editor.h @@ -0,0 +1,91 @@ +//========================================================= +// MusE +// Linux Music Editor +// vst_native_editor.h +// (C) Copyright 2012 Tim E. Real (terminator356 on users dot sourceforge dot net) +// Some of the editor window coding was adapted from QTractor (by rncbc aka Rui Nuno Capela) +// +// 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. +// +//========================================================= + +#ifndef __VST_NATIVE_EDITOR_H__ +#define __VST_NATIVE_EDITOR_H__ + +//#include "vst_native.h" +//#include <QtGlobal> +#include <QWidget> + +#if defined(Q_WS_X11) +#include <QX11Info> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#undef Bool +#undef Status +#undef None +#undef KeyPress +#undef KeyRelease +#undef FocusIn +#undef FocusOut +#undef Type +#undef FontChange +#undef CursorShape +#undef Unsorted +typedef void (*XEventProc)(XEvent *); +#endif + +namespace MusECore { +class VstNativeSynthIF; +} + +namespace MusEGui { + +class VstNativeEditor : public QWidget +{ + Q_OBJECT + +#if defined(Q_WS_X11) + Display* _display; + Window _vstEditor; + XEventProc _vstEventProc; + bool _buttonPress; +#endif + + MusECore::VstNativeSynthIF* _sif; + +protected: + + virtual void showEvent(QShowEvent *pShowEvent); + virtual void closeEvent(QCloseEvent *pCloseEvent); + virtual void moveEvent(QMoveEvent *pMoveEvent); + +public: + VstNativeEditor(QWidget *parent, Qt::WindowFlags wflags = 0); + ~VstNativeEditor(); + + void open(MusECore::VstNativeSynthIF* sif); + //void close(); + +#if defined(Q_WS_X11) + // Local X11 event filter. + bool x11EventFilter(XEvent *pEvent); +#endif + + //MusECore::VstNativeSynthIF* sif() const { return _sif; } +}; + +} // namespace MusEGui + +#endif |