diff --git a/src/Makefile b/src/Makefile index f70c72ee..48081030 100644 --- a/src/Makefile +++ b/src/Makefile @@ -20,7 +20,9 @@ META_OBJS=meta/adx_header.o \ meta/halpst.o \ meta/nds_strm.o \ meta/ngc_adpdtk.o \ - meta/rsf.o + meta/rsf.o \ + meta/rs03.o \ + meta/ngc_dsp_std.o OBJECTS=vgmstream.o streamfile.o util.o $(CODING_OBJS) $(LAYOUT_OBJS) $(META_OBJS) diff --git a/src/meta/ngc_dsp_std.c b/src/meta/ngc_dsp_std.c new file mode 100644 index 00000000..0f09b189 --- /dev/null +++ b/src/meta/ngc_dsp_std.c @@ -0,0 +1,97 @@ +#include "ngc_dsp_std.h" +#include "../coding/ngc_dsp_decoder.h" +#include "../util.h" + +/* The standard .dsp */ + +VGMSTREAM * init_vgmstream_ngc_dsp_std(const char * const filename) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * infile = NULL; + + int loop_flag; + off_t start_offset; + int i; + + /* check extension, case insensitive */ + if (strcasecmp("dsp",filename_extension(filename))) goto fail; + + /* try to open the file for header reading */ + infile = open_streamfile(filename); + if (!infile) goto fail; + + start_offset = 0x60; + + /* check initial predictor/scale */ + if (read_16bitBE(0x3e,infile) != (uint8_t)read_8bit(start_offset,infile)) + goto fail; + + /* check type==0 and gain==0 */ + if (read_16bitBE(0x0e,infile) || read_16bitBE(0x3c,infile)) + goto fail; + + loop_flag = read_16bitBE(0xc,infile); + if (loop_flag) { + off_t loop_off; + /* check loop predictor/scale */ + loop_off = read_32bitBE(0x10,infile)/16*8; + if (read_16bitBE(0x44,infile) != (uint8_t)read_8bit(start_offset+loop_off,infile)) + goto fail; + } + + /* compare num_samples with nibble count */ + fprintf(stderr,"num samples (literal): %d\n",read_32bitBE(0,infile)); + fprintf(stderr,"num samples (nibbles): %d\n",dsp_nibbles_to_samples(read_32bitBE(4,infile))); + + /* build the VGMSTREAM */ + + + vgmstream = allocate_vgmstream(1,loop_flag); + if (!vgmstream) goto fail; + + /* fill in the vital statistics */ + vgmstream->num_samples = read_32bitBE(0,infile); + vgmstream->sample_rate = read_32bitBE(8,infile); + + /* + vgmstream->loop_start_sample = + read_32bitBE(0x10,infile)/16*14; + vgmstream->loop_end_sample = + read_32bitBE(0x14,infile)/16*14; + */ + vgmstream->loop_start_sample = dsp_nibbles_to_samples( + read_32bitBE(0x10,infile)); + vgmstream->loop_end_sample = dsp_nibbles_to_samples( + read_32bitBE(0x14,infile))+1; + /* don't know why, but it does happen*/ + if (vgmstream->loop_end_sample > vgmstream->num_samples) + vgmstream->loop_end_sample = vgmstream->num_samples; + + start_offset = 0x60; + + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + vgmstream->meta_type = meta_DSP_STD; + + for (i=0;i<16;i++) + vgmstream->ch[0].adpcm_coef[i]=read_16bitBE(0x1c+i*2,infile); + + close_streamfile(infile); infile=NULL; + + /* open the file for reading */ + { + vgmstream->ch[0].streamfile = open_streamfile(filename); + + if (!vgmstream->ch[0].streamfile) goto fail; + + vgmstream->ch[0].channel_start_offset= + vgmstream->ch[0].offset=start_offset; + } + + return vgmstream; + + /* clean up anything we may have opened */ +fail: + if (infile) close_streamfile(infile); + if (vgmstream) close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/ngc_dsp_std.h b/src/meta/ngc_dsp_std.h new file mode 100644 index 00000000..c03ba5cd --- /dev/null +++ b/src/meta/ngc_dsp_std.h @@ -0,0 +1,8 @@ +#include "../vgmstream.h" + +#ifndef _NGC_DSP_STD_H +#define _NGC_DSP_STD_H + +VGMSTREAM * init_vgmstream_ngc_dsp_std(const char * const filename); + +#endif diff --git a/src/meta/rs03.c b/src/meta/rs03.c new file mode 100644 index 00000000..f6503b3d --- /dev/null +++ b/src/meta/rs03.c @@ -0,0 +1,87 @@ +#include "rs03.h" +#include "../coding/ngc_dsp_decoder.h" +#include "../util.h" + +/* .dsp w/ RS03 header - from Metroid Prime 2 */ + +VGMSTREAM * init_vgmstream_rs03(const char * const filename) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * infile = NULL; + + int channel_count; + int loop_flag; + off_t start_offset; + int i; + + /* check extension, case insensitive */ + if (strcasecmp("dsp",filename_extension(filename))) goto fail; + + /* try to open the file for header reading */ + infile = open_streamfile(filename); + if (!infile) goto fail; + + /* check header */ + if ((uint32_t)read_32bitBE(0,infile)!=0x52530003) + goto fail; + + channel_count = read_32bitBE(4,infile); + if (channel_count != 2) goto fail; + + /* build the VGMSTREAM */ + + loop_flag = read_16bitBE(0x14,infile); + + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + /* fill in the vital statistics */ + vgmstream->num_samples = read_32bitBE(8,infile); + vgmstream->sample_rate = read_32bitBE(0xc,infile); + + vgmstream->loop_start_sample = dsp_nibbles_to_samples( + read_32bitBE(0x18,infile)); + vgmstream->loop_end_sample = + dsp_nibbles_to_samples( + read_32bitBE(0x1c,infile)*2+16); + + start_offset = 0x60; + + vgmstream->coding_type = coding_NGC_DSP; + if (channel_count == 2) { + vgmstream->layout_type = layout_interleave_shortblock; + vgmstream->interleave_block_size = 0x8f00; + vgmstream->interleave_smallblock_size = (((get_streamfile_size(infile)-start_offset)%(0x8f00*2))/2+7)/8*8; + } else + vgmstream->layout_type = layout_none; + vgmstream->meta_type = meta_DSP_RS03; + + for (i=0;i<16;i++) + vgmstream->ch[0].adpcm_coef[i]=read_16bitBE(0x20+i*2,infile); + if (channel_count==2) { + for (i=0;i<16;i++) + vgmstream->ch[1].adpcm_coef[i]=read_16bitBE(0x40+i*2,infile); + } + + close_streamfile(infile); infile=NULL; + + /* open the file for reading by each channel */ + { + int i; + for (i=0;ich[i].streamfile = open_streamfile_buffer(filename,0x8f00); + + if (!vgmstream->ch[i].streamfile) goto fail; + + vgmstream->ch[i].channel_start_offset= + vgmstream->ch[i].offset= + start_offset+0x8f00*i; + } + } + + return vgmstream; + + /* clean up anything we may have opened */ +fail: + if (infile) close_streamfile(infile); + if (vgmstream) close_vgmstream(vgmstream); + return NULL; +} diff --git a/src/meta/rs03.h b/src/meta/rs03.h new file mode 100644 index 00000000..5e86a788 --- /dev/null +++ b/src/meta/rs03.h @@ -0,0 +1,8 @@ +#include "../vgmstream.h" + +#ifndef _RS03_H +#define _RS03_H + +VGMSTREAM * init_vgmstream_rs03(const char * const filename); + +#endif diff --git a/src/vgmstream.c b/src/vgmstream.c index a3d0215d..b1ff3cd1 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1,4 +1,6 @@ #include +#include +#include #include "vgmstream.h" #include "meta/adx_header.h" #include "meta/brstm.h" @@ -9,6 +11,8 @@ #include "meta/afc_header.h" #include "meta/ast.h" #include "meta/halpst.h" +#include "meta/rs03.h" +#include "meta/ngc_dsp_std.h" #include "layout/interleave.h" #include "layout/nolayout.h" #include "layout/blocked.h" @@ -24,7 +28,7 @@ * List of functions that will recognize files. These should correspond pretty * directly to the metadata types */ -#define INIT_VGMSTREAM_FCNS 9 +#define INIT_VGMSTREAM_FCNS 11 VGMSTREAM * (*init_vgmstream_fcns[INIT_VGMSTREAM_FCNS])(const char * const) = { init_vgmstream_adx, init_vgmstream_brstm, @@ -35,22 +39,42 @@ VGMSTREAM * (*init_vgmstream_fcns[INIT_VGMSTREAM_FCNS])(const char * const) = { init_vgmstream_afc, init_vgmstream_ast, init_vgmstream_halpst, + init_vgmstream_rs03, + init_vgmstream_ngc_dsp_std, }; -/* format detection and VGMSTREAM setup */ + +/* format detection and VGMSTREAM setup, uses default parameters */ VGMSTREAM * init_vgmstream(const char * const filename) { + return init_vgmstream_internal(filename, + 1 /* do dual file detection */ + ); +} + +/* internal version with all parameters */ +VGMSTREAM * init_vgmstream_internal(const char * const filename, int do_dfs) { int i; /* try a series of formats, see which works */ for (i=0;isample_rate)) { close_vgmstream(vgmstream); continue; } + + /* dual file stereo */ + if (do_dfs && vgmstream->meta_type == meta_DSP_STD && vgmstream->channels == 1) { + try_dual_file_stereo(vgmstream, filename); + } + /* save start things so we can restart for seeking */ + /* TODO: we may need to save other things here */ memcpy(vgmstream->start_ch,vgmstream->ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); vgmstream->start_block_offset = vgmstream->current_block_offset; return vgmstream; @@ -318,6 +342,12 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { vgmstream->loop_ch[i].adpcm_history2_32 = vgmstream->ch[i].adpcm_history2_32; } */ + int i; + for (i=0;ichannels;i++) { + fprintf(stderr,"ch%d hist: %04x %04x loop hist: %04x %04x\n",i, + vgmstream->ch[i].adpcm_history1_16,vgmstream->ch[i].adpcm_history2_16, + vgmstream->loop_ch[i].adpcm_history1_16,vgmstream->loop_ch[i].adpcm_history2_16); + } /* restore! */ memcpy(vgmstream->ch,vgmstream->loop_ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); vgmstream->current_sample=vgmstream->loop_sample; @@ -489,8 +519,165 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { case meta_HALPST: snprintf(temp,TEMPSIZE,"HALPST header"); break; + case meta_DSP_RS03: + snprintf(temp,TEMPSIZE,"Retro Studios RS03 header"); + break; + case meta_DSP_STD: + snprintf(temp,TEMPSIZE,"Standard NGC DSP header"); + break; default: snprintf(temp,TEMPSIZE,"THEY SHOULD HAVE SENT A POET"); } concatn(length,desc,temp); } + +/* */ +#define DFS_PAIR_COUNT 4 +const char * const dfs_pairs[DFS_PAIR_COUNT][2] = { + {"L","R"}, + {"l","r"}, + {"_0","_1"}, + {"left","right"}, +}; + +void try_dual_file_stereo(VGMSTREAM * opened_stream, const char * const filename) { + char * filename2; + char * ext; + int dfs_name= -1; /*-1=no stereo, 0=opened_stream is left, 1=opened_stream is right */ + VGMSTREAM * new_stream = NULL; + int i,j; + + if (opened_stream->channels != 1) return; + + /* we need at least a base and a name ending to replace */ + if (strlen(filename)<2) return; + + /* one extra for terminator, one for possible extra character (left>=right) */ + filename2 = malloc(strlen(filename)+2); + + if (!filename2) return; + + strcpy(filename2,filename); + + /* look relative to the extension; */ + ext = (char *)filename_extension(filename2); + + /* we treat the . as part of the extension */ + if (ext-filename2 >= 1 && ext[-1]=='.') ext--; + + for (i=0; dfs_name==-1 && ichannels == 1 && + /* we have seen legitimate pairs where these are off by one... */ + /* but leaving it commented out until I can find those and recheck */ + /* abs(new_stream->num_samples-opened_stream->num_samples <= 1) && */ + new_stream->num_samples == opened_stream->num_samples && + new_stream->sample_rate == opened_stream->sample_rate && + new_stream->meta_type == opened_stream->meta_type && + new_stream->coding_type == opened_stream->coding_type && + new_stream->layout_type == opened_stream->layout_type && + new_stream->loop_flag == opened_stream->loop_flag && + /* check these even if there is no loop, because they should then + * be zero in both */ + new_stream->loop_start_sample == opened_stream->loop_start_sample && + new_stream->loop_end_sample == opened_stream->loop_end_sample && + /* check even if the layout doesn't use them, because it is + * difficult to determine when it does, and they should be zero + * otherwise, anyway */ + new_stream->interleave_block_size == opened_stream->interleave_block_size && + new_stream->interleave_smallblock_size == opened_stream->interleave_smallblock_size && + new_stream->start_block_offset == opened_stream->start_block_offset) { + /* We seem to have a usable, matching file. Merge in the second channel. */ + VGMSTREAMCHANNEL * new_chans; + VGMSTREAMCHANNEL * new_loop_chans = NULL; + VGMSTREAMCHANNEL * new_start_chans = NULL; + + /* build the channels */ + new_chans = calloc(2,sizeof(VGMSTREAMCHANNEL)); + if (!new_chans) goto fail; + + memcpy(&new_chans[dfs_name],&opened_stream->ch[0],sizeof(VGMSTREAMCHANNEL)); + memcpy(&new_chans[dfs_name^1],&new_stream->ch[0],sizeof(VGMSTREAMCHANNEL)); + + /* loop and start will be initialized later, we just need to + * allocate them here */ + new_start_chans = calloc(2,sizeof(VGMSTREAMCHANNEL)); + if (!new_start_chans) { + free(new_chans); + goto fail; + } + + if (opened_stream->loop_ch) { + new_loop_chans = calloc(2,sizeof(VGMSTREAMCHANNEL)); + if (!new_loop_chans) { + free(new_chans); + free(new_start_chans); + goto fail; + } + } + + /* remove the existing structures */ + /* not using close_vgmstream as that would close the file */ + free(opened_stream->ch); + free(new_stream->ch); + + free(opened_stream->start_ch); + free(new_stream->start_ch); + + if (opened_stream->loop_ch) { + free(opened_stream->loop_ch); + free(new_stream->loop_ch); + } + + /* fill in the new structures */ + opened_stream->ch = new_chans; + opened_stream->start_ch = new_start_chans; + opened_stream->loop_ch = new_loop_chans; + + /* stereo! */ + opened_stream->channels = 2; + + /* discard the second VGMSTREAM */ + free(new_stream); + } + + if (filename2) free(filename2); + return; + +fail: + if (filename2) free(filename2); +} diff --git a/src/vgmstream.h b/src/vgmstream.h index ab5d55f2..1e2febb3 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -123,7 +123,7 @@ typedef struct { * isn't closed twice, but also that everything is deallocated. Generally * a channel should only have one STREAMFILE in its lifetime. */ - VGMSTREAMCHANNEL * start_ch; /* coptes of channel status as they were at the beginning of the stream */ + VGMSTREAMCHANNEL * start_ch; /* copies of channel status as they were at the beginning of the stream */ VGMSTREAMCHANNEL * loop_ch; /* copies of channel status as they were at the loop point */ /* layout-specific */ @@ -152,6 +152,10 @@ typedef struct { /* do format detection, return pointer to a usable VGMSTREAM, or NULL on failure */ VGMSTREAM * init_vgmstream(const char * const filename); +/* internal vgmstream that takes parameters the library user shouldn't have to know + * about */ +VGMSTREAM * init_vgmstream_internal(const char * const filename, int do_dfs); + /* allocate a VGMSTREAM and channel stuff */ VGMSTREAM * allocate_vgmstream(int channel_count, int looped); @@ -183,7 +187,15 @@ int vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMST * Returns 1 if loop was done. */ int vgmstream_do_loop(VGMSTREAM * vgmstream); +/* Write a description of the stream into array pointed by desc, + * which must be length bytes long. Will always be null-terminated if length > 0 + */ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length); +/* See if there is a second file which may be the second channel, given + * already opened mono opened_stream which was opened from filename. + * If a suitable file is found, open it and change opened_stream to a + * stereo stream. */ +void try_dual_file_stereo(VGMSTREAM * opened_stream, const char * const filename); #endif