Merge pull request #476 from bnnm/rws-aica-etc

rws aica etc
This commit is contained in:
bnnm 2019-09-21 21:02:01 +02:00 committed by GitHub
commit 3090c08e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 458 additions and 278 deletions

View File

@ -371,7 +371,7 @@ are used in few games.
- Microsoft MS IMA ADPCM (standard, Xbox, NDS, Radical, Wwise, FSB, WV6, etc) - Microsoft MS IMA ADPCM (standard, Xbox, NDS, Radical, Wwise, FSB, WV6, etc)
- Microsoft MS ADPCM (standard, Cricket Audio) - Microsoft MS ADPCM (standard, Cricket Audio)
- Westwood VBR ADPCM - Westwood VBR ADPCM
- Yamaha ADPCM (standard, Aska) - Yamaha ADPCM (AICA, Aska)
- Procyon Studio ADPCM - Procyon Studio ADPCM
- Level-5 0x555 ADPCM - Level-5 0x555 ADPCM
- lsf ADPCM - lsf ADPCM
@ -562,7 +562,7 @@ This list is not complete and many other files are supported.
- .xmu - .xmu
- .xvas - .xvas
- .xwav - .xwav
- Yamaha ADPCM: - Yamaha AICA ADPCM:
- .adpcm - .adpcm
- .dcs+.dcsw - .dcs+.dcsw
- .str - .str

View File

@ -126,25 +126,25 @@ bool read_data(const char * filename, Tuple & tuple) {
STREAMFILE *streamfile = open_vfs(filename); STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) return false; if (!streamfile) return false;
VGMSTREAM *vgmstream = init_vgmstream_from_STREAMFILE(streamfile); VGMSTREAM *infostream = init_vgmstream_from_STREAMFILE(streamfile);
if (!vgmstream) { if (!infostream) {
close_streamfile(streamfile); close_streamfile(streamfile);
return false; return false;
} }
tuple.set_filename(filename); //may leak string??? tuple.set_filename(filename); //may leak string???
int rate = get_vgmstream_average_bitrate(vgmstream); int bitrate = get_vgmstream_average_bitrate(infostream);
tuple.set_int(Tuple::Bitrate, rate); 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); int ms = get_vgmstream_play_samples(vgmstream_cfg.loop_count, vgmstream_cfg.fade_length, vgmstream_cfg.fade_delay, infostream);
ms = ms* 1000LL / vgmstream->sample_rate; ms = ms* 1000LL / infostream->sample_rate;
tuple.set_int(Tuple::Length, ms); tuple.set_int(Tuple::Length, ms);
tuple.set_str(Tuple::Codec, "vgmstream codec");//doesn't show? tuple.set_str(Tuple::Codec, "vgmstream codec");//doesn't show?
// here we could call describe_vgmstream() and get substring to add tags and stuff // here we could call describe_vgmstream() and get substring to add tags and stuff
close_streamfile(streamfile); close_streamfile(streamfile);
close_vgmstream(vgmstream); close_vgmstream(infostream);
return true; return true;
} }
@ -169,7 +169,11 @@ bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
debugMessage("start play"); debugMessage("start play");
int current_sample_pos = 0; int current_sample_pos = 0;
int rate; int bitrate;
// just in case
if (vgmstream)
close_vgmstream(vgmstream);
STREAMFILE *streamfile = open_vfs(filename); STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) { if (!streamfile) {
@ -194,9 +198,9 @@ bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
int stream_samples_amount = get_vgmstream_play_samples( int stream_samples_amount = get_vgmstream_play_samples(
vgmstream_cfg.loop_count, vgmstream_cfg.fade_length, vgmstream_cfg.loop_count, vgmstream_cfg.fade_length,
vgmstream_cfg.fade_delay, vgmstream); 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); open_audio(FMT_S16_LE, vgmstream->sample_rate, vgmstream->channels);
int fade_samples = vgmstream_cfg.fade_length * vgmstream->sample_rate; int fade_samples = vgmstream_cfg.fade_length * vgmstream->sample_rate;

View File

@ -12,7 +12,6 @@ typedef struct _VFSSTREAMFILE {
VFSFile *vfsFile; VFSFile *vfsFile;
off_t offset; off_t offset;
char name[32768]; char name[32768];
//char realname[32768];
} VFSSTREAMFILE; } VFSSTREAMFILE;
static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path); 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) { static void close_vfs(VFSSTREAMFILE *streamfile) {
debugMessage("close_vfs"); debugMessage("close_vfs");
free(streamfile->vfsFile); delete streamfile->vfsFile; //fcloses the internal file too
free(streamfile); free(streamfile);
} }

View File

@ -2,7 +2,7 @@
## Build requirements ## Build requirements
**CMake**: Needs v3.5 or later **CMake**: Needs v3.6 or later
- https://cmake.org/download/ - https://cmake.org/download/
**Git**: optional, to generate version numbers: **Git**: optional, to generate version numbers:

View File

@ -93,8 +93,8 @@ as explained below, but often will use default values. Accepted codec strings:
# * Special interleave is multiple of 0x1, often +0x80 # * Special interleave is multiple of 0x1, often +0x80
# - DVI_IMA IMA ADPCM (DVI order) # - DVI_IMA IMA ADPCM (DVI order)
# * Variation with modified encoding # * Variation with modified encoding
# - YAMAHA|AICA Yamaha ADPCM (mono/stereo) # - AICA Yamaha AICA ADPCM (mono/stereo)
# * For some Dreamcast games, and some arcade games # * For some Dreamcast games, and some arcade (Naomi) games
# * Special interleave is multiple of 0x1 # * Special interleave is multiple of 0x1
# - APPLE_IMA4 Apple Quicktime IMA ADPCM # - APPLE_IMA4 Apple Quicktime IMA ADPCM
# * For some Mac/iOS games # * For some Mac/iOS games

View File

@ -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)`** **`#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. 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 ### 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)** **Super Paper Mario (Wii)**
``` ```

View File

