Skip to content

Commit

Permalink
Expose and rewrite fuzzy matching
Browse files Browse the repository at this point in the history
fixes #78 - Expose fuzzy match option
  • Loading branch information
fredemmott committed Dec 31, 2023
1 parent 2858bce commit 1a2756d
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 58 deletions.
127 changes: 79 additions & 48 deletions Sources/BaseMuteAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,13 @@
#include <StreamDeckSDK/ESDLogger.h>

#include <functional>
#include <regex>

#include "DefaultAudioDevices.h"
#include "audio_json.h"

using json = nlohmann::json;

NLOHMANN_JSON_SERIALIZE_ENUM(
DeviceMatchStrategy,
{
{DeviceMatchStrategy::ID, "ID"},
{DeviceMatchStrategy::Fuzzy, "Fuzzy"},
{DeviceMatchStrategy::Special, "Special"},
});

NLOHMANN_JSON_SERIALIZE_ENUM(
ToggleKind,
{
Expand All @@ -33,17 +26,36 @@ NLOHMANN_JSON_SERIALIZE_ENUM(
{ToggleKind::PttPtmOnly, "PttPtmOnly"},
});

static AudioDeviceInfo GetAudioDeviceInfo(const std::string& id) {
{
const auto in = GetAudioDeviceList(AudioDeviceDirection::INPUT);
if (in.contains(id)) {
return in.at(id);
}
}

{
const auto out = GetAudioDeviceList(AudioDeviceDirection::OUTPUT);
if (out.contains(id)) {
return out.at(id);
}
}

return {.id = id};
}

void from_json(const json& json, MuteActionSettings& settings) {
if (json.contains("device")) {
settings.device = json.at("device");
settings.matchStrategy = json.at("matchStrategy");
} else if (json.contains("deviceID")) {
}

if (json.contains("deviceID")) {
const auto id = json.at("deviceID").get<std::string>();
settings.device = {.id = id};
if (DefaultAudioDevices::GetRealDeviceID(id) == id) {
settings.matchStrategy = DeviceMatchStrategy::ID;
} else {
settings.matchStrategy = DeviceMatchStrategy::Special;

if (DefaultAudioDevices::GetRealDeviceID(id) != id) {
settings.device = {.id = id};
} else if (id != settings.device.id) {
settings.device = GetAudioDeviceInfo(id);
}
} else {
settings.device = {
Expand All @@ -53,49 +65,83 @@ void from_json(const json& json, MuteActionSettings& settings) {
? DefaultAudioDevices::DEFAULT_INPUT_ID
: DefaultAudioDevices::COMMUNICATIONS_INPUT_ID,
};
settings.matchStrategy = DeviceMatchStrategy::Special;
};

settings.feedbackSounds = json.value<bool>("feedbackSounds", true);

// Legacy setting
// Legacy setting
if (json.value<bool>("ptt", false)) {
settings.toggleKind = ToggleKind::TogglePressPttPtmHold;
settings.toggleKind = ToggleKind::TogglePressPttPtmHold;
}
if (json.contains("toggleKind")) {
settings.toggleKind = json.at("toggleKind");
}
}

static std::string FuzzifyInterface(const std::string& name) {
// Windows likes to replace "Foo" with "2- Foo"
const std::regex pattern {"^([0-9]+- )?(.+)$"};
std::smatch captures;
if (!std::regex_match(name, captures, pattern)) {
return name;
}
return captures[2];
}

std::string MuteActionSettings::VolatileDeviceID() const {
if (matchStrategy == DeviceMatchStrategy::Special) {
return DefaultAudioDevices::GetRealDeviceID(device.id);
const auto specificID = DefaultAudioDevices::GetRealDeviceID(device.id);
if (GetAudioDeviceState(specificID) == AudioDeviceState::CONNECTED) {
return specificID;
}

if (matchStrategy == DeviceMatchStrategy::ID) {
return device.id;
if (!fuzzyMatching) {
return specificID;
}

if (GetAudioDeviceState(device.id) == AudioDeviceState::CONNECTED) {
return device.id;
if (device.interfaceName.empty()) {
ESDLog(
"Would try a fuzzy match, but don't have a saved interface name :'(");
return specificID;
}

for (const auto& [otherID, other]: GetAudioDeviceList(device.direction)) {
const auto fuzzyInterface = FuzzifyInterface(device.interfaceName);
ESDLog(
"Trying a fuzzy match: '{}' -> '{}'", device.interfaceName, fuzzyInterface);

for (const auto& [otherId, otherDevice]:
GetAudioDeviceList(device.direction)) {
const auto fuzzyOtherInterface
= FuzzifyInterface(otherDevice.interfaceName);
ESDLog(
"Trying '{}' -> '{}'", otherDevice.interfaceName, fuzzyOtherInterface);
if (
device.interfaceName == other.interfaceName
&& device.endpointName == other.endpointName) {
ESDDebug(
"Fuzzy device match for {}/{}",
device.interfaceName,
device.endpointName);
return otherID;
fuzzyInterface != fuzzyOtherInterface
|| device.endpointName != otherDevice.endpointName) {
continue;
}
ESDLog(
"Fuzzy device match for {}/{}",
device.interfaceName,
device.endpointName);
return otherId;
}

ESDLog(
"Failed fuzzy match for {}/{}", device.interfaceName, device.endpointName);
return device.id;
}

BaseMuteAction::BaseMuteAction(
ESDConnectionManager* esd_connection,
const std::string& action,
const std::string& context)
: ESDActionWithExternalState(esd_connection, action, context) {
mPlugEventCallbackHandle = AddAudioDevicePlugEventCallback(
std::bind_front(&BaseMuteAction::OnPlugEvent, this));
mDefaultChangeCallbackHandle = AddDefaultAudioDeviceChangeCallback(
std::bind_front(&BaseMuteAction::OnDefaultDeviceChange, this));
}

BaseMuteAction::~BaseMuteAction() {
}

Expand Down Expand Up @@ -132,29 +178,14 @@ void BaseMuteAction::SettingsDidChange(
return;
}
RealDeviceDidChange();
if (new_settings.matchStrategy == DeviceMatchStrategy::ID) {
mDefaultChangeCallbackHandle = {};
ESDDebug("Registering plugevent callback");
mPlugEventCallbackHandle = AddAudioDevicePlugEventCallback(
std::bind_front(&BaseMuteAction::OnPlugEvent, this));
return;
}

// Differing real device ID means we have a place holder device, e.g.
// 'Default input device'

mPlugEventCallbackHandle = {};
ESDDebug("Registering default device change callback for {}", GetContext());
mDefaultChangeCallbackHandle = AddDefaultAudioDeviceChangeCallback(
std::bind_front(&BaseMuteAction::OnDefaultDeviceChange, this));
}

void BaseMuteAction::OnPlugEvent(
AudioDevicePlugEvent event,
const std::string& deviceID) {
ESDLog(
"Received plug event {} for device {}", static_cast<int>(event), deviceID);
if (deviceID != GetSettings().device.id) {
if (deviceID != GetRealDeviceID()) {
ESDLog("Device is not a match");
return;
}
Expand Down
14 changes: 5 additions & 9 deletions Sources/BaseMuteAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@

using namespace FredEmmott::Audio;

enum class DeviceMatchStrategy {
ID,
Fuzzy,
Special,
};

enum class ToggleKind {
ToggleOnly,
TogglePressPttPtmHold,
Expand All @@ -25,7 +19,7 @@ enum class ToggleKind {

struct MuteActionSettings {
AudioDeviceInfo device;
DeviceMatchStrategy matchStrategy = DeviceMatchStrategy::ID;
bool fuzzyMatching = false;
bool feedbackSounds = true;
ToggleKind toggleKind = ToggleKind::ToggleOnly;

Expand All @@ -36,8 +30,10 @@ void from_json(const nlohmann::json& json, MuteActionSettings& settings);

class BaseMuteAction : public ESDActionWithExternalState<MuteActionSettings> {
public:
using ESDActionWithExternalState<
MuteActionSettings>::ESDActionWithExternalState;
BaseMuteAction(
ESDConnectionManager* esd_connection,
const std::string& action,
const std::string& context);
virtual ~BaseMuteAction();

void SendToPlugin(const nlohmann::json& payload) override;
Expand Down
17 changes: 16 additions & 1 deletion sdPlugin/propertyinspector/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
</optgroup>
</select>
</div>
<div class="sdpi-item">
<div class="sdpi-item-label">Device matching</div>
<select class="sdpi-item-value select" id="fuzzyMatching" onchange="saveSettings()">
<option value="off">Exact (by ID)</option>
<option value="on">Fuzzy (by name, ignoring some changes)</option>
</select>
</div>
<div class="sdpi-item hidden" id="toggleKindDiv">
<div class="sdpi-item-label">PTT/PTM</div>
<select class="sdpi-item-value select" id="toggleKind" onchange="saveSettings()"">
Expand Down Expand Up @@ -82,9 +89,17 @@
actionInfo = jsonObj.actionInfo.action;
settings = jsonObj.actionInfo.payload.settings;
ctx = jsonObj.actionInfo.context;

if (!('feedbackSounds' in settings)) {
settings.feedbackSounds = true;
}
document.getElementById('feedbackSounds').checked = settings.feedbackSounds;

if (!('fuzzyMatching' in settings)) {
settings.fuzzyMatching = false;
}
document.querySelector(`#fuzzyMatching option[value="${settings.fuzzyMatching ? "on" : "off"}"]`).setAttribute('selected', true);

if (actionInfo == ACTION_IDS.toggle) {
if (!('toggleKind' in settings)) {
if ('ptt' in settings) {
Expand All @@ -96,7 +111,6 @@
document.getElementById('toggleKindDiv').classList.remove('hidden');
document.querySelector(`#toggleKind option[value="${settings.toggleKind}"]`).setAttribute('selected', true);
}
document.getElementById('feedbackSounds').checked = settings.feedbackSounds;

$SD.api.sendToPlugin(uuid, actionInfo, { event: "getDeviceList" });
};
Expand Down Expand Up @@ -211,6 +225,7 @@
settings.deviceLabel = deviceElement.textContent;
settings.deviceContainer = deviceElement.parentElement.id;
settings.feedbackSounds = document.getElementById('feedbackSounds').checked;
settings.fuzzyMatching = document.getElementById('fuzzyMatching').value == 'on';
if (actionInfo == ACTION_IDS.toggle) {
settings.toggleKind = document.getElementById('toggleKind').value;
}
Expand Down

0 comments on commit 1a2756d

Please sign in to comment.