OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o
# Mixer objects
-OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampler.o httpd.o ebu_r128_proc.o flags.o image_input.o
+OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampler.o httpd.o ebu_r128_proc.o flags.o image_input.o stereocompressor.o
%.o: %.cpp
$(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
Nageru is Copyright (C) 2015 Steinar H. Gunderson <sgunderson@bigfoot.com>.
+Portions Copyright (C) 2003 Rune Holm.
Portions Copyright (C) 2010-2011 Fons Adriaensen <fons@linuxaudio.org>.
Portions Copyright (C) 2012-2015 Fons Adriaensen <fons@linuxaudio.org>.
Portions Copyright (c) 2007-2013 Intel Corporation. All Rights Reserved.
: httpd("test.ts", WIDTH, HEIGHT),
num_cards(num_cards),
mixer_surface(create_surface(format)),
- h264_encoder_surface(create_surface(format))
+ h264_encoder_surface(create_surface(format)),
+ compressor(48000.0f)
{
httpd.start(9095);
}
}
+ // Apply a level compressor to get the general level right.
+ // Basically, if it's over about -40 dBFS, we squeeze it down to that level
+ // (or more precisely, near it, since we don't use infinite ratio),
+ // then apply a makeup gain to get it to -12 dBFS. -12 dBFS is, of course,
+ // entirely arbitrary, but from practical tests with speech, it seems to
+ // put ut around -23 LUFS, so it's a reasonable starting point for later use.
+ //
+ // TODO: Hook this up to a UI, so we can see the effects, and/or turn it off
+ // to control the gain manually instead. For now, there's only the #if-ed out
+ // code below.
+ //
+ // TODO: Add the actual compressors/limiters (for taking care of transients)
+ // later in the chain.
+ float threshold = 0.01f; // -40 dBFS.
+ float ratio = 20.0f;
+ float attack_time = 0.1f;
+ float release_time = 10.0f;
+ float makeup_gain = pow(10.0f, 28.0f / 20.0f); // +28 dB takes us to -12 dBFS.
+ compressor.process(samples_out.data(), samples_out.size() / 2, threshold, ratio, attack_time, release_time, makeup_gain);
+
+#if 0
+ printf("level=%f (%+5.2f dBFS) attenuation=%f (%+5.2f dB) end_result=%+5.2f dB\n",
+ compressor.get_level(), 20.0 * log10(compressor.get_level()),
+ compressor.get_attenuation(), 20.0 * log10(compressor.get_attenuation()),
+ 20.0 * log10(compressor.get_level() * compressor.get_attenuation() * makeup_gain));
+#endif
+
// Find peak and R128 levels.
peak = std::max(peak, find_peak(samples_out));
vector<float> left, right;
#include "resampler.h"
#include "theme.h"
#include "timebase.h"
+#include "stereocompressor.h"
class H264Encoder;
class QSurface;
// TODO: Implement oversampled peak detection.
float peak = 0.0f;
+
+ StereoCompressor compressor;
};
extern Mixer *global_mixer;
--- /dev/null
+#include <math.h>
+#include <assert.h>
+#include <algorithm>
+
+#include "stereocompressor.h"
+
+namespace {
+
+inline float compressor_knee(float x, float threshold, float inv_threshold, float inv_ratio_minus_one, float postgain)
+{
+ assert(inv_ratio_minus_one <= 0.0f);
+ if (x > threshold && inv_ratio_minus_one < 0.0f) {
+ return postgain * pow(x * inv_threshold, inv_ratio_minus_one);
+ } else {
+ return postgain;
+ }
+}
+
+} // namespace
+
+void StereoCompressor::process(float *buf, size_t num_samples, float threshold, float ratio,
+ float attack_time, float release_time, float makeup_gain)
+{
+ float attack_increment = float(pow(2.0f, 1.0f / (attack_time * sample_rate + 1)));
+ if (attack_time == 0.0f) attack_increment = 100000; // For instant attack reaction.
+
+ const float release_increment = float(pow(2.0f, -1.0f / (release_time * sample_rate + 1)));
+ const float peak_increment = float(pow(2.0f, -1.0f / (0.003f * sample_rate + 1)));
+
+ float inv_ratio_minus_one = 1.0f / ratio - 1.0f;
+ if (ratio > 63) inv_ratio_minus_one = -1.0f; // Infinite ratio.
+ float inv_threshold = 1.0f / threshold;
+
+ float *left_ptr = buf;
+ float *right_ptr = buf + 1;
+
+ float peak_level = this->peak_level;
+ float compr_level = this->compr_level;
+
+ for (size_t i = 0; i < num_samples; ++i) {
+ if (fabs(*left_ptr) > peak_level) peak_level = float(fabs(*left_ptr));
+ if (fabs(*right_ptr) > peak_level) peak_level = float(fabs(*right_ptr));
+
+ if (peak_level > compr_level) {
+ compr_level = std::min(compr_level * attack_increment, peak_level);
+ } else {
+ compr_level = std::max(compr_level * release_increment, 0.0001f);
+ }
+
+ float scalefactor_with_gain = compressor_knee(compr_level, threshold, inv_threshold, inv_ratio_minus_one, makeup_gain);
+
+ *left_ptr *= scalefactor_with_gain;
+ left_ptr += 2;
+
+ *right_ptr *= scalefactor_with_gain;
+ right_ptr += 2;
+
+ peak_level = std::max(peak_level * peak_increment, 0.0001f);
+ }
+
+ // Store attenuation level for debug/visualization.
+ scalefactor = compressor_knee(compr_level, threshold, inv_threshold, inv_ratio_minus_one, 1.0f);
+
+ this->peak_level = peak_level;
+ this->compr_level = compr_level;
+}
+
--- /dev/null
+#ifndef _STEREOCOMPRESSOR_H
+#define _STEREOCOMPRESSOR_H 1
+
+// A simple compressor based on absolute values, with independent
+// attack/release times. There is no sidechain or lookahead, but the
+// peak value is shared between both channels.
+//
+// The compressor was originally written by, and is copyrighted by, Rune Holm.
+// It has been adapted and relicensed under GPLv3 (or, at your option,
+// any later version) for Nageru, so that its license matches the rest of the code.
+
+class StereoBuffer;
+
+class StereoCompressor {
+public:
+ StereoCompressor(float sample_rate)
+ : sample_rate(sample_rate) {
+ reset();
+ }
+
+ void reset() {
+ peak_level = compr_level = 0.1f;
+ scalefactor = 0.0f;
+ }
+
+ // Process <num_samples> interleaved stereo data in-place.
+ // Attack and release times are in seconds.
+ void process(float *buf, size_t num_samples, float threshold, float ratio,
+ float attack_time, float release_time, float makeup_gain);
+
+ // Last level estimated (after attack/decay applied).
+ float get_level() { return compr_level; }
+
+ // Last attenuation factor applied, e.g. if 5x compression is currently applied,
+ // this number will be 0.2.
+ float get_attenuation() { return scalefactor; }
+
+private:
+ float sample_rate;
+ float peak_level;
+ float compr_level;
+ float scalefactor;
+};
+
+#endif /* !defined(_STEREOCOMPRESSOR_H) */