@ -125,8 +125,7 @@ static void get_name_foo(FOO_STREAMFILE *streamfile,char *buffer,size_t length)
} }
} }
static void close_foo(FOO_STREAMFILE * streamfile) { static void close_foo(FOO_STREAMFILE * streamfile) {
if (streamfile->m_file_opened) streamfile->m_file.release(); //release alloc'ed ptr
streamfile->m_file.release();
free(streamfile->name); free(streamfile->name);
free(streamfile->buffer); free(streamfile->buffer);
free(streamfile); 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); streamFile = open_foo_streamfile_buffer_by_file(infile, infile_exists, filename, buffersize, p_abort);
if (!streamFile) { 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; return streamFile;

View File

@ -38,7 +38,7 @@ extern "C" {
"https://github.com/kode54/vgmstream/\n" \ "https://github.com/kode54/vgmstream/\n" \
"https://sourceforge.net/projects/vgmstream/ (original)" "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() { input_vgmstream::input_vgmstream() {
vgmstream = NULL; vgmstream = NULL;
subsong = 0; // 0 = not set, will be properly changed on first setup_vgmstream subsong = 0; // 0 = not set, will be properly changed on first setup_vgmstream
@ -67,12 +67,13 @@ input_vgmstream::input_vgmstream() {
load_settings(); load_settings();
} }
// called on stop or when playlist info has been read
input_vgmstream::~input_vgmstream() { input_vgmstream::~input_vgmstream() {
close_vgmstream(vgmstream); close_vgmstream(vgmstream);
vgmstream = NULL; 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) { 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 if (!p_path) { // shouldn't be possible
@ -132,10 +133,12 @@ unsigned input_vgmstream::get_subsong_count() {
return 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) { t_uint32 input_vgmstream::get_subsong(unsigned p_index) {
return p_index + 1; // translates index (0..N < subsong_count) for vgmstream: 1=first 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) { 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 length_in_ms=0, channels = 0, samplerate = 0;
int total_samples = -1; 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 ); 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) { bool input_vgmstream::decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {
if (!decoding) return false; if (!decoding) return false;
if (!vgmstream) 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) { 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); seek_pos_samples = (int) audio_math::time_to_samples(p_seconds, vgmstream->sample_rate);
int max_buffer_samples = SAMPLE_BUFFER_SIZE; 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();*/ } 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;} 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) { bool input_vgmstream::g_is_our_path(const char * p_path,const char * p_extension) {
const char ** ext_list; const char ** ext_list;
size_t ext_list_len; 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; 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 * input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort) {
VGMSTREAM *vgmstream = NULL; VGMSTREAM *vgmstream = NULL;
@ -410,6 +417,7 @@ VGMSTREAM * input_vgmstream::init_vgmstream_foo(t_uint32 p_subsong, const char *
return vgmstream; return vgmstream;
} }
// internal util to initialize vgmstream
void input_vgmstream::setup_vgmstream(abort_callback & p_abort) { void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
// close first in case of changing subsongs // close first in case of changing subsongs
if (vgmstream) { 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); 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) { 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; VGMSTREAM * infostream = NULL;
bool is_infostream = false; 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 } }; static const GUID guid = { 0x9e7263c7, 0x4cdd, 0x482c,{ 0x9a, 0xec, 0x5e, 0x71, 0x28, 0xcb, 0xc3, 0x4 } };
return guid; return guid;
} }
const char * input_vgmstream::g_get_name() const char * input_vgmstream::g_get_name() {
{
return "vgmstream"; 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 } };; static const GUID guid = { 0x2b5d0302, 0x165b, 0x409c,{ 0x94, 0x74, 0x2c, 0x8c, 0x2c, 0xd7, 0x6a, 0x25 } };;
return guid; return guid;
} }
bool input_vgmstream::g_is_low_merit() // checks priority (foobar 1.4+)
{ bool input_vgmstream::g_is_low_merit() {
return true; return true;
} }
/* foobar plugin defs */ // foobar plugin defs
static input_factory_t<input_vgmstream> g_input_vgmstream_factory; static input_factory_t<input_vgmstream> g_input_vgmstream_factory;
DECLARE_COMPONENT_VERSION(APP_NAME,PLUGIN_VERSION,PLUGIN_DESCRIPTION); DECLARE_COMPONENT_VERSION(APP_NAME,PLUGIN_VERSION,PLUGIN_DESCRIPTION);

View File

