Skip to content

Commit

Permalink
Added VstMidiProxy project + some fixes
Browse files Browse the repository at this point in the history
1. Added VstMidiProxy project. VstMidiProxy enables global ports so multiple clients can use the same ports. If it is enabled it also makes your heavy plugins (e.g. SC-VA) never close and stay ready all the time.
Autostart with Windows option is also implemented.
2. Added conversion of short Midi messages sent with midiOutLongMsg(). VSTi plugins usually cannot handle short messages sent as kVstSysExType. So the driver sends short channel messages always as kVstMidiType.
3. Fixed Winamp Midi plugin 3.x ANSI/Unicode bug.
  • Loading branch information
Falcosoft committed Mar 28, 2024
1 parent 4d6e61a commit 84a1cd0
Show file tree
Hide file tree
Showing 31 changed files with 1,301 additions and 210 deletions.
8 changes: 7 additions & 1 deletion Help/Readme.html
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,13 @@ <h2>VST MIDI Driver (Falcomod) </h2>
<em>Application</em>: Plugin scaling. This is the least compatible option. Since VST2 editors cannot do
scaling in 90% of cases this option most likely results in tiny editors on high DPI screens. </p>
</li>
</ol>
</ol>
<p><strong>VST Midi Driver Global Proxy:</strong></p>
<p>VST Midi driver is a user mode driver and every Midi client opens its own instance of the driver. This has the advantage that you can use as many Midi clients at once with VST Midi driver as you want. Each Midi client can open its own instance (provided the driver is not set to use exclusive mode ASIO/WASAPI audio output). In this respect VST Midi driver is fully multi-client and does not have such restrictions as traditional Midi Out ports/drivers.<br>
But sometimes you may want to use the same driver instance with multiple clients. E.g. one client to play some Midi files and another client/controller to alter Midi controller values real-time on the same instance that is also used for playback. You may also want a global instance so that your heavy plugin (e.g. SC-VA) never closes and stays ready all the time.<br>
The solution for the above scenarios is VST Midi Driver Global Proxy. It creates 2 new global ports named VST MIDI Synth Global (port A / B). Every Midi client that uses one of these ports can use the same instance of the driver. <br>
For this the proxy uses <a href="https://www.tobias-erichsen.de/software.html" target="_blank">Tobias Erichsen's virtualMIDI driver</a> that is also used by loopMIDI and rtpMIDI. You have to install either loopMIDI or rtpMIDI to use the global proxy. Download links can be found on the main dialog of the proxy. On the main dialog you can also select the 'Autostart with Windows' option. When no problem happens at startup the proxy is automatically loaded to the system tray. Clicking on the system tray icon opens the main dialog of the global proxy. <br>
Since Tobias Erichsen's virtualMIDI driver requires at least Win XP SP2 this is also true for the global proxy. </p>
<p>&nbsp;</p>
<p>
<strong>Storage of settings:</strong>
Expand Down
9 changes: 4 additions & 5 deletions cplwrapper/cplwrapper.08.vcproj
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,11 @@
/>
<Tool
Name="VCCLCompilerTool"
Optimization="1"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;CPLWRAPPER_EXPORTS"
RuntimeLibrary="2"
BufferSecurityCheck="false"
FloatingPointModel="2"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
Expand All @@ -137,14 +139,15 @@
<Tool
Name="VCLinkerTool"
AdditionalOptions="/ignore:4254"
AdditionalDependencies="msvcrt.lib"
AdditionalDependencies="..\external_packages\lib\msvcrt.lib"
OutputFile="$(OutDir)\vstmididrvcfg.cpl"
LinkIncremental="1"
GenerateManifest="false"
IgnoreAllDefaultLibraries="true"
ModuleDefinitionFile="cplwrapper.def"
GenerateDebugInformation="true"
SubSystem="2"
OptimizeReferences="2"
TargetMachine="1"
/>
<Tool
Expand Down Expand Up @@ -234,10 +237,6 @@
>
</File>
</Filter>
<File
RelativePath=".\ReadMe.txt"
>
</File>
</Files>
<Globals>
</Globals>
Expand Down
10 changes: 3 additions & 7 deletions cplwrapper/cplwrapper.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
<ProjectName>cplwrapper</ProjectName>
<ProjectGuid>{21817C19-DA31-4413-A92D-3DEE89C88F0F}</ProjectGuid>
<RootNamespace>cplwrapper</RootNamespace>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>7.0</WindowsTargetPlatformVersion>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
Expand All @@ -36,10 +35,7 @@
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>15.0.28307.799</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
Expand Down Expand Up @@ -85,7 +81,7 @@
</ClCompile>
<Link>
<AdditionalOptions>/ignore:4254 %(AdditionalOptions)</AdditionalOptions>
<AdditionalDependencies>msvcrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>..\external_packages\lib\msvcrt.lib;%(AdditionalDependencies)</AdditionalDependencies>
<OutputFile>$(OutDir)vstmididrvcfg.cpl</OutputFile>
<IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
<ModuleDefinitionFile>cplwrapper.def</ModuleDefinitionFile>
Expand Down
16 changes: 16 additions & 0 deletions driver/MidiSynth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,22 @@ namespace VSTMIDIDRV {
}

