feat: voice compressor with RMS+peak leveling, sliding window envelope, rate-limited gain

This commit is contained in:
Evgeny committed 2026-07-27 11:24:47 +03:00
1 parent 7004b110ae
commit f1b599f0c0
8 files changed
+406 -9

No files matched your search

+114 -1
View File
@@ -1,6 +1,7 @@
#include "audiodevicesettingspage.h"
#include "sound_manager.h"
#include "audiorecorder.h"
#include "audiocompressor.h"
#include "voicemessageencoder.h"
#include "../db/db_manager.h"
#include "../transport/gui_bridge.h"
@@ -111,6 +112,14 @@ AudioDeviceSettingsPage::AudioDeviceSettingsPage(DbManager* db, QWidget* parent)
m_codecPresetCombo->setStyleSheet(m_outputCombo->styleSheet());
layout->addWidget(m_codecPresetCombo);
/* --- Separator --- */
auto* sep4 = new QFrame(this);
sep4->setFrameShape(QFrame::HLine);
sep4->setStyleSheet("QFrame { color: palette(mid); }");
layout->addWidget(sep4);
buildCompressorUI(layout);
layout->addStretch();
m_levelTimer = new QTimer(this);
@@ -148,6 +157,12 @@ void AudioDeviceSettingsPage::loadFromDb() {
m_savedOutputIdx = m_db->getUiStateInt("audio_output_device", -1);
m_savedInputIdx = m_db->getUiStateInt("audio_input_device", -1);
m_savedCodecPreset = m_db->getUiStateInt("opus_codec_preset", 1);
m_savedCompressorEnabled = m_db->getUiStateInt("compressor_enabled", 0);
m_savedCompressorMaxGainDb = m_db->getUiStateInt("compressor_max_gain_db", 30);
m_savedCompressorLookbackMs = m_db->getUiStateInt("compressor_lookback_ms", 200);
m_savedCompressorLookaheadMs = m_db->getUiStateInt("compressor_lookahead_ms", 100);
m_savedCompressorRiseRateTenths = m_db->getUiStateInt("compressor_rise_rate_tenths", 20);
m_savedCompressorTargetLevelDb = m_db->getUiStateInt("compressor_target_level_db", -12);
}
/* Select saved indices */
@@ -163,6 +178,14 @@ void AudioDeviceSettingsPage::loadFromDb() {
if (m_codecPresetCombo->itemData(i).toInt() == m_savedCodecPreset)
{ m_codecPresetCombo->setCurrentIndex(i); break; }
}
m_compressorEnabledCb->setChecked(m_savedCompressorEnabled != 0);
m_compressorMaxGainSlider->setValue(m_savedCompressorMaxGainDb);
m_compressorLookbackSlider->setValue(m_savedCompressorLookbackMs);
m_compressorLookaheadSlider->setValue(m_savedCompressorLookaheadMs);
m_compressorRiseRateSlider->setValue(m_savedCompressorRiseRateTenths);
m_compressorTargetLevelSlider->setValue(m_savedCompressorTargetLevelDb);
updateCompressorLabels();
}
void AudioDeviceSettingsPage::applyAndSave() {
@@ -186,10 +209,32 @@ void AudioDeviceSettingsPage::applyAndSave() {
VoiceEncoder::setPreset(codecPreset);
int compEnabled = m_compressorEnabledCb->isChecked() ? 1 : 0;
int compMaxGain = m_compressorMaxGainSlider->value();
int compLookback = m_compressorLookbackSlider->value();
int compLookahead = m_compressorLookaheadSlider->value();
int compRiseRateTenths = m_compressorRiseRateSlider->value();
int compTargetLevel = m_compressorTargetLevelSlider->value();
saveKey("compressor_enabled", compEnabled);
saveKey("compressor_max_gain_db", compMaxGain);
saveKey("compressor_lookback_ms", compLookback);
saveKey("compressor_lookahead_ms", compLookahead);
saveKey("compressor_rise_rate_tenths", compRiseRateTenths);
saveKey("compressor_target_level_db", compTargetLevel);
m_savedOutputIdx = outIdx;
m_savedInputIdx = inIdx;
m_savedCodecPreset = codecPreset;
qDebug("AudioDeviceSettings: saved output=%d input=%d codec=%d", outIdx, inIdx, codecPreset);
m_savedCompressorEnabled = compEnabled;
m_savedCompressorMaxGainDb = compMaxGain;
m_savedCompressorLookbackMs = compLookback;
m_savedCompressorLookaheadMs = compLookahead;
m_savedCompressorRiseRateTenths = compRiseRateTenths;
m_savedCompressorTargetLevelDb = compTargetLevel;
qDebug("AudioDeviceSettings: saved output=%d input=%d codec=%d compressor=%d gain=%ddB lookback=%d lookahead=%d rise=%.1fx target=%ddB",
outIdx, inIdx, codecPreset, compEnabled, compMaxGain, compLookback, compLookahead,
(float)compRiseRateTenths / 10.0f, compTargetLevel);
}
void AudioDeviceSettingsPage::onTestMicToggled() {
@@ -244,3 +289,71 @@ void AudioDeviceSettingsPage::onTestSpeaker() {
pcm[i] = (int16_t)(12000.0 * sin(2.0 * M_PI * 440.0 * i / sampleRate));
SoundManager::instance()->playRawPcm(pcm.data(), samples, sampleRate, 1);
}
void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
auto* compLabel = new QLabel("Voice Compressor", this);
compLabel->setStyleSheet("font-size: 13px; font-weight: bold;");
layout->addWidget(compLabel);
m_compressorEnabledCb = new QCheckBox("Enable compressor (leveler/normalizer)", this);
m_compressorEnabledCb->setStyleSheet("font-size: 12px;");
layout->addWidget(m_compressorEnabledCb);
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->setFixedWidth(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("Max gain:", m_compressorMaxGainSlider, m_compressorMaxGainLabel,
0, 30, 1);
makeSliderRow("Lookback time:", m_compressorLookbackSlider, m_compressorLookbackLabel,
50, 500, 10);
makeSliderRow("Lookahead time:", m_compressorLookaheadSlider, m_compressorLookaheadLabel,
20, 500, 10);
makeSliderRow("Rise rate (x/500ms):", m_compressorRiseRateSlider, m_compressorRiseRateLabel,
11, 100, 1);
makeSliderRow("Target level:", m_compressorTargetLevelSlider, m_compressorTargetLevelLabel,
-40, 0, 1);
auto connectSlider = [this](QSlider* slider) {
connect(slider, &QSlider::valueChanged, this, &AudioDeviceSettingsPage::updateCompressorLabels);
};
connectSlider(m_compressorMaxGainSlider);
connectSlider(m_compressorLookbackSlider);
connectSlider(m_compressorLookaheadSlider);
connectSlider(m_compressorRiseRateSlider);
connectSlider(m_compressorTargetLevelSlider);
}
void AudioDeviceSettingsPage::updateCompressorLabels() {
if (m_compressorMaxGainLabel)
m_compressorMaxGainLabel->setText(QString::number(m_compressorMaxGainSlider->value()) + " dB");
if (m_compressorLookbackLabel)
m_compressorLookbackLabel->setText(QString::number(m_compressorLookbackSlider->value()) + " ms");
if (m_compressorLookaheadLabel)
m_compressorLookaheadLabel->setText(QString::number(m_compressorLookaheadSlider->value()) + " ms");
if (m_compressorRiseRateLabel) {
float v = (float)m_compressorRiseRateSlider->value() / 10.0f;
m_compressorRiseRateLabel->setText(QString::number(v, 'f', 1) + "x");
}
if (m_compressorTargetLevelLabel)
m_compressorTargetLevelLabel->setText(QString::number(m_compressorTargetLevelSlider->value()) + " dBFS");
}