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

Compare commits

21 Commits

Author SHA1 Message Date
695f6a1bf1 Fix oversight with the source path ending up the patch name & use output name when specified 2020-10-09 11:48:12 +11:00
862639b4fe Add crummy OPN/OPM preset conversion tool shat out in a sleep deprived stupor 2020-10-09 11:28:59 +11:00
5cfb861369 Refactor most of the opening and scanning between sources 2020-07-08 21:28:04 +10:00
6456404bd3 neotools: fuck it, we CMake now 2020-05-26 10:29:57 +10:00
08b61568e1 add stub for loading vgz directly 2019-10-03 00:44:27 +10:00
a76bb43ec1 makefile builds objects into a build artifacts directory
also forgot to commit the header
2019-10-02 12:37:08 +10:00
2a654f25e8 move main to a new source file, update makefile to support multiple sources 2019-10-02 12:26:12 +10:00
47df3e2177 add some basic error checking in DecodeSample 2019-10-02 10:59:07 +10:00
6510096f90 malloc cast is unnecessary in C 2019-10-01 23:50:01 +10:00
07ee0b546c rename and build as C 2019-10-01 23:46:52 +10:00
454fcd3226 use c include for stdint 2019-10-01 23:40:22 +10:00
c94016e793 use a lightweight buffer class for in-memory samples 2019-10-01 23:37:55 +10:00
172174c837 import stdio instead of string 2019-10-01 23:26:19 +10:00
3fafdfd3f4 use c string for out names 2019-10-01 23:25:13 +10:00
6ccf9f6e38 snprintf for output names 2019-10-01 23:23:38 +10:00
b12258970e replace cout with printf 2019-09-30 23:17:47 +10:00
886966d7dc merge DecodeSample & DumpBytes 2019-09-30 20:43:07 +10:00
63aacb9a01 FILE io for DumpBytes 2019-09-30 20:33:58 +10:00
7c66bbab52 fix some error prone stuff, also I goofed the short circuit... oops 2019-09-30 17:14:07 +10:00
fbf887b534 c io for input 2019-09-29 11:51:14 +10:00
0a26bc8998 reformat 2019-09-28 10:18:39 +10:00
11 changed files with 323 additions and 182 deletions

151
feropm.py Executable file
View File

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

7
neotools/.gitignore vendored
View File

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

18
neotools/CMakeLists.txt Normal file
View 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)

View File

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

View File

@@ -1,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
View File

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

View File

@@ -1,19 +1,12 @@
#!/bin/sh #!/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"

View File

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

View File

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

View File

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

View File

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