475 lines
21 KiB
C++
475 lines
21 KiB
C++
#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 <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QFormLayout>
|
|
#include <QFrame>
|
|
#include <QDebug>
|
|
#include <QDoubleSpinBox>
|
|
#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);
|
|
layout->addLayout(callForm);
|
|
|
|
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);
|
|
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() {
|
|
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 saveDevice = [](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) return;
|
|
if (keyLen + valueLen + 2 > sizeof(arg->data)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "audio: device ID too long bytes=%zu", 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);
|
|
};
|
|
saveDevice("audio_output_id", outputId);
|
|
saveDevice("audio_input_id", inputId);
|
|
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");
|
|
}
|
|
}
|