Skip to content

Commit 4acb6f4

Browse files
committed
Transient detection: Rework masking functions
This new format seems to result in the best behaviour to date, and should only require tweaking of the coefficients for better performance
1 parent 4a3b5ce commit 4acb6f4

File tree

1 file changed

+56
-30
lines changed

1 file changed

+56
-30
lines changed

libulc/ulcEncoder_WindowControl.h

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@
5555
//! -TransientFilter[] must be 3 elements in size. Initialize with 0.
5656
//! Internal layout is:
5757
//! {
58-
//! float EnvGainHP; //! Gain envelope tap for HP filter
59-
//! float EnvGainBP; //! Gain envelope tap for BP filter
60-
//! float EnvPost; //! Post-echo-compensated gain tap
58+
//! float EnvPostMaskHP;
59+
//! float EnvPostMaskBP;
60+
//! float EnvBlockMask;
6161
//! }
6262
//! -TmpBuffer[] must be BlockSize*2 elements in size.
6363
#pragma GCC push_options
@@ -102,43 +102,69 @@ static inline void Block_Transform_GetWindowCtrl_TransientFiltering(
102102
}
103103

104104
//! Model the energy curve and integrate it over each segment
105+
//! NOTE: All rates were determined experimentally, based on what
106+
//! resulted in the best sensitivity without excessive glitching.
105107
{
106108
int i, BinSize = BlockSize / ULC_MAX_BLOCK_DECIMATION_FACTOR;
107-
float EnvGainHP = TransientFilter[0], GainHPRate = expf(-0x1.5A92D6p2f / RateHz); //! -0.047dB/ms (1000 * Log[2^-(1/128)])
108-
float EnvGainBP = TransientFilter[1], GainBPRate = expf(-0x1.5A92D6p2f / RateHz); //! -0.047dB/ms (1000 * Log[2^-(1/128)])
109-
float EnvPost = TransientFilter[2], PostRate = expf(-0x1.5A92D6p6f / RateHz); //! -0.753dB/ms (1000 * Log[2^-(1/8)])
110109
struct ULC_TransientData_t *Dst = TransientBuffer + ULC_MAX_BLOCK_DECIMATION_FACTOR; //! Align to new block
111-
const float *Src = BufEnergy;
112110
i = ULC_MAX_BLOCK_DECIMATION_FACTOR; do {
113111
//! Swap out the old "new" data, and clear the new "new" data
114112
Dst[-ULC_MAX_BLOCK_DECIMATION_FACTOR] = *Dst;
115113
*Dst = (struct ULC_TransientData_t){.Sum = 0.0f, .SumW = 0.0f};
116-
n = BinSize; do {
117-
//! Accumulate and subtract 'drift' from the HP and BP
118-
//! signals to "unbias" the data for the next steps.
119-
//! This essentially 'enhances' energy differences.
114+
115+
//! Smear the energy forwards in time to account for post-masking
116+
//! Dev note: Closer to 0dB = Less sensitive
117+
float EnvPostMaskHP = TransientFilter[0];
118+
float EnvPostMaskBP = TransientFilter[1];
119+
float EnvPostMaskHP_Rate = expf(-0x1.CC845Cp6f / RateHz); //! -1.0dB/ms (1000 * Log[10^(-0.2/20))])
120+
float EnvPostMaskBP_Rate = expf(-0x1.9E771Ep6f / RateHz); //! -0.9dB/ms (1000 * Log[10^(-0.5/20))])
121+
for(n=0;n<BinSize;n++) {
120122
//! NOTE: This calculation must be done in the amplitude
121123
//! domain, as the power domain behaves too erratically.
122-
float vHP = sqrtf(Src[0]) - EnvGainHP;
123-
float vBP = sqrtf(Src[1]) - EnvGainBP;
124-
EnvGainHP += vHP * (1.0f-GainHPRate);
125-
EnvGainBP += vBP * (1.0f-GainBPRate);
126-
Src += 2;
127-
128-
//! Update the post-echo-compensating energy curve, and
129-
//! store the updated sum for this segment.
130-
//! NOTE: Delta HP and delta BP are cross-multiplied by
131-
//! their respective gains to sort of 'normalize' with
132-
//! respect to one another. This balances out their
133-
//! weaknesses somewhat.
134-
float vPost = SQR(vHP*EnvGainBP) + SQR(vBP*EnvGainHP) - EnvPost;
135-
EnvPost += vPost * (1.0f-PostRate);
136-
Dst->Sum += EnvPost, Dst->SumW += (SQR(EnvGainBP) + SQR(EnvGainHP));
137-
} while(--n);
124+
float vHP = sqrtf(BufEnergy[n*2+0]), dHP = vHP - EnvPostMaskHP;
125+
float vBP = sqrtf(BufEnergy[n*2+1]), dBP = vBP - EnvPostMaskBP;
126+
EnvPostMaskHP += dHP * (1.0f-EnvPostMaskHP_Rate);
127+
EnvPostMaskBP += dBP * (1.0f-EnvPostMaskBP_Rate);
128+
BufEnergy[n*2+0] = EnvPostMaskHP;
129+
BufEnergy[n*2+1] = EnvPostMaskBP;
130+
131+
}
132+
TransientFilter[0] = EnvPostMaskHP;
133+
TransientFilter[1] = EnvPostMaskBP;
134+
135+
//! Now smear backwards to account for pre-masking, but take the
136+
//! difference between post- and pre-masking to form the 'error'
137+
//! Dev note: Closer to 0dB = More sensitive
138+
float EnvPreMaskHP = EnvPostMaskHP;
139+
float EnvPreMaskBP = EnvPostMaskBP;
140+
float EnvPreMaskHP_Rate = expf(-0x1.CC845Cp7f / RateHz); //! -2.0dB/ms (1000 * Log[10^(-1.0/20)])
141+
float EnvPreMaskBP_Rate = expf(-0x1.144F6Ap5f / RateHz); //! -0.3dB/ms (1000 * Log[10^(-1.0/20)])
142+
for(n=BinSize-1;n>=0;n--) {
143+
//! NOTE: Cross-multiply HP with BP energy and vice-versa
144+
//! to normalize the levels with respect to one another
145+
float vHP = BufEnergy[n*2+0], dHP = vHP - EnvPreMaskHP;
146+
float vBP = BufEnergy[n*2+1], dBP = vBP - EnvPreMaskBP;
147+
EnvPreMaskHP += dHP * (1.0f-EnvPreMaskHP_Rate);
148+
EnvPreMaskBP += dBP * (1.0f-EnvPreMaskBP_Rate);
149+
BufEnergy[n*2+0] = SQR(dHP*EnvPreMaskBP) + SQR(dBP*EnvPreMaskHP);
150+
}
151+
152+
//! Finally, smooth the signal to account for the block size.
153+
//! Larger blocks get less smoothing to capture changes more
154+
//! easily, smaller blocks get more smoothing because they
155+
//! don't need to capture smooth-ish changes.
156+
float EnvBlockMask = TransientFilter[2];
157+
float EnvBlockMask_Rate = expf(-0x1.1AF110p-6f * BlockSize / RateHz); //! -0.00015dB/ms*BlockSize (1000 * Log[10^(-0.00015/20)])
158+
for(n=0;n<BinSize;n++) {
159+
float vEnergy = BufEnergy[n*2+0], dEnergy = vEnergy - EnvBlockMask;
160+
EnvBlockMask += dEnergy * (1.0f-EnvBlockMask_Rate);
161+
Dst->Sum += SQR(EnvBlockMask), Dst->SumW += EnvBlockMask;
162+
}
163+
TransientFilter[2] = EnvBlockMask;
164+
165+
//! Move to next segment
166+
BufEnergy += BinSize*2;
138167
} while(Dst++, --i);
139-
TransientFilter[0] = EnvGainHP;
140-
TransientFilter[1] = EnvGainBP;
141-
TransientFilter[2] = EnvPost;
142168
}
143169
}
144170
#pragma GCC pop_options

0 commit comments

Comments
 (0)