diff --git a/AbstractAPOInfo.cpp b/AbstractAPOInfo.cpp new file mode 100644 index 0000000..2871c00 --- /dev/null +++ b/AbstractAPOInfo.cpp @@ -0,0 +1,25 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2017 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "stdafx.h" +#include "AbstractAPOInfo.h" + +AbstractAPOInfo::~AbstractAPOInfo() +{ +} diff --git a/AbstractAPOInfo.h b/AbstractAPOInfo.h new file mode 100644 index 0000000..9f32b77 --- /dev/null +++ b/AbstractAPOInfo.h @@ -0,0 +1,45 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2017 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include + +class AbstractAPOInfo +{ +public: + virtual ~AbstractAPOInfo(); + virtual std::wstring getConnectionName() const = 0; + virtual std::wstring getDeviceName() const = 0; + virtual std::wstring getDeviceGuid() const = 0; + virtual std::wstring getDeviceString() const = 0; + virtual unsigned getChannelCount() const = 0; + virtual unsigned getSampleRate() const = 0; + virtual unsigned long getChannelMask() const = 0; + virtual bool isInput() const = 0; + virtual bool isInstalled() const = 0; + virtual bool canBeUpgraded() const = 0; + virtual bool hasChanges() const = 0; + virtual bool isExperimental() const = 0; + virtual bool isEnhancementsDisabled() const = 0; + virtual bool isDefaultDevice() const = 0; + virtual void install() = 0; + virtual void uninstall() = 0; + virtual void reinstall() = 0; +}; diff --git a/Benchmark/Benchmark.cpp b/Benchmark/Benchmark.cpp index 52f3f0d..4601c7f 100644 --- a/Benchmark/Benchmark.cpp +++ b/Benchmark/Benchmark.cpp @@ -164,9 +164,10 @@ int main(int argc, char** argv) timer.start(); FilterEngine engine; - engine.setDeviceInfo(false, true, StringHelper::toWString(devicenameArg.getValue(), CP_ACP), - StringHelper::toWString(connectionnameArg.getValue(), CP_ACP), - StringHelper::toWString(guidArg.getValue(), CP_ACP)); + wstring deviceName = StringHelper::toWString(devicenameArg.getValue(), CP_ACP); + wstring connectionName = StringHelper::toWString(connectionnameArg.getValue(), CP_ACP); + wstring deviceGuid = StringHelper::toWString(guidArg.getValue(), CP_ACP); + engine.setDeviceInfo(false, true, deviceName, connectionName, deviceGuid, deviceName + L" " + connectionName + L" " + deviceGuid); engine.initialize((float)sampleRate, channelCount, channelCount, channelCount, channelMask, batchsize); double initTime = timer.stop(); diff --git a/Benchmark/Benchmark.vcxproj b/Benchmark/Benchmark.vcxproj index fa00a24..d02bc6f 100644 --- a/Benchmark/Benchmark.vcxproj +++ b/Benchmark/Benchmark.vcxproj @@ -224,7 +224,9 @@ + + diff --git a/Benchmark/Benchmark.vcxproj.filters b/Benchmark/Benchmark.vcxproj.filters index 4b9547b..6765e0c 100644 --- a/Benchmark/Benchmark.vcxproj.filters +++ b/Benchmark/Benchmark.vcxproj.filters @@ -208,6 +208,12 @@ filters + + helpers + + + helpers + diff --git a/Configurator/Configurator.cpp b/Configurator/Configurator.cpp index 62cba7d..8c7fd81 100644 --- a/Configurator/Configurator.cpp +++ b/Configurator/Configurator.cpp @@ -25,6 +25,7 @@ #include #include "helpers/StringHelper.h" #include "helpers/RegistryHelper.h" +#include "VoicemeeterAPOInfo.h" #include "Configurator.h" using namespace std; @@ -176,26 +177,37 @@ void Configurator::onInitDialog(HWND hDlg) wstring defaultDevice = StringHelper::toLowerCase(DeviceAPOInfo::getDefaultDevice(i == 1)); int itemCount = 0; - for (vector::iterator it = apoInfos[i].begin(); it != apoInfos[i].end(); it++) + for (shared_ptr& apoInfo : apoInfos[i]) { - wcsncpy_s(stringBuf, sizeof(stringBuf) / sizeof(wchar_t), it->connectionName.c_str(), _TRUNCATE); + wcsncpy_s(stringBuf, sizeof(stringBuf) / sizeof(wchar_t), apoInfo->getConnectionName().c_str(), _TRUNCATE); item.iItem = itemCount; item.lParam = itemCount; ListView_InsertItem(deviceList, &item); - wcsncpy_s(stringBuf, sizeof(stringBuf) / sizeof(wchar_t), it->deviceName.c_str(), _TRUNCATE); + wcsncpy_s(stringBuf, sizeof(stringBuf) / sizeof(wchar_t), apoInfo->getDeviceName().c_str(), _TRUNCATE); ListView_SetItemText(deviceList, itemCount, 1, stringBuf); - if (it->isInstalled) + VoicemeeterAPOInfo* voicemeeterInfo = dynamic_cast(apoInfo.get()); + if (apoInfo->isInstalled()) { - ListView_SetCheckState(deviceList, itemCount, TRUE); - if (it->canBeUpgraded()) - LoadStringW(hInstance, IDS_WILL_BE_UPGRADED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); - else if (it->isEnhancementsDisabled) - LoadStringW(hInstance, IDS_ENHANCEMENTS_WILL_BE_ENABLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + if (voicemeeterInfo != NULL && !voicemeeterInfo->isVoicemeeterInstalled()) + { + ListView_SetCheckState(deviceList, itemCount, FALSE); + LoadStringW(hInstance, IDS_WILL_BE_UNINSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + } else - LoadStringW(hInstance, IDS_ALREADY_INSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + { + ListView_SetCheckState(deviceList, itemCount, TRUE); + if (apoInfo->canBeUpgraded()) + LoadStringW(hInstance, IDS_WILL_BE_UPGRADED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + else if (apoInfo->hasChanges()) + LoadStringW(hInstance, IDS_WILL_BE_CHANGED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + else if (apoInfo->isEnhancementsDisabled()) + LoadStringW(hInstance, IDS_ENHANCEMENTS_WILL_BE_ENABLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + else + LoadStringW(hInstance, IDS_ALREADY_INSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + } } - else if (it->isExperimental()) + else if (apoInfo->isExperimental()) { LoadStringW(hInstance, IDS_CAN_BE_INSTALLED_EXPERIMENTAL, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); } @@ -204,7 +216,15 @@ void Configurator::onInitDialog(HWND hDlg) LoadStringW(hInstance, IDS_CAN_BE_INSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); } - if (it->isDefaultDevice) + if (voicemeeterInfo != NULL && !voicemeeterInfo->isVoicemeeterInstalled()) + { + wstring statusText = stringBuf; + LoadStringW(hInstance, IDS_VOICEMEETER_WAS_UNINSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + statusText = wstring(stringBuf) + L", " + statusText; + + ListView_SetItemText(deviceList, itemCount, 2, const_cast(statusText.c_str())); + } + else if (apoInfo->isDefaultDevice()) { wstring statusText = stringBuf; LoadStringW(hInstance, IDS_DEFAULT_DEVICE, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); @@ -250,6 +270,8 @@ void Configurator::onLvnItemChanged(unsigned sourceId, LPNMLISTVIEW info) bool Configurator::onButtonClicked(unsigned sourceId) { + bool deviceUpdated = false; + switch (sourceId) { case IDOK: @@ -268,16 +290,25 @@ bool Configurator::onButtonClicked(unsigned sourceId) try { - DeviceAPOInfo info = apoInfos[index][item.lParam]; - if (ListView_GetCheckState(deviceList, i) && !info.isInstalled) - info.install(); - else if (!ListView_GetCheckState(deviceList, i) && info.isInstalled) - info.uninstall(); - else if (ListView_GetCheckState(deviceList, i) && (info.canBeUpgraded() || info.hasChanges() || info.isEnhancementsDisabled)) + shared_ptr& info = apoInfos[index][item.lParam]; + DeviceAPOInfo* deviceInfo = dynamic_cast(info.get()); + if (ListView_GetCheckState(deviceList, i) && !info->isInstalled()) { - info.uninstall(); - info.load(info.deviceGuid); - info.install(); + info->install(); + if (deviceInfo != NULL) + deviceUpdated = true; + } + else if (!ListView_GetCheckState(deviceList, i) && info->isInstalled()) + { + info->uninstall(); + if (deviceInfo != NULL) + deviceUpdated = true; + } + else if (ListView_GetCheckState(deviceList, i) && (info->canBeUpgraded() || info->hasChanges() || info->isEnhancementsDisabled())) + { + info->reinstall(); + if (deviceInfo != NULL) + deviceUpdated = true; } } catch (RegistryException e) @@ -286,6 +317,8 @@ bool Configurator::onButtonClicked(unsigned sourceId) } } } + + VoicemeeterAPOInfo::ensureVoicemeeterClientRunning(); } break; case IDC_COPY_DEVICE_COMMAND: @@ -312,8 +345,8 @@ bool Configurator::onButtonClicked(unsigned sourceId) else command += L"; "; - DeviceAPOInfo info = apoInfos[index][item.lParam]; - command += StringHelper::replaceCharacters(info.deviceName + L" " + info.connectionName + L" " + info.deviceGuid, L";", L" "); + shared_ptr& info = apoInfos[index][item.lParam]; + command += StringHelper::replaceCharacters(info->getDeviceString(), L";", L" "); } } @@ -357,31 +390,34 @@ bool Configurator::onButtonClicked(unsigned sourceId) if (item.state & LVIS_SELECTED) { - DeviceAPOInfo& info = apoInfos[index][item.lParam]; - - switch (sourceId) + shared_ptr& info = apoInfos[index][item.lParam]; + DeviceAPOInfo* deviceInfo = dynamic_cast(info.get()); + if (deviceInfo != NULL) { - case IDC_INSTALL_PRE_MIX: - info.selectedInstallState.installPreMix = Button_GetCheck(installPreMix) == BST_CHECKED; - break; - case IDC_INSTALL_POST_MIX: - info.selectedInstallState.installPostMix = Button_GetCheck(installPostMix) == BST_CHECKED; - break; - case IDC_USE_ORIGINAL_APO_PRE_MIX: - info.selectedInstallState.useOriginalAPOPreMix = Button_GetCheck(useOriginalAPOPreMix) == BST_CHECKED; - break; - case IDC_USE_ORIGINAL_APO_POST_MIX: - info.selectedInstallState.useOriginalAPOPostMix = Button_GetCheck(useOriginalAPOPostMix) == BST_CHECKED; - break; - case IDC_INSTALL_MODE_COMBOBOX: - info.selectedInstallState.installMode = (DeviceAPOInfo::InstallMode)ComboBox_GetCurSel(installModeComboBox); - break; - case IDC_ALLOW_SILENT_BUFFER: - info.selectedInstallState.allowSilentBufferModification = Button_GetCheck(allowSilentBuffer) == BST_CHECKED; - break; - } + switch (sourceId) + { + case IDC_INSTALL_PRE_MIX: + deviceInfo->getSelectedInstallState().installPreMix = Button_GetCheck(installPreMix) == BST_CHECKED; + break; + case IDC_INSTALL_POST_MIX: + deviceInfo->getSelectedInstallState().installPostMix = Button_GetCheck(installPostMix) == BST_CHECKED; + break; + case IDC_USE_ORIGINAL_APO_PRE_MIX: + deviceInfo->getSelectedInstallState().useOriginalAPOPreMix = Button_GetCheck(useOriginalAPOPreMix) == BST_CHECKED; + break; + case IDC_USE_ORIGINAL_APO_POST_MIX: + deviceInfo->getSelectedInstallState().useOriginalAPOPostMix = Button_GetCheck(useOriginalAPOPostMix) == BST_CHECKED; + break; + case IDC_INSTALL_MODE_COMBOBOX: + deviceInfo->getSelectedInstallState().installMode = (DeviceAPOInfo::InstallMode)ComboBox_GetCurSel(installModeComboBox); + break; + case IDC_ALLOW_SILENT_BUFFER: + deviceInfo->getSelectedInstallState().allowSilentBufferModification = Button_GetCheck(allowSilentBuffer) == BST_CHECKED; + break; + } - updateList(index, i); + updateList(index, i); + } } } @@ -408,7 +444,7 @@ bool Configurator::onButtonClicked(unsigned sourceId) LoadStringW(hInstance, IDS_AFTERINSTALL, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); MessageBoxW(hDlg, stringBuf, L"Info", MB_ICONINFORMATION | MB_OK); } - else if (sourceId == IDOK && RegistryHelper::isWindowsVersionAtLeast(6, 3) // Windows 8.1 + else if (sourceId == IDOK && deviceUpdated && RegistryHelper::isWindowsVersionAtLeast(6, 3) // Windows 8.1 || askForReboot) { wchar_t captionBuf[255]; @@ -470,7 +506,6 @@ bool Configurator::isAnySelected() item.iSubItem = 0; item.mask = LVIF_PARAM; ListView_GetItem(deviceList, &item); - DeviceAPOInfo apoInfo = apoInfos[index][item.lParam]; if (ListView_GetCheckState(deviceList, i) != 0) { anySelected = true; @@ -497,9 +532,9 @@ bool Configurator::isChanged() item.iSubItem = 0; item.mask = LVIF_PARAM; ListView_GetItem(deviceList, &item); - DeviceAPOInfo apoInfo = apoInfos[index][item.lParam]; - if ((ListView_GetCheckState(deviceList, i) != 0) != apoInfo.isInstalled - || ListView_GetCheckState(deviceList, i) && apoInfo.isInstalled && (apoInfo.canBeUpgraded() || apoInfo.hasChanges() || apoInfo.isEnhancementsDisabled)) + shared_ptr& apoInfo = apoInfos[index][item.lParam]; + if ((ListView_GetCheckState(deviceList, i) != 0) != apoInfo->isInstalled() + || ListView_GetCheckState(deviceList, i) && apoInfo->isInstalled() && (apoInfo->canBeUpgraded() || apoInfo->hasChanges() || apoInfo->isEnhancementsDisabled())) { changed = true; break; @@ -525,8 +560,8 @@ bool Configurator::hasUpgrades() item.iSubItem = 0; item.mask = LVIF_PARAM; ListView_GetItem(deviceList, &item); - DeviceAPOInfo apoInfo = apoInfos[index][item.lParam]; - if (ListView_GetCheckState(deviceList, i) && apoInfo.isInstalled && (apoInfo.canBeUpgraded() || apoInfo.isEnhancementsDisabled)) + shared_ptr& apoInfo = apoInfos[index][item.lParam]; + if (ListView_GetCheckState(deviceList, i) && apoInfo->isInstalled() && (apoInfo->canBeUpgraded() || apoInfo->isEnhancementsDisabled())) { hasUpgrades = true; break; @@ -586,27 +621,36 @@ void Configurator::updateList(int listIndex, int itemIndex) if (apoInfos[listIndex].size() == itemCount) { - DeviceAPOInfo apoInfo = apoInfos[listIndex][itemIndex]; + shared_ptr& apoInfo = apoInfos[listIndex][itemIndex]; bool checked = ListView_GetCheckState(deviceList, itemIndex) != 0; wchar_t stringBuf[255]; - if (checked && !apoInfo.isInstalled) + if (checked && !apoInfo->isInstalled()) LoadStringW(hInstance, IDS_WILL_BE_INSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); - else if (!checked && apoInfo.isInstalled) + else if (!checked && apoInfo->isInstalled()) LoadStringW(hInstance, IDS_WILL_BE_UNINSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); - else if (apoInfo.isInstalled && apoInfo.canBeUpgraded()) + else if (apoInfo->isInstalled() && apoInfo->canBeUpgraded()) LoadStringW(hInstance, IDS_WILL_BE_UPGRADED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); - else if (apoInfo.isInstalled && apoInfo.hasChanges()) + else if (apoInfo->isInstalled() && apoInfo->hasChanges()) LoadStringW(hInstance, IDS_WILL_BE_CHANGED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); - else if (apoInfo.isInstalled && apoInfo.isEnhancementsDisabled) + else if (apoInfo->isInstalled() && apoInfo->isEnhancementsDisabled()) LoadStringW(hInstance, IDS_ENHANCEMENTS_WILL_BE_ENABLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); - else if (apoInfo.isInstalled) + else if (apoInfo->isInstalled()) LoadStringW(hInstance, IDS_ALREADY_INSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); - else if (apoInfo.isExperimental()) + else if (apoInfo->isExperimental()) LoadStringW(hInstance, IDS_CAN_BE_INSTALLED_EXPERIMENTAL, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); else LoadStringW(hInstance, IDS_CAN_BE_INSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); - if (apoInfo.isDefaultDevice) + VoicemeeterAPOInfo* voicemeeterInfo = dynamic_cast(apoInfo.get()); + if (voicemeeterInfo != NULL && !voicemeeterInfo->isVoicemeeterInstalled()) + { + wstring statusText = stringBuf; + LoadStringW(hInstance, IDS_VOICEMEETER_WAS_UNINSTALLED, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); + statusText = wstring(stringBuf) + L", " + statusText; + + ListView_SetItemText(deviceList, itemCount, 2, const_cast(statusText.c_str())); + } + else if (apoInfo->isDefaultDevice()) { wstring statusText = stringBuf; LoadStringW(hInstance, IDS_DEFAULT_DEVICE, stringBuf, sizeof(stringBuf) / sizeof(wchar_t)); @@ -658,11 +702,15 @@ void Configurator::updateButtons(int listIndex) { enable = ListView_GetCheckState(deviceList, selectedIndex) != 0; - DeviceAPOInfo apoInfo = apoInfos[listIndex][selectedIndex]; - isInput = apoInfo.isInput; - hasOriginalAPOPreMix = apoInfo.getOriginalAPOPreMix() != L""; - hasOriginalAPOPostMix = apoInfo.getOriginalAPOPostMix() != L""; - installState = apoInfo.selectedInstallState; + shared_ptr& apoInfo = apoInfos[listIndex][selectedIndex]; + DeviceAPOInfo* deviceApoInfo = dynamic_cast(apoInfo.get()); + if (deviceApoInfo != NULL) + { + isInput = deviceApoInfo->isInput(); + hasOriginalAPOPreMix = deviceApoInfo->getOriginalAPOPreMix() != L""; + hasOriginalAPOPostMix = deviceApoInfo->getOriginalAPOPostMix() != L""; + installState = deviceApoInfo->getSelectedInstallState(); + } } } @@ -709,14 +757,14 @@ int APIENTRY wWinMain(HINSTANCE hInstance, { for (int index = 0; index <= 1; index++) { - vector apoInfos = DeviceAPOInfo::loadAllInfos(index == 1); + vector > apoInfos = DeviceAPOInfo::loadAllInfos(index == 1); - for (vector::iterator it = apoInfos.begin(); it != apoInfos.end(); it++) + for (shared_ptr& apoInfo : apoInfos) { try { - if (it->isInstalled) - it->uninstall(); + if (apoInfo->isInstalled()) + apoInfo->uninstall(); } catch (RegistryException e) { @@ -725,6 +773,8 @@ int APIENTRY wWinMain(HINSTANCE hInstance, } } } + + VoicemeeterAPOInfo::ensureVoicemeeterClientRunning(); } else { diff --git a/Configurator/Configurator.h b/Configurator/Configurator.h index 88f256e..3e87a0f 100644 --- a/Configurator/Configurator.h +++ b/Configurator/Configurator.h @@ -22,6 +22,7 @@ #include "resource.h" #include #include +#include #include "DeviceAPOInfo.h" class Configurator @@ -63,6 +64,6 @@ private: void updateList(int listIndex, int itemIndex); void updateButtons(int listIndex); - std::vector apoInfos[2]; + std::vector > apoInfos[2]; bool askForReboot = false; }; diff --git a/Configurator/Configurator.rc b/Configurator/Configurator.rc index cc2982e..29a552f 100644 Binary files a/Configurator/Configurator.rc and b/Configurator/Configurator.rc differ diff --git a/Configurator/Configurator.vcxproj b/Configurator/Configurator.vcxproj index c848422..9196a3d 100644 --- a/Configurator/Configurator.vcxproj +++ b/Configurator/Configurator.vcxproj @@ -19,17 +19,22 @@ + + + + + @@ -41,6 +46,7 @@ Create Create + diff --git a/Configurator/Configurator.vcxproj.filters b/Configurator/Configurator.vcxproj.filters index 2d7c09b..75d00b7 100644 --- a/Configurator/Configurator.vcxproj.filters +++ b/Configurator/Configurator.vcxproj.filters @@ -12,10 +12,12 @@ helpers + helpers - + + @@ -33,10 +35,18 @@ helpers + helpers - + + + + helpers + + + helpers + diff --git a/Configurator/resource.h b/Configurator/resource.h index 84f8dc4..1f6f1e8 100644 Binary files a/Configurator/resource.h and b/Configurator/resource.h differ diff --git a/DeviceAPOInfo.cpp b/DeviceAPOInfo.cpp index 888290f..1771d72 100644 --- a/DeviceAPOInfo.cpp +++ b/DeviceAPOInfo.cpp @@ -23,6 +23,7 @@ #include #include "DeviceAPOInfo.h" +#include "VoicemeeterAPOInfo.h" #include "helpers/StringHelper.h" #include "helpers/RegistryHelper.h" @@ -69,23 +70,27 @@ static const wchar_t* disableEnhancementsValueName = L"{1da5d803-d492-4edd-8c23- static const wchar_t* installVersion = L"2"; static PROPERTYKEY guidPropertyKey = {{0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e}, 4}; -vector DeviceAPOInfo::loadAllInfos(bool input) +vector > DeviceAPOInfo::loadAllInfos(bool input) { - vector result; + vector > result; + vector deviceGuidStrings = RegistryHelper::enumSubKeys(input ? captureKeyPath : renderKeyPath); wstring defaultDeviceGuid = getDefaultDevice(input); for (vector::iterator it = deviceGuidStrings.begin(); it != deviceGuidStrings.end(); it++) { wstring deviceGuidString = *it; - DeviceAPOInfo info; - if (info.load(deviceGuidString, defaultDeviceGuid)) + shared_ptr info = make_shared(); + if (info->load(deviceGuidString, defaultDeviceGuid)) { - info.selectedInstallState = info.currentInstallState; - result.push_back(info); + info->selectedInstallState = info->currentInstallState; + result.push_back(move(info)); } } + if (!input) + VoicemeeterAPOInfo::prependInfos(result); + return result; } @@ -170,12 +175,12 @@ bool DeviceAPOInfo::load(const wstring& deviceGuid, wstring defaultDeviceGuid) if (RegistryHelper::keyExists(renderKeyPath L"\\" + deviceGuid)) { keyPath = renderKeyPath L"\\" + deviceGuid; - isInput = false; + input = false; } else { keyPath = captureKeyPath L"\\" + deviceGuid; - isInput = true; + input = true; } unsigned long deviceState = RegistryHelper::readDWORDValue(keyPath, L"DeviceState"); @@ -209,27 +214,27 @@ bool DeviceAPOInfo::load(const wstring& deviceGuid, wstring defaultDeviceGuid) channelMask = RegistryHelper::readDWORDValue(keyPath + L"\\Properties", channelMaskValueName); if (defaultDeviceGuid == L"") - defaultDeviceGuid = getDefaultDevice(isInput); + defaultDeviceGuid = getDefaultDevice(input); GUID guid1, guid2; if (SUCCEEDED(CLSIDFromString(deviceGuid.c_str(), &guid1)) && SUCCEEDED(CLSIDFromString(defaultDeviceGuid.c_str(), &guid2))) - isDefaultDevice = (guid1 == guid2) != 0; + defaultDevice = (guid1 == guid2) != 0; else - isDefaultDevice = false; + defaultDevice = false; - isEnhancementsDisabled = false; + enhancementsDisabled = false; if (RegistryHelper::keyExists(keyPath + L"\\FxProperties") && RegistryHelper::valueExists(keyPath + L"\\FxProperties", disableEnhancementsValueName)) - isEnhancementsDisabled = RegistryHelper::readDWORDValue(keyPath + L"\\FxProperties", disableEnhancementsValueName) != 0; + enhancementsDisabled = RegistryHelper::readDWORDValue(keyPath + L"\\FxProperties", disableEnhancementsValueName) != 0; - isInstalled = false; + installed = false; currentInstallState.installMode = INSTALL_LFX_GFX; version = L"0"; preMixChildGuid = L""; postMixChildGuid = L""; currentInstallState.installPreMix = true; - currentInstallState.installPostMix = !isInput; + currentInstallState.installPostMix = !input; currentInstallState.useOriginalAPOPreMix = true; - currentInstallState.useOriginalAPOPostMix = !isInput; + currentInstallState.useOriginalAPOPostMix = !input; currentInstallState.allowSilentBufferModification = false; if (!RegistryHelper::keyExists(keyPath + L"\\FxProperties")) @@ -271,7 +276,7 @@ bool DeviceAPOInfo::load(const wstring& deviceGuid, wstring defaultDeviceGuid) if (found) { - isInstalled = true; + installed = true; if (RegistryHelper::keyExists(childApoPath L"\\" + deviceGuid)) { @@ -352,19 +357,19 @@ bool DeviceAPOInfo::load(const wstring& deviceGuid, wstring defaultDeviceGuid) return true; } -bool DeviceAPOInfo::canBeUpgraded() +bool DeviceAPOInfo::canBeUpgraded() const { - return isInstalled && version != installVersion; + return installed && version != installVersion; } -bool DeviceAPOInfo::hasChanges() +bool DeviceAPOInfo::hasChanges() const { - return isInstalled && selectedInstallState != currentInstallState; + return installed && selectedInstallState != currentInstallState; } -bool DeviceAPOInfo::isExperimental() +bool DeviceAPOInfo::isExperimental() const { - return !isInstalled && originalApoGuids[0] == APOGUID_NOKEY; + return !installed && originalApoGuids[0] == APOGUID_NOKEY; } wstring DeviceAPOInfo::getOriginalAPOPreMix() @@ -438,7 +443,7 @@ void DeviceAPOInfo::install() RegistryHelper::createKey(childApoPath L"\\" + deviceGuid); wstring keyPath; - if (!isInput) + if (!input) keyPath = renderKeyPath L"\\" + deviceGuid; else keyPath = captureKeyPath L"\\" + deviceGuid; @@ -502,7 +507,7 @@ void DeviceAPOInfo::install() { if (selectedInstallState.installPreMix) RegistryHelper::writeValue(keyPath + L"\\FxProperties", lfxGuidValueName, RegistryHelper::getGuidString(EQUALIZERAPO_PRE_MIX_GUID)); - if (selectedInstallState.installPostMix && !isInput) + if (selectedInstallState.installPostMix && !input) RegistryHelper::writeValue(keyPath + L"\\FxProperties", gfxGuidValueName, RegistryHelper::getGuidString(EQUALIZERAPO_POST_MIX_GUID)); if (RegistryHelper::valueExists(keyPath + L"\\FxProperties", sfxGuidValueName)) RegistryHelper::deleteValue(keyPath + L"\\FxProperties", sfxGuidValueName); @@ -523,7 +528,7 @@ void DeviceAPOInfo::install() if (!RegistryHelper::valueExists(keyPath + L"\\FxProperties", sfxProcessingModesValueName)) RegistryHelper::writeMultiValue(keyPath + L"\\FxProperties", sfxProcessingModesValueName, defaultProcessingModeValue); } - if (selectedInstallState.installPostMix && !isInput) + if (selectedInstallState.installPostMix && !input) { RegistryHelper::writeValue(keyPath + L"\\FxProperties", mfxGuidValueName, RegistryHelper::getGuidString(EQUALIZERAPO_POST_MIX_GUID)); if (!RegistryHelper::valueExists(keyPath + L"\\FxProperties", mfxProcessingModesValueName)) @@ -544,7 +549,7 @@ void DeviceAPOInfo::install() RegistryHelper::writeMultiValue(keyPath + L"\\FxProperties", sfxProcessingModesValueName, defaultProcessingModeValue); } // don't change mfx - if (selectedInstallState.installPostMix && !isInput) + if (selectedInstallState.installPostMix && !input) { RegistryHelper::writeValue(keyPath + L"\\FxProperties", efxGuidValueName, RegistryHelper::getGuidString(EQUALIZERAPO_POST_MIX_GUID)); if (!RegistryHelper::valueExists(keyPath + L"\\FxProperties", efxProcessingModesValueName)) @@ -560,7 +565,7 @@ void DeviceAPOInfo::install() void DeviceAPOInfo::uninstall() { wstring keyPath; - if (!isInput) + if (!input) keyPath = renderKeyPath L"\\" + deviceGuid; else keyPath = captureKeyPath L"\\" + deviceGuid; @@ -594,3 +599,85 @@ void DeviceAPOInfo::uninstall() if (RegistryHelper::keyEmpty(childApoPath)) RegistryHelper::deleteKey(childApoPath); } + +void DeviceAPOInfo::reinstall() +{ + uninstall(); + load(deviceGuid); + install(); +} + +wstring DeviceAPOInfo::getConnectionName() const +{ + return connectionName; +} + +wstring DeviceAPOInfo::getDeviceName() const +{ + return deviceName; +} + +wstring DeviceAPOInfo::getDeviceGuid() const +{ + return deviceGuid; +} + +wstring DeviceAPOInfo::getDeviceString() const +{ + return getConnectionName() + L" " + getDeviceName() + L" " + getDeviceGuid(); +} + +unsigned DeviceAPOInfo::getChannelCount() const +{ + return channelCount; +} + +unsigned DeviceAPOInfo::getSampleRate() const +{ + return sampleRate; +} + +unsigned long DeviceAPOInfo::getChannelMask() const +{ + return channelMask; +} + +bool DeviceAPOInfo::isInput() const +{ + return input; +} + +bool DeviceAPOInfo::isInstalled() const +{ + return installed; +} + +bool DeviceAPOInfo::isEnhancementsDisabled() const +{ + return enhancementsDisabled; +} + +bool DeviceAPOInfo::isDefaultDevice() const +{ + return defaultDevice; +} + +const DeviceAPOInfo::InstallState& DeviceAPOInfo::getCurrentInstallState() +{ + return currentInstallState; +} + +DeviceAPOInfo::InstallState& DeviceAPOInfo::getSelectedInstallState() +{ + return selectedInstallState; +} + +wstring DeviceAPOInfo::getPreMixChildGuid() +{ + return preMixChildGuid; +} + +wstring DeviceAPOInfo::getPostMixChildGuid() +{ + return postMixChildGuid; +} diff --git a/DeviceAPOInfo.h b/DeviceAPOInfo.h index eb6eeb6..3e386ef 100644 --- a/DeviceAPOInfo.h +++ b/DeviceAPOInfo.h @@ -21,12 +21,14 @@ #include #include +#include +#include "AbstractAPOInfo.h" #define APOGUID_NULL L"{00000000-0000-0000-0000-000000000000}" #define APOGUID_NOKEY L"!KEY" #define APOGUID_NOVALUE L"!VALUE" -class DeviceAPOInfo +class DeviceAPOInfo : public AbstractAPOInfo { public: enum InstallMode @@ -55,33 +57,50 @@ public: allowSilentBufferModification = false; } - bool operator!=(InstallState& other) + bool operator!=(const InstallState& other) const { return memcmp(this, &other, sizeof(InstallState)) != 0; } }; - static std::vector loadAllInfos(bool input); + static std::vector > loadAllInfos(bool input); static std::wstring getDefaultDevice(bool input, int role = 1); static bool checkProtectedAudioDG(bool fix); static bool checkAPORegistration(bool fix); bool load(const std::wstring& deviceGuid, std::wstring defaultDeviceGuid = L""); - bool canBeUpgraded(); - bool hasChanges(); - bool isExperimental(); + bool canBeUpgraded() const override; + bool hasChanges() const override; + bool isExperimental() const override; std::wstring getOriginalAPOPreMix(); std::wstring getOriginalAPOPostMix(); - void install(); - void uninstall(); + void install() override; + void uninstall() override; + void reinstall() override; + std::wstring getConnectionName() const override; + std::wstring getDeviceName() const override; + std::wstring getDeviceGuid() const override; + std::wstring getDeviceString() const override; + unsigned getChannelCount() const override; + unsigned getSampleRate() const override; + unsigned long getChannelMask() const override; + bool isInput() const override; + bool isInstalled() const override; + bool isEnhancementsDisabled() const override; + bool isDefaultDevice() const override; + const InstallState& getCurrentInstallState(); + InstallState& getSelectedInstallState(); + std::wstring getPreMixChildGuid(); + std::wstring getPostMixChildGuid(); +private: std::wstring deviceName; std::wstring connectionName; std::wstring deviceGuid; unsigned channelCount; unsigned sampleRate; unsigned long channelMask; - bool isDefaultDevice; - bool isEnhancementsDisabled; + bool defaultDevice; + bool enhancementsDisabled; // used for creating child APO std::wstring preMixChildGuid; @@ -90,8 +109,8 @@ public: // used for uninstallation std::wstring originalApoGuids[5]; - bool isInput; - bool isInstalled; + bool input; + bool installed; std::wstring version; InstallState currentInstallState; // selection in GUI diff --git a/Editor/AnalysisThread.cpp b/Editor/AnalysisThread.cpp index 1000f71..ecde39d 100644 --- a/Editor/AnalysisThread.cpp +++ b/Editor/AnalysisThread.cpp @@ -22,6 +22,8 @@ #include "FilterEngine.h" #include "AnalysisThread.h" +using namespace std; + AnalysisThread::AnalysisThread() { } @@ -50,10 +52,10 @@ AnalysisThread::~AnalysisThread() fftwf_destroy_plan(planForward); } -void AnalysisThread::setParameters(DeviceAPOInfo* device, int channelMask, int channelIndex, QString configPath, int frameCount) +void AnalysisThread::setParameters(shared_ptr device, int channelMask, int channelIndex, QString configPath, int frameCount) { QMutexLocker mutexLocker(&mutex); - this->device = *device; + this->device = device; this->channelMask = channelMask; this->channelIndex = channelIndex; this->configPath = configPath; @@ -125,7 +127,7 @@ void AnalysisThread::run() break; } - DeviceAPOInfo device = this->device; + shared_ptr device = this->device; int channelMask = this->channelMask; int channelIndex = this->channelIndex; QString configPath = this->configPath; @@ -136,8 +138,8 @@ void AnalysisThread::run() QElapsedTimer timer; timer.start(); - unsigned channelCount = device.channelCount; - if (channelMask != 0 && channelMask != device.channelMask) + unsigned channelCount = device->getChannelCount(); + if (channelMask != 0 && channelMask != device->getChannelMask()) { channelCount = 0; for (int i = 0; i < 31; i++) @@ -148,11 +150,11 @@ void AnalysisThread::run() } } - unsigned sampleRate = device.sampleRate; + unsigned sampleRate = device->getSampleRate(); qint64 startTime = timer.nsecsElapsed(); FilterEngine engine; - engine.setDeviceInfo(device.isInput, true, device.deviceName, device.connectionName, device.deviceGuid); + engine.setDeviceInfo(device->isInput(), true, device->getDeviceName(), device->getConnectionName(), device->getDeviceGuid(), device->getDeviceString()); engine.initialize(sampleRate, channelCount, channelCount, channelCount, channelMask, frameCount, configPath.toStdWString()); double initializationTime = (timer.nsecsElapsed() - startTime) / 1e6; diff --git a/Editor/AnalysisThread.h b/Editor/AnalysisThread.h index 907ffd8..6acd88a 100644 --- a/Editor/AnalysisThread.h +++ b/Editor/AnalysisThread.h @@ -32,7 +32,7 @@ class AnalysisThread : public QThread public: AnalysisThread(); ~AnalysisThread(); - void setParameters(DeviceAPOInfo* device, int channelMask, int channelIndex, QString configPath, int frameCount); + void setParameters(std::shared_ptr device, int channelMask, int channelIndex, QString configPath, int frameCount); void beginGetResult(); void endGetResult(); @@ -57,7 +57,7 @@ private: bool quit = false; // input - DeviceAPOInfo device; + std::shared_ptr device; int channelMask; int channelIndex; QString configPath; diff --git a/Editor/Editor.pro b/Editor/Editor.pro index 1e12787..7323025 100644 --- a/Editor/Editor.pro +++ b/Editor/Editor.pro @@ -114,7 +114,9 @@ SOURCES += main.cpp\ widgets/ExponentialSpinBox.cpp \ FilterTableMimeData.cpp \ helpers/DPIHelper.cpp \ - CustomStyle.cpp + CustomStyle.cpp \ + ../AbstractAPOInfo.cpp \ + ../VoicemeeterAPOInfo.cpp HEADERS += \ ../helpers/LogHelper.h \ @@ -217,7 +219,9 @@ HEADERS += \ widgets/ExponentialSpinBox.h \ FilterTableMimeData.h \ helpers/DPIHelper.h \ - CustomStyle.h + CustomStyle.h \ + ../AbstractAPOInfo.h \ + ../VoicemeeterAPOInfo.h FORMS += \ guis/PreampFilterGUI.ui \ @@ -238,7 +242,7 @@ FORMS += \ guis/StageFilterGUI.ui INCLUDEPATH += $$PWD/.. "C:/Program Files/libsndfile/include" "C:/Program Files/fftw3" "C:/Program Files/muparserx_v3_0_1/parser" -LIBS += advapi32.lib version.lib ole32.lib Shlwapi.lib authz.lib libsndfile-1.lib libfftw3f-3.lib +LIBS += user32.lib advapi32.lib version.lib ole32.lib Shlwapi.lib authz.lib libsndfile-1.lib libfftw3f-3.lib build_pass:CONFIG(debug, debug|release) { LIBS += muparserxd.lib @@ -256,6 +260,6 @@ RESOURCES += \ Editor.qrc TRANSLATIONS += translations/Editor_de.ts -RC_ICONS = icons/accessories-text-editor.ico +RC_FILE = Editor.rc DISTFILES += ../uncrustify.cfg diff --git a/Editor/Editor.rc b/Editor/Editor.rc new file mode 100644 index 0000000..fa43ef0 --- /dev/null +++ b/Editor/Editor.rc @@ -0,0 +1,44 @@ +# if defined(UNDER_CE) +# include +# else +# include +# endif +#include "../version.h" + +#define S2(x) #x +#define S(x) S2(x) + +IDI_ICON1 ICON DISCARDABLE "icons\\accessories-text-editor.ico" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION MAJOR,MINOR,REVISION,0 + PRODUCTVERSION MAJOR,MINOR,REVISION,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Equalizer APO Configuration Editor\0" + VALUE "FileVersion", S(MAJOR) "." S(MINOR) "." S(REVISION) ".0" + VALUE "LegalCopyright", "Copyright (C) 2017\0" + VALUE "OriginalFilename", "Editor.exe\0" + VALUE "ProductName", "Configuration Editor\0" + VALUE "ProductVersion", S(MAJOR) "." S(MINOR) "." S(REVISION) ".0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END + END +/* End of Version info */ + diff --git a/Editor/FilterTable.cpp b/Editor/FilterTable.cpp index 0fe1330..ca62bbd 100644 --- a/Editor/FilterTable.cpp +++ b/Editor/FilterTable.cpp @@ -83,7 +83,7 @@ FilterTable::FilterTable(MainWindow* mainWindow, QWidget* parent) QApplication::instance()->installEventFilter(this); } -void FilterTable::initialize(QScrollArea* scrollArea, const QList& outputDevices, const QList& inputDevices) +void FilterTable::initialize(QScrollArea* scrollArea, const QList >& outputDevices, const QList >& inputDevices) { this->scrollArea = scrollArea; this->outputDevices = outputDevices; @@ -93,7 +93,7 @@ void FilterTable::initialize(QScrollArea* scrollArea, const QList factory->initialize(this); } -void FilterTable::updateDeviceAndChannelMask(DeviceAPOInfo* selectedDevice, int channelMask) +void FilterTable::updateDeviceAndChannelMask(shared_ptr selectedDevice, int channelMask) { this->selectedDevice = selectedDevice; this->selectedChannelMask = channelMask; @@ -222,7 +222,9 @@ void FilterTable::updateGuis() void FilterTable::propagateChannels() { - vector channelNames = ChannelHelper::getChannelNames(selectedDevice->channelCount, selectedChannelMask); + vector channelNames; + if (selectedDevice != NULL) + channelNames = ChannelHelper::getChannelNames(selectedDevice->getChannelCount(), selectedChannelMask); for (Item* item : items) { @@ -1009,17 +1011,17 @@ const QSet& FilterTable::getSelectedItems() const return selected; } -const QList& FilterTable::getOutputDevices() const +const QList >& FilterTable::getOutputDevices() const { return outputDevices; } -const QList& FilterTable::getInputDevices() const +const QList >& FilterTable::getInputDevices() const { return inputDevices; } -DeviceAPOInfo* FilterTable::getSelectedDevice() const +shared_ptr FilterTable::getSelectedDevice() const { return selectedDevice; } diff --git a/Editor/FilterTable.h b/Editor/FilterTable.h index 888589a..8352627 100644 --- a/Editor/FilterTable.h +++ b/Editor/FilterTable.h @@ -56,8 +56,8 @@ public: }; explicit FilterTable(MainWindow* mainWindow, QWidget* parent = 0); - void initialize(QScrollArea* scrollArea, const QList& outputDevices, const QList& inputDevices); - void updateDeviceAndChannelMask(DeviceAPOInfo* selectedDevice, int selectedChannelMask); + void initialize(QScrollArea* scrollArea, const QList >& outputDevices, const QList >& inputDevices); + void updateDeviceAndChannelMask(std::shared_ptr selectedDevice, int selectedChannelMask); void updateGuis(); void propagateChannels(); QList getLines(); @@ -70,9 +70,9 @@ public: void paste(); void deleteSelectedLines(); - const QList& getOutputDevices() const; - const QList& getInputDevices() const; - DeviceAPOInfo* getSelectedDevice() const; + const QList >& getOutputDevices() const; + const QList >& getInputDevices() const; + std::shared_ptr getSelectedDevice() const; int getSelectedChannelMask() const; const QSet& getSelectedItems() const; @@ -131,9 +131,9 @@ private: QList factories; bool scrollingNow = false; QPoint scrollStartPoint; - QList outputDevices; - QList inputDevices; - DeviceAPOInfo* selectedDevice; + QList > outputDevices; + QList > inputDevices; + std::shared_ptr selectedDevice; int selectedChannelMask; QString configPath; int minimumHeightHint = 0; diff --git a/Editor/MainWindow.cpp b/Editor/MainWindow.cpp index 0620cd6..2d3ee32 100644 --- a/Editor/MainWindow.cpp +++ b/Editor/MainWindow.cpp @@ -51,15 +51,15 @@ using namespace std; MainWindow::MainWindow(QDir configDir, QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow), configDir(configDir) { - outputDevices = QList::fromVector(QVector::fromStdVector(DeviceAPOInfo::loadAllInfos(false))); - inputDevices = QList::fromVector(QVector::fromStdVector(DeviceAPOInfo::loadAllInfos(true))); + outputDevices = QList >::fromVector(QVector >::fromStdVector(DeviceAPOInfo::loadAllInfos(false))); + inputDevices = QList >::fromVector(QVector >::fromStdVector(DeviceAPOInfo::loadAllInfos(true))); defaultOutputDevice = NULL; - for (DeviceAPOInfo& apoInfo : outputDevices) + for (shared_ptr& apoInfo : outputDevices) { - if (apoInfo.isDefaultDevice) + if (apoInfo->isDefaultDevice()) { - defaultOutputDevice = &apoInfo; + defaultOutputDevice = apoInfo; break; } } @@ -106,7 +106,7 @@ MainWindow::MainWindow(QDir configDir, QWidget* parent) QStandardItemModel* model = qobject_cast(deviceComboBox->model()); if (defaultOutputDevice != NULL) - deviceComboBox->addItem(tr("Default") + " (" + QString::fromStdWString(defaultOutputDevice->connectionName) + " - " + QString::fromStdWString(defaultOutputDevice->deviceName) + ")", NULL); + deviceComboBox->addItem(tr("Default") + " (" + QString::fromStdWString(defaultOutputDevice->getConnectionName()) + " - " + QString::fromStdWString(defaultOutputDevice->getDeviceName()) + ")", NULL); deviceComboBox->addItem(tr("Playback devices:")); QStandardItem* item = model->item(model->rowCount() - 1); @@ -115,18 +115,18 @@ MainWindow::MainWindow(QDir configDir, QWidget* parent) item->setFont(font); item->setSelectable(false); - for (DeviceAPOInfo& apoInfo : outputDevices) - if (apoInfo.isInstalled) - deviceComboBox->addItem(QString::fromStdWString(apoInfo.connectionName) + " - " + QString::fromStdWString(apoInfo.deviceName), QVariant::fromValue(&apoInfo)); + for (shared_ptr& apoInfo : outputDevices) + if (apoInfo->isInstalled()) + deviceComboBox->addItem(QString::fromStdWString(apoInfo->getConnectionName()) + " - " + QString::fromStdWString(apoInfo->getDeviceName()), QVariant::fromValue(apoInfo)); deviceComboBox->addItem(tr("Capture devices:")); item = model->item(model->rowCount() - 1); item->setFont(font); item->setSelectable(false); - for (DeviceAPOInfo& apoInfo : inputDevices) - if (apoInfo.isInstalled) - deviceComboBox->addItem(QString::fromStdWString(apoInfo.connectionName) + " - " + QString::fromStdWString(apoInfo.deviceName), QVariant::fromValue(&apoInfo)); + for (shared_ptr& apoInfo : inputDevices) + if (apoInfo->isInstalled()) + deviceComboBox->addItem(QString::fromStdWString(apoInfo->getConnectionName()) + " - " + QString::fromStdWString(apoInfo->getDeviceName()), QVariant::fromValue(apoInfo)); connect(channelConfigurationComboBox, SIGNAL(activated(int)), this, SLOT(channelConfigurationSelected(int))); @@ -183,7 +183,7 @@ void MainWindow::doChecks() } } - if (!defaultOutputDevice->isInstalled) + if (defaultOutputDevice != NULL && !defaultOutputDevice->isInstalled()) { if (QMessageBox::warning(this, tr("APO not installed to device"), tr("Equalizer APO has not been installed to the selected device.\nDo you want to run the Configurator application to fix the problem?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { @@ -192,23 +192,23 @@ void MainWindow::doChecks() } } - DeviceAPOInfo* disabledApoInfo = NULL; - for (DeviceAPOInfo& apoInfo : outputDevices) + AbstractAPOInfo* disabledApoInfo = NULL; + for (shared_ptr& apoInfo : outputDevices) { - if (apoInfo.isInstalled && apoInfo.isEnhancementsDisabled) + if (apoInfo->isInstalled() && apoInfo->isEnhancementsDisabled()) { - disabledApoInfo = &apoInfo; + disabledApoInfo = apoInfo.get(); break; } } if (disabledApoInfo == NULL) { - for (DeviceAPOInfo& apoInfo : inputDevices) + for (shared_ptr& apoInfo : inputDevices) { - if (apoInfo.isInstalled && apoInfo.isEnhancementsDisabled) + if (apoInfo->isInstalled() && apoInfo->isEnhancementsDisabled()) { - disabledApoInfo = &apoInfo; + disabledApoInfo = apoInfo.get(); break; } } @@ -216,7 +216,7 @@ void MainWindow::doChecks() if (disabledApoInfo != NULL) { - if (QMessageBox::warning(this, tr("Audio enhancements disabled"), tr("Audio enhancements are not enabled for the device\n%0 %1.\nDo you want to run the Configurator application to fix the problem?").arg(QString::fromStdWString(disabledApoInfo->connectionName)).arg(QString::fromStdWString(disabledApoInfo->deviceName)), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + if (QMessageBox::warning(this, tr("Audio enhancements disabled"), tr("Audio enhancements are not enabled for the device\n%0 %1.\nDo you want to run the Configurator application to fix the problem?").arg(QString::fromStdWString(disabledApoInfo->getConnectionName())).arg(QString::fromStdWString(disabledApoInfo->getDeviceName())), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { runConfigurator(); return; @@ -402,7 +402,7 @@ void MainWindow::closeEvent(QCloseEvent* event) void MainWindow::deviceSelected(int index) { - DeviceAPOInfo* apoInfo = deviceComboBox->itemData(index).value(); + shared_ptr apoInfo = deviceComboBox->itemData(index).value >(); if (apoInfo == NULL) apoInfo = defaultOutputDevice; @@ -415,7 +415,7 @@ void MainWindow::deviceSelected(int index) const GUIChannelHelper::ChannelConfigurationInfo* selectedInfo = NULL; for (const GUIChannelHelper::ChannelConfigurationInfo& info : infos) { - if (info.channelMask == apoInfo->channelMask) + if (info.channelMask == apoInfo->getChannelMask()) { selectedInfo = &info; break; @@ -425,7 +425,7 @@ void MainWindow::deviceSelected(int index) if (selectedInfo != NULL) channelConfigurationComboBox->addItem(tr("From device") + " (" + selectedInfo->name + ")", 0); else - channelConfigurationComboBox->addItem(tr("From device") + " (" + apoInfo->channelCount + " channels)", 0); + channelConfigurationComboBox->addItem(tr("From device") + " (" + apoInfo->getChannelCount() + " channels)", 0); } for (const GUIChannelHelper::ChannelConfigurationInfo& info : infos) @@ -436,7 +436,7 @@ void MainWindow::deviceSelected(int index) void MainWindow::channelConfigurationSelected(int index) { - DeviceAPOInfo* selectedDevice; + shared_ptr selectedDevice; int channelMask; getDeviceAndChannelMask(&selectedDevice, &channelMask); @@ -451,8 +451,8 @@ void MainWindow::channelConfigurationSelected(int index) if (selectedDevice != NULL) { - unsigned channelCount = selectedDevice->channelCount; - if (channelMask != 0 && channelMask != selectedDevice->channelMask) + unsigned channelCount = selectedDevice->getChannelCount(); + if (channelMask != 0 && channelMask != selectedDevice->getChannelMask()) { channelCount = 0; for (int i = 0; i < 31; i++) @@ -816,7 +816,7 @@ FilterTable* MainWindow::addTab(QString title, QString tooltip, QString configPa filterTable->setAcceptDrops(true); filterTable->setFocusPolicy(Qt::WheelFocus); - DeviceAPOInfo* selectedDevice; + shared_ptr selectedDevice; int channelMask; getDeviceAndChannelMask(&selectedDevice, &channelMask); filterTable->updateDeviceAndChannelMask(selectedDevice, channelMask); @@ -829,19 +829,19 @@ FilterTable* MainWindow::addTab(QString title, QString tooltip, QString configPa return filterTable; } -void MainWindow::getDeviceAndChannelMask(DeviceAPOInfo** selectedDevice, int* channelMask) +void MainWindow::getDeviceAndChannelMask(shared_ptr* selectedDevice, int* channelMask) { - *selectedDevice = deviceComboBox->currentData().value(); + *selectedDevice = deviceComboBox->currentData().value >(); if (*selectedDevice == NULL) *selectedDevice = defaultOutputDevice; *channelMask = channelConfigurationComboBox->currentData().toInt(); if (*channelMask == 0 && selectedDevice != NULL) { - *channelMask = (*selectedDevice)->channelMask; + *channelMask = (*selectedDevice)->getChannelMask(); if (*channelMask == 0) - *channelMask = ChannelHelper::getDefaultChannelMask((*selectedDevice)->channelCount); + *channelMask = ChannelHelper::getDefaultChannelMask((*selectedDevice)->getChannelCount()); } } @@ -894,7 +894,7 @@ bool MainWindow::askForClose(int tabIndex) void MainWindow::startAnalysis() { - DeviceAPOInfo* selectedDevice; + shared_ptr selectedDevice; int channelMask; getDeviceAndChannelMask(&selectedDevice, &channelMask); @@ -933,15 +933,15 @@ void MainWindow::loadPreferences() if (stateValue.isValid()) restoreState(stateValue.toByteArray()); instantModeCheckBox->setChecked(settings.value("instantMode", true).toBool()); - QString selectedDeviceGuid = settings.value("selectedDeviceGuid").toString(); - if (!selectedDeviceGuid.isEmpty()) + QString selectedDevice = settings.value("selectedDevice").toString(); + if (!selectedDevice.isEmpty()) { for (int i = 0; i < deviceComboBox->count(); i++) { - DeviceAPOInfo* apoInfo = deviceComboBox->itemData(i).value(); + shared_ptr apoInfo = deviceComboBox->itemData(i).value >(); if (apoInfo != NULL) { - if (QString::fromStdWString(apoInfo->deviceGuid).compare(selectedDeviceGuid, Qt::CaseInsensitive) == 0) + if (QString::fromStdWString(apoInfo->getDeviceString()).compare(selectedDevice, Qt::CaseInsensitive) == 0) { deviceComboBox->setCurrentIndex(i); break; @@ -1012,8 +1012,8 @@ void MainWindow::savePreferences() settings.setValue("geometry", saveGeometry()); settings.setValue("windowState", saveState()); settings.setValue("instantMode", instantModeCheckBox->isChecked()); - DeviceAPOInfo* selectedDevice = deviceComboBox->currentData().value(); - settings.setValue("selectedDeviceGuid", selectedDevice != NULL ? QString::fromStdWString(selectedDevice->deviceGuid) : ""); + shared_ptr selectedDevice = deviceComboBox->currentData().value >(); + settings.setValue("selectedDevice", selectedDevice != NULL ? QString::fromStdWString(selectedDevice->getDeviceString()) : ""); int channelMask = channelConfigurationComboBox->currentData().toInt(); settings.setValue("selectedChannelMask", channelMask); diff --git a/Editor/MainWindow.h b/Editor/MainWindow.h index 6e95a3c..2e694bb 100644 --- a/Editor/MainWindow.h +++ b/Editor/MainWindow.h @@ -91,7 +91,7 @@ private slots: private: FilterTable* addTab(QString title, QString tooltip, QString configPath, QList lines); - void getDeviceAndChannelMask(DeviceAPOInfo** selectedDevice, int* channelMask); + void getDeviceAndChannelMask(std::shared_ptr* selectedDevice, int* channelMask); bool askForClose(int tabIndex); void startAnalysis(); void loadPreferences(); @@ -104,9 +104,9 @@ private: QCheckBox* instantModeCheckBox; QComboBox* deviceComboBox; QComboBox* channelConfigurationComboBox; - QList outputDevices; - QList inputDevices; - DeviceAPOInfo* defaultOutputDevice; + QList > outputDevices; + QList > inputDevices; + std::shared_ptr defaultOutputDevice; AnalysisPlotScene* analysisPlotScene; AnalysisThread* analysisThread; bool restart = false; @@ -115,4 +115,4 @@ private: QStringList recentFiles; }; -Q_DECLARE_METATYPE(DeviceAPOInfo*) +Q_DECLARE_METATYPE(std::shared_ptr) diff --git a/Editor/guis/ConvolutionFilterGUIFactory.cpp b/Editor/guis/ConvolutionFilterGUIFactory.cpp index 64211cd..b67f597 100644 --- a/Editor/guis/ConvolutionFilterGUIFactory.cpp +++ b/Editor/guis/ConvolutionFilterGUIFactory.cpp @@ -22,13 +22,19 @@ #include "ConvolutionFilterGUI.h" #include "ConvolutionFilterGUIFactory.h" +using namespace std; + ConvolutionFilterGUIFactory::ConvolutionFilterGUIFactory() { } void ConvolutionFilterGUIFactory::initialize(FilterTable* filterTable) { - deviceSampleRate = filterTable->getSelectedDevice()->sampleRate; + shared_ptr selectedDevice = filterTable->getSelectedDevice(); + if (selectedDevice != NULL) + deviceSampleRate = filterTable->getSelectedDevice()->getSampleRate(); + else + deviceSampleRate = 0; } QList ConvolutionFilterGUIFactory::createFilterTemplates() diff --git a/Editor/guis/DeviceFilterGUI.cpp b/Editor/guis/DeviceFilterGUI.cpp index 1641163..b999af6 100644 --- a/Editor/guis/DeviceFilterGUI.cpp +++ b/Editor/guis/DeviceFilterGUI.cpp @@ -21,6 +21,7 @@ #include "DeviceFilterGUI.h" #include "DeviceFilterGUIDialog.h" #include +#include #include "ui_DeviceFilterGUI.h" using namespace std; @@ -63,27 +64,39 @@ void DeviceFilterGUI::load(const QString& parameters) } else { - const QList& devices = factory->getDevices(); - for (const DeviceAPOInfo& apoInfo : devices) + const QList >& devices = factory->getDevices(); + bool anyInstalled = false; + for (const shared_ptr& apoInfo : devices) { - if (!apoInfo.isInstalled) + if (apoInfo->isInstalled() && DeviceFilterFactory::matchDevice(apoInfo->getDeviceString(), pattern.toStdWString())) + { + anyInstalled = true; + break; + } + } + + for (const shared_ptr& apoInfo : devices) + { + if (anyInstalled && !apoInfo->isInstalled()) continue; - wstring deviceString = apoInfo.connectionName + L" " + apoInfo.deviceName + L" " + apoInfo.deviceGuid; - if (DeviceFilterFactory::matchDevice(deviceString, pattern.toStdWString())) + if (DeviceFilterFactory::matchDevice(apoInfo->getDeviceString(), pattern.toStdWString())) { QStringList values; - if (apoInfo.isInput) + if (apoInfo->isInput()) values.append(tr("Capture")); else values.append(tr("Playback")); - values.append(QString::fromStdWString(apoInfo.connectionName)); - values.append(QString::fromStdWString(apoInfo.deviceName)); + values.append(QString::fromStdWString(apoInfo->getConnectionName())); + values.append(QString::fromStdWString(apoInfo->getDeviceName())); QString state; - if (apoInfo.isInstalled) + if (apoInfo->isInstalled()) state = tr("APO installed"); else state = tr("APO not installed"); + VoicemeeterAPOInfo* voicemeeterInfo = dynamic_cast(apoInfo.get()); + if (voicemeeterInfo != NULL && !voicemeeterInfo->isVoicemeeterInstalled()) + state += ", " + tr("Voicemeeter was uninstalled"); values.append(state); lastItem = new QTreeWidgetItem(ui->treeWidget, values); } @@ -93,7 +106,7 @@ void DeviceFilterGUI::load(const QString& parameters) { QStringList values; values.append(""); - values.append(tr("No device matched!")); + values.append(tr("No device matched") + " \"" + pattern.trimmed() + "\""); values.append(""); QTreeWidgetItem* item = new QTreeWidgetItem(ui->treeWidget, values); diff --git a/Editor/guis/DeviceFilterGUIDialog.cpp b/Editor/guis/DeviceFilterGUIDialog.cpp index c40633c..05a3003 100644 --- a/Editor/guis/DeviceFilterGUIDialog.cpp +++ b/Editor/guis/DeviceFilterGUIDialog.cpp @@ -22,6 +22,7 @@ #include "ui_DeviceFilterGUIDialog.h" #include +#include using namespace std; @@ -44,26 +45,28 @@ DeviceFilterGUIDialog::DeviceFilterGUIDialog(DeviceFilterGUI* gui, DeviceFilterG outputNode->setExpanded(true); QTreeWidgetItem* inputNode = new QTreeWidgetItem(ui->treeWidget, QStringList(tr("Capture devices"))); inputNode->setExpanded(true); - const QList& devices = factory->getDevices(); - for (const DeviceAPOInfo& apoInfo : devices) + const QList >& devices = factory->getDevices(); + for (const shared_ptr& apoInfo : devices) { QStringList values; - values.append(QString::fromStdWString(apoInfo.connectionName)); - values.append(QString::fromStdWString(apoInfo.deviceName)); + values.append(QString::fromStdWString(apoInfo->getConnectionName())); + values.append(QString::fromStdWString(apoInfo->getDeviceName())); QString state; - if (apoInfo.isInstalled) + if (apoInfo->isInstalled()) state = tr("APO installed"); else state = tr("APO not installed"); + VoicemeeterAPOInfo* voicemeeterInfo = dynamic_cast(apoInfo.get()); + if (voicemeeterInfo != NULL && !voicemeeterInfo->isVoicemeeterInstalled()) + state += ", " + tr("Voicemeeter was uninstalled"); values.append(state); - QTreeWidgetItem* item = new QTreeWidgetItem(apoInfo.isInput ? inputNode : outputNode, values); + QTreeWidgetItem* item = new QTreeWidgetItem(apoInfo->isInput() ? inputNode : outputNode, values); - wstring deviceString = apoInfo.connectionName + L" " + apoInfo.deviceName + L" " + apoInfo.deviceGuid; - bool matches = !all && DeviceFilterFactory::matchDevice(deviceString, pattern.toStdWString()); + bool matches = !all && DeviceFilterFactory::matchDevice(apoInfo->getDeviceString(), pattern.toStdWString()); item->setCheckState(0, matches ? Qt::Checked : Qt::Unchecked); - item->setData(0, Qt::UserRole, QVariant::fromValue(&apoInfo)); - item->setHidden(!matches && !apoInfo.isInstalled && ui->showOnlyInstalledCheckBox->isChecked()); - if (!apoInfo.isInstalled) + item->setData(0, Qt::UserRole, QVariant::fromValue(apoInfo)); + item->setHidden(!matches && !apoInfo->isInstalled() && ui->showOnlyInstalledCheckBox->isChecked()); + if (!apoInfo->isInstalled()) for (int i = 0; i < ui->treeWidget->columnCount(); i++) item->setForeground(i, QBrush(Qt::gray)); } @@ -96,9 +99,8 @@ QString DeviceFilterGUIDialog::getPattern() { if (pattern != "") pattern += "; "; - const DeviceAPOInfo* apoInfo = item->data(0, Qt::UserRole).value(); - wstring deviceString = apoInfo->connectionName + L" " + apoInfo->deviceName + L" " + apoInfo->deviceGuid; - pattern += QString::fromStdWString(deviceString); + shared_ptr apoInfo = item->data(0, Qt::UserRole).value >(); + pattern += QString::fromStdWString(apoInfo->getDeviceString()); } } } @@ -120,8 +122,8 @@ void DeviceFilterGUIDialog::on_showOnlyInstalledCheckBox_toggled(bool checked) for (int j = 0; j < groupItem->childCount(); j++) { QTreeWidgetItem* item = groupItem->child(j); - const DeviceAPOInfo* apoInfo = item->data(0, Qt::UserRole).value(); - item->setHidden(item->checkState(0) != Qt::Checked && !apoInfo->isInstalled && checked); + shared_ptr apoInfo = item->data(0, Qt::UserRole).value >(); + item->setHidden(item->checkState(0) != Qt::Checked && !apoInfo->isInstalled() && checked); } } diff --git a/Editor/guis/DeviceFilterGUIDialog.h b/Editor/guis/DeviceFilterGUIDialog.h index d775a44..a24ae5c 100644 --- a/Editor/guis/DeviceFilterGUIDialog.h +++ b/Editor/guis/DeviceFilterGUIDialog.h @@ -47,4 +47,4 @@ private: Ui::DeviceFilterGUIDialog* ui; }; -Q_DECLARE_METATYPE(const DeviceAPOInfo*) +Q_DECLARE_METATYPE(std::shared_ptr) diff --git a/Editor/guis/DeviceFilterGUIFactory.cpp b/Editor/guis/DeviceFilterGUIFactory.cpp index 264c85f..772da38 100644 --- a/Editor/guis/DeviceFilterGUIFactory.cpp +++ b/Editor/guis/DeviceFilterGUIFactory.cpp @@ -21,6 +21,8 @@ #include "DeviceFilterGUI.h" #include "DeviceFilterGUIFactory.h" +using namespace std; + DeviceFilterGUIFactory::DeviceFilterGUIFactory() { } @@ -51,7 +53,7 @@ IFilterGUI* DeviceFilterGUIFactory::createFilterGUI(QString& command, QString& p return result; } -const QList& DeviceFilterGUIFactory::getDevices() const +const QList >& DeviceFilterGUIFactory::getDevices() const { return devices; } diff --git a/Editor/guis/DeviceFilterGUIFactory.h b/Editor/guis/DeviceFilterGUIFactory.h index 7dfa65e..f8d61ff 100644 --- a/Editor/guis/DeviceFilterGUIFactory.h +++ b/Editor/guis/DeviceFilterGUIFactory.h @@ -34,8 +34,8 @@ public: QList createFilterTemplates() override; IFilterGUI* createFilterGUI(QString& command, QString& parameters) override; - const QList& getDevices() const; + const QList >& getDevices() const; private: - QList devices; + QList > devices; }; diff --git a/Editor/translations/Editor_de.qm b/Editor/translations/Editor_de.qm index 98238dd..a3db98c 100644 Binary files a/Editor/translations/Editor_de.qm and b/Editor/translations/Editor_de.qm differ diff --git a/Editor/translations/Editor_de.ts b/Editor/translations/Editor_de.ts index cd8733d..9acdecb 100644 --- a/Editor/translations/Editor_de.ts +++ b/Editor/translations/Editor_de.ts @@ -354,12 +354,12 @@ Wählen Sie eine andere Datei oder ändern Sie die Konfiguration des Geräts.Convolution (Faltung mit Impulsantwort) - + Advanced filters Fortgeschrittene Filter - + Convolution (Convolution with impulse response) Convolution (Faltung mit Impulsantwort) @@ -524,54 +524,63 @@ Wählen Sie eine andere Datei oder ändern Sie die Konfiguration des Geräts.Ändern... - + Type Typ - + Connection Anschluss - + Device Gerät - + State Status - + All devices Alle Geräte - + Capture Aufnahme - + Playback Wiedergabe - + APO installed APO installiert - + APO not installed APO nicht installiert - + + Voicemeeter was uninstalled + Voicemeeter wurde deinstalliert + + + + No device matched + Kein Gerät passt zu + + No device matched! - Kein Gerät stimmt überein! + Kein Gerät stimmt überein! @@ -592,40 +601,45 @@ Wählen Sie eine andere Datei oder ändern Sie die Konfiguration des Geräts.Nur Geräte mit installiertem APO zeigen - + Connection Anschluss - + Device Gerät - + State Status - + Playback devices Wiedergabegeräte - + Capture devices Aufnahmegeräte - + APO installed APO installiert - + APO not installed APO nicht installiert + + + Voicemeeter was uninstalled + Voicemeeter wurde deinstalliert + DeviceFilterGUIFactory @@ -634,12 +648,12 @@ Wählen Sie eine andere Datei oder ändern Sie die Konfiguration des Geräts.Device (Gerät auswählen) - + Control Steuerung - + Device (Select device) Device (Gerät auswählen) diff --git a/EqualizerAPO.cpp b/EqualizerAPO.cpp index 1bbb050..8318af2 100644 --- a/EqualizerAPO.cpp +++ b/EqualizerAPO.cpp @@ -138,14 +138,14 @@ HRESULT EqualizerAPO::Initialize(UINT32 cbDataSize, BYTE* pbyData) DeviceAPOInfo apoInfo; if (apoInfo.load(deviceGuid)) { - engine.setDeviceInfo(apoInfo.isInput, apoInfo.currentInstallState.installPostMix, apoInfo.deviceName, apoInfo.connectionName, apoInfo.deviceGuid); + engine.setDeviceInfo(apoInfo.isInput(), apoInfo.getCurrentInstallState().installPostMix, apoInfo.getDeviceName(), apoInfo.getConnectionName(), apoInfo.getDeviceGuid(), apoInfo.getDeviceString()); if (apoGuid == EQUALIZERAPO_PRE_MIX_GUID) - childApoGuid = apoInfo.preMixChildGuid; + childApoGuid = apoInfo.getPreMixChildGuid(); else - childApoGuid = apoInfo.postMixChildGuid; + childApoGuid = apoInfo.getPostMixChildGuid(); - allowSilentBufferModification = apoInfo.currentInstallState.allowSilentBufferModification; + allowSilentBufferModification = apoInfo.getCurrentInstallState().allowSilentBufferModification; } } catch (RegistryException e) diff --git a/EqualizerAPO.rc b/EqualizerAPO.rc index 289a2e1..ffc2c76 100644 Binary files a/EqualizerAPO.rc and b/EqualizerAPO.rc differ diff --git a/EqualizerAPO.sln b/EqualizerAPO.sln index e0a577b..a0423a0 100644 --- a/EqualizerAPO.sln +++ b/EqualizerAPO.sln @@ -12,6 +12,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Configurator", "Configurato EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Benchmark", "Benchmark\Benchmark.vcxproj", "{D4474852-5724-4253-95C6-7A6DC424F194}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VoicemeeterClient", "VoicemeeterClient\VoicemeeterClient.vcxproj", "{10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -44,6 +46,14 @@ Global {D4474852-5724-4253-95C6-7A6DC424F194}.Release|Win32.Build.0 = Release|Win32 {D4474852-5724-4253-95C6-7A6DC424F194}.Release|x64.ActiveCfg = Release|x64 {D4474852-5724-4253-95C6-7A6DC424F194}.Release|x64.Build.0 = Release|x64 + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}.Debug|Win32.ActiveCfg = Debug|Win32 + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}.Debug|Win32.Build.0 = Debug|Win32 + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}.Debug|x64.ActiveCfg = Debug|x64 + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}.Debug|x64.Build.0 = Debug|x64 + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}.Release|Win32.ActiveCfg = Release|Win32 + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}.Release|Win32.Build.0 = Release|Win32 + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}.Release|x64.ActiveCfg = Release|x64 + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/EqualizerAPO.vcxproj b/EqualizerAPO.vcxproj index b594700..3acdb83 100644 --- a/EqualizerAPO.vcxproj +++ b/EqualizerAPO.vcxproj @@ -162,6 +162,7 @@ + @@ -195,7 +196,9 @@ + + @@ -205,8 +208,10 @@ + + @@ -252,6 +257,7 @@ Create Create + diff --git a/EqualizerAPO.vcxproj.filters b/EqualizerAPO.vcxproj.filters index 22e58b7..38d3ae9 100644 --- a/EqualizerAPO.vcxproj.filters +++ b/EqualizerAPO.vcxproj.filters @@ -105,6 +105,8 @@ filters + + @@ -216,6 +218,14 @@ filters + + + helpers + + + helpers + + diff --git a/EqualizerAPO64.rc b/EqualizerAPO64.rc index 08aab34..c8ec5f4 100644 Binary files a/EqualizerAPO64.rc and b/EqualizerAPO64.rc differ diff --git a/FilterConfiguration.cpp b/FilterConfiguration.cpp index b2a24cc..15d4498 100644 --- a/FilterConfiguration.cpp +++ b/FilterConfiguration.cpp @@ -75,7 +75,7 @@ FilterConfiguration::~FilterConfiguration() } #pragma AVRT_CODE_BEGIN -void FilterConfiguration::process(float* input, unsigned frameCount) +void FilterConfiguration::read(float* input, unsigned frameCount) { #define DEINTERLEAVE_MACRO(ccount)\ {\ @@ -107,7 +107,16 @@ void FilterConfiguration::process(float* input, unsigned frameCount) default: DEINTERLEAVE_MACRO(realChannelCount) } +} +void FilterConfiguration::read(float** input, unsigned frameCount) +{ + for (unsigned c = 0; c < realChannelCount; c++) + memcpy(allSamples[c], input[c], frameCount * sizeof(float)); +} + +void FilterConfiguration::process(unsigned frameCount) +{ for (unsigned c = realChannelCount; c < allChannelCount; c++) memset(allSamples[c], 0, frameCount * sizeof(float)); @@ -142,6 +151,26 @@ void FilterConfiguration::process(float* input, unsigned frameCount) } } +unsigned FilterConfiguration::doTransition(FilterConfiguration* nextConfig, unsigned frameCount, unsigned transitionCounter, unsigned transitionLength) +{ + float** currentSamples = allSamples; + float** nextSamples = nextConfig->allSamples; + + for (unsigned f = 0; f < frameCount; f++) + { + float factor = 0.5f * (1.0f - cos(transitionCounter * (float)M_PI / transitionLength)); + if (transitionCounter >= transitionLength) + factor = 1.0f; + + for (unsigned c = 0; c < outputChannelCount; c++) + currentSamples[c][f] = currentSamples[c][f] * (1 - factor) + nextSamples[c][f] * factor; + + transitionCounter++; + } + + return transitionCounter; +} + void FilterConfiguration::write(float* output, unsigned frameCount) { #define INTERLEAVE_MACRO(ccount)\ @@ -173,6 +202,12 @@ void FilterConfiguration::write(float* output, unsigned frameCount) INTERLEAVE_MACRO(outputChannelCount) } } + +void FilterConfiguration::write(float** output, unsigned frameCount) +{ + for (unsigned i = 0; i < outputChannelCount; i++) + memcpy(output[i], allSamples[i], frameCount * sizeof(float)); +} #pragma AVRT_CODE_END bool FilterConfiguration::isEmpty() diff --git a/FilterConfiguration.h b/FilterConfiguration.h index 946b8ae..5e49a53 100644 --- a/FilterConfiguration.h +++ b/FilterConfiguration.h @@ -43,8 +43,12 @@ public: FilterConfiguration(FilterEngine* engine, const std::vector& filterInfos, unsigned allChannelCount); ~FilterConfiguration(); - void process(float* input, unsigned frameCount); + void read(float* input, unsigned frameCount); + void read(float** input, unsigned frameCount); + void process(unsigned frameCount); + unsigned doTransition(FilterConfiguration* nextConfig, unsigned frameCount, unsigned transitionCounter, unsigned transitionLength); void write(float* output, unsigned frameCount); + void write(float** output, unsigned frameCount); float** getOutputSamples() {return allSamples;} bool isEmpty(); diff --git a/FilterEngine.cpp b/FilterEngine.cpp index 2f27fca..ab29759 100644 --- a/FilterEngine.cpp +++ b/FilterEngine.cpp @@ -118,13 +118,14 @@ void FilterEngine::setPreMix(bool preMix) this->preMix = preMix; } -void FilterEngine::setDeviceInfo(bool capture, bool postMixInstalled, const wstring& deviceName, const wstring& connectionName, const wstring& deviceGuid) +void FilterEngine::setDeviceInfo(bool capture, bool postMixInstalled, const wstring& deviceName, const wstring& connectionName, const wstring& deviceGuid, const wstring& deviceString) { this->capture = capture; this->postMixInstalled = postMixInstalled; this->deviceName = deviceName; this->connectionName = connectionName; this->deviceGuid = deviceGuid; + this->deviceString = deviceString; } void FilterEngine::initialize(float sampleRate, unsigned inputChannelCount, unsigned realChannelCount, unsigned outputChannelCount, unsigned channelMask, unsigned maxFrameCount, const wstring& customPath) @@ -268,7 +269,7 @@ void FilterEngine::loadConfigFile(const wstring& path) DWORD error = GetLastError(); if (error != ERROR_SHARING_VIOLATION) { - LogF(L"Error while reading configuration file: %s", StringHelper::getSystemErrorString(error).c_str()); + LogF(L"Error while reading configuration file %s: %s", path.c_str(), StringHelper::getSystemErrorString(error).c_str()); return; } @@ -377,27 +378,55 @@ void FilterEngine::process(float* output, float* input, unsigned frameCount) } } - currentConfig->process(input, frameCount); + currentConfig->read(input, frameCount); + currentConfig->process(frameCount); if (nextConfig != NULL) { - nextConfig->process(input, frameCount); - float** currentSamples = currentConfig->getOutputSamples(); - float** nextSamples = nextConfig->getOutputSamples(); + nextConfig->read(input, frameCount); + nextConfig->process(frameCount); + transitionCounter = currentConfig->doTransition(nextConfig, frameCount, transitionCounter, transitionLength); + } - for (unsigned f = 0; f < frameCount; f++) + currentConfig->write(output, frameCount); + + if (nextConfig != NULL && transitionCounter >= transitionLength) + { + previousConfig = currentConfig; + currentConfig = nextConfig; + nextConfig = NULL; + transitionCounter = 0; + ReleaseSemaphore(loadSemaphore, 1, NULL); + } +} + +void FilterEngine::process(float** output, float** input, unsigned frameCount) +{ + if (currentConfig->isEmpty() && nextConfig == NULL) + { + // avoid double copying cost if no processing will happen anyway + if (realChannelCount == outputChannelCount) { - float factor = 0.5f * (1.0f - cos(transitionCounter * (float)M_PI / transitionLength)); - if (transitionCounter >= transitionLength) - factor = 1.0f; + if (input != output) + { + for (unsigned c = 0; c < realChannelCount; c++) + memcpy(output[c], input[c], frameCount * sizeof(float)); + } - for (unsigned c = 0; c < outputChannelCount; c++) - currentSamples[c][f] = currentSamples[c][f] * (1 - factor) + nextSamples[c][f] * factor; - - transitionCounter++; + return; } } + currentConfig->read(input, frameCount); + currentConfig->process(frameCount); + + if (nextConfig != NULL) + { + nextConfig->read(input, frameCount); + nextConfig->process(frameCount); + transitionCounter = currentConfig->doTransition(nextConfig, frameCount, transitionCounter, transitionLength); + } + currentConfig->write(output, frameCount); if (nextConfig != NULL && transitionCounter >= transitionLength) diff --git a/FilterEngine.h b/FilterEngine.h index 554df36..3911237 100644 --- a/FilterEngine.h +++ b/FilterEngine.h @@ -39,12 +39,13 @@ public: ~FilterEngine(); void setPreMix(bool preMix); - void setDeviceInfo(bool capture, bool postMixInstalled, const std::wstring& deviceName, const std::wstring& connectionName, const std::wstring& deviceGuid); + void setDeviceInfo(bool capture, bool postMixInstalled, const std::wstring& deviceName, const std::wstring& connectionName, const std::wstring& deviceGuid, const std::wstring& deviceString); void initialize(float sampleRate, unsigned inputChannelCount, unsigned realChannelCount, unsigned outputChannelCount, unsigned channelMask, unsigned maxFrameCount, const std::wstring& customPath = L""); void loadConfig(const std::wstring& customPath = L""); void loadConfigFile(const std::wstring& path); void watchRegistryKey(const std::wstring& key); void process(float* output, float* input, unsigned frameCount); + void process(float** output, float** input, unsigned frameCount); bool isPreMix() const {return preMix;} bool isCapture() const {return capture;} @@ -52,6 +53,7 @@ public: std::wstring getDeviceName() const {return deviceName;} std::wstring getConnectionName() const {return connectionName;} std::wstring getDeviceGuid() const {return deviceGuid;} + std::wstring getDeviceString() const {return deviceString;} unsigned getInputChannelCount() const {return inputChannelCount;} unsigned getRealChannelCount() const {return realChannelCount;} unsigned getOutputChannelCount() const {return outputChannelCount;} @@ -73,6 +75,7 @@ private: std::wstring deviceName; std::wstring connectionName; std::wstring deviceGuid; + std::wstring deviceString; std::wstring configPath; float sampleRate; // number of input channels that originally existed before child APO processing diff --git a/Setup/Setup.nsi b/Setup/Setup.nsi index 2acdb0c..8a3f9a4 100644 --- a/Setup/Setup.nsi +++ b/Setup/Setup.nsi @@ -115,10 +115,12 @@ Section "Install" SecInstall !insertmacro RenameAndDelete "$INSTDIR\msvcr100.dll" !insertmacro RenameAndDelete "$INSTDIR\msvcp120.dll" !insertmacro RenameAndDelete "$INSTDIR\msvcr120.dll" + !insertmacro RenameAndDelete "$INSTDIR\VoicemeeterClient.exe" File "${BINPATH}\EqualizerAPO.dll" File "${BINPATH}\Configurator.exe" File "${BINPATH}\Benchmark.exe" + File "${BINPATH}\VoicemeeterClient.exe" File "${BINPATH_EDITOR}\Editor.exe" @@ -227,6 +229,7 @@ Section "-un.Uninstall" Delete "$INSTDIR\Editor.exe" + Delete "$INSTDIR\VoicemeeterClient.exe" Delete "$INSTDIR\Benchmark.exe" Delete "$INSTDIR\Configurator.exe" Delete /REBOOTOK "$INSTDIR\EqualizerAPO.dll" diff --git a/Setup/config/iir_lowpass.txt b/Setup/config/iir_lowpass.txt index 34055db..79b22fb 100644 --- a/Setup/config/iir_lowpass.txt +++ b/Setup/config/iir_lowpass.txt @@ -2,17 +2,17 @@ # The result should be equivalent to the command # Filter: ON LP Fc 3000 Hz -Do: `freq = 3000` -Do: `Q = 1/sqrt(2)` -Do: `omega = 2 * pi * freq / sampleRate` -Do: `sn = sin(omega)` -Do: `cs = cos(omega)` -Do: `alpha = sin(omega) / (2 * Q)` -Do: `a0 = (1 - cs) / 2` -Do: `a1 = 1 - cs` -Do: `a2 = (1 - cs) / 2` -Do: `b0 = 1 + alpha` -Do: `b1 = -2 * cs` -Do: `b2 = 1 - alpha` +Eval: freq = 3000 +Eval: Q = 1/sqrt(2) +Eval: omega = 2 * pi * freq / sampleRate +Eval: sn = sin(omega) +Eval: cs = cos(omega) +Eval: alpha = sin(omega) / (2 * Q) +Eval: a0 = (1 - cs) / 2 +Eval: a1 = 1 - cs +Eval: a2 = (1 - cs) / 2 +Eval: b0 = 1 + alpha +Eval: b1 = -2 * cs +Eval: b2 = 1 - alpha -Filter: ON IIR Order 2 Coefficients `a0` `a1` `a2` `b0` `b1` `b2` \ No newline at end of file +Filter: ON IIR Order 2 Coefficients `a0` `a1` `a2` `b0` `b1` `b2` diff --git a/VoicemeeterAPOInfo.cpp b/VoicemeeterAPOInfo.cpp new file mode 100644 index 0000000..bf6ae7d --- /dev/null +++ b/VoicemeeterAPOInfo.cpp @@ -0,0 +1,501 @@ +/* + This file is part of EqualizerAPO, a system-wide equalizer. + Copyright (C) 2017 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "stdafx.h" +#include +#include +#include +#include +#include +#include +#include +#include "helpers/RegistryHelper.h" +#include "VoicemeeterAPOInfo.h" + +using namespace std; + +#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" +static const wchar_t* startupFilename = L"Equalizer APO Voicemeeter Client.lnk"; +static const wchar_t* clientFilename = L"VoicemeeterClient.exe"; +static const wchar_t* voicemeeterClientKeyPath = USER_REGPATH L"\\Voicemeeter Client"; +static const wchar_t* sampleRateValueName = L"sampleRate"; + +typedef NTSTATUS (NTAPI * pfnNtQueryInformationProcess)( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength OPTIONAL + ); + +void VoicemeeterAPOInfo::prependInfos(vector >& list) +{ + wstring voicemeeterDirectory; + if (RegistryHelper::keyExists(voicemeeterKeyPath)) + voicemeeterDirectory = RegistryHelper::readValue(voicemeeterKeyPath, uninstallStringValueName); + else if (RegistryHelper::keyExists(voicemeeterWowKeyPath)) + voicemeeterDirectory = RegistryHelper::readValue(voicemeeterWowKeyPath, uninstallStringValueName); + + if (voicemeeterDirectory.length() > 0) + { + size_t index = voicemeeterDirectory.find_last_of(L'\\'); + + wstring setupFilename = voicemeeterDirectory.substr(index + 1); + bool banana = (setupFilename == L"VoicemeeterProSetup.exe"); + + unsigned outputCount; + if (banana) + outputCount = 3; + else + outputCount = 1; + + bool defaultDevice = false; + list.erase(remove_if(list.begin(), list.end(), [&defaultDevice](shared_ptr& info) { + if (info->getDeviceName().find(L"VB-Audio VoiceMeeter") != wstring::npos) + { + if (info->isDefaultDevice()) + defaultDevice = true; + + return true; + } + + return false; + }), list.end()); + + bool anyInstalled = false; + for (unsigned i = 0; i < outputCount; i++) + { + wstringstream sstream; + sstream << "Output A" << (i + 1); + shared_ptr info = *list.insert(list.begin() + i, make_shared(sstream.str(), true)); + if (info->isInstalled()) + anyInstalled = true; + } + + if (defaultDevice) + { + for (unsigned i = 0; i < outputCount; i++) + { + shared_ptr& info = (shared_ptr&)list[i]; + if (!anyInstalled || info->isInstalled()) + { + info->defaultDevice = true; + break; + } + } + } + } + else + { + // Voicemeeter was uninstalled but Voicemeeter Client might still be installed + wstring startupFilePath = getStartupPath(); + wstring argString = getLinkArgs(startupFilePath); + vector args = splitArgs(argString); + + int i = 0; + for (wstring arg : args) + { + shared_ptr info = *list.insert(list.begin() + i, make_shared(arg, false)); + i++; + } + } +} + +VoicemeeterAPOInfo::VoicemeeterAPOInfo(const wstring& connectionName, bool voicemeeterInstalled) + : connectionName(connectionName), voicemeeterInstalled(voicemeeterInstalled) +{ + wstring startupFilePath = getStartupPath(); + wstring path; + wstring argString = getLinkArgs(startupFilePath, &path); + vector args = splitArgs(argString); + installed = find(args.begin(), args.end(), connectionName) != args.end(); + + sampleRate = 48000; + try + { + if (RegistryHelper::keyExists(voicemeeterClientKeyPath) && RegistryHelper::valueExists(voicemeeterClientKeyPath, sampleRateValueName)) + sampleRate = (unsigned)RegistryHelper::readDWORDValue(voicemeeterClientKeyPath, sampleRateValueName); + } + catch (RegistryException e) + { + // ignore + } + + wstring clientPath = getClientPath(); + changes = (path != clientPath); +} + +wstring VoicemeeterAPOInfo::getConnectionName() const +{ + return connectionName; +} + +wstring VoicemeeterAPOInfo::getDeviceName() const +{ + return L"Voicemeeter"; +} + +wstring VoicemeeterAPOInfo::getDeviceGuid() const +{ + return L""; +} + +wstring VoicemeeterAPOInfo::getDeviceString() const +{ + return getConnectionName() + L" " + getDeviceName(); +} + +unsigned VoicemeeterAPOInfo::getChannelCount() const +{ + return 8; +} + +unsigned VoicemeeterAPOInfo::getSampleRate() const +{ + return sampleRate; +} + +unsigned long VoicemeeterAPOInfo::getChannelMask() const +{ + return KSAUDIO_SPEAKER_7POINT1_SURROUND; +} + +bool VoicemeeterAPOInfo::isInput() const +{ + return false; +} + +bool VoicemeeterAPOInfo::isInstalled() const +{ + return installed; +} + +bool VoicemeeterAPOInfo::canBeUpgraded() const +{ + return false; +} + +bool VoicemeeterAPOInfo::hasChanges() const +{ + return changes; +} + +bool VoicemeeterAPOInfo::isExperimental() const +{ + return false; +} + +bool VoicemeeterAPOInfo::isEnhancementsDisabled() const +{ + return false; +} + +bool VoicemeeterAPOInfo::isDefaultDevice() const +{ + return defaultDevice; +} + +bool VoicemeeterAPOInfo::isVoicemeeterInstalled() const +{ + return voicemeeterInstalled; +} + +void VoicemeeterAPOInfo::install() +{ + wstring startupFilePath = getStartupPath(); + wstring argString = getLinkArgs(startupFilePath); + vector args = splitArgs(argString); + vector::iterator it = find(args.begin(), args.end(), connectionName); + if (it == args.end()) + args.push_back(connectionName); + sort(args.begin(), args.end()); + argString = joinArgs(args); + + wstring clientPath = getClientPath(); + + createLink(startupFilePath, clientPath, argString); +} + +void VoicemeeterAPOInfo::uninstall() +{ + wstring startupFilePath = getStartupPath(); + wstring argString = getLinkArgs(startupFilePath); + vector args = splitArgs(argString); + vector::iterator it = find(args.begin(), args.end(), connectionName); + if (it != args.end()) + args.erase(it); + if (args.size() > 0) + { + sort(args.begin(), args.end()); + argString = joinArgs(args); + + wchar_t filename[MAX_PATH]; + GetModuleFileNameW(NULL, filename, ARRAYSIZE(filename)); + PathRemoveFileSpecW(filename); + wstring clientPath = filename; + clientPath = clientPath + L"\\" + clientFilename; + + createLink(startupFilePath, clientPath, argString); + } + else + { + DeleteFileW(startupFilePath.c_str()); + } +} + +void VoicemeeterAPOInfo::reinstall() +{ + uninstall(); + install(); +} + +wstring VoicemeeterAPOInfo::getStartupPath() +{ + PWSTR startupPath; + SHGetKnownFolderPath(FOLDERID_Startup, KF_FLAG_DONT_UNEXPAND, NULL, &startupPath); + wstring result(startupPath); + + CoTaskMemFree(startupPath); + + return result + L"\\" + startupFilename; +} + +wstring VoicemeeterAPOInfo::getClientPath() +{ + wchar_t filename[MAX_PATH]; + GetModuleFileNameW(NULL, filename, ARRAYSIZE(filename)); + PathRemoveFileSpecW(filename); + wstring clientPath = filename; + clientPath = clientPath + L"\\" + clientFilename; + + return clientPath; +} + +void VoicemeeterAPOInfo::createLink(const wstring& lnkPath, const wstring& path, const wstring& args) +{ + IShellLink* shellLink; + HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&shellLink); + if (SUCCEEDED(hr)) + { + shellLink->SetPath(path.c_str()); + shellLink->SetArguments(args.c_str()); + IPersistFile* persistFile; + hr = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); + if (SUCCEEDED(hr)) + { + hr = persistFile->Save(lnkPath.c_str(), TRUE); + persistFile->Release(); + } + + shellLink->Release(); + } +} + +wstring VoicemeeterAPOInfo::getLinkArgs(const wstring& lnkPath, wstring* path) +{ + wstring result; + + IShellLink* shellLink; + HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&shellLink); + if (SUCCEEDED(hr)) + { + IPersistFile* persistFile; + hr = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); + if (SUCCEEDED(hr)) + { + hr = persistFile->Load(lnkPath.c_str(), STGM_READ); + if (SUCCEEDED(hr)) + { + wchar_t buf[MAX_PATH]; + hr = shellLink->GetArguments(buf, ARRAYSIZE(buf)); + if (SUCCEEDED(hr)) + result = buf; + + if (path != NULL) + { + hr = shellLink->GetPath(buf, ARRAYSIZE(buf), NULL, 0); + if (SUCCEEDED(hr)) + *path = buf; + } + } + persistFile->Release(); + } + + shellLink->Release(); + } + + return result; +} + +vector VoicemeeterAPOInfo::splitArgs(const wstring& argString) +{ + vector result; + + if (argString.length() > 0) + { + int argc; + wchar_t** argv = CommandLineToArgvW(argString.c_str(), &argc); + for (int i = 0; i < argc; i++) + result.push_back(argv[i]); + LocalFree(argv); + } + + return result; +} + +wstring VoicemeeterAPOInfo::joinArgs(const vector& args) +{ + wstring result; + for (const wstring& arg : args) + { + if (result.length() > 0) + result += L" "; + + if (arg.find(' ') != wstring::npos) + result += L"\"" + arg + L"\""; + else + result += arg; + } + + return result; +} + +void VoicemeeterAPOInfo::ensureVoicemeeterClientRunning() +{ + wstring startupFilePath = getStartupPath(); + wstring argString = getLinkArgs(startupFilePath); + vector args = splitArgs(argString); + wstring clientPath = getClientPath(); + + HANDLE tokenHandle; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &tokenHandle)) + throw RegistryException(L"Error in OpenProcessToken while taking ownership"); + + LUID luid; + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) + throw RegistryException(L"Error in LookupPrivilegeValue while taking ownership"); + + TOKEN_PRIVILEGES tp; + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(tokenHandle, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) + throw RegistryException(L"Error in AdjustTokenPrivileges while taking ownership"); + + HMODULE module = LoadLibraryW(L"ntdll.dll"); + if (module == NULL) + throw exception("Could not load ntdll.dll"); + SCOPE_EXIT {FreeLibrary(module);}; + + pfnNtQueryInformationProcess NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(module, "NtQueryInformationProcess"); + if (NtQueryInformationProcess == NULL) + throw exception("Function NtQueryInformationProcess not found in ntdll.dll"); + + HANDLE snapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshotHandle == INVALID_HANDLE_VALUE) + throw exception("Could not take a snapshot of all processes"); + SCOPE_EXIT {CloseHandle(snapshotHandle);}; + + bool matchingProcessExists = false; + + PROCESSENTRY32W entry; + entry.dwSize = sizeof(PROCESSENTRY32W); + bool loop = Process32FirstW(snapshotHandle, &entry) != 0; + while (loop) + { + if (wcscmp(entry.szExeFile, clientFilename) == 0) + { + HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, entry.th32ProcessID); + if (processHandle == INVALID_HANDLE_VALUE) + throw exception("Could not open process"); + SCOPE_EXIT {CloseHandle(processHandle);}; + + PROCESS_BASIC_INFORMATION basicInformation; + NTSTATUS status = NtQueryInformationProcess(processHandle, ProcessBasicInformation, &basicInformation, sizeof(basicInformation), NULL); + if (status < 0) + throw exception("Could not query process information"); + + _PEB peb; + if (!ReadProcessMemory(processHandle, basicInformation.PebBaseAddress, &peb, sizeof(peb), NULL)) + throw exception("Could not read peb from process memory"); + + RTL_USER_PROCESS_PARAMETERS processParams; + if (!ReadProcessMemory(processHandle, peb.ProcessParameters, &processParams, sizeof(processParams), NULL)) + throw exception("Could not read process parameters from process memory"); + + wchar_t* cmdLineBuf = new wchar_t[processParams.CommandLine.Length / sizeof(wchar_t)]; + SCOPE_EXIT {delete cmdLineBuf;}; + if (!ReadProcessMemory(processHandle, processParams.CommandLine.Buffer, cmdLineBuf, processParams.CommandLine.Length, NULL)) + throw exception("Could not read command line from process memory"); + wstring cmdLine(cmdLineBuf, processParams.CommandLine.Length / sizeof(wchar_t)); + + vector processArgs = splitArgs(cmdLine); + wstring path = processArgs.front(); + processArgs.erase(processArgs.begin()); + + if (path != clientPath || processArgs != args) + closeProcess(entry.th32ProcessID); + else + matchingProcessExists = true; + } + + loop = Process32NextW(snapshotHandle, &entry) != 0; + } + + if (!matchingProcessExists && !args.empty()) + { + ShellExecuteW(NULL, NULL, clientPath.c_str(), argString.c_str(), NULL, SW_SHOWDEFAULT); + } +} + +void VoicemeeterAPOInfo::saveVoicemeeterSampleRate(unsigned sampleRate) +{ + try + { + RegistryHelper::createKey(voicemeeterClientKeyPath); + RegistryHelper::writeDWORDValue(voicemeeterClientKeyPath, sampleRateValueName, sampleRate); + } + catch (RegistryException e) + { + // ignore + } +} + +void VoicemeeterAPOInfo::closeProcess(unsigned long processId) +{ + HANDLE snapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (snapshotHandle == INVALID_HANDLE_VALUE) + throw exception("Could not take a snapshot of all processes"); + SCOPE_EXIT {CloseHandle(snapshotHandle);}; + + THREADENTRY32 entry; + entry.dwSize = sizeof(THREADENTRY32); + bool loop = Thread32First(snapshotHandle, &entry) != 0; + while (loop) + { + if (entry.th32OwnerProcessID == processId) + // will just fail for threads not having a message queue + PostThreadMessageW(entry.th32ThreadID, WM_QUIT, 0, 0); + + loop = Thread32Next(snapshotHandle, &entry) != 0; + } +} diff --git a/VoicemeeterAPOInfo.h b/VoicemeeterAPOInfo.h new file mode 100644 index 0000000..06b2443 --- /dev/null +++ b/VoicemeeterAPOInfo.h @@ -0,0 +1,70 @@ +/* +This file is part of EqualizerAPO, a system-wide equalizer. +Copyright (C) 2017 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 +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#pragma once + +#include +#include +#include +#include "AbstractAPOInfo.h" + +class VoicemeeterAPOInfo : public AbstractAPOInfo +{ +public: + static void prependInfos(std::vector >& list); + + VoicemeeterAPOInfo(const std::wstring& connectionName, bool voicemeeterInstalled); + + std::wstring getConnectionName() const override; + std::wstring getDeviceName() const override; + std::wstring getDeviceGuid() const override; + std::wstring getDeviceString() const override; + unsigned getChannelCount() const override; + unsigned getSampleRate() const override; + unsigned long getChannelMask() const override; + bool isInput() const override; + bool isInstalled() const override; + bool canBeUpgraded() const override; + bool hasChanges() const override; + bool isExperimental() const override; + bool isEnhancementsDisabled() const override; + bool isDefaultDevice() const override; + bool isVoicemeeterInstalled() const; + void install() override; + void uninstall() override; + void reinstall() override; + static void ensureVoicemeeterClientRunning(); + static void saveVoicemeeterSampleRate(unsigned sampleRate); + +private: + static std::wstring getStartupPath(); + static std::wstring getClientPath(); + static void createLink(const std::wstring& lnkPath, const std::wstring& path, const std::wstring& args); + static std::wstring getLinkArgs(const std::wstring& lnkPath, std::wstring* path = NULL); + static std::vector splitArgs(const std::wstring& argString); + static std::wstring joinArgs(const std::vector& args); + static void closeProcess(unsigned long processId); + + std::wstring connectionName; + unsigned sampleRate; + bool installed; + bool defaultDevice = false; + bool voicemeeterInstalled; + bool changes = false; +}; diff --git a/VoicemeeterClient/VoicemeeterClient.cpp b/VoicemeeterClient/VoicemeeterClient.cpp new file mode 100644 index 0000000..6fbf058 --- /dev/null +++ b/VoicemeeterClient/VoicemeeterClient.cpp @@ -0,0 +1,266 @@ +#include "stdafx.h" +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#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 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& 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; +} diff --git a/VoicemeeterClient/VoicemeeterClient.h b/VoicemeeterClient/VoicemeeterClient.h new file mode 100644 index 0000000..a3bbe25 --- /dev/null +++ b/VoicemeeterClient/VoicemeeterClient.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include "FilterEngine.h" +#include "VoicemeeterRemote.h" + +class VoicemeeterClient +{ +public: + VoicemeeterClient(const std::vector& outputs); + ~VoicemeeterClient(); + void start(); + void stop(); + + void handle(long nCommand, void* lpData, long nnn); + bool restart = false; + +private: + bool isBufferSilent(float** sampleData, long sampleCount); + + unsigned long mainThreadId; + T_VBVMR_INTERFACE vmr; + std::vector engines; + std::vector idleSampleCounts; + bool loggedIn = false; + bool banana = false; + std::vector outputs; +}; diff --git a/VoicemeeterClient/VoicemeeterClient.rc b/VoicemeeterClient/VoicemeeterClient.rc new file mode 100644 index 0000000..5cb7e72 Binary files /dev/null and b/VoicemeeterClient/VoicemeeterClient.rc differ diff --git a/VoicemeeterClient/VoicemeeterClient.vcxproj b/VoicemeeterClient/VoicemeeterClient.vcxproj new file mode 100644 index 0000000..54fd5c6 --- /dev/null +++ b/VoicemeeterClient/VoicemeeterClient.vcxproj @@ -0,0 +1,249 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {10F1DD37-EA50-46C2-810D-D01A1A0D4D2F} + Win32Proj + VoicemeeterClient + + + + Application + true + v120 + Unicode + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + $(SolutionDir);C:\Program Files\libsndfile\include;C:\Program Files\muparserx_v3_0_1\parser;C:\Program Files (x86)\fftw3;$(IncludePath) + C:\Program Files (x86)\libsndfile\lib;C:\Program Files\muparserx_v3_0_1\lib;C:\Program Files (x86)\fftw3;$(LibraryPath) + + + true + $(SolutionDir);C:\Program Files\libsndfile\include;C:\Program Files\muparserx_v3_0_1\parser;C:\Program Files\fftw3;$(IncludePath) + C:\Program Files\libsndfile\lib;C:\Program Files\muparserx_v3_0_1\lib64;C:\Program Files\fftw3;$(LibraryPath) + + + false + $(SolutionDir);C:\Program Files\libsndfile\include;C:\Program Files\muparserx_v3_0_1\parser;C:\Program Files (x86)\fftw3;$(IncludePath) + C:\Program Files (x86)\libsndfile\lib;C:\Program Files\muparserx_v3_0_1\lib;C:\Program Files (x86)\fftw3;$(LibraryPath) + + + false + $(SolutionDir);C:\Program Files\libsndfile\include;C:\Program Files\muparserx_v3_0_1\parser;C:\Program Files\fftw3;$(IncludePath) + C:\Program Files\libsndfile\lib;C:\Program Files\muparserx_v3_0_1\lib64;C:\Program Files\fftw3;$(LibraryPath) + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + Windows + true + muparserxd.lib;version.lib;Shlwapi.lib;authz.lib;libsndfile-1.lib;libfftw3f-3.lib;%(AdditionalDependencies) + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + Windows + true + muparserxd.lib;version.lib;Shlwapi.lib;authz.lib;libsndfile-1.lib;libfftw3f-3.lib;%(AdditionalDependencies) + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + Fast + + + Windows + true + true + true + muparserx.lib;version.lib;Shlwapi.lib;authz.lib;libsndfile-1.lib;libfftw3f-3.lib;%(AdditionalDependencies) + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + Fast + + + Windows + true + true + true + muparserx.lib;version.lib;Shlwapi.lib;authz.lib;libsndfile-1.lib;libfftw3f-3.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + \ No newline at end of file diff --git a/VoicemeeterClient/VoicemeeterClient.vcxproj.filters b/VoicemeeterClient/VoicemeeterClient.vcxproj.filters new file mode 100644 index 0000000..f51dde6 --- /dev/null +++ b/VoicemeeterClient/VoicemeeterClient.vcxproj.filters @@ -0,0 +1,239 @@ + + + + + {0e9ead24-f831-4803-ab47-9b5208987e65} + + + {e112c6dd-61fc-4742-808b-298e228688ed} + + + {171427eb-7308-4b30-88c5-27068dd8a4f2} + + + {898022d7-bfa6-4240-b443-e82e638cf0f6} + + + + + helpers + + + + helpers + + + + + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + parser + + + parser + + + parser + + + libHybridConv-0.1.1 + + + + + helpers + + + helpers + + + helpers + + + helpers + + + + + + helpers + + + helpers + + + + + helpers + + + + helpers + + + + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + filters + + + parser + + + parser + + + parser + + + libHybridConv-0.1.1 + + + + helpers + + + helpers + + + helpers + + + helpers + + + + + + + + \ No newline at end of file diff --git a/VoicemeeterClient/VoicemeeterRemote.h b/VoicemeeterClient/VoicemeeterRemote.h new file mode 100644 index 0000000..179eb23 --- /dev/null +++ b/VoicemeeterClient/VoicemeeterRemote.h @@ -0,0 +1,775 @@ +/******************************************************************************/ +/* Voicemeeter Remote API. V.Burel©2015 */ +/******************************************************************************/ +/* This Library allows communication with Voicemeeter Applications */ +/* 4 Client Applications can be connected to remote Voicemeeter. */ +/******************************************************************************/ +/* */ +/* OFFICIAL LINK : WWW.VOICEMEETER.COM */ +/* */ +/******************************************************************************/ +/* COPYRIGHT: Vincent Burel(c)2015 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). */ +/******************************************************************************/ +/* long = 32 bit integer */ +/******************************************************************************/ + + +#ifndef __VOICEMEETER_REMOTE_H__ +#define __VOICEMEETER_REMOTE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + + +#define VBVMR_RESULT_OK 0 + +/******************************************************************************/ +/* */ +/* Login */ +/* */ +/******************************************************************************/ + +/** @name Communication Login / logout +* @{ */ + + /** + @brief Open Communication Pipe With Voicemeeter (typically called on software startup). + @return : 0: OK (no error). + 1: OK but Voicemeeter Application not launched. + -1: cannot get client (unexpected) + -2: unexpected login (logout was expected before). + */ + +long __stdcall VBVMR_Login(void); + + /** + @brief Close Communication Pipe With Voicemeeter (typically called on software end). + @return : 0 if ok. + */ + +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). + @return : 0: Ok. + -1: not installed + */ + +long __stdcall VBVMR_RunVoicemeeter(long vType); + + +/** @} */ + + + + + + + + + + + +/******************************************************************************/ +/* */ +/* General Information */ +/* */ +/******************************************************************************/ + +/** @name General Information +* @{ */ + + /** + @brief Get Voicemeeter Type + @param pType : Pointer on 32bit long receiving the type (1 = Voicemeeter, 2= Voicemeeter Banana). + + VOICEMEETER STRIP/BUS INDEX ASSIGNMENT + + | Strip 1 | Strip 2 |Virtual Input| BUS A | BUS B | + +---------+---------+-------------+---------+---------+ + | 0 | 1 | 2 | 0 | 1 | + + VOICEMEETER BANANA STRIP/BUS INDEX ASSIGNMENT + + | Strip 1 | Strip 2 | Strip 2 |Virtual Input|Virtual AUX|BUS A1|BUS A2|BUS A3|BUS B1|BUS B2| + +---------+---------+---------+-------------+-----------+------+------+------+------+------+ + | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 2 | 3 | 4 | + + + + @return : 0: OK (no error). + -1: cannot get client (unexpected) + -2: no server. + */ + +long __stdcall VBVMR_GetVoicemeeterType(long * pType); + + /** + @brief Get Voicemeeter Version + @param pType : Pointer on 32bit integer receiving the version (v1.v2.v3.v4) + v1 = (version & 0xFF000000)>>24; + v2 = (version & 0x00FF0000)>>16; + v3 = (version & 0x0000FF00)>>8; + v4 = version & 0x000000FF; + + @return : 0: OK (no error). + -1: cannot get client (unexpected) + -2: no server. + */ + +long __stdcall VBVMR_GetVoicemeeterVersion(long * pVersion); + + +/** @} */ + + + + + + + + + + + + + + + + + + +/******************************************************************************/ +/* */ +/* Get parameters */ +/* */ +/******************************************************************************/ + +/** @name Getting Parameters +* @{ */ + + /** + @brief Check if parameters have changed. + Call this function periodically (typically every 10 or 20ms). + (this function must be called from one thread only) + + @return: 0: no new paramters. + 1: New parameters -> update your display. + -1: error (unexpected) + -2: no server. + */ + +long __stdcall VBVMR_IsParametersDirty(void); + + /** + @brief get parameter value. + @param szParamName : Null Terminal ASCII String giving the name of the parameter (see parameters name table) + @param pValue : Pointer on float (32bit float by reference) receiving the wanted value. + @return : 0: OK (no error). + -1: error + -2: no server. + -3: unknown parameter + -5: structure mismatch + */ + +long __stdcall VBVMR_GetParameterFloat(char * szParamName, float * pValue); + + /** + @brief get parameter value. + @param szParamName : Null Terminal ASCII String giving the name of the parameter (see parameters name table) + @param pValue : Pointer on String (512 char or wchar) receiving the wanted value. + @return : 0: OK (no error). + -1: error + -2: no server. + -3: unknown parameter + -5: structure mismatch + */ + +long __stdcall VBVMR_GetParameterStringA(char * szParamName, char * szString); +long __stdcall VBVMR_GetParameterStringW(char * szParamName, unsigned short * wszString); + +/** @} */ + + + + + + + + + +/******************************************************************************/ +/* */ +/* Get levels */ +/* */ +/******************************************************************************/ + +/** @name Getting RT Data +* @{ */ + + /** + @brief Get Current levels. + (this function must be called from one thread only) + + @param nType: 0= pre fader input levels. + 1= post fader input levels. + 2= post Mute input levels. + 3= output levels. + + @param nuChannel: audio channel zero based index + for input 0 = in#1 left, 1= in#1 Right, etc... + for output 0 = busA ch1, 1 = busA ch2... + + VOICEMEETER CHANNEL ASSIGNMENT + + | Strip 1 | Strip 2 | Virtual Input | + +----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | + + | Output A1 / A2 | Virtual Output | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | + + VOICEMEETER BANANA CHANNEL ASSIGNMENT + + | Strip 1 | Strip 2 | Strip 3 | Virtual Input | Virtual Input AUX | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | + + | Output A1 | Output A2 | Output A3 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | + + | Virtual Output B1 | Virtual Output B2 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | + + + @return : 0: OK (no error). + -1: error + -2: no server. + -3: no level available + -4: out of range + */ + +long __stdcall VBVMR_GetLevel(long nType, long nuChannel, float * pValue); + + + /** + @brief Get MIDI message from M.I.D.I. input device used by Voicemeeter M.I.D.I. mapping. + (this function must be called from one thread only) + + @param pMIDIBuffer: pointer on MIDI Buffer. Expected message size is below 4 bytes, + but it's recommended to use 1024 Bytes local buffer to receive + possible multiple M.I.D.I. event message in optimal way: + unsigned char pBuffer[1024]; + + + @return : >0: number of bytes placed in buffer (2 or 3 byte for usual M.I.D.I. message) + -1: error + -2: no server. + -5: no MIDI data + -6: no MIDI data + */ + + +long __stdcall VBVMR_GetMidiMessage(unsigned char *pMIDIBuffer, long nbByteMax); + + +/** @} */ + + + + + + + + + + +/******************************************************************************/ +/* */ +/* Set Parameters */ +/* */ +/******************************************************************************/ + +/** @name Setting Parameters +* @{ */ + + /** + @brief Set a single float 32 bits parameters . + @param szParamName : Null Terminal ASCII String giving the name of the parameter (see parameters name table) + example: + Strip[1].gain + Strip[0].mute + Bus[0].gain + Bus[0].eq.channel[0].cell[0].gain + + @param pValue : float 32bit containing the new value. + @return : 0: OK (no error). + -1: error + -2: no server. + -3: unknown parameter + */ + +long __stdcall VBVMR_SetParameterFloat(char * szParamName, float Value); + + + + /** + @brief Set a single string parameters . + @param szParamName : Null Terminal ASCII String giving the name of the parameter (see parameters name table) + example: + Strip[1].name + Strip[0].device.mme + Bus[0].device.asio + + @param szString : zero terminal string. + @return : 0: OK (no error). + -1: error + -2: no server. + -3: unknown parameter + + */ + + +long __stdcall VBVMR_SetParameterStringA(char * szParamName, char * szString); +long __stdcall VBVMR_SetParameterStringW(char * szParamName, unsigned short * wszString); + + + + + /** + @brief Set one or several parameters by a script ( < 48 kB ). + @param szParamName : Null Terminal ASCII String giving the script + (script allows to change several parameters in the same time - SYNCHRO). + Possible Instuction separators: ',' ';' or '\n'(CR) + EXAMPLE: + "Strip[0].gain = -6.0 + Strip[0].A1 = 0 + Strip[0].B1 = 1 + Strip[1].gain = -6.0 + Strip[2].gain = 0.0 + Strip[3].name = "Skype Caller" " + + @return : 0: OK (no error). + >0: number of line causing script error. + -1: error + -2: no server. + -3: unexpected error + -4: unexpected error + */ + +long __stdcall VBVMR_SetParameters(char * szParamScript); +long __stdcall VBVMR_SetParametersW(unsigned short * szParamScript); + + +/** @} */ + + + + + + + + + + + +/******************************************************************************/ +/* DEVICES ENUMERATOR */ +/******************************************************************************/ + + +/** @name Device Enumeration Functions +* @{ */ + +#define VBVMR_DEVTYPE_MME 1 +#define VBVMR_DEVTYPE_WDM 3 +#define VBVMR_DEVTYPE_KS 4 +#define VBVMR_DEVTYPE_ASIO 5 + + /** + @brief Get number of Audio Output Device available on the system + @return : return number of device found. + */ + +long __stdcall VBVMR_Output_GetDeviceNumber(void); + + /** + @brief Return pointer on Output Device Descriptor according index + @param zindex : zero based index + @param nType : Pointer on 32bit long receiving the type (pointer can be NULL). + @param szName : Pointer on string (256 char min) receiving the device name (pointer can be NULL). + @param szHardwareId : Pointer on string (256 char min) receiving the hardware ID (pointer can be NULL). + @return : 0: OK (no error). + */ + +long __stdcall VBVMR_Output_GetDeviceDescA(long zindex, long * nType, char * szDeviceName, char * szHardwareId); +long __stdcall VBVMR_Output_GetDeviceDescW(long zindex, long * nType, unsigned short * wszDeviceName, unsigned short * wszHardwareId); + + /** + @brief Get number of Audio Input Device available on the system + @return : return number of device found. + */ + +long __stdcall VBVMR_Input_GetDeviceNumber(void); + + /** + @brief Return pointer on Input Device Descriptor according index + @param zindex : zero based index + @param nType : Pointer on 32bit long receiving the type (pointer can be NULL). + @param szName : Pointer on string (256 char min) receiving the device name (pointer can be NULL). + @param szHardwareId : Pointer on string (256 char min) receiving the hardware ID (pointer can be NULL). + @return : 0: OK (no error). + */ + +long __stdcall VBVMR_Input_GetDeviceDescA(long zindex, long * nType, char * szDeviceName, char * szHardwareId); +long __stdcall VBVMR_Input_GetDeviceDescW(long zindex, long * nType, unsigned short * wszDeviceName, unsigned short * wszHardwareId); + + + +/** @} */ + + + + + + +/******************************************************************************/ +/* VB-AUDIO CALLBACK */ +/******************************************************************************/ +/* 4x Functions to process all voicemeeter audio input and output channels */ +/* */ +/* VBVMR_AudioCallbackRegister :to register your audio callback(s) */ +/* VBVMR_AudioCallbackStart :to start the audio stream */ +/* VBVMR_AudioCallbackStop :to stop the audio stream */ +/* VBVMR_AudioCallbackUnregister :to unregister / Release callback(s) */ +/******************************************************************************/ + +/** @name VB-Audio Callback Functions +* @{ */ + + +typedef struct tagVBVMR_AUDIOINFO +{ + long samplerate; + long nbSamplePerFrame; +} VBVMR_T_AUDIOINFO, *VBVMR_PT_AUDIOINFO, *VBVMR_LPT_AUDIOINFO; + + +typedef struct tagVBVMR_AUDIOBUFFER +{ + long audiobuffer_sr; //Sampling Rate + long audiobuffer_nbs; //number of sample per frame + long audiobuffer_nbi; //number of inputs + long audiobuffer_nbo; //number of outputs + float * audiobuffer_r[128]; //nbi input pointers containing frame of nbs sample (of 32bits float) + float * audiobuffer_w[128]; //nbo output pointers containing frame of nbs sample (of 32bits float) +} VBVMR_T_AUDIOBUFFER, *VBVMR_PT_AUDIOBUFFER, *VBVMR_LPT_AUDIOBUFFER; + + + /** + @brief VB-AUDIO Callback is called for different task to Initialize, perform and end your process. + VB-AUDIO Callback is part of single TIME CRITICAL Thread. + VB-AUDIO Callback is non re-entrant (cannot be called while in process) + VB-AUDIO Callback is supposed to be REAL TIME when called to process buffer. + (it means that the process has to be performed as fast as possible, waiting cycles are forbidden. + do not use O/S synchronization object, even Critical_Section can generate waiting cycle. Do not use + system functions that can generate waiting cycle like display, disk or communication functions for example). + + @param lpUser: User pointer given on callback registration. + @param ncommand: reason why the callback is called. + @param lpData: pointer on structure, pending on nCommand. + @param nnn: additional data, unused + + @return : 0: always 0 (unused). + */ + + +typedef long (__stdcall *T_VBVMR_VBAUDIOCALLBACK)(void * lpUser, long nCommand, void * lpData, long nnn); + + +#define VBVMR_CBCOMMAND_STARTING 1 //command to initialize data according SR and buffer size + //info = (VBVMR_LPT_AUDIOINFO)lpData + +#define VBVMR_CBCOMMAND_ENDING 2 //command to release data +#define VBVMR_CBCOMMAND_CHANGE 3 //If change in audio stream, you will have to restart audio + +#define VBVMR_CBCOMMAND_BUFFER_IN 10 //input insert +#define VBVMR_CBCOMMAND_BUFFER_OUT 11 //bus output insert +#define VBVMR_CBCOMMAND_BUFFER_MAIN 20 //all i/o + //audiobuffer = (VBVMR_LPT_AUDIOBUFFER)lpData + //nnn = synchro = 1 if synchro with Voicemeeter + +/* + ----------------------------------------------------- + AUDIO BUFFER for VBVMR_CBCOMMAND_BUFFER_IN + ----------------------------------------------------- + VOICEMEETER + + | Strip 1 | Strip 2 | Virtual Input | + +----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | + + VOICEMEETER BANANA + + | Strip 1 | Strip 2 | Strip 3 | Virtual Input | Virtual Input AUX | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | + + VOICEMEETER 8 + + | Strip 1 | Strip 2 | Strip 3 | Strip 4 | Strip 5 | Virtual Input | Virtual Input AUX | Virtual Input 8 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 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 | + + +----------------------------------------------------- + AUDIO BUFFER for VBVMR_CBCOMMAND_BUFFER_OUT +----------------------------------------------------- + VOICEMEETER + + | Output A1 / A2 | Virtual Output | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | + + VOICEMEETER BANANA + + | Output A1 | Output A2 | Output A3 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | + + | Virtual Output B1 | Virtual Output B2 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | + + + VOICEMEETER 8 + + | 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 | + + +----------------------------------------------------- + AUDIO BUFFER for VBVMR_CBCOMMAND_BUFFER_MAIN +----------------------------------------------------- + VOICEMEETER + + | Strip 1 | Strip 2 | Virtual Input | + +----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | + + | Output A1 / A2 | Virtual Output | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | + Output buffer provides outputs only: + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | + + VOICEMEETER BANANA + + | Strip 1 | Strip 2 | Strip 3 | Virtual Input | Virtual Input AUX | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | + + | Output A1 | Output A2 | Output A3 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | + Output buffer provides outputs only: + | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | + + | Virtual Output B1 | Virtual Output B2 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | + Output buffer provides outputs only: + | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | + + + VOICEMEETER 8 + + | Strip 1 | Strip 2 | Strip 3 | Strip 4 | Strip 5 | Virtual Input | Virtual Input AUX | Virtual Input 8 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 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 | + + | Output A1 | Output A2 | Output A3 | Output A4 | Output A5 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | + Output buffer provides outputs only: + | 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 | + +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ + | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | + Output buffer provides outputs only: + | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | + + +*/ + + /** + @brief register your audio callback function to receive real time audio buffer + it's possible to register up to 3x different Audio Callback in the same application or in + 3x different applications. In the same application, this is possible because Voicemeeter + provides 3 kind of audio Streams: + - AUDIO INPUT INSERT (to process all Voicemeeter inputs as insert) + - AUDIO OUTPUT INSERT (to process all Voicemeeter BUS outputs as insert) + - ALL AUDIO I/O (to process all Voicemeeter i/o). + Note: a single callback can be used to receive the 3 possible audio streams. + + @param mode : callback type (main, input or bus output) see define below + @param pCallback : Pointer on your callback function. + @param lpUser : user pointer (pointer that will be passed in callback first argument). + @param szClientName[64]: IN: Name of the application registering the Callback. + OUT: Name of the application already registered. + @return : 0: OK (no error). + -1: error + 1: callback already registered (by another application). + */ + +long __stdcall VBVMR_AudioCallbackRegister(long mode, T_VBVMR_VBAUDIOCALLBACK pCallback, void * lpUser, char szClientName[64]); + +#define VBVMR_AUDIOCALLBACK_IN 0x00000001 //to process input insert +#define VBVMR_AUDIOCALLBACK_OUT 0x00000002 //to process output bus insert +#define VBVMR_AUDIOCALLBACK_MAIN 0x00000004 //to receive all i/o + + /** + @brief Start / Stop Audio processing + + he Callback will be called with + @return : 0: OK (no error). + -1: error + -2: no callback registred. + */ + +long __stdcall VBVMR_AudioCallbackStart(void); +long __stdcall VBVMR_AudioCallbackStop(void); + + + /** + @brief unregister your callback to release voicemeeter virtual driver + (this function will automatically call VBVMR_AudioCallbackStop() function) + @param pCallback : Pointer on your callback function. + @return : 0: OK (no error). + -1: error + 1: callback already unregistered. + */ + +long __stdcall VBVMR_AudioCallbackUnregister(void); + + + +/** @} */ + + + + + + + + + + + + +/******************************************************************************/ +/* 'C' STRUCTURED INTERFACE */ +/******************************************************************************/ + +typedef long (__stdcall *T_VBVMR_Login)(void); +typedef long (__stdcall *T_VBVMR_Logout)(void); +typedef long (__stdcall *T_VBVMR_RunVoicemeeter)(long vType); + +typedef long (__stdcall *T_VBVMR_GetVoicemeeterType)(long * pType); +typedef long (__stdcall *T_VBVMR_GetVoicemeeterVersion)(long * pVersion); + +typedef long (__stdcall *T_VBVMR_IsParametersDirty)(void); +typedef long (__stdcall *T_VBVMR_GetParameterFloat)(char * szParamName, float * pValue); +typedef long (__stdcall *T_VBVMR_GetParameterStringA)(char * szParamName, char * szString); +typedef long (__stdcall *T_VBVMR_GetParameterStringW)(char * szParamName, unsigned short * wszString); + + +typedef long (__stdcall *T_VBVMR_GetLevel)(long nType, long nuChannel, float * pValue); +typedef long (__stdcall *T_VBVMR_GetMidiMessage)(unsigned char *pMIDIBuffer, long nbByteMax); + +typedef long (__stdcall *T_VBVMR_SetParameterFloat)(char * szParamName, float Value); +typedef long (__stdcall *T_VBVMR_SetParameters)(char * szParamScript); +typedef long (__stdcall *T_VBVMR_SetParametersW)(unsigned short * szParamScript); +typedef long (__stdcall *T_VBVMR_SetParameterStringA)(char * szParamName, char * szString); +typedef long (__stdcall *T_VBVMR_SetParameterStringW)(char * szParamName, unsigned short * wszString); + +typedef long (__stdcall *T_VBVMR_Output_GetDeviceNumber)(void); +typedef long (__stdcall *T_VBVMR_Output_GetDeviceDescA)(long zindex, long * nType, char * szDeviceName, char * szHardwareId); +typedef long (__stdcall *T_VBVMR_Output_GetDeviceDescW)(long zindex, long * nType, unsigned short * wszDeviceName, unsigned short * wszHardwareId); +typedef long (__stdcall *T_VBVMR_Input_GetDeviceNumber)(void); +typedef long (__stdcall *T_VBVMR_Input_GetDeviceDescA)(long zindex, long * nType, char * szDeviceName, char * szHardwareId); +typedef long (__stdcall *T_VBVMR_Input_GetDeviceDescW)(long zindex, long * nType, unsigned short * wszDeviceName, unsigned short * wszHardwareId); + +typedef long (__stdcall *T_VBVMR_AudioCallbackRegister)(long mode, T_VBVMR_VBAUDIOCALLBACK pCallback, void * lpUser, char szClientName[64]); +typedef long (__stdcall *T_VBVMR_AudioCallbackStart)(void); +typedef long (__stdcall *T_VBVMR_AudioCallbackStop)(void); +typedef long (__stdcall *T_VBVMR_AudioCallbackUnregister)(void); + + +typedef struct tagVBVMR_INTERFACE +{ + T_VBVMR_Login VBVMR_Login; + T_VBVMR_Logout VBVMR_Logout; + T_VBVMR_RunVoicemeeter VBVMR_RunVoicemeeter; + T_VBVMR_GetVoicemeeterType VBVMR_GetVoicemeeterType; + T_VBVMR_GetVoicemeeterVersion VBVMR_GetVoicemeeterVersion; + T_VBVMR_IsParametersDirty VBVMR_IsParametersDirty; + T_VBVMR_GetParameterFloat VBVMR_GetParameterFloat; + T_VBVMR_GetParameterStringA VBVMR_GetParameterStringA; + T_VBVMR_GetParameterStringW VBVMR_GetParameterStringW; + + T_VBVMR_GetLevel VBVMR_GetLevel; + T_VBVMR_GetMidiMessage VBVMR_GetMidiMessage; + + T_VBVMR_SetParameterFloat VBVMR_SetParameterFloat; + T_VBVMR_SetParameters VBVMR_SetParameters; + T_VBVMR_SetParametersW VBVMR_SetParametersW; + T_VBVMR_SetParameterStringA VBVMR_SetParameterStringA; + T_VBVMR_SetParameterStringW VBVMR_SetParameterStringW; + + T_VBVMR_Output_GetDeviceNumber VBVMR_Output_GetDeviceNumber; + T_VBVMR_Output_GetDeviceDescA VBVMR_Output_GetDeviceDescA; + T_VBVMR_Output_GetDeviceDescW VBVMR_Output_GetDeviceDescW; + T_VBVMR_Input_GetDeviceNumber VBVMR_Input_GetDeviceNumber; + T_VBVMR_Input_GetDeviceDescA VBVMR_Input_GetDeviceDescA; + T_VBVMR_Input_GetDeviceDescW VBVMR_Input_GetDeviceDescW; + + T_VBVMR_AudioCallbackRegister VBVMR_AudioCallbackRegister; + T_VBVMR_AudioCallbackStart VBVMR_AudioCallbackStart; + T_VBVMR_AudioCallbackStop VBVMR_AudioCallbackStop; + T_VBVMR_AudioCallbackUnregister VBVMR_AudioCallbackUnregister; + +} T_VBVMR_INTERFACE, *PT_VBVMR_INTERFACE, *LPT_VBVMR_INTERFACE; + +#ifdef VBUSE_LOCALLIB + // internal used (not public) + void __stdcall VBVMR_SetHinstance(HINSTANCE hinst); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /*__VOICEMEETER_REMOTE_H__*/ + + diff --git a/VoicemeeterClient/resource.h b/VoicemeeterClient/resource.h new file mode 100644 index 0000000..a92fd12 --- /dev/null +++ b/VoicemeeterClient/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by VoicemeeterClient.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/build.bat b/build.bat index 478caa6..b9644f7 100644 --- a/build.bat +++ b/build.bat @@ -7,7 +7,7 @@ if %ERRORLEVEL% GEQ 1 goto done if not exist build-Editor-Desktop_Qt_5_7_MSVC2013_32bit-Release mkdir build-Editor-Desktop_Qt_5_7_MSVC2013_32bit-Release cd build-Editor-Desktop_Qt_5_7_MSVC2013_32bit-Release call "%VCINSTALLDIR%vcvarsall.bat" x86 -"C:\Qt\5.7\msvc2013\bin\qmake.exe" ..\Editor\Editor.pro -r -spec win32-msvc2013 +"C:\Qt\5.7\msvc2013\bin\qmake.exe" ..\Editor\Editor.pro -r -spec win32-msvc2013 "CONFIG+=release" "C:\Qt\Tools\QtCreator\bin\jom.exe" clean "C:\Qt\Tools\QtCreator\bin\jom.exe" if %ERRORLEVEL% GEQ 1 goto done @@ -16,7 +16,7 @@ cd.. if not exist build-Editor-Desktop_Qt_5_7_MSVC2013_64bit-Release mkdir build-Editor-Desktop_Qt_5_7_MSVC2013_64bit-Release cd build-Editor-Desktop_Qt_5_7_MSVC2013_64bit-Release call "%VCINSTALLDIR%vcvarsall.bat" amd64 -"C:\Qt\5.7\msvc2013_64\bin\qmake.exe" ..\Editor\Editor.pro -r -spec win32-msvc2013 +"C:\Qt\5.7\msvc2013_64\bin\qmake.exe" ..\Editor\Editor.pro -r -spec win32-msvc2013 "CONFIG+=release" "C:\Qt\Tools\QtCreator\bin\jom.exe" clean "C:\Qt\Tools\QtCreator\bin\jom.exe" if %ERRORLEVEL% GEQ 1 goto done diff --git a/filters/DeviceFilterFactory.cpp b/filters/DeviceFilterFactory.cpp index d4f3d60..9d520c5 100644 --- a/filters/DeviceFilterFactory.cpp +++ b/filters/DeviceFilterFactory.cpp @@ -30,7 +30,7 @@ using namespace std; #ifndef NO_FILTERENGINE void DeviceFilterFactory::initialize(FilterEngine* engine) { - deviceString = engine->getDeviceName() + L" " + engine->getConnectionName() + L" " + engine->getDeviceGuid(); + deviceString = engine->getDeviceString(); mup::ParserX* parser = engine->getParser(); parser->DefineConst(L"deviceName", engine->getDeviceName()); diff --git a/helpers/ScopeGuard.h b/helpers/ScopeGuard.h new file mode 100644 index 0000000..957dbce --- /dev/null +++ b/helpers/ScopeGuard.h @@ -0,0 +1,115 @@ +#ifndef SCOPEGUARD_H +#define SCOPEGUARD_H + +// Taken from "Declarative Control Flow" (CppCon 2015) +// adjusted to work with Visual C++ 2013 + +#include +#include +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define noexcept +#endif +#include "UncaughtExceptions.h" +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#undef noexcept +#endif + +#define CONCATENATE_IMPL(s1, s2) s1 ## s2 +#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2) +#ifdef __COUNTER__ +#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__) +#else +#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__) +#endif + +namespace detail { +enum class ScopeGuardOnExit +{ +}; + +enum class ScopeGuardOnFail +{ +}; + +enum class ScopeGuardOnSuccess +{ +}; + +template class ScopeGuard +{ +public: + ScopeGuard(Fun&& fn) + : fn(std::move(fn)) + { + } + ~ScopeGuard() + { + fn(); + } + +private: + Fun fn; +}; + +class UncaughtExceptionCounter +{ + int exceptionCount_; + +public: + UncaughtExceptionCounter() + : exceptionCount_(folly::uncaught_exceptions()) + { + } + + bool newUncaughtException() + { + return folly::uncaught_exceptions() > exceptionCount_; + } +}; + +template class ScopeGuardForNewException +{ + FunctionType function_; + UncaughtExceptionCounter ec_; + +public: + explicit ScopeGuardForNewException(const FunctionType& fn) + : function_(fn) + { + } + + explicit ScopeGuardForNewException(FunctionType&& fn) + : function_(std::move(fn)) + { + } + + ~ScopeGuardForNewException() + { + if (executeOnException == ec_.newUncaughtException()) + { + function_(); + } + } +}; + +template ScopeGuard operator+(ScopeGuardOnExit, Fun&& fn) +{ + return ScopeGuard(std::forward(fn)); +} + +template ScopeGuardForNewException, true> operator+(detail::ScopeGuardOnFail, Fun&& fn) +{ + return ScopeGuardForNewException, true>(std::forward(fn)); +} + +template ScopeGuardForNewException, false> operator+(detail::ScopeGuardOnSuccess, Fun&& fn) +{ + return ScopeGuardForNewException, false>(std::forward(fn)); +} +} + +#define SCOPE_FAIL auto ANONYMOUS_VARIABLE(SCOPE_FAIL_STATE) = ::detail::ScopeGuardOnFail() +[&]() +#define SCOPE_SUCCESS auto ANONYMOUS_VARIABLE(SCOPE_FAIL_STATE) = ::detail::ScopeGuardOnSuccess() +[&]() +#define SCOPE_EXIT auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) = ::detail::ScopeGuardOnExit() +[&]() + +#endif diff --git a/helpers/UncaughtExceptions.h b/helpers/UncaughtExceptions.h new file mode 100644 index 0000000..2a8529e --- /dev/null +++ b/helpers/UncaughtExceptions.h @@ -0,0 +1,66 @@ +/* + * Copyright 2017 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#if defined(__GNUG__) || defined(__CLANG__) +#define FOLLY_EXCEPTION_COUNT_USE_CXA_GET_GLOBALS +namespace __cxxabiv1 { +// forward declaration (originally defined in unwind-cxx.h from from libstdc++) +struct __cxa_eh_globals; +// declared in cxxabi.h from libstdc++-v3 +extern "C" __cxa_eh_globals* __cxa_get_globals() noexcept; +} +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) && \ + (_MSC_VER < 1900) // MSVC++ 8.0 or greater +#define FOLLY_EXCEPTION_COUNT_USE_GETPTD +// forward declaration (originally defined in mtdll.h from MSVCRT) +struct _tiddata; +extern "C" _tiddata* _getptd(); // declared in mtdll.h from MSVCRT +#elif defined(_MSC_VER) && (_MSC_VER >= 1900) // MSVC++ 2015 +#define FOLLY_EXCEPTION_COUNT_USE_STD +#else +// Raise an error when trying to use this on unsupported platforms. +#error "Unsupported platform, don't include this header." +#endif + +namespace folly { + +/** + * Returns the number of uncaught exceptions. + * + * This function is based on Evgeny Panasyuk's implementation from here: + * http://fburl.com/15190026 + */ +inline int uncaught_exceptions() noexcept { +#if defined(FOLLY_EXCEPTION_COUNT_USE_CXA_GET_GLOBALS) + // __cxa_get_globals returns a __cxa_eh_globals* (defined in unwind-cxx.h). + // The offset below returns __cxa_eh_globals::uncaughtExceptions. + return *(reinterpret_cast(static_cast( + static_cast(__cxxabiv1::__cxa_get_globals())) + sizeof(void*))); +#elif defined(FOLLY_EXCEPTION_COUNT_USE_GETPTD) + // _getptd() returns a _tiddata* (defined in mtdll.h). + // The offset below returns _tiddata::_ProcessingThrow. + return *(reinterpret_cast(static_cast( + static_cast(_getptd())) + sizeof(void*) * 28 + 0x4 * 8)); +#elif defined(FOLLY_EXCEPTION_COUNT_USE_STD) + return std::uncaught_exceptions(); +#endif +} + +} // namespaces diff --git a/stdafx.h b/stdafx.h index bfb0a94..1433b86 100644 --- a/stdafx.h +++ b/stdafx.h @@ -17,3 +17,4 @@ #include #include #include +#include "helpers/ScopeGuard.h"