Offline Limiter is an almost true-peak limiter. "Almost" here means that accuracy depends on up-sampling ratio.
2 different multirate technique is used. One is FIR polyphase, and other is using FFT. Default is FIR polyphase because FFT up-sampling requires large amount of memory.
It runs offline because it's too heavy.
When input signal contains sudden amplitude change, and FIR polyphase up-sampling is used, limiting may fail as output exceeds 0 dB. Mitigation is to apply limiter again, or use FFT up-sampling by setting 2 or greater number to --upsample
.
Possible reason is distortion introduced in limiting process. On up-sampled signal, limiter produces frequency components higher than source Nyquist frequency. However, down-sampler truncates them. This truncation changes the peak in down-sampled signal.
When --fadeout
is non-zero, --trim
is set, and FIR polyphase up-sampling is used, fade-out is only partially applied to output. Current implementation doesn't account the length of trim.
Requires C++ compiler with C++20 support.
Dependencies are following.
- Boost::program_options
- Boost::exception
- FFTW3
- libsndfile
- Install Visual Studio with C++20 support.
- Install CMake.
- Install vcpkg and dependency listed above.
- Run following command on PowerShell.
cd OfflineLimiter
./build.ps1
offlinelimiter.exe
will be built into build/Release
.
I didn't commit the change in this instruction because it's very ad-hoc. Issues are that:
- Apple clang comes with XCode doesn't provide
std::format
. - Homebrew doesn't provide some cmake files to link properly. (Link to a related issue)
Install Homebrew. Then, install following packages from Homebrew.
brew install --cask cmake # cmake-gui is convenient to have.
brew install boost libsndfile llvm
Install Xcode and its clang compiler. See link below for how.
Next, download FFTW source code from the link below.
Extract the downloaded FFTW archive and build it with double precision (default).
tar -xf fftw-[version].tar.gz # Replace [version] to the actual version numbers.
cd fftw-[version]
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -GXcode
cd build # It somehow didn't work without this cd.
cmake --build . --config Release
sudo cmake --build . --target install --config Release # Beware the sudo.
Go back to this OfflineLimiter
repository.
cd /path/to/OfflineLimiter # Replace `/path/to`.
Modify CMakeLists.txt
. First, set CMAKE_C_COMPILER
and CMAKE_CXX_COMPILER
as following. It's probably better to put these lines before project
.
set(CMAKE_C_COMPILER /opt/homebrew/opt/llvm/bin/clang)
set(CMAKE_CXX_COMPILER /opt/homebrew/opt/llvm/bin/clang++)
Then change target_compile_options
as following.
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall
-O3
-ffast-math
)
Finally, it's possible to build.
cd OfflineLimiter
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config release
Some modification to top level CMakeLists.txt
is required.
Changing following part:
target_compile_options(${PROJECT_NAME} PRIVATE
/W4
/fp:fast
# /Qvec-report:1
)
to:
target_compile_options(${PROJECT_NAME} PRIVATE
-Wall
-O3
-ffast-math
)
might do the trick for g++ and clang++.
After modification, following command might work.
cd OfflineLimiter
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config release
Output format is fixed to 32-bit float WAV.
Detail of limiter parameter is written in BasicLimiter manual linked below. The limiter algorithm is the same. Only multirate processing part is changed.
Below is the list of command line options.
-h [ --help ] Show this message.
-v [ --verbose ] Show processing status.
-p [ --prompt ] arg Answer and skip prompt when value is
set to "yes" or "no". Otherwise, prompt
will show up.
-i [ --input ] arg Input audio file path.
-o [ --output ] arg Output audio file path.
-u [ --upsample ] arg (=1) Up-sampling ratio. When set to 1, FIR
polyphase up-sampling is used. When set
to greater than 1, FFT up-sampling is
used. FFT up-sampling requires large
amount of memory that is multiple of
input file size and up-sampling ratio.
If FFT up-sampling is enabled and
up-sampled peak is below threshold,
processing will be skipped. Recommend
to set to 16 or greater for precise
true-peak limiting.
--trim --trim has no effect when --upsample is
set to greater than 1. When specified,
input frame count and output frame
count become the same, by trimming
artifacts introduced by multirate
processing. When not specified, output
signal becomes longer than input
signal. Additional frame count is (158
+ attack * samplerate) at front, and
290 at back. Theoretically, trimmed
signal is no longer true-peak limited.
-m [ --memory ] arg (=1) Memory warning threshold in GiB. When
estimated memory allocation exceeds
this value, prompt will show up.
--maxiter arg (=4) Maximum iteration count for additional
stage limiting. Sometimes the result of
true-peak limiting still exceeds the
threshold. It's hard to predict the
final sample-peak before donw-sampling.
(If you know the method, please let me
know!) Therefore offlinelimiter applies
extra stage limiting in case of
insufficient limiting. Loop continues
until the final sample-peak becomes
below 0 dB, or iteration count reaches
--maxiter.
--highpass arg (=0) Cutoff frequency of linear phase
highpass filter in Hz. Inactivate when
set to 0. Useful to eliminate direct
current.
-a [ --attack ] arg (=0.0013333333333333333)
Attack time in seconds.
-s [ --sustain ] arg (=0.0013333333333333333)
Sustain time in seconds.
-r [ --release ] arg (=0) Release time in seconds.
-t [ --threshold ] arg (=-0.10000000000000001)
Limiter threshold in decibel.
-g [ --gate ] arg (=-inf) Gate threshold in decibel. Setting the
value to -inf disables the gate.
-l [ --link ] arg (=0.5) Stereo or multi-channel link amount in
[0.0, 1.0]. 0.0 is no link, and 1.0 is
full link.
-f [ --fadeout ] arg (=0.001) Fade-out time in seconds. Equal power
curve (or quarter cosine curve) is
used.
-n [ --normalize ] arg (=-inf) Target amplitude of normalization in
decibel. Setting the value to +inf,
-inf, or other non-finite value
bypasses normalization. The metering is
10-second moving average. In other
words, this is not sample-peak
normalization, and more close to RMS
normalization.
Below is an example invocation in PowerShell.
offlinelimiter.exe `
--verbose `
--prompt yes `
--input .\data\oracleengine.wav `
--release 0.02
Limiter algorithm is the same one used in BasicLimiter.
Details are written in following link.
True-peak here means that absolute maximum of sinc interpolated signal.
Sinc interpolation is defined as below.
-
$x[n]$ is input signal in discrete time domain. -
$x(t)$ is interpolated signal in continuous time domain. -
$n$ is discrete time in samples. -
$t$ is continuous time in samples. To get time in seconds, divide this value with sampling rate.
And I define true-peak as following.
As you can see, sinc interpolation requires infinite length convolution. It can't be computed in real-time or in real-life. However, we can approximate true-peak by using up-sampling with discrete fourier transform (DFT). I found this from experiment, but I guess there are some books that explain the theory behind it (See reference below). The idea is that ideal lowpass equation matches to DFT equation. Also the use of DFT is the reason this limiter runs offline, not in real-time. It has to know all the input beforehand.
The accuracy of true-peak reconstruction depends on how fine grained the up-sampling is. I'm not good at math, but it might be written as following.
-
$\tau$ is up-sampled discrete time in samples. -
$N$ is number of input samples. -
$L$ is up-sampling ratio.
The idea is that if we increase
FIR polyphase is used when --precise
option is not set. This is default because of the better memory efficiency.
Specification:
- 64 taps high elimination lowpass.
- 64 taps * 8 phase up-sampler.
- 64 taps * 8 phase down-sampler.
Because of the high elimination lowpass, the amplitude of frequency components decrease as it approaches to Nyquist frequency.
When --trim
option is not specified, 158 + attack * samplerate
frames are added to the front, and 290
frames are added to the back of input signal. See usage section for more details.
Comments in the codes in fir
directory provides Python3 code used to design filter.
GPLv2+