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

Compare commits

21 Commits

Author SHA1 Message Date
effcf727ac normalise macros 2023-12-29 06:21:32 +11:00
78790991b6 add .editorconfig 2023-12-29 06:15:28 +11:00
47f6b23943 neotools: neoadpcmextract now converts ADPCM-B to Wave 2023-12-29 04:04:28 +11:00
5b28c18472 neotools: make adpcmb decoder work on nibble pairs like adpcma 2023-12-29 03:39:33 +11:00
70bbf3d0d1 neotools: make adpcmb encoder & decoder streamable (encoder untested) 2023-12-29 03:25:14 +11:00
9f6c0664ff neotools: use common wave writer in adpcm.c 2023-12-29 02:27:11 +11:00
4013d1809c neotools: factor adpcmb.c encode/decode into functions 2023-12-29 01:35:14 +11:00
802bdef961 neotools: implement adpcma extraction in neoadpcmextract 2023-12-29 00:53:59 +11:00
ae14868953 neotools: adpcm-a split backend code 2023-12-10 11:36:05 +11:00
46c78c24e1 neotools: adpcm-a partial rewrite 2023-12-10 11:28:52 +11:00
ff41b5415e neotools: adpcm.c replace errorlog with stderr 2023-12-10 10:48:57 +11:00
7764375ec9 neotools: delete old shell scripts 2023-12-10 10:42:23 +11:00
e334ad82cc neotools: remove autoextract.c and move main logic into the main file 2023-12-10 10:41:22 +11:00
353d4e5def neotools: code fixup & vgz support 2023-12-10 10:37:15 +11:00
9c5e19264b wavetable stuff 2023-11-07 02:43:28 +11:00
111f800c49 generalise python wave writer 2023-11-07 01:34:29 +11:00
dbce8e5c29 normalise macro formatting 2023-03-21 14:28:36 +11:00
c1f36bd322 oops lol 2023-03-21 14:26:00 +11:00
9946144995 combine all (except spctools) as subprojects 2023-03-21 14:25:14 +11:00
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
36 changed files with 1246 additions and 834 deletions

22
.editorconfig Normal file
View File

@@ -0,0 +1,22 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
tab_width = 4
[{CMakeLists.txt,*.cmake}]
indent_style = tab
indent_size = tab
max_line_length = 120
[*.{c,cc,cpp,h,hpp,hh,m,mm}]
indent_style = tab
indent_size = tab
max_line_length = 120
[*.py]
indent_style = tab
indent_size = tab
max_line_length = 120

18
.gitignore vendored
View File

@@ -1 +1,19 @@
.idea/ .idea/
**/build/
**/cmake-build-*/
*.exe
*.log
*.wav
neotools/**/*.pcm
neotools/**/*.vgm
neotools/**/*.vgz
spctools/it
spctools/spc
spctools/sample
FM Harmonics/
.DS_Store

10
CMakeLists.txt Normal file
View File

@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(VGM-Tools LANGUAGES C)
set(WARNINGS
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic -Wno-unused-parameter>
$<$<C_COMPILER_ID:MSVC>:/Wall /wd4100>)
add_subdirectory(common)
add_subdirectory(dsptools)
add_subdirectory(neotools)

