#include "adpcm.h"
#include <algorithm>
#include <array>
#include <cstring>
namespace whiteout::storages::mpq {
namespace {
static constexpr std::array<int, 32> kNextStepTable = {{
-1, 0, -1, 4, -1, 2, -1, 6, -1, 1, -1, 5, -1, 3, -1, 7,
-1, 1, -1, 5, -1, 3, -1, 7, -1, 2, -1, 4, -1, 6, -1, 8,
}};
static constexpr std::array<int, 89> kStepSizeTable = {{
7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23,
25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80,
88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279,
307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963,
1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327,
3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487,
12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767,
}};
static constexpr int kInitialStepIndex = 0x2C;
static constexpr int kMaxStepIndex = 88;
inline short getNextStepIndex(int stepIndex, unsigned int encodedSample) {
stepIndex += kNextStepTable[encodedSample & 0x1F];
return static_cast<short>(std::clamp(stepIndex, 0, kMaxStepIndex));
}
inline int updatePredictedSample(int predicted, int encoded, int difference, int signBit = 0x40) {
if (encoded & signBit) {
predicted -= difference;
if (predicted <= -32768)
predicted = -32768;
} else {
predicted += difference;
if (predicted >= 32767)
predicted = 32767;
}
return predicted;
}
inline int decodeSample(int predicted, int encoded, int stepSize, int difference) {
if (encoded & 0x01)
difference += (stepSize >> 0);
if (encoded & 0x02)
difference += (stepSize >> 1);
if (encoded & 0x04)
difference += (stepSize >> 2);
if (encoded & 0x08)
difference += (stepSize >> 3);
if (encoded & 0x10)
difference += (stepSize >> 4);
if (encoded & 0x20)
difference += (stepSize >> 5);
return updatePredictedSample(predicted, encoded, difference);
}
struct AdpcmStream {
const u8* data;
size_t size;
size_t pos = 0;
AdpcmStream(const u8* d, size_t s) : data(d), size(s) {}
bool readByte(u8& out) {
if (pos >= size)
return false;
out = data[pos++];
return true;
}
bool readWord(short& out) {
if (pos + 2 > size)
return false;
out = static_cast<short>(data[pos] | (static_cast<u16>(data[pos + 1]) << 8));
pos += 2;
return true;
}
};
}
std::vector<u8> adpcmDecompress(std::span<const u8> src, size_t expectedSize, int channelCount) {
if (channelCount < 1 || channelCount > 2)
return {};
if (src.size() < 4)
return {};
std::vector<u8> out;
out.reserve(expectedSize);
AdpcmStream is(src.data(), src.size());
std::array<short, 2> predictedSamples = {0, 0};
std::array<short, 2> stepIndexes = {kInitialStepIndex, kInitialStepIndex};
u8 dummy, bitShift;
is.readByte(dummy);
if (!is.readByte(bitShift))
return {};
for (int i = 0; i < channelCount; i++) {
short initialSample;
if (!is.readWord(initialSample))
return out;
predictedSamples[i] = initialSample;
u8 const lo = static_cast<u8>(initialSample & 0xFF);
u8 const hi = static_cast<u8>((initialSample >> 8) & 0xFF);
out.push_back(lo);
out.push_back(hi);
}
int channelIndex = channelCount - 1;
u8 encodedSample;
while (is.readByte(encodedSample)) {
channelIndex = (channelIndex + 1) % channelCount;
if (encodedSample == 0x80) {
if (stepIndexes[channelIndex] != 0)
stepIndexes[channelIndex]--;
short const sample = predictedSamples[channelIndex];
out.push_back(static_cast<u8>(sample & 0xFF));
out.push_back(static_cast<u8>((sample >> 8) & 0xFF));
} else if (encodedSample == 0x81) {
stepIndexes[channelIndex] += 8;
if (stepIndexes[channelIndex] > kMaxStepIndex)
stepIndexes[channelIndex] = kMaxStepIndex;
channelIndex = (channelIndex + 1) % channelCount;
} else {
int const stepIndex = stepIndexes[channelIndex];
int const stepSize = kStepSizeTable[stepIndex];
predictedSamples[channelIndex] = static_cast<short>(decodeSample(
predictedSamples[channelIndex], encodedSample, stepSize, stepSize >> bitShift));
short const sample = predictedSamples[channelIndex];
out.push_back(static_cast<u8>(sample & 0xFF));
out.push_back(static_cast<u8>((sample >> 8) & 0xFF));
stepIndexes[channelIndex] = getNextStepIndex(stepIndex, encodedSample);
}
if (out.size() >= expectedSize)
break;
}
return out;
}
std::vector<u8> adpcmCompress(std::span<const u8> src, int channelCount) {
if (channelCount < 1 || channelCount > 2)
return {};
if (src.size() < static_cast<size_t>(channelCount * 2))
return {};
if (src.size() % 2 != 0)
return {};
const size_t sampleCount = src.size() / 2;
std::vector<short> samples(sampleCount);
for (size_t i = 0; i < sampleCount; i++) {
samples[i] = static_cast<short>(static_cast<u16>(src[i * 2]) |
(static_cast<u16>(src[i * 2 + 1]) << 8));
}
std::vector<u8> out;
out.reserve(src.size());
constexpr u8 kBitShift = 1;
out.push_back(0x00);
out.push_back(kBitShift);
std::array<short, 2> predictedSamples = {0, 0};
std::array<short, 2> stepIndexes = {kInitialStepIndex, kInitialStepIndex};
size_t samplePos = 0;
for (int i = 0; i < channelCount; i++) {
short const initialSample = samples[samplePos++];
predictedSamples[i] = initialSample;
out.push_back(static_cast<u8>(initialSample & 0xFF));
out.push_back(static_cast<u8>((initialSample >> 8) & 0xFF));
}
int channelIndex = channelCount - 1;
while (samplePos < sampleCount) {
channelIndex = (channelIndex + 1) % channelCount;
short const targetSample = samples[samplePos++];
int const predicted = predictedSamples[channelIndex];
int const stepIndex = stepIndexes[channelIndex];
int const stepSize = kStepSizeTable[stepIndex];
int difference = targetSample - predicted;
bool const negative = (difference < 0);
if (negative)
difference = -difference;
int encoded = 0;
int const baseStep = stepSize >> kBitShift;
int totalDelta = baseStep;
if (difference >= totalDelta + (stepSize >> 0)) {
encoded |= 0x01;
totalDelta += (stepSize >> 0);
}
if (difference >= totalDelta + (stepSize >> 1)) {
encoded |= 0x02;
totalDelta += (stepSize >> 1);
}
if (difference >= totalDelta + (stepSize >> 2)) {
encoded |= 0x04;
totalDelta += (stepSize >> 2);
}
if (difference >= totalDelta + (stepSize >> 3)) {
encoded |= 0x08;
totalDelta += (stepSize >> 3);
}
if (difference >= totalDelta + (stepSize >> 4)) {
encoded |= 0x10;
totalDelta += (stepSize >> 4);
}
if (difference >= totalDelta + (stepSize >> 5)) {
encoded |= 0x20;
totalDelta += (stepSize >> 5);
}
if (negative)
encoded |= 0x40;
predictedSamples[channelIndex] =
static_cast<short>(decodeSample(predicted, encoded, stepSize, stepSize >> kBitShift));
stepIndexes[channelIndex] = getNextStepIndex(stepIndex, encoded);
out.push_back(static_cast<u8>(encoded));
}
return out;
}
}