Skip to content

Commit

Permalink
Ported to bass, and detect all mutes, regardless if hardware or not. …
Browse files Browse the repository at this point in the history
…Co-authored by @tspivey.
  • Loading branch information
trypsynth committed Mar 1, 2024
1 parent dd8341f commit 2728287
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 42 deletions.
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ project(micstate LANGUAGES CXX)
add_executable(micstate WIN32 micstate.cpp)
target_include_directories(micstate PRIVATE C:/dev/include)
target_link_directories(micstate PRIVATE C:/dev/lib)
target_link_libraries(micstate PRIVATE Mmdevapi Tolk)
target_link_libraries(micstate PRIVATE Mmdevapi Tolk Bass)

file(COPY "c:/dev/bin/Tolk.dll" DESTINATION ${CMAKE_BINARY_DIR}/Release)
file(COPY "c:/dev/bin/SAAPI64.dll" DESTINATION ${CMAKE_BINARY_DIR}/Release)
file(COPY "c:/dev/bin/nvdaControllerClient64.dll" DESTINATION ${CMAKE_BINARY_DIR}/Release)
file(COPY "c:/dev/bin/Bass.dll" DESTINATION ${CMAKE_BINARY_DIR}/Release)
file(COPY "README.md" DESTINATION ${CMAKE_BINARY_DIR}/Release)
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Micstate

Simple Win32 program to speak the muted state of your default input device using a global hotkey.
Simple C++ program to speak the muted state of your default input device using a global hotkey.

## Why?

The current way of determining if an input device is muted or not is incredibly annoying:
The current way of determining if an input device is muted or not on Windows is incredibly annoying:

1. Open mmsys.cpl.
2. Find the recording tab.
Expand All @@ -14,16 +14,20 @@ The current way of determining if an input device is muted or not is incredibly
6. Check listen to this device.
7. Press Alt+A.
8. Listen, and determine if it is or not, finally.
9. Uncheck the box and close all the dialogs you just opened.
9. Uncheck the box, hit apply, and close all the dialogs you just opened.

I have a laptop with a deticated mute key that's super easy to hit, and I've hit it by accident many times, initially leading to my great confusion when no one could hear me, and later my paranoia that I hit it every time I launched a program that used the microphone. Hence, this program was born. Once you run it, it will passively sit in the background, waiting for you to press Control+Alt+Shift+M. When you do, the current state of your default input device will be spoken.

## Limitations
## Configuration

This program is sometimes currently unable to tell if a microphone has been muted at the hardware level. For example, if I mute my studio microphone with the mute button, it will report that, but if I mute my headset mic by pressing the physical mute button instead of doing it through Windows, this program will still report that it's unmuted. This is a limitation of the CoreAudio API and microphones not sending proper messages to the operating system, and not something I can work around as far as I'm aware. Suggestions appreciated!
The way this program works is by taking a tiny sample of audio (50 MS or so) from your default input device, getting the noise level (in DB), and comparing it to a configurable value. If the value is less than or equal to the configured value, the microphone is considered muted. By default, the value is -60 DB, but you can always change it. Open config.ini (generated when you run the program for the first time), and change the value of min_level in the DEFAULT section to your desired value. You'll have to relaunch the program for this change to take effect.

## Building
## Building from Source

I'm still very new to CMake, and even more so to needing to make my builds easily reproducible. As such, the current CMakeLists in this repository would probably give any CMake maintainer a heart attack. The only external dependency you should need is [Tolk](https://github.com/dkager/Tolk).
I'm still very new to CMake, and even more so to needing to make my builds easily reproducible. As such, the current CMakeLists in this repository would probably give any CMake maintainer a heart attack. The external dependencies you'll need are:

