/* * 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; import net.sourceforge.jaad.aac.AACException; import net.sourceforge.jaad.aac.SampleFrequency; //stores and calculates frequency tables, TODO: make arrays final with max sizes class FrequencyTables implements SBRConstants { private static final int[] MFT_START_MIN = {7, 7, 10, 11, 12, 16, 16, 17, 24}; private static final int[] MFT_STOP_MIN = {13, 15, 20, 21, 23, 32, 32, 35, 48}; private static final int[] MFT_SF_OFFSETS = {5, 5, 4, 4, 4, 3, 2, 1, 0}; private static final int[][] MFT_START_OFFSETS = { {-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}, //16000 {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13}, //22050 {-5, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16}, //24000 {-6, -4, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16}, //32000 {-4, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16, 20}, //44100-64000 {-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 11, 13, 16, 20, 24} //>64000 }; private static final int[][] MFT_STOP_OFFSETS = { {2, 4, 6, 8, 11, 14, 18, 22, 26, 31, 37, 44, 51}, {2, 4, 6, 8, 11, 14, 18, 22, 26, 31, 36, 42, 49}, {2, 4, 6, 9, 11, 14, 17, 21, 25, 29, 34, 39, 44}, {2, 4, 6, 9, 11, 14, 17, 21, 24, 28, 33, 38, 43}, {2, 4, 6, 9, 11, 14, 17, 20, 24, 28, 32, 36, 41}, {2, 4, 6, 8, 10, 12, 14, 17, 20, 23, 26, 29, 32}, {2, 4, 6, 8, 10, 12, 14, 17, 20, 23, 26, 29, 32}, {2, 3, 5, 7, 9, 11, 13, 16, 18, 21, 23, 26, 29}, {1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 15, 16} }; private static final int[] MFT_INPUT1 = {12, 10, 8}; private static final float[] MFT_INPUT2 = {1.0f, 1.3f}; private static final float[] LIM_BANDS_PER_OCTAVE_POW = { 1.32715174233856803909f, //2^(0.49/1.2) 1.18509277094158210129f, //2^(0.49/2) 1.11987160404675912501f //2^(0.49/3) }; private static final float GOAL_SB_FACTOR = 2.048E6f; int k0, k2; //TODO: private //master private int[] mft; private int nMaster; //frequency tables private final int[][] fTable; private final int[] n; private int m, mPrev, kx, kxPrev; //noise table private int[] fNoise; int nq; //TODO: private //limiter table private int[] fLim; private int nl; //patches private int patchCount; private final int[] patchSubbands, patchStartSubband; private int[] patchBorders; FrequencyTables() { n = new int[2]; fTable = new int[2][]; patchSubbands = new int[MAX_PATCHES]; patchStartSubband = new int[MAX_PATCHES]; kx = 0; kxPrev = 0; m = 0; mPrev = 0; } void calculate(SBRHeader header, int sampleRate) throws AACException { calculateMFT(header, sampleRate); calculateFrequencyTables(header); calculateNoiseTable(header); calculatePatches(sampleRate); calculateLimiterTable(header); } private void calculateMFT(SBRHeader header, int sampleRate) throws AACException { //lower border k0 final int sfIndex = SampleFrequency.forFrequency(sampleRate).getIndex(); final int sfOff = MFT_SF_OFFSETS[sfIndex]; k0 = MFT_START_MIN[sfIndex]+MFT_START_OFFSETS[sfOff][header.getStartFrequency(false)]; //higher border k2 final int stop = header.getStopFrequency(false); final int x; if(stop==15) x = 3*k0; else if(stop==14) x = 2*k0; else x = MFT_STOP_MIN[sfIndex]+MFT_STOP_OFFSETS[sfOff][header.getStopFrequency(false)-1]; k2 = Math.min(MAX_BANDS, x); if(k0>=k2) throw new AACException("SBR: MFT borders out of range: lower="+k0+", higher="+k2); //check requirement (4.6.18.3.6): final int max; if(sampleRate==44100) max = 35; else if(sampleRate>=48000) max = 32; else max = 48; if((k2-k0)>max) throw new AACException("SBR: too many subbands: "+(k2-k0)+", maximum number for samplerate "+sampleRate+": "+max); //MFT calculation if(header.getFrequencyScale(false)==0) calculateMFT1(header, k0, k2); else calculateMFT2(header, k0, k2); //check requirement (4.6.18.3.6): if(header.getXOverBand(false)>=nMaster) throw new AACException("SBR: illegal length of master frequency table: "+nMaster+", xOverBand: "+header.getXOverBand(false)); } //MFT calculation if frequencyScale==0 private void calculateMFT1(SBRHeader header, int k0, int k2) throws AACException { final int dk; if(header.isAlterScale(false)) { dk = 2; nMaster = 2*Math.round((float) (k2-k0)/4.0f); } else { dk = 1; nMaster = 2*(int) ((float) (k2-k0)/2.0f); } //check requirement (4.6.18.6.3): if(nMaster<=0) throw new AACException("SBR: illegal number of bands for master frequency table: "+nMaster); final int k2Achieved = k0+nMaster*dk; int k2Diff = k2-k2Achieved; final int[] vDk = new int[nMaster]; Arrays.fill(vDk, dk); if(k2Diff!=0) { final int incr = (k2Diff>0) ? -1 : 1; int k = (k2Diff>0) ? nMaster-1 : 0; while(k2Diff!=0) { vDk[k] -= incr; k += incr; k2Diff += incr; } } mft = new int[nMaster+1]; mft[0] = k0; for(int i = 1; i<=nMaster; i++) { mft[i] = mft[i-1]+vDk[i-1]; } } //MFT calculation if frequencyScale>0 private void calculateMFT2(SBRHeader header, int k0, int k2) throws AACException { final int bands = MFT_INPUT1[header.getFrequencyScale(false)-1]; final float warp = MFT_INPUT2[header.isAlterScale(false) ? 1 : 0]; float div1 = (float) k2/(float) k0; final boolean twoRegions; final int k1; if(div1>2.2449) { twoRegions = true; k1 = 2*k0; } else { twoRegions = false; k1 = k2; } final float div2 = (float) k1/(float) k0; float log = (float) Math.log(div2)/(float) (2*LOG2); final int bandCount0 = 2*Math.round(bands*log); //check requirement (4.6.18.6.3): if(bandCount0<=0) throw new AACException("SBR: illegal band count for master frequency table: "+bandCount0); final int[] vDk0 = new int[bandCount0]; float pow1, pow2; for(int i = 0; i<bandCount0; i++) { pow1 = (float) Math.pow(div2, (float) (i+1)/bandCount0); pow2 = (float) Math.pow(div2, (float) i/bandCount0); vDk0[i] = Math.round(k0*pow1)-Math.round(k0*pow2); //check requirement (4.6.18.6.3): if(vDk0[i]<=0) throw new AACException("SBR: illegal value in master frequency table: "+vDk0[i]); } Arrays.sort(vDk0); final int[] vk0 = new int[bandCount0+1]; vk0[0] = k0; for(int i = 1; i<=bandCount0; i++) { vk0[i] = vk0[i-1]+vDk0[i-1]; } if(twoRegions) { div1 = (float) k2/(float) k1; log = (float) Math.log(div1); final int bandCount1 = 2*(int) Math.round(bands*log/(2*LOG2*warp)); final int[] vDk1 = new int[bandCount1]; int min = -1; for(int i = 0; i<bandCount1; i++) { pow1 = (float) Math.pow(div1, (float) (i+1)/bandCount1); pow2 = (float) Math.pow(div1, (float) i/bandCount1); vDk1[i] = Math.round(k1*pow1)-Math.round(k1*pow2); if(min<0||vDk1[i]<min) min = vDk1[i]; //check requirement (4.6.18.6.3): else if(vDk1[i]<=0) throw new AACException("SBR: illegal value in master frequency table: "+vDk1[i]); } if(min<vDk0[vDk0.length-1]) { Arrays.sort(vDk1); int change = vDk0[vDk0.length-1]-vDk1[0]; final int x = (int) (vDk1[bandCount1-1]-(float) vDk1[0]/2.0); if(change>x) change = x; vDk1[0] += change; vDk1[bandCount1-1] -= change; } Arrays.sort(vDk1); final int[] vk1 = new int[bandCount1+1]; vk1[0] = k1; for(int i = 1; i<=bandCount1; i++) { vk1[i] = vk1[i-1]+vDk1[i-1]; } nMaster = bandCount0+bandCount1; mft = new int[nMaster+1]; System.arraycopy(vk0, 0, mft, 0, bandCount0+1); System.arraycopy(vk1, 1, mft, bandCount0+1, bandCount1); } else { nMaster = bandCount0; mft = new int[nMaster+1]; System.arraycopy(vk0, 0, mft, 0, nMaster+1); } } private void calculateFrequencyTables(SBRHeader header) throws AACException { final int xover = header.getXOverBand(false); n[HIGH] = getNMaster()-xover; fTable[HIGH] = new int[n[HIGH]+1]; System.arraycopy(mft, xover, fTable[HIGH], 0, n[HIGH]+1); kxPrev = kx; kx = fTable[HIGH][0]; mPrev = m; m = fTable[HIGH][getN(HIGH)]-kx; //check requirements (4.6.18.3.6): if(kx>32) throw new AACException("SBR: start frequency border out of range: "+kx); if((kx+m)>64) throw new AACException("SBR: stop frequency border out of range: "+(kx+m)); final int half = (int) ((float) n[HIGH]/2.0); n[LOW] = half+(n[HIGH]-2*half); fTable[LOW] = new int[n[LOW]+1]; fTable[LOW][0] = fTable[HIGH][0]; final int div = n[HIGH]&1; for(int i = 1; i<=n[LOW]; i++) { fTable[LOW][i] = fTable[HIGH][2*i-div]; } } private void calculateNoiseTable(SBRHeader header) throws AACException { final float log = (float) Math.log((float) k2/(float) kx)/(float) LOG2; final int x = Math.round(header.getNoiseBands(false)*log); nq = Math.max(1, x); //check requirement (4.6.18.6.3): if(nq>5) throw new AACException("SBR: too many noise floor scalefactors: "+nq); fNoise = new int[nq+1]; fNoise[0] = fTable[LOW][0]; int i = 0; for(int k = 1; k<=nq; k++) { i += (int) ((float) (n[LOW]-i)/(float) (nq+1-k)); fNoise[k] = fTable[LOW][i]; } } private void calculatePatches(int sampleRate) throws AACException { //patch construction (flowchart 4.46, p231) int msb = k0; int usb = kx; patchCount = 0; int goalSb = Math.round(GOAL_SB_FACTOR/(float) sampleRate); //TODO: replace with table int k; if(goalSb<kx+m) { k = 0; for(int i = 0; mft[i]<goalSb; i++) { k = i+1; } } else k = nMaster; int sb, j, odd; do { j = k+1; do { j--; sb = mft[j]; odd = (sb-2+k0)&1; } while(sb>(k0-1+msb-odd)); patchSubbands[patchCount] = Math.max(sb-usb, 0); patchStartSubband[patchCount] = k0-odd-patchSubbands[patchCount]; if(patchSubbands[patchCount]>0) { usb = sb; msb = sb; patchCount++; } else msb = kx; if(mft[k]-sb<3) k = nMaster; } while(sb!=(kx+m)); if(patchSubbands[patchCount-1]<3&&patchCount>1) patchCount--; //check requirement (4.6.18.6.3): if(patchCount>5) throw new AACException("SBR: too many patches: "+patchCount); } private void calculateLimiterTable(SBRHeader header) throws AACException { //calculation of fTableLim (figure 4.40, p.213) final int bands = header.getLimiterBands(); if(bands==0) { fLim = new int[]{fTable[LOW][0], fTable[LOW][n[LOW]]}; nl = 1; patchBorders = new int[0]; } else { final float limBandsPerOctaveWarped = LIM_BANDS_PER_OCTAVE_POW[header.getLimiterBands()-1]; patchBorders = new int[patchCount+1]; patchBorders[0] = kx; for(int i = 1; i<=patchCount; i++) { patchBorders[i] = patchBorders[i-1]+patchSubbands[i-1]; } int[] limTable = new int[n[LOW]+patchCount]; System.arraycopy(fTable[LOW], 0, limTable, 0, n[LOW]+1); if(patchCount>1) System.arraycopy(patchBorders, 1, limTable, n[LOW]+1, patchCount-1); Arrays.sort(limTable); int in = 1; int out = 0; int lims = n[LOW]+patchCount-1; while(out<lims) { if(limTable[in]>=limTable[out]*limBandsPerOctaveWarped) { limTable[++out] = limTable[in++]; } else if(limTable[in]==limTable[out] ||!inArray(patchBorders, limTable[in])) { in++; lims--; } else if(!inArray(patchBorders, limTable[out])) { limTable[out] = limTable[in++]; lims--; } else { limTable[++out] = limTable[in++]; } } fLim = new int[lims+1]; System.arraycopy(limTable, 0, fLim, 0, lims+1); nl = lims; } } private boolean inArray(int[] a, int x) { boolean found = false; for(int i = 0; !found&&i<a.length; i++) { if(a[i]==x) found = true; } return found; } //lower MFT border: k0 public int getK0() { return k0; } //higher MFT border: k2 public int getK2() { return k2; } //the master frequency table: fMaster public int[] getMFT() { return mft; } //bands in master frequency table: Nmaster public int getNMaster() { return nMaster; } //the frequency tables: fTableHigh, fTableLow public int[] getFrequencyTable(int i) { return fTable[i]; } //bands in frequency tables: Nhigh, Nlow public int getN(int i) { return n[i]; } //both ns public int[] getN() { return n; } //first subband in fTableHigh: kx public int getKx(boolean previous) { return previous ? kxPrev : kx; } //number of SBR subbands: M public int getM(boolean previous) { return previous ? mPrev : m; } //the noise floor frequency table: fTableNoise public int[] getNoiseTable() { return fNoise; } //bands in noise table: Nq public int getNq() { return nq; } public int getPatchCount() { return patchCount; } public int[] getPatchSubbands() { return patchSubbands; } public int[] getPatchStartSubband() { return patchStartSubband; } public int[] getPatchBorders() { return patchBorders; } //the limiter frequency band table: fTableLim public int[] getLimiterTable() { return fLim; } //bands in limiter table: Nl public int getNl() { return nl; } }