mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-12 21:52:01 +00:00
482 lines
12 KiB
C
482 lines
12 KiB
C
|
/* _______ ____ __ ___ ___
|
||
|
* \ _ \ \ / \ / \ \ / / ' ' '
|
||
|
* | | \ \ | | || | \/ | . .
|
||
|
* | | | | | | || ||\ /| |
|
||
|
* | | | | | | || || \/ | | ' ' '
|
||
|
* | | | | | | || || | | . .
|
||
|
* | |_/ / \ \__// || | |
|
||
|
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
|
||
|
* / \
|
||
|
* / . \
|
||
|
* dumb2wav.c - Utility to convert DUH to WAV. / / \ \
|
||
|
* | < / \_
|
||
|
* By Chad Austin, based on dumbout.c by entheh. | \/ /\ /
|
||
|
* \_ / > /
|
||
|
* | \ / /
|
||
|
* | ' /
|
||
|
* \__/
|
||
|
*/
|
||
|
|
||
|
#include <time.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include <ctype.h>
|
||
|
#include <math.h>
|
||
|
#include <string.h>
|
||
|
#include <dumb.h>
|
||
|
|
||
|
#include <internal/it.h>
|
||
|
|
||
|
union {
|
||
|
float s32[4096];
|
||
|
short s16[8192];
|
||
|
char s8[16384];
|
||
|
} buffer;
|
||
|
|
||
|
sample_t ** internal_buffer;
|
||
|
|
||
|
int loop_count = 1;
|
||
|
|
||
|
|
||
|
static int write32_le(FILE* outf, unsigned int value) {
|
||
|
int total = 0;
|
||
|
total += fputc(value & 0xFF, outf);
|
||
|
total += fputc((value >> 8) & 0xFF, outf);
|
||
|
total += fputc((value >> 16) & 0xFF, outf);
|
||
|
total += fputc((value >> 24) & 0xFF, outf);
|
||
|
return total;
|
||
|
}
|
||
|
|
||
|
static int write16_le(FILE* outf, unsigned int value) {
|
||
|
int total = 0;
|
||
|
total += fputc(value & 0xFF, outf);
|
||
|
total += fputc((value >> 8) & 0xFF, outf);
|
||
|
return total;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int loop_callback(void* data) {
|
||
|
return (--loop_count <= 0 ? -1 : 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
int main(int argc, const char *argv[])
|
||
|
{
|
||
|
DUH *duh;
|
||
|
DUH_SIGRENDERER *sr;
|
||
|
|
||
|
const char *fn = NULL;
|
||
|
const char *fn_out = NULL;
|
||
|
FILE *outf;
|
||
|
|
||
|
int depth = 16;
|
||
|
int unsign = 0;
|
||
|
int freq = 44100;
|
||
|
int n_channels = 2;
|
||
|
int solo = -1;
|
||
|
float volume = 1.0f;
|
||
|
float delay = 0.0f;
|
||
|
float delta;
|
||
|
int bufsize;
|
||
|
clock_t start, end;
|
||
|
int data_written = 0; /* total bytes written to data chunk */
|
||
|
|
||
|
int i = 1;
|
||
|
|
||
|
LONG_LONG length;
|
||
|
LONG_LONG done;
|
||
|
int dots;
|
||
|
|
||
|
while (i < argc) {
|
||
|
const char *arg = argv[i++];
|
||
|
if (*arg != '-') {
|
||
|
if (fn) {
|
||
|
fprintf(stderr,
|
||
|
"Cannot specify multiple filenames!\n"
|
||
|
"Second filename found: \"%s\"\n", arg);
|
||
|
return 1;
|
||
|
}
|
||
|
fn = arg;
|
||
|
continue;
|
||
|
}
|
||
|
arg++;
|
||
|
while (*arg) {
|
||
|
char *endptr;
|
||
|
switch (*arg++) {
|
||
|
case 'o':
|
||
|
case 'O':
|
||
|
if (i >= argc) {
|
||
|
fprintf(stderr, "Out of arguments; output filename expected!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
fn_out = argv[i++];
|
||
|
break;
|
||
|
case 'd':
|
||
|
case 'D':
|
||
|
if (i >= argc) {
|
||
|
fprintf(stderr, "Out of arguments; delay expected!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
delay = (float)strtod(argv[i++], &endptr);
|
||
|
if (*endptr != 0 || delay < 0.0f || delay > 64.0f) {
|
||
|
fprintf(stderr, "Invalid delay!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
case 'v':
|
||
|
case 'V':
|
||
|
if (i >= argc) {
|
||
|
fprintf(stderr, "Out of arguments; volume expected!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
volume = (float)strtod(argv[i++], &endptr);
|
||
|
if (*endptr != 0 || volume < -8.0f || volume > 8.0f) {
|
||
|
fprintf(stderr, "Invalid volume!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
case 's':
|
||
|
case 'S':
|
||
|
if (i >= argc) {
|
||
|
fprintf(stderr, "Out of arguments; sampling rate expected!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
freq = strtol(argv[i++], &endptr, 10);
|
||
|
if (*endptr != 0 || freq < 1 || freq > 960000) {
|
||
|
fprintf(stderr, "Invalid sampling rate!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
case 'f':
|
||
|
depth = 32;
|
||
|
break;
|
||
|
case '8':
|
||
|
depth = 8;
|
||
|
break;
|
||
|
case 'l':
|
||
|
case 'L':
|
||
|
if (i >= argc) {
|
||
|
fprintf(stderr, "Out of arguments: loop count expected!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
loop_count = strtol(argv[i++], &endptr, 10);
|
||
|
break;
|
||
|
case 'm':
|
||
|
case 'M':
|
||
|
n_channels = 1;
|
||
|
break;
|
||
|
case 'u':
|
||
|
case 'U':
|
||
|
unsign = 1;
|
||
|
break;
|
||
|
case 'r':
|
||
|
case 'R':
|
||
|
if (i >= argc) {
|
||
|
fprintf(stderr, "Out of arguments; resampling quality expected!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
dumb_resampling_quality = strtol(argv[i++], &endptr, 10);
|
||
|
if (*endptr != 0 || dumb_resampling_quality < 0 || dumb_resampling_quality > 2) {
|
||
|
fprintf(stderr, "Invalid resampling quality!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
case 'c':
|
||
|
case 'C':
|
||
|
if (i >= argc) {
|
||
|
fprintf(stderr, "Out of arguments; channel number expected!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
solo = strtol(argv[i++], &endptr, 10);
|
||
|
if (*endptr != 0 || solo < 0 || solo >= DUMB_IT_N_CHANNELS) {
|
||
|
fprintf(stderr, "Invalid channel number!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr, "Invalid switch - '%c'!\n", isprint(arg[-1]) ? arg[-1] : '?');
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!fn) {
|
||
|
fprintf(stderr,
|
||
|
"Usage: dumb2wav [options] module [more-options]\n"
|
||
|
"\n"
|
||
|
"The module can be any IT, XM, S3M or MOD file. It will be rendered to a .wav\n"
|
||
|
"file of the same name, unless you specify otherwise with the -o option.\n"
|
||
|
"\n"
|
||
|
"The valid options are:\n"
|
||
|
"-o <file> specify the output filename (defaults to the input filename with\n"
|
||
|
" the extension replaced with .wav); use - to write to standard\n"
|
||
|
" output or . to write nowhere (useful for measuring DUMB's\n"
|
||
|
" performance, and DOS and Windows don't have /dev/null!)\n"
|
||
|
"-d <delay> set the initial delay, in seconds (default 0.0)\n"
|
||
|
"-v <volume> adjust the volume (default 1.0)\n"
|
||
|
"-s <freq> set the sampling rate in Hz (default 44100)\n"
|
||
|
"-8 generate 8-bit instead of 16-bit\n"
|
||
|
"-f generate floating point samples instead of 16-bit\n"
|
||
|
"-m generate mono output instead of stereo left/right pairs\n"
|
||
|
"-u generated unsigned output instead of signed\n"
|
||
|
"-r <value> specify the resampling quality to use\n"
|
||
|
"-l <value> specify the number of times to loop (default 1)\n"
|
||
|
"-c <value> specify a channel number to solo\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
atexit(&dumb_exit);
|
||
|
dumb_register_stdfiles();
|
||
|
|
||
|
dumb_it_max_to_mix = 256;
|
||
|
|
||
|
duh = load_duh(fn);
|
||
|
if (!duh) {
|
||
|
duh = dumb_load_it(fn);
|
||
|
if (!duh) {
|
||
|
duh = dumb_load_xm(fn);
|
||
|
if (!duh) {
|
||
|
duh = dumb_load_s3m(fn);
|
||
|
if (!duh) {
|
||
|
duh = dumb_load_mod(fn);
|
||
|
if (!duh) {
|
||
|
fprintf(stderr, "Unable to open %s!\n", fn);
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sr = duh_start_sigrenderer(duh, 0, n_channels, 0);
|
||
|
if (!sr) {
|
||
|
unload_duh(duh);
|
||
|
fprintf(stderr, "Unable to play file!\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (solo >= 0) {
|
||
|
DUMB_IT_SIGRENDERER * itsr = duh_get_it_sigrenderer(sr);
|
||
|
if (itsr) {
|
||
|
for (i = 0; i < DUMB_IT_N_CHANNELS; i++) {
|
||
|
if (i != solo) {
|
||
|
IT_CHANNEL * channel = &itsr->channel[i];
|
||
|
IT_PLAYING * playing = channel->playing;
|
||
|
channel->flags |= IT_CHANNEL_MUTED;
|
||
|
/* start_sigrenderer leaves me all of the channels the first tick triggered */
|
||
|
if (playing) {
|
||
|
playing->ramp_volume[0] = 0;
|
||
|
playing->ramp_volume[1] = 0;
|
||
|
playing->ramp_delta[0] = 0;
|
||
|
playing->ramp_delta[1] = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (fn_out) {
|
||
|
if (fn_out[0] == '-' && fn_out[1] == 0)
|
||
|
outf = stdout;
|
||
|
else if (fn_out[0] == '.' && fn_out[1] == 0)
|
||
|
outf = NULL;
|
||
|
else {
|
||
|
outf = fopen(fn_out, "wb");
|
||
|
if (!outf) {
|
||
|
fprintf(stderr, "Unable to open %s for writing!\n", fn_out);
|
||
|
duh_end_sigrenderer(sr);
|
||
|
unload_duh(duh);
|
||
|
return 1;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
char *extptr = NULL, *p;
|
||
|
char *fn_out = malloc(strlen(fn)+5);
|
||
|
if (!fn_out) {
|
||
|
fprintf(stderr, "Out of memory!\n");
|
||
|
duh_end_sigrenderer(sr);
|
||
|
unload_duh(duh);
|
||
|
return 1;
|
||
|
}
|
||
|
strcpy(fn_out, fn);
|
||
|
for (p = fn_out; *p; p++)
|
||
|
if (*p == '.') extptr = p;
|
||
|
if (!extptr) extptr = p;
|
||
|
strcpy(extptr, ".wav");
|
||
|
outf = fopen(fn_out, "wb");
|
||
|
if (!outf) {
|
||
|
fprintf(stderr, "Unable to open %s for writing!\n", fn_out);
|
||
|
free(fn_out);
|
||
|
duh_end_sigrenderer(sr);
|
||
|
unload_duh(duh);
|
||
|
return 1;
|
||
|
}
|
||
|
free(fn_out);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr);
|
||
|
dumb_it_set_ramp_style(itsr, 2);
|
||
|
dumb_it_set_loop_callback(itsr, loop_callback, NULL);
|
||
|
dumb_it_set_xm_speed_zero_callback(itsr, &dumb_it_callback_terminate, NULL);
|
||
|
dumb_it_set_global_volume_zero_callback(itsr, &dumb_it_callback_terminate, NULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
if (outf) {
|
||
|
/* write RIFF header: fill file length later */
|
||
|
fwrite("RIFF", 1, 4, outf);
|
||
|
fwrite(" ", 1, 4, outf);
|
||
|
fwrite("WAVE", 1, 4, outf);
|
||
|
|
||
|
/* write format chunk */
|
||
|
fwrite("fmt ", 1, 4, outf);
|
||
|
|
||
|
if (depth == 32)
|
||
|
{
|
||
|
write32_le(outf, 18);
|
||
|
write16_le(outf, 3);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
write32_le(outf, 16); /* header length */
|
||
|
write16_le(outf, 1); /* WAVE_FORMAT_PCM */
|
||
|
}
|
||
|
write16_le(outf, n_channels); /* channel count */
|
||
|
write32_le(outf, freq); /* frequency */
|
||
|
write32_le(outf, freq * n_channels * depth / 8); /*bytes/sec*/
|
||
|
write16_le(outf, n_channels * depth / 8); /* block alignment */
|
||
|
write16_le(outf, depth); /* bits per sample */
|
||
|
|
||
|
if (depth == 32)
|
||
|
{
|
||
|
write16_le(outf, 0);
|
||
|
}
|
||
|
|
||
|
/* start data chunk */
|
||
|
fwrite("data", 1, 4, outf);
|
||
|
fwrite(" ", 1, 4, outf); /* fill in later */
|
||
|
}
|
||
|
|
||
|
length = (LONG_LONG)_dumb_it_build_checkpoints(duh_get_it_sigdata(duh), 0) * freq >> 16;
|
||
|
done = 0;
|
||
|
dots = 0;
|
||
|
delta = 65536.0f / freq;
|
||
|
bufsize = sizeof(buffer);
|
||
|
if (depth == 32) bufsize /= sizeof(*buffer.s32);
|
||
|
else if (depth == 16) bufsize /= sizeof(*buffer.s16);
|
||
|
bufsize /= n_channels;
|
||
|
|
||
|
if (depth == 32) {
|
||
|
internal_buffer = create_sample_buffer(n_channels, bufsize);
|
||
|
if (!internal_buffer) {
|
||
|
fprintf(stderr, "Out of memory!\n");
|
||
|
duh_end_sigrenderer(sr);
|
||
|
unload_duh(duh);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
{
|
||
|
long l = (long)floor(delay * freq + 0.5f);
|
||
|
l *= n_channels * (depth >> 3);
|
||
|
if (l) {
|
||
|
if (unsign && depth != 32) {
|
||
|
if (depth == 16) {
|
||
|
for (i = 0; i < 8192; i++) {
|
||
|
buffer.s8[i*2] = 0x00;
|
||
|
buffer.s8[i*2+1] = 0x80;
|
||
|
}
|
||
|
} else
|
||
|
memset(buffer.s8, 0x80, 16384);
|
||
|
} else
|
||
|
memset(buffer.s8, 0, 16384);
|
||
|
while (l >= 16384) {
|
||
|
if (outf) fwrite(buffer.s8, 1, 16384, outf);
|
||
|
l -= 16384;
|
||
|
data_written += 16384;
|
||
|
}
|
||
|
if (l) {
|
||
|
if (outf) fwrite(buffer.s8, 1, l, outf);
|
||
|
data_written += 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
start = clock();
|
||
|
|
||
|
fprintf(stderr, "................................................................\n");
|
||
|
for (;;) {
|
||
|
int write_size;
|
||
|
int l;
|
||
|
|
||
|
if (depth != 32) {
|
||
|
l = duh_render(sr, depth, unsign, volume, delta, bufsize, &buffer);
|
||
|
if (depth == 16) {
|
||
|
for (i = 0; i < l * n_channels; i++) {
|
||
|
short val = buffer.s16[i];
|
||
|
buffer.s8[i*2] = val;
|
||
|
buffer.s8[i*2+1] = val >> 8;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
int j;
|
||
|
dumb_silence(internal_buffer[0], bufsize * n_channels);
|
||
|
l = duh_sigrenderer_get_samples(sr, volume, delta, bufsize, internal_buffer);
|
||
|
for (i = 0; i < n_channels; i++) {
|
||
|
for (j = 0; j < l; j++) {
|
||
|
buffer.s32[j * n_channels + i] = (float)((double)internal_buffer[i][j] * (1.0 / (double)(0x800000)));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
write_size = l * n_channels * (depth >> 3);
|
||
|
if (outf) fwrite(buffer.s8, 1, write_size, outf);
|
||
|
data_written += write_size;
|
||
|
if (l < bufsize) break;
|
||
|
done += l;
|
||
|
l = done * 64 / length;
|
||
|
while (dots < 64 && l > dots) {
|
||
|
fprintf(stderr, "|");
|
||
|
dots++;
|
||
|
}
|
||
|
if (dots >= 64) {
|
||
|
putchar('\n');
|
||
|
dots = 0;
|
||
|
done = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while (64 > dots) {
|
||
|
fprintf(stderr, "|");
|
||
|
dots++;
|
||
|
}
|
||
|
fprintf(stderr, "\n");
|
||
|
|
||
|
end = clock();
|
||
|
|
||
|
if (depth == 32) destroy_sample_buffer(internal_buffer);
|
||
|
|
||
|
/* fill in blanks we left in WAVE file */
|
||
|
if (outf) {
|
||
|
/* file size, not including RIFF header */
|
||
|
const int fmt_size = 8 + ((depth == 32) ? 18 : 16);
|
||
|
const int data_size = 8 + data_written;
|
||
|
const int file_size = fmt_size + data_size;
|
||
|
|
||
|
/* can we seek stdout? */
|
||
|
fseek(outf, 4, SEEK_SET);
|
||
|
write32_le(outf, file_size);
|
||
|
|
||
|
fseek(outf, 12 + fmt_size + 4, SEEK_SET);
|
||
|
write32_le(outf, data_written);
|
||
|
}
|
||
|
|
||
|
|
||
|
duh_end_sigrenderer(sr);
|
||
|
unload_duh(duh);
|
||
|
if (outf && outf != stdout) fclose(outf);
|
||
|
|
||
|
fprintf(stderr, "Elapsed time: %f seconds\n", (end - start) / (float)CLOCKS_PER_SEC);
|
||
|
|
||
|
return 0;
|
||
|
}
|