8
common/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
add_library(headers INTERFACE)
add_library(Common::headers ALIAS headers)
target_include_directories(headers INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
add_library(wave endian.h wave.h wave.c wavefile.c)
add_library(Common::wave ALIAS wave)
target_compile_options(wave PRIVATE ${WARNINGS})
target_link_libraries(wave PUBLIC headers)

68
common/endian.h Normal file
View File

@@ -0,0 +1,68 @@
#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)
# 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

40
common/riffwriter.py Normal file
View File

@@ -0,0 +1,40 @@
# riffwriter.py -- Generic RIFF writing framework
# (C) 2023 a dinosaur (zlib)
from abc import ABC, abstractmethod
from typing import BinaryIO, List
class AbstractRiffChunk(ABC):
@abstractmethod
def fourcc(self) -> bytes: raise NotImplementedError
@abstractmethod
def size(self) -> int: raise NotImplementedError
@abstractmethod
def write(self, f: BinaryIO): raise NotImplementedError
class RiffFile(AbstractRiffChunk):
def fourcc(self) -> bytes: return b"RIFF"
def size(self) -> int: return 4 + sum(8 + c.size() for c in self._chunks)
def __init__(self, type: bytes, chunks: List[AbstractRiffChunk]):
self._chunks = chunks
if len(type) != 4: raise ValueError
self._type = type
def write(self, f: BinaryIO):
f.writelines([
self.fourcc(),
self.size().to_bytes(4, "little", signed=False),
self._type])
for chunk in self._chunks:
size = chunk.size()
if size & 0x3: raise AssertionError("Unaligned chunks will produce malformed riff files")
f.writelines([
chunk.fourcc(),
size.to_bytes(4, "little", signed=False)])
chunk.write(f)

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

View File

@@ -1,7 +1,7 @@
/* wave.c (c) 2023 a dinosaur (zlib) */ /* wave.c (c) 2023 a dinosaur (zlib) */
#include "wave.h" #include "wave.h"
#include "common.h" #include "endian.h"
#define FOURCC_RIFF "RIFF" #define FOURCC_RIFF "RIFF"
#define FOURCC_WAVE "WAVE" #define FOURCC_WAVE "WAVE"
@@ -116,14 +116,12 @@ static int waveWriteHeader(const WaveSpec* spec, size_t dataLen, const WaveStrea
int waveWrite(const WaveSpec* spec, const void* data, size_t dataLen, const WaveStreamCb* cb, void* user) 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 // Write RIFF/Wave header and raw interleaved samples
int res = waveWriteHeader(spec, dataLen, cb, user); int res = waveWriteHeader(spec, dataLen, cb, user);
if (res) if (res)
return res; return res;
//FIXME: not endian safe //FIXME: not endian safe
if (data)
cb->write(user, data, 1, dataLen); cb->write(user, data, 1, dataLen);
return 0; return 0;

View File

@@ -6,6 +6,7 @@ extern "C" {
#endif #endif
#include <stdint.h> #include <stdint.h>
#include <stddef.h>
typedef enum typedef enum
{ {
@@ -33,6 +34,8 @@ typedef struct
int (*eof)(void* user); int (*eof)(void* user);
} WaveStreamCb; } WaveStreamCb;
extern const WaveStreamCb waveStreamDefaultCb;
int waveWrite(const WaveSpec* spec, const void* data, size_t dataLen, const WaveStreamCb* cb, void* user); 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 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 waveWriteBlock(const WaveSpec* spec, const void* blocks[], size_t blockLen, const WaveStreamCb* cb, void* user);

View File

@@ -32,14 +32,22 @@ static int waveFileEof(void* user)
return feof((FILE*)user) || ferror((FILE*)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) int waveWriteFile(const WaveSpec* spec, const void* data, size_t dataLen, const char* path)
{ {
FILE* file = fopen(path, "wb"); FILE* file = fopen(path, "wb");
if (!file) if (!file)
return 1; return 1;
const WaveStreamCb cb = { .write = waveFileWrite }; int res = waveWrite(spec, data, dataLen, &waveStreamDefaultCb, (void*)file);
int res = waveWrite(spec, data, dataLen, &cb, (void*)file);
fclose(file); fclose(file);
return res; return res;
} }
@@ -50,8 +58,7 @@ int waveWriteBlockFile(const WaveSpec* spec, const void* blocks[], size_t blockL
if (!file) if (!file)
return 1; return 1;
const WaveStreamCb cb = { .write = waveFileWrite }; int res = waveWriteBlock(spec, blocks, blockLen, &waveStreamDefaultCb, (void*)file);
int res = waveWriteBlock(spec, blocks, blockLen, &cb, (void*)file);
fclose(file); fclose(file);
return res; return res;
} }

110
common/wavesampler.py Normal file
View File

@@ -0,0 +1,110 @@
# wavesampler.py -- Support for the non-standard "Sampler" WAVE chunk
# (C) 2023 a dinosaur (zlib)
import struct
from enum import Enum
from typing import BinaryIO, List
from common.riffwriter import AbstractRiffChunk
class WaveSamplerSMPTEOffset:
def __init__(self, hours: int=0, minutes: int=0, seconds: int=0, frames: int=0):
if -23 > hours > 23: raise ValueError("Hours out of range")
if 0 > minutes > 59: raise ValueError("Minutes out of range")
if 0 > seconds > 59: raise ValueError("Seconds out of range")
if 0 > frames > 0xFF: raise ValueError("Frames out of range")
self._hours = hours
self._minutes = minutes
self._seconds = seconds
self._frames = frames
def hours(self) -> int: return self._hours
def minutes(self) -> int: return self._minutes
def seconds(self) -> int: return self._seconds
def frames(self) -> int: return self._frames
def pack(self) -> bytes:
#FIXME: endianess??
return struct.pack("<bBBB", self._hours, self._minutes, self._seconds, self._frames)
class WaveSamplerLoopType(Enum):
FORWARD = 0
BIDIRECTIONAL = 1
REVERSE = 2
class WaveSamplerLoop:
def __init__(self,
cueId: int=0,
type: int|WaveSamplerLoopType=0,
start: int=0,
end: int=0,
fraction: int=0,
loopCount: int=0):
self._cueId = cueId
self._type = type.value if type is WaveSamplerLoopType else type
self._start = start
self._end = end
self._fraction = fraction
self._loopCount = loopCount
def pack(self) -> bytes:
return struct.pack("<IIIIII",
self._cueId, # Cue point ID
self._type, # Loop type
self._start, # Loop start
self._end, # Loop end
self._fraction, # Fraction (none)
self._loopCount) # Loop count (infinite)
class WaveSamplerChunk(AbstractRiffChunk):
def fourcc(self) -> bytes: return b"smpl"
def loopsSize(self) -> int: return len(self._loops) * 24
def size(self) -> int: return 36 + self.loopsSize()
def write(self, f: BinaryIO):
#TODO: unused data dummied out for now
f.write(struct.pack("<4sIiiii4sII",
self._manufacturer, # MMA Manufacturer code
self._product, # Product
self._period, # Playback period (ns)
self._unityNote, # MIDI unity note
self._fineTune, # MIDI pitch fraction
self._smpteFormat, # SMPTE format
self._smpteOffset.pack(), # SMPTE offset
len(self._loops), # Number of loops
self.loopsSize())) # Loop data length
f.writelines(loop.pack() for loop in self._loops)
def __init__(self,
manufacturer: bytes|None=None,
product: int=0,
period: int=0,
midiUnityNote: int=0,
midiPitchFraction: int=0,
smpteFormat: int=0,
smpteOffset: WaveSamplerSMPTEOffset|None=None,
loops: List[WaveSamplerLoop]=None):
if manufacturer is not None:
if len(manufacturer) not in [1, 3]: raise ValueError("Malformed MIDI manufacturer code")
self._manufacturer = len(manufacturer).to_bytes(1, byteorder="little", signed=False)
self._manufacturer += manufacturer.rjust(3, b"\x00")
else:
self._manufacturer = b"\x00" * 4
if 0 > product > 0xFFFF: raise ValueError("Product code out of range")
self._product = product # Arbitrary vendor specific product code, dunno if this should be signed or unsigned
self._period = period # Sample period in ns, (1 / samplerate) * 10^9, who cares
if 0 > midiUnityNote > 127: raise ValueError("MIDI Unity note out of range")
self._unityNote = midiUnityNote # MIDI note that plays the sample unpitched, middle C=60
self._fineTune = midiPitchFraction # Finetune fraction, 256 == 100 cents
if smpteFormat not in [0, 24, 25, 29, 30]: raise ValueError("Invalid SMPTE format")
self._smpteFormat = smpteFormat
self._smpteOffset = smpteOffset if (smpteOffset and smpteFormat > 0) else WaveSamplerSMPTEOffset()
if self._smpteOffset.frames() > self._smpteFormat: raise ValueError("SMPTE frame offset can't exceed SMPTE format")
self._loops = loops if loops is not None else list()

18
common/waveserum.py Normal file
View File

@@ -0,0 +1,18 @@
# waveserum.py -- Serum comment chunk support
# (C) 2023 a dinosaur (zlib)
from enum import Enum
from common.wavewriter import WaveCommentChunk
class SerumWavetableInterpolation(Enum):
NONE = 0
LINEAR_XFADE = 1
SPECTRAL_MORPH = 2
class WaveSerumCommentChunk(WaveCommentChunk):
def __init__(self, size: int, mode: SerumWavetableInterpolation, factory=False):
comment = f"<!>{size: <4} {mode.value}{'1' if factory else '0'}000000"
comment += " wavetable (www.xferrecords.com)"
super().__init__(comment.encode("ascii"))

98
common/wavewriter.py Normal file
View File

@@ -0,0 +1,98 @@
# wavewriter.py -- Extensible WAVE writing framework
# (C) 2023 a dinosaur (zlib)
import struct
from abc import abstractmethod
from enum import Enum
from typing import BinaryIO, List
from common.riffwriter import RiffFile, AbstractRiffChunk
class WaveSampleFormat(Enum):
PCM = 0x0001
IEEE_FLOAT = 0x0003
ALAW = 0x0006
MULAW = 0x0007
EXTENSIBLE = 0xFFFE
class WaveAbstractFormatChunk(AbstractRiffChunk):
def fourcc(self) -> bytes: return b"fmt "
def size(self) -> int: return 16
@abstractmethod
def sampleformat(self) -> WaveSampleFormat: raise NotImplementedError
@abstractmethod
def channels(self) -> int: raise NotImplementedError
@abstractmethod
def samplerate(self) -> int: raise NotImplementedError
@abstractmethod
def byterate(self) -> int: raise NotImplementedError
@abstractmethod
def align(self) -> int: raise NotImplementedError
@abstractmethod
def bitdepth(self) -> int: raise NotImplementedError
def write(self, f: BinaryIO):
f.write(struct.pack("<HHIIHH",
self.sampleformat().value,
self.channels(),
self.samplerate(),
self.byterate(),
self.align(),
self.bitdepth()))
class WaveFile(RiffFile):
def __init__(self, format: WaveAbstractFormatChunk, chunks: List[AbstractRiffChunk]):
super().__init__(b"WAVE", [format] + chunks)
class WavePcmFormatChunk(WaveAbstractFormatChunk):
def sampleformat(self) -> WaveSampleFormat: return WaveSampleFormat.PCM
def channels(self) -> int: return self._channels
def samplerate(self) -> int: return self._samplerate
def byterate(self) -> int: return self._samplerate * self._channels * self._bytedepth
def align(self) -> int: return self._channels * self._bytedepth
def bitdepth(self) -> int: return self._bytedepth * 8
def __init__(self, channels: int, samplerate: int, bitdepth: int):
if channels < 0 or channels >= 256: raise ValueError
if samplerate < 1 or samplerate > 0xFFFFFFFF: raise ValueError
if bitdepth not in [8, 16, 32]: raise ValueError
self._channels = channels
self._samplerate = samplerate
self._bytedepth = bitdepth // 8
class WaveDataChunk(AbstractRiffChunk):
def fourcc(self) -> bytes: return b"data"
def size(self) -> int: return len(self._data)
def write(self, f: BinaryIO): f.write(self._data)
def __init__(self, data: bytes):
self._data = data
class WaveCommentChunk(AbstractRiffChunk):
def fourcc(self) -> bytes: return b"clm "
def size(self) -> int: return len(self._comment)
def write(self, f: BinaryIO): f.write(self._comment)
def __init__(self, comment: bytes):
self._comment = comment

View File

@@ -1,21 +1,7 @@
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(dsptools LANGUAGES C)
include(CheckIncludeFile)
check_include_file("endian.h" HAVE_ENDIAN_H)
if (HAVE_ENDIAN_H)
add_definitions(HAVE_ENDIAN_H)
endif()
add_subdirectory(libdsptool) add_subdirectory(libdsptool)
set(HEADERS wave.h) add_executable(dspdecode dspdecode.c)
set(SOURCES wavefile.c wave.c dspdecode.c)
add_executable(dspdecode ${HEADERS} ${SOURCES})
set_property(TARGET dspdecode PROPERTY C_STANDARD 99) set_property(TARGET dspdecode PROPERTY C_STANDARD 99)
target_link_libraries(dspdecode DspTool::DspTool) target_include_directories(dspdecode PRIVATE ${COMMON})
target_compile_options(dspdecode PRIVATE target_link_libraries(dspdecode Common::wave DspTool::DspTool)
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic> target_compile_options(dspdecode PRIVATE ${WARNINGS})
$<$<C_COMPILER_ID:MSVC>:/Wall>)

View File

@@ -1,44 +0,0 @@
#ifndef COMMON_H
#define COMMON_H
#ifdef HAVE_ENDIAN_H
# include <endian.h>
# 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

View File

@@ -2,7 +2,7 @@
#include "dsptool.h" #include "dsptool.h"
#include "wave.h" #include "wave.h"
#include "common.h" #include "endian.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -182,7 +182,7 @@ int main(int argc, char* argv[])
if (opt || !inPathL) if (opt || !inPathL)
usage(argv[0]); usage(argv[0]);
// C // Compute output path if one wasn't specified
if (!outPath) if (!outPath)
{ {
const char* base = strrchr(inPathL, '/'); const char* base = strrchr(inPathL, '/');
@@ -205,6 +205,7 @@ int main(int argc, char* argv[])
outPathAlloc = true; outPathAlloc = true;
} }
// Convert left (and optionally right) channels to PCM, save as wave
int ret; int ret;
PcmFile left = PCMFILE_CLEAR(), right = PCMFILE_CLEAR(); PcmFile left = PCMFILE_CLEAR(), right = PCMFILE_CLEAR();
if ((ret = loadDsp(inPathL, &left))) if ((ret = loadDsp(inPathL, &left)))
@@ -253,6 +254,8 @@ int main(int argc, char* argv[])
Cleanup: Cleanup:
free(right.pcm); free(right.pcm);
free(left.pcm); free(left.pcm);
if (outPathAlloc)
free(outPath);
return ret; return ret;
} }

View File

