#define POSIXLY_CORRECT #include #include "../src/vgmstream.h" #include "../src/util.h" #ifdef WIN32 #include #include #else #include #endif #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif #ifndef VERSION /* To include the git version number / commit in test.exe, compile outside of Visual Studio and make sure git / sh is in the current PATH */ #define VERSION "" #endif #define BUFSIZE 0x8000 extern char * optarg; extern int optind, opterr, optopt; static void make_wav_header(uint8_t * buf, int32_t sample_count, int32_t sample_rate, int channels); static void make_smpl_chunk(uint8_t * buf, int32_t loop_start, int32_t loop_end); void usage(const char * name) { fprintf(stderr,"vgmstream test decoder " VERSION " " __DATE__ "\n" "Usage: %s [-o outfile.wav] [-l loop count]\n" " [-f fade time] [-d fade delay] [-ipcmxeE] infile\n" "Options:\n" " -o outfile.wav: name of output .wav file, default is dump.wav\n" " -l loop count: loop count, default 2.0\n" " -f fade time: fade time (seconds), default 10.0\n" " -d fade delay: fade delay (seconds, default 0.0\n" " -i: ignore looping information and play the whole stream once\n" " -p: output to stdout (for piping into another program)\n" " -P: output to stdout even if stdout is a terminal\n" " -c: loop forever (continuously)\n" " -m: print metadata only, don't decode\n" " -x: decode and print adxencd command line to encode as ADX\n" " -g: decode and print oggenc command line to encode as OGG\n" " -b: decode and print batch variable commands\n" " -L: append a smpl chunk and create a looping wav\n" " -e: force end-to-end looping\n" " -E: force end-to-end looping even if file has real loop points\n" " -r outfile2.wav: output a second time after resetting\n" " -2 N: only output the Nth (first is 0) set of stereo channels\n" ,name); } int main(int argc, char ** argv) { VGMSTREAM * s; sample * buf = NULL; int32_t len; int32_t fade_samples; int i; FILE * outfile = NULL; char * outfilename = NULL; char * reset_outfilename = NULL; int opt; int ignore_loop = 0; int force_loop = 0; int really_force_loop = 0; int play = 0; int play_wreckless = 0; int forever = 0; int metaonly = 0; int adxencd = 0; int oggenc = 0; int lwav = 0; int batchvar = 0; int only_stereo = -1; double loop_count = 2.0; double fade_seconds = 10.0; double fade_delay_seconds = 0.0; int32_t bytecount; while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEr:gb2:")) != -1) { switch (opt) { case 'o': outfilename = optarg; break; case 'l': loop_count = atof(optarg); break; case 'f': fade_seconds = atof(optarg); break; case 'd': fade_delay_seconds = atof(optarg); break; case 'i': ignore_loop = 1; break; case 'p': play = 1; break; case 'P': play_wreckless = 1; play = 1; break; case 'c': forever = 1; break; case 'm': metaonly = 1; break; case 'x': adxencd = 1; break; case 'g': oggenc = 1; break; case 'b': batchvar = 1; break; case 'e': force_loop = 1; break; case 'E': really_force_loop = 1; break; case 'L': lwav = 1; break; case 'r': reset_outfilename = optarg; break; case '2': only_stereo = atoi(optarg); break; default: usage(argv[0]); return 1; break; } } if (optind!=argc-1) { usage(argv[0]); return 1; } if (forever && !play) { fprintf(stderr,"A file of infinite size? Not likely.\n"); return 1; } if (play && (!play_wreckless &&isatty(STDOUT_FILENO))) { fprintf(stderr,"Are you sure you want to output wave data to the terminal?\nIf so use -P instead of -p.\n"); return 1; } #ifdef WIN32 /* make stdout output work with windows */ if (play) { _setmode(fileno(stdout),_O_BINARY); } #endif if (ignore_loop && force_loop) { fprintf(stderr,"-e and -i are incompatible\n"); return 1; } if (ignore_loop && really_force_loop) { fprintf(stderr,"-E and -i are incompatible\n"); return 1; } if (force_loop && really_force_loop) { fprintf(stderr,"-E and -e are somewhat redundant, are you confused?\n"); return 1; } s = init_vgmstream(argv[optind]); if (!s) { fprintf(stderr,"failed opening %s\n",argv[optind]); return 1; } /* force only if there aren't already loop points */ if (force_loop && !s->loop_flag) { /* this requires a bit more messing with the VGMSTREAM than I'm * comfortable with... */ s->loop_flag=1; s->loop_start_sample=0; s->loop_end_sample=s->num_samples; s->loop_ch=calloc(s->channels,sizeof(VGMSTREAMCHANNEL)); } /* force even if there are loop points */ if (really_force_loop) { if (!s->loop_flag) s->loop_ch=calloc(s->channels,sizeof(VGMSTREAMCHANNEL)); s->loop_flag=1; s->loop_start_sample=0; s->loop_end_sample=s->num_samples; } if (ignore_loop) s->loop_flag=0; if (play) { if (outfilename) { fprintf(stderr,"either -p or -o, make up your mind\n"); return 1; } outfile = stdout; } else if (!metaonly) { if (!outfilename) outfilename = "dump.wav"; outfile = fopen(outfilename,"wb"); if (!outfile) { fprintf(stderr,"failed to open %s for output\n",outfilename); return 1; } } if (forever && !s->loop_flag) { fprintf(stderr,"I could play a nonlooped track forever, but it wouldn't end well."); return 1; } if (!play) { if (adxencd) { printf("adxencd"); if (!metaonly) printf(" \"%s\"",outfilename); if (s->loop_flag) printf(" -lps%d -lpe%d",s->loop_start_sample,s->loop_end_sample); printf("\n"); } else if (oggenc) { printf("oggenc"); if (!metaonly) printf(" \"%s\"",outfilename); if (s->loop_flag) printf(" -c LOOPSTART=%d -c LOOPLENGTH=%d",s->loop_start_sample, s->loop_end_sample-s->loop_start_sample); printf("\n"); } else if (batchvar) { if (!metaonly) printf("set fname=\"%s\"\n",outfilename); printf("set tsamp=%d\nset chan=%d\n", s->num_samples, s->channels); if (s->loop_flag) printf("set lstart=%d\nset lend=%d\nset loop=1\n", s->loop_start_sample, s->loop_end_sample); else printf("set loop=0\n"); } else if (metaonly) printf("metadata for %s\n",argv[optind]); else printf("decoding %s\n",argv[optind]); } if (!play && !adxencd && !oggenc && !batchvar) { char description[1024]; description[0]='\0'; describe_vgmstream(s,description,1024); printf("%s\n",description); } if (metaonly) { close_vgmstream(s); return 0; } buf = malloc(BUFSIZE*sizeof(sample)*s->channels); len = get_vgmstream_play_samples(loop_count,fade_seconds,fade_delay_seconds,s); if (!play && !adxencd && !oggenc && !batchvar) printf("samples to play: %d (%.4lf seconds)\n",len,(double)len/s->sample_rate); fade_samples = fade_seconds * s->sample_rate; /* slap on a .wav header */ if (only_stereo != -1) { make_wav_header((uint8_t*)buf, len, s->sample_rate, 2); } else { make_wav_header((uint8_t*)buf, len, s->sample_rate, s->channels); } if (lwav && s->loop_flag) { // Adding space for smpl chunk at end bytecount = get_32bitLE((uint8_t*)buf + 4); put_32bitLE((uint8_t*)buf + 4, bytecount + 0x44); } fwrite(buf,1,0x2c,outfile); /* decode forever */ while (forever) { render_vgmstream(buf,BUFSIZE,s); swap_samples_le(buf,s->channels*BUFSIZE); if (only_stereo != -1) { int j; for (j=0;jchannels+(only_stereo*2),sizeof(sample),2,outfile); } } else { fwrite(buf,sizeof(sample)*s->channels,BUFSIZE,outfile); } } /* decode */ for (i=0;ilen) toget=len-i; render_vgmstream(buf,toget,s); if (s->loop_flag && fade_samples > 0) { int samples_into_fade = i - (len - fade_samples); if (samples_into_fade + toget > 0) { int j,k; for (j=0;j 0) { double fadedness = (double)(fade_samples-samples_into_fade)/fade_samples; for (k=0;kchannels;k++) { buf[j*s->channels+k] = buf[j*s->channels+k]*fadedness; } } } } } swap_samples_le(buf,s->channels*toget); if (only_stereo != -1) { int j; for (j=0;jchannels+(only_stereo*2),sizeof(sample),2,outfile); } } else { fwrite(buf,sizeof(sample)*s->channels,toget,outfile); } } if (lwav && s->loop_flag) { // Writing smpl chuck make_smpl_chunk((uint8_t*)buf, s->loop_start_sample, s->loop_end_sample); fwrite(buf,1,0x44,outfile); } fclose(outfile); outfile = NULL; #ifdef PROFILE_STREAMFILE { int i,j; size_t total_bytes_read = 0; for (i=0;ichannels;i++) { size_t bytes_read = get_streamfile_bytes_read(s->ch[i].streamfile); size_t file_size = get_streamfile_size(s->ch[i].streamfile); int error_count = get_streamfile_error_count(s->ch[i].streamfile); int already_reported = 0; /* see if we've reported this STREAMFILE already */ for (j=i-1;!already_reported && j>=0;j--) { if (s->ch[j].streamfile == s->ch[i].streamfile) { already_reported=1; } } if (already_reported) continue; total_bytes_read += bytes_read; fprintf(stderr,"ch%d: %lf%% (%d bytes read, file is %d bytes) %d errors\n",i, bytes_read*100.0/file_size,bytes_read,file_size,error_count); } fprintf(stderr,"total bytes read: %d\n",total_bytes_read); } #endif if (reset_outfilename) { outfile = fopen(reset_outfilename,"wb"); if (!outfile) { fprintf(stderr,"failed to open %s for output\n",reset_outfilename); return 1; } /* slap on a .wav header */ make_wav_header((uint8_t*)buf, len, s->sample_rate, s->channels); fwrite(buf,1,0x2c,outfile); reset_vgmstream(s); /* these manipulations are undone by reset */ /* force only if there aren't already loop points */ if (force_loop && !s->loop_flag) { /* this requires a bit more messing with the VGMSTREAM than I'm * comfortable with... */ s->loop_flag=1; s->loop_start_sample=0; s->loop_end_sample=s->num_samples; s->loop_ch=calloc(s->channels,sizeof(VGMSTREAMCHANNEL)); } /* force even if there are loop points */ if (really_force_loop) { if (!s->loop_flag) s->loop_ch=calloc(s->channels,sizeof(VGMSTREAMCHANNEL)); s->loop_flag=1; s->loop_start_sample=0; s->loop_end_sample=s->num_samples; } if (ignore_loop) s->loop_flag=0; /* decode */ for (i=0;ilen) toget=len-i; render_vgmstream(buf,toget,s); if (s->loop_flag && fade_samples > 0) { int samples_into_fade = i - (len - fade_samples); if (samples_into_fade + toget > 0) { int j,k; for (j=0;j 0) { double fadedness = (double)(fade_samples-samples_into_fade)/fade_samples; for (k=0;kchannels;k++) { buf[j*s->channels+k] = buf[j*s->channels+k]*fadedness; } } } } } /* do proper little endian samples */ swap_samples_le(buf,s->channels*toget); if (only_stereo != -1) { int j; for (j=0;jchannels+(only_stereo*2),sizeof(sample),2,outfile); } } else { fwrite(buf,sizeof(sample)*s->channels,toget,outfile); } } fclose(outfile); outfile = NULL; } close_vgmstream(s); free(buf); return 0; } /** * make a header for PCM .wav * buffer must be 0x2c bytes */ static void make_wav_header(uint8_t * buf, int32_t sample_count, int32_t sample_rate, int channels) { size_t bytecount; bytecount = sample_count*channels*sizeof(sample); memcpy(buf+0, "RIFF", 4); /* RIFF header */ put_32bitLE(buf+4, (int32_t)(bytecount+0x2c-8)); /* size of RIFF */ memcpy(buf+8, "WAVE", 4); /* WAVE header */ memcpy(buf+0xc, "fmt ", 4); /* WAVE fmt chunk */ put_32bitLE(buf+0x10, 0x10); /* size of WAVE fmt chunk */ put_16bitLE(buf+0x14, 1); /* compression code 1=PCM */ put_16bitLE(buf+0x16, channels); /* channel count */ put_32bitLE(buf+0x18, sample_rate); /* sample rate */ put_32bitLE(buf+0x1c, sample_rate*channels*sizeof(sample)); /* bytes per second */ put_16bitLE(buf+0x20, (int16_t)(channels*sizeof(sample))); /* block align */ put_16bitLE(buf+0x22, sizeof(sample)*8); /* significant bits per sample */ /* PCM has no extra format bytes, so we don't even need to specify a count */ memcpy(buf+0x24, "data", 4); /* WAVE data chunk */ put_32bitLE(buf+0x28, (int32_t)bytecount); /* size of WAVE data chunk */ } static void make_smpl_chunk(uint8_t * buf, int32_t loop_start, int32_t loop_end) { int i; memcpy(buf+0, "smpl", 4);/* header */ put_32bitLE(buf+4, 0x3c);/* size */ for (i = 0; i < 7; i++) put_32bitLE(buf+8 + i * 4, 0); put_32bitLE(buf+36, 1); for (i = 0; i < 3; i++) put_32bitLE(buf+40 + i * 4, 0); put_32bitLE(buf+52, loop_start); put_32bitLE(buf+56, loop_end); put_32bitLE(buf+60, 0); put_32bitLE(buf+64, 0); }