2024-09-24 12:54:57 +00:00
/*
* Vstplug . cpp
* - - - - - - - - - - -
* Purpose : VST Plugin handling / processing
* Notes : ( currently none )
* Authors : OpenMPT Devs
* The OpenMPT source code is released under the BSD license . Read LICENSE for more details .
*/
# include "stdafx.h"
# ifdef MPT_WITH_VST
# include "Vstplug.h"
# ifdef MODPLUG_TRACKER
# include "Moddoc.h"
# include "Mainfrm.h"
# include "AbstractVstEditor.h"
# include "VSTEditor.h"
# include "DefaultVstEditor.h"
# include "ExceptionHandler.h"
# endif // MODPLUG_TRACKER
# include "../soundlib/Sndfile.h"
# include "../soundlib/MIDIEvents.h"
# include "MIDIMappingDialog.h"
# include "../common/mptStringBuffer.h"
# include "FileDialog.h"
# include "../pluginBridge/BridgeWrapper.h"
# include "../pluginBridge/BridgeOpCodes.h"
# include "../soundlib/plugins/OpCodes.h"
# include "../soundlib/plugins/PluginManager.h"
# include "../misc/mptOSException.h"
using namespace Vst ;
DECLARE_FLAGSET ( Vst : : VstTimeInfoFlags )
OPENMPT_NAMESPACE_BEGIN
static VstTimeInfo g_timeInfoFallback = { 0 } ;
# ifdef MPT_ALL_LOGGING
# define VST_LOG
# endif
using VstCrash = Windows : : SEH : : Code ;
bool CVstPlugin : : MaskCrashes ( ) noexcept
{
return m_maskCrashes ;
}
template < typename Tfn >
DWORD CVstPlugin : : SETryOrError ( bool maskCrashes , Tfn fn )
{
DWORD exception = 0 ;
if ( maskCrashes )
{
exception = Windows : : SEH : : TryOrError ( fn ) ;
if ( exception )
{
ExceptionHandler : : TaintProcess ( ExceptionHandler : : TaintReason : : Plugin ) ;
}
} else
{
fn ( ) ;
}
return exception ;
}
template < typename Tfn >
DWORD CVstPlugin : : SETryOrError ( Tfn fn )
{
DWORD exception = 0 ;
if ( MaskCrashes ( ) )
{
exception = Windows : : SEH : : TryOrError ( fn ) ;
if ( exception )
{
ExceptionHandler : : TaintProcess ( ExceptionHandler : : TaintReason : : Plugin ) ;
}
} else
{
# ifdef MODPLUG_TRACKER
ExceptionHandler : : ContextSetter ectxguard { & m_Ectx } ;
# endif // MODPLUG_TRACKER
fn ( ) ;
}
return exception ;
}
AEffect * CVstPlugin : : LoadPlugin ( bool maskCrashes , VSTPluginLib & plugin , HMODULE & library , BridgeMode bridgeMode )
{
const mpt : : PathString & pluginPath = plugin . dllPath ;
AEffect * effect = nullptr ;
library = nullptr ;
const bool isNative = plugin . IsNative ( false ) ;
if ( bridgeMode ! = BridgeMode : : Automatic | | plugin . useBridge | | ! isNative )
{
if ( bridgeMode = = BridgeMode : : DetectRequiredBridgeMode )
{
// First try modern bridge, then legacy bridge
plugin . modernBridge = true ;
try
{
effect = BridgeWrapper : : Create ( plugin , false ) ;
if ( effect ! = nullptr )
{
return effect ;
}
} catch ( BridgeWrapper : : BridgeNotFoundException & )
{
} catch ( BridgeWrapper : : BridgeException & )
{
}
// Retry with legacy bridge
plugin . useBridge = true ;
plugin . modernBridge = false ;
}
try
{
effect = BridgeWrapper : : Create ( plugin , bridgeMode = = BridgeMode : : DetectRequiredBridgeMode ) ;
if ( effect ! = nullptr )
{
return effect ;
}
} catch ( BridgeWrapper : : BridgeNotFoundException & )
{
// Try normal loading
if ( ! isNative )
{
Reporting : : Error ( " Could not locate the plugin bridge executable, which is required for running non-native plugins. " , " OpenMPT Plugin Bridge " ) ;
return nullptr ;
}
} catch ( BridgeWrapper : : BridgeException & e )
{
// If there was some error, don't try normal loading as well... unless the user really wants it.
if ( isNative )
{
const CString msg =
MPT_CFORMAT ( " The following error occurred while trying to load \n {} \n \n {} \n \n Do you want to try to load the plugin natively? " )
( plugin . dllPath , mpt : : get_exception_text < mpt : : ustring > ( e ) ) ;
if ( Reporting : : Confirm ( msg , _T ( " OpenMPT Plugin Bridge " ) ) = = cnfNo )
{
return nullptr ;
}
} else
{
Reporting : : Error ( mpt : : get_exception_text < mpt : : ustring > ( e ) , " OpenMPT Plugin Bridge " ) ;
return nullptr ;
}
}
// If plugin was marked to use the plugin bridge but this somehow doesn't work (e.g. because the bridge is missing),
// disable the plugin bridge for this plugin.
plugin . useBridge = false ;
plugin . modernBridge = true ;
}
{
# ifdef MODPLUG_TRACKER
ExceptionHandler : : Context ectx { MPT_UFORMAT ( " VST Plugin: {} " ) ( plugin . dllPath . ToUnicode ( ) ) } ;
ExceptionHandler : : ContextSetter ectxguard { & ectx } ;
# endif // MODPLUG_TRACKER
DWORD exception = SETryOrError ( maskCrashes , [ & ] ( ) { library = LoadLibrary ( pluginPath . AsNative ( ) . c_str ( ) ) ; } ) ;
if ( exception )
{
CVstPluginManager : : ReportPlugException ( MPT_UFORMAT ( " Exception caught while loading {} " ) ( pluginPath ) ) ;
return nullptr ;
}
}
if ( library = = nullptr )
{
DWORD error = GetLastError ( ) ;
if ( error = = ERROR_MOD_NOT_FOUND )
{
return nullptr ;
} else if ( error = = ERROR_DLL_INIT_FAILED )
{
// A likely reason for this error is that Fiber Local Storage slots are exhausted, e.g. because too many plugins ship with a statically linked runtime.
// Before Windows 10 1903, there was a limit of 128 FLS slots per process, and the VS2017 runtime uses two FLS slots, so this could cause a worst-case limit
// of 62 different plugins per process (assuming they all use a statically-linked runtime).
// In Windows 10 1903, the FLS limit was finally raised, so this message is mostly relevant for older systems.
CVstPluginManager : : ReportPlugException ( U_ ( " Plugin initialization failed. This may be caused by loading too many plugins. \n Try activating the Plugin Bridge for this plugin. " ) ) ;
}
# ifdef _DEBUG
mpt : : ustring buf = MPT_UFORMAT ( " Warning: encountered problem when loading plugin dll. Error {}: {} " )
( mpt : : ufmt : : hex ( error )
2024-09-29 02:04:03 +00:00
, mpt : : ToUnicode ( mpt : : arch : : GetErrorMessage ( error ) )
2024-09-24 12:54:57 +00:00
) ;
Reporting : : Error ( buf , " DEBUG: Error when loading plugin dll " ) ;
# endif //_DEBUG
}
if ( library ! = nullptr & & library ! = INVALID_HANDLE_VALUE )
{
auto pMainProc = ( Vst : : MainProc ) GetProcAddress ( library , " VSTPluginMain " ) ;
if ( pMainProc = = nullptr )
{
pMainProc = ( Vst : : MainProc ) GetProcAddress ( library , " main " ) ;
}
if ( pMainProc ! = nullptr )
{
# ifdef MODPLUG_TRACKER
ExceptionHandler : : Context ectx { MPT_UFORMAT ( " VST Plugin: {} " ) ( plugin . dllPath . ToUnicode ( ) ) } ;
ExceptionHandler : : ContextSetter ectxguard { & ectx } ;
# endif // MODPLUG_TRACKER
DWORD exception = SETryOrError ( maskCrashes , [ & ] ( ) { effect = pMainProc ( CVstPlugin : : MasterCallBack ) ; } ) ;
if ( exception )
{
return nullptr ;
}
} else
{
# ifdef VST_LOG
MPT_LOG_GLOBAL ( LogDebug , " VST " , MPT_UFORMAT ( " Entry point not found! (handle={}) " ) ( mpt : : ufmt : : PTR ( library ) ) ) ;
# endif // VST_LOG
return nullptr ;
}
}
return effect ;
}
static void operator | = ( Vst : : VstTimeInfoFlags & lhs , Vst : : VstTimeInfoFlags rhs )
{
lhs = ( lhs | rhs ) . as_enum ( ) ;
}
intptr_t VSTCALLBACK CVstPlugin : : MasterCallBack ( AEffect * effect , VstOpcodeToHost opcode , int32 index , intptr_t value , void * ptr , float opt )
{
# ifdef VST_LOG
MPT_LOG_GLOBAL ( LogDebug , " VST " , MPT_UFORMAT ( " VST plugin to host: Eff: {}, Opcode = {}, Index = {}, Value = {}, PTR = {}, OPT = {} \n " ) (
mpt : : ufmt : : PTR ( effect ) , mpt : : ufmt : : val ( opcode ) ,
mpt : : ufmt : : val ( index ) , mpt : : ufmt : : PTR ( value ) , mpt : : ufmt : : PTR ( ptr ) , mpt : : ufmt : : flt ( opt , 3 ) ) ) ;
MPT_TRACE ( ) ;
# else
MPT_UNREFERENCED_PARAMETER ( opt ) ;
# endif
enum
{
HostDoNotKnow = 0 ,
HostCanDo = 1 ,
HostCanNotDo = - 1
} ;
CVstPlugin * pVstPlugin = nullptr ;
if ( effect ! = nullptr )
{
pVstPlugin = static_cast < CVstPlugin * > ( effect - > reservedForHost1 ) ;
}
switch ( opcode )
{
// Called when plugin param is changed via gui
case audioMasterAutomate :
// Strum Acoustic GS-1 and Strum Electric GS-1 send audioMasterAutomate during effOpen (WTF #1),
// but when sending back effCanBeAutomated, they just crash (WTF #2).
// As a consequence, just generally forbid this action while the plugin is not fully initialized yet.
if ( pVstPlugin ! = nullptr & & pVstPlugin - > m_isInitialized & & pVstPlugin - > CanAutomateParameter ( index ) )
{
// This parameter can be automated. Ugo Motion constantly sends automation callback events for parameters that cannot be automated...
pVstPlugin - > AutomateParameter ( ( PlugParamIndex ) index ) ;
}
return 0 ;
// Called when plugin asks for VST version supported by host
case audioMasterVersion :
return kVstVersion ;
// Returns the unique id of a plugin that's currently loading
// We don't support shell plugins currently, so we only support one effect ID as well.
case audioMasterCurrentId :
return ( effect ! = nullptr ) ? effect - > uniqueID : 0 ;
// Call application idle routine (this will call effEditIdle for all open editors too)
case audioMasterIdle :
theApp . GetPluginManager ( ) - > OnIdle ( ) ;
return 0 ;
// 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.
case audioMasterPinConnected :
if ( value ) //input:
return ( index < 2 ) ? 0 : 1 ; //we only support up to 2 inputs. Remember: 0 means yes.
else //output:
return ( index < 2 ) ? 0 : 1 ; //2 outputs max too
//---from here VST 2.0 extension opcodes------------------------------------------------------
// <value> is a filter which is currently ignored - DEPRECATED in VST 2.4
case audioMasterWantMidi :
return 1 ;
// returns const VstTimeInfo* (or 0 if not supported)
// <value> should contain a mask indicating which fields are required
case audioMasterGetTime :
if ( pVstPlugin )
{
VstTimeInfo & timeInfo = pVstPlugin - > timeInfo ;
MemsetZero ( timeInfo ) ;
timeInfo . sampleRate = pVstPlugin - > m_nSampleRate ;
CSoundFile & sndFile = pVstPlugin - > GetSoundFile ( ) ;
if ( pVstPlugin - > IsSongPlaying ( ) )
{
timeInfo . flags | = kVstTransportPlaying ;
if ( pVstPlugin - > GetSoundFile ( ) . m_SongFlags [ SONG_PATTERNLOOP ] ) timeInfo . flags | = kVstTransportCycleActive ;
timeInfo . samplePos = sndFile . GetTotalSampleCount ( ) ;
if ( pVstPlugin - > m_positionChanged )
{
timeInfo . flags | = kVstTransportChanged ;
pVstPlugin - > lastBarStartPos = - 1.0 ;
}
} else
{
timeInfo . flags | = kVstTransportChanged ; //just stopped.
timeInfo . samplePos = 0 ;
pVstPlugin - > lastBarStartPos = - 1.0 ;
}
if ( ( value & kVstNanosValid ) )
{
timeInfo . flags | = kVstNanosValid ;
timeInfo . nanoSeconds = static_cast < double > ( Util : : mul32to64_unsigned ( timeGetTime ( ) , 1000000 ) ) ;
}
if ( ( value & kVstPpqPosValid ) )
{
timeInfo . flags | = kVstPpqPosValid ;
if ( timeInfo . flags & kVstTransportPlaying )
{
timeInfo . ppqPos = ( timeInfo . samplePos / timeInfo . sampleRate ) * ( sndFile . GetCurrentBPM ( ) / 60.0 ) ;
} else
{
timeInfo . ppqPos = 0 ;
}
ROWINDEX rpm = pVstPlugin - > GetSoundFile ( ) . m_PlayState . m_nCurrentRowsPerMeasure ;
if ( ! rpm )
rpm = 4 ;
if ( ( pVstPlugin - > GetSoundFile ( ) . m_PlayState . m_nRow % rpm ) = = 0 )
{
pVstPlugin - > lastBarStartPos = std : : floor ( timeInfo . ppqPos ) ;
}
if ( pVstPlugin - > lastBarStartPos > = 0 )
{
timeInfo . barStartPos = pVstPlugin - > lastBarStartPos ;
timeInfo . flags | = kVstBarsValid ;
}
}
if ( ( value & kVstTempoValid ) )
{
timeInfo . tempo = sndFile . GetCurrentBPM ( ) ;
if ( timeInfo . tempo )
{
timeInfo . flags | = kVstTempoValid ;
}
}
if ( ( value & kVstTimeSigValid ) )
{
timeInfo . flags | = kVstTimeSigValid ;
// Time signature. numerator = rows per beats / rows pear measure (should sound somewhat logical to you).
// the denominator is a bit more tricky, since it cannot be set explicitely. so we just assume quarters for now.
ROWINDEX rpb = std : : max ( sndFile . m_PlayState . m_nCurrentRowsPerBeat , ROWINDEX ( 1 ) ) ;
timeInfo . timeSigNumerator = std : : max ( sndFile . m_PlayState . m_nCurrentRowsPerMeasure , rpb ) / rpb ;
timeInfo . timeSigDenominator = 4 ; //std::gcd(pSndFile->m_nCurrentRowsPerMeasure, pSndFile->m_nCurrentRowsPerBeat);
}
return ToIntPtr ( & timeInfo ) ;
} else
{
MemsetZero ( g_timeInfoFallback ) ;
return ToIntPtr ( & g_timeInfoFallback ) ;
}
// Receive MIDI events from plugin
case audioMasterProcessEvents :
if ( pVstPlugin ! = nullptr & & ptr ! = nullptr )
{
pVstPlugin - > ReceiveVSTEvents ( static_cast < VstEvents * > ( ptr ) ) ;
return 1 ;
}
break ;
// DEPRECATED in VST 2.4
case audioMasterSetTime :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Set Time " ) ) ;
break ;
// returns tempo (in bpm * 10000) at sample frame location passed in <value> - DEPRECATED in VST 2.4
case audioMasterTempoAt :
// Screw it! Let's just return the tempo at this point in time (might be a bit wrong).
if ( pVstPlugin ! = nullptr )
{
return mpt : : saturate_round < int32 > ( pVstPlugin - > GetSoundFile ( ) . GetCurrentBPM ( ) * 10000 ) ;
}
return ( 125 * 10000 ) ;
// parameters - DEPRECATED in VST 2.4
case audioMasterGetNumAutomatableParameters :
//MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Num Automatable Parameters"));
if ( pVstPlugin ! = nullptr )
{
return pVstPlugin - > GetNumParameters ( ) ;
}
break ;
// Apparently, this one is broken in VST SDK anyway. - DEPRECATED in VST 2.4
case audioMasterGetParameterQuantization :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Audio Master Get Parameter Quantization " ) ) ;
break ;
// numInputs and/or numOutputs has changed
case audioMasterIOChanged :
if ( pVstPlugin ! = nullptr )
{
CriticalSection cs ;
return pVstPlugin - > InitializeIOBuffers ( ) ? 1 : 0 ;
}
break ;
// Plugin needs idle calls (outside its editor window) - DEPRECATED in VST 2.4
case audioMasterNeedIdle :
if ( pVstPlugin ! = nullptr )
{
pVstPlugin - > m_needIdle = true ;
}
return 1 ;
// index: width, value: height
case audioMasterSizeWindow :
if ( pVstPlugin ! = nullptr )
{
CAbstractVstEditor * pVstEditor = pVstPlugin - > GetEditor ( ) ;
if ( pVstEditor & & pVstEditor - > IsResizable ( ) )
{
pVstEditor - > SetSize ( index , static_cast < int > ( value ) ) ;
}
}
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Size Window " ) ) ;
return 1 ;
case audioMasterGetSampleRate :
if ( pVstPlugin )
{
return pVstPlugin - > m_nSampleRate ;
} else
{
// HERCs Abakos queries the sample rate while the plugin is being created and then never again...
return TrackerSettings : : Instance ( ) . GetMixerSettings ( ) . gdwMixingFreq ;
}
case audioMasterGetBlockSize :
return MIXBUFFERSIZE ;
case audioMasterGetInputLatency :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Get Input Latency " ) ) ;
break ;
case audioMasterGetOutputLatency :
if ( pVstPlugin )
{
return mpt : : saturate_round < intptr_t > ( pVstPlugin - > GetOutputLatency ( ) * pVstPlugin - > GetSoundFile ( ) . GetSampleRate ( ) ) ;
}
break ;
// input pin in <value> (-1: first to come), returns cEffect* - DEPRECATED in VST 2.4
case audioMasterGetPreviousPlug :
if ( pVstPlugin ! = nullptr )
{
std : : vector < IMixPlugin * > list ;
if ( pVstPlugin - > GetInputPlugList ( list ) ! = 0 )
{
// We don't assign plugins to pins...
CVstPlugin * plugin = dynamic_cast < CVstPlugin * > ( list [ 0 ] ) ;
if ( plugin ! = nullptr )
{
return ToIntPtr ( & plugin - > m_Effect ) ;
}
}
}
break ;
// output pin in <value> (-1: first to come), returns cEffect* - DEPRECATED in VST 2.4
case audioMasterGetNextPlug :
if ( pVstPlugin ! = nullptr )
{
std : : vector < IMixPlugin * > list ;
if ( pVstPlugin - > GetOutputPlugList ( list ) ! = 0 )
{
// We don't assign plugins to pins...
CVstPlugin * plugin = dynamic_cast < CVstPlugin * > ( list [ 0 ] ) ;
if ( plugin ! = nullptr )
{
return ToIntPtr ( & plugin - > m_Effect ) ;
}
}
}
break ;
// realtime info
// returns: 0: not supported, 1: replace, 2: accumulate - DEPRECATED in VST 2.4 (replace is default)
case audioMasterWillReplaceOrAccumulate :
return 1 ; //we replace.
case audioMasterGetCurrentProcessLevel :
if ( pVstPlugin ! = nullptr & & pVstPlugin - > GetSoundFile ( ) . IsRenderingToDisc ( ) )
return kVstProcessLevelOffline ;
else
return kVstProcessLevelRealtime ;
break ;
// returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write
case audioMasterGetAutomationState :
// Not entirely sure what this means. We can write automation TO the plug.
// Is that "read" in this context?
//MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Automation State"));
return kVstAutomationReadWrite ;
case audioMasterOfflineStart :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Offlinestart " ) ) ;
break ;
case audioMasterOfflineRead :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Offlineread " ) ) ;
break ;
case audioMasterOfflineWrite :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Offlinewrite " ) ) ;
break ;
case audioMasterOfflineGetCurrentPass :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: OfflineGetcurrentpass " ) ) ;
break ;
case audioMasterOfflineGetCurrentMetaPass :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: OfflineGetCurrentMetapass " ) ) ;
break ;
// for variable i/o, sample rate in <opt> - DEPRECATED in VST 2.4
case audioMasterSetOutputSampleRate :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Set Output Sample Rate " ) ) ;
break ;
// result in ret - DEPRECATED in VST 2.4
case audioMasterGetOutputSpeakerArrangement :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Get Output Speaker Arrangement " ) ) ;
break ;
case audioMasterGetVendorString :
strcpy ( ( char * ) ptr , TrackerSettings : : Instance ( ) . vstHostVendorString . Get ( ) . c_str ( ) ) ;
return 1 ;
case audioMasterGetProductString :
strcpy ( ( char * ) ptr , TrackerSettings : : Instance ( ) . vstHostProductString . Get ( ) . c_str ( ) ) ;
return 1 ;
case audioMasterGetVendorVersion :
return TrackerSettings : : Instance ( ) . vstHostVendorVersion ;
case audioMasterVendorSpecific :
return 0 ;
// void* in <ptr>, format not defined yet - DEPRECATED in VST 2.4
case audioMasterSetIcon :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Set Icon " ) ) ;
break ;
// string in ptr, see below
case audioMasterCanDo :
//Other possible Can Do strings are:
if ( ! strcmp ( ( char * ) ptr , HostCanDo : : sendVstEvents )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : sendVstMidiEvent )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : sendVstTimeInfo )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : receiveVstEvents )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : receiveVstMidiEvent )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : supplyIdle )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : sizeWindow )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : openFileSelector )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : closeFileSelector )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : acceptIOChanges )
| | ! strcmp ( ( char * ) ptr , HostCanDo : : reportConnectionChanges ) )
{
return HostCanDo ;
} else
{
return HostCanNotDo ;
}
case audioMasterGetLanguage :
return kVstLangEnglish ;
// returns platform specific ptr - DEPRECATED in VST 2.4
case audioMasterOpenWindow :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Open Window " ) ) ;
break ;
// close window, platform specific handle in <ptr> - DEPRECATED in VST 2.4
case audioMasterCloseWindow :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Close Window " ) ) ;
break ;
// get plugin directory, FSSpec on MAC, else char*
case audioMasterGetDirectory :
//MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Directory"));
// Need to allocate space for path only, but I guess noone relies on this anyway.
//return ToVstPtr(pVstPlugin->GetPluginFactory().dllPath.GetPath().ToLocale());
//return ToVstPtr(TrackerSettings::Instance().PathPlugins.GetDefaultDir());
break ;
// something has changed, update 'multi-fx' display
case audioMasterUpdateDisplay :
if ( pVstPlugin ! = nullptr )
{
// Note to self for testing: Electri-Q sends opcode. Korg M1 sends this when switching between Combi and Multi mode to update the preset names.
CAbstractVstEditor * pVstEditor = pVstPlugin - > GetEditor ( ) ;
if ( pVstEditor & & : : IsWindow ( pVstEditor - > m_hWnd ) )
{
pVstEditor - > UpdateDisplay ( ) ;
}
}
return 0 ;
//---from here VST 2.1 extension opcodes------------------------------------------------------
// begin of automation session (when mouse down), parameter index in <index>
case audioMasterBeginEdit :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Begin Edit " ) ) ;
break ;
// end of automation session (when mouse up), parameter index in <index>
case audioMasterEndEdit :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: End Edit " ) ) ;
break ;
// open a fileselector window with VstFileSelect* in <ptr>
case audioMasterOpenFileSelector :
//---from here VST 2.2 extension opcodes------------------------------------------------------
// close a fileselector operation with VstFileSelect* in <ptr>: Must be always called after an open !
case audioMasterCloseFileSelector :
if ( pVstPlugin ! = nullptr & & ptr ! = nullptr )
{
return pVstPlugin - > VstFileSelector ( opcode = = audioMasterCloseFileSelector , * static_cast < VstFileSelect * > ( ptr ) ) ;
}
// open an editor for audio (defined by XML text in ptr) - DEPRECATED in VST 2.4
case audioMasterEditFile :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Edit File " ) ) ;
break ;
// get the native path of currently loading bank or project
// (called from writeChunk) void* in <ptr> (char[2048], or sizeof(FSSpec)) - DEPRECATED in VST 2.4
// Note: The shortcircuit VSTi actually uses this feature.
case audioMasterGetChunkFile :
# ifdef MODPLUG_TRACKER
if ( pVstPlugin & & pVstPlugin - > GetModDoc ( ) )
{
mpt : : ustring pathStr = TrackerSettings : : Instance ( ) . pluginProjectPath ;
if ( pathStr . empty ( ) )
{
pathStr = U_ ( " %1 " ) ;
}
const mpt : : PathString projectPath = pVstPlugin - > GetModDoc ( ) - > GetPathNameMpt ( ) . GetPath ( ) ;
const mpt : : PathString projectFile = pVstPlugin - > GetModDoc ( ) - > GetPathNameMpt ( ) . GetFullFileName ( ) ;
pathStr = mpt : : String : : Replace ( pathStr , U_ ( " %1 " ) , U_ ( " ?1? " ) ) ;
pathStr = mpt : : String : : Replace ( pathStr , U_ ( " %2 " ) , U_ ( " ?2? " ) ) ;
pathStr = mpt : : String : : Replace ( pathStr , U_ ( " ?1? " ) , projectPath . ToUnicode ( ) ) ;
pathStr = mpt : : String : : Replace ( pathStr , U_ ( " ?2? " ) , projectFile . ToUnicode ( ) ) ;
mpt : : PathString path = mpt : : PathString : : FromUnicode ( pathStr ) ;
if ( path . empty ( ) )
{
return 0 ;
}
path . EnsureTrailingSlash ( ) ;
: : SHCreateDirectoryEx ( NULL , path . AsNative ( ) . c_str ( ) , nullptr ) ;
path + = projectFile ;
strcpy ( static_cast < char * > ( ptr ) , path . ToLocale ( ) . c_str ( ) ) ;
return 1 ;
}
# endif
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Get Chunk File " ) ) ;
break ;
//---from here VST 2.3 extension opcodes------------------------------------------------------
// result a VstSpeakerArrangement in ret - DEPRECATED in VST 2.4
case audioMasterGetInputSpeakerArrangement :
MPT_LOG_GLOBAL ( LogDebug , " VST " , U_ ( " VST plugin to host: Get Input Speaker Arrangement " ) ) ;
break ;
}
// Unknown codes:
return 0 ;
}
// Helper function for file selection dialog stuff.
intptr_t CVstPlugin : : VstFileSelector ( bool destructor , VstFileSelect & fileSel )
{
if ( ! destructor )
{
fileSel . returnMultiplePaths = nullptr ;
fileSel . numReturnPaths = 0 ;
fileSel . reserved = 0 ;
std : : string returnPath ;
if ( fileSel . command ! = kVstDirectorySelect )
{
// Plugin wants to load or save a file.
std : : string extensions , workingDir ;
for ( int32 i = 0 ; i < fileSel . numFileTypes ; i + + )
{
const VstFileType & type = fileSel . fileTypes [ i ] ;
extensions + = type . name ;
extensions + = " | " ;
# if MPT_OS_WINDOWS
extensions + = " *. " ;
extensions + = type . dosType ;
# elif MPT_OS_MACOSX_OR_IOS
extensions + = " * " ;
extensions + = type . macType ;
# elif MPT_OS_GENERIC_UNIX
extensions + = " *. " ;
extensions + = type . unixType ;
# else
# error Platform-specific code missing
# endif
extensions + = " | " ;
}
extensions + = " | " ;
if ( fileSel . initialPath ! = nullptr )
{
workingDir = fileSel . initialPath ;
} else
{
// Plugins are probably looking for presets...?
//workingDir = TrackerSettings::Instance().PathPluginPresets.GetWorkingDir();
}
FileDialog dlg = OpenFileDialog ( ) ;
if ( fileSel . command = = kVstFileSave )
{
dlg = SaveFileDialog ( ) ;
} else if ( fileSel . command = = kVstMultipleFilesLoad )
{
dlg = OpenFileDialog ( ) . AllowMultiSelect ( ) ;
}
dlg . ExtensionFilter ( extensions )
. WorkingDirectory ( mpt : : PathString : : FromLocale ( workingDir ) )
. AddPlace ( GetPluginFactory ( ) . dllPath . GetPath ( ) ) ;
if ( ! dlg . Show ( GetEditor ( ) ) )
return 0 ;
if ( fileSel . command = = kVstMultipleFilesLoad )
{
// Multiple paths
const auto & files = dlg . GetFilenames ( ) ;
fileSel . numReturnPaths = mpt : : saturate_cast < int32 > ( files . size ( ) ) ;
fileSel . returnMultiplePaths = new ( std : : nothrow ) char * [ fileSel . numReturnPaths ] ;
if ( ! fileSel . returnMultiplePaths )
return 0 ;
for ( int32 i = 0 ; i < fileSel . numReturnPaths ; i + + )
{
const std : : string fname_ = files [ i ] . ToLocale ( ) ;
char * fname = new ( std : : nothrow ) char [ fname_ . length ( ) + 1 ] ;
if ( fname )
strcpy ( fname , fname_ . c_str ( ) ) ;
fileSel . returnMultiplePaths [ i ] = fname ;
}
return 1 ;
} else
{
// Single path
// VOPM doesn't initialize required information properly (it doesn't memset the struct to 0)...
if ( FourCC ( " VOPM " ) = = GetUID ( ) )
{
fileSel . sizeReturnPath = _MAX_PATH ;
}
returnPath = dlg . GetFirstFile ( ) . ToLocale ( ) ;
}
} else
{
// Plugin wants a directory
BrowseForFolder dlg ( mpt : : PathString : : FromLocale ( fileSel . initialPath ! = nullptr ? fileSel . initialPath : " " ) , mpt : : ToCString ( mpt : : Charset : : Locale , fileSel . title ! = nullptr ? fileSel . title : " " ) ) ;
if ( ! dlg . Show ( GetEditor ( ) ) )
return 0 ;
returnPath = dlg . GetDirectory ( ) . ToLocale ( ) ;
if ( FourCC ( " VSTr " ) = = GetUID ( ) & & fileSel . returnPath ! = nullptr & & fileSel . sizeReturnPath = = 0 )
{
// Old versions of reViSiT (which still relied on the host's file selector) seem to be dodgy.
// They report a path size of 0, but when using an own buffer, they will crash.
// So we'll just assume that reViSiT can handle long enough (_MAX_PATH) paths here.
fileSel . sizeReturnPath = mpt : : saturate_cast < int32 > ( returnPath . length ( ) + 1 ) ;
}
}
// Return single path (file or directory)
if ( fileSel . returnPath = = nullptr | | fileSel . sizeReturnPath = = 0 )
{
// Provide some memory for the return path.
fileSel . sizeReturnPath = mpt : : saturate_cast < int32 > ( returnPath . length ( ) + 1 ) ;
fileSel . returnPath = new ( std : : nothrow ) char [ fileSel . sizeReturnPath ] ;
if ( fileSel . returnPath = = nullptr )
{
return 0 ;
}
fileSel . reserved = 1 ;
} else
{
fileSel . reserved = 0 ;
}
const auto len = std : : min ( returnPath . size ( ) , static_cast < size_t > ( fileSel . sizeReturnPath - 1 ) ) ;
strncpy ( fileSel . returnPath , returnPath . data ( ) , len ) ;
fileSel . returnPath [ len ] = ' \0 ' ;
fileSel . numReturnPaths = 1 ;
fileSel . returnMultiplePaths = nullptr ;
return 1 ;
} else
{
// Close file selector - delete allocated strings.
if ( fileSel . command = = kVstMultipleFilesLoad & & fileSel . returnMultiplePaths ! = nullptr )
{
for ( int32 i = 0 ; i < fileSel . numReturnPaths ; i + + )
{
if ( fileSel . returnMultiplePaths [ i ] ! = nullptr )
{
delete [ ] fileSel . returnMultiplePaths [ i ] ;
}
}
delete [ ] fileSel . returnMultiplePaths ;
fileSel . returnMultiplePaths = nullptr ;
} else
{
if ( fileSel . reserved = = 1 & & fileSel . returnPath ! = nullptr )
{
delete [ ] fileSel . returnPath ;
fileSel . returnPath = nullptr ;
}
}
return 1 ;
}
}
//////////////////////////////////////////////////////////////////////////////
//
// CVstPlugin
//
CVstPlugin : : CVstPlugin ( bool maskCrashes , HMODULE hLibrary , VSTPluginLib & factory , SNDMIXPLUGIN & mixStruct , AEffect & effect , CSoundFile & sndFile )
: IMidiPlugin ( factory , sndFile , & mixStruct )
, m_maskCrashes ( maskCrashes )
, m_Effect ( effect )
, timeInfo { }
, isBridged ( ! memcmp ( & effect . reservedForHost2 , " OMPT " , 4 ) )
, m_hLibrary ( hLibrary )
, m_nSampleRate ( sndFile . GetSampleRate ( ) )
, m_isInitialized ( false )
, m_needIdle ( false )
{
// Open plugin and initialize data structures
Initialize ( ) ;
InsertIntoFactoryList ( ) ;
m_isInitialized = true ;
}
void CVstPlugin : : Initialize ( )
{
m_Ectx = { MPT_UFORMAT ( " VST Plugin: {} " ) ( m_Factory . dllPath . ToUnicode ( ) ) } ;
// If filename matched during load but plugin ID didn't, make sure it's updated.
m_pMixStruct - > Info . dwPluginId1 = m_Factory . pluginId1 = m_Effect . magic ;
m_pMixStruct - > Info . dwPluginId2 = m_Factory . pluginId2 = m_Effect . uniqueID ;
// Store a pointer so we can get the CVstPlugin object from the basic VST effect object.
m_Effect . reservedForHost1 = this ;
m_nSampleRate = m_SndFile . GetSampleRate ( ) ;
// First try to let the plugin know the render parameters.
Dispatch ( effSetSampleRate , 0 , 0 , nullptr , static_cast < float > ( m_nSampleRate ) ) ;
Dispatch ( effSetBlockSize , 0 , MIXBUFFERSIZE , nullptr , 0.0f ) ;
Dispatch ( effOpen , 0 , 0 , nullptr , 0.0f ) ;
// VST 2.0 plugins return 2 here, VST 2.4 plugins return 2400... Great!
m_isVst2 = Dispatch ( effGetVstVersion , 0 , 0 , nullptr , 0.0f ) > = 2 ;
if ( m_isVst2 )
{
// Set VST speaker in/out setup to Stereo. Required for some plugins (e.g. Voxengo SPAN 2)
// All this might get more interesting when adding sidechaining support...
VstSpeakerArrangement sa { } ;
sa . numChannels = 2 ;
sa . type = kSpeakerArrStereo ;
for ( std : : size_t i = 0 ; i < std : : size ( sa . speakers ) ; i + + )
{
// For now, only left and right speaker are used.
switch ( i )
{
case 0 :
sa . speakers [ i ] . type = kSpeakerL ;
mpt : : String : : WriteAutoBuf ( sa . speakers [ i ] . name ) = " Left " ;
break ;
case 1 :
sa . speakers [ i ] . type = kSpeakerR ;
mpt : : String : : WriteAutoBuf ( sa . speakers [ i ] . name ) = " Right " ;
break ;
default :
sa . speakers [ i ] . type = kSpeakerUndefined ;
break ;
}
}
// For some reason, this call crashes in a call to free() in AdmiralQuality NaiveLPF / SCAMP 1.2 (newer versions are fine).
// This does not happen when running the plugin in pretty much any host, or when running in OpenMPT 1.22 and older
// (EXCEPT when recompiling those old versions with VS2010), so it sounds like an ASLR issue to me.
// AdmiralQuality also doesn't know what to do.
if ( GetUID ( ) ! = FourCC ( " CSI4 " ) )
{
// For now, input setup = output setup.
Dispatch ( effSetSpeakerArrangement , 0 , ToIntPtr ( & sa ) , & sa , 0.0f ) ;
}
// Dummy pin properties collection.
// We don't use them but some plugs might do inits in here.
VstPinProperties tempPinProperties ;
Dispatch ( effGetInputProperties , 0 , 0 , & tempPinProperties , 0 ) ;
Dispatch ( effGetOutputProperties , 0 , 0 , & tempPinProperties , 0 ) ;
Dispatch ( effConnectInput , 0 , 1 , nullptr , 0.0f ) ;
if ( m_Effect . numInputs > 1 ) Dispatch ( effConnectInput , 1 , 1 , nullptr , 0.0f ) ;
Dispatch ( effConnectOutput , 0 , 1 , nullptr , 0.0f ) ;
if ( m_Effect . numOutputs > 1 ) Dispatch ( effConnectOutput , 1 , 1 , nullptr , 0.0f ) ;
// Disable all inputs and outputs beyond stereo left and right:
for ( int32 i = 2 ; i < m_Effect . numInputs ; i + + )
Dispatch ( effConnectInput , i , 0 , nullptr , 0.0f ) ;
for ( int32 i = 2 ; i < m_Effect . numOutputs ; i + + )
Dispatch ( effConnectOutput , i , 0 , nullptr , 0.0f ) ;
}
// Second try to let the plugin know the render parameters.
Dispatch ( effSetSampleRate , 0 , 0 , nullptr , static_cast < float > ( m_nSampleRate ) ) ;
Dispatch ( effSetBlockSize , 0 , MIXBUFFERSIZE , nullptr , 0.0f ) ;
if ( m_Effect . numPrograms > 0 )
{
BeginSetProgram ( 0 ) ;
EndSetProgram ( ) ;
}
InitializeIOBuffers ( ) ;
Dispatch ( effSetProcessPrecision , 0 , kVstProcessPrecision32 , nullptr , 0.0f ) ;
m_isInstrument = IsInstrument ( ) ;
RecalculateGain ( ) ;
m_pProcessFP = ( m_Effect . flags & effFlagsCanReplacing ) ? m_Effect . processReplacing : m_Effect . process ;
// Issue samplerate again here, cos some plugs like it before the block size, other like it right at the end.
Dispatch ( effSetSampleRate , 0 , 0 , nullptr , static_cast < float > ( m_nSampleRate ) ) ;
// Korg Wavestation GUI won't work until plugin was resumed at least once.
// On the other hand, some other plugins (notably Synthedit plugins like Superwave P8 2.3 or Rez 3.0) don't like this
// and won't load their stored plugin data instantly, so only do this for the troublesome plugins...
// Also apply this fix for Korg's M1 plugin, as this will fixes older versions of said plugin, newer versions don't require the fix.
// EZDrummer / Superior Drummer won't load their samples until playback has started.
if ( GetUID ( ) = = FourCC ( " KLWV " ) // Wavestation
| | GetUID ( ) = = FourCC ( " KLM1 " ) // M1
| | GetUID ( ) = = FourCC ( " dfhe " ) // EZDrummer
| | GetUID ( ) = = FourCC ( " dfh2 " ) ) // Superior Drummer
{
Resume ( ) ;
Suspend ( ) ;
}
}
bool CVstPlugin : : InitializeIOBuffers ( )
{
// Input pointer array size must be >= 2 for now - the input buffer assignment might write to non allocated mem. otherwise
// In case of a bridged plugin, the AEffect struct has been updated before calling this opcode, so we don't have to worry about it being up-to-date.
return m_mixBuffer . Initialize ( std : : max ( m_Effect . numInputs , int32 ( 2 ) ) , m_Effect . numOutputs ) ;
}
CVstPlugin : : ~ CVstPlugin ( )
{
CriticalSection cs ;
CloseEditor ( ) ;
if ( m_isVst2 )
{
Dispatch ( effConnectInput , 0 , 0 , nullptr , 0 ) ;
if ( m_Effect . numInputs > 1 ) Dispatch ( effConnectInput , 1 , 0 , nullptr , 0 ) ;
Dispatch ( effConnectOutput , 0 , 0 , nullptr , 0 ) ;
if ( m_Effect . numOutputs > 1 ) Dispatch ( effConnectOutput , 1 , 0 , nullptr , 0 ) ;
}
CVstPlugin : : Suspend ( ) ;
m_isInitialized = false ;
Dispatch ( effClose , 0 , 0 , nullptr , 0 ) ;
if ( TrackerSettings : : Instance ( ) . BrokenPluginsWorkaroundVSTNeverUnloadAnyPlugin )
{
// Buggy SynthEdit 1.4 plugins: Showing a SynthEdit 1.4 plugin's editor, fully unloading the plugin,
// then loading another (unrelated) SynthEdit 1.4 plugin and showing its editor causes a crash.
} else
{
if ( m_hLibrary )
{
FreeLibrary ( m_hLibrary ) ;
}
}
}
void CVstPlugin : : Release ( )
{
delete this ;
}
void CVstPlugin : : Idle ( )
{
if ( m_needIdle )
{
if ( ! ( Dispatch ( effIdle , 0 , 0 , nullptr , 0.0f ) ) )
m_needIdle = false ;
}
if ( m_pEditor & & m_pEditor - > m_hWnd )
{
Dispatch ( effEditIdle , 0 , 0 , nullptr , 0.0f ) ;
}
}
int32 CVstPlugin : : GetNumPrograms ( ) const
{
return std : : max ( m_Effect . numPrograms , int32 ( 0 ) ) ;
}
PlugParamIndex CVstPlugin : : GetNumParameters ( ) const
{
return m_Effect . numParams ;
}
// Check whether a VST parameter can be automated
bool CVstPlugin : : CanAutomateParameter ( PlugParamIndex index )
{
return ( Dispatch ( effCanBeAutomated , index , 0 , nullptr , 0.0f ) ! = 0 ) ;
}
int32 CVstPlugin : : GetUID ( ) const
{
return m_Effect . uniqueID ;
}
int32 CVstPlugin : : GetVersion ( ) const
{
return m_Effect . version ;
}
// Wrapper for VST dispatch call with structured exception handling.
intptr_t CVstPlugin : : DispatchSEH ( bool maskCrashes , AEffect * effect , VstOpcodeToPlugin opCode , int32 index , intptr_t value , void * ptr , float opt , unsigned long & exception )
{
if ( effect - > dispatcher ! = nullptr )
{
intptr_t result = 0 ;
DWORD e = SETryOrError ( maskCrashes , [ & ] ( ) { result = effect - > dispatcher ( effect , opCode , index , value , ptr , opt ) ; } ) ;
if ( e )
{
exception = e ;
}
return result ;
}
return 0 ;
}
intptr_t CVstPlugin : : Dispatch ( VstOpcodeToPlugin opCode , int32 index , intptr_t value , void * ptr , float opt )
{
# ifdef VST_LOG
{
mpt : : ustring codeStr ;
if ( opCode > = 0 & & static_cast < std : : size_t > ( opCode ) < std : : size ( VstOpCodes ) )
{
codeStr = mpt : : ToUnicode ( mpt : : Charset : : ASCII , VstOpCodes [ opCode ] ) ;
} else
{
codeStr = mpt : : ufmt : : val ( opCode ) ;
}
MPT_LOG_GLOBAL ( LogDebug , " VST " , MPT_UFORMAT ( " About to Dispatch({}) (Plugin= \" {} \" ), index: {}, value: {}, ptr: {}, opt: {}! \n " ) ( codeStr , m_Factory . libraryName , index , mpt : : ufmt : : PTR ( value ) , mpt : : ufmt : : PTR ( ptr ) , mpt : : ufmt : : flt ( opt , 3 ) ) ) ;
}
# endif
if ( ! m_Effect . dispatcher )
{
return 0 ;
}
intptr_t result = 0 ;
{
DWORD exception = SETryOrError ( [ & ] ( ) { result = m_Effect . dispatcher ( & m_Effect , opCode , index , value , ptr , opt ) ; } ) ;
if ( exception )
{
mpt : : ustring codeStr ;
if ( opCode < mpt : : saturate_cast < int32 > ( std : : size ( VstOpCodes ) ) )
{
codeStr = mpt : : ToUnicode ( mpt : : Charset : : ASCII , VstOpCodes [ opCode ] ) ;
} else
{
codeStr = mpt : : ufmt : : val ( opCode ) ;
}
ReportPlugException ( MPT_UFORMAT ( " Exception {} in Dispatch({}) " ) ( mpt : : ufmt : : HEX0 < 8 > ( exception ) , codeStr ) ) ;
}
}
return result ;
}
int32 CVstPlugin : : GetCurrentProgram ( )
{
if ( m_Effect . numPrograms > 0 )
{
return static_cast < int32 > ( Dispatch ( effGetProgram , 0 , 0 , nullptr , 0 ) ) ;
}
return 0 ;
}
CString CVstPlugin : : GetCurrentProgramName ( )
{
std : : vector < char > s ( 256 , 0 ) ;
// kVstMaxProgNameLen is 24... too short for some plugins, so use at least 256 bytes.
Dispatch ( effGetProgramName , 0 , 0 , s . data ( ) , 0 ) ;
return mpt : : ToCString ( mpt : : Charset : : Locale , s . data ( ) ) ;
}
void CVstPlugin : : SetCurrentProgramName ( const CString & name )
{
Dispatch ( effSetProgramName , 0 , 0 , const_cast < char * > ( mpt : : ToCharset ( mpt : : Charset : : Locale , name . Left ( kVstMaxProgNameLen ) ) . c_str ( ) ) , 0.0f ) ;
}
CString CVstPlugin : : GetProgramName ( int32 program )
{
// kVstMaxProgNameLen is 24... too short for some plugins, so use at least 256 bytes.
std : : vector < char > rawname ( 256 , 0 ) ;
if ( program < m_Effect . numPrograms )
{
if ( Dispatch ( effGetProgramNameIndexed , program , - 1 /*category*/ , rawname . data ( ) , 0 ) ! = 1 )
{
// Fallback: Try to get current program name.
rawname . assign ( 256 , 0 ) ;
int32 curProg = GetCurrentProgram ( ) ;
if ( program ! = curProg )
{
SetCurrentProgram ( program ) ;
}
Dispatch ( effGetProgramName , 0 , 0 , rawname . data ( ) , 0 ) ;
if ( program ! = curProg )
{
SetCurrentProgram ( curProg ) ;
}
}
}
return mpt : : ToCString ( mpt : : Charset : : Locale , rawname . data ( ) ) ;
}
void CVstPlugin : : SetCurrentProgram ( int32 nIndex )
{
if ( m_Effect . numPrograms > 0 )
{
if ( nIndex < m_Effect . numPrograms )
{
BeginSetProgram ( nIndex ) ;
EndSetProgram ( ) ;
}
}
}
void CVstPlugin : : BeginSetProgram ( int32 program )
{
Dispatch ( effBeginSetProgram , 0 , 0 , nullptr , 0 ) ;
if ( program ! = - 1 )
Dispatch ( effSetProgram , 0 , program , nullptr , 0 ) ;
}
void CVstPlugin : : EndSetProgram ( )
{
Dispatch ( effEndSetProgram , 0 , 0 , nullptr , 0 ) ;
}
void CVstPlugin : : BeginGetProgram ( int32 program )
{
if ( program ! = - 1 )
Dispatch ( effSetProgram , 0 , program , nullptr , 0 ) ;
if ( isBridged )
Dispatch ( effVendorSpecific , kVendorOpenMPT , kBeginGetProgram , nullptr , 0 ) ;
}
void CVstPlugin : : EndGetProgram ( )
{
if ( isBridged )
Dispatch ( effVendorSpecific , kVendorOpenMPT , kEndGetProgram , nullptr , 0 ) ;
}
PlugParamValue CVstPlugin : : GetParameter ( PlugParamIndex nIndex )
{
float fResult = 0 ;
if ( nIndex < m_Effect . numParams & & m_Effect . getParameter ! = nullptr )
{
DWORD exception = SETryOrError ( [ & ] ( ) { fResult = m_Effect . getParameter ( & m_Effect , nIndex ) ; } ) ;
if ( exception )
{
//ReportPlugException(U_("Exception in getParameter (Plugin=\"{}\")!\n"), m_Factory.szLibraryName);
}
}
return fResult ;
}
void CVstPlugin : : SetParameter ( PlugParamIndex nIndex , PlugParamValue fValue )
{
DWORD exception = 0 ;
if ( nIndex < m_Effect . numParams & & m_Effect . setParameter )
{
exception = SETryOrError ( [ & ] ( ) { m_Effect . setParameter ( & m_Effect , nIndex , fValue ) ; } ) ;
}
ResetSilence ( ) ;
if ( exception )
{
//ReportPlugException(mpt::format(U_("Exception in SetParameter({}, {})!"))(nIndex, fValue));
}
}
// Helper function for retreiving parameter name / label / display
CString CVstPlugin : : GetParamPropertyString ( PlugParamIndex param , Vst : : VstOpcodeToPlugin opcode )
{
if ( m_Effect . numParams > 0 & & param < m_Effect . numParams )
{
// Increased to 256 bytes since SynthMaster 2.8 writes more than 64 bytes of 0-padding. Kind of ridiculous if you consider that kVstMaxParamStrLen = 8...
std : : vector < char > s ( 256 , 0 ) ;
Dispatch ( opcode , param , 0 , s . data ( ) , 0 ) ;
return mpt : : ToCString ( mpt : : Charset : : Locale , s . data ( ) ) ;
}
return CString ( ) ;
}
CString CVstPlugin : : GetParamName ( PlugParamIndex param )
{
VstParameterProperties properties { } ;
if ( param < m_Effect . numParams & & Dispatch ( effGetParameterProperties , param , 0 , & properties , 0.0f ) = = 1 )
{
mpt : : String : : SetNullTerminator ( properties . label ) ;
return mpt : : ToCString ( mpt : : Charset : : Locale , properties . label ) ;
} else
{
return GetParamPropertyString ( param , effGetParamName ) ;
}
}
CString CVstPlugin : : GetDefaultEffectName ( )
{
if ( m_isVst2 )
{
std : : vector < char > s ( 256 , 0 ) ;
Dispatch ( effGetEffectName , 0 , 0 , s . data ( ) , 0 ) ;
return mpt : : ToCString ( mpt : : Charset : : Locale , s . data ( ) ) ;
}
return CString ( ) ;
}
void CVstPlugin : : Resume ( )
{
const uint32 sampleRate = m_SndFile . GetSampleRate ( ) ;
//reset some stuff
m_MixState . nVolDecayL = 0 ;
m_MixState . nVolDecayR = 0 ;
if ( m_isResumed )
{
Dispatch ( effStopProcess , 0 , 0 , nullptr , 0.0f ) ;
Dispatch ( effMainsChanged , 0 , 0 , nullptr , 0.0f ) ; // calls plugin's suspend
}
if ( sampleRate ! = m_nSampleRate )
{
m_nSampleRate = sampleRate ;
Dispatch ( effSetSampleRate , 0 , 0 , nullptr , static_cast < float > ( m_nSampleRate ) ) ;
}
Dispatch ( effSetBlockSize , 0 , MIXBUFFERSIZE , nullptr , 0.0f ) ;
//start off some stuff
Dispatch ( effMainsChanged , 0 , 1 , nullptr , 0.0f ) ; // calls plugin's resume
Dispatch ( effStartProcess , 0 , 0 , nullptr , 0.0f ) ;
m_isResumed = true ;
}
void CVstPlugin : : Suspend ( )
{
if ( m_isResumed )
{
Dispatch ( effStopProcess , 0 , 0 , nullptr , 0.0f ) ;
Dispatch ( effMainsChanged , 0 , 0 , nullptr , 0.0f ) ; // calls plugin's suspend (theoretically, plugins should clean their buffers here, but oh well, the number of plugins which don't do this is surprisingly high.)
m_isResumed = false ;
}
}
// Send events to plugin. Returns true if there are events left to be processed.
void CVstPlugin : : ProcessVSTEvents ( )
{
// Process VST events
if ( m_Effect . dispatcher ! = nullptr & & vstEvents . Finalise ( ) > 0 )
{
DWORD exception = SETryOrError ( [ & ] ( ) { m_Effect . dispatcher ( & m_Effect , effProcessEvents , 0 , 0 , & vstEvents , 0 ) ; } ) ;
ResetSilence ( ) ;
if ( exception )
{
ReportPlugException ( MPT_UFORMAT ( " Exception {} in ProcessVSTEvents(numEvents:{})! " ) (
mpt : : ufmt : : HEX0 < 8 > ( exception ) ,
vstEvents . size ( ) ) ) ;
}
}
}
// Receive events from plugin and send them to the next plugin in the chain.
void CVstPlugin : : ReceiveVSTEvents ( const VstEvents * events )
{
if ( m_pMixStruct = = nullptr )
{
return ;
}
ResetSilence ( ) ;
// I think we should only route events to plugins that are explicitely specified as output plugins of the current plugin.
// This should probably use GetOutputPlugList here if we ever get to support multiple output plugins.
PLUGINDEX receiver = m_pMixStruct - > GetOutputPlugin ( ) ;
if ( receiver ! = PLUGINDEX_INVALID )
{
IMixPlugin * plugin = m_SndFile . m_MixPlugins [ receiver ] . pMixPlugin ;
CVstPlugin * vstPlugin = dynamic_cast < CVstPlugin * > ( plugin ) ;
// Add all events to the plugin's queue.
for ( const auto & ev : * events )
{
if ( vstPlugin ! = nullptr )
{
// Directly enqueue the message and preserve as much of the event data as possible (e.g. delta frames, which are currently not used by OpenMPT but might be by plugins)
vstPlugin - > vstEvents . Enqueue ( ev ) ;
} else if ( plugin ! = nullptr )
{
if ( ev - > type = = kVstMidiType )
{
plugin - > MidiSend ( static_cast < const VstMidiEvent * > ( ev ) - > midiData ) ;
} else if ( ev - > type = = kVstSysExType )
{
auto event = static_cast < const VstMidiSysexEvent * > ( ev ) ;
plugin - > MidiSysexSend ( mpt : : as_span ( mpt : : byte_cast < const std : : byte * > ( event - > sysexDump ) , event - > dumpBytes ) ) ;
}
}
}
}
# ifdef MODPLUG_TRACKER
if ( m_recordMIDIOut )
{
// Spam MIDI data to all views
for ( const auto & ev : * events )
{
if ( ev - > type = = kVstMidiType )
{
VstMidiEvent * event = static_cast < VstMidiEvent * > ( ev ) ;
: : SendNotifyMessage ( CMainFrame : : GetMainFrame ( ) - > GetMidiRecordWnd ( ) , WM_MOD_MIDIMSG , event - > midiData , reinterpret_cast < LPARAM > ( this ) ) ;
}
}
}
# endif // MODPLUG_TRACKER
}
void CVstPlugin : : Process ( float * pOutL , float * pOutR , uint32 numFrames )
{
ProcessVSTEvents ( ) ;
// If the plugin is found & ok, continue
if ( m_pProcessFP ! = nullptr & & m_mixBuffer . Ok ( ) )
{
int32 numInputs = m_Effect . numInputs , numOutputs = m_Effect . numOutputs ;
//RecalculateGain();
// Merge stereo input before sending to the plugin if it can only handle one input.
if ( numInputs = = 1 )
{
float * plugInputL = m_mixBuffer . GetInputBuffer ( 0 ) ;
float * plugInputR = m_mixBuffer . GetInputBuffer ( 1 ) ;
for ( uint32 i = 0 ; i < numFrames ; i + + )
{
plugInputL [ i ] = 0.5f * ( plugInputL [ i ] + plugInputR [ i ] ) ;
}
}
float * * outputBuffers = m_mixBuffer . GetOutputBufferArray ( ) ;
if ( ! isBridged )
{
m_mixBuffer . ClearOutputBuffers ( numFrames ) ;
}
// Do the VST processing magic
MPT_ASSERT ( numFrames < = MIXBUFFERSIZE ) ;
{
DWORD exception = SETryOrError ( [ & ] ( ) { m_pProcessFP ( & m_Effect , m_mixBuffer . GetInputBufferArray ( ) , outputBuffers , numFrames ) ; } ) ;
if ( exception )
{
Bypass ( ) ;
mpt : : ustring processMethod = ( m_Effect . flags & effFlagsCanReplacing ) ? U_ ( " processReplacing " ) : U_ ( " process " ) ;
ReportPlugException ( MPT_UFORMAT ( " The plugin threw an exception ({}) in {}. It has automatically been set to \" Bypass \" . " ) ( mpt : : ufmt : : HEX0 < 8 > ( exception ) , processMethod ) ) ;
}
}
// Mix outputs of multi-output VSTs:
if ( numOutputs > 2 )
{
MPT_ASSERT ( outputBuffers ! = nullptr ) ;
// first, mix extra outputs on a stereo basis
int32 outs = numOutputs ;
// so if nOuts is not even, let process the last output later
if ( ( outs % 2u ) = = 1 ) outs - - ;
// mix extra stereo outputs
for ( int32 iOut = 2 ; iOut < outs ; iOut + + )
{
for ( uint32 i = 0 ; i < numFrames ; i + + )
{
outputBuffers [ iOut % 2u ] [ i ] + = outputBuffers [ iOut ] [ i ] ; // assumed stereo.
}
}
// if m_Effect.numOutputs is odd, mix half the signal of last output to each channel
if ( outs ! = numOutputs )
{
// trick : if we are here, numOutputs = m_Effect.numOutputs - 1 !!!
for ( uint32 i = 0 ; i < numFrames ; i + + )
{
float v = 0.5f * outputBuffers [ outs ] [ i ] ;
outputBuffers [ 0 ] [ i ] + = v ;
outputBuffers [ 1 ] [ i ] + = v ;
}
}
}
if ( numOutputs ! = 0 )
{
MPT_ASSERT ( outputBuffers ! = nullptr ) ;
ProcessMixOps ( pOutL , pOutR , outputBuffers [ 0 ] , outputBuffers [ numOutputs > 1 ? 1 : 0 ] , numFrames ) ;
}
// If the I/O format of the bridge changed in the meanwhile, update it now.
if ( isBridged & & Dispatch ( effVendorSpecific , kVendorOpenMPT , kCloseOldProcessingMemory , nullptr , 0.0f ) ! = 0 )
{
InitializeIOBuffers ( ) ;
}
}
vstEvents . Clear ( ) ;
m_positionChanged = false ;
}
bool CVstPlugin : : MidiSend ( uint32 dwMidiCode )
{
// Note-Offs go at the start of the queue (since OpenMPT 1.17). Needed for situations like this:
// ... ..|C-5 01
// C-5 01|=== ..
// TODO: Should not be used with real-time notes! Letting the key go too quickly
// (e.g. while output device is being initalized) will cause the note to be stuck!
bool insertAtFront = ( MIDIEvents : : GetTypeFromEvent ( dwMidiCode ) = = MIDIEvents : : evNoteOff ) ;
VstMidiEvent event { } ;
event . type = kVstMidiType ;
event . byteSize = sizeof ( event ) ;
event . midiData = dwMidiCode ;
ResetSilence ( ) ;
return vstEvents . Enqueue ( & event , insertAtFront ) ;
}
bool CVstPlugin : : MidiSysexSend ( mpt : : const_byte_span sysex )
{
VstMidiSysexEvent event { } ;
event . type = kVstSysExType ;
event . byteSize = sizeof ( event ) ;
event . dumpBytes = mpt : : saturate_cast < int32 > ( sysex . size ( ) ) ;
event . sysexDump = sysex . data ( ) ; // We will make our own copy in VstEventQueue::Enqueue
ResetSilence ( ) ;
return vstEvents . Enqueue ( & event ) ;
}
void CVstPlugin : : HardAllNotesOff ( )
{
constexpr uint32 SCRATCH_BUFFER_SIZE = 64 ;
float out [ 2 ] [ SCRATCH_BUFFER_SIZE ] ; // scratch buffers
// The JUCE framework doesn't like processing while being suspended.
const bool wasSuspended = ! IsResumed ( ) ;
if ( wasSuspended )
{
Resume ( ) ;
}
const bool isWavestation = GetUID ( ) = = FourCC ( " KLWV " ) ;
const bool isSawer = GetUID ( ) = = FourCC ( " SaWR " ) ;
for ( uint8 mc = 0 ; mc < m_MidiCh . size ( ) ; mc + + )
{
PlugInstrChannel & channel = m_MidiCh [ mc ] ;
channel . ResetProgram ( ) ;
SendMidiPitchBend ( mc , EncodePitchBendParam ( MIDIEvents : : pitchBendCentre ) ) ; // centre pitch bend
if ( ! isWavestation & & ! isSawer )
{
// Korg Wavestation doesn't seem to like this CC, it can introduce ghost notes or
// prevent new notes from being played.
// Image-Line Sawer does not like it either and resets some parameters so that the plugin is all
// distorted afterwards.
MidiSend ( MIDIEvents : : CC ( MIDIEvents : : MIDICC_AllControllersOff , mc , 0 ) ) ;
}
if ( ! isSawer )
{
// Image-Line Sawer takes ages to execute this CC.
MidiSend ( MIDIEvents : : CC ( MIDIEvents : : MIDICC_AllNotesOff , mc , 0 ) ) ;
}
MidiSend ( MIDIEvents : : CC ( MIDIEvents : : MIDICC_AllSoundOff , mc , 0 ) ) ;
for ( std : : size_t i = 0 ; i < std : : size ( channel . noteOnMap ) ; i + + ) //all notes
{
for ( auto & c : channel . noteOnMap [ i ] )
{
while ( c ! = 0 )
{
MidiSend ( MIDIEvents : : NoteOff ( mc , static_cast < uint8 > ( i ) , 0 ) ) ;
c - - ;
}
}
}
}
// Let plugin process events
while ( vstEvents . GetNumQueuedEvents ( ) > 0 )
{
Process ( out [ 0 ] , out [ 1 ] , SCRATCH_BUFFER_SIZE ) ;
}
if ( wasSuspended )
{
Suspend ( ) ;
}
}
void CVstPlugin : : SaveAllParameters ( )
{
if ( m_pMixStruct = = nullptr )
{
return ;
}
m_pMixStruct - > defaultProgram = - 1 ;
if ( ProgramsAreChunks ( ) )
{
void * p = nullptr ;
// Try to get whole bank
intptr_t byteSize = Dispatch ( effGetChunk , 0 , 0 , & p , 0 ) ;
if ( ! p )
{
// Getting bank failed, try to get just preset
byteSize = Dispatch ( effGetChunk , 1 , 0 , & p , 0 ) ;
} else
{
// We managed to get the bank, now we need to remember which program we're on.
m_pMixStruct - > defaultProgram = GetCurrentProgram ( ) ;
}
if ( p ! = nullptr )
{
LimitMax ( byteSize , Util : : MaxValueOfType ( byteSize ) - 4 ) ;
try
{
m_pMixStruct - > pluginData . resize ( byteSize + 4 ) ;
auto data = m_pMixStruct - > pluginData . data ( ) ;
memcpy ( data , " fEvN " , 4 ) ; // 'NvEf', return value of deprecated effIdentify call
memcpy ( data + 4 , p , byteSize ) ;
return ;
} catch ( mpt : : out_of_memory e )
{
mpt : : delete_out_of_memory ( e ) ;
}
}
}
// This plugin doesn't support chunks: save parameters
IMixPlugin : : SaveAllParameters ( ) ;
}
void CVstPlugin : : RestoreAllParameters ( int32 program )
{
if ( m_pMixStruct ! = nullptr & & m_pMixStruct - > pluginData . size ( ) > = 4 )
{
auto data = m_pMixStruct - > pluginData . data ( ) ;
if ( ! memcmp ( data , " fEvN " , 4 ) ) // 'NvEf', return value of deprecated effIdentify call
{
if ( ( program > = 0 ) & & ( program < m_Effect . numPrograms ) )
{
// Bank
Dispatch ( effSetChunk , 0 , m_pMixStruct - > pluginData . size ( ) - 4 , data + 4 , 0 ) ;
SetCurrentProgram ( program ) ;
} else
{
// Program
BeginSetProgram ( - 1 ) ;
Dispatch ( effSetChunk , 1 , m_pMixStruct - > pluginData . size ( ) - 4 , data + 4 , 0 ) ;
EndSetProgram ( ) ;
}
} else
{
IMixPlugin : : RestoreAllParameters ( program ) ;
}
}
}
CAbstractVstEditor * CVstPlugin : : OpenEditor ( )
{
try
{
if ( HasEditor ( ) )
return new COwnerVstEditor ( * this ) ;
else
return new CDefaultVstEditor ( * this ) ;
} catch ( mpt : : out_of_memory e )
{
mpt : : delete_out_of_memory ( e ) ;
ReportPlugException ( U_ ( " Exception in OpenEditor() " ) ) ;
return nullptr ;
}
}
void CVstPlugin : : Bypass ( bool bypass )
{
Dispatch ( effSetBypass , bypass ? 1 : 0 , 0 , nullptr , 0.0f ) ;
IMixPlugin : : Bypass ( bypass ) ;
}
void CVstPlugin : : NotifySongPlaying ( bool playing )
{
m_isSongPlaying = playing ;
}
bool CVstPlugin : : IsInstrument ( ) const
{
return ( ( m_Effect . flags & effFlagsIsSynth ) | | ( ! m_Effect . numInputs ) ) ;
}
bool CVstPlugin : : CanRecieveMidiEvents ( )
{
return Dispatch ( effCanDo , 0 , 0 , const_cast < char * > ( PluginCanDo : : receiveVstMidiEvent ) , 0.0f ) ! = 0 ;
}
void CVstPlugin : : ReportPlugException ( const mpt : : ustring & text ) const
{
CVstPluginManager : : ReportPlugException ( MPT_UFORMAT ( " {} (Plugin: {}) " ) ( text , m_Factory . libraryName ) ) ;
}
// Cache program names for plugin bridge
void CVstPlugin : : CacheProgramNames ( int32 firstProg , int32 lastProg )
{
if ( isBridged )
{
int32 offsets [ 2 ] = { firstProg , lastProg } ;
Dispatch ( effVendorSpecific , kVendorOpenMPT , kCacheProgramNames , offsets , 0.0f ) ;
}
}
// Cache parameter names for plugin bridge
void CVstPlugin : : CacheParameterNames ( int32 firstParam , int32 lastParam )
{
if ( isBridged )
{
int32 offsets [ 2 ] = { firstParam , lastParam } ;
Dispatch ( effVendorSpecific , kVendorOpenMPT , kCacheParameterInfo , offsets , 0.0f ) ;
}
}
IMixPlugin : : ChunkData CVstPlugin : : GetChunk ( bool isBank )
{
std : : byte * chunk = nullptr ;
auto size = Dispatch ( effGetChunk , isBank ? 0 : 1 , 0 , & chunk , 0 ) ;
if ( chunk = = nullptr )
{
size = 0 ;
}
return ChunkData ( chunk , size ) ;
}
void CVstPlugin : : SetChunk ( const ChunkData & chunk , bool isBank )
{
Dispatch ( effSetChunk , isBank ? 0 : 1 , chunk . size ( ) , const_cast < std : : byte * > ( chunk . data ( ) ) , 0 ) ;
}
OPENMPT_NAMESPACE_END
# endif // MPT_WITH_VST