The only lines you should need to change in the CMakeLists file are the `target_include_directories`, `target_link_directories`, and `file` calls to point to wherever you have Tolk.h, Tolk.lib, and all the required DLLs, respectively. I keep mine in C:\dev\include, C:\dev\lib, and C:\dev\bin, but it's completely up to you where you put them, as long as you remember to update the paths. I appologise for this incredibly kludgy setup, and hope to improve it at some point in the future. This is yet another area where I'm very actively open to suggestions.
1. [Tolk](https://github.com/dkager/Tolk)
2. [Bass](https://www.un4seen.com/bass.html)
3. [SimpleINI](https://github.com/brofield/simpleini)

The only lines you should need to change in the CMakeLists file are the `target_include_directories`, `target_link_directories`, and `file` calls to point to wherever you have the required headers, lib files, and DLLs, respectively. I keep mine in C:\dev\include, C:\dev\lib, and C:\dev\bin, but it's completely up to you where you put them, as long as you remember to update the paths. I appologise for this incredibly kludgy setup, and hope to improve it at some point in the future. This is yet another area where I'm very actively open to suggestions.
79 changes: 46 additions & 33 deletions micstate.cpp
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
#include <math.h>
#include <Windows.h>
#include <Mmdeviceapi.h>
#include <endpointvolume.h>
#include <Functiondiscoverykeys_devpkey.h>
#include <Tolk.h>
#include <bass.h>
#include <SimpleIni.h>

// Yes, globals, I know, sorry about that.
IMMDeviceEnumerator* enumerator;
IMMDevice* default_device;
IAudioEndpointVolume* endpoint_volume;
int min_level; // The minimum level (in DB) to indicate muted state.

bool load_config();
void check_mic_state();
LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
float linear_to_decibel(float linear);

int WINAPI WinMain(HINSTANCE inst, HINSTANCE pinst, LPSTR cmdline, int show) {
HRESULT hr;
CoInitialize(NULL);
Tolk_Load();
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID*)&enumerator);
if (FAILED(hr)) {
CoUninitialize();
return 1;
}
hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &default_device);
if (FAILED(hr)) {
enumerator->Release();
CoUninitialize();
if (!BASS_RecordInit(-1)) {
MessageBox(NULL, "Failed to initialize Bass.", "Error", MB_ICONERROR);
return 1;
}
hr = default_device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpoint_volume);
if (FAILED(hr)) {
default_device->Release();
enumerator->Release();
CoUninitialize();
if (!load_config()) {
MessageBox(NULL, "Couldn't load configuration.", "Error", MB_ICONERROR);
return 1;
}
WNDCLASS wc = {0};
Expand All @@ -45,19 +34,36 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE pinst, LPSTR cmdline, int show) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
endpoint_volume->Release();
default_device->Release();
enumerator->Release();
Tolk_Unload();
UnregisterHotKey(hwnd, 1);
CoUninitialize();
return 0;
}

bool load_config() {
const char* filename = "config.ini";
CSimpleIniA ini;
SI_Error rc = ini.LoadFile(filename);
if (rc < 0) {
ini.SetValue("DEFAULT", "min_level", "-60");
ini.SaveFile(filename);
}
rc = ini.LoadFile(filename);
if (rc < 0) return false;
const char* min_level_str = ini.GetValue("DEFAULT", "min_level", "-60");
min_level = std::atoi(min_level_str);
return true;
}

void check_mic_state() {
BOOL is_muted;
endpoint_volume->GetMute(&is_muted);
Tolk_Output(is_muted ? L"Muted" : L"Unmuted");
HRECORD rec = BASS_RecordStart(0, 0, 0, NULL, NULL);
if (rec == 0) {
Beep(100, 100);
return;
}
Sleep(50);
FLOAT levels[2];
BASS_ChannelGetLevelEx(rec, levels, 1.0, BASS_LEVEL_MONO);
BASS_ChannelFree(rec);
int level = linear_to_decibel(levels[0]);
Tolk_Output(level <= min_level ? L"Muted" : L"Unmuted");
}

LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
Expand All @@ -73,9 +79,6 @@ LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
check_mic_state();
break;
case WM_DESTROY:
endpoint_volume->Release();
default_device->Release();
enumerator->Release();
Tolk_Unload();
UnregisterHotKey(hwnd, 1);
CoUninitialize();
Expand All @@ -86,3 +89,13 @@ LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
}
return 0;
}

// https://forum.juce.com/t/float-to-decibel-conversion/1841
float linear_to_decibel(float linear) {
float db;
if (linear != 0.0f)
db = 20.0f * log10(linear);
else
db = -144.0f; // effectively minus infinity
return db;
}

0 comments on commit 2728287

Please sign in to comment.