diff options
author | ouwou <26526779+ouwou@users.noreply.github.com> | 2023-08-26 07:44:40 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-26 07:44:40 +0000 |
commit | 97240237585db9abac22eb214f1865694706af1e (patch) | |
tree | 46384ad17c988d679b4137805a3dbc9212a50629 | |
parent | eea5987f379cd76786ace382da09dd687ecca28a (diff) | |
parent | daacad489a4cd2a080c8bb811d9c8fb26c80b7cb (diff) | |
download | abaddon-portaudio-97240237585db9abac22eb214f1865694706af1e.tar.gz abaddon-portaudio-97240237585db9abac22eb214f1865694706af1e.zip |
Merge pull request #195 from uowuo/rnnoise
Use rnnoise for VAD
-rw-r--r-- | .clang-format | 2 | ||||
-rw-r--r-- | .github/workflows/ci.yml | 2 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 47 | ||||
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | cmake/Findrnnoise.cmake | 48 | ||||
-rw-r--r-- | src/abaddon.cpp | 19 | ||||
-rw-r--r-- | src/audio/manager.cpp | 146 | ||||
-rw-r--r-- | src/audio/manager.hpp | 64 | ||||
-rw-r--r-- | src/settings.cpp | 4 | ||||
-rw-r--r-- | src/settings.hpp | 7 | ||||
-rw-r--r-- | src/windows/voicewindow.cpp | 115 | ||||
-rw-r--r-- | src/windows/voicewindow.hpp | 19 | ||||
m--------- | subprojects/rnnoise | 0 |
14 files changed, 418 insertions, 65 deletions
diff --git a/.clang-format b/.clang-format index 074ecf0..6bd8ca0 100644 --- a/.clang-format +++ b/.clang-format @@ -35,7 +35,7 @@ DerivePointerAlignment: 'false' FixNamespaceComments: 'true' IncludeBlocks: Merge IndentCaseLabels: 'true' -IndentPPDirectives: BeforeHash +IndentPPDirectives: None IndentWidth: '4' IndentWrappedFunctionNames: 'false' JavaScriptQuotes: Double diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6cfb8b7..0f20cb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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" diff --git a/.gitmodules b/.gitmodules index aae3862..e173bb4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index b7fe511..6804671 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") @@ -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 | diff --git a/cmake/Findrnnoise.cmake b/cmake/Findrnnoise.cmake new file mode 100644 index 0000000..588c1e3 --- /dev/null +++ b/cmake/Findrnnoise.cmake @@ -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() + diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 2be54ba..a8360c2 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -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); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index a2fd4df..739e835 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -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; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index ed40f35..80a2542 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -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; + + double GetCaptureVolumeLevel() const noexcept; + double GetSSRCVolumeLevel(uint32_t ssrc) const noexcept; - [[nodiscard]] double GetCaptureVolumeLevel() const noexcept; - [[nodiscard]] double GetSSRCVolumeLevel(uint32_t ssrc) const noexcept; + AudioDevices &GetDevices(); - [[nodiscard]] AudioDevices &GetDevices(); + uint32_t GetRTPTimestamp() const noexcept; - [[nodiscard]] 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: diff --git a/src/settings.cpp b/src/settings.cpp index 0b868da..601205a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -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 diff --git a/src/settings.hpp b/src/settings.hpp index 40cb1d3..0d2e68b 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -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 }; }; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 829a1b8..73a09c2 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -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_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_value.SetShowTick(true); + + 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; } diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index 805d94a..6db2563 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -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; }; diff --git a/subprojects/rnnoise b/subprojects/rnnoise new file mode 160000 +Subproject 1cbdbcf1283499bbb2230a6b0f126eb9b236def |