@@ -1,19 +1,18 @@
cmake_minimum_required(VERSION 3.15 FATAL_ERROR) option(DSPTOOL_BUILD_SHARED_LIBS "Build as a Shared Object or DLL" OFF)
project(DspTool LANGUAGES C)
option(BUILD_SHARED_LIBS "Build as a Shared Object or DLL" OFF) set(HEADERS dsptool.h)
set(HEADERS ../common.h dsptool.h)
set(SOURCES math.c decode.c encode.c) set(SOURCES math.c decode.c encode.c)
add_library(DspTool ${HEADERS} ${SOURCES}) if (DSPTOOL_BUILD_SHARED_LIBS)
add_library(DspTool SHARED ${HEADERS} ${SOURCES})
target_compile_definitions(DspTool PRIVATE BUILD_SHARED)
else()
add_library(DspTool STATIC ${HEADERS} ${SOURCES})
target_compile_definitions(DspTool PRIVATE BUILD_STATIC)
endif()
add_library(DspTool::DspTool ALIAS DspTool) add_library(DspTool::DspTool ALIAS DspTool)
target_include_directories(DspTool PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TARGET DspTool PROPERTY C_STANDARD 99) set_property(TARGET DspTool PROPERTY C_STANDARD 99)
if (BUILD_SHARED_LIBS) target_include_directories(DspTool PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(DspTool PRIVATE BUILD_SHARED) target_compile_options(DspTool PRIVATE ${WARNINGS})
endif() target_link_libraries(DspTool PRIVATE Common::headers)
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

@@ -1,7 +1,7 @@
/* (c) 2017 Alex Barney (MIT) */ /* (c) 2017 Alex Barney (MIT) */
#include <stdint.h> #include <stdint.h>
#include "../common.h" #include "util.h"
#include "dsptool.h" #include "dsptool.h"
static inline uint8_t GetHighNibble(uint8_t value) static inline uint8_t GetHighNibble(uint8_t value)

View File

@@ -14,7 +14,7 @@ extern "C" {
#if defined( _WIN32 ) || defined( __CYGWIN__ ) #if defined( _WIN32 ) || defined( __CYGWIN__ )
# ifdef BUILD_SHARED # ifdef BUILD_SHARED
# define DLLEXPORT __declspec(dllexport) # define DLLEXPORT __declspec(dllexport)
# elif defined( WHY_ARE_YOU_USING_THIS_AS_A_DLL ) # elif !defined( BUILD_STATIC )
# define DLLEXPORT __declspec(dllimport) # define DLLEXPORT __declspec(dllimport)
# else # else
# define DLLEXPORT # define DLLEXPORT

View File

@@ -6,7 +6,7 @@
#include <math.h> #include <math.h>
#include <float.h> #include <float.h>
#include <string.h> #include <string.h>
#include "../common.h" #include "util.h"
#include "dsptool.h" #include "dsptool.h"
/* Temporal Vector /* Temporal Vector

10
neotools/.gitignore vendored
View File

@@ -1,10 +0,0 @@
*.exe
*.vgm
*.vgz
*.log
*.pcm
*.wav
.idea/
build/
cmake-build-*/

View File

@@ -1,18 +1,24 @@
project(neoadpcmtools) find_package(ZLIB)
cmake_minimum_required(VERSION "3.1" FATAL_ERROR) option(USE_ZLIB "Link Zlib for VGZ support" ${ZLIB_FOUND})
if (USE_ZLIB AND NOT ZLIB_FOUND)
message(FATAL_ERROR "USE_ZLIB specified but Zlib was not found")
endif()
add_executable(adpcm adpcm.c) add_executable(adpcm adpcm.h libadpcma.c adpcm.c)
target_compile_options(adpcm PRIVATE set_property(TARGET adpcm PROPERTY C_STANDARD 99)
-fomit-frame-pointer -Werror -Wall target_compile_options(adpcm PRIVATE ${WARNINGS})
-W -Wno-sign-compare -Wno-unused target_link_libraries(adpcm Common::wave $<$<C_COMPILER_ID:Clang,GNU>:m>)
-Wpointer-arith -Wbad-function-cast -Wcast-align -Waggregate-return
-pedantic
-Wshadow
-Wstrict-prototypes)
target_link_libraries(adpcm m)
add_executable(adpcmb adpcmb.c) add_executable(adpcmb adpcmb.h libadpcmb.c adpcmb.c)
set_property(TARGET adpcmb PROPERTY C_STANDARD 99)
target_compile_options(adpcmb PRIVATE ${WARNINGS})
target_link_libraries(adpcmb Common::wave)
add_executable(neoadpcmextract autoextract.c neoadpcmextract.c) add_executable(neoadpcmextract
libadpcma.c adpcm.h
libadpcmb.c adpcmb.h
neoadpcmextract.c)
set_property(TARGET neoadpcmextract PROPERTY C_STANDARD 99) set_property(TARGET neoadpcmextract PROPERTY C_STANDARD 99)
target_compile_options(neoadpcmextract PRIVATE -Wall -Wextra -pedantic) target_compile_definitions(neoadpcmextract PRIVATE $<$<BOOL:${USE_ZLIB}>:USE_ZLIB=1>)
target_compile_options(neoadpcmextract PRIVATE ${WARNINGS})
target_link_libraries(neoadpcmextract $<$<BOOL:${USE_ZLIB}>:ZLIB::ZLIB> Common::wave)

View File

@@ -1,159 +1,88 @@
/* adpcm.c
* original ADPCM to PCM converter v 1.01 By MARTINEZ Fabrice aka SNK of SUPREMACY
*/
#include "adpcm.h"
#include "wave.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.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
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] = { int main(int argc, char* argv[])
-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;
int main(int argc, char *argv[])
{ {
FILE *Fp1, *Fp2; fprintf(stderr, "**** ADPCM to PCM converter v 1.01\n\n");
void *InputBuffer, *OutputBuffer; if (argc != 3)
int Readed; {
unsigned int Filelen; fprintf(stderr, "USAGE: adpcm <InputFile.pcm> <OutputFile.wav>\n");
return -1;
puts("**** ADPCM to PCM converter v 1.01\n");
if (argc!=3) {
puts("USAGE: adpcm <InputFile.pcm> <OutputFile.wav>");
exit(-1);
} }
Fp1 = fopen(argv[1], "rb"); FILE* inFile = fopen(argv[1], "rb");
if (Fp1==NULL) { if (!inFile)
printf("Could not open inputfile %s\n", argv[1]); {
exit(-2); fprintf(stderr, "Could not open inputfile %s\n", argv[1]);
return -2;
} }
Fp2 = fopen(argv[2], "wb"); FILE* outFile = fopen(argv[2], "wb");
if (Fp2==NULL) { if (!outFile)
printf("Could not open outputfile %s\n", argv[2]); {
exit(-3); fprintf(stderr, "Could not open outputfile %s\n", argv[2]);
return -3;
} }
errorlog = fopen("error.log", "wb"); char* InputBuffer = malloc(BUFFER_SIZE);
if (InputBuffer == NULL)
InputBuffer = malloc(BUFFER_SIZE); {
if (InputBuffer == NULL) { fprintf(stderr, "Could not allocate input buffer. (%d bytes)\n", BUFFER_SIZE);
printf("Could not allocate input buffer. (%d bytes)\n", BUFFER_SIZE); return -4;
exit(-4);
} }
OutputBuffer = malloc(BUFFER_SIZE*10); short* OutputBuffer = malloc(BUFFER_SIZE * 4);
if (OutputBuffer == NULL) { if (OutputBuffer == NULL)
printf("Could not allocate output buffer. (%d bytes)\n", BUFFER_SIZE*4); {
exit(-5); fprintf(stderr, "Could not allocate output buffer. (%d bytes)\n", BUFFER_SIZE * 4);
return -5;
} }
adpcm_init(); AdpcmADecoderState decoder;
adpcmAInit(&decoder);
fseek(Fp1, 0, SEEK_END); fseek(inFile, 0, SEEK_END);
Filelen = ftell(Fp1); unsigned int Filelen = ftell(inFile);
fseek(Fp1, 0, SEEK_SET); fseek(inFile, 0, SEEK_SET);
*((unsigned int*)(&RiffWave[4])) = Filelen*4 + 0x2C; // Write wave header
*((unsigned int*)(&RiffWave[0x28])) = Filelen*4; waveWrite(&(const WaveSpec)
{
.format = WAVE_FMT_PCM,
.channels = 1,
.rate = 18500,
.bytedepth = 2
},
NULL, Filelen * 4, &waveStreamDefaultCb, outFile);
fwrite(RiffWave, 0x2c, 1, Fp2); // Convert ADPCM to PCM and write to wave
int bytesRead;
do { do
Readed = fread(InputBuffer, 1, BUFFER_SIZE, Fp1); {
if (Readed>0) { bytesRead = fread(InputBuffer, 1, BUFFER_SIZE, inFile);
adpcm_decode(InputBuffer, OutputBuffer, Readed); if (bytesRead > 0)
fwrite(OutputBuffer, Readed*4, 1, Fp2); {
adpcmADecode(&decoder, InputBuffer, OutputBuffer, bytesRead);
fwrite(OutputBuffer, bytesRead * 4, 1, outFile);
} }
} while (Readed==BUFFER_SIZE); }
while (bytesRead == BUFFER_SIZE);
free(InputBuffer);
free(OutputBuffer); free(OutputBuffer);
fclose(Fp1); free(InputBuffer);
fclose(Fp2); fclose(outFile);
fclose(inFile);
puts("Done..."); fprintf(stderr, "Done...\n");
return 0; return 0;
} }
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 */
for (nib = 0; nib < 16; nib++)
{
int value = stepval*((nib&0x07)*2+1)/8;
jedi_table[step*16+nib] = (nib&0x08) ? -value : value;
}
}
delta = 0;
signal = 0;
}
void adpcm_decode(void *InputBuffer, void *OutputBuffer, int Length)
{
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]);
}
*(out++) = (signal&0xffff)*32;
}
}
int Limit( int val, int max, int min ) {
if ( val > max )
val = max;
else if ( val < min )
val = min;
return val;
}

