/*
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.sound;
import org.apache.log4j.Logger;
import jpcsp.Memory;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.MemoryReader;
import jpcsp.util.Utilities;
/**
* @author gid15
*
*/
public class SampleSourceVAG implements ISampleSource {
private static Logger log = SoftwareSynthesizer.log;
private SoundVoice voice;
private int address;
private int numberSamples;
private IMemoryReader memoryReader;
private final int[] unpackedSamples = new int[28];
private final short[] samples = new short[28];
private int sampleIndex = samples.length;
private int numberVGABlocks;
private int currentVAGBlock;
private int currentSampleIndex;
private int hist1;
private int hist2;
private boolean loopMode;
private int loopStartVAGBlock;
private boolean loopAtNextVAGBlock;
private static final double[][] VAG_f = {
{0.0, 0.0},
{60.0 / 64.0, 0.0},
{115.0 / 64.0, -52.0 / 64.0},
{98.0 / 64.0, -55.0 / 64.0},
{122.0 / 64.0, -60.0 / 64.0}
};
public SampleSourceVAG(SoundVoice voice, int address, int size, boolean loopMode) {
this.voice = voice;
this.address = address;
this.loopMode = loopMode;
if (address == 0) {
numberSamples = 0;
numberVGABlocks = 0;
} else {
readHeader();
numberVGABlocks = size / 16;
numberSamples = numberVGABlocks * 28;
currentSampleIndex = -1;
setSampleIndex(0);
if (log.isTraceEnabled()) {
log.trace(String.format("VAG numberVGABlocks=%d, numberSamples=%d", numberVGABlocks, numberSamples));
}
}
}
private void readHeader() {
Memory mem = Memory.getInstance();
int header = mem.read32(address);
if ((header & 0x00FFFFFF) == 0x00474156) { // VAGx.
int version = Integer.reverseBytes(mem.read32(address + 4));
int dataSize = Integer.reverseBytes(mem.read32(address + 12));
int sampleRate = Integer.reverseBytes(mem.read32(address + 16));
String dataName = new StringBuffer(Utilities.readStringNZ(address + 32, 16)).reverse().toString();
if (log.isDebugEnabled()) {
log.debug(String.format("SampleSourceVAG found VAG/ADPCM data: version=%d, size=%d, sampleRate=%d, dataName='%s'", version, dataSize, sampleRate, dataName));
}
address += 0x30;
}
}
private boolean unpackNextVAGBlock() {
if (currentVAGBlock >= numberVGABlocks) {
if (log.isTraceEnabled()) {
log.trace(String.format("VAG reached end of blocks currentVAGBlock=%d, numberVAGBlocks=%d", currentVAGBlock, numberVGABlocks));
}
return false;
}
sampleIndex = 0;
int n = memoryReader.readNext();
int predict_nr = n >> 4;
if (predict_nr >= VAG_f.length) {
predict_nr = 0;
}
int shift_factor = n & 0x0F;
int flag = memoryReader.readNext();
if (flag == 0x03) {
// If loop mode is enabled, this flag indicates
// the final block of the loop.
// Do not loop if the voice has been keyed Off.
if (loopMode && voice.isOn()) {
if (log.isTraceEnabled()) {
log.trace(String.format("SampleSourceVAG loop at next VAG Block[%d], voice=0x%X", currentVAGBlock, voice.getIndex()));
}
loopAtNextVAGBlock = true;
}
} else if (flag == 0x06) {
// If loop mode is enabled, this flag indicates
// the first block of the loop.
// TODO: Implement loop processing by decoding
// the same samples within the loop flags
// when loop mode is on.
if (log.isTraceEnabled()) {
log.trace(String.format("SampleSourceVAG loop start VAG Block[%d], voice=0x%X", currentVAGBlock, voice.getIndex()));
}
loopStartVAGBlock = currentVAGBlock;
} else if (flag == 0x07) {
numberVGABlocks = currentVAGBlock;
numberSamples = numberVGABlocks * 28;
sampleIndex = samples.length;
return false; // End of stream flag.
}
for (int j = 0; j < 28; j += 2) {
int d = memoryReader.readNext();
int s = (short) ((d & 0x0F) << 12);
unpackedSamples[j] = s >> shift_factor;
s = (short) ((d & 0xF0) << 8);
unpackedSamples[j + 1] = s >> shift_factor;
}
for (int j = 0; j < 28; j++) {
int sample = (int) (unpackedSamples[j] + hist1 * VAG_f[predict_nr][0] + hist2 * VAG_f[predict_nr][1]);
hist2 = hist1;
hist1 = sample;
if (sample < -32768) {
samples[j] = -32768;
} else if (sample > 0x7FFF) {
samples[j] = 0x7FFF;
} else {
samples[j] = (short) sample;
}
}
currentVAGBlock++;
return true;
}
@Override
public int getNextSample() {
if (sampleIndex >= samples.length) {
if (!unpackNextVAGBlock()) {
return 0;
}
}
short sample = samples[sampleIndex];
if (log.isTraceEnabled()) {
log.trace(String.format("SampleSourceVAG.getNextSample[%d/%d]=0x%04X, voice=0x%X", sampleIndex, currentVAGBlock, sample & 0xFFFF, voice.getIndex()));
}
sampleIndex++;
currentSampleIndex++;
if (loopAtNextVAGBlock && sampleIndex >= samples.length) {
loopAtNextVAGBlock = false;
setSampleIndex(loopStartVAGBlock * 28);
}
return sample & 0x0000FFFF;
}
private void setSampleIndex(int index) {
currentSampleIndex = index;
currentVAGBlock = index / 28;
if (currentVAGBlock >= numberVGABlocks) {
sampleIndex = samples.length;
} else {
int restSamples = numberSamples - index;
memoryReader = MemoryReader.getMemoryReader(address + (currentVAGBlock << 4), restSamples << 2, 1);
if (unpackNextVAGBlock()) {
sampleIndex = index % 28;
}
}
if (log.isTraceEnabled()) {
log.trace(String.format("SampleSourceVAG.setSampleIndex %d = %d/%d, voice=0x%X", index, sampleIndex, currentVAGBlock, voice.getIndex()));
}
}
@Override
public void resetToStart() {
currentSampleIndex = -1;
setSampleIndex(0);
}
@Override
public boolean isEnded() {
if (currentVAGBlock > numberVGABlocks) {
return true;
}
if (currentVAGBlock == numberVGABlocks && sampleIndex >= samples.length) {
return true;
}
return false;
}
@Override
public String toString() {
return String.format("SampleSourceVAG[index=%d,VAG=%d[%d],loopStart=%d,loop at next=%b]", currentSampleIndex, currentVAGBlock, sampleIndex, loopStartVAGBlock, loopAtNextVAGBlock);
}
}