Merge pull request #484 from bnnm/tests-misc

tests misc
This commit is contained in:
bnnm 2019-10-07 00:33:56 +02:00 committed by GitHub
commit c66fe3ee96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1020 additions and 765 deletions

View File

@ -93,11 +93,11 @@ automatically. You need to manually refresh it by selecting songs and doing
### Audacious plugin
*Installation*: needs to be manually built. Instructions can be found in the BUILD
document.
document in vgmstream's source code.
### vgmstream123
*Installation*: needs to be manually built. Instructions can be found in the
BUILD document in vgmstream's source code.
*Installation*: needs to be manually built. Instructions can be found in the BUILD
document in vgmstream's source code.
Usage: `vgmstream123 [options] INFILE ...`
@ -148,15 +148,18 @@ them playable through vgmstream.
- .wav to .lwav (standard WAV)
- .wma to .lwma (standard WMA)
- .(any) to .vgmstream (FFmpeg formats or TXTH)
Command line tools don't have this restriction and will accept the original
filename.
The main advantage to rename them is that vgmstream may use the file's
internal loop info, or apply subtle fixes, but is also limited in some ways
(like standard/player's tagging).
(like standard/player's tagging). .vgmstream is a catch-all extension that
may work as a last resort to make a file playable.
.vgmstream is a catch-all extension that may work as a last resort to make
a file playable.
Some plugins have options that allow any extension (common or unknown) to be
played, making renaming is unnecessary (may need to adjust plugin priority in
player's options).
When extracting from a bigfile, sometimes internal files don't have an actual
name+extension. Those should be renamed to its proper/common extension, as the
@ -214,7 +217,7 @@ a companion file:
The key file can be ".(ext)key" (for the whole folder), or "(name).(ext)key"
(for a single file). The format is made up to suit vgmstream.
### Artificial/generic headers
### Artificial files
In some cases a file only has raw data, while important header info (codec type,
sample rate, channels, etc) is stored in the .exe or other hard to locate places.
@ -225,18 +228,29 @@ The resulting file must be (name).genh. Contains static header data.
Programs like VGMToolbox can help to create GENH.
**TXTH**: a text header placed in an external file. The TXTH must be named
".txth" or ".(ext).txth" (for the whole folder), or "(name.ext).txth" (for a
`.txth` or `.(ext).txth` (for the whole folder), or `(name.ext).txth` (for a
single file). Contains dynamic text commands to read data from the original
file, or static values.
**TXTP**: a text playing configurator. Can contain a list of filenames to
play as one (ex. "intro.vag" "loop.vag"), list of separate channel files
to join as a single multichannel file, subsong index (ex. bgm.sxd#10),
per-file configurations like number of loops, and many other features.
*TXTH* is recomended over *GENH* as it's far easier to create and has many
more functions.
For files that already play, sometimes they are used by the game in various
complex and non-standard ways, like playing multiple small songs as a single
one, or using some channels as a section of the song. For those cases we
can use create a *TXTP* file.
**TXTP**: a text player configurator named `(name).txtp`. Text inside can
contain a list of filenames to play as one (ex. `intro.vag(line)loop.vag`),
list of separate channel files to join as a single multichannel file,
subsong index (ex. `bgm.sxd#10`), per-file configurations like number of
loops, remove unneeded channels, and many other features.
Creation of those files is meant for advanced users, docs can be found in
vgmstream source.
### Plugin conflicts
Since vgmstream supports a huge amount of formats it's possibly that some of
them are also supported in other plugins, and this sometimes causes conflicts.
@ -244,9 +258,18 @@ If a file that should isn't playing or looping, first make sure vgmstream is
really opening it (should show "VGMSTREAM" somewhere in the file info), and
try to remove a few other plugins.
foobar's ffmpeg plugin and foo_adpcm are known to cause issues, but in
foobar's FFmpeg plugin and foo_adpcm are known to cause issues, but in
recent versions (1.4.x) you can configure plugin priority.
In Audacious, vgmstream is set with slightly higher priority than FFmpeg,
since it steals many formats that you normally want to loop (like .adx).
However other plugins may set themselves higher, stealing formats instead.
If current Audacious version doesn't let to change plugin priority you may
need to disable some plugins (requires restart) or set priority on compile
time. Particularly, mpg123 plugin may steal formats that aren't even MP3,
making impossible for vgmstream to play it properly.
### Channel issues
Some games layer a huge number of channels, that are disabled or downmixed
during gameplay. The player may be unable to play those files (for example
@ -275,17 +298,17 @@ filename1
filename2
```
Accepted tags depend on the player (foobar: any; winamp: see ATF config),
typically ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc, lower or uppercase,
typically *ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc*, lower or uppercase,
separated by one or multiple spaces. Repeated tags overwrite previous
(ex.- may define @COMPOSER for multiple tracks). It only reads up to current
_filename_ though, so any @TAG below would be ignored.
(ex.- may define *@COMPOSER* for multiple tracks). It only reads up to current
*filename* though, so any *@TAG* below would be ignored.
Playlist formatting should follow player's config. ASCII or UTF-8 tags work.
GLOBAL_COMMANDs currently can be:
- AUTOTRACK: sets %TRACK% tag automatically (1..N as files are encountered
*GLOBAL_COMMAND*s currently can be:
- *AUTOTRACK*: sets *%TRACK* tag automatically (1..N as files are encountered
in the tag file).
- AUTOALBUM: sets %ALBUM% tag automatically using the containing dir as album.
- *AUTOALBUM*: sets *%ALBUM* tag automatically using the containing dir as album.
Note that since you can use global tags don't need to put all files inside.
This would be a perfectly valid *!tags.m3u*:
@ -306,7 +329,7 @@ enabled in preferences):
If your player isn't picking tags make sure vgmstream is detecting the song
(as other plugins can steal its extensions, see above), .m3u is properly
named and that filenames inside match the song filename. For Winamp you need
to make sure options > titles > advanced title formatting checkbox is set and
to make sure *options > titles > advanced title formatting* checkbox is set and
the format defined.
## Virtual TXTP files

View File

@ -16,6 +16,7 @@
extern "C" {
#include "../src/vgmstream.h"
#include "../src/plugins.h"
}
#include "plugin.h"
#include "vfs.h"
@ -31,20 +32,24 @@ extern "C" {
#define CFG_ID "vgmstream" // ID for storing in audacious
#define MIN_BUFFER_SIZE 576
/* internal state */
VgmstreamPlugin aud_plugin_instance;
Settings vgmstream_cfg;
VGMSTREAM *vgmstream = NULL;
/* global state */
/*EXPORT*/ VgmstreamPlugin aud_plugin_instance;
audacious_settings settings;
VGMSTREAM *vgmstream = NULL; //todo make local?
const char *const VgmstreamPlugin::defaults[] = {
"loop_forever", "1",
"loop_count", "2",
"fade_length", "3",
"fade_delay", "3",
"loop_forever", "FALSE",
"loop_count", "2", //maybe double?
"fade_length", "10.0",
"fade_delay", "0.0",
"downmix_channels", "8",
"exts_unknown_on", "FALSE",
"exts_common_on", "FALSE",
NULL
};
// N_(...) for i18n but not much point here
const char VgmstreamPlugin::about[] =
"vgmstream plugin " VERSION " " __DATE__ "\n"
"by hcs, FastElbja, manakoAT, bxaimc, snakemeat, soneek, kode54, bnnm and many others\n"
@ -58,70 +63,99 @@ const char VgmstreamPlugin::about[] =
"https://sourceforge.net/projects/vgmstream/ (original)";
const PreferencesWidget VgmstreamPlugin::widgets[] = {
WidgetLabel(N_("<b>vgmstream Config</b>")),
WidgetCheck(N_("Loop Forever:"), WidgetBool(vgmstream_cfg.loop_forever)),
WidgetSpin(N_("Loop Count:"), WidgetInt(vgmstream_cfg.loop_count), {1, 20, 1}),
WidgetSpin(N_("Fade Length:"), WidgetFloat(vgmstream_cfg.fade_length), {0, 60, 0.1}),
WidgetSpin(N_("Fade Delay:"), WidgetFloat(vgmstream_cfg.fade_delay), {0, 60, 0.1}),
WidgetLabel(N_("<b>vgmstream config</b>")),
WidgetCheck(N_("Loop forever:"), WidgetBool(settings.loop_forever)),
WidgetSpin(N_("Loop count:"), WidgetInt(settings.loop_count), {1, 20, 1}),
WidgetSpin(N_("Fade length:"), WidgetFloat(settings.fade_length), {0, 60, 0.1}),
WidgetSpin(N_("Fade delay:"), WidgetFloat(settings.fade_delay), {0, 60, 0.1}),
WidgetSpin(N_("Downmix:"), WidgetInt(settings.downmix_channels), {1, 20, 1}),
WidgetCheck(N_("Enable unknown exts"), WidgetBool(settings.exts_unknown_on)),
// Audacious 3.6 will only match one plugin so this option has no actual use
// (ex. a fake .flac only gets to the FLAC plugin and never to vgmstream, even on error)
//WidgetCheck(N_("Enable common exts"), WidgetBool(settings.exts_common_on)),
};
void vgmstream_cfg_load() {
debugMessage("cfg_load called");
void vgmstream_settings_load() {
AUDINFO("load settings\n");
aud_config_set_defaults(CFG_ID, VgmstreamPlugin::defaults);
vgmstream_cfg.loop_forever = aud_get_bool(CFG_ID, "loop_forever");
vgmstream_cfg.loop_count = aud_get_int(CFG_ID, "loop_count");
vgmstream_cfg.fade_length = aud_get_double(CFG_ID, "fade_length");
vgmstream_cfg.fade_delay = aud_get_double(CFG_ID, "fade_delay");
settings.loop_forever = aud_get_bool(CFG_ID, "loop_forever");
settings.loop_count = aud_get_int(CFG_ID, "loop_count");
settings.fade_length = aud_get_double(CFG_ID, "fade_length");
settings.fade_delay = aud_get_double(CFG_ID, "fade_delay");
settings.downmix_channels = aud_get_int(CFG_ID, "downmix_channels");
settings.exts_unknown_on = aud_get_bool(CFG_ID, "exts_unknown_on");
settings.exts_common_on = aud_get_bool(CFG_ID, "exts_common_on");
}
void vgmstream_cfg_save() {
debugMessage("cfg_save called");
aud_set_bool(CFG_ID, "loop_forever", vgmstream_cfg.loop_forever);
aud_set_int(CFG_ID, "loop_count", vgmstream_cfg.loop_count);
aud_set_double(CFG_ID, "fade_length", vgmstream_cfg.fade_length);
aud_set_double(CFG_ID, "fade_delay", vgmstream_cfg.fade_delay);
void vgmstream_settings_save() {
AUDINFO("save settings\n");
aud_set_bool(CFG_ID, "loop_forever", settings.loop_forever);
aud_set_int(CFG_ID, "loop_count", settings.loop_count);
aud_set_double(CFG_ID, "fade_length", settings.fade_length);
aud_set_double(CFG_ID, "fade_delay", settings.fade_delay);
aud_set_int(CFG_ID, "downmix_channels", settings.downmix_channels);
aud_set_bool(CFG_ID, "exts_unknown_on", settings.exts_unknown_on);
aud_set_bool(CFG_ID, "exts_common_on", settings.exts_common_on);
}
const PluginPreferences VgmstreamPlugin::prefs = {
{widgets}, vgmstream_cfg_load, vgmstream_cfg_save
{widgets}, vgmstream_settings_load, vgmstream_settings_save
};
// validate extension
// validate extension (thread safe)
bool VgmstreamPlugin::is_our_file(const char *filename, VFSFile &file) {
const char * ext = strrchr(filename,'.');
if (ext==NULL)
ext = filename+strlen(filename); /* point to null, i.e. an empty string for the extension */
else
ext = ext+1; /* skip the dot */
AUDDBG("test file=%s\n", filename);
size_t ext_list_len = 0;
const char ** ext_list = vgmstream_get_formats(&ext_list_len);
vgmstream_ctx_valid_cfg cfg = {0};
for (int i=0; i < ext_list_len; i++) {
if (!strcasecmp(ext, ext_list[i]))
return true;
}
return false;
cfg.accept_unknown = settings.exts_unknown_on;
cfg.accept_common = settings.exts_common_on;
return vgmstream_ctx_is_valid(filename, &cfg) > 0 ? true : false;
}
// called on startup (main thread)
bool VgmstreamPlugin::init() {
debugMessage("init");
AUDINFO("plugin start\n");
vgmstream_cfg_load();
vgmstream_settings_load();
debugMessage("after load cfg");
return true;
}
// called on stop (main thread)
void VgmstreamPlugin::cleanup() {
debugMessage("cleanup");
AUDINFO("plugin end\n");
vgmstream_cfg_save();
vgmstream_settings_save();
}
// called every time the user adds a new file to playlist
bool read_data(const char * filename, Tuple & tuple) {
#if 0
static int get_filename_subtune(const char * filename) {
int subtune;
int pos = strstr("?"))
if (pos <= 0)
return -1;
if (sscanf(filename + pos, "%i", &subtune) != 1)
return -1;
return subtune;
}
#endif
// internal helper, called every time user adds a new file to playlist
static bool read_info(const char * filename, Tuple & tuple) {
AUDINFO("read file=%s\n", filename);
#if 0
//todo subsongs:
// in theory just set FlagSubtunes in plugin.h, and set_subtunes below
// Audacious will call "play" and this(?) with filename?N where N=subtune
// and you just load subtune N, but I can't get it working
int subtune;
string basename;
int subtune = get_filename_subtune(basename, &subtune);
//must use basename to open streamfile
#endif
STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) return false;
@ -132,16 +166,47 @@ bool read_data(const char * filename, Tuple & tuple) {
return false;
}
tuple.set_filename(filename); //may leak string???
int bitrate = get_vgmstream_average_bitrate(infostream);
tuple.set_int(Tuple::Bitrate, bitrate);
#if 0
int total_subtunes = infostream->num_streams;
int ms = get_vgmstream_play_samples(vgmstream_cfg.loop_count, vgmstream_cfg.fade_length, vgmstream_cfg.fade_delay, infostream);
//somehow max was changed to short in recent versions, though
// some formats can exceed this
if (total_subtunes > 32767)
total_subtunes = 32767;
if (infostream->num_streams > 1 && subtune <= 0) {
//set nullptr to leave subsong index linear (must add +1 to subtune)
tuple.set_subtunes(total_subtunes, nullptr);
return true; //must return?
}
streamfile->stream_index = (subtune + 1);
#endif
//todo apply_config
int output_channels = infostream->channels;
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
vgmstream_mixing_enable(infostream, 0, NULL, &output_channels);
int bitrate = get_vgmstream_average_bitrate(infostream);
int ms = get_vgmstream_play_samples(settings.loop_count, settings.fade_length, settings.fade_delay, infostream);
ms = ms* 1000LL / infostream->sample_rate;
// short form, not sure if better way
tuple.set_format("vgmstream codec", output_channels, infostream->sample_rate, bitrate);
tuple.set_filename(filename); //used?
tuple.set_int(Tuple::Bitrate, bitrate); //in kb/s
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
//todo here we could call describe_vgmstream() and get substring to add tags and stuff
tuple.set_str(Tuple::Codec, "vgmstream codec");
//tuple.set_int(Tuple::Subtune, subtune);
//tuple.set_int(Tuple::NumSubtunes, subtune); //done un set_subtunes
//todo tags (see tuple.h)
//tuple.set_int (Tuple::Track, ...);
//tuple.set_str (Tuple::Artist, ...);
//tuple.set_str (Tuple::Album, ...);
close_streamfile(streamfile);
close_vgmstream(infostream);
@ -149,118 +214,31 @@ bool read_data(const char * filename, Tuple & tuple) {
return true;
}
//for Audacious <= 3.7 (unused otherwise)
// thread safe (for Audacious <= 3.7, unused otherwise)
Tuple VgmstreamPlugin::read_tuple(const char *filename, VFSFile &file) {
debugMessage("read_tuple");
Tuple tuple;
read_data(filename, tuple);
read_info(filename, tuple);
return tuple;
}
//for Audacious >= 3.8 (unused otherwise)
// thread safe (for Audacious >= 3.8, unused otherwise)
bool VgmstreamPlugin::read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image) {
debugMessage("read_tag");
return read_data(filename, tuple);
return read_info(filename, tuple);
}
bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
debugMessage("start play");
int current_sample_pos = 0;
int bitrate;
// just in case
if (vgmstream)
close_vgmstream(vgmstream);
STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) {
printf("failed opening %s\n", filename);
return false;
}
vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
close_streamfile(streamfile);
if (!vgmstream || vgmstream->channels <= 0) {
printf("Error::Channels are zero or couldn't init plugin\n");
if (vgmstream)
close_vgmstream(vgmstream);
vgmstream = NULL;
return false;
}
short buffer[MIN_BUFFER_SIZE * vgmstream->channels];
int max_buffer_samples = sizeof(buffer) / sizeof(buffer[0]) / vgmstream->channels;
int stream_samples_amount = get_vgmstream_play_samples(
vgmstream_cfg.loop_count, vgmstream_cfg.fade_length,
vgmstream_cfg.fade_delay, vgmstream);
bitrate = get_vgmstream_average_bitrate(vgmstream);
set_stream_bitrate(bitrate);
open_audio(FMT_S16_LE, vgmstream->sample_rate, vgmstream->channels);
int fade_samples = vgmstream_cfg.fade_length * vgmstream->sample_rate;
while (!check_stop()) {
int toget = max_buffer_samples;
int seek_value = check_seek();
if (seek_value > 0)
seek(seek_value, current_sample_pos);
// If we haven't configured the plugin to play forever
// or the current song is not loopable.
if (!vgmstream_cfg.loop_forever || !vgmstream->loop_flag) {
if (current_sample_pos >= stream_samples_amount)
break;
if (current_sample_pos + toget > stream_samples_amount)
toget = stream_samples_amount - current_sample_pos;
}
render_vgmstream(buffer, toget, vgmstream);
if (vgmstream->loop_flag && fade_samples > 0 &&
!vgmstream_cfg.loop_forever) {
int samples_into_fade =
current_sample_pos - (stream_samples_amount - fade_samples);
if (samples_into_fade + toget > 0) {
for (int j = 0; j < toget; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness =
(double)(fade_samples - samples_into_fade) / fade_samples;
for (int k = 0; k < vgmstream->channels; k++)
buffer[j * vgmstream->channels + k] *= fadedness;
}
}
}
}
write_audio(buffer, toget * sizeof(short) * vgmstream->channels);
current_sample_pos += toget;
}
debugMessage("finished");
if (vgmstream)
close_vgmstream(vgmstream);
vgmstream = NULL;
return true;
}
void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
debugMessage("seeking");
// internal util to seek during play
static void seek_helper(int seek_value, int &current_sample_pos, int input_channels) {
AUDINFO("seeking\n");
// compute from ms to samples
int seek_needed_samples = (long long)seek_value * vgmstream->sample_rate / 1000L;
short buffer[MIN_BUFFER_SIZE * vgmstream->channels];
int max_buffer_samples = sizeof(buffer) / sizeof(buffer[0]) / vgmstream->channels;
short buffer[MIN_BUFFER_SIZE * input_channels];
int max_buffer_samples = MIN_BUFFER_SIZE;
int samples_to_do = 0;
if (seek_needed_samples < current_sample_pos) {
// go back in time, reopen file
debugMessage("reopen file to seek backward");
AUDINFO("resetting file to seek backwards\n");
reset_vgmstream(vgmstream);
current_sample_pos = 0;
samples_to_do = seek_needed_samples;
@ -271,7 +249,7 @@ void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
// do the actual seeking
if (samples_to_do >= 0) {
debugMessage("render forward");
AUDINFO("rendering forward\n");
// render till seeked sample
while (samples_to_do > 0) {
@ -280,21 +258,97 @@ void VgmstreamPlugin::seek(int seek_value, int &current_sample_pos) {
samples_to_do -= seek_samples;
render_vgmstream(buffer, seek_samples, vgmstream);
}
debugMessage("after render vgmstream");
}
}
void debugMessage(const char *str) {
#ifdef DEBUG
timeval curTime;
gettimeofday(&curTime, NULL);
int milli = curTime.tv_usec / 1000;
// called on play (play thread)
bool VgmstreamPlugin::play(const char *filename, VFSFile &file) {
AUDINFO("play file=%s\n", filename);
char buffer[80];
strftime(buffer, 80, "%H:%M:%S", localtime(&curTime.tv_sec));
// just in case
if (vgmstream)
close_vgmstream(vgmstream);
char currentTime[84] = "";
sprintf(currentTime, "%s:%d", buffer, milli);
printf("[%s] Debug: %s\n", currentTime, str);
#endif
STREAMFILE *streamfile = open_vfs(filename);
if (!streamfile) {
AUDERR("failed opening file %s\n", filename);
return false;
}
vgmstream = init_vgmstream_from_STREAMFILE(streamfile);
close_streamfile(streamfile);
if (!vgmstream) {
AUDINFO("filename %s is not a valid format\n", filename);
close_vgmstream(vgmstream);
vgmstream = NULL;
return false;
}
int bitrate = get_vgmstream_average_bitrate(vgmstream);
set_stream_bitrate(bitrate);
//todo apply config
int input_channels = vgmstream->channels;
int output_channels = vgmstream->channels;
/* enable after all config but before outbuf */
vgmstream_mixing_autodownmix(vgmstream, settings.downmix_channels);
vgmstream_mixing_enable(vgmstream, MIN_BUFFER_SIZE, &input_channels, &output_channels);
//FMT_S8 / FMT_S16_NE / FMT_S24_NE / FMT_S32_NE / FMT_FLOAT
open_audio(FMT_S16_LE, vgmstream->sample_rate, output_channels);
// play
short buffer[MIN_BUFFER_SIZE * input_channels];
int max_buffer_samples = MIN_BUFFER_SIZE;
int stream_samples_amount = get_vgmstream_play_samples(
settings.loop_count, settings.fade_length,
settings.fade_delay, vgmstream);
int fade_samples = settings.fade_length * vgmstream->sample_rate;
int current_sample_pos = 0;
while (!check_stop()) {
int toget = max_buffer_samples;
// handle seek request
int seek_value = check_seek();
if (seek_value >= 0)
seek_helper(seek_value, current_sample_pos, input_channels);
// check stream finished
if (!settings.loop_forever || !vgmstream->loop_flag) {
if (current_sample_pos >= stream_samples_amount)
break;
if (current_sample_pos + toget > stream_samples_amount)
toget = stream_samples_amount - current_sample_pos;
}
render_vgmstream(buffer, toget, vgmstream);
if (vgmstream->loop_flag && fade_samples > 0 &&
!settings.loop_forever) {
int samples_into_fade =
current_sample_pos - (stream_samples_amount - fade_samples);
if (samples_into_fade + toget > 0) {
for (int j = 0; j < toget; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness =
(double)(fade_samples - samples_into_fade) / fade_samples;
for (int k = 0; k < output_channels; k++)
buffer[j * output_channels + k] *= fadedness;
}
}
}
}
write_audio(buffer, toget * sizeof(short) * output_channels);
current_sample_pos += toget;
}
AUDINFO("play finished\n");
close_vgmstream(vgmstream);
vgmstream = NULL;
return true;
}

View File

@ -7,6 +7,11 @@
#include <libaudcore/preferences.h>
#include <libaudcore/runtime.h>
#ifndef AUDACIOUS_VGMSTREAM_PRIORITY
/* set higher than FFmpeg's 10 */
#define AUDACIOUS_VGMSTREAM_PRIORITY 9
#endif
class VgmstreamPlugin : public InputPlugin {
public:
//static const char *const exts[];
@ -19,12 +24,11 @@ public:
N_("vgmstream Decoder"), N_("vgmstream"), about, &prefs,
};
// accepted exts are validated at runtime in is_our_file now, this is to set a static list
//static constexpr auto iinfo = InputInfo().with_exts(exts);
//constexpr VgmstreamPlugin() : InputPlugin(info, iinfo) {}
//constexpr VgmstreamPlugin() : InputPlugin (info, InputInfo().with_exts(exts)) {}
constexpr VgmstreamPlugin() : InputPlugin (info, InputInfo()) {}
constexpr VgmstreamPlugin() : InputPlugin (info,
InputInfo() //InputInfo(FlagSubtunes) // allow subsongs
.with_priority(AUDACIOUS_VGMSTREAM_PRIORITY) // where 0=default(?), 10=lowest
//.with_exts(exts)) {} //accepted exts are validated at runtime now
) {}
bool init();
void cleanup();
@ -33,12 +37,9 @@ public:
bool read_tag(const char * filename, VFSFile & file, Tuple & tuple, Index<char> * image);
bool play(const char *filename, VFSFile &file);
private:
void seek(int seek_value, int &current_sample_pos);
};
// reminder of usage, probably no more need
// static extension list, not sure of advantages (uses is_our_file)
//const char *const VgmstreamPlugin::exts[] = { "ext1", "ext2", ..., NULL }
@ -47,12 +48,14 @@ typedef struct {
int loop_count;
double fade_length;
double fade_delay;
} Settings;
int downmix_channels;
bool exts_unknown_on;
bool exts_common_on;
} audacious_settings;
extern Settings vgmstream_cfg;
extern audacious_settings settings;
void debugMessage(const char *str);
void vgmstream_cfg_load();
void vgmstream_cfg_save();
void vgmstream_settings_load();
void vgmstream_settings_save();
#endif

View File

@ -7,99 +7,98 @@
#include "plugin.h"
#include "vfs.h"
typedef struct _VFSSTREAMFILE {
STREAMFILE sf;
VFSFile *vfsFile;
off_t offset;
char name[32768];
} VFSSTREAMFILE;
typedef struct {
STREAMFILE sf;
VFSFile *vfsFile;
off_t offset;
char name[32768];
} VFS_STREAMFILE;
static STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path);
static size_t read_vfs(VFSSTREAMFILE *streamfile, uint8_t *dest, off_t offset,
size_t length) {
size_t sz;
// if the offsets don't match, then we need to perform a seek
if (streamfile->offset != offset) {
streamfile->vfsFile->fseek(offset, VFS_SEEK_SET);
streamfile->offset = offset;
}
static size_t read_vfs(VFS_STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length) {
size_t bytes_read;
sz = streamfile->vfsFile->fread(dest, 1, length);
// increment our current offset
streamfile->offset += sz;
if (!dest || length <= 0 || offset < 0)
return 0;
return sz;
// if the offsets don't match, then we need to perform a seek
if (streamfile->offset != offset) {
int ok = streamfile->vfsFile->fseek(offset, VFS_SEEK_SET);
if (ok != 0) return 0;
streamfile->offset = offset;
}
bytes_read = streamfile->vfsFile->fread(dest, 1, length);
streamfile->offset += bytes_read;
return bytes_read;
}
static void close_vfs(VFSSTREAMFILE *streamfile) {
debugMessage("close_vfs");
delete streamfile->vfsFile; //fcloses the internal file too
free(streamfile);
static void close_vfs(VFS_STREAMFILE *streamfile) {
delete streamfile->vfsFile; //fcloses the internal file too
free(streamfile);
}
static size_t get_size_vfs(VFSSTREAMFILE *streamfile) {
return streamfile->vfsFile->fsize();
static size_t get_size_vfs(VFS_STREAMFILE *streamfile) {
return streamfile->vfsFile->fsize();
}
static size_t get_offset_vfs(VFSSTREAMFILE *streamfile) {
return streamfile->offset;
static size_t get_offset_vfs(VFS_STREAMFILE *streamfile) {
return streamfile->offset;
}
static void get_name_vfs(VFSSTREAMFILE *streamfile, char *buffer,
size_t length) {
strncpy(buffer, streamfile->name, length);
buffer[length - 1] = '\0';
static void get_name_vfs(VFS_STREAMFILE *streamfile, char *buffer, size_t length) {
strncpy(buffer, streamfile->name, length);
buffer[length - 1] = '\0';
}
static STREAMFILE *open_vfs_impl(VFSSTREAMFILE *streamfile,
const char *const filename,
size_t buffersize) {
if (!filename)
return NULL;
static STREAMFILE *open_vfs_impl(VFS_STREAMFILE *streamfile, const char *const filename, size_t buffersize) {
if (!filename)
return NULL;
return open_vfs(filename);
return open_vfs(filename);
}
STREAMFILE *open_vfs_by_VFSFILE(VFSFile *file, const char *path) {
VFSSTREAMFILE *streamfile = (VFSSTREAMFILE *)malloc(sizeof(VFSSTREAMFILE));
if (!streamfile)
return NULL;
VFS_STREAMFILE *streamfile = (VFS_STREAMFILE *)malloc(sizeof(VFS_STREAMFILE));
if (!streamfile)
return NULL;
// success, set our pointers
memset(streamfile, 0, sizeof(VFSSTREAMFILE));
// success, set our pointers
memset(streamfile, 0, sizeof(VFS_STREAMFILE));
streamfile->sf.read = (size_t (*)(STREAMFILE *, uint8_t *, off_t, size_t))read_vfs;
streamfile->sf.get_size = (size_t (*)(STREAMFILE *))get_size_vfs;
streamfile->sf.get_offset = (off_t (*)(STREAMFILE *))get_offset_vfs;
streamfile->sf.get_name = (void (*)(STREAMFILE *, char *, size_t))get_name_vfs;
streamfile->sf.open = (STREAMFILE *(*)(STREAMFILE *, const char *, size_t))open_vfs_impl;
streamfile->sf.close = (void (*)(STREAMFILE *))close_vfs;
streamfile->sf.read = (size_t (*)(STREAMFILE *, uint8_t *, off_t, size_t))read_vfs;
streamfile->sf.get_size = (size_t (*)(STREAMFILE *))get_size_vfs;
streamfile->sf.get_offset = (off_t (*)(STREAMFILE *))get_offset_vfs;
streamfile->sf.get_name = (void (*)(STREAMFILE *, char *, size_t))get_name_vfs;
streamfile->sf.open = (STREAMFILE *(*)(STREAMFILE *, const char *, size_t))open_vfs_impl;
streamfile->sf.close = (void (*)(STREAMFILE *))close_vfs;
streamfile->vfsFile = file;
streamfile->offset = 0;
strncpy(streamfile->name, path, sizeof(streamfile->name));
streamfile->name[sizeof(streamfile->name) - 1] = '\0';
streamfile->vfsFile = file;
streamfile->offset = 0;
strncpy(streamfile->name, path, sizeof(streamfile->name));
streamfile->name[sizeof(streamfile->name) - 1] = '\0';
// for reference, actual file path ("name" has protocol path, file://...).
// name should work for all situations but in case it's needed again maybe
// get_name should always return realname, as it's used to open companion VFSFiles
//{
// gchar *realname = g_filename_from_uri(path, NULL, NULL);
// strncpy(streamfile->realname, realname, sizeof(streamfile->realname));
// streamfile->realname[sizeof(streamfile->realname) - 1] = '\0';
// g_free(realname);
//}
// for reference, actual file path ("name" has protocol path, file://...).
// name should work for all situations but in case it's needed again maybe
// get_name should always return realname, as it's used to open companion VFSFiles
//{
// gchar *realname = g_filename_from_uri(path, NULL, NULL);
// strncpy(streamfile->realname, realname, sizeof(streamfile->realname));
// streamfile->realname[sizeof(streamfile->realname) - 1] = '\0';
// g_free(realname);
//}
return &streamfile->sf;
return &streamfile->sf;
}
STREAMFILE *open_vfs(const char *path) {
VFSFile *vfsFile = new VFSFile(path, "rb");
if (!vfsFile || !*vfsFile) {
delete vfsFile;
return NULL;
}
VFSFile *vfsFile = new VFSFile(path, "rb");
if (!vfsFile || !*vfsFile) {
delete vfsFile;
return NULL;
}
return open_vfs_by_VFSFILE(vfsFile, path);
return open_vfs_by_VFSFILE(vfsFile, path);
}

View File

@ -509,6 +509,7 @@ static int play_compressed_file(const char *filename, struct params *par, const
if (!mkdtemp(temp_dir)) {
fprintf(stderr, "%s: error creating temp dir for decompression\n", temp_dir);
ret = -1;
goto fail;
}
@ -567,8 +568,7 @@ static int play_compressed_file(const char *filename, struct params *par, const
remove(temp_file);
remove(temp_dir);
fail:
fail:
free(cmd);
free(temp_file);

View File

@ -30,7 +30,7 @@ extern int optind, opterr, optopt;
static size_t make_wav_header(uint8_t * buf, size_t buf_size, int32_t sample_count, int32_t sample_rate, int channels, int smpl_chunk, int32_t loop_start, int32_t loop_end);
static void usage(const char * name) {
static void usage(const char * name, int is_full) {
fprintf(stderr,"vgmstream CLI decoder " VERSION " " __DATE__ "\n"
"Usage: %s [-o outfile.wav] [options] infile\n"
"Options:\n"
@ -52,10 +52,16 @@ static void usage(const char * name) {
" -x: decode and print adxencd command line to encode as ADX\n"
" -g: decode and print oggenc command line to encode as OGG\n"
" -b: decode and print batch variable commands\n"
" -r: output a second file after resetting (for testing)\n"
" -k N: seeks to N samples before decoding (for testing)\n"
" -t file: print if tags are found in file (for testing)\n"
" -h: print extra commands\n"
, name);
if (is_full) {
fprintf(stderr,
" -r: output a second file after resetting (for reset testing)\n"
" -k N: seeks to N samples before decoding (for seek testing)\n"
" -t file: print tags found in file (for tag testing)\n"
" -O: decode but don't write to file (for performance testing)\n"
);
}
}
@ -63,6 +69,7 @@ typedef struct {
char * infilename;
char * outfilename;
char * tag_filename;
int decode_only;
int ignore_loop;
int force_loop;
int really_force_loop;
@ -101,7 +108,7 @@ static int parse_config(cli_config *cfg, int argc, char ** argv) {
opterr = 0;
/* read config */
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:")) != -1) {
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:t:k:hO")) != -1) {
switch (opt) {
case 'o':
cfg->outfilename = optarg;
@ -167,18 +174,24 @@ static int parse_config(cli_config *cfg, int argc, char ** argv) {
case 'k':
cfg->seek_samples = atoi(optarg);
break;
case 'O':
cfg->decode_only = 1;
break;
case 'h':
usage(argv[0], 1);
goto fail;
case '?':
fprintf(stderr, "Unknown option -%c found\n", optopt);
goto fail;
default:
usage(argv[0]);
usage(argv[0], 0);
goto fail;
}
}
/* filename goes last */
if (optind != argc - 1) {
usage(argv[0]);
usage(argv[0], 0);
goto fail;
}
cfg->infilename = argv[optind];
@ -435,7 +448,7 @@ int main(int argc, char ** argv) {
if (cfg.play_sdtout) {
outfile = stdout;
}
else if (!cfg.print_metaonly) {
else if (!cfg.print_metaonly && !cfg.decode_only) {
if (!cfg.outfilename) {
/* note that outfilename_temp must persist outside this block, hence the external array */
strcpy(outfilename_temp, cfg.infilename);
@ -521,7 +534,7 @@ int main(int argc, char ** argv) {
}
/* slap on a .wav header */
{
if (!cfg.decode_only) {
uint8_t wav_buf[0x100];
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
size_t bytes_done;
@ -563,13 +576,15 @@ int main(int argc, char ** argv) {
apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples, channels);
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
for (j = 0; j < to_get; j++) {
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
if (!cfg.decode_only) {
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
for (j = 0; j < to_get; j++) {
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
}
} else {
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
}
} else {
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
}
}
@ -577,7 +592,7 @@ int main(int argc, char ** argv) {
outfile = NULL;
/* try again with (for testing reset_vgmstream, simulates a seek to 0) */
/* try again with (for testing reset_vgmstream, simulates a seek to 0 after changing internal state) */
if (cfg.test_reset) {
char outfilename_reset[PATH_LIMIT];
strcpy(outfilename_reset, cfg.outfilename);
@ -597,7 +612,7 @@ int main(int argc, char ** argv) {
apply_seek(buf, vgmstream, cfg.seek_samples);
/* slap on a .wav header */
{
if (!cfg.decode_only) {
uint8_t wav_buf[0x100];
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
size_t bytes_done;
@ -619,13 +634,15 @@ int main(int argc, char ** argv) {
apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples, channels);
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
for (j = 0; j < to_get; j++) {
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
if (!cfg.decode_only) {
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
for (j = 0; j < to_get; j++) {
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample_t), 2, outfile);
}
} else {
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
}
} else {
fwrite(buf, sizeof(sample_t) * channels, to_get, outfile);
}
}
fclose(outfile);

View File

@ -29,6 +29,7 @@ set OP_NODELETE=
REM # -nc: don't report correct files
set OP_NOCORRECT=
REM # -p: performance test (decode with new exe and no comparison done)
REM # -P: performance test (same but also don't write file)
set OP_PERFORMANCE=
REM # -fc <exe>: file comparer (Windows's FC is slow)
set OP_CMD_FC=fc /a /b
@ -43,7 +44,8 @@ if "%~1"=="-f" set OP_SEARCH=%2
if "%~1"=="-r" set OP_RECURSIVE=/s
if "%~1"=="-nd" set OP_NODELETE=true
if "%~1"=="-nc" set OP_NOCORRECT=true
if "%~1"=="-p" set OP_PERFORMANCE=true
if "%~1"=="-p" set OP_PERFORMANCE=1
if "%~1"=="-P" set OP_PERFORMANCE=2
if "%~1"=="-fc" set OP_CMD_FC=%2
shift
goto set_options
@ -198,9 +200,15 @@ REM # ########################################################################
set CMD_FILE=%CMD_FILE:"=%
REM echo VTRS: file %CMD_FILE%
REM # new temp output
set WAV_NEW=%CMD_FILE%.test.wav
set CMD_VGM_NEW="%OP_CMD_NEW%" -o "%WAV_NEW%" "%CMD_FILE%"
if "%OP_PERFORMANCE%" == "2" (
REM # don't actually write file
set CMD_VGM_NEW="%OP_CMD_NEW%" -O "%CMD_FILE%"
) else (
REM # new temp output
set CMD_VGM_NEW="%OP_CMD_NEW%" -o "%WAV_NEW%" "%CMD_FILE%"
)
%CMD_VGM_NEW% 1> nul 2>&1 & REM || goto error
call :echo_color %C_O% "%CMD_FILE%" "done"

View File

@ -32,7 +32,7 @@ If you wish to use CMake, see [CMAKE.md](CMAKE.md).
**With GCC**: use the *./Makefile* in the root folder, see inside for options. For compilation flags check the *Makefile* in each folder.
You may need to manually rebuild if you change a *.h* file (use *make clean*).
In Linux, Makefiles can be used to cross-compile with the MingW headers, but may not be updated to generate native code at the moment. It should be fixable with some effort. Autotools should build it as vgmstream-cli instead (see the Audacious section). Some Linux distributions like Arch Linux include pre-patched vgmstream with most libraries, you may want that instead (see: https://aur.archlinux.org/packages/vgmstream-kode54-git/).
In Linux, Makefiles can be used to cross-compile with the MingW headers, but may not be updated to generate native code at the moment. It should be fixable with some effort. Autotools should build and install it as `vgmstream-cli` instead (see the Audacious section). Some Linux distributions like Arch Linux include pre-patched vgmstream with most libraries, you may want that instead (see: https://aur.archlinux.org/packages/vgmstream-kode54-git/).
Windows CMD example:
```
@ -99,10 +99,14 @@ libvorbis and libmpg123 will be used if found, while FFmpeg and other external l
Windows builds aren't supported at the moment (should be possible but there are complex dependency chains).
Take note of other plugins stealing extensions (see README). To change Audacious's default priority for vgmstream you can make with CFLAG `AUDACIOUS_VGMSTREAM_PRIORITY n` (where `N` is number with 10=lowest)
Terminal example, assuming a Ubuntu-based Linux distribution:
```
# build requirements
# build setup
# default requirements
sudo apt-get update
sudo apt-get install gcc g++ make
sudo apt-get install autoconf automake libtool
@ -113,10 +117,15 @@ sudo apt-get install libmpg123-dev libvorbis-dev
sudo apt-get install audacious
sudo apt-get install audacious-dev libglib2.0-dev libgtk2.0-dev libpango1.0-dev
# if you want vgmstream123 do this too
sudo apt-get install libao-dev
# check Audacious version >= 3.5
pkg-config --modversion audacious
```
```
# vgmstream build
# build
git clone https://github.com/kode54/vgmstream
cd vgmstream
@ -124,13 +133,21 @@ cd vgmstream
./configure
make -f Makefile.autotools
# copy to audacious plugins and update global libvgmstream.so.0 refs
# copy to audacious plugins (note that this will also install "libvgmstream",
# vgmstream-cli and vgmstream123, so they can be invoked from the terminal)
sudo make -f Makefile.autotools install
# update global libvgmstream.so.0 refs
sudo ldconfig
# start audacious in verbose mode to check if it was installed correctly
audacious -V
# if all goes well no "ERROR (something) referencing libvgmstream should show
# in the terminal log, then go to menu services > plugins > input tab and check
# vgmstream is there (you can start audacious normally next time)
```
```
# uninstall if needed
sudo make -f Makefile.autotools uninstall
@ -141,11 +158,13 @@ find . -name ".deps" -type d -exec rm -r "{}" \;
## WARNING, removes *all* untracked files not in .gitignore
git clean -fd
```
To update vgmstream it's probably easiest to remove the `vgmstream` folder and start again from "vgmstream build" step, since updates often require a full rebuild anyway.
### vgmstream123 player
Should be buildable with Autotools, much like the Audacious plugin, though requires libao (libao-dev).
Should be buildable with Autotools by following the same steps as listen in the Audacious section (requires libao-dev).
Windows builds are possible with libao.dll and includes, but some features are disabled.
Windows builds are possible with `libao.dll` and `libao` includes (found elsewhere) through the `Makefile`, but some features are disabled.
libao is licensed under the GPL v2 or later.

View File

@ -53,7 +53,7 @@ The following can be used in place of `(value)` for `(key) = (value)` commands.
- `(other)`: other special values for certain keys, described per key
The above may be combined with math operations (+-*/): `(key) = (number) (op) (offset) (op) (field) (...)`
The above may be combined with math operations (+-*/&): `(key) = (number) (op) (offset) (op) (field) (...)`
### KEYS
@ -240,7 +240,7 @@ Modifies the meaning of sample fields when set *before* them.
Accepted values:
- `samples`: exact sample (default)
- `bytes`: automatically converts bytes/offset to samples (applies after */+- modifiers)
- `bytes`: automatically converts bytes/offset to samples (applies after */+-& modifiers)
- `blocks`: same as bytes, but value is given in blocks/frames
* Value is internally converted from blocks to bytes first: `bytes = (value * interleave*channels)`
@ -520,13 +520,13 @@ sample_rate = 0x04 # sample rate is the same for all subsongs
```
### Math
Sometimes header values are in "sectors" or similar concepts (typical in DVD games), and need to be adjusted to a real value.
Sometimes header values are in "sectors" or similar concepts (typical in DVD games), and need to be adjusted to a real value using some complex math:
```
sample_type = bytes
start_offset = @0x10 * 0x800 # 0x15 * DVD sector size, for example
```
You can also use certain fields' values:
You can use `+-*/&` operators, and also certain fields' values:
```
num_samples = @0x10 * channels # byte-to-samples of channel_size
```
@ -825,3 +825,17 @@ PAD.XAG : 0x150
JIN002.XAG: 0x168
JIN003.XAG: 0x180
```
** Grandia (PS1) **
```
header_file = GM1.IDX
body_file = GM1.STZ
subsong_count = 394 #last doesn't have size though
subsong_offset = 0x04
subfile_offset = (@0x00 & 0xFFFFF) * 0x800
subfile_extension = seb
subfile_size = ((@0x04 - @0x00) & 0xFFFFF) * 0x800
```

View File

@ -1,155 +1,84 @@
#include "coding.h"
#include "../util.h"
void decode_adx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes) {
int i;
int32_t sample_count;
int32_t frame_samples = (frame_bytes - 2) * 2;
int framesin = first_sample/frame_samples;
int32_t scale = read_16bitBE(stream->offset+framesin*frame_bytes,stream->streamfile) + 1;
void decode_adx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_size, coding_t coding_type) {
uint8_t frame[0x12] = {0};
off_t frame_offset;
int i, frames_in, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
int scale, coef1, coef2;
int32_t hist1 = stream->adpcm_history1_32;
int32_t hist2 = stream->adpcm_history2_32;
int coef1 = stream->adpcm_coef[0];
int coef2 = stream->adpcm_coef[1];
first_sample = first_sample%frame_samples;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
int sample_byte = read_8bit(stream->offset+framesin*frame_bytes +2+i/2,stream->streamfile);
/* external interleave (fixed size), mono */
bytes_per_frame = frame_size;
samples_per_frame = (bytes_per_frame - 0x02) * 2; /* always 32 */
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
outbuf[sample_count] = clamp16(
(i&1?
get_low_nibble_signed(sample_byte):
get_high_nibble_signed(sample_byte)
) * scale +
(coef1 * hist1 >> 12) + (coef2 * hist2 >> 12)
);
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
hist2 = hist1;
hist1 = outbuf[sample_count];
scale = get_16bitBE(frame+0x00);
switch(coding_type) {
case coding_CRI_ADX:
scale = scale + 1;
coef1 = stream->adpcm_coef[0];
coef2 = stream->adpcm_coef[1];
break;
case coding_CRI_ADX_exp:
scale = 1 << (12 - scale);
coef1 = stream->adpcm_coef[0];
coef2 = stream->adpcm_coef[1];
break;
case coding_CRI_ADX_fixed:
scale = (scale & 0x1fff) + 1;
coef1 = stream->adpcm_coef[(frame[0] >> 5)*2 + 0];
coef2 = stream->adpcm_coef[(frame[0] >> 5)*2 + 1];
break;
case coding_CRI_ADX_enc_8:
case coding_CRI_ADX_enc_9:
scale = ((scale ^ stream->adx_xor) & 0x1fff) + 1;
coef1 = stream->adpcm_coef[0];
coef2 = stream->adpcm_coef[1];
break;
default:
scale = scale + 1;
coef1 = stream->adpcm_coef[0];
coef2 = stream->adpcm_coef[1];
break;
}
stream->adpcm_history1_32 = hist1;
stream->adpcm_history2_32 = hist2;
}
/* decode nibbles */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t sample = 0;
uint8_t nibbles = frame[0x02 + i/2];
void decode_adx_exp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes) {
int i;
int32_t sample_count;
int32_t frame_samples = (frame_bytes - 2) * 2;
sample = i&1 ? /* high nibble first */
get_low_nibble_signed(nibbles):
get_high_nibble_signed(nibbles);
sample = sample * scale + (coef1 * hist1 >> 12) + (coef2 * hist2 >> 12);
sample = clamp16(sample);
int framesin = first_sample/frame_samples;
int32_t scale = read_16bitBE(stream->offset+framesin*frame_bytes,stream->streamfile);
int32_t hist1, hist2;
int coef1, coef2;
scale = 1 << (12 - scale);
hist1 = stream->adpcm_history1_32;
hist2 = stream->adpcm_history2_32;
coef1 = stream->adpcm_coef[0];
coef2 = stream->adpcm_coef[1];
first_sample = first_sample%frame_samples;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
int sample_byte = read_8bit(stream->offset+framesin*frame_bytes +2+i/2,stream->streamfile);
outbuf[sample_count] = clamp16(
(i&1?
get_low_nibble_signed(sample_byte):
get_high_nibble_signed(sample_byte)
) * scale +
(coef1 * hist1 >> 12) + (coef2 * hist2 >> 12)
);
outbuf[sample_count] = sample;
sample_count += channelspacing;
hist2 = hist1;
hist1 = outbuf[sample_count];
}
stream->adpcm_history1_32 = hist1;
stream->adpcm_history2_32 = hist2;
}
void decode_adx_fixed(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes) {
int i;
int32_t sample_count;
int32_t frame_samples = (frame_bytes - 2) * 2;
int framesin = first_sample/frame_samples;
int32_t scale = (read_16bitBE(stream->offset + framesin*frame_bytes, stream->streamfile) & 0x1FFF) + 1;
int32_t predictor = read_8bit(stream->offset + framesin*frame_bytes, stream->streamfile) >> 5;
int32_t hist1 = stream->adpcm_history1_32;
int32_t hist2 = stream->adpcm_history2_32;
int coef1 = stream->adpcm_coef[predictor * 2];
int coef2 = stream->adpcm_coef[predictor * 2 + 1];
first_sample = first_sample%frame_samples;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
int sample_byte = read_8bit(stream->offset+framesin*frame_bytes +2+i/2,stream->streamfile);
outbuf[sample_count] = clamp16(
(i&1?
get_low_nibble_signed(sample_byte):
get_high_nibble_signed(sample_byte)
) * scale +
(coef1 * hist1 >> 12) + (coef2 * hist2 >> 12)
);
hist2 = hist1;
hist1 = outbuf[sample_count];
}
stream->adpcm_history1_32 = hist1;
stream->adpcm_history2_32 = hist2;
}
void adx_next_key(VGMSTREAMCHANNEL * stream)
{
stream->adx_xor = ( stream->adx_xor * stream->adx_mult + stream->adx_add ) & 0x7fff;
}
void decode_adx_enc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes) {
int i;
int32_t sample_count;
int32_t frame_samples = (frame_bytes - 2) * 2;
int framesin = first_sample/frame_samples;
int32_t scale = ((read_16bitBE(stream->offset+framesin*frame_bytes,stream->streamfile) ^ stream->adx_xor)&0x1fff) + 1;
int32_t hist1 = stream->adpcm_history1_32;
int32_t hist2 = stream->adpcm_history2_32;
int coef1 = stream->adpcm_coef[0];
int coef2 = stream->adpcm_coef[1];
first_sample = first_sample%frame_samples;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
int sample_byte = read_8bit(stream->offset+framesin*frame_bytes +2+i/2,stream->streamfile);
outbuf[sample_count] = clamp16(
(i&1?
get_low_nibble_signed(sample_byte):
get_high_nibble_signed(sample_byte)
) * scale +
(coef1 * hist1 >> 12) + (coef2 * hist2 >> 12)
);
hist2 = hist1;
hist1 = outbuf[sample_count];
hist1 = sample;
}
stream->adpcm_history1_32 = hist1;
stream->adpcm_history2_32 = hist2;
if (!(i % 32)) {
for (i=0;i<stream->adx_channels;i++)
{
if ((coding_type == coding_CRI_ADX_enc_8 || coding_type == coding_CRI_ADX_enc_9) && !(i % 32)) {
for (i =0; i < stream->adx_channels; i++) {
adx_next_key(stream);
}
}
}
void adx_next_key(VGMSTREAMCHANNEL * stream) {
stream->adx_xor = (stream->adx_xor * stream->adx_mult + stream->adx_add) & 0x7fff;
}

View File

@ -4,10 +4,7 @@
#include "../vgmstream.h"
/* adx_decoder */
void decode_adx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes);
void decode_adx_exp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes);
void decode_adx_fixed(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes);
void decode_adx_enc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes);
void decode_adx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int32_t frame_bytes, coding_t coding_type);
void adx_next_key(VGMSTREAMCHANNEL * stream);
/* g721_decoder */
@ -92,10 +89,10 @@ size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels);
int ps_check_format(STREAMFILE *streamFile, off_t offset, size_t max);
/* psv_decoder */
void decode_hevag(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
void decode_hevag(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
/* xa_decoder */
void decode_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
void decode_xa(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked);
/* ea_xa_decoder */

View File

@ -1,6 +1,12 @@
#include "coding.h"
#include "../util.h"
#if 0
/* known game code/platforms use float buffer and coefs, but some approximations around use this int math:
* ...
* coef1 = table[index + 0]
* coef2 = table[index + 4]
* sample = clamp16(((signed_nibble << (20 - shift)) + hist1 * coef1 + hist2 * coef2 + 128) >> 8); */
static const int EA_XA_TABLE[20] = {
0, 240, 460, 392,
0, 0, -208, -220,
@ -8,33 +14,58 @@ static const int EA_XA_TABLE[20] = {
7, 8, 10, 11,
0, -1, -3, -4
};
#endif
/* EA-XAS v1, evolution of EA-XA/XAS and cousin of MTA2. From FFmpeg (general info) + MTA2 (layout) + EA-XA (decoding)
/* standard CD-XA's K0/K1 filter pairs */
static const float xa_coefs[16][2] = {
{ 0.0, 0.0 },
{ 0.9375, 0.0 },
{ 1.796875, -0.8125 },
{ 1.53125, -0.859375 },
/* only 4 pairs exist, assume 0s for bad indexes */
};
/* EA-XAS v1, evolution of EA-XA/XAS and cousin of MTA2. Reverse engineered from various .exes/.so
*
* Layout: blocks of 0x4c per channel (128 samples), divided into 4 headers + 4 vertical groups of 15 bytes (for parallelism?).
* Layout: blocks of 0x4c per channel (128 samples), divided into 4 headers + 4 vertical groups of 15 bytes.
* Original code reads all headers first then processes all nibbles (for CPU cache/parallelism/SIMD optimizations).
* To simplify, always decodes the block and discards unneeded samples, so doesn't use external hist. */
void decode_ea_xas_v1(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
int group, row, i;
int samples_done = 0, sample_count = 0;
void decode_ea_xas_v1(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
uint8_t frame[0x4c] = {0};
off_t frame_offset;
int group, row, i, samples_done = 0, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
/* internal interleave */
int block_samples = 128;
first_sample = first_sample % block_samples;
bytes_per_frame = 0x4c;
samples_per_frame = 128;
first_sample = first_sample % samples_per_frame;
frame_offset = stream->offset + bytes_per_frame * channel;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
//todo: original code uses float sample buffer:
//- header pcm-hist to float-hist: hist * (1/32768)
//- nibble to signed to float: (int32_t)(pnibble << 28) * SHIFT_MUL_LUT[shift_index]
// look-up table just simplifies ((nibble << 12 << 12) >> 12 + shift) * (1/32768)
// though maybe introduces rounding errors?
//- coefs apply normally, though hists are already floats
//- final float sample isn't clamped
/* process groups */
/* parse group headers */
for (group = 0; group < 4; group++) {
int coef1, coef2;
float coef1, coef2;
int16_t hist1, hist2;
uint8_t shift;
uint32_t group_header = (uint32_t)read_32bitLE(stream->offset + channel*0x4c + group*0x4, stream->streamfile); /* always LE */
uint32_t group_header = (uint32_t)get_32bitLE(frame + group*0x4); /* always LE */
coef1 = EA_XA_TABLE[(uint8_t)(group_header & 0x0F) + 0];
coef2 = EA_XA_TABLE[(uint8_t)(group_header & 0x0F) + 4];
hist2 = (int16_t)(group_header & 0xFFF0);
coef1 = xa_coefs[group_header & 0x0F][0];
coef2 = xa_coefs[group_header & 0x0F][1];
hist2 = (int16_t)((group_header >> 0) & 0xFFF0);
hist1 = (int16_t)((group_header >> 16) & 0xFFF0);
shift = 20 - ((group_header >> 16) & 0x0F);
shift = (group_header >> 16) & 0x0F;
/* write header samples (needed) */
if (sample_count >= first_sample && samples_done < samples_to_do) {
@ -51,12 +82,14 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
/* process nibbles per group */
for (row = 0; row < 15; row++) {
for (i = 0; i < 1*2; i++) {
uint8_t sample_byte = (uint8_t)read_8bit(stream->offset + channel*0x4c + 4*4 + row*0x04 + group + i/2, stream->streamfile);
uint8_t nibbles = frame[4*4 + row*0x04 + group + i/2];
int sample;
sample = get_nibble_signed(sample_byte, !(i&1)); /* upper first */
sample = sample << shift;
sample = (sample + hist1 * coef1 + hist2 * coef2 + 128) >> 8;
sample = i&1 ? /* high nibble first */
(nibbles >> 0) & 0x0f :
(nibbles >> 4) & 0x0f;
sample = (int16_t)(sample << 12) >> shift; /* 16b sign extend + scale */
sample = sample + hist1 * coef1 + hist2 * coef2;
sample = clamp16(sample);
if (sample_count >= first_sample && samples_done < samples_to_do) {
@ -73,37 +106,43 @@ void decode_ea_xas_v1(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
/* internal interleave (interleaved channels, but manually advances to co-exist with ea blocks) */
if (first_sample + samples_done == block_samples) {
stream->offset += 0x4c * channelspacing;
if (first_sample + samples_done == samples_per_frame) {
stream->offset += bytes_per_frame * channelspacing;
}
}
/* EA-XAS v0, without complex layouts and closer to EA-XA. Somewhat based on daemon1's decoder */
void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
uint8_t frame[0x13] = {0};
off_t frame_offset;
int i;
int block_samples, frames_in, samples_done = 0, sample_count = 0;
int i, frames_in, samples_done = 0, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
/* external interleave (fixed size), mono */
block_samples = 32;
frames_in = first_sample / block_samples;
first_sample = first_sample % block_samples;
bytes_per_frame = 0x02 + 0x02 + 0x0f;
samples_per_frame = 1 + 1 + 0x0f*2;
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
frame_offset = stream->offset + (0x0f+0x02+0x02)*frames_in;
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
/* process frames */
//todo see above
/* process frame */
{
int coef1, coef2;
float coef1, coef2;
int16_t hist1, hist2;
uint8_t shift;
uint32_t frame_header = (uint32_t)read_32bitLE(frame_offset, stream->streamfile); /* always LE */
uint32_t frame_header = (uint32_t)get_32bitLE(frame); /* always LE */
coef1 = EA_XA_TABLE[(uint8_t)(frame_header & 0x0F) + 0];
coef2 = EA_XA_TABLE[(uint8_t)(frame_header & 0x0F) + 4];
hist2 = (int16_t)(frame_header & 0xFFF0);
coef1 = xa_coefs[frame_header & 0x0F][0];
coef2 = xa_coefs[frame_header & 0x0F][1];
hist2 = (int16_t)((frame_header >> 0) & 0xFFF0);
hist1 = (int16_t)((frame_header >> 16) & 0xFFF0);
shift = 20 - ((frame_header >> 16) & 0x0F);
shift = (frame_header >> 16) & 0x0F;
/* write header samples (needed) */
if (sample_count >= first_sample && samples_done < samples_to_do) {
@ -119,12 +158,14 @@ void decode_ea_xas_v0(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa
/* process nibbles */
for (i = 0; i < 0x0f*2; i++) {
uint8_t sample_byte = (uint8_t)read_8bit(frame_offset + 0x02 + 0x02 + i/2, stream->streamfile);
uint8_t nibbles = frame[0x02 + 0x02 + i/2];
int sample;
sample = get_nibble_signed(sample_byte, !(i&1)); /* upper first */
sample = sample << shift;
sample = (sample + hist1 * coef1 + hist2 * coef2 + 128) >> 8;
sample = i&1 ? /* high nibble first */
(nibbles >> 0) & 0x0f :
(nibbles >> 4) & 0x0f;
sample = (int16_t)(sample << 12) >> shift; /* 16b sign extend + scale */
sample = sample + hist1 * coef1 + hist2 * coef2;
sample = clamp16(sample);
if (sample_count >= first_sample && samples_done < samples_to_do) {

View File

@ -1124,11 +1124,14 @@ size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels) {
}
size_t xbox_ima_bytes_to_samples(size_t bytes, int channels) {
int mod;
int block_align = 0x24 * channels;
if (channels <= 0) return 0;
mod = bytes % block_align;
/* XBOX IMA blocks have a 4 byte header per channel; 2 samples per byte (2 nibbles) */
return (bytes / block_align) * (block_align - 4 * channels) * 2 / channels
+ ((bytes % block_align) ? ((bytes % block_align) - 4 * channels) * 2 / channels : 0); /* unlikely (encoder aligns) */
+ ((mod > 0 && mod > 0x04*channels) ? (mod - 0x04*channels) * 2 / channels : 0); /* unlikely (encoder aligns) */
}
size_t dat4_ima_bytes_to_samples(size_t bytes, int channels) {

View File

@ -1,69 +1,103 @@
#include "coding.h"
#include "../util.h"
void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
int i=first_sample;
int32_t sample_count;
int framesin = first_sample/14;
int8_t header = read_8bit(framesin*8+stream->offset,stream->streamfile);
int32_t scale = 1 << (header & 0xf);
int coef_index = (header >> 4) & 0xf;
uint8_t frame[0x08] = {0};
off_t frame_offset;
int i, frames_in, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
int coef_index, scale, coef1, coef2;
int32_t hist1 = stream->adpcm_history1_16;
int32_t hist2 = stream->adpcm_history2_16;
int coef1 = stream->adpcm_coef[coef_index*2];
int coef2 = stream->adpcm_coef[coef_index*2+1];
first_sample = first_sample%14;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
int sample_byte = read_8bit(framesin*8+stream->offset+1+i/2,stream->streamfile);
/* external interleave (fixed size), mono */
bytes_per_frame = 0x08;
samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
outbuf[sample_count] = clamp16((
(((i&1?
get_low_nibble_signed(sample_byte):
get_high_nibble_signed(sample_byte)
) * scale)<<11) + 1024 +
(coef1 * hist1 + coef2 * hist2))>>11
);
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
scale = 1 << ((frame[0] >> 0) & 0xf);
coef_index = (frame[0] >> 4) & 0xf;
VGM_ASSERT_ONCE(coef_index > 8, "DSP: incorrect coefs at %x\n", (uint32_t)frame_offset);
//if (coef_index > 8) //todo not correctly clamped in original decoder?
// coef_index = 8;
coef1 = stream->adpcm_coef[coef_index*2 + 0];
coef2 = stream->adpcm_coef[coef_index*2 + 1];
/* decode nibbles */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t sample = 0;
uint8_t nibbles = frame[0x01 + i/2];
sample = i&1 ? /* high nibble first */
get_low_nibble_signed(nibbles) :
get_high_nibble_signed(nibbles);
sample = ((sample * scale) << 11);
sample = (sample + 1024 + coef1*hist1 + coef2*hist2) >> 11;
sample = clamp16(sample);
outbuf[sample_count] = sample;
sample_count += channelspacing;
hist2 = hist1;
hist1 = outbuf[sample_count];
hist1 = sample;
}
stream->adpcm_history1_16 = hist1;
stream->adpcm_history2_16 = hist2;
}
/* read from memory rather than a file */
static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, uint8_t * mem) {
int i=first_sample;
int32_t sample_count;
int8_t header = mem[0];
int32_t scale = 1 << (header & 0xf);
int coef_index = (header >> 4) & 0xf;
/* read from memory rather than a file */
static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, uint8_t * frame) {
int i, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
int coef_index, scale, coef1, coef2;
int32_t hist1 = stream->adpcm_history1_16;
int32_t hist2 = stream->adpcm_history2_16;
int coef1 = stream->adpcm_coef[coef_index*2];
int coef2 = stream->adpcm_coef[coef_index*2+1];
first_sample = first_sample%14;
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
int sample_byte = mem[1 + i/2];
/* external interleave (fixed size), mono */
bytes_per_frame = 0x08;
samples_per_frame = (bytes_per_frame - 0x01) * 2; /* always 14 */
first_sample = first_sample % samples_per_frame;
VGM_ASSERT_ONCE(samples_to_do > samples_per_frame, "DSP: layout error, too many samples\n");
outbuf[sample_count] = clamp16((
(((i&1?
get_low_nibble_signed(sample_byte):
get_high_nibble_signed(sample_byte)
) * scale)<<11) + 1024 +
(coef1 * hist1 + coef2 * hist2))>>11
);
/* parse frame header */
scale = 1 << ((frame[0] >> 0) & 0xf);
coef_index = (frame[0] >> 4) & 0xf;
VGM_ASSERT_ONCE(coef_index > 8, "DSP: incorrect coefs\n");
//if (coef_index > 8) //todo not correctly clamped in original decoder?
// coef_index = 8;
coef1 = stream->adpcm_coef[coef_index*2 + 0];
coef2 = stream->adpcm_coef[coef_index*2 + 1];
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t sample = 0;
uint8_t nibbles = frame[0x01 + i/2];
sample = i&1 ?
get_low_nibble_signed(nibbles) :
get_high_nibble_signed(nibbles);
sample = ((sample * scale) << 11);
sample = (sample + 1024 + coef1*hist1 + coef2*hist2) >> 11;
sample = clamp16(sample);
outbuf[sample_count] = sample;
sample_count += channelspacing;
hist2 = hist1;
hist1 = outbuf[sample_count];
hist1 = sample;
}
stream->adpcm_history1_16 = hist1;
@ -72,22 +106,21 @@ static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample_t *
/* decode DSP with byte-interleaved frames (ex. 0x08: 1122112211221122) */
void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave) {
uint8_t sample_data[0x08];
uint8_t frame[0x08];
int i;
int frames_in = first_sample / 14;
int framesin = first_sample/14;
for (i=0; i < 0x08; i++) {
for (i = 0; i < 0x08; i++) {
/* base + current frame + subint section + subint byte + channel adjust */
sample_data[i] = read_8bit(
frame[i] = read_8bit(
stream->offset
+ framesin*(0x08*channelspacing)
+ frames_in*(0x08*channelspacing)
+ i/interleave * interleave * channelspacing
+ i%interleave
+ interleave * channel, stream->streamfile);
}
decode_ngc_dsp_subint_internal(stream, outbuf, channelspacing, first_sample, samples_to_do, sample_data);
decode_ngc_dsp_subint_internal(stream, outbuf, channelspacing, first_sample, samples_to_do, frame);
}

View File

@ -3,7 +3,7 @@
#include "../util.h"
/* PSVita ADPCM table */
static const int16_t HEVAG_coefs[128][4] = {
static const int16_t hevag_coefs[128][4] = {
{ 0, 0, 0, 0 },
{ 7680, 0, 0, 0 },
{ 14720, -6656, 0, 0 },
@ -141,59 +141,58 @@ static const int16_t HEVAG_coefs[128][4] = {
*
* Original research and algorithm by id-daemon / daemon1.
*/
void decode_hevag(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
uint8_t predict_nr, shift, flag, byte;
int32_t scale = 0;
int32_t sample;
void decode_hevag(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
uint8_t frame[0x10] = {0};
off_t frame_offset;
int i, frames_in, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
int coef_index, shift_factor, flag;
int32_t hist1 = stream->adpcm_history1_32;
int32_t hist2 = stream->adpcm_history2_32;
int32_t hist3 = stream->adpcm_history3_32;
int32_t hist4 = stream->adpcm_history4_32;
int i, sample_count;
/* external interleave (fixed size), mono */
bytes_per_frame = 0x10;
samples_per_frame = (bytes_per_frame - 0x02) * 2; /* always 28 */
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
int framesin = first_sample / 28;
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
coef_index = (frame[0] >> 4) & 0xf;
shift_factor = (frame[0] >> 0) & 0xf;
coef_index = ((frame[1] >> 0) & 0xf0) | coef_index;
flag = (frame[1] >> 0) & 0xf; /* same flags */
/* 4 byte header: predictor = 3rd and 1st, shift = 2nd, flag = 4th */
byte = (uint8_t)read_8bit(stream->offset+framesin*16+0,stream->streamfile);
predict_nr = byte >> 4;
shift = byte & 0x0f;
byte = (uint8_t)read_8bit(stream->offset+framesin*16+1,stream->streamfile);
predict_nr = (byte & 0xF0) | predict_nr;
flag = byte & 0x0f; /* no change in flags */
VGM_ASSERT_ONCE(coef_index > 127 || shift_factor > 12, "HEVAG: in+correct coefs/shift at %x\n", (uint32_t)frame_offset);
if (coef_index > 127)
coef_index = 127; /* ? */
if (shift_factor > 12)
shift_factor = 9; /* ? */
first_sample = first_sample % 28;
/* decode nibbles */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t sample = 0, scale = 0;
if (first_sample & 1) { /* if first sample is odd, read byte first */
byte = read_8bit(stream->offset+(framesin*16)+2+first_sample/2,stream->streamfile);
}
if (flag < 0x07) { /* with flag 0x07 decoded sample must be 0 */
uint8_t nibbles = frame[0x02 + i/2];
for (i = first_sample, sample_count = 0; i < first_sample + samples_to_do; i++, sample_count += channelspacing) {
sample = 0;
if (flag < 7 && predict_nr < 128) {
if (i & 1) {/* odd/even nibble */
scale = byte >> 4;
} else {
byte = read_8bit(stream->offset+(framesin*16)+2+i/2,stream->streamfile);
scale = byte & 0x0f;
}
if (scale > 7) { /* sign extend */
scale = scale - 16;
}
sample = (hist1 * HEVAG_coefs[predict_nr][0] +
hist2 * HEVAG_coefs[predict_nr][1] +
hist3 * HEVAG_coefs[predict_nr][2] +
hist4 * HEVAG_coefs[predict_nr][3] ) / 32;
sample = (sample + (scale << (20 - shift)) + 128) >> 8;
scale = i&1 ? /* low nibble first */
get_high_nibble_signed(nibbles):
get_low_nibble_signed(nibbles);
sample = (hist1 * hevag_coefs[coef_index][0] +
hist2 * hevag_coefs[coef_index][1] +
hist3 * hevag_coefs[coef_index][2] +
hist4 * hevag_coefs[coef_index][3] ) / 32;
sample = (sample + (scale << (20 - shift_factor)) + 128) >> 8;
}
outbuf[sample_count] = clamp16(sample);
outbuf[sample_count] = sample;
sample_count += channelspacing;
hist4 = hist3;
hist3 = hist2;
hist2 = hist1;

View File

@ -2,7 +2,7 @@
/* PS-ADPCM table, defined as rational numbers (as in the spec) */
static const double ps_adpcm_coefs_f[5][2] = {
static const float ps_adpcm_coefs_f[5][2] = {
{ 0.0 , 0.0 }, //{ 0.0 , 0.0 },
{ 0.9375 , 0.0 }, //{ 60.0 / 64.0 , 0.0 },
{ 1.796875 , -0.8125 }, //{ 115.0 / 64.0 , -52.0 / 64.0 },
@ -44,6 +44,7 @@ static const int ps_adpcm_coefs_i[5][2] = {
/* standard PS-ADPCM (float math version) */
void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int is_badflags) {
uint8_t frame[0x10] = {0};
off_t frame_offset;
int i, frames_in, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
@ -51,6 +52,7 @@ void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing
int32_t hist1 = stream->adpcm_history1_32;
int32_t hist2 = stream->adpcm_history2_32;
/* external interleave (fixed size), mono */
bytes_per_frame = 0x10;
samples_per_frame = (bytes_per_frame - 0x02) * 2; /* always 28 */
@ -58,10 +60,11 @@ void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing
first_sample = first_sample % samples_per_frame;
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame*frames_in;
coef_index = ((uint8_t)read_8bit(frame_offset+0x00,stream->streamfile) >> 4) & 0xf;
shift_factor = ((uint8_t)read_8bit(frame_offset+0x00,stream->streamfile) >> 0) & 0xf;
flag = (uint8_t)read_8bit(frame_offset+0x01,stream->streamfile); /* only lower nibble needed */
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
coef_index = (frame[0] >> 4) & 0xf;
shift_factor = (frame[0] >> 0) & 0xf;
flag = frame[1]; /* only lower nibble needed */
VGM_ASSERT_ONCE(coef_index > 5 || shift_factor > 12, "PS-ADPCM: incorrect coefs/shift at %x\n", (uint32_t)frame_offset);
if (coef_index > 5) /* needed by inFamous (PS3) (maybe it's supposed to use more filters?) */
@ -73,18 +76,19 @@ void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing
flag = 0;
VGM_ASSERT_ONCE(flag > 7,"PS-ADPCM: unknown flag at %x\n", (uint32_t)frame_offset); /* meta should use PSX-badflags */
/* decode nibbles */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t sample = 0;
if (flag < 0x07) { /* with flag 0x07 decoded sample must be 0 */
uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x02+i/2,stream->streamfile);
uint8_t nibbles = frame[0x02 + i/2];
sample = i&1 ? /* low nibble first */
(nibbles >> 4) & 0x0f :
(nibbles >> 0) & 0x0f;
sample = (int16_t)((sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */
sample = (int)(sample + ps_adpcm_coefs_f[coef_index][0]*hist1 + ps_adpcm_coefs_f[coef_index][1]*hist2);
sample = (int32_t)(sample + ps_adpcm_coefs_f[coef_index][0]*hist1 + ps_adpcm_coefs_f[coef_index][1]*hist2);
sample = clamp16(sample);
}
@ -105,6 +109,7 @@ void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing
*
* Uses int math to decode, which seems more likely (based on FF XI PC's code in Moogle Toolbox). */
void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size) {
uint8_t frame[0x50] = {0};
off_t frame_offset;
int i, frames_in, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
@ -112,6 +117,7 @@ void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int c
int32_t hist1 = stream->adpcm_history1_32;
int32_t hist2 = stream->adpcm_history2_32;
/* external interleave (variable size), mono */
bytes_per_frame = frame_size;
samples_per_frame = (bytes_per_frame - 0x01) * 2;
@ -119,9 +125,10 @@ void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int c
first_sample = first_sample % samples_per_frame;
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame*frames_in;
coef_index = ((uint8_t)read_8bit(frame_offset+0x00,stream->streamfile) >> 4) & 0xf;
shift_factor = ((uint8_t)read_8bit(frame_offset+0x00,stream->streamfile) >> 0) & 0xf;
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
coef_index = (frame[0] >> 4) & 0xf;
shift_factor = (frame[0] >> 0) & 0xf;
VGM_ASSERT_ONCE(coef_index > 5 || shift_factor > 12, "PS-ADPCM: incorrect coefs/shift at %x\n", (uint32_t)frame_offset);
if (coef_index > 5) /* needed by Afrika (PS3) (maybe it's supposed to use more filters?) */
@ -129,10 +136,11 @@ void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int c
if (shift_factor > 12)
shift_factor = 9; /* supposedly, from Nocash PSX docs */
/* decode nibbles */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t sample = 0;
uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x01+i/2,stream->streamfile);
uint8_t nibbles = frame[0x01 + i/2];
sample = i&1 ? /* low nibble first */
(nibbles >> 4) & 0x0f :
@ -154,6 +162,7 @@ void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int c
/* PS-ADPCM from Pivotal games, exactly like psx_cfg but with float math (reverse engineered from the exe) */
void decode_psx_pivotal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size) {
uint8_t frame[0x50] = {0};
off_t frame_offset;
int i, frames_in, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
@ -162,6 +171,7 @@ void decode_psx_pivotal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channe
int32_t hist2 = stream->adpcm_history2_32;
float scale;
/* external interleave (variable size), mono */
bytes_per_frame = frame_size;
samples_per_frame = (bytes_per_frame - 0x01) * 2;
@ -169,21 +179,24 @@ void decode_psx_pivotal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channe
first_sample = first_sample % samples_per_frame;
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame*frames_in;
coef_index = ((uint8_t)read_8bit(frame_offset+0x00,stream->streamfile) >> 4) & 0xf;
shift_factor = ((uint8_t)read_8bit(frame_offset+0x00,stream->streamfile) >> 0) & 0xf;
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
coef_index = (frame[0] >> 4) & 0xf;
shift_factor = (frame[0] >> 0) & 0xf;
VGM_ASSERT_ONCE(coef_index > 5 || shift_factor > 12, "PS-ADPCM: incorrect coefs/shift at %x\n", (uint32_t)frame_offset);
VGM_ASSERT_ONCE(coef_index > 5 || shift_factor > 12, "PS-ADPCM-piv: incorrect coefs/shift\n");
if (coef_index > 5) /* just in case */
coef_index = 5;
if (shift_factor > 12) /* same */
shift_factor = 12;
scale = (float)(1.0 / (double)(1 << shift_factor));
/* decode nibbles */
for (i = first_sample; i < first_sample + samples_to_do; i++) {
int32_t sample = 0;
uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x01+i/2,stream->streamfile);
uint8_t nibbles = frame[0x01 + i/2];
sample = !(i&1) ? /* low nibble first */
(nibbles >> 0) & 0x0f :

View File

@ -6,11 +6,13 @@
// May be implemented like the SNES/SPC700 BRR.
/* XA ADPCM gain values */
static const double K0[4] = { 0.0, 0.9375, 1.796875, 1.53125 };
static const double K1[4] = { 0.0, 0.0, -0.8125,-0.859375};
/* K0/1 floats to int, K*2^10 = K*(1<<10) = K*1024 */
static int get_IK0(int fid) { return ((int)((-K0[fid]) * (1 << 10))); }
static int get_IK1(int fid) { return ((int)((-K1[fid]) * (1 << 10))); }
#if 0
static const float K0[4] = { 0.0, 0.9375, 1.796875, 1.53125 };
static const float K1[4] = { 0.0, 0.0, -0.8125, -0.859375 };
#endif
/* K0/1 floats to int, -K*2^10 = -K*(1<<10) = -K*1024 */
static const int IK0[4] = { 0, -960, -1840, -1568 };
static const int IK1[4] = { 0, 0, 832, 880 };
/* Sony XA ADPCM, defined for CD-DA/CD-i in the "Red Book" (private) or "Green Book" (public) specs.
* The algorithm basically is BRR (Bit Rate Reduction) from the SNES SPC700, while the data layout is new.
@ -40,18 +42,14 @@ static int get_IK1(int fid) { return ((int)((-K1[fid]) * (1 << 10))); }
* (bsnes): https://gitlab.com/higan/higan/blob/master/higan/sfc/dsp/brr.cpp
*/
void decode_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
off_t frame_offset, sp_offset;
int i,j, frames_in, samples_done = 0, sample_count = 0;
void decode_xa(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
uint8_t frame[0x80] = {0};
off_t frame_offset;
int i,j, sp_pos, frames_in, samples_done = 0, sample_count = 0;
size_t bytes_per_frame, samples_per_frame;
int32_t hist1 = stream->adpcm_history1_32;
int32_t hist2 = stream->adpcm_history2_32;
/* external interleave (fixed size), mono/stereo */
bytes_per_frame = 0x80;
samples_per_frame = 28*8 / channelspacing;
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
/* data layout (mono):
* - CD-XA audio is divided into sectors ("audio blocks"), each with 18 size 0x80 frames
@ -72,12 +70,19 @@ void decode_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, i
* ...
* subframe 7: header @ 0x0b or 0x0f, 28 nibbles (high) @ 0x13,17,1b,1f,23 ... 7f
*/
frame_offset = stream->offset + bytes_per_frame*frames_in;
if (read_32bitBE(frame_offset+0x00,stream->streamfile) != read_32bitBE(frame_offset+0x04,stream->streamfile) ||
read_32bitBE(frame_offset+0x08,stream->streamfile) != read_32bitBE(frame_offset+0x0c,stream->streamfile)) {
VGM_LOG("bad frames at %x\n", (uint32_t)frame_offset);
}
/* external interleave (fixed size), mono/stereo */
bytes_per_frame = 0x80;
samples_per_frame = 28*8 / channelspacing;
frames_in = first_sample / samples_per_frame;
first_sample = first_sample % samples_per_frame;
/* parse frame header */
frame_offset = stream->offset + bytes_per_frame * frames_in;
read_streamfile(frame, frame_offset, bytes_per_frame, stream->streamfile); /* ignore EOF errors */
VGM_ASSERT(get_32bitBE(frame+0x0) != get_32bitBE(frame+0x4) || get_32bitBE(frame+0x8) != get_32bitBE(frame+0xC),
"bad frames at %x\n", (uint32_t)frame_offset);
/* decode subframes */
@ -86,18 +91,18 @@ void decode_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, i
uint8_t coef_index, shift_factor;
/* parse current subframe (sound unit)'s header (sound parameters) */
sp_offset = frame_offset + 0x04 + i*channelspacing + channel;
coef_index = ((uint8_t)read_8bit(sp_offset,stream->streamfile) >> 4) & 0xf;
shift_factor = ((uint8_t)read_8bit(sp_offset,stream->streamfile) >> 0) & 0xf;
sp_pos = 0x04 + i*channelspacing + channel;
coef_index = (frame[sp_pos] >> 4) & 0xf;
shift_factor = (frame[sp_pos] >> 0) & 0xf;
VGM_ASSERT(coef_index > 4 || shift_factor > 12, "XA: incorrect coefs/shift at %x\n", (uint32_t)sp_offset);
VGM_ASSERT(coef_index > 4 || shift_factor > 12, "XA: incorrect coefs/shift at %x\n", (uint32_t)frame_offset + sp_pos);
if (coef_index > 4)
coef_index = 0; /* only 4 filters are used, rest is apparently 0 */
if (shift_factor > 12)
shift_factor = 9; /* supposedly, from Nocash PSX docs */
coef1 = get_IK0(coef_index);
coef2 = get_IK1(coef_index);
coef1 = IK0[coef_index];
coef2 = IK1[coef_index];
/* decode subframe nibbles */
@ -105,9 +110,9 @@ void decode_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, i
uint8_t nibbles;
int32_t new_sample;
off_t su_offset = (channelspacing==1) ?
frame_offset + 0x10 + j*0x04 + (i/2) : /* mono */
frame_offset + 0x10 + j*0x04 + i; /* stereo */
int su_pos = (channelspacing==1) ?
0x10 + j*0x04 + (i/2) : /* mono */
0x10 + j*0x04 + i; /* stereo */
int get_high_nibble = (channelspacing==1) ?
(i&1) : /* mono (even subframes = low, off subframes = high) */
(channel == 1); /* stereo (L channel / even subframes = low, R channel / odd subframes = high) */
@ -118,11 +123,11 @@ void decode_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, i
continue;
}
nibbles = (uint8_t)read_8bit(su_offset,stream->streamfile);
nibbles = frame[su_pos];
new_sample = get_high_nibble ?
(nibbles >> 4) & 0x0f :
(nibbles ) & 0x0f;
(nibbles >> 0) & 0x0f;
new_sample = (int16_t)((new_sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */
new_sample = new_sample << 4;

View File

@ -282,6 +282,7 @@ static const char* extension_list[] = {
"mihb",
"mnstr",
"mogg",
//"mp+", //common [Moonshine Runners (PC)]
//"mp2", //common
//"mp3", //common
//"mp4", //common
@ -584,6 +585,7 @@ static const char* common_extension_list[] = {
"bin", //common
"flac", //common
"gsf", //conflicts with GBA gsf plugins?
"mp+", //common [Moonshine Runners (PC)]
"mp2", //common
"mp3", //common
"mp4", //common
@ -942,6 +944,7 @@ static const meta_info meta_info_list[] = {
{meta_XMU, "Outrage XMU header"},
{meta_XVAS, "Konami .XVAS header"},
{meta_PS2_XA2, "Acclaim XA2 Header"},
{meta_SAP, "VING .SAP header"},
{meta_DC_IDVI, "Capcom IDVI header"},
{meta_KRAW, "Geometry Wars: Galaxies KRAW header"},
{meta_NGC_YMF, "YMF DSP Header"},

View File

@ -70,19 +70,51 @@ fail:
/* ************************************** */
#define ACB_TABLE_BUFFER_SIZE 0x4000
STREAMFILE* setup_acb_streamfile(STREAMFILE *streamFile, size_t buffer_size) {
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
new_streamFile = open_wrap_streamfile(streamFile);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
new_streamFile = open_buffer_streamfile(temp_streamFile, buffer_size);
if (!new_streamFile) goto fail;
temp_streamFile = new_streamFile;
return temp_streamFile;
fail:
close_streamfile(temp_streamFile);
return NULL;
}
typedef struct {
STREAMFILE *acbFile; /* original reference, don't close */
/* keep track of these tables so they can be closed when done */
utf_context *Header;
utf_context *CueNameTable;
utf_context *CueTable;
utf_context *BlockTable;
utf_context *SequenceTable;
utf_context *TrackTable;
utf_context *TrackEventTable;
utf_context *CommandTable;
utf_context *TrackCommandTable;
utf_context *SynthTable;
utf_context *WaveformTable;
STREAMFILE *CueNameSf;
STREAMFILE *CueSf;
STREAMFILE *BlockSf;
STREAMFILE *SequenceSf;
STREAMFILE *TrackSf;
STREAMFILE *TrackCommandSf;
STREAMFILE *SynthSf;
STREAMFILE *WaveformSf;
/* config */
int is_memory;
int target_waveid;
@ -102,16 +134,21 @@ typedef struct {
} acb_header;
static int load_utf_subtable(STREAMFILE *acbFile, acb_header* acb, utf_context* *Table, const char* TableName, int* rows) {
static int open_utf_subtable(acb_header* acb, STREAMFILE* *TableSf, utf_context* *Table, const char* TableName, int* rows) {
uint32_t offset = 0;
/* already loaded */
if (*Table != NULL)
return 1;
if (!utf_query_data(acbFile, acb->Header, 0, TableName, &offset, NULL))
if (!utf_query_data(acb->acbFile, acb->Header, 0, TableName, &offset, NULL))
goto fail;
*Table = utf_open(acbFile, offset, rows, NULL);
/* open a buffered streamfile to avoid so much IO back and forth between all the tables */
*TableSf = setup_acb_streamfile(acb->acbFile, ACB_TABLE_BUFFER_SIZE);
if (!*TableSf) goto fail;
*Table = utf_open(*TableSf, offset, rows, NULL);
if (!*Table) goto fail;
//;VGM_LOG("ACB: loaded table %s\n", TableName);
@ -121,7 +158,7 @@ fail:
}
static void add_acb_name(STREAMFILE *acbFile, acb_header* acb, int8_t Waveform_Streaming) {
static void add_acb_name(acb_header* acb, int8_t Waveform_Streaming) {
//todo safe string ops
/* ignore name repeats */
@ -154,23 +191,23 @@ static void add_acb_name(STREAMFILE *acbFile, acb_header* acb, int8_t Waveform_S
}
static int load_acb_waveform(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
static int load_acb_waveform(acb_header* acb, int16_t Index) {
int16_t Waveform_Id;
int8_t Waveform_Streaming;
/* read Waveform[Index] */
if (!load_utf_subtable(acbFile, acb, &acb->WaveformTable, "WaveformTable", NULL))
if (!open_utf_subtable(acb, &acb->WaveformSf, &acb->WaveformTable, "WaveformTable", NULL))
goto fail;
if (!utf_query_s16(acbFile, acb->WaveformTable, Index, "Id", &Waveform_Id)) { /* older versions use Id */
if (!utf_query_s16(acb->WaveformSf, acb->WaveformTable, Index, "Id", &Waveform_Id)) { /* older versions use Id */
if (acb->is_memory) {
if (!utf_query_s16(acbFile, acb->WaveformTable, Index, "MemoryAwbId", &Waveform_Id))
if (!utf_query_s16(acb->WaveformSf, acb->WaveformTable, Index, "MemoryAwbId", &Waveform_Id))
goto fail;
} else {
if (!utf_query_s16(acbFile, acb->WaveformTable, Index, "StreamAwbId", &Waveform_Id))
if (!utf_query_s16(acb->WaveformSf, acb->WaveformTable, Index, "StreamAwbId", &Waveform_Id))
goto fail;
}
}
if (!utf_query_s8(acbFile, acb->WaveformTable, Index, "Streaming", &Waveform_Streaming))
if (!utf_query_s8(acb->WaveformSf, acb->WaveformTable, Index, "Streaming", &Waveform_Streaming))
goto fail;
//;VGM_LOG("ACB: Waveform[%i]: Id=%i, Streaming=%i\n", Index, Waveform_Id, Waveform_Streaming);
@ -182,7 +219,7 @@ static int load_acb_waveform(STREAMFILE *acbFile, acb_header* acb, int16_t Index
return 1;
/* aaand finally get name (phew) */
add_acb_name(acbFile, acb, Waveform_Streaming);
add_acb_name(acb, Waveform_Streaming);
return 1;
fail:
@ -190,9 +227,9 @@ fail:
}
/* define here for Synths pointing to Sequences */
static int load_acb_sequence(STREAMFILE *acbFile, acb_header* acb, int16_t Index);
static int load_acb_sequence(acb_header* acb, int16_t Index);
static int load_acb_synth(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
static int load_acb_synth(acb_header* acb, int16_t Index) {
int i, count;
int8_t Synth_Type;
uint32_t Synth_ReferenceItems_offset;
@ -200,11 +237,11 @@ static int load_acb_synth(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
/* read Synth[Index] */
if (!load_utf_subtable(acbFile, acb, &acb->SynthTable, "SynthTable", NULL))
if (!open_utf_subtable(acb, &acb->SynthSf, &acb->SynthTable, "SynthTable", NULL))
goto fail;
if (!utf_query_s8(acbFile, acb->SynthTable, Index, "Type", &Synth_Type))
if (!utf_query_s8(acb->SynthSf, acb->SynthTable, Index, "Type", &Synth_Type))
goto fail;
if (!utf_query_data(acbFile, acb->SynthTable, Index, "ReferenceItems", &Synth_ReferenceItems_offset, &Synth_ReferenceItems_size))
if (!utf_query_data(acb->SynthSf, acb->SynthTable, Index, "ReferenceItems", &Synth_ReferenceItems_offset, &Synth_ReferenceItems_size))
goto fail;
//;VGM_LOG("ACB: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, Synth_Type, Synth_ReferenceItems_offset, Synth_ReferenceItems_size);
@ -232,8 +269,8 @@ static int load_acb_synth(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
count = Synth_ReferenceItems_size / 0x04;
for (i = 0; i < count; i++) {
uint16_t Synth_ReferenceItem_type = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x00, acbFile);
uint16_t Synth_ReferenceItem_index = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x02, acbFile);
uint16_t Synth_ReferenceItem_type = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x00, acb->SynthSf);
uint16_t Synth_ReferenceItem_index = read_u16be(Synth_ReferenceItems_offset + i*0x04 + 0x02, acb->SynthSf);
//;VGM_LOG("ACB: Synth.ReferenceItem: type=%x, index=%x\n", Synth_ReferenceItem_type, Synth_ReferenceItem_index);
switch(Synth_ReferenceItem_type) {
@ -242,17 +279,17 @@ static int load_acb_synth(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
break;
case 0x01: /* Waveform (most common) */
if (!load_acb_waveform(acbFile, acb, Synth_ReferenceItem_index))
if (!load_acb_waveform(acb, Synth_ReferenceItem_index))
goto fail;
break;
case 0x02: /* Synth, possibly random (rare, found in Sonic Lost World with ReferenceType 2) */
if (!load_acb_synth(acbFile, acb, Synth_ReferenceItem_index))
if (!load_acb_synth(acb, Synth_ReferenceItem_index))
goto fail;
break;
case 0x03: /* Sequence of Synths w/ % in Synth.TrackValues (rare, found in Sonic Lost World with ReferenceType 2) */
if (!load_acb_sequence(acbFile, acb, Synth_ReferenceItem_index))
if (!load_acb_sequence(acb, Synth_ReferenceItem_index))
goto fail;
break;
@ -271,33 +308,33 @@ fail:
return 0;
}
static int load_acb_track_event_command(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
static int load_acb_track_event_command(acb_header* acb, int16_t Index) {
int16_t Track_EventIndex;
uint32_t Track_Command_offset;
uint32_t Track_Command_size;
/* read Track[Index] */
if (!load_utf_subtable(acbFile, acb, &acb->TrackTable, "TrackTable", NULL))
if (!open_utf_subtable(acb, &acb->TrackSf, &acb->TrackTable, "TrackTable", NULL))
goto fail;
if (!utf_query_s16(acbFile, acb->TrackTable, Index, "EventIndex", &Track_EventIndex))
if (!utf_query_s16(acb->TrackSf, acb->TrackTable, Index, "EventIndex", &Track_EventIndex))
goto fail;
//;VGM_LOG("ACB: Track[%i]: EventIndex=%i\n", Index, Track_EventIndex);
/* next link varies with version, check by table existence */
if (acb->has_CommandTable) { /* <=v1.27 */
/* read Command[EventIndex] */
if (!load_utf_subtable(acbFile, acb, &acb->CommandTable, "CommandTable", NULL))
if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "CommandTable", NULL))
goto fail;
if (!utf_query_data(acbFile, acb->CommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size))
if (!utf_query_data(acb->TrackCommandSf, acb->TrackCommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size))
goto fail;
//;VGM_LOG("ACB: Command[%i]: Command={%x,%x}\n", Track_EventIndex, Track_Command_offset,Track_Command_size);
}
else if (acb->has_TrackEventTable) { /* >=v1.28 */
/* read TrackEvent[EventIndex] */
if (!load_utf_subtable(acbFile, acb, &acb->TrackEventTable, "TrackEventTable", NULL))
if (!open_utf_subtable(acb, &acb->TrackCommandSf, &acb->TrackCommandTable, "TrackEventTable", NULL))
goto fail;
if (!utf_query_data(acbFile, acb->TrackEventTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size))
if (!utf_query_data(acb->TrackCommandSf, acb->TrackCommandTable, Track_EventIndex, "Command", &Track_Command_offset, &Track_Command_size))
goto fail;
//;VGM_LOG("ACB: TrackEvent[%i]: Command={%x,%x}\n", Track_EventIndex, Track_Command_offset,Track_Command_size);
}
@ -315,8 +352,8 @@ static int load_acb_track_event_command(STREAMFILE *acbFile, acb_header* acb, in
while (offset < max_offset) {
tlv_code = read_u16be(offset + 0x00, acbFile);
tlv_size = read_u8 (offset + 0x02, acbFile);
tlv_code = read_u16be(offset + 0x00, acb->TrackCommandSf);
tlv_size = read_u8 (offset + 0x02, acb->TrackCommandSf);
offset += 0x03;
if (tlv_code == 0x07D0) {
@ -325,20 +362,20 @@ static int load_acb_track_event_command(STREAMFILE *acbFile, acb_header* acb, in
break;
}
tlv_type = read_u16be(offset + 0x00, acbFile);
tlv_index = read_u16be(offset + 0x02, acbFile);
tlv_type = read_u16be(offset + 0x00, acb->TrackCommandSf);
tlv_index = read_u16be(offset + 0x02, acb->TrackCommandSf);
//;VGM_LOG("ACB: TLV at %x: type %x, index=%x\n", offset, tlv_type, tlv_index);
/* probably same as Synth_ReferenceItem_type */
switch(tlv_type) {
case 0x02: /* Synth (common) */
if (!load_acb_synth(acbFile, acb, tlv_index))
if (!load_acb_synth(acb, tlv_index))
goto fail;
break;
case 0x03: /* Sequence of Synths (common, ex. Yakuza 6, Yakuza Kiwami 2) */
if (!load_acb_sequence(acbFile, acb, tlv_index))
if (!load_acb_sequence(acb, tlv_index))
goto fail;
break;
@ -360,7 +397,7 @@ fail:
return 0;
}
static int load_acb_sequence(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
static int load_acb_sequence(acb_header* acb, int16_t Index) {
int i;
int16_t Sequence_NumTracks;
uint32_t Sequence_TrackIndex_offset;
@ -368,11 +405,11 @@ static int load_acb_sequence(STREAMFILE *acbFile, acb_header* acb, int16_t Index
/* read Sequence[Index] */
if (!load_utf_subtable(acbFile, acb, &acb->SequenceTable, "SequenceTable", NULL))
if (!open_utf_subtable(acb, &acb->SequenceSf, &acb->SequenceTable, "SequenceTable", NULL))
goto fail;
if (!utf_query_s16(acbFile, acb->SequenceTable, Index, "NumTracks", &Sequence_NumTracks))
if (!utf_query_s16(acb->SequenceSf, acb->SequenceTable, Index, "NumTracks", &Sequence_NumTracks))
goto fail;
if (!utf_query_data(acbFile, acb->SequenceTable, Index, "TrackIndex", &Sequence_TrackIndex_offset, &Sequence_TrackIndex_size))
if (!utf_query_data(acb->SequenceSf, acb->SequenceTable, Index, "TrackIndex", &Sequence_TrackIndex_offset, &Sequence_TrackIndex_size))
goto fail;
//;VGM_LOG("ACB: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, Sequence_NumTracks, Sequence_TrackIndex_offset,Sequence_TrackIndex_size);
@ -390,9 +427,9 @@ static int load_acb_sequence(STREAMFILE *acbFile, acb_header* acb, int16_t Index
/* read Tracks inside Sequence */
for (i = 0; i < Sequence_NumTracks; i++) {
int16_t Sequence_TrackIndex_index = read_s16be(Sequence_TrackIndex_offset + i*0x02, acbFile);
int16_t Sequence_TrackIndex_index = read_s16be(Sequence_TrackIndex_offset + i*0x02, acb->SequenceSf);
if (!load_acb_track_event_command(acbFile, acb, Sequence_TrackIndex_index))
if (!load_acb_track_event_command(acb, Sequence_TrackIndex_index))
goto fail;
}
@ -403,7 +440,7 @@ fail:
return 0;
}
static int load_acb_block(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
static int load_acb_block(acb_header* acb, int16_t Index) {
int i;
int16_t Block_NumTracks;
uint32_t Block_TrackIndex_offset;
@ -411,11 +448,11 @@ static int load_acb_block(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
/* read Block[Index] */
if (!load_utf_subtable(acbFile, acb, &acb->BlockTable, "BlockTable", NULL))
if (!open_utf_subtable(acb, &acb->BlockSf, &acb->BlockTable, "BlockTable", NULL))
goto fail;
if (!utf_query_s16(acbFile, acb->BlockTable, Index, "NumTracks", &Block_NumTracks))
if (!utf_query_s16(acb->BlockSf, acb->BlockTable, Index, "NumTracks", &Block_NumTracks))
goto fail;
if (!utf_query_data(acbFile, acb->BlockTable, Index, "TrackIndex", &Block_TrackIndex_offset, &Block_TrackIndex_size))
if (!utf_query_data(acb->BlockSf, acb->BlockTable, Index, "TrackIndex", &Block_TrackIndex_offset, &Block_TrackIndex_size))
goto fail;
//;VGM_LOG("ACB: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, Block_NumTracks, Block_TrackIndex_offset,Block_TrackIndex_size);
@ -426,9 +463,9 @@ static int load_acb_block(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
/* read Tracks inside Block */
for (i = 0; i < Block_NumTracks; i++) {
int16_t Block_TrackIndex_index = read_s16be(Block_TrackIndex_offset + i*0x02, acbFile);
int16_t Block_TrackIndex_index = read_s16be(Block_TrackIndex_offset + i*0x02, acb->BlockSf);
if (!load_acb_track_event_command(acbFile, acb, Block_TrackIndex_index))
if (!load_acb_track_event_command(acb, Block_TrackIndex_index))
goto fail;
}
@ -438,17 +475,17 @@ fail:
}
static int load_acb_cue(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
static int load_acb_cue(acb_header* acb, int16_t Index) {
int8_t Cue_ReferenceType;
int16_t Cue_ReferenceIndex;
/* read Cue[Index] */
if (!load_utf_subtable(acbFile, acb, &acb->CueTable, "CueTable", NULL))
if (!open_utf_subtable(acb, &acb->CueSf, &acb->CueTable, "CueTable", NULL))
goto fail;
if (!utf_query_s8 (acbFile, acb->CueTable, Index, "ReferenceType", &Cue_ReferenceType))
if (!utf_query_s8 (acb->CueSf, acb->CueTable, Index, "ReferenceType", &Cue_ReferenceType))
goto fail;
if (!utf_query_s16(acbFile, acb->CueTable, Index, "ReferenceIndex", &Cue_ReferenceIndex))
if (!utf_query_s16(acb->CueSf, acb->CueTable, Index, "ReferenceIndex", &Cue_ReferenceIndex))
goto fail;
//;VGM_LOG("ACB: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, Cue_ReferenceType, Cue_ReferenceIndex);
@ -457,22 +494,22 @@ static int load_acb_cue(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
switch(Cue_ReferenceType) {
case 1: /* Cue > Waveform (ex. PES 2015) */
if (!load_acb_waveform(acbFile, acb, Cue_ReferenceIndex))
if (!load_acb_waveform(acb, Cue_ReferenceIndex))
goto fail;
break;
case 2: /* Cue > Synth > Waveform (ex. Ukiyo no Roushi) */
if (!load_acb_synth(acbFile, acb, Cue_ReferenceIndex))
if (!load_acb_synth(acb, Cue_ReferenceIndex))
goto fail;
break;
case 3: /* Cue > Sequence > Track > Command > Synth > Waveform (ex. Valkyrie Profile anatomia, Yakuza Kiwami 2) */
if (!load_acb_sequence(acbFile, acb, Cue_ReferenceIndex))
if (!load_acb_sequence(acb, Cue_ReferenceIndex))
goto fail;
break;
case 8: /* Cue > Block > Track > Command > Synth > Waveform (ex. Sonic Lost World, rare) */
if (!load_acb_block(acbFile, acb, Cue_ReferenceIndex))
if (!load_acb_block(acb, Cue_ReferenceIndex))
goto fail;
break;
@ -488,17 +525,17 @@ fail:
}
static int load_acb_cuename(STREAMFILE *acbFile, acb_header* acb, int16_t Index) {
static int load_acb_cuename(acb_header* acb, int16_t Index) {
int16_t CueName_CueIndex;
const char* CueName_CueName;
/* read CueName[Index] */
if (!load_utf_subtable(acbFile, acb, &acb->CueNameTable, "CueNameTable", NULL))
if (!open_utf_subtable(acb, &acb->CueNameSf, &acb->CueNameTable, "CueNameTable", NULL))
goto fail;
if (!utf_query_s16(acbFile, acb->CueNameTable, Index, "CueIndex", &CueName_CueIndex))
if (!utf_query_s16(acb->CueNameSf, acb->CueNameTable, Index, "CueIndex", &CueName_CueIndex))
goto fail;
if (!utf_query_string(acbFile, acb->CueNameTable, Index, "CueName", &CueName_CueName))
if (!utf_query_string(acb->CueNameSf, acb->CueNameTable, Index, "CueName", &CueName_CueName))
goto fail;
//;VGM_LOG("ACB: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, CueName_CueIndex, CueName_CueName);
@ -507,7 +544,7 @@ static int load_acb_cuename(STREAMFILE *acbFile, acb_header* acb, int16_t Index)
acb->cuename_index = Index;
acb->cuename_name = CueName_CueName;
if (!load_acb_cue(acbFile, acb, CueName_CueIndex))
if (!load_acb_cue(acb, CueName_CueIndex))
goto fail;
return 1;
@ -516,12 +553,12 @@ fail:
}
void load_acb_wave_name(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int is_memory) {
void load_acb_wave_name(STREAMFILE *streamFile, VGMSTREAM* vgmstream, int waveid, int is_memory) {
acb_header acb = {0};
int i, CueName_rows;
if (!acbFile || !vgmstream || waveid < 0)
if (!streamFile || !vgmstream || waveid < 0)
return;
/* Normally games load a .acb + .awb, and asks the .acb to play a cue by name or index.
@ -548,21 +585,23 @@ void load_acb_wave_name(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, i
//;VGM_LOG("ACB: find waveid=%i\n", waveid);
acb.Header = utf_open(acbFile, 0x00, NULL, NULL);
acb.acbFile = streamFile;
acb.Header = utf_open(acb.acbFile, 0x00, NULL, NULL);
if (!acb.Header) goto fail;
acb.target_waveid = waveid;
acb.is_memory = is_memory;
acb.has_TrackEventTable = utf_query_data(acbFile, acb.Header, 0, "TrackEventTable", NULL,NULL);
acb.has_CommandTable = utf_query_data(acbFile, acb.Header, 0, "CommandTable", NULL,NULL);
acb.has_TrackEventTable = utf_query_data(acb.acbFile, acb.Header, 0, "TrackEventTable", NULL,NULL);
acb.has_CommandTable = utf_query_data(acb.acbFile, acb.Header, 0, "CommandTable", NULL,NULL);
/* read all possible cue names and find which waveids are referenced by it */
if (!load_utf_subtable(acbFile, &acb, &acb.CueNameTable, "CueNameTable", &CueName_rows))
if (!open_utf_subtable(&acb, &acb.CueNameSf, &acb.CueNameTable, "CueNameTable", &CueName_rows))
goto fail;
for (i = 0; i < CueName_rows; i++) {
if (!load_acb_cuename(acbFile, &acb, i))
if (!load_acb_cuename(&acb, i))
goto fail;
}
@ -574,12 +613,20 @@ void load_acb_wave_name(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, i
fail:
utf_close(acb.Header);
utf_close(acb.CueNameTable);
utf_close(acb.CueTable);
utf_close(acb.SequenceTable);
utf_close(acb.TrackTable);
utf_close(acb.TrackEventTable);
utf_close(acb.CommandTable);
utf_close(acb.TrackCommandTable);
utf_close(acb.SynthTable);
utf_close(acb.WaveformTable);
close_streamfile(acb.CueNameSf);
close_streamfile(acb.CueSf);
close_streamfile(acb.SequenceSf);
close_streamfile(acb.TrackSf);
close_streamfile(acb.TrackCommandSf);
close_streamfile(acb.SynthSf);
close_streamfile(acb.WaveformSf);
}

View File

@ -294,6 +294,12 @@ static const hcakey_info hcakey_list[] = {
/* Uta Macross SmaPho De Culture (Android) */
{396798934275978741}, // 0581B68744C5F5F5
/* Touhou Cannonball (Android) BGM */
{6081391598581785139}, // 5465717035832233
/* Love Live! School idol festival ALL STARS (Android) */
{6498535309877346413}, // 5A2F6F6F0192806D
/* Dragalia Lost (Cygames) [iOS/Android] */
{2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD

View File

@ -1,67 +1,46 @@
#include "meta.h"
#include "../util.h"
/* SAP (from Bubble_Symphony) */
/* SAP - from Bubble Symphony (SAT) */
VGMSTREAM * init_vgmstream_sat_sap(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
char filename[PATH_LIMIT];
off_t start_offset;
int num_samples;
int loop_flag = 0, channel_count;
int loop_flag = 0;
int channel_count;
/* check extension, case insensitive */
streamFile->get_name(streamFile,filename,sizeof(filename));
if (strcasecmp("sap",filename_extension(filename))) goto fail;
/* check header */
if (read_32bitBE(0x0A,streamFile) != 0x0010400E) /* "0010400E" */
/* checks */
if (!check_extensions(streamFile, "sap"))
goto fail;
loop_flag = 0; /* (read_32bitLE(0x08,streamFile)!=0); */
num_samples = read_32bitBE(0x00,streamFile); /* first for I/O reasons */
channel_count = read_32bitBE(0x04,streamFile);
if (channel_count != 1) goto fail; /* unknown layout */
/* build the VGMSTREAM */
if (read_32bitBE(0x08,streamFile) != 0x10) /* bps? */
goto fail;
if (read_16bitBE(0x0c,streamFile) != 0x400E) /* ? */
goto fail;
loop_flag = 0;
start_offset = 0x800;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
/* fill in the vital statistics */
start_offset = 0x800;
vgmstream->channels = channel_count;
vgmstream->meta_type = meta_SAP;
vgmstream->sample_rate = (uint16_t)read_16bitBE(0x0E,streamFile);
vgmstream->num_samples = num_samples;
vgmstream->coding_type = coding_PCM16BE;
vgmstream->num_samples = read_32bitBE(0x00,streamFile);
if (loop_flag) {
vgmstream->loop_start_sample = 0; /* (read_32bitLE(0x08,streamFile)-1)*28; */
vgmstream->loop_end_sample = read_32bitBE(0x00,streamFile);
}
vgmstream->layout_type = layout_none;
vgmstream->interleave_block_size = 0x10;
vgmstream->meta_type = meta_SAT_SAP;
/* open the file for reading */
{
int i;
STREAMFILE * file;
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
if (!file) goto fail;
for (i=0;i<channel_count;i++) {
vgmstream->ch[i].streamfile = file;
vgmstream->ch[i].channel_start_offset=
vgmstream->ch[i].offset=start_offset+
vgmstream->interleave_block_size*i;
}
}
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
goto fail;
return vgmstream;
/* clean up anything we may have opened */
fail:
if (vgmstream) close_vgmstream(vgmstream);
close_vgmstream(vgmstream);
return NULL;
}

View File

@ -552,8 +552,15 @@ static VGMSTREAM *init_subfile(txth_header * txth) {
STREAMFILE * streamSubfile = NULL;
if (txth->subfile_size == 0)
txth->subfile_size = txth->data_size - txth->subfile_offset;
if (txth->subfile_size == 0) {
if (txth->data_size_set)
txth->subfile_size = txth->data_size;
else
txth->subfile_size = txth->data_size - txth->subfile_offset;
if (txth->subfile_size + txth->subfile_offset > get_streamfile_size(txth->streamBody))
txth->subfile_size = get_streamfile_size(txth->streamBody) - txth->subfile_offset;
}
if (txth->subfile_extension[0] == '\0')
get_streamfile_ext(txth->streamFile,txth->subfile_extension,sizeof(txth->subfile_extension));
@ -586,7 +593,8 @@ static VGMSTREAM *init_subfile(txth_header * txth) {
vgmstream_force_loop(vgmstream, 0, 0, 0);
}
if (txth->chunk_count && txth->subsong_count) {
/* assumes won't point to subfiles with subsongs */
if (/*txth->chunk_count &&*/ txth->subsong_count) {
vgmstream->num_streams = txth->subsong_count;
}
//todo: other combos with subsongs + subfile?
@ -1249,7 +1257,7 @@ static int is_substring(const char * val, const char * cmp, int inline_field) {
chr = val[len];
/* "val" can end with math for inline fields (like interleave*0x10) */
if (inline_field && (chr == '+' || chr == '-' || chr == '*' || chr == '/'))
if (inline_field && (chr == '+' || chr == '-' || chr == '*' || chr == '/' || chr == '&'))
return len;
/* otherwise "val" ends in space or eof (to tell "interleave" and "interleave_last" apart) */
@ -1525,7 +1533,7 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v
brackets--;
n = 1;
}
else if (type == '+' || type == '-' || type == '/' || type == '*') { /* op */
else if (type == '+' || type == '-' || type == '/' || type == '*' || type == '&') { /* op */
op = type;
n = 1;
}
@ -1593,6 +1601,8 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v
else if ((n = is_string_field(val,"loop_end_sample"))) value = txth->loop_end_sample;
else if ((n = is_string_field(val,"subsong_count"))) value = txth->subsong_count;
else if ((n = is_string_field(val,"subsong_offset"))) value = txth->subsong_offset;
else if ((n = is_string_field(val,"subfile_offset"))) value = txth->subfile_offset;
else if ((n = is_string_field(val,"subfile_size"))) value = txth->subfile_size;
//todo whatever, improve
else if ((n = is_string_field(val,"name_value"))) value = txth->name_values[0];
else if ((n = is_string_field(val,"name_value1"))) value = txth->name_values[0];
@ -1624,6 +1634,7 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v
case '-': value = result - value; break;
case '*': value = result * value; break;
case '/': if (value == 0) goto fail; value = result / value; break;
case '&': value = result & value; break;
default: break;
}
op = ' '; /* consume */

View File

@ -91,6 +91,45 @@ static void build_readable_name(char * buf, size_t buf_size, ubi_hx_header * hx)
snprintf(buf,buf_size, "%s/%i/%08x-%08x/%s", "hx", hx->header_index, hx->cuuid1,hx->cuuid2, grp_name);
}
#define TXT_LINE_MAX 0x1000
/* get name */
static int parse_name_bnh(ubi_hx_header * hx, STREAMFILE *sf, uint32_t cuuid1, uint32_t cuuid2) {
STREAMFILE *sf_t;
off_t txt_offset = 0;
char line[TXT_LINE_MAX];
char cuuid[40];
sf_t = open_streamfile_by_ext(sf,"bnh");
if (sf_t == NULL) goto fail;
snprintf(cuuid,sizeof(cuuid), "cuuid( 0x%08x, 0x%08x )", cuuid1, cuuid2);
/* each .bnh line has a cuuid, a bunch of repeated fields and name (sometimes name is filename or "bad name") */
while (txt_offset < get_streamfile_size(sf)) {
int line_read, bytes_read;
bytes_read = get_streamfile_text_line(TXT_LINE_MAX,line, txt_offset,sf_t, &line_read);
if (!line_read) break;
txt_offset += bytes_read;
if (strncmp(line,cuuid,31) != 0)
continue;
if (bytes_read <= 79)
goto fail;
/* cuuid found, copy name (lines are fixed and always starts from the same position) */
strcpy(hx->internal_name, &line[79]);
close_streamfile(sf_t);
return 1;
}
fail:
close_streamfile(sf_t);
return 0;
}
/* get referenced name from WavRes, using the index again (abridged) */
static int parse_name(ubi_hx_header * hx, STREAMFILE *sf) {
@ -107,6 +146,7 @@ static int parse_name(ubi_hx_header * hx, STREAMFILE *sf) {
off_t header_offset;
size_t class_size;
int j, link_count, language_count, is_found = 0;
uint32_t cuuid1, cuuid2;
class_size = read_32bit(offset + 0x00, sf);
@ -114,6 +154,9 @@ static int parse_name(ubi_hx_header * hx, STREAMFILE *sf) {
read_string(class_name,class_size+1, offset + 0x04, sf); /* not null-terminated */
offset += 0x04 + class_size;
cuuid1 = (uint32_t)read_32bit(offset + 0x00, sf);
cuuid2 = (uint32_t)read_32bit(offset + 0x04, sf);
header_offset = read_32bit(offset + 0x08, sf);
offset += 0x10;
@ -159,10 +202,18 @@ static int parse_name(ubi_hx_header * hx, STREAMFILE *sf) {
resclass_size = read_32bit(wavres_offset, sf);
wavres_offset += 0x04 + resclass_size + 0x08 + 0x04; /* skip class + cuiid + flags */
internal_size = read_32bit(wavres_offset + 0x00, sf); /* usually 0 in consoles */
internal_size = read_32bit(wavres_offset + 0x00, sf);
if (internal_size > sizeof(hx->internal_name)+1) goto fail;
read_string(hx->internal_name,internal_size+1, wavres_offset + 0x04, sf);
return 1;
/* usually 0 in consoles */
if (internal_size != 0) {
read_string(hx->internal_name,internal_size+1, wavres_offset + 0x04, sf);
return 1;
}
else {
parse_name_bnh(hx, sf, cuuid1, cuuid2);
return 1; /* ignore error */
}
}
}
@ -181,7 +232,7 @@ static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t
//todo cleanup/unify common readings
//;VGM_LOG("UBI HX: header class %s, o=%lx, s=%x\n\n", class_name, header_offset, header_size);
//;VGM_LOG("UBI HX: header o=%lx, s=%x\n\n", offset, size);
hx->header_index = index;
hx->header_offset = offset;
@ -307,6 +358,8 @@ static int parse_header(ubi_hx_header * hx, STREAMFILE *sf, off_t offset, size_t
hx->stream_size = read_32bit(offset + 0x04, sf);
offset += 0x08;
//todo some dummy files have 0 size
if (read_32bit(offset + 0x00, sf) != 0x01) goto fail;
/* 0x04: some kind of parent id shared by multiple Waves, or 0 */
offset += 0x08;
@ -454,6 +507,10 @@ static int parse_hx(ubi_hx_header * hx, STREAMFILE *sf, int target_subsong) {
}
//todo figure out CProgramResData sequences
// Format is pretty complex list of values and some offsets in between, then field names
// then more values and finally a list of linked IDs Links are the same as in the index,
// but doesn't seem to be a straight sequence list. Seems it can be used for other config too.
/* identify all possible names so unknown platforms fail */
if (strcmp(class_name, "CEventResData") == 0 || /* play/stop/etc event */
strcmp(class_name, "CProgramResData") == 0 || /* some kind of map/object-like config to make sequences in some cases? */

View File

@ -1495,37 +1495,15 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
switch (vgmstream->coding_type) {
case coding_CRI_ADX:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_adx(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
vgmstream->channels,vgmstream->samples_into_block,samples_to_do,
vgmstream->interleave_block_size);
}
break;
case coding_CRI_ADX_exp:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_adx_exp(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
vgmstream->channels,vgmstream->samples_into_block,samples_to_do,
vgmstream->interleave_block_size);
}
break;
case coding_CRI_ADX_fixed:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_adx_fixed(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
vgmstream->channels,vgmstream->samples_into_block,samples_to_do,
vgmstream->interleave_block_size);
}
break;
case coding_CRI_ADX_enc_8:
case coding_CRI_ADX_enc_9:
for (ch = 0; ch < vgmstream->channels; ch++) {
decode_adx_enc(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
decode_adx(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch,
vgmstream->channels,vgmstream->samples_into_block,samples_to_do,
vgmstream->interleave_block_size);
vgmstream->interleave_block_size, vgmstream->coding_type);
}
break;
case coding_NGC_DSP:
for (ch = 0; ch < vgmstream->channels; ch++) {
@ -2417,7 +2395,7 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
}
/* codecs with configurable frame size */
if (vgmstream->layout_type == layout_none && vgmstream->interleave_block_size > 0) {
if (vgmstream->interleave_block_size > 0) {
switch (vgmstream->coding_type) {
case coding_MSADPCM:
case coding_MSADPCM_int:
@ -2813,6 +2791,23 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s
return 1;
#endif
if ((vgmstream->coding_type == coding_PSX_cfg ||
vgmstream->coding_type == coding_PSX_pivotal) &&
(vgmstream->interleave_block_size == 0 || vgmstream->interleave_block_size > 0x50)) {
VGM_LOG("VGMSTREAM: PSX-cfg decoder with wrong frame size %x\n", vgmstream->interleave_block_size);
return 0;
}
if ((vgmstream->coding_type == coding_CRI_ADX ||
vgmstream->coding_type == coding_CRI_ADX_enc_8 ||
vgmstream->coding_type == coding_CRI_ADX_enc_9 ||
vgmstream->coding_type == coding_CRI_ADX_exp ||
vgmstream->coding_type == coding_CRI_ADX_fixed) &&
(vgmstream->interleave_block_size == 0 || vgmstream->interleave_block_size > 0x12)) {
VGM_LOG("VGMSTREAM: ADX decoder with wrong frame size %x\n", vgmstream->interleave_block_size);
return 0;
}
/* if interleave is big enough keep a buffer per channel */
if (vgmstream->interleave_block_size * vgmstream->channels >= STREAMFILE_DEFAULT_BUFFER_SIZE) {
use_streamfile_per_channel = 1;

View File

@ -397,7 +397,7 @@ typedef enum {
meta_DC_STR, /* SEGA Stream Asset Builder */
meta_DC_STR_V2, /* variant of SEGA Stream Asset Builder */
meta_NGC_BH2PCM, /* Bio Hazard 2 */
meta_SAT_SAP, /* Bubble Symphony */
meta_SAP,
meta_DC_IDVI, /* Eldorado Gate */
meta_KRAW, /* Geometry Wars - Galaxies */
meta_PS2_OMU, /* PS2 Int file with Header */

View File

@ -902,7 +902,7 @@ static void add_extension(int length, char * dst, const char * ext) {
/* copy new extension + double null terminate */
/* ex: "vgmstream\0vgmstream Audio File (*.VGMSTREAM)\0" */
written = sprintf(buf, "%s%c%s Audio File (*.%s)%c", ext,'\0',ext_upp,ext_upp,'\0');
written = snprintf(buf,sizeof(buf)-1, "%s%c%s Audio File (*.%s)%c", ext,'\0',ext_upp,ext_upp,'\0');
for (j = 0; j < written; i++,j++)
dst[i] = buf[j];
dst[i] = '\0';