Merge pull request #195 from uowuo/rnnoise

Use rnnoise for VAD
This commit is contained in:
ouwou 2023-08-26 07:44:40 +00:00 committed by GitHub
commit 9724023758
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 418 additions and 64 deletions

View File

@ -35,7 +35,7 @@ DerivePointerAlignment: 'false'
FixNamespaceComments: 'true'
IncludeBlocks: Merge
IndentCaseLabels: 'true'
IndentPPDirectives: BeforeHash
IndentPPDirectives: None
IndentWidth: '4'
IndentWrappedFunctionNames: 'false'
JavaScriptQuotes: Double

View File

@ -67,7 +67,7 @@ jobs:
with:
cond: ${{ matrix.mindeps == true }}
if_true: |
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF -DENABLE_NOTIFICATION_SOUNDS=OFF -DENABLE_QRCODE_LOGIN=OFF
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF -DENABLE_NOTIFICATION_SOUNDS=OFF -DENABLE_QRCODE_LOGIN=OFF -DENABLE_RNNOISE=OFF
cmake --build build
if_false: |
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DCMAKE_CXX_FLAGS="-Wl,--default-image-base-low"

3
.gitmodules vendored
View File

@ -13,6 +13,9 @@
[submodule "subprojects/miniaudio"]
path = subprojects/miniaudio
url = https://github.com/mackron/miniaudio
[submodule "subprojects/rnnoise"]
path = subprojects/rnnoise
url = https://github.com/xiph/rnnoise
[submodule "subprojects/qrcodegen"]
path = subprojects/qrcodegen
url = https://github.com/nayuki/QR-Code-generator

View File

@ -11,6 +11,7 @@ option(USE_LIBHANDY "Enable features that require libhandy (default)" ON)
option(ENABLE_VOICE "Enable voice suppport" ON)
option(USE_KEYCHAIN "Store the token in the keychain (default)" ON)
option(ENABLE_NOTIFICATION_SOUNDS "Enable notification sounds (default)" ON)
option(ENABLE_RNNOISE "Enable RNNoise for voice activity detection (default)" ON)
option(ENABLE_QRCODE_LOGIN "Enable QR code login (default)" ON)
find_package(nlohmann_json REQUIRED)
@ -161,6 +162,44 @@ if (ENABLE_VOICE)
target_link_libraries(abaddon ${CMAKE_DL_LIBS})
if (ENABLE_RNNOISE)
target_compile_definitions(abaddon PRIVATE WITH_RNNOISE)
find_package(rnnoise QUIET)
if (NOT rnnoise_FOUND)
message("rnnoise was not found and will be included as a submodule")
# This is potentially really stupid
add_library(rnnoise STATIC
subprojects/rnnoise/src/arch.h
subprojects/rnnoise/src/celt_lpc.c
subprojects/rnnoise/src/celt_lpc.h
subprojects/rnnoise/src/common.h
subprojects/rnnoise/src/denoise.c
subprojects/rnnoise/src/kiss_fft.c
subprojects/rnnoise/src/kiss_fft.h
subprojects/rnnoise/src/opus_types.h
subprojects/rnnoise/src/pitch.c
subprojects/rnnoise/src/pitch.h
subprojects/rnnoise/src/rnn_data.c
subprojects/rnnoise/src/rnn_data.h
subprojects/rnnoise/src/rnn_reader.c
subprojects/rnnoise/src/rnn.c
subprojects/rnnoise/src/rnn.h
subprojects/rnnoise/src/tansig_table.h
subprojects/rnnoise/src/_kiss_fft_guts.h
subprojects/rnnoise/include/rnnoise.h)
target_include_directories(rnnoise PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/subprojects/rnnoise/include")
target_link_libraries(abaddon rnnoise)
else ()
target_link_libraries(abaddon rnnoise::rnnoise)
endif ()
endif ()
if (APPLE)
target_link_libraries(abaddon "-framework CoreFoundation")
target_link_libraries(abaddon "-framework CoreAudio")
target_link_libraries(abaddon "-framework AudioToolbox")
endif ()
endif ()
if (${ENABLE_NOTIFICATION_SOUNDS})
@ -170,10 +209,10 @@ endif ()
if (USE_MINIAUDIO)
find_path(MINIAUDIO_INCLUDE_DIR
NAMES miniaudio.h
HINTS subprojects
PATH_SUFFIXES miniaudio
REQUIRED)
NAMES miniaudio.h
HINTS subprojects
PATH_SUFFIXES miniaudio
REQUIRED)
if (APPLE)
target_link_libraries(abaddon "-framework CoreFoundation")