void MidiSynth::PlaySysEx(unsigned uDeviceID, unsigned char* bufpos, DWORD len) {

//Short Midi channel messages can be also sent by midiOutLongMsg(). But VSTi plugins many times cannot handle short messages sent as kVstSysExType.
//So we will send short channel messages always as kVstMidiType. No running status support in this case.
if (bufpos[0] < 0xF0 && len > 1) {

DWORD startPos = 0;
for (DWORD i = 0; i < len; i++) {
if (bufpos[i] > 0x7F || i == len - 1) {
DWORD shortMsg = 0;
memcpy(&shortMsg, &bufpos[startPos], DwordMin(i - startPos + 1, 3));
PushMIDI(uDeviceID, shortMsg);
startPos = i;
}
}
return;
}

ScopeLock<Win32Lock> scopeLock(&synthLock);

Expand Down
192 changes: 65 additions & 127 deletions driver/winmm_drv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

#include "stdafx.h"
#include "../version.h"
#include <math.h>
#include <commdlg.h>

Expand All @@ -30,6 +31,23 @@ static bool synthOpened = false;
//static HWND hwnd = NULL;
static int driverCount;

static TCHAR* GetFileVersion(TCHAR* result, unsigned int buffSize)
{
TCHAR tmpBuff[12] = { 0 };

_tcscat_s(result, buffSize, _T("version: "));
_ultot_s(VERSION_MAJOR, tmpBuff, _countof(tmpBuff), 10);
_tcscat_s(result, buffSize, tmpBuff);
_tcscat_s(result, buffSize, _T("."));
_ultot_s(VERSION_MINOR, tmpBuff, _countof(tmpBuff), 10);
_tcscat_s(result, buffSize, tmpBuff);
_tcscat_s(result, buffSize, _T("."));
_ultot_s(VERSION_PATCH, tmpBuff, _countof(tmpBuff), 10);
_tcscat_s(result, buffSize, tmpBuff);

return result;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ){
if (fdwReason == DLL_PROCESS_ATTACH){
hinst_vst_driver = hinstDLL;
Expand All @@ -56,53 +74,6 @@ struct Driver {
} clients[MAX_CLIENTS];
} drivers[MAX_DRIVERS];

#pragma comment(lib,"Version.lib")
static TCHAR* GetFileVersion(TCHAR* result, unsigned int buffSize)
{
DWORD dwSize = 0;
BYTE* pVersionInfo = NULL;
VS_FIXEDFILEINFO* pFileInfo = NULL;
UINT pLenFileInfo = 0;
TCHAR tmpBuff[MAX_PATH];

GetModuleFileName(hinst_vst_driver, tmpBuff, MAX_PATH);

dwSize = GetFileVersionInfoSize(tmpBuff, NULL);
if (dwSize == 0)
{
return NULL;
}

pVersionInfo = new BYTE[dwSize];

if (!GetFileVersionInfo(tmpBuff, 0, dwSize, pVersionInfo))
{
delete[] pVersionInfo;
return NULL;
}

if (!VerQueryValue(pVersionInfo, TEXT("\\"), (LPVOID*)&pFileInfo, &pLenFileInfo))
{
delete[] pVersionInfo;
return NULL;
}

_tcscat_s(result, buffSize, _T("version: "));
_ultot_s((pFileInfo->dwFileVersionMS >> 16) & 0xffff, tmpBuff, MAX_PATH, 10);
_tcscat_s(result, buffSize, tmpBuff);
_tcscat_s(result, buffSize, _T("."));
_ultot_s((pFileInfo->dwFileVersionMS) & 0xffff, tmpBuff, MAX_PATH, 10);
_tcscat_s(result, buffSize, tmpBuff);
_tcscat_s(result, buffSize, _T("."));
_ultot_s((pFileInfo->dwFileVersionLS >> 16) & 0xffff, tmpBuff, MAX_PATH, 10);
_tcscat_s(result, buffSize, tmpBuff);
//_tcscat_s(result, buffSize, _T("."));
//_tcscat_s(result, buffSize, _ultot((pFileInfo->dwFileVersionLS) & 0xffff, tmpBuff, 10));

return result;
}


EXTERN_C LRESULT WINAPI DriverProc(DWORD dwDriverID, HDRVR hdrvr, WORD wMessage, DWORD dwParam1, DWORD dwParam2) {

TCHAR fileversionBuff[32] = _T("Driver ");
Expand Down Expand Up @@ -163,80 +134,37 @@ EXTERN_C LRESULT WINAPI DriverProc(DWORD dwDriverID, HDRVR hdrvr, WORD wMessage,


HRESULT modGetCaps(UINT uDeviceID, PVOID capsPtr, DWORD capsSize) {
MIDIOUTCAPSA * myCapsA;
MIDIOUTCAPSW * myCapsW;
MIDIOUTCAPS2A * myCaps2A;
MIDIOUTCAPS2W * myCaps2W;

const CHAR synthName[] = "VST MIDI Synth\0";
const WCHAR synthNameW[] = L"VST MIDI Synth\0";

const CHAR synthPortA[] = " (port A)\0";
const WCHAR synthPortAW[] = L" (port A)\0";

const CHAR synthPortB[] = " (port B)\0";
const WCHAR synthPortBW[] = L" (port B)\0";

switch (capsSize) {
case (sizeof(MIDIOUTCAPSA)):
myCapsA = (MIDIOUTCAPSA *)capsPtr;
myCapsA->wMid = MM_UNMAPPED;
myCapsA->wPid = MM_MPU401_MIDIOUT;
memcpy(myCapsA->szPname, synthName, sizeof(synthName));
memcpy(myCapsA->szPname + strlen(synthName), uDeviceID ? synthPortB : synthPortA, sizeof(synthPortA));
myCapsA->wTechnology = MOD_MIDIPORT;
myCapsA->vDriverVersion = 0x0090;
myCapsA->wVoices = 0;
myCapsA->wNotes = 0;
myCapsA->wChannelMask = 0xffff;
myCapsA->dwSupport = MIDICAPS_VOLUME;
return MMSYSERR_NOERROR;

case (sizeof(MIDIOUTCAPSW)):
myCapsW = (MIDIOUTCAPSW *)capsPtr;
myCapsW->wMid = MM_UNMAPPED;
myCapsW->wPid = MM_MPU401_MIDIOUT;
memcpy(myCapsW->szPname, synthNameW, sizeof(synthNameW));
memcpy(myCapsW->szPname + wcslen(synthNameW), uDeviceID ? synthPortBW : synthPortAW, sizeof(synthPortAW));
myCapsW->wTechnology = MOD_MIDIPORT;
myCapsW->vDriverVersion = 0x0090;
myCapsW->wVoices = 0;
myCapsW->wNotes = 0;
myCapsW->wChannelMask = 0xffff;
myCapsW->dwSupport = MIDICAPS_VOLUME;
return MMSYSERR_NOERROR;

case (sizeof(MIDIOUTCAPS2A)):
myCaps2A = (MIDIOUTCAPS2A *)capsPtr;
myCaps2A->wMid = MM_UNMAPPED;
myCaps2A->wPid = MM_MPU401_MIDIOUT;
memcpy(myCaps2A->szPname, synthName, sizeof(synthName));
memcpy(myCaps2A->szPname + strlen(synthName), uDeviceID ? synthPortB : synthPortA, sizeof(synthPortA));
myCaps2A->wTechnology = MOD_MIDIPORT;
myCaps2A->vDriverVersion = 0x0090;
myCaps2A->wVoices = 0;
myCaps2A->wNotes = 0;
myCaps2A->wChannelMask = 0xffff;
myCaps2A->dwSupport = MIDICAPS_VOLUME;
return MMSYSERR_NOERROR;

case (sizeof(MIDIOUTCAPS2W)):
myCaps2W = (MIDIOUTCAPS2W *)capsPtr;
myCaps2W->wMid = MM_UNMAPPED;
myCaps2W->wPid = MM_MPU401_MIDIOUT;
memcpy(myCaps2W->szPname, synthNameW, sizeof(synthNameW));
memcpy(myCaps2W->szPname + wcslen(synthNameW), uDeviceID ? synthPortBW : synthPortAW, sizeof(synthPortAW));
myCaps2W->wTechnology = MOD_MIDIPORT;
myCaps2W->vDriverVersion = 0x0090;
myCaps2W->wVoices = 0;
myCaps2W->wNotes = 0;
myCaps2W->wChannelMask = 0xffff;
myCaps2W->dwSupport = MIDICAPS_VOLUME;
return MMSYSERR_NOERROR;

default:
return MMSYSERR_ERROR;

//Rewritten to give unique GUIDs for vstmidiproxy and to fix Winamp Midi plugin 3.x ANSI/Unicode bug:
//It seems under unicode WinNT you never get a real ANSI request even form ANSI Midi clients. WinMM handles conversion internally.
//If capsSize accidentally equals an ANSI structure size (MIDIOUTCAPSA, MIDIOUTCAPS2A) the request still expects wide strings.

static MIDIOUTCAPS2 myCaps2[MAX_DRIVERS] = { 0 };
static const TCHAR synthName[] = _T("VST MIDI Synth (port X)");

if (!myCaps2[uDeviceID].wMid)
{
myCaps2[uDeviceID].wMid = MM_UNMAPPED;
myCaps2[uDeviceID].wPid = MM_MPU401_MIDIOUT;
myCaps2[uDeviceID].wTechnology = MOD_MIDIPORT;
myCaps2[uDeviceID].vDriverVersion = VERSION_MAJOR << 8 | VERSION_MINOR;
myCaps2[uDeviceID].wVoices = 0;
myCaps2[uDeviceID].wNotes = 0;
myCaps2[uDeviceID].wChannelMask = 0xffff;
myCaps2[uDeviceID].dwSupport = MIDICAPS_VOLUME;

_tcscpy_s(myCaps2[uDeviceID].szPname, synthName);
unsigned portCharPos = (unsigned)(_tcschr(synthName, 'X') - synthName);
myCaps2[uDeviceID].szPname[portCharPos] = _T('A') + static_cast<TCHAR>(uDeviceID);

//MIDIOUTCAPS2 extra
myCaps2[uDeviceID].ManufacturerGuid = VSTMidiDrvManufacturerGuid;
myCaps2[uDeviceID].ProductGuid = uDeviceID ? VSTMidiDrvPortBGuid : VSTMidiDrvPortAGuid;
}

memcpy(capsPtr, &myCaps2[uDeviceID], min(sizeof(MIDIOUTCAPS2), capsSize));
return MMSYSERR_NOERROR;

}

void DoCallback(int driverNum, DWORD_PTR clientNum, DWORD msg, DWORD_PTR param1, DWORD_PTR param2) {
Expand Down Expand Up @@ -281,10 +209,14 @@ LONG CloseDriver(Driver *driver, UINT uDeviceID, UINT uMsg, DWORD_PTR dwUser, DW
}

EXTERN_C DWORD WINAPI modMessage(DWORD uDeviceID, DWORD uMsg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {

if (uDeviceID >= MAX_DRIVERS) return MMSYSERR_BADDEVICEID;

MIDIHDR *midiHdr;
Driver *driver = &drivers[uDeviceID];
DWORD instance;
DWORD res;

switch (uMsg) {
case MODM_OPEN:
if (!synthOpened) {
Expand Down Expand Up @@ -332,13 +264,7 @@ EXTERN_C DWORD WINAPI modMessage(DWORD uDeviceID, DWORD uMsg, DWORD_PTR dwUser,
}

}
return res;

case MODM_PREPARE:
return MMSYSERR_NOTSUPPORTED;

case MODM_UNPREPARE:
return MMSYSERR_NOTSUPPORTED;
return res;

case MODM_GETDEVCAPS:
return modGetCaps(uDeviceID, (PVOID)dwParam1, (DWORD)dwParam2);
Expand Down Expand Up @@ -391,6 +317,18 @@ EXTERN_C DWORD WINAPI modMessage(DWORD uDeviceID, DWORD uMsg, DWORD_PTR dwUser,
case MODM_GETNUMDEVS:
return MAX_DRIVERS;

case MODM_PREPARE:
return MMSYSERR_NOTSUPPORTED;

case MODM_UNPREPARE:
return MMSYSERR_NOTSUPPORTED;

case MODM_CACHEPATCHES:
return MMSYSERR_NOTSUPPORTED;

case MODM_CACHEDRUMPATCHES:
return MMSYSERR_NOTSUPPORTED;

default:

return MMSYSERR_NOERROR;
Expand Down
10 changes: 7 additions & 3 deletions drivercfg/MainDlg.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ BOOL IsWinVistaOrWin7()

BOOL IsCoolSoftMidiMapperInstalled()
{
MIDIOUTCAPS Caps;
ZeroMemory(&Caps, sizeof(Caps));
MIDIOUTCAPS Caps = { 0 };
MMRESULT result = midiOutGetDevCaps(0, &Caps, sizeof(Caps));
if (result != MMSYSERR_NOERROR)
return FALSE;
Expand Down Expand Up @@ -207,9 +206,10 @@ class CMainDlg : public CDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>,

LRESULT OnTcnSelchangeTab(int idCtrl, LPNMHDR pNMHDR, BOOL& bHandled)
{
BOOL dummy;
if(m_ctrlTab.GetCurSel() == 0 && m_view3.GetDriverChanged())
{
BOOL dummy;

m_view1.ResetDriverSettings();
m_view1.OnCbnSelchangeBuffersize(0, 0, 0, dummy);
m_view1.OnCbnSelchangeSamplerate(0, 0, 0, dummy);
Expand All @@ -219,6 +219,10 @@ class CMainDlg : public CDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>,
{
m_view3.ShowPortBControls(usingASIO && is4chMode);
}
else if (m_ctrlTab.GetCurSel() == 3)
{
m_view2.OnShowDialogView2(WM_INITDIALOG, 0, 0, dummy);
}

bHandled = false;
return 1;
Expand Down
Loading

0 comments on commit 84a1cd0

Please sign in to comment.