ref: 478feff9f20614e5205a7486feb5ed32ceca019b
dir: /examples/dumbout.c/
#include <argtable2.h> #include <dumb.h> #include <stdint.h> #include <string.h> #include <stdbool.h> #include <math.h> static const int endian_test = 1; #define is_bigendian() ((*(char*)&endian_test) == 0) enum ENDIANNESS { DUMB_LITTLE_ENDIAN = 0, DUMB_BIG_ENDIAN }; typedef struct { DUH_SIGRENDERER *renderer; DUH *src; FILE *dst; float delta; int bufsize; bool is_stdout; } streamer_t; typedef struct { int bits; int endianness; int is_unsigned; int freq; int quality; int n_channels; float volume; float delay; const char *input; char *output; } settings_t; int main(int argc, char *argv[]) { int retcode = 1; int nerrors = 0; streamer_t streamer; settings_t settings; memset(&streamer, 0, sizeof(streamer_t)); memset(&settings, 0, sizeof(settings_t)); // Defaults settings.freq = 44100; settings.n_channels = 2; settings.bits = 16; settings.endianness = DUMB_LITTLE_ENDIAN; settings.is_unsigned = false; settings.volume = 1.0f; settings.delay = 0.0f; settings.quality = DUMB_RQ_CUBIC; // commandline argument parser options struct arg_lit *arg_help = arg_lit0("h", "help", "print this help and exits"); struct arg_dbl *arg_delay = arg_dbl0("d", "delay", "<delay>", "sets the initial delay in seconds (0.0 to 64.0, default 0.0)"); struct arg_dbl *arg_volume = arg_dbl0("v", "volume", "<volume", "sets the output volume (-8.0 to +8.0, default 1.0)"); struct arg_int *arg_samplerate = arg_int0("s", "samplerate", "<freq>", "sets the sampling rate (default 44100)"); struct arg_int *arg_quality = arg_int0("r", "quality", "<quality>", "specify the resampling quality to use"); struct arg_lit *arg_mono = arg_lit0("m", "mono", "generate mono output instead of stereo"); struct arg_lit *arg_bigendian = arg_lit0("b", "bigendian", "generate big-endian data instead of little-endian"); struct arg_lit *arg_eight = arg_lit0("8", "eight", "generate 8-bit instead of 16-bit"); struct arg_lit *arg_unsigned = arg_lit0("u", "unsigned", "generate unsigned output instead of signed"); struct arg_file *arg_output = arg_file0("o", "output", "<file>", "output file"); struct arg_file *arg_input = arg_file1(NULL, NULL, "<file>", "input module file"); struct arg_end *arg_fend = arg_end(20); void* argtable[] = {arg_help, arg_input, arg_output, arg_delay, arg_volume, arg_samplerate, arg_quality, arg_mono, arg_bigendian, arg_eight, arg_unsigned, arg_fend}; const char* progname = "dumbout"; // Make sure everything got allocated if(arg_nullcheck(argtable) != 0) { fprintf(stderr, "%s: insufficient memory\n", progname); goto exit_0; } // Parse inputs nerrors = arg_parse(argc, argv, argtable); // Handle help if(arg_help->count > 0) { fprintf(stderr, "Usage: %s", progname); arg_print_syntax(stderr, argtable, "\n"); fprintf(stderr, "\nArguments:\n"); arg_print_glossary(stderr, argtable, "%-25s %s\n"); goto exit_0; } // Handle errors if(nerrors > 0) { arg_print_errors(stderr, arg_fend, progname); fprintf(stderr, "Try '%s --help' for more information.\n", progname); goto exit_0; } // Get input and output filenames settings.input = arg_input->filename[0]; if(arg_output->count > 0) { settings.output = malloc(strlen(arg_output->filename[0])+1); strcpy(settings.output, arg_output->filename[0]); } else { settings.output = malloc(strlen(arg_output->basename[0])+5); sprintf(settings.output, "%s%s", arg_output->basename[0], ".pcm"); } // Handle the switch options if(arg_bigendian->count > 0) { settings.endianness = DUMB_BIG_ENDIAN; } if(arg_eight->count > 0) { settings.bits = 8; } if(arg_unsigned->count > 0) { settings.is_unsigned = true; } if(arg_mono->count > 0) { settings.n_channels = 1; } if(arg_delay->count > 0) { settings.delay = arg_delay->dval[0]; if(settings.delay < 0.0f || settings.delay >= 64.0f) { fprintf(stderr, "Initial delay must be between 0.0f and 64.0f.\n"); goto exit_0; } } if(arg_volume->count > 0) { settings.volume = arg_volume->dval[0]; if(settings.volume < -8.0f || settings.volume > 8.0f) { fprintf(stderr, "Volume must be between -8.0f and 8.0f.\n"); goto exit_0; } } if(arg_samplerate->count > 0) { settings.freq = arg_samplerate->ival[0]; if(settings.freq < 1 || settings.freq > 96000) { fprintf(stderr, "Sampling rate must be between 1 and 96000.\n"); goto exit_0; } } if(arg_quality->count > 0) { settings.quality = arg_quality->ival[0]; if(settings.quality < 0 || settings.quality >= DUMB_RQ_N_LEVELS) { fprintf(stderr, "Quality must be between %d and %d.\n", 0, DUMB_RQ_N_LEVELS-1); goto exit_0; } } // dumb settings stuff dumb_register_stdfiles(); // Load source streamer.src = dumb_load_any(settings.input, 0, 0); if(!streamer.src) { fprintf(stderr, "Unable to load file %s for playback!\n", settings.input); goto exit_0; } // Set up playback streamer.renderer = duh_start_sigrenderer(streamer.src, 0, settings.n_channels, 0); streamer.delta = 65536.0f / settings.freq; streamer.bufsize = 4096 * (settings.bits / 8) * settings.n_channels; // Stop producing samples on module end DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(streamer.renderer); dumb_it_set_loop_callback(itsr, &dumb_it_callback_terminate, NULL); dumb_it_set_xm_speed_zero_callback(itsr, &dumb_it_callback_terminate, NULL); dumb_it_set_resampling_quality(itsr, settings.quality); // Open output if(strcmp(settings.output, "-") == 0) { streamer.dst = stdout; streamer.is_stdout = true; } else { streamer.dst = fopen(settings.output, "wb"); streamer.is_stdout = false; if(!streamer.dst) { fprintf(stderr, "Could not open output file %s!", settings.output); goto exit_1; } } bool run = true; char *buffer = malloc(streamer.bufsize); int read_samples; int read_bytes; // If output endianness is different than machine endianness, and output is 16 bits, reorder bytes. int switch_endianness = ((is_bigendian() && settings.endianness == DUMB_LITTLE_ENDIAN) || (!is_bigendian() && settings.endianness == DUMB_BIG_ENDIAN)); // Write the initial delay to the file if one was requested. long d = ((long)floor(settings.delay * settings.freq + 0.5f)) * settings.n_channels * (settings.bits / 8); if(d) { // Fill the buffer with silence. Remember to take into account endianness if(settings.is_unsigned) { if(settings.bits == 16) { if(settings.endianness == DUMB_BIG_ENDIAN) { // Unsigned 16bits big endian for(int i = 0; i < streamer.bufsize; i += 2) { buffer[i ] = (char)0x80; buffer[i+1] = (char)0x00; } } else { // Unsigned 16bits little endian for(int i = 0; i < streamer.bufsize; i += 2) { buffer[i ] = (char)0x00; buffer[i+1] = (char)0x80; } } } else { // Unsigned 8 bits memset(buffer, 0x80, streamer.bufsize); } } else { // Signed memset(buffer, 0, streamer.bufsize); } while(d >= streamer.bufsize) { fwrite(buffer, 1, streamer.bufsize, streamer.dst); d -= streamer.bufsize; } if(d) { fwrite(buffer, 1, d, streamer.dst); } } // Loop until we have nothing to loop through. Dumb will stop giving out bytes when the file is done. while(run) { read_samples = duh_render(streamer.renderer, settings.bits, (int)settings.is_unsigned, settings.volume, streamer.delta, 4096, buffer); read_bytes = read_samples * (settings.bits / 8) * settings.n_channels; // switch endianness if required if(switch_endianness && settings.bits == 16) { char tmp; for(int i = 0; i < read_bytes / 2; i++) { tmp = buffer[i*2+0]; buffer[i*2+0] = buffer[i*2+1]; buffer[i*2+1] = tmp; } } // Write to output stream and flush if it happens to be stdout fwrite(buffer, 1, read_bytes, streamer.dst); if(streamer.is_stdout) { fflush(streamer.dst); } run = (read_samples > 0); } free(buffer); // We made it this far without crashing, so let's just exit with no error :) retcode = 0; if(!streamer.is_stdout && streamer.dst) { fclose(streamer.dst); } exit_1: if(streamer.renderer) { duh_end_sigrenderer(streamer.renderer); } if(streamer.src) { unload_duh(streamer.src); } exit_0: free(settings.output); arg_freetable(argtable, sizeof(argtable)/sizeof(argtable[0])); return retcode; }