@ -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); long msadpcm_bytes_to_samples(long bytes, int block_size, int channels);
/* yamaha_decoder */ /* 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_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); 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); size_t yamaha_bytes_to_samples(size_t bytes, int channels);

View File

@ -1,30 +1,88 @@
#include "../util.h" #include "../util.h"
#include "coding.h" #include "coding.h"
/* fixed point (.8) amount to scale the current step size by */ /* fixed point amount to scale the current step size */
/* part of the same series as used in MS ADPCM "ADPCMTable" */ static const unsigned int scale_step_aica[16] = {
static const unsigned int scale_step[16] = {
230, 230, 230, 230, 307, 409, 512, 614, 230, 230, 230, 230, 307, 409, 512, 614,
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_adpcmb[16] = {
static const int scale_step_aska[8] = { 57, 57, 57, 57, 77, 102, 128, 153,
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] = { static const int scale_delta[16] = {
1, 3, 5, 7, 9, 11, 13, 15, 1, 3, 5, 7, 9, 11, 13, 15,
-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, code = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift) & 0xf;
* also found in Windows RIFF and older Yamaha's arcade sound chips. */ delta = ((((code & 0x7) * 2) + 1) * (*step_size)) >> 3; /* like 'mul' IMA */
void decode_yamaha(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) { if (code & 8)
int i, sample_count; 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; int32_t hist1 = stream->adpcm_history1_16;
int step_size = stream->adpcm_step_index; 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 < 0x7f) step_size = 0x7f;
if (step_size > 0x6000) step_size = 0x6000; if (step_size > 0x6000) step_size = 0x6000;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) { for (i = first_sample; i < first_sample + samples_to_do; i++) {
int sample_nibble, sample_decoded, sample_delta;
off_t byte_offset = is_stereo ? off_t byte_offset = is_stereo ?
stream->offset + i : /* stereo: one nibble per channel */ stream->offset + i : /* stereo: one nibble per channel */
stream->offset + i/2; /* mono: consecutive nibbles */ 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 */ (!(channel&1) ? 0:4) : /* even = low/L, odd = high/R */
(!(i&1) ? 0:4); /* low nibble first */ (!(i&1) ? 0:4); /* low nibble first */
/* Yamaha/AICA expand, but same result as IMA's (((delta * 2 + 1) * step) >> 3) */ yamaha_aica_expand_nibble(stream, byte_offset, nibble_shift, &hist1, &step_size, &out_sample);
sample_nibble = ((read_8bit(byte_offset,stream->streamfile) >> nibble_shift))&0xf; outbuf[sample_count] = out_sample;
sample_delta = (step_size * scale_delta[sample_nibble]) / 8; sample_count += channelspacing;
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;
} }
stream->adpcm_history1_16 = hist1; stream->adpcm_history1_16 = hist1;
stream->adpcm_step_index = step_size; 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) { 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; int32_t hist1 = stream->adpcm_history1_32;
int step_size = stream->adpcm_step_index; 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); hist1 = read_16bitLE(header_offset+0x00,stream->streamfile);
step_size = read_16bitLE(header_offset+0x02,stream->streamfile); step_size = read_16bitLE(header_offset+0x02,stream->streamfile);
if (step_size < 0x7f) step_size = 0x7f; /* in most files 1st frame has step 0 but it seems ok and accounted for */
if (step_size > 0x6000) step_size = 0x6000; //if (step_size < 0x7f) step_size = 0x7f;
//else if (step_size > 0x6000) step_size = 0x6000;
} }
/* decode nibbles (layout: varies) */ /* decode nibbles (layout: varies) */
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) { for (i = first_sample; i < first_sample + samples_to_do; i++) {
int sample_nibble, sample_decoded, sample_delta;
off_t byte_offset = (channelspacing == 2) ? 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 : /* stereo: one nibble per channel */
(stream->offset + 0x40*num_frame + 0x04*channelspacing) + i/2; /* mono: consecutive nibbles */ (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) : (!(channel&1) ? 0:4) :
(!(i&1) ? 0:4); /* even = low, odd = high */ (!(i&1) ? 0:4); /* even = low, odd = high */
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift) & 0xf; yamaha_adpcmb_expand_nibble(stream, byte_offset, nibble_shift, &hist1, &step_size, &out_sample);
sample_delta = ((((sample_nibble & 0x7) * 2) | 1) * step_size) >> 3; /* like 'mul' IMA with 'or' */ outbuf[sample_count] = out_sample;
if (sample_nibble & 8) sample_delta = -sample_delta; sample_count += channelspacing;
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;
} }
stream->adpcm_history1_32 = hist1; stream->adpcm_history1_32 = hist1;
stream->adpcm_step_index = step_size; stream->adpcm_step_index = step_size;
} }
/* Yamaha ADPCM with unknown expand variation (noisy), step size is double of normal Yamaha? */ /* 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) { 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; int32_t hist1 = stream->adpcm_history1_32;
int step_size = stream->adpcm_step_index; 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); hist1 = read_16bitLE(header_offset+0x00,stream->streamfile);
step_size = read_16bitLE(header_offset+0x02,stream->streamfile); step_size = read_16bitLE(header_offset+0x02,stream->streamfile);
if (step_size < 0x7f) step_size = 0x7f; 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) */ /* 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) { for (i = first_sample; i < first_sample + samples_to_do; i++) {
int sample_nibble, sample_decoded, sample_delta; int code, delta, sample;
off_t byte_offset = (stream->offset + 0x40*num_frame + 0x04) + i/2; 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? */ code = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; delta = (step_size * scale_delta[code]) / 8; //todo wrong
sample_delta = (step_size * scale_delta[sample_nibble] / 4) / 8; //todo not ok sample = hist1 + delta;
sample_decoded = hist1 + sample_delta;
outbuf[sample_count] = clamp16(sample_decoded); outbuf[sample_count] = clamp16(sample);
hist1 = outbuf[sample_count]; 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 < 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; stream->adpcm_history1_32 = hist1;

View File

