/*
* 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.ps2;
import java.util.Arrays;
import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.syntax.BitStream;
public class PS implements PSConstants, PSTables, HuffmanTables {
//generated tables
private final float[][][] HA, HB;
private final float[][] PHI_FRACT_20, PHI_FRACT_34;
private final float[][][] Q_FRACT_ALLPASS_20, Q_FRACT_ALLPASS_34;
private final float[][] SMOOTHING_TABLE;
//header
private boolean headerEnabled;
private final PSHeader header;
//bitstream variables
private boolean frameClass;
private int envCount, envCountPrev;
private final int[] borderPositions;
//pars
private final int[][] iidPars, iccPars, ipdPars, opdPars;
private final int[][] iidMapped, iccMapped, ipdMapped, opdMapped;
private final int[] ipdPrev, opdPrev;
//working buffer
private final float[][][] lBuf, rBuf;
//buffers for decorrelation in z-domain
private final float[] peakDecayNrg, smoothNrg, smoothPeakDecayDiffNrg;
private final float[][][] delay;
private final float[][][][] apDelay;
//buffers for stereo processing
private final float[][][] H11, H12, H21, H22;
public PS() {
HA = new float[46][8][4];
HB = new float[46][8][4];
TableGenerator.generateMixingTables(HA, HB);
PHI_FRACT_20 = new float[30][2];
Q_FRACT_ALLPASS_20 = new float[30][3][2];
TableGenerator.generateFractTables20(PHI_FRACT_20, Q_FRACT_ALLPASS_20);
PHI_FRACT_34 = new float[50][2];
Q_FRACT_ALLPASS_34 = new float[50][3][2];
TableGenerator.generateFractTables34(PHI_FRACT_34, Q_FRACT_ALLPASS_34);
SMOOTHING_TABLE = new float[8*8*8][2];
TableGenerator.generateIPDOPDSmoothingTables(SMOOTHING_TABLE);
headerEnabled = false;
header = new PSHeader();
borderPositions = new int[MAX_ENVELOPES];
iidPars = new int[MAX_ENVELOPES][MAX_IID_ICC_PARS];
iccPars = new int[MAX_ENVELOPES][MAX_IID_ICC_PARS];
ipdPars = new int[MAX_ENVELOPES][MAX_IPD_OPD_PARS];
opdPars = new int[MAX_ENVELOPES][MAX_IPD_OPD_PARS];
iidMapped = new int[MAX_ENVELOPES][MAX_IID_ICC_PARS];
iccMapped = new int[MAX_ENVELOPES][MAX_IID_ICC_PARS];
ipdMapped = new int[MAX_ENVELOPES][MAX_IPD_OPD_PARS];
opdMapped = new int[MAX_ENVELOPES][MAX_IPD_OPD_PARS];
ipdPrev = new int[MAX_IPD_OPD_PARS];
opdPrev = new int[MAX_IPD_OPD_PARS];
lBuf = new float[91][32][2];
rBuf = new float[91][32][2];
peakDecayNrg = new float[MAX_IID_ICC_PARS];
smoothNrg = new float[MAX_IID_ICC_PARS];
smoothPeakDecayDiffNrg = new float[MAX_IID_ICC_PARS];
delay = new float[91][32+MAX_DELAY][2];
apDelay = new float[50][ALLPASS_LINKS][32+MAX_DELAY][2];
//complex in first dimension makes mapping easier
H11 = new float[2][MAX_ENVELOPES+1][MAX_IID_ICC_PARS];
H12 = new float[2][MAX_ENVELOPES+1][MAX_IID_ICC_PARS];
H21 = new float[2][MAX_ENVELOPES+1][MAX_IID_ICC_PARS];
H22 = new float[2][MAX_ENVELOPES+1][MAX_IID_ICC_PARS];
}
/*========================= decoding =========================*/
public void decode(BitStream in) throws AACException {
header.startNewFrame();
if(headerEnabled = in.readBool()) header.decode(in);
frameClass = in.readBool();
//envelopes (table 8.29)
final int envIdx = in.readBits(2);
envCountPrev = envCount;
envCount = envIdx+(frameClass ? 1 : 0);
if(envIdx==3&&!frameClass) envCount++;
//if envCount==0: no new parameters, use old ones
if(envCount==0) envCount = envCountPrev;
//border positions
int e;
if(frameClass) {
for(e = 0; e<envCount; e++) {
borderPositions[e] = in.readBits(5);
}
}
else {
//fixed borders (8.6.4.6.2)
for(e = 0; e<envCount; e++) {
borderPositions[e] = (32*(e+1))/envCount-1;
}
}
int len;
boolean dt;
int[][] table;
//iid
if(header.isIIDEnabled()) {
len = header.getIIDPars();
final boolean fine = header.useIIDQuantFine();
for(e = 0; e<envCount; e++) {
dt = in.readBool();
table = dt ? (fine ? HUFF_IID_FINE_DT : HUFF_IID_DEFAULT_DT)
: (fine ? HUFF_IID_FINE_DF : HUFF_IID_DEFAULT_DF);
decodePars(in, table, iidPars, e, len, dt, false);
}
}
//icc
if(header.isICCEnabled()) {
len = header.getICCPars();
for(e = 0; e<envCount; e++) {
dt = in.readBool();
table = dt ? HUFF_ICC_DT : HUFF_ICC_DF;
decodePars(in, table, iccPars, e, len, dt, false);
}
}
//extension
if(header.isExtEnabled()) {
int left = in.readBits(4);
if(left==15) left += in.readBits(8);
left *= 8;
int id;
while(left>7) {
id = in.readBits(2);
left -= 2;
left -= decodeExtension(in, id);
}
in.skipBits(left);
}
}
private void decodePars(BitStream in, int[][] table, int[][] pars, int env, int len, boolean dt, boolean mod) throws AACException {
//huffman delta decoding
if(dt) {
final int prev = (env>0) ? env-1 : envCountPrev-1;
for(int i = 0; i<len; i++) {
pars[env][i] = pars[prev][i]+decodeHuffman(in, table);
if(mod) pars[env][i] &= 7;
}
}
else {
pars[env][0] = decodeHuffman(in, table);
for(int i = 1; i<len; i++) {
pars[env][i] = pars[env][i-1]+decodeHuffman(in, table);
if(mod) pars[env][i] &= 7;
}
}
}
private int decodeHuffman(BitStream in, int[][] table) throws AACException {
int off = 0;
int len = table[off][0];
int cw = in.readBits(len);
int j;
while(cw!=table[off][1]) {
off++;
j = table[off][0]-len;
len = table[off][0];
cw <<= j;
cw |= in.readBits(j);
}
return table[off][2];
}
private int decodeExtension(BitStream in, int id) throws AACException {
final int start = in.getPosition();
if(id==0) {
//ipdopd
final boolean b = in.readBool();
header.setIPDOPDEnabled(b);
if(b) {
final int len = header.getIPDOPDPars();
boolean dt;
int[][] table;
for(int e = 0; e<envCount; e++) {
dt = in.readBool();
table = dt ? HUFF_IPD_DT : HUFF_IPD_DF;
decodePars(in, table, ipdPars, e, len, dt, true);
//dequant(ipdPars[e], ipd[e], len, dt ? IPD_OPD_QUANT : ICC_QUANT);
dt = in.readBool();
table = dt ? HUFF_OPD_DT : HUFF_OPD_DF;
decodePars(in, table, opdPars, e, len, dt, true);
//dequant(opdPars[e], opd[e], len, dt ? IPD_OPD_QUANT : ICC_QUANT);
}
}
in.skipBit(); //reserved
}
return in.getPosition()-start;
}
/*========================= processing =========================*/
public boolean hasHeader() {
return headerEnabled;
}
//left: 64 x 38 complex in-/output, left/right: 64 x 38 complex output
public void process(float[][][] left, float[][][] right) {
//1. hybrid analysis (in -> lBuf)
HybridFilterbank.analyze(left, lBuf, header.use34Bands(false));
//2. decorrelation (lBuf -> rBuf)
decorrelate();
//3. stereo processing
performStereoProcessing();
//4. hybrid synthesis
HybridFilterbank.synthesize(lBuf, left, header.use34Bands(false));
HybridFilterbank.synthesize(rBuf, right, header.use34Bands(false));
}
private void decorrelate() {
final boolean use34 = header.use34Bands(false);
//reset if necessary
if(header.use34Bands(true)!=use34) {
Arrays.fill(peakDecayNrg, 0);
Arrays.fill(smoothNrg, 0);
Arrays.fill(smoothPeakDecayDiffNrg, 0);
for(int i = 0; i<delay.length; i++) {
for(int j = 0; j<delay[i].length; j++) {
delay[i][j][0] = 0;
delay[i][j][1] = 0;
}
}
}
final int mode = header.getBandMode();
final int[] map = use34 ? K_TO_BK_34 : K_TO_BK_20;
int k, n, m, b;
//calculate transients
final int parBands = PAR_BANDS[mode];
final float[][] power = new float[parBands][32];
for(k = 0; k<32; k++) {
Arrays.fill(power[k], 0);
}
for(k = 0; k<parBands; k++) {
for(n = 0; n<32; n++) {
b = map[k];
power[b][n] += lBuf[k][n][0]*lBuf[k][n][0]+lBuf[k][n][1]*lBuf[k][n][1];
}
}
//perform transient detection
final float[][] gTransientRatio = new float[parBands][32];
float tmp;
for(k = 0; k<parBands; k++) {
peakDecayNrg[k] = power[k][0];
for(n = 0; n<32; n++) {
peakDecayNrg[k] *= PEAK_DECAY_FACTOR;
peakDecayNrg[k] = Math.max(peakDecayNrg[k], power[k][n]);
smoothNrg[k] += A_SMOOTH*(power[k][n]-smoothNrg[k]);
smoothPeakDecayDiffNrg[k] += A_SMOOTH*(peakDecayNrg[k]-power[k][n]-smoothPeakDecayDiffNrg[k]);
tmp = GAMMA*smoothPeakDecayDiffNrg[k];
gTransientRatio[k][n] = (tmp>smoothNrg[k]) ? (smoothNrg[k]/tmp) : 1.0f;
}
}
final float[][] transientGain = new float[parBands][32];
for(k = 0; k<parBands; k++) {
for(n = 0; n<32; n++) {
transientGain[k][n] = gTransientRatio[map[k]][n];
}
}
//calculate transfer function and apply transient reduction
final int allpassBands = ALLPASS_BANDS[mode];
final float[][] phiFract = use34 ? PHI_FRACT_34 : PHI_FRACT_20;
final float[][][] qFract = use34 ? Q_FRACT_ALLPASS_34 : Q_FRACT_ALLPASS_20;
final float[][][] H = new float[allpassBands][32][2];
final float[] ag = new float[ALLPASS_LINKS];
float gDecaySlope, re, im;
for(k = 0; k<allpassBands; k++) {
b = map[k];
gDecaySlope = (k>DECAY_CUTOFF[mode]) ? Math.max(0, 1-DECAY_SLOPE*(k-DECAY_CUTOFF[mode])) : 1;
for(m = 0; m<ALLPASS_LINKS; m++) {
for(n = 0; n<5; n++) {
apDelay[k][m][n][0] = apDelay[k][m][32][0];
apDelay[k][m][n][1] = apDelay[k][m][32][1];
}
ag[m] = FILTER_COEFFICIENTS[m]*gDecaySlope;
}
addNewSamples(k);
for(n = 0; n<32; n++) {
re = delay[k][n+MAX_DELAY-2][0]*phiFract[k][0]
-delay[k][n+MAX_DELAY-2][1]*phiFract[k][1];
im = delay[k][n+MAX_DELAY-2][0]*phiFract[k][1]
+delay[k][n+MAX_DELAY-2][1]*phiFract[k][0];
for(m = 0; m<ALLPASS_LINKS; m++) {
float a_re = ag[m]*re;
float a_im = ag[m]*im;
float link_delay_re = apDelay[k][m][n+5-LINK_DELAY[m]][0];
float link_delay_im = apDelay[k][m][n+5-LINK_DELAY[m]][1];
float fractional_delay_re = qFract[k][m][0];
float fractional_delay_im = qFract[k][m][1];
apDelay[k][m][n+5][0] = re;
apDelay[k][m][n+5][1] = im;
re = link_delay_re*fractional_delay_re-link_delay_im*fractional_delay_im-a_re;
im = link_delay_re*fractional_delay_im+link_delay_im*fractional_delay_re-a_im;
apDelay[k][m][n+5][0] += ag[m]*re;
apDelay[k][m][n+5][1] += ag[m]*im;
}
rBuf[k][n][0] = transientGain[b][n]*re;
rBuf[k][n][1] = transientGain[b][n]*im;
}
}
for(k = allpassBands; k<SHORT_DELAY_BANDS[mode]; k++) {
addNewSamples(k);
for(n = 0; n<32; n++) {
rBuf[k][n][0] = transientGain[map[k]][n]*delay[k][n+MAX_DELAY-14][0];
rBuf[k][n][1] = transientGain[map[k]][n]*delay[k][n+MAX_DELAY-14][1];
}
}
for(k = SHORT_DELAY_BANDS[mode]; k<BANDS[mode]; k++) {
addNewSamples(k);
for(n = 0; n<32; n++) {
//H = delay 1
rBuf[k][n][0] = transientGain[map[k]][n]*delay[k][n+MAX_DELAY-1][0];
rBuf[k][n][1] = transientGain[map[k]][n]*delay[k][n+MAX_DELAY-1][1];
}
}
}
//helper method for decorrelation
private void addNewSamples(int k) {
//copy previous
for(int n = 0; n<MAX_DELAY; n++) {
delay[k][n][0] = delay[k][n+32][0];
delay[k][n][1] = delay[k][n+32][1];
}
//add new
for(int n = 0; n<32; n++) {
delay[k][n+MAX_DELAY][0] = lBuf[k][n][0];
delay[k][n+MAX_DELAY][1] = lBuf[k][n][1];
}
}
private void performStereoProcessing() {
//remapping
mapPars(iidPars, iidMapped, header.getIIDPars(), true);
mapPars(iccPars, iccMapped, header.getICCPars(), true);
if(header.isIPDOPDEnabled()) {
final int pars = header.getIPDOPDPars();
mapPars(ipdPars, ipdMapped, pars, true);
mapPars(opdPars, opdMapped, pars, true);
}
System.arraycopy(H11[0][envCountPrev], 0, H11[0][0], 0, 34);
System.arraycopy(H11[1][envCountPrev], 0, H11[0][0], 0, 34);
System.arraycopy(H12[0][envCountPrev], 0, H12[0][0], 0, 34);
System.arraycopy(H12[1][envCountPrev], 0, H12[0][0], 0, 34);
System.arraycopy(H21[0][envCountPrev], 0, H21[0][0], 0, 34);
System.arraycopy(H21[1][envCountPrev], 0, H21[0][0], 0, 34);
System.arraycopy(H22[0][envCountPrev], 0, H22[0][0], 0, 34);
System.arraycopy(H22[1][envCountPrev], 0, H22[0][0], 0, 34);
final boolean use34 = header.use34Bands(false), use34Prev = header.use34Bands(true);
if(use34&&!use34Prev) {
Utils.map20To34(H11[0][0]);
Utils.map20To34(H11[1][0]);
Utils.map20To34(H12[0][0]);
Utils.map20To34(H12[1][0]);
Utils.map20To34(H21[0][0]);
Utils.map20To34(H21[1][0]);
Utils.map20To34(H22[0][0]);
Utils.map20To34(H22[1][0]);
Arrays.fill(ipdPrev, 0);
Arrays.fill(opdPrev, 0);
}
else if(!use34&&use34Prev) {
Utils.map34To20(H11[0][0]);
Utils.map34To20(H11[1][0]);
Utils.map34To20(H12[0][0]);
Utils.map34To20(H12[1][0]);
Utils.map34To20(H21[0][0]);
Utils.map34To20(H21[1][0]);
Utils.map34To20(H22[0][0]);
Utils.map34To20(H22[1][0]);
Arrays.fill(ipdPrev, 0);
Arrays.fill(opdPrev, 0);
}
//mixing
final boolean ipdopd = header.isIPDOPDEnabled();
final int mode = header.getBandMode();
final float[][][] filter = header.useICCMixingB() ? HB : HA;
final int[] map = header.use34Bands(false) ? PARAMETER_MAP_34 : PARAMETER_MAP_20;
final int iidQuant = header.useIIDQuantFine() ? 1 : 0;
final float[] h11 = new float[2], h12 = new float[2];
final float[] h21 = new float[2], h22 = new float[2];
final float[] h11Step = new float[2], h12Step = new float[2];
final float[] h21Step = new float[2], h22Step = new float[2];
final float[] tmp = new float[2];
final float[] l = new float[2], r = new float[2];
float[] ipd, opd;
float width;
int ipdIndex, opdIndex;
int b, k, n;
for(int e = 0; e<envCount; e++) {
for(b = 0; b<PAR_BANDS[mode]; b++) {
h11[0] = filter[iidMapped[e][b]+7+23*iidQuant][iccMapped[e][b]][0];
h12[0] = filter[iidMapped[e][b]+7+23*iidQuant][iccMapped[e][b]][1];
h21[0] = filter[iidMapped[e][b]+7+23*iidQuant][iccMapped[e][b]][2];
h22[0] = filter[iidMapped[e][b]+7+23*iidQuant][iccMapped[e][b]][3];
if(ipdopd&&b<header.getIPDOPDPars()) {
ipdIndex = ipdPrev[b]*8+ipdMapped[e][b];
opdIndex = opdPrev[b]*8+opdMapped[e][b];
opdPrev[b] = opdIndex&0x3F;
ipdPrev[b] = ipdIndex&0x3F;
ipd = SMOOTHING_TABLE[ipdIndex];
opd = SMOOTHING_TABLE[opdIndex];
tmp[0] = opd[0]*ipd[0]+opd[1]*ipd[1];
tmp[1] = opd[1]*ipd[0]-opd[0]*ipd[1];
h11[1] = h11[0]*opd[1];
h11[0] *= opd[0];
h12[1] = h12[0]*tmp[1];
h12[0] *= tmp[0];
h21[1] = h21[0]*opd[1];
h21[0] *= opd[0];
h22[1] = h22[0]*tmp[1];
h22[0] *= tmp[0];
H11[1][e+1][b] = h11[1];
H12[1][e+1][b] = h12[1];
H21[1][e+1][b] = h21[1];
H22[1][e+1][b] = h22[1];
}
H11[0][e+1][b] = h11[0];
H12[0][e+1][b] = h12[0];
H21[0][e+1][b] = h21[0];
H22[0][e+1][b] = h22[0];
}
for(k = 0; k<BANDS[mode]; k++) {
width = 1.f/(borderPositions[e]+1-borderPositions[e+1]);
b = map[k];
h11[0] = H11[0][e][b];
h12[0] = H12[0][e][b];
h21[0] = H21[0][e][b];
h22[0] = H22[0][e][b];
if(ipdopd) {
if((use34&&k>=9&&k<=13)||(!use34&&k<=1)) {
h11[1] = -H11[1][e][b];
h12[1] = -H12[1][e][b];
h21[1] = -H21[1][e][b];
h22[1] = -H22[1][e][b];
}
else {
h11[1] = H11[1][e][b];
h12[1] = H12[1][e][b];
h21[1] = H21[1][e][b];
h22[1] = H22[1][e][b];
}
}
//interpolation
h11Step[0] = (H11[0][e+1][b]-h11[0])*width;
h12Step[0] = (H12[0][e+1][b]-h12[0])*width;
h21Step[0] = (H21[0][e+1][b]-h21[0])*width;
h22Step[0] = (H22[0][e+1][b]-h22[0])*width;
if(ipdopd) {
h11Step[1] = (H11[1][e+1][b]-h11[1])*width;
h12Step[1] = (H12[1][e+1][b]-h12[1])*width;
h21Step[1] = (H21[1][e+1][b]-h21[1])*width;
h22Step[1] = (H22[1][e+1][b]-h22[1])*width;
}
for(n = borderPositions[e]+1; n<=borderPositions[e+1]; n++) {
l[0] = lBuf[k][n][0];
l[1] = lBuf[k][n][1];
r[0] = rBuf[k][n][0];
r[1] = rBuf[k][n][1];
h11[0] += h11Step[0];
h12[0] += h12Step[0];
h21[0] += h21Step[0];
h22[0] += h22Step[0];
lBuf[k][n][0] = h11[0]*l[0]+h21[0]*r[0];
lBuf[k][n][1] = h11[0]*l[1]+h21[0]*r[1];
rBuf[k][n][0] = h12[0]*l[0]+h22[0]*r[0];
rBuf[k][n][1] = h12[0]*l[1]+h22[0]*r[1];
if(ipdopd) {
h11[1] += h11Step[1];
h12[1] += h12Step[1];
h21[1] += h21Step[1];
h22[1] += h22Step[1];
lBuf[k][n][0] -= h11[1]*l[1]-h21[1]*r[1];
lBuf[k][n][1] += h11[1]*l[0]+h21[1]*r[0];
rBuf[k][n][0] -= h12[1]*l[1]-h22[1]*r[1];
rBuf[k][n][1] += h12[1]*l[0]+h22[1]*r[0];
}
}
}
}
}
private void mapPars(int[][] in, int[][] out, int len, boolean full) {
int i;
if(header.use34Bands(false)) {
if(len==10) {
for(i = 0; i<envCount; i++) {
Utils.map10To34(in[i], out[i], full);
}
}
else if(len==20) {
for(i = 0; i<envCount; i++) {
Utils.map20To34(in[i], out[i], full);
}
}
}
else {
if(len==10) {
for(i = 0; i<envCount; i++) {
Utils.map10To20(in[i], out[i], full);
}
}
else if(len==34) {
for(i = 0; i<envCount; i++) {
Utils.map34To20(in[i], out[i], full);
}
}
}
}
}