diff --git a/src/coding/coding.h b/src/coding/coding.h index 22c08405..92d51428 100644 --- a/src/coding/coding.h +++ b/src/coding/coding.h @@ -24,9 +24,8 @@ void decode_wv6_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci void decode_ms_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); -void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo); void decode_xbox_ima_mch(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_xbox_ima_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); void decode_nds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_dat4_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); void decode_rad_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); diff --git a/src/coding/ima_decoder.c b/src/coding/ima_decoder.c index da3ff67b..9da012cb 100644 --- a/src/coding/ima_decoder.c +++ b/src/coding/ima_decoder.c @@ -430,22 +430,26 @@ void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * o /* ************************************ */ /* MS-IMA with fixed frame size, and outputs an even number of samples per frame (skips last nibble). - * Defined in Xbox's SDK. Multichannel interleaves 2ch*N/2, or 1ch*N with odd num_channels - * (seen in some Koei .wav, could be simplified as interleaved stereo) --unsure if official. */ -void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { - int i, sample_count = 0; + * Defined in Xbox's SDK. Usable in mono or stereo modes (both suitable for interleaved multichannel). */ +void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) { + int i, frames_in, sample_pos = 0, block_samples, frame_size; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; + off_t frame_offset; - /* internal interleave (fixed size), mixed channels */ - int block_samples = (0x24-0x4) * 2; + /* external interleave (fixed size), stereo/mono */ + block_samples = (0x24 - 0x4) * 2; + frames_in = first_sample / block_samples; first_sample = first_sample % block_samples; + frame_size = is_stereo ? 0x24*2 : 0x24; - /* normal header (hist+step+reserved), per stereo/mono channel in blocks */ + frame_offset = stream->offset + frame_size*frames_in; + + /* normal header (hist+step+reserved), stereo/mono */ if (first_sample == 0) { - off_t header_offset = (channelspacing & 1) ? - stream->offset + 0x24*(channel) + 0x00: - stream->offset + 0x48*(channel/2) + 0x04*(channel%2); + off_t header_offset = is_stereo ? + frame_offset + 0x04*(channel % 2) : + frame_offset + 0x00; hist1 = read_16bitLE(header_offset+0x00,stream->streamfile); step_index = read_8bit(header_offset+0x02,stream->streamfile); @@ -453,32 +457,27 @@ void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac if (step_index > 88) step_index=88; /* write header sample (even samples per block, skips last nibble) */ - outbuf[sample_count] = (short)(hist1); - sample_count += channelspacing; + outbuf[sample_pos] = (short)(hist1); + sample_pos += channelspacing; first_sample += 1; samples_to_do -= 1; } - /* decode nibbles (layout: alternates 4 bytes/4*2 nibbles per channel, in stereo blocks) */ + /* decode nibbles (layout: straight in mono or 4 bytes per channel in stereo) */ for (i = first_sample; i < first_sample + samples_to_do; i++) { - off_t byte_offset = (channelspacing & 1) ? - (stream->offset + 0x24*(channel) + 0x04) + (i-1)/2: - (stream->offset + 0x48*(channel/2) + 0x04*2) + 0x04*(channel%2) + 0x04*2*((i-1)/8) + ((i-1)%8)/2; - int nibble_shift = ((i-1)&1?4:0); /* low nibble first */ + off_t byte_offset = is_stereo ? + frame_offset + 0x04*2 + 0x04*(channel % 2) + 0x04*2*((i-1)/8) + ((i-1)%8)/2 : + frame_offset + 0x04 + (i-1)/2; + int nibble_shift = (!((i-1)&1) ? 0:4); /* low first */ - /* must skip last nibble per official decoder, probably not needed though */ + /* must skip last nibble per spec, rarely needed though (ex. Gauntlet Dark Legacy) */ if (i < block_samples) { std_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index); - outbuf[sample_count] = (short)(hist1); - sample_count += channelspacing; + outbuf[sample_pos] = (short)(hist1); + sample_pos += channelspacing; } } - /* internal interleave: increment offset on complete frame */ - if (i == block_samples) { - stream->offset += 0x24*channelspacing; - } - stream->adpcm_history1_32 = hist1; stream->adpcm_step_index = step_index; } @@ -527,50 +526,6 @@ void decode_xbox_ima_mch(VGMSTREAMCHANNEL * stream, sample * outbuf, int channel stream->adpcm_step_index = step_index; } -/* Mono XBOX-IMA ADPCM, used for interleave. Also defined in Xbox's SDK. */ -void decode_xbox_ima_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { - int i, sample_count = 0, num_frame; - int32_t hist1 = stream->adpcm_history1_32; - int step_index = stream->adpcm_step_index; - - /* external interleave (fixed size), mono */ - int block_samples = (0x24 - 0x4) * 2; - num_frame = first_sample / block_samples; - first_sample = first_sample % block_samples; - - /* normal header (hist+step+reserved), single channel */ - if (first_sample == 0) { - off_t header_offset = stream->offset + 0x24*num_frame; - - hist1 = read_16bitLE(header_offset+0x00,stream->streamfile); - step_index = read_8bit(header_offset+0x02,stream->streamfile); - if (step_index < 0) step_index=0; - if (step_index > 88) step_index=88; - - /* write header sample (even samples per block, skips last nibble) */ - outbuf[sample_count] = (short)(hist1); - sample_count += channelspacing; - first_sample += 1; - samples_to_do -= 1; - } - - /* decode nibbles (layout: all nibbles from one channel) */ - for (i = first_sample; i < first_sample + samples_to_do; i++) { - off_t byte_offset = (stream->offset + 0x24*num_frame + 0x4) + (i-1)/2; - int nibble_shift = ((i-1)&1?4:0); /* low nibble first */ - - /* must skip last nibble per spec, rarely needed though (ex. Gauntlet Dark Legacy) */ - if (i < block_samples) { - std_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index); - outbuf[sample_count] = (short)(hist1); - sample_count += channelspacing; - } - } - - stream->adpcm_history1_32 = hist1; - stream->adpcm_step_index = step_index; -} - /* Similar to MS-IMA with even number of samples, header sample is not written (setup only). * Apparently clamps to -32767 unlike standard's -32768 (probably not noticeable). * Info here: http://problemkaputt.de/gbatek.htm#dssoundnotes */ diff --git a/src/meta/mss.c b/src/meta/mss.c index e3596dae..d04a30d2 100644 --- a/src/meta/mss.c +++ b/src/meta/mss.c @@ -9,46 +9,48 @@ VGMSTREAM * init_vgmstream_mss(STREAMFILE *streamFile) { size_t data_size; int loop_flag = 0, channel_count; - /* check extension, case insensitive */ - if (!check_extensions(streamFile, "mss")) goto fail; - - /* check header */ + /* checks */ + if (!check_extensions(streamFile, "mss")) + goto fail; if (read_32bitBE(0x00,streamFile) != 0x4D435353) /* "MCSS" */ goto fail; loop_flag = 0; channel_count = read_16bitLE(0x16,streamFile); - if (read_32bitLE(0x18,streamFile) == 0x4800 && channel_count > 2) - channel_count = 2; //todo add support for interleave stereo streams + + /* 0x04: version? (always 0x00000100 LE) */ + start_offset = read_32bitLE(0x08,streamFile); + data_size = read_32bitLE(0x0c,streamFile); + /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - /* fill in the vital statistics */ - /* 0x04: version? (always 0x00000100 LE) */ - start_offset = read_32bitLE(0x08,streamFile); - data_size = read_32bitLE(0x0c,streamFile); vgmstream->sample_rate = read_32bitLE(0x10,streamFile); /* 0x14(1): 1/2/3/4 if 2/4/6/8ch, 0x15(1): 0/1?, 0x16: ch */ + vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = read_32bitLE(0x18,streamFile); vgmstream->num_samples = read_32bitLE(0x1C,streamFile); vgmstream->meta_type = meta_MSS; /* no other way to know */ if (vgmstream->interleave_block_size == 0x4800) { - /* interleaved stereo streams (2ch 0x4800 + 2ch 0x4800 = 4ch) */ vgmstream->coding_type = coding_XBOX_IMA; - vgmstream->layout_type = layout_interleave; + + /* in stereo multichannel this value is distance between 2ch pair, but we need + * interleave*ch = full block (2ch 0x4800 + 2ch 0x4800 = 4ch, 0x4800+4800 / 4 = 0x2400) */ + vgmstream->interleave_block_size = vgmstream->interleave_block_size / 2; + if (vgmstream->channels > 2 && vgmstream->channels % 2 != 0) + goto fail; /* only 2ch+..+2ch layout is known */ /* header values are somehow off? */ - data_size = get_streamfile_size(streamFile); + data_size = get_streamfile_size(streamFile) - start_offset; vgmstream->num_samples = xbox_ima_bytes_to_samples(data_size, vgmstream->channels); } else { /* 0x800 interleave */ vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; if (vgmstream->num_samples * vgmstream->channels <= data_size) vgmstream->num_samples = vgmstream->num_samples / 16 * 28; diff --git a/src/meta/riff.c b/src/meta/riff.c index e25b8aac..2aca1992 100644 --- a/src/meta/riff.c +++ b/src/meta/riff.c @@ -612,6 +612,15 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { break; } + /* Dynasty Warriors 5 (Xbox) 6ch interleaves stereo frames, probably not official */ + if (vgmstream->coding_type == coding_XBOX_IMA && vgmstream->channels > 2) { + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x24; /* block_size / channels */ + if (vgmstream->channels > 2 && vgmstream->channels % 2 != 0) + goto fail; /* only 2ch+..+2ch layout is known */ + } + + /* meta, loops */ vgmstream->meta_type = meta_RIFF_WAVE; if (loop_flag) { diff --git a/src/meta/str_wav.c b/src/meta/str_wav.c index 9ac552d5..ffb0c403 100644 --- a/src/meta/str_wav.c +++ b/src/meta/str_wav.c @@ -120,8 +120,10 @@ VGMSTREAM * init_vgmstream_str_wav(STREAMFILE *streamFile) { case XBOX: vgmstream->coding_type = coding_XBOX_IMA; - vgmstream->layout_type = layout_none; - if (strwav.channels > 2) goto fail; //todo multistreams are 2ch*N interleaved using ~0xD000 + vgmstream->layout_type = layout_interleave; /* interleaved stereo for >2ch*/ + vgmstream->interleave_block_size = strwav.interleave; + if (vgmstream->channels > 2 && vgmstream->channels % 2 != 0) + goto fail; /* only 2ch+..+2ch layout is known */ break; #ifdef VGM_USE_FFMPEG @@ -190,7 +192,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->interleave = 0; strwav->codec = WMA; - ;VGM_LOG("STR+WAV: header Fuzion Frenzy (Xbox)\n"); + //;VGM_LOG("STR+WAV: header Fuzion Frenzy (Xbox)\n"); return 1; } @@ -213,7 +215,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->coefs_offset = 0xdc; strwav->codec = DSP; - ;VGM_LOG("STR+WAV: header Taz: Wanted (GC)\n"); + //;VGM_LOG("STR+WAV: header Taz: Wanted (GC)\n"); return 1; } @@ -231,10 +233,10 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->channels = read_32bitLE(0x70,streamHeader) * (strwav->flags & 0x02 ? 2 : 1); /* tracks of 2/1ch */ strwav->loop_flag = strwav->flags & 0x01; - strwav->interleave = strwav->channels > 2 ? 0xD000 : 0x0; + strwav->interleave = 0xD800/2; strwav->codec = XBOX; - ;VGM_LOG("STR+WAV: header The Fairly OddParents (Xbox)\n"); + //;VGM_LOG("STR+WAV: header The Fairly OddParents (Xbox)\n"); return 1; } @@ -257,32 +259,10 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->dsps_table = 0xe0; strwav->codec = DSP; - ;VGM_LOG("STR+WAV: header Bad Boys II (GC)\n"); + //;VGM_LOG("STR+WAV: header Bad Boys II (GC)\n"); return 1; } -#if 0 - if ( read_32bitBE(0x04,streamHeader) == 0x00000800 && - read_32bitBE(0x24,streamHeader) == read_32bitBE(0xb0,streamHeader) && /* sample rate repeat */ - read_32bitBE(0x24,streamHeader) == read_32bitBE(read_32bitBE(0xf0,streamHeader)+0x08,streamHeader) && /* sample rate vs 1st DSP header */ - read_32bitBE(0x28,streamHeader) == 0x10 && - read_32bitBE(0xc0,streamHeader)*0x04 + read_32bitBE(0xc4,streamHeader) == header_size /* variable + variable */ - ) { - strwav->num_samples = read_32bitBE(0x20,streamHeader); - strwav->sample_rate = read_32bitBE(0x24,streamHeader); - strwav->flags = read_32bitBE(0x2c,streamHeader); - strwav->loop_start = read_32bitBE(0xd8,streamHeader); - strwav->loop_end = read_32bitBE(0xdc,streamHeader); - strwav->channels = read_32bitBE(0x70,streamHeader) * read_32bitBE(0x88,streamHeader); /* tracks of Nch */ - strwav->loop_flag = strwav->flags & 0x01; - strwav->interleave = strwav->channels > 2 ? 0x8000 : 0x10000; - - strwav->dsps_table = 0xf0; - strwav->codec = DSP; - ;VGM_LOG("STR+WAV: header Pac-Man World 3 (GC)\n"); - return 1; - } -#endif /* Bad Boys II (PS2)[2004] */ /* Pac-Man World 3 (PS2)[2005] */ if ((read_32bitBE(0x04,streamHeader) == 0x00000800 || @@ -303,7 +283,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->interleave = strwav->channels > 2 ? 0x4000 : 0x8000; strwav->codec = PSX; - ;VGM_LOG("STR+WAV: header Bad Boys II (PS2)\n"); + //;VGM_LOG("STR+WAV: header Bad Boys II (PS2)\n"); return 1; } @@ -324,7 +304,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->interleave = strwav->channels > 2 ? 0x4000 : 0x8000; strwav->codec = PSX; - ;VGM_LOG("STR+WAV: header Zapper (PS2)\n"); + //;VGM_LOG("STR+WAV: header Zapper (PS2)\n"); return 1; } @@ -346,7 +326,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->dsps_table = 0xe0; strwav->codec = DSP; - ;VGM_LOG("STR+WAV: header Zapper (GC)\n"); + //;VGM_LOG("STR+WAV: header Zapper (GC)\n"); return 1; } @@ -372,7 +352,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->dsps_table = 0xf0; strwav->codec = DSP; - ;VGM_LOG("STR+WAV: header SpongeBob SquarePants (GC)\n"); + //;VGM_LOG("STR+WAV: header SpongeBob SquarePants (GC)\n"); return 1; } @@ -394,7 +374,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000; strwav->codec = PSX; - ;VGM_LOG("STR+WAV: header SpongeBob SquarePants (PS2)\n"); + //;VGM_LOG("STR+WAV: header SpongeBob SquarePants (PS2)\n"); return 1; } @@ -415,7 +395,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000; strwav->codec = PSX; - ;VGM_LOG("STR+WAV: header Tak (PS2)\n"); + //;VGM_LOG("STR+WAV: header Tak (PS2)\n"); return 1; } @@ -439,7 +419,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->coefs_table = 0x7c; strwav->codec = DSP; - ;VGM_LOG("STR+WAV: header Tak (Wii)\n"); + //;VGM_LOG("STR+WAV: header Tak (Wii)\n"); return 1; } diff --git a/src/vgmstream.c b/src/vgmstream.c index 55604cfc..eaef48fc 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -1547,9 +1547,12 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to } break; case coding_XBOX_IMA: + case coding_XBOX_IMA_int: for (ch = 0; ch < vgmstream->channels; ch++) { + int is_stereo = (vgmstream->channels > 1 && vgmstream->coding_type == coding_XBOX_IMA); decode_xbox_ima(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, - vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch); + vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch, + is_stereo); } break; case coding_XBOX_IMA_mch: @@ -1558,12 +1561,6 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch); } break; - case coding_XBOX_IMA_int: - for (ch = 0; ch < vgmstream->channels; ch++) { - decode_xbox_ima_int(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, - vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch); - } - break; case coding_MS_IMA: for (ch = 0; ch < vgmstream->channels; ch++) { decode_ms_ima(vgmstream,&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, @@ -2611,6 +2608,7 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s int ch; int use_streamfile_per_channel = 0; int use_same_offset_per_channel = 0; + int is_stereo_codec = 0; /* stream/offsets not needed, managed by layout */ @@ -2640,12 +2638,15 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s use_streamfile_per_channel = 1; } - /* for mono or codecs like IMA (XBOX, MS IMA, MS ADPCM) where channels work with the same bytes */ if (vgmstream->layout_type == layout_none) { use_same_offset_per_channel = 1; } + /* stereo codecs interleave in 2ch pairs (interleave size should still be: full_block_size / channels) */ + if (vgmstream->layout_type == layout_interleave && vgmstream->coding_type == coding_XBOX_IMA) { + is_stereo_codec = 1; + } streamFile->get_name(streamFile,filename,sizeof(filename)); /* open the file for reading by each channel */ @@ -2659,6 +2660,10 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s off_t offset; if (use_same_offset_per_channel) { offset = start_offset; + } else if (is_stereo_codec) { + int ch_mod = (ch & 1) ? ch - 1 : ch; /* adjust odd channels (ch 0,1,2,3,4,5 > ch 0,0,2,2,4,4) */ + offset = start_offset + vgmstream->interleave_block_size*ch_mod; + //VGM_LOG("ch%i offset=%lx\n", ch,offset); } else { offset = start_offset + vgmstream->interleave_block_size*ch; }