aboutsummaryrefslogtreecommitdiff
path: root/src/modules/audio_processing/aecm/main/matlab/matlab/AECMobile.m
blob: 2d3e6867dffb25ef2fb4e71bb082e239601ad77e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
function [femicrophone, aecmStructNew, enerNear, enerFar] = AECMobile(fmicrophone, afTheFarEnd, setupStruct, aecmStruct)
global NEARENDFFT;
global F;

aecmStructNew = aecmStruct;

% Magnitude spectrum of near end signal
afmicrophone = abs(fmicrophone);
%afmicrophone = NEARENDFFT(setupStruct.currentBlock,:)'/2^F(setupStruct.currentBlock,end);
% Near end energy level
ener_orig = afmicrophone'*afmicrophone;
if( ener_orig == 0)
    lowlevel = 0.01;
    afmicrophone = lowlevel*ones(size(afmicrophone));
end
%adiff = max(abs(afmicrophone - afTheFarEnd));
%if (adiff > 0)
%    disp([setupStruct.currentBlock adiff])
%end

% Store the near end energy
%aecmStructNew.enerNear(setupStruct.currentBlock) = log(afmicrophone'*afmicrophone);
aecmStructNew.enerNear(setupStruct.currentBlock) = log(sum(afmicrophone));
% Store the far end energy
%aecmStructNew.enerFar(setupStruct.currentBlock) = log(afTheFarEnd'*afTheFarEnd);
aecmStructNew.enerFar(setupStruct.currentBlock) = log(sum(afTheFarEnd));

% Update subbands (We currently use all frequency bins, hence .useSubBand is turned off)
if aecmStructNew.useSubBand
    internalIndex = 1;
    for kk=1:setupStruct.subBandLength+1
        ySubBand(kk) = mean(afmicrophone(internalIndex:internalIndex+setupStruct.numInBand(kk)-1).^aecmStructNew.bandFactor);
        xSubBand(kk) = mean(afTheFarEnd(internalIndex:internalIndex+setupStruct.numInBand(kk)-1).^aecmStructNew.bandFactor);
        internalIndex = internalIndex + setupStruct.numInBand(kk);
    end
else
    ySubBand = afmicrophone.^aecmStructNew.bandFactor;
    xSubBand = afTheFarEnd.^aecmStructNew.bandFactor;
end

% Estimated echo energy
if (aecmStructNew.bandFactor == 1)
    %aecmStructNew.enerEcho(setupStruct.currentBlock) = log((aecmStructNew.H.*xSubBand)'*(aecmStructNew.H.*xSubBand));
    %aecmStructNew.enerEchoStored(setupStruct.currentBlock) = log((aecmStructNew.HStored.*xSubBand)'*(aecmStructNew.HStored.*xSubBand));
    aecmStructNew.enerEcho(setupStruct.currentBlock) = log(sum(aecmStructNew.H.*xSubBand));
    aecmStructNew.enerEchoStored(setupStruct.currentBlock) = log(sum(aecmStructNew.HStored.*xSubBand));
elseif (aecmStructNew.bandFactor == 2)
    aecmStructNew.enerEcho(setupStruct.currentBlock) = log(aecmStructNew.H'*xSubBand);
    aecmStructNew.enerEchoStored(setupStruct.currentBlock) = log(aecmStructNew.HStored'*xSubBand);
end

% Last 100 blocks of data, used for plotting
n100 = max(1,setupStruct.currentBlock-99):setupStruct.currentBlock;
enerError = aecmStructNew.enerNear(n100)-aecmStructNew.enerEcho(n100);
enerErrorStored = aecmStructNew.enerNear(n100)-aecmStructNew.enerEchoStored(n100);

% Store the far end sub band. This is needed if we use LSE instead of NLMS
aecmStructNew.X = [xSubBand aecmStructNew.X(:,1:end-1)];

% Update energy levels, which control the VAD
if ((aecmStructNew.enerFar(setupStruct.currentBlock) < aecmStructNew.energyMin) & (aecmStructNew.enerFar(setupStruct.currentBlock) >= aecmStruct.FAR_ENERGY_MIN))
    aecmStructNew.energyMin = aecmStructNew.enerFar(setupStruct.currentBlock);
    %aecmStructNew.energyMin = max(aecmStructNew.energyMin,12);
    aecmStructNew.energyMin = max(aecmStructNew.energyMin,aecmStruct.FAR_ENERGY_MIN);
    aecmStructNew.energyLevel = (aecmStructNew.energyMax-aecmStructNew.energyMin)*aecmStructNew.energyThres+aecmStructNew.energyMin;
    aecmStructNew.energyLevelMSE = (aecmStructNew.energyMax-aecmStructNew.energyMin)*aecmStructNew.energyThresMSE+aecmStructNew.energyMin;
end
if (aecmStructNew.enerFar(setupStruct.currentBlock) > aecmStructNew.energyMax)
    aecmStructNew.energyMax = aecmStructNew.enerFar(setupStruct.currentBlock);
    aecmStructNew.energyLevel = (aecmStructNew.energyMax-aecmStructNew.energyMin)*aecmStructNew.energyThres+aecmStructNew.energyMin;
    aecmStructNew.energyLevelMSE = (aecmStructNew.energyMax-aecmStructNew.energyMin)*aecmStructNew.energyThresMSE+aecmStructNew.energyMin;
end

% Calculate current energy error in near end (estimated echo vs. near end)
dE = aecmStructNew.enerNear(setupStruct.currentBlock)-aecmStructNew.enerEcho(setupStruct.currentBlock);

%%%%%%%%
% Calculate step size used in LMS algorithm, based on current far end energy and near end energy error (dE)
%%%%%%%%
if setupStruct.stepSize_flag
    [mu, aecmStructNew] = calcStepSize(aecmStructNew.enerFar(setupStruct.currentBlock), dE, aecmStructNew, setupStruct.currentBlock, 1);
else
    mu = 0.25;
end
aecmStructNew.muLog(setupStruct.currentBlock) = mu; % Store the step size

% Estimate Echo Spectral Shape
[U, aecmStructNew.H] = fallerEstimator(ySubBand,aecmStructNew.X,aecmStructNew.H,mu);

%%%%%
% Determine if we should store or restore the channel
%%%%%
if ((setupStruct.currentBlock <= aecmStructNew.convLength) | (~setupStruct.channelUpdate_flag))
    aecmStructNew.HStored = aecmStructNew.H; % Store what you have after startup
elseif ((setupStruct.currentBlock > aecmStructNew.convLength) & (setupStruct.channelUpdate_flag))
    if ((aecmStructNew.enerFar(setupStruct.currentBlock) < aecmStructNew.energyLevelMSE) & (aecmStructNew.enerFar(setupStruct.currentBlock-1) >= aecmStructNew.energyLevelMSE))
        xxx = aecmStructNew.countMseH;
        if (xxx > 20)
            mseStored = mean(abs(aecmStructNew.enerEchoStored(setupStruct.currentBlock-xxx:setupStruct.currentBlock-1)-aecmStructNew.enerNear(setupStruct.currentBlock-xxx:setupStruct.currentBlock-1)));
            mseLatest = mean(abs(aecmStructNew.enerEcho(setupStruct.currentBlock-xxx:setupStruct.currentBlock-1)-aecmStructNew.enerNear(setupStruct.currentBlock-xxx:setupStruct.currentBlock-1)));
            %fprintf('Stored: %4f Latest: %4f\n', mseStored, mseLatest) % Uncomment if you want to display the MSE values
            if ((mseStored < 0.8*mseLatest) & (aecmStructNew.mseHStoredOld < 0.8*aecmStructNew.mseHLatestOld))
                aecmStructNew.H = aecmStructNew.HStored;
                fprintf('Restored H at block %d\n',setupStruct.currentBlock)
            elseif (((0.8*mseStored > mseLatest) & (mseLatest < aecmStructNew.mseHThreshold) & (aecmStructNew.mseHLatestOld < aecmStructNew.mseHThreshold)) | (mseStored == Inf))
                aecmStructNew.HStored = aecmStructNew.H;
                fprintf('Stored new H at block %d\n',setupStruct.currentBlock)
            end
            aecmStructNew.mseHStoredOld = mseStored;
            aecmStructNew.mseHLatestOld = mseLatest;
        end
    elseif ((aecmStructNew.enerFar(setupStruct.currentBlock) >= aecmStructNew.energyLevelMSE) & (aecmStructNew.enerFar(setupStruct.currentBlock-1) < aecmStructNew.energyLevelMSE))
        aecmStructNew.countMseH = 1;
    elseif (aecmStructNew.enerFar(setupStruct.currentBlock) >= aecmStructNew.energyLevelMSE)
        aecmStructNew.countMseH = aecmStructNew.countMseH + 1;
    end
end

%%%%%
% Check delay (calculate the delay offset (if we can))
% The algorithm is not tuned and should be used with care. It runs separately from Bastiaan's algorithm.
%%%%%
yyy = 31; % Correlation buffer length (currently unfortunately hard coded)
dxxx = 25; % Maximum offset (currently unfortunately hard coded)
if (setupStruct.currentBlock > aecmStructNew.convLength)
    if (aecmStructNew.enerFar(setupStruct.currentBlock-(yyy+2*dxxx-1):setupStruct.currentBlock) > aecmStructNew.energyLevelMSE)
        for xxx = -dxxx:dxxx
            aecmStructNew.delayLatestS(xxx+dxxx+1) = sum(sign(aecmStructNew.enerEcho(setupStruct.currentBlock-(yyy+dxxx-xxx)+1:setupStruct.currentBlock+xxx-dxxx)-mean(aecmStructNew.enerEcho(setupStruct.currentBlock-(yyy++dxxx-xxx)+1:setupStruct.currentBlock+xxx-dxxx))).*sign(aecmStructNew.enerNear(setupStruct.currentBlock-yyy-dxxx+1:setupStruct.currentBlock-dxxx)-mean(aecmStructNew.enerNear(setupStruct.currentBlock-yyy-dxxx+1:setupStruct.currentBlock-dxxx))));
        end
        aecmStructNew.newDelayCurve = 1;
    end
end
if ((setupStruct.currentBlock > 2*aecmStructNew.convLength) & ~rem(setupStruct.currentBlock,yyy*2) & aecmStructNew.newDelayCurve)
    [maxV,maxP] = max(aecmStructNew.delayLatestS);
    if ((maxP > 2) & (maxP < 2*dxxx))
        maxVLeft = aecmStructNew.delayLatestS(max(1,maxP-4));
        maxVRight = aecmStructNew.delayLatestS(min(2*dxxx+1,maxP+4));
        %fprintf('Max %d, Left %d, Right %d\n',maxV,maxVLeft,maxVRight) % Uncomment if you want to see max value
        if ((maxV > 24) & (maxVLeft < maxV - 10)  & (maxVRight < maxV - 10))
            aecmStructNew.feedbackDelay = maxP-dxxx-1;
            aecmStructNew.newDelayCurve = 0;
            aecmStructNew.feedbackDelayUpdate = 1;
            fprintf('Feedback Update at block %d\n',setupStruct.currentBlock)
        end
    end
end
% End of "Check delay"
%%%%%%%%

%%%%%
% Calculate suppression gain, based on far end energy and near end energy error (dE)
if (setupStruct.supGain_flag)
    [gamma_echo, aecmStructNew.cntIn, aecmStructNew.cntOut] = calcFilterGain(aecmStructNew.enerFar(setupStruct.currentBlock), dE, aecmStructNew, setupStruct.currentBlock, aecmStructNew.convLength, aecmStructNew.cntIn, aecmStructNew.cntOut);
else
    gamma_echo = 1;
end
aecmStructNew.gammaLog(setupStruct.currentBlock) = gamma_echo; % Store the gain
gamma_use = gamma_echo;

% Use the stored channel
U = aecmStructNew.HStored.*xSubBand;

% compute Wiener filter and suppressor function
Iy = find(ySubBand);
subBandFilter = zeros(size(ySubBand));
if (aecmStructNew.bandFactor == 2)
    subBandFilter(Iy) = (1 - gamma_use*sqrt(U(Iy)./ySubBand(Iy))); % For Faller
else
    subBandFilter(Iy) = (1 - gamma_use*(U(Iy)./ySubBand(Iy))); % For COV
end
ix0 = find(subBandFilter < 0);   % bounding trick 1
subBandFilter(ix0) = 0;
ix0 = find(subBandFilter > 1);   % bounding trick 1
subBandFilter(ix0) = 1;

% Interpolate back to normal frequency bins if we use sub bands
if aecmStructNew.useSubBand
    thefilter = interp1(setupStruct.centerFreq,subBandFilter,linspace(0,setupStruct.samplingfreq/2,setupStruct.hsupport1)','nearest');
    testfilter = interp1(setupStruct.centerFreq,subBandFilter,linspace(0,setupStruct.samplingfreq/2,1000),'nearest');
    thefilter(end) = subBandFilter(end);
    
    internalIndex = 1;
    for kk=1:setupStruct.subBandLength+1
        internalIndex:internalIndex+setupStruct.numInBand(kk)-1;
        thefilter(internalIndex:internalIndex+setupStruct.numInBand(kk)-1) = subBandFilter(kk);
        internalIndex = internalIndex + setupStruct.numInBand(kk);
    end
else
    thefilter = subBandFilter;
    testfilter = subBandFilter;
end

% Bound the filter
ix0 = find(thefilter < setupStruct.de_echo_bound);   % bounding trick 1
thefilter(ix0) = setupStruct.de_echo_bound;     % bounding trick 2
ix0 = find(thefilter > 1);   % bounding in reasonable range
thefilter(ix0) = 1;

%%%%
% NLP
%%%%
thefmean = mean(thefilter(8:16));
if (thefmean < 1)
    disp('');
end
aecmStructNew.runningfmean = setupStruct.nl_alpha*aecmStructNew.runningfmean + (1-setupStruct.nl_alpha)*thefmean;
slope0 = 1.0/(1.0 - setupStruct.nlSeverity); %
thegain = max(0.0, min(1.0, slope0*(aecmStructNew.runningfmean - setupStruct.nlSeverity)));
if ~setupStruct.nlp_flag
    thegain = 1;
end
% END NONLINEARITY
thefilter = thegain*thefilter;

%%%%
% The suppression
%%%%
femicrophone = fmicrophone .* thefilter;
% Store the output energy (used for plotting)
%aecmStructNew.enerOut(setupStruct.currentBlock) = log(abs(femicrophone)'*abs(femicrophone));
aecmStructNew.enerOut(setupStruct.currentBlock) = log(sum(abs(femicrophone)));

if aecmStructNew.plotIt
    figure(13)
    subplot(311)
    %plot(n100,enerFar(n100),'b-',n100,enerNear(n100),'k--',n100,enerEcho(n100),'r-',[n100(1) n100(end)],[1 1]*vadThNew,'b:',[n100(1) n100(end)],[1 1]*((energyMax-energyMin)/4+energyMin),'r-.',[n100(1) n100(end)],[1 1]*vadNearThNew,'g:',[n100(1) n100(end)],[1 1]*energyMax,'r-.',[n100(1) n100(end)],[1 1]*energyMin,'r-.','LineWidth',2)
    plot(n100,aecmStructNew.enerFar(n100),'b-',n100,aecmStructNew.enerNear(n100),'k--',n100,aecmStructNew.enerOut(n100),'r-.',n100,aecmStructNew.enerEcho(n100),'r-',n100,aecmStructNew.enerEchoStored(n100),'c-',[n100(1) n100(end)],[1 1]*((aecmStructNew.energyMax-aecmStructNew.energyMin)/4+aecmStructNew.energyMin),'g-.',[n100(1) n100(end)],[1 1]*aecmStructNew.energyMax,'g-.',[n100(1) n100(end)],[1 1]*aecmStructNew.energyMin,'g-.','LineWidth',2)
    %title(['Frame ',int2str(i),' av ',int2str(setupStruct.updateno),' State = ',int2str(speechState),' \mu = ',num2str(mu)])
    title(['\gamma = ',num2str(gamma_echo),' \mu = ',num2str(mu)])
    subplot(312)
    %plot(n100,enerError,'b-',[n100(1) n100(end)],[1 1]*vadNearTh,'r:',[n100(1) n100(end)],[-1.5 -1.5]*vadNearTh,'r:','LineWidth',2)
    %plot(n100,enerError,'b-',[n100(1) n100(end)],[1 1],'r:',[n100(1) n100(end)],[-2 -2],'r:','LineWidth',2)
    plot(n100,enerError,'b-',n100,enerErrorStored,'c-',[n100(1) n100(end)],[1 1]*aecmStructNew.varMean,'k--',[n100(1) n100(end)],[1 1],'r:',[n100(1) n100(end)],[-2 -2],'r:','LineWidth',2)
    % Plot mu
    %plot(n100,log2(aecmStructNew.muLog(n100)),'b-','LineWidth',2)
    %plot(n100,log2(aecmStructNew.HGain(n100)),'b-',[n100(1) n100(end)],[1 1]*log2(sum(aecmStructNew.HStored)),'r:','LineWidth',2)
    title(['Block ',int2str(setupStruct.currentBlock),' av ',int2str(setupStruct.updateno)])
    subplot(313)
    %plot(n100,enerVar(n100),'b-',[n100(1) n100(end)],[1 1],'r:',[n100(1) n100(end)],[-2 -2],'r:','LineWidth',2)
    %plot(n100,enerVar(n100),'b-','LineWidth',2)
    % Plot correlation curve

    %plot(-25:25,aecmStructNew.delayStored/max(aecmStructNew.delayStored),'c-',-25:25,aecmStructNew.delayLatest/max(aecmStructNew.delayLatest),'r-',-25:25,(max(aecmStructNew.delayStoredS)-aecmStructNew.delayStoredS)/(max(aecmStructNew.delayStoredS)-min(aecmStructNew.delayStoredS)),'c:',-25:25,(max(aecmStructNew.delayLatestS)-aecmStructNew.delayLatestS)/(max(aecmStructNew.delayLatestS)-min(aecmStructNew.delayLatestS)),'r:','LineWidth',2)
    %plot(-25:25,aecmStructNew.delayStored,'c-',-25:25,aecmStructNew.delayLatest,'r-',-25:25,(max(aecmStructNew.delayStoredS)-aecmStructNew.delayStoredS)/(max(aecmStructNew.delayStoredS)-min(aecmStructNew.delayStoredS)),'c:',-25:25,(max(aecmStructNew.delayLatestS)-aecmStructNew.delayLatestS)/(max(aecmStructNew.delayLatestS)-min(aecmStructNew.delayLatestS)),'r:','LineWidth',2)
    %plot(-25:25,aecmStructNew.delayLatest,'r-',-25:25,(50-aecmStructNew.delayLatestS)/100,'r:','LineWidth',2)
    plot(-25:25,aecmStructNew.delayLatestS,'r:','LineWidth',2)
    %plot(-25:25,aecmStructNew.delayStored,'c-',-25:25,aecmStructNew.delayLatest,'r-','LineWidth',2)
    plot(0:32,aecmStruct.HStored,'bo-','LineWidth',2)
    %title(['\gamma | In = ',int2str(aecmStructNew.muStruct.countInInterval),' | Out High = ',int2str(aecmStructNew.muStruct.countOutHighInterval),' | Out Low = ',int2str(aecmStructNew.muStruct.countOutLowInterval)])
    pause(1)
    %if ((setupStruct.currentBlock == 860) | (setupStruct.currentBlock == 420) | (setupStruct.currentBlock == 960))
    if 0%(setupStruct.currentBlock == 960)
        figure(60)
        plot(n100,aecmStructNew.enerNear(n100),'k--',n100,aecmStructNew.enerEcho(n100),'k:','LineWidth',2)
        legend('Near End','Estimated Echo')
        title('Signal Energy witH offset compensation')
        figure(61)
        subplot(211)
        stem(sign(aecmStructNew.enerNear(n100)-mean(aecmStructNew.enerNear(n100))))
        title('Near End Energy Pattern (around mean value)')
        subplot(212)
        stem(sign(aecmStructNew.enerEcho(n100)-mean(aecmStructNew.enerEcho(n100))))
        title('Estimated Echo Energy Pattern (around mean value)')
        pause
    end
    drawnow%,pause
elseif ~rem(setupStruct.currentBlock,100)
    fprintf('Block %d of %d\n',setupStruct.currentBlock,setupStruct.updateno)
end