14
neotools/adpcm.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef ADPCM_H
#define ADPCM_H
typedef struct AdpcmADecoderState
{
int jediTable[49 * 16];
int cursignal;
int delta;
} AdpcmADecoderState;
void adpcmAInit(AdpcmADecoderState* decoder);
void adpcmADecode(AdpcmADecoderState* decoder, const char* restrict in, short* restrict out, int len);
#endif//ADPCM_H

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,231 +1,222 @@
/*; YM2610 ADPCM-B Codec /* adpcmb.c - CLI for encoding & decoding YM2610 ADPCM-B files
;
;**** 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
;
;http://www.raregame.ru/file/15/YM2610.pdf YM2610 DATASHEET
;
; Fred/FRONT ; Fred/FRONT
; ;
;Usage 1: ADPCM_Encode.exe -d [-r:reg,clock] Input.bin Output.wav ;Usage 1: ADPCM_Encode -d [-r:reg,clock] Input.bin Output.wav
;Usage 2: ADPCM_Encode.exe -e Input.wav Output.bin ;Usage 2: ADPCM_Encode -e Input.wav Output.bin
; ;
; Valley Bell ; Valley Bell
;----------------------------------------------------------------------------------------------------------------------------*/ ;----------------------------------------------------------------------------------------------------------------------------*/
#include "adpcmb.h"
#include "wave.h"
#include "util.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <malloc.h>
#include <math.h>
#include <string.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 static FORCE_INLINE uint32_t DeltaTReg2SampleRate(uint16_t DeltaN, uint32_t Clock)
{
return (uint32_t)(DeltaN * (Clock / 72.0) / 65536.0 + 0.5);
}
#include <stdint.h> #define BUFFER_SIZE 2048
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 static int decode(const char* inPath, const char* outPath, uint32_t sampleRate)
{
FILE* inFile = fopen(inPath, "rb");
if (inFile == NULL)
{
printf("Error opening input file!\n");
return 2;
}
typedef unsigned char UINT8; fseek(inFile, 0, SEEK_END);
typedef signed char INT8; long adpcmSize = ftell(inFile);
typedef unsigned short UINT16; fseek(inFile, 0, SEEK_SET);
typedef signed short INT16;
typedef unsigned int UINT32;
typedef signed int INT32;
#endif uint8_t* adpcmData = malloc(BUFFER_SIZE);
int16_t* wavData = malloc(BUFFER_SIZE * 2 * sizeof(int16_t));
FILE* outFile = fopen(outPath, "wb");
if (outFile == NULL)
{
printf("Error opening output file!\n");
free(wavData);
free(adpcmData);
fclose(inFile);
return 3;
}
typedef UINT8 BYTE; // Write wave header
typedef UINT16 WORD; waveWrite(&(const WaveSpec)
typedef UINT32 DWORD; {
.format = WAVE_FMT_PCM,
.channels = 1,
.rate = sampleRate ? sampleRate : 22050,
.bytedepth = 2
},
NULL, (size_t)adpcmSize * 2 * sizeof(int16_t), &waveStreamDefaultCb, outFile);
// -- from mmsystem.h -- printf("Decoding ...");
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \ AdpcmBDecoderState decoder;
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \ adpcmBDecoderInit(&decoder);
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 )) size_t read;
do
{
if ((read = fread(adpcmData, 1, BUFFER_SIZE, inFile)) > 0)
{
adpcmBDecode(&decoder, adpcmData, wavData, read);
fwrite(wavData, sizeof(int16_t), read * 2, outFile);
}
}
while (read == BUFFER_SIZE);
printf(" OK\n");
fclose(outFile);
// -- from mmreg.h, slightly modified -- free(wavData);
free(adpcmData);
fclose(inFile);
printf("File written.\n");
return 0;
}
/* general waveform format structure (information common to all formats) */
typedef struct waveformat_tag { typedef struct waveformat_tag {
WORD wFormatTag; /* format type */ uint16_t wFormatTag; /* format type */
WORD nChannels; /* number of channels (i.e. mono, stereo...) */ uint16_t nChannels; /* number of channels (i.e. mono, stereo...) */
DWORD nSamplesPerSec; /* sample rate */ uint32_t nSamplesPerSec; /* sample rate */
DWORD nAvgBytesPerSec; /* for buffer estimation */ uint32_t nAvgBytesPerSec; /* for buffer estimation */
WORD nBlockAlign; /* block size of data */ uint16_t nBlockAlign; /* block size of data */
WORD wBitsPerSample; uint16_t wBitsPerSample;
} WAVEFORMAT; } WAVEFORMAT;
/* flags for wFormatTag field of WAVEFORMAT */
#define WAVE_FORMAT_PCM 1
// -- 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')
typedef struct typedef struct
{ {
UINT32 RIFFfcc; // 'RIFF' char RIFFfcc[4];
UINT32 RIFFLen; uint32_t RIFFLen;
UINT32 WAVEfcc; // 'WAVE' char WAVEfcc[4];
UINT32 fmt_fcc; // 'fmt ' char fmt_fcc[4];
UINT32 fmt_Len; uint32_t fmt_Len;
WAVEFORMAT fmt_Data; WAVEFORMAT fmt_Data;
UINT32 datafcc; // 'data' char datafcc[4];
UINT32 dataLen; uint32_t dataLen;
} WAVE_FILE; } WAVE_FILE;
static int encode(const char* inPath, const char* outPath)
static const long stepsizeTable[16] =
{ {
57, 57, 57, 57, 77, 102, 128, 153, FILE* hFile = fopen(inPath, "rb");
57, 57, 57, 57, 77, 102, 128, 153 if (hFile == NULL)
};
static int YM2610_ADPCM_Encode(INT16 *src, UINT8 *dest, int len)
{
int lpc, flag;
long i, dn, xn, stepSize;
UINT8 adpcm;
UINT8 adpcmPack;
xn = 0;
stepSize = 127;
flag = 0;
for (lpc = 0; lpc < len; lpc ++)
{ {
dn = *src - xn; printf("Error opening input file!\n");
src ++; return 2;
i = (abs(dn) << 16) / (stepSize << 14);
if (i > 7)
i = 7;
adpcm = (UINT8)i;
i = (adpcm * 2 + 1) * stepSize / 8;
if (dn < 0)
{
adpcm |= 0x8;
xn -= i;
}
else
{
xn += i;
} }
stepSize = (stepsizeTable[adpcm] * stepSize) / 64; WAVE_FILE WaveFile;
fread(&WaveFile.RIFFfcc, 0x0C, 0x01, hFile);
if (stepSize < 127) if (memcmp(WaveFile.RIFFfcc, "RIFF", 4) != 0 || memcmp(WaveFile.WAVEfcc, "WAVE", 4) != 0)
stepSize = 127;
else if (stepSize > 24576)
stepSize = 24576;
if (flag == 0)
{ {
adpcmPack = (adpcm << 4); fclose(hFile);
flag = 1; printf("This is no wave file!\n");
} return 4;
else
{
adpcmPack |= adpcm;
*dest = adpcmPack;
dest ++;
flag = 0;
}
} }
unsigned int TempLng = fread(&WaveFile.fmt_fcc, 0x04, 0x01, hFile);
fread(&WaveFile.fmt_Len, 0x04, 0x01, hFile);
while (memcmp(WaveFile.fmt_fcc, "fmt ", 4) != 0)
{
if (!TempLng) // TempLng == 0 -> EOF reached
{
fclose(hFile);
printf("Error in wave file: Can't find format-tag!\n");
return 4;
}
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);
WAVEFORMAT* TempFmt = &WaveFile.fmt_Data;
if (TempFmt->wFormatTag != WAVE_FMT_PCM)
{
fclose(hFile);
printf("Error in wave file: Compressed wave file are not supported!\n");
return 4;
}
if (TempFmt->nChannels != 1)
{
fclose(hFile);
printf("Error in wave file: Unsupported number of channels (%hu)!\n", TempFmt->nChannels);
return 4;
}
if (TempFmt->wBitsPerSample != 16)
{
fclose(hFile);
printf("Error in wave file: Only 16-bit waves are supported! (File uses %hu bit)\n", TempFmt->wBitsPerSample);
return 4;
}
TempLng = fread(&WaveFile.datafcc, 0x04, 0x01, hFile);
fread(&WaveFile.dataLen, 0x04, 0x01, hFile);
while (memcmp(WaveFile.datafcc, "data", 4) != 0)
{
if (!TempLng) // TempLng == 0 -> EOF reached
{
fclose(hFile);
printf("Error in wave file: Can't find data-tag!\n");
return 4;
}
fseek(hFile, WaveFile.dataLen, SEEK_CUR);
TempLng = fread(&WaveFile.datafcc, 0x04, 0x01, hFile);
fread(&WaveFile.dataLen, 0x04, 0x01, hFile);
};
unsigned int WaveSize = WaveFile.dataLen / 2;
int16_t* WaveData = malloc(WaveSize * 2);
fread(WaveData, 0x02, WaveSize, hFile);
fclose(hFile);
unsigned int AdpcmSize = WaveSize / 2;
uint8_t* AdpcmData = malloc(AdpcmSize);
printf("Encoding ...");
AdpcmBEncoderState encoder;
adpcmBEncoderInit(&encoder);
adpcmBEncode(&encoder, WaveData, AdpcmData, WaveSize);
printf(" OK\n");
hFile = fopen(outPath, "wb");
if (hFile == NULL)
{
printf("Error opening output file!\n");
free(AdpcmData);
free(WaveData);
return 3;
}
fwrite(AdpcmData, 0x01, AdpcmSize, hFile);
fclose(hFile);
printf("File written.\n");
free(AdpcmData);
free(WaveData);
return 0; return 0;
} }
static int YM2610_ADPCM_Decode(UINT8 *src, INT16 *dest, int len)
{
int lpc, flag, shift, step;
long i, xn, stepSize;
UINT8 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;
stepSize = stepSize * stepsizeTable[adpcm] / 64;
if (stepSize < 127)
stepSize = 127;
else if (stepSize > 24576)
stepSize = 24576;
*dest = (INT16)xn;
dest ++;
src += step;
step = step ^ 1;
shift = shift ^ 4;
}
return 0;
}
INLINE UINT32 DeltaTReg2SampleRate(UINT16 DeltaN, UINT32 Clock)
{
return (UINT32)(DeltaN * (Clock / 72.0) / 65536.0 + 0.5);
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
int ErrVal;
int ArgBase;
DWORD OutSmplRate;
FILE* hFile;
unsigned int AdpcmSize;
UINT8* AdpcmData;
unsigned int WaveSize;
UINT16* WaveData;
WAVE_FILE WaveFile;
WAVEFORMAT* TempFmt;
unsigned int TempLng; unsigned int TempLng;
UINT16 DTRegs; uint16_t DTRegs;
char* TempPnt; char* TempPnt;
printf("NeoGeo ADPCM-B En-/Decoder\n--------------------------\n"); printf("NeoGeo ADPCM-B En-/Decoder\n--------------------------\n");
if (argc < 4) if (argc < 4)
{ {
printf("Usage: ADPCM_Encode.exe -method [-option] InputFile OutputFile\n"); printf("Usage: ADPCM_Encode -method [-option] InputFile OutputFile\n");
printf("-method - En-/Decoding Method:\n"); printf("-method - En-/Decoding Method:\n");
printf(" -d for decode (bin -> wav)\n"); printf(" -d for decode (bin -> wav)\n");
printf(" -e for encode (wav -> bin)\n"); printf(" -e for encode (wav -> bin)\n");
@@ -244,202 +235,45 @@ int main(int argc, char* argv[])
return 1; return 1;
} }
ErrVal = 0; int ErrVal = 0;
AdpcmData = NULL; uint32_t OutSmplRate = 0;
WaveData = NULL;
OutSmplRate = 0;
ArgBase = 2; int ArgBase = 2;
if (argv[2][0] == '-' && argv[2][2] == ':') if (argv[2][0] == '-' && argv[2][2] == ':')
{ {
switch(argv[2][1]) switch (argv[2][1])
{ {
case 's': case 's':
OutSmplRate = strtol(argv[2] + 3, NULL, 0); OutSmplRate = strtol(argv[2] + 3, NULL, 0);
break; break;
case 'r': case 'r':
DTRegs = (UINT16)strtoul(argv[2] + 3, &TempPnt, 0); DTRegs = (uint16_t)strtoul(argv[2] + 3, &TempPnt, 0);
TempLng = 0; TempLng = 0;
if (*TempPnt == ',') if (*TempPnt == ',')
{ {
TempLng = strtoul(TempPnt + 1, NULL, 0); TempLng = strtoul(TempPnt + 1, NULL, 0);
} }
if (! TempLng) if (!TempLng)
TempLng = 4000000; TempLng = 4000000;
OutSmplRate = DeltaTReg2SampleRate(DTRegs, TempLng); OutSmplRate = DeltaTReg2SampleRate(DTRegs, TempLng);
break; break;
} }
ArgBase ++; if (argc < ++ArgBase + 2)
if (argc < ArgBase + 2)
{ {
printf("Not enought arguments!\n"); printf("Not enought arguments!\n");
return 1; return 1;
} }
} }
switch(argv[1][1]) switch (argv[1][1])
{ {
case 'd': case 'd':
hFile = fopen(argv[ArgBase + 0], "rb"); ErrVal = decode(argv[ArgBase + 0], argv[ArgBase + 1], OutSmplRate);
if (hFile == NULL)
{
printf("Error opening input file!\n");
ErrVal = 2;
goto Finish;
}
fseek(hFile, 0x00, SEEK_END);
AdpcmSize = ftell(hFile);
fseek(hFile, 0x00, SEEK_SET);
AdpcmData = (UINT8*)malloc(AdpcmSize);
fread(AdpcmData, 0x01, AdpcmSize, hFile);
fclose(hFile);
WaveSize = AdpcmSize * 2; // 4-bit ADPCM -> 2 values per byte
WaveData = (UINT16*)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;
TempFmt->wBitsPerSample = 16;
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)
{
printf("Error opening output file!\n");
ErrVal = 3;
goto Finish;
}
fwrite(&WaveFile, sizeof(WAVE_FILE), 0x01, hFile);
fwrite(WaveData, 0x02, WaveSize, hFile);
fclose(hFile);
printf("File written.\n");
break; break;
case 'e': case 'e':
hFile = fopen(argv[ArgBase + 0], "rb"); ErrVal = encode(argv[ArgBase + 0], argv[ArgBase + 1]);
if (hFile == NULL)
{
printf("Error opening input file!\n");
ErrVal = 2;
goto Finish;
}
fread(&WaveFile.RIFFfcc, 0x0C, 0x01, hFile);
if (WaveFile.RIFFfcc != FOURCC_RIFF || WaveFile.WAVEfcc != FOURCC_WAVE)
{
fclose(hFile);
printf("This is no wave file!\n");
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
{
fclose(hFile);
printf("Error in wave file: Can't find format-tag!\n");
ErrVal = 4;
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)
{
fclose(hFile);
printf("Error in wave file: Compressed wave file are not supported!\n");
ErrVal = 4;
goto Finish;
}
if (TempFmt->nChannels != 1)
{
fclose(hFile);
printf("Error in wave file: Unsupported number of channels (%hu)!\n", TempFmt->nChannels);
ErrVal = 4;
goto Finish;
}
if (TempFmt->wBitsPerSample != 16)
{
fclose(hFile);
printf("Error in wave file: Only 16-bit waves are supported! (File uses %hu bit)\n", TempFmt->wBitsPerSample);
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
{
fclose(hFile);
printf("Error in wave file: Can't find data-tag!\n");
ErrVal = 4;
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);
fread(WaveData, 0x02, WaveSize, hFile);
fclose(hFile);
AdpcmSize = WaveSize / 2;
AdpcmData = (UINT8*)malloc(AdpcmSize);
printf("Encoding ...");
YM2610_ADPCM_Encode(WaveData, AdpcmData, WaveSize);
printf(" OK\n");
hFile = fopen(argv[ArgBase + 1], "wb");
if (hFile == NULL)
{
printf("Error opening output file!\n");
ErrVal = 3;
goto Finish;
}
fwrite(AdpcmData, 0x01, AdpcmSize, hFile);
fclose(hFile);
printf("File written.\n");
break; break;
} }
Finish:
free(AdpcmData);
free(WaveData);
return ErrVal; return ErrVal;
} }

