1
0
mirror of https://github.com/mon/2dxTools.git synced 2024-11-23 22:10:57 +01:00

Bout time I made a repo for this

This commit is contained in:
William Toohey 2017-03-23 22:36:32 +10:00
commit 9ec75bd24f
12 changed files with 565 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
sox/
*.dll
*.wav
*.exe
*.o

19
2dx.h Normal file
View File

@ -0,0 +1,19 @@
#include <stdint.h>
typedef struct {
char name[16];
uint32_t headerSize; // this + offsets table
uint32_t fileCount;
char unknown[48]; // contains rest of title, some random flags
} fileHeader_t;
typedef struct{
char dx[4]; // should be "2DX9";
uint32_t headerSize; // always 24, includes dx chars
uint32_t wavSize;
int16_t unk1; // always 0x3231
int16_t trackId; // always -1 for previews, 0-7 for song + effected versions, 9 to 11 used for a few effects
int16_t unk2; // all 64, except song selection change 'click' is 40
int16_t attenuation; // 0-127 for varying quietness
int32_t loopPoint; // sample to loop at * 4
} dxHeader_t;

88
2dxBuild.c Normal file
View File

@ -0,0 +1,88 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "2dx.h"
#include "shared.h"
int count_wav(void) {
int ret = 0;
char filename[256];
while(snprintf(filename, 256, "%d.wav", ret), file_exists(filename))
ret++;
return ret;
}
void build_2dx(char* path) {
int wavCount = count_wav();
printf("Writing %d wavs to %s\n",wavCount, path);
FILE* outFile = fopen(path, "wb");
if(!outFile) {
printf("Could not open %s\n", path);
return;
}
fileHeader_t fileHeader;
dxHeader_t dxHeader;
char wavPath[256];
// +1 to ignore null terminator, clobbers headerSize but we write that next
strncpy(fileHeader.name, path, sizeof(fileHeader.name)+1);
fileHeader.headerSize = sizeof(fileHeader) + sizeof(uint32_t)*wavCount;
fileHeader.fileCount = wavCount;
memset(fileHeader.unknown, 0, sizeof(fileHeader.unknown));
fwrite(&fileHeader, sizeof(fileHeader), 1, outFile);
// file offsets
uint32_t offset = fileHeader.headerSize;
for(int i = 0; i < wavCount; i++) {
snprintf(wavPath, 256, "%d.wav", i);
FILE* wav = fopen(wavPath, "rb");
fseek(wav, 0L, SEEK_END);
size_t wavSize = ftell(wav);
rewind(wav);
fwrite(&offset, sizeof(uint32_t), 1, outFile);
offset += wavSize + sizeof(dxHeader_t);
fclose(wav);
}
// the actual wavs
for(int i = 0; i < wavCount; i++) {
snprintf(wavPath, 256, "%d.wav", i);
FILE* wav = fopen(wavPath, "rb");
fseek(wav, 0L, SEEK_END);
size_t wavSize = ftell(wav);
rewind(wav);
memcpy(dxHeader.dx, "2DX9", 4);
dxHeader.headerSize = 24;
dxHeader.wavSize = wavSize;
dxHeader.unk1 = 0x3231;
// these match preview files for convenience
dxHeader.trackId = -1; // I may regret this
dxHeader.unk2 = 64;
dxHeader.attenuation = 1;
dxHeader.loopPoint = 0;
fwrite(&dxHeader, sizeof(dxHeader), 1, outFile);
transfer_file(wav, outFile, wavSize);
fclose(wav);
}
fclose(outFile);
printf("Done!\n");
}
int main(int argc, char** argv) {
if(argc != 2) {
printf("Usage: 2dxbuild output\n");
return 1;
}
build_2dx(argv[1]);
return 0;
}

55
2dxDump.c Normal file
View File

