Ich benutze das Shorthand- Fenster für die "Fensterfunktion".
Mit Audio klingt jede Verarbeitung, die so etwas wie ein Pre-Ringing oder ein Pre-Echo erzeugt, wie eine MP3-Datei mit niedriger Bitrate. Dies geschieht, wenn die lokalisierte Energie eines Transienten oder eines Impulses zeitlich rückwärts gestreut wird, beispielsweise durch Modifikation der Spektraldaten in überlappten Transformationen wie der überlappten modifizierten diskreten Cosinustransformation (MDCT). Bei einer solchen Verarbeitung wird Audio durch überlappende Analysefenster gefenstert , transformiert, im Frequenzbereich verarbeitet (wie auf eine kleinere Bitrate komprimierte Daten), erneut mit einem Synthesefenster gefenstert und wieder zusammenaddiert. Das Produkt des Analyse- und Synthesefensters muss so sein, dass sich die überlappenden Fenster zu einer Einheit summieren.
Traditionell waren die verwendeten Fensterfunktionen symmetrisch und ihre Breite war ein Kompromiss zwischen Frequenzselektivität (langes Fenster) und Vermeidung von Zeitbereichsartefakten (kurzes Fenster). Je breiter das Fenster, desto später kann die Verarbeitung das Signal verbreiten. Eine neuere Lösung ist die Verwendung eines asymmetrischen Fensters. Die beiden verwendeten Fenster können Spiegelbilder voneinander sein. Das Analysefenster fällt schnell von der Spitze auf Null ab, so dass Impulse nicht viel im Voraus "erkannt" werden, und das Syntheisfenster steigt schnell von Null auf die Spitze an, so dass sich die Auswirkungen einer Verarbeitung nicht viel zeitlich nach hinten ausbreiten. Ein weiterer Vorteil ist die geringe Latenz. Die asymmetrischen Fenster können eine gute Frequenzselektivität aufweisen und symmetrische Fenster mit variabler Größe bei der Audiokomprimierung ersetzen, wie eine Art Allheilmittel. SehenM. Schnell, M. Schmidt, M. Jander, T. Albert, R. Geiger, V. Ruoppila, P. Ekstrand, M. Lutzky, B. Grill, „MPEG-4 Enhanced Low Delay AAC - ein neuer Standard für High Qualitätskommunikation “ , 125. AES Convention, San Francisco, CA, USA, Preprint 7503, Okt. 2008 und ein weiteres Konferenzpapier, in dem sie auch das Ausmaß der Fourier-Transformation ihres Fensters zeigen: Schnell, M., et al. 2007. Verbesserte MPEG-4-AAC mit geringer Verzögerung - Kommunikation mit niedriger Bitrate und hoher Qualität. In 122. AES-Konvention .
Abbildung 1. Verwendungsbeispiele für asymmetrische Fenster in der überlappenden Analyse-Verarbeitung-Synthese. Das Produkt (schwarz gestrichelt) des Analysefensters (blau) und des Synthesefensters (gelblich-orange) summiert sich mit dem Fenster des vorherigen Rahmens (grau gestrichelt) zu einer Einheit. Weitere Einschränkungen sind erforderlich, um eine perfekte Rekonstruktion bei der Verwendung von MDCT zu gewährleisten.
Diskrete Fouriertransformation (DFT, FFT) könnte anstelle von MDCT verwendet werden, liefert jedoch in solchen Zusammenhängen redundante Spektraldaten. Im Vergleich zu DFT liefert MDCT nur die Hälfte der Spektraldaten und ermöglicht dennoch eine perfekte Rekonstruktion, wenn geeignete Fenster ausgewählt werden.
Hier ist mein eigenes asymmetrisches Fensterdesign (Abb. 2), das für überlappte Analyse-Verarbeitung-Synthese mit DFT, aber nicht mit MDCT geeignet ist und bei dem keine perfekte Rekonstruktion möglich ist. Das Fenster versucht, das Produkt der mittleren quadratischen Zeit- und Frequenzbandbreiten (ähnlich dem begrenzten Gaußschen Fenster ) zu minimieren, während einige potenziell nützliche Zeitdomäneneigenschaften beibehalten werden: nichtnegativ, unimodal mit dem Peak zum "Zeitpunkt Null", um den die Analyse und Synthese erfolgt Fenster sind Spiegelbilder voneinander, Funktion und Kontinuität der ersten Ableitung, Mittelwert Null, wenn das Quadrat der Fensterfunktion als eine nicht normalisierte Wahrscheinlichkeitsdichtefunktion interpretiert wird. Das Fenster wurde mit Differential Evolution optimiert .
Abbildung 2. Links: Ein asymmetrisches Analysefenster, das sich zusammen mit seinem zeitlich umgekehrten Gegenstücksynthesefenster für die überlappende Analyseverarbeitung eignet. Rechts: Cosine-Fenster mit der gleichen Latenz wie das asymmetrische Fenster
Abbildung 3. Größe der Fourier-Transformationen des Kosinusfensters (blau) und des asymmetrischen Fensters (orange) in Abbildung 2. Das asymmetrische Fenster zeigt eine bessere Frequenzselektivität.
Hier ist der Octave-Quellcode für die Diagramme und für das asymmetrische Fenster. Der Plotcode stammt von Wikimedia Commons . Unter Linux empfehle ich die Installation gnuplot
, epstool
, pstoedit
, transfig
erste und librsvg2-bin
für die Anzeige verwendet display
.
pkg load signal
graphics_toolkit gnuplot
set (0, "defaultaxesfontname", "sans-serif")
set (0, "defaultaxesfontsize", 12)
set (0, "defaultaxeslinewidth", 1)
function plotWindow (w, wname, wfilename = "", wspecifier = "", wfilespecifier = "")
M = 32; % Fourier transform size as multiple of window length
Q = 512; % Number of samples in time domain plot
P = 40; % Maximum bin index drawn
dr = 130; % Maximum attenuation (dB) drawn in frequency domain plot
N = length(w);
B = N*sum(w.^2)/sum(w)^2 % noise bandwidth (bins)
k = [0 : 1/Q : 1];
w2 = interp1 ([0 : 1/(N-1) : 1], w, k);
if (M/N < Q)
Q = M/N;
endif
figure('position', [1 1 1200 600])
subplot(1,2,1)
area(k,w2,'FaceColor', [0 0.4 0.6], 'edgecolor', [0 0 0], 'linewidth', 1)
if (min(w) >= -0.01)
ylim([0 1.05])
set(gca,'YTick', [0 : 0.1 : 1])
else
ylim([-1 5])
set(gca,'YTick', [-1 : 1 : 5])
endif
ylabel('amplitude')
set(gca,'XTick', [0 : 1/8 : 1])
set(gca,'XTickLabel',[' 0'; ' '; ' '; ' '; ' '; ' '; ' '; ' '; 'N-1'])
grid('on')
set(gca,'gridlinestyle','-')
xlabel('samples')
if (strcmp (wspecifier, ""))
title(cstrcat(wname,' window'), 'interpreter', 'none')
else
title(cstrcat(wname,' window (', wspecifier, ')'), 'interpreter', 'none')
endif
set(gca,'Position',[0.094 0.17 0.38 0.71])
H = abs(fft([w zeros(1,(M-1)*N)]));
H = fftshift(H);
H = H/max(H);
H = 20*log10(H);
H = max(-dr,H);
k = ([1:M*N]-1-M*N/2)/M;
k2 = [-P : 1/M : P];
H2 = interp1 (k, H, k2);
subplot(1,2,2)
set(gca,'FontSize',28)
h = stem(k2,H2,'-');
set(h,'BaseValue',-dr)
xlim([-P P])
ylim([-dr 6])
set(gca,'YTick', [0 : -10 : -dr])
set(findobj('Type','line'),'Marker','none','Color',[0.8710 0.49 0])
grid('on')
set(findobj('Type','gridline'),'Color',[.871 .49 0])
set(gca,'gridlinestyle','-')
ylabel('decibels')
xlabel('bins')
title('Fourier transform')
set(gca,'Position',[0.595 0.17 0.385 0.71])
if (strcmp (wfilename, ""))
wfilename = wname;
endif
if (strcmp (wfilespecifier, ""))
wfilespecifier = wspecifier;
endif
if (strcmp (wfilespecifier, ""))
savetoname = cstrcat('Window function and frequency response - ', wfilename, '.svg');
else
savetoname = cstrcat('Window function and frequency response - ', wfilename, ' (', wfilespecifier, ').svg');
endif
print(savetoname, '-dsvg', '-S1200,600')
close
endfunction
N=2^17; % Window length, B is equal for Triangular and Bartlett from 2^17
k=0:N-1;
w = -cos(2*pi*k/(N-1));
w .*= w > 0;
plotWindow(w, "Cosine")
freqData = [0.66697133904805994131, -0.20556692772918355727, 0.49267389481655493588, -0.25062332863369246594, -0.42388422228212319087, 0.42317609537724842905, -0.03930334287740060856, -0.11936153294075849129, 0.30201210285940127687, -0.15541616804857899536, -0.16208119255594669039, 0.12843871362286504723, -0.04470810646117385351, -0.00521885027256757845, 0.07185811583185619522, -0.02835116723496184862, -0.01393644785822748498, 0.00780746224568363342, -0.00748496824751256583, 0.00119325723511989282, 0.00194602547595042175];
freqData(1) /= 2;
scale = freqData(1) + sum(freqData.*not(mod(1:length(freqData), 2)));
freqData /= scale;
w = freqData(1)*ones(1, N);
for bin = 1:(length(freqData)/2)
w += freqData(bin*2)*cos(2*pi*bin*((1:N)-1)/N);
w += freqData(bin*2+1)*sin(2*pi*bin*((1:N)-1)/N);
endfor
w(N/4+1:N/2+1) = 0;
w(N/8+2:N/4) = (1 - w(N/8:-1:2).*w(7*N/8+2:N))./w(7*N/8:-1:6*N/8+2);
w = shift(w, -N/2);
plotWindow(w, "Asymmetrical");
Möglicherweise möchten Sie nur jedes zweite Beispiel des Fensters verwenden, da es bei Null beginnt und endet. Der folgende C ++ - Code erledigt das für Sie, damit Sie keine Null-Samples erhalten, außer in einem Viertel des Fensters, das überall Null ist. Für das Analysefenster ist dies das erste Quartal und für das Synthesefenster das letzte Quartal. Die zweite Hälfte des Analysefensters sollte zur Berechnung ihres Produkts an der ersten Hälfte des Synthesefensters ausgerichtet sein. Der Code testet auch den Mittelwert des Fensters (als Wahrscheinlichkeitsdichtefunktion) und zeigt die Ebenheit der überlappenden Rekonstruktion.
#include <stdio.h>
#include <math.h>
int main() {
const int windowSize = 400;
double *analysisWindow = new double[windowSize];
double *synthesisWindow = new double[windowSize];
for (int k = 0; k < windowSize/4; k++) {
analysisWindow[k] = 0;
}
for (int k = windowSize/4; k < windowSize*7/8; k++) {
double x = 2 * M_PI * ((k+0.5)/windowSize - 1.75);
analysisWindow[k] = 2.57392230162633461887-1.58661480271141974718*cos(x)+3.80257516644523141380*sin(x)
-1.93437090055110760822*cos(2*x)-3.27163999159752183488*sin(2*x)+3.26617449847621266201*cos(3*x)
-0.30335261753524439543*sin(3*x)-0.92126091064427817479*cos(4*x)+2.33100177294084742741*sin(4*x)
-1.19953922321306438725*cos(5*x)-1.25098147932225423062*sin(5*x)+0.99132076607048635886*cos(6*x)
-0.34506787787355830410*sin(6*x)-0.04028033685700077582*cos(7*x)+0.55461815542612269425*sin(7*x)
-0.21882110175036428856*cos(8*x)-0.10756484378756643594*sin(8*x)+0.06025986430527170007*cos(9*x)
-0.05777077835678736534*sin(9*x)+0.00920984524892982936*cos(10*x)+0.01501989089735343216*sin(10*x);
}
for (int k = 0; k < windowSize/8; k++) {
analysisWindow[windowSize-1-k] = (1 - analysisWindow[windowSize*3/4-1-k]*analysisWindow[windowSize*3/4+k])/analysisWindow[windowSize/2+k];
}
printf("Analysis window:\n");
for (int k = 0; k < windowSize; k++) {
printf("%d\t%.10f\n", k, analysisWindow[k]);
}
double accu, accu2;
for (int k = 0; k < windowSize; k++) {
accu += k*analysisWindow[k]*analysisWindow[k];
accu2 += analysisWindow[k]*analysisWindow[k];
}
for (int k = 0; k < windowSize; k++) {
synthesisWindow[k] = analysisWindow[windowSize-1-k];
}
printf("\nSynthesis window:\n");
for (int k = 0; k < windowSize; k++) {
printf("%d\t%.10f\n", k, synthesisWindow[k]);
}
printf("Mean of square of analysis window as probability density function:\n%f", accu/accu2);
printf("\nProduct of analysis and synthesis windows:\n");
for (int k = 0; k < windowSize/2; k++) {
printf("%d\t%.10f\n", k, analysisWindow[windowSize/2+k]*synthesisWindow[k]);
}
printf("\nSum of overlapping products of windows:\n");
for (int k = 0; k < windowSize/4; k++) {
printf("%d\t%.10f\n", k, analysisWindow[windowSize/2+k]*synthesisWindow[k]+analysisWindow[windowSize/2+k+windowSize/4]*synthesisWindow[k+windowSize/4]);
}
delete[] analysisWindow;
delete[] synthesisWindow;
}
Und der Quellcode für die Optimierungskostenfunktion, die mit Kiss FFT und einer Optimierungsbibliothek verwendet werden soll :
class WinProblem : public Opti::Problem {
private:
int numParams;
double *min;
double *max;
kiss_fft_scalar *timeData;
kiss_fft_cpx *freqData;
int smallSize;
int bigSize;
kiss_fftr_cfg smallFFTR;
kiss_fftr_cfg smallIFFTR;
kiss_fftr_cfg bigFFTR;
kiss_fftr_cfg bigIFFTR;
public:
// numParams must be odd
WinProblem(int numParams, int smallSize, int bigSize, double* candidate = NULL) : numParams(numParams), smallSize(smallSize), bigSize(bigSize) {
min = new double[numParams];
max = new double[numParams];
if (candidate != NULL) {
for (int i = 0; i < numParams; i++) {
min[i] = candidate[i]-fabs(candidate[i])*(1.0/65536);
max[i] = candidate[i]+fabs(candidate[i])*(1.0/65536);
}
} else {
for (int i = 0; i < numParams; i++) {
min[i] = -1;
max[i] = 1;
}
}
timeData = new kiss_fft_scalar[bigSize];
freqData = new kiss_fft_cpx[bigSize/2+1];
smallFFTR = kiss_fftr_alloc(smallSize, 0, NULL, NULL);
smallIFFTR = kiss_fftr_alloc(smallSize, 1, NULL, NULL);
bigFFTR = kiss_fftr_alloc(bigSize, 0, NULL, NULL);
bigIFFTR = kiss_fftr_alloc(bigSize, 1, NULL, NULL);
}
double *getMin() {
return min;
}
double *getMax() {
return max;
}
// ___ __ 1
// | \ | | | | | | | / |
// | \ | | | | | | | / |
// | \_ | | | | | | | / |
// | \|__ | | | | | | /| |
// | | -----|_______|___ | | | | / | |
// | | | | ----| | | |/ | |
// --------------------------------x-----------------------x---|---- 0
// 0 1/8 2/8 3/8 4/8 5/8 6/8 7/8 15/16
// |-------------------------------| |-------|
// zeroStarts winStarts
//
// f(x) = 0 if 4/8 < x < 7/8
// f(-x)f(x) + f(-x+1/8)f(x-1/8) = 1 if 0 < x < 1/8
double costFunction(double *params, double compare, int print) {
double penalty = 0;
double accu = params[0]/2;
for (int i = 1; i < numParams; i += 2) {
accu += params[i];
}
if (print) {
printf("%.20f", params[0]/2/accu);
for (int i = 1; i < numParams; i += 2) {
printf("+%.20fcos(%d pi x)", params[i]/accu, (i+1)/2);
printf("+%.20fsin(%d pi x)", params[i+1]/accu, (i+1)/2);
}
printf("\n");
}
if (accu != 0) {
for (int i = 0; i < numParams; i++) {
params[i] /= accu;
}
}
const int zeroStarts = 4; // Normally 4
const int winStarts = 2; // Normally 1
int i = 0;
int j = 0;
freqData[j].r = params[i++];
freqData[j++].i = 0;
for (; i < numParams;) {
freqData[j].r = params[i++];
freqData[j++].i = params[i++];
}
for (; j <= smallSize/2;) {
freqData[j].r = 0;
freqData[j++].i = 0;
}
kiss_fftri(smallIFFTR, freqData, timeData);
double scale = 1.0/timeData[0];
double tilt = 0;
double tilt2 = 0;
for (int i = 2; i < numParams; i += 2) {
if ((i/2)%2) {
tilt2 += (i/2)*params[i]*scale;
} else {
tilt2 -= (i/2)*params[i]*scale;
}
tilt += (i/2)*params[i]*scale;
}
penalty += fabs(tilt);
penalty += fabs(tilt2);
double accu2 = 0;
for (int i = 0; i < smallSize; i++) {
timeData[i] *= scale;
}
penalty += fabs(timeData[zeroStarts*smallSize/8]);
penalty += fabs(timeData[winStarts*smallSize/16]*timeData[smallSize-winStarts*smallSize/16]-0.5);
for (int i = 1; i < winStarts*smallSize/16; i++) {
// Last 16th
timeData[bigSize-winStarts*smallSize/16+i] = timeData[smallSize-winStarts*smallSize/16+i];
accu2 += timeData[bigSize-winStarts*smallSize/16+i]*timeData[bigSize-winStarts*smallSize/16+i];
}
// f(-1/8+i)*f(1/8-i) + f(i)*f(-i) = 1
// => f(-1/8+i) = (1 - f(i)*f(-i))/f(1/8-i)
// => f(-1/16) = (1 - f(1/16)*f(-1/16))/f(1/16)
// = 1/(2 f(1/16))
for (int i = 1; i < winStarts*smallSize/16; i++) {
// 2nd last 16th
timeData[bigSize-winStarts*smallSize/8+i] = (1 - timeData[i]*timeData[bigSize-i])/timeData[winStarts*smallSize/8-i];
accu2 += timeData[bigSize-winStarts*smallSize/8+i]*timeData[bigSize-winStarts*smallSize/8+i];
}
// Between 2nd last and last 16th
timeData[bigSize-winStarts*smallSize/16] = 1/(2*timeData[winStarts*smallSize/16]);
accu2 += timeData[bigSize-winStarts*smallSize/16]*timeData[bigSize-winStarts*smallSize/16];
for (int i = zeroStarts*smallSize/8; i <= bigSize-winStarts*smallSize/8; i++) {
timeData[i] = 0;
}
for (int i = 0; i < zeroStarts*smallSize/8; i++) {
accu2 += timeData[i]*timeData[i];
}
if (print > 1) {
printf("\n");
for (int x = 0; x < bigSize; x++) {
printf("%d,%f\n", x, timeData[x]);
}
}
scale = 1/sqrt(accu2);
if (print) {
printf("sqrt(accu2) = %f\n", sqrt(accu2));
}
double tSpread = 0;
timeData[0] *= scale;
double tMean = 0;
for (int i = 1; i <= zeroStarts*smallSize/8; i++) {
timeData[i] *= scale;
// tSpread += ((double)i)*((double)i)*(timeData[i]*timeData[i]);
double x_0 = timeData[i-1]*timeData[i-1];
double x_1 = timeData[i]*timeData[i];
tSpread += ((double)i)*((double)i)*(x_0 + x_1)*0.5 - ((double)i)*(2.0/3*x_0 + 1.0/3*x_1) + 0.25*x_0 + 1.0/12*x_1;
double slope = timeData[i]-timeData[i-1];
if (slope > 0) {
penalty += slope+1;
}
tMean += x_1*i;
if (timeData[i] < 0) {
penalty -= timeData[i];
}
}
double x_0 = timeData[0]*timeData[0];
for (int i = 1; i <= winStarts*smallSize/8; i++) {
timeData[bigSize-i] *= scale;
double x_1 = timeData[bigSize-i]*timeData[bigSize-i];
tSpread += ((double)i)*((double)i)*(x_0 + x_1)*0.5 - ((double)i)*(2.0/3*x_0 + 1.0/3*x_1) + 0.25*x_0 + 1.0/12*x_1;
x_0 = x_1;
tMean += x_1*(-i);
}
tMean /= smallSize;
penalty += fabs(tMean);
if (tMean > 0) {
penalty += 1;
}
tSpread /= ((double)smallSize)*((double)smallSize);
if (print) {
printf("tSpread = %f\n", tSpread);
}
kiss_fftr(bigFFTR, timeData, freqData);
double fSpread = 0;
x_0 = freqData[0].r*freqData[0].r;
for (int i = 1; i <= bigSize/2; i++) {
double x_1 = freqData[i].r*freqData[i].r+freqData[i].i*freqData[i].i;
fSpread += ((double)i)*((double)i)*(x_0 + x_1)*0.5 - ((double)i)*(2.0/3*x_0 + 1.0/3*x_1) + 0.25*x_0 + 1.0/12*x_1;
x_0 = x_1;
}
if (print > 1) {
for (int i = 0; i <= bigSize/2; i++) {
printf("%d,%f,%f\n", i, freqData[i].r, freqData[i].i);
}
}
fSpread /= bigSize; // Includes kiss_fft scaling
if (print) {
printf("fSpread = %f\n", fSpread);
printf("%f,%f,%f\n", tSpread, fSpread, tSpread*fSpread);
}
return tSpread*fSpread + penalty;
}
double costFunction(double *params, double compare) {
return costFunction(params, compare, false);
}
int getNumDimensions() {
return numParams;
}
~WinProblem() {
delete[] min;
delete[] max;
delete[] timeData;
delete[] freqData;
KISS_FFT_FREE(smallFFTR);
KISS_FFT_FREE(smallIFFTR);
KISS_FFT_FREE(bigFFTR);
KISS_FFT_FREE(bigIFFTR);
}
};