mirror of
https://github.com/ScrelliCopter/VGM-Tools
synced 2025-02-21 04:09:25 +11:00
Compare commits
42 Commits
b12258970e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| effcf727ac | |||
| 78790991b6 | |||
| 47f6b23943 | |||
| 5b28c18472 | |||
| 70bbf3d0d1 | |||
| 9f6c0664ff | |||
| 4013d1809c | |||
| 802bdef961 | |||
| ae14868953 | |||
| 46c78c24e1 | |||
| ff41b5415e | |||
| 7764375ec9 | |||
| e334ad82cc | |||
| 353d4e5def | |||
| 9c5e19264b | |||
| 111f800c49 | |||
| dbce8e5c29 | |||
| c1f36bd322 | |||
| 9946144995 | |||
| 05008f5c47 | |||
| f70bcd0a50 | |||
| 7c9c2464cb | |||
| 99bf061438 | |||
| ac89564669 | |||
| 9cdae38cbf | |||
| 5a059441df | |||
| a8cd2f0f36 | |||
| 695f6a1bf1 | |||
| 862639b4fe | |||
| 5cfb861369 | |||
| 6456404bd3 | |||
| 08b61568e1 | |||
| a76bb43ec1 | |||
| 2a654f25e8 | |||
| 47df3e2177 | |||
| 6510096f90 | |||
| 07ee0b546c | |||
| 454fcd3226 | |||
| c94016e793 | |||
| 172174c837 | |||
| 3fafdfd3f4 | |||
| 6ccf9f6e38 |
22
.editorconfig
Normal file
22
.editorconfig
Normal 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
18
.gitignore
vendored
@@ -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
10
CMakeLists.txt
Normal 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
15
COPYING.zlib
Normal 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
11
README.md
Normal 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
8
common/CMakeLists.txt
Normal 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
68
common/endian.h
Normal 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
40
common/riffwriter.py
Normal 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
16
common/util.h
Normal 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
147
common/wave.c
Normal 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
48
common/wave.h
Normal 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
64
common/wavefile.c
Normal 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
110
common/wavesampler.py
Normal 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
18
common/waveserum.py
Normal 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
98
common/wavewriter.py
Normal 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
7
dsptools/CMakeLists.txt
Normal 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
261
dsptools/dspdecode.c
Normal 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;
|
||||
}
|
||||
18
dsptools/libdsptool/CMakeLists.txt
Normal file
18
dsptools/libdsptool/CMakeLists.txt
Normal 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)
|
||||
21
dsptools/libdsptool/LICENSE
Normal file
21
dsptools/libdsptool/LICENSE
Normal 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.
|
||||
101
dsptools/libdsptool/decode.c
Normal file
101
dsptools/libdsptool/decode.c
Normal 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;
|
||||
}
|
||||
61
dsptools/libdsptool/dsptool.h
Normal file
61
dsptools/libdsptool/dsptool.h
Normal 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
|
||||
558
dsptools/libdsptool/encode.c
Normal file
558
dsptools/libdsptool/encode.c
Normal 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]);
|
||||
}
|
||||
69
dsptools/libdsptool/math.c
Normal file
69
dsptools/libdsptool/math.c
Normal 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
152
feropm.py
Executable 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
11
neotools/.gitignore
vendored
@@ -1,11 +0,0 @@
|
||||
*.exe
|
||||
*.vgm
|
||||
*.vgz
|
||||
*.log
|
||||
*.pcm
|
||||
*.wav
|
||||
|
||||
*.o
|
||||
adpcm
|
||||
adpcmb
|
||||
neoadpcmextract
|
||||
24
neotools/CMakeLists.txt
Normal file
24
neotools/CMakeLists.txt
Normal 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)
|
||||
@@ -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)
|
||||
193
neotools/adpcm.c
193
neotools/adpcm.c
@@ -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[])
|
||||
{
|
||||
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
14
neotools/adpcm.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
FILE* hFile = fopen(inPath, "rb");
|
||||
if (hFile == NULL)
|
||||
{
|
||||
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);
|
||||
|
||||
static int YM2610_ADPCM_Encode(INT16 *src, UINT8 *dest, int len)
|
||||
WAVEFORMAT* TempFmt = &WaveFile.fmt_Data;
|
||||
if (TempFmt->wFormatTag != WAVE_FMT_PCM)
|
||||
{
|
||||
int lpc, flag;
|
||||
long i, dn, xn, stepSize;
|
||||
UINT8 adpcm;
|
||||
UINT8 adpcmPack;
|
||||
|
||||
xn = 0;
|
||||
stepSize = 127;
|
||||
flag = 0;
|
||||
|
||||
for (lpc = 0; lpc < len; lpc ++)
|
||||
{
|
||||
dn = *src - xn;
|
||||
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;
|
||||
fclose(hFile);
|
||||
printf("Error in wave file: Compressed wave file are not supported!\n");
|
||||
return 4;
|
||||
}
|
||||
else
|
||||
if (TempFmt->nChannels != 1)
|
||||
{
|
||||
xn += i;
|
||||
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;
|
||||
}
|
||||
|
||||
stepSize = (stepsizeTable[adpcm] * stepSize) / 64;
|
||||
|
||||
if (stepSize < 127)
|
||||
stepSize = 127;
|
||||
else if (stepSize > 24576)
|
||||
stepSize = 24576;
|
||||
|
||||
if (flag == 0)
|
||||
TempLng = fread(&WaveFile.datafcc, 0x04, 0x01, hFile);
|
||||
fread(&WaveFile.dataLen, 0x04, 0x01, hFile);
|
||||
while (memcmp(WaveFile.datafcc, "data", 4) != 0)
|
||||
{
|
||||
adpcmPack = (adpcm << 4);
|
||||
flag = 1;
|
||||
}
|
||||
else
|
||||
if (!TempLng) // TempLng == 0 -> EOF reached
|
||||
{
|
||||
adpcmPack |= adpcm;
|
||||
*dest = adpcmPack;
|
||||
dest ++;
|
||||
flag = 0;
|
||||
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,12 +235,10 @@ 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])
|
||||
@@ -258,7 +247,7 @@ int main(int argc, char* argv[])
|
||||
OutSmplRate = strtol(argv[2] + 3, NULL, 0);
|
||||
break;
|
||||
case 'r':
|
||||
DTRegs = (UINT16)strtoul(argv[2] + 3, &TempPnt, 0);
|
||||
DTRegs = (uint16_t)strtoul(argv[2] + 3, &TempPnt, 0);
|
||||
TempLng = 0;
|
||||
if (*TempPnt == ',')
|
||||
{
|
||||
@@ -269,8 +258,7 @@ int main(int argc, char* argv[])
|
||||
OutSmplRate = DeltaTReg2SampleRate(DTRegs, TempLng);
|
||||
break;
|
||||
}
|
||||
ArgBase ++;
|
||||
if (argc < ArgBase + 2)
|
||||
if (argc < ++ArgBase + 2)
|
||||
{
|
||||
printf("Not enought arguments!\n");
|
||||
return 1;
|
||||
@@ -280,166 +268,12 @@ int main(int argc, char* argv[])
|
||||
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
25
neotools/adpcmb.h
Normal 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
|
||||
@@ -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"
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
FILE="$1"
|
||||
NAME="$(basename "$FILE")"
|
||||
WAVDIR="${NAME%%.*}"
|
||||
|
||||
if [ "${NAME##*.}" = "vgz" ]; then
|
||||
cp "$FILE" "temp.vgm.gz"
|
||||
gzip -fd "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
57
neotools/libadpcma.c
Normal 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
92
neotools/libadpcmb.c
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
TARGET := neoadpcmextract
|
||||
SOURCE := neoadpcmextract.cpp
|
||||
CXXFLAGS := -O2 -pipe -Wall -Wextra -pedantic
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SOURCE)
|
||||
$(CXX) $(CXXFLAGS) $< -o $@
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
184
neotools/neoadpcmextract.c
Normal file
184
neotools/neoadpcmextract.c
Normal 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;
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/* neoadpcmextract.cpp
|
||||
Copyright (C) 2017, 2019 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 <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
void DecodeSample(FILE* fin, const std::string& name, std::vector <uint8_t>& buf)
|
||||
{
|
||||
// Set up output vector.
|
||||
uint32_t sampLen = 0;
|
||||
fread(&sampLen, sizeof(uint32_t), 1, fin);
|
||||
if (sampLen < sizeof(uint64_t))
|
||||
return;
|
||||
|
||||
sampLen -= sizeof(uint64_t);
|
||||
buf.clear();
|
||||
buf.resize(sampLen);
|
||||
|
||||
// Ignore 8 bytes.
|
||||
uint64_t dummy;
|
||||
fread(&dummy, sizeof(uint64_t), 1, fin);
|
||||
|
||||
// Read adpcm data.
|
||||
fread(buf.data(), sizeof(uint8_t), sampLen, fin);
|
||||
|
||||
FILE* fout = fopen(name.c_str(), "wb");
|
||||
if (!fout)
|
||||
return;
|
||||
|
||||
fwrite(buf.data(), sizeof(uint8_t), buf.size(), fout);
|
||||
fclose(fout);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 2)
|
||||
return 1;
|
||||
|
||||
// Open file.
|
||||
FILE* file = fopen(argv[1], "rb");
|
||||
if (!file)
|
||||
return 1;
|
||||
|
||||
// Search for pcm headers.
|
||||
std::vector<uint8_t> smpBytes;
|
||||
int smpaCount = 0, smpbCount = 0;
|
||||
while (!feof(file) && !ferror(file))
|
||||
{
|
||||
if (fgetc(file) != 0x67 || fgetc(file) != 0x66)
|
||||
continue;
|
||||
|
||||
uint8_t byte = fgetc(file);
|
||||
if (byte == 0x82)
|
||||
{
|
||||
printf("ADPCM-A data found at 0x%08lX\n", ftell(file));
|
||||
std::stringstream path;
|
||||
path << std::hex << "smpa_" << (smpaCount++) << ".pcm";
|
||||
DecodeSample(file, path.str(), smpBytes);
|
||||
}
|
||||
else if (byte == 0x83)
|
||||
{
|
||||
printf("ADPCM-B data found at 0x%08lX\n", ftell(file));
|
||||
std::stringstream path;
|
||||
path << std::hex << "smpb_" << (smpbCount++) << ".pcm";
|
||||
DecodeSample(file, path.str(), smpBytes);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
39
neotools/neoadpcmextract.h
Normal file
39
neotools/neoadpcmextract.h
Normal 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
78
sinharmonicswt.py
Normal 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()
|
||||
@@ -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.
|
||||
# Actual main stuff
|
||||
if __name__ == "__main__":
|
||||
scanspc(SPCDIR, ITDIR)
|
||||
scanit(ITDIR, SMPDIR)
|
||||
|
||||
Reference in New Issue
Block a user