text: //
  // Vst2xHostCallback.cpp - MrsWatson
  // Copyright (c) 2016 Teragon Audio. All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are met:
  //
  // * Redistributions of source code must retain the above copyright notice,
  //   this list of conditions and the following disclaimer.
  // * Redistributions in binary form must reproduce the above copyright notice,
  //   this list of conditions and the following disclaimer in the documentation
  //   and/or other materials provided with the distribution.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  // POSSIBILITY OF SUCH DAMAGE.
  //
  
  #include "PluginVst2xHostCallback.h"
  
  // C++ includes
  #define VST_FORCE_DEPRECATED 0
  #include "aeffectx.h"
  
  // C includes
  extern "C" {
  #include "app/BuildInfo.h"
  #include "audio/AudioSettings.h"
  #include "base/CharString.h"
  #include "logging/EventLogger.h"
  #include "plugin/PluginChain.h"
  #include "plugin/PluginVst2x.h"
  #include "plugin/PluginVst2xId.h"
  #include "time/AudioClock.h"
  
  #include <math.h>
  #include <stdio.h>
  #include <string.h>
  
  void pluginVst2xAudioMasterIOChanged(const Plugin self,
                                       AEffect const *const newValues);
  }
  
  // Global variables (sigh, unfortunately yes). When plugins ask for the time,
  // they expect that the host will pass them a pointer to a VstTimeInfo struct, a
  // pointer to which they are not the owner of and are not expected to free
  // afterwards... which is actually the correct thing to do, given that if this
  // were the case, a huge number of plugins would probably fail to do this and
  // leak memory all over the place. Anyways, since we cannot scope this variable
  // intelligently, we instead keep one instance of it as a static variable, so it
  // is always available to plugins when they ask for the time.
  static VstTimeInfo vstTimeInfo;
  
  extern "C" {
  
  // Current plugin ID, which is mostly used by shell plugins during
  // initialization. See PluginVst2x.cpp for more details, including why this
  // must be global.
  extern VstInt32 currentPluginUniqueId;
  
  static int _canHostDo(const char *pluginName, const char *canDoString) {
    boolByte supported = false;
  
    logDebug("Plugin '%s' asked if we can do '%s'", pluginName, canDoString);
  
    if (!strcmp(canDoString, EMPTY_STRING)) {
      logWarn("Plugin '%s' asked if we can do an empty string. This is probably "
              "a bug in the plugin.",
              pluginName);
    } else if (!strcmp(canDoString, "sendVstEvents")) {
      supported = true;
    } else if (!strcmp(canDoString, "sendVstMidiEvent")) {
      supported = true;
    } else if (!strcmp(canDoString, "sendVstTimeInfo")) {
      supported = true;
    } else if (!strcmp(canDoString, "receiveVstEvents")) {
      supported = false;
    } else if (!strcmp(canDoString, "receiveVstMidiEvent")) {
      supported = false;
    } else if (!strcmp(canDoString, "reportConnectionChanges")) {
      supported = false;
    } else if (!strcmp(canDoString, "acceptIOChanges")) {
      supported = false;
    } else if (!strcmp(canDoString, "sizeWindow")) {
      supported = false;
    } else if (!strcmp(canDoString, "offline")) {
      supported = false;
    } else if (!strcmp(canDoString, "openFileSelector")) {
      supported = false;
    } else if (!strcmp(canDoString, "closeFileSelector")) {
      supported = false;
    } else if (!strcmp(canDoString, "startStopProcess")) {
      supported = true;
    } else if (!strcmp(canDoString, "shellCategory")) {
      supported = true;
    } else if (!strcmp(canDoString, "sendVstMidiEventFlagIsRealtime")) {
      supported = false;
    } else {
      logInfo("Plugin '%s' asked if host canDo '%s' (unimplemented)", pluginName,
              canDoString);
    }
  
    return supported;
  }
  
  VstIntPtr VSTCALLBACK pluginVst2xHostCallback(AEffect *effect, VstInt32 opcode,
                                                VstInt32 index, VstIntPtr value,
                                                void *dataPtr, float opt) {
    // This string is used in a bunch of logging calls below
    PluginVst2xId pluginId;
  
    if (effect != NULL) {
      pluginId = newPluginVst2xIdWithId((unsigned long)effect->uniqueID);
    } else {
      // During plugin initialization, the dispatcher can be called without a
      // valid plugin instance, as the AEffect* struct is still not fully
      // constructed at that point.
      pluginId = newPluginVst2xId();
    }
  
    const char *pluginIdString = pluginId->idString->data;
    VstIntPtr result = 0;
  
    logDebug("Plugin '%s' called host dispatcher with %d, %d, %d", pluginIdString,
             opcode, index, value);
  
    switch (opcode) {
    case audioMasterAutomate:
      // The plugin will call this if a parameter has changed via MIDI or the GUI,
      // so the host can update itself accordingly. We don't care about this (for
      // the time being), and as we don't support either GUI's or live MIDI, this
      // opcode can be ignored.
      break;
  
    case audioMasterVersion:
      // We are a VST 2.4 compatible host
      result = 2400;
      break;
  
    case audioMasterCurrentId:
      // Use the current plugin ID, needed by VST shell plugins to determine which
      // sub-plugin to load
      result = currentPluginUniqueId;
      break;
  
    case audioMasterIdle:
      // Ignore
      result = 1;
      break;
  
    case audioMasterPinConnected:
      logDeprecated("audioMasterPinConnected", pluginIdString);
      break;
  
    case audioMasterWantMidi:
      // This (deprecated) call is sometimes made by VST2.3 instruments to tell
      // the host that it is an instrument. We can safely ignore it.
      result = 1;
      break;
  
    case audioMasterGetTime: {
      AudioClock audioClock = getAudioClock();
  
      // These values are always valid
      vstTimeInfo.samplePos = audioClock->currentFrame;
      vstTimeInfo.sampleRate = getSampleRate();
  
      // Set flags for transport state
      vstTimeInfo.flags = 0;
      vstTimeInfo.flags |=
          audioClock->transportChanged ? kVstTransportChanged : 0;
      vstTimeInfo.flags |= audioClock->isPlaying ? kVstTransportPlaying : 0;
  
      // Fill values based on other flags which may have been requested
      if (value & kVstNanosValid) {
        // It doesn't make sense to return this value, as the plugin may try to
        // calculate something based on the current system time. As we are running
        // offline, anything the plugin calculates here will probably be wrong
        // given the way we are running.  However, for real-time mode, this flag
        // should be implemented in that case.
        logWarn("Plugin '%s' asked for time in nanoseconds (unsupported)",
                pluginIdString);
      }
  
      if (value & kVstPpqPosValid) {
        // TODO: Move calculations to AudioClock
        double samplesPerBeat = (60.0 / getTempo()) * getSampleRate();
        // Musical time starts with 1, not 0
        vstTimeInfo.ppqPos = (vstTimeInfo.samplePos / samplesPerBeat) + 1.0;
        logDebug("Current PPQ position is %g", vstTimeInfo.ppqPos);
        vstTimeInfo.flags |= kVstPpqPosValid;
      }
  
      if (value & kVstTempoValid) {
        vstTimeInfo.tempo = getTempo();
        vstTimeInfo.flags |= kVstTempoValid;
      }
  
      if (value & kVstBarsValid) {
        if (!(value & kVstPpqPosValid)) {
          logError("Plugin requested position in bars, but not PPQ");
        }
  
        // TODO: Move calculations to AudioClock
        double currentBarPos =
            floor(vstTimeInfo.ppqPos / (double)getTimeSignatureBeatsPerMeasure());
        vstTimeInfo.barStartPos =
            currentBarPos * (double)getTimeSignatureBeatsPerMeasure() + 1.0;
        logDebug("Current bar is %g", vstTimeInfo.barStartPos);
        vstTimeInfo.flags |= kVstBarsValid;
      }
  
      if (value & kVstCyclePosValid) {
        // We don't support cycling, so this is always 0
      }
  
      if (value & kVstTimeSigValid) {
        vstTimeInfo.timeSigNumerator = getTimeSignatureBeatsPerMeasure();
        vstTimeInfo.timeSigDenominator = getTimeSignatureNoteValue();
        vstTimeInfo.flags |= kVstTimeSigValid;
      }
  
      if (value & kVstSmpteValid) {
        logUnsupportedFeature("Current time in SMPTE format");
      }
  
      if (value & kVstClockValid) {
        logUnsupportedFeature("Sample frames until next clock");
      }
  
      result = (VstIntPtr)&vstTimeInfo;
      break;
    }
  
    case audioMasterProcessEvents:
      logUnsupportedFeature("VST master opcode audioMasterProcessEvents");
      break;
  
    case audioMasterSetTime:
      logDeprecated("audioMasterSetTime", pluginIdString);
      break;
  
    case audioMasterTempoAt:
      logDeprecated("audioMasterTempoAt", pluginIdString);
      break;
  
    case audioMasterGetNumAutomatableParameters:
      logDeprecated("audioMasterGetNumAutomatableParameters", pluginIdString);
      break;
  
    case audioMasterGetParameterQuantization:
      logDeprecated("audioMasterGetParameterQuantization", pluginIdString);
      break;
  
    case audioMasterIOChanged: {
      if (effect != NULL) {
        PluginChain pluginChain = getPluginChain();
        logDebug("Number of inputs: %d", effect->numInputs);
        logDebug("Number of outputs: %d", effect->numOutputs);
        logDebug("Number of parameters: %d", effect->numParams);
        logDebug("Initial Delay: %d", effect->initialDelay);
        result = -1;
  
        for (unsigned int i = 0; i < pluginChain->numPlugins; ++i) {
          if ((unsigned long)effect->uniqueID ==
              pluginVst2xGetUniqueId(pluginChain->plugins[i])) {
            logDebug("Updating plugin");
            pluginVst2xAudioMasterIOChanged(pluginChain->plugins[i], effect);
            result = 0;
            break; // Only one plugin will match anyway.
          }
        }
  
        break;
      }
    }
  
    case audioMasterNeedIdle:
      logDeprecated("audioMasterNeedIdle", pluginIdString);
      break;
  
    case audioMasterSizeWindow:
      logWarn("Plugin '%s' asked us to resize window (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterGetSampleRate:
      result = (int)getSampleRate();
      break;
  
    case audioMasterGetBlockSize:
      result = (VstIntPtr)getBlocksize();
      break;
  
    case audioMasterGetInputLatency:
      // Input latency is not used, and is always 0
      result = 0;
      break;
  
    case audioMasterGetOutputLatency:
      // Output latency is not used, and is always 0
      result = 0;
      break;
  
    case audioMasterGetPreviousPlug:
      logDeprecated("audioMasterGetPreviousPlug", pluginIdString);
      break;
  
    case audioMasterGetNextPlug:
      logDeprecated("audioMasterGetNextPlug", pluginIdString);
      break;
  
    case audioMasterWillReplaceOrAccumulate:
      logDeprecated("audioMasterWillReplaceOrAccumulate", pluginIdString);
      break;
  
    case audioMasterGetCurrentProcessLevel:
      // We are not a multi-threaded app and have no GUI, so this is unsupported
      result = kVstProcessLevelUnknown;
      break;
  
    case audioMasterGetAutomationState:
      // Automation is also not supported (for now)
      result = kVstAutomationUnsupported;
      break;
  
    case audioMasterOfflineStart:
      logWarn("Plugin '%s' asked us to start offline processing (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterOfflineRead:
      logWarn("Plugin '%s' asked to read offline data (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterOfflineWrite:
      logWarn("Plugin '%s' asked to write offline data (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterOfflineGetCurrentPass:
      logWarn("Plugin '%s' asked for current offline pass (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterOfflineGetCurrentMetaPass:
      logWarn("Plugin '%s' asked for current offline meta pass (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterSetOutputSampleRate:
      logDeprecated("audioMasterSetOutputSampleRate", pluginIdString);
      break;
  
    case audioMasterGetOutputSpeakerArrangement:
      logDeprecated("audioMasterGetOutputSpeakerArrangement", pluginIdString);
      break;
  
    case audioMasterGetVendorString:
      strncpy((char *)dataPtr, VENDOR_NAME, kVstMaxVendorStrLen);
      result = 1;
      break;
  
    case audioMasterGetProductString:
      strncpy((char *)dataPtr, PROGRAM_NAME, kVstMaxProductStrLen);
      result = 1;
      break;
  
    case audioMasterGetVendorVersion:
      // Return our version as a single string, in the form ABCC, which
      // corresponds to version A.B.C
      // Often times the patch can reach double-digits, so it gets two decimal
      // places.
      result = VERSION_MAJOR * 1000 + VERSION_MINOR * 100 + VERSION_PATCH;
      break;
  
    case audioMasterVendorSpecific:
      logWarn("Plugin '%s' made a vendor specific call (unsupported). Arguments: "
              "%d, %d, %f",
              pluginIdString, index, value, opt);
      break;
  
    case audioMasterCanDo:
      result = _canHostDo(pluginIdString, (char *)dataPtr);
      break;
  
    case audioMasterSetIcon:
      logDeprecated("audioMasterSetIcon", pluginIdString);
      break;
  
    case audioMasterGetLanguage:
      result = kVstLangEnglish;
      break;
  
    case audioMasterOpenWindow:
      logDeprecated("audioMasterOpenWindow", pluginIdString);
      break;
  
    case audioMasterCloseWindow:
      logDeprecated("audioMasterCloseWindow", pluginIdString);
      break;
  
    case audioMasterGetDirectory:
      logWarn("Plugin '%s' asked for directory pointer (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterUpdateDisplay:
      // Ignore
      break;
  
    case audioMasterBeginEdit:
      logWarn("Plugin '%s' asked to begin parameter automation (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterEndEdit:
      logWarn("Plugin '%s' asked to end parameter automation (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterOpenFileSelector:
      logWarn("Plugin '%s' asked us to open file selector (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterCloseFileSelector:
      logWarn("Plugin '%s' asked us to close file selector (unsupported)",
              pluginIdString);
      break;
  
    case audioMasterEditFile:
      logDeprecated("audioMasterEditFile", pluginIdString);
      break;
  
    case audioMasterGetChunkFile:
      logDeprecated("audioMasterGetChunkFile", pluginIdString);
      break;
  
    case audioMasterGetInputSpeakerArrangement:
      logDeprecated("audioMasterGetInputSpeakerArrangement", pluginIdString);
      break;
  
    default:
      logWarn("Plugin '%s' asked if host can do unknown opcode %d",
              pluginIdString, opcode);
      break;
    }
  
    freePluginVst2xId(pluginId);
    return result;
  }
  } // extern "C"
  