mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-16 17:31:50 +00:00
01f59fa85f
heavily customized version of DUMB (Dynamic Universal Music Bibliotheque). It has been slightly modified by me: * Added support for Ogg Vorbis-compressed samples in XM files ala FMOD. * Removed excessive mallocs from the replay core. * Rerolled the loops in resample.c. Unrolling them made the object file ~250k large while providing little benefit. Even at ~100k, I think it's still larger than it ought to be, but I'll live with it for now. Other than that, it's essentially the same thing you'd hear in foobar2000, minus some subsong detection features. Release builds of the library look like they might even be slightly faster than FMOD, which is a plus. - Fixed: Timidity::font_add() did not release the file reader it created. - Fixed: The SF2 loader did not free the sample headers in its destructor. SVN r995 (trunk)
481 lines
12 KiB
C
481 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;
|
|
}
|