25
neotools/adpcmb.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef ADPCMB_H
#define ADPCMB_H
#include <stdint.h>
#include <stdbool.h>
typedef struct AdpcmBEncoderState
{
bool flag;
long xn, stepSize;
uint8_t adpcmPack;
} AdpcmBEncoderState;
void adpcmBEncoderInit(AdpcmBEncoderState* encoder);
void adpcmBEncode(AdpcmBEncoderState* encoder, const int16_t* restrict in, uint8_t* restrict out, int len);
typedef struct AdpcmBDecoderState
{
long xn, stepSize;
} AdpcmBDecoderState;
void adpcmBDecoderInit(AdpcmBDecoderState* decoder);
void adpcmBDecode(AdpcmBDecoderState* decoder, const uint8_t* restrict in, int16_t* restrict out, int len);
#endif//ADPCMB_H

View File

@@ -1,19 +0,0 @@
if ["%~x1"]==[".vgz"] goto vgztopcm else goto vgmtopcm
:vgmtopcm
neoadpcmextract.exe %1
goto pcmtowav
:vgztopcm
copy /y %1 temp.vgm.gz
gzip.exe -d temp.vgm.gz
neoadpcmextract.exe temp.vgm
del temp.vgm
goto pcmtowav
:pcmtowav
for /r %%v in (smpa_*.pcm) do adpcm.exe "%%v" "%%v.wav"
for /r %%v in (smpb_*.pcm) do adpcmb.exe -d "%%v" "%%v.wav"
del "*.pcm"
mkdir "%~n1"
move "*.wav" "%~n1"

