From 2a6c2aaf806b01a4df27a4ea86fcf476176964cc Mon Sep 17 00:00:00 2001 From: Max Maisel Date: Sun, 31 May 2020 10:49:08 +0200 Subject: [PATCH] Add Compressor 2 octave tests. Signed-off-by: Max Maisel --- tests/octave/compressor2_test.m | 306 ++++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 tests/octave/compressor2_test.m diff --git a/tests/octave/compressor2_test.m b/tests/octave/compressor2_test.m new file mode 100644 index 000000000..b24c83a82 --- /dev/null +++ b/tests/octave/compressor2_test.m @@ -0,0 +1,306 @@ +## Audacity Compressor2 effect unit test +# +# Max Maisel +# +# This tests the Compressor effect with various pseudo-random mono and stereo +# noise sequences and sinewaves. The test sequences have different amplitudes +# per channel and sometimes a DC component. +# +# Avoid large parameters for AttackTime, ReleaseTime and LookaroundTime in +# this script as settling behaviour is different and will cause test failure. +# + +pkg load signal; +pkg load image; + +printf("Running Compressor effect tests.\n"); + +EXPORT_TEST_SIGNALS = true; + +## PT1 envelope helper function for symmetric attack and release times. +function y = env_PT1(x, fs, t_ar, gain = 0) + T = 1/(t_ar*fs); + si = mean(mean(abs(x(1:fs*t_ar/20,:)))); + c = (gain != 0) * gain + (gain == 0) * (1.0 + exp(t_ar/30.0)); + y = c*filter(T, [1 T-1], mean(abs(x), 2), si*c); +end + +## PT1 envelope helper function for asymmetric attack and release times. +# This function is much slower than the symmetric counterpart. +function y = env_PT1_asym(x, fs, t_a, t_r, gain = 0) + C_a = 1.0 / (fs*t_a); + C_r = 1.0 / (fs*t_r); + si = mean(mean(abs(x(1:fs*t_a/20,:)))); + c = (gain != 0) * gain + (gain == 0) * (1.0 + exp(t_a/30.0)); + + x_m = mean(abs(x), 2); + y = zeros(length(x_m), 1); + level = si; + + for k = 1:1:length(x_m) + if x_m(k) >= level + level = level + C_a * (x_m(k) - level); + else + level = level + C_r * (x_m(k) - level); + end + y(k) = c * level; + end +end + +## Compressor gain helper function +function gain = comp_gain(env, thresh_DB, ratio, kneeW_DB, makeup) + makeupG_DB = -thresh_DB * (1-1/ratio) * makeup / 100; + env_DB = 20*log10(env); + kneeCond_DB = 2*(env_DB-thresh_DB); + + belowKnee = kneeCond_DB < -kneeW_DB; + aboveKnee = kneeCond_DB >= kneeW_DB; + # & is element-wise && + withinKnee = (kneeCond_DB >= -kneeW_DB) & (kneeCond_DB < kneeW_DB); + + gain_DB = zeros(size(env)); + gain_DB(belowKnee) = makeupG_DB; + gain_DB(aboveKnee) = thresh_DB + ... + (env_DB(aboveKnee) - thresh_DB) / ratio + ... + makeupG_DB - env_DB(aboveKnee); + # Prevent division by zero + kneeW_DB(kneeW_DB==0) = 0.000001; + gain_DB(withinKnee) = (1/ratio-1) * ... + (env_DB(withinKnee) - thresh_DB + kneeW_DB/2).^2 / ... + (2*kneeW_DB) + makeupG_DB; + + gain = 10.^(gain_DB/20); +end + +# Ignore first samples due to settling effects helper function +function y = settled(x, fs = 44100, tau = 1, both = 0) + y = x(round(3*fs*tau):length(x)-round(3*fs*tau*both),:); +end + +# XXX: This Octave function is REALLY slow. +# Maximum value of n*fs < 10000 +function y = lookaround_RMS(x, fs, n1, n2) + kernel = cat(1, zeros(n2*fs,1), ones(n1*fs, 1), ones(n2*fs, 1), zeros(n1*fs, 1)); + y = zeros(size(x)); + for i=1:1:size(x)(2) + y(:,i) = conv(x(:,i).^2, kernel, 'same')./(n1+n2)/fs; + end + + y = 2*sqrt(sum(y, 2)./size(y)(2)); +end + +# XXX: This Octave function is REALLY slow. +# Maximum value of n*fs < 10000 +function y = lookaround_max(x, fs, n1, n2) + kernel = cat(1, zeros(n2*fs,1), ones(n1*fs, 1), ones(n2*fs, 1), zeros(n1*fs, 1)); + x = mean(abs(x), 2); + y = imdilate(x, kernel); +end + +################################################################################ + +## Test Compressor, mono thresholding +CURRENT_TEST = "Compressor2, mono thresholding"; +fs = 44100; + +randn("seed", 1); +x1 = 0.01*randn(30*fs,1) .* sin(2*pi/fs/35*(1:1:30*fs)).'; + +remove_all_tracks(); +x = export_to_aud(x1, fs, name = "Compressor-threshold-test.wav"); +aud_do("CompressorV2: Threshold=-20 Algorithm=0 AttackTime=0.1 ReleaseTime=0.3 LookaheadTime=0 LookbehindTime=0 KneeWidth=0\n"); +y = import_from_aud(1); + +# All input samples are below threshold so output must be equal to input. +do_test_equ(x, y); + +## Test Compressor, mono compression PT1 - no lookaround +CURRENT_TEST = "Compressor2, mono compression PT1"; +x1 = x1.*10; +remove_all_tracks(); +x = export_to_aud(x1, fs); +aud_do("CompressorV2: Threshold=-20 Algorithm=1 CompressBy=0 Ratio=2.5 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0.0 LookbehindTime=0.0 KneeWidth=12\n"); +y = import_from_aud(1); + +do_test_equ(settled(y, fs, 1), ... + comp_gain(settled(env_PT1(x, fs, 0.5, 1), fs, 1), -20, 2.5, 12, 0).* ... + settled(x, fs, 1)); + +## Test Compressor, mono compression PT1 - sinewave - no lookaround +CURRENT_TEST = "Compressor2, mono compression PT1 - sinewave"; + +x2 = sin(2*pi*300/fs*(1:1:20*fs)).'; +remove_all_tracks(); +x = export_to_aud(x2, fs, "Compressor-mono-sine-test.wav"); +aud_do("CompressorV2: Threshold=-23 Algorithm=1 CompressBy=1 Ratio=2.5 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=12\n"); +y = import_from_aud(1); + +# Gain factor 2 because we compress by RMS but do not use lookaround_RMS as +# lookaround is zero. +do_test_equ(settled(y, fs, 1), ... + comp_gain(settled(2*env_PT1(x, fs, 0.5), fs, 1), -23, 2.5, 12, 0).* ... + settled(x, fs, 1)); + +## Test Compressor, mono compression PT1 - faded sinewave - medium signal +CURRENT_TEST = "Compressor2, mono compression PT1 - faded sinewave - medium signal"; + +x2 = sin(2*pi*300/fs*(1:1:50*fs)).' .* horzcat(1:1:25*fs, 25*fs:-1:1).' ./ (25*fs); +remove_all_tracks(); +x = export_to_aud(x2, fs, "Compressor-mono-sine-test.wav"); +aud_do("CompressorV2: Threshold=-10 Algorithm=1 CompressBy=0 Ratio=100 AttackTime=0.01 ReleaseTime=0.01 LookaheadTime=0 LookbehindTime=0 KneeWidth=0\n"); +y = import_from_aud(1); + +do_test_equ(settled(y, fs, 1), ... + comp_gain(settled(env_PT1(x, fs, 0.01, 1), fs, 1), -10, 100, 0, 0).* ... + settled(x, fs, 1)); + +## Test Compressor, mono compression PT1 - faded sinewave - 50 sec signal - no lookaround +CURRENT_TEST = "Compressor2, mono compression PT1 - faded sinewave - long signal"; + +x2 = vertcat(x2, x2); +remove_all_tracks(); +x = export_to_aud(x2, fs, "Compressor-mono-sine-test.wav"); +aud_do("CompressorV2: Threshold=-10 Algorithm=1 CompressBy=0 Ratio=100 AttackTime=0.01 ReleaseTime=0.01 LookaheadTime=0 LookbehindTime=0 KneeWidth=0\n"); +y = import_from_aud(1); + +do_test_equ(settled(y, fs, 1), ... + comp_gain(settled(env_PT1(x, fs, 0.01, 1), fs, 1), -10, 100, 0, 0).* ... + settled(x, fs, 1)); + +## Test Compressor, mono compression PT1 - sinewave - no lookaround - long attack time +CURRENT_TEST = "Compressor2, mono compression PT1 - sinewave - asymetric attack / release"; + +x2 = sin(2*pi*300/fs*(1:1:20*fs)).'; +remove_all_tracks(); +x = export_to_aud(x2, fs, "Compressor-mono-sine-test.wav"); +aud_do("CompressorV2: Threshold=-6 Algorithm=1 CompressBy=0 Ratio=2.0 AttackTime=1.0 ReleaseTime=0.3 LookaheadTime=0 LookbehindTime=0 KneeWidth=0 MakeupGain=0\n"); +y = import_from_aud(1); + +do_test_equ(settled(y, fs, 1), ... + comp_gain(settled(env_PT1_asym(x, fs, 1.0, 0.3, 1.0), fs, 1), -6, 2.0, 0, 0).* + settled(x, fs, 1)); + +## Test Compressor, mono lookaround max +CURRENT_TEST = "Compressor2, mono asymmetric lookaround max"; +remove_all_tracks(); +x = export_to_aud(x1, fs); +aud_do("CompressorV2: Threshold=-17 Algorithm=1 CompressBy=0 Ratio=1.2 AttackTime=0.3 ReleaseTime=0.3 LookaheadTime=0.2 LookbehindTime=0.1 KneeWidth=5 MakeupGain=50\n"); +y = import_from_aud(1); + +do_test_equ(settled(y, fs, 0.6), ... + comp_gain(settled(env_PT1(lookaround_max(x, fs, 0.2, 0.1), fs, 0.3, 1), fs, 0.6), ... + -17, 1.2, 5, 50).*settled(x, fs, 0.6)); + +## Test Compressor, mono lookaround RMS +CURRENT_TEST = "Compressor2, mono asymmetric lookaround RMS"; +remove_all_tracks(); +x = export_to_aud(x1, fs); +aud_do("CompressorV2: Threshold=-20 Algorithm=1 CompressBy=1 Ratio=3 AttackTime=1 ReleaseTime=1 LookaheadTime=0.1 LookbehindTime=0.2 KneeWidth=3 MakeupGain=80\n"); +y = import_from_aud(1); + +do_test_equ(settled(y, fs, 2), ... + comp_gain(settled(env_PT1(lookaround_RMS(x, fs, 0.1, 0.2), fs, 1), fs, 2), -20, 3, 3, 80) ... + .*settled(x, fs, 2)); + +## Test Compressor, mono lookaround max with selection +CURRENT_TEST = "Compressor2, mono lookaround max with selection"; +remove_all_tracks(); +x = export_to_aud(x1, fs); + +aud_do("Select: Start=2 End=5 Mode=Set\n"); +aud_do("CompressorV2: Threshold=-17 Algorithm=1 CompressBy=0 Ratio=1.2 AttackTime=0.3 ReleaseTime=0.3 LookaheadTime=0.2 LookbehindTime=0.2 KneeWidth=5 MakeupGain=50\n"); +y = import_from_aud(1); +x = x(2*fs+1:5*fs); + +do_test_equ(settled(y, fs, 0.1), ... + comp_gain(settled(env_PT1(lookaround_max(x, fs, 0.2, 0.2), fs, 0.3, 1), fs, 0.1), ... + -17, 1.2, 5, 50).*settled(x, fs, 0.1)); + +## Test Compressor, mono, ultra short attack time +CURRENT_TEST = "Compressor2, mono, ultra short attack time"; +remove_all_tracks(); +x = export_to_aud(x1, fs); +aud_do("CompressorV2: Threshold=-20 Algorithm=1 CompressBy=0 Ratio=2 AttackTime=0.00001 ReleaseTime=0.00001 LookaheadTime=0 LookbehindTime=0 KneeWidth=10\n"); +y = import_from_aud(2); + +# XXX: use larger epsilon due to numerical issues +# (float in audacity vs double in octave vs wav files for exchange) +do_test_equ(settled(y, fs, 1), ... + comp_gain(settled(env_PT1(x, fs, 0.00001, 1), fs), -20, 2, 10, 0) ... + .*settled(x, fs, 1), "", 0.15); + +## Test Compressor, stereo compression PT1 - no lookaround +randn("seed", 2); +x1 = 0.2*randn(35*fs, 2); +x1(:,1) = x1(:,1) .* sin(2*pi/fs/35*(1:1:35*fs)).'; +x1(:,2) = x1(:,2) .* (sin(2*pi/fs/75*(1:1:35*fs)).' + 0.1); + +CURRENT_TEST = "Compressor2, stereo compression PT1"; +remove_all_tracks(); +x = export_to_aud(x1, fs, "Compressor-stereo-test.wav"); +aud_do("CompressorV2: Threshold=-20 Algorithm=1 CompressBy=0 Ratio=2 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=10\n"); +y = import_from_aud(2); + +do_test_equ(settled(y, fs, 1), ... + comp_gain(settled(env_PT1(x, fs, 0.5, 1), fs), -20, 2, 10, 0) ... + .*settled(x, fs, 1), "stereo dependent"); + +remove_all_tracks(); +x = export_to_aud(x1, fs); +aud_do("CompressorV2: Threshold=-20 Algorithm=1 Ratio=2 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=10 StereoIndependent=1\n"); +y = import_from_aud(2); + +do_test_equ(settled(y(:,1), fs, 1), ... + comp_gain(settled(env_PT1(x(:,1), fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ... + .*settled(x(:,1), fs, 1), "channel 1"); +do_test_equ(settled(y(:,2), fs, 1), ... + comp_gain(settled(env_PT1(x(:,2), fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ... + .*settled(x(:,2), fs, 1), "channel 2"); + +## Test Compressor, stereo compression PT1 - sinewave +CURRENT_TEST = "Compressor2, stereo compression PT1 - sinewave"; +x2 = sin(2*pi*300/fs*(1:1:20*fs)).'; +x2 = [x2, sin(2*pi*310/fs*(1:1:20*fs)).']; + +remove_all_tracks(); +x = export_to_aud(x2, fs, "Compressor-stereo-sine-test.wav"); +aud_do("CompressorV2: Threshold=-20 Algorithm=1 CompressBy=0 Ratio=2 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=10\n"); +y = import_from_aud(2); + +do_test_equ(settled(y, fs, 1), ... + comp_gain(settled(env_PT1(x, fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ... + .*settled(x, fs, 1), "stereo dependent"); + +remove_all_tracks(); +x = export_to_aud(x2, fs); +aud_do("CompressorV2: Threshold=-20 Algorithm=1 Ratio=2 AttackTime=0.5 ReleaseTime=0.5 LookaheadTime=0 LookbehindTime=0 KneeWidth=10 StereoIndependent=1\n"); +y = import_from_aud(2); + +do_test_equ(settled(y(:,1), fs, 1), ... + comp_gain(settled(env_PT1(x(:,1), fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ... + .*settled(x(:,1), fs, 1), "channel 1"); +do_test_equ(settled(y(:,2), fs, 1), ... + comp_gain(settled(env_PT1(x(:,2), fs, 0.5, 1), fs, 1), -20, 2, 10, 0) ... + .*settled(x(:,2), fs, 1), "channel 2"); + +## Test Compressor, stereo lookaround max +CURRENT_TEST = "Compressor2, stereo lookaround max"; +remove_all_tracks(); +x = export_to_aud(x1, fs); +aud_do("CompressorV2: Threshold=-17 Algorithm=1 Ratio=1.2 AttackTime=0.3 ReleaseTime=0.3 LookaheadTime=0.2 LookbehindTime=0.2 KneeWidth=5 MakeupGain=50\n"); +y = import_from_aud(2); + +do_test_equ(settled(y, fs, 0.6), ... + comp_gain(settled(env_PT1(lookaround_max(x, fs, 0.2, 0.2), fs, 0.3, 1), fs, 0.6), ... + -17, 1.2, 5, 50).*settled(x, fs, 0.6)); + +## Test Compressor, stereo lookaround RMS +CURRENT_TEST = "Compressor2, stereo lookaround RMS"; +remove_all_tracks(); +x = export_to_aud(x1, fs); +aud_do("CompressorV2: Threshold=-20 Algorithm=1 Ratio=3 AttackTime=1 ReleaseTime=1 LookaheadTime=0.1 LookbehindTime=0.1 KneeWidth=3 CompressBy=1 MakeupGain=60\n"); +y = import_from_aud(2); + +do_test_equ(settled(y, fs, 2.5), ... + comp_gain(settled(env_PT1(lookaround_RMS(x, fs, 0.1, 0.1), fs, 1), fs, 2.5), -20, 3, 3, 60) ... + .*settled(x, fs, 2.5));