mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-17 23:36:41 +01:00
commit
3090c08e18
@ -371,7 +371,7 @@ are used in few games.
|
||||
- Microsoft MS IMA ADPCM (standard, Xbox, NDS, Radical, Wwise, FSB, WV6, etc)
|
||||
- Microsoft MS ADPCM (standard, Cricket Audio)
|
||||
- Westwood VBR ADPCM
|
||||
- Yamaha ADPCM (standard, Aska)
|
||||
- Yamaha ADPCM (AICA, Aska)
|
||||
- Procyon Studio ADPCM
|
||||
- Level-5 0x555 ADPCM
|
||||
- lsf ADPCM
|
||||
@ -562,7 +562,7 @@ This list is not complete and many other files are supported.
|
||||
- .xmu
|
||||
- .xvas
|
||||
- .xwav
|
||||
- Yamaha ADPCM:
|
||||
- Yamaha AICA ADPCM:
|
||||
- .adpcm
|
||||
- .dcs+.dcsw
|
||||
- .str
|
||||
|
@ -126,25 +126,25 @@ bool read_data(const char * filename, Tuple & tuple) {
|
||||
STREAMFILE *streamfile = open_vfs(filename);
|
||||
if (!streamfile) return false;
|
||||
|
||||
VGMSTREAM *vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
|
||||
if (!vgmstream) {
|
||||
VGMSTREAM *infostream = init_vgmstream_from_STREAMFILE(streamfile);
|
||||
if (!infostream) {
|
||||
close_streamfile(streamfile);
|
||||
return false;
|
||||
}
|
||||
|
||||
tuple.set_filename(filename); //may leak string???
|
||||
int rate = get_vgmstream_average_bitrate(vgmstream);
|
||||
tuple.set_int(Tuple::Bitrate, rate);
|
||||
int bitrate = get_vgmstream_average_bitrate(infostream);
|
||||
tuple.set_int(Tuple::Bitrate, bitrate);
|
||||
|
||||
int ms = get_vgmstream_play_samples(vgmstream_cfg.loop_count, vgmstream_cfg.fade_length, vgmstream_cfg.fade_delay, vgmstream);
|
||||
ms = ms* 1000LL / vgmstream->sample_rate;
|
||||
int ms = get_vgmstream_play_samples(vgmstream_cfg.loop_count, vgmstream_cfg.fade_length, vgmstream_cfg.fade_delay, infostream);
|
||||
ms = ms* 1000LL / infostream->sample_rate;
|
||||
tuple.set_int(Tuple::Length, ms);
|
||||
|
||||
tuple.set_str(Tuple::Codec, "vgmstream codec");//doesn't show?
|
||||
// here we could call describe_vgmstream() and get substring to add tags and stuff
|
||||
|
||||
close_streamfile(streamfile);
|
||||
close_vgmstream(vgmstream);
|
||||
close_vgmstream(infostream);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -169,7 +169,11 @@ bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
|
||||
debugMessage("start play");
|
||||
|
||||
int current_sample_pos = 0;
|
||||
int rate;
|
||||
int bitrate;
|
||||
|
||||
// just in case
|
||||
if (vgmstream)
|
||||
close_vgmstream(vgmstream);
|
||||
|
||||
STREAMFILE *streamfile = open_vfs(filename);
|
||||
if (!streamfile) {
|
||||
@ -194,9 +198,9 @@ bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
|
||||
int stream_samples_amount = get_vgmstream_play_samples(
|
||||
vgmstream_cfg.loop_count, vgmstream_cfg.fade_length,
|
||||
vgmstream_cfg.fade_delay, vgmstream);
|
||||
rate = get_vgmstream_average_bitrate(vgmstream);
|
||||
bitrate = get_vgmstream_average_bitrate(vgmstream);
|
||||
|
||||
set_stream_bitrate(rate);
|
||||
set_stream_bitrate(bitrate);
|
||||
open_audio(FMT_S16_LE, vgmstream->sample_rate, vgmstream->channels);
|
||||
|
||||
int fade_samples = vgmstream_cfg.fade_length * vgmstream->sample_rate;
|
||||
|
@ -12,7 +12,6 @@ typedef struct _VFSSTREAMFILE {
|
||||
VFSFile *vfsFile;
|
||||
off_t offset;
|
||||
char name[32768];
|
||||
//char realname[32768];
|
||||
} VFSSTREAMFILE;
|
||||
|
||||
static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path);
|
||||
@ -35,7 +34,7 @@ static size_t read_vfs(VFSSTREAMFILE *streamfile, uint8_t *dest, off_t offset,
|
||||
|
||||
static void close_vfs(VFSSTREAMFILE *streamfile) {
|
||||
debugMessage("close_vfs");
|
||||
free(streamfile->vfsFile);
|
||||
delete streamfile->vfsFile; //fcloses the internal file too
|
||||
free(streamfile);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
## Build requirements
|
||||
|
||||
**CMake**: Needs v3.5 or later
|
||||
**CMake**: Needs v3.6 or later
|
||||
- https://cmake.org/download/
|
||||
|
||||
**Git**: optional, to generate version numbers:
|
||||
|
@ -93,8 +93,8 @@ as explained below, but often will use default values. Accepted codec strings:
|
||||
# * Special interleave is multiple of 0x1, often +0x80
|
||||
# - DVI_IMA IMA ADPCM (DVI order)
|
||||
# * Variation with modified encoding
|
||||
# - YAMAHA|AICA Yamaha ADPCM (mono/stereo)
|
||||
# * For some Dreamcast games, and some arcade games
|
||||
# - AICA Yamaha AICA ADPCM (mono/stereo)
|
||||
# * For some Dreamcast games, and some arcade (Naomi) games
|
||||
# * Special interleave is multiple of 0x1
|
||||
# - APPLE_IMA4 Apple Quicktime IMA ADPCM
|
||||
# * For some Mac/iOS games
|
||||
|
25
doc/TXTP.md
25
doc/TXTP.md
@ -224,7 +224,7 @@ music_Home.ps3.scd#C3 4
|
||||
```
|
||||
|
||||
|
||||
### Custom play settings
|
||||
### Play settings
|
||||
**`#l(loops)`**, **`#f(fade)`**, **`#d(fade-delay)`**, **`#i(ignore loop)`**, **`#F(ignore fade)`**, **`#E(end-to-end loop)`**
|
||||
|
||||
Those setting should override player's defaults if set (except "loop forever"). They are equivalent to some test.exe options.
|
||||
@ -259,8 +259,29 @@ boss2_3ningumi_ver6.adx#l1.5#d1#f5
|
||||
```
|
||||
|
||||
|
||||
### Time modifications
|
||||
**`#t(time)`**: trims the file so base duration (before applying loops/fades/etc) is `(time)`. If value is negative substracts `(time)` to duration. Loop end is adjusted when necessary, and ignored if value is bigger than possible (use `#l(loops)` config to extend time instead).
|
||||
|
||||
Time values can be `M:S(.n)` (minutes and seconds), `S.n` (seconds with dot), `0xN` (samples in hex format) or `N` (samples). Beware of the subtle difference between 10.0 (ten seconds) and 10 (ten samples).
|
||||
|
||||
Some segments have padding/silence at the end for some reason, that don't allow smooth transitions. You can fix it like this:
|
||||
```
|
||||
intro.fsb #t -1.0 #intro segment has 1 second of silence.
|
||||
main.fsb
|
||||
```
|
||||
|
||||
Similarly other games don't use loop points, but rather repeat/loops the song internally many times:
|
||||
```
|
||||
intro.vag #t3:20 #i #l1.0 #trim + combine with forced loops for easy fades
|
||||
```
|
||||
|
||||
Note that if you need to remove very few samples (like 1) to get smooth transitions it may be a bug in vgmstream, consider reporting.
|
||||
|
||||
|
||||
### Force sample rate
|
||||
**`#h(sample rate)`**: for a few games that set a sample rate value in the header but actually play with other (applying some of pitch or just forcing it).
|
||||
**`#h(sample rate)`**: changes sample rate to selected value (within some limits).
|
||||
|
||||
Needed for a few games set a sample rate value in the header but actually play with other (applying some of pitch or just forcing it).
|
||||
|
||||
**Super Paper Mario (Wii)**
|
||||
```
|
||||
|
@ -125,8 +125,7 @@ static void get_name_foo(FOO_STREAMFILE *streamfile,char *buffer,size_t length)
|
||||
}
|
||||
}
|
||||
static void close_foo(FOO_STREAMFILE * streamfile) {
|
||||
if (streamfile->m_file_opened)
|
||||
streamfile->m_file.release();
|
||||
streamfile->m_file.release(); //release alloc'ed ptr
|
||||
free(streamfile->name);
|
||||
free(streamfile->buffer);
|
||||
free(streamfile);
|
||||
@ -215,7 +214,7 @@ static STREAMFILE * open_foo_streamfile_buffer(const char * const filename, size
|
||||
|
||||
streamFile = open_foo_streamfile_buffer_by_file(infile, infile_exists, filename, buffersize, p_abort);
|
||||
if (!streamFile) {
|
||||
//m_file.release(); //todo not needed after g_open_read?
|
||||
//m_file.release(); //refcounted and cleaned after it goes out of scope
|
||||
}
|
||||
|
||||
return streamFile;
|
||||
|
@ -38,7 +38,7 @@ extern "C" {
|
||||
"https://github.com/kode54/vgmstream/\n" \
|
||||
"https://sourceforge.net/projects/vgmstream/ (original)"
|
||||
|
||||
|
||||
// called every time a file is added to the playlist (to get info) or when playing
|
||||
input_vgmstream::input_vgmstream() {
|
||||
vgmstream = NULL;
|
||||
subsong = 0; // 0 = not set, will be properly changed on first setup_vgmstream
|
||||
@ -67,12 +67,13 @@ input_vgmstream::input_vgmstream() {
|
||||
load_settings();
|
||||
}
|
||||
|
||||
// called on stop or when playlist info has been read
|
||||
input_vgmstream::~input_vgmstream() {
|
||||
close_vgmstream(vgmstream);
|
||||
vgmstream = NULL;
|
||||
}
|
||||
|
||||
// called first when a new file is opened
|
||||
// called first when a new file is accepted, before playing it
|
||||
void input_vgmstream::open(service_ptr_t<file> p_filehint, const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) {
|
||||
|
||||
if (!p_path) { // shouldn't be possible
|
||||
@ -132,10 +133,12 @@ unsigned input_vgmstream::get_subsong_count() {
|
||||
return subsong_count;
|
||||
}
|
||||
|
||||
// called after get_subsong_count to play subsong N (even when count is 1)
|
||||
t_uint32 input_vgmstream::get_subsong(unsigned p_index) {
|
||||
return p_index + 1; // translates index (0..N < subsong_count) for vgmstream: 1=first
|
||||
}
|
||||
|
||||
// called before playing to get info
|
||||
void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_callback & p_abort) {
|
||||
int length_in_ms=0, channels = 0, samplerate = 0;
|
||||
int total_samples = -1;
|
||||
@ -252,6 +255,7 @@ void input_vgmstream::decode_initialize(t_uint32 p_subsong, unsigned p_flags, ab
|
||||
decode_seek( 0, p_abort );
|
||||
};
|
||||
|
||||
// called when audio buffer needs to be filled
|
||||
bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {
|
||||
if (!decoding) return false;
|
||||
if (!vgmstream) return false;
|
||||
@ -306,6 +310,7 @@ bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort)
|
||||
}
|
||||
}
|
||||
|
||||
// called when seeking
|
||||
void input_vgmstream::decode_seek(double p_seconds,abort_callback & p_abort) {
|
||||
seek_pos_samples = (int) audio_math::time_to_samples(p_seconds, vgmstream->sample_rate);
|
||||
int max_buffer_samples = SAMPLE_BUFFER_SIZE;
|
||||
@ -377,6 +382,8 @@ void input_vgmstream::retag_set_info(t_uint32 p_subsong, const file_info & p_inf
|
||||
void input_vgmstream::retag_commit(abort_callback & p_abort) { /*throw exception_io_data();*/ }
|
||||
|
||||
bool input_vgmstream::g_is_our_content_type(const char * p_content_type) {return false;}
|
||||
|
||||
// called to check if file can be processed by the plugin
|
||||
bool input_vgmstream::g_is_our_path(const char * p_path,const char * p_extension) {
|
||||
const char ** ext_list;
|
||||
size_t ext_list_len;
|
||||
@ -397,7 +404,7 @@ bool input_vgmstream::g_is_our_path(const char * p_path,const char * p_extension
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// internal util to create a VGMSTREAM
|
||||
VGMSTREAM * input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
|
||||
@ -410,6 +417,7 @@ VGMSTREAM * input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char *
|
||||
return vgmstream;
|
||||
}
|
||||
|
||||
// internal util to initialize vgmstream
|
||||
void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
|
||||
// close first in case of changing subsongs
|
||||
if (vgmstream) {
|
||||
@ -447,6 +455,7 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
|
||||
fade_samples = (int)(config.song_fade_time * vgmstream->sample_rate);
|
||||
}
|
||||
|
||||
// internal util to get info
|
||||
void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort) {
|
||||
VGMSTREAM * infostream = NULL;
|
||||
bool is_infostream = false;
|
||||
@ -601,29 +610,26 @@ void input_vgmstream::apply_config(VGMSTREAM * vgmstream, foobar_song_config *cu
|
||||
}
|
||||
}
|
||||
|
||||
GUID input_vgmstream::g_get_guid()
|
||||
{
|
||||
GUID input_vgmstream::g_get_guid() {
|
||||
static const GUID guid = { 0x9e7263c7, 0x4cdd, 0x482c,{ 0x9a, 0xec, 0x5e, 0x71, 0x28, 0xcb, 0xc3, 0x4 } };
|
||||
return guid;
|
||||
}
|
||||
|
||||
const char * input_vgmstream::g_get_name()
|
||||
{
|
||||
const char * input_vgmstream::g_get_name() {
|
||||
return "vgmstream";
|
||||
}
|
||||
|
||||
GUID input_vgmstream::g_get_preferences_guid()
|
||||
{
|
||||
GUID input_vgmstream::g_get_preferences_guid() {
|
||||
static const GUID guid = { 0x2b5d0302, 0x165b, 0x409c,{ 0x94, 0x74, 0x2c, 0x8c, 0x2c, 0xd7, 0x6a, 0x25 } };;
|
||||
return guid;
|
||||
}
|
||||
|
||||
bool input_vgmstream::g_is_low_merit()
|
||||
{
|
||||
// checks priority (foobar 1.4+)
|
||||
bool input_vgmstream::g_is_low_merit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* foobar plugin defs */
|
||||
// foobar plugin defs
|
||||
static input_factory_t<input_vgmstream> g_input_vgmstream_factory;
|
||||
|
||||
DECLARE_COMPONENT_VERSION(APP_NAME,PLUGIN_VERSION,PLUGIN_DESCRIPTION);
|
||||
|
@ -133,7 +133,7 @@ void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspac
|
||||
long msadpcm_bytes_to_samples(long bytes, int block_size, int channels);
|
||||
|
||||
/* yamaha_decoder */
|
||||
void decode_yamaha(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo);
|
||||
void decode_aica(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo);
|
||||
void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_nxap(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
size_t yamaha_bytes_to_samples(size_t bytes, int channels);
|
||||
|
@ -1,30 +1,88 @@
|
||||
#include "../util.h"
|
||||
#include "coding.h"
|
||||
|
||||
/* fixed point (.8) amount to scale the current step size by */
|
||||
/* part of the same series as used in MS ADPCM "ADPCMTable" */
|
||||
static const unsigned int scale_step[16] = {
|
||||
/* fixed point amount to scale the current step size */
|
||||
static const unsigned int scale_step_aica[16] = {
|
||||
230, 230, 230, 230, 307, 409, 512, 614,
|
||||
230, 230, 230, 230, 307, 409, 512, 614
|
||||
};
|
||||
|
||||
/* actually implemented with if-else/switchs but that's too goofy */
|
||||
static const int scale_step_aska[8] = {
|
||||
static const int scale_step_adpcmb[16] = {
|
||||
57, 57, 57, 57, 77, 102, 128, 153,
|
||||
57, 57, 57, 57, 77, 102, 128, 153,
|
||||
};
|
||||
|
||||
/* expand an unsigned four bit delta to a wider signed range */
|
||||
/* look-up for 'mul' IMA's sign*((code&7) * 2 + 1) for every code */
|
||||
static const int scale_delta[16] = {
|
||||
1, 3, 5, 7, 9, 11, 13, 15,
|
||||
-1, -3, -5, -7, -9,-11,-13,-15
|
||||
};
|
||||
|
||||
/* Yamaha ADPCM-B (aka DELTA-T) expand used in YM2608/YM2610/etc (cross referenced with various sources and .so) */
|
||||
static void yamaha_adpcmb_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t* hist1, int32_t* step_size, int16_t *out_sample) {
|
||||
int code, delta, sample;
|
||||
|
||||
/* raw Yamaha ADPCM a.k.a AICA as it's prominently used in Naomi/Dreamcast's Yamaha AICA sound chip,
|
||||
* also found in Windows RIFF and older Yamaha's arcade sound chips. */
|
||||
void decode_yamaha(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) {
|
||||
int i, sample_count;
|
||||
code = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift) & 0xf;
|
||||
delta = ((((code & 0x7) * 2) + 1) * (*step_size)) >> 3; /* like 'mul' IMA */
|
||||
if (code & 8)
|
||||
delta = -delta;
|
||||
sample = *hist1 + delta;
|
||||
|
||||
sample = clamp16(sample); /* this may not be needed (not done in Aska) but no byte changes */
|
||||
|
||||
*step_size = ((*step_size) * scale_step_adpcmb[code]) >> 6;
|
||||
if (*step_size < 0x7f) *step_size = 0x7f;
|
||||
else if (*step_size > 0x6000) *step_size = 0x6000;
|
||||
|
||||
*out_sample = sample;
|
||||
*hist1 = sample;
|
||||
}
|
||||
|
||||
/* Yamaha AICA expand, slightly filtered vs "ACM" Yamaha ADPCM, same as Creative ADPCM
|
||||
* (some info from https://github.com/vgmrips/vgmplay, https://wiki.multimedia.cx/index.php/Creative_ADPCM) */
|
||||
static void yamaha_aica_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t* hist1, int32_t* step_size, int16_t *out_sample) {
|
||||
int code, delta, sample;
|
||||
|
||||
*hist1 = *hist1 * 254 / 256; /* hist filter is vital to get correct waveform but not done in many emus */
|
||||
|
||||
code = ((read_8bit(byte_offset,stream->streamfile) >> nibble_shift))&0xf;
|
||||
delta = (*step_size * scale_delta[code]) / 8; /* 'mul' IMA with table (not sure if part of encoder) */
|
||||
sample = *hist1 + delta;
|
||||
|
||||
sample = clamp16(sample); /* apparently done by official encoder */
|
||||
|
||||
*step_size = ((*step_size) * scale_step_aica[code]) >> 8;
|
||||
if (*step_size < 0x7f) *step_size = 0x7f;
|
||||
else if (*step_size > 0x6000) *step_size = 0x6000;
|
||||
|
||||
*out_sample = sample;
|
||||
*hist1 = sample;
|
||||
}
|
||||
|
||||
|
||||
/* info about Yamaha ADPCM as created by official yadpcm.acm (in 'Yamaha-ADPCM-ACM-Driver-100-j')
|
||||
* - possibly RIFF codec 0x20
|
||||
* - simply called "Yamaha ADPCM Codec" (even though not quite like Yamaha ADPCM-B)
|
||||
* - block_align = (sample_rate / 0x3C + 0x04) * channels (ex. 0x2E6 for 22050 stereo, probably given in RIFF)
|
||||
* - low nibble first, stereo or mono modes (no interleave)
|
||||
* - expand (old IMA 'shift+add' style, not 'mul' style):
|
||||
* delta = step_size >> 3;
|
||||
* if (code & 1) delta += step_size >> 2;
|
||||
* if (code & 2) delta += step_size >> 1;
|
||||
* if (code & 4) delta += step_size;
|
||||
* if (code & 8) delta = -delta;
|
||||
* sample = hist + clamp16(delta);
|
||||
* though compiled more like:
|
||||
* sample = hist + (1-2*(code & 8) * (step_size/8 + step_size/2 * (code&2) + step_size/4 * (code&1) + step_size * (code&4))
|
||||
* - step_size update:
|
||||
* step_size = clamp_range(step_size * scale_step_aica[code] >> 8, 0x7F, 0x6000)
|
||||
*/
|
||||
|
||||
|
||||
/* Yamaha AICA ADPCM (also used in YMZ280B with high nibble first) */
|
||||
void decode_aica(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) {
|
||||
int i, sample_count = 0;
|
||||
int16_t out_sample;
|
||||
int32_t hist1 = stream->adpcm_history1_16;
|
||||
int step_size = stream->adpcm_step_index;
|
||||
|
||||
@ -32,8 +90,7 @@ void decode_yamaha(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspac
|
||||
if (step_size < 0x7f) step_size = 0x7f;
|
||||
if (step_size > 0x6000) step_size = 0x6000;
|
||||
|
||||
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
|
||||
int sample_nibble, sample_decoded, sample_delta;
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
off_t byte_offset = is_stereo ?
|
||||
stream->offset + i : /* stereo: one nibble per channel */
|
||||
stream->offset + i/2; /* mono: consecutive nibbles */
|
||||
@ -41,26 +98,20 @@ void decode_yamaha(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspac
|
||||
(!(channel&1) ? 0:4) : /* even = low/L, odd = high/R */
|
||||
(!(i&1) ? 0:4); /* low nibble first */
|
||||
|
||||
/* Yamaha/AICA expand, but same result as IMA's (((delta * 2 + 1) * step) >> 3) */
|
||||
sample_nibble = ((read_8bit(byte_offset,stream->streamfile) >> nibble_shift))&0xf;
|
||||
sample_delta = (step_size * scale_delta[sample_nibble]) / 8;
|
||||
sample_decoded = hist1 + sample_delta;
|
||||
|
||||
outbuf[sample_count] = clamp16(sample_decoded);
|
||||
hist1 = outbuf[sample_count];
|
||||
|
||||
step_size = (step_size * scale_step[sample_nibble]) >> 8;
|
||||
if (step_size < 0x7f) step_size = 0x7f;
|
||||
if (step_size > 0x6000) step_size = 0x6000;
|
||||
yamaha_aica_expand_nibble(stream, byte_offset, nibble_shift, &hist1, &step_size, &out_sample);
|
||||
outbuf[sample_count] = out_sample;
|
||||
sample_count += channelspacing;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_16 = hist1;
|
||||
stream->adpcm_step_index = step_size;
|
||||
}
|
||||
|
||||
/* tri-Ace Aska ADPCM, same-ish with modified step table (reversed from Android SO's .so) */
|
||||
/* tri-Ace Aska ADPCM, Yamaha ADPCM-B with headered frames (reversed from Android SO's .so)
|
||||
* implements table with if-else/switchs too but that's too goofy */
|
||||
void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
int i, sample_count, num_frame;
|
||||
int i, sample_count = 0, num_frame;
|
||||
int16_t out_sample;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_size = stream->adpcm_step_index;
|
||||
|
||||
@ -75,13 +126,13 @@ void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
|
||||
|
||||
hist1 = read_16bitLE(header_offset+0x00,stream->streamfile);
|
||||
step_size = read_16bitLE(header_offset+0x02,stream->streamfile);
|
||||
if (step_size < 0x7f) step_size = 0x7f;
|
||||
if (step_size > 0x6000) step_size = 0x6000;
|
||||
/* in most files 1st frame has step 0 but it seems ok and accounted for */
|
||||
//if (step_size < 0x7f) step_size = 0x7f;
|
||||
//else if (step_size > 0x6000) step_size = 0x6000;
|
||||
}
|
||||
|
||||
/* decode nibbles (layout: varies) */
|
||||
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
|
||||
int sample_nibble, sample_decoded, sample_delta;
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
off_t byte_offset = (channelspacing == 2) ?
|
||||
(stream->offset + 0x40*num_frame + 0x04*channelspacing) + i : /* stereo: one nibble per channel */
|
||||
(stream->offset + 0x40*num_frame + 0x04*channelspacing) + i/2; /* mono: consecutive nibbles */
|
||||
@ -89,26 +140,19 @@ void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
|
||||
(!(channel&1) ? 0:4) :
|
||||
(!(i&1) ? 0:4); /* even = low, odd = high */
|
||||
|
||||
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift) & 0xf;
|
||||
sample_delta = ((((sample_nibble & 0x7) * 2) | 1) * step_size) >> 3; /* like 'mul' IMA with 'or' */
|
||||
if (sample_nibble & 8) sample_delta = -sample_delta;
|
||||
sample_decoded = hist1 + sample_delta;
|
||||
|
||||
outbuf[sample_count] = sample_decoded; /* not clamped */
|
||||
hist1 = outbuf[sample_count];
|
||||
|
||||
step_size = (step_size * scale_step_aska[sample_nibble & 0x07]) >> 6;
|
||||
if (step_size < 0x7f) step_size = 0x7f;
|
||||
if (step_size > 0x6000) step_size = 0x6000;
|
||||
yamaha_adpcmb_expand_nibble(stream, byte_offset, nibble_shift, &hist1, &step_size, &out_sample);
|
||||
outbuf[sample_count] = out_sample;
|
||||
sample_count += channelspacing;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
stream->adpcm_step_index = step_size;
|
||||
}
|
||||
|
||||
|
||||
/* Yamaha ADPCM with unknown expand variation (noisy), step size is double of normal Yamaha? */
|
||||
void decode_nxap(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
int i, sample_count, num_frame;
|
||||
int i, sample_count = 0, num_frame;
|
||||
int32_t hist1 = stream->adpcm_history1_32;
|
||||
int step_size = stream->adpcm_step_index;
|
||||
|
||||
@ -124,26 +168,27 @@ void decode_nxap(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacin
|
||||
hist1 = read_16bitLE(header_offset+0x00,stream->streamfile);
|
||||
step_size = read_16bitLE(header_offset+0x02,stream->streamfile);
|
||||
if (step_size < 0x7f) step_size = 0x7f;
|
||||
if (step_size > 0x6000) step_size = 0x6000;
|
||||
else if (step_size > 0x6000) step_size = 0x6000;
|
||||
}
|
||||
|
||||
/* decode nibbles (layout: all nibbles from one channel) */
|
||||
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
|
||||
int sample_nibble, sample_decoded, sample_delta;
|
||||
for (i = first_sample; i < first_sample + samples_to_do; i++) {
|
||||
int code, delta, sample;
|
||||
off_t byte_offset = (stream->offset + 0x40*num_frame + 0x04) + i/2;
|
||||
int nibble_shift = (i&1?4:0); /* low nibble first */
|
||||
int nibble_shift = (i&1?4:0); /* low nibble first? */
|
||||
|
||||
/* Yamaha expand? */
|
||||
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
|
||||
sample_delta = (step_size * scale_delta[sample_nibble] / 4) / 8; //todo not ok
|
||||
sample_decoded = hist1 + sample_delta;
|
||||
code = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
|
||||
delta = (step_size * scale_delta[code]) / 8; //todo wrong
|
||||
sample = hist1 + delta;
|
||||
|
||||
outbuf[sample_count] = clamp16(sample_decoded);
|
||||
outbuf[sample_count] = clamp16(sample);
|
||||
hist1 = outbuf[sample_count];
|
||||
|
||||
step_size = (step_size * scale_step[sample_nibble]) >> 8;
|
||||
step_size = (step_size * scale_step_aica[code]) / 260.0; //todo wrong
|
||||
if (step_size < 0x7f) step_size = 0x7f;
|
||||
if (step_size > 0x6000) step_size = 0x6000;
|
||||
else if (step_size > 0x6000) step_size = 0x6000;
|
||||
|
||||
sample_count += channelspacing;
|
||||
}
|
||||
|
||||
stream->adpcm_history1_32 = hist1;
|
||||
|
@ -676,8 +676,8 @@ static const coding_info coding_info_list[] = {
|
||||
{coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"},
|
||||
{coding_MSADPCM_ck, "Microsoft 4-bit ADPCM (Cricket Audio)"},
|
||||
{coding_WS, "Westwood Studios VBR ADPCM"},
|
||||
{coding_YAMAHA, "Yamaha 4-bit ADPCM"},
|
||||
{coding_YAMAHA_int, "Yamaha 4-bit ADPCM (mono/interleave)"},
|
||||
{coding_AICA, "Yamaha AICA 4-bit ADPCM"},
|
||||
{coding_AICA_int, "Yamaha AICA 4-bit ADPCM (mono/interleave)"},
|
||||
{coding_ASKA, "tri-Ace Aska 4-bit ADPCM"},
|
||||
{coding_NXAP, "Nex NXAP 4-bit ADPCM"},
|
||||
{coding_NDS_PROCYON, "Procyon Studio Digital Sound Elements NDS 4-bit APDCM"},
|
||||
|
@ -39,7 +39,7 @@ VGMSTREAM * init_vgmstream_dc_str(STREAMFILE *streamFile) {
|
||||
/* fill in the vital statistics */
|
||||
switch (samples) {
|
||||
case 4:
|
||||
vgmstream->coding_type = coding_YAMAHA_int;
|
||||
vgmstream->coding_type = coding_AICA_int;
|
||||
vgmstream->num_samples = read_32bitLE(0x14,streamFile);
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = 0;
|
||||
|
@ -42,7 +42,7 @@ VGMSTREAM * init_vgmstream_dcs_wav(STREAMFILE *streamFile) {
|
||||
vgmstream->meta_type = meta_DCS_WAV;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = yamaha_bytes_to_samples(get_streamfile_size(streamFile), channel_count);
|
||||
vgmstream->coding_type = coding_YAMAHA_int;
|
||||
vgmstream->coding_type = coding_AICA_int;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x4000;
|
||||
|
||||
|
@ -16,7 +16,7 @@ typedef enum {
|
||||
DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */
|
||||
MPEG = 8, /* MPEG (MP3) */
|
||||
IMA = 9, /* IMA ADPCM (low nibble first) */
|
||||
YAMAHA = 10, /* YAMAHA (AICA) ADPCM (Dreamcast games) */
|
||||
AICA = 10, /* YAMAHA AICA ADPCM (Dreamcast games) */
|
||||
MSADPCM = 11, /* MS ADPCM (Windows games) */
|
||||
NGC_DSP = 12, /* NGC DSP (Nintendo games) */
|
||||
PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */
|
||||
@ -102,7 +102,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
|
||||
case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */
|
||||
#endif
|
||||
case IMA: coding = coding_IMA; break;
|
||||
case YAMAHA: coding = coding_YAMAHA; break;
|
||||
case AICA: coding = coding_AICA; break;
|
||||
case MSADPCM: coding = coding_MSADPCM; break;
|
||||
case NGC_DSP: coding = coding_NGC_DSP; break;
|
||||
case PCM8_U_int: coding = coding_PCM8_U_int; break;
|
||||
@ -153,7 +153,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
|
||||
case coding_PSX_badflags:
|
||||
case coding_DVI_IMA:
|
||||
case coding_IMA:
|
||||
case coding_YAMAHA:
|
||||
case coding_AICA:
|
||||
case coding_APPLE_IMA4:
|
||||
vgmstream->interleave_block_size = genh.interleave;
|
||||
vgmstream->interleave_last_block_size = genh.interleave_last;
|
||||
@ -172,8 +172,8 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
|
||||
coding = coding_DVI_IMA_int;
|
||||
if (coding == coding_IMA)
|
||||
coding = coding_IMA_int;
|
||||
if (coding == coding_YAMAHA)
|
||||
coding = coding_YAMAHA_int;
|
||||
if (coding == coding_AICA)
|
||||
coding = coding_AICA_int;
|
||||
}
|
||||
|
||||
/* to avoid endless loops */
|
||||
@ -190,11 +190,11 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
|
||||
}
|
||||
|
||||
/* to avoid problems with dual stereo files (_L+_R) for codecs with stereo modes */
|
||||
if (coding == coding_YAMAHA && genh.channels == 1)
|
||||
coding = coding_YAMAHA_int;
|
||||
if (coding == coding_AICA && genh.channels == 1)
|
||||
coding = coding_AICA_int;
|
||||
|
||||
/* setup adpcm */
|
||||
if (coding == coding_YAMAHA || coding == coding_YAMAHA_int) {
|
||||
if (coding == coding_AICA || coding == coding_AICA_int) {
|
||||
int ch;
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
vgmstream->ch[ch].adpcm_step_index = 0x7f;
|
||||
|
@ -29,7 +29,7 @@ VGMSTREAM * init_vgmstream_naomi_adpcm(STREAMFILE *streamFile) {
|
||||
vgmstream->sample_rate = 44100;
|
||||
vgmstream->num_samples = yamaha_bytes_to_samples(data_size, channel_count);
|
||||
|
||||
vgmstream->coding_type = coding_YAMAHA_int;
|
||||
vgmstream->coding_type = coding_AICA_int;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = data_size / channel_count;
|
||||
vgmstream->meta_type = meta_NAOMI_ADPCM;
|
||||
|
@ -59,7 +59,7 @@ VGMSTREAM * init_vgmstream_naomi_spsd(STREAMFILE *streamFile) {
|
||||
break;
|
||||
|
||||
case 0x03: /* standard */
|
||||
vgmstream->coding_type = coding_YAMAHA_int;
|
||||
vgmstream->coding_type = coding_AICA_int;
|
||||
vgmstream->num_samples = yamaha_bytes_to_samples(data_size,channel_count);
|
||||
vgmstream->loop_start_sample = /*read_32bitLE(0x2c,streamFile) +*/ yamaha_bytes_to_samples(0x2000*channel_count,channel_count);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
@ -135,10 +135,10 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk
|
||||
}
|
||||
|
||||
switch (fmt->codec) {
|
||||
case 0x00: /* Yamaha ADPCM (raw) [Headhunter (DC), Bomber hehhe (DC)] (unofficial) */
|
||||
case 0x00: /* Yamaha AICA ADPCM (raw) [Headhunter (DC), Bomber hehhe (DC)] (unofficial) */
|
||||
if (fmt->bps != 4) goto fail;
|
||||
if (fmt->block_size != 0x02*fmt->channel_count) goto fail;
|
||||
fmt->coding_type = coding_YAMAHA_int;
|
||||
fmt->coding_type = coding_AICA_int;
|
||||
fmt->interleave = 0x01;
|
||||
break;
|
||||
|
||||
@ -174,9 +174,11 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk
|
||||
fmt->coding_type = coding_MS_IMA;
|
||||
break;
|
||||
|
||||
case 0x20: /* Yamaha ADPCM (raw) [Takuyo/Dynamix/etc DC games] */
|
||||
case 0x20: /* Yamaha AICA ADPCM (raw) [Takuyo/Dynamix/etc DC games] */
|
||||
if (fmt->bps != 4) goto fail;
|
||||
fmt->coding_type = coding_YAMAHA;
|
||||
fmt->coding_type = coding_AICA;
|
||||
/* official RIFF spec has 0x20 as 'Yamaha ADPCM', but data is probably not pure AICA
|
||||
* (maybe with headered frames and would need extra detection?) */
|
||||
break;
|
||||
|
||||
case 0x69: /* XBOX IMA ADPCM [Dynasty Warriors 5 (Xbox)] */
|
||||
@ -598,8 +600,8 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
||||
vgmstream->num_samples = fact_sample_count;
|
||||
break;
|
||||
|
||||
case coding_YAMAHA:
|
||||
case coding_YAMAHA_int:
|
||||
case coding_AICA:
|
||||
case coding_AICA_int:
|
||||
vgmstream->num_samples = yamaha_bytes_to_samples(data_size, fmt.channel_count);
|
||||
break;
|
||||
|
||||
@ -675,7 +677,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
|
||||
switch (fmt.coding_type) {
|
||||
case coding_MSADPCM:
|
||||
case coding_MS_IMA:
|
||||
case coding_YAMAHA:
|
||||
case coding_AICA:
|
||||
case coding_XBOX_IMA:
|
||||
case coding_IMA:
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
280
src/meta/rws.c
280
src/meta/rws.c
@ -7,20 +7,24 @@ static off_t get_rws_string_size(off_t offset, STREAMFILE *streamFile);
|
||||
|
||||
|
||||
typedef struct {
|
||||
int big_endian;
|
||||
|
||||
uint32_t codec;
|
||||
int channel_count;
|
||||
int codec;
|
||||
int sample_rate;
|
||||
|
||||
off_t file_name_offset;
|
||||
|
||||
int total_segments;
|
||||
int target_segment;
|
||||
off_t segment_offset;
|
||||
size_t segment_size;
|
||||
size_t segment_layers_size;
|
||||
off_t segment_name_offset;
|
||||
|
||||
int total_layers;
|
||||
int target_layer;
|
||||
off_t layer_offset;
|
||||
size_t layer_size;
|
||||
off_t layer_start;
|
||||
//size_t layer_size;
|
||||
off_t layer_name_offset;
|
||||
|
||||
size_t file_size;
|
||||
@ -28,14 +32,13 @@ typedef struct {
|
||||
size_t data_size;
|
||||
off_t data_offset;
|
||||
|
||||
//size_t stream_size;
|
||||
size_t usable_size;
|
||||
size_t block_size;
|
||||
size_t block_size_total;
|
||||
size_t stream_size_full;
|
||||
size_t block_layers_size;
|
||||
|
||||
off_t coefs_offset;
|
||||
|
||||
int use_segment_subsongs; /* otherwise play the whole thing */
|
||||
char readable_name[STREAM_NAME_SIZE];
|
||||
} rws_header;
|
||||
|
||||
|
||||
@ -43,7 +46,6 @@ typedef struct {
|
||||
VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset, offset;
|
||||
off_t stream_offset, name_offset;
|
||||
size_t stream_size;
|
||||
int loop_flag;
|
||||
int i;
|
||||
@ -56,99 +58,112 @@ VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
|
||||
if (!check_extensions(streamFile,"rws"))
|
||||
goto fail;
|
||||
|
||||
/* parse chunks (always LE) */
|
||||
/* RWS are made of a file chunk with header and data chunks (other chunks exist for non-audio .RWS).
|
||||
* A chunk is: id, size, RW version (no real diffs), data of size (version is repeated but same for all chunks).
|
||||
* Version: 16b main + 16b build (can vary between files) ex: 0c02, 1003, 1400 = 3.5, 1803 = 3.6, 1C02 = 3.7. */
|
||||
/* Audio .RWS is made of file + header + data chunks (non-audio .RWS with other chunks exist).
|
||||
* Chunk format (LE): id, size, RW version, data of size (version is repeated but same for all chunks).
|
||||
* Version is 16b main + 16b build (possibly shifted), no known differences between versions,
|
||||
* and can vary between files of a game. ex: 0c02, 1003, 1400 = 3.5, 1803 = 3.6, 1C02 = 3.7. */
|
||||
|
||||
if (read_32bitLE(0x00,streamFile) != 0x0000080d) /* audio file chunk id */
|
||||
/* parse audio chunks */
|
||||
if (read_32bitLE(0x00,streamFile) != 0x0000080d) /* audio file id */
|
||||
goto fail;
|
||||
rws.file_size = read_32bitLE(0x04,streamFile); /* audio file chunk size */
|
||||
rws.file_size = read_32bitLE(0x04, streamFile); /* audio file size */
|
||||
if (rws.file_size + 0x0c != get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitLE(0x0c,streamFile) != 0x0000080e) /* header chunk id */
|
||||
if (read_32bitLE(0x0c,streamFile) != 0x0000080e) /* header id */
|
||||
goto fail;
|
||||
rws.header_size = read_32bitLE(0x10,streamFile); /* header chunk size */
|
||||
rws.header_size = read_32bitLE(0x10, streamFile); /* header size */
|
||||
|
||||
rws.data_offset = 0x0c + 0x0c + rws.header_size; /* usually 0x800 but not always */
|
||||
if (read_32bitLE(rws.data_offset+0x00,streamFile) != 0x0000080f) /* data chunk id */
|
||||
if (read_32bitLE(rws.data_offset + 0x00, streamFile) != 0x0000080f) /* data chunk id */
|
||||
goto fail;
|
||||
rws.data_size = read_32bitLE(rws.data_offset+0x04,streamFile); /* data chunk size */
|
||||
rws.data_size = read_32bitLE(rws.data_offset + 0x04, streamFile); /* data chunk size */
|
||||
if (rws.data_size+0x0c + rws.data_offset != get_streamfile_size(streamFile))
|
||||
goto fail;
|
||||
|
||||
/* inside header chunk (many unknown fields are probably IDs/config, as two same-sized files vary a lot) */
|
||||
/* inside header chunk (many unknown fields are probably IDs/config/garbage,
|
||||
* as two files of the same size vary a lot) */
|
||||
offset = 0x0c + 0x0c;
|
||||
|
||||
rws.big_endian = guess_endianness32bit(offset + 0x00, streamFile); /* GC/Wii/X360 */
|
||||
read_32bit = rws.big_endian ? read_32bitBE : read_32bitLE;
|
||||
|
||||
/* base header */
|
||||
/* 0x00: actual header size (less than chunk size), 0x04/08/10: sizes of various sections?, 0x14/18/24/2C: commands?
|
||||
* 0x1c: null? 0x30: 0x800?, 0x34: block_size_total?, 0x38: data offset, 0x3c: 0?, 0x40-50: file uuid */
|
||||
read_32bit = (read_32bitLE(offset+0x00,streamFile) > rws.header_size) ? read_32bitBE : read_32bitLE; /* GC/Wii/X360 = BE */
|
||||
rws.total_segments = read_32bit(offset+0x20,streamFile);
|
||||
rws.total_layers = read_32bit(offset+0x28,streamFile);
|
||||
if (rws.total_segments > 1 && rws.total_layers > 1) {
|
||||
VGM_LOG("RWS: unknown segments+layers layout\n");
|
||||
{
|
||||
/* 0x00: actual header size (less than chunk size) */
|
||||
/* 0x04/08/10: sizes of various sections? */
|
||||
/* 0x14/18: config? */
|
||||
/* 0x1c: null? */
|
||||
rws.total_segments = read_32bit(offset + 0x20, streamFile);
|
||||
/* 0x24: config? */
|
||||
rws.total_layers = read_32bit(offset + 0x28, streamFile);
|
||||
/* 0x2c: config? */
|
||||
/* 0x30: 0x800? */
|
||||
/* 0x34: block_layers_size? */
|
||||
/* 0x38: data offset */
|
||||
/* 0x3c: 0? */
|
||||
/* 0x40-50: file uuid */
|
||||
offset += 0x50;
|
||||
}
|
||||
|
||||
/* audio file name */
|
||||
offset += 0x50 + get_rws_string_size(offset+0x50, streamFile);
|
||||
|
||||
{
|
||||
rws.file_name_offset = offset;
|
||||
offset += get_rws_string_size(offset, streamFile);
|
||||
}
|
||||
|
||||
/* RWS data can be divided in two ways:
|
||||
* - "substreams" (layers): interleaved blocks, for fake multichannel L/R+C/S+LS/RS [Burnout 2 (GC/Xbox)]
|
||||
* or song variations [Neighbours From Hell (Xbox/GC)]. Last layer may have padding to keep chunks aligned:
|
||||
* ex.- 0x1700 data of substream_0 2ch, 0x1700 data + 0x200 pad of substream1 2ch, repeat until end
|
||||
* - "segments": cues/divisions within data, like intro+main/loop [[Max Payne 2 (PS2), Nana (PS2)]
|
||||
* or voice1+2+..N, song1+2+..N [Madagascar (PS2), The Legend of Spyro: Dawn of the Dragon (X360)]
|
||||
* - "streams" (layers): interleaved blocks, like L/R+C/S+LS/RS [Burnout 2 (GC/Xbox)]. Last stream has padding:
|
||||
* ex.- 1 block: 0x1800 data of stream_0 2ch, 0x1800 data + 0x200 pad of stream1 2ch.
|
||||
*
|
||||
* Layers seem only used to fake multichannel, but as they are given sample rate/channel/codec/coefs/etc
|
||||
* they are treated as subsongs. Similarly segments can be treated as subsongs in some cases.
|
||||
* They don't seem used at the same time, though could be possible. */
|
||||
* As each layer is given sample rate/channel/codec/etc they are treated as full subsongs, though usually
|
||||
* all layers are the same. Segments are just divisions and can be played one after another, but are useful
|
||||
* to play as subsongs. Since both can exist at the same time (rarely) we make layers*segments=subsongs.
|
||||
* Ex.- subsong1=layer1 blocks in segment1, subsong2=layer2 blocks in segment1, subsong3=layer1 blocks in segment2, ...
|
||||
*
|
||||
* Segment1 Segment2
|
||||
* +-------------------------------------------+-----------------
|
||||
* |Layer1|Layer2|(pad)|...|Layer1|Layer2|(pad)|Layer1|Layer2|...
|
||||
* --------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/* Use either layers or segments as subsongs (with layers having priority). This divides Nana (PS2)
|
||||
* or Max Payne 2 (PS2) intro+main, so it could be adjusted to >2 (>4 for Max Payne 2) if undesired */
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
|
||||
rws.use_segment_subsongs = (rws.total_layers == 1 && rws.total_segments > 1);
|
||||
if (rws.use_segment_subsongs) {
|
||||
rws.target_layer = 1;
|
||||
rws.target_segment = target_subsong;
|
||||
total_subsongs = rws.total_segments;
|
||||
}
|
||||
else {
|
||||
rws.target_layer = target_subsong;
|
||||
rws.target_segment = 0; /* none = play all */
|
||||
total_subsongs = rws.total_layers;
|
||||
}
|
||||
rws.target_layer = ((target_subsong-1) % rws.total_layers) + 1;
|
||||
rws.target_segment = ((target_subsong-1) / rws.total_layers) + 1;
|
||||
total_subsongs = rws.total_layers * rws.total_segments;
|
||||
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
|
||||
/* segment info, for all layers */
|
||||
/* 0x00/04/0c: command?, 0x18: full segment size (including padding), 0x1c: offset, others: ?) */
|
||||
/* segment info */
|
||||
for (i = 0; i < rws.total_segments; i++) {
|
||||
if (i+1 == rws.target_segment) {
|
||||
rws.segment_offset = read_32bit(offset + 0x20*i + 0x1c,streamFile);
|
||||
/* 0x00/04/0c: config? */
|
||||
/* others: ? */
|
||||
rws.segment_layers_size = read_32bit(offset + 0x18, streamFile); /* sum of all including padding */
|
||||
rws.segment_offset = read_32bit(offset + 0x1c, streamFile);
|
||||
}
|
||||
rws.stream_size_full += read_32bit(offset + 0x20*i + 0x18,streamFile);
|
||||
offset += 0x20;
|
||||
}
|
||||
offset += 0x20 * rws.total_segments;
|
||||
|
||||
/* usable segment/layer sizes (assumed either one, sometimes incorrect size?) */
|
||||
for (i = 0; i < (rws.total_segments * rws.total_layers); i++) { /* sum usable segment sizes (no padding) */
|
||||
size_t usable_size = read_32bit(offset + 0x04*i,streamFile); /* size without padding */
|
||||
if (i+1 == rws.target_segment) {
|
||||
rws.segment_size = usable_size;
|
||||
}
|
||||
if (i+1 == rws.target_layer || rws.total_layers == 1) {
|
||||
rws.layer_size += usable_size;
|
||||
/* usable layer sizes per segment */
|
||||
for (i = 0; i < (rws.total_segments * rws.total_layers); i++) {
|
||||
size_t usable_size = read_32bit(offset, streamFile); /* without padding */
|
||||
/* size order: segment1 layer1 size, ..., segment1 layerN size, segment2 layer1 size, etc */
|
||||
if (i+1 == target_subsong) { /* order matches our subsong order */
|
||||
rws.usable_size = usable_size;
|
||||
}
|
||||
offset += 0x04;
|
||||
}
|
||||
offset += 0x04 * (rws.total_segments * rws.total_layers);
|
||||
|
||||
/* segment uuids */
|
||||
offset += 0x10 * rws.total_segments;
|
||||
{
|
||||
offset += 0x10 * rws.total_segments;
|
||||
}
|
||||
|
||||
/* segment names */
|
||||
for (i = 0; i < rws.total_segments; i++) {
|
||||
@ -158,46 +173,53 @@ VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
|
||||
offset += get_rws_string_size(offset, streamFile);
|
||||
}
|
||||
|
||||
|
||||
/* layer info */
|
||||
/* 0x00/04/14: command?, 0x08: null? 0x0c: related to samples per frame? (XADPCM=07, VAG=1C, DSP=0E, PCM=01)
|
||||
* 0x24: offset within data chunk, 0x1c: codec related?, others: ?) */
|
||||
for (i = 0; i < rws.total_layers; i++) { /* get block_sizes */
|
||||
rws.block_size_total += read_32bit(offset + 0x10 + 0x28*i, streamFile); /* for all layers, to skip during decode */
|
||||
if (i+1 == rws.target_layer) {
|
||||
//block_size_full = read_32bit(off + 0x10 + 0x28*i, streamFile); /* with padding, can be different per stream */
|
||||
rws.block_size = read_32bit(offset + 0x20 + 0x28*i, streamFile); /* without padding */
|
||||
rws.layer_offset = read_32bit(offset + 0x24 + 0x28*i, streamFile); /* within data */
|
||||
/* 0x00/04: config? */
|
||||
/* 0x08: null? */
|
||||
/* 0x0c: related to samples per frame? (XBOX-IMA=07, PSX=1C, DSP=0E, PCM=01) */
|
||||
//block_size_pad = read_32bit(offset + 0x10, streamFile); /* with padding, can be different per layer */
|
||||
/* 0x14/18: ? */
|
||||
/* 0x1c: codec related? */
|
||||
rws.block_size = read_32bit(offset + 0x20, streamFile); /* without padding */
|
||||
rws.layer_start = read_32bit(offset + 0x24, streamFile); /* skip data */
|
||||
}
|
||||
rws.block_layers_size += read_32bit(offset + 0x10, streamFile); /* needed to skip during decode */
|
||||
offset += 0x28;
|
||||
}
|
||||
offset += 0x28 * rws.total_layers;
|
||||
|
||||
/* layer config */
|
||||
/* 0x04: command?, 0x0c(1): bits per sample, others: null? */
|
||||
for (i = 0; i < rws.total_layers; i++) { /* size depends on codec so we must parse it */
|
||||
int prev_codec = 0;
|
||||
for (i = 0; i < rws.total_layers; i++) {
|
||||
uint32_t layer_codec = 0;
|
||||
if (i+1 == rws.target_layer) {
|
||||
rws.sample_rate = read_32bit(offset+0x00, streamFile);
|
||||
//unk_size = read_32bit(off+0x08, streamFile); /* segment size again? loop-related? */
|
||||
rws.channel_count = read_8bit(offset+0x0d, streamFile);
|
||||
rws.codec = read_32bitBE(offset+0x1c, streamFile); /* uuid of 128b but first 32b is enough */
|
||||
rws.sample_rate = read_32bit(offset + 0x00, streamFile);
|
||||
/* 0x04: config? */
|
||||
//rws.layer_size = read_32bit(offset + 0x08, streamFile); /* same or close to usable size */
|
||||
/* 0x0c: bits per sample */
|
||||
rws.channel_count = read_8bit(offset + 0x0d, streamFile);
|
||||
/* others: ? */
|
||||
rws.codec = (uint32_t)read_32bit(offset + 0x1c, streamFile); /* 128b uuid (32b-16b-16b-8b*8) but first 32b is enough */
|
||||
}
|
||||
prev_codec = read_32bitBE(offset+0x1c, streamFile);
|
||||
layer_codec = (uint32_t)read_32bit(offset + 0x1c, streamFile);
|
||||
offset += 0x2c;
|
||||
|
||||
if (prev_codec == 0xF86215B0) { /* if codec is DSP there is an extra field per layer */
|
||||
/* 0x00: approx num samples? 0x04: approx size/loop related? (can be 0) */
|
||||
/* DSP has an extra field per layer */
|
||||
if (layer_codec == 0xF86215B0) {
|
||||
/* 0x00: approx num samples? */
|
||||
/* 0x04: approx size/loop related? (can be 0) */
|
||||
if (i+1 == rws.target_layer) {
|
||||
rws.coefs_offset = offset + 0x1c;
|
||||
}
|
||||
offset += 0x60;
|
||||
}
|
||||
|
||||
offset += 0x04; /* padding/garbage */
|
||||
}
|
||||
|
||||
/* layer uuids */
|
||||
offset += 0x10 * rws.total_layers;
|
||||
{
|
||||
offset += 0x10 * rws.total_layers;
|
||||
}
|
||||
|
||||
/* layer names */
|
||||
for (i = 0; i < rws.total_layers; i++) {
|
||||
@ -207,35 +229,49 @@ VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
|
||||
offset += get_rws_string_size(offset, streamFile);
|
||||
}
|
||||
|
||||
|
||||
/* rest is padding/garbage until chunk end (may contain strings and weird stuff) */
|
||||
/* rest is padding/garbage until chunk end (may contain strings and uninitialized memory) */
|
||||
// ...
|
||||
|
||||
start_offset = rws.data_offset + 0x0c + (rws.segment_offset + rws.layer_start);
|
||||
stream_size = rws.usable_size;
|
||||
|
||||
if (rws.use_segment_subsongs) {
|
||||
stream_offset = rws.segment_offset;
|
||||
stream_size = rws.segment_size;
|
||||
name_offset = rws.segment_name_offset;
|
||||
}
|
||||
else {
|
||||
stream_offset = rws.layer_offset;
|
||||
stream_size = rws.layer_size;
|
||||
name_offset = rws.layer_name_offset;
|
||||
}
|
||||
start_offset = rws.data_offset + 0x0c + stream_offset;
|
||||
|
||||
|
||||
/* sometimes it's wrong in XBOX-IMA for no apparent reason (probably a bug in RWS) */
|
||||
if (!rws.use_segment_subsongs) {
|
||||
size_t stream_size_expected = (rws.stream_size_full / rws.block_size_total) * (rws.block_size * rws.total_layers) / rws.total_layers;
|
||||
if (stream_size > stream_size_expected) {
|
||||
VGM_LOG("RWS: readjusting wrong stream size %x vs expected %x\n", stream_size, stream_size_expected);
|
||||
stream_size = stream_size_expected;
|
||||
/* sometimes segment/layers go over file size in XBOX-IMA for no apparent reason, with usable_size bigger
|
||||
* than segment_layers_size yet data_size being correct (bug in RWS header? maybe stops decoding on file end) */
|
||||
{
|
||||
size_t expected_size = (rws.segment_layers_size / rws.block_layers_size) * (rws.block_size * rws.total_layers) / rws.total_layers;
|
||||
if (stream_size > expected_size) {
|
||||
VGM_LOG("RWS: readjusting wrong stream size %x vs expected %x\n", stream_size, expected_size);
|
||||
stream_size = expected_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* build readable name */
|
||||
{
|
||||
char base_name[STREAM_NAME_SIZE], file_name[STREAM_NAME_SIZE], segment_name[STREAM_NAME_SIZE], layer_name[STREAM_NAME_SIZE];
|
||||
|
||||
loop_flag = 0; /* RWX doesn't seem to include actual looping (so devs may fake it with segments) */
|
||||
get_streamfile_basename(streamFile, base_name, sizeof(base_name));
|
||||
/* null terminated */
|
||||
read_string(file_name,STREAM_NAME_SIZE, rws.file_name_offset, streamFile);
|
||||
read_string(segment_name,STREAM_NAME_SIZE, rws.segment_name_offset, streamFile);
|
||||
read_string(layer_name,STREAM_NAME_SIZE, rws.layer_name_offset, streamFile);
|
||||
|
||||
/* some internal names aren't very interesting and are stuff like "SubStream" */
|
||||
if (strcmp(base_name, file_name) == 0) {
|
||||
if (rws.total_layers > 1)
|
||||
snprintf(rws.readable_name,STREAM_NAME_SIZE, "%s/%s", segment_name, layer_name);
|
||||
else
|
||||
snprintf(rws.readable_name,STREAM_NAME_SIZE, "%s", segment_name);
|
||||
}
|
||||
else {
|
||||
if (rws.total_layers > 1)
|
||||
snprintf(rws.readable_name,STREAM_NAME_SIZE, "%s/%s/%s", file_name, segment_name, layer_name);
|
||||
else
|
||||
snprintf(rws.readable_name,STREAM_NAME_SIZE, "%s/%s", file_name, segment_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* seemingly no actual looping supported (devs may fake it with segments) */
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
@ -246,25 +282,23 @@ VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
|
||||
vgmstream->sample_rate = rws.sample_rate;
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
if (name_offset)
|
||||
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile);
|
||||
strcpy(vgmstream->stream_name, rws.readable_name);
|
||||
|
||||
vgmstream->layout_type = layout_blocked_rws;
|
||||
vgmstream->current_block_size = rws.block_size / vgmstream->channels;
|
||||
vgmstream->full_block_size = rws.block_size_total;
|
||||
vgmstream->full_block_size = rws.block_layers_size;
|
||||
|
||||
switch(rws.codec) {
|
||||
case 0x17D21BD0: /* PCM PC (17D21BD0 8735ED4E B9D9B8E8 6EA9B995) */
|
||||
case 0xD01BD217: /* PCM X360 (D01BD217 35874EED B9D9B8E8 6EA9B995) */
|
||||
/* ex. D.i.R.T. - Origin of the Species (PC), The Legend of Spyro (X360) */
|
||||
case 0xD01BD217: /* {D01BD217,3587,4EED,B9,D9,B8,E8,6E,A9,B9,95} PCM PC/X360 */
|
||||
/* ex. D.i.R.T.: Origin of the Species (PC), The Legend of Spyro (X360) */
|
||||
vgmstream->coding_type = coding_PCM16_int;
|
||||
vgmstream->codec_endian = (rws.codec == 0xD01BD217); /* X360: BE */
|
||||
vgmstream->interleave_block_size = 0x02; /* only used to setup channels */
|
||||
vgmstream->codec_endian = (rws.big_endian);
|
||||
vgmstream->interleave_block_size = 0x02; /* only to setup channels */
|
||||
|
||||
vgmstream->num_samples = pcm_bytes_to_samples(stream_size, rws.channel_count, 16);
|
||||
break;
|
||||
|
||||
case 0x9897EAD9: /* PS-ADPCM PS2 (9897EAD9 BCBB7B44 96B26547 59102E16) */
|
||||
case 0xD9EA9798: /* {D9EA9798,BBBC,447B,96,B2,65,47,59,10,2E,16} PS-ADPCM PS2 */
|
||||
/* ex. Silent Hill Origins (PS2), Ghost Rider (PS2), Max Payne 2 (PS2), Nana (PS2) */
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->interleave_block_size = rws.block_size / 2;
|
||||
@ -272,21 +306,21 @@ VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
|
||||
vgmstream->num_samples = ps_bytes_to_samples(stream_size, rws.channel_count);
|
||||
break;
|
||||
|
||||
case 0xF86215B0: /* DSP GC/Wii (F86215B0 31D54C29 BD37CDBF 9BD10C53) */
|
||||
case 0xF86215B0: /* {F86215B0,31D5,4C29,BD,37,CD,BF,9B,D1,0C,53} DSP GC/Wii */
|
||||
/* ex. Burnout 2 (GC), Alice in Wonderland (Wii) */
|
||||
vgmstream->coding_type = coding_NGC_DSP;
|
||||
vgmstream->interleave_block_size = rws.block_size / 2;
|
||||
|
||||
/* get coefs (all channels share them so 0 spacing; also seem fixed for all RWS) */
|
||||
dsp_read_coefs_be(vgmstream,streamFile,rws.coefs_offset, 0);
|
||||
/* get coefs (all channels share them; also seem fixed for all RWS) */
|
||||
dsp_read_coefs_be(vgmstream, streamFile, rws.coefs_offset, 0);
|
||||
|
||||
vgmstream->num_samples = dsp_bytes_to_samples(stream_size, rws.channel_count);
|
||||
break;
|
||||
|
||||
case 0x936538EF: /* XBOX-IMA PC (936538EF 11B62D43 957FA71A DE44227A) */
|
||||
case 0x2BA22F63: /* XBOX-IMA Xbox (2BA22F63 DD118F45 AA27A5C3 46E9790E) */
|
||||
case 0xEF386593: /* {EF386593,B611,432D,95,7F,A7,1A,DE,44,22,7A} XBOX-IMA PC */
|
||||
case 0x632FA22B: /* {632FA22B,11DD,458F,AA,27,A5,C3,46,E9,79,0E} XBOX-IMA Xbox */
|
||||
/* ex. Broken Sword 3 (PC), Jacked (PC/Xbox), Burnout 2 (Xbox) */
|
||||
vgmstream->coding_type = coding_XBOX_IMA; /* PC and Xbox share the same data */
|
||||
vgmstream->coding_type = coding_XBOX_IMA; /* same data though different uuid */
|
||||
vgmstream->interleave_block_size = 0;
|
||||
|
||||
vgmstream->num_samples = xbox_ima_bytes_to_samples(stream_size, rws.channel_count);
|
||||
@ -298,7 +332,7 @@ VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
|
||||
}
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, streamFile, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
@ -308,11 +342,11 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
/* rws-strings are null-terminated then padded to 0x10 (weirdly the padding contains garbage) */
|
||||
/* rws-strings are null-terminated then padded to 0x10 (weirdly enough the padding contains garbage) */
|
||||
static off_t get_rws_string_size(off_t offset, STREAMFILE *streamFile) {
|
||||
int i;
|
||||
for (i = 0; i < 0x800; i++) { /* 0x800=arbitrary max */
|
||||
if (read_8bit(offset+i,streamFile) == 0) { /* null terminator */
|
||||
for (i = 0; i < 255; i++) { /* arbitrary max */
|
||||
if (read_8bit(offset+i, streamFile) == 0) { /* null terminator */
|
||||
return i + (0x10 - (i % 0x10)); /* size is padded */
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ typedef enum {
|
||||
DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */
|
||||
MPEG = 8, /* MPEG (MP3) */
|
||||
IMA = 9, /* IMA ADPCM (low nibble first) */
|
||||
YAMAHA = 10, /* YAMAHA (AICA) ADPCM (Dreamcast games) */
|
||||
AICA = 10, /* YAMAHA AICA ADPCM (Dreamcast games) */
|
||||
MSADPCM = 11, /* MS ADPCM (Windows games) */
|
||||
NGC_DSP = 12, /* NGC DSP (Nintendo games) */
|
||||
PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */
|
||||
@ -200,7 +200,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
||||
case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */
|
||||
#endif
|
||||
case IMA: coding = coding_IMA; break;
|
||||
case YAMAHA: coding = coding_YAMAHA; break;
|
||||
case AICA: coding = coding_AICA; break;
|
||||
case MSADPCM: coding = coding_MSADPCM; break;
|
||||
case NGC_DSP: coding = coding_NGC_DSP; break;
|
||||
case PCM8_U_int: coding = coding_PCM8_U_int; break;
|
||||
@ -264,7 +264,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
||||
case coding_PSX_badflags:
|
||||
case coding_DVI_IMA:
|
||||
case coding_IMA:
|
||||
case coding_YAMAHA:
|
||||
case coding_AICA:
|
||||
case coding_APPLE_IMA4:
|
||||
vgmstream->interleave_block_size = txth.interleave;
|
||||
vgmstream->interleave_last_block_size = txth.interleave_last;
|
||||
@ -283,8 +283,8 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
||||
coding = coding_DVI_IMA_int;
|
||||
if (coding == coding_IMA)
|
||||
coding = coding_IMA_int;
|
||||
if (coding == coding_YAMAHA)
|
||||
coding = coding_YAMAHA_int;
|
||||
if (coding == coding_AICA)
|
||||
coding = coding_AICA_int;
|
||||
}
|
||||
|
||||
/* to avoid endless loops */
|
||||
@ -294,7 +294,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
||||
coding == coding_IMA_int ||
|
||||
coding == coding_DVI_IMA_int ||
|
||||
coding == coding_SDX2_int ||
|
||||
coding == coding_YAMAHA_int) ) {
|
||||
coding == coding_AICA_int) ) {
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
@ -302,11 +302,11 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
|
||||
}
|
||||
|
||||
/* to avoid problems with dual stereo files (_L+_R) for codecs with stereo modes */
|
||||
if (coding == coding_YAMAHA && txth.channels == 1)
|
||||
coding = coding_YAMAHA_int;
|
||||
if (coding == coding_AICA && txth.channels == 1)
|
||||
coding = coding_AICA_int;
|
||||
|
||||
/* setup adpcm */
|
||||
if (coding == coding_YAMAHA || coding == coding_YAMAHA_int) {
|
||||
if (coding == coding_AICA || coding == coding_AICA_int) {
|
||||
int ch;
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
vgmstream->ch[ch].adpcm_step_index = 0x7f;
|
||||
@ -813,8 +813,7 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char
|
||||
else if (is_string(val,"DVI_IMA")) txth->codec = DVI_IMA;
|
||||
else if (is_string(val,"MPEG")) txth->codec = MPEG;
|
||||
else if (is_string(val,"IMA")) txth->codec = IMA;
|
||||
else if (is_string(val,"YAMAHA")) txth->codec = YAMAHA;
|
||||
else if (is_string(val,"AICA")) txth->codec = YAMAHA;
|
||||
else if (is_string(val,"AICA")) txth->codec = AICA;
|
||||
else if (is_string(val,"MSADPCM")) txth->codec = MSADPCM;
|
||||
else if (is_string(val,"NGC_DSP")) txth->codec = NGC_DSP;
|
||||
else if (is_string(val,"DSP")) txth->codec = NGC_DSP;
|
||||
@ -1302,7 +1301,7 @@ static int is_string_match(const char * text, const char * pattern) {
|
||||
int t_pos = 0, p_pos = 0;
|
||||
int p_size, t_size;
|
||||
uint16_t p_char, t_char;
|
||||
;VGM_LOG("TXTH: match '%s' vs '%s'\n", text,pattern);
|
||||
//;VGM_LOG("TXTH: match '%s' vs '%s'\n", text,pattern);
|
||||
|
||||
/* compares 2 strings (case insensitive, to a point) allowing wildcards
|
||||
* ex. for "test": match = "Test*", "*est", "*teSt","T*ES*T"; fail = "tst", "teest"
|
||||
@ -1321,7 +1320,7 @@ static int is_string_match(const char * text, const char * pattern) {
|
||||
|
||||
while(text[t_pos] != '\0') {
|
||||
t_char = get_string_wchar(text, t_pos, &t_size);
|
||||
;VGM_LOG("TXTH: consume %i '%s'\n", t_size, (text+t_pos) );
|
||||
//;VGM_LOG("TXTH: consume %i '%s'\n", t_size, (text+t_pos) );
|
||||
|
||||
if (t_char == p_char)
|
||||
break;
|
||||
@ -1704,7 +1703,7 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) {
|
||||
case IMA:
|
||||
case DVI_IMA:
|
||||
return ima_bytes_to_samples(bytes, txth->channels);
|
||||
case YAMAHA:
|
||||
case AICA:
|
||||
return yamaha_bytes_to_samples(bytes, txth->channels);
|
||||
case PCFX:
|
||||
case OKI16:
|
||||
|
@ -83,13 +83,18 @@ typedef struct {
|
||||
int config_ignore_fade;
|
||||
|
||||
int sample_rate;
|
||||
int loop_install;
|
||||
|
||||
int loop_install_set;
|
||||
int loop_end_max;
|
||||
double loop_start_second;
|
||||
int32_t loop_start_sample;
|
||||
double loop_end_second;
|
||||
int32_t loop_end_sample;
|
||||
|
||||
int trim_set;
|
||||
double trim_second;
|
||||
int32_t trim_sample;
|
||||
|
||||
} txtp_entry;
|
||||
|
||||
|
||||
@ -442,7 +447,7 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||
if (current->sample_rate > 0)
|
||||
vgmstream->sample_rate = current->sample_rate;
|
||||
|
||||
if (current->loop_install) {
|
||||
if (current->loop_install_set) {
|
||||
if (current->loop_start_second > 0 || current->loop_end_second > 0) {
|
||||
current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate;
|
||||
current->loop_end_sample = current->loop_end_second * vgmstream->sample_rate;
|
||||
@ -455,9 +460,27 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
|
||||
current->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
|
||||
vgmstream_force_loop(vgmstream, current->loop_install, current->loop_start_sample, current->loop_end_sample);
|
||||
vgmstream_force_loop(vgmstream, current->loop_install_set, current->loop_start_sample, current->loop_end_sample);
|
||||
}
|
||||
|
||||
if (current->trim_set) {
|
||||
if (current->trim_second != 0.0) {
|
||||
current->trim_sample = current->trim_second * vgmstream->sample_rate;
|
||||
}
|
||||
|
||||
if (current->trim_sample < 0) {
|
||||
vgmstream->num_samples += current->trim_sample; /* trim from end (add negative) */
|
||||
}
|
||||
else if (vgmstream->num_samples > current->trim_sample) {
|
||||
vgmstream->num_samples = current->trim_sample; /* trim to value */
|
||||
}
|
||||
|
||||
/* readjust after triming if it went over (could check for more edge cases but eh) */
|
||||
if (vgmstream->loop_end_sample > vgmstream->num_samples)
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
}
|
||||
|
||||
|
||||
/* add macro to mixing list */
|
||||
if (current->channel_mask) {
|
||||
int ch;
|
||||
@ -625,7 +648,7 @@ static int get_time(const char * config, double *value_f, int32_t *value_i) {
|
||||
m = sscanf(config, " %i%c%i%n", &temp_i1,&temp_c,&temp_i2,&n);
|
||||
if (m == 3 && (temp_c == ':' || temp_c == '_')) {
|
||||
m = sscanf(config, " %lf%c%lf%n", &temp_f1,&temp_c,&temp_f2,&n);
|
||||
if (m != 3 || temp_f1 < 0.0 || temp_f1 >= 60.0 || temp_f2 < 0.0 || temp_f2 >= 60.0)
|
||||
if (m != 3 || /*temp_f1 < 0.0 ||*/ temp_f1 >= 60.0 || temp_f2 < 0.0 || temp_f2 >= 60.0)
|
||||
return 0;
|
||||
|
||||
*value_f = temp_f1 * 60.0 + temp_f2;
|
||||
@ -636,7 +659,7 @@ static int get_time(const char * config, double *value_f, int32_t *value_i) {
|
||||
m = sscanf(config, " %i.%i%n", &temp_i1,&temp_i2,&n);
|
||||
if (m == 2) {
|
||||
m = sscanf(config, " %lf%n", &temp_f1,&n);
|
||||
if (m != 1 || temp_f1 < 0.0)
|
||||
if (m != 1 /*|| temp_f1 < 0.0*/)
|
||||
return 0;
|
||||
*value_f = temp_f1;
|
||||
return n;
|
||||
@ -896,14 +919,20 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam
|
||||
current->sample_rate = cfg->sample_rate;
|
||||
}
|
||||
|
||||
if (cfg->loop_install) {
|
||||
current->loop_install = cfg->loop_install;
|
||||
if (cfg->loop_install_set) {
|
||||
current->loop_install_set = cfg->loop_install_set;
|
||||
current->loop_end_max = cfg->loop_end_max;
|
||||
current->loop_start_sample = cfg->loop_start_sample;
|
||||
current->loop_start_second = cfg->loop_start_second;
|
||||
current->loop_end_sample = cfg->loop_end_sample;
|
||||
current->loop_end_second = cfg->loop_end_second;
|
||||
}
|
||||
|
||||
if (cfg->trim_set) {
|
||||
current->trim_set = cfg->trim_set;
|
||||
current->trim_second = cfg->trim_second;
|
||||
current->trim_sample = cfg->trim_sample;
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_config(txtp_entry *cfg, char *config) {
|
||||
@ -1084,12 +1113,17 @@ static void parse_config(txtp_entry *cfg, char *config) {
|
||||
}
|
||||
|
||||
config += n;
|
||||
cfg->loop_install = 1;
|
||||
cfg->loop_install_set = 1;
|
||||
}
|
||||
|
||||
//;VGM_LOG("TXTP: loop_install %i (max=%i): %i %i / %f %f\n", cfg->loop_install, cfg->loop_end_max,
|
||||
// cfg->loop_start_sample, cfg->loop_end_sample, cfg->loop_start_second, cfg->loop_end_second);
|
||||
}
|
||||
else if (strcmp(command,"t") == 0) {
|
||||
n = get_time(config, &cfg->trim_second, &cfg->trim_sample);
|
||||
cfg->trim_set = (n > 0);
|
||||
//;VGM_LOG("TXTP: trim %i - %f / %i\n", cfg->trim_set, cfg->trim_second, cfg->trim_sample);
|
||||
}
|
||||
//todo cleanup
|
||||
else if (strcmp(command,"@volume") == 0) {
|
||||
txtp_mix_data mix = {0};
|
||||
|
@ -47,7 +47,7 @@ VGMSTREAM * init_vgmstream_ubi_hx(STREAMFILE *streamFile) {
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .hxd: Rayman Arena (all), PK: Out of Shadows (all)
|
||||
/* .hxd: Rayman M/Arena (all), PK: Out of Shadows (all)
|
||||
* .hxc: Rayman 3 (PC), XIII (PC)
|
||||
* .hx2: Rayman 3 (PS2), XIII (PS2)
|
||||
* .hxg: Rayman 3 (GC), XIII (GC)
|
||||
@ -176,6 +176,7 @@ static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = hx->big_endian ? read_32bitBE : read_32bitLE;
|
||||
int16_t (*read_16bit)(off_t,STREAMFILE*) = hx->big_endian ? read_16bitBE : read_16bitLE;
|
||||
off_t riff_offset, riff_size, chunk_offset, stream_adjust = 0, resource_size;
|
||||
size_t chunk_size;
|
||||
int cue_flag = 0;
|
||||
|
||||
//todo cleanup/unify common readings
|
||||
@ -274,10 +275,18 @@ static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t
|
||||
|
||||
/* find "datx" (external) or "data" (internal) also in machine endianness */
|
||||
if (hx->is_external) {
|
||||
if (!find_chunk_riff_ve(sf, 0x78746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian))
|
||||
if (find_chunk_riff_ve(sf, 0x78746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian)) {
|
||||
hx->stream_size = read_32bit(chunk_offset + 0x00, sf);
|
||||
hx->stream_offset = read_32bit(chunk_offset + 0x04, sf) + stream_adjust;
|
||||
}
|
||||
else if ((flag_type == 0x01 || flag_type == 0x02) && /* Rayman M (not Arena) uses "data" instead */
|
||||
find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,&chunk_size, hx->big_endian)) {
|
||||
hx->stream_size = chunk_size;
|
||||
hx->stream_offset = read_32bit(chunk_offset + 0x00, sf) + stream_adjust;
|
||||
}
|
||||
else {
|
||||
goto fail;
|
||||
hx->stream_size = read_32bit(chunk_offset + 0x00, sf);
|
||||
hx->stream_offset = read_32bit(chunk_offset + 0x04, sf) + stream_adjust;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian))
|
||||
|
@ -11,6 +11,7 @@ struct VGMSTREAM_TAGS {
|
||||
char val[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* file to find tags for */
|
||||
int targetname_len;
|
||||
char targetname[VGMSTREAM_TAGS_LINE_MAX];
|
||||
/* path of targetname */
|
||||
char targetpath[VGMSTREAM_TAGS_LINE_MAX];
|
||||
@ -59,17 +60,21 @@ void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
|
||||
free(tags);
|
||||
}
|
||||
|
||||
/* Tags are divided in two: "global" @TAGS and "file" %TAGS for target filename. To extract both
|
||||
* we find the filename's tag "section": (other_filename) ..(#tag section).. (target_filename).
|
||||
* When a new "other_filename" is found that offset is marked as section_start, and when target_filename
|
||||
* is found it's marked as section_end. Then we can begin extracting tags within that section, until
|
||||
* all tags are exhausted. Global tags are extracted while searching, so they always go first, and
|
||||
* also meaning any tags after the section is found are ignored. */
|
||||
/* Find next tag and return 1 if found.
|
||||
*
|
||||
* Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
|
||||
* To extract tags we must find either global tags, or the filename's tag "section"
|
||||
* where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
|
||||
* When a new "other_filename" is found that offset is marked as section_start, and when
|
||||
* target_filename is found it's marked as section_end. Then we can begin extracting tags
|
||||
* within that section, until all tags are exhausted. Global tags are extracted as found,
|
||||
* so they always go first, also meaning any tags after file's section are ignored.
|
||||
* Command tags have special meanings and are output after all section tags. */
|
||||
int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
off_t file_size = get_streamfile_size(tagfile);
|
||||
char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
|
||||
char line[VGMSTREAM_TAGS_LINE_MAX] = {0};
|
||||
int ok, bytes_read, line_done;
|
||||
int ok, bytes_read, line_done, n1,n2;
|
||||
|
||||
if (!tags)
|
||||
return 0;
|
||||
@ -92,7 +97,7 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
/* read lines */
|
||||
while (tags->offset <= file_size) {
|
||||
|
||||
/* no more tags to extract */
|
||||
/* after section: no more tags to extract */
|
||||
if (tags->section_found && tags->offset >= tags->section_end) {
|
||||
|
||||
/* write extra tags after all regular tags */
|
||||
@ -163,10 +168,32 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
continue; /* next line */
|
||||
}
|
||||
|
||||
/* find possible filename and section start/end */
|
||||
ok = sscanf(line, " %[^\r\n] ", currentname);
|
||||
/* find possible filename and section start/end
|
||||
* (.m3u seem to allow filenames with whitespaces before, make sure to trim) */
|
||||
ok = sscanf(line, " %n%[^\r\n]%n ", &n1, currentname, &n2);
|
||||
if (ok == 1) {
|
||||
if (strcasecmp(tags->targetname,currentname) == 0) { /* looks ok even for UTF-8 */
|
||||
int currentname_len = n2 - n1;
|
||||
int filename_found = 0;
|
||||
|
||||
/* we want to find file with the same name (case insensitive), OR a virtual .txtp with
|
||||
* the filename inside (so 'file.adx' gets tags from 'file.adx#i.txtp', reading
|
||||
* tags even if we don't open !tags.m3u with virtual .txtp directly) */
|
||||
|
||||
/* strcasecmp works ok even for UTF-8 */
|
||||
if (currentname_len >= tags->targetname_len && /* starts with targetname */
|
||||
strncasecmp(currentname, tags->targetname, tags->targetname_len) == 0) {
|
||||
|
||||
if (currentname_len == tags->targetname_len) { /* exact match */
|
||||
filename_found = 1;
|
||||
}
|
||||
else if (vgmstream_is_virtual_filename(currentname)) { /* ends with .txth */
|
||||
char c = currentname[tags->targetname_len];
|
||||
/* tell apart the unlikely case of having both 'bgm01.ad.txtp' and 'bgm01.adp.txtp' */
|
||||
filename_found = (c==' ' || c == '.' || c == '#');
|
||||
}
|
||||
}
|
||||
|
||||
if (filename_found) {
|
||||
/* section ok, start would be set before this (or be 0) */
|
||||
tags->section_end = tags->offset;
|
||||
tags->section_found = 1;
|
||||
@ -223,6 +250,7 @@ void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
|
||||
tags->targetpath[0] = '\0';
|
||||
strcpy(tags->targetname, target_filename);
|
||||
}
|
||||
tags->targetname_len = strlen(tags->targetname);
|
||||
}
|
||||
|
||||
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) {
|
||||
|
@ -1214,9 +1214,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
return (vgmstream->interleave_block_size - 0x07)*2 + 2;
|
||||
case coding_WS: /* only works if output sample size is 8 bit, which always is for WS ADPCM */
|
||||
return vgmstream->ws_output_size;
|
||||
case coding_YAMAHA:
|
||||
case coding_AICA:
|
||||
return 1;
|
||||
case coding_YAMAHA_int:
|
||||
case coding_AICA_int:
|
||||
return 2;
|
||||
case coding_ASKA:
|
||||
return (0x40-0x04*vgmstream->channels) * 2 / vgmstream->channels;
|
||||
@ -1410,8 +1410,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
||||
return vgmstream->interleave_block_size;
|
||||
case coding_WS:
|
||||
return vgmstream->current_block_size;
|
||||
case coding_YAMAHA:
|
||||
case coding_YAMAHA_int:
|
||||
case coding_AICA:
|
||||
case coding_AICA_int:
|
||||
return 0x01;
|
||||
case coding_ASKA:
|
||||
case coding_NXAP:
|
||||
@ -2010,12 +2010,12 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
vgmstream->channels,vgmstream->samples_into_block, samples_to_do, ch);
|
||||
}
|
||||
break;
|
||||
case coding_YAMAHA:
|
||||
case coding_YAMAHA_int:
|
||||
case coding_AICA:
|
||||
case coding_AICA_int:
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
int is_stereo = (vgmstream->channels > 1 && vgmstream->coding_type == coding_YAMAHA);
|
||||
int is_stereo = (vgmstream->channels > 1 && vgmstream->coding_type == coding_AICA);
|
||||
|
||||
decode_yamaha(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
|
||||
decode_aica(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
|
||||
vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch,
|
||||
is_stereo);
|
||||
}
|
||||
|
@ -148,8 +148,8 @@ typedef enum {
|
||||
coding_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */
|
||||
coding_WS, /* Westwood Studios VBR ADPCM */
|
||||
|
||||
coding_YAMAHA, /* Yamaha ADPCM (stereo) */
|
||||
coding_YAMAHA_int, /* Yamaha ADPCM (mono/interleave) */
|
||||
coding_AICA, /* Yamaha AICA ADPCM (stereo) */
|
||||
coding_AICA_int, /* Yamaha AICA ADPCM (mono/interleave) */
|
||||
coding_ASKA, /* Aska ADPCM */
|
||||
coding_NXAP, /* NXAP ADPCM */
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user