mirror of
https://github.com/ScrelliCopter/VGM-Tools
synced 2025-02-21 04:09:25 +11:00
Compare commits
27 Commits
v0.2.0
...
7c9c2464cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c9c2464cb | |||
| 99bf061438 | |||
| ac89564669 | |||
| 9cdae38cbf | |||
| 5a059441df | |||
| a8cd2f0f36 | |||
| 695f6a1bf1 | |||
| 862639b4fe | |||
| 5cfb861369 | |||
| 6456404bd3 | |||
| 08b61568e1 | |||
| a76bb43ec1 | |||
| 2a654f25e8 | |||
| 47df3e2177 | |||
| 6510096f90 | |||
| 07ee0b546c | |||
| 454fcd3226 | |||
| c94016e793 | |||
| 172174c837 | |||
| 3fafdfd3f4 | |||
| 6ccf9f6e38 | |||
| b12258970e | |||
| 886966d7dc | |||
| 63aacb9a01 | |||
| 7c66bbab52 | |||
| fbf887b534 | |||
| 0a26bc8998 |
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.
|
||||||
21
dsptools/CMakeLists.txt
Normal file
21
dsptools/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
|
||||||
|
project(dsptools LANGUAGES C)
|
||||||
|
|
||||||
|
include(CheckIncludeFile)
|
||||||
|
check_include_file("endian.h" HAVE_ENDIAN_H)
|
||||||
|
if (HAVE_ENDIAN_H)
|
||||||
|
add_definitions(HAVE_ENDIAN_H)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(libdsptool)
|
||||||
|
|
||||||
|
set(HEADERS wave.h)
|
||||||
|
set(SOURCES wavefile.c wave.c dspdecode.c)
|
||||||
|
|
||||||
|
add_executable(dspdecode ${HEADERS} ${SOURCES})
|
||||||
|
|
||||||
|
set_property(TARGET dspdecode PROPERTY C_STANDARD 99)
|
||||||
|
target_link_libraries(dspdecode DspTool::DspTool)
|
||||||
|
target_compile_options(dspdecode PRIVATE
|
||||||
|
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic>
|
||||||
|
$<$<C_COMPILER_ID:MSVC>:/Wall>)
|
||||||
44
dsptools/common.h
Normal file
44
dsptools/common.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef COMMON_H
|
||||||
|
#define COMMON_H
|
||||||
|
|
||||||
|
#ifdef HAVE_ENDIAN_H
|
||||||
|
# include <endian.h>
|
||||||
|
# define BYTE_ORDER __BYTE_ORDER
|
||||||
|
# define LITTLE_ENDIAN __LITTLE_ENDIAN
|
||||||
|
# define BIG_ENDIAN __BIG_ENDIAN
|
||||||
|
#else
|
||||||
|
# define LITTLE_ENDIAN 1234
|
||||||
|
# define BIG_ENDIAN 4321
|
||||||
|
# if defined( __APPLE__ ) || defined( _WIN32 )
|
||||||
|
# define BYTE_ORDER LITTLE_ENDIAN
|
||||||
|
# else
|
||||||
|
# error Can't detect endianness
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline uint32_t swap32(uint32_t v)
|
||||||
|
{
|
||||||
|
return (v << 24) | ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00) | (v >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t swap16(uint16_t v)
|
||||||
|
{
|
||||||
|
return (v << 8) | (v >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||||
|
# define SWAP_LE32(V) (V)
|
||||||
|
# define SWAP_LE16(V) (V)
|
||||||
|
# define SWAP_BE32(V) swap32((uint32_t)V)
|
||||||
|
# define SWAP_BE16(V) swap16((uint16_t)V)
|
||||||
|
#elif BYTE_ORDER == BIG_ENDIAN
|
||||||
|
# define SWAP_LE32(V) swap32((uint32_t)V)
|
||||||
|
# define SWAP_LE16(V) swap16((uint16_t)V)
|
||||||
|
# define SWAP_BE32(V) (V)
|
||||||
|
# define SWAP_BE16(V) (V)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MIN(A, B) (((A) < (B)) ? (A) : (B))
|
||||||
|
#define MAX(A, B) (((A) > (B)) ? (A) : (B))
|
||||||
|
|
||||||
|
#endif//COMMON_H
|
||||||
258
dsptools/dspdecode.c
Normal file
258
dsptools/dspdecode.c
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
/* dspdecode.c (c) 2023 a dinosaur (zlib) */
|
||||||
|
|
||||||
|
#include "dsptool.h"
|
||||||
|
#include "wave.h"
|
||||||
|
#include "common.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]);
|
||||||
|
|
||||||
|
// C
|
||||||
|
if (!outPath)
|
||||||
|
{
|
||||||
|
const char* base = strrchr(inPathL, '/');
|
||||||
|
#ifdef _WIN32
|
||||||
|
const char* base2 = strrchr(inPathL, '\\');
|
||||||
|
if (base2 && base2 > base)
|
||||||
|
base = base2;
|
||||||
|
#endif
|
||||||
|
if (!base)
|
||||||
|
base = inPathL;
|
||||||
|
char* ext = strrchr(base, '.');
|
||||||
|
size_t nameLen = strlen(inPathL);
|
||||||
|
if (ext)
|
||||||
|
nameLen -= strlen(ext);
|
||||||
|
outPath = malloc(nameLen + 5);
|
||||||
|
if (!outPath)
|
||||||
|
return 2;
|
||||||
|
memcpy(outPath, inPathL, nameLen);
|
||||||
|
memcpy(outPath + nameLen, ".wav", 5);
|
||||||
|
outPathAlloc = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
PcmFile left = PCMFILE_CLEAR(), right = PCMFILE_CLEAR();
|
||||||
|
if ((ret = loadDsp(inPathL, &left)))
|
||||||
|
goto Cleanup;
|
||||||
|
if (inPathR)
|
||||||
|
{
|
||||||
|
if ((ret = loadDsp(inPathR, &right)))
|
||||||
|
goto Cleanup;
|
||||||
|
if (left.pcmSize != right.pcmSize || left.rate != right.rate)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Rate or length mismatch");
|
||||||
|
ret = 1;
|
||||||
|
goto Cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveSpec wav =
|
||||||
|
{
|
||||||
|
.format = WAVE_FMT_PCM,
|
||||||
|
.channels = 2,
|
||||||
|
.rate = left.rate,
|
||||||
|
.bytedepth = sizeof(int16_t)
|
||||||
|
};
|
||||||
|
if ((ret = waveWriteBlockFile(&wav, (const void*[2]){left.pcm, right.pcm}, left.pcmSize, outPath)))
|
||||||
|
goto Cleanup;
|
||||||
|
|
||||||
|
free(right.pcm);
|
||||||
|
right = PCMFILE_CLEAR();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WaveSpec wav =
|
||||||
|
{
|
||||||
|
.format = WAVE_FMT_PCM,
|
||||||
|
.channels = 1,
|
||||||
|
.rate = left.rate,
|
||||||
|
.bytedepth = sizeof(int16_t)
|
||||||
|
};
|
||||||
|
if ((ret = waveWriteFile(&wav, left.pcm, left.pcmSize, outPath)))
|
||||||
|
goto Cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(left.pcm);
|
||||||
|
left = PCMFILE_CLEAR();
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
Cleanup:
|
||||||
|
free(right.pcm);
|
||||||
|
free(left.pcm);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
19
dsptools/libdsptool/CMakeLists.txt
Normal file
19
dsptools/libdsptool/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
|
||||||
|
project(DspTool LANGUAGES C)
|
||||||
|
|
||||||
|
option(BUILD_SHARED_LIBS "Build as a Shared Object or DLL" OFF)
|
||||||
|
|
||||||
|
set(HEADERS ../common.h dsptool.h)
|
||||||
|
set(SOURCES math.c decode.c encode.c)
|
||||||
|
|
||||||
|
add_library(DspTool ${HEADERS} ${SOURCES})
|
||||||
|
add_library(DspTool::DspTool ALIAS DspTool)
|
||||||
|
|
||||||
|
target_include_directories(DspTool PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
set_property(TARGET DspTool PROPERTY C_STANDARD 99)
|
||||||
|
if (BUILD_SHARED_LIBS)
|
||||||
|
target_compile_definitions(DspTool PRIVATE BUILD_SHARED)
|
||||||
|
endif()
|
||||||
|
target_compile_options(DspTool PRIVATE
|
||||||
|
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic -Wno-unused-parameter>
|
||||||
|
$<$<C_COMPILER_ID:MSVC>:/Wall /wd4100>)
|
||||||
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 "../common.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( WHY_ARE_YOU_USING_THIS_AS_A_DLL )
|
||||||
|
# define DLLEXPORT __declspec(dllimport)
|
||||||
|
# else
|
||||||
|
# define DLLEXPORT
|
||||||
|
# endif
|
||||||
|
#elif __GNUC__ >= 4
|
||||||
|
# define DLLEXPORT __attribute__((visibility("default")))
|
||||||
|
#else
|
||||||
|
# define DLLEXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int16_t coef[16];
|
||||||
|
uint16_t gain;
|
||||||
|
uint16_t pred_scale;
|
||||||
|
int16_t yn1;
|
||||||
|
int16_t yn2;
|
||||||
|
|
||||||
|
uint16_t loop_pred_scale;
|
||||||
|
int16_t loop_yn1;
|
||||||
|
int16_t loop_yn2;
|
||||||
|
} ADPCMINFO;
|
||||||
|
|
||||||
|
DLLEXPORT void encode(int16_t* src, uint8_t* dst, ADPCMINFO* cxt, uint32_t samples);
|
||||||
|
DLLEXPORT void decode(uint8_t* src, int16_t* dst, ADPCMINFO* cxt, uint32_t samples);
|
||||||
|
DLLEXPORT void getLoopContext(uint8_t* src, ADPCMINFO* cxt, uint32_t samples);
|
||||||
|
|
||||||
|
DLLEXPORT void encodeFrame(int16_t* src, uint8_t* dst, int16_t* coefs, uint8_t one);
|
||||||
|
DLLEXPORT void correlateCoefs(int16_t* src, uint32_t samples, int16_t* coefsOut);
|
||||||
|
|
||||||
|
DLLEXPORT uint32_t getBytesForAdpcmBuffer(uint32_t samples);
|
||||||
|
DLLEXPORT uint32_t getBytesForAdpcmSamples(uint32_t samples);
|
||||||
|
DLLEXPORT uint32_t getBytesForPcmBuffer(uint32_t samples);
|
||||||
|
DLLEXPORT uint32_t getBytesForPcmSamples(uint32_t samples);
|
||||||
|
DLLEXPORT uint32_t getNibbleAddress(uint32_t samples);
|
||||||
|
DLLEXPORT uint32_t getNibblesForNSamples(uint32_t samples);
|
||||||
|
DLLEXPORT uint32_t getSampleForAdpcmNibble(uint32_t nibble);
|
||||||
|
DLLEXPORT uint32_t getBytesForAdpcmInfo(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
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 "../common.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;
|
||||||
|
}
|
||||||
149
dsptools/wave.c
Normal file
149
dsptools/wave.c
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/* wave.c (c) 2023 a dinosaur (zlib) */
|
||||||
|
|
||||||
|
#include "wave.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#define FOURCC_RIFF "RIFF"
|
||||||
|
#define FOURCC_WAVE "WAVE"
|
||||||
|
#define FOURCC_FORM "fmt "
|
||||||
|
#define FOURCC_SAMP "smpl"
|
||||||
|
#define FOURCC_DATA "data"
|
||||||
|
|
||||||
|
#define FORMAT_CHUNK_SIZE 16
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint16_t format;
|
||||||
|
uint16_t channels;
|
||||||
|
uint32_t samplerate;
|
||||||
|
uint32_t byterate;
|
||||||
|
uint16_t alignment;
|
||||||
|
uint16_t bitdepth;
|
||||||
|
|
||||||
|
} FormatChunk;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t loopstart;
|
||||||
|
uint32_t loopend;
|
||||||
|
uint32_t fraction;
|
||||||
|
uint32_t playcount;
|
||||||
|
|
||||||
|
} SampleLoopChunk;
|
||||||
|
|
||||||
|
#define SMPL_CHUNK_HEAD_SIZE 24
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t manufacturer;
|
||||||
|
uint32_t product;
|
||||||
|
uint32_t samplePeriod;
|
||||||
|
uint32_t midiUniNote;
|
||||||
|
uint32_t midiPitchFrac;
|
||||||
|
uint32_t smpteFormat;
|
||||||
|
uint32_t smpteOffset;
|
||||||
|
uint32_t sampleLoopCount;
|
||||||
|
uint32_t sampleData;
|
||||||
|
|
||||||
|
} SamplerChunk;
|
||||||
|
|
||||||
|
static void writeFourcc(const WaveStreamCb* restrict cb, void* restrict user, const char fourcc[4])
|
||||||
|
{
|
||||||
|
cb->write(user, fourcc, 1, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeU32le(const WaveStreamCb* restrict cb, void* restrict user, uint32_t v)
|
||||||
|
{
|
||||||
|
uint32_t tmp = SWAP_LE32(v);
|
||||||
|
cb->write(user, &tmp, sizeof(uint32_t), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeU16le(const WaveStreamCb* restrict cb, void* restrict user, uint16_t v)
|
||||||
|
{
|
||||||
|
uint16_t tmp = SWAP_LE16(v);
|
||||||
|
cb->write(user, &tmp, sizeof(uint16_t), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeRiffChunk(const WaveStreamCb* restrict cb, void* restrict user, char fourcc[4], uint32_t size)
|
||||||
|
{
|
||||||
|
writeFourcc(cb, user, fourcc);
|
||||||
|
writeU32le(cb, user, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeFormatChunk(const WaveStreamCb* restrict cb, void* restrict user, const FormatChunk* fmt)
|
||||||
|
{
|
||||||
|
writeRiffChunk(cb, user, FOURCC_FORM, FORMAT_CHUNK_SIZE);
|
||||||
|
writeU16le(cb, user, fmt->format);
|
||||||
|
writeU16le(cb, user, fmt->channels);
|
||||||
|
writeU32le(cb, user, fmt->samplerate);
|
||||||
|
writeU32le(cb, user, fmt->byterate);
|
||||||
|
writeU16le(cb, user, fmt->alignment);
|
||||||
|
writeU16le(cb, user, fmt->bitdepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int waveWriteHeader(const WaveSpec* spec, size_t dataLen, const WaveStreamCb* cb, void* user)
|
||||||
|
{
|
||||||
|
if (!spec || !dataLen || dataLen >= UINT32_MAX || !cb || !cb->write)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (spec->format != WAVE_FMT_PCM)
|
||||||
|
return 1;
|
||||||
|
if (spec->channels <= 0 || spec->channels >= INT16_MAX)
|
||||||
|
return 1;
|
||||||
|
if (spec->format == WAVE_FMT_PCM && (spec->bytedepth <= 0 || spec->bytedepth > 4))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// write riff container
|
||||||
|
writeRiffChunk(cb, user, FOURCC_RIFF, sizeof(uint32_t) * 5 + FORMAT_CHUNK_SIZE + (uint32_t)dataLen);
|
||||||
|
writeFourcc(cb, user, FOURCC_WAVE);
|
||||||
|
|
||||||
|
writeFormatChunk(cb, user, &(const FormatChunk)
|
||||||
|
{
|
||||||
|
.format = spec->format,
|
||||||
|
.channels = (uint16_t)spec->channels,
|
||||||
|
.samplerate = spec->rate,
|
||||||
|
.byterate = spec->rate * spec->channels * spec->bytedepth,
|
||||||
|
.alignment = (uint16_t)(spec->channels * spec->bytedepth),
|
||||||
|
.bitdepth = (uint16_t)spec->bytedepth * 8
|
||||||
|
});
|
||||||
|
|
||||||
|
// write data chunk
|
||||||
|
writeFourcc(cb, user, FOURCC_DATA);
|
||||||
|
writeU32le(cb, user, (uint32_t)dataLen);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int waveWrite(const WaveSpec* spec, const void* data, size_t dataLen, const WaveStreamCb* cb, void* user)
|
||||||
|
{
|
||||||
|
if (!data)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Write RIFF/Wave header and raw interleaved samples
|
||||||
|
int res = waveWriteHeader(spec, dataLen, cb, user);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
//FIXME: not endian safe
|
||||||
|
cb->write(user, data, 1, dataLen);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int waveWriteBlock(const WaveSpec* spec, const void* blocks[], size_t blockLen, const WaveStreamCb* cb, void* user)
|
||||||
|
{
|
||||||
|
if (!blocks)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Write RIFF/Wave header to file
|
||||||
|
int res = waveWriteHeader(spec, blockLen * spec->channels, cb, user);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Copy & interleave
|
||||||
|
//FIXME: not endian safe
|
||||||
|
for (size_t i = 0; i < blockLen / spec->bytedepth; ++i)
|
||||||
|
for (int j = 0; j < spec->channels; ++j)
|
||||||
|
cb->write(user, &((const char**)blocks)[j][i * spec->bytedepth], spec->bytedepth, 1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
45
dsptools/wave.h
Normal file
45
dsptools/wave.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef WAVE_H
|
||||||
|
#define WAVE_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.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;
|
||||||
|
|
||||||
|
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
|
||||||
57
dsptools/wavefile.c
Normal file
57
dsptools/wavefile.c
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int waveWriteFile(const WaveSpec* spec, const void* data, size_t dataLen, const char* path)
|
||||||
|
{
|
||||||
|
FILE* file = fopen(path, "wb");
|
||||||
|
if (!file)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
const WaveStreamCb cb = { .write = waveFileWrite };
|
||||||
|
int res = waveWrite(spec, data, dataLen, &cb, (void*)file);
|
||||||
|
fclose(file);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int waveWriteBlockFile(const WaveSpec* spec, const void* blocks[], size_t blockLen, const char* path)
|
||||||
|
{
|
||||||
|
FILE* file = fopen(path, "wb");
|
||||||
|
if (!file)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
const WaveStreamCb cb = { .write = waveFileWrite };
|
||||||
|
int res = waveWriteBlock(spec, blocks, blockLen, &cb, (void*)file);
|
||||||
|
fclose(file);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
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()
|
||||||
7
neotools/.gitignore
vendored
7
neotools/.gitignore
vendored
@@ -5,7 +5,6 @@
|
|||||||
*.pcm
|
*.pcm
|
||||||
*.wav
|
*.wav
|
||||||
|
|
||||||
*.o
|
.idea/
|
||||||
adpcm
|
build/
|
||||||
adpcmb
|
cmake-build-*/
|
||||||
neoadpcmextract
|
|
||||||
|
|||||||
18
neotools/CMakeLists.txt
Normal file
18
neotools/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
project(neoadpcmtools)
|
||||||
|
cmake_minimum_required(VERSION "3.1" FATAL_ERROR)
|
||||||
|
|
||||||
|
add_executable(adpcm adpcm.c)
|
||||||
|
target_compile_options(adpcm PRIVATE
|
||||||
|
-fomit-frame-pointer -Werror -Wall
|
||||||
|
-W -Wno-sign-compare -Wno-unused
|
||||||
|
-Wpointer-arith -Wbad-function-cast -Wcast-align -Waggregate-return
|
||||||
|
-pedantic
|
||||||
|
-Wshadow
|
||||||
|
-Wstrict-prototypes)
|
||||||
|
target_link_libraries(adpcm m)
|
||||||
|
|
||||||
|
add_executable(adpcmb adpcmb.c)
|
||||||
|
|
||||||
|
add_executable(neoadpcmextract autoextract.c neoadpcmextract.c)
|
||||||
|
set_property(TARGET neoadpcmextract PROPERTY C_STANDARD 99)
|
||||||
|
target_compile_options(neoadpcmextract PRIVATE -Wall -Wextra -pedantic)
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
63
neotools/autoextract.c
Normal file
63
neotools/autoextract.c
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "neoadpcmextract.h"
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc != 2)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Open file.
|
||||||
|
FILE* file = fopen(argv[1], "rb");
|
||||||
|
if (!file)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Error on VGZ's for now.
|
||||||
|
if (fgetc(file) == 0x1F && fgetc(file) == 0x8B)
|
||||||
|
{
|
||||||
|
printf("I'm a little gzip short and stout\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
Buffer smpbuf = {NULL, 0, 0};
|
||||||
|
char name[32];
|
||||||
|
int smpaCount = 0, smpbCount = 0;
|
||||||
|
|
||||||
|
// Find ADCPM samples.
|
||||||
|
int scanType;
|
||||||
|
while ((scanType = vgmScanSample(file)))
|
||||||
|
{
|
||||||
|
if (scanType != 'A' && scanType != 'B')
|
||||||
|
continue;
|
||||||
|
fprintf(stderr, "ADPCM-%c data found at 0x%08lX\n", scanType, ftell(file));
|
||||||
|
|
||||||
|
if (vgmReadSample(file, &smpbuf) || smpbuf.size == 0)
|
||||||
|
continue;
|
||||||
|
if (scanType == 'A')
|
||||||
|
{
|
||||||
|
// decode
|
||||||
|
snprintf(name, sizeof(name), "smpa_%02x.pcm", smpaCount++);
|
||||||
|
printf("./adpcm \"%s\" \"$WAVDIR/%s.wav\"\n", name, name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// decode
|
||||||
|
snprintf(name, sizeof(name), "smpb_%02x.pcm", smpbCount++);
|
||||||
|
printf("./adpcmb -d \"%s\" \"$WAVDIR/%s.wav\"\n", name, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write adpcm sample.
|
||||||
|
FILE* fout = fopen(name, "wb");
|
||||||
|
if (!fout)
|
||||||
|
continue;
|
||||||
|
fwrite(smpbuf.data, sizeof(uint8_t), smpbuf.size, fout);
|
||||||
|
fclose(fout);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(smpbuf.data);
|
||||||
|
fclose(file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -1,19 +1,12 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
FILE="$1"
|
FILE="$1"
|
||||||
NAME="$(basename "$FILE")"
|
NAME="$(basename "$FILE")"
|
||||||
WAVDIR="${NAME%%.*}"
|
WAVDIR="${NAME%%.*}"
|
||||||
|
|
||||||
if [ "${NAME##*.}" = "vgz" ]; then
|
|
||||||
cp "$FILE" "temp.vgm.gz"
|
|
||||||
gzip -d "temp.vgm.gz"
|
|
||||||
FILE="temp.vgm"
|
|
||||||
fi
|
|
||||||
|
|
||||||
./neoadpcmextract "$FILE"
|
./neoadpcmextract "$FILE"
|
||||||
mkdir -p "$WAVDIR"
|
mkdir -p "$WAVDIR"
|
||||||
for I in smpa_*.pcm; do ./adpcm "$I" "$WAVDIR/${I%%.*}.wav"; done
|
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
|
for I in smpb_*.pcm; do ./adpcmb -d "$I" "$WAVDIR/${I%%.*}.wav"; done
|
||||||
find . -type f -name "*.pcm" -exec rm -f {} \;
|
find . -type f -name "*.pcm" -exec rm -f {} \;
|
||||||
|
|
||||||
[ "$FILE" = "temp.vgm" ] && rm -f "temp.vgm"
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
TARGET := neoadpcmextract
|
|
||||||
SOURCE := neoadpcmextract.cpp
|
|
||||||
CXXFLAGS := -O2 -pipe -Wall -Wextra
|
|
||||||
|
|
||||||
all: $(TARGET)
|
|
||||||
|
|
||||||
$(TARGET): $(SOURCE)
|
|
||||||
$(CXX) $(CXXFLAGS) $< -o $@
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean:
|
|
||||||
rm -f $(TARGET)
|
|
||||||
57
neotools/neoadpcmextract.c
Normal file
57
neotools/neoadpcmextract.c
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/* neoadpcmextract.c (C) 2017, 2019, 2020 a dinosaur (zlib) */
|
||||||
|
|
||||||
|
#include "neoadpcmextract.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
|
int vgmReadSample(FILE* fin, Buffer* buf)
|
||||||
|
{
|
||||||
|
// Get sample data length.
|
||||||
|
uint32_t sampLen = 0;
|
||||||
|
fread(&sampLen, sizeof(uint32_t), 1, fin);
|
||||||
|
if (sampLen < sizeof(uint64_t))
|
||||||
|
return 1;
|
||||||
|
sampLen -= sizeof(uint64_t);
|
||||||
|
|
||||||
|
// Resize buffer if needed.
|
||||||
|
buf->size = sampLen;
|
||||||
|
if (!buf->data || buf->reserved < sampLen)
|
||||||
|
{
|
||||||
|
free(buf->data);
|
||||||
|
buf->reserved = sampLen;
|
||||||
|
buf->data = malloc(sampLen);
|
||||||
|
if (!buf->data)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore 8 bytes.
|
||||||
|
uint64_t dummy;
|
||||||
|
fread(&dummy, sizeof(uint64_t), 1, fin);
|
||||||
|
|
||||||
|
// Read adpcm data.
|
||||||
|
fread(buf->data, sizeof(uint8_t), sampLen, fin);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vgmScanSample(FILE* file)
|
||||||
|
{
|
||||||
|
// Scan for pcm headers.
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
if (feof(file) || ferror(file))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Patterns to match (in hex):
|
||||||
|
// 67 66 82 - ADPCM-A
|
||||||
|
// 67 66 83 - ADPCM-B
|
||||||
|
if (fgetc(file) != 0x67 || fgetc(file) != 0x66)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint8_t byte = fgetc(file);
|
||||||
|
if (byte == 0x82)
|
||||||
|
return 'A';
|
||||||
|
else if (byte == 0x83)
|
||||||
|
return 'B';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/* neoadpcmextract.cpp
|
|
||||||
Copyright (C) 2017 Nicholas Curtis
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
void DecodeSample ( std::ifstream& a_file, std::vector<uint8_t>& a_out )
|
|
||||||
{
|
|
||||||
// Set up output vector.
|
|
||||||
uint32_t sampLen = 0;
|
|
||||||
a_file.read ( (char*)&sampLen, sizeof(uint32_t) );
|
|
||||||
if ( sampLen < sizeof(uint64_t) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sampLen -= sizeof(uint64_t);
|
|
||||||
a_out.clear ();
|
|
||||||
a_out.resize ( sampLen );
|
|
||||||
|
|
||||||
// Ignore 8 bytes.
|
|
||||||
uint64_t dummy;
|
|
||||||
a_file.read ( (char*)&dummy, sizeof(uint64_t) );
|
|
||||||
|
|
||||||
// Read adpcm data.
|
|
||||||
a_file.read ( (char*)a_out.data (), sampLen );
|
|
||||||
}
|
|
||||||
|
|
||||||
void DumpBytes ( std::string a_path, const std::vector<uint8_t>& a_bytes )
|
|
||||||
{
|
|
||||||
std::ofstream fileOut ( a_path, std::ios::binary );
|
|
||||||
fileOut.write ( (const char*)a_bytes.data (), a_bytes.size () );
|
|
||||||
fileOut.close ();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main ( int argc, char** argv )
|
|
||||||
{
|
|
||||||
if ( argc != 2 )
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open file.
|
|
||||||
std::ifstream file ( argv[1], std::ios::binary );
|
|
||||||
if ( !file.is_open () )
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for pcm headers.
|
|
||||||
std::vector<uint8_t> smpBytes;
|
|
||||||
int smpA = 0, smpB = 0;
|
|
||||||
while ( !file.eof () && !file.fail () )
|
|
||||||
{
|
|
||||||
uint8_t byte;
|
|
||||||
|
|
||||||
file >> byte;
|
|
||||||
if ( byte == 0x67 )
|
|
||||||
{
|
|
||||||
file >> byte;
|
|
||||||
if ( byte == 0x66 )
|
|
||||||
{
|
|
||||||
file >> byte;
|
|
||||||
if ( byte == 0x82 )
|
|
||||||
{
|
|
||||||
std::cout << "ADPCM-A data found at 0x" << std::hex << file.tellg () << std::endl;
|
|
||||||
DecodeSample ( file, smpBytes );
|
|
||||||
std::stringstream path;
|
|
||||||
path << std::hex << "smpa_" << (smpA++) << ".pcm";
|
|
||||||
DumpBytes ( path.str (), smpBytes );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if ( byte == 0x83 )
|
|
||||||
{
|
|
||||||
std::cout << "ADPCM-B data found at 0x" << std::hex << file.tellg () << std::endl;
|
|
||||||
DecodeSample ( file, smpBytes );
|
|
||||||
std::stringstream path;
|
|
||||||
path << std::hex << "smpb_" << (smpB++) << ".pcm";
|
|
||||||
DumpBytes ( path.str (), smpBytes );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close ();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
12
neotools/neoadpcmextract.h
Normal file
12
neotools/neoadpcmextract.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef __NEOADPCMEXTRACT_H__
|
||||||
|
#define __NEOADPCMEXTRACT_H__
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct { uint8_t* data; size_t size, reserved; } Buffer;
|
||||||
|
|
||||||
|
int vgmReadSample(FILE* fin, Buffer* buf);
|
||||||
|
int vgmScanSample(FILE* file);
|
||||||
|
|
||||||
|
#endif//__NEOADPCMEXTRACT_H__
|
||||||
@@ -1,26 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
''' ripsamples.py -- a python script for mass extracting samples from SPC files.
|
# ripsamples.py -- a python script for mass extracting samples from SPC files.
|
||||||
|
# (C) 2018 neoadpcmextract.c (C) 2018 a dinosaur (zlib)
|
||||||
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.
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|||||||
Reference in New Issue
Block a user