View File

@@ -1,63 +0,0 @@
#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,12 +0,0 @@
#!/bin/sh
set -e
FILE="$1"
NAME="$(basename "$FILE")"
WAVDIR="${NAME%%.*}"
./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 {} \;

57
neotools/libadpcma.c Normal file
View File

@@ -0,0 +1,57 @@
/* libadpcma.c (C) 2023 a dinosaur (zlib)
Original ADPCM to PCM converter v 1.01 By MARTINEZ Fabrice aka SNK of SUPREMACY */
#include "adpcm.h"
#include "util.h"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#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)
void adpcmAInit(AdpcmADecoderState* decoder)
{
for (int step = 0; step <= 48; step++)
{
int stepval = floor(16.0 * pow(11.0 / 10.0, step) * ADPCMA_VOLUME_RATE);
// Loop over all nibbles and compute the difference
for (int nib = 0; nib < 16; nib++)
{
int value = stepval * ((nib & 0x07) * 2 + 1) / 8;
decoder->jediTable[step * 16 + nib] = (nib & 0x08) ? -value : value;
}
}
decoder->delta = 0;
decoder->cursignal = 0;
}
static const int decodeTableA1[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
};
void adpcmADecode(AdpcmADecoderState* decoder, const char* restrict in, short* restrict out, int len)
{
for (int i = 0; i < len * 2; ++i)
{
int data = (!(i & 0x1) ? ((*in) >> 4) : (*in++)) & 0x0F;
int oldsignal = decoder->cursignal;
decoder->cursignal = CLAMP(decoder->cursignal + decoder->jediTable[data + decoder->delta],
ADPCMA_DECODE_MIN, ADPCMA_DECODE_MAX);
decoder->delta = CLAMP(decoder->delta + decodeTableA1[data], 0 * 16, 48 * 16);
if (abs(oldsignal - decoder->cursignal) > 2500)
{
fprintf(stderr, "WARNING: Suspicious signal evolution %06x,%06x pos:%06x delta:%06x\n",
oldsignal, decoder->cursignal, i % len, decoder->delta);
fprintf(stderr, "data:%02x dx:%08x\n",
data, decoder->jediTable[data + decoder->delta]);
}
*(out++) = (decoder->cursignal & 0xffff) * 32;
}
}

92
neotools/libadpcmb.c Normal file
View File

@@ -0,0 +1,92 @@
/* libadpcmb.c (C) 2023 a dinosaur (zlib)
** 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
http://www.raregame.ru/file/15/YM2610.pdf YM2610 DATASHEET
*/
#include "adpcmb.h"
#include "util.h"
#include <stdlib.h>
static const long stepSizeTable[16] =
{
57, 57, 57, 57, 77, 102, 128, 153,
57, 57, 57, 57, 77, 102, 128, 153
};
void adpcmBEncoderInit(AdpcmBEncoderState* encoder)
{
encoder->xn = 0;
encoder->stepSize = 127;
encoder->flag = false;
encoder->adpcmPack = 0;
}
void adpcmBEncode(AdpcmBEncoderState* encoder, const int16_t* restrict in, uint8_t* restrict out, int len)
{
for (int lpc = 0; lpc < len; ++lpc)
{
long dn = (*in++) - encoder->xn;
long i = (labs(dn) << 16) / (encoder->stepSize << 14);
i = MIN(i, 7);
uint8_t adpcm = i;
i = (adpcm * 2 + 1) * encoder->stepSize / 8;
if (dn < 0)
{
adpcm |= 0x8;
encoder->xn -= i;
}
else
{
encoder->xn += i;
}
encoder->stepSize = (stepSizeTable[adpcm] * encoder->stepSize) / 64;
encoder->stepSize = CLAMP(encoder->stepSize, 127, 24576);
if (!encoder->flag)
{
encoder->adpcmPack = adpcm << 4;
encoder->flag = true;
}
else
{
(*out++) = encoder->adpcmPack |= adpcm;
encoder->flag = false;
}
}
}
void adpcmBDecoderInit(AdpcmBDecoderState* decoder)
{
decoder->xn = 0;
decoder->stepSize = 127;
}
void adpcmBDecode(AdpcmBDecoderState* decoder, const uint8_t* restrict in, int16_t* restrict out, int len)
{
for (long lpc = 0; lpc < len * 2; ++lpc)
{
uint8_t adpcm = (!(lpc & 0x1) ? (*in) >> 4 : (*in++)) & 0xF;
long i = ((adpcm & 7) * 2 + 1) * decoder->stepSize / 8;
if (adpcm & 8)
decoder->xn -= i;
else
decoder->xn += i;
decoder->xn = CLAMP(decoder->xn, -32768, 32767);
decoder->stepSize = decoder->stepSize * stepSizeTable[adpcm] / 64;
decoder->stepSize = CLAMP(decoder->stepSize, 127, 24576);
(*out++) = (int16_t)decoder->xn;
}
}

View File

@@ -1,57 +1,184 @@
/* neoadpcmextract.c (C) 2017, 2019, 2020 a dinosaur (zlib) */ /* neoadpcmextract.c (C) 2017, 2019, 2020, 2023 a dinosaur (zlib) */
#include "neoadpcmextract.h" #include "neoadpcmextract.h"
#include "adpcm.h"
#include "adpcmb.h"
#include "wave.h"
#include "endian.h"
#include "util.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
int vgmReadSample(FILE* fin, Buffer* buf) static uint32_t read32le(nfile* fin)
{ {
// Get sample data length. uint32_t tmp = 0;
uint32_t sampLen = 0; nread(&tmp, sizeof(uint32_t), 1, fin);
fread(&sampLen, sizeof(uint32_t), 1, fin); return SWAP_LE32(tmp);
if (sampLen < sizeof(uint64_t)) }
return 1;
sampLen -= sizeof(uint64_t);
// Resize buffer if needed. bool bufferResize(Buffer* buf, size_t size)
buf->size = sampLen; {
if (!buf->data || buf->reserved < sampLen) if (!buf)
return false;
buf->size = size;
if (!buf->data || buf->reserved < size)
{ {
free(buf->data); free(buf->data);
buf->reserved = sampLen; buf->reserved = size;
buf->data = malloc(sampLen); buf->data = malloc(size);
if (!buf->data) if (!buf->data)
return 1; return false;
} }
return true;
}
// Ignore 8 bytes. int vgmReadSample(nfile* restrict fin, Buffer* restrict buf)
uint64_t dummy; {
fread(&dummy, sizeof(uint64_t), 1, fin); uint32_t sampLen = read32le(fin); // Get sample data length
if (sampLen <= 8)
// Read adpcm data. return 1;
fread(buf->data, sizeof(uint8_t), sampLen, fin); sampLen -= 8;
if (!bufferResize(buf, sampLen)) // Resize buffer if needed
return false;
nseek(fin, 8, SEEK_CUR); // Ignore 8 bytes
nread(buf->data, 1, sampLen, fin); // Read adpcm data
return 0; return 0;
} }
int vgmScanSample(FILE* file) int vgmScanSample(nfile* file)
{ {
// Scan for pcm headers. // Scan for pcm headers
while (1) while (1)
{ {
if (feof(file) || ferror(file)) if (neof(file) || nerror(file))
return 0; return 0;
if (ngetc(file) != 0x67 || ngetc(file) != 0x66) // Match data block
// Patterns to match (in hex):
// 67 66 82 - ADPCM-A
// 67 66 83 - ADPCM-B
if (fgetc(file) != 0x67 || fgetc(file) != 0x66)
continue; continue;
switch (ngetc(file))
uint8_t byte = fgetc(file); {
if (byte == 0x82) case 0x82: return 'A'; // 67 66 82 - ADPCM-A
return 'A'; case 0x83: return 'B'; // 67 66 83 - ADPCM-B
else if (byte == 0x83) default: return 0;
return 'B'; }
} }
} }
#define DECODE_BUFFER_SIZE 0x4000
int writeAdpcmA(int id, const Buffer* enc, Buffer* pcm)
{
char name[32];
snprintf(name, sizeof(name), "smpa_%02x.wav", id);
FILE* fout = fopen(name, "wb");
if (!fout)
return 1;
// Write wave header
const uint32_t decodedSize = enc->size * 2 * sizeof(short);
waveWrite(&(const WaveSpec)
{
.format = WAVE_FMT_PCM,
.channels = 1,
.rate = 18500,
.bytedepth = 2
},
NULL, decodedSize, &waveStreamDefaultCb, fout);
bufferResize(pcm, DECODE_BUFFER_SIZE * 2 * sizeof(short));
AdpcmADecoderState decoder;
adpcmAInit(&decoder);
size_t decoded = 0;
do
{
const size_t blockSize = MIN(enc->size - decoded, DECODE_BUFFER_SIZE);
adpcmADecode(&decoder, &((const char*)enc->data)[decoded], (short*)pcm->data, blockSize);
fwrite(pcm->data, sizeof(short), blockSize * 2, fout);
decoded += DECODE_BUFFER_SIZE;
}
while (decoded < enc->size);
fclose(fout);
fprintf(stderr, "Wrote \"%s\"\n", name);
return 0;
}
int writeAdpcmB(int id, const Buffer* enc, Buffer* pcm)
{
char name[32];
snprintf(name, sizeof(name), "smpb_%02x.wav", id);
FILE* fout = fopen(name, "wb");
if (!fout)
return 1;
// Write wave header
const uint32_t decodedSize = enc->size * 2 * sizeof(short);
waveWrite(&(const WaveSpec)
{
.format = WAVE_FMT_PCM,
.channels = 1,
.rate = 22050,
.bytedepth = 2
},
NULL, decodedSize, &waveStreamDefaultCb, fout);
bufferResize(pcm, DECODE_BUFFER_SIZE * 2 * sizeof(short));
AdpcmBDecoderState decoder;
adpcmBDecoderInit(&decoder);
size_t decoded = 0;
do
{
const size_t blockSize = MIN(enc->size - decoded, DECODE_BUFFER_SIZE);
adpcmBDecode(&decoder, &((const uint8_t*)enc->data)[decoded], (int16_t*)pcm->data, blockSize);
fwrite(pcm->data, sizeof(int16_t), blockSize * 2, fout);
decoded += DECODE_BUFFER_SIZE;
}
while (decoded < enc->size);
fclose(fout);
fprintf(stderr, "Wrote \"%s\"\n", name);
return 0;
}
int main(int argc, char** argv)
{
if (argc != 2)
return 1;
nfile* file = nopen(argv[1], "rb"); // Open file
if (!file) return 1;
#if !USE_ZLIB
if (ngetc(file) == 0x1F && ngetc(file) == 0x8B)
{
printf("I'm a little gzip short and stout\n");
return 2;
}
nseek(file, 0, SEEK_SET);
#endif
Buffer rawbuf = BUFFER_CLEAR(), decbuf = BUFFER_CLEAR();
int smpaCount = 0, smpbCount = 0;
// Find ADCPM samples
int scanType;
while ((scanType = vgmScanSample(file)))
{
fprintf(stderr, "ADPCM-%c data found at 0x%08lX\n", scanType, ntell(file));
if (vgmReadSample(file, &rawbuf) || rawbuf.size == 0)
continue;
if (scanType == 'A')
writeAdpcmA(smpaCount++, &rawbuf, &decbuf);
else if (scanType == 'B')
writeAdpcmB(smpbCount++, &rawbuf, &decbuf);
}
free(rawbuf.data);
nclose(file);
return 0;
}