@ -0,0 +1,55 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "2dx.h"
#include "shared.h"
void extract_2dx(char* path) {
FILE* f = fopen(path, "rb");
if(!f) {
printf("Could not open %s, skipping\n", path);
return;
}
fileHeader_t fileHeader;
uint32_t *fileOffsets;
dxHeader_t dxHeader;
char outPath[256];
FILE* outFile;
fread(&fileHeader, sizeof(fileHeader), 1, f);
//printf("2dx contains %d file(s)\n", fileHeader.fileCount);
fileOffsets = malloc(sizeof(uint32_t) * fileHeader.fileCount);
fread(fileOffsets, sizeof(uint32_t), fileHeader.fileCount, f);
for(int i = 0; i < fileHeader.fileCount; i++) {
fseek(f, fileOffsets[i], SEEK_SET);
// TODO verify 2DX9
fread(&dxHeader, sizeof(dxHeader), 1, f);
snprintf(outPath, 256, "%d.wav", i);
outFile = fopen(outPath, "wb");
// seek to RIFF start
fseek(f, fileOffsets[i]+dxHeader.headerSize, SEEK_SET);
fprintf(stderr, "Extracting %s...\n", outPath);
transfer_file(f, outFile, dxHeader.wavSize);
fclose(outFile);
}
free(fileOffsets);
//printf("Done!\n");
}
int main(int argc, char** argv) {
if(argc < 2) {
printf("Usage: 2dxdump file1 [file2 ...]\n");
return 1;
}
for(int i = 1; i < argc; i++) {
extract_2dx(argv[i]);
}
return 0;
}

84
2dxMerge.c Normal file
View File

@ -0,0 +1,84 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "2dx.h"
#include "shared.h"
void update_2dx(char* inPath, char* outPath) {
size_t wavSize;
fileHeader_t fileHeader;
uint32_t *fileOffsets;
uint32_t *newOffsets;
dxHeader_t dxHeader;
char wavPath[256];
FILE* inFile = fopen(inPath, "rb");
if(!inFile) {
printf("Could not open %s for reading\n", inPath);
return;
}
FILE* outFile = fopen(outPath, "wb");
if(!outFile) {
printf("Could not open %s for writing\n", outPath);
return;
}
fread(&fileHeader, sizeof(fileHeader), 1, inFile);
fileOffsets = malloc(sizeof(uint32_t) * fileHeader.fileCount);
newOffsets = malloc(sizeof(uint32_t) * fileHeader.fileCount);
fread(fileOffsets, sizeof(uint32_t), fileHeader.fileCount, inFile);
memcpy(newOffsets, fileOffsets, sizeof(uint32_t) * fileHeader.fileCount);
// update offsets for new wavs, start at 1 cause it has same offset
for(int i = 1; i < fileHeader.fileCount; i++) {
snprintf(wavPath, 256, "%d.wav", i-1);
// we have a new wav!
if((wavSize = file_size(wavPath)) == 0) {
// calculate size from old offsets
wavSize = fileOffsets[i] - fileOffsets[i-1] - sizeof(dxHeader);
}
newOffsets[i] = newOffsets[i-1] + sizeof(dxHeader) + wavSize;
}
rewind(inFile);
// unchanged
fwrite(&fileHeader, sizeof(fileHeader), 1, outFile);
// new offsets
fwrite(newOffsets, sizeof(uint32_t), fileHeader.fileCount, outFile);
// new wavs
for(int i = 0; i < fileHeader.fileCount; i++) {
// load existing header
fseek(inFile, fileOffsets[i], SEEK_SET);
fread(&dxHeader, sizeof(dxHeader), 1, inFile);
snprintf(wavPath, 256, "%d.wav", i);
// new wav to insert
if((wavSize = file_size(wavPath))) {
printf("Updating %s\n", wavPath);
dxHeader.wavSize = wavSize;
FILE* newWav = fopen(wavPath, "rb");
fwrite(&dxHeader, sizeof(dxHeader), 1, outFile);
transfer_file(newWav, outFile, wavSize);
fclose(newWav);
} else {
fwrite(&dxHeader, sizeof(dxHeader), 1, outFile);
transfer_file(inFile, outFile, dxHeader.wavSize);
}
}
fclose(outFile);
fclose(inFile);
printf("Done!\n");
}
int main(int argc, char** argv) {
if(argc != 3) {
printf("Usage: 2dxupdate input output\n");
return 1;
}
update_2dx(argv[1], argv[2]);
return 0;
}

