mirror of
https://github.com/ScrelliCopter/VGM-Tools
synced 2025-02-21 04:09:25 +11:00
262 lines
5.4 KiB
C
262 lines
5.4 KiB
C
/* dspdecode.c (c) 2023 a dinosaur (zlib) */
|
|
|
|
#include "dsptool.h"
|
|
#include "wave.h"
|
|
#include "endian.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
|
|
typedef struct
|
|
{
|
|
uint32_t numSamples;
|
|
uint32_t numNibbles;
|
|
uint32_t sampleRate;
|
|
uint16_t loopFlag;
|
|
uint16_t format; // Reserved, always 0
|
|
uint32_t loopBeg;
|
|
uint32_t loopEnd;
|
|
uint32_t curAddress; // Reserved, always 0
|
|
int16_t coefs[16];
|
|
uint16_t gain; // Always 0
|
|
uint16_t predScale;
|
|
int16_t history[2];
|
|
uint16_t loopPredScale;
|
|
int16_t loopHistory[2];
|
|
int16_t channels;
|
|
uint16_t blockSize;
|
|
uint16_t reserved1[9];
|
|
|
|
} DspHeader;
|
|
|
|
static void readBeU32(uint32_t* restrict dst, size_t count, FILE* restrict file)
|
|
{
|
|
fread(dst, sizeof(uint32_t), count, file);
|
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
|
for (size_t i = 0; i < count; ++i)
|
|
dst[i] = swap32(dst[i]);
|
|
#endif
|
|
}
|
|
|
|
static void readBeU16(uint16_t* restrict dst, size_t count, FILE* restrict file)
|
|
{
|
|
fread(dst, sizeof(uint16_t), count, file);
|
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
|
for (size_t i = 0; i < count; ++i)
|
|
dst[i] = swap16(dst[i]);
|
|
#endif
|
|
}
|
|
|
|
static void readBeS16(int16_t* restrict dst, size_t count, FILE* restrict file)
|
|
{
|
|
fread(dst, sizeof(int16_t), count, file);
|
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
|
for (size_t i = 0; i < count; ++i)
|
|
dst[i] = (int16_t)swap16((uint16_t)dst[i]);
|
|
#endif
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
int16_t* pcm;
|
|
size_t pcmSize;
|
|
uint32_t rate;
|
|
} PcmFile;
|
|
|
|
#define PCMFILE_CLEAR() (PcmFile){ NULL, 0, 0 }
|
|
|
|
static int loadDsp(const char* path, PcmFile* out)
|
|
{
|
|
FILE* file = fopen(path, "rb");
|
|
if (!file)
|
|
{
|
|
fprintf(stderr, "File not found\n");
|
|
return 1;
|
|
}
|
|
|
|
uint8_t* adpcm = NULL;
|
|
out->pcm = NULL;
|
|
|
|
DspHeader dsp;
|
|
readBeU32(&dsp.numSamples, 1, file);
|
|
readBeU32(&dsp.numNibbles, 1, file);
|
|
readBeU32(&dsp.sampleRate, 1, file);
|
|
readBeU16(&dsp.loopFlag, 1, file);
|
|
readBeU16(&dsp.format, 1, file);
|
|
readBeU32(&dsp.loopBeg, 1, file);
|
|
readBeU32(&dsp.loopEnd, 1, file);
|
|
readBeU32(&dsp.curAddress, 1, file);
|
|
readBeS16(dsp.coefs, 16, file);
|
|
readBeU16(&dsp.gain, 1, file);
|
|
readBeU16(&dsp.predScale, 1, file);
|
|
readBeS16(dsp.history, 2, file);
|
|
readBeU16(&dsp.loopPredScale, 1, file);
|
|
readBeS16(dsp.loopHistory, 2, file);
|
|
readBeS16(&dsp.channels, 1, file);
|
|
readBeU16(&dsp.blockSize, 1, file);
|
|
readBeU16(dsp.reserved1, 9, file);
|
|
|
|
if (dsp.loopFlag > 1 || dsp.format)
|
|
goto Fail;
|
|
|
|
size_t adpcmSize = getBytesForAdpcmBuffer(dsp.numSamples);
|
|
adpcm = malloc(adpcmSize);
|
|
if (!adpcm)
|
|
goto Fail;
|
|
out->pcmSize = getBytesForPcmBuffer(dsp.numSamples);
|
|
out->pcm = malloc(out->pcmSize * sizeof(int16_t));
|
|
if (!out->pcm)
|
|
goto Fail;
|
|
|
|
fread(adpcm, 1, adpcmSize, file);
|
|
fclose(file);
|
|
file = NULL;
|
|
|
|
ADPCMINFO adpcmInfo =
|
|
{
|
|
.gain = dsp.gain,
|
|
.pred_scale = dsp.predScale,
|
|
.yn1 = dsp.history[0],
|
|
.yn2 = dsp.history[1],
|
|
.loop_pred_scale = dsp.loopPredScale,
|
|
.loop_yn1 = dsp.loopHistory[0],
|
|
.loop_yn2 = dsp.loopHistory[1]
|
|
};
|
|
memcpy(adpcmInfo.coef, dsp.coefs, sizeof(int16_t) * 16);
|
|
decode(adpcm, out->pcm, &adpcmInfo, dsp.numSamples);
|
|
|
|
free(adpcm);
|
|
|
|
out->rate = dsp.sampleRate;
|
|
return 0;
|
|
|
|
Fail:
|
|
free(out->pcm);
|
|
free(adpcm);
|
|
if (file)
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
|
|
static void usage(const char* argv0)
|
|
{
|
|
fprintf(stderr, "Usage: %s <in.dsp> [inR.dsp] [-o out.wav]\n", argv0);
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
// Parse cli arguments
|
|
char* inPathL = NULL, * inPathR = NULL, * outPath = NULL;
|
|
bool opt = false, outPathAlloc = false;
|
|
for (int i = 1; i < argc; ++i)
|
|
{
|
|
if (opt)
|
|
{
|
|
if (outPath)
|
|
usage(argv[0]);
|
|
outPath = argv[i];
|
|
opt = false;
|
|
}
|
|
else if (argv[i][0] == '-')
|
|
{
|
|
if (argv[i][1] != 'o')
|
|
usage(argv[0]);
|
|
opt = true;
|
|
}
|
|
else if (!inPathL)
|
|
{
|
|
inPathL = argv[i];
|
|
}
|
|
else if (!inPathR)
|
|
{
|
|
inPathR = argv[i];
|
|
}
|
|
else
|
|
{
|
|
usage(argv[0]);
|
|
}
|
|
}
|
|
|
|
if (opt || !inPathL)
|
|
usage(argv[0]);
|
|
|
|
// Compute output path if one wasn't specified
|
|
if (!outPath)
|
|
{
|
|
const char* base = strrchr(inPathL, '/');
|
|
#ifdef _WIN32
|
|
const char* base2 = strrchr(inPathL, '\\');
|
|
if (base2 && base2 > base)
|
|
base = base2;
|
|
#endif
|
|
if (!base)
|
|
base = inPathL;
|
|
char* ext = strrchr(base, '.');
|
|
size_t nameLen = strlen(inPathL);
|
|
if (ext)
|
|
nameLen -= strlen(ext);
|
|
outPath = malloc(nameLen + 5);
|
|
if (!outPath)
|
|
return 2;
|
|
memcpy(outPath, inPathL, nameLen);
|
|
memcpy(outPath + nameLen, ".wav", 5);
|
|
outPathAlloc = true;
|
|
}
|
|
|
|
// Convert left (and optionally right) channels to PCM, save as wave
|
|
int ret;
|
|
PcmFile left = PCMFILE_CLEAR(), right = PCMFILE_CLEAR();
|
|
if ((ret = loadDsp(inPathL, &left)))
|
|
goto Cleanup;
|
|
if (inPathR)
|
|
{
|
|
if ((ret = loadDsp(inPathR, &right)))
|
|
goto Cleanup;
|
|
if (left.pcmSize != right.pcmSize || left.rate != right.rate)
|
|
{
|
|
fprintf(stderr, "Rate or length mismatch");
|
|
ret = 1;
|
|
goto Cleanup;
|
|
}
|
|
|
|
WaveSpec wav =
|
|
{
|
|
.format = WAVE_FMT_PCM,
|
|
.channels = 2,
|
|
.rate = left.rate,
|
|
.bytedepth = sizeof(int16_t)
|
|
};
|
|
if ((ret = waveWriteBlockFile(&wav, (const void*[2]){left.pcm, right.pcm}, left.pcmSize, outPath)))
|
|
goto Cleanup;
|
|
|
|
free(right.pcm);
|
|
right = PCMFILE_CLEAR();
|
|
}
|
|
else
|
|
{
|
|
WaveSpec wav =
|
|
{
|
|
.format = WAVE_FMT_PCM,
|
|
.channels = 1,
|
|
.rate = left.rate,
|
|
.bytedepth = sizeof(int16_t)
|
|
};
|
|
if ((ret = waveWriteFile(&wav, left.pcm, left.pcmSize, outPath)))
|
|
goto Cleanup;
|
|
}
|
|
|
|
free(left.pcm);
|
|
left = PCMFILE_CLEAR();
|
|
|
|
ret = 0;
|
|
Cleanup:
|
|
free(right.pcm);
|
|
free(left.pcm);
|
|
if (outPathAlloc)
|
|
free(outPath);
|
|
|
|
return ret;
|
|
}
|