1
0
mirror of https://github.com/ScrelliCopter/VGM-Tools synced 2025-02-21 04:09:25 +11:00

Compare commits

29 Commits

Author SHA1 Message Date
05008f5c47 get adpcm tools working on mac and somewhat unify the codebase 2023-03-20 11:52:50 +11:00
f70bcd0a50 Fix apple build and sleep deprivation code 2023-03-20 10:09:28 +11:00
7c9c2464cb dsptool: basic dspdecode implementation 2023-03-19 21:32:18 +11:00
99bf061438 dsptool: refactor 2023-03-19 14:16:06 +11:00
ac89564669 DspTool dump 2023-03-19 08:45:10 +11:00
9cdae38cbf update licenses 2023-03-19 08:30:02 +11:00
5a059441df Prob not a bad idea to link back here in feropm.py 2020-12-15 21:51:27 +11:00
a8cd2f0f36 Create README.md 2020-10-31 15:32:54 +11:00
695f6a1bf1 Fix oversight with the source path ending up the patch name & use output name when specified 2020-10-09 11:48:12 +11:00
862639b4fe Add crummy OPN/OPM preset conversion tool shat out in a sleep deprived stupor 2020-10-09 11:28:59 +11:00
5cfb861369 Refactor most of the opening and scanning between sources 2020-07-08 21:28:04 +10:00
6456404bd3 neotools: fuck it, we CMake now 2020-05-26 10:29:57 +10:00
08b61568e1 add stub for loading vgz directly 2019-10-03 00:44:27 +10:00
a76bb43ec1 makefile builds objects into a build artifacts directory
also forgot to commit the header
2019-10-02 12:37:08 +10:00
2a654f25e8 move main to a new source file, update makefile to support multiple sources 2019-10-02 12:26:12 +10:00
47df3e2177 add some basic error checking in DecodeSample 2019-10-02 10:59:07 +10:00
6510096f90 malloc cast is unnecessary in C 2019-10-01 23:50:01 +10:00
07ee0b546c rename and build as C 2019-10-01 23:46:52 +10:00
454fcd3226 use c include for stdint 2019-10-01 23:40:22 +10:00
c94016e793 use a lightweight buffer class for in-memory samples 2019-10-01 23:37:55 +10:00
172174c837 import stdio instead of string 2019-10-01 23:26:19 +10:00
3fafdfd3f4 use c string for out names 2019-10-01 23:25:13 +10:00
6ccf9f6e38 snprintf for output names 2019-10-01 23:23:38 +10:00
b12258970e replace cout with printf 2019-09-30 23:17:47 +10:00
886966d7dc merge DecodeSample & DumpBytes 2019-09-30 20:43:07 +10:00
63aacb9a01 FILE io for DumpBytes 2019-09-30 20:33:58 +10:00
7c66bbab52 fix some error prone stuff, also I goofed the short circuit... oops 2019-09-30 17:14:07 +10:00
fbf887b534 c io for input 2019-09-29 11:51:14 +10:00
0a26bc8998 reformat 2019-09-28 10:18:39 +10:00
30 changed files with 1986 additions and 445 deletions

15
COPYING.zlib Normal file
View File

@@ -0,0 +1,15 @@
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

11
README.md Normal file
View File