170
2dxWav.c Normal file
View File

@ -0,0 +1,170 @@
#include "sox/sox.h"
#include "2dxWav.h"
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
// the assert in assert.h produces a crash, this just returns
#define assert(cond) if(!(cond)) {printf("Something went wrong: " #cond "\n"); return 1;}
/* Private data for .wav file, taken from wav.c so we can change
certain attributes to the 2dx specific format */
typedef struct {
uint64_t numSamples;
size_t dataLength;
unsigned short formatTag;
unsigned short samplesPerBlock;
unsigned short blockAlign;
size_t dataStart;
char * comment;
int ignoreSize;
unsigned short nCoefs; /* ADPCM: number of coef sets */
short *lsx_ms_adpcm_i_coefs; /* ADPCM: coef sets */
unsigned char *packet; /* Temporary buffer for packets */
short *samples; /* interleaved samples buffer */
short *samplePtr; /* Pointer to current sample */
short *sampleTop; /* End of samples-buffer */
unsigned short blockSamplesRemaining;/* Samples remaining per channel */
int state[16]; /* step-size info for *ADPCM writes */
/* there are more things in here but they're related to GSM encoding */
} priv_t;
/* For debugging */
void printwav(priv_t* wav) {
printf("\t numSamples: %d\n" , wav->numSamples );
printf("\t dataLength: %zu\n" , wav->dataLength );
printf("\t formatTag: %hu\n" , wav->formatTag );
printf("\t samplesPerBlock: %hu\n" , wav->samplesPerBlock );
printf("\t blockAlign: %hu\n" , wav->blockAlign );
printf("\t dataStart: %zu\n" , wav->dataStart );
printf("\t ignoreSize: %d\n" , wav->ignoreSize );
printf("\t nCoefs: %hu\n" , wav->nCoefs );
printf("\t lsx_ms_adpcm_i_coefs: %d\n" , wav->lsx_ms_adpcm_i_coefs );
printf("\t packet: %d\n:" , wav->packet );
printf("\t samples: %d\n" , wav->samples );
printf("\t samplePtr: %d\n" , wav->samplePtr );
printf("\t sampleTop: %d\n" , wav->sampleTop );
printf("\t blockSamplesRemaining: %d\n" , wav->blockSamplesRemaining );
}
int convert_wav(char* inFile, char* outWav, int trimPreview) {
static sox_format_t *in, *out;
sox_effects_chain_t *chain;
sox_effect_t *e;
sox_signalinfo_t interm_signal;
char *args[10];
assert(sox_init() == SOX_SUCCESS);
assert(in = sox_open_read(inFile, NULL, NULL, NULL));
// 2dx specific format
sox_signalinfo_t out_signal = {
.rate = 44100,
.channels = 2, // have to be 2 for later hacks to work
.precision = 0,
.length = 0,
.mult = NULL
};
sox_encodinginfo_t out_encoding;
memset(&out_encoding, 0, sizeof(out_encoding));
out_encoding.encoding = SOX_ENCODING_MS_ADPCM;
out_encoding.bits_per_sample = 4;
assert(out = sox_open_write(outWav, &out_signal, &out_encoding, "wav", NULL, NULL));
// Forcibly set the values we want
priv_t *wav = (priv_t *) out->priv;
wav->samplesPerBlock = 244;
wav->blockAlign = 256;
// taken from wav.c, with locked 2 channels
// The buffers become the wrong size, but we use smaller buffers so it's ok
size_t sbsize = 2 * wav->samplesPerBlock;
wav->sampleTop = wav->samples + sbsize;
// we'll need these later to fix the headers
uint16_t blockAlign = wav->blockAlign;
uint16_t samplesPerBlock = wav->samplesPerBlock;
uint32_t avgBytes = (double)blockAlign*out_signal.rate / (double)samplesPerBlock + 0.5;
// Debugging
/*printf("\n\nOutput data:\n");
printwav(wav);
wav = (priv_t *) in->priv;
printf("\n\nInput data:\n");
printwav(wav);*/
chain = sox_create_effects_chain(&in->encoding, &out->encoding);
interm_signal = in->signal; /* NB: deep copy */
e = sox_create_effect(sox_find_effect("input"));
args[0] = (char *)in;
assert(sox_effect_options(e, 1, args) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &in->signal, &in->signal) == SOX_SUCCESS);
free(e);
if (in->signal.rate != out->signal.rate) {
e = sox_create_effect(sox_find_effect("rate"));
assert(sox_effect_options(e, 0, NULL) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &interm_signal, &out->signal) == SOX_SUCCESS);
free(e);
}
if (in->signal.channels != out->signal.channels) {
e = sox_create_effect(sox_find_effect("channels"));
assert(sox_effect_options(e, 0, NULL) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &interm_signal, &out->signal) == SOX_SUCCESS);
free(e);
}
// Only use 10 seconds of audio for previews
if(trimPreview) {
e = sox_create_effect(sox_find_effect("trim"));
args[0] = "0";
args[1] = "10";
assert(sox_effect_options(e, 1, args) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &interm_signal, &in->signal) == SOX_SUCCESS);
free(e);
}
e = sox_create_effect(sox_find_effect("output"));
args[0] = (char *)out;
assert(sox_effect_options(e, 1, args) == SOX_SUCCESS);
assert(sox_add_effect(chain, e, &in->signal, &in->signal) == SOX_SUCCESS);
free(e);
/* Flow samples through the effects processing chain until EOF is reached */
sox_flow_effects(chain, NULL, NULL);
/* All done; tidy up: */
sox_delete_effects_chain(chain);
sox_close(out);
sox_close(in);
sox_quit();
// Fix the header, since sox wrote it before we changed our block stuff
FILE* outFile = fopen(outWav, "r+b");
// AvgBytesPerSec
fseek(outFile, 28, SEEK_SET);
fwrite(&avgBytes, sizeof(uint32_t), 1, outFile);
fwrite(&blockAlign, sizeof(uint16_t), 1, outFile);
// samples per block
fseek(outFile, 38, SEEK_SET);
fwrite(&samplesPerBlock, sizeof(uint16_t), 1, outFile);
// RIFF size
// minus 4 byte "RIFF" and 4 bytes for this number
size_t wavSize = file_size(outWav) - 8;
fseek(outFile, 4, SEEK_SET);
fwrite(&wavSize, sizeof(uint32_t), 1, outFile);
//// data size
// minus the RIFF header size
wavSize -= 82;
fseek(outFile, 86, SEEK_SET);
fwrite(&wavSize, sizeof(uint32_t), 1, outFile);
fclose(outFile);
return 0;
}

9
2dxWav.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef _2DX_WAV_H
#define _2DX_WAV_H
#include "shared.h"
// Converts a wav to a file
int convert_wav(char* inFile, char* outWav, int trimPreview);
#endif

20
2dxWavConvert.c Normal file
View File

@ -0,0 +1,20 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "shared.h"
#include "2dxWav.h"
int main(int argc, char * argv[])
{
if(argc != 3) {
printf("2dxWavConvert infile outwav\n");
return 1;
}
if(convert_wav(argv[1], argv[2], 0)) {
printf("Conversion failed!\n");
return 1;
}
return 0;
}

26
Makefile Normal file
View File

@ -0,0 +1,26 @@
CC=gcc
CFLAGS=-std=c99 -Wall -pedantic
all: 2dxWavConvert 2dxDump 2dxBuild 2dxMerge
shared.o: shared.c shared.h
$(CC) $(CFLAGS) -c shared.c -o shared.o
2dxWav.o: 2dxWav.c 2dxWav.h shared.o
$(CC) $(CFLAGS) -c 2dxWav.c -o 2dxWav.o
2dxDump: 2dxDump.c 2dx.h shared.o
$(CC) $(CFLAGS) 2dxDump.c shared.o -o 2dxDump
2dxBuild: 2dxBuild.c 2dx.h shared.o
$(CC) $(CFLAGS) 2dxBuild.c shared.o -o 2dxBuild
2dxMerge: 2dxMerge.c 2dx.h shared.o
$(CC) $(CFLAGS) 2dxMerge.c shared.o -o 2dxMerge
2dxWavConvert: 2dxWavConvert.c shared.o 2dxWav.o
$(CC) $(CFLAGS) 2dxWavConvert.c shared.o 2dxWav.o -o 2dxWavConvert libsox-3.dll
.PHONY: clean
clean:
rm -f *.o 2dxDump 2dxBuild 2dxMerge

44
README.md Normal file
View File

@ -0,0 +1,44 @@
#2dxTools
A set of tools for working with 2dx audio containers.
###2dxDump
`2dxDump infile.2dx`
Takes a 2dx and dumps all its wavs. Names them sequentially with no leading zeros.
###2dxBuild
`2dxBuild outfile.2dx`
Builds a 2dx with default paramaters for loop point/volume etc.
###2dxMerge
`2dxMerge infile.2dx outfile.2dx`
For each file in `infile.2dx`, if there is a `.wav` present in the directory,
it will replace it in the new file. Useful for extracting a single audio file,
editing it, then adding it back.
###2dxWavConvert
`2dxWavConvert infile outfile.wav [preview]`
Takes any file that `sox` supports (mp3, flac, wav, ogg, etc) and converts it to
the specific format required for 2dx files (MS-ADPCM wav with a block size of 256).
If the third argument is "preview", the file is clipped to exactly 10 seconds
to comply with preview wav requirements.
##Tools to come:
2dxTransfer - will work like 2dxMerge, but will take two input files, an output,
and a list of tracks to transfer. Metadata (such as loop points) will also be
transferred.
Enhancements to 2dxDump and 2dxBuild to generate and load xml files so track
parameters can be modified.
If you have a burning need for one of these unfinished tools, please get in
contact. I appreciate motivation.

35
shared.c Normal file
View File

@ -0,0 +1,35 @@
#include "shared.h"
#include <stdlib.h>
// TODO: buffering for speed
void transfer_file(FILE* in, FILE* out, size_t bytes) {
char byte;
for(size_t i = 0; i < bytes; i++) {
if(!fread(&byte, 1, 1, in)) {
printf("Could not read all bytes from input!\n");
exit(1);
}
fwrite(&byte, 1, 1, out);
}
}
// returns 0 if file is empty or noexistant, filesize otherwise
size_t file_size(const char* path) {
FILE* f = fopen(path, "rb");
if(!f)
return 0;
fseek(f, 0L, SEEK_END);
size_t size = ftell(f);
fclose(f);
return size;
}
// platform agnostic and proves we can read a file too
int file_exists(const char *path) {
FILE *file;
if ((file = fopen(path, "rb"))) {
fclose(file);
return 1;
}
return 0;
}

10
shared.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef _SHARED_H
#define _SHARED_H
#include <stdio.h>
void transfer_file(FILE* in, FILE* out, size_t bytes);
size_t file_size(const char* path);
int file_exists(const char *path);
#endif