@ -676,8 +676,8 @@ static const coding_info coding_info_list[] = {
{coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"}, {coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"},
{coding_MSADPCM_ck, "Microsoft 4-bit ADPCM (Cricket Audio)"}, {coding_MSADPCM_ck, "Microsoft 4-bit ADPCM (Cricket Audio)"},
{coding_WS, "Westwood Studios VBR ADPCM"}, {coding_WS, "Westwood Studios VBR ADPCM"},
{coding_YAMAHA, "Yamaha 4-bit ADPCM"}, {coding_AICA, "Yamaha AICA 4-bit ADPCM"},
{coding_YAMAHA_int, "Yamaha 4-bit ADPCM (mono/interleave)"}, {coding_AICA_int, "Yamaha AICA 4-bit ADPCM (mono/interleave)"},
{coding_ASKA, "tri-Ace Aska 4-bit ADPCM"}, {coding_ASKA, "tri-Ace Aska 4-bit ADPCM"},
{coding_NXAP, "Nex NXAP 4-bit ADPCM"}, {coding_NXAP, "Nex NXAP 4-bit ADPCM"},
{coding_NDS_PROCYON, "Procyon Studio Digital Sound Elements NDS 4-bit APDCM"}, {coding_NDS_PROCYON, "Procyon Studio Digital Sound Elements NDS 4-bit APDCM"},

View File

@ -39,7 +39,7 @@ VGMSTREAM * init_vgmstream_dc_str(STREAMFILE *streamFile) {
/* fill in the vital statistics */ /* fill in the vital statistics */
switch (samples) { switch (samples) {
case 4: case 4:
vgmstream->coding_type = coding_YAMAHA_int; vgmstream->coding_type = coding_AICA_int;
vgmstream->num_samples = read_32bitLE(0x14,streamFile); vgmstream->num_samples = read_32bitLE(0x14,streamFile);
if (loop_flag) { if (loop_flag) {
vgmstream->loop_start_sample = 0; vgmstream->loop_start_sample = 0;

View File

@ -42,7 +42,7 @@ VGMSTREAM * init_vgmstream_dcs_wav(STREAMFILE *streamFile) {
vgmstream->meta_type = meta_DCS_WAV; vgmstream->meta_type = meta_DCS_WAV;
vgmstream->sample_rate = sample_rate; vgmstream->sample_rate = sample_rate;
vgmstream->num_samples = yamaha_bytes_to_samples(get_streamfile_size(streamFile), channel_count); 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->layout_type = layout_interleave;
vgmstream->interleave_block_size = 0x4000; vgmstream->interleave_block_size = 0x4000;

View File

@ -16,7 +16,7 @@ typedef enum {
DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */ DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */
MPEG = 8, /* MPEG (MP3) */ MPEG = 8, /* MPEG (MP3) */
IMA = 9, /* IMA ADPCM (low nibble first) */ 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) */ MSADPCM = 11, /* MS ADPCM (Windows games) */
NGC_DSP = 12, /* NGC DSP (Nintendo games) */ NGC_DSP = 12, /* NGC DSP (Nintendo games) */
PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */ 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 */ case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */
#endif #endif
case IMA: coding = coding_IMA; break; 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 MSADPCM: coding = coding_MSADPCM; break;
case NGC_DSP: coding = coding_NGC_DSP; break; case NGC_DSP: coding = coding_NGC_DSP; break;
case PCM8_U_int: coding = coding_PCM8_U_int; 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_PSX_badflags:
case coding_DVI_IMA: case coding_DVI_IMA:
case coding_IMA: case coding_IMA:
case coding_YAMAHA: case coding_AICA:
case coding_APPLE_IMA4: case coding_APPLE_IMA4:
vgmstream->interleave_block_size = genh.interleave; vgmstream->interleave_block_size = genh.interleave;
vgmstream->interleave_last_block_size = genh.interleave_last; vgmstream->interleave_last_block_size = genh.interleave_last;
@ -172,8 +172,8 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) {
coding = coding_DVI_IMA_int; coding = coding_DVI_IMA_int;
if (coding == coding_IMA) if (coding == coding_IMA)
coding = coding_IMA_int; coding = coding_IMA_int;
if (coding == coding_YAMAHA) if (coding == coding_AICA)
coding = coding_YAMAHA_int; coding = coding_AICA_int;
} }
/* to avoid endless loops */ /* 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 */ /* to avoid problems with dual stereo files (_L+_R) for codecs with stereo modes */
if (coding == coding_YAMAHA && genh.channels == 1) if (coding == coding_AICA && genh.channels == 1)
coding = coding_YAMAHA_int; coding = coding_AICA_int;
/* setup adpcm */ /* setup adpcm */
if (coding == coding_YAMAHA || coding == coding_YAMAHA_int) { if (coding == coding_AICA || coding == coding_AICA_int) {
int ch; int ch;
for (ch = 0; ch < vgmstream->channels; ch++) { for (ch = 0; ch < vgmstream->channels; ch++) {
vgmstream->ch[ch].adpcm_step_index = 0x7f; vgmstream->ch[ch].adpcm_step_index = 0x7f;

View File

@ -29,7 +29,7 @@ VGMSTREAM * init_vgmstream_naomi_adpcm(STREAMFILE *streamFile) {
vgmstream->sample_rate = 44100; vgmstream->sample_rate = 44100;
vgmstream->num_samples = yamaha_bytes_to_samples(data_size, channel_count); 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->layout_type = layout_interleave;
vgmstream->interleave_block_size = data_size / channel_count; vgmstream->interleave_block_size = data_size / channel_count;
vgmstream->meta_type = meta_NAOMI_ADPCM; vgmstream->meta_type = meta_NAOMI_ADPCM;

View File

@ -59,7 +59,7 @@ VGMSTREAM * init_vgmstream_naomi_spsd(STREAMFILE *streamFile) {
break; break;
case 0x03: /* standard */ 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->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_start_sample = /*read_32bitLE(0x2c,streamFile) +*/ yamaha_bytes_to_samples(0x2000*channel_count,channel_count);
vgmstream->loop_end_sample = vgmstream->num_samples; vgmstream->loop_end_sample = vgmstream->num_samples;

View File

@ -135,10 +135,10 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk
} }
switch (fmt->codec) { 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->bps != 4) goto fail;
if (fmt->block_size != 0x02*fmt->channel_count) 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; fmt->interleave = 0x01;
break; break;
@ -174,9 +174,11 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk
fmt->coding_type = coding_MS_IMA; fmt->coding_type = coding_MS_IMA;
break; 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; 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; break;
case 0x69: /* XBOX IMA ADPCM [Dynasty Warriors 5 (Xbox)] */ 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; vgmstream->num_samples = fact_sample_count;
break; break;
case coding_YAMAHA: case coding_AICA:
case coding_YAMAHA_int: case coding_AICA_int:
vgmstream->num_samples = yamaha_bytes_to_samples(data_size, fmt.channel_count); vgmstream->num_samples = yamaha_bytes_to_samples(data_size, fmt.channel_count);
break; break;
@ -675,7 +677,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) {
switch (fmt.coding_type) { switch (fmt.coding_type) {
case coding_MSADPCM: case coding_MSADPCM:
case coding_MS_IMA: case coding_MS_IMA:
case coding_YAMAHA: case coding_AICA:
case coding_XBOX_IMA: case coding_XBOX_IMA:
case coding_IMA: case coding_IMA:
#ifdef VGM_USE_FFMPEG #ifdef VGM_USE_FFMPEG

View File

@ -7,20 +7,24 @@ static off_t get_rws_string_size(off_t offset, STREAMFILE *streamFile);
typedef struct { typedef struct {
int big_endian;
uint32_t codec;
int channel_count; int channel_count;
int codec;
int sample_rate; int sample_rate;
off_t file_name_offset;
int total_segments; int total_segments;
int target_segment; int target_segment;
off_t segment_offset; off_t segment_offset;
size_t segment_size; size_t segment_layers_size;
off_t segment_name_offset; off_t segment_name_offset;
int total_layers; int total_layers;
int target_layer; int target_layer;
off_t layer_offset; off_t layer_start;
size_t layer_size; //size_t layer_size;
off_t layer_name_offset; off_t layer_name_offset;
size_t file_size; size_t file_size;
@ -28,14 +32,13 @@ typedef struct {
size_t data_size; size_t data_size;
off_t data_offset; off_t data_offset;
//size_t stream_size; size_t usable_size;
size_t block_size; size_t block_size;
size_t block_size_total; size_t block_layers_size;
size_t stream_size_full;
off_t coefs_offset; off_t coefs_offset;
int use_segment_subsongs; /* otherwise play the whole thing */ char readable_name[STREAM_NAME_SIZE];
} rws_header; } rws_header;
@ -43,7 +46,6 @@ typedef struct {
VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) { VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL; VGMSTREAM * vgmstream = NULL;
off_t start_offset, offset; off_t start_offset, offset;
off_t stream_offset, name_offset;
size_t stream_size; size_t stream_size;
int loop_flag; int loop_flag;
int i; int i;
@ -56,99 +58,112 @@ VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
if (!check_extensions(streamFile,"rws")) if (!check_extensions(streamFile,"rws"))
goto fail; goto fail;
/* parse chunks (always LE) */ /* Audio .RWS is made of file + header + data chunks (non-audio .RWS with other chunks exist).
/* RWS are made of a file chunk with header and data chunks (other chunks exist for non-audio .RWS). * Chunk format (LE): id, size, RW version, data of size (version is repeated but same for all chunks).
* A chunk is: id, size, RW version (no real diffs), data of size (version is repeated but same for all chunks). * Version is 16b main + 16b build (possibly shifted), no known differences between versions,
* Version: 16b main + 16b build (can vary between files) ex: 0c02, 1003, 1400 = 3.5, 1803 = 3.6, 1C02 = 3.7. */ * 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; 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)) if (rws.file_size + 0x0c != get_streamfile_size(streamFile))
goto fail; goto fail;
if (read_32bitLE(0x0c,streamFile) != 0x0000080e) /* header chunk id */ if (read_32bitLE(0x0c,streamFile) != 0x0000080e) /* header id */
goto fail; 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 */ 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; 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)) if (rws.data_size+0x0c + rws.data_offset != get_streamfile_size(streamFile))
goto fail; 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; 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 */ /* 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 */ /* 0x00: actual header size (less than chunk size) */
read_32bit = (read_32bitLE(offset+0x00,streamFile) > rws.header_size) ? read_32bitBE : read_32bitLE; /* GC/Wii/X360 = BE */ /* 0x04/08/10: sizes of various sections? */
rws.total_segments = read_32bit(offset+0x20,streamFile); /* 0x14/18: config? */
rws.total_layers = read_32bit(offset+0x28,streamFile); /* 0x1c: null? */
if (rws.total_segments > 1 && rws.total_layers > 1) { rws.total_segments = read_32bit(offset + 0x20, streamFile);
VGM_LOG("RWS: unknown segments+layers layout\n"); /* 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 */ /* 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: /* 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)] * - "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)] * 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 * As each layer is given sample rate/channel/codec/etc they are treated as full subsongs, though usually
* they are treated as subsongs. Similarly segments can be treated as subsongs in some cases. * all layers are the same. Segments are just divisions and can be played one after another, but are useful
* They don't seem used at the same time, though could be possible. */ * 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; if (target_subsong == 0) target_subsong = 1;
rws.use_segment_subsongs = (rws.total_layers == 1 && rws.total_segments > 1); rws.target_layer = ((target_subsong-1) % rws.total_layers) + 1;
if (rws.use_segment_subsongs) { rws.target_segment = ((target_subsong-1) / rws.total_layers) + 1;
rws.target_layer = 1; total_subsongs = rws.total_layers * rws.total_segments;
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;
}
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
/* segment info, for all layers */ /* segment info */
/* 0x00/04/0c: command?, 0x18: full segment size (including padding), 0x1c: offset, others: ?) */
for (i = 0; i < rws.total_segments; i++) { for (i = 0; i < rws.total_segments; i++) {
if (i+1 == rws.target_segment) { 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?) */ /* usable layer sizes per segment */
for (i = 0; i < (rws.total_segments * rws.total_layers); i++) { /* sum usable segment sizes (no padding) */ for (i = 0; i < (rws.total_segments * rws.total_layers); i++) {
size_t usable_size = read_32bit(offset + 0x04*i,streamFile); /* size without padding */ size_t usable_size = read_32bit(offset, streamFile); /* without padding */
if (i+1 == rws.target_segment) { /* size order: segment1 layer1 size, ..., segment1 layerN size, segment2 layer1 size, etc */
rws.segment_size = usable_size; if (i+1 == target_subsong) { /* order matches our subsong order */
} rws.usable_size = usable_size;
if (i+1 == rws.target_layer || rws.total_layers == 1) {
rws.layer_size += usable_size;
} }
offset += 0x04;
} }
offset += 0x04 * (rws.total_segments * rws.total_layers);
/* segment uuids */ /* segment uuids */
offset += 0x10 * rws.total_segments; {
offset += 0x10 * rws.total_segments;
}
/* segment names */ /* segment names */
for (i = 0; i < rws.total_segments; i++) { 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); offset += get_rws_string_size(offset, streamFile);
} }
/* layer info */ /* 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 */ 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) { if (i+1 == rws.target_layer) {
//block_size_full = read_32bit(off + 0x10 + 0x28*i, streamFile); /* with padding, can be different per stream */ /* 0x00/04: config? */
rws.block_size = read_32bit(offset + 0x20 + 0x28*i, streamFile); /* without padding */ /* 0x08: null? */
rws.layer_offset = read_32bit(offset + 0x24 + 0x28*i, streamFile); /* within data */ /* 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 */ /* layer config */
/* 0x04: command?, 0x0c(1): bits per sample, others: null? */ for (i = 0; i < rws.total_layers; i++) {
for (i = 0; i < rws.total_layers; i++) { /* size depends on codec so we must parse it */ uint32_t layer_codec = 0;
int prev_codec = 0;
if (i+1 == rws.target_layer) { if (i+1 == rws.target_layer) {
rws.sample_rate = read_32bit(offset+0x00, streamFile); rws.sample_rate = read_32bit(offset + 0x00, streamFile);
//unk_size = read_32bit(off+0x08, streamFile); /* segment size again? loop-related? */ /* 0x04: config? */
rws.channel_count = read_8bit(offset+0x0d, streamFile); //rws.layer_size = read_32bit(offset + 0x08, streamFile); /* same or close to usable size */
rws.codec = read_32bitBE(offset+0x1c, streamFile); /* uuid of 128b but first 32b is enough */ /* 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; offset += 0x2c;
if (prev_codec == 0xF86215B0) { /* if codec is DSP there is an extra field per layer */ /* DSP has an extra field per layer */
/* 0x00: approx num samples? 0x04: approx size/loop related? (can be 0) */ if (layer_codec == 0xF86215B0) {
/* 0x00: approx num samples? */
/* 0x04: approx size/loop related? (can be 0) */
if (i+1 == rws.target_layer) { if (i+1 == rws.target_layer) {
rws.coefs_offset = offset + 0x1c; rws.coefs_offset = offset + 0x1c;
} }
offset += 0x60; offset += 0x60;
} }
offset += 0x04; /* padding/garbage */ offset += 0x04; /* padding/garbage */
} }
/* layer uuids */ /* layer uuids */
offset += 0x10 * rws.total_layers; {
offset += 0x10 * rws.total_layers;
}
/* layer names */ /* layer names */
for (i = 0; i < rws.total_layers; i++) { 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); offset += get_rws_string_size(offset, streamFile);
} }
/* rest is padding/garbage until chunk end (may contain strings and uninitialized memory) */
/* rest is padding/garbage until chunk end (may contain strings and weird stuff) */
// ... // ...
start_offset = rws.data_offset + 0x0c + (rws.segment_offset + rws.layer_start);
stream_size = rws.usable_size;
if (rws.use_segment_subsongs) { /* sometimes segment/layers go over file size in XBOX-IMA for no apparent reason, with usable_size bigger
stream_offset = rws.segment_offset; * than segment_layers_size yet data_size being correct (bug in RWS header? maybe stops decoding on file end) */
stream_size = rws.segment_size; {
name_offset = rws.segment_name_offset; 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) {
else { VGM_LOG("RWS: readjusting wrong stream size %x vs expected %x\n", stream_size, expected_size);
stream_offset = rws.layer_offset; stream_size = expected_size;
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;
} }
} }
/* 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 */ /* build the VGMSTREAM */
@ -246,25 +282,23 @@ VGMSTREAM * init_vgmstream_rws(STREAMFILE *streamFile) {
vgmstream->sample_rate = rws.sample_rate; vgmstream->sample_rate = rws.sample_rate;
vgmstream->num_streams = total_subsongs; vgmstream->num_streams = total_subsongs;
vgmstream->stream_size = stream_size; vgmstream->stream_size = stream_size;
if (name_offset) strcpy(vgmstream->stream_name, rws.readable_name);
read_string(vgmstream->stream_name,STREAM_NAME_SIZE, name_offset,streamFile);
vgmstream->layout_type = layout_blocked_rws; vgmstream->layout_type = layout_blocked_rws;
vgmstream->current_block_size = rws.block_size / vgmstream->channels; 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) { switch(rws.codec) {
case 0x17D21BD0: /* PCM PC (17D21BD0 8735ED4E B9D9B8E8 6EA9B995) */ case 0xD01BD217: /* {D01BD217,3587,4EED,B9,D9,B8,E8,6E,A9,B9,95} PCM PC/X360 */
case 0xD01BD217: /* PCM X360 (D01BD217 35874EED B9D9B8E8 6EA9B995) */ /* ex. D.i.R.T.: Origin of the Species (PC), The Legend of Spyro (X360) */
/* ex. D.i.R.T. - Origin of the Species (PC), The Legend of Spyro (X360) */
vgmstream->coding_type = coding_PCM16_int; vgmstream->coding_type = coding_PCM16_int;
vgmstream->codec_endian = (rws.codec == 0xD01BD217); /* X360: BE */ vgmstream->codec_endian = (rws.big_endian);
vgmstream->interleave_block_size = 0x02; /* only used to setup channels */ vgmstream->interleave_block_size = 0x02; /* only to setup channels */
vgmstream->num_samples = pcm_bytes_to_samples(stream_size, rws.channel_count, 16); vgmstream->num_samples = pcm_bytes_to_samples(stream_size, rws.channel_count, 16);
break; 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) */ /* ex. Silent Hill Origins (PS2), Ghost Rider (PS2), Max Payne 2 (PS2), Nana (PS2) */
vgmstream->coding_type = coding_PSX; vgmstream->coding_type = coding_PSX;
vgmstream->interleave_block_size = rws.block_size / 2; 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); vgmstream->num_samples = ps_bytes_to_samples(stream_size, rws.channel_count);
break; 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) */ /* ex. Burnout 2 (GC), Alice in Wonderland (Wii) */
vgmstream->coding_type = coding_NGC_DSP; vgmstream->coding_type = coding_NGC_DSP;
vgmstream->interleave_block_size = rws.block_size / 2; vgmstream->interleave_block_size = rws.block_size / 2;
/* get coefs (all channels share them so 0 spacing; also seem fixed for all RWS) */ /* get coefs (all channels share them; also seem fixed for all RWS) */
dsp_read_coefs_be(vgmstream,streamFile,rws.coefs_offset, 0); dsp_read_coefs_be(vgmstream, streamFile, rws.coefs_offset, 0);
vgmstream->num_samples = dsp_bytes_to_samples(stream_size, rws.channel_count); vgmstream->num_samples = dsp_bytes_to_samples(stream_size, rws.channel_count);
break; break;
case 0x936538EF: /* XBOX-IMA PC (936538EF 11B62D43 957FA71A DE44227A) */ case 0xEF386593: /* {EF386593,B611,432D,95,7F,A7,1A,DE,44,22,7A} XBOX-IMA PC */
case 0x2BA22F63: /* XBOX-IMA Xbox (2BA22F63 DD118F45 AA27A5C3 46E9790E) */ 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) */ /* 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->interleave_block_size = 0;
vgmstream->num_samples = xbox_ima_bytes_to_samples(stream_size, rws.channel_count); 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; goto fail;
return vgmstream; 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) { static off_t get_rws_string_size(off_t offset, STREAMFILE *streamFile) {
int i; int i;
for (i = 0; i < 0x800; i++) { /* 0x800=arbitrary max */ for (i = 0; i < 255; i++) { /* arbitrary max */
if (read_8bit(offset+i,streamFile) == 0) { /* null terminator */ if (read_8bit(offset+i, streamFile) == 0) { /* null terminator */
return i + (0x10 - (i % 0x10)); /* size is padded */ return i + (0x10 - (i % 0x10)); /* size is padded */
} }
} }

View File

@ -17,7 +17,7 @@ typedef enum {
DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */ DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */
MPEG = 8, /* MPEG (MP3) */ MPEG = 8, /* MPEG (MP3) */
IMA = 9, /* IMA ADPCM (low nibble first) */ 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) */ MSADPCM = 11, /* MS ADPCM (Windows games) */
NGC_DSP = 12, /* NGC DSP (Nintendo games) */ NGC_DSP = 12, /* NGC DSP (Nintendo games) */
PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */ 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 */ case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */
#endif #endif
case IMA: coding = coding_IMA; break; 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 MSADPCM: coding = coding_MSADPCM; break;
case NGC_DSP: coding = coding_NGC_DSP; break; case NGC_DSP: coding = coding_NGC_DSP; break;
case PCM8_U_int: coding = coding_PCM8_U_int; 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_PSX_badflags:
case coding_DVI_IMA: case coding_DVI_IMA:
case coding_IMA: case coding_IMA:
case coding_YAMAHA: case coding_AICA:
case coding_APPLE_IMA4: case coding_APPLE_IMA4:
vgmstream->interleave_block_size = txth.interleave; vgmstream->interleave_block_size = txth.interleave;
vgmstream->interleave_last_block_size = txth.interleave_last; vgmstream->interleave_last_block_size = txth.interleave_last;
@ -283,8 +283,8 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
coding = coding_DVI_IMA_int; coding = coding_DVI_IMA_int;
if (coding == coding_IMA) if (coding == coding_IMA)
coding = coding_IMA_int; coding = coding_IMA_int;
if (coding == coding_YAMAHA) if (coding == coding_AICA)
coding = coding_YAMAHA_int; coding = coding_AICA_int;
} }
/* to avoid endless loops */ /* to avoid endless loops */
@ -294,7 +294,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) {
coding == coding_IMA_int || coding == coding_IMA_int ||
coding == coding_DVI_IMA_int || coding == coding_DVI_IMA_int ||
coding == coding_SDX2_int || coding == coding_SDX2_int ||
coding == coding_YAMAHA_int) ) { coding == coding_AICA_int) ) {
goto fail; goto fail;
} }
} else { } 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 */ /* to avoid problems with dual stereo files (_L+_R) for codecs with stereo modes */
if (coding == coding_YAMAHA && txth.channels == 1) if (coding == coding_AICA && txth.channels == 1)
coding = coding_YAMAHA_int; coding = coding_AICA_int;
/* setup adpcm */ /* setup adpcm */
if (coding == coding_YAMAHA || coding == coding_YAMAHA_int) { if (coding == coding_AICA || coding == coding_AICA_int) {
int ch; int ch;
for (ch = 0; ch < vgmstream->channels; ch++) { for (ch = 0; ch < vgmstream->channels; ch++) {
vgmstream->ch[ch].adpcm_step_index = 0x7f; 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,"DVI_IMA")) txth->codec = DVI_IMA;
else if (is_string(val,"MPEG")) txth->codec = MPEG; else if (is_string(val,"MPEG")) txth->codec = MPEG;
else if (is_string(val,"IMA")) txth->codec = IMA; 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 = AICA;
else if (is_string(val,"AICA")) txth->codec = YAMAHA;
else if (is_string(val,"MSADPCM")) txth->codec = MSADPCM; 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,"NGC_DSP")) txth->codec = NGC_DSP;
else if (is_string(val,"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 t_pos = 0, p_pos = 0;
int p_size, t_size; int p_size, t_size;
uint16_t p_char, t_char; 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 /* compares 2 strings (case insensitive, to a point) allowing wildcards
* ex. for "test": match = "Test*", "*est", "*teSt","T*ES*T"; fail = "tst", "teest" * 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') { while(text[t_pos] != '\0') {
t_char = get_string_wchar(text, t_pos, &t_size); 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) if (t_char == p_char)
break; break;
@ -1704,7 +1703,7 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) {
case IMA: case IMA:
case DVI_IMA: case DVI_IMA:
return ima_bytes_to_samples(bytes, txth->channels); return ima_bytes_to_samples(bytes, txth->channels);
case YAMAHA: case AICA:
return yamaha_bytes_to_samples(bytes, txth->channels); return yamaha_bytes_to_samples(bytes, txth->channels);
case PCFX: case PCFX:
case OKI16: case OKI16:

View File

@ -83,13 +83,18 @@ typedef struct {
int config_ignore_fade; int config_ignore_fade;
int sample_rate; int sample_rate;
int loop_install;
int loop_install_set;
int loop_end_max; int loop_end_max;
double loop_start_second; double loop_start_second;
int32_t loop_start_sample; int32_t loop_start_sample;
double loop_end_second; double loop_end_second;
int32_t loop_end_sample; int32_t loop_end_sample;
int trim_set;
double trim_second;
int32_t trim_sample;
} txtp_entry; } txtp_entry;
@ -442,7 +447,7 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
if (current->sample_rate > 0) if (current->sample_rate > 0)
vgmstream->sample_rate = current->sample_rate; 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) { if (current->loop_start_second > 0 || current->loop_end_second > 0) {
current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate; current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate;
current->loop_end_sample = current->loop_end_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; 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 */ /* add macro to mixing list */
if (current->channel_mask) { if (current->channel_mask) {
int ch; 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); m = sscanf(config, " %i%c%i%n", &temp_i1,&temp_c,&temp_i2,&n);
if (m == 3 && (temp_c == ':' || temp_c == '_')) { if (m == 3 && (temp_c == ':' || temp_c == '_')) {
m = sscanf(config, " %lf%c%lf%n", &temp_f1,&temp_c,&temp_f2,&n); 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; return 0;
*value_f = temp_f1 * 60.0 + temp_f2; *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); m = sscanf(config, " %i.%i%n", &temp_i1,&temp_i2,&n);
if (m == 2) { if (m == 2) {
m = sscanf(config, " %lf%n", &temp_f1,&n); m = sscanf(config, " %lf%n", &temp_f1,&n);
if (m != 1 || temp_f1 < 0.0) if (m != 1 /*|| temp_f1 < 0.0*/)
return 0; return 0;
*value_f = temp_f1; *value_f = temp_f1;
return n; 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; current->sample_rate = cfg->sample_rate;
} }
if (cfg->loop_install) { if (cfg->loop_install_set) {
current->loop_install = cfg->loop_install; current->loop_install_set = cfg->loop_install_set;
current->loop_end_max = cfg->loop_end_max; current->loop_end_max = cfg->loop_end_max;
current->loop_start_sample = cfg->loop_start_sample; current->loop_start_sample = cfg->loop_start_sample;
current->loop_start_second = cfg->loop_start_second; current->loop_start_second = cfg->loop_start_second;
current->loop_end_sample = cfg->loop_end_sample; current->loop_end_sample = cfg->loop_end_sample;
current->loop_end_second = cfg->loop_end_second; 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) { static void parse_config(txtp_entry *cfg, char *config) {
@ -1084,12 +1113,17 @@ static void parse_config(txtp_entry *cfg, char *config) {
} }
config += n; 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, //;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); // 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 //todo cleanup
else if (strcmp(command,"@volume") == 0) { else if (strcmp(command,"@volume") == 0) {
txtp_mix_data mix = {0}; txtp_mix_data mix = {0};

View File

@ -47,7 +47,7 @@ VGMSTREAM * init_vgmstream_ubi_hx(STREAMFILE *streamFile) {
/* checks */ /* 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) * .hxc: Rayman 3 (PC), XIII (PC)
* .hx2: Rayman 3 (PS2), XIII (PS2) * .hx2: Rayman 3 (PS2), XIII (PS2)
* .hxg: Rayman 3 (GC), XIII (GC) * .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; 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; 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; off_t riff_offset, riff_size, chunk_offset, stream_adjust = 0, resource_size;
size_t chunk_size;
int cue_flag = 0; int cue_flag = 0;
//todo cleanup/unify common readings //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 */ /* find "datx" (external) or "data" (internal) also in machine endianness */
if (hx->is_external) { 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; goto fail;
hx->stream_size = read_32bit(chunk_offset + 0x00, sf); }
hx->stream_offset = read_32bit(chunk_offset + 0x04, sf) + stream_adjust;
} }
else { else {
if (!find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian)) if (!find_chunk_riff_ve(sf, 0x61746164,riff_offset + 0x0c,riff_size - 0x0c, &chunk_offset,NULL, hx->big_endian))

View File

@ -11,6 +11,7 @@ struct VGMSTREAM_TAGS {
char val[VGMSTREAM_TAGS_LINE_MAX]; char val[VGMSTREAM_TAGS_LINE_MAX];
/* file to find tags for */ /* file to find tags for */
int targetname_len;
char targetname[VGMSTREAM_TAGS_LINE_MAX]; char targetname[VGMSTREAM_TAGS_LINE_MAX];
/* path of targetname */ /* path of targetname */
char targetpath[VGMSTREAM_TAGS_LINE_MAX]; char targetpath[VGMSTREAM_TAGS_LINE_MAX];
@ -59,17 +60,21 @@ void vgmstream_tags_close(VGMSTREAM_TAGS *tags) {
free(tags); free(tags);
} }
/* Tags are divided in two: "global" @TAGS and "file" %TAGS for target filename. To extract both /* Find next tag and return 1 if found.
* 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 * Tags can be "global" @TAGS, "command" $TAGS, and "file" %TAGS for a target filename.
* is found it's marked as section_end. Then we can begin extracting tags within that section, until * To extract tags we must find either global tags, or the filename's tag "section"
* all tags are exhausted. Global tags are extracted while searching, so they always go first, and * where tags apply: (# @TAGS ) .. (other_filename) ..(# %TAGS section).. (target_filename).
* also meaning any tags after the section is found are ignored. */ * 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) { int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
off_t file_size = get_streamfile_size(tagfile); off_t file_size = get_streamfile_size(tagfile);
char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0}; char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0};
char line[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) if (!tags)
return 0; return 0;
@ -92,7 +97,7 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
/* read lines */ /* read lines */
while (tags->offset <= file_size) { 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) { if (tags->section_found && tags->offset >= tags->section_end) {
/* write extra tags after all regular tags */ /* write extra tags after all regular tags */
@ -163,10 +168,32 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
continue; /* next line */ continue; /* next line */
} }
/* find possible filename and section start/end */ /* find possible filename and section start/end
ok = sscanf(line, " %[^\r\n] ", currentname); * (.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 (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) */ /* section ok, start would be set before this (or be 0) */
tags->section_end = tags->offset; tags->section_end = tags->offset;
tags->section_found = 1; tags->section_found = 1;
@ -223,6 +250,7 @@ void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) {
tags->targetpath[0] = '\0'; tags->targetpath[0] = '\0';
strcpy(tags->targetname, target_filename); 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) { void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) {

View File

@ -1214,9 +1214,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
return (vgmstream->interleave_block_size - 0x07)*2 + 2; 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 */ case coding_WS: /* only works if output sample size is 8 bit, which always is for WS ADPCM */
return vgmstream->ws_output_size; return vgmstream->ws_output_size;
case coding_YAMAHA: case coding_AICA:
return 1; return 1;
case coding_YAMAHA_int: case coding_AICA_int:
return 2; return 2;
case coding_ASKA: case coding_ASKA:
return (0x40-0x04*vgmstream->channels) * 2 / vgmstream->channels; return (0x40-0x04*vgmstream->channels) * 2 / vgmstream->channels;
@ -1410,8 +1410,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
return vgmstream->interleave_block_size; return vgmstream->interleave_block_size;
case coding_WS: case coding_WS:
return vgmstream->current_block_size; return vgmstream->current_block_size;
case coding_YAMAHA: case coding_AICA:
case coding_YAMAHA_int: case coding_AICA_int:
return 0x01; return 0x01;
case coding_ASKA: case coding_ASKA:
case coding_NXAP: 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); vgmstream->channels,vgmstream->samples_into_block, samples_to_do, ch);
} }
break; break;
case coding_YAMAHA: case coding_AICA:
case coding_YAMAHA_int: case coding_AICA_int:
for (ch = 0; ch < vgmstream->channels; ch++) { 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, vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch,
is_stereo); is_stereo);
} }

View File

@ -148,8 +148,8 @@ typedef enum {
coding_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */ coding_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */
coding_WS, /* Westwood Studios VBR ADPCM */ coding_WS, /* Westwood Studios VBR ADPCM */
coding_YAMAHA, /* Yamaha ADPCM (stereo) */ coding_AICA, /* Yamaha AICA ADPCM (stereo) */
coding_YAMAHA_int, /* Yamaha ADPCM (mono/interleave) */ coding_AICA_int, /* Yamaha AICA ADPCM (mono/interleave) */
coding_ASKA, /* Aska ADPCM */ coding_ASKA, /* Aska ADPCM */
coding_NXAP, /* NXAP ADPCM */ coding_NXAP, /* NXAP ADPCM */