feat: voice compressor with RMS+peak leveling, sliding window envelope, rate-limited gain
This commit is contained in:
1 parent
7004b110ae
commit
f1b599f0c0
8 files changed
+406
-9
No files matched your search
@@ -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");
|
||||
}
|
||||
Reference in new issue
Block a user