View File

@ -151,6 +151,7 @@ spam filter's wrath:
* [miniaudio](https://miniaud.io/) (optional, provided as submodule, required for voice)
* [libopus](https://opus-codec.org/) (optional, required for voice)
* [libsodium](https://doc.libsodium.org/) (optional, required for voice)
* [rnnoise](https://gitlab.xiph.org/xiph/rnnoise) (optional, provided as submodule, noise suppression and improved VAD)
### TODO:
@ -320,6 +321,12 @@ For example, memory_db would be set by adding `memory_db = true` under the line
| `enabled` | boolean | true (if not on Windows) | Enable desktop notifications |
| `playsound` | boolean | true | Enable notification sounds. Requires ENABLE_NOTIFICATION_SOUNDS=TRUE in CMake |
#### voice
| Setting | Type | Default | Description |
|---------|--------|------------------------------------|------------------------------------------------------------|
| vad | string | rnnoise if enabled, gate otherwise | Method used for voice activity detection. Changeable in UI |
### Environment variables
| variable | Description |

48
cmake/Findrnnoise.cmake Normal file
View File

@ -0,0 +1,48 @@
function(add_imported_library library headers)
add_library(rnnoise::rnnoise UNKNOWN IMPORTED)
set_target_properties(rnnoise::rnnoise PROPERTIES
IMPORTED_LOCATION ${library}
INTERFACE_INCLUDE_DIRECTORIES ${headers}
)
set(rnnoise_FOUND 1 CACHE INTERNAL "rnnoise found" FORCE)
set(rnnoise_LIBRARIES ${library}
CACHE STRING "Path to rnnoise library" FORCE)
set(rnnoise_INCLUDES ${headers}
CACHE STRING "Path to rnnoise headers" FORCE)
mark_as_advanced(FORCE rnnoise_LIBRARIES)
mark_as_advanced(FORCE rnnoise_INCLUDES)
endfunction()
if (rnnoise_LIBRARIES AND rnnoise_INCLUDES)
add_imported_library(${rnnoise_LIBRARIES} ${rnnoise_INCLUDES})
return()
endif()
file(TO_CMAKE_PATH "$ENV{rnnoise_DIR}" _rnnoise_DIR)
find_library(rnnoise_LIBRARY_PATH
NAMES librnnoise rnnoise
PATHS
${_rnnoise_DIR}/lib/${CMAKE_LIBRARY_ARCHITECTURE}
/usr/lib
)
find_path(rnnoise_HEADER_PATH
NAMES rnnoise.h
PATHS
${_rnnoise_DIR}/include
/usr/include
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
rnnoise DEFAULT_MSG rnnoise_LIBRARY_PATH rnnoise_HEADER_PATH
)
if (rnnoise_FOUND)
add_imported_library(
"${rnnoise_LIBRARY_PATH};${rnnoise_LIBRARIES}"
"${rnnoise_HEADER_PATH};${rnnoise_INCLUDE_DIRECTORIES}"
)
endif()

View File

@ -23,11 +23,11 @@
#include "remoteauth/remoteauthdialog.hpp"
#ifdef WITH_LIBHANDY
#include <handy.h>
#include <handy.h>
#endif
#ifdef _WIN32
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "crypt32.lib")
#endif
Abaddon::Abaddon()
@ -67,7 +67,7 @@ Abaddon::Abaddon()
if (!accessible)
m_channels_requested.erase(id);
});
if (GetSettings().Prefetch)
if (GetSettings().Prefetch) {
m_discord.signal_message_create().connect([this](const Message &message) {
if (message.Author.HasAvatar())
m_img_mgr.Prefetch(message.Author.GetAvatarURL());
@ -76,6 +76,11 @@ Abaddon::Abaddon()
m_img_mgr.Prefetch(attachment.ProxyURL);
}
});
}
#ifdef WITH_VOICE
m_audio.SetVADMethod(GetSettings().VAD);
#endif
}
Abaddon &Abaddon::Get() {
@ -475,14 +480,6 @@ void Abaddon::ShowVoiceWindow() {
m_audio.SetPlayback(!is_deaf);
});
wnd->signal_gate().connect([this](double gate) {
m_audio.SetCaptureGate(gate);
});
wnd->signal_gain().connect([this](double gain) {
m_audio.SetCaptureGain(gain);
});
wnd->signal_mute_user_cs().connect([this](Snowflake id, bool is_mute) {
if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) {
m_audio.SetMuteSSRC(*ssrc, is_mute);

View File

@ -64,6 +64,10 @@ void capture_data_callback(ma_device *pDevice, void *pOutput, const void *pInput
AudioManager::AudioManager() {
m_ok = true;
#ifdef WITH_RNNOISE
RNNoiseInitialize();
#endif
int err;
m_encoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_VOIP, &err);
if (err != OPUS_OK) {
@ -144,6 +148,10 @@ AudioManager::~AudioManager() {
ma_device_uninit(&m_capture_device);
ma_context_uninit(&m_context);
RemoveAllSSRCs();
#ifdef WITH_RNNOISE
RNNoiseUninitialize();
#endif
}
void AudioManager::AddSSRC(uint32_t ssrc) {
@ -420,10 +428,43 @@ void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) {
UpdateCaptureVolume(new_pcm.data(), frames);
if (m_capture_peak_meter / 32768.0 < m_capture_gate) return;
static std::array<float, 480> denoised_L;
static std::array<float, 480> denoised_R;
bool m_rnnoise_passed = false;
#ifdef WITH_RNNOISE
if (m_vad_method == VADMethod::RNNoise || m_enable_noise_suppression) {
m_rnnoise_passed = CheckVADRNNoise(new_pcm.data(), denoised_L.data(), denoised_R.data());
}
#endif
switch (m_vad_method) {
case VADMethod::Gate:
if (!CheckVADVoiceGate()) return;
break;
#ifdef WITH_RNNOISE
case VADMethod::RNNoise:
if (!m_rnnoise_passed) return;
break;
#endif
}
m_enc_mutex.lock();
int payload_len = opus_encode(m_encoder, new_pcm.data(), 480, static_cast<unsigned char *>(m_opus_buffer), 1275);
int payload_len = -1;
if (m_enable_noise_suppression) {
static std::array<int16_t, 960> denoised_interleaved;
for (size_t i = 0; i < 480; i++) {
denoised_interleaved[i * 2] = static_cast<int16_t>(denoised_L[i]);
}
for (size_t i = 0; i < 480; i++) {
denoised_interleaved[i * 2 + 1] = static_cast<int16_t>(denoised_R[i]);
}
payload_len = opus_encode(m_encoder, denoised_interleaved.data(), 480, static_cast<unsigned char *>(m_opus_buffer), 1275);
} else {
payload_len = opus_encode(m_encoder, new_pcm.data(), 480, static_cast<unsigned char *>(m_opus_buffer), 1275);
}
m_enc_mutex.unlock();
if (payload_len < 0) {
spdlog::get("audio")->error("encoding error: {}", payload_len);
@ -453,6 +494,9 @@ bool AudioManager::DecayVolumeMeters() {
m_capture_peak_meter -= 600;
if (m_capture_peak_meter < 0) m_capture_peak_meter = 0;
const auto x = m_vad_prob.load() - 0.05f;
m_vad_prob.store(x < 0.0f ? 0.0f : x);
std::lock_guard<std::mutex> _(m_vol_mtx);
for (auto &[ssrc, meter] : m_volumes) {
@ -463,6 +507,55 @@ bool AudioManager::DecayVolumeMeters() {
return true;
}
bool AudioManager::CheckVADVoiceGate() {
return m_capture_peak_meter / 32768.0 > m_capture_gate;
}
#ifdef WITH_RNNOISE
bool AudioManager::CheckVADRNNoise(const int16_t *pcm, float *denoised_left, float *denoised_right) {
// use left channel for vad, only denoise right if noise suppression enabled
std::unique_lock<std::mutex> _(m_rnn_mutex);
static float rnnoise_input[480];
for (size_t i = 0; i < 480; i++) {
rnnoise_input[i] = static_cast<float>(pcm[i * 2]);
}
m_vad_prob = std::max(m_vad_prob.load(), rnnoise_process_frame(m_rnnoise[0], denoised_left, rnnoise_input));
if (m_enable_noise_suppression) {
for (size_t i = 0; i < 480; i++) {
rnnoise_input[i] = static_cast<float>(pcm[i * 2 + 1]);
}
rnnoise_process_frame(m_rnnoise[1], denoised_right, rnnoise_input);
}
return m_vad_prob > m_prob_threshold;
}
void AudioManager::RNNoiseInitialize() {
spdlog::get("audio")->debug("Initializing RNNoise");
RNNoiseUninitialize();
std::unique_lock<std::mutex> _(m_rnn_mutex);
m_rnnoise[0] = rnnoise_create(nullptr);
m_rnnoise[1] = rnnoise_create(nullptr);
const auto expected = rnnoise_get_frame_size();
if (expected != 480) {
spdlog::get("audio")->warn("RNNoise expects a frame count other than 480");
}
}
void AudioManager::RNNoiseUninitialize() {
if (m_rnnoise[0] != nullptr) {
spdlog::get("audio")->debug("Uninitializing RNNoise");
std::unique_lock<std::mutex> _(m_rnn_mutex);
rnnoise_destroy(m_rnnoise[0]);
rnnoise_destroy(m_rnnoise[1]);
m_rnnoise[0] = nullptr;
m_rnnoise[1] = nullptr;
}
}
#endif
bool AudioManager::OK() const {
return m_ok;
}
@ -487,6 +580,55 @@ uint32_t AudioManager::GetRTPTimestamp() const noexcept {
return m_rtp_timestamp;
}
void AudioManager::SetVADMethod(const std::string &method) {
spdlog::get("audio")->debug("Setting VAD method to {}", method);
if (method == "gate") {
SetVADMethod(VADMethod::Gate);
} else if (method == "rnnoise") {
#ifdef WITH_RNNOISE
SetVADMethod(VADMethod::RNNoise);
#else
SetVADMethod(VADMethod::Gate);
spdlog::get("audio")->error("Tried to set RNNoise VAD method with support disabled");
#endif
} else {
SetVADMethod(VADMethod::Gate);
spdlog::get("audio")->error("Tried to set unknown VAD method {}", method);
}
}
void AudioManager::SetVADMethod(VADMethod method) {
const auto method_int = static_cast<int>(method);
spdlog::get("audio")->debug("Setting VAD method to enum {}", method_int);
m_vad_method = method;
}
AudioManager::VADMethod AudioManager::GetVADMethod() const {
return m_vad_method;
}
#ifdef WITH_RNNOISE
float AudioManager::GetCurrentVADProbability() const {
return m_vad_prob;
}
double AudioManager::GetRNNProbThreshold() const {
return m_prob_threshold;
}
void AudioManager::SetRNNProbThreshold(double value) {
m_prob_threshold = value;
}
void AudioManager::SetSuppressNoise(bool value) {
m_enable_noise_suppression = value;
}
bool AudioManager::GetSuppressNoise() const {
return m_enable_noise_suppression;
}
#endif
AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() {
return m_signal_opus_packet;
}

View File

@ -14,6 +14,11 @@
#include <miniaudio.h>
#include <opus.h>
#include <sigc++/sigc++.h>
#ifdef WITH_RNNOISE
#include <rnnoise.h>
#endif
#include "devices.hpp"
// clang-format on
@ -40,30 +45,47 @@ public:
void SetCaptureGate(double gate);
void SetCaptureGain(double gain);
[[nodiscard]] double GetCaptureGate() const noexcept;
[[nodiscard]] double GetCaptureGain() const noexcept;
double GetCaptureGate() const noexcept;
double GetCaptureGain() const noexcept;
void SetMuteSSRC(uint32_t ssrc, bool mute);
void SetVolumeSSRC(uint32_t ssrc, double volume);
[[nodiscard]] double GetVolumeSSRC(uint32_t ssrc) const;
double GetVolumeSSRC(uint32_t ssrc) const;
void SetEncodingApplication(int application);
[[nodiscard]] int GetEncodingApplication();
int GetEncodingApplication();
void SetSignalHint(int signal);
[[nodiscard]] int GetSignalHint();
int GetSignalHint();
void SetBitrate(int bitrate);
[[nodiscard]] int GetBitrate();
int GetBitrate();
void Enumerate();
[[nodiscard]] bool OK() const;
bool OK() const;
[[nodiscard]] double GetCaptureVolumeLevel() const noexcept;
[[nodiscard]] double GetSSRCVolumeLevel(uint32_t ssrc) const noexcept;
double GetCaptureVolumeLevel() const noexcept;
double GetSSRCVolumeLevel(uint32_t ssrc) const noexcept;
[[nodiscard]] AudioDevices &GetDevices();
AudioDevices &GetDevices();
[[nodiscard]] uint32_t GetRTPTimestamp() const noexcept;
uint32_t GetRTPTimestamp() const noexcept;
enum class VADMethod {
Gate,
RNNoise,
};
void SetVADMethod(const std::string &method);
void SetVADMethod(VADMethod method);
VADMethod GetVADMethod() const;
#ifdef WITH_RNNOISE
float GetCurrentVADProbability() const;
double GetRNNProbThreshold() const;
void SetRNNProbThreshold(double value);
void SetSuppressNoise(bool value);
bool GetSuppressNoise() const;
#endif
private:
void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames);
@ -74,6 +96,15 @@ private:
bool DecayVolumeMeters();
bool CheckVADVoiceGate();
#ifdef WITH_RNNOISE
bool CheckVADRNNoise(const int16_t *pcm, float *denoised_left, float *denoised_right);
void RNNoiseInitialize();
void RNNoiseUninitialize();
#endif
friend void data_callback(ma_device *, void *, const void *, ma_uint32);
friend void capture_data_callback(ma_device *, void *, const void *, ma_uint32);
@ -95,6 +126,10 @@ private:
mutable std::mutex m_mutex;
mutable std::mutex m_enc_mutex;
#ifdef WITH_RNNOISE
mutable std::mutex m_rnn_mutex;
#endif
std::unordered_map<uint32_t, std::pair<std::deque<int16_t>, OpusDecoder *>> m_sources;
OpusEncoder *m_encoder;
@ -106,6 +141,9 @@ private:
std::atomic<double> m_capture_gate = 0.0;
std::atomic<double> m_capture_gain = 1.0;
std::atomic<double> m_prob_threshold = 0.5;
std::atomic<float> m_vad_prob = 0.0;
std::atomic<bool> m_enable_noise_suppression = false;
std::unordered_set<uint32_t> m_muted_ssrcs;
std::unordered_map<uint32_t, double> m_volume_ssrc;
@ -115,6 +153,10 @@ private:
AudioDevices m_devices;
VADMethod m_vad_method;
#ifdef WITH_RNNOISE
DenoiseState *m_rnnoise[2];
#endif
std::atomic<uint32_t> m_rtp_timestamp = 0;
public:

View File

@ -4,7 +4,7 @@
#include <glibmm/miscutils.h>
#ifdef WITH_KEYCHAIN
#include <keychain/keychain.h>
#include <keychain/keychain.h>
#endif
const std::string KeychainPackage = "com.github.uowuo.abaddon";
@ -70,6 +70,7 @@ void SettingsManager::ReadSettings() {
SMSTR("style", "unreadcolor", UnreadIndicatorColor);
SMBOOL("notifications", "enabled", NotificationsEnabled);
SMBOOL("notifications", "playsound", NotificationsPlaySound);
SMSTR("voice", "vad", VAD);
SMBOOL("windows", "hideconsole", HideConsole);
#ifdef WITH_KEYCHAIN
@ -154,6 +155,7 @@ void SettingsManager::Close() {
SMSTR("style", "unreadcolor", UnreadIndicatorColor);
SMBOOL("notifications", "enabled", NotificationsEnabled);
SMBOOL("notifications", "playsound", NotificationsPlaySound);
SMSTR("voice", "vad", VAD);
SMBOOL("windows", "hideconsole", HideConsole);
#ifdef WITH_KEYCHAIN

View File

@ -53,6 +53,13 @@ public:
#endif
bool NotificationsPlaySound { true };
// [voice]
#ifdef WITH_RNNOISE
std::string VAD { "rnnoise" };
#else
std::string VAD { "gate" };
#endif
// [windows]
bool HideConsole { false };
};

View File

@ -88,6 +88,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
, m_controls(Gtk::ORIENTATION_HORIZONTAL)
, m_mute("Mute")
, m_deafen("Deafen")
, m_noise_suppression("Suppress Noise")
, m_channel_id(channel_id)
, m_menu_view("View")
, m_menu_view_settings("More _Settings", true) {
@ -115,31 +116,74 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
m_scroll.set_hexpand(true);
m_scroll.set_vexpand(true);
m_capture_volume.SetShowTick(true);
m_vad_value.SetShowTick(true);
m_capture_gate.set_range(0.0, 100.0);
m_capture_gate.set_value_pos(Gtk::POS_LEFT);
m_capture_gate.set_value(audio.GetCaptureGate() * 100.0);
m_capture_gate.signal_value_changed().connect([this]() {
const double val = m_capture_gate.get_value() * 0.01;
m_signal_gate.emit(val);
m_capture_volume.SetTick(val);
m_vad_param.set_range(0.0, 100.0);
m_vad_param.set_value_pos(Gtk::POS_LEFT);
m_vad_param.signal_value_changed().connect([this]() {
auto &audio = Abaddon::Get().GetAudio();
const double val = m_vad_param.get_value() * 0.01;
switch (audio.GetVADMethod()) {
case AudioManager::VADMethod::Gate:
audio.SetCaptureGate(val);
m_vad_value.SetTick(val);
break;
#ifdef WITH_RNNOISE
case AudioManager::VADMethod::RNNoise:
audio.SetRNNProbThreshold(val);
m_vad_value.SetTick(val);
break;
#endif
};
});
UpdateVADParamValue();
m_capture_gain.set_range(0.0, 200.0);
m_capture_gain.set_value_pos(Gtk::POS_LEFT);
m_capture_gain.set_value(audio.GetCaptureGain() * 100.0);
m_capture_gain.signal_value_changed().connect([this]() {
const double val = m_capture_gain.get_value();
m_signal_gain.emit(val / 100.0);
const double val = m_capture_gain.get_value() / 100.0;
Abaddon::Get().GetAudio().SetCaptureGain(val);
});
m_vad_combo.set_valign(Gtk::ALIGN_END);
m_vad_combo.set_hexpand(true);
m_vad_combo.set_halign(Gtk::ALIGN_FILL);
m_vad_combo.set_tooltip_text(
"Voice Activation Detection method\n"
"Gate - Simple volume threshold. Slider changes threshold\n"
"RNNoise - Heavier on CPU. Slider changes probability threshold");
m_vad_combo.append("gate", "Gate");
#ifdef WITH_RNNOISE
m_vad_combo.append("rnnoise", "RNNoise");
#endif
if (!m_vad_combo.set_active_id(Abaddon::Get().GetSettings().VAD)) {
#ifdef WITH_RNNOISE
m_vad_combo.set_active_id("rnnoise");
#else
m_vad_combo.set_active_id("gate");
#endif
}
m_vad_combo.signal_changed().connect([this]() {
auto &audio = Abaddon::Get().GetAudio();
const auto id = m_vad_combo.get_active_id();
audio.SetVADMethod(id);
Abaddon::Get().GetSettings().VAD = id;
UpdateVADParamValue();
});
m_noise_suppression.set_active(audio.GetSuppressNoise());
m_noise_suppression.signal_toggled().connect([this]() {
Abaddon::Get().GetAudio().SetSuppressNoise(m_noise_suppression.get_active());
});
auto *playback_renderer = Gtk::make_managed<Gtk::CellRendererText>();
m_playback_combo.set_valign(Gtk::ALIGN_END);
m_playback_combo.set_hexpand(true);
m_playback_combo.set_halign(Gtk::ALIGN_FILL);
m_playback_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetPlaybackDeviceModel());
if (const auto iter = Abaddon::Get().GetAudio().GetDevices().GetActivePlaybackDevice()) {
m_playback_combo.set_model(audio.GetDevices().GetPlaybackDeviceModel());
if (const auto iter = audio.GetDevices().GetActivePlaybackDevice()) {
m_playback_combo.set_active(iter);
}
m_playback_combo.pack_start(*playback_renderer);
@ -169,7 +213,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
auto *window = new VoiceSettingsWindow;
const auto cb = [this](double gain) {
m_capture_gain.set_value(gain * 100.0);
m_signal_gain.emit(gain);
Abaddon::Get().GetAudio().SetCaptureGain(gain);
};
window->signal_gain().connect(sigc::track_obj(cb, *this));
window->show();
@ -178,12 +222,14 @@ VoiceWindow::VoiceWindow(Snowflake channel_id)
m_scroll.add(m_user_list);
m_controls.add(m_mute);
m_controls.add(m_deafen);
m_controls.add(m_noise_suppression);
m_main.add(m_menu_bar);
m_main.add(m_controls);
m_main.add(m_capture_volume);
m_main.add(m_capture_gate);
m_main.add(m_vad_value);
m_main.add(m_vad_param);
m_main.add(m_capture_gain);
m_main.add(m_scroll);
m_main.add(m_vad_combo);
m_main.add(m_playback_combo);
m_main.add(m_capture_combo);
add(m_main);
@ -224,16 +270,41 @@ void VoiceWindow::OnDeafenChanged() {
}
bool VoiceWindow::UpdateVoiceMeters() {
m_capture_volume.SetVolume(Abaddon::Get().GetAudio().GetCaptureVolumeLevel());
auto &audio = Abaddon::Get().GetAudio();
switch (audio.GetVADMethod()) {
case AudioManager::VADMethod::Gate:
m_vad_value.SetVolume(audio.GetCaptureVolumeLevel());
break;
#ifdef WITH_RNNOISE
case AudioManager::VADMethod::RNNoise:
m_vad_value.SetVolume(audio.GetCurrentVADProbability());
break;
#endif
}
for (auto [id, row] : m_rows) {
const auto ssrc = Abaddon::Get().GetDiscordClient().GetSSRCOfUser(id);
if (ssrc.has_value()) {
row->SetVolumeMeter(Abaddon::Get().GetAudio().GetSSRCVolumeLevel(*ssrc));
row->SetVolumeMeter(audio.GetSSRCVolumeLevel(*ssrc));
}
}
return true;
}
void VoiceWindow::UpdateVADParamValue() {
auto &audio = Abaddon::Get().GetAudio();
switch (audio.GetVADMethod()) {
case AudioManager::VADMethod::Gate:
m_vad_param.set_value(audio.GetCaptureGate() * 100.0);
break;
#ifdef WITH_RNNOISE
case AudioManager::VADMethod::RNNoise:
m_vad_param.set_value(audio.GetRNNProbThreshold() * 100.0);
break;
#endif
}
}
void VoiceWindow::OnUserConnect(Snowflake user_id, Snowflake to_channel_id) {
if (m_channel_id == to_channel_id) {
if (auto it = m_rows.find(user_id); it == m_rows.end()) {
@ -259,14 +330,6 @@ VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() {
return m_signal_deafen;
}
VoiceWindow::type_signal_gate VoiceWindow::signal_gate() {
return m_signal_gate;
}
VoiceWindow::type_signal_gate VoiceWindow::signal_gain() {
return m_signal_gain;
}
VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() {
return m_signal_mute_user_cs;
}

View File

@ -34,6 +34,8 @@ private:
bool UpdateVoiceMeters();
void UpdateVADParamValue();
Gtk::Box m_main;
Gtk::Box m_controls;
@ -43,10 +45,17 @@ private:
Gtk::ScrolledWindow m_scroll;
Gtk::ListBox m_user_list;
VolumeMeter m_capture_volume;
Gtk::Scale m_capture_gate;
// Shows volume for gate VAD method
// Shows probability for RNNoise VAD method
VolumeMeter m_vad_value;
// Volume threshold for gate VAD method
// VAD probability threshold for RNNoise VAD method
Gtk::Scale m_vad_param;
Gtk::Scale m_capture_gain;
Gtk::CheckButton m_noise_suppression;
Gtk::ComboBoxText m_vad_combo;
Gtk::ComboBox m_playback_combo;
Gtk::ComboBox m_capture_combo;
@ -62,23 +71,17 @@ private:
public:
using type_signal_mute = sigc::signal<void(bool)>;
using type_signal_deafen = sigc::signal<void(bool)>;
using type_signal_gate = sigc::signal<void(double)>;
using type_signal_gain = sigc::signal<void(double)>;
using type_signal_mute_user_cs = sigc::signal<void(Snowflake, bool)>;
using type_signal_user_volume_changed = sigc::signal<void(Snowflake, double)>;
type_signal_mute signal_mute();
type_signal_deafen signal_deafen();
type_signal_gate signal_gate();
type_signal_gain signal_gain();
type_signal_mute_user_cs signal_mute_user_cs();
type_signal_user_volume_changed signal_user_volume_changed();
private:
type_signal_mute m_signal_mute;
type_signal_deafen m_signal_deafen;
type_signal_gate m_signal_gate;
type_signal_gain m_signal_gain;
type_signal_mute_user_cs m_signal_mute_user_cs;
type_signal_user_volume_changed m_signal_user_volume_changed;
};

1
subprojects/rnnoise Submodule

@ -0,0 +1 @@
Subproject commit 1cbdbcf1283499bbb2230a6b0f126eb9b236defd