2538 lines
106 KiB
C
2538 lines
106 KiB
C
/************************************************************************************
|
|
** **
|
|
** UHSDR Firmware **
|
|
** **
|
|
**---------------------------------------------------------------------------------**
|
|
** **
|
|
** Description: **
|
|
** Licence: GNU GPLv3 **
|
|
************************************************************************************/
|
|
|
|
#include "uhsdr_board_config.h"
|
|
#include "audio_nr.h"
|
|
#include "arm_const_structs.h"
|
|
#include "profiling.h"
|
|
|
|
//#define debug_alternate_NR
|
|
|
|
#ifdef USE_ALTERNATE_NR
|
|
|
|
static void alt_noise_blanking();
|
|
static void spectral_noise_reduction_3();
|
|
static void AudioNr_RunNoiseReduction(float32_t* inputsamples, float32_t* outputsamples );
|
|
|
|
audio_nr_params_t nr_params;
|
|
|
|
typedef struct NoiseReduction // declaration
|
|
{
|
|
float32_t last_iFFT_result [NR_FFT_L_2 / 2];
|
|
float32_t last_sample_buffer_L [NR_FFT_L_2 / 2];
|
|
float32_t FFT_buffer[NR_FFT_L_2 * 2];
|
|
float32_t Nest[NR_FFT_L_2 / 2][2]; // noise estimates for the current and the last FFT frame
|
|
float32_t vk; // saved 0.24kbytes
|
|
float32_t SNR_prio[NR_FFT_L_2 / 2];
|
|
float32_t SNR_post[NR_FFT_L_2 / 2];
|
|
float32_t SNR_post_pos; // saved 0.24kbytes
|
|
float32_t Hk_old[NR_FFT_L_2 / 2];
|
|
// float32_t VAD;
|
|
// float32_t VAD_Esch; // holds the VAD sum for the Esh & Vary 2009 type of VAD
|
|
// float32_t notch1_f;
|
|
// bool notch1_enable;
|
|
// float32_t notch2_f;
|
|
// bool notch2_enable;
|
|
//ulong long_tone_counter;
|
|
uint16_t current_buffer_idx;
|
|
bool was_here;
|
|
|
|
} NoiseReduction;
|
|
|
|
|
|
NoiseReduction __MCHF_SPECIALMEM NR; // definition
|
|
NoiseReduction2 NR2; // definition
|
|
|
|
__IO int32_t NR_in_head = 0;
|
|
__IO int32_t NR_in_tail = 0;
|
|
__IO int32_t NR_out_head = 0;
|
|
__IO int32_t NR_out_tail = 0;
|
|
|
|
NR_Buffer* NR_in_buffers[NR_BUFFER_FIFO_SIZE];
|
|
NR_Buffer* NR_out_buffers[NR_BUFFER_FIFO_SIZE];
|
|
|
|
/*const float32_t SQRT_van_hann[128]= {0.000000000, 0.024734427, 0.04945372, 0.074142753, 0.098786418, 0.123369638, 0.14787737, 0.172294617,
|
|
0.196606441, 0.220797963, 0.244854382, 0.268760979, 0.292503125, 0.316066292, 0.339436063, 0.362598137,
|
|
0.385538344, 0.408242645, 0.430697148, 0.452888114, 0.474801964, 0.49642529, 0.51774486, 0.53874763,
|
|
0.559420747, 0.579751564, 0.599727639, 0.619336749, 0.638566896, 0.657406313, 0.675843473, 0.693867094,
|
|
0.711466148, 0.728629866, 0.745347746, 0.761609559, 0.777405353, 0.792725465, 0.807560519, 0.821901439,
|
|
0.835739449, 0.849066083, 0.861873185, 0.874152919, 0.885897772, 0.897100557, 0.907754419, 0.91785284,
|
|
0.927389639, 0.936358983, 0.944755382, 0.952573698, 0.959809149, 0.966457306, 0.972514103, 0.977975832,
|
|
0.982839151, 0.987101086, 0.990759028, 0.993810738, 0.996254351, 0.99808837, 0.999311673, 0.999923511,
|
|
0.999923511, 0.999311673, 0.99808837, 0.996254351, 0.993810738, 0.990759028, 0.987101086, 0.982839151,
|
|
0.977975832, 0.972514103, 0.966457306, 0.959809149, 0.952573698, 0.944755382, 0.936358983, 0.927389639,
|
|
0.91785284, 0.907754419, 0.897100557, 0.885897772, 0.874152919, 0.861873185, 0.849066083, 0.835739449,
|
|
0.821901439, 0.807560519, 0.792725465, 0.777405353, 0.761609559, 0.745347746, 0.728629866, 0.711466148,
|
|
0.693867094, 0.675843473, 0.657406313, 0.638566896, 0.619336749, 0.599727639, 0.579751564, 0.559420747,
|
|
0.53874763, 0.51774486, 0.49642529, 0.474801964, 0.452888114, 0.430697148, 0.408242645, 0.385538344,
|
|
0.362598137, 0.339436063, 0.316066292, 0.292503125, 0.268760979, 0.244854382, 0.220797963, 0.196606441,
|
|
0.172294617, 0.14787737, 0.123369638, 0.098786418, 0.074142753, 0.04945372, 0.024734427, 0.00000000};
|
|
*/
|
|
const float32_t SQRT_von_Hann_256[256] = {0,0.01231966,0.024637449,0.036951499,0.049259941,0.061560906,0.073852527,0.086132939,0.098400278,0.110652682,0.122888291,0.135105247,0.147301698,0.159475791,0.171625679,0.183749518,0.195845467,0.207911691,0.219946358,0.231947641,0.24391372,0.255842778,0.267733003,0.279582593,0.291389747,0.303152674,0.314869589,0.326538713,0.338158275,0.349726511,0.361241666,0.372701992,0.384105749,0.395451207,0.406736643,0.417960345,0.429120609,0.440215741,0.451244057,0.462203884,0.473093557,0.483911424,0.494655843,0.505325184,0.515917826,0.526432163,0.536866598,0.547219547,0.557489439,0.567674716,0.577773831,0.587785252,0.597707459,0.607538946,0.617278221,0.626923806,0.636474236,0.645928062,0.65528385,0.664540179,0.673695644,0.682748855,0.691698439,0.700543038,0.709281308,0.717911923,0.726433574,0.734844967,0.743144825,0.75133189,0.759404917,0.767362681,0.775203976,0.78292761,0.790532412,0.798017227,0.805380919,0.812622371,0.819740483,0.826734175,0.833602385,0.840344072,0.846958211,0.853443799,0.859799851,0.866025404,0.872119511,0.878081248,0.88390971,0.889604013,0.895163291,0.900586702,0.905873422,0.911022649,0.916033601,0.920905518,0.92563766,0.930229309,0.934679767,0.938988361,0.943154434,0.947177357,0.951056516,0.954791325,0.958381215,0.961825643,0.965124085,0.968276041,0.971281032,0.974138602,0.976848318,0.979409768,0.981822563,0.984086337,0.986200747,0.988165472,0.989980213,0.991644696,0.993158666,0.994521895,0.995734176,0.996795325,0.99770518,0.998463604,0.999070481,0.99952572,0.99982925,0.999981027,0.999981027,0.99982925,0.99952572,0.999070481,0.998463604,0.99770518,0.996795325,0.995734176,0.994521895,0.993158666,0.991644696,0.989980213,0.988165472,0.986200747,0.984086337,0.981822563,0.979409768,0.976848318,0.974138602,0.971281032,0.968276041,0.965124085,0.961825643,0.958381215,0.954791325,0.951056516,0.947177357,0.943154434,0.938988361,0.934679767,0.930229309,0.92563766,0.920905518,0.916033601,0.911022649,0.905873422,0.900586702,0.895163291,0.889604013,0.88390971,0.878081248,0.872119511,0.866025404,0.859799851,0.853443799,0.846958211,0.840344072,0.833602385,0.826734175,0.819740483,0.812622371,0.805380919,0.798017227,0.790532412,0.78292761,0.775203976,0.767362681,0.759404917,0.75133189,0.743144825,0.734844967,0.726433574,0.717911923,0.709281308,0.700543038,0.691698439,0.682748855,0.673695644,0.664540179,0.65528385,0.645928062,0.636474236,0.626923806,0.617278221,0.607538946,0.597707459,0.587785252,0.577773831,0.567674716,0.557489439,0.547219547,0.536866598,0.526432163,0.515917826,0.505325184,0.494655843,0.483911424,0.473093557,0.462203884,0.451244057,0.440215741,0.429120609,0.417960345,0.406736643,0.395451207,0.384105749,0.372701992,0.361241666,0.349726511,0.338158275,0.326538713,0.314869589,0.303152674,0.291389747,0.279582593,0.267733003,0.255842778,0.24391372,0.231947641,0.219946358,0.207911691,0.195845467,0.183749518,0.171625679,0.159475791,0.147301698,0.135105247,0.122888291,0.110652682,0.098400278,0.086132939,0.073852527,0.061560906,0.049259941,0.036951499,0.024637449,0.01231966,0};
|
|
|
|
void NR_Init()
|
|
{
|
|
nr_params.alpha = 0.94; // spectral noise reduction
|
|
nr_params.beta = 0.96;
|
|
nr_params.enable = false;
|
|
nr_params.NR_FFT_L = 256;
|
|
nr_params.NR_FFT_LOOP_NO = 1;
|
|
|
|
nr_params.first_time = 1;
|
|
nr_params.NR_decimation_enable = true;
|
|
nr_params.fft_256_enable = true;
|
|
ts.special_functions_enabled = 0;
|
|
NR2.width = 4;
|
|
NR2.power_threshold = 0.40;
|
|
NR2.asnr = 30;
|
|
|
|
// TODO: Decide to through out these unused parameters
|
|
// nr_params.gain_smooth_enable = false;
|
|
// nr_params.gain_smooth_alpha = 0.25;
|
|
// nr_params.long_tone_enable = false;
|
|
// nr_params.long_tone_alpha = 0.999;
|
|
// nr_params.long_tone_thresh = 10000;
|
|
// nr_params.long_tone_reset = true;
|
|
// nr_params.vad_thresh = 4.0;
|
|
// nr_params.vad_delay = 7;
|
|
}
|
|
|
|
#if 0
|
|
// biquad IIR filter with a maximum of four notch filters
|
|
// pre-filled with passthrough coefficients
|
|
static arm_biquad_casd_df1_inst_f32 NR_notch_biquad =
|
|
{
|
|
.numStages = 4,
|
|
.pCoeffs = (float32_t *)(float32_t [])
|
|
{
|
|
1,0,0,0,0, 1,0,0,0,0, 1,0,0,0,0, 1,0,0,0,0
|
|
}, // 4 x 5 = 20 coefficients
|
|
|
|
.pState = (float32_t *)(float32_t [])
|
|
{
|
|
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
|
|
} // 4 x 4 = 16 state variables
|
|
};
|
|
|
|
static const float32_t biquad_passthrough[] = { 1, 0, 0, 0, 0 };
|
|
|
|
static float32_t NR_notch_coeffs[5];
|
|
|
|
|
|
void AudioNR_SetBiquadCoeffs(float32_t* coeffsTo,const float32_t* coeffsFrom, float scaling)
|
|
{
|
|
coeffsTo[0] = coeffsFrom[0]/scaling;
|
|
coeffsTo[1] = coeffsFrom[1]/scaling;
|
|
coeffsTo[2] = coeffsFrom[2]/scaling;
|
|
coeffsTo[3] = coeffsFrom[3]/scaling;
|
|
coeffsTo[4] = coeffsFrom[4]/scaling;
|
|
}
|
|
|
|
void AudioNr_CalculateAutoNotch(float32_t coeffs[6], uint8_t notch1_bin, bool notch1_active, float32_t BW, float32_t FS)
|
|
{
|
|
// formula taken from:
|
|
// DSP Audio-EQ-cookbook for generating the coeffs of the filters on the fly
|
|
// www.musicdsp.org/files/Audio-EQ-Cookbook.txt [by Robert Bristow-Johnson]
|
|
float32_t bin_BW = FS / nr_params.NR_FFT_L;
|
|
float32_t f0 = ((float32_t)notch1_bin + 0.5) * bin_BW; // where its happening, man ;-) centre f of the bin
|
|
float32_t Q = 10; // larger Q gives narrower notch
|
|
float32_t w0 = 6.28318530717958 * f0 / FS;
|
|
float32_t alpha = sinf(w0) / (2 * Q);
|
|
|
|
// float32_t alpha = sinf(w0)*sinh( 0.3465735902799726 * BW * w0/sinf(w0) );
|
|
|
|
coeffs[0] = 1;
|
|
coeffs[1] = - 2 * cosf(w0);
|
|
coeffs[2] = 1;
|
|
coeffs[3] = 1 + alpha;
|
|
coeffs[4] = 2 * cosf(w0); // already negated!
|
|
coeffs[5] = alpha - 1; // already negated!
|
|
}
|
|
|
|
void AudioNr_ActivateAutoNotch(uint8_t notch1_bin, bool notch1_active)
|
|
{
|
|
if(notch1_active)
|
|
{ // set coeffs to new notch frequency
|
|
AudioNr_CalculateAutoNotch(NR_notch_coeffs, notch1_bin, notch1_active, 100, 12000);
|
|
AudioNR_SetBiquadCoeffs(&NR_notch_biquad.pCoeffs[0], NR_notch_coeffs, NR_notch_coeffs[3]); // first biquad
|
|
}
|
|
else
|
|
|
|
{ // set coeffs to passthrough = NO notch
|
|
AudioNR_SetBiquadCoeffs(&NR_notch_biquad.pCoeffs[0], biquad_passthrough, 1.0); // first biquad --> passthrough coeffs
|
|
}
|
|
// second biquad
|
|
// AudioNR_SetBiquadCoeffs(&NR_notch_biquad.pCoeffs[5], NR_notch_coeffs, NR_notch_coeffs[0],1.0); // second biquad
|
|
}
|
|
#endif
|
|
|
|
int NR_in_buffer_peek(NR_Buffer** c_ptr)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (NR_in_head != NR_in_tail)
|
|
{
|
|
NR_Buffer* c = NR_in_buffers[NR_in_tail];
|
|
*c_ptr = c;
|
|
ret++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
int NR_in_buffer_remove(NR_Buffer** c_ptr)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (NR_in_head != NR_in_tail)
|
|
{
|
|
NR_Buffer* c = NR_in_buffers[NR_in_tail];
|
|
NR_in_tail = (NR_in_tail + 1) % NR_BUFFER_FIFO_SIZE;
|
|
*c_ptr = c;
|
|
ret++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* no room left in the buffer returns 0 */
|
|
int NR_in_buffer_add(NR_Buffer* c)
|
|
{
|
|
int ret = 0;
|
|
int32_t next_head = (NR_in_head + 1) % NR_BUFFER_FIFO_SIZE;
|
|
|
|
if (next_head != NR_in_tail)
|
|
{
|
|
/* there is room */
|
|
NR_in_buffers[NR_in_head] = c;
|
|
NR_in_head = next_head;
|
|
ret ++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void NR_in_buffer_reset()
|
|
{
|
|
NR_in_tail = NR_in_head;
|
|
}
|
|
|
|
int8_t NR_in_has_data()
|
|
{
|
|
int32_t len = NR_in_head - NR_in_tail;
|
|
return len < 0?len+NR_BUFFER_FIFO_SIZE:len;
|
|
}
|
|
|
|
int32_t NR_in_has_room()
|
|
{
|
|
// FIXME: Since we cannot completely fill the buffer
|
|
// we need to say full 1 element earlier
|
|
return NR_BUFFER_FIFO_SIZE - 1 - NR_in_has_data();
|
|
}
|
|
|
|
|
|
//*********Out Buffer handling
|
|
|
|
int NR_out_buffer_peek(NR_Buffer** c_ptr)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (NR_out_head != NR_out_tail)
|
|
{
|
|
NR_Buffer* c = NR_out_buffers[NR_out_tail];
|
|
*c_ptr = c;
|
|
ret++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
int NR_out_buffer_remove(NR_Buffer** c_ptr)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (NR_out_head != NR_out_tail)
|
|
{
|
|
NR_Buffer* c = NR_out_buffers[NR_out_tail];
|
|
NR_out_tail = (NR_out_tail + 1) % NR_BUFFER_FIFO_SIZE;
|
|
*c_ptr = c;
|
|
ret++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* no room left in the buffer returns 0 */
|
|
int NR_out_buffer_add(NR_Buffer* c)
|
|
{
|
|
int ret = 0;
|
|
int32_t next_head = (NR_out_head + 1) % NR_BUFFER_FIFO_SIZE;
|
|
|
|
if (next_head != NR_out_tail)
|
|
{
|
|
/* there is room */
|
|
NR_out_buffers[NR_out_head] = c;
|
|
NR_out_head = next_head;
|
|
ret ++;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void NR_out_buffer_reset()
|
|
{
|
|
NR_out_tail = NR_out_head;
|
|
}
|
|
|
|
int8_t NR_out_has_data()
|
|
{
|
|
int32_t len = NR_out_head - NR_out_tail;
|
|
return len < 0?len+NR_BUFFER_FIFO_SIZE:len;
|
|
}
|
|
|
|
int32_t NR_out_has_room()
|
|
{
|
|
// FIXME: Since we cannot completely fill the buffer
|
|
// we need to say full 1 element earlier
|
|
return NR_BUFFER_FIFO_SIZE - 1 - NR_out_has_data();
|
|
}
|
|
|
|
|
|
void AudioNr_Prepare()
|
|
{
|
|
NR.current_buffer_idx = 0;
|
|
NR.was_here = false;
|
|
memset(&mmb.nr_audio_buff[0].samples[0], 0, NR_BUFFER_NUM * sizeof(mmb.nr_audio_buff[0]));
|
|
}
|
|
|
|
/**
|
|
* External "interface" of the noise reduction, handles the buffer communication and runs the actual processing algorithm
|
|
* Runs for a significant time (couple of milliseconds on a STM32F4) so must be run in an interruptible context.
|
|
* All "communication" with the outside world is through parameter and the input and output buffers.
|
|
*/
|
|
void AudioNr_HandleNoiseReduction()
|
|
{
|
|
|
|
if (NR.was_here == false)
|
|
{
|
|
NR.was_here = true;
|
|
NR.current_buffer_idx = 0;
|
|
NR_in_buffer_reset();
|
|
NR_out_buffer_reset();
|
|
}
|
|
|
|
if ( NR_in_has_data() && NR_out_has_room())
|
|
{ // audio data is ready to be processed
|
|
|
|
NR.current_buffer_idx %= NR_BUFFER_NUM;
|
|
|
|
NR_Buffer* input_buf = NULL;
|
|
NR_in_buffer_remove(&input_buf); //&input_buffer points to the current valid audio data
|
|
|
|
// inside here do all the necessary noise reduction stuff!!!!!
|
|
// here are the current input samples: input_buf->samples
|
|
// NR_output samples have to be placed here: fdv_iq_buff[NR.current_buffer_idx].samples
|
|
// but starting at an offset of NR_FFT_SIZE as we are using the same buffer for in and out
|
|
// here is the only place where we are referring to fdv_iq... as this is the name of the used freedv buffer
|
|
|
|
//profileTimedEventStart(ProfileTP8);
|
|
|
|
AudioNr_RunNoiseReduction(&input_buf->samples[0].real,&mmb.nr_audio_buff[NR.current_buffer_idx].samples[NR_FFT_SIZE].real);
|
|
|
|
//profileTimedEventStop(ProfileTP8);
|
|
|
|
NR_out_buffer_add(&mmb.nr_audio_buff[NR.current_buffer_idx]);
|
|
NR.current_buffer_idx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* This is an internal function doing the actual noise reduction
|
|
* @param inputsamples buffer with input samples
|
|
* @param outputsamples buffer with processed output samples
|
|
*/
|
|
static void AudioNr_RunNoiseReduction(float32_t* inputsamples, float32_t* outputsamples )
|
|
{
|
|
|
|
float32_t* Energy=0;
|
|
|
|
if(is_dsp_nb_active())
|
|
{
|
|
alt_noise_blanking(inputsamples,NR_FFT_SIZE,Energy);
|
|
}
|
|
|
|
// if((ts.dsp_active & DSP_NR_ENABLE) || (ts.dsp_active & DSP_NOTCH_ENABLE))
|
|
if(is_dsp_nr() && !is_dsp_anr())
|
|
{
|
|
profileTimedEventStart(ProfileTP8);
|
|
|
|
/* // spectral_noise_reduction_2(inputsamples);
|
|
if (nr_params.mode == 0)
|
|
spectral_noise_reduction(inputsamples);
|
|
else
|
|
if (nr_params.mode == 1)
|
|
spectral_noise_reduction_2(inputsamples);
|
|
else
|
|
*/
|
|
|
|
spectral_noise_reduction_3(inputsamples);
|
|
|
|
profileTimedEventStop(ProfileTP8);
|
|
}
|
|
|
|
for (int k=0; k < NR_FFT_SIZE; k++)
|
|
{
|
|
outputsamples[k] = inputsamples[k];
|
|
}
|
|
|
|
}
|
|
|
|
// debugging switches
|
|
//#define OLD_LONG_TONE_DETECTION
|
|
//#define NR_NOTCHTEST
|
|
//#define NR_FFT_256 // if you change this, also changnr_params.NR_FFT_LFFT_L in audio_nr.h
|
|
|
|
|
|
|
|
|
|
// window switches
|
|
// choose exactly ONE window
|
|
//#define NR_WINDOW_HANN
|
|
//#define NR_WINDOW_SIN2
|
|
//#define NR_WINDOW_SIN4
|
|
//#define NR_WINDOW_HANN_CONST
|
|
|
|
|
|
#if 0
|
|
void spectral_noise_reduction (float* in_buffer)
|
|
{
|
|
|
|
// arm_rfft_fast_instance_f32 fftInstance;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#ifdef NR_NOTCHTEST
|
|
// Result of the test: I cannot find a combination of
|
|
// bin gains, where the distortion is away
|
|
// the brizzling sound is always there
|
|
// more pronounced, when gain combination along the bins is steeper
|
|
|
|
|
|
// bin width is 12000ksps / 128 bins = 93.75Hz
|
|
// bin_c = 11 is a notch centred at 10 * 93.75Hz + 93.75Hz / 2 = 984.38 Hz
|
|
#ifdef NR_FFT_256
|
|
const uint8_t bin_c = 22; // centre_bin to be notched
|
|
#else
|
|
const uint8_t bin_c = 11; // centre_bin to be notched
|
|
#endif
|
|
const uint8_t bin_p1 = bin_c + 1;
|
|
const uint8_t bin_p2 = bin_c + 2;
|
|
const uint8_t bin_p3 = bin_c + 3;
|
|
const uint8_t bin_p4 = bin_c + 4;
|
|
const uint8_t bin_m1 = bin_c - 1;
|
|
const uint8_t bin_m2 = bin_c - 2;
|
|
const uint8_t bin_m3 = bin_c - 3;
|
|
const uint8_t bin_m4 = bin_c - 4;
|
|
const float32_t bin1_att = 0.0; // -24dB
|
|
const float32_t bin2_att = 0.15; // -12dB
|
|
const float32_t bin3_att = 0.3; // -6.0dB
|
|
const float32_t bin4_att = 0.55; // -2.5dB
|
|
const float32_t bin5_att = 0.7; // -2.5dB
|
|
|
|
// const float32_t bin1_att = 0.05; // -26dB
|
|
// const float32_t bin2_att = 0.15; // -16.5dB
|
|
// const float32_t bin3_att = 0.45; // -10.5dB
|
|
// linear gains mean logarithmic bin powers
|
|
// the best
|
|
// const float32_t bin1_att = 0.001; // -24dB
|
|
// const float32_t bin2_att = 0.25; // -12dB
|
|
// const float32_t bin3_att = 0.5; // -6.0dB
|
|
// const float32_t bin4_att = 0.75; // -2.5dB
|
|
// one of the best combinations so far
|
|
// const float32_t bin1_att = 0.01; // -40dB
|
|
// const float32_t bin2_att = 0.25; // dB
|
|
// const float32_t bin3_att = 0.5; // dB
|
|
// const float32_t bin4_att = 0.75; // dB
|
|
// GOOD!
|
|
// const float32_t bin1_att = 0.1; // dB
|
|
// const float32_t bin2_att = 0.2; // dB
|
|
// const float32_t bin3_att = 0.3; // dB
|
|
// const float32_t bin4_att = 0.4; // dB
|
|
#endif
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Frank DD4WH & Michael DL2FW, November 2017
|
|
// NOISE REDUCTION BASED ON SPECTRAL SUBTRACTION
|
|
// following Romanin et al. 2009 on the basis of Ephraim & Malah 1984 and Hu et al. 2001
|
|
// detailed technical description of the implemented algorithm
|
|
// can be found in our WIKI
|
|
// https://github.com/df8oe/UHSDR/wiki/Noise-reduction
|
|
//
|
|
// half-overlapping input buffers (= overlap 50%)
|
|
// Hann window on 128 samples
|
|
// FFT128 - inverse FFT128
|
|
// overlap-add
|
|
|
|
/* const float32_t v_hann_window[128]= {0 , 0.0006117919, 0.0024456704, 0.0054971478, 0.0097587564 ,0.0152200676, 0.0218677165, 0.0296854352, 0.0386540925,
|
|
0.0487517405, 0.0599536686, 0.0722324638 ,0.0855580779, 0.0998979008, 0.1152168405, 0.1314774092, 0.1486398144,
|
|
0.1666620568 ,0.1855000331, 0.2051076434, 0.2254369048, 0.2464380681, 0.2680597399, 0.2902490084, 0.3129515727,
|
|
0.3361118759, 0.3596732407, 0.3835780087, 0.4077676808, 0.4321830608, 0.4567644003, 0.4814515445, 0.5061840798,
|
|
0.5309014817, 0.5555432625, 0.5800491196, 0.6043590832, 0.6284136625, 0.6521539921, 0.6755219754, 0.698460427,
|
|
0.7209132127, 0.7428253867, 0.7641433263, 0.7848148629, 0.8047894099, 0.8240180861, 0.8424538357, 0.8600515434,
|
|
0.8767681446, 0.8925627311, 0.9073966507, 0.9212336025, 0.9340397251, 0.9457836798, 0.956436727, 0.9659727972,
|
|
0.9743685538, 0.981603451, 0.987659784, 0.9925227317, 0.9961803937, 0.9986238192, 0.9998470286, 0.9998470286,
|
|
0.9986238192, 0.9961803937, 0.9925227317, 0.987659784, 0.981603451, 0.9743685538, 0.9659727972, 0.956436727,
|
|
0.9457836798, 0.9340397251, 0.9212336025, 0.9073966507, 0.8925627311, 0.8767681446, 0.8600515434, 0.8424538357,
|
|
0.8240180861, 0.8047894099, 0.7848148629, 0.7641433263, 0.7428253867, 0.7209132127, 0.698460427, 0.6755219754,
|
|
0.6521539921, 0.6284136625, 0.6043590832, 0.5800491196, 0.5555432625, 0.5309014817, 0.5061840798, 0.4814515445,
|
|
0.4567644003, 0.4321830608, 0.4077676808, 0.3835780087, 0.3596732407, 0.3361118759, 0.3129515727, 0.2902490084,
|
|
0.2680597399, 0.2464380681, 0.2254369048, 0.2051076434, 0.1855000331, 0.1666620568, 0.1486398144, 0.1314774092,
|
|
0.1152168405, 0.0998979008, 0.0855580779, 0.0722324638, 0.0599536686, 0.0487517405, 0.0386540925, 0.0296854352,
|
|
0.0218677165, 0.0152200676, 0.0097587564, 0.0054971478, 0.0024456704, 0.0006117919, 0};
|
|
*/
|
|
float32_t NR_sample_rate = 12000.0;
|
|
if(nr_params.NR_decimation_enable)
|
|
{
|
|
NR_sample_rate = 6000.0;
|
|
}
|
|
uint8_t zero_cross_count=0;
|
|
bool VAD_ZCR, VAD_EN, VAD_E_Z;
|
|
float32_t VAD_E,VAD_energy_ratio;
|
|
static uint8_t NR_init_counter = 0;
|
|
uint8_t VAD_low=0;
|
|
uint8_t VAD_high=63;
|
|
float32_t NR_temp_sum = 0.0;
|
|
float32_t width = FilterInfo[ts.filters_p->id].width;
|
|
float32_t offset = ts.filters_p->offset;
|
|
float32_t lf_freq = (offset - width/2) / (NR_sample_rate / nr_params.NR_FFT_L); // bin BW is 93.75Hz [12000Hz / 128 bins]
|
|
float32_t uf_freq = (offset + width/2) / (NR_sample_rate / nr_params.NR_FFT_L);
|
|
|
|
if(nr_params.first_time == 1)
|
|
{ // TODO: properly initialize all the variables
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
if (nr_params.mode==0)
|
|
{
|
|
NR.last_sample_buffer_L[bindx] = 20.0;
|
|
NR.Hk_old[bindx] = 0.1; // old gain or xu in development mode
|
|
NR.Nest[bindx][0] = 50000.0;
|
|
NR.Nest[bindx][1] = 40000.0;
|
|
NR2.X[bindx][1] = 15000.0;
|
|
NR.SNR_post[bindx] = 2.0;
|
|
NR.SNR_prio[bindx] = 1.0;
|
|
|
|
}
|
|
else //development mode
|
|
{
|
|
NR.last_sample_buffer_L[bindx] = 0.0;
|
|
NR2.Hk[bindx] = 1.0;
|
|
//xu[bindx] = 1.0; //has to be replaced by other variable
|
|
NR2.Hk_old[bindx] = 1.0; // old gain or xu in development mode
|
|
NR.Nest[bindx][0] = 0.0;
|
|
NR.Nest[bindx][1] = 1.0;
|
|
//NR2.X[bindx][1] = 0.0;
|
|
//NR.SNR_post[bindx] = 1.0;
|
|
//NR.SNR_prio[bindx] = 1.0;
|
|
|
|
}
|
|
}
|
|
nr_params.first_time = 2; // we need to do some more a bit later down
|
|
}
|
|
|
|
|
|
if(nr_params.long_tone_reset)
|
|
{
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR2.long_tone_gain[bindx] = 1.0;
|
|
NR2.long_tone_counter[bindx] = 10;
|
|
}
|
|
nr_params.long_tone_reset = false;
|
|
NR2.notch_change = false;
|
|
NR2.notch1_bin = 10; // frequency bin where notch filter 1 has to work
|
|
NR2.max_bin = 10; // holds the bin number of the strongest persistent tone during tone detection
|
|
NR2.long_tone_max = 100.0; // power value of the strongest persistent tone, used for max search
|
|
NR2.notch1_active = false; // is notch1 active?
|
|
NR2.notch2_active = false; // is notch21 active?
|
|
NR2.notch3_active = false; // is notch3 active?
|
|
NR2.notch4_active = false; // is notch4 active?
|
|
}
|
|
|
|
for(int k = 0; k < nr_params.NR_FFT_LOOP_NO; k++)
|
|
{
|
|
// NR_FFT_buffer is 256 floats big
|
|
// interleaved r, i, r, i . . .
|
|
// fill first half of FFT_buffer with last events audio samples
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.FFT_buffer[i * 2] = NR.last_sample_buffer_L[i]; // real
|
|
NR.FFT_buffer[i * 2 + 1] = 0.0; // imaginary
|
|
}
|
|
// copy recent samples to last_sample_buffer for next time!
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.last_sample_buffer_L [i] = in_buffer[i + k * (nr_params.NR_FFT_L / 2)];
|
|
}
|
|
// now fill recent audio samples into second half of FFT_buffer
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.FFT_buffer[nr_params.NR_FFT_L + i * 2] = in_buffer[i+ k * (nr_params.NR_FFT_L / 2)]; // real
|
|
NR.FFT_buffer[nr_params.NR_FFT_L + i * 2 + 1] = 0.0;
|
|
}
|
|
/////////////////////////////////7
|
|
// WINDOWING
|
|
if(nr_params.fft_256_enable)
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len256, NR.FFT_buffer, 0, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_von_Hann_256[idx];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len128, NR.FFT_buffer, 0, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_van_hann[idx];
|
|
}
|
|
}
|
|
|
|
#ifdef NR_WINDOW_HANN
|
|
// perform windowing on 128 real samples in the NR_FFT_buffer
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{ // Hann window
|
|
float32_t temp_sample = 0.5 * (float32_t)(1.0 - (cosf(PI* 2.0 * (float32_t)idx / (float32_t)((nr_params.NR_FFT_L) - 1))));
|
|
NR.FFT_buffer[idx * 2] *= temp_sample;
|
|
}
|
|
#endif
|
|
#ifdef NR_WINDOW_SIN2
|
|
// perform windowing on 128 real samples in the NR_FFT_buffer
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
// SIN^2 window
|
|
float32_t SINF = (sinf(PI * (float32_t)idx / (float32_t)(nr_params.NR_FFT_L - 1)));
|
|
SINF = (SINF * SINF);
|
|
NR.FFT_buffer[idx * 2] *= SINF;
|
|
}
|
|
#endif
|
|
#ifdef NR_WINDOW_SIN4
|
|
// perform windowing on 128 real samples in the NR_FFT_buffer
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
// SIN^2 window
|
|
float32_t SINF = (sinf(PI * (float32_t)idx / (float32_t)(nr_params.NR_FFT_L - 1)));
|
|
SINF *= SINF;
|
|
SINF *= SINF;
|
|
NR.FFT_buffer[idx * 2] *= SINF;
|
|
}
|
|
#endif
|
|
|
|
#ifndef NR_NOTCHTEST
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
// this is magnitude for the current frame
|
|
if (nr_params.mode==0)
|
|
NR2.X[bindx][0] = sqrtf(NR.FFT_buffer[bindx * 2] * NR.FFT_buffer[bindx * 2] + NR.FFT_buffer[bindx * 2 + 1] * NR.FFT_buffer[bindx * 2 + 1]);
|
|
else //here we need only the squared magnitude
|
|
NR2.X[bindx][0] = (NR.FFT_buffer[bindx * 2] * NR.FFT_buffer[bindx * 2] + NR.FFT_buffer[bindx * 2 + 1] * NR.FFT_buffer[bindx * 2 + 1]);
|
|
}
|
|
|
|
if(nr_params.first_time == 2)
|
|
{ // TODO: properly initialize all the variables
|
|
if (nr_params.mode==1)
|
|
{
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR.Nest[bindx][0] = NR.Nest[bindx][0] + 0.05* NR2.X[bindx][0];// we do it 20 times to average over 20 frames for app. 100ms only on NR_on/bandswitch/modeswitch,...
|
|
}
|
|
NR_init_counter++;
|
|
if (NR_init_counter > 19)//average over 20 frames for app. 100ms
|
|
{
|
|
NR_init_counter = 0;
|
|
nr_params.first_time = 3; // now we did all the necessary initialization to actually start the noise reduction
|
|
}
|
|
}
|
|
else nr_params.first_time = 3;
|
|
|
|
}
|
|
if (nr_params.first_time == 3)
|
|
{
|
|
|
|
if((ts.dsp_active & DSP_NOTCH_ENABLE))
|
|
{
|
|
// detection of long tones
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR2.long_tone[bindx][0] = (nr_params.long_tone_alpha) * NR2.long_tone[bindx][1] + (1.0 - nr_params.long_tone_alpha) * NR2.X[bindx][0]; //
|
|
NR2.long_tone[bindx][1] = NR2.long_tone[bindx][0];
|
|
}
|
|
}
|
|
|
|
if (nr_params.mode == 1) // in development mode we start here with the calculation of the SNRs
|
|
{
|
|
// 3 calculate gamma (SNRpost)
|
|
// and also xi (SNRprio)
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR.SNR_post[bindx] = fmax(fmin(NR2.X[bindx][0] / NR.Nest[bindx][0],1000.0),0.001); // limited to +30 /-30 dB, might be still too much of reduction, let's try it?
|
|
|
|
NR.SNR_prio[bindx] = fmax(nr_params.alpha * NR.Hk_old[bindx] + (1.0 - nr_params.alpha)*fmax(NR.SNR_post[bindx]-1.0,0.0),0.0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// 2b.) voice activity detector
|
|
// we restrict the calculation of the VAD to the bins in the filter bandwidth
|
|
// float32_t NR_temp_sum = 0.0;
|
|
// float32_t width = FilterInfo[ts.filters_p->id].width;
|
|
// float32_t offset = ts.filters_p->offset;
|
|
|
|
if (offset == 0)
|
|
{
|
|
offset = width/2;
|
|
}
|
|
|
|
// float32_t lf_freq = (offset - width/2) / (12000 / NR_FFT_L); // bin BW is 93.75Hz [12000Hz / 128 bins]
|
|
// float32_t uf_freq = (offset + width/2) / (12000 / NR_FFT_L);
|
|
VAD_low = (int)lf_freq;
|
|
VAD_high = (int)uf_freq;
|
|
if(VAD_low == VAD_high)
|
|
{
|
|
VAD_high++;
|
|
}
|
|
if(VAD_low < 1)
|
|
{
|
|
VAD_low = 1;
|
|
}
|
|
else
|
|
if(VAD_low > nr_params.NR_FFT_L / 2 - 2)
|
|
{
|
|
VAD_low = nr_params.NR_FFT_L / 2 - 2;
|
|
}
|
|
if(VAD_high < 1)
|
|
{
|
|
VAD_high = 1;
|
|
}
|
|
else
|
|
if(VAD_high > nr_params.NR_FFT_L / 2)
|
|
{
|
|
VAD_high = nr_params.NR_FFT_L / 2;
|
|
}
|
|
|
|
// ******* alternative VAD trials
|
|
|
|
if (nr_params.mode == 0) //mode "0" is the older version
|
|
{
|
|
|
|
zero_cross_count=0;
|
|
for (int i = 1; i < nr_params.NR_FFT_L/2; i++)
|
|
{
|
|
if ((in_buffer[i + k * (nr_params.NR_FFT_L / 2)] * in_buffer[ i-1 + k * (nr_params.NR_FFT_L / 2)]) < 0)
|
|
{
|
|
zero_cross_count++; //if product of current sample with last sample is negative, we had a zero crossing
|
|
}
|
|
} // # of zero crossing tend to be higher at only noise containing frames
|
|
|
|
if (zero_cross_count > 10)
|
|
{
|
|
VAD_ZCR = false;
|
|
//Board_RedLed(LED_STATE_OFF);
|
|
}
|
|
else
|
|
{
|
|
VAD_ZCR = true;
|
|
//Board_RedLed(LED_STATE_ON);
|
|
}
|
|
|
|
VAD_E = 0.0;
|
|
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++)
|
|
{
|
|
if (nr_params.mode == 0) //if mode=1, we need to take the squareroot to have the same performance - will later e fixed
|
|
VAD_E = VAD_E + NR2.X[bindx][0];
|
|
else
|
|
VAD_E = VAD_E + sqrtf(NR2.X[bindx][0]);
|
|
}
|
|
|
|
VAD_energy_ratio = VAD_E / (VAD_high-VAD_low);
|
|
|
|
if (VAD_energy_ratio > (350 * nr_params.vad_thresh)) //nr_params.vad_thresh is per default 1000/1000!!!
|
|
{
|
|
VAD_EN=true;
|
|
//Board_RedLed(LED_STATE_OFF);
|
|
|
|
}
|
|
else
|
|
{
|
|
VAD_EN=false;
|
|
//Board_RedLed(LED_STATE_ON);
|
|
}
|
|
|
|
if (VAD_EN && VAD_ZCR)
|
|
{
|
|
VAD_E_Z=true;
|
|
//Board_RedLed(LED_STATE_ON);
|
|
|
|
}
|
|
else
|
|
{
|
|
VAD_E_Z=false;
|
|
//Board_RedLed(LED_STATE_OFF);
|
|
}
|
|
|
|
|
|
// ******* end of alternative VAD trials
|
|
float32_t NR_VAD_temp;
|
|
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++) // try 128:
|
|
{
|
|
float32_t D_squared = NR.Nest[bindx][0] * NR.Nest[bindx][0];
|
|
// NR_temp_sum += (NR_X[bindx][0]/ (D_squared) ) - logf((NR_X[bindx][0] / (D_squared) )) - 1.0; // unpredictable behaviour
|
|
// NR_temp_sum += (NR_X[bindx][0] * NR_X[bindx][0]/ (D_squared) ) - logf((NR_X[bindx][0] * NR_X[bindx][0] / (D_squared) )) - 1.0; //nice behaviour
|
|
NR_temp_sum += (NR2.X[bindx][0] * NR2.X[bindx][0]/ (D_squared) ); // try without log
|
|
}
|
|
NR.VAD = NR_temp_sum / (VAD_high - VAD_low);
|
|
//float32_t NR_VAD_temp;
|
|
if(NR2.VAD_type == 0)
|
|
{
|
|
NR_VAD_temp = NR.VAD;
|
|
}
|
|
else
|
|
if(NR2.VAD_type == 1)
|
|
{
|
|
NR_VAD_temp = 10.0 * NR.VAD_Esch;
|
|
}
|
|
else
|
|
if(NR2.VAD_type == 2)
|
|
|
|
{
|
|
if (VAD_E_Z == true)
|
|
{
|
|
NR_VAD_temp = 1000000;// just very high, because we already have a boolean decision
|
|
}
|
|
else
|
|
{
|
|
NR_VAD_temp = 0;
|
|
}
|
|
}
|
|
|
|
|
|
if((NR_VAD_temp < nr_params.vad_thresh) || nr_params.first_time == 2)
|
|
{ // VAD has detected NOISE
|
|
// noise estimation with exponential averager
|
|
NR2.VAD_duration=0;
|
|
NR2.VAD_crash_detector = 0;
|
|
|
|
if (NR2.VAD_delay == 0) //update noise level after Speech is really over
|
|
{
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{ // exponential averager for current noise estimate
|
|
if (nr_params.mode == 0)
|
|
{
|
|
NR.Nest[bindx][0] = (1.0 - nr_params.beta) * NR2.X[bindx][0] + nr_params.beta * NR.Nest[bindx][1]; //
|
|
NR.Nest[bindx][1] = NR.Nest[bindx][0];
|
|
}
|
|
else
|
|
{
|
|
NR.Nest[bindx][0] = NR.Nest[bindx][0] * nr_params.beta + (1.0 - nr_params.beta) * NR2.X[bindx][0]; //already squared!
|
|
}
|
|
}
|
|
|
|
Board_RedLed(LED_STATE_OFF);
|
|
}
|
|
else // we wait a little until the last vowel has vanished
|
|
{
|
|
|
|
if (NR2.VAD_delay > 0) NR2.VAD_delay--;
|
|
|
|
}
|
|
}
|
|
else // VAD has detected speech
|
|
{
|
|
NR2.VAD_crash_detector++;
|
|
NR2.VAD_duration++;
|
|
if (NR2.VAD_duration > 1) //a vowel should be longer than app. 20ms
|
|
{
|
|
NR2.VAD_delay = nr_params.vad_delay; // we wait 1 times app. 10ms before we start updating the noisefloor
|
|
Board_RedLed(LED_STATE_ON);
|
|
}
|
|
}
|
|
// this helps to get the noise estimate out of a deep depression
|
|
// sometimes the VAD is not possible to become lower than VAD_thresh, because Nest is too small
|
|
// this helps avoiding this
|
|
if(NR2.VAD_crash_detector > 100)
|
|
{
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{ // increase noise estimate
|
|
NR.Nest[bindx][0] = NR2.X[bindx][0] * 1.2; //
|
|
NR.Nest[bindx][1] = NR.Nest[bindx][0];
|
|
}
|
|
}
|
|
|
|
|
|
// 3 calculate SNRpost (n, bin[i]) = (X(n, bin[i])^2 / Nest(n, bin[i])^2) - 1 (eq. 13 of Schmitt et al. 2002)
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
// (Yk)^2 / Dk (eq 11, Romanin et al. 2009)
|
|
if(NR.Nest[bindx][0] != 0.0)
|
|
{ // do we have to square the noise estimate NR_Nest[bindx] or not? Schmitt says yes, Romanin says no . . .
|
|
// NR_SNR_post[bindx] = NR_X[bindx][0] / (NR_Nest[bindx][0] * NR_Nest[bindx][0]); // audio crushed
|
|
NR.SNR_post[bindx] = NR2.X[bindx][0] / (NR.Nest[bindx][0]); //
|
|
}
|
|
// "half-wave rectification" of NR_SR_post_pos --> always >= 0
|
|
if(NR.SNR_post[bindx] >= 0.0)
|
|
{
|
|
NR.SNR_post_pos = NR.SNR_post[bindx];
|
|
}
|
|
else
|
|
{
|
|
NR.SNR_post_pos = 0.0;
|
|
}
|
|
// 3 calculate SNRprio (n, bin[i]) = (1 - alpha) * Q(SNRpost(n, bin[i]) + alpha * (Hk(n - 1, bin[i]) * X(n - 1, bin[i])^2 / Nest(n, bin[i])^2 (eq. 14 of Schmitt et al. 2002, eq. 13 of Romanin et al. 2009) [Q[x] = x if x>=0, else Q[x] = 0]
|
|
// again: do we have to square the noise estimate NR_M[bindx] or not? Schmitt says yes, Romanin says no . . .
|
|
if(NR.Nest[bindx][0] != 0.0)
|
|
{
|
|
NR.SNR_prio[bindx] = (1.0 - nr_params.alpha) * NR.SNR_post_pos +
|
|
// nr_params.alpha * ((NR_Hk_old[bindx] * NR_Hk_old[bindx] * NR_X[bindx][1]) / (NR_Nest[bindx][0])); // no NR effect
|
|
// nr_params.alpha * ((NR_Hk_old[bindx] * NR_Hk_old[bindx] * NR_X[bindx][1] * NR_X[bindx][1]) / (NR_Nest[bindx][0])); // no NR effect
|
|
// nr_params.alpha * ((NR_Hk_old[bindx] * NR_Hk_old[bindx] * NR_X[bindx][1] * NR_X[bindx][1]) / (NR_Nest[bindx][0] * NR_Nest[bindx][0])); // very strong, but strange effect
|
|
nr_params.alpha * ((NR.Hk_old[bindx] * NR.Hk_old[bindx] * NR2.X[bindx][1]) / (NR.Nest[bindx][0])); // working
|
|
}
|
|
// 4 calculate vk = SNRprio(n, bin[i]) / (SNRprio(n, bin[i]) + 1) * SNRpost(n, bin[i]) (eq. 12 of Schmitt et al. 2002, eq. 9 of Romanin et al. 2009)
|
|
NR.vk = NR.SNR_post[bindx] * NR.SNR_prio[bindx] / (1.0 + NR.SNR_prio[bindx]);
|
|
// calculate Hk
|
|
// 5 finally calculate the weighting function for each bin: Hk(n, bin[i]) = 1 / SNRpost(n, [i]) * sqrtf(0.7212 * vk + vk * vk) (eq. 26 of Romanin et al. 2009)
|
|
if(NR.vk > 0.0 && NR.SNR_post[bindx] != 0.0) // prevent sqrtf of negatives
|
|
{
|
|
NR2.Hk[bindx] = 1.0 / NR.SNR_post[bindx] * sqrtf(0.7212 * NR.vk + NR.vk * NR.vk);
|
|
}
|
|
else
|
|
{
|
|
NR2.Hk[bindx] = 1.0;
|
|
}
|
|
if(!(ts.dsp_active & DSP_NR_ENABLE)) // if NR is not enabled (but notch is enabled !)
|
|
{
|
|
NR2.Hk[bindx] = 1.0;
|
|
}
|
|
NR.Hk_old[bindx] = NR2.Hk[bindx];
|
|
NR2.X[bindx][1] = NR2.X[bindx][0];
|
|
}
|
|
}
|
|
|
|
else //new mode under development, following the same equations, sligthly different implemented
|
|
{
|
|
|
|
// ***** Starting with the voice activity detection **************
|
|
float32_t frame_speech=0.0;
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++) // maybe we should limit this to the signal containing bins (filtering!!)
|
|
{
|
|
frame_speech = frame_speech + (NR.SNR_post[bindx] * NR.SNR_prio[bindx]/(1.0 + NR.SNR_prio[bindx]))
|
|
- logf(1.0 + NR.SNR_prio[bindx]); // looks like we had a similar one before, Frank?? ?
|
|
|
|
}
|
|
|
|
if (frame_speech < (0.15 * (VAD_high - VAD_low + 1.0)))// then we have noise!!!!
|
|
|
|
{
|
|
Board_RedLed(LED_STATE_OFF); //Noise!
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++) //update the noise estimate
|
|
{
|
|
NR.Nest[bindx][0] = nr_params.beta * NR.Nest[bindx][0] + (1.0 - nr_params.beta) * NR2.X[bindx][0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Board_RedLed(LED_STATE_ON);//Speech!
|
|
}
|
|
|
|
|
|
// 4 calculate v = SNRprio(n, bin[i]) / (SNRprio(n, bin[i]) + 1) * SNRpost(n, bin[i]) (eq. 12 of Schmitt et al. 2002, eq. 9 of Romanin et al. 2009)
|
|
// and calculate the HK's
|
|
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++)// maybe we should limit this to the signal containing bins (filtering!!)
|
|
{
|
|
float32_t v = NR.SNR_prio[bindx] * NR.SNR_post[bindx] / (1.0 + NR.SNR_prio[bindx]);
|
|
|
|
NR2.Hk[bindx] = 1.0 / NR.SNR_post[bindx] * sqrtf((0.7212 * v + v * v));
|
|
|
|
NR.Hk_old[bindx] = NR.SNR_post[bindx] * NR2.Hk[bindx] * NR2.Hk[bindx]; //
|
|
|
|
|
|
if(!(ts.dsp_active & DSP_NR_ENABLE)) // if NR is not enabled (but notch is enabled !)
|
|
{
|
|
NR2.Hk[bindx] = 1.0;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#ifdef OLD_LONG_TONE_DETECTION
|
|
if((ts.dsp_active & DSP_NOTCH_ENABLE))
|
|
{
|
|
// long tone attenuation = automatic notch filter
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
if(NR2.long_tone[bindx][0] > (float32_t)nr_params.long_tone_thresh)
|
|
{
|
|
NR2.long_tone_gain[bindx] = NR2.long_tone_gain[bindx] * 0.99;
|
|
|
|
if(bindx != 0)
|
|
{
|
|
NR2.long_tone_gain[bindx - 1] = NR2.long_tone_gain[bindx - 1] * 0.9995;
|
|
if(NR2.long_tone_gain[bindx - 1] < 0.2)
|
|
{
|
|
NR2.long_tone_gain[bindx - 1] = 0.2;
|
|
}
|
|
}
|
|
else
|
|
if(bindx != (nr_params.NR_FFT_L / 2 - 1))
|
|
{
|
|
NR2.long_tone_gain[bindx + 1] = NR2.long_tone_gain[bindx + 1] * 0.9995;
|
|
if(NR2.long_tone_gain[bindx + 1] < 0.2)
|
|
{
|
|
NR2.long_tone_gain[bindx + 1] = 0.2;
|
|
}
|
|
}
|
|
if(NR2.long_tone_gain[bindx] < 0.05)
|
|
{
|
|
NR2.long_tone_gain[bindx] = 0.05;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NR2.long_tone_gain[bindx] *= 1.01;
|
|
if(bindx != 0)
|
|
{
|
|
NR2.long_tone_gain[bindx - 1] = NR2.long_tone_gain[bindx - 1] * 1.0005;
|
|
if(NR2.long_tone_gain[bindx - 1] > 1.0)
|
|
{
|
|
NR2.long_tone_gain[bindx - 1] = 1.0;
|
|
}
|
|
}
|
|
else
|
|
if(bindx != (nr_params.NR_FFT_L / 2 - 1))
|
|
{
|
|
NR2.long_tone_gain[bindx + 1] = NR2.long_tone_gain[bindx + 1] * 1.0005;
|
|
if(NR2.long_tone_gain[bindx + 1] > 1.0)
|
|
{
|
|
NR2.long_tone_gain[bindx + 1] = 1.0;
|
|
}
|
|
}
|
|
if(NR2.long_tone_gain[bindx] > 1.0)
|
|
{
|
|
NR2.long_tone_gain[bindx] = 1.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
if((ts.dsp_active & DSP_NOTCH_ENABLE))
|
|
{
|
|
NR2.notch_change = false;
|
|
// long tone attenuation = automatic notch filter
|
|
// first version, only one notch implemented - finds the largest persisting signal and notches it with an IIR
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
// if the (strongly time smoothed) signal in a bin exceeds the threshold,
|
|
// increase the counter for that bin
|
|
if(NR2.long_tone[bindx][0] > (float32_t)nr_params.long_tone_thresh)
|
|
{
|
|
NR2.long_tone_counter[bindx]++;
|
|
}
|
|
// if it does not exceed the threshold, decrement its counter
|
|
else
|
|
{
|
|
NR2.long_tone_counter[bindx]--;
|
|
}
|
|
// care for low counter values
|
|
if(NR2.long_tone_counter[bindx] < 1)
|
|
{
|
|
NR2.long_tone_counter[bindx] = 0;
|
|
}
|
|
// care for high counter values
|
|
else if (NR2.long_tone_counter[bindx] > 200)
|
|
{
|
|
NR2.long_tone_counter[bindx] = 200;
|
|
}
|
|
}
|
|
|
|
// before we look for new notches, we have to care for existing notches
|
|
if(NR2.notch1_active == true)
|
|
{ // Is notch1 till notchworthy ?
|
|
if(NR2.long_tone_counter[NR2.notch1_bin] > 100)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
// if a notch is no longer notchworthy, switch it off
|
|
// and do not switch it on again for at least one second --> set counter to 0
|
|
NR2.long_tone_counter[NR2.notch1_bin] = 0;
|
|
NR2.notch1_active = false;
|
|
NR2.notch_change = true;
|
|
}
|
|
}
|
|
|
|
// set max long tone to zero for maximum search
|
|
NR2.long_tone_max = 0.0;
|
|
NR2.max_bin = -99; // -99 is the indication for reset value
|
|
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
// look for new notches
|
|
// if a tone persists strong for at least one second = 100 frames
|
|
// its a notchworthy tone
|
|
if(NR2.long_tone_counter[bindx] > 100)
|
|
{
|
|
if(NR2.long_tone[bindx][0] > NR2.long_tone_max)
|
|
// find out, if this is the loudest long tone
|
|
{
|
|
NR2.long_tone_max = NR2.long_tone[bindx][0];
|
|
NR2.max_bin = bindx;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(NR2.max_bin != -99)
|
|
{ // yes, we found a notchworthy bin
|
|
NR2.notch1_active = true;
|
|
NR2.long_tone_counter[NR2.max_bin] = 200; // hysteresis ! This notch will stay at least one second
|
|
// was this bin already notched last round?
|
|
if(NR2.notch1_bin != NR2.max_bin)
|
|
{
|
|
NR2.notch1_bin = NR2.max_bin;
|
|
NR2.notch_change = true; // indicate a change
|
|
}
|
|
else
|
|
{
|
|
|
|
}
|
|
}
|
|
// this activates (and deactivates) the autonotch(es)
|
|
if(NR2.notch_change)
|
|
{
|
|
AudioNr_ActivateAutoNotch(NR2.notch1_bin, NR2.notch1_active);
|
|
}
|
|
} // END NOTCH_ENABLE
|
|
#endif
|
|
|
|
|
|
if(nr_params.gain_smooth_enable)
|
|
{
|
|
// we hear considerable distortion in the end result
|
|
// this can be healed significantly by frequency smoothing the gain values
|
|
// this is a trial for smoothing among the gain values
|
|
|
|
//remark: if we smooth the gains and really modify the HK's like here, the noise reduction algorithm might directly "fight" against this!
|
|
// might be better to keep the HK's internaly and smooth a copy of the gains which are than working on the signal.
|
|
|
|
for(int bindx = 1; bindx < (nr_params.NR_FFT_L / 2) - 1; bindx++)
|
|
{
|
|
NR2.Hk[bindx] = nr_params.gain_smooth_alpha * NR2.Hk[bindx - 1] + (1.0 - 2.0 * nr_params.gain_smooth_alpha) * NR2.Hk[bindx] + nr_params.gain_smooth_alpha * NR2.Hk[bindx + 1];
|
|
|
|
}
|
|
NR2.Hk[0] = (1.0 - nr_params.gain_smooth_alpha) * NR2.Hk[0] + nr_params.gain_smooth_alpha * NR2.Hk[1];
|
|
NR2.Hk[(nr_params.NR_FFT_L / 2) - 1] = (1.0 - nr_params.gain_smooth_alpha) * NR2.Hk[(nr_params.NR_FFT_L / 2) - 1] + nr_params.gain_smooth_alpha * NR2.Hk[(nr_params.NR_FFT_L / 2) - 2];
|
|
}
|
|
|
|
} //end of "if nr_params.first_time == 3"
|
|
|
|
|
|
|
|
#if 1
|
|
// FINAL SPECTRAL WEIGHTING: Multiply current FFT results with NR_FFT_buffer for 64 bins with the 64 bin-specific gain factors
|
|
// only do this for the bins inside the filter passband
|
|
// if you do this for all the bins, you will get distorted audio: plopping !
|
|
// for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++) // plopping !!!!
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++) // no plopping
|
|
{
|
|
NR.FFT_buffer[bindx * 2] = NR.FFT_buffer [bindx * 2] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // real part
|
|
NR.FFT_buffer[bindx * 2 + 1] = NR.FFT_buffer [bindx * 2 + 1] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // imag part
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] = NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // real part conjugate symmetric
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] = NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // imag part conjugate symmetric
|
|
}
|
|
|
|
#endif
|
|
// this is another VAD
|
|
// following Esh & Vary 2009
|
|
// https://pdfs.semanticscholar.org/8fdb/fef3bac889a4f7f5ac382377fa7e1a2b8a7f.pdf
|
|
|
|
if(NR2.VAD_type == 1)
|
|
{
|
|
NR_temp_sum = 0;
|
|
float32_t NR_temp_sum2 = 0;
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++)
|
|
{
|
|
NR_temp_sum += NR2.Hk[bindx] * NR2.X[bindx][0];
|
|
NR_temp_sum2 += NR2.X[bindx][0];
|
|
}
|
|
NR.VAD_Esch = NR_temp_sum / NR_temp_sum2;
|
|
}
|
|
|
|
|
|
#endif
|
|
/*****************************************************************
|
|
* NOISE REDUCTION CODE ENDS HERE
|
|
*****************************************************************/
|
|
// very interesting!
|
|
// if I leave the FFT_buffer as is and just multiply the 19 bins below with 0.1, the audio
|
|
// is distorted a little bit !
|
|
// To me, this is an indicator of a problem with windowing . . .
|
|
// OR: smooth the bin gains by frequency, because the problem could be that one bin has gain 1.0 and
|
|
// the adjacent bin has gain 0.1 --> a 20dB difference!
|
|
|
|
#if 0
|
|
for(int bindx = 1; bindx < 20; bindx++)
|
|
// bins 2 to 29 attenuated
|
|
// set real values to 0.1 of their original value
|
|
{
|
|
NR.FFT_buffer[bindx * 2] *= 0.1;
|
|
// NR_FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] *= 0.1; //NR_iFFT_buffer[idx] * 0.1;
|
|
NR.FFT_buffer[bindx * 2 + 1] *= 0.1; //NR_iFFT_buffer[idx] * 0.1;
|
|
// NR_FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] *= 0.1; //NR_iFFT_buffer[idx] * 0.1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef NR_NOTCHTEST // this is a test of a smoother notch filter
|
|
// centre bin to be notched
|
|
NR.FFT_buffer[ bin_c * 2] *= bin1_att; // real
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_c * 2 - 2] *= bin1_att; // imaginary
|
|
NR.FFT_buffer[ bin_c * 2 + 1] *= bin1_att; // real conjugate symmetric
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_c * 2 - 1] *= bin1_att; // imaginary conjugate symmetric
|
|
// centre_bin + 1 to be notched
|
|
NR.FFT_buffer[ bin_p1 * 2] *= bin2_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p1 * 2 - 2] *= bin2_att;
|
|
NR.FFT_buffer[ bin_p1 * 2 + 1] *= bin2_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p1 * 2 - 1] *= bin2_att;
|
|
// centre_bin - 1 to be notched
|
|
NR.FFT_buffer[ bin_m1 * 2] *= bin2_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m1 * 2 - 2] *= bin2_att;
|
|
NR.FFT_buffer[ bin_m1 * 2 + 1] *= bin2_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m1 * 2 - 1] *= bin2_att;
|
|
// centre_bin + 2 to be notched
|
|
NR.FFT_buffer[ bin_p2 * 2] *= bin3_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p2 * 2 - 2] *= bin3_att;
|
|
NR.FFT_buffer[ bin_p2 * 2 + 1] *= bin3_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p2 * 2 - 1] *= bin3_att;
|
|
// centre_bin - 2 to be notched
|
|
NR.FFT_buffer[ bin_m2 * 2] *= bin3_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m2 * 2 - 2] *= bin3_att;
|
|
NR.FFT_buffer[ bin_m2 * 2 + 1] *= bin3_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m2 * 2 - 1] *= bin3_att;
|
|
// centre_bin + 3 to be notched
|
|
NR.FFT_buffer[ bin_p3 * 2] *= bin4_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p3 * 2 - 2] *= bin4_att;
|
|
NR.FFT_buffer[ bin_p3 * 2 + 1] *= bin4_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p3 * 2 - 1] *= bin4_att;
|
|
// centre_bin - 3 to be notched
|
|
NR.FFT_buffer[ bin_m3 * 2] *= bin4_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m3 * 2 - 2] *= bin4_att;
|
|
NR.FFT_buffer[ bin_m3 * 2 + 1] *= bin4_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m3 * 2 - 1] *= bin4_att;
|
|
// centre_bin + 4 to be notched
|
|
NR.FFT_buffer[ bin_p4 * 2] *= bin5_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p4 * 2 - 2] *= bin5_att;
|
|
NR.FFT_buffer[ bin_p4 * 2 + 1] *= bin5_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p4 * 2 - 1] *= bin5_att;
|
|
// centre_bin - 4 to be notched
|
|
NR.FFT_buffer[ bin_m4 * 2] *= bin5_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m4 * 2 - 2] *= bin5_att;
|
|
NR.FFT_buffer[ bin_m4 * 2 + 1] *= bin5_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m4 * 2 - 1] *= bin5_att;
|
|
#endif
|
|
|
|
// Window on exit!
|
|
if(nr_params.fft_256_enable)
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len256, NR.FFT_buffer, 1, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_von_Hann_256[idx];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len128, NR.FFT_buffer, 1, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_van_hann[idx];
|
|
}
|
|
}
|
|
|
|
// do the overlap & add
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{ // take real part of first half of current iFFT result and add to 2nd half of last iFFT_result
|
|
// NR_output_audio_buffer[i + k * (nr_params.NR_FFT_L / 2)] = NR_FFT_buffer[i * 2] + NR_last_iFFT_result[i];
|
|
in_buffer[i + k * (nr_params.NR_FFT_L / 2)] = NR.FFT_buffer[i * 2] + NR.last_iFFT_result[i];
|
|
// FIXME: take out scaling !
|
|
// in_buffer[i + k * (nr_params.NR_FFT_L / 2)] *= 0.3;
|
|
}
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.last_iFFT_result[i] = NR.FFT_buffer[nr_params.NR_FFT_L + i * 2];
|
|
}
|
|
// end of "for" loop which repeats the FFT_iFFT_chain two times !!!
|
|
}
|
|
|
|
// IIR biquad notch filter with four independent notches
|
|
// arm_biquad_cascade_df1_f32 (&NR_notch_biquad, in_buffer, in_buffer, nr_params.NR_FFT_L);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void spectral_noise_reduction_2 (float* in_buffer)
|
|
{
|
|
|
|
// arm_rfft_fast_instance_f32 fftInstance;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Frank DD4WH & Michael DL2FW, November 2017
|
|
// NOISE REDUCTION BASED ON SPECTRAL SUBTRACTION
|
|
// following Romanin et al. 2009 on the basis of Ephraim & Malah 1984 and Hu et al. 2001
|
|
// detailed technical description of the implemented algorithm
|
|
// can be found in our WIKI
|
|
// https://github.com/df8oe/UHSDR/wiki/Noise-reduction
|
|
//
|
|
// half-overlapping input buffers (= overlap 50%)
|
|
// Hann window on 128 samples
|
|
// FFT128 - inverse FFT128
|
|
// overlap-add
|
|
|
|
float32_t NR_sample_rate = 12000.0;
|
|
if(nr_params.NR_decimation_enable)
|
|
{
|
|
NR_sample_rate = 6000.0;
|
|
}
|
|
|
|
static uint8_t NR_init_counter = 0;
|
|
uint8_t VAD_low=0;
|
|
uint8_t VAD_high=63;
|
|
//float32_t NR_temp_sum = 0.0;
|
|
float32_t width = FilterInfo[ts.filters_p->id].width;
|
|
float32_t offset = ts.filters_p->offset;
|
|
float32_t lf_freq = (offset - width/2) / (NR_sample_rate / nr_params.NR_FFT_L); // bin BW is 93.75Hz [12000Hz / 128 bins]
|
|
float32_t uf_freq = (offset + width/2) / (NR_sample_rate / nr_params.NR_FFT_L);
|
|
|
|
if(nr_params.first_time == 1)
|
|
{ // TODO: properly initialize all the variables
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR.last_sample_buffer_L[bindx] = 0.0;
|
|
NR2.Hk[bindx] = 1.0;
|
|
//xu[bindx] = 1.0; //has to be replaced by other variable
|
|
NR.Hk_old[bindx] = 1.0; // old gain or xu in development mode
|
|
NR.Nest[bindx][0] = 0.0;
|
|
NR.Nest[bindx][1] = 1.0;
|
|
}
|
|
nr_params.first_time = 2; // we need to do some more a bit later down
|
|
}
|
|
|
|
if(nr_params.long_tone_reset)
|
|
{
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR2.long_tone_gain[bindx] = 1.0;
|
|
NR2.long_tone_counter[bindx] = 10;
|
|
}
|
|
nr_params.long_tone_reset = false;
|
|
NR2.notch_change = false;
|
|
NR2.notch1_bin = 10; // frequency bin where notch filter 1 has to work
|
|
NR2.max_bin = 10; // holds the bin number of the strongest persistent tone during tone detection
|
|
NR2.long_tone_max = 100.0; // power value of the strongest persistent tone, used for max search
|
|
NR2.notch1_active = false; // is notch1 active?
|
|
NR2.notch2_active = false; // is notch21 active?
|
|
NR2.notch3_active = false; // is notch3 active?
|
|
NR2.notch4_active = false; // is notch4 active?
|
|
}
|
|
|
|
for(int k = 0; k < nr_params.NR_FFT_LOOP_NO; k++)
|
|
{
|
|
// NR_FFT_buffer is 256 floats big
|
|
// interleaved r, i, r, i . . .
|
|
// fill first half of FFT_buffer with last events audio samples
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.FFT_buffer[i * 2] = NR.last_sample_buffer_L[i]; // real
|
|
NR.FFT_buffer[i * 2 + 1] = 0.0; // imaginary
|
|
}
|
|
// copy recent samples to last_sample_buffer for next time!
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.last_sample_buffer_L [i] = in_buffer[i + k * (nr_params.NR_FFT_L / 2)];
|
|
}
|
|
// now fill recent audio samples into second half of FFT_buffer
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.FFT_buffer[nr_params.NR_FFT_L + i * 2] = in_buffer[i+ k * (nr_params.NR_FFT_L / 2)]; // real
|
|
NR.FFT_buffer[nr_params.NR_FFT_L + i * 2 + 1] = 0.0;
|
|
}
|
|
|
|
|
|
|
|
if(nr_params.fft_256_enable)
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len256, NR.FFT_buffer, 0, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_von_Hann_256[idx];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len128, NR.FFT_buffer, 0, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_van_hann[idx];
|
|
}
|
|
}
|
|
|
|
#ifndef NR_NOTCHTEST
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
// this is magnitude for the current frame
|
|
if (nr_params.mode==0)
|
|
NR2.X[bindx][0] = sqrtf(NR.FFT_buffer[bindx * 2] * NR.FFT_buffer[bindx * 2] + NR.FFT_buffer[bindx * 2 + 1] * NR.FFT_buffer[bindx * 2 + 1]);
|
|
else //here we need only the squared magnitude
|
|
NR2.X[bindx][0] = (NR.FFT_buffer[bindx * 2] * NR.FFT_buffer[bindx * 2] + NR.FFT_buffer[bindx * 2 + 1] * NR.FFT_buffer[bindx * 2 + 1]);
|
|
}
|
|
|
|
if(nr_params.first_time == 2)
|
|
{ // TODO: properly initialize all the variables
|
|
if (nr_params.mode==1)
|
|
{
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR.Nest[bindx][0] = NR.Nest[bindx][0] + 0.05* NR2.X[bindx][0];// we do it 20 times to average over 20 frames for app. 100ms only on NR_on/bandswitch/modeswitch,...
|
|
}
|
|
NR_init_counter++;
|
|
if (NR_init_counter > 19)//average over 20 frames for app. 100ms
|
|
{
|
|
NR_init_counter = 0;
|
|
nr_params.first_time = 3; // now we did all the necessary initialization to actually start the noise reduction
|
|
}
|
|
}
|
|
else nr_params.first_time = 3;
|
|
|
|
}
|
|
if (nr_params.first_time == 3)
|
|
{
|
|
|
|
if((ts.dsp_active & DSP_NOTCH_ENABLE))
|
|
{
|
|
// detection of long tones
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR2.long_tone[bindx][0] = (nr_params.long_tone_alpha) * NR2.long_tone[bindx][1] + (1.0 - nr_params.long_tone_alpha) * NR2.X[bindx][0]; //
|
|
NR2.long_tone[bindx][1] = NR2.long_tone[bindx][0];
|
|
}
|
|
}
|
|
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)// 1. Step of NR - calculate the SNR's
|
|
{
|
|
NR.SNR_post[bindx] = fmax(fmin(NR2.X[bindx][0] / NR.Nest[bindx][0],1000.0),0.03); // limited to +30 /-15 dB, might be still too much of reduction, let's try it?
|
|
|
|
NR.SNR_prio[bindx] = fmax(nr_params.alpha * NR.Hk_old[bindx] + (1.0 - nr_params.alpha)*fmax(NR.SNR_post[bindx]-1.0,0.0),0.0);
|
|
}
|
|
|
|
|
|
if (offset == 0)
|
|
{
|
|
offset = width/2;
|
|
}
|
|
|
|
VAD_low = (int)lf_freq;
|
|
VAD_high = (int)uf_freq;
|
|
if(VAD_low == VAD_high)
|
|
{
|
|
VAD_high++;
|
|
}
|
|
if(VAD_low < 1)
|
|
{
|
|
VAD_low = 1;
|
|
}
|
|
else
|
|
if(VAD_low > nr_params.NR_FFT_L / 2 - 2)
|
|
{
|
|
VAD_low = nr_params.NR_FFT_L / 2 - 2;
|
|
}
|
|
if(VAD_high < 1)
|
|
{
|
|
VAD_high = 1;
|
|
}
|
|
else
|
|
if(VAD_high > nr_params.NR_FFT_L / 2)
|
|
{
|
|
VAD_high = nr_params.NR_FFT_L / 2;
|
|
}
|
|
|
|
// ******* alternative VAD trials
|
|
|
|
//new mode under development, following the same equations, sligthly different implemented
|
|
|
|
|
|
// ***** Starting with the voice activity detection **************
|
|
float32_t frame_speech=0.0;
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++) // maybe we should limit this to the signal containing bins (filtering!!)
|
|
{
|
|
frame_speech = frame_speech + (NR.SNR_post[bindx] * NR.SNR_prio[bindx]/(1.0 + NR.SNR_prio[bindx]))
|
|
- logf(1.0 + NR.SNR_prio[bindx]); // looks like we had a similar one before, Frank?? ?
|
|
|
|
}
|
|
|
|
if (frame_speech < (0.15 * (VAD_high - VAD_low + 1.0)))// then we have noise!!!!
|
|
|
|
{
|
|
NR2.VAD_duration = 0;
|
|
if (NR2.VAD_delay > 0)
|
|
{
|
|
NR2.VAD_delay--;
|
|
}
|
|
else
|
|
{
|
|
Board_RedLed(LED_STATE_OFF); //Noise!
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++) //update the noise estimate in dpi
|
|
{
|
|
NR.Nest[bindx][0] = NR.Nest[bindx][0] * nr_params.beta + (1.0 - nr_params.beta) * NR2.X[bindx][0];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NR2.VAD_duration++;
|
|
if (NR2.VAD_duration > 1)
|
|
{
|
|
NR2.VAD_delay=nr_params.vad_delay;
|
|
|
|
|
|
Board_RedLed(LED_STATE_ON);//Speech!
|
|
}
|
|
}
|
|
|
|
// 4 calculate v = SNRprio(n, bin[i]) / (SNRprio(n, bin[i]) + 1) * SNRpost(n, bin[i]) (eq. 12 of Schmitt et al. 2002, eq. 9 of Romanin et al. 2009)
|
|
// and calculate the HK's
|
|
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++)// maybe we should limit this to the signal containing bins (filtering!!)
|
|
{
|
|
float32_t v = NR.SNR_prio[bindx] * NR.SNR_post[bindx] / (1.0 + NR.SNR_prio[bindx]);
|
|
|
|
NR2.Hk[bindx] = 1.0 / NR.SNR_post[bindx] * sqrtf((0.7212 * v + v * v));
|
|
|
|
NR.Hk_old[bindx] = NR.SNR_post[bindx] * NR2.Hk[bindx] * NR2.Hk[bindx]; //
|
|
|
|
|
|
if(!(ts.dsp_active & DSP_NR_ENABLE)) // if NR is not enabled (but notch is enabled !)
|
|
{
|
|
NR2.Hk[bindx] = 1.0;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef OLD_LONG_TONE_DETECTION
|
|
if((ts.dsp_active & DSP_NOTCH_ENABLE))
|
|
{
|
|
// long tone attenuation = automatic notch filter
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
if(NR2.long_tone[bindx][0] > (float32_t)nr_params.long_tone_thresh)
|
|
{
|
|
NR2.long_tone_gain[bindx] = NR2.long_tone_gain[bindx] * 0.99;
|
|
|
|
if(bindx != 0)
|
|
{
|
|
NR2.long_tone_gain[bindx - 1] = NR2.long_tone_gain[bindx - 1] * 0.9995;
|
|
if(NR2.long_tone_gain[bindx - 1] < 0.2)
|
|
{
|
|
NR2.long_tone_gain[bindx - 1] = 0.2;
|
|
}
|
|
}
|
|
else
|
|
if(bindx != (nr_params.NR_FFT_L / 2 - 1))
|
|
{
|
|
NR2.long_tone_gain[bindx + 1] = NR2.long_tone_gain[bindx + 1] * 0.9995;
|
|
if(NR2.long_tone_gain[bindx + 1] < 0.2)
|
|
{
|
|
NR2.long_tone_gain[bindx + 1] = 0.2;
|
|
}
|
|
}
|
|
if(NR2.long_tone_gain[bindx] < 0.05)
|
|
{
|
|
NR2.long_tone_gain[bindx] = 0.05;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NR2.long_tone_gain[bindx] *= 1.01;
|
|
if(bindx != 0)
|
|
{
|
|
NR2.long_tone_gain[bindx - 1] = NR2.long_tone_gain[bindx - 1] * 1.0005;
|
|
if(NR2.long_tone_gain[bindx - 1] > 1.0)
|
|
{
|
|
NR2.long_tone_gain[bindx - 1] = 1.0;
|
|
}
|
|
}
|
|
else
|
|
if(bindx != (nr_params.NR_FFT_L / 2 - 1))
|
|
{
|
|
NR2.long_tone_gain[bindx + 1] = NR2.long_tone_gain[bindx + 1] * 1.0005;
|
|
if(NR2.long_tone_gain[bindx + 1] > 1.0)
|
|
{
|
|
NR2.long_tone_gain[bindx + 1] = 1.0;
|
|
}
|
|
}
|
|
if(NR2.long_tone_gain[bindx] > 1.0)
|
|
{
|
|
NR2.long_tone_gain[bindx] = 1.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
if((ts.dsp_active & DSP_NOTCH_ENABLE))
|
|
{
|
|
NR2.notch_change = false;
|
|
// long tone attenuation = automatic notch filter
|
|
// first version, only one notch implemented - finds the largest persisting signal and notches it with an IIR
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
// if the (strongly time smoothed) signal in a bin exceeds the threshold,
|
|
// increase the counter for that bin
|
|
if(NR2.long_tone[bindx][0] > (float32_t)nr_params.long_tone_thresh)
|
|
{
|
|
NR2.long_tone_counter[bindx]++;
|
|
}
|
|
// if it does not exceed the threshold, decrement its counter
|
|
else
|
|
{
|
|
NR2.long_tone_counter[bindx]--;
|
|
}
|
|
// care for low counter values
|
|
if(NR2.long_tone_counter[bindx] < 1)
|
|
{
|
|
NR2.long_tone_counter[bindx] = 0;
|
|
}
|
|
// care for high counter values
|
|
else if (NR2.long_tone_counter[bindx] > 200)
|
|
{
|
|
NR2.long_tone_counter[bindx] = 200;
|
|
}
|
|
}
|
|
|
|
// before we look for new notches, we have to care for existing notches
|
|
if(NR2.notch1_active == true)
|
|
{ // Is notch1 till notchworthy ?
|
|
if(NR2.long_tone_counter[NR2.notch1_bin] > 100)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
// if a notch is no longer notchworthy, switch it off
|
|
// and do not switch it on again for at least one second --> set counter to 0
|
|
NR2.long_tone_counter[NR2.notch1_bin] = 0;
|
|
NR2.notch1_active = false;
|
|
NR2.notch_change = true;
|
|
}
|
|
}
|
|
|
|
// set max long tone to zero for maximum search
|
|
NR2.long_tone_max = 0.0;
|
|
NR2.max_bin = -99; // -99 is the indication for reset value
|
|
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
// look for new notches
|
|
// if a tone persists strong for at least one second = 100 frames
|
|
// its a notchworthy tone
|
|
if(NR2.long_tone_counter[bindx] > 100)
|
|
{
|
|
if(NR2.long_tone[bindx][0] > NR2.long_tone_max)
|
|
// find out, if this is the loudest long tone
|
|
{
|
|
NR2.long_tone_max = NR2.long_tone[bindx][0];
|
|
NR2.max_bin = bindx;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(NR2.max_bin != -99)
|
|
{ // yes, we found a notchworthy bin
|
|
NR2.notch1_active = true;
|
|
NR2.long_tone_counter[NR2.max_bin] = 200; // hysteresis ! This notch will stay at least one second
|
|
// was this bin already notched last round?
|
|
if(NR2.notch1_bin != NR2.max_bin)
|
|
{
|
|
NR2.notch1_bin = NR2.max_bin;
|
|
NR2.notch_change = true; // indicate a change
|
|
}
|
|
else
|
|
{
|
|
|
|
}
|
|
}
|
|
// this activates (and deactivates) the autonotch(es)
|
|
if(NR2.notch_change)
|
|
{
|
|
AudioNr_ActivateAutoNotch(NR2.notch1_bin, NR2.notch1_active);
|
|
}
|
|
} // END NOTCH_ENABLE
|
|
#endif
|
|
|
|
|
|
if(nr_params.gain_smooth_enable)
|
|
{
|
|
// we hear considerable distortion in the end result
|
|
// this can be healed significantly by frequency smoothing the gain values
|
|
// this is a trial for smoothing among the gain values
|
|
|
|
//remark: if we smooth the gains and really modify the HK's like here, the noise reduction algorithm might directly "fight" against this!
|
|
// might be better to keep the HK's internaly and smooth a copy of the gains which are than working on the signal.
|
|
|
|
for(int bindx = 1; bindx < (nr_params.NR_FFT_L / 2) - 1; bindx++)
|
|
{
|
|
NR2.Hk[bindx] = nr_params.gain_smooth_alpha * NR2.Hk[bindx - 1] + (1.0 - 2.0 * nr_params.gain_smooth_alpha) * NR2.Hk[bindx] + nr_params.gain_smooth_alpha * NR2.Hk[bindx + 1];
|
|
|
|
}
|
|
NR2.Hk[0] = (1.0 - nr_params.gain_smooth_alpha) * NR2.Hk[0] + nr_params.gain_smooth_alpha * NR2.Hk[1];
|
|
NR2.Hk[(nr_params.NR_FFT_L / 2) - 1] = (1.0 - nr_params.gain_smooth_alpha) * NR2.Hk[(nr_params.NR_FFT_L / 2) - 1] + nr_params.gain_smooth_alpha * NR2.Hk[(nr_params.NR_FFT_L / 2) - 2];
|
|
}
|
|
|
|
} //end of "if nr_params.first_time == 3"
|
|
|
|
|
|
|
|
// FINAL SPECTRAL WEIGHTING: Multiply current FFT results with NR_FFT_buffer for 64 bins with the 64 bin-specific gain factors
|
|
// only do this for the bins inside the filter passband
|
|
// if you do this for all the bins, you will get distorted audio: plopping !
|
|
// for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++) // plopping !!!!
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++) // no plopping
|
|
{
|
|
NR.FFT_buffer[bindx * 2] = NR.FFT_buffer [bindx * 2] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // real part
|
|
NR.FFT_buffer[bindx * 2 + 1] = NR.FFT_buffer [bindx * 2 + 1] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // imag part
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] = NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // real part conjugate symmetric
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] = NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // imag part conjugate symmetric
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
/*****************************************************************
|
|
* NOISE REDUCTION CODE ENDS HERE
|
|
*****************************************************************/
|
|
// very interesting!
|
|
// if I leave the FFT_buffer as is and just multiply the 19 bins below with 0.1, the audio
|
|
// is distorted a little bit !
|
|
// To me, this is an indicator of a problem with windowing . . .
|
|
// OR: smooth the bin gains by frequency, because the problem could be that one bin has gain 1.0 and
|
|
// the adjacent bin has gain 0.1 --> a 20dB difference!
|
|
|
|
#if 0
|
|
for(int bindx = 1; bindx < 20; bindx++)
|
|
// bins 2 to 29 attenuated
|
|
// set real values to 0.1 of their original value
|
|
{
|
|
NR.FFT_buffer[bindx * 2] *= 0.1;
|
|
// NR_FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] *= 0.1; //NR_iFFT_buffer[idx] * 0.1;
|
|
NR.FFT_buffer[bindx * 2 + 1] *= 0.1; //NR_iFFT_buffer[idx] * 0.1;
|
|
// NR_FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] *= 0.1; //NR_iFFT_buffer[idx] * 0.1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef NR_NOTCHTEST // this is a test of a smoother notch filter
|
|
// centre bin to be notched
|
|
NR.FFT_buffer[ bin_c * 2] *= bin1_att; // real
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_c * 2 - 2] *= bin1_att; // imaginary
|
|
NR.FFT_buffer[ bin_c * 2 + 1] *= bin1_att; // real conjugate symmetric
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_c * 2 - 1] *= bin1_att; // imaginary conjugate symmetric
|
|
// centre_bin + 1 to be notched
|
|
NR.FFT_buffer[ bin_p1 * 2] *= bin2_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p1 * 2 - 2] *= bin2_att;
|
|
NR.FFT_buffer[ bin_p1 * 2 + 1] *= bin2_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p1 * 2 - 1] *= bin2_att;
|
|
// centre_bin - 1 to be notched
|
|
NR.FFT_buffer[ bin_m1 * 2] *= bin2_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m1 * 2 - 2] *= bin2_att;
|
|
NR.FFT_buffer[ bin_m1 * 2 + 1] *= bin2_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m1 * 2 - 1] *= bin2_att;
|
|
// centre_bin + 2 to be notched
|
|
NR.FFT_buffer[ bin_p2 * 2] *= bin3_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p2 * 2 - 2] *= bin3_att;
|
|
NR.FFT_buffer[ bin_p2 * 2 + 1] *= bin3_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p2 * 2 - 1] *= bin3_att;
|
|
// centre_bin - 2 to be notched
|
|
NR.FFT_buffer[ bin_m2 * 2] *= bin3_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m2 * 2 - 2] *= bin3_att;
|
|
NR.FFT_buffer[ bin_m2 * 2 + 1] *= bin3_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m2 * 2 - 1] *= bin3_att;
|
|
// centre_bin + 3 to be notched
|
|
NR.FFT_buffer[ bin_p3 * 2] *= bin4_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p3 * 2 - 2] *= bin4_att;
|
|
NR.FFT_buffer[ bin_p3 * 2 + 1] *= bin4_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p3 * 2 - 1] *= bin4_att;
|
|
// centre_bin - 3 to be notched
|
|
NR.FFT_buffer[ bin_m3 * 2] *= bin4_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m3 * 2 - 2] *= bin4_att;
|
|
NR.FFT_buffer[ bin_m3 * 2 + 1] *= bin4_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m3 * 2 - 1] *= bin4_att;
|
|
// centre_bin + 4 to be notched
|
|
NR.FFT_buffer[ bin_p4 * 2] *= bin5_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p4 * 2 - 2] *= bin5_att;
|
|
NR.FFT_buffer[ bin_p4 * 2 + 1] *= bin5_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_p4 * 2 - 1] *= bin5_att;
|
|
// centre_bin - 4 to be notched
|
|
NR.FFT_buffer[ bin_m4 * 2] *= bin5_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m4 * 2 - 2] *= bin5_att;
|
|
NR.FFT_buffer[ bin_m4 * 2 + 1] *= bin5_att;
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bin_m4 * 2 - 1] *= bin5_att;
|
|
#endif
|
|
|
|
|
|
// Window on exit!
|
|
if(nr_params.fft_256_enable)
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len256, NR.FFT_buffer, 1, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_von_Hann_256[idx];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len128, NR.FFT_buffer, 1, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_van_hann[idx];
|
|
}
|
|
}
|
|
|
|
// do the overlap & add
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{ // take real part of first half of current iFFT result and add to 2nd half of last iFFT_result
|
|
// NR_output_audio_buffer[i + k * (nr_params.NR_FFT_L / 2)] = NR_FFT_buffer[i * 2] + NR_last_iFFT_result[i];
|
|
in_buffer[i + k * (nr_params.NR_FFT_L / 2)] = NR.FFT_buffer[i * 2] + NR.last_iFFT_result[i];
|
|
// FIXME: take out scaling !
|
|
// in_buffer[i + k * (nr_params.NR_FFT_L / 2)] *= 0.3;
|
|
}
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.last_iFFT_result[i] = NR.FFT_buffer[nr_params.NR_FFT_L + i * 2];
|
|
}
|
|
// end of "for" loop which repeats the FFT_iFFT_chain two times !!!
|
|
}
|
|
|
|
// IIR biquad notch filter with four independent notches
|
|
// arm_biquad_cascade_df1_f32 (&NR_notch_biquad, in_buffer, in_buffer, nr_params.NR_FFT_L);
|
|
}
|
|
#endif
|
|
|
|
void spectral_noise_reduction_3 (float* in_buffer)
|
|
{
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Frank DD4WH & Michael DL2FW, November 2017
|
|
// NOISE REDUCTION BASED ON SPECTRAL SUBTRACTION
|
|
// following Romanin et al. 2009 on the basis of Ephraim & Malah 1984 and Hu et al. 2001
|
|
// detailed technical description of the implemented algorithm
|
|
// can be found in our WIKI
|
|
// https://github.com/df8oe/UHSDR/wiki/Noise-reduction
|
|
//
|
|
// half-overlapping input buffers (= overlap 50%)
|
|
// Hann window on 128 or 256 samples
|
|
// FFT128 - inverse FFT128 or FFT256 / iFFT256
|
|
// overlap-add
|
|
|
|
const float32_t width = FilterInfo[ts.filters_p->id].width;
|
|
const float32_t offset = ts.filters_p->offset;
|
|
|
|
float32_t NR_sample_rate = nr_params.NR_decimation_active? 6000.0: 12000.0;
|
|
|
|
static uint8_t NR_init_counter = 0;
|
|
uint8_t VAD_low=0;
|
|
uint8_t VAD_high=63;
|
|
|
|
float32_t lf_freq = (offset - width/2) / (NR_sample_rate / nr_params.NR_FFT_L); // bin BW is 93.75Hz [12000Hz / 128 bins]
|
|
float32_t uf_freq = (offset + width/2) / (NR_sample_rate / nr_params.NR_FFT_L);
|
|
|
|
//const float32_t tinc = 0.00533333; // frame time 5.3333ms
|
|
//const float32_t tax=0.071; // noise output smoothing time constant - absolut value in seconds
|
|
//const float32_t tap=0.152; // speech prob smoothing time constant - absolut value in seconds
|
|
const float32_t psthr=0.99; // threshold for smoothed speech probability [0.99]
|
|
const float32_t pnsaf=0.01; // noise probability safety value [0.01]
|
|
//const float32_t asnr=15; // active SNR in dB
|
|
const float32_t psini=0.5; // initial speech probability [0.5]
|
|
const float32_t pspri=0.5; // prior speech probability [0.5]
|
|
|
|
// for 12ksps and FFT128
|
|
//NR2.ax = 0.9276; //expf(-tinc / tax);
|
|
//NR2.ap = 0.9655; //expf(-tinc / tap);
|
|
|
|
// for 6ksps and FFT256
|
|
NR2.ax = 0.7405;
|
|
NR2.ap = 0.8691;
|
|
//NR2.xih1 = 31.62; //powf(10, (float32_t)NR2.asnr / 10.0);
|
|
NR2.xih1 = pow10f((float32_t)NR2.asnr / 10.0);
|
|
NR2.xih1r = 1.0 / (1.0 + NR2.xih1) - 1.0;
|
|
NR2.pfac= (1.0 / pspri - 1.0) * (1.0 + NR2.xih1);
|
|
NR2.snr_prio_min = 0.001; //powf(10, - (float32_t)NR2.snr_prio_min_int / 10.0); //range should be down to -30dB min
|
|
NR2.power_threshold = (float32_t)(NR2.power_threshold_int)/100.0;
|
|
static float32_t pslp[NR_FFT_SIZE];
|
|
static float32_t xt[NR_FFT_SIZE];
|
|
float32_t xtr;
|
|
float32_t ph1y[NR_FFT_SIZE];
|
|
|
|
//ax and ap adjustment according to FFT-size and decimation
|
|
// for 12KS and 128'er FFT we are doing well with the above values
|
|
// This table shows the tinc values in ms for the calculation of the coefficients ax and ap
|
|
//
|
|
// FFT Size: | 128 256
|
|
// ______|________________________
|
|
// Samplerate: 12KS | 5.333 10.666
|
|
// |
|
|
// 6KS |10.666 21.333
|
|
|
|
|
|
/*if (nr_params.fft_256_enable && nr_params.NR_decimation_enable)
|
|
{
|
|
NR2.ax = 0.7405;
|
|
NR2.ap = 0.8691;
|
|
}
|
|
else
|
|
if (nr_params.fft_256_enable || nr_params.NR_decimation_enable)
|
|
{
|
|
NR2.ax = 0.8605;
|
|
NR2.ap = 0.9322;
|
|
} */
|
|
|
|
|
|
if(nr_params.first_time == 1)
|
|
{ // TODO: properly initialize all the variables
|
|
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR.last_sample_buffer_L[bindx] = 0.0;
|
|
NR2.Hk[bindx] = 1.0;
|
|
//xu[bindx] = 1.0; //has to be replaced by other variable
|
|
NR.Hk_old[bindx] = 1.0; // old gain or xu in development mode
|
|
NR.Nest[bindx][0] = 0.0;
|
|
NR.Nest[bindx][1] = 1.0;
|
|
pslp[bindx] = 0.5;
|
|
// NR2.long_tone_gain[bindx] = 1.0;
|
|
}
|
|
nr_params.first_time = 2; // we need to do some more a bit later down
|
|
}
|
|
|
|
for(int k = 0; k < nr_params.NR_FFT_LOOP_NO; k++)
|
|
{
|
|
// NR_FFT_buffer is 256 floats big
|
|
// interleaved r, i, r, i . . .
|
|
// fill first half of FFT_buffer with last events audio samples
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.FFT_buffer[i * 2] = NR.last_sample_buffer_L[i]; // real
|
|
NR.FFT_buffer[i * 2 + 1] = 0.0; // imaginary
|
|
}
|
|
// copy recent samples to last_sample_buffer for next time!
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.last_sample_buffer_L [i] = in_buffer[i + k * (nr_params.NR_FFT_L / 2)];
|
|
}
|
|
// now fill recent audio samples into second half of FFT_buffer
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.FFT_buffer[nr_params.NR_FFT_L + i * 2] = in_buffer[i+ k * (nr_params.NR_FFT_L / 2)]; // real
|
|
NR.FFT_buffer[nr_params.NR_FFT_L + i * 2 + 1] = 0.0;
|
|
}
|
|
/////////////////////////////////7
|
|
// WINDOWING
|
|
|
|
// if(nr_params.fft_256_enable)
|
|
{
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_von_Hann_256[idx];
|
|
}
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len256, NR.FFT_buffer, 0, 1);
|
|
}
|
|
/*else
|
|
{
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_van_hann[idx];
|
|
}
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len128, NR.FFT_buffer, 0, 1);
|
|
}*/
|
|
|
|
// NR_FFT
|
|
// calculation is performed in-place the FFT_buffer [re, im, re, im, re, im . . .]
|
|
|
|
|
|
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
//here we need squared magnitude
|
|
NR2.X[bindx][0] = (NR.FFT_buffer[bindx * 2] * NR.FFT_buffer[bindx * 2] + NR.FFT_buffer[bindx * 2 + 1] * NR.FFT_buffer[bindx * 2 + 1]);
|
|
}
|
|
|
|
if(nr_params.first_time == 2)
|
|
{
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)
|
|
{
|
|
NR.Nest[bindx][0] = NR.Nest[bindx][0] + 0.05* NR2.X[bindx][0];// we do it 20 times to average over 20 frames for app. 100ms only on NR_on/bandswitch/modeswitch,...
|
|
xt[bindx] = psini * NR.Nest[bindx][0];
|
|
}
|
|
NR_init_counter++;
|
|
if (NR_init_counter > 19)//average over 20 frames for app. 100ms
|
|
{
|
|
NR_init_counter = 0;
|
|
nr_params.first_time = 3; // now we did all the necessary initialization to actually start the noise reduction
|
|
}
|
|
}
|
|
if (nr_params.first_time == 3)
|
|
{
|
|
|
|
//new noise estimate MMSE based!!!
|
|
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)// 1. Step of NR - calculate the SNR's
|
|
{
|
|
ph1y[bindx] = 1.0 / (1.0 + NR2.pfac * expf(NR2.xih1r * NR2.X[bindx][0]/xt[bindx]));
|
|
pslp[bindx] = NR2.ap * pslp[bindx] + (1.0 - NR2.ap) * ph1y[bindx];
|
|
//ph1y[bindx] = fmin(ph1y[bindx], 1.0 - pnsaf * (pslp[bindx] > psthr)); //?????
|
|
|
|
if (pslp[bindx] > psthr)
|
|
{
|
|
ph1y[bindx] = 1.0 - pnsaf;
|
|
}
|
|
else
|
|
{
|
|
ph1y[bindx] = fmin(ph1y[bindx] , 1.0);
|
|
}
|
|
xtr = (1.0 - ph1y[bindx]) * NR2.X[bindx][0] + ph1y[bindx] * xt[bindx];
|
|
xt[bindx] = NR2.ax * xt[bindx] + (1.0 - NR2.ax) * xtr;
|
|
}
|
|
|
|
|
|
for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++)// 1. Step of NR - calculate the SNR's
|
|
{
|
|
NR.SNR_post[bindx] = fmax(fmin(NR2.X[bindx][0] / xt[bindx],1000.0), NR2.snr_prio_min); // limited to +30 /-15 dB, might be still too much of reduction, let's try it?
|
|
|
|
NR.SNR_prio[bindx] = fmax(nr_params.alpha * NR.Hk_old[bindx] + (1.0 - nr_params.alpha) * fmax(NR.SNR_post[bindx] - 1.0, 0.0), 0.0);
|
|
}
|
|
|
|
VAD_low = (int)lf_freq;
|
|
|
|
VAD_high = (int)uf_freq;
|
|
|
|
if(VAD_low == VAD_high)
|
|
{
|
|
VAD_high++;
|
|
}
|
|
if(VAD_low < 1)
|
|
{
|
|
VAD_low = 1;
|
|
}
|
|
else
|
|
if(VAD_low > nr_params.NR_FFT_L / 2 - 2)
|
|
{
|
|
VAD_low = nr_params.NR_FFT_L / 2 - 2;
|
|
}
|
|
if(VAD_high < 1)
|
|
{
|
|
VAD_high = 1;
|
|
}
|
|
else
|
|
if(VAD_high > nr_params.NR_FFT_L / 2)
|
|
{
|
|
VAD_high = nr_params.NR_FFT_L / 2;
|
|
}
|
|
|
|
|
|
// 4 calculate v = SNRprio(n, bin[i]) / (SNRprio(n, bin[i]) + 1) * SNRpost(n, bin[i]) (eq. 12 of Schmitt et al. 2002, eq. 9 of Romanin et al. 2009)
|
|
// and calculate the HK's
|
|
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++)// maybe we should limit this to the signal containing bins (filtering!!)
|
|
{
|
|
float32_t v = NR.SNR_prio[bindx] * NR.SNR_post[bindx] / (1.0 + NR.SNR_prio[bindx]);
|
|
|
|
NR2.Hk[bindx] = fmax(1.0 / NR.SNR_post[bindx] * sqrtf((0.7212 * v + v * v)),0.001); //limit HK's to 0.001'
|
|
|
|
NR.Hk_old[bindx] = NR.SNR_post[bindx] * NR2.Hk[bindx] * NR2.Hk[bindx]; //
|
|
|
|
|
|
/*if(!(ts.dsp_active & DSP_NR_ENABLE)) // if NR is not enabled (but notch is enabled !)
|
|
{
|
|
NR2.Hk[bindx] = 1.0;
|
|
} */
|
|
}
|
|
// musical noise "artefact" reduction by dynamic averaging - depending on SNR ratio
|
|
NR2.pre_power = 0.0;
|
|
NR2.post_power = 0.0;
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++)
|
|
{
|
|
NR2.pre_power += NR2.X[bindx][0];
|
|
NR2.post_power += NR2.Hk[bindx] * NR2.Hk[bindx] * NR2.X[bindx][0];
|
|
}
|
|
|
|
NR2.power_ratio = NR2.post_power / NR2.pre_power;
|
|
if (NR2.power_ratio > NR2.power_threshold)
|
|
{
|
|
NR2.power_ratio = 1.0;
|
|
NR2.NN = 1;
|
|
}
|
|
else
|
|
{
|
|
NR2.NN = 1 + 2 * (int)(0.5 + NR2.width * (1.0 - NR2.power_ratio / NR2.power_threshold));
|
|
}
|
|
|
|
for(int bindx = VAD_low + NR2.NN/2; bindx < VAD_high - NR2.NN/2; bindx++)
|
|
{
|
|
NR.Nest[bindx][0] = 0.0;
|
|
for(int m = bindx - NR2.NN/2; m <= bindx + NR2.NN/2;m++)
|
|
{
|
|
NR.Nest[bindx][0] += NR2.Hk[m];
|
|
}
|
|
NR.Nest[bindx][0] /= (float32_t)NR2.NN;
|
|
}
|
|
|
|
// and now the edges - only going NN steps forward and taking the average
|
|
// lower edge
|
|
for(int bindx = VAD_low; bindx < VAD_low + NR2.NN/2; bindx++)
|
|
{
|
|
NR.Nest[bindx][0] = 0.0;
|
|
for(int m = bindx; m < (bindx + NR2.NN);m++)
|
|
{
|
|
NR.Nest[bindx][0] += NR2.Hk[m];
|
|
}
|
|
NR.Nest[bindx][0] /= (float32_t)NR2.NN;
|
|
}
|
|
|
|
// upper edge - only going NN steps backward and taking the average
|
|
for(int bindx = VAD_high - NR2.NN; bindx < VAD_high; bindx++)
|
|
{
|
|
NR.Nest[bindx][0] = 0.0;
|
|
for(int m = bindx; m > (bindx - NR2.NN); m--)
|
|
{
|
|
NR.Nest[bindx][0] += NR2.Hk[m];
|
|
}
|
|
NR.Nest[bindx][0] /= (float32_t)NR2.NN;
|
|
}
|
|
|
|
// end of edge treatment
|
|
|
|
for(int bindx = VAD_low + NR2.NN/2; bindx < VAD_high - NR2.NN/2; bindx++)
|
|
{
|
|
NR2.Hk[bindx] = NR.Nest[bindx][0];
|
|
}
|
|
// end of musical noise reduction
|
|
} //end of "if nr_params.first_time == 3"
|
|
|
|
|
|
// FINAL SPECTRAL WEIGHTING: Multiply current FFT results with NR_FFT_buffer for 64 bins with the 64 bin-specific gain factors
|
|
// only do this for the bins inside the filter passband
|
|
// if you do this for all the bins, you will get distorted audio: plopping !
|
|
// for(int bindx = 0; bindx < nr_params.NR_FFT_L / 2; bindx++) // plopping !!!!
|
|
for(int bindx = VAD_low; bindx < VAD_high; bindx++) // no plopping
|
|
{
|
|
NR.FFT_buffer[bindx * 2] = NR.FFT_buffer [bindx * 2] * NR2.Hk[bindx]; // real part
|
|
NR.FFT_buffer[bindx * 2 + 1] = NR.FFT_buffer [bindx * 2 + 1] * NR2.Hk[bindx]; // imag part
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] = NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] * NR2.Hk[bindx]; // real part conjugate symmetric
|
|
NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] = NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] * NR2.Hk[bindx]; // imag part conjugate symmetric
|
|
// NR.FFT_buffer[bindx * 2] = NR.FFT_buffer [bindx * 2] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // real part
|
|
// NR.FFT_buffer[bindx * 2 + 1] = NR.FFT_buffer [bindx * 2 + 1] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // imag part
|
|
// NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] = NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 2] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // real part conjugate symmetric
|
|
// NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] = NR.FFT_buffer[nr_params.NR_FFT_L * 2 - bindx * 2 - 1] * NR2.Hk[bindx] * NR2.long_tone_gain[bindx]; // imag part conjugate symmetric
|
|
}
|
|
|
|
/*****************************************************************
|
|
* NOISE REDUCTION CODE ENDS HERE
|
|
*****************************************************************/
|
|
// NR_iFFT
|
|
// & Window on exit!
|
|
// if(nr_params.fft_256_enable)
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len256, NR.FFT_buffer, 1, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_von_Hann_256[idx];
|
|
}
|
|
}
|
|
/*else
|
|
{
|
|
arm_cfft_f32(&arm_cfft_sR_f32_len128, NR.FFT_buffer, 1, 1);
|
|
for (int idx = 0; idx < nr_params.NR_FFT_L; idx++)
|
|
{
|
|
NR.FFT_buffer[idx * 2] *= SQRT_van_hann[idx];
|
|
}
|
|
}*/
|
|
|
|
// do the overlap & add
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{ // take real part of first half of current iFFT result and add to 2nd half of last iFFT_result
|
|
// NR_output_audio_buffer[i + k * (nr_params.NR_FFT_L / 2)] = NR_FFT_buffer[i * 2] + NR_last_iFFT_result[i];
|
|
in_buffer[i + k * (nr_params.NR_FFT_L / 2)] = NR.FFT_buffer[i * 2] + NR.last_iFFT_result[i];
|
|
}
|
|
for(int i = 0; i < nr_params.NR_FFT_L / 2; i++)
|
|
{
|
|
NR.last_iFFT_result[i] = NR.FFT_buffer[nr_params.NR_FFT_L + i * 2];
|
|
}
|
|
// end of "for" loop which repeats the FFT_iFFT_chain two times !!!
|
|
}
|
|
|
|
// IIR biquad notch filter with four independent notches
|
|
// arm_biquad_cascade_df1_f32 (&NR_notch_biquad, in_buffer, in_buffer, nr_params.NR_FFT_L);
|
|
}
|
|
|
|
//alt noise blanking is trying to localize some impulse noise within the samples and after that
|
|
//trying to replace corrupted samples by linear predicted samples.
|
|
//therefore, first we calculate the lpc coefficients which represent the actual status of the
|
|
//speech or sound generating "instrument" (in case of speech this is an estimation of the current
|
|
//filter-function of the voice generating tract behind our lips :-) )
|
|
//after finding this function we inverse filter the actual samples by this function
|
|
//so we are eliminating the speech, but not the noise. Then we do a matched filtering an thereby detecting impulses
|
|
//After that we threshold the remaining samples by some
|
|
//level and so detecting impulse noise's positions within the current frame - if one (or more) impulses are there.
|
|
//finally some area around the impulse position will be replaced by predicted samples from both sides (forward and
|
|
//backward prediction)
|
|
//hopefully we have enough processor power left....
|
|
|
|
void alt_noise_blanking(float* insamp,int Nsam, int order, float* E ) //Nsam = 128
|
|
{
|
|
|
|
#ifdef debug_alternate_NR
|
|
|
|
const float32_t NR_test_samp[128] = { 853.472351,629.066223,864.270813,1012.3078,738.378113,
|
|
446.268219,635.763123,1062.71118,955.245667,22.6679211,-1130.45386,-1737.12817,
|
|
-1728.79114,-1594.82227,-1545.75671,-1208.91003,-252.898315,993.880493,1820.26538,
|
|
1915.65186,1597.90259,1248.58838,809.456909,28.6509247,-961.62677,-1604.66443,-1499.18225,
|
|
-824.882935,-85.1342163,432.899261,782.52063,1029.38452,1040.57166,692.128662,138.820541,
|
|
-286.785767,-420.356415,-384.165161,-348.958527,-308.304718,-171.111633,4.52698851, //last value:4.52698851,
|
|
-5.53196001,-368.999939,-1031.19165,-1766.01074,-2290.01587,-2293.98853,-1514.0238,
|
|
23.0157223,1797.16394,3018.3894,3231.77148,2702.38745,2085.92676,1685.99255,1145.43176,
|
|
-31.9259377,-1722.42847,-3112.2937,-3453.61426,-2790.31763,-1812.12769,-1028.70874,
|
|
-1812 ,897.985779,2375.50903,3409.33472,3332.44238,2293.16602,1067.26196,183.806381,
|
|
-548.479553,-1549.47034,-2692.18213,-3288.44702,-2873.70239,-1761.34033,-636.71936,
|
|
250.664383,1198.7804,2336.43726,3121.80615,2848.64355,1556.67969,110.084801,-724.328186,
|
|
-1013.82141,-1265.38879,-1506.06091,-1177.04529,-35.6577721,1209.823,1520.28088,679.406555,
|
|
-514.541626,-1245.55945,-1508.29407,-1707.93408,-1736.12427,-965.137085,752.618347,2518.7168,
|
|
3185.57031,2563.83838,1472.3927,613.243835,-172.269989,-1311.97058,-2534.06421,-2982.73169,
|
|
-2282.05859,-1025.64673,12.714426,809.696228,1828.12854,2977.01709,3388.77612,2460.82178,
|
|
751.800781,-567.183105,-1026.46143,-1190.80762,-1635.05701,-2060.84619,-1785.74683,-841.740173,
|
|
-62.468441
|
|
|
|
};
|
|
|
|
|
|
|
|
const float32_t NR_test_sinus_samp[128] = {
|
|
0, 765.3668647302, 1414.2135623731, 1847.7590650226, 2000, 1847.7590650226, 1414.2135623731, 765.3668647302,
|
|
0, -765.3668647302, -1414.2135623731, -1847.7590650226, -2000, -1847.7590650226, -1414.2135623731, -765.3668647302,
|
|
|
|
0, 765.3668647302, 1414.2135623731, 1847.7590650226, 2000, 1847.7590650226, 1414.2135623731, 765.3668647302,
|
|
0, -765.3668647302, -1414.2135623731, -1847.7590650226, -2000, -1847.7590650226, -1414.2135623731, -765.3668647302,
|
|
|
|
0, 765.3668647302, 1414.2135623731, 1847.7590650226, 2000, 1847.7590650226, 1414.2135623731, 765.3668647302,
|
|
0, -765.3668647302, -1414.2135623731, -1847.7590650226, -2000, -1847.7590650226, -1414.2135623731, -765.3668647302,
|
|
|
|
0, 765.3668647302, 1414.2135623731, 1847.7590650226, 2000, 1847.7590650226, 1414.2135623731, 765.3668647302,
|
|
0, -765.3668647302, -1414.2135623731, -1847.7590650226, -2000, -1847.7590650226, -1414.2135623731, -765.3668647302,
|
|
|
|
0, 765.3668647302, 1414.2135623731, 1847.7590650226, 2000, 1847.7590650226, 1414.2135623731, 765.3668647302,
|
|
0, -765.3668647302, -1414.2135623731, -1847.7590650226, -2000, -1847.7590650226, -1414.2135623731, -765.3668647302,
|
|
|
|
0, 765.3668647302, 1414.2135623731, 1847.7590650226, 2000, 1847.7590650226, 1414.2135623731, 765.3668647302,
|
|
0, -765.3668647302, -1414.2135623731, -1847.7590650226, -2000, -1847.7590650226, -1414.2135623731, -765.3668647302,
|
|
|
|
0, 765.3668647302, 1414.2135623731, 1847.7590650226, 2000, 1847.7590650226, 1414.2135623731, 765.3668647302,
|
|
0, -765.3668647302, -1414.2135623731, -1847.7590650226, -2000, -1847.7590650226, -1414.2135623731, -765.3668647302,
|
|
|
|
0, 765.3668647302, 1414.2135623731, 1847.7590650226, 2000, 1847.7590650226, 1414.2135623731, 765.3668647302,
|
|
0, -765.3668647302, -1414.2135623731, -1847.7590650226, -2000, -1847.7590650226, -1414.2135623731, -765.3668647302
|
|
};
|
|
|
|
|
|
#endif
|
|
|
|
#define boundary_blank 14 // for first trials very large!!!!
|
|
#define impulse_length 7 // has to be odd!!!! 7 / 3 should be enough
|
|
#define PL 3 // has to be (impulse_length-1)/2 !!!!
|
|
#define order 10 // lpc's order
|
|
arm_fir_instance_f32 LPC;
|
|
float32_t lpcs[order+1]; // we reserve one more than "order" because of a leading "1"
|
|
float32_t reverse_lpcs[order+1]; //this takes the reversed order lpc coefficients
|
|
float32_t firStateF32[NR_FFT_SIZE + order];
|
|
float32_t tempsamp[NR_FFT_SIZE];
|
|
float32_t sigma2; //taking the variance of the inpo
|
|
float32_t lpc_power;
|
|
float32_t impulse_threshold;
|
|
int impulse_positions[5]; //we allow a maximum of 5 impulses per frame
|
|
int search_pos=0;
|
|
int impulse_count=0;
|
|
//static float32_t last_frame_end[order+PL]; //this takes the last samples from the previous frame to do the prediction within the boundaries
|
|
#ifdef debug_alternate_NR
|
|
static int frame_count=0; //only used for the distortion insertion - can alter be deleted
|
|
int dist_level=0;//only used for the distortion insertion - can alter be deleted
|
|
int nr_setting = 0;
|
|
#endif
|
|
|
|
float32_t R[11]; // takes the autocorrelation results
|
|
float32_t k,alfa;
|
|
|
|
float32_t any[order+1]; //some internal buffers for the levinson durben algorithm
|
|
|
|
float32_t Rfw[impulse_length+order]; // takes the forward predicted audio restauration
|
|
float32_t Rbw[impulse_length+order]; // takes the backward predicted audio restauration
|
|
float32_t Wfw[impulse_length],Wbw[impulse_length]; // taking linear windows for the combination of fwd and bwd
|
|
|
|
float32_t s;
|
|
|
|
static float32_t working_buffer[NR_FFT_SIZE + 2 * order + 2 * PL]; //we need 128 + 26 floats to work on -
|
|
//necessary to watch for impulses as close to the frame boundaries as possible
|
|
|
|
#ifdef debug_alternate_NR // generate test frames to test the noise blanker function
|
|
// using the RIT (0..55) to select the test frame
|
|
// 00 = noise blanker active on orig. audio; threshold factor=3
|
|
// 01 = frame of vocal "a" undistorted
|
|
// 02 .. 05 = frame of vocal "a" with different impulse distortion levels
|
|
// 06 .. 09 = frame of vocal "a" with different impulse distortion levels
|
|
// noise blanker operating!!
|
|
// 5 / 9 is the biggest "click" and it is slightly noticeable in the restored audio (9)
|
|
// 10 = noise blanker active on orig. audio threshold factor=3
|
|
// 11 = sinusoidal signal undistorted
|
|
// 12 ..15 = sinusoidal signal with different impulse distortion levels
|
|
// 16 ..19 = sinusoidal signal with different impulse distortion levels
|
|
// noise blanker operating!!
|
|
// 20 ..50 noise blanker active on orig. audio; threshold factor varying between 3 and 0.26
|
|
|
|
nr_setting = (int)ts.rit_value;
|
|
//*********************************from here just debug impulse / signal generation
|
|
if ((nr_setting > 0) && (nr_setting < 10)) // we use the vocal "a" frame
|
|
{
|
|
for (int i=0; i<128;i++) // not using vocal "a" but the original signal
|
|
{insamp[i]=NR_test_samp[i];}
|
|
|
|
if ((frame_count > 19) && (nr_setting > 1)) // insert a distorting pulse
|
|
{
|
|
dist_level=nr_setting;
|
|
if (dist_level > 5) dist_level=dist_level-4; // distortion level is 1...5
|
|
insamp[4]=insamp[4] + dist_level*3000; // overlaying a short distortion pulse +/-
|
|
insamp[5]=insamp[5] - dist_level*1000;
|
|
}
|
|
|
|
}
|
|
|
|
if ((nr_setting > 10) && (nr_setting < 20)) // we use the sinus frame
|
|
{
|
|
for (int i=0; i<128;i++)
|
|
insamp[i]=NR_test_sinus_samp[i];
|
|
|
|
if ((frame_count > 19) && (nr_setting > 11)) // insert a distorting pulse
|
|
{
|
|
dist_level=nr_setting-10;
|
|
if (dist_level > 5) dist_level=dist_level-4;
|
|
insamp[24]=insamp[24] + dist_level*1000; // overlaying a short distortion pulse +/-
|
|
insamp[25]=insamp[25] + dist_level*500;
|
|
insamp[26]=insamp[26] - dist_level*200; // overlaying a short distortion pulse +/-
|
|
insamp[27]=insamp[27] - dist_level*100;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
frame_count++;
|
|
if (frame_count > 20) frame_count=0;
|
|
|
|
#endif
|
|
|
|
//*****************************end of debug impulse generation
|
|
|
|
memcpy(&working_buffer[2*PL + 2*order],insamp,NR_FFT_SIZE * sizeof(float32_t));// copy incomming samples to the end of our working bufer
|
|
|
|
|
|
// start of test timing zone
|
|
|
|
for (int i=0; i<impulse_length; i++) // generating 2 Windows for the combination of the 2 predictors
|
|
{ // will be a constant window later!
|
|
Wbw[i]=1.0*i/(impulse_length-1);
|
|
Wfw[impulse_length-i-1]=Wbw[i];
|
|
}
|
|
|
|
// calculate the autocorrelation of insamp (moving by max. of #order# samples)
|
|
for(int i=0; i < (order+1); i++)
|
|
{
|
|
// arm_dot_prod_f32(&insamp[0],&insamp[i],Nsam-i,&R[i]); // R is carrying the crosscorrelations
|
|
arm_dot_prod_f32(&working_buffer[order+PL+0],&working_buffer[order+PL+i],Nsam-i,&R[i]); // R is carrying the crosscorrelations
|
|
}
|
|
// end of autocorrelation
|
|
|
|
|
|
|
|
//alternative levinson durben algorithm to calculate the lpc coefficients from the crosscorrelation
|
|
|
|
R[0] = R[0] * (1.0 + 1.0e-9);
|
|
|
|
lpcs[0] = 1; //set lpc 0 to 1
|
|
|
|
for (int i=1; i < order+1; i++)
|
|
lpcs[i]=0; // fill rest of array with zeros - could be done by memfill
|
|
|
|
alfa = R[0];
|
|
|
|
for (int m = 1; m <= order; m++)
|
|
{
|
|
s = 0.0;
|
|
for (int u = 1; u < m; u++)
|
|
s = s + lpcs[u] * R[m-u];
|
|
|
|
k = -(R[m] + s) / alfa;
|
|
|
|
for (int v = 1;v < m; v++)
|
|
any[v] = lpcs[v] + k * lpcs[m-v];
|
|
|
|
for (int w = 1; w < m; w++)
|
|
lpcs[w] = any[w];
|
|
|
|
lpcs[m] = k;
|
|
alfa = alfa * (1 - k * k);
|
|
}
|
|
|
|
// end of levinson durben algorithm
|
|
|
|
for (int o = 0; o < order+1; o++ ) //store the reverse order coefficients separately
|
|
reverse_lpcs[order-o]=lpcs[o]; // for the matched impulse filter
|
|
|
|
arm_fir_init_f32(&LPC,order+1,&reverse_lpcs[0],&firStateF32[0],NR_FFT_SIZE); // we are using the same function as used in freedv
|
|
|
|
//arm_fir_f32(&LPC,insamp,tempsamp,Nsam); //do the inverse filtering to eliminate voice and enhance the impulses
|
|
arm_fir_f32(&LPC,&working_buffer[order+PL],tempsamp,Nsam); //do the inverse filtering to eliminate voice and enhance the impulses
|
|
|
|
arm_fir_init_f32(&LPC,order+1,&lpcs[0],&firStateF32[0],NR_FFT_SIZE); // we are using the same function as used in freedv
|
|
|
|
arm_fir_f32(&LPC,tempsamp,tempsamp,Nsam); // do a matched filtering to detect an impulse in our now voiceless signal
|
|
|
|
|
|
arm_var_f32(tempsamp,NR_FFT_SIZE,&sigma2); //calculate sigma2 of the original signal ? or tempsignal
|
|
|
|
arm_power_f32(lpcs,order,&lpc_power); // calculate the sum of the squares (the "power") of the lpc's
|
|
|
|
// impulse_threshold = (float32_t)ts.nb_setting * 0.5 * sqrtf(sigma2 * lpc_power); //set a detection level (3 is not really a final setting)
|
|
impulse_threshold = (float32_t)(16 - ts.dsp.nb_setting) * 0.5 * sqrtf(sigma2 * lpc_power); //set a detection level (3 is not really a final setting)
|
|
|
|
//if ((nr_setting > 20) && (nr_setting <51))
|
|
// impulse_threshold = impulse_threshold / (0.9 + (nr_setting-20.0)/10); //scaling the threshold by 1 ... 0.26
|
|
|
|
search_pos = order+PL; // lower boundary problem has been solved! - so here we start from 1 or 0?
|
|
//search_pos = 1;
|
|
impulse_count=0;
|
|
|
|
do { //going through the filtered samples to find an impulse larger than the threshold
|
|
|
|
if ((tempsamp[search_pos] > impulse_threshold)||(tempsamp[search_pos] < (-impulse_threshold)))
|
|
{
|
|
impulse_positions[impulse_count]=search_pos - order; // save the impulse positions and correct it by the filter delay
|
|
impulse_count++;
|
|
search_pos+=PL; // set search_pos a bit away, cause we are already repairing this area later
|
|
// and the next impulse should not be that close
|
|
}
|
|
|
|
search_pos++;
|
|
|
|
//} while ((search_pos < NR_FFT_SIZE-boundary_blank) && (impulse_count < 5));// avoid upper boundary
|
|
} while ((search_pos < NR_FFT_SIZE) && (impulse_count < 5));
|
|
|
|
|
|
// from here: reconstruction of the impulse-distorted audio part:
|
|
|
|
// first we form the forward and backward prediction transfer functions from the lpcs
|
|
// that is easy, as they are just the negated coefficients without the leading "1"
|
|
// we can do this in place of the lpcs, as they are not used here anymore and being recalculated in the next frame!
|
|
|
|
arm_negate_f32(&lpcs[1],&lpcs[1],order);
|
|
arm_negate_f32(&reverse_lpcs[0],&reverse_lpcs[0],order);
|
|
|
|
|
|
for (int j=0; j<impulse_count; j++)
|
|
{
|
|
for (int k = 0; k<order; k++) // we have to copy some samples from the original signal as
|
|
{ // basis for the reconstructions - could be done by memcopy
|
|
|
|
//if ((impulse_positions[j]-PL-order+k) < 0)// this solves the prediction problem at the left boundary
|
|
//{
|
|
// Rfw[k]=last_frame_end[impulse_positions[j]+k];//take the sample from the last frame
|
|
//}
|
|
//else
|
|
//{
|
|
//Rfw[k]=insamp[impulse_positions[j]-PL-order+k];//take the sample from this frame as we are away from the boundary
|
|
Rfw[k]=working_buffer[impulse_positions[j]+k];//take the sample from this frame as we are away from the boundary
|
|
//}
|
|
|
|
//Rbw[impulse_length+k]=insamp[impulse_positions[j]+PL+k+1];
|
|
Rbw[impulse_length+k]=working_buffer[order+PL+impulse_positions[j]+PL+k+1];
|
|
|
|
|
|
|
|
} //bis hier alles ok
|
|
|
|
for (int i = 0; i < impulse_length; i++) //now we calculate the forward and backward predictions
|
|
{
|
|
arm_dot_prod_f32(&reverse_lpcs[0],&Rfw[i],order,&Rfw[i+order]);
|
|
arm_dot_prod_f32(&lpcs[1],&Rbw[impulse_length-i],order,&Rbw[impulse_length-i-1]);
|
|
|
|
}
|
|
|
|
arm_mult_f32(&Wfw[0],&Rfw[order],&Rfw[order],impulse_length); // do the windowing, or better: weighing
|
|
arm_mult_f32(&Wbw[0],&Rbw[0],&Rbw[0],impulse_length);
|
|
|
|
|
|
|
|
#ifdef debug_alternate_NR
|
|
// in debug mode do the restoration only in some cases
|
|
if (((ts.rit_value > 0) && (ts.rit_value < 6))||((ts.rit_value > 10) && (ts.rit_value < 16)))
|
|
{
|
|
// just let the distortion pass at setting 1...5 and 11...15
|
|
// arm_add_f32(&Rfw[order],&Rbw[0],&insamp[impulse_positions[j]-PL],impulse_length);
|
|
}
|
|
else
|
|
{
|
|
//finally add the two weighted predictions and insert them into the original signal - thereby eliminating the distortion
|
|
//arm_add_f32(&Rfw[order],&Rbw[0],&insamp[impulse_positions[j]-PL],impulse_length);
|
|
arm_add_f32(&Rfw[order],&Rbw[0],&working_buffer[order+PL+impulse_positions[j]-PL],impulse_length);
|
|
}
|
|
#else
|
|
//finally add the two weighted predictions and insert them into the original signal - thereby eliminating the distortion
|
|
//arm_add_f32(&Rfw[order],&Rbw[0],&insamp[impulse_positions[j]-PL],impulse_length);
|
|
arm_add_f32(&Rfw[order],&Rbw[0],&working_buffer[order+impulse_positions[j]],impulse_length);
|
|
|
|
#endif
|
|
}
|
|
|
|
//for (int p=0; p<(order+PL); p++)
|
|
//{
|
|
// last_frame_end[p]=insamp[NR_FFT_SIZE-1-order-PL+p];// store 13 samples from the current frame to use at the next frame
|
|
//}
|
|
//end of test timing zone
|
|
memcpy(insamp,&working_buffer[order+PL],NR_FFT_SIZE * sizeof(float32_t));// copy the samples of the current frame back to the insamp-buffer for output
|
|
memcpy(working_buffer,&working_buffer[NR_FFT_SIZE],(2*order + 2*PL) * sizeof(float32_t)); // copy
|
|
}
|
|
|
|
|
|
|
|
#endif
|