Files
utun2/tools/chatgui/src/audiodevicesettingspage.cpp
T

517 lines
24 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <QCoreApplication>
#include "audiodevicesettingspage.h"
#include "sound_manager.h"
#include "audiorecorder.h"
#include "voicemessageencoder.h"
extern "C" {
#include "../../lib/audio_compressor.h"
}
#include "../../lib/debug_config.h"
#include "../db/db_manager.h"
#include "../transport/gui_bridge.h"
#include "../transport/config_updater.h"
#include "../../lib/mem.h"
extern "C" {
#include "chat/chat_core.h"
#include "call/call_video_rate.h"
}
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QFormLayout>
#include <QFrame>
#include <QDebug>
#include <QDoubleSpinBox>
#include <QSpinBox>
#include <QMessageBox>
#include <cmath>
AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, AudioRecorder* recorder, QWidget* parent)
: QWidget(parent), m_db(db), m_recorder(recorder)
{
auto* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 12, 0, 0);
auto* formLayout = new QFormLayout();
formLayout->setContentsMargins(0, 0, 0, 0);
formLayout->setSpacing(10);
auto* outLabel = new QLabel(QCoreApplication::translate("ChatGui", "Playback device:"), this);
m_outputCombo = new QComboBox(this);
m_outputCombo->setStyleSheet(
"QComboBox { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 10px; font-size: 13px; background: palette(base); }");
formLayout->addRow(outLabel, m_outputCombo);
auto* inLabel = new QLabel(QCoreApplication::translate("ChatGui", "Recording device:"), this);
m_inputCombo = new QComboBox(this);
m_inputCombo->setStyleSheet(m_outputCombo->styleSheet());
formLayout->addRow(inLabel, m_inputCombo);
layout->addLayout(formLayout);
/* --- Separator --- */
auto* sep1 = new QFrame(this);
sep1->setFrameShape(QFrame::HLine);
sep1->setStyleSheet("QFrame { color: palette(mid); }");
layout->addWidget(sep1);
/* --- Microphone test --- */
auto* micLabel = new QLabel(QCoreApplication::translate("ChatGui", "Microphone test"), this);
micLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(micLabel);
auto* micRow = new QHBoxLayout();
micRow->setSpacing(8);
m_micLevelBar = new QProgressBar(this);
m_micLevelBar->setRange(0, 100);
m_micLevelBar->setValue(0);
m_micLevelBar->setTextVisible(false);
m_micLevelBar->setFixedHeight(16);
micRow->addWidget(m_micLevelBar, 1);
m_testMicBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Test Microphone"), this);
m_testMicBtn->setCheckable(true);
m_testMicBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 4px 12px; font-size: 12px; background: palette(button); }"
"QPushButton:checked { background: #c0392b; color: white; }");
connect(m_testMicBtn, &QPushButton::toggled, this, &AudioDeviceSettingsPage::onTestMicToggled);
micRow->addWidget(m_testMicBtn);
layout->addLayout(micRow);
/* --- Separator --- */
auto* sep2 = new QFrame(this);
sep2->setFrameShape(QFrame::HLine);
sep2->setStyleSheet("QFrame { color: palette(mid); }");
layout->addWidget(sep2);
/* --- Speaker test --- */
auto* spkLabel = new QLabel(QCoreApplication::translate("ChatGui", "Speaker test"), this);
spkLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(spkLabel);
m_testSpeakerBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Test Speaker (440Hz)"), this);
m_testSpeakerBtn->setStyleSheet(
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 6px 16px; font-size: 13px; background: palette(button); }"
"QPushButton:hover { background: palette(light); }");
connect(m_testSpeakerBtn, &QPushButton::clicked, this, &AudioDeviceSettingsPage::onTestSpeaker);
layout->addWidget(m_testSpeakerBtn);
/* --- Separator --- */
auto* sep3 = new QFrame(this);
sep3->setFrameShape(QFrame::HLine);
sep3->setStyleSheet("QFrame { color: palette(mid); }");
layout->addWidget(sep3);
/* --- Codec quality --- */
auto* codecLabel = new QLabel(QCoreApplication::translate("ChatGui", "Voice codec quality"), this);
codecLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(codecLabel);
m_codecPresetCombo = new QComboBox(this);
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "Low (16 kbps, fast)"), 0);
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "Standard (32 kbps)"), 1);
m_codecPresetCombo->addItem(QCoreApplication::translate("ChatGui", "High (64 kbps, best)"), 2);
m_codecPresetCombo->setStyleSheet(m_outputCombo->styleSheet());
layout->addWidget(m_codecPresetCombo);
/* --- Separator --- */
auto* sep5 = new QFrame(this);
sep5->setFrameShape(QFrame::HLine);
sep5->setStyleSheet("QFrame { color: palette(mid); }");
layout->addWidget(sep5);
/* --- Radio (PTT) Opus --- */
auto* radioLabel = new QLabel(QCoreApplication::translate("ChatGui", "Radio (PTT) — Opus"), this);
radioLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(radioLabel);
auto* radioForm = new QFormLayout();
radioForm->setContentsMargins(0, 0, 0, 0);
radioForm->setSpacing(10);
m_radioOpusModeCombo = new QComboBox(this);
m_radioOpusModeCombo->addItem(QCoreApplication::translate("ChatGui", "Voice (VOIP)"), 0);
m_radioOpusModeCombo->addItem(QCoreApplication::translate("ChatGui", "Music (AUDIO)"), 1);
m_radioOpusModeCombo->setStyleSheet(m_outputCombo->styleSheet());
radioForm->addRow(new QLabel(QCoreApplication::translate("ChatGui", "Mode:"), this), m_radioOpusModeCombo);
m_radioOpusBitrateCombo = new QComboBox(this);
for (int br : {16, 32, 64, 96, 128})
m_radioOpusBitrateCombo->addItem(QString::number(br) + QCoreApplication::translate("ChatGui", " kbps"), br);
m_radioOpusBitrateCombo->setStyleSheet(m_outputCombo->styleSheet());
radioForm->addRow(new QLabel(QCoreApplication::translate("ChatGui", "Bitrate:"), this), m_radioOpusBitrateCombo);
layout->addLayout(radioForm);
m_radioCaptureStereoCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Capture stereo (playback is always stereo)"), this);
m_radioCaptureStereoCb->setStyleSheet("font-size: 12px;");
layout->addWidget(m_radioCaptureStereoCb);
/* --- Separator --- */
auto* sep4 = new QFrame(this);
sep4->setFrameShape(QFrame::HLine);
sep4->setStyleSheet("QFrame { color: palette(mid); }");
layout->addWidget(sep4);
auto* callForm = new QFormLayout();
m_jitterReserveSpin = new QDoubleSpinBox(this);
m_jitterReserveSpin->setRange(0.5, 2.0);
m_jitterReserveSpin->setDecimals(1);
m_jitterReserveSpin->setSingleStep(0.1);
m_jitterReserveSpin->setValue(1.0);
m_jitterReserveSpin->setSuffix(QCoreApplication::translate("ChatGui", " s"));
m_jitterReserveSpin->setToolTip(QCoreApplication::translate("ChatGui", "Maximum adaptive jitter reserve for calls. Applies to the next call."));
callForm->addRow(QCoreApplication::translate("ChatGui", "Call: maximum jitter reserve"), m_jitterReserveSpin);
m_videoMinimumSpin = new QSpinBox(this);
m_videoMaximumSpin = new QSpinBox(this);
for (auto* spin : {m_videoMinimumSpin, m_videoMaximumSpin}) {
spin->setRange(CALL_VIDEO_RATE_MIN_BPS / 1000, CALL_VIDEO_RATE_MAX_BPS / 1000);
spin->setSingleStep(100);
spin->setSuffix(QCoreApplication::translate("ChatGui", " kbit/s"));
}
m_videoMinimumSpin->setValue(CALL_VIDEO_RATE_MIN_BPS / 1000);
m_videoMaximumSpin->setValue(CALL_VIDEO_RATE_MAX_BPS / 1000);
connect(m_videoMinimumSpin, QOverload<int>::of(&QSpinBox::valueChanged), m_videoMaximumSpin, &QSpinBox::setMinimum);
connect(m_videoMaximumSpin, QOverload<int>::of(&QSpinBox::valueChanged), m_videoMinimumSpin, &QSpinBox::setMaximum);
callForm->addRow(QCoreApplication::translate("ChatGui", "Call video: minimum bitrate"), m_videoMinimumSpin);
callForm->addRow(QCoreApplication::translate("ChatGui", "Call video: maximum bitrate"), m_videoMaximumSpin);
layout->addLayout(callForm);
auto* videoHint = new QLabel(QCoreApplication::translate("ChatGui",
"Video bitrate adapts to the connection within this range. Default: 300–4000 kbit/s."), this);
videoHint->setWordWrap(true);
layout->addWidget(videoHint);
buildCompressorUI(layout);
layout->addStretch();
m_levelTimer = new QTimer(this);
m_levelTimer->setInterval(25);
connect(m_levelTimer, &QTimer::timeout, this, &AudioDeviceSettingsPage::onLevelTimer);
}
AudioDeviceSettingsPage::~AudioDeviceSettingsPage() {
stopMicTest();
}
void AudioDeviceSettingsPage::loadFromDb() {
auto playbackDevs = SoundManager::enumeratePlaybackDevices();
auto captureDevs = SoundManager::enumerateCaptureDevices();
/* Output devices */
m_outputCombo->clear();
m_outputCombo->addItem(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
for (int i = 0; i < playbackDevs.size(); i++) {
QString label = playbackDevs[i].name;
if (playbackDevs[i].isDefault) label += QCoreApplication::translate("ChatGui", " (default)");
m_outputCombo->addItem(label, playbackDevs[i].id);
}
/* Input devices */
m_inputCombo->clear();
m_inputCombo->addItem(QCoreApplication::translate("ChatGui", "Default (auto)"), QString());
for (int i = 0; i < captureDevs.size(); i++) {
QString label = captureDevs[i].name;
if (captureDevs[i].isDefault) label += QCoreApplication::translate("ChatGui", " (default)");
m_inputCombo->addItem(label, captureDevs[i].id);
}
if (m_db) {
m_savedOutputId = m_db->getUiState("audio_output_id");
m_savedInputId = m_db->getUiState("audio_input_id");
m_savedCodecPreset = m_db->getUiStateInt("opus_codec_preset", 1);
m_savedRadioOpusMode = m_db->getUiStateInt("radio_opus_mode", 0);
m_savedRadioOpusBitrate = m_db->getUiStateInt("radio_opus_bitrate", 32);
m_savedRadioCaptureStereo = m_db->getUiStateInt("radio_capture_stereo", 0);
m_savedCompressorEnabled = m_db->getUiStateInt("compressor_enabled", 0);
m_savedCompressorMaxGainDb = m_db->getUiStateInt("compressor_max_gain_db", 25);
m_savedCompressorRiseRate = m_db->getUiStateInt("compressor_rise_rate", 10);
m_jitterReserveSpin->setValue(m_db->getUiStateInt("call_jitter_max_reserve_ms", 1000) / 1000.0);
QByteArray range = m_db->getUiState("call_video_bitrate_range_kbps").toUtf8();
uint32_t minimum = CALL_VIDEO_RATE_MIN_BPS, maximum = CALL_VIDEO_RATE_MAX_BPS;
if (!range.isEmpty()) call_video_rate_parse_range(range.constData(), &minimum, &maximum);
m_videoMinimumSpin->setValue(CALL_VIDEO_RATE_MIN_BPS / 1000);
m_videoMaximumSpin->setValue(maximum / 1000);
m_videoMinimumSpin->setValue(minimum / 1000);
m_savedAecEnabled = m_db->getUiStateInt("aec_enabled", 1);
}
/* Select saved indices */
for (int i = 0; i < m_outputCombo->count(); i++) {
if (m_outputCombo->itemData(i).toString() == m_savedOutputId)
{ m_outputCombo->setCurrentIndex(i); break; }
}
for (int i = 0; i < m_inputCombo->count(); i++) {
if (m_inputCombo->itemData(i).toString() == m_savedInputId)
{ m_inputCombo->setCurrentIndex(i); break; }
}
for (int i = 0; i < m_codecPresetCombo->count(); i++) {
if (m_codecPresetCombo->itemData(i).toInt() == m_savedCodecPreset)
{ m_codecPresetCombo->setCurrentIndex(i); break; }
}
for (int i = 0; i < m_radioOpusModeCombo->count(); i++) {
if (m_radioOpusModeCombo->itemData(i).toInt() == m_savedRadioOpusMode)
{ m_radioOpusModeCombo->setCurrentIndex(i); break; }
}
for (int i = 0; i < m_radioOpusBitrateCombo->count(); i++) {
if (m_radioOpusBitrateCombo->itemData(i).toInt() == m_savedRadioOpusBitrate)
{ m_radioOpusBitrateCombo->setCurrentIndex(i); break; }
}
m_radioCaptureStereoCb->setChecked(m_savedRadioCaptureStereo != 0);
m_compressorEnabledCb->setChecked(m_savedCompressorEnabled != 0);
m_compressorMaxGainSlider->setValue(m_savedCompressorMaxGainDb);
m_compressorRiseRateSlider->setValue(m_savedCompressorRiseRate);
m_aecEnabledCb->setChecked(m_savedAecEnabled != 0);
updateCompressorLabels();
}
void AudioDeviceSettingsPage::applyAndSave() {
const char* rangeKey = "call_video_bitrate_range_kbps";
QString range = QStringLiteral("%1:%2").arg(m_videoMinimumSpin->value()).arg(m_videoMaximumSpin->value());
if (!m_configPath.isEmpty() && !config_update_option(m_configPath, "chatserver", rangeKey, range)) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video bitrate range config save failed path=%s", qPrintable(m_configPath));
QMessageBox::warning(this, QCoreApplication::translate("ChatGui", "Settings"),
QCoreApplication::translate("ChatGui", "Could not save the call video bitrate range."));
return;
}
auto* rangeArg = static_cast<chat_setting_arg*>(u_calloc(1, sizeof(chat_setting_arg)));
if (!rangeArg) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video bitrate range command allocation failed"); return; }
rangeArg->inst = gui_bridge_get_inst();
memcpy(rangeArg->data, rangeKey, strlen(rangeKey) + 1);
QByteArray rangeBytes = range.toUtf8();
memcpy(rangeArg->data + strlen(rangeKey) + 1, rangeBytes.constData(), rangeBytes.size() + 1);
gui_bridge_post_uasync_fn(chat_core_set_setting_trampoline, rangeArg);
QString outputId = m_outputCombo->currentData().toString();
QString inputId = m_inputCombo->currentData().toString();
int codecPreset = m_codecPresetCombo->currentData().toInt();
auto saveKey = [](const char* key, int val) {
QByteArray v = QByteArray::number(val);
size_t klen = strlen(key), vlen = (size_t)v.size();
size_t total = klen + 1 + vlen + 1;
if (total > 256) return;
struct save_ui_state_arg* arg = (struct save_ui_state_arg*)u_malloc(sizeof(struct save_ui_state_arg));
if (!arg) return;
arg->inst = gui_bridge_get_inst();
memcpy(arg->data, key, klen + 1);
memcpy(arg->data + klen + 1, v.constData(), vlen + 1);
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
};
auto saveText = [](const char* key, const QString& id) {
QByteArray value = id.toUtf8();
size_t keyLen = strlen(key), valueLen = value.size();
auto* arg = static_cast<save_ui_state_arg*>(u_calloc(1, sizeof(save_ui_state_arg)));
if (!arg) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "settings save allocation failed key=%s", key); return; }
if (keyLen + valueLen + 2 > sizeof(arg->data)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "settings value too long key=%s bytes=%zu", key, valueLen);
u_free(arg); return;
}
arg->inst = gui_bridge_get_inst();
memcpy(arg->data, key, keyLen + 1);
memcpy(arg->data + keyLen + 1, value.constData(), valueLen + 1);
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
};
saveText("audio_output_id", outputId);
saveText("audio_input_id", inputId);
saveText(rangeKey, range);
SoundManager::instance()->setDevices(outputId, inputId);
saveKey("opus_codec_preset", codecPreset);
VoiceEncoder::setPreset(codecPreset);
int radioOpusMode = m_radioOpusModeCombo->currentData().toInt();
int radioOpusBitrate = m_radioOpusBitrateCombo->currentData().toInt();
int radioCaptureStereo = m_radioCaptureStereoCb->isChecked() ? 1 : 0;
saveKey("radio_opus_mode", radioOpusMode);
saveKey("radio_opus_bitrate", radioOpusBitrate);
saveKey("radio_capture_stereo", radioCaptureStereo);
int compEnabled = m_compressorEnabledCb->isChecked() ? 1 : 0;
int compMaxGain = m_compressorMaxGainSlider->value();
int compRiseRate = m_compressorRiseRateSlider->value();
saveKey("compressor_enabled", compEnabled);
saveKey("compressor_max_gain_db", compMaxGain);
saveKey("compressor_rise_rate", compRiseRate);
int jitterReserveMs = (int)std::lround(m_jitterReserveSpin->value() * 1000);
saveKey("call_jitter_max_reserve_ms", jitterReserveMs);
int aecEnabled = m_aecEnabledCb->isChecked() ? 1 : 0;
saveKey("aec_enabled", aecEnabled);
/* also update [chat] config section + live apply for headless-compat settings */
if (!m_configPath.isEmpty()) {
auto postChat = [](const QString& path, const char* name, int val) {
config_update_option(path, "chatserver", name, QString::number(val), CONFIG_UPDATE_REPLACE);
QByteArray v = QByteArray::number(val);
size_t nlen = strlen(name), vlen = (size_t)v.size(), total = nlen + 1 + vlen + 1;
if (total > 256) return;
struct chat_setting_arg* arg = (struct chat_setting_arg*)u_malloc(sizeof(struct chat_setting_arg));
if (!arg) return;
arg->inst = gui_bridge_get_inst();
memcpy(arg->data, name, nlen + 1);
memcpy(arg->data + nlen + 1, v.constData(), vlen + 1);
gui_bridge_post_uasync_fn(chat_core_set_setting_trampoline, arg);
};
postChat(m_configPath, "opus_codec_preset", codecPreset);
postChat(m_configPath, "radio_opus_mode", radioOpusMode);
postChat(m_configPath, "radio_opus_bitrate", radioOpusBitrate);
postChat(m_configPath, "radio_capture_stereo", radioCaptureStereo);
postChat(m_configPath, "compressor_enabled", compEnabled);
postChat(m_configPath, "compressor_max_gain_db", compMaxGain);
postChat(m_configPath, "compressor_rise_rate", compRiseRate);
postChat(m_configPath, "aec_enabled", aecEnabled);
postChat(m_configPath, "call_jitter_max_reserve_ms", jitterReserveMs);
}
m_savedOutputId = outputId;
m_savedInputId = inputId;
m_savedCodecPreset = codecPreset;
m_savedRadioOpusMode = radioOpusMode;
m_savedRadioOpusBitrate = radioOpusBitrate;
m_savedRadioCaptureStereo = radioCaptureStereo;
m_savedCompressorEnabled = compEnabled;
m_savedCompressorMaxGainDb = compMaxGain;
m_savedCompressorRiseRate = compRiseRate;
m_savedAecEnabled = aecEnabled;
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG,
"saved output_default=%d input_default=%d codec=%d radioOpus=%d/%dkbps stereo=%d compressor=%d gain=%ddB rise=%.1fx aec=%d",
outputId.isEmpty(), inputId.isEmpty(), codecPreset, radioOpusMode, radioOpusBitrate, radioCaptureStereo,
compEnabled, compMaxGain, (float)compRiseRate, aecEnabled);
if (m_recorder) {
m_recorder->setCompressorEnabled(compEnabled != 0);
{
audio_compressor_config_t compCfg = {0};
compCfg.sample_rate = 48000;
compCfg.channels = 1;
compCfg.max_gain_db = (float)compMaxGain;
compCfg.lookback_ms = 200;
compCfg.lookahead_ms = 100;
compCfg.rise_rate_per_sec = (float)compRiseRate;
compCfg.target_level = 1.0f;
m_recorder->configureCompressor(compCfg);
}
}
}
void AudioDeviceSettingsPage::onTestMicToggled() {
if (m_testMicBtn->isChecked()) {
m_testRecorder = new AudioRecorder(this);
m_testRecorder->init();
QString id = m_inputCombo->currentData().toString();
m_testRecorder->setCaptureDevice(id);
m_testRecorder->startRecording(true);
m_micTestActive = true;
m_levelTimer->start();
m_testMicBtn->setText(QCoreApplication::translate("ChatGui", "Stop Test"));
m_micLevelBar->setValue(0);
} else {
stopMicTest();
m_testMicBtn->setText(QCoreApplication::translate("ChatGui", "Test Microphone"));
}
}
void AudioDeviceSettingsPage::stopMicTest() {
m_levelTimer->stop();
if (m_testRecorder) {
m_testRecorder->stopRecording();
m_testRecorder->shutdown();
delete m_testRecorder;
m_testRecorder = nullptr;
}
m_micTestActive = false;
m_micLevelBar->setValue(0);
m_testMicBtn->setChecked(false);
}
void AudioDeviceSettingsPage::onLevelTimer() {
if (!m_micTestActive || !m_testRecorder || !m_testRecorder->isRecording()) {
stopMicTest();
return;
}
float peak = m_testRecorder->peakLevel();
/* Linear to dB: 20*log10(peak), map -60..0 dB to 0..100 */
float dbVal = peak > 0.0001f ? 20.0f * std::log10(peak) : -70.0f;
int pct = (int)((dbVal + 70.0f) / 70.0f * 100.0f);
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
m_micLevelBar->setValue(pct);
const char* color = pct < 60 ? "#27ae60" : pct < 85 ? "#f1c40f" : "#e74c3c";
m_micLevelBar->setStyleSheet(QString(
"QProgressBar { border: 1px solid palette(mid); border-radius: 4px; background: #333; }"
"QProgressBar::chunk { background: %1; border-radius: 3px; }").arg(color));
}
void AudioDeviceSettingsPage::onTestSpeaker() {
int sampleRate = 48000;
int samples = sampleRate / 4; /* 250ms */
std::vector<int16_t> pcm(samples);
for (int i = 0; i < samples; i++)
pcm[i] = (int16_t)(12000.0 * sin(2.0 * M_PI * 440.0 * i / sampleRate));
SoundManager::instance()->playRawPcm(pcm.data(), samples, sampleRate, 1, {}, true);
}
void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
auto* compLabel = new QLabel(QCoreApplication::translate("ChatGui", "Voice Compressor"), this);
compLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(compLabel);
m_compressorEnabledCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Enable compressor (leveler/normalizer)"), this);
m_compressorEnabledCb->setStyleSheet("font-size: 12px;");
layout->addWidget(m_compressorEnabledCb);
m_aecEnabledCb = new QCheckBox(QCoreApplication::translate("ChatGui", "Enable echo cancellation (AEC) in calls"), this);
m_aecEnabledCb->setStyleSheet("font-size: 12px;");
layout->addWidget(m_aecEnabledCb);
auto makeSliderRow = [&](const QString& name, QSlider*& slider, QLabel*& lbl,
int min, int max, int step) {
auto* row = new QHBoxLayout();
row->setSpacing(6);
auto* textLabel = new QLabel(name, this);
textLabel->setMinimumWidth(180);
textLabel->setStyleSheet("font-size: 12px;");
row->addWidget(textLabel);
slider = new QSlider(Qt::Horizontal, this);
slider->setRange(min, max);
slider->setSingleStep(step);
slider->setStyleSheet(
"QSlider::groove:horizontal { height: 4px; background: palette(mid); border-radius: 2px; }"
"QSlider::handle:horizontal { width: 12px; margin: -5px 0; background: palette(highlight); border-radius: 6px; }");
row->addWidget(slider, 1);
lbl = new QLabel(this);
lbl->setFixedWidth(60);
lbl->setStyleSheet("font-size: 12px;");
lbl->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
row->addWidget(lbl);
layout->addLayout(row);
};
makeSliderRow(QCoreApplication::translate("ChatGui", "Max gain:"), m_compressorMaxGainSlider, m_compressorMaxGainLabel,
1, 50, 1);
makeSliderRow(QCoreApplication::translate("ChatGui", "Rise rate (x/sec):"), m_compressorRiseRateSlider, m_compressorRiseRateLabel,
2, 10, 1);
auto connectSlider = [this](QSlider* slider) {
connect(slider, &QSlider::valueChanged, this, &AudioDeviceSettingsPage::updateCompressorLabels);
};
connectSlider(m_compressorMaxGainSlider);
connectSlider(m_compressorRiseRateSlider);
}
void AudioDeviceSettingsPage::updateCompressorLabels() {
if (m_compressorMaxGainLabel)
m_compressorMaxGainLabel->setText(QString::number(m_compressorMaxGainSlider->value()) + QCoreApplication::translate("ChatGui", " dB"));
if (m_compressorRiseRateLabel) {
int v = m_compressorRiseRateSlider->value();
m_compressorRiseRateLabel->setText(QString::number(v) + "x");
}
}