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

48 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
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
43 changed files with 2772 additions and 826 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/
**/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)

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.

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

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;
}

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

7
dsptools/CMakeLists.txt Normal file
View File

@@ -0,0 +1,7 @@
add_subdirectory(libdsptool)
add_executable(dspdecode dspdecode.c)
set_property(TARGET dspdecode PROPERTY C_STANDARD 99)
target_include_directories(dspdecode PRIVATE ${COMMON})
target_link_libraries(dspdecode Common::wave DspTool::DspTool)
target_compile_options(dspdecode PRIVATE ${WARNINGS})

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,18 @@
option(DSPTOOL_BUILD_SHARED_LIBS "Build as a Shared Object or DLL" OFF)
set(HEADERS dsptool.h)
set(SOURCES math.c decode.c encode.c)
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)
set_property(TARGET DspTool PROPERTY C_STANDARD 99)
target_include_directories(DspTool PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(DspTool PRIVATE ${WARNINGS})
target_link_libraries(DspTool PRIVATE Common::headers)

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( BUILD_STATIC )
# 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()

11
neotools/.gitignore vendored
View File

@@ -1,11 +0,0 @@
*.exe
*.vgm
*.vgz
*.log
*.pcm
*.wav
*.o
adpcm
adpcmb
neoadpcmextract

24
neotools/CMakeLists.txt Normal file
View File

@@ -0,0 +1,24 @@
find_package(ZLIB)
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.h libadpcma.c adpcm.c)
set_property(TARGET adpcm PROPERTY C_STANDARD 99)
target_compile_options(adpcm PRIVATE ${WARNINGS})
target_link_libraries(adpcm Common::wave $<$<C_COMPILER_ID:Clang,GNU>:m>)
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
libadpcma.c adpcm.h
libadpcmb.c adpcmb.h
neoadpcmextract.c)
set_property(TARGET neoadpcmextract PROPERTY C_STANDARD 99)
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,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,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 <stdlib.h>
#include <math.h>
#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 BUFFER_SIZE (1024 * 256)
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 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[])
int main(int argc, char* argv[])
{
FILE *Fp1, *Fp2;
void *InputBuffer, *OutputBuffer;
int Readed;
unsigned int Filelen;
puts("**** ADPCM to PCM converter v 1.01\n");
if (argc!=3) {
puts("USAGE: adpcm <InputFile.pcm> <OutputFile.wav>");
exit(-1);
fprintf(stderr, "**** ADPCM to PCM converter v 1.01\n\n");
if (argc != 3)
{
fprintf(stderr, "USAGE: adpcm <InputFile.pcm> <OutputFile.wav>\n");
return -1;
}
Fp1 = fopen(argv[1], "rb");
if (Fp1==NULL) {
printf("Could not open inputfile %s\n", argv[1]);
exit(-2);
FILE* inFile = fopen(argv[1], "rb");
if (!inFile)
{
fprintf(stderr, "Could not open inputfile %s\n", argv[1]);
return -2;
}
Fp2 = fopen(argv[2], "wb");
if (Fp2==NULL) {
printf("Could not open outputfile %s\n", argv[2]);
exit(-3);
FILE* outFile = fopen(argv[2], "wb");
if (!outFile)
{
fprintf(stderr, "Could not open outputfile %s\n", argv[2]);
return -3;
}
errorlog = fopen("error.log", "wb");
InputBuffer = malloc(BUFFER_SIZE);
if (InputBuffer == NULL) {
printf("Could not allocate input buffer. (%d bytes)\n", BUFFER_SIZE);
exit(-4);
char* InputBuffer = malloc(BUFFER_SIZE);
if (InputBuffer == NULL)
{
fprintf(stderr, "Could not allocate input buffer. (%d bytes)\n", BUFFER_SIZE);
return -4;
}
OutputBuffer = malloc(BUFFER_SIZE*10);
if (OutputBuffer == NULL) {
printf("Could not allocate output buffer. (%d bytes)\n", BUFFER_SIZE*4);
exit(-5);
short* OutputBuffer = malloc(BUFFER_SIZE * 4);
if (OutputBuffer == NULL)
{
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);
Filelen = ftell(Fp1);
fseek(Fp1, 0, SEEK_SET);
fseek(inFile, 0, SEEK_END);
unsigned int Filelen = ftell(inFile);
fseek(inFile, 0, SEEK_SET);
*((unsigned int*)(&RiffWave[4])) = Filelen*4 + 0x2C;
*((unsigned int*)(&RiffWave[0x28])) = Filelen*4;
// Write wave header
waveWrite(&(const WaveSpec)
{
.format = WAVE_FMT_PCM,
.channels = 1,
.rate = 18500,
.bytedepth = 2
},
NULL, Filelen * 4, &waveStreamDefaultCb, outFile);
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);
// Convert ADPCM to PCM and write to wave
int bytesRead;
do
{
bytesRead = fread(InputBuffer, 1, BUFFER_SIZE, inFile);
if (bytesRead > 0)
{
adpcmADecode(&decoder, InputBuffer, OutputBuffer, bytesRead);
fwrite(OutputBuffer, bytesRead * 4, 1, outFile);
}
} while (Readed==BUFFER_SIZE);
}
while (bytesRead == BUFFER_SIZE);
free(InputBuffer);
free(OutputBuffer);
fclose(Fp1);
fclose(Fp2);
free(InputBuffer);
fclose(outFile);
fclose(inFile);
puts("Done...");
fprintf(stderr, "Done...\n");
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,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,231 +1,222 @@
/*; 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
;
/* adpcmb.c - CLI for encoding & decoding YM2610 ADPCM-B files
; Fred/FRONT
;
;Usage 1: ADPCM_Encode.exe -d [-r:reg,clock] Input.bin Output.wav
;Usage 2: ADPCM_Encode.exe -e Input.wav Output.bin
;Usage 1: ADPCM_Encode -d [-r:reg,clock] Input.bin Output.wav
;Usage 2: ADPCM_Encode -e Input.wav Output.bin
;
; Valley Bell
;----------------------------------------------------------------------------------------------------------------------------*/
#include "adpcmb.h"
#include "wave.h"
#include "util.h"
#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
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>
typedef uint8_t UINT8;
typedef int8_t INT8;
typedef uint16_t UINT16;
typedef int16_t INT16;
typedef uint32_t UINT32;
typedef int32_t INT32;
#define BUFFER_SIZE 2048
#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;
typedef signed char INT8;
typedef unsigned short UINT16;
typedef signed short INT16;
typedef unsigned int UINT32;
typedef signed int INT32;
fseek(inFile, 0, SEEK_END);
long adpcmSize = ftell(inFile);
fseek(inFile, 0, SEEK_SET);
#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;
typedef UINT16 WORD;
typedef UINT32 DWORD;
// Write wave header
waveWrite(&(const WaveSpec)
{
.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 --
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
printf("Decoding ...");
AdpcmBDecoderState decoder;
adpcmBDecoderInit(&decoder);
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 {
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 */
#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
{
UINT32 RIFFfcc; // 'RIFF'
UINT32 RIFFLen;
UINT32 WAVEfcc; // 'WAVE'
UINT32 fmt_fcc; // 'fmt '
UINT32 fmt_Len;
char RIFFfcc[4];
uint32_t RIFFLen;
char WAVEfcc[4];
char fmt_fcc[4];
uint32_t fmt_Len;
WAVEFORMAT fmt_Data;
UINT32 datafcc; // 'data'
UINT32 dataLen;
char datafcc[4];
uint32_t dataLen;
} WAVE_FILE;
static const long stepsizeTable[16] =
static int encode(const char* inPath, const char* outPath)
{
57, 57, 57, 57, 77, 102, 128, 153,
57, 57, 57, 57, 77, 102, 128, 153
};
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 ++)
FILE* hFile = fopen(inPath, "rb");
if (hFile == NULL)
{
dn = *src - xn;
src ++;
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;
if (stepSize < 127)
stepSize = 127;
else if (stepSize > 24576)
stepSize = 24576;
if (flag == 0)
{
adpcmPack = (adpcm << 4);
flag = 1;
}
else
{
adpcmPack |= adpcm;
*dest = adpcmPack;
dest ++;
flag = 0;
}
printf("Error opening input file!\n");
return 2;
}
WAVE_FILE WaveFile;
fread(&WaveFile.RIFFfcc, 0x0C, 0x01, hFile);
if (memcmp(WaveFile.RIFFfcc, "RIFF", 4) != 0 || memcmp(WaveFile.WAVEfcc, "WAVE", 4) != 0)
{
fclose(hFile);
printf("This is no wave file!\n");
return 4;
}
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;
}
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 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;
UINT16 DTRegs;
uint16_t DTRegs;
char* TempPnt;
printf("NeoGeo ADPCM-B En-/Decoder\n--------------------------\n");
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(" -d for decode (bin -> wav)\n");
printf(" -e for encode (wav -> bin)\n");
@@ -244,202 +235,45 @@ int main(int argc, char* argv[])
return 1;
}
ErrVal = 0;
AdpcmData = NULL;
WaveData = NULL;
OutSmplRate = 0;
int ErrVal = 0;
uint32_t OutSmplRate = 0;
ArgBase = 2;
int ArgBase = 2;
if (argv[2][0] == '-' && argv[2][2] == ':')
{
switch(argv[2][1])
switch (argv[2][1])
{
case 's':
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 == ',')
{
TempLng = strtoul(TempPnt + 1, NULL, 0);
}
if (! TempLng)
if (!TempLng)
TempLng = 4000000;
OutSmplRate = DeltaTReg2SampleRate(DTRegs, TempLng);
break;
}
ArgBase ++;
if (argc < ArgBase + 2)
if (argc < ++ArgBase + 2)
{
printf("Not enought arguments!\n");
return 1;
}
}
switch(argv[1][1])
switch (argv[1][1])
{
case 'd':
hFile = fopen(argv[ArgBase + 0], "rb");
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");
ErrVal = decode(argv[ArgBase + 0], argv[ArgBase + 1], OutSmplRate);
break;
case 'e':
hFile = fopen(argv[ArgBase + 0], "rb");
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");
ErrVal = encode(argv[ArgBase + 0], argv[ArgBase + 1]);
break;
}
Finish:
free(AdpcmData);
free(WaveData);
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,19 +0,0 @@
#!/bin/sh
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"

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,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)

184
neotools/neoadpcmextract.c Normal file
View File

@@ -0,0 +1,184 @@
/* neoadpcmextract.c (C) 2017, 2019, 2020, 2023 a dinosaur (zlib) */
#include "neoadpcmextract.h"
#include "adpcm.h"
#include "adpcmb.h"
#include "wave.h"
#include "endian.h"
#include "util.h"
#include <stdlib.h>
#include <stdio.h>
static uint32_t read32le(nfile* fin)
{
uint32_t tmp = 0;
nread(&tmp, sizeof(uint32_t), 1, fin);
return SWAP_LE32(tmp);
}
bool bufferResize(Buffer* buf, size_t size)
{
if (!buf)
return false;
buf->size = size;
if (!buf->data || buf->reserved < size)
{
free(buf->data);
buf->reserved = size;
buf->data = malloc(size);
if (!buf->data)
return false;
}
return true;
}
int vgmReadSample(nfile* restrict fin, Buffer* restrict buf)
{
uint32_t sampLen = read32le(fin); // Get sample data length
if (sampLen <= 8)
return 1;
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;
}
int vgmScanSample(nfile* file)
{
// Scan for pcm headers
while (1)
{
if (neof(file) || nerror(file))
return 0;
if (ngetc(file) != 0x67 || ngetc(file) != 0x66) // Match data block
continue;
switch (ngetc(file))
{
case 0x82: return 'A'; // 67 66 82 - ADPCM-A
case 0x83: return 'B'; // 67 66 83 - ADPCM-B
default: return 0;
}
}
}
#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,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,39 @@
#ifndef NEOADPCMEXTRACT_H
#define NEOADPCMEXTRACT_H
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#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
typedef struct { void* data; size_t size, reserved; } Buffer;
#define BUFFER_CLEAR() { NULL, 0, 0 }
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,40 +1,29 @@
#!/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, 2023 a dinosaur (zlib)
import os
import subprocess
import pathlib
import struct
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"
ITDIR = "./it"
SMPDIR = "./sample"
# External programs used by this script.
SPC2IT = "spc2it"
# External programs used by this script
SPC2IT = "spc2it/spc2it"
class Sample:
@@ -43,63 +32,34 @@ class Sample:
loopEnd = 0
rate = 0
data = None
data: bytes = None
def writesmp(smp, path):
def writesmp(smp: Sample, path: str):
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...
if smp.rate == 0:
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.
wav.write(b"RIFF")
# Size of entire file following
riffSize = 104 if writeLoop else 36
wav.write(struct.pack("<I", riffSize + smp.length * 2))
wav.write(b"WAVE")
WaveFile(fmtChunk,
[dataChunk] if loopChunk is None else [loopChunk, dataChunk]
).write(wav)
# 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).
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):
def readsmp(f: BinaryIO, ofs: int, idx: int):
# List of assumptions made:
# - Samples are 16 bit
# - Samples are mono
@@ -112,34 +72,34 @@ def readsmp(f, ofs, idx):
f.seek(ofs)
if f.read(4) != b"IMPS": return None
# Skip fname to flags & read.
# Skip fname to flags & read
f.seek(ofs + 0x12)
flags = int.from_bytes(f.read(1), byteorder="little", signed=False)
# Read flag values.
if not flags & 0b00000001: return None # Check sample data bit.
# Read flag values
if not flags & 0b00000001: return None # Check sample data bit
loopBit = True if flags & 0b00010000 else False
smp = Sample()
# Read the rest of the header.
# Read the rest of the header
f.seek(ofs + 0x30)
smp.length = int.from_bytes(f.read(4), byteorder="little", signed=False)
if loopBit:
smp.loopBeg = int.from_bytes(f.read(4), byteorder="little", signed=False)
smp.loopEnd = int.from_bytes(f.read(4), byteorder="little", signed=False)
else:
f.seek(8, 1) # Skip over.
f.seek(8, 1) # Skip over
smp.loopBeg = 0
smp.loopEnd = 0
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)
smp.data = f.read(smp.length * 2)
# Compute hash of data.
# Compute hash of data
#FIXME: This actually generates a butt ton of collisions...
# there's got to be a better way!
h = hashlib.md5(struct.pack("<pII", smp.data, smp.loopBeg, smp.loopEnd))
@@ -147,15 +107,16 @@ def readsmp(f, ofs, idx):
return smp
def readit(path, outpath):
def readit(path: str, outpath: str):
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
#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)
ordNum = int.from_bytes(f.read(2), byteorder="little", signed=False)
insNum = int.from_bytes(f.read(2), byteorder="little", signed=False)
@@ -180,7 +141,8 @@ def readit(path, outpath):
pathlib.Path(outpath).mkdir(parents=True, exist_ok=True)
writesmp(smp, outwav)
def scanit(srcPath, dstPath):
def scanit(srcPath: str, dstPath: str):
for directory, subdirectories, files in os.walk(srcPath):
for file in files:
if file.endswith(".it"):
@@ -188,18 +150,19 @@ def scanit(srcPath, dstPath):
outpath = dstPath + path[len(srcPath):-len(file)]
readit(path, outpath)
def scanspc(srcPath, dstPath):
def scanspc(srcPath: str, dstPath: str):
for directory, subdirectories, files in os.walk(srcPath):
# Create output dir for each game.
# Create output dir for each game
for sub in subdirectories:
path = os.path.join(dstPath, sub)
pathlib.Path(path).mkdir(parents=True, exist_ok=True)
# Convert spc files.
# Convert spc files
for file in files:
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")
if not os.path.isfile(itpath):
path = os.path.join(directory, file)
@@ -209,6 +172,7 @@ def scanspc(srcPath, dstPath):
os.rename(path, itpath)
# Actual main stuff.
scanspc(SPCDIR, ITDIR)
scanit(ITDIR, SMPDIR)
# Actual main stuff
if __name__ == "__main__":
scanspc(SPCDIR, ITDIR)
scanit(ITDIR, SMPDIR)