Skip to content

Commit 2f83af8

Browse files
authored
feat: Aligned Masking Model with Johnston (1988) and standardized configurations (#89)
* feat: Align masking model with Johnston (1988) and standardize configurations * Fix CI * refactor: Configure masking components at initialization and integrate critical bands into the spectral smoother. * fix: Pass `real_size` instead of `fft_size` to `transient_detector_initialize` in `test_shared_utils.c`. * feat: Unify global and per-band transient detection into a single `transient_detector_process` function. * refactor: Update masking veto logic and introduce transient-aware spectral smoothing. * fix: Remove unused delayed_magnitude_spectrum causing CI failure.
1 parent 0fbc3c1 commit 2f83af8

31 files changed

+868
-415
lines changed

examples/denoiser_demo.c

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ static void print_usage(const char* prog_name) {
5151
fprintf(stderr, " --smoothing <val> Smoothing factor (default: 0.0)\n");
5252
fprintf(stderr,
5353
" --masking-depth <val> Masking depth (0.0-1.0, default: 0.5)\n");
54-
fprintf(stderr,
55-
" --masking-elasticity <val> Masking elasticity (0.0-1.0, default: "
56-
"0.1)\n");
5754
fprintf(stderr,
5855
" --learn-avg <val> Learn average mode (0-3, default: 3)\n");
5956
fprintf(stderr, " --adaptive Enable adaptive noise estimation\n");
@@ -99,15 +96,13 @@ int main(int argc, char** argv) {
9996
.reduction_amount = 20.F,
10097
.smoothing_factor = 0.F,
10198
.whitening_factor = 50.F,
102-
.masking_depth = 0.5F,
103-
.masking_elasticity = 0.1F};
99+
.masking_depth = 0.5F};
104100

105101
static struct option long_options[] = {
106102
{"reduction", required_argument, 0, 'r'},
107103
{"whitening", required_argument, 0, 'w'},
108104
{"smoothing", required_argument, 0, 's'},
109105
{"masking-depth", required_argument, 0, 'd'},
110-
{"masking-elasticity", required_argument, 0, 'e'},
111106
{"steering-response", required_argument, 0, 'l'},
112107
{"adaptive", no_argument, 0, 'a'},
113108
{"noise-method", required_argument, 0, 'm'},
@@ -119,7 +114,7 @@ int main(int argc, char** argv) {
119114
float frame_size_ms = FRAME_SIZE;
120115
uint32_t learn_frames = NOISE_FRAMES;
121116
int opt;
122-
while ((opt = getopt_long(argc, argv, "r:w:s:d:e:l:am:f:n:", long_options,
117+
while ((opt = getopt_long(argc, argv, "r:w:s:d:l:am:f:n:", long_options,
123118
NULL)) != -1) {
124119
switch (opt) {
125120
case 'r':
@@ -134,9 +129,6 @@ int main(int argc, char** argv) {
134129
case 'd':
135130
parameters.masking_depth = (float)atof(optarg);
136131
break;
137-
case 'e':
138-
parameters.masking_elasticity = (float)atof(optarg);
139-
break;
140132
case 'l':
141133
parameters.aggressiveness = (float)atof(optarg);
142134
break;

include/specbleach_2d_denoiser.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,6 @@ typedef struct SpectralBleach2DDenoiserParameters {
8484
*/
8585
float nlm_masking_protection;
8686

87-
/**
88-
Range: 0.0 (Pure masking) to 1.0 (Bypass protection).
89-
*/
90-
float masking_elasticity;
91-
9287
/** Sets the suppression aggressiveness (0-100%).
9388
* Controls the SNR-dependent oversubtraction factor.
9489
*/

include/specbleach_denoiser.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ typedef struct SpectralBleachDenoiserParameters {
6767
int noise_estimation_method;
6868

6969
/** Masking Veto depth (0.0 - 1.0) */
70-
float masking_depth; // 0.0 - 1.0: Depth of signal energy preservation
71-
float masking_elasticity; // 0.0 - 1.0: Tolerance for model inaccuracies
70+
float masking_depth; // 0.0 - 1.0: Depth of signal energy preservation
7271

7372
/** Suppression aggressiveness (0.0 - 1.0) */
7473
float suppression_strength; // 0.0 - 1.0: Berouti oversubtraction factor

src/processors/denoiser/spectral_denoiser.c

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ typedef struct SbSpectralDenoiser {
3131
float* beta;
3232
float* noise_spectrum;
3333
float* manual_noise_floor;
34-
float* noisy_reference;
3534

3635
TonalReducer* tonal_reducer;
3736

@@ -115,10 +114,7 @@ SpectralProcessorHandle spectral_denoiser_initialize(
115114

116115
self->manual_noise_floor =
117116
(float*)calloc(self->real_spectrum_size, sizeof(float));
118-
self->noisy_reference =
119-
(float*)calloc(self->real_spectrum_size, sizeof(float));
120-
121-
if (!self->manual_noise_floor || !self->noisy_reference) {
117+
if (!self->manual_noise_floor) {
122118
spectral_denoiser_free(self);
123119
return NULL;
124120
}
@@ -144,8 +140,8 @@ SpectralProcessorHandle spectral_denoiser_initialize(
144140
return NULL;
145141
}
146142

147-
self->spectrum_smoothing =
148-
spectral_smoothing_initialize(self->fft_size, self->time_smoothing_type);
143+
self->spectrum_smoothing = spectral_smoothing_initialize(
144+
self->fft_size, self->sample_rate, self->time_smoothing_type);
149145
if (!self->spectrum_smoothing) {
150146
spectral_denoiser_free(self);
151147
return NULL;
@@ -159,10 +155,11 @@ SpectralProcessorHandle spectral_denoiser_initialize(
159155
self->denoise_parameters.tonal_reduction = 0.0f;
160156

161157
self->masking_veto = masking_veto_initialize(
162-
self->fft_size, self->sample_rate, self->band_type, self->spectrum_type);
163-
self->suppression_engine =
164-
suppression_engine_initialize(self->real_spectrum_size, self->sample_rate,
165-
self->band_type, self->spectrum_type);
158+
self->fft_size, self->sample_rate, CRITICAL_BANDS_TYPE_1D,
159+
self->spectrum_type, false, USE_TEMPORAL_MASKING_1D_DEFAULT);
160+
self->suppression_engine = suppression_engine_initialize(
161+
self->real_spectrum_size, self->sample_rate, self->band_type,
162+
self->spectrum_type, true, USE_TEMPORAL_MASKING_1D_DEFAULT);
166163

167164
if (!self->noise_floor_manager || !self->masking_veto ||
168165
!self->suppression_engine) {
@@ -219,9 +216,7 @@ void spectral_denoiser_free(SpectralProcessorHandle instance) {
219216
if (self->manual_noise_floor) {
220217
free(self->manual_noise_floor);
221218
}
222-
if (self->noisy_reference) {
223-
free(self->noisy_reference);
224-
}
219+
225220
if (self->tonal_reducer) {
226221
tonal_reducer_free(self->tonal_reducer);
227222
}
@@ -296,11 +291,6 @@ bool spectral_denoiser_run(SpectralProcessorHandle instance,
296291

297292
// 3. Denoising Stage: Calculate gains and apply psychoacoustic constraints
298293

299-
// Preservation of 'noisy' reference before temporal smoothing for Veto
300-
// comparison
301-
memcpy(self->noisy_reference, reference_spectrum,
302-
self->real_spectrum_size * sizeof(float));
303-
304294
// 3.1. Calculate SNR-dependent oversubtraction factors (Alpha/Beta)
305295
SuppressionParameters suppression_params = {
306296
.type = SUPPRESSION_BEROUTI_PER_BIN,
@@ -326,10 +316,8 @@ bool spectral_denoiser_run(SpectralProcessorHandle instance,
326316

327317
// 3.4. Apply Structural Veto to rescue transients and moderate artifacts
328318
masking_veto_apply(self->masking_veto, reference_spectrum,
329-
self->noisy_reference, self->noise_spectrum, self->alpha,
330-
MASKING_VETO_ALPHA_FLOOR,
331-
self->denoise_parameters.masking_depth,
332-
self->denoise_parameters.masking_elasticity);
319+
self->noise_spectrum, NULL, self->alpha,
320+
self->denoise_parameters.masking_depth);
333321

334322
// 3.5. Final Gain Calculation
335323
calculate_gains(self->real_spectrum_size, self->fft_size, reference_spectrum,

src/processors/denoiser/spectral_denoiser.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ typedef struct DenoiserParameters {
3535
int adaptive_noise;
3636
int noise_estimation_method; /**< 0=SPP-MMSE, 1=Brandt, 2=Martin MS */
3737
float masking_depth;
38-
float masking_elasticity;
3938
float suppression_strength;
4039
float aggressiveness; /**< -1.0 (Median/Min) to 1.0 (Max), 0.0 (Mean) */
4140
float tonal_reduction; /**< 0.0 to 1.0 (Phase 3) */

src/processors/denoiser2d/spectral_2d_denoiser.c

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ typedef struct Spectral2DDenoiser {
5858
SbSpectralCircularBuffer* circular_buffer;
5959
uint32_t layer_fft;
6060
uint32_t layer_noise;
61-
uint32_t layer_magnitude;
6261

6362
SpectrumType spectrum_type;
6463
GainCalculationType gain_calculation_type;
@@ -167,13 +166,10 @@ SpectralProcessorHandle spectral_2d_denoiser_initialize(
167166

168167
self->layer_fft =
169168
spectral_circular_buffer_add_layer(self->circular_buffer, self->fft_size);
170-
self->layer_magnitude = spectral_circular_buffer_add_layer(
171-
self->circular_buffer, self->real_spectrum_size);
172169
self->layer_noise = spectral_circular_buffer_add_layer(
173170
self->circular_buffer, self->real_spectrum_size);
174171

175-
if (self->layer_fft == 0xFFFFFFFF || self->layer_magnitude == 0xFFFFFFFF ||
176-
self->layer_noise == 0xFFFFFFFF) {
172+
if (self->layer_fft == 0xFFFFFFFF || self->layer_noise == 0xFFFFFFFF) {
177173
spectral_2d_denoiser_free(self);
178174
return NULL;
179175
}
@@ -216,21 +212,21 @@ SpectralProcessorHandle spectral_2d_denoiser_initialize(
216212
return NULL;
217213
}
218214

219-
self->masking_veto =
220-
masking_veto_initialize(self->fft_size, self->sample_rate,
221-
CRITICAL_BANDS_TYPE_2D, self->spectrum_type);
215+
self->masking_veto = masking_veto_initialize(
216+
self->fft_size, self->sample_rate, CRITICAL_BANDS_TYPE_2D,
217+
self->spectrum_type, false, USE_TEMPORAL_MASKING_2D_DEFAULT);
222218
self->suppression_engine = suppression_engine_initialize(
223219
self->real_spectrum_size, self->sample_rate, CRITICAL_BANDS_TYPE_2D,
224-
self->spectrum_type);
220+
self->spectrum_type, true, USE_TEMPORAL_MASKING_2D_DEFAULT);
225221

226222
if (!self->masking_veto || !self->suppression_engine) {
227223
spectral_2d_denoiser_free(self);
228224
return NULL;
229225
}
230226

231-
// Initialize noise floor manager
232227
self->noise_floor_manager =
233228
noise_floor_manager_initialize(fft_size, sample_rate, self->hop);
229+
234230
if (!self->noise_floor_manager) {
235231
spectral_2d_denoiser_free(self);
236232
return NULL;
@@ -362,13 +358,10 @@ bool spectral_2d_denoiser_run(SpectralProcessorHandle instance,
362358
// 2.1 Align internal state and output to the delayed frame (temporal
363359
// plumbing)
364360
float* delayed_noise = NULL;
365-
float* delayed_magnitude_spectrum = NULL;
366361

367362
// 2.1.1 Push current spectra to circular buffer
368363
spectral_circular_buffer_push(self->circular_buffer, self->layer_fft,
369364
fft_spectrum);
370-
spectral_circular_buffer_push(self->circular_buffer, self->layer_magnitude,
371-
reference_spectrum);
372365
spectral_circular_buffer_push(self->circular_buffer, self->layer_noise,
373366
self->noise_spectrum);
374367

@@ -381,9 +374,6 @@ bool spectral_2d_denoiser_run(SpectralProcessorHandle instance,
381374
delayed_noise = spectral_circular_buffer_retrieve(
382375
self->circular_buffer, self->layer_noise, delay_frames);
383376

384-
delayed_magnitude_spectrum = spectral_circular_buffer_retrieve(
385-
self->circular_buffer, self->layer_magnitude, delay_frames);
386-
387377
// 2.1.3 Align output to the delayed frame by default (Passthrough)
388378
memcpy(fft_spectrum, delayed_spectrum, self->fft_size * sizeof(float));
389379

@@ -421,11 +411,11 @@ bool spectral_2d_denoiser_run(SpectralProcessorHandle instance,
421411

422412
// 3.3.4 Apply psychoacoustic veto to preserve transients and moderate
423413
// artifacts
424-
masking_veto_apply(self->masking_veto, smoothed_magnitude,
425-
delayed_magnitude_spectrum, delayed_noise, self->alpha,
426-
MASKING_VETO_ALPHA_FLOOR,
427-
self->parameters.nlm_masking_protection,
428-
self->parameters.masking_elasticity);
414+
// We pass the CURRENT spectrum (fft_spectrum) as the lookahead for the
415+
// DELAYED frame being processed.
416+
masking_veto_apply(self->masking_veto, smoothed_magnitude, delayed_noise,
417+
fft_spectrum, self->alpha,
418+
self->parameters.nlm_masking_protection);
429419

430420
// 3.3.5 Final Gain Calculation
431421
calculate_gains(self->real_spectrum_size, self->fft_size,

src/processors/denoiser2d/spectral_2d_denoiser.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ typedef struct Denoiser2DParameters {
3939
int adaptive_noise; /**< Adaptive noise mode: 0=disabled, 1=enabled */
4040
int noise_estimation_method; /**< 0=SPP-MMSE, 1=Brandt, 2=Martin MS */
4141
float nlm_masking_protection; /**< Masking protection depth (0.0 to 1.0) */
42-
float masking_elasticity; /**< Masking elasticity (0.0 to 1.0) */
4342
float suppression_strength; /**< Suppression aggressiveness (0.0 to 1.0) */
4443
float aggressiveness; /**< -1.0 (Median/Min) to 1.0 (Max), 0.0 (Mean) */
4544
float tonal_reduction; /**< 0.0 to 1.0 (Phase 3) */

src/processors/specbleach_2d_denoiser.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ bool specbleach_2d_load_parameters(
241241
.adaptive_noise = parameters.adaptive_noise,
242242
.noise_estimation_method = parameters.noise_estimation_method,
243243
.nlm_masking_protection = parameters.nlm_masking_protection,
244-
.masking_elasticity = parameters.masking_elasticity,
245244
.suppression_strength = parameters.suppression_strength / 100.F,
246245
.aggressiveness = parameters.aggressiveness,
247246
.tonal_reduction =

src/processors/specbleach_denoiser.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,6 @@ bool specbleach_load_parameters(SpectralBleachHandle instance,
216216
.adaptive_noise = parameters.adaptive_noise,
217217
.noise_estimation_method = parameters.noise_estimation_method,
218218
.masking_depth = parameters.masking_depth,
219-
.masking_elasticity = parameters.masking_elasticity,
220219
.suppression_strength = parameters.suppression_strength / 100.F,
221220
.aggressiveness = parameters.aggressiveness,
222221
.tonal_reduction =

src/shared/configurations.h

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,42 @@ _Static_assert(sizeof(uint32_t) == 4, "uint32_t must be exactly 32 bits");
5858
#define REFERENCE_LEVEL (90.F)
5959
#define SINE_AMPLITUDE (1.F)
6060

61-
// Masking Thresholds
62-
#define BIAS false
63-
#define HIGH_FREQ_BIAS 20.F
64-
#if BIAS
65-
// clang-format off
66-
#define relative_thresholds \
67-
(float[25]){-16.F, -17.F, -18.F, -19.F, -20.F, -21.F, -22.F, -23.F, -24.F, \
68-
-25.F, -25.F, -25.F, -25.F, -25.F, -25.F, -24.F, -23.F, -22.F, \
69-
-19.F, -18.F, -18.F, -18.F, -18.F, -18.F, -18.F}
70-
// clang-format on
71-
#endif
61+
// Johnston Psychoacoustic Model (1988)
62+
#define DB_FS_TO_SPL_REF 96.0F // 0dBFS -> 96dB SPL reference level
63+
#define POWER_LAW_EXPONENT (0.6F) // Johnston power-law integration exponent
64+
65+
// Default Additivity Exponents
66+
#define SPECTRAL_ADDITIVITY_EXPONENT_STANDARD (1.0F) // Pure Johnston
67+
#define SPECTRAL_ADDITIVITY_EXPONENT_PEAQ (0.4F) // Advanced precision
68+
69+
// Schroeder spreading function constants
70+
#define S_MIN_UPWARD (5.0F) // Minimum upward spreading slope (dB/Bark)
71+
#define S_MAX_UPWARD (15.0F) // Maximum upward spreading slope (dB/Bark)
72+
#define S_DOWNWARD (25.0F) // Constant downward spreading slope (dB/Bark)
73+
74+
// Threshold Offsets (NMT = Noise-Masking-Tone, TMN = Tone-Masking-Noise)
75+
#define NMT_OFFSET_DB (6.0F) // Standard offset for NMT
76+
#define TMN_OFFSET_BASE (14.5F) // Base offset for TMN (Bark-dependent)
77+
78+
// Johnston SFM (Spectral Flatness Measure) constants
79+
#define SFM_MIN_DB (-60.0F) // Minimum expected SFM (highly tonal)
80+
#define SFM_MAX_DB (0.0F) // Maximum expected SFM (random noise)
81+
// Temporal Masking Constants
82+
#define FORWARD_MASKING_TAU_LOW_MS (0.100F) // 100ms decay for low frequencies
83+
#define FORWARD_MASKING_TAU_HIGH_MS (0.025F) // 25ms decay for high frequencies
84+
#define BACKWARD_MASKING_TAU_MS (0.010F) // 10ms decay for pre-masking
85+
86+
// Schroeder Slope Adaptation Constants
87+
#define S_LEVEL_REF_DB 40.0F // Reference level for slope adaptation (dB SPL)
88+
#define S_SLOPE_FACTOR 0.2F // Slope broadening factor per dB
7289

7390
// Veto Parameters
74-
#define MASKING_VETO_ALPHA_FLOOR \
75-
(0.2F) // Set to 0.0 for full preservation, or 1.0 for standard reduction.
91+
#define MASKING_VETO_SMOOTHING 0.5F // Stabilization alpha for clean signal
92+
#define MASKING_VETO_NMR_RANGE 20.0F // NMR range for protection mapping (dB)
7693

7794
// Gain Estimators
7895
#define GSS_EXPONENT \
79-
2.0F // 2 Power Subtraction / 1 Magnitude Subtraxtion / 0.5 Spectral
96+
2.0F // 2 Power Subtraction / 1 Magnitude Subtraction / 0.5 Spectral
8097
// Subtraction
8198

8299
// Oversubtraction criteria
@@ -92,7 +109,6 @@ _Static_assert(sizeof(uint32_t) == 4, "uint32_t must be exactly 32 bits");
92109
#define ESTIMATOR_SILENCE_THRESHOLD (1e-10F) // Roughly -100dB in power
93110

94111
// Martin (2001) Constants
95-
#define MARTIN_WINDOW_LEN 96 // Total window length (frames)
96112
#define MARTIN_SUBWIN_COUNT 8 // Number of sub-windows
97113
#define MARTIN_SUBWIN_LEN 12 // Sub-window length (96/8)
98114
#define MARTIN_BIAS_CORR 1.5F // Conservative bias correction for min tracking
@@ -116,7 +132,8 @@ _Static_assert(sizeof(uint32_t) == 4, "uint32_t must be exactly 32 bits");
116132
(1e-6F) // Precision for bias correction calc
117133
#define BRANDT_ESTIMATOR_MIN_HISTORY_FRAMES \
118134
5U // Minimum frames for history-based tracking
119-
#define BRANDT_ESTIMATOR_MIN_DURATION_MS 0.1F // Safety floor for duration calcs
135+
#define BRANDT_ESTIMATOR_MIN_DURATION_MS \
136+
(0.1F) // Safety floor for duration calcs
120137

121138
// Tonal Detector Constants
122139
#define PEAK_THRESHOLD 1.41f // ~3dB above neighbor background
@@ -132,6 +149,9 @@ _Static_assert(sizeof(uint32_t) == 4, "uint32_t must be exactly 32 bits");
132149
// Transient Detector Constants
133150
#define UPPER_LIMIT (5.F)
134151
#define DEFAULT_TRANSIENT_THRESHOLD (2.F)
152+
#define MIN_INNOVATION_ENERGY 1e-10F // ~ -100dB floor for transient trigger
153+
#define ONSET_RATIO_SENSITIVITY 0.25F // Innovation required for full weight
154+
#define TRANSIENT_SMOOTH_ALPHA 0.8F // Reference smoothing alpha
135155

136156
// Noise Estimator Constants
137157
#define MIN_NUMBER_OF_WINDOWS_NOISE_AVERAGED 5
@@ -170,6 +190,9 @@ _Static_assert(sizeof(uint32_t) == 4, "uint32_t must be exactly 32 bits");
170190
#define CRITICAL_BANDS_TYPE_1D BARK_SCALE
171191
#define GAIN_ESTIMATION_TYPE_1D WIENER
172192

193+
// Masking Veto defaults
194+
#define USE_TEMPORAL_MASKING_1D_DEFAULT true
195+
173196
/* ------------------------------------------------------------------ */
174197
/* ------------------- 2D Denoiser configurations ------------------- */
175198
/* ------------------------------------------------------------------ */
@@ -190,4 +213,7 @@ _Static_assert(sizeof(uint32_t) == 4, "uint32_t must be exactly 32 bits");
190213
#define CRITICAL_BANDS_TYPE_2D BARK_SCALE
191214
#define GAIN_ESTIMATION_TYPE_2D WIENER
192215

216+
// Masking Veto defaults
217+
#define USE_TEMPORAL_MASKING_2D_DEFAULT true
218+
193219
#endif // MODULES_CONFIGURATIONS_H

0 commit comments

Comments
 (0)