/* * Copyright (C) 2011 in-somnia * * This file is part of JAAD. * * JAAD is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * JAAD is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General * Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. * If not, see <http://www.gnu.org/licenses/>. */ package net.sourceforge.jaad.aac.sbr2; import java.util.Arrays; /* * HFAdjustment accoding to 4.6.18.7: Xhigh -> Y * * process() calls submethods in following order: * * map() maps dequantized data to eMapped, qMapped, sMapped * estimateEnvelopes() calculates eCurr from Xhigh * calculateGain() calculates Qm, Sm, gain * assembleSignals() assembles everything to Y */ class HFAdjuster implements SBRConstants, NoiseTable { private static final float[] LIMITER_GAINS = {0.70795f, 1.0f, 1.41254f, 10000000000f}; private static final float EPSILON = 1.0f; private static final float EPSILON_0 = 1E-12f; private static final double MAX_BOOST = 1.584893192; private static final double[] SMOOTHING_FACTORS = { 0.33333333333333, 0.30150283239582, 0.21816949906249, 0.11516383427084, 0.03183050093751 }; private static final int[][] PHI = { {1, 0, -1, 0}, {0, 1, 0, -1} }; private static final int MAX_GAIN = 100000; private static class Parameter { //helper class containing arrays calculated by and passed to different methods float[][] eMapped, qMapped; boolean[][] sIndexMapped, sMapped; float[][] Qm, Sm, Glim; } public static void process(SBRHeader header, FrequencyTables tables, ChannelData cd, float[][][] Xhigh, float[][][] Y) { final Parameter p = map(tables, cd); final float[][] eCurr = estimateEnvelopes(header, tables, cd, Xhigh); calculateGain(header, tables, cd, p, eCurr); assembleSignals(header, tables, cd, p, Xhigh, Y); } //mapping of dequantized values (4.6.18.7.2) private static Parameter map(FrequencyTables tables, ChannelData cd) { //parameter from FrequencyTables final int kx = tables.getKx(false); final int[] noiseTable = tables.getNoiseTable(); final int[] fHigh = tables.getFrequencyTable(HIGH); final int nHigh = tables.getN(HIGH); final int M = tables.getM(false); final int nq = tables.getNq(); //parameter from ChannelData final int le = cd.getEnvCount(); final int lq = cd.getNoiseCount(); final int[] freqRes = cd.getFrequencyResolutions(); final int la = cd.getLa(false); //input and output arrays final float[][] eOrig = cd.getEnvelopeScalefactors(); final float[][] eMapped = new float[le][M]; final float[][] qOrig = cd.getNoiseFloorData(); final float[][] qMapped = new float[le][M]; final boolean[] sinusoidals = cd.getSinusoidals(); final boolean[] sIndexMappedPrev = cd.getSIndexMappedPrevious(); final boolean[][] sIndexMapped = new boolean[le][M]; final boolean[][] sMapped = new boolean[le][M]; //tmp integer int fr, maxI, k, i, m; int[] table; for(int e = 0; e<le; e++) { //envelopes: eOrig -> eMapped fr = freqRes[e]; maxI = tables.getN(fr); table = tables.getFrequencyTable(fr); for(i = 0; i<maxI; i++) { for(m = table[i]; m<table[i+1]; m++) { eMapped[e][m-kx] = eOrig[e][i]; } } //noise: qOrig -> qMapped k = ((lq>1)&&(cd.getTe()[e]>=cd.getTq()[1])) ? 1 : 0; for(i = 0; i<nq; i++) { for(m = noiseTable[i]; m<noiseTable[i+1]; m++) { qMapped[e][m-kx] = qOrig[k][i]; } } //sinusoidals: cd.sinusoidals -> sIndexMapped for(i = 0; i<nHigh; i++) { if(cd.areSinusoidalsPresent()) { m = (fHigh[i]+fHigh[i+1])>>1; sIndexMapped[e][m-kx] = sinusoidals[i]&&(e>=la||sIndexMappedPrev[m-kx]); } } //sinusoidals: sIndexMapped -> sMapped boolean found; for(i = 0; i<maxI; i++) { found = false; for(m = table[i]; !found&&m<table[i+1]; m++) { if(sIndexMapped[e][m-kx]) found = true; } for(m = table[i]; m<table[i+1]; m++) { sMapped[e][m-kx] = found; } } } cd.setSIndexMappedPrevious(sIndexMapped[le-1]); final Parameter p = new Parameter(); p.eMapped = eMapped; p.qMapped = qMapped; p.sIndexMapped = sIndexMapped; p.sMapped = sMapped; return p; } //envelope estimation (4.6.18.7.3) private static float[][] estimateEnvelopes(SBRHeader header, FrequencyTables tables, ChannelData cd, float[][][] Xhigh) { final int[] te = cd.getTe(); final int M = tables.getM(false); final int kx = tables.getKx(false); final int le = cd.getEnvCount(); final float[][] eCurr = new float[le][M]; float sum; int e, m, i, iLow, iHigh; if(header.interpolateFrequency()) { float div; for(e = 0; e<le; e++) { div = te[e+1]-te[e]; iLow = RATE*te[e]+T_HF_ADJ; iHigh = RATE*te[e+1]+T_HF_ADJ; for(m = 0; m<M; m++) { sum = 0.0f; //energy = sum over squares of absolute value for(i = iLow; i<iHigh; i++) { sum += Xhigh[m+kx][i][0]*Xhigh[m+kx][i][0]+Xhigh[m+kx][i][1]*Xhigh[m+kx][i][1]; } eCurr[e][m] = sum/div; } } } else { final int[] n = tables.getN(); final int[] freqRes = cd.getFrequencyResolutions(); int k; int[] table; int div1, div2; for(e = 0; e<le; e++) { div1 = RATE*(te[e+1]-te[e]); iLow = RATE*te[e]+T_HF_ADJ; iHigh = RATE*te[e+1]+T_HF_ADJ; table = tables.getFrequencyTable(freqRes[e+1]); for(m = 0; m<n[freqRes[e+1]]; m++) { sum = 0.0f; div2 = div1*(table[m+1]-table[m]); for(k = table[m]; k<table[m+1]; k++) { for(i = iLow; i<iHigh; i++) { sum += Xhigh[k][i][0]*Xhigh[k][i][0]+Xhigh[k][i][1]*Xhigh[k][i][1]; } } sum /= div2; for(k = table[m]; k<table[m+1]; k++) { eCurr[e][k-kx] = sum; } } } } return eCurr; } //calculation of levels of additional HF signal components (4.6.18.7.4) and gain calculation (4.6.18.7.5) private static void calculateGain(SBRHeader header, FrequencyTables tables, ChannelData cd, Parameter p, float[][] eCurr) { final int limGain = header.getLimiterGains(); final int M = tables.getM(false); final int nl = tables.getNl(); final int[] fLim = tables.getLimiterTable(); final int kx = tables.getKx(false); final int la = cd.getLa(false); final int laPrevious = cd.getLa(true); final int le = cd.getEnvCount(); //output arrays final float[][] Qm = new float[le][M]; final float[][] Sm = new float[le][M]; final float[][] gain = new float[le][M]; boolean delta, delta2; int m, k, i; final int[] km = new int[M]; final float[] eMappedSum = new float[nl]; float tmp; final float[][] gTemp = new float[le][nl]; float gMax; //TODO: optimize this loops for(int e = 0; e<le; e++) { delta = (e!=la)&&(e!=laPrevious); //level of additional HF components + gain for(m = 0; m<M; m++) { tmp = p.eMapped[e][m]/(1.0f+p.qMapped[e][m]); Qm[e][m] = (float) Math.sqrt(tmp*p.qMapped[e][m]); Sm[e][m] = p.sIndexMapped[e][m] ? (float) Math.sqrt(tmp) : 0; //TODO: is epsilon==1.0f ??? if(p.sMapped[e][m]) { gain[e][m] = (float) Math.sqrt(p.eMapped[e][m]*p.qMapped[e][m] /((EPSILON+eCurr[e][m])*(1.0f+p.qMapped[e][m]))); } else { gain[e][m] = (float) Math.sqrt(p.eMapped[e][m] /((EPSILON+eCurr[e][m])*(1.0f+(delta ? p.qMapped[e][m] : 0)))); } } //limiter for(k = 0; k<nl; k++) { eMappedSum[k] = EPSILON_0; tmp = EPSILON_0; for(i = fLim[k]-kx; i<fLim[k+1]-kx; i++) { eMappedSum[k] += p.eMapped[e][i]; tmp += eCurr[e][i]; } gTemp[e][k] = (float) Math.sqrt(eMappedSum[k]/tmp)*LIMITER_GAINS[limGain]; } for(m = 0; m<M; m++) { km[m] = -1; for(i = 0; km[m]<0&&i<fLim.length; i++) { if(fLim[i]<=(m+kx)&&fLim[i+1]>(m+kx)) km[m] = i; } gMax = Math.min(gTemp[e][km[m]], MAX_GAIN); Qm[e][m] = Math.min(Qm[e][m], Qm[e][m]*(gMax/gain[e][m])); gain[e][m] = Math.min(gain[e][m], gMax); } //compensate for(k = 0; k<nl; k++) { tmp = EPSILON_0; for(i = fLim[k]-kx; i<fLim[k+1]-kx; i++) { delta2 = Sm[e][i]==0&δ tmp += (eCurr[e][i]*gain[e][i]*gain[e][i]) +(Sm[e][i]*Sm[e][i]) +(delta2 ? (Qm[e][i]*Qm[e][i]) : 0); } gTemp[e][k] = (float) Math.sqrt(eMappedSum[k]/tmp); } //apply boost for(m = 0; m<M; m++) { gMax = (float) Math.min(gTemp[e][km[m]], MAX_BOOST); Qm[e][m] *= gMax; Sm[e][m] *= gMax; gain[e][m] *= gMax; } } p.Qm = Qm; p.Sm = Sm; p.Glim = gain; } //assembling HF signals (4.6.18.7.5) private static void assembleSignals(SBRHeader header, FrequencyTables tables, ChannelData cd, Parameter p, float[][][] Xhigh, float[][][] Y) { final boolean reset = header.isReset(); final int hSL = header.isSmoothingMode() ? 0 : 4; final int M = tables.getM(false); final int le = cd.getEnvCount(); final int lePrev = cd.getEnvCountPrevious(); final int[] te = cd.getTe(); final int la = cd.getLa(false); final int laPrev = cd.getLa(true); final int kx = tables.getKx(false); int noiseIndex = reset ? 0 : cd.getNoiseIndex(); int sineIndex = cd.getSineIndex(); final float[][] gTmp = cd.getGTmp(); final float[][] qTmp = cd.getQTmp(); int e, i, m, j; //save previous values if(reset) { for(i = 0; i<hSL; i++) { System.arraycopy(gTmp[lePrev-hSL+i], 0, p.Glim[0], 0, M); System.arraycopy(qTmp[lePrev-hSL+i], 0, p.Qm[0], 0, M); } } else if(hSL!=0) { for(i = 0; i<hSL; i++) { System.arraycopy(gTmp[lePrev-hSL+i], 0, gTmp[RATE*te[i]], 0, M); System.arraycopy(qTmp[lePrev-hSL+i], 0, qTmp[RATE*te[i]], 0, M); } } //calculate new int phiSign = (1-2*(kx&1)); float gFilt, qFilt; for(e = 0; e<le; e++) { for(i = RATE*te[e]; i<RATE*te[e+1]; i++) { if(hSL!=0&&e!=la&&e!=laPrev) { for(m = 0; m<M; m++) { final int idx1 = i+hSL; gFilt = 0.0f; for(j = 0; j<=hSL; j++) { gFilt += gTmp[idx1-j][m]*SMOOTHING_FACTORS[j]; } Y[i][m+kx][0] = Xhigh[m+kx][i+T_HF_ADJ][0]*gFilt; Y[i][m+kx][1] = Xhigh[m+kx][i+T_HF_ADJ][1]*gFilt; } } else { for(m = 0; m<M; m++) { final float g_filt = gTmp[i+hSL][m]; Y[i][m+kx][0] = Xhigh[m+kx][i+T_HF_ADJ][0]*g_filt; Y[i][m+kx][1] = Xhigh[m+kx][i+T_HF_ADJ][1]*g_filt; } } if(e!=la&&e!=laPrev) { for(m = 0; m<M; m++) { noiseIndex = (noiseIndex+1)&0x1ff; if(p.Sm[e][m]!=0) { Y[i][m+kx][0] += p.Sm[e][m]*PHI[0][sineIndex]; Y[i][m+kx][1] += p.Sm[e][m]*(PHI[1][sineIndex]*phiSign); } else { if(hSL!=0) { final int idx1 = i+hSL; qFilt = 0.0f; for(j = 0; j<=hSL; j++) { qFilt += qTmp[idx1-j][m]*SMOOTHING_FACTORS[j]; } } else qFilt = qTmp[i][m]; Y[i][m+kx][0] += qFilt*NOISE_TABLE[noiseIndex][0]; Y[i][m+kx][1] += qFilt*NOISE_TABLE[noiseIndex][1]; } phiSign = -phiSign; } } else { noiseIndex = (noiseIndex+M)&0x1ff; for(m = 0; m<M; m++) { Y[i][m+kx][0] += p.Sm[e][m]*PHI[0][sineIndex]; Y[i][m+kx][1] += p.Sm[e][m]*(PHI[1][sineIndex]*phiSign); phiSign = -phiSign; } } sineIndex = (sineIndex+1)&3; } } cd.setNoiseIndex(noiseIndex); cd.setSineIndex(sineIndex); } }