diff --git a/dsptools/CMakeLists.txt b/dsptools/CMakeLists.txt index 743186e..b0154b5 100644 --- a/dsptools/CMakeLists.txt +++ b/dsptools/CMakeLists.txt @@ -1,18 +1,21 @@ cmake_minimum_required(VERSION 3.15 FATAL_ERROR) -project(DspTool LANGUAGES C) +project(dsptools LANGUAGES C) -option(BUILD_SHARED_LIBS "Build as a Shared Object or DLL" ON) - -set(HEADERS common.h dsptool.h) -set(SOURCES math.c decode.c encode.c) - -add_library(DspTool ${HEADERS} ${SOURCES}) -set_property(TARGET DspTool PROPERTY C_STANDARD 99) -if (BUILD_SHARED_LIBS) - target_compile_definitions(DspTool PRIVATE BUILD_SHARED) -else() - target_compile_definitions(DspTool PRIVATE BUILD_STATIC) +include(CheckIncludeFile) +check_include_file("endian.h" HAVE_ENDIAN_H) +if (HAVE_ENDIAN_H) + add_definitions(HAVE_ENDIAN_H) endif() -target_compile_options(DspTool PRIVATE - $<$:-Wall -Wextra -pedantic -Wno-unused-parameter> - $<$:/Wall /wd4100>) + +add_subdirectory(libdsptool) + +set(HEADERS wave.h) +set(SOURCES wavefile.c wave.c dspdecode.c) + +add_executable(dspdecode ${HEADERS} ${SOURCES}) + +set_property(TARGET dspdecode PROPERTY C_STANDARD 99) +target_link_libraries(dspdecode DspTool::DspTool) +target_compile_options(dspdecode PRIVATE + $<$:-Wall -Wextra -pedantic> + $<$:/Wall>) diff --git a/dsptools/common.h b/dsptools/common.h new file mode 100644 index 0000000..325c711 --- /dev/null +++ b/dsptools/common.h @@ -0,0 +1,44 @@ +#ifndef COMMON_H +#define COMMON_H + +#ifdef HAVE_ENDIAN_H +# include +# define BYTE_ORDER __BYTE_ORDER +# define LITTLE_ENDIAN __LITTLE_ENDIAN +# define BIG_ENDIAN __BIG_ENDIAN +#else +# define LITTLE_ENDIAN 1234 +# define BIG_ENDIAN 4321 +# if defined( __APPLE__ ) || defined( _WIN32 ) +# define BYTE_ORDER LITTLE_ENDIAN +# else +# error Can't detect endianness +# endif +#endif + +static inline uint32_t swap32(uint32_t v) +{ + return (v << 24) | ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00) | (v >> 24); +} + +static inline uint16_t swap16(uint16_t v) +{ + return (v << 8) | (v >> 8); +} + +#if BYTE_ORDER == LITTLE_ENDIAN +# define SWAP_LE32(V) (V) +# define SWAP_LE16(V) (V) +# define SWAP_BE32(V) swap32((uint32_t)V) +# define SWAP_BE16(V) swap16((uint16_t)V) +#elif BYTE_ORDER == BIG_ENDIAN +# define SWAP_LE32(V) swap32((uint32_t)V) +# define SWAP_LE16(V) swap16((uint16_t)V) +# define SWAP_BE32(V) (V) +# define SWAP_BE16(V) (V) +#endif + +#define MIN(A, B) (((A) < (B)) ? (A) : (B)) +#define MAX(A, B) (((A) > (B)) ? (A) : (B)) + +#endif//COMMON_H diff --git a/dsptools/dspdecode.c b/dsptools/dspdecode.c new file mode 100644 index 0000000..71c2df0 --- /dev/null +++ b/dsptools/dspdecode.c @@ -0,0 +1,258 @@ +/* dspdecode.c (c) 2023 a dinosaur (zlib) */ + +#include "dsptool.h" +#include "wave.h" +#include "common.h" +#include +#include +#include +#include + +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 [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]); + + // C + 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; + } + + 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); + + return ret; +} diff --git a/dsptools/libdsptool/CMakeLists.txt b/dsptools/libdsptool/CMakeLists.txt index 8516cb3..6a75c83 100644 --- a/dsptools/libdsptool/CMakeLists.txt +++ b/dsptools/libdsptool/CMakeLists.txt @@ -3,7 +3,7 @@ project(DspTool LANGUAGES C) option(BUILD_SHARED_LIBS "Build as a Shared Object or DLL" OFF) -set(HEADERS common.h dsptool.h) +set(HEADERS ../common.h dsptool.h) set(SOURCES math.c decode.c encode.c) add_library(DspTool ${HEADERS} ${SOURCES}) diff --git a/dsptools/libdsptool/common.h b/dsptools/libdsptool/common.h deleted file mode 100644 index c8aaa45..0000000 --- a/dsptools/libdsptool/common.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef COMMON_H -#define COMMON_H - -#define MIN(a,b) (((a)<(b))?(a):(b)) -#define MAX(a,b) (((a)>(b))?(a):(b)) - -#endif//COMMON_H diff --git a/dsptools/libdsptool/decode.c b/dsptools/libdsptool/decode.c index c852f28..3742fb9 100644 --- a/dsptools/libdsptool/decode.c +++ b/dsptools/libdsptool/decode.c @@ -1,7 +1,7 @@ /* (c) 2017 Alex Barney (MIT) */ #include -#include "common.h" +#include "../common.h" #include "dsptool.h" static inline uint8_t GetHighNibble(uint8_t value) diff --git a/dsptools/libdsptool/encode.c b/dsptools/libdsptool/encode.c index 0ab96dd..faf16d2 100644 --- a/dsptools/libdsptool/encode.c +++ b/dsptools/libdsptool/encode.c @@ -6,7 +6,7 @@ #include #include #include -#include "common.h" +#include "../common.h" #include "dsptool.h" /* Temporal Vector diff --git a/dsptools/wave.c b/dsptools/wave.c new file mode 100644 index 0000000..3403cfa --- /dev/null +++ b/dsptools/wave.c @@ -0,0 +1,149 @@ +/* wave.c (c) 2023 a dinosaur (zlib) */ + +#include "wave.h" +#include "common.h" + +#define FOURCC_RIFF "RIFF" +#define FOURCC_WAVE "WAVE" +#define FOURCC_FORM "fmt " +#define FOURCC_SAMP "smpl" +#define FOURCC_DATA "data" + +#define FORMAT_CHUNK_SIZE 16 +typedef struct +{ + uint16_t format; + uint16_t channels; + uint32_t samplerate; + uint32_t byterate; + uint16_t alignment; + uint16_t bitdepth; + +} FormatChunk; + +typedef struct +{ + uint32_t id; + uint32_t type; + uint32_t loopstart; + uint32_t loopend; + uint32_t fraction; + uint32_t playcount; + +} SampleLoopChunk; + +#define SMPL_CHUNK_HEAD_SIZE 24 +typedef struct +{ + uint32_t manufacturer; + uint32_t product; + uint32_t samplePeriod; + uint32_t midiUniNote; + uint32_t midiPitchFrac; + uint32_t smpteFormat; + uint32_t smpteOffset; + uint32_t sampleLoopCount; + uint32_t sampleData; + +} SamplerChunk; + +static void writeFourcc(const WaveStreamCb* restrict cb, void* restrict user, const char fourcc[4]) +{ + cb->write(user, fourcc, 1, 4); +} + +static void writeU32le(const WaveStreamCb* restrict cb, void* restrict user, uint32_t v) +{ + uint32_t tmp = SWAP_LE32(v); + cb->write(user, &tmp, sizeof(uint32_t), 1); +} + +static void writeU16le(const WaveStreamCb* restrict cb, void* restrict user, uint16_t v) +{ + uint16_t tmp = SWAP_LE16(v); + cb->write(user, &tmp, sizeof(uint16_t), 1); +} + +void writeRiffChunk(const WaveStreamCb* restrict cb, void* restrict user, char fourcc[4], uint32_t size) +{ + writeFourcc(cb, user, fourcc); + writeU32le(cb, user, size); +} + +void writeFormatChunk(const WaveStreamCb* restrict cb, void* restrict user, const FormatChunk* fmt) +{ + writeRiffChunk(cb, user, FOURCC_FORM, FORMAT_CHUNK_SIZE); + writeU16le(cb, user, fmt->format); + writeU16le(cb, user, fmt->channels); + writeU32le(cb, user, fmt->samplerate); + writeU32le(cb, user, fmt->byterate); + writeU16le(cb, user, fmt->alignment); + writeU16le(cb, user, fmt->bitdepth); +} + +static int waveWriteHeader(const WaveSpec* spec, size_t dataLen, const WaveStreamCb* cb, void* user) +{ + if (!spec || !dataLen || dataLen >= UINT32_MAX || !cb || !cb->write) + return 1; + + if (spec->format != WAVE_FMT_PCM) + return 1; + if (spec->channels <= 0 || spec->channels >= INT16_MAX) + return 1; + if (spec->format == WAVE_FMT_PCM && (spec->bytedepth <= 0 || spec->bytedepth > 4)) + return 1; + + // write riff container + writeRiffChunk(cb, user, FOURCC_RIFF, sizeof(uint32_t) * 5 + FORMAT_CHUNK_SIZE + (uint32_t)dataLen); + writeFourcc(cb, user, FOURCC_WAVE); + + writeFormatChunk(cb, user, &(const FormatChunk) + { + .format = spec->format, + .channels = (uint16_t)spec->channels, + .samplerate = spec->rate, + .byterate = spec->rate * spec->channels * spec->bytedepth, + .alignment = (uint16_t)(spec->channels * spec->bytedepth), + .bitdepth = (uint16_t)spec->bytedepth * 8 + }); + + // write data chunk + writeFourcc(cb, user, FOURCC_DATA); + writeU32le(cb, user, (uint32_t)dataLen); + + return 0; +} + +int waveWrite(const WaveSpec* spec, const void* data, size_t dataLen, const WaveStreamCb* cb, void* user) +{ + if (!data) + return 1; + + // Write RIFF/Wave header and raw interleaved samples + int res = waveWriteHeader(spec, dataLen, cb, user); + if (res) + return res; + //FIXME: not endian safe + cb->write(user, data, 1, dataLen); + + return 0; +} + +int waveWriteBlock(const WaveSpec* spec, const void* blocks[], size_t blockLen, const WaveStreamCb* cb, void* user) +{ + if (!blocks) + return 1; + + // Write RIFF/Wave header to file + int res = waveWriteHeader(spec, blockLen * spec->channels, cb, user); + if (res) + return res; + + // Copy & interleave + //FIXME: not endian safe + for (size_t i = 0; i < blockLen / spec->bytedepth; ++i) + for (int j = 0; j < spec->channels; ++j) + cb->write(user, &((const char**)blocks)[j][i * spec->bytedepth], spec->bytedepth, 1); + + return 0; +} diff --git a/dsptools/wave.h b/dsptools/wave.h new file mode 100644 index 0000000..c1e24a2 --- /dev/null +++ b/dsptools/wave.h @@ -0,0 +1,45 @@ +#ifndef WAVE_H +#define WAVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef enum +{ + WAVE_FMT_PCM = 0x0001, + WAVE_FMT_IEEE_FLOAT = 0x0003, + WAVE_FMT_ALAW = 0x0006, + WAVE_FMT_MULAW = 0x0007, + WAVE_FMT_EXTENSIBLE = 0xFFFE +} WaveFormat; + +typedef struct +{ + WaveFormat format; + int channels; + unsigned rate; + int bytedepth; +} WaveSpec; + +typedef struct +{ + size_t (*read)(void* restrict user, void* restrict out, size_t size, size_t num); + size_t (*write)(void* restrict user, const void* restrict src, size_t size, size_t num); + void (*seek)(void* restrict user, int offset); + size_t (*tell)(void* user); + int (*eof)(void* user); +} WaveStreamCb; + +int waveWrite(const WaveSpec* spec, const void* data, size_t dataLen, const WaveStreamCb* cb, void* user); +int waveWriteFile(const WaveSpec* spec, const void* data, size_t dataLen, const char* path); +int waveWriteBlock(const WaveSpec* spec, const void* blocks[], size_t blockLen, const WaveStreamCb* cb, void* user); +int waveWriteBlockFile(const WaveSpec* spec, const void* blocks[], size_t blockLen, const char* path); + +#ifdef __cplusplus +} +#endif + +#endif//WAVE_H diff --git a/dsptools/wavefile.c b/dsptools/wavefile.c new file mode 100644 index 0000000..750db9a --- /dev/null +++ b/dsptools/wavefile.c @@ -0,0 +1,57 @@ +/* wavefile.c (c) 2023 a dinosaur (zlib) */ + +#include "wave.h" +#include + +static size_t waveFileRead(void* restrict user, void* restrict out, size_t size, size_t num) +{ + return fread(out, size, num, (FILE*)user); +} + +static size_t waveFileWrite(void* restrict user, const void* restrict src, size_t size, size_t num) +{ + return fwrite(src, size, num, (FILE*)user); +} + +static void waveFileSeek(void* restrict user, int offset) +{ +#ifndef _MSC_VER + fseek((FILE*)user, (off_t)offset, SEEK_CUR); +#else + fseek((FILE*)user, (long)offset, SEEK_CUR); +#endif +} + +static size_t waveFileTell(void* user) +{ + return ftell((FILE*)user); +} + +static int waveFileEof(void* user) +{ + return feof((FILE*)user) || ferror((FILE*)user); +} + +int waveWriteFile(const WaveSpec* spec, const void* data, size_t dataLen, const char* path) +{ + FILE* file = fopen(path, "wb"); + if (!file) + return 1; + + const WaveStreamCb cb = { .write = waveFileWrite }; + int res = waveWrite(spec, data, dataLen, &cb, (void*)file); + fclose(file); + return res; +} + +int waveWriteBlockFile(const WaveSpec* spec, const void* blocks[], size_t blockLen, const char* path) +{ + FILE* file = fopen(path, "wb"); + if (!file) + return 1; + + const WaveStreamCb cb = { .write = waveFileWrite }; + int res = waveWriteBlock(spec, blocks, blockLen, &cb, (void*)file); + fclose(file); + return res; +}