/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.media.codec.atrac3plus;
import static java.lang.Math.abs;
import static jpcsp.media.codec.atrac3plus.Atrac3plusData1.atrac3p_spectra_tabs;
import static jpcsp.media.codec.atrac3plus.Atrac3plusData2.atrac3p_ct_restricted_to_full;
import static jpcsp.media.codec.atrac3plus.Atrac3plusData2.atrac3p_qu_num_to_seg;
import static jpcsp.media.codec.atrac3plus.Atrac3plusData2.atrac3p_qu_to_subband;
import static jpcsp.media.codec.atrac3plus.Atrac3plusData2.atrac3p_sf_shapes;
import static jpcsp.media.codec.atrac3plus.Atrac3plusData2.atrac3p_subband_to_num_powgrps;
import static jpcsp.media.codec.atrac3plus.Atrac3plusData2.atrac3p_wl_shapes;
import static jpcsp.media.codec.atrac3plus.Atrac3plusData2.atrac3p_wl_weights;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDecoder.AT3P_ERROR;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDecoder.ATRAC3P_FRAME_SAMPLES;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDecoder.ATRAC3P_POWER_COMP_OFF;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDecoder.ATRAC3P_SUBBANDS;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDecoder.ATRAC3P_SUBBAND_SAMPLES;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDecoder.CH_UNIT_STEREO;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDsp.ff_atrac3p_mant_tab;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDsp.ff_atrac3p_qu_to_spec_pos;
import static jpcsp.media.codec.atrac3plus.Atrac3plusDsp.ff_atrac3p_sf_tab;
import static jpcsp.media.codec.util.CodecUtils.avLog2;
import static jpcsp.util.Utilities.signExtend;
import java.util.Arrays;
import jpcsp.media.codec.atrac3plus.Atrac3plusData1.Atrac3pSpecCodeTab;
import jpcsp.media.codec.util.BitReader;
import jpcsp.media.codec.util.VLC;
import org.apache.log4j.Logger;
/*
* Based on the FFmpeg version from Maxim Poliakovski.
* All credits go to him.
*/
public class ChannelUnit {
private static Logger log = Atrac3plusDecoder.log;
public ChannelUnitContext ctx = new ChannelUnitContext();
private BitReader br;
private Atrac3plusDsp dsp;
private int numChannels;
private static final VLC wl_vlc_tabs[] = new VLC[4];
private static final VLC sf_vlc_tabs[] = new VLC[8];
private static final VLC ct_vlc_tabs[] = new VLC[4];
private static final VLC spec_vlc_tabs[] = new VLC[112];
private static final VLC gain_vlc_tabs[] = new VLC[11];
private static final VLC tone_vlc_tabs[] = new VLC[7];
private static final int wl_nb_bits[] = { 2, 3, 5, 5 };
private static final int wl_nb_codes[] = { 3, 5, 8, 8 };
private static final int wl_bits[][] = {
Atrac3plusData2.atrac3p_wl_huff_bits1, Atrac3plusData2.atrac3p_wl_huff_bits2,
Atrac3plusData2.atrac3p_wl_huff_bits3, Atrac3plusData2.atrac3p_wl_huff_bits4
};
private static final int wl_codes[][] = {
Atrac3plusData2.atrac3p_wl_huff_code1, Atrac3plusData2.atrac3p_wl_huff_code2,
Atrac3plusData2.atrac3p_wl_huff_code3, Atrac3plusData2.atrac3p_wl_huff_code4
};
private static final int wl_xlats[][] = {
Atrac3plusData2.atrac3p_wl_huff_xlat1, Atrac3plusData2.atrac3p_wl_huff_xlat2, null, null
};
private static final int ct_nb_bits[] = { 3, 4, 4, 4 };
private static final int ct_nb_codes[] = { 4, 8, 8, 8 };
private static final int ct_bits[][] = {
Atrac3plusData2.atrac3p_ct_huff_bits1, Atrac3plusData2.atrac3p_ct_huff_bits2,
Atrac3plusData2.atrac3p_ct_huff_bits2, Atrac3plusData2.atrac3p_ct_huff_bits3
};
private static final int ct_codes[][] = {
Atrac3plusData2.atrac3p_ct_huff_code1, Atrac3plusData2.atrac3p_ct_huff_code2,
Atrac3plusData2.atrac3p_ct_huff_code2, Atrac3plusData2.atrac3p_ct_huff_code3
};
private static final int ct_xlats[][] = {
null, null, Atrac3plusData2.atrac3p_ct_huff_xlat1, null
};
private static final int sf_nb_bits[] = { 9, 9, 9, 9, 6, 6, 7, 7 };
private static final int sf_nb_codes[] = { 64, 64, 64, 64, 16, 16, 16, 16 };
private static final int sf_bits[][] = {
Atrac3plusData2.atrac3p_sf_huff_bits1, Atrac3plusData2.atrac3p_sf_huff_bits1, Atrac3plusData2.atrac3p_sf_huff_bits2,
Atrac3plusData2.atrac3p_sf_huff_bits3, Atrac3plusData2.atrac3p_sf_huff_bits4, Atrac3plusData2.atrac3p_sf_huff_bits4,
Atrac3plusData2.atrac3p_sf_huff_bits5, Atrac3plusData2.atrac3p_sf_huff_bits6
};
private static final int sf_codes[][] = {
Atrac3plusData2.atrac3p_sf_huff_code1, Atrac3plusData2.atrac3p_sf_huff_code1, Atrac3plusData2.atrac3p_sf_huff_code2,
Atrac3plusData2.atrac3p_sf_huff_code3, Atrac3plusData2.atrac3p_sf_huff_code4, Atrac3plusData2.atrac3p_sf_huff_code4,
Atrac3plusData2.atrac3p_sf_huff_code5, Atrac3plusData2.atrac3p_sf_huff_code6
};
private static final int sf_xlats[][] = {
Atrac3plusData2.atrac3p_sf_huff_xlat1, Atrac3plusData2.atrac3p_sf_huff_xlat2, null, null,
Atrac3plusData2.atrac3p_sf_huff_xlat4, Atrac3plusData2.atrac3p_sf_huff_xlat5, null, null
};
private static final int gain_cbs[][] = {
Atrac3plusData2.atrac3p_huff_gain_npoints1_cb, Atrac3plusData2.atrac3p_huff_gain_npoints1_cb,
Atrac3plusData2.atrac3p_huff_gain_lev1_cb, Atrac3plusData2.atrac3p_huff_gain_lev2_cb,
Atrac3plusData2.atrac3p_huff_gain_lev3_cb, Atrac3plusData2.atrac3p_huff_gain_lev4_cb,
Atrac3plusData2.atrac3p_huff_gain_loc3_cb, Atrac3plusData2.atrac3p_huff_gain_loc1_cb,
Atrac3plusData2.atrac3p_huff_gain_loc4_cb, Atrac3plusData2.atrac3p_huff_gain_loc2_cb,
Atrac3plusData2.atrac3p_huff_gain_loc5_cb
};
private static final int gain_xlats[][] = {
null, Atrac3plusData2.atrac3p_huff_gain_npoints2_xlat, Atrac3plusData2.atrac3p_huff_gain_lev1_xlat,
Atrac3plusData2.atrac3p_huff_gain_lev2_xlat, Atrac3plusData2.atrac3p_huff_gain_lev3_xlat,
Atrac3plusData2.atrac3p_huff_gain_lev4_xlat, Atrac3plusData2.atrac3p_huff_gain_loc3_xlat,
Atrac3plusData2.atrac3p_huff_gain_loc1_xlat, Atrac3plusData2.atrac3p_huff_gain_loc4_xlat,
Atrac3plusData2.atrac3p_huff_gain_loc2_xlat, Atrac3plusData2.atrac3p_huff_gain_loc5_xlat
};
private static final int tone_cbs[][] = {
Atrac3plusData2.atrac3p_huff_tonebands_cb, Atrac3plusData2.atrac3p_huff_numwavs1_cb,
Atrac3plusData2.atrac3p_huff_numwavs2_cb, Atrac3plusData2.atrac3p_huff_wav_ampsf1_cb,
Atrac3plusData2.atrac3p_huff_wav_ampsf2_cb, Atrac3plusData2.atrac3p_huff_wav_ampsf3_cb,
Atrac3plusData2.atrac3p_huff_freq_cb
};
private static final int tone_xlats[][] = {
null, null, Atrac3plusData2.atrac3p_huff_numwavs2_xlat, Atrac3plusData2.atrac3p_huff_wav_ampsf1_xlat,
Atrac3plusData2.atrac3p_huff_wav_ampsf2_xlat, Atrac3plusData2.atrac3p_huff_wav_ampsf3_xlat,
Atrac3plusData2.atrac3p_huff_freq_xlat
};
public static void init() {
for (int i = 0; i < 4; i++) {
wl_vlc_tabs[i] = new VLC();
wl_vlc_tabs[i].initVLCSparse(wl_nb_bits[i], wl_nb_codes[i], wl_bits[i], wl_codes[i], wl_xlats[i]);
ct_vlc_tabs[i] = new VLC();
ct_vlc_tabs[i].initVLCSparse(ct_nb_bits[i], ct_nb_codes[i], ct_bits[i], ct_codes[i], ct_xlats[i]);
}
for (int i = 0; i < 8; i++) {
sf_vlc_tabs[i] = new VLC();
sf_vlc_tabs[i].initVLCSparse(sf_nb_bits[i], sf_nb_codes[i], sf_bits[i], sf_codes[i], sf_xlats[i]);
}
/* build huffman tables for spectrum decoding */
for (int i = 0; i < 112; i++) {
if (atrac3p_spectra_tabs[i].cb != null) {
spec_vlc_tabs[i] = new VLC();
buildCanonicalHuff(atrac3p_spectra_tabs[i].cb, atrac3p_spectra_tabs[i].xlat, spec_vlc_tabs[i]);
}
}
/* build huffman tables for gain data decoding */
for (int i = 0; i < 11; i++) {
gain_vlc_tabs[i] = new VLC();
buildCanonicalHuff(gain_cbs[i], gain_xlats[i], gain_vlc_tabs[i]);
}
/* build huffman tables for tone decoding */
for (int i = 0; i < 7; i++) {
tone_vlc_tabs[i] = new VLC();
buildCanonicalHuff(tone_cbs[i], tone_xlats[i], tone_vlc_tabs[i]);
}
}
private static int buildCanonicalHuff(int[] cb, int[] xlat, VLC vlc) {
int codes[] = new int[256];
int bits[] = new int[256];
int cbIndex = 0;
int index = 0;
int code = 0;
int minLen = cb[cbIndex++]; // get shortest codeword length
int maxLen = cb[cbIndex++]; // get longest codeword length
for (int b = minLen; b <= maxLen; b++) {
for (int i = cb[cbIndex++]; i > 0; i--) {
bits[index] = b;
codes[index] = code++;
index++;
}
code <<= 1;
}
return vlc.initVLCSparse(maxLen, index, bits, codes, xlat);
}
public void setBitReader(BitReader br) {
this.br = br;
}
public void setDsp(Atrac3plusDsp dsp) {
this.dsp = dsp;
}
public void setNumChannels(int numChannels) {
this.numChannels = numChannels;
}
public int decode() {
int ret;
ctx.numQuantUnits = br.read(5) + 1;
if (ctx.numQuantUnits > 28 && ctx.numQuantUnits < 32) {
log.error(String.format("Invalid number of quantization units: %d", ctx.numQuantUnits));
return AT3P_ERROR;
}
ctx.muteFlag = br.readBool();
ret = decodeQuantWordlen();
if (ret < 0) {
return ret;
}
ctx.numSubbands = atrac3p_qu_to_subband[ctx.numQuantUnits - 1] + 1;
ctx.numCodedSubbands = ctx.usedQuantUnits > 0 ? atrac3p_qu_to_subband[ctx.usedQuantUnits - 1] + 1 : 0;
ret = decodeScaleFactors();
if (ret < 0) {
return ret;
}
ret = decodeCodeTableIndexes();
if (ret < 0) {
return ret;
}
decodeSpectrum();
if (numChannels == 2) {
getSubbandFlags(ctx.swapChannels, ctx.numCodedSubbands);
getSubbandFlags(ctx.negateCoeffs, ctx.numCodedSubbands);
}
decodeWindowShape();
ret = decodeGaincData();
if (ret < 0) {
return ret;
}
ret = decodeTonesInfo();
if (ret < 0) {
return ret;
}
ctx.noisePresent = br.readBool();
if (ctx.noisePresent) {
ctx.noiseLevelIndex = br.read(4);
ctx.noiseTableIndex = br.read(4);
}
return 0;
}
/**
* Decode number of coded quantization units.
*
* @param[in,out] chan ptr to the channel parameters
* @return result code: 0 = OK, otherwise - error code
*/
private int numCodedUnits(Channel chan) {
chan.fillMode = br.read(2);
if (chan.fillMode == 0) {
chan.numCodedVals = ctx.numQuantUnits;
} else {
chan.numCodedVals = br.read(5);
if (chan.numCodedVals > ctx.numQuantUnits) {
log.error(String.format("Invalid number of transmitted units"));
return AT3P_ERROR;
}
if (chan.fillMode == 3) {
chan.splitPoint = br.read(2) + (chan.chNum << 1) + 1;
}
}
return 0;
}
private int getDelta(int deltaBits) {
return deltaBits <= 0 ? 0 : br.read(deltaBits);
}
/**
* Unpack vector quantization tables.
*
* @param[in] start_val start value for the unpacked table
* @param[in] shape_vec ptr to table to unpack
* @param[out] dst ptr to output array
* @param[in] num_values number of values to unpack
*/
private void unpackVqShape(int startVal, int[] shapeVec, int[] dst, int numValues) {
if (numValues > 0) {
dst[0] = startVal;
dst[1] = startVal;
dst[2] = startVal;
for (int i = 3; i < numValues; i++) {
dst[i] = startVal - shapeVec[atrac3p_qu_num_to_seg[i] - 1];
}
}
}
private void unpackSfVqShape(int[] dst, int numValues) {
int startVal = br.read(6);
unpackVqShape(startVal, atrac3p_sf_shapes[br.read(6)], dst, numValues);
}
/**
* Add weighting coefficients to the decoded word-length information.
*
* @param[in,out] chan ptr to the channel parameters
* @param[in] wtab_idx index of the table of weights
* @return result code: 0 = OK, otherwise - error code
*/
private int addWordlenWeights(Channel chan, int weightIdx) {
int[] weigthsTab = atrac3p_wl_weights[chan.chNum * 3 + weightIdx - 1];
for (int i = 0; i < ctx.numQuantUnits; i++) {
chan.quWordlen[i] += weigthsTab[i];
if (chan.quWordlen[i] < 0 || chan.quWordlen[i] > 7) {
log.error(String.format("WL index out of range pos=%d, val=%d", i, chan.quWordlen[i]));
return AT3P_ERROR;
}
}
return 0;
}
/**
* Decode word length for each quantization unit of a channel.
*
* @param[in] chNum channel to process
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeChannelWordlen(int chNum) {
int ret;
Channel chan = ctx.channels[chNum];
Channel refChan = ctx.channels[0];
int weightIdx = 0;
chan.fillMode = 0;
switch (br.read(2)) { // switch according to coding mode
case 0: // coded using constant number of bits
for (int i = 0; i < ctx.numQuantUnits; i++) {
chan.quWordlen[i] = br.read(3);
}
break;
case 1:
if (chNum > 0) {
ret = numCodedUnits(chan);
if (ret < 0) {
return ret;
}
if (chan.numCodedVals > 0) {
VLC vlcTab = wl_vlc_tabs[br.read(2)];
for (int i = 0; i < chan.numCodedVals; i++) {
int delta = vlcTab.getVLC2(br);
chan.quWordlen[i] = (refChan.quWordlen[i] + delta) & 7;
}
}
} else {
weightIdx = br.read(2);
ret = numCodedUnits(chan);
if (ret < 0) {
return ret;
}
if (chan.numCodedVals > 0) {
int pos = br.read(5);
if (pos > chan.numCodedVals) {
log.error(String.format("WL mode 1: invalid position %d", pos));
return AT3P_ERROR;
}
int deltaBits = br.read(2);
int minVal = br.read(3);
for (int i = 0; i < pos; i++) {
chan.quWordlen[i] = br.read(3);
}
for (int i = pos; i < chan.numCodedVals; i++) {
chan.quWordlen[i] = (minVal + getDelta(deltaBits)) & 7;
}
}
}
break;
case 2:
ret = numCodedUnits(chan);
if (ret < 0) {
return ret;
}
if (chNum > 0 && chan.numCodedVals > 0) {
VLC vlcTab = wl_vlc_tabs[br.read(2)];
int delta = vlcTab.getVLC2(br);
chan.quWordlen[0] = (refChan.quWordlen[0] + delta) & 7;
for (int i = 1; i < chan.numCodedVals; i++) {
int diff = refChan.quWordlen[i] - refChan.quWordlen[i - 1];
delta = vlcTab.getVLC2(br);
chan.quWordlen[i] = (chan.quWordlen[i - 1] + diff + delta) & 7;
}
} else if (chan.numCodedVals > 0) {
boolean flag = br.readBool();
VLC vlcTab = wl_vlc_tabs[br.read(1)];
int startVal = br.read(3);
unpackVqShape(startVal, atrac3p_wl_shapes[startVal][br.read(4)], chan.quWordlen, chan.numCodedVals);
if (!flag) {
for (int i = 0; i < chan.numCodedVals; i++) {
int delta = vlcTab.getVLC2(br);
chan.quWordlen[i] = (chan.quWordlen[i] + delta) & 7;
}
} else {
int i;
for (i = 0; i < (chan.numCodedVals & -2); i += 2) {
if (!br.readBool()) {
chan.quWordlen[i ] = (chan.quWordlen[i ] + vlcTab.getVLC2(br)) & 7;
chan.quWordlen[i + 1] = (chan.quWordlen[i + 1] + vlcTab.getVLC2(br)) & 7;
}
}
if ((chan.numCodedVals & 1) != 0) {
chan.quWordlen[i] = (chan.quWordlen[i] + vlcTab.getVLC2(br)) & 7;
}
}
}
break;
case 3:
weightIdx = br.read(2);
ret = numCodedUnits(chan);
if (ret < 0) {
return ret;
}
if (chan.numCodedVals > 0) {
VLC vlcTab = wl_vlc_tabs[br.read(2)];
// first coefficient is coded directly
chan.quWordlen[0] = br.read(3);
for (int i = 1; i < chan.numCodedVals; i++) {
int delta = vlcTab.getVLC2(br);
chan.quWordlen[i] = (chan.quWordlen[i - 1] + delta) & 7;
}
}
break;
}
if (chan.fillMode == 2) {
for (int i = chan.numCodedVals; i < ctx.numQuantUnits; i++) {
chan.quWordlen[i] = (chNum > 0 ? br.read1() : 1);
}
} else if (chan.fillMode == 3) {
int pos = (chNum > 0 ? chan.numCodedVals + chan.splitPoint : ctx.numQuantUnits - chan.splitPoint);
for (int i = chan.numCodedVals; i < pos; i++) {
chan.quWordlen[i] = 1;
}
}
if (weightIdx != 0) {
return addWordlenWeights(chan, weightIdx);
}
return 0;
}
/**
* Subtract weighting coefficients from decoded scalefactors.
*
* @param[in,out] chan ptr to the channel parameters
* @param[in] wtab_idx index of table of weights
* @return result code: 0 = OK, otherwise - error code
*/
private int substractSfWeights(Channel chan, int wtabIdx) {
int[] weigthsTab = Atrac3plusData2.atrac3p_sf_weights[wtabIdx - 1];
for (int i = 0; i < ctx.usedQuantUnits; i++) {
chan.quSfIdx[i] -= weigthsTab[i];
if (chan.quSfIdx[i] < 0 || chan.quSfIdx[i] > 63) {
log.error(String.format("SF index out of range pos=%d, val=%d", i, chan.quSfIdx[i]));
return AT3P_ERROR;
}
}
return 0;
}
/**
* Decode scale factor indexes for each quant unit of a channel.
*
* @param[in] chNum channel to process
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeChannelSfIdx(int chNum) {
Channel chan = ctx.channels[chNum];
Channel refChan = ctx.channels[0];
int weightIdx = 0;
chan.fillMode = 0;
switch (br.read(2)) { // switch according to coding mode
case 0: // coded using constant number of bits
for (int i = 0; i < ctx.usedQuantUnits; i++) {
chan.quSfIdx[i] = br.read(6);
}
break;
case 1:
if (chNum > 0) {
VLC vlcTab = sf_vlc_tabs[br.read(2)];
for (int i = 0; i < ctx.usedQuantUnits; i++) {
int delta = vlcTab.getVLC2(br);
chan.quSfIdx[i] = (refChan.quSfIdx[i] + delta) & 0x3F;
}
} else {
weightIdx = br.read(2);
if (weightIdx == 3) {
unpackSfVqShape(chan.quSfIdx, ctx.usedQuantUnits);
int numLongVals = br.read(5);
int deltaBits = br.read(2);
int minVal = br.read(4) - 7;
for (int i = 0; i < numLongVals; i++) {
chan.quSfIdx[i] = (chan.quSfIdx[i] + br.read(4) - 7) & 0x3F;
}
// All others are: minVal + delta
for (int i = numLongVals; i < ctx.usedQuantUnits; i++) {
chan.quSfIdx[i] = (chan.quSfIdx[i] + minVal + getDelta(deltaBits)) & 0x3F;
}
} else {
int numLongVals = br.read(5);
int deltaBits = br.read(3);
int minVal = br.read(6);
if (numLongVals > ctx.usedQuantUnits || deltaBits == 7) {
log.error(String.format("SF mode 1: invalid parameters"));
return AT3P_ERROR;
}
// Read full-precision SF indexes
for (int i = 0; i < numLongVals; i++) {
chan.quSfIdx[i] = br.read(6);
}
// All others are: minVal + delta
for (int i = numLongVals; i < ctx.usedQuantUnits; i++) {
chan.quSfIdx[i] = (minVal + getDelta(deltaBits)) & 0x3F;
}
}
}
break;
case 2:
if (chNum > 0) {
VLC vlcTab = sf_vlc_tabs[br.read(2)];
int delta = vlcTab.getVLC2(br);
chan.quSfIdx[0] = (refChan.quSfIdx[0] + delta) & 0x3F;
for (int i = 1; i < ctx.usedQuantUnits; i++) {
int diff = refChan.quSfIdx[i] - refChan.quSfIdx[i - 1];
delta = vlcTab.getVLC2(br);
chan.quSfIdx[i] = (chan.quSfIdx[i - 1] + diff + delta) & 0x3F;
}
} else if (chan.numCodedVals > 0) {
VLC vlcTab = sf_vlc_tabs[br.read(2) + 4];
unpackSfVqShape(chan.quSfIdx, ctx.usedQuantUnits);
for (int i = 0; i < ctx.usedQuantUnits; i++) {
int delta = vlcTab.getVLC2(br);
chan.quSfIdx[i] = (chan.quSfIdx[i] + signExtend(delta, 4)) & 0x3F;
}
}
break;
case 3:
if (chNum > 0) {
// Copy coefficients from reference channel
for (int i = 0; i < ctx.usedQuantUnits; i++) {
chan.quSfIdx[i] = refChan.quSfIdx[i];
}
} else {
weightIdx = br.read(2);
int vlcSel = br.read(2);
VLC vlcTab = sf_vlc_tabs[vlcSel];
if (weightIdx == 3) {
vlcTab = sf_vlc_tabs[vlcSel + 4];
unpackSfVqShape(chan.quSfIdx, ctx.usedQuantUnits);
int diff = (br.read(4) + 56) & 0x3F;
chan.quSfIdx[0] = (chan.quSfIdx[0] + diff) & 0x3F;
for (int i = 1; i < ctx.usedQuantUnits; i++) {
int delta = vlcTab.getVLC2(br);
diff = (diff + signExtend(delta, 4)) & 0x3F;
chan.quSfIdx[i] = (diff + chan.quSfIdx[i]) & 0x3F;
}
} else {
// 1st coefficient is coded directly
chan.quSfIdx[0] = br.read(6);
for (int i = 1; i < ctx.usedQuantUnits; i++) {
int delta = vlcTab.getVLC2(br);
chan.quSfIdx[i] = (chan.quSfIdx[i - 1] + delta) & 0x3F;
}
}
}
break;
}
if (weightIdx != 0 && weightIdx < 3) {
return substractSfWeights(chan, weightIdx);
}
return 0;
}
/**
* Decode word length information for each channel.
*
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeQuantWordlen() {
for (int chNum = 0; chNum < numChannels; chNum++) {
Arrays.fill(ctx.channels[chNum].quWordlen, 0);
int ret = decodeChannelWordlen(chNum);
if (ret < 0) {
return ret;
}
}
/* scan for last non-zero coeff in both channels and
* set number of quant units having coded spectrum */
int i;
for (i = ctx.numQuantUnits - 1; i >= 0; i--) {
if (ctx.channels[0].quWordlen[i] != 0 || (numChannels == 2 && ctx.channels[1].quWordlen[i] != 0)) {
break;
}
}
ctx.usedQuantUnits = i + 1;
return 0;
}
private int decodeScaleFactors() {
if (ctx.usedQuantUnits == 0) {
return 0;
}
for (int chNum = 0; chNum < numChannels; chNum++) {
Arrays.fill(ctx.channels[chNum].quSfIdx, 0);
int ret = decodeChannelSfIdx(chNum);
if (ret < 0) {
return ret;
}
}
return 0;
}
/**
* Decode number of code table values.
*
* @return result code: 0 = OK, otherwise - error code
*/
private int getNumCtValues() {
if (!br.readBool()) {
return ctx.usedQuantUnits;
}
int numCodedVals = br.read(5);
if (numCodedVals > ctx.usedQuantUnits) {
log.error(String.format("Invalid number of code table indexes: %d", numCodedVals));
return AT3P_ERROR;
}
return numCodedVals;
}
/**
* Decode code table indexes for each quant unit of a channel.
*
* @param[in] chNum channel to process
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeChannelCodeTab(int chNum) {
VLC vlcTab;
int numVals;
int mask = ctx.useFullTable ? 7 : 3; // mask for modular arithmetic
Channel chan = ctx.channels[chNum];
Channel refChan = ctx.channels[0];
chan.tableType = br.read(1);
switch (br.read(2)) { // switch according to coding mode
case 0: // directly coded
int numBits = ctx.useFullTable ? 3 : 2;
numVals = getNumCtValues();
if (numVals < 0) {
return numVals;
}
for (int i = 0; i < numVals; i++) {
if (chan.quWordlen[i] != 0) {
chan.quTabIdx[i] = br.read(numBits);
} else if (chNum > 0 && refChan.quWordlen[i] != 0) {
// get clone master flag
chan.quTabIdx[i] = br.read1();
}
}
break;
case 1: // entropy-coded
vlcTab = ctx.useFullTable ? ct_vlc_tabs[1] : ct_vlc_tabs[0];
numVals = getNumCtValues();
if (numVals < 0) {
return numVals;
}
for (int i = 0; i < numVals; i++) {
if (chan.quWordlen[i] != 0) {
chan.quTabIdx[i] = vlcTab.getVLC2(br);
} else if (chNum > 0 && refChan.quWordlen[i] != 0) {
// get clone master flag
chan.quTabIdx[i] = br.read1();
}
}
break;
case 2: // entropy-coded delta
VLC deltaVlc;
if (ctx.useFullTable) {
vlcTab = ct_vlc_tabs[1];
deltaVlc = ct_vlc_tabs[2];
} else {
vlcTab = ct_vlc_tabs[0];
deltaVlc = ct_vlc_tabs[0];
}
int pred = 0;
numVals = getNumCtValues();
if (numVals < 0) {
return numVals;
}
for (int i = 0; i < numVals; i++) {
if (chan.quWordlen[i] != 0) {
chan.quTabIdx[i] = (i == 0 ? vlcTab.getVLC2(br) : (pred + deltaVlc.getVLC2(br)) & mask);
pred = chan.quTabIdx[i];
} else if (chNum > 0 && refChan.quWordlen[i] != 0) {
// get clone master flag
chan.quTabIdx[i] = br.read1();
}
}
break;
case 3: // entropy-coded difference to master
if (chNum > 0) {
vlcTab = ctx.useFullTable ? ct_vlc_tabs[3] : ct_vlc_tabs[0];
numVals = getNumCtValues();
if (numVals < 0) {
return numVals;
}
for (int i = 0; i < numVals; i++) {
if (chan.quWordlen[i] != 0) {
chan.quTabIdx[i] = (refChan.quTabIdx[i] + vlcTab.getVLC2(br)) & mask;
} else if (chNum > 0 && refChan.quWordlen[i] != 0) {
// get clone master flag
chan.quTabIdx[i] = br.read1();
}
}
}
break;
}
return 0;
}
/**
* Decode code table indexes for each channel.
*
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeCodeTableIndexes() {
if (ctx.usedQuantUnits == 0) {
return 0;
}
ctx.useFullTable = br.readBool();
for (int chNum = 0; chNum < numChannels; chNum++) {
Arrays.fill(ctx.channels[chNum].quTabIdx, 0);
int ret = decodeChannelCodeTab(chNum);
if (ret < 0) {
return ret;
}
}
return 0;
}
private void decodeQuSpectra(Atrac3pSpecCodeTab tab, VLC vlcTab, int[] out, int outOffset, int numSpecs) {
int groupSize = tab.groupSize;
int numCoeffs = tab.numCoeffs;
int bits = tab.bits;
boolean isSigned = tab.isSigned;
int mask = (1 << bits) - 1;
for (int pos = 0; pos < numSpecs; ) {
if (groupSize == 1 || br.readBool()) {
for (int j = 0; j < groupSize; j++) {
int val = vlcTab.getVLC2(br);
for (int i = 0; i < numCoeffs; i++) {
int cf = val & mask;
if (isSigned) {
cf = signExtend(cf, bits);
} else if (cf != 0 && br.readBool()) {
cf = -cf;
}
out[outOffset + pos] = cf;
pos++;
val >>= bits;
}
}
} else {
// Group skipped
pos += groupSize * numCoeffs;
}
}
}
private void decodeSpectrum() {
for (int chNum = 0; chNum < numChannels; chNum++) {
Channel chan = ctx.channels[chNum];
Arrays.fill(chan.spectrum, 0);
Arrays.fill(chan.powerLevs, ATRAC3P_POWER_COMP_OFF);
for (int qu = 0; qu < ctx.usedQuantUnits; qu++) {
int numSpecs = ff_atrac3p_qu_to_spec_pos[qu + 1] - ff_atrac3p_qu_to_spec_pos[qu];
int wordlen = chan.quWordlen[qu];
int codetab = chan.quTabIdx[qu];
if (wordlen > 0) {
if (!ctx.useFullTable) {
codetab = atrac3p_ct_restricted_to_full[chan.tableType][wordlen - 1][codetab];
}
int tabIndex = (chan.tableType * 8 + codetab) * 7 + wordlen - 1;
Atrac3pSpecCodeTab tab = atrac3p_spectra_tabs[tabIndex];
if (tab.redirect >= 0) {
tabIndex = tab.redirect;
}
decodeQuSpectra(tab, spec_vlc_tabs[tabIndex], chan.spectrum, ff_atrac3p_qu_to_spec_pos[qu], numSpecs);
} else if (chNum > 0 && ctx.channels[0].quWordlen[qu] != 0 && codetab == 0) {
// Copy coefficients from master
System.arraycopy(ctx.channels[0].spectrum, ff_atrac3p_qu_to_spec_pos[qu], chan.spectrum, ff_atrac3p_qu_to_spec_pos[qu], numSpecs);
chan.quWordlen[qu] = ctx.channels[0].quWordlen[qu];
}
}
/* Power compensation levels only present in the bitstream
* if there are more than 2 quant units. The lowest two units
* correspond to the frequencies 0...351 Hz, whose shouldn't
* be affected by the power compensation. */
if (ctx.usedQuantUnits > 2) {
int numSpecs = atrac3p_subband_to_num_powgrps[ctx.numCodedSubbands - 1];
for (int i = 0; i < numSpecs; i++) {
chan.powerLevs[i] = br.read(4);
}
}
}
}
private boolean getSubbandFlags(boolean[] out, int numFlags) {
boolean result = br.readBool();
if (result) {
if (br.readBool()) {
for (int i = 0; i < numFlags; i++) {
out[i] = br.readBool();
}
} else {
for (int i = 0; i < numFlags; i++) {
out[i] = true;
}
}
} else {
for (int i = 0; i < numFlags; i++) {
out[i] = false;
}
}
return result;
}
/**
* Decode mdct window shape flags for all channels.
*
*/
private void decodeWindowShape() {
for (int i = 0; i < numChannels; i++) {
getSubbandFlags(ctx.channels[i].wndShape, ctx.numSubbands);
}
}
private int decodeGaincNPoints(int chNum, int codedSubbands) {
Channel chan = ctx.channels[chNum];
Channel refChan = ctx.channels[0];
switch (br.read(2)) { // switch according to coding mode
case 0: // fixed-length coding
for (int i = 0; i < codedSubbands; i++) {
chan.gainData[i].numPoints = br.read(3);
}
break;
case 1: // variable-length coding
for (int i = 0; i < codedSubbands; i++) {
chan.gainData[i].numPoints = gain_vlc_tabs[0].getVLC2(br);
}
break;
case 2:
if (chNum > 0) { // VLC modulo delta to master channel
for (int i = 0; i < codedSubbands; i++) {
int delta = gain_vlc_tabs[1].getVLC2(br);
chan.gainData[i].numPoints = (refChan.gainData[i].numPoints + delta) & 7;
}
} else { // VLC modulo delta to previous
chan.gainData[0].numPoints = gain_vlc_tabs[0].getVLC2(br);
for (int i = 1; i < codedSubbands; i++) {
int delta = gain_vlc_tabs[1].getVLC2(br);
chan.gainData[i].numPoints = (chan.gainData[i - 1].numPoints + delta) & 7;
}
}
break;
case 3:
if (chNum > 0) { // copy data from master channel
for (int i = 0; i < codedSubbands; i++) {
chan.gainData[i].numPoints = refChan.gainData[i].numPoints;
}
} else { // shorter delta to min
int deltaBits = br.read(2);
int minVal = br.read(3);
for (int i = 0; i < codedSubbands; i++) {
chan.gainData[i].numPoints = minVal + getDelta(deltaBits);
if (chan.gainData[i].numPoints > 7) {
return AT3P_ERROR;
}
}
}
break;
}
return 0;
}
/**
* Implements coding mode 1 (master) for gain compensation levels.
*
* @param[out] dst ptr to the output array
*/
private void gaincLevelMode1m(AtracGainInfo dst) {
if (dst.numPoints > 0) {
dst.levCode[0] = gain_vlc_tabs[2].getVLC2(br);
}
for (int i = 1; i < dst.numPoints; i++) {
int delta = gain_vlc_tabs[3].getVLC2(br);
dst.levCode[i] = (dst.levCode[i - 1] + delta) & 0xF;
}
}
/**
* Implements coding mode 3 (slave) for gain compensation levels.
*
* @param[out] dst ptr to the output array
* @param[in] ref ptr to the reference channel
*/
private void gaincLevelMode3s(AtracGainInfo dst, AtracGainInfo ref) {
for (int i = 0; i < dst.numPoints; i++) {
dst.levCode[i] = (i >= ref.numPoints ? 7 : ref.levCode[i]);
}
}
/**
* Decode level code for each gain control point.
*
* @param[in] ch_num channel to process
* @param[in] coded_subbands number of subbands to process
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeGaincLevels(int chNum, int codedSubbands) {
Channel chan = ctx.channels[chNum];
Channel refChan = ctx.channels[0];
switch (br.read(2)) { // switch according to coding mode
case 0: // fixed-length coding
for (int sb = 0; sb < codedSubbands; sb++) {
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
chan.gainData[sb].levCode[i] = br.read(4);
}
}
break;
case 1:
if (chNum > 0) { // VLC module delta to master channel
for (int sb = 0; sb < codedSubbands; sb++) {
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
int delta = gain_vlc_tabs[5].getVLC2(br);
int pred = (i >= refChan.gainData[sb].numPoints ? 7 : refChan.gainData[sb].levCode[i]);
chan.gainData[sb].levCode[i] = (pred + delta) & 0xF;
}
}
} else { // VLC module delta to previous
for (int sb = 0; sb < codedSubbands; sb++) {
gaincLevelMode1m(chan.gainData[sb]);
}
}
break;
case 2:
if (chNum > 0) { // VLC modulo delta to previous or clone master
for (int sb = 0; sb < codedSubbands; sb++) {
if (chan.gainData[sb].numPoints > 0) {
if (br.readBool()) {
gaincLevelMode1m(chan.gainData[sb]);
} else {
gaincLevelMode3s(chan.gainData[sb], refChan.gainData[sb]);
}
}
}
} else { // VLC modulo delta to lev_codes of previous subband
if (chan.gainData[0].numPoints > 0) {
gaincLevelMode1m(chan.gainData[0]);;
}
for (int sb = 1; sb < codedSubbands; sb++) {
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
int delta = gain_vlc_tabs[4].getVLC2(br);
int pred = (i >= chan.gainData[sb - 1].numPoints ? 7 : chan.gainData[sb - 1].levCode[i]);
chan.gainData[sb].levCode[i] = (pred + delta) & 0xF;
}
}
}
break;
case 3:
if (chNum > 0) { // clone master
for (int sb = 0; sb < codedSubbands; sb++) {
gaincLevelMode3s(chan.gainData[sb], refChan.gainData[sb]);
}
} else { // shorter delta to min
int deltaBits = br.read(2);
int minVal = br.read(4);
for (int sb = 0; sb < codedSubbands; sb++) {
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
chan.gainData[sb].levCode[i] = minVal + getDelta(deltaBits);
if (chan.gainData[sb].levCode[i] > 15) {
return AT3P_ERROR;
}
}
}
}
break;
}
return 0;
}
/**
* Implements coding mode 0 for gain compensation locations.
*
* @param[out] dst ptr to the output array
* @param[in] pos position of the value to be processed
*/
private void gaincLocMode0(AtracGainInfo dst, int pos) {
if (pos == 0 || dst.locCode[pos - 1] < 15) {
dst.locCode[pos] = br.read(5);
} else if (dst.locCode[pos - 1] >= 30) {
dst.locCode[pos] = 31;
} else {
int deltaBits = avLog2(30 - dst.locCode[pos - 1]) + 1;
dst.locCode[pos] = dst.locCode[pos - 1] + br.read(deltaBits) + 1;
}
}
/**
* Implements coding mode 1 for gain compensation locations.
*
* @param[out] dst ptr to the output array
*/
private void gaincLocMode1(AtracGainInfo dst) {
if (dst.numPoints > 0) {
// 1st coefficient is stored directly
dst.locCode[0] = br.read(5);
for (int i = 1; i < dst.numPoints; i++) {
// Switch VLC according to the curve direction
// (ascending/descending)
VLC tab = (dst.levCode[i] <= dst.levCode[i - 1] ? gain_vlc_tabs[7] : gain_vlc_tabs[9]);
dst.locCode[i] = dst.locCode[i - 1] + tab.getVLC2(br);
}
}
}
/**
* Decode location code for each gain control point.
*
* @param[in] chNum channel to process
* @param[in] codedSubbands number of subbands to process
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeGaincLocCodes(int chNum, int codedSubbands) {
Channel chan = ctx.channels[chNum];
Channel refChan = ctx.channels[0];
int codingMode = br.read(2);
switch (codingMode) { // switch according to coding mode
case 0: // sequence of numbers in ascending order
for (int sb = 0; sb < codedSubbands; sb++) {
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
gaincLocMode0(chan.gainData[sb], i);
}
}
break;
case 1:
if (chNum > 0) {
for (int sb = 0; sb < codedSubbands; sb++) {
if (chan.gainData[sb].numPoints <= 0) {
continue;
}
AtracGainInfo dst = chan.gainData[sb];
AtracGainInfo ref = refChan.gainData[sb];
// 1st value is vlc-coded modulo delta to master
int delta = gain_vlc_tabs[10].getVLC2(br);
int pred = ref.numPoints > 0 ? ref.locCode[0] : 0;
dst.locCode[0] = (pred + delta) & 0x1F;
for (int i = 1; i < dst.numPoints; i++) {
boolean moreThanRef = i >= ref.numPoints;
if (dst.levCode[i] > dst.levCode[i - 1]) {
// ascending curve
if (moreThanRef) {
delta = gain_vlc_tabs[9].getVLC2(br);
dst.locCode[i] = dst.locCode[i - 1] + delta;
} else {
if (br.readBool()) {
gaincLocMode0(dst, i); // direct coding
} else {
dst.locCode[i] = ref.locCode[i]; // clone master
}
}
} else { // descending curve
VLC tab = moreThanRef ? gain_vlc_tabs[7] : gain_vlc_tabs[10];
delta = tab.getVLC2(br);
if (moreThanRef) {
dst.locCode[i] = dst.locCode[i - 1] + delta;
} else {
dst.locCode[i] = (ref.locCode[i] + delta) & 0x1F;
}
}
}
}
} else { // VLC delta to previous
for (int sb = 0; sb < codedSubbands; sb++) {
gaincLocMode1(chan.gainData[sb]);
}
}
break;
case 2:
if (chNum > 0) {
for (int sb = 0; sb < codedSubbands; sb++) {
if (chan.gainData[sb].numPoints <= 0) {
continue;
}
AtracGainInfo dst = chan.gainData[sb];
AtracGainInfo ref = refChan.gainData[sb];
if (dst.numPoints > ref.numPoints || br.readBool()) {
gaincLocMode1(dst);
} else { // clone master for the whole subband
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
dst.locCode[i] = ref.locCode[i];
}
}
}
} else {
// data for the first subband is coded directly
for (int i = 0; i < chan.gainData[0].numPoints; i++) {
gaincLocMode0(chan.gainData[0], i);
}
for (int sb = 1; sb < codedSubbands; sb++) {
if (chan.gainData[sb].numPoints <= 0) {
continue;
}
AtracGainInfo dst = chan.gainData[sb];
// 1st value is vlc-coded modulo delta to the corresponding
// value of the previous subband if any or zero
int delta = gain_vlc_tabs[6].getVLC2(br);
int pred = chan.gainData[sb - 1].numPoints > 0 ? chan.gainData[sb - 1].locCode[0] : 0;
dst.locCode[0] = (pred + delta) & 0x1F;
for (int i = 1; i < dst.numPoints; i++) {
boolean moreThanRef = i >= chan.gainData[sb - 1].numPoints;
// Select VLC table according to curve direction and
// presence of prediction
VLC tab = gain_vlc_tabs[(dst.levCode[i] > dst.levCode[i - 1] ? 2 : 0) + (moreThanRef ? 1 : 0) + 6];
delta = tab.getVLC2(br);
if (moreThanRef) {
dst.locCode[i] = dst.locCode[i - 1] + delta;
} else {
dst.locCode[i] = (chan.gainData[sb - 1].locCode[i] + delta) & 0x1F;
}
}
}
}
break;
case 3:
if (chNum > 0) { // clone master or direct or direct coding
for (int sb = 0; sb < codedSubbands; sb++) {
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
if (i >= refChan.gainData[sb].numPoints) {
gaincLocMode0(chan.gainData[sb], i);
} else {
chan.gainData[sb].locCode[i] = refChan.gainData[sb].locCode[i];
}
}
}
} else { // shorter delta to min
int deltaBits = br.read(2) + 1;
int minVal = br.read(5);
for (int sb = 0; sb < codedSubbands; sb++) {
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
chan.gainData[sb].locCode[i] = minVal + i + br.read(deltaBits);
}
}
}
break;
}
// Validate decoded information
for (int sb = 0; sb < codedSubbands; sb++) {
AtracGainInfo dst = chan.gainData[sb];
for (int i = 0; i < chan.gainData[sb].numPoints; i++) {
if (dst.locCode[i] < 0 || dst.locCode[i] > 31 || (i > 0 && dst.locCode[i] <= dst.locCode[i - 1])) {
log.error(String.format("Invalid gain location: ch=%d, sb=%d, pos=%d, val=%d", chNum, sb, i, dst.locCode[i]));
return AT3P_ERROR;
}
}
}
return 0;
}
/**
* Decode gain control data for all channels.
*
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeGaincData() {
int ret;
for (int chNum = 0; chNum < numChannels; chNum++) {
for (int i = 0; i < ATRAC3P_SUBBANDS; i++) {
ctx.channels[chNum].gainData[i].clear();
}
if (br.readBool()) { // gain control data present?
int codedSubbands = br.read(4) + 1;
if (br.readBool()) { // is high band gain data replication on?
ctx.channels[chNum].numGainSubbands = br.read(4) + 1;
} else {
ctx.channels[chNum].numGainSubbands = codedSubbands;
}
ret = decodeGaincNPoints(chNum, codedSubbands);
if (ret < 0) {
return ret;
}
ret = decodeGaincLevels(chNum, codedSubbands);
if (ret < 0) {
return ret;
}
ret = decodeGaincLocCodes(chNum, codedSubbands);
if (ret < 0) {
return ret;
}
if (codedSubbands > 0) { // propagate gain data if requested
for (int sb = codedSubbands; sb < ctx.channels[chNum].numGainSubbands; sb++) {
ctx.channels[chNum].gainData[sb].copy(ctx.channels[chNum].gainData[sb - 1]);
}
}
} else {
ctx.channels[chNum].numGainSubbands = 0;
}
}
return 0;
}
/**
* Decode envelope for all tones of a channel.
*
* @param[in] chNum channel to process
* @param[in] bandHasTones ptr to an array of per-band-flags:
* 1 - tone data present
*/
private void decodeTonesEnvelope(int chNum, boolean bandHasTones[]) {
WavesData dst[] = ctx.channels[chNum].tonesInfo;
WavesData ref[] = ctx.channels[0].tonesInfo;
if (chNum == 0 || !br.readBool()) { // mode 0: fixed-length coding
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb]) {
continue;
}
dst[sb].pendEnv.hasStartPoint = br.readBool();
dst[sb].pendEnv.startPos = (dst[sb].pendEnv.hasStartPoint ? br.read(5) : -1);
dst[sb].pendEnv.hasStopPoint = br.readBool();
dst[sb].pendEnv.stopPos = (dst[sb].pendEnv.hasStopPoint ? br.read(5) : 32);
}
} else { // mode 1(slave only): copy master
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb]) {
continue;
}
dst[sb].pendEnv.copy(ref[sb].pendEnv);
}
}
}
/**
* Decode number of tones for each subband of a channel.
*
* @param[in] chNum channel to process
* @param[in] bandHasTones ptr to an array of per-band-flags:
* 1 - tone data present
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeBandNumwavs(int chNum, boolean bandHasTones[]) {
WavesData dst[] = ctx.channels[chNum].tonesInfo;
WavesData ref[] = ctx.channels[0].tonesInfo;
int mode = br.read(chNum + 1);
switch (mode) {
case 0: // fixed-length coding
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (bandHasTones[sb]) {
dst[sb].numWavs = br.read(4);
}
}
break;
case 1: // variable-length coding
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (bandHasTones[sb]) {
dst[sb].numWavs = tone_vlc_tabs[1].getVLC2(br);
}
}
break;
case 2: // VLC modulo delta to master (slave only)
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (bandHasTones[sb]) {
int delta = tone_vlc_tabs[2].getVLC2(br);
delta = signExtend(delta, 3);
dst[sb].numWavs = (ref[sb].numWavs + delta) & 0xF;
}
}
break;
case 3: // copy master (slave only)
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (bandHasTones[sb]) {
dst[sb].numWavs = ref[sb].numWavs;
}
}
break;
}
// initialize start tone index for each subband
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (bandHasTones[sb]) {
if (ctx.wavesInfo.tonesIndex + dst[sb].numWavs > 48) {
log.error(String.format("Too many tones: %d (max. 48)", ctx.wavesInfo.tonesIndex + dst[sb].numWavs));
return AT3P_ERROR;
}
dst[sb].startIndex = ctx.wavesInfo.tonesIndex;
ctx.wavesInfo.tonesIndex += dst[sb].numWavs;
}
}
return 0;
}
/**
* Decode frequency information for each subband of a channel.
*
* @param[in] chNum channel to process
* @param[in] bandHasTones ptr to an array of per-band-flags:
* 1 - tone data present
*/
private void decodeTonesFrequency(int chNum, boolean bandHasTones[]) {
WavesData dst[] = ctx.channels[chNum].tonesInfo;
WavesData ref[] = ctx.channels[0].tonesInfo;
if (chNum == 0 || !br.readBool()) { // mode 0: fixed-length coding
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb] || dst[sb].numWavs == 0) {
continue;
}
int iwav = dst[sb].startIndex;
boolean direction = (dst[sb].numWavs > 1 ? br.readBool() : false);
if (direction) { // packed numbers in descending order
if (dst[sb].numWavs > 0) {
ctx.wavesInfo.waves[iwav + dst[sb].numWavs - 1].freqIndex = br.read(10);
}
for (int i = dst[sb].numWavs - 2; i >= 0; i--) {
int nbits = avLog2(ctx.wavesInfo.waves[iwav + i + 1].freqIndex) + 1;
ctx.wavesInfo.waves[iwav + i].freqIndex = br.read(nbits);
}
} else { // packed numbers in ascending order
for (int i = 0; i < dst[sb].numWavs; i++) {
if (i == 0 || ctx.wavesInfo.waves[iwav + i - 1].freqIndex < 512) {
ctx.wavesInfo.waves[iwav + i].freqIndex = br.read(10);
} else {
int nbits = avLog2(1023 - ctx.wavesInfo.waves[iwav + i - 1].freqIndex) + 1;
ctx.wavesInfo.waves[iwav + i].freqIndex = br.read(nbits) + 1024 - (1 << nbits);
}
}
}
}
} else { // mode 1: VLC module delta to master (slave only)
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb] || dst[sb].numWavs == 0) {
continue;
}
int iwav = ref[sb].startIndex;
int owav = dst[sb].startIndex;
for (int i = 0; i < dst[sb].numWavs; i++) {
int delta = tone_vlc_tabs[6].getVLC2(br);
delta = signExtend(delta, 8);
int pred = (i < ref[sb].numWavs ? ctx.wavesInfo.waves[iwav + i].freqIndex : (ref[sb].numWavs > 0 ? ctx.wavesInfo.waves[iwav + ref[sb].numWavs - 1].freqIndex : 0));
ctx.wavesInfo.waves[owav + i].freqIndex = (pred + delta) & 0x3FF;
}
}
}
}
/**
* Decode amplitude information for each subband of a channel.
*
* @param[in] chNum channel to process
* @param[in] bandHasTones ptr to an array of per-band-flags:
* 1 - tone data present
*/
private void decodeTonesAmplitude(int chNum, boolean bandHasTones[]) {
WavesData dst[] = ctx.channels[chNum].tonesInfo;
WavesData ref[] = ctx.channels[0].tonesInfo;
final int refwaves[] = new int[48];
if (chNum > 0) {
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb] || dst[sb].numWavs == 0) {
continue;
}
int wsrc = dst[sb].startIndex;
int wref = ref[sb].startIndex;
for (int j = 0; j < dst[sb].numWavs; j++) {
int fi = 0;
int maxdiff = 1024;
for (int i = 0; i < ref[sb].numWavs; i++) {
int diff = abs(ctx.wavesInfo.waves[wsrc + j].freqIndex - ctx.wavesInfo.waves[wref + i].freqIndex);
if (diff < maxdiff) {
maxdiff = diff;
fi = i;
}
}
if (maxdiff < 8) {
refwaves[dst[sb].startIndex + j] = fi + ref[sb].startIndex;
} else if (j < ref[sb].numWavs) {
refwaves[dst[sb].startIndex + j] = j + ref[sb].startIndex;
} else {
refwaves[dst[sb].startIndex + j] = -1;
}
}
}
}
int mode = br.read(chNum + 1);
switch (mode) {
case 0: // fixed-length coding
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb] || dst[sb].numWavs == 0) {
continue;
}
if (ctx.wavesInfo.amplitudeMode != 0) {
for (int i = 0; i < dst[sb].numWavs; i++) {
ctx.wavesInfo.waves[dst[sb].startIndex + i].ampSf = br.read(6);
}
} else {
ctx.wavesInfo.waves[dst[sb].startIndex].ampSf = br.read(6);
}
}
break;
case 1: // min + VLC delta
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb] || dst[sb].numWavs == 0) {
continue;
}
if (ctx.wavesInfo.amplitudeMode != 0) {
for (int i = 0; i < dst[sb].numWavs; i++) {
ctx.wavesInfo.waves[dst[sb].startIndex + i].ampSf = tone_vlc_tabs[3].getVLC2(br) + 20;
}
} else {
ctx.wavesInfo.waves[dst[sb].startIndex].ampSf = tone_vlc_tabs[4].getVLC2(br) + 24;
}
}
break;
case 2: // VLC module delta to master (slave only)
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb] || dst[sb].numWavs == 0) {
continue;
}
for (int i = 0; i < dst[sb].numWavs; i++) {
int delta = tone_vlc_tabs[5].getVLC2(br);
delta = signExtend(delta, 5);
int pred = refwaves[dst[sb].startIndex + i] >= 0 ? ctx.wavesInfo.waves[refwaves[dst[sb].startIndex + i]].ampSf : 34;
ctx.wavesInfo.waves[dst[sb].startIndex + i].ampSf = (pred + delta) & 0x3F;
}
}
break;
case 3: // clone master (slave only)
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb]) {
continue;
}
for (int i = 0; i < dst[sb].numWavs; i++) {
ctx.wavesInfo.waves[dst[sb].startIndex + i].ampSf = refwaves[dst[sb].startIndex + i] >= 0 ? ctx.wavesInfo.waves[refwaves[dst[sb].startIndex + i]].ampSf : 32;
}
}
break;
}
}
/**
* Decode phase information for each subband of a channel.
*
* @param[in] chNnum channel to process
* @param[in] bandHasTones ptr to an array of per-band-flags:
* 1 - tone data present
*/
private void decodeTonesPhase(int chNum, boolean bandHasTones[]) {
WavesData dst[] = ctx.channels[chNum].tonesInfo;
for (int sb = 0; sb < ctx.wavesInfo.numToneBands; sb++) {
if (!bandHasTones[sb]) {
continue;
}
int wparam = dst[sb].startIndex;
for (int i = 0; i < dst[sb].numWavs; i++) {
ctx.wavesInfo.waves[wparam + i].phaseIndex = br.read(5);
}
}
}
/**
* Decode tones info for all channels.
*
* @return result code: 0 = OK, otherwise - error code
*/
private int decodeTonesInfo() {
for (int chNum = 0; chNum < numChannels; chNum++) {
for (int i = 0; i < ATRAC3P_SUBBANDS; i++) {
ctx.channels[chNum].tonesInfo[i].clear();;
}
}
ctx.wavesInfo.tonesPresent = br.readBool();
if (!ctx.wavesInfo.tonesPresent) {
return 0;
}
for (int i = 0; i < ctx.wavesInfo.waves.length; i++) {
ctx.wavesInfo.waves[i].clear();;
}
ctx.wavesInfo.amplitudeMode = br.read1();
if (ctx.wavesInfo.amplitudeMode == 0) {
log.error(String.format("GHA amplitude mode 0"));
return AT3P_ERROR;
}
ctx.wavesInfo.numToneBands = tone_vlc_tabs[0].getVLC2(br) + 1;
if (numChannels == 2) {
getSubbandFlags(ctx.wavesInfo.toneSharing, ctx.wavesInfo.numToneBands);
getSubbandFlags(ctx.wavesInfo.toneMaster, ctx.wavesInfo.numToneBands);
if (getSubbandFlags(ctx.wavesInfo.phaseShift, ctx.wavesInfo.numToneBands)) {
log.warn(String.format("GHA Phase shifting"));
}
}
ctx.wavesInfo.tonesIndex = 0;
for (int chNum = 0; chNum < numChannels; chNum++) {
final boolean bandHasTones[] = new boolean[16];
for (int i = 0; i < ctx.wavesInfo.numToneBands; i++) {
bandHasTones[i] = (chNum == 0 ? true : !ctx.wavesInfo.toneSharing[i]);
}
decodeTonesEnvelope(chNum, bandHasTones);
int ret = decodeBandNumwavs(chNum, bandHasTones);
if (ret < 0) {
return ret;
}
decodeTonesFrequency(chNum, bandHasTones);
decodeTonesAmplitude(chNum, bandHasTones);
decodeTonesPhase(chNum, bandHasTones);
}
if (numChannels == 2) {
for (int i = 0; i < ctx.wavesInfo.numToneBands; i++) {
if (ctx.wavesInfo.toneSharing[i]) {
ctx.channels[1].tonesInfo[i].copy(ctx.channels[0].tonesInfo[i]);
}
if (ctx.wavesInfo.toneMaster[i]) {
// Swap channels 0 and 1
WavesData tmp = new WavesData();
tmp.copy(ctx.channels[0].tonesInfo[i]);
ctx.channels[0].tonesInfo[i].copy(ctx.channels[1].tonesInfo[i]);
ctx.channels[1].tonesInfo[i].copy(tmp);
}
}
}
return 0;
}
public void decodeResidualSpectrum(float[][] out) {
final int sbRNGindex[] = new int[ATRAC3P_SUBBANDS];
if (ctx.muteFlag) {
for (int ch = 0; ch < numChannels; ch++) {
Arrays.fill(out[ch], 0f);
}
return;
}
int RNGindex = 0;
for (int qu = 0; qu < ctx.usedQuantUnits; qu++) {
RNGindex += ctx.channels[0].quSfIdx[qu] + ctx.channels[1].quSfIdx[qu];
}
for (int sb = 0; sb < ctx.numCodedSubbands; sb++, RNGindex += 128) {
sbRNGindex[sb] = RNGindex & 0x3FC;
}
// inverse quant and power compensation
for (int ch = 0; ch < numChannels; ch++) {
// clear channel's residual spectrum
Arrays.fill(out[ch], 0, ATRAC3P_FRAME_SAMPLES, 0f);
for (int qu = 0; qu < ctx.usedQuantUnits; qu++) {
int src = ff_atrac3p_qu_to_spec_pos[qu];
int dst = ff_atrac3p_qu_to_spec_pos[qu];
int nspeclines = ff_atrac3p_qu_to_spec_pos[qu + 1] - ff_atrac3p_qu_to_spec_pos[qu];
if (ctx.channels[ch].quWordlen[qu] > 0) {
float q = ff_atrac3p_sf_tab[ctx.channels[ch].quSfIdx[qu]] * ff_atrac3p_mant_tab[ctx.channels[ch].quWordlen[qu]];
for (int i = 0; i < nspeclines; i++) {
out[ch][dst + i] = ctx.channels[ch].spectrum[src + i] * q;
}
}
}
for (int sb = 0; sb < ctx.numCodedSubbands; sb++) {
dsp.powerCompensation(ctx, ch, out[ch], sbRNGindex[sb], sb);
}
}
if (ctx.unitType == CH_UNIT_STEREO) {
final float tmp[] = new float[ATRAC3P_SUBBAND_SAMPLES];
for (int sb = 0; sb < ctx.numCodedSubbands; sb++) {
if (ctx.swapChannels[sb]) {
// Swap both channels
System.arraycopy(out[0], sb * ATRAC3P_SUBBAND_SAMPLES, tmp , 0, ATRAC3P_SUBBAND_SAMPLES);
System.arraycopy(out[1], sb * ATRAC3P_SUBBAND_SAMPLES, out[0], sb * ATRAC3P_SUBBAND_SAMPLES, ATRAC3P_SUBBAND_SAMPLES);
System.arraycopy(tmp , 0, out[1], sb * ATRAC3P_SUBBAND_SAMPLES, ATRAC3P_SUBBAND_SAMPLES);
}
// flip coefficients' sign if requested
if (ctx.negateCoeffs[sb]) {
for (int i = 0; i < ATRAC3P_SUBBAND_SAMPLES; i++) {
out[1][sb * ATRAC3P_SUBBAND_SAMPLES + i] = -(out[1][sb * ATRAC3P_SUBBAND_SAMPLES + i]);
}
}
}
}
}
public void reconstructFrame(Context at3pContext) {
for (int ch = 0; ch < numChannels; ch++) {
for (int sb = 0; sb < ctx.numSubbands; sb++) {
// inverse transform and windowing
dsp.imdct(at3pContext.mdctCtx, at3pContext.samples[ch], sb * ATRAC3P_SUBBAND_SAMPLES, at3pContext.mdctBuf[ch], sb * ATRAC3P_SUBBAND_SAMPLES, (ctx.channels[ch].wndShapePrev[sb] ? 2 : 0) + (ctx.channels[ch].wndShape[sb] ? 1 : 0), sb);
// gain compensation and overlapping
at3pContext.gaincCtx.gainCompensation(at3pContext.mdctBuf[ch], sb * ATRAC3P_SUBBAND_SAMPLES, ctx.prevBuf[ch], sb * ATRAC3P_SUBBAND_SAMPLES, ctx.channels[ch].gainDataPrev[sb], ctx.channels[ch].gainData[sb], ATRAC3P_SUBBAND_SAMPLES, at3pContext.timeBuf[ch], sb * ATRAC3P_SUBBAND_SAMPLES);
}
// zero unused subbands in both output and overlapping buffers
Arrays.fill( ctx.prevBuf[ch], ctx.numSubbands * ATRAC3P_SUBBAND_SAMPLES, ctx.prevBuf[ch].length, 0f);
Arrays.fill(at3pContext.timeBuf[ch], ctx.numSubbands * ATRAC3P_SUBBAND_SAMPLES, at3pContext.timeBuf[ch].length, 0f);
// resynthesize and add tonal signal
if (ctx.wavesInfo.tonesPresent || ctx.wavesInfoPrev.tonesPresent) {
for (int sb = 0; sb < ctx.numSubbands; sb++) {
if (ctx.channels[ch].tonesInfo[sb].numWavs > 0 || ctx.channels[ch].tonesInfoPrev[sb].numWavs > 0) {
dsp.generateTones(ctx, ch, sb, at3pContext.timeBuf[ch], sb * 128);
}
}
}
// subband synthesis and acoustic signal output
dsp.ipqf(at3pContext.ipqfDctCtx, ctx.ipqfCtx[ch], at3pContext.timeBuf[ch], at3pContext.outpBuf[ch]);
}
// swap window shape and gain control buffers
for (int ch = 0; ch < numChannels; ch++) {
boolean tmp1[] = ctx.channels[ch].wndShape;
ctx.channels[ch].wndShape = ctx.channels[ch].wndShapePrev;
ctx.channels[ch].wndShapePrev = tmp1;
AtracGainInfo tmp2[] = ctx.channels[ch].gainData;
ctx.channels[ch].gainData = ctx.channels[ch].gainDataPrev;
ctx.channels[ch].gainDataPrev = tmp2;
WavesData tmp3[] = ctx.channels[ch].tonesInfo;
ctx.channels[ch].tonesInfo = ctx.channels[ch].tonesInfoPrev;
ctx.channels[ch].tonesInfoPrev = tmp3;
}
WaveSynthParams tmp = ctx.wavesInfo;
ctx.wavesInfo = ctx.wavesInfoPrev;
ctx.wavesInfoPrev = tmp;
}
}