Added: Voicemeeter audio processing support. When Voicemeeter is installed, it can be selected in the Configurator.
This commit is contained in:
1 parent
46d921945f
commit
989812ba36
55 files changed
+3044
-278
No files matched your search
@@ -0,0 +1,266 @@
|
||||
#include "stdafx.h"
|
||||
#include <string>
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include "helpers/RegistryHelper.h"
|
||||
#include "VoicemeeterClient.h"
|
||||
#include "VoicemeeterAPOInfo.h"
|
||||
|
||||
#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"
|
||||
|
||||
using namespace std;
|
||||
|
||||
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)
|
||||
{
|
||||
vector<wstring> outputs;
|
||||
if (lpCmdLine[0] != 0)
|
||||
{
|
||||
int argc;
|
||||
wchar_t** argv = CommandLineToArgvW(lpCmdLine, &argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
outputs.push_back(argv[i]);
|
||||
LocalFree(argv);
|
||||
}
|
||||
|
||||
VoicemeeterClient* client = new VoicemeeterClient(outputs);
|
||||
|
||||
MSG msg;
|
||||
msg.wParam = 0;
|
||||
|
||||
bool loop = client->restart;
|
||||
while (loop)
|
||||
{
|
||||
client->start();
|
||||
client->restart = false;
|
||||
|
||||
while (GetMessage(&msg, NULL, 0, 0))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
loop = client->restart;
|
||||
|
||||
client->stop();
|
||||
}
|
||||
delete client;
|
||||
|
||||
return (int)msg.wParam;
|
||||
}
|
||||
|
||||
VoicemeeterClient::VoicemeeterClient(const vector<wstring>& outputs)
|
||||
: outputs(outputs)
|
||||
{
|
||||
mainThreadId = GetCurrentThreadId();
|
||||
|
||||
wstring voicemeeterDirectory;
|
||||
if (RegistryHelper::keyExists(voicemeeterKeyPath))
|
||||
voicemeeterDirectory = RegistryHelper::readValue(voicemeeterKeyPath, uninstallStringValueName);
|
||||
else if (RegistryHelper::keyExists(voicemeeterWowKeyPath))
|
||||
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);
|
||||
banana = (setupFilename == L"VoicemeeterProSetup.exe");
|
||||
|
||||
voicemeeterDirectory = voicemeeterDirectory.substr(0, index);
|
||||
|
||||
#ifdef _WIN64
|
||||
voicemeeterModule = LoadLibraryW((voicemeeterDirectory + L"\\VoicemeeterRemote64.dll").c_str());
|
||||
#else
|
||||
voicemeeterModule = LoadLibraryW((voicemeeterDirectory + L"\\VoicemeeterRemote.dll").c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
VoicemeeterClient::~VoicemeeterClient()
|
||||
{
|
||||
if (loggedIn)
|
||||
{
|
||||
vmr.VBVMR_Logout();
|
||||
loggedIn = false;
|
||||
}
|
||||
}
|
||||
|
||||
void VoicemeeterClient::start()
|
||||
{
|
||||
if (!loggedIn)
|
||||
loggedIn = (vmr.VBVMR_Login() == 0);
|
||||
|
||||
if (loggedIn)
|
||||
{
|
||||
long type;
|
||||
if (vmr.VBVMR_GetVoicemeeterType(&type) == 0)
|
||||
banana = (type == 2);
|
||||
}
|
||||
|
||||
unsigned outputCount;
|
||||
if (banana)
|
||||
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())
|
||||
{
|
||||
FilterEngine* engine = new FilterEngine();
|
||||
engine->setDeviceInfo(false, true, L"Voicemeeter", output, L"", L"Voicemeeter " + output);
|
||||
engines.push_back(engine);
|
||||
}
|
||||
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)
|
||||
{
|
||||
switch (nCommand)
|
||||
{
|
||||
case VBVMR_CBCOMMAND_STARTING:
|
||||
{
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
case VBVMR_CBCOMMAND_BUFFER_OUT:
|
||||
{
|
||||
VBVMR_LPT_AUDIOBUFFER audioBuffer = (VBVMR_LPT_AUDIOBUFFER)lpData;
|
||||
int nbi = audioBuffer->audiobuffer_nbi;
|
||||
int nbo = audioBuffer->audiobuffer_nbo;
|
||||
int n = min(nbi, nbo) / 8;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
FilterEngine* engine = NULL;
|
||||
if (i < engines.size())
|
||||
engine = engines[i];
|
||||
|
||||
bool idle = true;
|
||||
bool inputSilent = true;
|
||||
if (engine != NULL)
|
||||
{
|
||||
inputSilent = isBufferSilent(audioBuffer->audiobuffer_r + 8 * i, audioBuffer->audiobuffer_nbs);
|
||||
idle = inputSilent && idleSampleCounts[i] > 10 * engine->getSampleRate();
|
||||
}
|
||||
|
||||
// avoid processing when idle (Voicemeeter does still call this when no audio is played)
|
||||
if (!idle)
|
||||
{
|
||||
engine->process(audioBuffer->audiobuffer_w + 8 * i, audioBuffer->audiobuffer_r + 8 * i, audioBuffer->audiobuffer_nbs);
|
||||
|
||||
bool outputSilent = isBufferSilent(audioBuffer->audiobuffer_w + 8 * i, audioBuffer->audiobuffer_nbs);
|
||||
if (inputSilent && outputSilent)
|
||||
idleSampleCounts[i] += audioBuffer->audiobuffer_nbs;
|
||||
else
|
||||
idleSampleCounts[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < 8; j++)
|
||||
memcpy(audioBuffer->audiobuffer_w[8 * i + j], audioBuffer->audiobuffer_r[8 * i + j], audioBuffer->audiobuffer_nbs * sizeof(float));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool VoicemeeterClient::isBufferSilent(float** sampleData, long sampleCount)
|
||||
{
|
||||
bool silent = true;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
float* buf = sampleData[j];
|
||||
for (int k = 0; k < sampleCount; k++)
|
||||
{
|
||||
if (buf[k] != 0.0f)
|
||||
{
|
||||
silent = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return silent;
|
||||
}
|
||||
|
||||
long __stdcall callback(void* lpUser, long nCommand, void* lpData, long nnn)
|
||||
{
|
||||
VoicemeeterClient* client = (VoicemeeterClient*)lpUser;
|
||||
client->handle(nCommand, lpData, nnn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user