Skip to content

Instantly share code, notes, and snippets.

@ParticleG
Created December 11, 2024 18:49
Show Gist options
  • Save ParticleG/45ba8bae68f6f5ae7beac1888c73ff16 to your computer and use it in GitHub Desktop.
Save ParticleG/45ba8bae68f6f5ae7beac1888c73ff16 to your computer and use it in GitHub Desktop.
Windows Volume Manager
#include <iostream>
#include <mmdeviceapi.h>
#include <VolumeManager.h>
using namespace std;
namespace {
IMMDevice* getDefaultIMMDevice() {
IMMDevice* defaultDevice = nullptr;
IMMDeviceEnumerator* deviceEnumerator = nullptr;
if (const auto result = CoCreateInstance(
__uuidof(MMDeviceEnumerator),
nullptr,
CLSCTX_INPROC_SERVER,
__uuidof(IMMDeviceEnumerator),
reinterpret_cast<LPVOID *>(&deviceEnumerator)
); result != S_OK) {
switch (result) {
case E_NOINTERFACE: {
cout << "The specified class does not implement the requested interface." << endl;
break;
}
case E_POINTER: {
cout << "The ppv parameter is NULL." << endl;
break;
}
case CLASS_E_NOAGGREGATION: {
cout << "This class cannot be created as part of an aggregate." << endl;
break;
}
case REGDB_E_CLASSNOTREG: {
cout << "A specified class is not registered in the registration database." << endl;
break;
}
default: {
cout << "Unknown error." << endl;
break;
}
}
}
if (const auto result = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
result != S_OK) {
switch (result) {
case E_POINTER: {
cout << "The ppDevice parameter is NULL." << endl;
break;
}
case E_INVALIDARG: {
cout << "Parameter dataFlow or role is out of range." << endl;
break;
}
case E_NOTFOUND: {
cout << "No device is available." << endl;
break;
}
case E_OUTOFMEMORY: {
cout << "Out of memory." << endl;
break;
}
default: {
cout << "Unknown error." << endl;
break;
}
}
}
deviceEnumerator->Release();
return defaultDevice;
}
IAudioEndpointVolume* getDefaultEndpointVolume() {
IMMDevice* defaultDevice = getDefaultIMMDevice();
IAudioEndpointVolume* endpointVolume = nullptr;
if (const auto result = defaultDevice->Activate(
__uuidof(IAudioEndpointVolume),
CLSCTX_INPROC_SERVER,
nullptr,
reinterpret_cast<LPVOID *>(&endpointVolume)
); result != S_OK) {
switch (result) {
case E_NOINTERFACE: {
cout << "The object does not support the requested interface type." << endl;
break;
}
case E_POINTER: {
cout << "Parameter ppInterface is NULL." << endl;
break;
}
case E_INVALIDARG: {
cout << "The pActivationParams parameter must be NULL for the specified interface." << endl;
break;
}
case E_OUTOFMEMORY: {
cout << "Out of memory." << endl;
break;
}
default: {
cout << "Unknown error." << endl;
break;
}
}
}
defaultDevice->Release();
return endpointVolume;
}
}
VolumeManager::VolumeManager() {
if (const auto result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); result != S_OK) {
switch (result) {
case S_FALSE: {
cout << format("[{}] COM library is already initialized.", __FUNCTION__) << endl;
break;
}
case RPC_E_CHANGED_MODE: {
cout << format("[{}] This thread is an MTA.", __FUNCTION__) << endl;
}
case E_OUTOFMEMORY: {
throw runtime_error(format("[{}] Out of memory.", __FUNCTION__));
}
case E_INVALIDARG: {
throw runtime_error(format("[{}] Parameter 'pvReserved' must be nullptr.", __FUNCTION__));
}
case E_UNEXPECTED: {
throw runtime_error(format("[{}] An unexpected error occurred.", __FUNCTION__));
}
default: {
throw runtime_error(format("[{}] An unknown error occurred.", __FUNCTION__));
}
}
cout << "Failed to initialize COM library" << endl;
exit(1);
}
_endpointVolume.reset(getDefaultEndpointVolume());
}
VolumeManager::~VolumeManager() {
_endpointVolume->Release();
_endpointVolume.release();
CoUninitialize();
}
float VolumeManager::getMasterVolumeLevel(const VolumeType volumeType) const {
float volume{};
switch (volumeType) {
case VolumeType::Decibels: {
if (const auto result = _endpointVolume->GetMasterVolumeLevel(&volume);
result != S_OK) {
switch (result) {
case E_POINTER: {
throw runtime_error(format("[{}] The pfLevelDB parameter is NULL.", __FUNCTION__));
}
default: {
throw runtime_error(format("[{}] Unknown error.", __FUNCTION__));
}
}
}
break;
}
case VolumeType::Scalar: {
if (const auto result = _endpointVolume->GetMasterVolumeLevelScalar(&volume);
result != S_OK) {
switch (result) {
case E_POINTER: {
throw runtime_error(format("[{}] The pfLevelDB parameter is NULL.", __FUNCTION__));
}
default: {
throw runtime_error(format("[{}] Unknown error.", __FUNCTION__));
}
}
}
break;
}
}
return volume;
}
void VolumeManager::setMasterVolumeLevel(const float volume, const VolumeType volumeType) const {
switch (volumeType) {
case VolumeType::Decibels: {
if (const auto result = _endpointVolume->SetMasterVolumeLevel(volume, nullptr);
result != S_OK) {
switch (result) {
case E_INVALIDARG: {
throw runtime_error(format(
"[{}] Parameter fLevelDB lies outside of the volume range supported by the device.",
__FUNCTION__));
}
case E_OUTOFMEMORY: {
throw runtime_error(format("[{}] Out of memory.", __FUNCTION__));
}
default: {
throw runtime_error(format("[{}] Unknown error.", __FUNCTION__));
}
}
}
break;
}
case VolumeType::Scalar: {
if (const auto result = _endpointVolume->SetMasterVolumeLevelScalar(volume, nullptr);
result != S_OK) {
switch (result) {
case E_INVALIDARG: {
throw runtime_error(format("[{}] Parameter fLevel is outside the range from 0.0 to 1.0.",
__FUNCTION__));
}
case E_OUTOFMEMORY: {
throw runtime_error(format("[{}] Out of memory.", __FUNCTION__));
}
default: {
throw runtime_error(format("[{}] Unknown error.", __FUNCTION__));
}
}
}
break;
}
}
}
#pragma once
#include <endpointvolume.h>
class VolumeManager {
public:
enum class VolumeType {
Decibels,
Scalar
};
VolumeManager();
~VolumeManager();
float getMasterVolumeLevel(VolumeType volumeType = VolumeType::Scalar) const;
void setMasterVolumeLevel(float volume, VolumeType volumeType = VolumeType::Scalar) const;
private:
std::unique_ptr<IAudioEndpointVolume> _endpointVolume;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment