pokeemmo/tools/wav2agb/wav2agb.cpp
2025-12-28 08:50:34 -06:00

248 lines
9.0 KiB
C++

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdarg>
#include <cassert>
#include <string>
#include "converter.h"
#include "wav_file.h"
static void usage() {
fprintf(stderr, "wav2agb\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: wav2agb [options] <input.wav> [<output>]\n");
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, "-s, --symbol <sym> | symbol name for wave header (default: file name)\n");
fprintf(stderr, "-l, --lookahead <amount> | DPCM compression lookahead 1..8 (default: 3)\n");
fprintf(stderr, "-c, --compress | compress output with DPCM\n");
fprintf(stderr, "-f, --fast-compress | compress output with DPCM fast\n");
fprintf(stderr, "--no-pad | omit trailing padding in compressed output\n");
fprintf(stderr, "-b, --binary | output raw binary instead of assembly\n");
fprintf(stderr, "--loop-start <pos> | override loop start (integer)\n");
fprintf(stderr, "--loop-end <pos> | override loop end (integer)\n");
fprintf(stderr, "--tune <cents> | override tuning (float)\n");
fprintf(stderr, "--key <key> | override midi key (int)\n");
fprintf(stderr, "--rate <rate> | override base samplerate (int)\n");
fprintf(stderr, "--set-agbl <loop-end> | adds the custom agbl chunk to the given input .wav file\n");
exit(1);
}
static void version() {
printf("wav2agb v1.1 (c) 2019 ipatix\n");
exit(0);
}
static void die(const char *msg, ...) {
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
exit(1);
}
static void fix_str(std::string& str) {
// replaces all characters that are not alphanumerical
for (size_t i = 0; i < str.size(); i++) {
if (str[i] >= 'a' && str[i] <= 'z')
continue;
if (str[i] >= 'A' && str[i] <= 'Z')
continue;
if (str[i] >= '0' && str[i] <= '9' && i > 0)
continue;
str[i] = '_';
}
}
static char path_seperators[] = {
'/',
#ifdef _WIN32
'\\',
#endif
'\0'
};
static std::string filename_without_ext(const std::string& str) {
size_t last_path_seperator = 0;
char *sep = path_seperators;
while (*sep) {
size_t pos = str.find_last_of(*sep);
if (pos != std::string::npos)
last_path_seperator = std::max(pos, last_path_seperator);
sep += 1;
}
size_t file_ext_dot_pos = str.find_last_of('.');
if (file_ext_dot_pos == std::string::npos)
return std::string(str);
assert(file_ext_dot_pos != last_path_seperator);
if (file_ext_dot_pos > last_path_seperator)
return str.substr(0, file_ext_dot_pos);
return std::string(str);
}
static std::string filename_without_dir(const std::string& str) {
size_t last_path_seperator = 0;
bool path_seperator_found = false;
char *sep = path_seperators;
while (*sep) {
size_t pos = str.find_last_of(*sep);
if (pos != std::string::npos) {
last_path_seperator = std::max(pos, last_path_seperator);
path_seperator_found = true;
}
sep += 1;
}
if (str.size() > 0 && path_seperator_found) {
return str.substr(last_path_seperator + 1);
} else {
return std::string(str);
}
}
static cmp_type arg_compress = cmp_type::none;
static out_type arg_output_type = out_type::assembly;
static std::string arg_sym;
static bool arg_input_file_read = false;
static bool arg_output_file_read = false;
static std::string arg_input_file;
static std::string arg_output_file;
static bool arg_set_agbl = false;
static int32_t arg_agbl_value = 0;
int main(int argc, char *argv[]) {
try {
if (argc == 1)
usage();
for (int i = 1; i < argc; i++) {
std::string st(argv[i]);
if (st == "-s" || st == "--symbol") {
if (++i >= argc)
die("-s: missing symbol name\n");
arg_sym = argv[i];
fix_str(arg_sym);
} else if (st == "-c" || st == "--compress") {
arg_compress = cmp_type::dpcm;
} else if (st == "-f" || st == "--compress-fast") {
arg_compress = cmp_type::dpcm;
enable_dpcm_lookahead_fast();
} else if (st == "--no-pad") {
disable_dpcm_padding();
} else if (st == "-b" || st == "--binary") {
arg_output_type = out_type::binary;
} else if (st == "--verbose") {
enable_dpcm_verbose();
} else if (st == "-l" || st == "--lookahead") {
if (++i >= argc)
die("-l: missing parameter");
set_dpcm_lookahead(std::stoul(argv[i], nullptr, 10));
} else if (st == "--version") {
version();
} else if (st == "--loop-start") {
if (++i >= argc)
die("--loop-start: missing parameter");
uint32_t start = static_cast<uint32_t>(std::stoul(argv[i], nullptr, 10));
set_wav_loop_start(start);
} else if (st == "--loop-end") {
if (++i >= argc)
die("--loop-end: missing parameter");
uint32_t end = static_cast<uint32_t>(std::stoul(argv[i], nullptr, 10));
set_wav_loop_end(end);
} else if (st == "--tune") {
if (++i >= argc)
die("--tune: missing parameter");
double tune = std::stod(argv[i], nullptr);
set_wav_tune(tune);
} else if (st == "--key") {
if (++i >= argc)
die("--key: missing parameter");
int key = std::stoi(argv[i], nullptr, 10);
if (key < 0) key = 0;
if (key > 127) key = 127;
set_wav_key(static_cast<uint8_t>(key));
} else if (st == "--rate") {
if (++i >= argc)
die("--rate: missing parameter");
uint32_t rate = static_cast<uint32_t>(std::stoul(argv[i], nullptr, 10));
set_wav_rate(rate);
} else if (st == "--set-agbl") {
if (++i >= argc)
die("--set-agbl: missing parameter");
arg_agbl_value = std::stoi(argv[i], nullptr, 10);
arg_set_agbl = true;
} else {
if (st == "--") {
if (++i >= argc)
die("--: missing file name\n");
}
if (!arg_input_file_read) {
arg_input_file = argv[i];
if (arg_input_file.size() < 1)
die("empty input file name\n");
arg_input_file_read = true;
} else if (!arg_output_file_read) {
arg_output_file = argv[i];
if (arg_output_file.size() < 1)
die("empty output file name\n");
arg_output_file_read = true;
} else {
die("Too many files specified\n");
}
}
}
// check arguments
if (!arg_input_file_read) {
die("No input file specified\n");
}
if (!arg_output_file_read) {
// create output file name if none is provided
if (arg_set_agbl) {
arg_output_file = arg_input_file;
} else if (arg_output_type == out_type::binary) {
arg_output_file = filename_without_ext(arg_input_file) + ".bin";
} else {
arg_output_file = filename_without_ext(arg_input_file) + ".s";
}
arg_output_file_read = true;
}
if (arg_sym.size() == 0) {
arg_sym = filename_without_dir(filename_without_ext(arg_output_file));
fix_str(arg_sym);
}
if (arg_set_agbl) {
// Parse the WAV file once to get both chunks and metadata
wav_file wav(arg_input_file);
// Calculate actual loop-end value
uint32_t loop_end_value;
if (arg_agbl_value < 0) {
// Negative value: offset from end of samples
int64_t calculated = static_cast<int64_t>(wav.numSamples) + arg_agbl_value;
if (calculated < 0) {
die("--set-agbl: negative offset %d exceeds total samples %u\n",
arg_agbl_value, wav.numSamples);
}
loop_end_value = static_cast<uint32_t>(calculated);
} else {
// Positive value: use directly
loop_end_value = static_cast<uint32_t>(arg_agbl_value);
}
write_wav_with_agbl_chunk(arg_output_file, wav.chunks, loop_end_value);
return 0;
}
convert(arg_input_file, arg_output_file, arg_sym, arg_compress, arg_output_type);
return 0;
} catch (const std::exception& e) {
fprintf(stderr, "std lib error:\n%s\n", e.what());
}
return 1;
}