Skip to content

Commit 4d2bb2b

Browse files
committed
add --crest-factor-scaling to norm for better perceived loudness norm
1 parent 2797122 commit 4d2bb2b

3 files changed

Lines changed: 43 additions & 5 deletions

File tree

code/signet/commands/normalise/normalise.cpp

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ CLI::App *NormaliseCommand::CreateCommandCLI(CLI::App &app) {
3939
->check(CLI::Range(0, 100))
4040
->needs(independent_chans);
4141

42+
norm->add_option(
43+
"--crest-factor-scaling", m_crest_factor_scaling,
44+
"Add an additional volume reduction for audio that has very low crest factors; in other words, audio that is consistently loud. This is useful when trying to achieve a consistent perceived loudness. A value of 0 means no reduction, and 100 means reduce the volume of non-peaky audio by 12dB. The default is 0.")
45+
->check(CLI::Range(0, 100));
46+
4247
return norm;
4348
}
4449

@@ -76,19 +81,48 @@ void NormaliseCommand::ProcessFiles(AudioFiles &files) {
7681
normalising_independently = true;
7782
}
7883

79-
const auto GetGain = [&](AudioData &audio) {
84+
const auto GetGain = [&](AudioData &audio, EditTrackedAudioFile const &f) {
8085
if (normalising_independently) {
8186
gain_calculator->Reset();
8287
gain_calculator->RegisterBufferMagnitudes(audio, {});
8388
}
84-
return ScaleMultiplier(gain_calculator->GetGain(DBToAmp(m_target_decibels)),
85-
m_norm_mix_percent / 100.0);
89+
auto gain =
90+
ScaleMultiplier(gain_calculator->GetGain(DBToAmp(m_target_decibels)), m_norm_mix_percent / 100.0);
91+
if (m_crest_factor_scaling) {
92+
auto const rms = GetRMS(audio.interleaved_samples);
93+
auto const peak = GetPeak(audio.interleaved_samples);
94+
95+
constexpr auto k_max_crest_factor = 200.0;
96+
constexpr auto k_max_reduction_db = -12.0;
97+
98+
auto const crest_factor = std::min(peak / rms, k_max_crest_factor);
99+
100+
// The larger the crest factor the larger the 'peakiness' of the audio. We want to reduce the
101+
// volume of non-peaky audio since it may be perceived as louder than peaky.
102+
// A crest factor of 1 should have the largest volume reduction.
103+
// A crest factor of 300 should have the smallest volume reduction.
104+
105+
auto map = [](double x, double in_min, double in_max, double out_min, double out_max) {
106+
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
107+
};
108+
109+
auto const non_peakiness_01 = map(crest_factor, 1.0, k_max_crest_factor, 1.0, 0.0);
110+
auto const reduction_db = (non_peakiness_01 * m_crest_factor_scaling / 100) * k_max_reduction_db;
111+
auto const reduction_amp = DBToAmp(reduction_db);
112+
113+
MessageWithNewLine(GetName(), f,
114+
"Crest factor: {:.2f}, non-peakiness: {:.2f}%, reducing volume by {:.2f} dB",
115+
crest_factor, non_peakiness_01 * 100, reduction_db);
116+
117+
gain *= reduction_amp;
118+
}
119+
return gain;
86120
};
87121

88122
for (auto &f : files) {
89123
auto &audio = f.GetWritableAudio();
90124
if (!m_normalise_channels_separately) {
91-
const auto gain = GetGain(audio);
125+
const auto gain = GetGain(audio, f);
92126
MessageWithNewLine(GetName(), f, "Applying a gain of {:.2f}", gain);
93127
audio.MultiplyByScalar(gain);
94128
} else {
@@ -101,7 +135,7 @@ void NormaliseCommand::ProcessFiles(AudioFiles &files) {
101135
}
102136
const auto max_channel_gain = *std::max_element(channel_peaks.begin(), channel_peaks.end());
103137

104-
const auto gain = GetGain(audio);
138+
const auto gain = GetGain(audio, f);
105139

106140
for (unsigned chan = 0; chan < audio.num_channels; ++chan) {
107141
auto channel_gain = gain * ScaleMultiplier(max_channel_gain / channel_peaks[chan],

code/signet/commands/normalise/normalise.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class NormaliseCommand final : public Command {
1010
private:
1111
double m_norm_mix_percent {100.0};
1212
double m_norm_channel_mix_percent {100.0};
13+
double m_crest_factor_scaling {0.0};
1314
bool m_normalise_independently = false;
1415
bool m_normalise_channels_separately = false;
1516
double m_target_decibels = 0.0;

docs/usage.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ The mix of the normalised signal, where 100% means normalise to exactly to the t
290290
`--mix-channels FLOAT:INT in [0 - 100] Needs: --independent-channels`
291291
When --independent-channels is also given, this option controls the mix of each channels normalised signal, where 100% means normalise to exactly to the target, and 50% means apply a gain to get halfway from the current level to the target. The default is 100%.
292292

293+
`--crest-factor-scaling FLOAT:INT in [0 - 100]`
294+
Add an additional volume reduction for audio that has very low crest factors; in other words, audio that is consistently loud. This is useful when trying to achieve a consistent perceived loudness. A value of 0 means no reduction, and 100 means reduce the volume of non-peaky audio by 12dB. The default is 0.
295+
293296
## :sound: pan
294297
### Description:
295298
Changes the pan of stereo file(s). Does not work on non-stereo files.

0 commit comments

Comments
 (0)