Fixed: Reimplemented Voicemeeter integration (based on official examples) to fix stuttering issues.

This commit is contained in:
jthedering committed 2022-05-15 18:27:24 +00:00
1 parent da3341ed42
commit 93af3de251
5 files changed
+331 -142

No files matched your search

+178 -122
View File
@@ -1,6 +1,6 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Jonas Thedering
Copyright (C) 2022 Jonas Thedering
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
@@ -18,7 +18,7 @@
*/
#include "stdafx.h"
#include <string>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellapi.h>
@@ -29,15 +29,21 @@
#define voicemeeterKeyPath L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\VB:Voicemeeter {17359A74-1236-5467}"
#define voicemeeterWowKeyPath L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\VB:Voicemeeter {17359A74-1236-5467}"
#define uninstallStringValueName L"UninstallString"
#ifdef _WIN64
#define voicemeeterRemoteFileName L"VoicemeeterRemote64.dll"
#else
#define voicemeeterRemoteFileName L"VoicemeeterRemote.dll"
#endif
#define IDM_RESTART 200
using namespace std;
long __stdcall callback(void* lpUser, long nCommand, void* lpData, long nnn);
static long __stdcall callback(void* lpUser, long nCommand, void* lpData, long nnn);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd)
{
vector<wstring> outputs;
if (lpCmdLine[0] != 0)
@@ -49,30 +55,17 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
LocalFree(argv);
}
VoicemeeterClient* client = new VoicemeeterClient(outputs);
MSG msg;
msg.wParam = 0;
bool loop = client->restart;
while (loop)
try
{
client->start();
client->restart = false;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
loop = client->restart;
client->stop();
VoicemeeterClient client(outputs);
client.run();
return 0;
}
catch (InitError e)
{
MessageBoxW(NULL, e.getMessage().c_str(), L"Init Error", MB_APPLMODAL | MB_OK | MB_ICONERROR);
return -1;
}
delete client;
return (int)msg.wParam;
}
VoicemeeterClient::VoicemeeterClient(const vector<wstring>& outputs)
@@ -87,108 +80,72 @@ VoicemeeterClient::VoicemeeterClient(const vector<wstring>& outputs)
voicemeeterDirectory = RegistryHelper::readValue(voicemeeterWowKeyPath, uninstallStringValueName);
size_t index = voicemeeterDirectory.find_last_of(L'\\');
HMODULE voicemeeterModule = NULL;
if (index != wstring::npos)
{
wstring setupFilename = voicemeeterDirectory.substr(index + 1);
voicemeeterType = 1;
if (setupFilename == L"VoicemeeterProSetup.exe")
voicemeeterType = 2;// banana
else if (setupFilename == L"Voicemeeter8Setup.exe")
voicemeeterType = 3;// potato
voicemeeterDirectory = voicemeeterDirectory.substr(0, index);
else
throw InitError(L"Voicemeeter is not installed");
#ifdef _WIN64
voicemeeterModule = LoadLibraryW((voicemeeterDirectory + L"\\VoicemeeterRemote64.dll").c_str());
#else
voicemeeterModule = LoadLibraryW((voicemeeterDirectory + L"\\VoicemeeterRemote.dll").c_str());
#endif
}
HMODULE module = NULL;
module = LoadLibraryW((voicemeeterDirectory + L"\\" voicemeeterRemoteFileName).c_str());
if (module == NULL)
throw InitError(L"Failed to load " voicemeeterRemoteFileName);
memset(&vmr, 0, sizeof(T_VBVMR_INTERFACE));
#define LOAD_PROC(proc) vmr.proc = (T_ ## proc)GetProcAddress(voicemeeterModule, # proc)
if (voicemeeterModule != NULL)
{
LOAD_PROC(VBVMR_Login);
LOAD_PROC(VBVMR_Logout);
LOAD_PROC(VBVMR_AudioCallbackRegister);
LOAD_PROC(VBVMR_AudioCallbackStart);
LOAD_PROC(VBVMR_AudioCallbackStop);
LOAD_PROC(VBVMR_AudioCallbackUnregister);
LOAD_PROC(VBVMR_GetVoicemeeterType);
restart = true;
}
#define LOAD_PROC(proc) vmr.proc = (T_ ## proc)GetProcAddress(module, # proc);if (vmr.proc == NULL) throw InitError(L"Did not find function \"" # proc L"\" in " voicemeeterRemoteFileName)
LOAD_PROC(VBVMR_Login);
LOAD_PROC(VBVMR_Logout);
LOAD_PROC(VBVMR_GetVoicemeeterType);
LOAD_PROC(VBVMR_IsParametersDirty);
LOAD_PROC(VBVMR_AudioCallbackRegister);
LOAD_PROC(VBVMR_AudioCallbackStart);
LOAD_PROC(VBVMR_AudioCallbackStop);
LOAD_PROC(VBVMR_AudioCallbackUnregister);
initSoftware();
}
VoicemeeterClient::~VoicemeeterClient()
{
if (loggedIn)
if (wTimer != 0)
{
vmr.VBVMR_Logout();
loggedIn = false;
KillTimer(NULL, wTimer);
wTimer = 0;
}
endSoftware();
}
void VoicemeeterClient::start()
void VoicemeeterClient::run()
{
if (!loggedIn)
loggedIn = (vmr.VBVMR_Login() == 0);
wTimer = SetTimer(NULL, 0, 500, NULL);
PostThreadMessage(mainThreadId, WM_COMMAND, IDM_RESTART, 0);
if (loggedIn)
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
long type;
if (vmr.VBVMR_GetVoicemeeterType(&type) == 0)
voicemeeterType = type;
}
unsigned outputCount;
if (voicemeeterType == 3)
outputCount = 5;
else if (voicemeeterType == 2)
outputCount = 3;
else
outputCount = 1;
for (unsigned i = 0; i < outputCount; i++)
{
wstringstream sstream;
sstream << "Output A" << (i + 1);
wstring output = sstream.str();
if (find(outputs.begin(), outputs.end(), output) != outputs.end())
switch (msg.message)
{
FilterEngine* engine = new FilterEngine();
engine->setDeviceInfo(false, true, L"Voicemeeter", output, L"", L"Voicemeeter " + output);
engines.push_back(engine);
case WM_COMMAND:
handleCommand(msg.wParam, msg.lParam);
break;
case WM_TIMER:
if (msg.wParam == wTimer)
{
// check if Voicemeeter type has changed
if (vmr.VBVMR_IsParametersDirty() >= 0)
{
if (!connected)
detectVoicemeeterType();
}
else
{
// Voicemeeter has been shut down
connected = false;
}
}
break;
}
else
{
engines.push_back(NULL);
}
idleSampleCounts.push_back(0);
}
if (vmr.VBVMR_AudioCallbackRegister(VBVMR_AUDIOCALLBACK_OUT, callback, this, "Equalizer APO") != 0)
{
PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
return;
}
vmr.VBVMR_AudioCallbackStart();
}
void VoicemeeterClient::stop()
{
vmr.VBVMR_AudioCallbackStop();
vmr.VBVMR_AudioCallbackUnregister();
idleSampleCounts.clear();
for (FilterEngine* engine : engines)
if (engine != NULL)
delete engine;
engines.clear();
}
void VoicemeeterClient::handle(long nCommand, void* lpData, long nnn)
@@ -198,26 +155,18 @@ void VoicemeeterClient::handle(long nCommand, void* lpData, long nnn)
case VBVMR_CBCOMMAND_STARTING:
{
VBVMR_LPT_AUDIOINFO audioInfo = (VBVMR_LPT_AUDIOINFO)lpData;
sampleRate = (float)audioInfo->samplerate;
maxFrameCount = audioInfo->nbSamplePerFrame;
for (FilterEngine* engine : engines)
if (engine != NULL)
engine->initialize((float)audioInfo->samplerate, 8, 8, 8, 0, audioInfo->nbSamplePerFrame);
engine->initialize(sampleRate, 8, 8, 8, 0, maxFrameCount);
VoicemeeterAPOInfo::saveVoicemeeterSampleRate((unsigned)audioInfo->samplerate);
}
break;
case VBVMR_CBCOMMAND_ENDING:
{
restart = true;
PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
}
break;
case VBVMR_CBCOMMAND_CHANGE:
{
VBVMR_LPT_AUDIOINFO audioInfo = (VBVMR_LPT_AUDIOINFO)lpData;
for (FilterEngine* engine : engines)
if (engine != NULL)
engine->initialize((float)audioInfo->samplerate, 8, 8, 8, 0, audioInfo->nbSamplePerFrame);
VoicemeeterAPOInfo::saveVoicemeeterSampleRate((unsigned)audioInfo->samplerate);
}
PostThreadMessage(mainThreadId, WM_COMMAND, IDM_RESTART, 0);
break;
case VBVMR_CBCOMMAND_BUFFER_OUT:
{
@@ -262,6 +211,113 @@ void VoicemeeterClient::handle(long nCommand, void* lpData, long nnn)
}
}
void VoicemeeterClient::initSoftware()
{
long rep = vmr.VBVMR_Login();
if (rep < 0)
throw InitError(L"Failed To Login");
if (vmr.VBVMR_IsParametersDirty() == 0)
detectVoicemeeterType();
else
connected = false;
unsigned tries = 30;
bool loop = true;
while (loop)
{
loop = false;
char clientName[64] = "Equalizer APO";
rep = vmr.VBVMR_AudioCallbackRegister(VBVMR_AUDIOCALLBACK_OUT, callback, this, clientName);
if (rep == 1)
{
wchar_t message[512];
wsprintf(message, L"Voicemeeter Output Insert already in use by:\n%S", clientName);
throw InitError(message);
}
else if (rep != 0)
{
if (tries > 1)
{
// sometimes fails temporarily after restarting VoicemeeterClient
Sleep(100);
tries--;
loop = true;
}
else
{
throw InitError(L"Failed to register audio callback");
}
}
}
}
void VoicemeeterClient::detectVoicemeeterType()
{
long vmType;
long rep = vmr.VBVMR_GetVoicemeeterType(&vmType);
if (rep == 0)
{
connected = true;
unsigned outputCount;
if (vmType == 3)
outputCount = 5;
else if (vmType == 2)
outputCount = 3;
else
outputCount = 1;
if (outputCount != engines.size())
{
idleSampleCounts.clear();
for (FilterEngine* engine : engines)
if (engine != NULL)
delete engine;
engines.clear();
for (unsigned i = 0; i < outputCount; i++)
{
wstringstream sstream;
sstream << "Output A" << (i + 1);
wstring output = sstream.str();
if (find(outputs.begin(), outputs.end(), output) != outputs.end())
{
FilterEngine* engine = new FilterEngine();
engine->setDeviceInfo(false, true, L"Voicemeeter", output, L"", L"Voicemeeter " + output);
if (sampleRate != 0.0f && maxFrameCount != 0)
engine->initialize(sampleRate, 8, 8, 8, 0, maxFrameCount);
engines.push_back(engine);
}
else
{
engines.push_back(NULL);
}
idleSampleCounts.push_back(0);
}
}
}
}
void VoicemeeterClient::endSoftware()
{
if (vmr.VBVMR_Logout != NULL)
vmr.VBVMR_Logout();
if (vmr.VBVMR_AudioCallbackUnregister != NULL)
vmr.VBVMR_AudioCallbackUnregister();
}
void VoicemeeterClient::handleCommand(WPARAM wparam, LPARAM lparam)
{
switch (LOWORD(wparam))
{
case IDM_RESTART:
Sleep(50);
if (vmr.VBVMR_AudioCallbackStart != NULL)
vmr.VBVMR_AudioCallbackStart();
break;
}
}
bool VoicemeeterClient::isBufferSilent(float** sampleData, long sampleCount)
{
bool silent = true;
@@ -282,7 +338,7 @@ bool VoicemeeterClient::isBufferSilent(float** sampleData, long sampleCount)
return silent;
}
long __stdcall callback(void* lpUser, long nCommand, void* lpData, long nnn)
static long __stdcall callback(void* lpUser, long nCommand, void* lpData, long nnn)
{
VoicemeeterClient* client = (VoicemeeterClient*)lpUser;
client->handle(nCommand, lpData, nnn);
+29 -8
View File
@@ -1,6 +1,6 @@
/*
This file is part of Equalizer APO, a system-wide equalizer.
Copyright (C) 2017 Jonas Thedering
Copyright (C) 2022 Jonas Thedering
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
@@ -29,20 +29,41 @@ class VoicemeeterClient
public:
VoicemeeterClient(const std::vector<std::wstring>& outputs);
~VoicemeeterClient();
void start();
void stop();
void run();
void handle(long nCommand, void* lpData, long nnn);
bool restart = false;
private:
void initSoftware();
void detectVoicemeeterType();
void endSoftware();
void handleCommand(WPARAM wparam, LPARAM lparam);
bool isBufferSilent(float** sampleData, long sampleCount);
std::vector<std::wstring> outputs;
unsigned long mainThreadId;
T_VBVMR_INTERFACE vmr;
size_t wTimer = 0;
bool connected = true;
float sampleRate = 0.0f;
unsigned maxFrameCount = 0;
std::vector<FilterEngine*> engines;
std::vector<int> idleSampleCounts;
bool loggedIn = false;
long voicemeeterType = 1;
std::vector<std::wstring> outputs;
};
class InitError
{
public:
InitError(const std::wstring& message)
: message(message)
{
}
std::wstring getMessage()
{
return message;
}
private:
std::wstring message;
};
Binary file not shown.
+123 -11
View File
@@ -1,22 +1,20 @@
/******************************************************************************/
/* Voicemeeter Remote API. V.Burel©2015 */
/* Voicemeeter Remote API. V.Burel©2015-2021 */
/******************************************************************************/
/* This Library allows communication with Voicemeeter Applications */
/* This Library allows communication with Voicemeeter applications */
/* 4 Client Applications can be connected to remote Voicemeeter. */
/* it also allows communication with MacroButtons application. */
/******************************************************************************/
/* */
/* OFFICIAL LINK : WWW.VOICEMEETER.COM */
/* */
/******************************************************************************/
/* COPYRIGHT: Vincent Burel(c)2015 All Rights Reserved */
/* COPYRIGHT: Vincent Burel(c)2015-2021 All Rights Reserved */
/******************************************************************************/
/* LICENSING: This header file and VoicemeeterRemote.dll are made available */
/* for evaluation and testing purpose only (to make demo and proof */
/* of concept). */
/* */
/* for any commercial exploitation or to get specific support, */
/* a commercial license needs to be purchased. thanks to contact */
/* us by e-mail (get our contact on www.vb-audio.com). */
/* LICENSING: VoicemeeterRemote.dll usage is driven by a license agreement */
/* given in VoicemeeterRemoteAPI.pdf or readme.txt */
/* */
/******************************************************************************/
/* long = 32 bit integer */
/******************************************************************************/
@@ -61,9 +59,10 @@ long __stdcall VBVMR_Logout(void);
/**
@brief Run Voicemeeter Application (get installation directory and run Voicemeeter Application).
@param vType : Voicemeeter type (1 = Voicemeeter, 2= Voicemeeter Banana).
@param vType : Voicemeeter type (1 = Voicemeeter, 2= Voicemeeter Banana, 3= Voicemeeter Potato, 6 = Potato x64 bits).
@return : 0: Ok.
-1: not installed
-1: not installed (UninstallString not found in registry).
-2: unknown vType number
*/
long __stdcall VBVMR_RunVoicemeeter(long vType);
@@ -106,6 +105,11 @@ long __stdcall VBVMR_RunVoicemeeter(long vType);
+---------+---------+---------+-------------+-----------+------+------+------+------+------+
| 0 | 1 | 2 | 3 | 4 | 0 | 1 | 2 | 3 | 4 |
VOICEMEETER POTATO STRIP/BUS INDEX ASSIGNMENT
| Strip 1 | Strip 2 | Strip 2 | Strip 2 | Strip 2 |Virtual Input|Virtual AUX| VAIO3 |BUS A1|BUS A2|BUS A3|BUS A4|BUS A5|BUS B1|BUS B2|BUS B3|
+---------+---------+---------+---------+---------+-------------+-----------+-----------+------+------+------+------+------+------+------+------+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
@return : 0: OK (no error).
@@ -255,6 +259,20 @@ long __stdcall VBVMR_GetParameterStringW(char * szParamName, unsigned short * ws
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
VOICEMEETER POTATO CHANNEL ASSIGNMENT
| Strip 1 | Strip 2 | Strip 3 | Strip 4 | Strip 5 | Virtual Input | Virtual Input AUX | VAIO3 |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
| Output A1 | Output A2 | Output A3 | Output A4 | Output A5 |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
| Virtual Output B1 | Virtual Output B2 | Virtual Output B3 |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
@return : 0: OK (no error).
-1: error
@@ -681,6 +699,66 @@ long __stdcall VBVMR_AudioCallbackUnregister(void);
/******************************************************************************/
/* */
/* Macro Buttons */
/* */
/******************************************************************************/
/** @name Macro Buttons functions
* @{ */
/**
@brief Check if Macro Buttons states changed.
Call this function periodically (typically every 50 or 500ms) to know if something happen on MacroButton states .
(this function must be called from one thread only)
@return: 0: no new status.
>0: last nu logical button status changed.
-1: error (unexpected)
-2: no server.
*/
long __stdcall VBVMR_MacroButton_IsDirty(void);
/**
@brief get current status of a given button.
@param nuLogicalButton : button index: 0 to 79)
@param pValue : Pointer on float (32bit float by reference) receiving the wanted value (0.0 = OFF / 1.0 = ON).
@param bitmode: define what kind of value you want to read (see MACROBUTTON_MODE below)
@return : 0: OK (no error).
-1: error
-2: no server.
-3: unknown parameter
-5: structure mismatch
*/
long __stdcall VBVMR_MacroButton_GetStatus(long nuLogicalButton, float * pValue, long bitmode);
/**
@brief set current button value.
@param nuLogicalButton : button index: 0 to 79)
@param fValue : float 32 bit value giving the status (0.0 = OFF / 1.0 = ON).
@param bitmode: define what kind of value you want to write/modify (see MACROBUTTON_MODE below)
@return : 0: OK (no error).
-1: error
-2: no server.
-3: unknown parameter
-5: structure mismatch
*/
long __stdcall VBVMR_MacroButton_SetStatus(long nuLogicalButton, float fValue, long bitmode);
#define VBVMR_MACROBUTTON_MODE_DEFAULT 0x00000000 //PUSH or RELEASE button
#define VBVMR_MACROBUTTON_MODE_STATEONLY 0x00000002 //change Displayed State only
#define VBVMR_MACROBUTTON_MODE_TRIGGER 0x00000003 //change Trigger State
/** @} */
@@ -725,6 +803,11 @@ typedef long (__stdcall *T_VBVMR_AudioCallbackStart)(void);
typedef long (__stdcall *T_VBVMR_AudioCallbackStop)(void);
typedef long (__stdcall *T_VBVMR_AudioCallbackUnregister)(void);
typedef long (__stdcall *T_VBVMR_MacroButton_IsDirty)(void);
typedef long (__stdcall *T_VBVMR_MacroButton_GetStatus)(long nuLogicalButton, float * pValue, long bitmode);
typedef long (__stdcall *T_VBVMR_MacroButton_SetStatus)(long nuLogicalButton, float fValue, long bitmode);
typedef struct tagVBVMR_INTERFACE
{
@@ -759,6 +842,10 @@ typedef struct tagVBVMR_INTERFACE
T_VBVMR_AudioCallbackStop VBVMR_AudioCallbackStop;
T_VBVMR_AudioCallbackUnregister VBVMR_AudioCallbackUnregister;
T_VBVMR_MacroButton_IsDirty VBVMR_MacroButton_IsDirty;
T_VBVMR_MacroButton_GetStatus VBVMR_MacroButton_GetStatus;
T_VBVMR_MacroButton_SetStatus VBVMR_MacroButton_SetStatus;
} T_VBVMR_INTERFACE, *PT_VBVMR_INTERFACE, *LPT_VBVMR_INTERFACE;
#ifdef VBUSE_LOCALLIB
@@ -766,6 +853,31 @@ typedef struct tagVBVMR_INTERFACE
void __stdcall VBVMR_SetHinstance(HINSTANCE hinst);
#endif
/******************************************************************************/
/* LOCAL FUNCTIONS */
/******************************************************************************/
long VBVMR_LocalInit(void);
long VBVMR_LocalEnd(void);
void * VBVMR_GetRequestVB0STREAMPTR(void);
long VBVMR_SetParametersWEx(unsigned short * szParamScript, long fCopyToClient);
long VBVMR_LoginEx(long properties);
long VBVMR_MB_PushSettings(void * lpParam);
#ifdef __cplusplus
}
#endif
+1 -1
View File
@@ -25,7 +25,7 @@
class PrecisionTimer
{
LARGE_INTEGER freq;
LARGE_INTEGER startCount;
LARGE_INTEGER startCount{};
public:
PrecisionTimer()