/*
* 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;
import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.SampleFrequency;
import net.sourceforge.jaad.aac.ps.PS;
import net.sourceforge.jaad.aac.syntax.BitStream;
import net.sourceforge.jaad.aac.syntax.Constants;
import java.util.Arrays;
import java.util.logging.Level;
public class SBR implements Constants, SBRConstants, SBRTables {
private final ChannelData[] cd;
int sampleRate;
boolean reset;
//header
private final SBRHeader header;
//read
private boolean coupling;
int extensionID, extensionData;
//calculated
int k0; //first band (=f_master[0])
int kx, kxPrev;
int[] mft; //master frequency table
int[][] ftRes; //0=ftHigh and 1=ftLow
int[] ftNoise; //frequency border table for noise floors
int[][] ftLim; //limiter frequency table
int N_master; //length of MFT
int N_high, N_low;
int N_Q;
int M, Mprev;
int[] N_L;
int[] n; //number of frequency bands for low (0) and high (1) frequency
//patches
int[] tableMapKToG;
int patches; //number of patches
int[] patchNoSubbands, patchStartSubband;
//filterbank
private final Filterbank filterBank;
private final QMFAnalysis[] qmfa;
private final QMFSynthesis[] qmfs;
private final HFAdjustment hfAdj;
private final HFGeneration hfGen;
//processing buffers
private float[][][] buffer, bufLeftPS, bufRightPS;
//PS extension data
private PS ps;
private boolean psUsed, psExtensionRead;
public SBR(SampleFrequency sf, boolean downSampled) {
//global
sampleRate = 2*sf.getFrequency();
cd = new ChannelData[2];
cd[0] = new ChannelData();
cd[1] = new ChannelData();
filterBank = new Filterbank();
qmfa = new QMFAnalysis[2];
qmfa[0] = new QMFAnalysis(filterBank, 32);
qmfs = new QMFSynthesis[2];
qmfs[0] = new QMFSynthesis(filterBank, downSampled ? 32 : 64);
hfAdj = new HFAdjustment(this);
hfGen = new HFGeneration(this);
patchNoSubbands = new int[64];
patchStartSubband = new int[64];
N_L = new int[4];
n = new int[2];
tableMapKToG = new int[64];
mft = new int[64];
ftRes = new int[2][64];
ftNoise = new int[64];
ftLim = new int[4][64];
//header defaults
header = new SBRHeader();
Mprev = 0;
}
/* ================== decoding ================== */
public void decode(BitStream in, int count, boolean stereo, boolean crc) throws AACException {
final int pos = in.getPosition();
if(crc) {
LOGGER.info("SBR CRC bits present");
in.skipBits(10); //TODO: implement crc check
}
if(in.readBool()) header.decode(in);
if(reset) calculateTables();
//can't decode before the first header
if(header.isDecoded()) decodeData(in, stereo);
//else LOGGER.warning("no SBR header found");
final int len = in.getPosition()-pos;
final int bitsLeft = count-len;
if(bitsLeft>=8) LOGGER.log(Level.WARNING, "SBR: bits left: {0}", bitsLeft);
else if(bitsLeft<0) throw new AACException("SBR data overread: "+bitsLeft);
in.skipBits(bitsLeft);
}
//calculates the master frequency table
private void calculateTables() throws AACException {
k0 = Calculation.getStartChannel(header.getStartFrequency(false), sampleRate);
final int k2 = Calculation.getStopChannel(header.getStopFrequency(false), sampleRate, k0);
//check k0 and k2 (MFT length)
final int len = k2-k0;
int maxLen;
if(sampleRate>=48000) maxLen = 32;
else if(sampleRate<=32000) maxLen = 48;
else maxLen = 45;
if(len<=maxLen) {
int[] table;
if(header.getFrequencyScale(false)==0) table = Calculation.calculateMasterFrequencyTableFS0(k0, k2, header.isAlterScale(false));
else table = Calculation.calculateMasterFrequencyTable(k0, k2, header.getFrequencyScale(false), header.isAlterScale(false));
if(table!=null) {
mft = table;
N_master = table.length-1;
}
calculateDerivedFrequencyTable(k2);
}
else throw new AACException("SBR: master frequency table too long: "+len+", max. length: "+maxLen);
}
//calculates the derived frequency border table from the master table
private void calculateDerivedFrequencyTable(int k2) throws AACException {
final int xOverBand = header.getXOverBand(false);
if(N_master<=xOverBand) throw new AACException("SBR: derived frequency table: N_master="+N_master+", xOverBand="+xOverBand);
int i;
N_high = N_master-xOverBand;
N_low = (N_high>>1)+(N_high-((N_high>>1)<<1));
n[0] = N_low;
n[1] = N_high;
//fill high resolution table
for(i = 0; i<=N_high; i++) {
ftRes[HI_RES][i] = mft[i+xOverBand];
}
M = ftRes[HI_RES][N_high]-ftRes[HI_RES][0];
kx = ftRes[HI_RES][0];
if(kx>32) throw new AACException("SBR: kx>32: "+kx);
if(kx+M>64) throw new AACException("SBR: kx+M>64: "+(kx+M));
//fill low resolution table
final int minus = N_high&1;
int x = 0;
for(i = 0; i<=N_low; i++) {
x = (i==0) ? 0 : 2*i-minus;
ftRes[LO_RES][i] = ftRes[HI_RES][x];
}
final int noiseBands = header.getNoiseBands(false);
if(noiseBands==0) N_Q = 1;
else N_Q = Math.min(5, Math.max(1, Calculation.findBands(false, noiseBands, kx, k2)));
//fill noise table
for(i = 0; i<=N_Q; i++) {
x = (i==0) ? 0 : x+(N_low-x)/(N_Q+1-i);
ftNoise[i] = ftRes[LO_RES][x];
}
//build table for mapping k to g in HF patching
int j;
for(i = 0; i<64; i++) {
for(j = 0; j<N_Q; j++) {
if((ftNoise[j]<=i)&&(i<ftNoise[j+1])) {
tableMapKToG[i] = j;
break;
}
}
}
}
//fills ftLim and N_L
void calculateLimiterFrequencyTable() {
ftLim[0][0] = ftRes[LO_RES][0]-kx;
ftLim[0][1] = ftRes[LO_RES][N_low]-kx;
N_L[0] = 1;
int j;
//calculate patch borders
final int[] patchBorders = new int[patches+1];
patchBorders[0] = kx;
for(j = 1; j<=patches; j++) {
patchBorders[j] = patchBorders[j-1]+patchNoSubbands[j-1];
}
final int[] limTable = new int[patches+N_low];
int k, limCount;
float octaves;
//fill N_L[i]
for(int i = 1; i<4; i++) {
//set up limTable
System.arraycopy(ftRes[LO_RES], 0, limTable, 0, N_low+1);
System.arraycopy(patchBorders, 1, limTable, N_low+1, patches-1);
Arrays.sort(limTable, 0, patches+N_low); //needed!
j = 1;
limCount = patches+N_low-1;
if(limCount<0) return;
while(j<=limCount) {
octaves = (limTable[j-1]==0) ? 0 : (float) limTable[j]/(float) limTable[j-1];
if(octaves<LIMITER_BANDS_COMPARE[i-1]) {
if(limTable[j]!=limTable[j-1]) {
boolean found = false;
for(k = 0; k<=patches; k++) {
if(limTable[j]==patchBorders[k]) {
found = false;
for(k = 0; k<=patches; k++) {
if(limTable[j-1]==patchBorders[k]) found = true;
}
if(found) {
j++;
continue;
}
else {
//remove (k-1)th element
limTable[j-1] = ftRes[LO_RES][N_low];
Arrays.sort(limTable, 0, patches+N_low);
limCount--;
continue;
}
}
}
}
//remove jth element
limTable[j] = ftRes[LO_RES][N_low];
Arrays.sort(limTable, 0, limCount);
limCount--;
continue;
}
else j++;
}
N_L[i] = limCount;
for(j = 0; j<=limCount; j++) {
ftLim[i][j] = limTable[j]-kx;
}
}
}
private void decodeData(BitStream in, boolean stereo) throws AACException {
if(stereo) decodeChannelPairElement(in);
else decodeSingleChannelElement(in);
//extended data
if(in.readBool()) {
psExtensionRead = false;
int count = in.readBits(4);
if(count==15) count += in.readBits(8);
int bitsLeft = 8*count;
while(bitsLeft>7) {
bitsLeft -= 2;
extensionID = in.readBits(2);
if(extensionID==EXTENSION_ID_PS&&psExtensionRead) extensionID = 3;
bitsLeft -= decodeExtension(in, extensionID);
}
if(bitsLeft>0) in.skipBits(bitsLeft);
}
}
private void decodeSingleChannelElement(BitStream in) throws AACException {
if(in.readBool()) in.skipBits(4); //reserved
cd[0].decodeGrid(in);
cd[0].decodeDTDF(in);
cd[0].decodeInvfMode(in, N_Q);
cd[0].decodeEnvelope(in, this, 0, coupling, header.getAmpRes());
cd[0].decodeNoise(in, this, 0, coupling);
cd[0].decodeSinusoidalCoding(in, N_high);
dequantEnvelopeNoise(0);
}
private void decodeChannelPairElement(BitStream in) throws AACException {
if(in.readBool()) in.skipBits(8); //reserved
final boolean ampRes = header.getAmpRes();
if(coupling = in.readBool()) {
cd[0].decodeGrid(in);
cd[1].copyGrid(cd[0]);
cd[0].decodeDTDF(in);
cd[1].decodeDTDF(in);
cd[0].decodeInvfMode(in, N_Q);
cd[1].copyInvfMode(cd[0], N_Q);
cd[0].decodeEnvelope(in, this, 0, coupling, ampRes);
cd[0].decodeNoise(in, this, 0, coupling);
cd[1].decodeEnvelope(in, this, 1, coupling, ampRes);
cd[1].decodeNoise(in, this, 1, coupling);
}
else {
cd[0].decodeGrid(in);
cd[1].decodeGrid(in);
cd[0].decodeDTDF(in);
cd[1].decodeDTDF(in);
cd[0].decodeInvfMode(in, N_Q);
cd[1].decodeInvfMode(in, N_Q);
cd[0].decodeEnvelope(in, this, 0, coupling, ampRes);
cd[1].decodeEnvelope(in, this, 1, coupling, ampRes);
cd[0].decodeNoise(in, this, 0, coupling);
cd[1].decodeNoise(in, this, 1, coupling);
}
cd[0].decodeSinusoidalCoding(in, N_high);
cd[1].decodeSinusoidalCoding(in, N_high);
dequantEnvelopeNoise(0);
dequantEnvelopeNoise(1);
if(coupling) unmapEnvelopeNoise();
}
private int decodeExtension(BitStream in, int extensionID) throws AACException {
int ret;
switch(extensionID) {
case EXTENSION_ID_PS:
if(!psExtensionRead) {
psExtensionRead = true;
if(ps==null) ps = new PS();
ret = ps.decode(in);
if(!psUsed&&ps.hasHeader()) psUsed = true;
}
else ret = 0;
break;
default:
extensionData = in.readBits(6);
ret = 6;
break;
}
return ret;
}
/* ================== dequant/unmap ================== */
//dequantizes envelope and noise values
private void dequantEnvelopeNoise(int ch) {
final ChannelData c = cd[ch];
if(!coupling) {
final int amp = c.ampRes ? 0 : 1;
int exp, i, j;
for(i = 0; i<c.L_E; i++) {
for(j = 0; j<n[c.f[i] ? 1 : 0]; j++) {
exp = c.E[j][i]>>amp;
if((exp<0)||(exp>=64)) c.E_orig[j][i] = 0;
else {
c.E_orig[j][i] = ENVELOPE_DEQUANT_TABLE[exp];
if(amp!=0&&(c.E[j][i]&1)==1) c.E_orig[j][i] *= SQRT2;
}
}
}
for(i = 0; i<c.L_Q; i++) {
for(j = 0; j<N_Q; j++) {
c.Q_div[j][i] = calculateQDiv(ch, j, i);
c.Q_div2[j][i] = calculateQDiv2(ch, j, i);
}
}
}
}
//unmaps envelope and noise values
private void unmapEnvelopeNoise() {
final int amp0 = cd[0].ampRes ? 0 : 1;
final int amp1 = cd[1].ampRes ? 0 : 1;
int i, j, exp0, exp1;
float tmp;
for(i = 0; i<cd[0].L_E; i++) {
for(j = 0; j<n[cd[0].f[i] ? 1 : 0]; j++) {
exp0 = (cd[0].E[j][i]>>amp0)+1;
exp1 = (cd[1].E[j][i]>>amp1);
if((exp0<0)||(exp0>=64)||(exp1<0)||(exp1>24)) {
cd[1].E_orig[j][i] = 0;
cd[0].E_orig[j][i] = 0;
}
else {
tmp = ENVELOPE_DEQUANT_TABLE[exp0];
if(amp0!=0&&(cd[0].E[j][i]&1)==1) tmp *= SQRT2;
//panning
cd[0].E_orig[j][i] = tmp*ENVELOPE_PANNING_TABLE[exp1];
cd[1].E_orig[j][i] = tmp*ENVELOPE_PANNING_TABLE[24-exp1];
}
}
}
for(i = 0; i<cd[0].L_Q; i++) {
for(j = 0; j<N_Q; j++) {
cd[0].Q_div[j][i] = calculateQDiv(0, j, i);
cd[1].Q_div[j][i] = calculateQDiv(1, j, i);
cd[0].Q_div2[j][i] = calculateQDiv2(0, j, i);
cd[1].Q_div2[j][i] = calculateQDiv2(1, j, i);
}
}
}
//calculates 1/(1+Q), [0..1]
private float calculateQDiv(int ch, int m, int l) {
if(coupling) {
if((cd[0].Q[m][l]<0||cd[0].Q[m][l]>30)||(cd[1].Q[m][l]<0||cd[1].Q[m][l]>24)) return 0;
else {
if(ch==0) return Q_DIV_TABLE_LEFT[cd[0].Q[m][l]][cd[1].Q[m][l]>>1];
else return Q_DIV_TABLE_RIGHT[cd[0].Q[m][l]][cd[1].Q[m][l]>>1];
}
}
else {
if(cd[ch].Q[m][l]<0||cd[ch].Q[m][l]>30) return 0;
else return Q_DIV_TABLE[cd[ch].Q[m][l]];
}
}
//calculates Q/(1+Q), [0..1]
private float calculateQDiv2(int ch, int m, int l) {
if(coupling) {
if((cd[0].Q[m][l]<0||cd[0].Q[m][l]>30)||(cd[1].Q[m][l]<0||cd[1].Q[m][l]>24)) return 0;
else {
if(ch==0) return Q_DIV2_TABLE_LEFT[cd[0].Q[m][l]][cd[1].Q[m][l]>>1];
else return Q_DIV2_TABLE_RIGHT[cd[0].Q[m][l]][cd[1].Q[m][l]>>1];
}
}
else {
if(cd[ch].Q[m][l]<0||cd[ch].Q[m][l]>30) return 0;
else return Q_DIV2_TABLE[cd[ch].Q[m][l]];
}
}
/* ================== processing ================== */
public boolean isPSUsed() {
return psUsed;
}
public void processSingleFrame(float[] channel, boolean downSampled) {
if(buffer==null) buffer = new float[TIME_SLOTS_RATE][64][2];
boolean process = true;
//can't decode before the first header
if(!header.isDecoded()) {
//don't process just upsample
process = false;
//re-activate reset for next frame
//if(reset) header.getStartFrequency(true) = -1;
}
processChannel(channel, buffer, 0, process);
//subband synthesis
if(downSampled) qmfs[0].performSynthesis32(buffer, channel, TIME_SLOTS_RATE);
else qmfs[0].performSynthesis64(buffer, channel, TIME_SLOTS_RATE);
if(header.isDecoded()) savePreviousData(0);
cd[0].saveMatrix();
}
public void processCoupleFrame(float[] left, float[] right, boolean downSampled) {
if(buffer==null) buffer = new float[TIME_SLOTS_RATE][64][2];
boolean process = true;
if(!header.isDecoded()) {
//don't process just upsample
process = false;
//re-activate reset for next frame
//if(reset) startFrequencyPrev = -1;
}
processChannel(left, buffer, 0, process);
if(downSampled) qmfs[0].performSynthesis32(buffer, left, TIME_SLOTS_RATE);
else qmfs[0].performSynthesis64(buffer, left, TIME_SLOTS_RATE);
if(qmfs[1]==null) qmfs[1] = new QMFSynthesis(filterBank, downSampled ? 32 : 64);
processChannel(right, buffer, 1, process);
if(downSampled) qmfs[1].performSynthesis32(buffer, right, TIME_SLOTS_RATE);
else qmfs[1].performSynthesis64(buffer, right, TIME_SLOTS_RATE);
if(header.isDecoded()) {
savePreviousData(0);
savePreviousData(1);
}
cd[0].saveMatrix();
cd[1].saveMatrix();
}
public void processSingleFramePS(float[] left, float[] right, boolean downSampled) throws AACException {
if(bufLeftPS==null) {
bufLeftPS = new float[38][64][2];
bufRightPS = new float[38][64][2];
}
boolean process = true;
if(!header.isDecoded()) {
//don't process just upsample
process = false;
//re-activate reset for next frame
//startFrequencyPrev = -1;
}
processChannel(left, bufLeftPS, 0, process);
//copy extra data for PS
int k;
for(int l = TIME_SLOTS_RATE; l<TIME_SLOTS_RATE+6; l++) {
for(k = 0; k<5; k++) {
bufLeftPS[l][k][0] = cd[0].Xsbr[T_HFADJ+l][k][0];
bufLeftPS[l][k][1] = cd[0].Xsbr[T_HFADJ+l][k][1];
}
}
//perform parametric stereo
ps.process(bufLeftPS, bufRightPS, kx+M);
if(qmfs[1]==null) qmfs[1] = new QMFSynthesis(filterBank, downSampled ? 32 : 64);
//subband synthesis
if(downSampled) {
qmfs[0].performSynthesis32(bufLeftPS, left, TIME_SLOTS_RATE);
qmfs[1].performSynthesis32(bufRightPS, right, TIME_SLOTS_RATE);
}
else {
qmfs[0].performSynthesis64(bufLeftPS, left, TIME_SLOTS_RATE);
qmfs[1].performSynthesis64(bufRightPS, right, TIME_SLOTS_RATE);
}
if(header.isDecoded()) savePreviousData(0);
cd[0].saveMatrix();
}
private void processChannel(float[] channel, float[][][] X, int ch, boolean process) {
int i, j;
//subband analysis
final int param = process ? kx : 32;
if(qmfa[ch]==null) qmfa[ch] = new QMFAnalysis(filterBank, 32);
qmfa[ch].performAnalysis32(channel, cd[ch].Xsbr, T_HFGEN, param, TIME_SLOTS_RATE);
if(process) {
hfGen.process(cd[ch].Xsbr, cd[ch].Xsbr, ch, cd[ch]);
hfAdj.process(cd[ch].Xsbr, cd[ch], header);
int kx_band, M_band;
for(i = 0; i<TIME_SLOTS_RATE; i++) {
if(i<cd[ch].t_E[0]) {
kx_band = kxPrev;
M_band = Mprev;
}
else {
kx_band = kx;
M_band = M;
}
for(j = 0; j<kx_band; j++) {
X[i][j][0] = cd[ch].Xsbr[i+T_HFADJ][j][0];
X[i][j][1] = cd[ch].Xsbr[i+T_HFADJ][j][1];
}
for(j = kx_band; j<kx_band+M_band; j++) {
X[i][j][0] = cd[ch].Xsbr[i+T_HFADJ][j][0];
X[i][j][1] = cd[ch].Xsbr[i+T_HFADJ][j][1];
}
for(j = kx_band+M_band; j<64; j++) {
X[i][j][0] = 0;
X[i][j][1] = 0;
}
}
}
else {
for(i = 0; i<TIME_SLOTS_RATE; i++) {
for(j = 0; j<32; j++) {
X[i][j][0] = cd[ch].Xsbr[i+T_HFADJ][j][0];
X[i][j][1] = cd[ch].Xsbr[i+T_HFADJ][j][1];
}
for(j = 32; j<64; j++) {
X[i][j][0] = 0;
X[i][j][1] = 0;
}
}
}
}
private void savePreviousData(int ch) {
//save data for next frame
kxPrev = kx;
Mprev = M;
cd[ch].savePreviousData();
}
}