/*
* 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.gain;
import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.syntax.BitStream;
import net.sourceforge.jaad.aac.syntax.ICSInfo.WindowSequence;
import java.util.Arrays;
public class GainControl implements GCConstants {
private final int frameLen, lbLong, lbShort;
private final IMDCT imdct;
private final IPQF ipqf;
private final float[] buffer1, function;
private final float[][] buffer2, overlap;
private int maxBand;
private int[][][] level, levelPrev;
private int[][][] location, locationPrev;
public GainControl(int frameLen) {
this.frameLen = frameLen;
lbLong = frameLen/BANDS;
lbShort = lbLong/8;
imdct = new IMDCT(frameLen);
ipqf = new IPQF();
levelPrev = new int[0][][];
locationPrev = new int[0][][];
buffer1 = new float[frameLen/2];
buffer2 = new float[BANDS][lbLong];
function = new float[lbLong*2];
overlap = new float[BANDS][lbLong*2];
}
public void decode(BitStream in, WindowSequence winSeq) throws AACException {
maxBand = in.readBits(2)+1;
int wdLen, locBits, locBits2 = 0;
switch(winSeq) {
case ONLY_LONG_SEQUENCE:
wdLen = 1;
locBits = 5;
locBits2 = 5;
break;
case EIGHT_SHORT_SEQUENCE:
wdLen = 8;
locBits = 2;
locBits2 = 2;
break;
case LONG_START_SEQUENCE:
wdLen = 2;
locBits = 4;
locBits2 = 2;
break;
case LONG_STOP_SEQUENCE:
wdLen = 2;
locBits = 4;
locBits2 = 5;
break;
default:
return;
}
level = new int[maxBand][wdLen][];
location = new int[maxBand][wdLen][];
int wd, k, len, bits;
for(int bd = 1; bd<maxBand; bd++) {
for(wd = 0; wd<wdLen; wd++) {
len = in.readBits(3);
level[bd][wd] = new int[len];
location[bd][wd] = new int[len];
for(k = 0; k<len; k++) {
level[bd][wd][k] = in.readBits(4);
bits = (wd==0) ? locBits : locBits2;
location[bd][wd][k] = in.readBits(bits);
}
}
}
}
public void process(float[] data, int winShape, int winShapePrev, WindowSequence winSeq) throws AACException {
imdct.process(data, buffer1, winShape, winShapePrev, winSeq);
for(int i = 0; i<BANDS; i++) {
compensate(buffer1, buffer2, winSeq, i);
}
ipqf.process(buffer2, frameLen, maxBand, data);
}
/**
* gain compensation and overlap-add:
* - the gain control function is calculated
* - the gain control function applies to IMDCT output samples as a another IMDCT window
* - the reconstructed time domain signal produces by overlap-add
*/
private void compensate(float[] in, float[][] out, WindowSequence winSeq, int band) {
int j;
if(winSeq.equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) {
int a, b;
for(int k = 0; k<8; k++) {
//calculation
calculateFunctionData(lbShort*2, band, winSeq, k);
//applying
for(j = 0; j<lbShort*2; j++) {
a = band*lbLong*2+k*lbShort*2+j;
in[a] *= function[j];
}
//overlapping
for(j = 0; j<lbShort; j++) {
a = j+lbLong*7/16+lbShort*k;
b = band*lbLong*2+k*lbShort*2+j;
overlap[band][a] += in[b];
}
//store for next frame
for(j = 0; j<lbShort; j++) {
a = j+lbLong*7/16+lbShort*(k+1);
b = band*lbLong*2+k*lbShort*2+lbShort+j;
overlap[band][a] = in[b];
}
locationPrev[band][0] = Arrays.copyOf(location[band][k], location[band][k].length);
levelPrev[band][0] = Arrays.copyOf(level[band][k], level[band][k].length);
}
System.arraycopy(overlap[band], 0, out[band], 0, lbLong);
System.arraycopy(overlap[band], lbLong, overlap[band], 0, lbLong);
}
else {
//calculation
calculateFunctionData(lbLong*2, band, winSeq, 0);
//applying
for(j = 0; j<lbLong*2; j++) {
in[band*lbLong*2+j] *= function[j];
}
//overlapping
for(j = 0; j<lbLong; j++) {
out[band][j] = overlap[band][j]+in[band*lbLong*2+j];
}
//store for next frame
for(j = 0; j<lbLong; j++) {
overlap[band][j] = in[band*lbLong*2+lbLong+j];
}
final int lastBlock = winSeq.equals(WindowSequence.ONLY_LONG_SEQUENCE) ? 1 : 0;
locationPrev[band][0] = Arrays.copyOf(location[band][lastBlock], location[band][lastBlock].length);
levelPrev[band][0] = Arrays.copyOf(level[band][lastBlock], level[band][lastBlock].length);
}
}
//produces gain control function data, stores it in 'function' array
private void calculateFunctionData(int samples, int band, WindowSequence winSeq, int blockID) {
final int[] locA = new int[10];
final float[] levA = new float[10];
final float[] modFunc = new float[samples];
final float[] buf1 = new float[samples/2];
final float[] buf2 = new float[samples/2];
final float[] buf3 = new float[samples/2];
int maxLocGain0 = 0, maxLocGain1 = 0, maxLocGain2 = 0;
switch(winSeq) {
case ONLY_LONG_SEQUENCE:
case EIGHT_SHORT_SEQUENCE:
maxLocGain0 = maxLocGain1 = samples/2;
maxLocGain2 = 0;
break;
case LONG_START_SEQUENCE:
maxLocGain0 = samples/2;
maxLocGain1 = samples*7/32;
maxLocGain2 = samples/16;
break;
case LONG_STOP_SEQUENCE:
maxLocGain0 = samples/16;
maxLocGain1 = samples*7/32;
maxLocGain2 = samples/2;
break;
}
//calculate the fragment modification functions
//for the first half region
calculateFMD(band, 0, true, maxLocGain0, samples, locA, levA, buf1);
//for the latter half region
int block = (winSeq.equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) ? blockID : 0;
float secLevel = calculateFMD(band, block, false, maxLocGain1, samples, locA, levA, buf2);
//for the non-overlapped region
if(winSeq.equals(WindowSequence.LONG_START_SEQUENCE)||winSeq.equals(WindowSequence.LONG_STOP_SEQUENCE)) {
calculateFMD(band, 1, false, maxLocGain2, samples, locA, levA, buf3);
}
//calculate a gain modification function
int i;
int flatLen = 0;
if(winSeq.equals(WindowSequence.LONG_STOP_SEQUENCE)) {
flatLen = samples/2-maxLocGain0-maxLocGain1;
for(i = 0; i<flatLen; i++) {
modFunc[i] = 1.0f;
}
}
if(winSeq.equals(WindowSequence.ONLY_LONG_SEQUENCE)||winSeq.equals(WindowSequence.EIGHT_SHORT_SEQUENCE)) levA[0] = 1.0f;
for(i = 0; i<maxLocGain0; i++) {
modFunc[i+flatLen] = levA[0]*secLevel*buf1[i];
}
for(i = 0; i<maxLocGain1; i++) {
modFunc[i+flatLen+maxLocGain0] = levA[0]*buf2[i];
}
if(winSeq.equals(WindowSequence.LONG_START_SEQUENCE)) {
for(i = 0; i<maxLocGain2; i++) {
modFunc[i+maxLocGain0+maxLocGain1] = buf3[i];
}
flatLen = samples/2-maxLocGain1-maxLocGain2;
for(i = 0; i<flatLen; i++) {
modFunc[i+maxLocGain0+maxLocGain1+maxLocGain2] = 1.0f;
}
}
else if(winSeq.equals(WindowSequence.LONG_STOP_SEQUENCE)) {
for(i = 0; i<maxLocGain2; i++) {
modFunc[i+flatLen+maxLocGain0+maxLocGain1] = buf3[i];
}
}
//calculate a gain control function
for(i = 0; i<samples; i++) {
function[i] = 1.0f/modFunc[i];
}
}
/*
* calculates a fragment modification function by interpolating the gain
* values of the gain change positions
*/
private float calculateFMD(int bd, int wd, boolean prev, int maxLocGain, int samples,
int[] loc, float[] lev, float[] fmd) {
final int[] m = new int[samples/2];
final int[] lct = prev ? locationPrev[bd][wd] : location[bd][wd];
final int[] lvl = prev ? levelPrev[bd][wd] : level[bd][wd];
final int length = lct.length;
int lngain;
int i;
for(i = 0; i<length; i++) {
loc[i+1] = 8*lct[i]; //gainc
lngain = getGainChangePointID(lvl[i]); //gainc
if(lngain<0) lev[i+1] = 1.0f/(float) Math.pow(2, -lngain);
else lev[i+1] = (float) Math.pow(2, lngain);
}
//set start point values
loc[0] = 0;
if(length==0) lev[0] = 1.0f;
else lev[0] = lev[1];
float secLevel = lev[0];
//set end point values
loc[length+1] = maxLocGain;
lev[length+1] = 1.0f;
int j;
for(i = 0; i<maxLocGain; i++) {
m[i] = 0;
for(j = 0; j<=length+1; j++) {
if(loc[j]<=i) m[i] = j;
}
}
for(i = 0; i<maxLocGain; i++) {
if((i>=loc[m[i]])&&(i<=loc[m[i]]+7)) fmd[i] = interpolateGain(lev[m[i]], lev[m[i]+1], i-loc[m[i]]);
else fmd[i] = lev[m[i]+1];
}
return secLevel;
}
/**
* transformes the exponent value of the gain to the id of the gain change
* point
*/
private int getGainChangePointID(int lngain) {
for(int i = 0; i<ID_GAIN; i++) {
if(lngain==LN_GAIN[i]) return i;
}
return 0; //shouldn't happen
}
/**
* calculates a fragment modification function
* the interpolated gain value between the gain values of two gain change
* positions is calculated by the formula:
* f(a,b,j) = 2^(((8-j)log2(a)+j*log2(b))/8)
*/
private float interpolateGain(float alev0, float alev1, int iloc) {
final float a0 = (float) (Math.log(alev0)/Math.log(2));
final float a1 = (float) (Math.log(alev1)/Math.log(2));
return (float) Math.pow(2.0f, (((8-iloc)*a0+iloc*a1)/8));
}
}