@@ -0,0 +1,11 @@
# [VGM-Tools](https://github.com/ScrelliCopter/VGM-Tools)
Personal scratch pad for various VGM extraction/transformation projects.
Tools that are used in scripts are usually included in-tree and may be out of date or augmented from their upstream versions.
All original code is Zlib licensed (see COPYING.zlib for details), other copyrights will be specified where appropriate.
This repository is in a constant state of flux and things may not work or compile at any one time, you may want to look at the [Releases](https://github.com/ScrelliCopter/VGM-Tools/releases) tab for snapshots of the repository that may prove more useful.
The tools herein are primarily made for my own use or some kind of one-off problem, but they are provided here in the hopes they may be useful to someone.

69
common/endian.h Normal file
View File

@@ -0,0 +1,69 @@
#ifndef COMMON_ENDIAN_H
#define COMMON_ENDIAN_H
#ifdef __APPLE__
# include <machine/endian.h>
#elif defined( __linux__ ) || defined( __CYGWIN__ ) || defined( __OpenBSD__ )
# include <endian.h>
#elif defined( __NetBSD__ ) || defined( __FreeBSD__ ) || defined( __DragonFly__ )
# include <sys/endian.h>
# ifdef __FreeBSD__
# define LITTLE_ENDIAN _LITTLE_ENDIAN
# define BIG_ENDIAN _BIG_ENDIAN
# define BYTE_ORDER _BYTE_ORDER
# endif
#elif defined( _MSC_VER ) || defined( _WIN16 ) || defined( _WIN32 ) || defined( _WIN64 )
# ifdef _MSC_VER
# define LITTLE_ENDIAN 1234
# define BIG_ENDIAN 4321
# if defined( _M_IX86 ) || defined( _M_X64 ) || defined( _M_AMD64 ) || defined( _M_IA64 )
# define BYTE_ORDER LITTLE_ENDIAN
# elif defined( _M_PPC )
// Probably not reliable but eh
# define BYTE_ORDER BIG_ENDIAN
# endif
# elif defined( __GNUC__ )
# include <sys/param.h>
# endif
#endif
#if !defined( BYTE_ORDER ) || !defined( LITTLE_ENDIAN ) || !defined( BIG_ENDIAN ) || \
!( BYTE_ORDER == LITTLE_ENDIAN || BYTE_ORDER == BIG_ENDIAN )
# error "Couldn't determine endianness or unsupported platform"
#endif
#ifdef _MSC_VER
# define swap32(X) _byteswap_ulong((X))
# define swap16(X) _byteswap_ushort((X))
#elif ( __GNUC__ == 4 && __GNUC_MINOR__ >= 8 ) || ( __GNUC__ > 4 )
# pragma message("CLion penis")
# define swap32(X) __builtin_bswap32((X))
# define swap16(X) __builtin_bswap16((X))
// Apparently smelly GCC 5 blows up on this test so this is done separately for Clang
#elif defined( __has_builtin ) && __has_builtin( __builtin_bswap32 ) && __has_builtin( __builtin_bswap16 )
# define swap32(X) __builtin_bswap32((X))
# define swap16(X) __builtin_bswap16((X))
#else
static inline uint32_t swap32(uint32_t v)
{
return (v << 24U) | ((v << 8U) & 0x00FF0000U) | ((v >> 8U) & 0x0000FF00U) | (v >> 24U);
}
static inline uint16_t swap16(uint16_t v)
{
return (v << 8U) | (v >> 8U);
}
#endif
#if BYTE_ORDER == LITTLE_ENDIAN
# define SWAP_LE32(V) (V)
# define SWAP_LE16(V) (V)
# define SWAP_BE32(V) swap32((V))
# define SWAP_BE16(V) swap16((V))
#elif BYTE_ORDER == BIG_ENDIAN
# define SWAP_LE32(V) swap32((V))
# define SWAP_LE16(V) swap16((V))
# define SWAP_BE32(V) (V)
# define SWAP_BE16(V) (V)
#endif
#endif//COMMON_ENDIAN_H

16
common/util.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef COMMON_UTIL_H
#define COMMON_UTIL_H
#define MIN(A, B) (((A) < (B)) ? (A) : (B))
#define MAX(A, B) (((A) > (B)) ? (A) : (B))
#define CLAMP(X, A, B) (MIN(MAX((X), (A)), (B)))
#if ( defined( __GNUC__ ) && ( __GNUC__ >= 4 ) ) || defined( __clang__ )
#define FORCE_INLINE inline __attribute__((always_inline))
#elif defined( _MSC_VER )
#define FORCE_INLINE __forceinline
#else
#define FORCE_INLINE inline
#endif
#endif//COMMON_UTIL_H

147
common/wave.c Normal file
View File

@@ -0,0 +1,147 @@
/* wave.c (c) 2023 a dinosaur (zlib) */
#include "wave.h"
#include "endian.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)
{
// Write RIFF/Wave header and raw interleaved samples
int res = waveWriteHeader(spec, dataLen, cb, user);
if (res)
return res;
//FIXME: not endian safe
if (data)
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;
}

48
common/wave.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef WAVE_H
#define WAVE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
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;
extern const WaveStreamCb waveStreamDefaultCb;
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

64
common/wavefile.c Normal file
View File

@@ -0,0 +1,64 @@
/* wavefile.c (c) 2023 a dinosaur (zlib) */
#include "wave.h"
#include <stdio.h>
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);
}
const WaveStreamCb waveStreamDefaultCb =
{
.read = waveFileRead,
.write = waveFileWrite,
.seek = waveFileSeek,
.tell = waveFileTell,
.eof = waveFileEof
};
int waveWriteFile(const WaveSpec* spec, const void* data, size_t dataLen, const char* path)
{
FILE* file = fopen(path, "wb");
if (!file)
return 1;
int res = waveWrite(spec, data, dataLen, &waveStreamDefaultCb, (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;
int res = waveWriteBlock(spec, blocks, blockLen, &waveStreamDefaultCb, (void*)file);
fclose(file);
return res;
}

18
dsptools/CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(dsptools LANGUAGES C)
set(COMMON "${CMAKE_SOURCE_DIR}/../common")
add_subdirectory(libdsptool)
set(HEADERS ${COMMON}/wave.h ${COMMON}/endian.h)
set(SOURCES ${COMMON}/wavefile.c ${COMMON}/wave.c dspdecode.c)
add_executable(dspdecode ${HEADERS} ${SOURCES})
set_property(TARGET dspdecode PROPERTY C_STANDARD 99)
target_include_directories(dspdecode PRIVATE ${COMMON})
target_link_libraries(dspdecode DspTool::DspTool)
target_compile_options(dspdecode PRIVATE
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic>
$<$<C_COMPILER_ID:MSVC>:/Wall>)

261
dsptools/dspdecode.c Normal file
View File

@@ -0,0 +1,261 @@
/* 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;
}

View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(DspTool LANGUAGES C)
option(BUILD_SHARED_LIBS "Build as a Shared Object or DLL" OFF)
set(HEADERS dsptool.h ${COMMON}/util.h)
set(SOURCES math.c decode.c encode.c)
add_library(DspTool ${HEADERS} ${SOURCES})
add_library(DspTool::DspTool ALIAS DspTool)
set_property(TARGET DspTool PROPERTY C_STANDARD 99)
target_include_directories(DspTool
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${COMMON})
if (BUILD_SHARED_LIBS)
target_compile_definitions(DspTool PRIVATE BUILD_SHARED)
endif()
target_compile_options(DspTool PRIVATE
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic -Wno-unused-parameter>
$<$<C_COMPILER_ID:MSVC>:/Wall /wd4100>)

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Alex Barney
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,101 @@
/* (c) 2017 Alex Barney (MIT) */
#include <stdint.h>
#include "util.h"
#include "dsptool.h"
static inline uint8_t GetHighNibble(uint8_t value)
{
return value >> 4 & 0xF;
}
static inline uint8_t GetLowNibble(uint8_t value)
{
return value & 0xF;
}
static inline int16_t Clamp16(int value)
{
if (value > INT16_MAX)
return INT16_MAX;
if (value < INT16_MIN)
return INT16_MIN;
return (int16_t)value;
}
void decode(uint8_t* src, int16_t* dst, ADPCMINFO* cxt, uint32_t samples)
{
short hist1 = cxt->yn1;
short hist2 = cxt->yn2;
short* coefs = cxt->coef;
uint32_t frameCount = (samples + SAMPLES_PER_FRAME - 1) / SAMPLES_PER_FRAME;
uint32_t samplesRemaining = samples;
for (uint32_t i = 0; i < frameCount; i++)
{
int predictor = GetHighNibble(*src);
int scale = 1 << GetLowNibble(*src++);
short coef1 = coefs[predictor * 2];
short coef2 = coefs[predictor * 2 + 1];
uint32_t samplesToRead = MIN(SAMPLES_PER_FRAME, samplesRemaining);
for (uint32_t s = 0; s < samplesToRead; s++)
{
int sample = (s & 0x1)
? GetLowNibble(*src++)
: GetHighNibble(*src);
sample = sample >= 8 ? sample - 16 : sample;
sample = (scale * sample) << 11;
sample = (sample + 1024 + (coef1 * hist1 + coef2 * hist2)) >> 11;
short finalSample = Clamp16(sample);
hist2 = hist1;
hist1 = finalSample;
*dst++ = finalSample;
}
samplesRemaining -= samplesToRead;
}
}
void getLoopContext(uint8_t* src, ADPCMINFO* cxt, uint32_t samples)
{
short hist1 = cxt->yn1;
short hist2 = cxt->yn2;
short* coefs = cxt->coef;
uint8_t ps = 0;
int frameCount = (int)((samples + SAMPLES_PER_FRAME - 1) / SAMPLES_PER_FRAME);
uint32_t samplesRemaining = samples;
for (int i = 0; i < frameCount; i++)
{
ps = *src;
int predictor = GetHighNibble(*src);
int scale = 1 << GetLowNibble(*src++);
short coef1 = coefs[predictor * 2];
short coef2 = coefs[predictor * 2 + 1];
uint32_t samplesToRead = MIN(SAMPLES_PER_FRAME, samplesRemaining);
for (uint32_t s = 0; s < samplesToRead; s++)
{
int sample = (s & 0x1)
? GetLowNibble(*src++)
: GetHighNibble(*src);
sample = sample >= 8 ? sample - 16 : sample;
sample = (scale * sample) << 11;
sample = (sample + 1024 + (coef1 * hist1 + coef2 * hist2)) >> 11;
short finalSample = Clamp16(sample);
hist2 = hist1;
hist1 = finalSample;
}
samplesRemaining -= samplesToRead;
}
cxt->loop_pred_scale = ps;
cxt->loop_yn1 = hist1;
cxt->loop_yn2 = hist2;
}

View File

@@ -0,0 +1,61 @@
#ifndef DSPTOOL_H
#define DSPTOOL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#define BYTES_PER_FRAME 8
#define SAMPLES_PER_FRAME 14
#define NIBBLES_PER_FRAME 16
#if defined( _WIN32 ) || defined( __CYGWIN__ )
# ifdef BUILD_SHARED
# define DLLEXPORT __declspec(dllexport)
# elif defined( WHY_ARE_YOU_USING_THIS_AS_A_DLL )
# define DLLEXPORT __declspec(dllimport)
# else
# define DLLEXPORT
# endif
#elif __GNUC__ >= 4
# define DLLEXPORT __attribute__((visibility("default")))
#else
# define DLLEXPORT
#endif
typedef struct
{
int16_t coef[16];
uint16_t gain;
uint16_t pred_scale;
int16_t yn1;
int16_t yn2;
uint16_t loop_pred_scale;
int16_t loop_yn1;
int16_t loop_yn2;
} ADPCMINFO;
DLLEXPORT void encode(int16_t* src, uint8_t* dst, ADPCMINFO* cxt, uint32_t samples);
DLLEXPORT void decode(uint8_t* src, int16_t* dst, ADPCMINFO* cxt, uint32_t samples);
DLLEXPORT void getLoopContext(uint8_t* src, ADPCMINFO* cxt, uint32_t samples);
DLLEXPORT void encodeFrame(int16_t* src, uint8_t* dst, int16_t* coefs, uint8_t one);
DLLEXPORT void correlateCoefs(int16_t* src, uint32_t samples, int16_t* coefsOut);
DLLEXPORT uint32_t getBytesForAdpcmBuffer(uint32_t samples);
DLLEXPORT uint32_t getBytesForAdpcmSamples(uint32_t samples);
DLLEXPORT uint32_t getBytesForPcmBuffer(uint32_t samples);
DLLEXPORT uint32_t getBytesForPcmSamples(uint32_t samples);
DLLEXPORT uint32_t getNibbleAddress(uint32_t samples);
DLLEXPORT uint32_t getNibblesForNSamples(uint32_t samples);
DLLEXPORT uint32_t getSampleForAdpcmNibble(uint32_t nibble);
DLLEXPORT uint32_t getBytesForAdpcmInfo(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,558 @@
/* (c) 2017 Alex Barney (MIT) */
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <float.h>
#include <string.h>
#include "util.h"
#include "dsptool.h"
/* Temporal Vector
* A contiguous history of 3 samples starting with
* 'current' and going 2 backwards
*/
typedef double tvec[3];
static void DSPEncodeFrame(short pcmInOut[16], int sampleCount, unsigned char adpcmOut[8], const short coefsIn[8][2]);
void encode(int16_t* src, uint8_t* dst, ADPCMINFO* cxt, uint32_t samples)
{
int16_t* coefs = cxt->coef;
correlateCoefs(src, samples, coefs);
uint32_t frameCount = samples / SAMPLES_PER_FRAME + (samples % SAMPLES_PER_FRAME != 0);
int16_t* pcm = src;
uint8_t* adpcm = dst;
int16_t pcmFrame[SAMPLES_PER_FRAME + 2] = { 0 };
uint8_t adpcmFrame[BYTES_PER_FRAME] = { 0 };
for (uint32_t i = 0; i < frameCount; ++i, pcm += SAMPLES_PER_FRAME, adpcm += BYTES_PER_FRAME)
{
uint32_t sampleCount = MIN(samples - i * SAMPLES_PER_FRAME, SAMPLES_PER_FRAME);
memset(pcmFrame + 2, 0, SAMPLES_PER_FRAME * sizeof(int16_t));
memcpy(pcmFrame + 2, pcm, sampleCount * sizeof(int16_t));
DSPEncodeFrame(pcmFrame, SAMPLES_PER_FRAME, adpcmFrame, (const short(*)[2])&coefs[0]);
pcmFrame[0] = pcmFrame[14];
pcmFrame[1] = pcmFrame[15];
memcpy(adpcm, adpcmFrame, getBytesForAdpcmSamples(sampleCount));
}
cxt->gain = 0;
cxt->pred_scale = *dst;
cxt->yn1 = 0;
cxt->yn2 = 0;
}
static void InnerProductMerge(tvec vecOut, const short pcmBuf[14])
{
for (int i = 0; i <= 2; i++)
{
vecOut[i] = 0.0f;
for (int x = 0; x < 14; x++)
vecOut[i] -= pcmBuf[x - i] * pcmBuf[x];
}
}
static void OuterProductMerge(tvec mtxOut[3], const short pcmBuf[14])
{
for (int x = 1; x <= 2; x++)
for (int y = 1; y <= 2; y++)
{
mtxOut[x][y] = 0.0;
for (int z = 0; z < 14; z++)
mtxOut[x][y] += pcmBuf[z - x] * pcmBuf[z - y];
}
}
static bool AnalyzeRanges(tvec mtx[3], int* vecIdxsOut)
{
double recips[3];
double val, tmp, min, max;
// Get greatest distance from zero
for (int x = 1; x <= 2; x++)
{
val = MAX(fabs(mtx[x][1]), fabs(mtx[x][2]));
if (val < DBL_EPSILON)
return true;
recips[x] = 1.0 / val;
}
int maxIndex = 0;
for (int i = 1; i <= 2; i++)
{
for (int x = 1; x < i; x++)
{
tmp = mtx[x][i];
for (int y = 1; y < x; y++)
tmp -= mtx[x][y] * mtx[y][i];
mtx[x][i] = tmp;
}
val = 0.0;
for (int x = i; x <= 2; x++)
{
tmp = mtx[x][i];
for (int y = 1; y < i; y++)
tmp -= mtx[x][y] * mtx[y][i];
mtx[x][i] = tmp;
tmp = fabs(tmp) * recips[x];
if (tmp >= val)
{
val = tmp;
maxIndex = x;
}
}
if (maxIndex != i)
{
for (int y = 1; y <= 2; y++)
{
tmp = mtx[maxIndex][y];
mtx[maxIndex][y] = mtx[i][y];
mtx[i][y] = tmp;
}
recips[maxIndex] = recips[i];
}
vecIdxsOut[i] = maxIndex;
if (mtx[i][i] == 0.0)
return true;
if (i != 2)
{
tmp = 1.0 / mtx[i][i];
for (int x = i + 1; x <= 2; x++)
mtx[x][i] *= tmp;
}
}
// Get range
min = 1.0e10;
max = 0.0;
for (int i = 1; i <= 2; i++)
{
tmp = fabs(mtx[i][i]);
if (tmp < min)
min = tmp;
if (tmp > max)
max = tmp;
}
if (min / max < 1.0e-10)
return true;
return false;
}
static void BidirectionalFilter(tvec mtx[3], const int* vecIdxs, tvec vecOut)
{
double tmp;
for (int i = 1, x = 0; i <= 2; i++)
{
int index = vecIdxs[i];
tmp = vecOut[index];
vecOut[index] = vecOut[i];
if (x != 0)
for (int y = x; y <= i - 1; y++)
tmp -= vecOut[y] * mtx[i][y];
else if (tmp != 0.0)
x = i;
vecOut[i] = tmp;
}
for (int i = 2; i > 0; i--)
{
tmp = vecOut[i];
for (int y = i + 1; y <= 2; y++)
tmp -= vecOut[y] * mtx[i][y];
vecOut[i] = tmp / mtx[i][i];
}
vecOut[0] = 1.0;
}
static bool QuadraticMerge(tvec inOutVec)
{
double v0, v1, v2 = inOutVec[2];
double tmp = 1.0 - (v2 * v2);
if (tmp == 0.0)
return true;
v0 = (inOutVec[0] - (v2 * v2)) / tmp;
v1 = (inOutVec[1] - (inOutVec[1] * v2)) / tmp;
inOutVec[0] = v0;
inOutVec[1] = v1;
return fabs(v1) > 1.0;
}
static void FinishRecord(tvec in, tvec out)
{
for (int z = 1; z <= 2; z++)
{
if (in[z] >= 1.0)
in[z] = 0.9999999999;
else if (in[z] <= -1.0)
in[z] = -0.9999999999;
}
out[0] = 1.0;
out[1] = (in[2] * in[1]) + in[1];
out[2] = in[2];
}
static void MatrixFilter(const tvec src, tvec dst)
{
tvec mtx[3];
mtx[2][0] = 1.0;
for (int i = 1; i <= 2; i++)
mtx[2][i] = -src[i];
for (int i = 2; i > 0; i--)
{
double val = 1.0 - (mtx[i][i] * mtx[i][i]);
for (int y = 1; y <= i; y++)
mtx[i - 1][y] = ((mtx[i][i] * mtx[i][y]) + mtx[i][y]) / val;
}
dst[0] = 1.0;
for (int i = 1; i <= 2; i++)
{
dst[i] = 0.0;
for (int y = 1; y <= i; y++)
dst[i] += mtx[i][y] * dst[i - y];
}
}
static void MergeFinishRecord(const tvec src, tvec dst)
{
tvec tmp;
double val = src[0];
dst[0] = 1.0;
for (int i = 1; i <= 2; i++)
{
double v2 = 0.0;
for (int y = 1; y < i; y++)
v2 += dst[y] * src[i - y];
if (val > 0.0)
dst[i] = -(v2 + src[i]) / val;
else
dst[i] = 0.0;
tmp[i] = dst[i];
for (int y = 1; y < i; y++)
dst[y] += dst[i] * dst[i - y];
val *= 1.0 - (dst[i] * dst[i]);
}
FinishRecord(tmp, dst);
}
static double ContrastVectors(const tvec source1, const tvec source2)
{
double val = (source2[2] * source2[1] + -source2[1]) / (1.0 - source2[2] * source2[2]);
double val1 = (source1[0] * source1[0]) + (source1[1] * source1[1]) + (source1[2] * source1[2]);
double val2 = (source1[0] * source1[1]) + (source1[1] * source1[2]);
double val3 = source1[0] * source1[2];
return val1 + (2.0 * val * val2) + (2.0 * (-source2[1] * val + -source2[2]) * val3);
}
static void FilterRecords(tvec vecBest[8], int exp, tvec records[], int recordCount)
{
tvec bufferList[8];
int buffer1[8];
tvec buffer2;
int index;
double value, tempVal = 0;
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < exp; y++)
{
buffer1[y] = 0;
for (int i = 0; i <= 2; i++)
bufferList[y][i] = 0.0;
}
for (int z = 0; z < recordCount; z++)
{
index = 0;
value = 1.0e30;
for (int i = 0; i < exp; i++)
{
tempVal = ContrastVectors(vecBest[i], records[z]);
if (tempVal < value)
{
value = tempVal;
index = i;
}
}
buffer1[index]++;
MatrixFilter(records[z], buffer2);
for (int i = 0; i <= 2; i++)
bufferList[index][i] += buffer2[i];
}
for (int i = 0; i < exp; i++)
if (buffer1[i] > 0)
for (int y = 0; y <= 2; y++)
bufferList[i][y] /= buffer1[i];
for (int i = 0; i < exp; i++)
MergeFinishRecord(bufferList[i], vecBest[i]);
}
}
void correlateCoefs(int16_t* source, uint32_t samples, int16_t* coefsOut)
{
uint32_t numFrames = (samples + 13) / 14;
uint32_t frameSamples;
short* blockBuffer = (short*)calloc(sizeof(short), 0x3800);
short pcmHistBuffer[2][14] = { 0 };
tvec vec1;
tvec vec2;
tvec mtx[3];
int vecIdxs[3];
tvec* records = (tvec*)calloc(sizeof(tvec), numFrames * 2);
int recordCount = 0;
tvec vecBest[8];
// Iterate though 1024-block frames
for (uint32_t x = samples; x > 0;)
{
if (x > 0x3800) // Full 1024-block frame
{
frameSamples = 0x3800;
x -= 0x3800;
}
else // Partial frame
{
// Zero lingering block samples
frameSamples = x;
for (int z = 0; z < 14 && z + frameSamples < 0x3800; z++)
blockBuffer[frameSamples + z] = 0;
x = 0;
}
// Copy (potentially non-frame-aligned PCM samples into aligned buffer)
memcpy(blockBuffer, source, frameSamples * sizeof(short));
source += frameSamples;
for (uint32_t i = 0; i < frameSamples;)
{
for (int z = 0; z < 14; z++)
pcmHistBuffer[0][z] = pcmHistBuffer[1][z];
for (int z = 0; z < 14; z++)
pcmHistBuffer[1][z] = blockBuffer[i++];
InnerProductMerge(vec1, pcmHistBuffer[1]);
if (fabs(vec1[0]) > 10.0)
{
OuterProductMerge(mtx, pcmHistBuffer[1]);
if (!AnalyzeRanges(mtx, vecIdxs))
{
BidirectionalFilter(mtx, vecIdxs, vec1);
if (!QuadraticMerge(vec1))
{
FinishRecord(vec1, records[recordCount]);
recordCount++;
}
}
}
}
}
vec1[0] = 1.0;
vec1[1] = 0.0;
vec1[2] = 0.0;
for (int z = 0; z < recordCount; z++)
{
MatrixFilter(records[z], vecBest[0]);
for (int y = 1; y <= 2; y++)
vec1[y] += vecBest[0][y];
}
for (int y = 1; y <= 2; y++)
vec1[y] /= recordCount;
MergeFinishRecord(vec1, vecBest[0]);
int exp = 1;
for (int w = 0; w < 3;)
{
vec2[0] = 0.0;
vec2[1] = -1.0;
vec2[2] = 0.0;
for (int i = 0; i < exp; i++)
for (int y = 0; y <= 2; y++)
vecBest[exp + i][y] = (0.01 * vec2[y]) + vecBest[i][y];
++w;
exp = 1 << w;
FilterRecords(vecBest, exp, records, recordCount);
}
// Write output
for (int z = 0; z < 8; z++)
{
double d;
d = -vecBest[z][1] * 2048.0;
if (d > 0.0)
coefsOut[z * 2] = (d > 32767.0) ? (short)32767 : (short)lround(d);
else
coefsOut[z * 2] = (d < -32768.0) ? (short)-32768 : (short)lround(d);
d = -vecBest[z][2] * 2048.0;
if (d > 0.0)
coefsOut[z * 2 + 1] = (d > 32767.0) ? (short)32767 : (short)lround(d);
else
coefsOut[z * 2 + 1] = (d < -32768.0) ? (short)-32768 : (short)lround(d);
}
// Free memory
free(records);
free(blockBuffer);
}
// Make sure source includes the yn values (16 samples total)
void DSPEncodeFrame(short pcmInOut[16], int sampleCount, unsigned char adpcmOut[8], const short coefsIn[8][2])
{
int inSamples[8][16];
int outSamples[8][14];
int bestIndex = 0;
int scale[8];
double distAccum[8];
// Iterate through each coef set, finding the set with the smallest error
for (int i = 0; i < 8; i++)
{
int v1, v2, v3;
int distance, index;
// Set yn values
inSamples[i][0] = pcmInOut[0];
inSamples[i][1] = pcmInOut[1];
// Round and clamp samples for this coef set
distance = 0;
for (int s = 0; s < sampleCount; s++)
{
// Multiply previous samples by coefs
inSamples[i][s + 2] = v1 = ((pcmInOut[s] * coefsIn[i][1]) + (pcmInOut[s + 1] * coefsIn[i][0])) / 2048;
// Subtract from current sample
v2 = pcmInOut[s + 2] - v1;
// Clamp
v3 = (v2 >= 32767) ? 32767 : (v2 <= -32768) ? -32768 : v2;
// Compare distance
if (abs(v3) > abs(distance))
distance = v3;
}
// Set initial scale
for (scale[i] = 0; (scale[i] <= 12) && ((distance > 7) || (distance < -8)); scale[i]++, distance /= 2)
{
}
scale[i] = (scale[i] <= 1) ? -1 : scale[i] - 2;
do
{
scale[i]++;
distAccum[i] = 0;
index = 0;
for (int s = 0; s < sampleCount; s++)
{
// Multiply previous
v1 = ((inSamples[i][s] * coefsIn[i][1]) + (inSamples[i][s + 1] * coefsIn[i][0]));
// Evaluate from real sample
v2 = (pcmInOut[s + 2] << 11) - v1;
// Round to nearest sample
v3 = (v2 > 0) ? (int)((double)v2 / (1 << scale[i]) / 2048 + 0.4999999f) : (int)((double)v2 / (1 << scale[i]) / 2048 - 0.4999999f);
// Clamp sample and set index
if (v3 < -8)
{
if (index < (v3 = -8 - v3))
index = v3;
v3 = -8;
}
else if (v3 > 7)
{
if (index < (v3 -= 7))
index = v3;
v3 = 7;
}
// Store result
outSamples[i][s] = v3;
// Round and expand
v1 = (v1 + ((v3 * (1 << scale[i])) << 11) + 1024) >> 11;
// Clamp and store
inSamples[i][s + 2] = v2 = (v1 >= 32767) ? 32767 : (v1 <= -32768) ? -32768 : v1;
// Accumulate distance
v3 = pcmInOut[s + 2] - v2;
distAccum[i] += v3 * (double)v3;
}
for (int x = index + 8; x > 256; x >>= 1)
if (++scale[i] >= 12)
scale[i] = 11;
} while ((scale[i] < 12) && (index > 1));
}
double min = DBL_MAX;
for (int i = 0; i < 8; i++)
{
if (distAccum[i] < min)
{
min = distAccum[i];
bestIndex = i;
}
}
// Write converted samples
for (int s = 0; s < sampleCount; s++)
pcmInOut[s + 2] = (short)inSamples[bestIndex][s + 2];
// Write ps
adpcmOut[0] = (char)((bestIndex << 4) | (scale[bestIndex] & 0xF));
// Zero remaining samples
for (int s = sampleCount; s < 14; s++)
outSamples[bestIndex][s] = 0;
// Write output samples
for (int y = 0; y < 7; y++)
adpcmOut[y + 1] = (char)((outSamples[bestIndex][y * 2] << 4) | (outSamples[bestIndex][y * 2 + 1] & 0xF));
}
void encodeFrame(int16_t* src, uint8_t* dst, int16_t* coefs, uint8_t one)
{
DSPEncodeFrame(src, 14, dst, (const short(*)[2])&coefs[0]);
}

View File

@@ -0,0 +1,69 @@
/* (c) 2017 Alex Barney (MIT) */
#include "dsptool.h"
uint32_t getBytesForAdpcmBuffer(uint32_t samples)
{
uint32_t frames = samples / SAMPLES_PER_FRAME;
if (samples % SAMPLES_PER_FRAME)
frames++;
return frames * BYTES_PER_FRAME;
}
uint32_t getBytesForAdpcmInfo(void)
{
return sizeof(ADPCMINFO);
}
uint32_t getBytesForAdpcmSamples(uint32_t samples)
{
uint32_t extraBytes = 0;
uint32_t frames = samples / SAMPLES_PER_FRAME;
uint32_t extraSamples = samples % SAMPLES_PER_FRAME;
if (extraSamples)
extraBytes = (extraSamples / 2) + (extraSamples % 2) + 1;
return BYTES_PER_FRAME * frames + extraBytes;
}
uint32_t getBytesForPcmBuffer(uint32_t samples)
{
uint32_t frames = samples / SAMPLES_PER_FRAME;
if (samples % SAMPLES_PER_FRAME)
frames++;
return frames * SAMPLES_PER_FRAME * sizeof(int16_t);
}
uint32_t getBytesForPcmSamples(uint32_t samples)
{
return samples * sizeof(int16_t);
}
uint32_t getNibbleAddress(uint32_t samples)
{
uint32_t frames = samples / SAMPLES_PER_FRAME;
uint32_t extraSamples = samples - (frames * SAMPLES_PER_FRAME);
return NIBBLES_PER_FRAME * frames + extraSamples + 2;
}
uint32_t getNibblesForNSamples(uint32_t samples)
{
uint32_t frames = samples / SAMPLES_PER_FRAME;
uint32_t extraSamples = samples - (frames * SAMPLES_PER_FRAME);
uint32_t extraNibbles = extraSamples == 0 ? 0 : extraSamples + 2;
return NIBBLES_PER_FRAME * frames + extraNibbles;
}
uint32_t getSampleForAdpcmNibble(uint32_t nibble)
{
uint32_t frames = nibble / NIBBLES_PER_FRAME;
uint32_t extraNibbles = nibble - (frames * NIBBLES_PER_FRAME);
uint32_t samples = SAMPLES_PER_FRAME * frames;
return samples + extraNibbles - 2;
}

152
feropm.py Executable file
View File

@@ -0,0 +1,152 @@
#!/usr/bin/env python3
# Name: feropm.py
# Copyright: © 2020 a dinosaur
# Homepage: https://github.com/ScrelliCopter/VGM-Tools
# License: Zlib (https://opensource.org/licenses/Zlib)
# Description: Script to convert csMD presets to VOPM files.
# Based on documentation provided by MovieMovies1.
from argparse import ArgumentParser
from xml.dom import minidom
from pathlib import Path
from typing import TextIO
class Preset:
class Operator:
tl = 0
ml = 0
dt = 0
ar = 0
d1 = 0
sl = 0
d2 = 0
r = 0
am_mask = 0
ssg_eg = 0
kr = 0
velo = 0
ks_lvl = 0
def __init__(self):
self.name = ""
self.alg = 0
self.fb = 0
self.lfo_vib = 0
self.lfo_trem = 0
self.op = [self.Operator() for i in range(4)]
def parse_fermatap(path: Path) -> Preset:
xdoc = minidom.parse(str(path))
xpreset = xdoc.getElementsByTagName("Preset")
xtarget = xpreset[0].getElementsByTagName("Target")
xparams = xtarget[0].getElementsByTagName("Param")
patch = Preset()
clamp = lambda x, a, b: x if x < b else b if x > a else a
invert = lambda x, a, b: b - clamp(x, a, b)
def parseop(op, id, x):
if id == 0:
patch.op[op].tl = invert(x, 0, 127)
elif id == 1:
patch.op[op].ml = clamp(x, 0, 15)
elif id == 2:
x = clamp(x, -3, 3)
patch.op[op].dt = x if x >= 0 else 4 - x
elif id == 3:
patch.op[op].ar = invert(x, 0, 31)
elif id == 4:
patch.op[op].d1 = invert(x, 0, 31)
elif id == 5:
patch.op[op].sl = invert(x, 0, 15)
elif id == 6:
patch.op[op].d2 = invert(x, 0, 31)
elif id == 7:
patch.op[op].r = invert(x, 0, 15)
elif id == 8:
patch.op[op].am_mask = clamp(x, 0, 1)
elif id == 9:
patch.op[op].ssg_eg = clamp(x, 0, 8)
elif id == 10:
patch.op[op].kr = clamp(x, 0, 3)
elif id == 11:
patch.op[op].velo = clamp(x, 0, 3)
elif id == 12:
patch.op[op].ks_lvl = clamp(x, 0, 99)
for i in xparams:
id = int(i.attributes["id"].value)
x = int(i.attributes["value"].value)
if 0 <= id <= 15:
parseop(0, id, x)
elif id <= 31:
parseop(1, id - 16, x)
elif id <= 47:
parseop(2, id - 32, x)
elif id <= 63:
parseop(3, id - 48, x)
elif id == 64:
patch.alg = clamp(x, 0, 7)
elif id == 65:
patch.fb = clamp(x, 0, 7)
elif id == 66:
patch.lfo_vib = clamp(x, 0, 7)
elif id == 67:
patch.lfo_trem = clamp(x, 0, 3)
elif id <= 71:
pass
else:
print("unrecognised parameter id {}".format(id))
return patch
def save_vopm(path: Path, patch: Preset):
fmtnum = lambda n: " " + f"{n}".rjust(3, " ")
def writech(file: TextIO):
file.write("CH:")
file.write(fmtnum(64))
file.write(fmtnum(patch.fb))
file.write(fmtnum(patch.alg))
file.write(fmtnum(patch.lfo_trem))
file.write(fmtnum(patch.lfo_vib))
file.write(fmtnum(120))
file.write(fmtnum(0))
file.write("\n")
def writeop(file: TextIO, label: str, op: Preset.Operator):
file.write(label)
for i in [op.ar, op.d1, op.d2, op.r, op.sl, op.tl, op.kr, op.ml, op.dt, 0, op.am_mask]:
file.write(fmtnum(i))
file.write("\n")
with path.open("w", encoding="utf-8") as file:
file.write(f"@:0 {patch.name}\n")
file.write("LFO: 0 0 0 0 0\n")
writech(file)
writeop(file, "M1:", patch.op[0])
writeop(file, "C1:", patch.op[1])
writeop(file, "M2:", patch.op[2])
writeop(file, "C2:", patch.op[3])
def main():
p = ArgumentParser(description="Convert fermatap-formatted csMD presets to VOPM (.opm) files.")
p.add_argument("infile", type=Path, help="Path to input csMD (.fermatap) file")
p.add_argument("--output", "-o", type=Path, required=False, help="Name of output VOPM (.opm) file")
args = p.parse_args()
patch = parse_fermatap(args.infile)
patch.name = args.output.name.rstrip(".opm") if args.output is not None else args.infile.name.rstrip(".fermatap")
outpath = args.output if args.output is not None else args.infile.parent.joinpath(patch.name + ".opm")
save_vopm(outpath, patch)
if __name__ == "__main__":
main()

7
neotools/.gitignore vendored
View File

@@ -5,7 +5,6 @@
*.pcm
*.wav
*.o
adpcm
adpcmb
neoadpcmextract
.idea/
build/
cmake-build-*/

21
neotools/CMakeLists.txt Normal file
View File

@@ -0,0 +1,21 @@
project(neoadpcmtools LANGUAGES C)
cmake_minimum_required(VERSION "3.1" FATAL_ERROR)
set(COMMON "${CMAKE_SOURCE_DIR}/../common")
set(WARNINGS
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic -Wno-unused-parameter>
$<$<C_COMPILER_ID:MSVC>:/Wall /wd4100>)
add_executable(adpcm ${COMMON}/util.h ${COMMON}/wave.h ${COMMON}/wave.c ${COMMON}/wavefile.c adpcm.c)
set_property(TARGET adpcm PROPERTY C_STANDARD 99)
target_compile_options(adpcm PRIVATE ${WARNINGS})
target_include_directories(adpcm PRIVATE ${COMMON})
target_link_libraries(adpcm $<$<C_COMPILER_ID:Clang,GNU>:m>)
add_executable(adpcmb ${COMMON}/util.h adpcmb.c)
target_compile_options(adpcmb PRIVATE ${WARNINGS})
target_include_directories(adpcmb PRIVATE ${COMMON})
add_executable(neoadpcmextract autoextract.c neoadpcmextract.c)
set_property(TARGET neoadpcmextract PROPERTY C_STANDARD 99)
target_compile_options(neoadpcmextract PRIVATE ${WARNINGS})

View File

@@ -1,34 +0,0 @@
# Standard makefile to use as a base for DJGPP projects (not anymore lol)
# By MARTINEZ Fabrice aka SNK of SUPREMACY
# Programs to use during make
LD := $(CC)
TARGET := adpcm
SOURCE := adpcm.c
# Flags for compilation
CFLAGS := -fomit-frame-pointer -O3 -Werror -Wall \
-W -Wno-sign-compare -Wno-unused \
-Wpointer-arith -Wbad-function-cast -Wcast-align -Waggregate-return \
-pedantic \
-Wshadow \
-Wstrict-prototypes
LDFLAGS := -lm
# Object files
OBJECT := $(SOURCE:%.c=%.o)
# Make rules
all: $(TARGET)
$(TARGET): $(OBJECT)
$(LD) $(CFLAGS) $(LDFLAGS) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Rules to manage files
.PHONY: clean
clean:
rm -f $(TARGET) $(OBJECT)

View File

@@ -1,159 +1,165 @@
/* adpcm.c
* original ADPCM to PCM converter v 1.01 By MARTINEZ Fabrice aka SNK of SUPREMACY
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "util.h"
#include "wave.h"
#define BUFFER_SIZE 1024*256
#define BUFFER_SIZE (1024 * 256)
#define ADPCMA_VOLUME_RATE 1
#define ADPCMA_DECODE_RANGE 1024
#define ADPCMA_DECODE_MIN (-(ADPCMA_DECODE_RANGE*ADPCMA_VOLUME_RATE))
#define ADPCMA_DECODE_MAX ((ADPCMA_DECODE_RANGE*ADPCMA_VOLUME_RATE)-1)
#define ADPCMA_VOLUME_DIV 1
#define ADPCMA_DECODE_MIN (-(ADPCMA_DECODE_RANGE * ADPCMA_VOLUME_RATE))
#define ADPCMA_DECODE_MAX ((ADPCMA_DECODE_RANGE * ADPCMA_VOLUME_RATE) - 1)
unsigned char RiffWave[44] = {
0x52, 0x49, 0x46, 0x46, 0x24, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74,
0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x44, 0x48, 0x00, 0x00, 0x88, 0x90,
0x00, 0x00, 0x02, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
} ;
static int decode_tableA1[16] = {
-1*16, -1*16, -1*16, -1*16, 2*16, 5*16, 7*16, 9*16,
-1*16, -1*16, -1*16, -1*16, 2*16, 5*16, 7*16, 9*16
static int decode_tableA1[16] =
{
-1 * 16, -1 * 16, -1 * 16, -1 * 16, 2 * 16, 5 * 16, 7 * 16, 9 * 16,
-1 * 16, -1 * 16, -1 * 16, -1 * 16, 2 * 16, 5 * 16, 7 * 16, 9 * 16
};
static int jedi_table[49*16];
static int signal;
static int delta;
void adpcm_init(void);
void adpcm_decode(void *, void *, int);
int Limit(int, int, int);
FILE* errorlog;
static int jedi_table[49 * 16];
static int cursignal;
static int delta;
void adpcm_init(void);
void adpcm_decode(void *, void *, int);
FILE* errorlog;
int main(int argc, char *argv[])
{
FILE *Fp1, *Fp2;
void *InputBuffer, *OutputBuffer;
int Readed;
unsigned int Filelen;
FILE *InputFile, *OutputFile;
void *InputBuffer, *OutputBuffer;
int bytesRead;
unsigned int Filelen;
puts("**** ADPCM to PCM converter v 1.01\n");
if (argc!=3) {
if (argc != 3)
{
puts("USAGE: adpcm <InputFile.pcm> <OutputFile.wav>");
exit(-1);
}
Fp1 = fopen(argv[1], "rb");
if (Fp1==NULL) {
InputFile = fopen(argv[1], "rb");
if (!InputFile)
{
printf("Could not open inputfile %s\n", argv[1]);
exit(-2);
}
Fp2 = fopen(argv[2], "wb");
if (Fp2==NULL) {
OutputFile = fopen(argv[2], "wb");
if (!OutputFile)
{
printf("Could not open outputfile %s\n", argv[2]);
exit(-3);
}
errorlog = fopen("error.log", "wb");
InputBuffer = malloc(BUFFER_SIZE);
if (InputBuffer == NULL) {
if (InputBuffer == NULL)
{
printf("Could not allocate input buffer. (%d bytes)\n", BUFFER_SIZE);
exit(-4);
}
OutputBuffer = malloc(BUFFER_SIZE*10);
if (OutputBuffer == NULL) {
printf("Could not allocate output buffer. (%d bytes)\n", BUFFER_SIZE*4);
OutputBuffer = malloc(BUFFER_SIZE * 10);
if (OutputBuffer == NULL)
{
printf("Could not allocate output buffer. (%d bytes)\n", BUFFER_SIZE * 4);
exit(-5);
}
adpcm_init();
fseek(Fp1, 0, SEEK_END);
Filelen = ftell(Fp1);
fseek(Fp1, 0, SEEK_SET);
fseek(InputFile, 0, SEEK_END);
Filelen = ftell(InputFile);
fseek(InputFile, 0, SEEK_SET);
*((unsigned int*)(&RiffWave[4])) = Filelen*4 + 0x2C;
*((unsigned int*)(&RiffWave[0x28])) = Filelen*4;
fwrite(RiffWave, 0x2c, 1, Fp2);
do {
Readed = fread(InputBuffer, 1, BUFFER_SIZE, Fp1);
if (Readed>0) {
adpcm_decode(InputBuffer, OutputBuffer, Readed);
fwrite(OutputBuffer, Readed*4, 1, Fp2);
// Write wave header
waveWrite(&(const WaveSpec)
{
.format = WAVE_FMT_PCM,
.channels = 1,
.rate = 18500,
.bytedepth = 2
},
NULL, Filelen * 4, &waveStreamDefaultCb, OutputFile);
// Convert ADPCM to PCM and write to wave
do
{
bytesRead = fread(InputBuffer, 1, BUFFER_SIZE, InputFile);
if (bytesRead > 0)
{
adpcm_decode(InputBuffer, OutputBuffer, bytesRead);
fwrite(OutputBuffer, bytesRead * 4, 1, OutputFile);
}
} while (Readed==BUFFER_SIZE);
} while (bytesRead == BUFFER_SIZE);
free(InputBuffer);
free(OutputBuffer);
fclose(Fp1);
fclose(Fp2);
fclose(InputFile);
fclose(OutputFile);
puts("Done...");
return 0;
}
void adpcm_init(void)
void adpcm_init(void)
{
int step, nib;
for (step = 0; step <= 48; step++)
{
int stepval = floor (16.0 * pow (11.0 / 10.0, (double)step) * ADPCMA_VOLUME_RATE);
/* loop over all nibbles and compute the difference */
int stepval = floor(16.0 * pow (11.0 / 10.0, (double)step) * ADPCMA_VOLUME_RATE);
// Loop over all nibbles and compute the difference
for (nib = 0; nib < 16; nib++)
{
int value = stepval*((nib&0x07)*2+1)/8;
jedi_table[step*16+nib] = (nib&0x08) ? -value : value;
int value = stepval * ((nib & 0x07) * 2 + 1) / 8;
jedi_table[step * 16 + nib] = (nib & 0x08) ? -value : value;
}
}
delta = 0;
signal = 0;
cursignal = 0;
}
void adpcm_decode(void *InputBuffer, void *OutputBuffer, int Length)
void adpcm_decode(void *InputBuffer, void *OutputBuffer, int Length)
{
char *in;
short *out;
int i, data, oldsignal;
char *in;
short *out;
int i, data, oldsignal;
in = (char *)InputBuffer;
out = (short *)OutputBuffer;
for(i=0;i<Length;i++) {
data = ((*in)>>4)&0x0F;
oldsignal = signal;
signal = Limit(signal + (jedi_table[data+delta]), ADPCMA_DECODE_MAX, ADPCMA_DECODE_MIN);
delta = Limit(delta + decode_tableA1[data], 48*16, 0*16);
if (abs(oldsignal-signal)>2500) {
fprintf(errorlog, "WARNING: Suspicious signal evolution %06x,%06x pos:%06x delta:%06x\n", oldsignal, signal, i, delta);
fprintf(errorlog, "data:%02x dx:%08x\n", data, jedi_table[data+delta]);
}
*(out++) = (signal&0xffff)*32;
data = (*in++)&0x0F;
oldsignal = signal;
signal = Limit(signal + (jedi_table[data+delta]), ADPCMA_DECODE_MAX, ADPCMA_DECODE_MIN);
delta = Limit(delta + decode_tableA1[data], 48*16, 0*16);
if (abs(oldsignal-signal)>2500) {
fprintf(errorlog, "WARNING: Suspicious signal evolution %06x,%06x pos:%06x delta:%06x\n", oldsignal, signal, i, delta);
fprintf(errorlog, "data:%02x dx:%08x\n", data, jedi_table[data+delta]);
for (i = 0; i < Length; i++)
{
data = ((*in) >> 4) & 0x0F;
oldsignal = cursignal;
cursignal = CLAMP(cursignal + (jedi_table[data + delta]), ADPCMA_DECODE_MIN, ADPCMA_DECODE_MAX);
delta = CLAMP(delta + decode_tableA1[data], 0 * 16, 48 * 16);
if (abs(oldsignal - cursignal) > 2500)
{
fprintf(errorlog, "WARNING: Suspicious signal evolution %06x,%06x pos:%06x delta:%06x\n", oldsignal, cursignal, i, delta);
fprintf(errorlog, "data:%02x dx:%08x\n", data, jedi_table[data + delta]);
}
*(out++) = (signal&0xffff)*32;
*(out++) = (cursignal & 0xffff) * 32;
data = (*in++) & 0x0F;
oldsignal = cursignal;
cursignal = CLAMP(cursignal + (jedi_table[data + delta]), ADPCMA_DECODE_MIN, ADPCMA_DECODE_MAX);
delta = CLAMP(delta + decode_tableA1[data], 0 * 16, 48 * 16);
if (abs(oldsignal - cursignal) > 2500)
{
fprintf(errorlog, "WARNING: Suspicious signal evolution %06x,%06x pos:%06x delta:%06x\n", oldsignal, cursignal, i, delta);
fprintf(errorlog, "data:%02x dx:%08x\n", data, jedi_table[data + delta]);
}
*(out++) = (cursignal & 0xffff) * 32;
}
}
int Limit( int val, int max, int min ) {
if ( val > max )
val = max;
else if ( val < min )
val = min;
return val;
}

View File

@@ -1,16 +0,0 @@
**** ADPCM to PCM converter v 1.01
USAGE: adpcm <InputFile.pcm> <OutputFile.wav>
By MARTINEZ Fabrice aka SNK of SUPREMACY
--(ADPCM.diz.txt)--
ADPCM To WAV Converter
This simple utility converts ADPCM files from NeoGeo cartridge systems
(*_v?.rom) or NeoGeo CD systems (*.PCM). This tool converts ADPCM A type
samples (99% of the samples), ADPCM B type samples will sound distorted...
Get it here

View File

@@ -1,15 +0,0 @@
CC = gcc
CFLAGS = -O3
SRC = .
OBJ = .
OUT_OBJ = $(OBJ)/adpcmb
all: $(OUT_OBJ)
$(OBJ)/%.exe: $(SRC)/%.c
$(CC) $(CCFLAGS) $(MAINFLAGS) $< -o $@
clean:
rm $(OUT_OBJ)

View File

@@ -1,4 +1,4 @@
/*; YM2610 ADPCM-B Codec
/*; YM2610 ADPCM-B Codec
;
;**** PCM to ADPCM-B & ADPCM-B to PCM converters for NEO-GEO System ****
;ADPCM-B - 1 channel 1.8-55.5 KHz, 16 MB Sample ROM size, 256 B min size of sample, 16 MB max, compatable with YM2608
@@ -15,59 +15,26 @@
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <math.h>
#include <string.h>
#if defined(_MSC_VER)
#define INLINE static __inline
#elif defined(__GNUC__)
#define INLINE static __inline__
#else
#define INLINE static inline
#endif
#ifdef USE_STDINT
#include <stdint.h>
typedef uint8_t UINT8;
typedef int8_t INT8;
typedef uint16_t UINT16;
typedef int16_t INT16;
typedef uint32_t UINT32;
typedef int32_t INT32;
#else
typedef unsigned char UINT8;
typedef signed char INT8;
typedef unsigned short UINT16;
typedef signed short INT16;
typedef unsigned int UINT32;
typedef signed int INT32;
#endif
typedef UINT8 BYTE;
typedef UINT16 WORD;
typedef UINT32 DWORD;
#include "util.h"
// -- from mmsystem.h --
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
//FIXME: DEELT THIS
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) | \
((uint32_t)(uint8_t)(ch2) << 16) | ((uint32_t)(uint8_t)(ch3) << 24 ))
// -- from mmreg.h, slightly modified --
/* general waveform format structure (information common to all formats) */
typedef struct waveformat_tag {
WORD wFormatTag; /* format type */
WORD nChannels; /* number of channels (i.e. mono, stereo...) */
DWORD nSamplesPerSec; /* sample rate */
DWORD nAvgBytesPerSec; /* for buffer estimation */
WORD nBlockAlign; /* block size of data */
WORD wBitsPerSample;
uint16_t wFormatTag; /* format type */
uint16_t nChannels; /* number of channels (i.e. mono, stereo...) */
uint32_t nSamplesPerSec; /* sample rate */
uint32_t nAvgBytesPerSec; /* for buffer estimation */
uint16_t nBlockAlign; /* block size of data */
uint16_t wBitsPerSample;
} WAVEFORMAT;
/* flags for wFormatTag field of WAVEFORMAT */
@@ -75,21 +42,21 @@ typedef struct waveformat_tag {
// -- from mm*.h end --
#define FOURCC_RIFF MAKEFOURCC('R', 'I', 'F', 'F')
#define FOURCC_WAVE MAKEFOURCC('W', 'A', 'V', 'E')
#define FOURCC_fmt_ MAKEFOURCC('f', 'm', 't', ' ')
#define FOURCC_data MAKEFOURCC('d', 'a', 't', 'a')
#define FOURCC_RIFF MAKEFOURCC('R', 'I', 'F', 'F')
#define FOURCC_WAVE MAKEFOURCC('W', 'A', 'V', 'E')
#define FOURCC_fmt_ MAKEFOURCC('f', 'm', 't', ' ')
#define FOURCC_data MAKEFOURCC('d', 'a', 't', 'a')
typedef struct
{
UINT32 RIFFfcc; // 'RIFF'
UINT32 RIFFLen;
UINT32 WAVEfcc; // 'WAVE'
UINT32 fmt_fcc; // 'fmt '
UINT32 fmt_Len;
uint32_t RIFFfcc; // 'RIFF'
uint32_t RIFFLen;
uint32_t WAVEfcc; // 'WAVE'
uint32_t fmt_fcc; // 'fmt '
uint32_t fmt_Len;
WAVEFORMAT fmt_Data;
UINT32 datafcc; // 'data'
UINT32 dataLen;
uint32_t datafcc; // 'data'
uint32_t dataLen;
} WAVE_FILE;
@@ -99,29 +66,29 @@ static const long stepsizeTable[16] =
57, 57, 57, 57, 77, 102, 128, 153
};
static int YM2610_ADPCM_Encode(INT16 *src, UINT8 *dest, int len)
static int YM2610_ADPCM_Encode(int16_t *src, uint8_t *dest, int len)
{
int lpc, flag;
long i, dn, xn, stepSize;
UINT8 adpcm;
UINT8 adpcmPack;
uint8_t adpcm;
uint8_t adpcmPack;
xn = 0;
stepSize = 127;
flag = 0;
for (lpc = 0; lpc < len; lpc ++)
{
dn = *src - xn;
src ++;
i = (abs(dn) << 16) / (stepSize << 14);
i = (labs(dn) << 16) / (stepSize << 14);
if (i > 7)
i = 7;
adpcm = (UINT8)i;
adpcm = (uint8_t)i;
i = (adpcm * 2 + 1) * stepSize / 8;
if (dn < 0)
{
adpcm |= 0x8;
@@ -131,14 +98,14 @@ static int YM2610_ADPCM_Encode(INT16 *src, UINT8 *dest, int len)
{
xn += i;
}
stepSize = (stepsizeTable[adpcm] * stepSize) / 64;
if (stepSize < 127)
stepSize = 127;
else if (stepSize > 24576)
stepSize = 24576;
if (flag == 0)
{
adpcmPack = (adpcm << 4);
@@ -152,76 +119,72 @@ static int YM2610_ADPCM_Encode(INT16 *src, UINT8 *dest, int len)
flag = 0;
}
}
return 0;
}
static int YM2610_ADPCM_Decode(UINT8 *src, INT16 *dest, int len)
static int YM2610_ADPCM_Decode(uint8_t *src, int16_t *dest, int len)
{
int lpc, flag, shift, step;
int lpc, shift, step;
long i, xn, stepSize;
UINT8 adpcm;
uint8_t adpcm;
xn = 0;
stepSize = 127;
flag = 0;
shift = 4;
step = 0;
for (lpc = 0; lpc < len; lpc ++)
{
adpcm = (*src >> shift) & 0xf;
i = ((adpcm & 7) * 2 + 1) * stepSize / 8;
if (adpcm & 8)
xn -= i;
else
xn += i;
if (xn > 32767)
xn = 32767;
else if (xn < -32768)
xn = -32768;
xn = CLAMP(xn, -32768, 32767);
stepSize = stepSize * stepsizeTable[adpcm] / 64;
if (stepSize < 127)
stepSize = 127;
else if (stepSize > 24576)
stepSize = 24576;
*dest = (INT16)xn;
*dest = (int16_t)xn;
dest ++;
src += step;
step = step ^ 1;
shift = shift ^ 4;
}
return 0;
}
INLINE UINT32 DeltaTReg2SampleRate(UINT16 DeltaN, UINT32 Clock)
static FORCE_INLINE uint32_t DeltaTReg2SampleRate(uint16_t DeltaN, uint32_t Clock)
{
return (UINT32)(DeltaN * (Clock / 72.0) / 65536.0 + 0.5);
return (uint32_t)(DeltaN * (Clock / 72.0) / 65536.0 + 0.5);
}
int main(int argc, char* argv[])
{
int ErrVal;
int ArgBase;
DWORD OutSmplRate;
uint32_t OutSmplRate;
FILE* hFile;
unsigned int AdpcmSize;
UINT8* AdpcmData;
uint8_t* AdpcmData;
unsigned int WaveSize;
UINT16* WaveData;
uint16_t* WaveData;
WAVE_FILE WaveFile;
WAVEFORMAT* TempFmt;
unsigned int TempLng;
UINT16 DTRegs;
uint16_t DTRegs;
char* TempPnt;
printf("NeoGeo ADPCM-B En-/Decoder\n--------------------------\n");
if (argc < 4)
{
@@ -237,18 +200,18 @@ int main(int argc, char* argv[])
printf("Wave In-/Output is 16-bit, Mono\n");
return 1;
}
if (strcmp(argv[1], "-d") && strcmp(argv[1], "-e"))
{
printf("Wrong option! Use -d or -e!\n");
return 1;
}
ErrVal = 0;
AdpcmData = NULL;
WaveData = NULL;
OutSmplRate = 0;
ArgBase = 2;
if (argv[2][0] == '-' && argv[2][2] == ':')
{
@@ -258,7 +221,7 @@ int main(int argc, char* argv[])
OutSmplRate = strtol(argv[2] + 3, NULL, 0);
break;
case 'r':
DTRegs = (UINT16)strtoul(argv[2] + 3, &TempPnt, 0);
DTRegs = (uint16_t)strtoul(argv[2] + 3, &TempPnt, 0);
TempLng = 0;
if (*TempPnt == ',')
{
@@ -276,7 +239,7 @@ int main(int argc, char* argv[])
return 1;
}
}
switch(argv[1][1])
{
case 'd':
@@ -287,26 +250,26 @@ int main(int argc, char* argv[])
ErrVal = 2;
goto Finish;
}
fseek(hFile, 0x00, SEEK_END);
AdpcmSize = ftell(hFile);
fseek(hFile, 0x00, SEEK_SET);
AdpcmData = (UINT8*)malloc(AdpcmSize);
AdpcmData = (uint8_t*)malloc(AdpcmSize);
fread(AdpcmData, 0x01, AdpcmSize, hFile);
fclose(hFile);
WaveSize = AdpcmSize * 2; // 4-bit ADPCM -> 2 values per byte
WaveData = (UINT16*)malloc(WaveSize * 2);
WaveSize = AdpcmSize * 2; // 4-bit ADPCM -> 2 values per byte
WaveData = (uint16_t*)malloc(WaveSize * 2);
printf("Decoding ...");
YM2610_ADPCM_Decode(AdpcmData, WaveData, WaveSize);
printf(" OK\n");
WaveFile.RIFFfcc = FOURCC_RIFF;
WaveFile.WAVEfcc = FOURCC_WAVE;
WaveFile.fmt_fcc = FOURCC_fmt_;
WaveFile.fmt_Len = sizeof(WAVEFORMAT);
TempFmt = &WaveFile.fmt_Data;
TempFmt->wFormatTag = WAVE_FORMAT_PCM;
TempFmt->nChannels = 1;
@@ -314,11 +277,11 @@ int main(int argc, char* argv[])
TempFmt->nSamplesPerSec = OutSmplRate ? OutSmplRate : 22050;
TempFmt->nBlockAlign = TempFmt->nChannels * TempFmt->wBitsPerSample / 8;
TempFmt->nAvgBytesPerSec = TempFmt->nBlockAlign * TempFmt->nSamplesPerSec;
WaveFile.datafcc = FOURCC_data;
WaveFile.dataLen = WaveSize * 2;
WaveFile.RIFFLen = 0x04 + 0x08 + WaveFile.fmt_Len + 0x08 + WaveFile.dataLen;
hFile = fopen(argv[ArgBase + 1], "wb");
if (hFile == NULL)
{
@@ -326,12 +289,12 @@ int main(int argc, char* argv[])
ErrVal = 3;
goto Finish;
}
fwrite(&WaveFile, sizeof(WAVE_FILE), 0x01, hFile);
fwrite(WaveData, 0x02, WaveSize, hFile);
fclose(hFile);
printf("File written.\n");
break;
case 'e':
hFile = fopen(argv[ArgBase + 0], "rb");
@@ -341,7 +304,7 @@ int main(int argc, char* argv[])
ErrVal = 2;
goto Finish;
}
fread(&WaveFile.RIFFfcc, 0x0C, 0x01, hFile);
if (WaveFile.RIFFfcc != FOURCC_RIFF || WaveFile.WAVEfcc != FOURCC_WAVE)
{
@@ -350,12 +313,12 @@ int main(int argc, char* argv[])
ErrVal = 4;
goto Finish;
}
TempLng = fread(&WaveFile.fmt_fcc, 0x04, 0x01, hFile);
fread(&WaveFile.fmt_Len, 0x04, 0x01, hFile);
while(WaveFile.fmt_fcc != FOURCC_fmt_)
{
if (! TempLng) // TempLng == 0 -> EOF reached
if (! TempLng) // TempLng == 0 -> EOF reached
{
fclose(hFile);
printf("Error in wave file: Can't find format-tag!\n");
@@ -363,14 +326,14 @@ int main(int argc, char* argv[])
goto Finish;
}
fseek(hFile, WaveFile.fmt_Len, SEEK_CUR);
TempLng = fread(&WaveFile.fmt_fcc, 0x04, 0x01, hFile);
fread(&WaveFile.fmt_Len, 0x04, 0x01, hFile);
};
TempLng = ftell(hFile) + WaveFile.fmt_Len;
fread(&WaveFile.fmt_Data, sizeof(WAVEFORMAT), 0x01, hFile);
fseek(hFile, TempLng, SEEK_SET);
TempFmt = &WaveFile.fmt_Data;
if (TempFmt->wFormatTag != WAVE_FORMAT_PCM)
{
@@ -393,12 +356,12 @@ int main(int argc, char* argv[])
ErrVal = 4;
goto Finish;
}
TempLng = fread(&WaveFile.datafcc, 0x04, 0x01, hFile);
fread(&WaveFile.dataLen, 0x04, 0x01, hFile);
while(WaveFile.datafcc != FOURCC_data)
{
if (! TempLng) // TempLng == 0 -> EOF reached
if (! TempLng) // TempLng == 0 -> EOF reached
{
fclose(hFile);
printf("Error in wave file: Can't find data-tag!\n");
@@ -406,22 +369,22 @@ int main(int argc, char* argv[])
goto Finish;
}
fseek(hFile, WaveFile.dataLen, SEEK_CUR);
TempLng = fread(&WaveFile.datafcc, 0x04, 0x01, hFile);
fread(&WaveFile.dataLen, 0x04, 0x01, hFile);
};
WaveSize = WaveFile.dataLen / 2;
WaveData = (UINT16*)malloc(WaveSize * 2);
WaveData = (uint16_t*)malloc(WaveSize * 2);
fread(WaveData, 0x02, WaveSize, hFile);
fclose(hFile);
AdpcmSize = WaveSize / 2;
AdpcmData = (UINT8*)malloc(AdpcmSize);
AdpcmData = (uint8_t*)malloc(AdpcmSize);
printf("Encoding ...");
YM2610_ADPCM_Encode(WaveData, AdpcmData, WaveSize);
printf(" OK\n");
hFile = fopen(argv[ArgBase + 1], "wb");
if (hFile == NULL)
{
@@ -429,17 +392,17 @@ int main(int argc, char* argv[])
ErrVal = 3;
goto Finish;
}
fwrite(AdpcmData, 0x01, AdpcmSize, hFile);
fclose(hFile);
printf("File written.\n");
break;
}
Finish:
free(AdpcmData);
free(WaveData);
return ErrVal;
}

63
neotools/autoextract.c Normal file
View File

@@ -0,0 +1,63 @@
#include <stdio.h>
#include <stdlib.h>
#include "neoadpcmextract.h"
int main(int argc, char** argv)
{
if (argc != 2)
return 1;
// Open file.
FILE* file = fopen(argv[1], "rb");
if (!file)
return 1;
// Error on VGZ's for now.
if (fgetc(file) == 0x1F && fgetc(file) == 0x8B)
{
printf("I'm a little gzip short and stout\n");
return 2;
}
fseek(file, 0, SEEK_SET);
Buffer smpbuf = {NULL, 0, 0};
char name[32];
int smpaCount = 0, smpbCount = 0;
// Find ADCPM samples.
int scanType;
while ((scanType = vgmScanSample(file)))
{
if (scanType != 'A' && scanType != 'B')
continue;
fprintf(stderr, "ADPCM-%c data found at 0x%08lX\n", scanType, ftell(file));
if (vgmReadSample(file, &smpbuf) || smpbuf.size == 0)
continue;
if (scanType == 'A')
{
// decode
snprintf(name, sizeof(name), "smpa_%02x.pcm", smpaCount++);
printf("./adpcm \"%s\" \"$WAVDIR/%s.wav\"\n", name, name);
}
else
{
// decode
snprintf(name, sizeof(name), "smpb_%02x.pcm", smpbCount++);
printf("./adpcmb -d \"%s\" \"$WAVDIR/%s.wav\"\n", name, name);
}
// Write adpcm sample.
FILE* fout = fopen(name, "wb");
if (!fout)
continue;
fwrite(smpbuf.data, sizeof(uint8_t), smpbuf.size, fout);
fclose(fout);
}
free(smpbuf.data);
fclose(file);
return 0;
}

View File

@@ -1,19 +1,12 @@
#!/bin/sh
set -e
FILE="$1"
NAME="$(basename "$FILE")"
WAVDIR="${NAME%%.*}"
if [ "${NAME##*.}" = "vgz" ]; then
cp "$FILE" "temp.vgm.gz"
gzip -d "temp.vgm.gz"
FILE="temp.vgm"
fi
./neoadpcmextract "$FILE"
mkdir -p "$WAVDIR"
for I in smpa_*.pcm; do ./adpcm "$I" "$WAVDIR/${I%%.*}.wav"; done
for I in smpb_*.pcm; do ./adpcmb -d "$I" "$WAVDIR/${I%%.*}.wav"; done
find . -type f -name "*.pcm" -exec rm -f {} \;
[ "$FILE" = "temp.vgm" ] && rm -f "temp.vgm"

View File

@@ -1,12 +0,0 @@
TARGET := neoadpcmextract
SOURCE := neoadpcmextract.cpp
CXXFLAGS := -O2 -pipe -Wall -Wextra
all: $(TARGET)
$(TARGET): $(SOURCE)
$(CXX) $(CXXFLAGS) $< -o $@
.PHONY: clean
clean:
rm -f $(TARGET)

View File

@@ -0,0 +1,57 @@
/* neoadpcmextract.c (C) 2017, 2019, 2020 a dinosaur (zlib) */
#include "neoadpcmextract.h"
#include <stdlib.h>
int vgmReadSample(FILE* fin, Buffer* buf)
{
// Get sample data length.
uint32_t sampLen = 0;
fread(&sampLen, sizeof(uint32_t), 1, fin);
if (sampLen < sizeof(uint64_t))
return 1;
sampLen -= sizeof(uint64_t);
// Resize buffer if needed.
buf->size = sampLen;
if (!buf->data || buf->reserved < sampLen)
{
free(buf->data);
buf->reserved = sampLen;
buf->data = malloc(sampLen);
if (!buf->data)
return 1;
}
// Ignore 8 bytes.
uint64_t dummy;
fread(&dummy, sizeof(uint64_t), 1, fin);
// Read adpcm data.
fread(buf->data, sizeof(uint8_t), sampLen, fin);
return 0;
}
int vgmScanSample(FILE* file)
{
// Scan for pcm headers.
while (1)
{
if (feof(file) || ferror(file))
return 0;
// Patterns to match (in hex):
// 67 66 82 - ADPCM-A
// 67 66 83 - ADPCM-B
if (fgetc(file) != 0x67 || fgetc(file) != 0x66)
continue;
uint8_t byte = fgetc(file);
if (byte == 0x82)
return 'A';
else if (byte == 0x83)
return 'B';
}
}

View File

@@ -1,109 +0,0 @@
/* neoadpcmextract.cpp
Copyright (C) 2017 Nicholas Curtis
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <cstdint>
void DecodeSample ( std::ifstream& a_file, std::vector<uint8_t>& a_out )
{
// Set up output vector.
uint32_t sampLen = 0;
a_file.read ( (char*)&sampLen, sizeof(uint32_t) );
if ( sampLen < sizeof(uint64_t) )
{
return;
}
sampLen -= sizeof(uint64_t);
a_out.clear ();
a_out.resize ( sampLen );
// Ignore 8 bytes.
uint64_t dummy;
a_file.read ( (char*)&dummy, sizeof(uint64_t) );
// Read adpcm data.
a_file.read ( (char*)a_out.data (), sampLen );
}
void DumpBytes ( std::string a_path, const std::vector<uint8_t>& a_bytes )
{
std::ofstream fileOut ( a_path, std::ios::binary );
fileOut.write ( (const char*)a_bytes.data (), a_bytes.size () );
fileOut.close ();
}
int main ( int argc, char** argv )
{
if ( argc != 2 )
{
return -1;
}
// Open file.
std::ifstream file ( argv[1], std::ios::binary );
if ( !file.is_open () )
{
return -1;
}
// Search for pcm headers.
std::vector<uint8_t> smpBytes;
int smpA = 0, smpB = 0;
while ( !file.eof () && !file.fail () )
{
uint8_t byte;
file >> byte;
if ( byte == 0x67 )
{
file >> byte;
if ( byte == 0x66 )
{
file >> byte;
if ( byte == 0x82 )
{
std::cout << "ADPCM-A data found at 0x" << std::hex << file.tellg () << std::endl;
DecodeSample ( file, smpBytes );
std::stringstream path;
path << std::hex << "smpa_" << (smpA++) << ".pcm";
DumpBytes ( path.str (), smpBytes );
}
else
if ( byte == 0x83 )
{
std::cout << "ADPCM-B data found at 0x" << std::hex << file.tellg () << std::endl;
DecodeSample ( file, smpBytes );
std::stringstream path;
path << std::hex << "smpb_" << (smpB++) << ".pcm";
DumpBytes ( path.str (), smpBytes );
}
}
}
}
file.close ();
return 0;
}

View File

@@ -0,0 +1,12 @@
#ifndef __NEOADPCMEXTRACT_H__
#define __NEOADPCMEXTRACT_H__
#include <stdio.h>
#include <stdint.h>
typedef struct { uint8_t* data; size_t size, reserved; } Buffer;
int vgmReadSample(FILE* fin, Buffer* buf);
int vgmScanSample(FILE* file);
#endif//__NEOADPCMEXTRACT_H__

View File

@@ -1,26 +1,7 @@
#!/usr/bin/env python3
''' ripsamples.py -- a python script for mass extracting samples from SPC files.
Copyright (C) 2018 Nicholas Curtis (a_dinosaur)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
'''
# ripsamples.py -- a python script for mass extracting samples from SPC files.
# (C) 2018 neoadpcmextract.c (C) 2018 a dinosaur (zlib)
import os
import subprocess