/*
* 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.sbr;
class HFAdjustment implements SBRConstants, NoiseTable {
private static final float[] LIM_GAIN = {0.5f, 1.0f, 2.0f, 1e10f};
private static final float EPS = 1e-12f;
private static final float[] H_SMOOTH = {
0.03183050093751f, 0.11516383427084f,
0.21816949906249f, 0.30150283239582f,
0.33333333333333f
};
private static final int[] PHI_REAL = {1, 0, -1, 0};
private static final int[] PHI_IMAG = {0, 1, 0, -1};
private static final float G_BOOST_MAX = 2.51188643f; //1.584893192^2
private static final float MAXIMUM_GAIN = 1e10f;
private static class AdjustmentParams {
final float[][] G_lim_boost = new float[MAX_L_E][MAX_M];
final float[][] Q_M_lim_boost = new float[MAX_L_E][MAX_M];
final float[][] S_M_boost = new float[MAX_L_E][MAX_M];
private void reset() {
int j;
for(int i = 0; i<MAX_L_E; i++) {
for(j = 0; j<MAX_M; j++) {
G_lim_boost[i][j] = 0;
Q_M_lim_boost[i][j] = 0;
S_M_boost[i][j] = 0;
}
}
}
}
private final SBR sbr;
private final AdjustmentParams adj;
HFAdjustment(SBR sbr) {
this.sbr = sbr;
adj = new AdjustmentParams();
}
void process(float[][][] Xsbr, ChannelData cd, SBRHeader header) {
if(cd.frameClass==FIXFIX) cd.l_A = -1;
else if(cd.frameClass==VARFIX) cd.l_A = (cd.pointer>1) ? -1 : cd.pointer-1;
else cd.l_A = (cd.pointer==0) ? -1 : cd.L_E+1-cd.pointer;
adj.reset(); //TODO: needed?
estimateCurrentEnvelope(Xsbr, cd, header.hasInterpolFrequency());
calculateGain(cd, header.getLimiterBands(), header.getLimiterGains());
assembleHF(Xsbr, cd, header.isSmoothingMode());
}
private void estimateCurrentEnvelope(float[][][] Xsbr, ChannelData cd, boolean interpolFrequency) {
int i, j, k, l, m, curr, next, low, high;
float nrg, div;
if(interpolFrequency) {
for(i = 0; i<cd.L_E; i++) {
curr = cd.t_E[i];
next = cd.t_E[i+1];
div = (float) (next-curr);
if(div==0) div = 1;
for(j = 0; j<sbr.M; j++) {
nrg = 0;
for(l = curr+T_HFADJ; l<next+T_HFADJ; l++) {
nrg += (Xsbr[l][j+sbr.kx][0]*Xsbr[l][j+sbr.kx][0])
+(Xsbr[l][j+sbr.kx][1]*Xsbr[l][j+sbr.kx][1]);
}
cd.E_curr[j][i] = nrg/div;
}
}
}
else {
for(i = 0; i<cd.L_E; i++) {
for(j = 0; j<sbr.n[cd.f[i] ? 1 : 0]; j++) {
low = sbr.ftRes[cd.f[i] ? 1 : 0][j];
high = sbr.ftRes[cd.f[i] ? 1 : 0][j+1];
for(k = low; k<high; k++) {
curr = cd.t_E[i];
next = cd.t_E[i+1];
div = (float) ((next-curr)*(high-low));
if(div==0) div = 1;
nrg = 0;
for(l = curr+T_HFADJ; l<next+T_HFADJ; l++) {
for(m = low; m<high; m++) {
nrg += (Xsbr[l][m][0]*Xsbr[l][m][0])
+(Xsbr[l][m][1]*Xsbr[l][m][1]);
}
}
cd.E_curr[k-sbr.kx][i] = nrg/div;
}
}
}
}
}
private void calculateGain(ChannelData cd, int limiterBands, int limiterGains) {
final float[] qmLim = new float[MAX_M];
final float[] gLim = new float[MAX_M];
final float[] sm = new float[MAX_M];
int j, k;
int currentTNoiseBand = 0;
int currFNoiseBand, currResBand, currResBand2, currHiResBand;
int ml1, ml2;
boolean sMapped, delta, sIndexMapped;
float gBoost, gMax, den, acc1, acc2;
float G, Q_M, Q_div, Q_div2;
for(int i = 0; i<cd.L_E; i++) {
currFNoiseBand = 0;
currResBand = 0;
currResBand2 = 0;
currHiResBand = 0;
delta = i!=cd.l_A&&i!=cd.prevEnvIsShort;
sMapped = getSMapped(cd, i, currResBand2);
if(cd.t_E[i+1]>cd.t_Q[currentTNoiseBand+1]) currentTNoiseBand++;
for(j = 0; j<sbr.N_L[limiterBands]; j++) {
den = 0;
acc1 = 0;
acc2 = 0;
ml1 = sbr.ftLim[limiterBands][j];
ml2 = sbr.ftLim[limiterBands][j+1];
//calculate the accumulated E_orig and E_curr over the limiter band
for(k = ml1; k<ml2; k++) {
if((k+sbr.kx)==sbr.ftRes[cd.f[i] ? 1 : 0][currResBand+1]) currResBand++;
acc1 += cd.E_orig[currResBand][i];
acc2 += cd.E_curr[k][i];
}
//calculate the maximum gain
gMax = Math.min(MAXIMUM_GAIN, ((EPS+acc1)/(EPS+acc2))*LIM_GAIN[limiterGains]);
for(k = ml1; k<ml2; k++) {
//check if m is on a noise band border
if((k+sbr.kx)==sbr.ftNoise[currFNoiseBand+1]) currFNoiseBand++;
//check if m is on a resolution band border
if((k+sbr.kx)==sbr.ftRes[cd.f[i] ? 1 : 0][currResBand2+1]) {
currResBand2++;
sMapped = getSMapped(cd, i, currResBand2);
}
//check if m is on a HI_RES band border
if((k+sbr.kx)==sbr.ftRes[HI_RES][currHiResBand+1]) currHiResBand++;
//find sIndexMapped
sIndexMapped = false;
if((i>=cd.l_A)||(cd.addHarmonicPrev[currHiResBand]&&cd.hasHarmonicPrev())
&&((k+sbr.kx)==(sbr.ftRes[HI_RES][currHiResBand+1]+sbr.ftRes[HI_RES][currHiResBand])>>1)) {
sIndexMapped = cd.addHarmonic[currHiResBand];
}
Q_div = cd.Q_div[currFNoiseBand][currentTNoiseBand];
Q_div2 = cd.Q_div2[currFNoiseBand][currentTNoiseBand];
Q_M = cd.E_orig[currResBand2][i]*Q_div2;
if(sIndexMapped) {
sm[k] = cd.E_orig[currResBand2][i]*Q_div;
den += sm[k];
}
else sm[k] = 0;
//calculate gain
G = cd.E_orig[currResBand2][i]/(1.0f+cd.E_curr[k][i]);
if((!sMapped)&&delta) G *= Q_div;
else if(sMapped) G *= Q_div2;
//limit the additional noise energy level and apply the limiter
if(gMax>G) {
qmLim[k] = Q_M;
gLim[k] = G;
}
else {
qmLim[k] = Q_M*gMax/G;
gLim[k] = gMax;
}
//accumulate the total energy
den += cd.E_curr[k][i]*gLim[k];
if((!sIndexMapped)&&(i!=cd.l_A)) den += qmLim[k];
}
//gBoost: [0..2.51188643]
gBoost = Math.min((acc1+EPS)/(den+EPS), G_BOOST_MAX);
//apply compensation to gain, noise floor sf's and sinusoid levels
for(k = ml1; k<ml2; k++) {
adj.G_lim_boost[i][k] = (float) Math.sqrt(gLim[k]*gBoost);
adj.Q_M_lim_boost[i][k] = (float) Math.sqrt(qmLim[k]*gBoost);
adj.S_M_boost[i][k] = (float) ((sm[k]==0) ? 0 : Math.sqrt(sm[k]*gBoost));
}
}
}
}
private boolean getSMapped(ChannelData cd, int l, int currentBand) {
final boolean previousHarmonic = cd.hasHarmonicPrev();
if((cd.f[l] ? 1 : 0)==HI_RES) {
//ftRes[HIGH]: just 1 to 1 mapping from addHarmonic[l][k]
if((l>=cd.l_A)||(cd.addHarmonicPrev[currentBand]&&previousHarmonic)) {
return cd.addHarmonic[currentBand];
}
}
else {
/* ftLow: check if any of the HI_RES bands
* within this LO_RES band has bs_add_harmonic[l][k] turned on */
//find first HI_RES band in current LO_RES band
final int lb = 2*currentBand-(sbr.N_high&1);
//find first HI_RES band in next LO_RES band
final int ub = 2*(currentBand+1)-(sbr.N_high&1);
//check all HI_RES bands in current LO_RES band for sinusoid
for(int b = lb; b<ub; b++) {
if(((l>=cd.l_A)||(cd.addHarmonicPrev[b]&&previousHarmonic))&&cd.addHarmonic[b]) return true;
}
}
return false;
}
private void assembleHF(float[][][] Xsbr, ChannelData cd, boolean smoothingMode) {
boolean reset = sbr.reset;
int fIndexNoise = sbr.reset ? 0 : cd.indexNoisePrev;
int fIndexSine = cd.psiIsPrev;
int j, k, l, h_SL, ri, rev;
boolean noNoise;
float gFilt, qFilt, currHSmooth;
for(int i = 0; i<cd.L_E; i++) {
noNoise = (i==cd.l_A||i==cd.prevEnvIsShort);
h_SL = noNoise ? 0 : ((smoothingMode) ? 0 : 4);
if(reset) {
for(l = 0; l<4; l++) {
System.arraycopy(adj.G_lim_boost[i], 0, cd.gTempPrev[l], 0, sbr.M);
System.arraycopy(adj.Q_M_lim_boost[i], 0, cd.qTempPrev[l], 0, sbr.M);
}
//reset ringbuffer index
cd.gqIndex = 4;
reset = false;
}
for(j = cd.t_E[i]; j<cd.t_E[i+1]; j++) {
//load new values into ringbuffer
System.arraycopy(adj.G_lim_boost[i], 0, cd.gTempPrev[cd.gqIndex], 0, sbr.M);
System.arraycopy(adj.Q_M_lim_boost[i], 0, cd.qTempPrev[cd.gqIndex], 0, sbr.M);
for(k = 0; k<sbr.M; k++) {
gFilt = 0;
qFilt = 0;
if(h_SL==0) {
gFilt = cd.gTempPrev[cd.gqIndex][k];
qFilt = cd.qTempPrev[cd.gqIndex][k];
}
else {
ri = cd.gqIndex;
for(l = 0; l<=4; l++) {
currHSmooth = H_SMOOTH[l];
ri++;
if(ri>=5) ri -= 5;
gFilt += cd.gTempPrev[ri][k]*currHSmooth;
qFilt += cd.qTempPrev[ri][k]*currHSmooth;
}
}
qFilt = (adj.S_M_boost[i][k]!=0||noNoise) ? 0 : qFilt;
//add noise to the output
fIndexNoise = (fIndexNoise+1)&511;
//the smoothed gain values are applied to Xsbr
Xsbr[j+T_HFADJ][k+sbr.kx][0] = gFilt*Xsbr[j+T_HFADJ][k+sbr.kx][0]+(qFilt*NOISE_TABLE[fIndexNoise][0]);
if(sbr.extensionID==3&&sbr.extensionData==42) Xsbr[j+T_HFADJ][k+sbr.kx][0] = 16428320f;
Xsbr[j+T_HFADJ][k+sbr.kx][1] = (gFilt*Xsbr[j+T_HFADJ][k+sbr.kx][1])+(qFilt*NOISE_TABLE[fIndexNoise][1]);
rev = ((k+sbr.kx)&1)==1 ? -1 : 1;
Xsbr[j+T_HFADJ][k+sbr.kx][0] += adj.S_M_boost[i][k]*PHI_REAL[fIndexSine];
Xsbr[j+T_HFADJ][k+sbr.kx][1] += rev*adj.S_M_boost[i][k]*PHI_IMAG[fIndexSine];
}
fIndexSine = (fIndexSine+1)&3;
cd.gqIndex++;
if(cd.gqIndex>=5) cd.gqIndex = 0;
}
}
cd.indexNoisePrev = fIndexNoise;
cd.psiIsPrev = fIndexSine;
}
}