View File

@@ -1,12 +1,39 @@
#ifndef __NEOADPCMEXTRACT_H__ #ifndef NEOADPCMEXTRACT_H
#define __NEOADPCMEXTRACT_H__ #define NEOADPCMEXTRACT_H
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
typedef struct { uint8_t* data; size_t size, reserved; } Buffer; #if USE_ZLIB
#include <zlib.h>
typedef struct gzFile_s nfile;
# define nopen gzopen
# define nclose gzclose
# define nread gzfread
# define ngetc gzgetc
# define nseek gzseek
# define ntell gztell
# define neof gzeof
static inline int nerror(gzFile file) { int err; gzerror(file, &err); return err; }
#else
typedef FILE nfile;
# define nopen fopen
# define nclose fclose
# define nread fread
# define ngetc fgetc
# define nseek fseek
# define ntell ftell
# define neof feof
# define nerror ferror
#endif
int vgmReadSample(FILE* fin, Buffer* buf); typedef struct { void* data; size_t size, reserved; } Buffer;
int vgmScanSample(FILE* file); #define BUFFER_CLEAR() { NULL, 0, 0 }
#endif//__NEOADPCMEXTRACT_H__ bool bufferResize(Buffer* buf, size_t size);
int vgmReadSample(nfile* restrict fin, Buffer* restrict buf);
int vgmScanSample(nfile* file);
#endif//NEOADPCMEXTRACT_H

78
sinharmonicswt.py Normal file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
# Name: sinharmonicswt.py
# Copyright: © 2023 a dinosaur
# Homepage: https://github.com/ScrelliCopter/VGM-Tools
# License: Zlib (https://opensource.org/licenses/Zlib)
# Description: Generate Serum format wavetables of the harmonic series
# for a handful of common FM waveforms, intended for improving
# the workflow of creating FM sounds in Vital.
import math
from pathlib import Path
from typing import NamedTuple
from common.wavewriter import WaveFile, WavePcmFormatChunk, WaveDataChunk
from common.waveserum import WaveSerumCommentChunk, SerumWavetableInterpolation
def write_wavetable(name: str, generator,
mode: SerumWavetableInterpolation = SerumWavetableInterpolation.NONE,
num: int = 64, size: int = 2048):
def sweep_table() -> bytes:
for i in range(num - 1):
yield b"".join(generator(size, i + 1))
with open(name, "wb") as f:
WaveFile(WavePcmFormatChunk(1, 44100, 16), [
WaveSerumCommentChunk(size, mode),
WaveDataChunk(b"".join(sweep_table()))
]).write(f)
def main():
clip = lambda x, a, b: max(a, min(b, x))
def clamp2short(a: float) -> int: return clip(int(a * 0x7FFF), -0x8000, 0x7FFF)
def sinetable16(size: int, harmonic: int):
# Generate a simple sine wave
for i in range(size):
sample = clamp2short(math.sin(i / size * math.tau * harmonic))
yield sample.to_bytes(2, byteorder="little", signed=True)
#TODO: probably make bandlimited versions of the nonlinear waves
def hsinetable16(size: int, harmonic: int):
# Generate one half of a sine wave with the negative pole hard clipped off
for i in range(size):
sample = clamp2short(max(0.0, math.sin(i / size * math.tau * harmonic)))
yield sample.to_bytes(2, byteorder="little", signed=True)
#TODO: probably make bandlimited versions of the nonlinear waves
def asinetable16(size: int, harmonic: int):
# Generate a sine wave with the negative pole mirrored positively
for i in range(size):
sample = clamp2short(math.fabs(math.sin(i / size * math.pi * harmonic)))
yield sample.to_bytes(2, byteorder="little", signed=True)
outfolder = Path("FM Harmonics")
outfolder.mkdir(exist_ok=True)
# Build queue of files to generate
GenItem = NamedTuple("GenItem", generator=any, steps=int, mode=SerumWavetableInterpolation, name=str)
genqueue: list[GenItem] = list()
# All waveform types with 64 harmonic steps in stepped and linear versions
for mode in [("", SerumWavetableInterpolation.NONE), (" (XFade)", SerumWavetableInterpolation.LINEAR_XFADE)]:
for generator in [("Sine", sinetable16), ("Half Sine", hsinetable16), ("Abs Sine", asinetable16)]:
genqueue.append(GenItem(generator[1], 64, mode[1], f"{generator[0]} Harmonics{mode[0]}"))
# Shorter linear versions of hsine and asine
for steps in [8, 16, 32]:
spec = SerumWavetableInterpolation.LINEAR_XFADE
for generator in [("Half Sine", hsinetable16), ("Abs Sine", asinetable16)]:
genqueue.append(GenItem(generator[1], steps, spec, f"{generator[0]} (XFade {steps})"))
# Generate & write wavetables
for i in genqueue:
write_wavetable(str(outfolder.joinpath(f"{i.name}.wav")), i.generator, i.mode, i.steps)
if __name__ == "__main__":
main()

View File

