mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 16:30:54 +01:00
add .stm
git-svn-id: https://vgmstream.svn.sourceforge.net/svnroot/vgmstream@108 51a99a44-fe44-0410-b1ba-c3e57ba2b86b
This commit is contained in:
parent
014d22b5eb
commit
bad669a3f9
@ -43,6 +43,7 @@ File types supported by this version of vgmstream:
|
||||
- standard, with dual file stereo
|
||||
- RS03
|
||||
- Cstr
|
||||
- .stm
|
||||
- .gcsw (16 bit PCM)
|
||||
- .ads/.ss2 (PSX ADPCM)
|
||||
- .npsf (PSX ADPCM)
|
||||
|
@ -25,6 +25,8 @@ VGMSTREAM * init_vgmstream_ngc_adpdtk(const char * const filename);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ngc_dsp_std(const char * const filename);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ngc_dsp_stm(const char * const filename);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_ads(const char * const filename);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_npsf(const char * const filename);
|
||||
|
@ -152,9 +152,187 @@ VGMSTREAM * init_vgmstream_ngc_dsp_std(const char * const filename) {
|
||||
|
||||
return vgmstream;
|
||||
|
||||
/* clean up anything we may have opened */
|
||||
fail:
|
||||
/* clean up anything we may have opened */
|
||||
if (infile) close_streamfile(infile);
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Some very simple stereo variants of standard dsp just use the standard header
|
||||
* twice and add interleave, or just concatenate the channels. We'll support
|
||||
* them all here.
|
||||
* Note that Cstr isn't here, despite using the form of the standard header,
|
||||
* because its loop values are wacky. */
|
||||
|
||||
/* .stm
|
||||
* Used in Paper Mario 2, Fire Emblem: Path of Radiance, Cubivore
|
||||
* I suspected that this was an Intelligent Systems format, but its use in
|
||||
* Cubivore calls that into question. */
|
||||
VGMSTREAM * init_vgmstream_ngc_dsp_stm(const char * const filename) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE * infile = NULL;
|
||||
|
||||
struct dsp_header ch0_header, ch1_header;
|
||||
int i;
|
||||
int stm_header_sample_rate;
|
||||
int channel_count;
|
||||
const off_t start_offset = 0x100;
|
||||
off_t first_channel_size;
|
||||
off_t second_channel_start;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* to avoid collision with Scream Tracker 2 Modules, also ending in .stm
|
||||
* and supported by default in Winamp, it was policy in the old days to
|
||||
* rename these files to .dsp */
|
||||
if (strcasecmp("stm",filename_extension(filename)) &&
|
||||
strcasecmp("dsp",filename_extension(filename))) goto fail;
|
||||
|
||||
/* try to open the file for header reading */
|
||||
infile = open_streamfile(filename);
|
||||
if (!infile) goto fail;
|
||||
|
||||
/* check intro magic */
|
||||
if (read_16bitBE(0, infile) != 0x0200) goto fail;
|
||||
|
||||
channel_count = read_32bitBE(4, infile);
|
||||
/* only stereo and mono are known */
|
||||
if (channel_count != 1 && channel_count != 2) goto fail;
|
||||
|
||||
first_channel_size = read_32bitBE(8, infile);
|
||||
/* this is bad rounding, wastes space, but it looks like that's what's
|
||||
* used */
|
||||
second_channel_start = ((start_offset+first_channel_size)+0x20)/0x20*0x20;
|
||||
|
||||
/* an additional check */
|
||||
stm_header_sample_rate = (uint16_t)read_16bitBE(2, infile);
|
||||
|
||||
/* read the DSP headers */
|
||||
if (read_dsp_header(&ch0_header, 0x40, infile)) goto fail;
|
||||
if (channel_count == 2) {
|
||||
if (read_dsp_header(&ch1_header, 0xa0, infile)) goto fail;
|
||||
}
|
||||
|
||||
/* checks for fist channel */
|
||||
{
|
||||
if (ch0_header.sample_rate != stm_header_sample_rate) goto fail;
|
||||
|
||||
/* check initial predictor/scale */
|
||||
if (ch0_header.initial_ps != (uint8_t)read_8bit(start_offset, infile))
|
||||
goto fail;
|
||||
|
||||
/* check type==0 and gain==0 */
|
||||
if (ch0_header.format || ch0_header.gain)
|
||||
goto fail;
|
||||
|
||||
if (ch0_header.loop_flag) {
|
||||
off_t loop_off;
|
||||
/* check loop predictor/scale */
|
||||
loop_off = ch0_header.loop_start_offset/16*8;
|
||||
if (ch0_header.loop_ps != (uint8_t)read_8bit(start_offset+loop_off,infile))
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* checks for second channel */
|
||||
if (channel_count == 2) {
|
||||
if (ch1_header.sample_rate != stm_header_sample_rate) goto fail;
|
||||
|
||||
/* check for agreement with first channel header */
|
||||
if (
|
||||
ch0_header.sample_count != ch1_header.sample_count ||
|
||||
ch0_header.nibble_count != ch1_header.nibble_count ||
|
||||
ch0_header.loop_flag != ch1_header.loop_flag ||
|
||||
ch0_header.loop_start_offset != ch1_header.loop_start_offset ||
|
||||
ch0_header.loop_end_offset != ch1_header.loop_end_offset
|
||||
) goto fail;
|
||||
|
||||
/* check initial predictor/scale */
|
||||
if (ch1_header.initial_ps != (uint8_t)read_8bit(second_channel_start, infile))
|
||||
goto fail;
|
||||
|
||||
/* check type==0 and gain==0 */
|
||||
if (ch1_header.format || ch1_header.gain)
|
||||
goto fail;
|
||||
|
||||
if (ch1_header.loop_flag) {
|
||||
off_t loop_off;
|
||||
/* check loop predictor/scale */
|
||||
loop_off = ch1_header.loop_start_offset/16*8;
|
||||
/*printf("loop_start_offset=%x\nloop_ps=%x\nloop_off=%x\n",ch1_header.loop_start_offset,ch1_header.loop_ps,second_channel_start+loop_off);*/
|
||||
if (ch1_header.loop_ps != (uint8_t)read_8bit(second_channel_start+loop_off,infile))
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
|
||||
vgmstream = allocate_vgmstream(channel_count, ch0_header.loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
vgmstream->num_samples = ch0_header.sample_count;
|
||||
vgmstream->sample_rate = ch0_header.sample_rate;
|
||||
|
||||
vgmstream->loop_start_sample = dsp_nibbles_to_samples(
|
||||
ch0_header.loop_start_offset);
|
||||
vgmstream->loop_end_sample = dsp_nibbles_to_samples(
|
||||
ch0_header.loop_end_offset)+1;
|
||||
|
||||
/* don't know why, but it does happen*/
|
||||
if (vgmstream->loop_end_sample > vgmstream->num_samples)
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->meta_type = meta_DSP_STM;
|
||||
|
||||
/* coeffs */
|
||||
for (i=0;i<16;i++)
|
||||
vgmstream->ch[0].adpcm_coef[i] = ch0_header.coef[i];
|
||||
|
||||
/* initial history */
|
||||
/* always 0 that I've ever seen, but for completeness... */
|
||||
vgmstream->ch[0].adpcm_history1_16 = ch0_header.initial_hist1;
|
||||
vgmstream->ch[0].adpcm_history2_16 = ch0_header.initial_hist2;
|
||||
|
||||
if (channel_count == 2) {
|
||||
/* coeffs */
|
||||
for (i=0;i<16;i++)
|
||||
vgmstream->ch[1].adpcm_coef[i] = ch1_header.coef[i];
|
||||
|
||||
/* initial history */
|
||||
/* always 0 that I've ever seen, but for completeness... */
|
||||
vgmstream->ch[1].adpcm_history1_16 = ch1_header.initial_hist1;
|
||||
vgmstream->ch[1].adpcm_history2_16 = ch1_header.initial_hist2;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (channel_count == 2) {
|
||||
vgmstream->ch[1].streamfile = open_streamfile(filename);
|
||||
|
||||
if (!vgmstream->ch[1].streamfile) goto fail;
|
||||
|
||||
vgmstream->ch[1].channel_start_offset=
|
||||
vgmstream->ch[1].offset=start_offset+second_channel_start;
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
/* clean up anything we may have opened */
|
||||
if (infile) close_streamfile(infile);
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -15,27 +15,28 @@
|
||||
* List of functions that will recognize files. These should correspond pretty
|
||||
* directly to the metadata types
|
||||
*/
|
||||
#define INIT_VGMSTREAM_FCNS 19
|
||||
#define INIT_VGMSTREAM_FCNS 20
|
||||
VGMSTREAM * (*init_vgmstream_fcns[INIT_VGMSTREAM_FCNS])(const char * const) = {
|
||||
init_vgmstream_adx,
|
||||
init_vgmstream_brstm,
|
||||
init_vgmstream_nds_strm,
|
||||
init_vgmstream_agsc,
|
||||
init_vgmstream_ngc_adpdtk,
|
||||
init_vgmstream_rsf,
|
||||
init_vgmstream_afc,
|
||||
init_vgmstream_ast,
|
||||
init_vgmstream_halpst,
|
||||
init_vgmstream_rs03,
|
||||
init_vgmstream_ngc_dsp_std,
|
||||
init_vgmstream_Cstr,
|
||||
init_vgmstream_gcsw,
|
||||
init_vgmstream_ps2_ads,
|
||||
init_vgmstream_ps2_npsf,
|
||||
init_vgmstream_rwsd,
|
||||
init_vgmstream_cdxa,
|
||||
init_vgmstream_ps2_rxw,
|
||||
init_vgmstream_ps2_int,
|
||||
init_vgmstream_adx, /* 0 */
|
||||
init_vgmstream_brstm, /* 1 */
|
||||
init_vgmstream_nds_strm, /* 2 */
|
||||
init_vgmstream_agsc, /* 3 */
|
||||
init_vgmstream_ngc_adpdtk, /* 4 */
|
||||
init_vgmstream_rsf, /* 5 */
|
||||
init_vgmstream_afc, /* 6 */
|
||||
init_vgmstream_ast, /* 7 */
|
||||
init_vgmstream_halpst, /* 8 */
|
||||
init_vgmstream_rs03, /* 9 */
|
||||
init_vgmstream_ngc_dsp_std, /* 10 */
|
||||
init_vgmstream_Cstr, /* 11 */
|
||||
init_vgmstream_gcsw, /* 12 */
|
||||
init_vgmstream_ps2_ads, /* 13 */
|
||||
init_vgmstream_ps2_npsf, /* 14 */
|
||||
init_vgmstream_rwsd, /* 15 */
|
||||
init_vgmstream_cdxa, /* 16 */
|
||||
init_vgmstream_ps2_rxw, /* 17 */
|
||||
init_vgmstream_ps2_int, /* 18 */
|
||||
init_vgmstream_ngc_dsp_stm, /* 19 */
|
||||
};
|
||||
|
||||
|
||||
@ -592,7 +593,10 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
|
||||
snprintf(temp,TEMPSIZE,"RXWS File (Arc The Lad)");
|
||||
break;
|
||||
case meta_PS2_RAW:
|
||||
snprintf(temp,TEMPSIZE,"RAW Interleaved PCM");
|
||||
snprintf(temp,TEMPSIZE,"assumed RAW Interleaved PCM by .int extension");
|
||||
break;
|
||||
case meta_DSP_STM:
|
||||
snprintf(temp,TEMPSIZE,"Nintendo STM header");
|
||||
break;
|
||||
default:
|
||||
snprintf(temp,TEMPSIZE,"THEY SHOULD HAVE SENT A POET");
|
||||
|
Loading…
Reference in New Issue
Block a user