@@ -1,21 +1,29 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# ripsamples.py -- a python script for mass extracting samples from SPC files. # ripsamples.py -- a python script for mass extracting samples from SPC files.
# (C) 2018 neoadpcmextract.c (C) 2018 a dinosaur (zlib) # (C) 2018, 2023 a dinosaur (zlib)
import os import os
import subprocess import subprocess
import pathlib import pathlib
import struct import struct
import hashlib import hashlib
from typing import BinaryIO
from io import BytesIO
# Directory constants. import sys
sys.path.append("..")
from common.wavewriter import WaveFile, WavePcmFormatChunk, WaveDataChunk
from common.wavesampler import WaveSamplerChunk, WaveSamplerLoop
# Directory constants
SPCDIR = "./spc" SPCDIR = "./spc"
ITDIR = "./it" ITDIR = "./it"
SMPDIR = "./sample" SMPDIR = "./sample"
# External programs used by this script. # External programs used by this script
SPC2IT = "spc2it" SPC2IT = "spc2it/spc2it"
class Sample: class Sample:
@@ -24,63 +32,34 @@ class Sample:
loopEnd = 0 loopEnd = 0
rate = 0 rate = 0
data = None data: bytes = None
def writesmp(smp, path):
def writesmp(smp: Sample, path: str):
with open(path, "wb") as wav: with open(path, "wb") as wav:
# Make sure sample rate is nonzero
# Make sure sample rate is nonzero.
#TODO: figure out why this even happens... #TODO: figure out why this even happens...
if smp.rate == 0: if smp.rate == 0:
smp.rate = 32000 smp.rate = 32000
#print(path + " may be corrupted...") #print(path + " may be corrupted")
writeLoop = True if smp.loopEnd > smp.loopBeg else False fmtChunk = WavePcmFormatChunk( # Audio format (uncompressed)
1, # Channel count (mono)
smp.rate, # Samplerate
16) # Bits per sample (16 bit)
dataChunk = WaveDataChunk(smp.data)
loopChunk = None
if smp.loopEnd > smp.loopBeg:
loopChunk = WaveSamplerChunk(loops=[WaveSamplerLoop(
start=smp.loopBeg, # Loop start
end=smp.loopEnd)]) # Loop end
# Write RIFF chunk. WaveFile(fmtChunk,
wav.write(b"RIFF") [dataChunk] if loopChunk is None else [loopChunk, dataChunk]
# Size of entire file following ).write(wav)
riffSize = 104 if writeLoop else 36
wav.write(struct.pack("<I", riffSize + smp.length * 2))
wav.write(b"WAVE")
# Write fmt chunk.
wav.write(b"fmt ")
wav.write(struct.pack("<I", 16)) # Subchunk size.
wav.write(struct.pack("<H", 1)) # Audio format (uncompressed)
wav.write(struct.pack("<H", 1)) # Channel count (mono)
wav.write(struct.pack("<I", smp.rate)) # Samplerate
wav.write(struct.pack("<I", smp.rate * 2 )) # Byte rate (16 bit mono)
wav.write(struct.pack("<H", 2)) # Bytes per sample (16 bit mono)
wav.write(struct.pack("<H", 16)) # Bits per sample (16 bit)
# Write sampler chunk (if looped). def readsmp(f: BinaryIO, ofs: int, idx: int):
if writeLoop:
wav.write(b"smpl")
wav.write(struct.pack("<I", 60)) # Chunk size (36 + loops * 24)
wav.write(b"\x00\x00\x00\x00") # Manufacturer
wav.write(b"\x00\x00\x00\x00") # Product
wav.write(b"\x00\x00\x00\x00") # Sample period
wav.write(b"\x00\x00\x00\x00") # MIDI unity note
wav.write(b"\x00\x00\x00\x00") # MIDI pitch fraction
wav.write(b"\x00\x00\x00\x00") # SMPTE format
wav.write(b"\x00\x00\x00\x00") # SMPTE offset
wav.write(struct.pack("<I", 1)) # Loop count
wav.write(struct.pack("<I", 24)) # Loop data length
wav.write(struct.pack("<I", 0)) # Cue point ID (none)
wav.write(struct.pack("<I", 0)) # Loop type (forward)
wav.write(struct.pack("<I", smp.loopBeg)) # Loop start
wav.write(struct.pack("<I", smp.loopEnd)) # Loop end
wav.write(struct.pack("<I", 0)) # Fraction (none)
wav.write(struct.pack("<I", 0)) # Loop count (infinite)
# Write data chunk.
wav.write(b"data")
wav.write(struct.pack("<I", smp.length * 2))
wav.write(smp.data)
def readsmp(f, ofs, idx):
# List of assumptions made: # List of assumptions made:
# - Samples are 16 bit # - Samples are 16 bit
# - Samples are mono # - Samples are mono
@@ -93,34 +72,34 @@ def readsmp(f, ofs, idx):
f.seek(ofs) f.seek(ofs)
if f.read(4) != b"IMPS": return None if f.read(4) != b"IMPS": return None
# Skip fname to flags & read. # Skip fname to flags & read
f.seek(ofs + 0x12) f.seek(ofs + 0x12)
flags = int.from_bytes(f.read(1), byteorder="little", signed=False) flags = int.from_bytes(f.read(1), byteorder="little", signed=False)
# Read flag values. # Read flag values
if not flags & 0b00000001: return None # Check sample data bit. if not flags & 0b00000001: return None # Check sample data bit
loopBit = True if flags & 0b00010000 else False loopBit = True if flags & 0b00010000 else False
smp = Sample() smp = Sample()
# Read the rest of the header. # Read the rest of the header
f.seek(ofs + 0x30) f.seek(ofs + 0x30)
smp.length = int.from_bytes(f.read(4), byteorder="little", signed=False) smp.length = int.from_bytes(f.read(4), byteorder="little", signed=False)
if loopBit: if loopBit:
smp.loopBeg = int.from_bytes(f.read(4), byteorder="little", signed=False) smp.loopBeg = int.from_bytes(f.read(4), byteorder="little", signed=False)
smp.loopEnd = int.from_bytes(f.read(4), byteorder="little", signed=False) smp.loopEnd = int.from_bytes(f.read(4), byteorder="little", signed=False)
else: else:
f.seek(8, 1) # Skip over. f.seek(8, 1) # Skip over
smp.loopBeg = 0 smp.loopBeg = 0
smp.loopEnd = 0 smp.loopEnd = 0
smp.rate = int.from_bytes(f.read(4), byteorder="little", signed=False) smp.rate = int.from_bytes(f.read(4), byteorder="little", signed=False)
f.seek(8, 1) # Skip over sustain shit. f.seek(8, 1) # Skip over sustain shit
# Read sample data. # Read sample data
dataOfs = int.from_bytes(f.read(4), byteorder="little", signed=False) dataOfs = int.from_bytes(f.read(4), byteorder="little", signed=False)
smp.data = f.read(smp.length * 2) smp.data = f.read(smp.length * 2)
# Compute hash of data. # Compute hash of data
#FIXME: This actually generates a butt ton of collisions... #FIXME: This actually generates a butt ton of collisions...
# there's got to be a better way! # there's got to be a better way!
h = hashlib.md5(struct.pack("<pII", smp.data, smp.loopBeg, smp.loopEnd)) h = hashlib.md5(struct.pack("<pII", smp.data, smp.loopBeg, smp.loopEnd))
@@ -128,15 +107,16 @@ def readsmp(f, ofs, idx):
return smp return smp
def readit(path, outpath):
def readit(path: str, outpath: str):
with open(path, "r+b") as f: with open(path, "r+b") as f:
# Don't bother scanning non IT files. # Don't bother scanning non IT files
if f.read(4) != b"IMPM": return if f.read(4) != b"IMPM": return
#print("Song name: " + f.read(26).decode('utf-8')) #print("Song name: " + f.read(26).decode('utf-8'))
# Need order list size and num instruments to know how far to skip. # Need order list size and num instruments to know how far to skip
f.seek(0x20) f.seek(0x20)
ordNum = int.from_bytes(f.read(2), byteorder="little", signed=False) ordNum = int.from_bytes(f.read(2), byteorder="little", signed=False)
insNum = int.from_bytes(f.read(2), byteorder="little", signed=False) insNum = int.from_bytes(f.read(2), byteorder="little", signed=False)
@@ -161,7 +141,8 @@ def readit(path, outpath):
pathlib.Path(outpath).mkdir(parents=True, exist_ok=True) pathlib.Path(outpath).mkdir(parents=True, exist_ok=True)
writesmp(smp, outwav) writesmp(smp, outwav)
def scanit(srcPath, dstPath):
def scanit(srcPath: str, dstPath: str):
for directory, subdirectories, files in os.walk(srcPath): for directory, subdirectories, files in os.walk(srcPath):
for file in files: for file in files:
if file.endswith(".it"): if file.endswith(".it"):
@@ -169,18 +150,19 @@ def scanit(srcPath, dstPath):
outpath = dstPath + path[len(srcPath):-len(file)] outpath = dstPath + path[len(srcPath):-len(file)]
readit(path, outpath) readit(path, outpath)
def scanspc(srcPath, dstPath):
def scanspc(srcPath: str, dstPath: str):
for directory, subdirectories, files in os.walk(srcPath): for directory, subdirectories, files in os.walk(srcPath):
# Create output dir for each game. # Create output dir for each game
for sub in subdirectories: for sub in subdirectories:
path = os.path.join(dstPath, sub) path = os.path.join(dstPath, sub)
pathlib.Path(path).mkdir(parents=True, exist_ok=True) pathlib.Path(path).mkdir(parents=True, exist_ok=True)
# Convert spc files. # Convert spc files
for file in files: for file in files:
if file.endswith(".spc"): if file.endswith(".spc"):
# Don't convert files that have already been converted. # Don't convert files that have already been converted
itpath = os.path.join(dstPath + directory[len(srcPath):], file[:-3] + "it") itpath = os.path.join(dstPath + directory[len(srcPath):], file[:-3] + "it")
if not os.path.isfile(itpath): if not os.path.isfile(itpath):
path = os.path.join(directory, file) path = os.path.join(directory, file)
@@ -190,6 +172,7 @@ def scanspc(srcPath, dstPath):
os.rename(path, itpath) os.rename(path, itpath)
# Actual main stuff. # Actual main stuff
scanspc(SPCDIR, ITDIR) if __name__ == "__main__":
scanit(ITDIR, SMPDIR) scanspc(SPCDIR, ITDIR)
scanit(ITDIR, SMPDIR)