mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-19 00:04:04 +01:00
Merge pull request #1577 from bnnm/api-misc7
- Add .ntx extension [GTA3 (Android)] - Remove Camelot .bcstm loop hack - cleanup
This commit is contained in:
commit
0c9088a2a3
@ -121,7 +121,7 @@ static int api_example(const char* infile) {
|
||||
buf_bytes = err * sizeof(short) * lib->format->channels;
|
||||
}
|
||||
else {
|
||||
err = libvgmstream_play(lib);
|
||||
err = libvgmstream_render(lib);
|
||||
if (err < 0) goto fail;
|
||||
|
||||
buf = lib->decoder->buf;
|
||||
|
@ -94,7 +94,7 @@ as explained below, but often will use default values. Accepted codec strings:
|
||||
# * Variation with modified encoding
|
||||
# - PCM8_SB PCM 8-bit with sign bit
|
||||
# * Variation with modified encoding
|
||||
# * For few rare games [Sonic CD (SCD)]
|
||||
# * For few rare games [Sonic CD (SCD), Road Avenger (SCD)]
|
||||
# - PCM24LE PCM 24-bit little endian
|
||||
# * For few rare games [100% Orange Juice (PC)-sfx]
|
||||
# * Interleave is multiple of 0x3 (default)
|
||||
|
48
doc/USAGE.md
48
doc/USAGE.md
@ -216,14 +216,29 @@ WAV/MP3 conversions ahead of time.
|
||||
The tag syntax follows the conventions established in Apple's HTTP Live Streaming
|
||||
standard, whose docs discuss extending M3U with arbitrary tags.
|
||||
|
||||
### Related projects
|
||||
We only manage the above components, but there are other projects using
|
||||
vgmstream that may useful for other cases. A few of them:
|
||||
- Web browser player: https://github.com/KatieFrogs/vgmstream-web
|
||||
- AIMP plugin: https://github.com/ArtemIzmaylov/aimp_vgmstream
|
||||
- DeaDBeeF plugin: https://github.com/jchv/deadbeef-vgmstream
|
||||
- Python bindings: https://github.com/hugeBlack/pyvgmstream
|
||||
- 3DS port: https://github.com/TricksterGuy/3ds-vgmstream
|
||||
- Reaper plugin: https://github.com/maxton/reaper_vgmstream
|
||||
- Simple GUI: https://github.com/BENICHN/VGMGUI
|
||||
|
||||
They may not be up to date though, and since they aren't part of vgmstream
|
||||
issues should be directed to each project.
|
||||
|
||||
|
||||
## Special cases
|
||||
vgmstream aims to support most audio formats as-is, but some files require extra
|
||||
handling.
|
||||
|
||||
### Subsongs
|
||||
Certain container formats have multiple audio files, usually called "subsongs", often
|
||||
not meant to be extracted (no simple separation from container).
|
||||
Certain container formats have multiple audio files, usually called "subsongs",
|
||||
which usually are not meant to be extracted as single files (can't easily separate
|
||||
from their container).
|
||||
|
||||
By default vgmstream plays first subsong and reports total subsongs, if the format
|
||||
is able to contain them. Easiest to use would be the *foobar/winamp/Audacious*
|
||||
@ -233,17 +248,17 @@ With CLI tools, you can select a subsong using the `-s` flag followed by a numbe
|
||||
for example: `vgmstream-cli -s 5 file.bank` or `vgmstream123 -s 5 file.bank`.
|
||||
|
||||
Using *vgmstream-cli* you can convert multiple subsongs at once using the `-S` flag.
|
||||
**WARNING, MAY TAKE A LOT OF SPACE!** Some files have been observed to contain +20000
|
||||
**WARNING, MAY TAKE A LOT OF SPACE!** Some containers have been observed to contain +20000
|
||||
subsongs, so don't use this lightly. Remember to set an output name (`-o`) with subsong
|
||||
wildcards (or leave it alone for the defaults).
|
||||
wildcards (or leave it alone for good defaults).
|
||||
- `vgmstream-cli -s 1 -S 100 file.bank`: writes from subsong 1 to subsong 100
|
||||
- `vgmstream-cli -s 101 -S 0 file.bank`: writes from subsong 101 to max subsong (automatically changes 0 to max)
|
||||
- `vgmstream-cli -S 0 file.bank`: writes from subsong 1 to max subsong
|
||||
- `vgmstream-cli -s 1 -S 5 -o bgm.wav file.bank`: writes 5 subsongs, but all overwrite the same file = wrong.
|
||||
- `vgmstream-cli -s 1 -S 5 -o bgm_?02s.wav file.bank`: writes 5 subsongs, each named differently = correct.
|
||||
|
||||
For other players without support, or to play only a few choice subsongs, you
|
||||
can create multiple `.txtp` (explained later) to select one, like `bgm.sxd#10.txtp`
|
||||
For players without subsong support, or to play only a few choice subsongs you can
|
||||
create multiple `.txtp` (explained later) to select one subsong, like `bgm.sxd#10.txtp`
|
||||
(plays subsong 10 in `bgm.sxd`).
|
||||
|
||||
You can use this python script to autogenerate one `.txtp` per subsong:
|
||||
@ -693,16 +708,20 @@ order). The format is meant to be both a quick playlist and tags, but the tagfil
|
||||
itself just 'looks' like an M3U. you can load files manually or using other playlists
|
||||
and still get tags.
|
||||
|
||||
Currently there is no way to simplify adding tags and you need to manually add them,
|
||||
but format is just a text file. You can use your player to save a playlist in `.m3u`
|
||||
format sinde the folder with your files, then edit it with any text editor.
|
||||
|
||||
Format is:
|
||||
```
|
||||
# ignored comment
|
||||
# comment (ignored)
|
||||
# $GLOBAL_COMMAND (extra features)
|
||||
# @GLOBAL_TAG text (applies all following tracks)
|
||||
|
||||
# %LOCAL_TAG text (applies to next track only)
|
||||
filename1
|
||||
filename1.ext
|
||||
# %LOCAL_TAG text (applies to next track only)
|
||||
filename2
|
||||
filename2.ext
|
||||
```
|
||||
Accepted tags depend on the player (foobar: any; Winamp: see ATF config, Audacious:
|
||||
few standard ones), typically *ALBUM/ARTIST/TITLE/DISC/TRACK/COMPOSER/etc*, lower
|
||||
@ -727,7 +746,7 @@ Example:
|
||||
# * Global tags apply to all songs, unless overwritten
|
||||
# Better use ARTIST instead of ALBUMARTIST (more compatible)
|
||||
# Tags usually go in CAPS for readability but no differences
|
||||
|
||||
#
|
||||
# $AUTOTRACK
|
||||
# * This adds TRACK tags automatically from 1 to N
|
||||
|
||||
@ -842,10 +861,11 @@ BGM01.adx #I 1.0 90.0 .txtp
|
||||
|
||||
### Issues
|
||||
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
|
||||
the format defined.
|
||||
and "vgmstream version" or such text shows in the file properties (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 the
|
||||
format defined.
|
||||
|
||||
When tags change behavior varies depending on player:
|
||||
- *Winamp*: should refresh tags when a different file is played.
|
||||
|
@ -51,7 +51,7 @@ static void update_decoder_info(libvgmstream_priv_t* priv, int samples_done) {
|
||||
priv->dec.done = priv->decode_done;
|
||||
}
|
||||
|
||||
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib) {
|
||||
LIBVGMSTREAM_API int libvgmstream_render(libvgmstream_t* lib) {
|
||||
if (!lib || !lib->priv)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
@ -85,7 +85,7 @@ LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_s
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
if (priv->buf.consumed >= priv->buf.samples) {
|
||||
int err = libvgmstream_play(lib);
|
||||
int err = libvgmstream_render(lib);
|
||||
if (err < 0) return err;
|
||||
}
|
||||
|
||||
|
@ -88,4 +88,8 @@ LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_ti
|
||||
return LIBVGMSTREAM_OK;
|
||||
}
|
||||
|
||||
LIBVGMSTREAM_API bool libvgmstream_is_virtual_filename(const char* filename) {
|
||||
return vgmstream_is_virtual_filename(filename);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "decode.h"
|
||||
#include "mixing.h"
|
||||
#include "plugins.h"
|
||||
#include "sbuf.h"
|
||||
|
||||
/* custom codec handling, not exactly "decode" stuff but here to simplify adding new codecs */
|
||||
|
||||
@ -824,18 +825,19 @@ bool decode_uses_internal_offset_updates(VGMSTREAM* vgmstream) {
|
||||
return vgmstream->coding_type == coding_MS_IMA || vgmstream->coding_type == coding_MS_IMA_mono;
|
||||
}
|
||||
|
||||
/* Decode samples into the buffer. Assume that we have written samples_written into the
|
||||
/* Decode samples into the buffer. Assume that we have written samples_filled into the
|
||||
* buffer already, and we have samples_to_do consecutive samples ahead of us (won't call
|
||||
* more than one frame if configured above to do so).
|
||||
* Called by layouts since they handle samples written/to_do */
|
||||
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_do, sample_t* buffer) {
|
||||
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_do, sample_t* buffer) {
|
||||
int ch;
|
||||
|
||||
buffer += samples_written * vgmstream->channels; /* passed externally to simplify I guess */
|
||||
buffer += samples_filled * vgmstream->channels; /* passed externally to simplify I guess */
|
||||
//samples_to_do -= samples_filled; /* pre-adjusted */
|
||||
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_SILENCE:
|
||||
memset(buffer, 0, samples_to_do * vgmstream->channels * sizeof(sample_t));
|
||||
sbuf_silence(buffer, samples_to_do, vgmstream->channels, 0);
|
||||
break;
|
||||
|
||||
case coding_CRI_ADX:
|
||||
@ -1601,10 +1603,10 @@ int decode_get_samples_to_do(int samples_this_block, int samples_per_frame, VGMS
|
||||
return samples_to_do;
|
||||
}
|
||||
|
||||
/* Detect loop start and save values, or detect loop end and restore (loop back).
|
||||
* Returns 1 if loop was done. */
|
||||
int decode_do_loop(VGMSTREAM* vgmstream) {
|
||||
/*if (!vgmstream->loop_flag) return 0;*/
|
||||
|
||||
/* Detect loop start and save values, or detect loop end and restore (loop back). Returns true if loop was done. */
|
||||
bool decode_do_loop(VGMSTREAM* vgmstream) {
|
||||
//if (!vgmstream->loop_flag) return false;
|
||||
|
||||
/* is this the loop end? = new loop, continue from loop_start_sample */
|
||||
if (vgmstream->current_sample == vgmstream->loop_end_sample) {
|
||||
@ -1613,8 +1615,8 @@ int decode_do_loop(VGMSTREAM* vgmstream) {
|
||||
* (only needed with the "play stream end after looping N times" option enabled) */
|
||||
vgmstream->loop_count++;
|
||||
if (vgmstream->loop_target && vgmstream->loop_target == vgmstream->loop_count) {
|
||||
vgmstream->loop_flag = 0; /* could be improved but works ok, will be restored on resets */
|
||||
return 0;
|
||||
vgmstream->loop_flag = false; /* could be improved but works ok, will be restored on resets */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* against everything I hold sacred, preserve adpcm history before looping for certain types */
|
||||
@ -1623,8 +1625,7 @@ int decode_do_loop(VGMSTREAM* vgmstream) {
|
||||
vgmstream->meta_type == meta_DSP_CSTR ||
|
||||
vgmstream->coding_type == coding_PSX ||
|
||||
vgmstream->coding_type == coding_PSX_badflags) {
|
||||
int ch;
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
for (int ch = 0; ch < vgmstream->channels; ch++) {
|
||||
vgmstream->loop_ch[ch].adpcm_history1_16 = vgmstream->ch[ch].adpcm_history1_16;
|
||||
vgmstream->loop_ch[ch].adpcm_history2_16 = vgmstream->ch[ch].adpcm_history2_16;
|
||||
vgmstream->loop_ch[ch].adpcm_history1_32 = vgmstream->ch[ch].adpcm_history1_32;
|
||||
@ -1633,12 +1634,13 @@ int decode_do_loop(VGMSTREAM* vgmstream) {
|
||||
}
|
||||
|
||||
//TODO: improve
|
||||
/* loop codecs that need special handling, usually:
|
||||
* - on hit_loop, current offset is copied to loop_ch[].offset
|
||||
* - some codecs will overwrite loop_ch[].offset with a custom value
|
||||
* - loop_ch[] is copied to ch[] (with custom value)
|
||||
* - then codec will use ch[]'s offset
|
||||
* regular codecs may use copied loop_ch[] offset without issue */
|
||||
/* codecs with codec_data that decode_seek need special handling, usually:
|
||||
* - during decode, codec uses vgmstream->ch[].offset to handle current offset
|
||||
* - on hit_loop, current offset is auto-copied to vgmstream->loop_ch[].offset
|
||||
* - decode_seek codecs may overwrite vgmstream->loop_ch[].offset with a custom value (such as start_offset)
|
||||
* - vgmstream->loop_ch[] is copied below to vgmstream->ch[] (with the newly assigned custom value)
|
||||
* - then codec will use vgmstream->ch[].offset during decode
|
||||
* regular codecs will use copied vgmstream->loop_ch[].offset without issue */
|
||||
decode_seek(vgmstream);
|
||||
|
||||
/* restore! */
|
||||
@ -1649,7 +1651,7 @@ int decode_do_loop(VGMSTREAM* vgmstream) {
|
||||
vgmstream->current_block_samples = vgmstream->loop_block_samples;
|
||||
vgmstream->current_block_offset = vgmstream->loop_block_offset;
|
||||
vgmstream->next_block_offset = vgmstream->loop_next_block_offset;
|
||||
//vgmstream->pstate = vgmstream->lstate; /* play state is applied over loops */
|
||||
vgmstream->full_block_size = vgmstream->loop_full_block_size;
|
||||
|
||||
/* loop layouts (after restore, in case layout needs state manipulations) */
|
||||
switch(vgmstream->layout_type) {
|
||||
@ -1663,24 +1665,30 @@ int decode_do_loop(VGMSTREAM* vgmstream) {
|
||||
break;
|
||||
}
|
||||
|
||||
return 1; /* looped */
|
||||
/* play state is applied over loops and stream decoding, so it's not restored on loops */
|
||||
//vgmstream->pstate = vgmstream->lstate;
|
||||
|
||||
return true; /* has looped */
|
||||
}
|
||||
|
||||
|
||||
/* is this the loop start? save if we haven't saved yet (right when first loop starts) */
|
||||
if (!vgmstream->hit_loop && vgmstream->current_sample == vgmstream->loop_start_sample) {
|
||||
/* save! */
|
||||
memcpy(vgmstream->loop_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels);
|
||||
memcpy(vgmstream->loop_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL) * vgmstream->channels);
|
||||
vgmstream->loop_current_sample = vgmstream->current_sample;
|
||||
vgmstream->loop_samples_into_block = vgmstream->samples_into_block;
|
||||
vgmstream->loop_block_size = vgmstream->current_block_size;
|
||||
vgmstream->loop_block_samples = vgmstream->current_block_samples;
|
||||
vgmstream->loop_block_offset = vgmstream->current_block_offset;
|
||||
vgmstream->loop_next_block_offset = vgmstream->next_block_offset;
|
||||
//vgmstream->lstate = vgmstream->pstate; /* play state is applied over loops */
|
||||
vgmstream->loop_full_block_size = vgmstream->full_block_size;
|
||||
|
||||
/* play state is applied over loops and stream decoding, so it's not saved on loops */
|
||||
//vgmstream->lstate = vgmstream->pstate;
|
||||
|
||||
vgmstream->hit_loop = true; /* info that loop is now ready to use */
|
||||
}
|
||||
|
||||
return 0; /* not looped */
|
||||
return false; /* has not looped */
|
||||
}
|
||||
|
@ -7,12 +7,12 @@ void decode_free(VGMSTREAM* vgmstream);
|
||||
void decode_seek(VGMSTREAM* vgmstream);
|
||||
void decode_reset(VGMSTREAM* vgmstream);
|
||||
|
||||
/* Decode samples into the buffer. Assume that we have written samples_written into the
|
||||
/* Decode samples into the buffer. Assume that we have written samples_filled into the
|
||||
* buffer already, and we have samples_to_do consecutive samples ahead of us. */
|
||||
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_do, sample_t* buffer);
|
||||
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_do, sample_t* buffer);
|
||||
|
||||
/* Detect loop start and save values, or detect loop end and restore (loop back). Returns 1 if loop was done. */
|
||||
int decode_do_loop(VGMSTREAM* vgmstream);
|
||||
/* Detect loop start and save values, or detect loop end and restore (loop back). Returns true if loop was done. */
|
||||
bool decode_do_loop(VGMSTREAM* vgmstream);
|
||||
|
||||
/* Calculate number of consecutive samples to do (taking into account stopping for loop start and end) */
|
||||
int decode_get_samples_to_do(int samples_this_block, int samples_per_frame, VGMSTREAM* vgmstream);
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "../util/sf_utils.h"
|
||||
|
||||
#define TEMPSIZE (256+32)
|
||||
|
||||
/*******************************************************************************/
|
||||
/* TEXT */
|
||||
/*******************************************************************************/
|
||||
@ -21,7 +23,6 @@ static void describe_get_time(int32_t samples, int sample_rate, double* p_time_m
|
||||
/* Write a description of the stream into array pointed by desc, which must be length bytes long.
|
||||
* Will always be null-terminated if length > 0 */
|
||||
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
||||
#define TEMPSIZE (256+32)
|
||||
char temp[TEMPSIZE];
|
||||
double time_mm, time_ss;
|
||||
|
||||
|
@ -115,7 +115,7 @@ void mixer_op_downmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
||||
sbuf_tmp[ch] = sbuf[ch]; /* copy untouched channels */
|
||||
}
|
||||
|
||||
for (int ch = op->ch_dst; ch < max_channels; ch++) {
|
||||
for (int ch = op->ch_dst; ch < max_channels - 1; ch++) {
|
||||
sbuf_tmp[ch] = sbuf[ch + 1]; /* 'pull' dropped channels back */
|
||||
}
|
||||
|
||||
|
@ -13,50 +13,50 @@ static void copy_time(bool* dst_flag, int32_t* dst_time, double* dst_time_s, boo
|
||||
*dst_time_s = *src_time_s;
|
||||
}
|
||||
|
||||
//todo reuse in txtp?
|
||||
// config that has been set internally via TXTP
|
||||
static void load_default_config(play_config_t* def, play_config_t* tcfg) {
|
||||
|
||||
/* loop limit: txtp #L > txtp #l > player #L > player #l */
|
||||
if (tcfg->play_forever) {
|
||||
def->play_forever = 1;
|
||||
def->ignore_loop = 0;
|
||||
def->play_forever = true;
|
||||
def->ignore_loop = false;
|
||||
}
|
||||
if (tcfg->loop_count_set) {
|
||||
def->loop_count = tcfg->loop_count;
|
||||
def->loop_count_set = 1;
|
||||
def->ignore_loop = 0;
|
||||
def->loop_count_set = true;
|
||||
def->ignore_loop = false;
|
||||
if (!tcfg->play_forever)
|
||||
def->play_forever = 0;
|
||||
def->play_forever = false;
|
||||
}
|
||||
|
||||
/* fade priority: #F > #f, #d */
|
||||
if (tcfg->ignore_fade) {
|
||||
def->ignore_fade = 1;
|
||||
def->ignore_fade = true;
|
||||
}
|
||||
if (tcfg->fade_delay_set) {
|
||||
def->fade_delay = tcfg->fade_delay;
|
||||
def->fade_delay_set = 1;
|
||||
def->fade_delay_set = true;
|
||||
}
|
||||
if (tcfg->fade_time_set) {
|
||||
def->fade_time = tcfg->fade_time;
|
||||
def->fade_time_set = 1;
|
||||
def->fade_time_set = true;
|
||||
}
|
||||
|
||||
/* loop priority: #i > #e > #E (respect player's ignore too) */
|
||||
if (tcfg->really_force_loop) {
|
||||
//def->ignore_loop = 0;
|
||||
def->force_loop = 0;
|
||||
def->really_force_loop = 1;
|
||||
//def->ignore_loop = false;
|
||||
def->force_loop = false;
|
||||
def->really_force_loop = true;
|
||||
}
|
||||
if (tcfg->force_loop) {
|
||||
//def->ignore_loop = 0;
|
||||
def->force_loop = 1;
|
||||
def->really_force_loop = 0;
|
||||
//def->ignore_loop = false;
|
||||
def->force_loop = true;
|
||||
def->really_force_loop = false;
|
||||
}
|
||||
if (tcfg->ignore_loop) {
|
||||
def->ignore_loop = 1;
|
||||
def->force_loop = 0;
|
||||
def->really_force_loop = 0;
|
||||
def->ignore_loop = true;
|
||||
def->force_loop = false;
|
||||
def->really_force_loop = false;
|
||||
}
|
||||
|
||||
copy_time(&def->pad_begin_set, &def->pad_begin, &def->pad_begin_s, &tcfg->pad_begin_set, &tcfg->pad_begin, &tcfg->pad_begin_s);
|
||||
@ -69,7 +69,8 @@ static void load_default_config(play_config_t* def, play_config_t* tcfg) {
|
||||
def->is_txtp = tcfg->is_txtp;
|
||||
}
|
||||
|
||||
static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
|
||||
/* config that has been set externally by plugins */
|
||||
static void load_external_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
|
||||
def->play_forever = vcfg->play_forever;
|
||||
def->ignore_loop = vcfg->ignore_loop;
|
||||
def->force_loop = vcfg->force_loop;
|
||||
@ -77,31 +78,32 @@ static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) {
|
||||
def->ignore_fade = vcfg->ignore_fade;
|
||||
|
||||
def->loop_count = vcfg->loop_count;
|
||||
def->loop_count_set = 1;
|
||||
def->loop_count_set = true;
|
||||
def->fade_delay = vcfg->fade_delay;
|
||||
def->fade_delay_set = 1;
|
||||
def->fade_delay_set = true;
|
||||
def->fade_time = vcfg->fade_time;
|
||||
def->fade_time_set = 1;
|
||||
def->fade_time_set = true;
|
||||
}
|
||||
|
||||
/* apply play config to vgmstream */
|
||||
void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* vcfg) {
|
||||
play_config_t defs = {0};
|
||||
play_config_t* def = &defs; /* for convenience... */
|
||||
play_config_t* tcfg = &vgmstream->config;
|
||||
|
||||
|
||||
load_player_config(def, vcfg);
|
||||
def->config_set = 1;
|
||||
load_external_config(def, vcfg);
|
||||
def->config_set = true;
|
||||
|
||||
if (!vcfg->disable_config_override)
|
||||
load_default_config(def, tcfg);
|
||||
|
||||
if (!vcfg->allow_play_forever)
|
||||
def->play_forever = 0;
|
||||
def->play_forever = false;
|
||||
|
||||
/* copy final config back */
|
||||
*tcfg = *def;
|
||||
|
||||
vgmstream->config_enabled = def->config_set;
|
||||
setup_state_vgmstream(vgmstream);
|
||||
setup_vgmstream_play_state(vgmstream);
|
||||
}
|
185
src/base/play_state.c
Normal file
185
src/base/play_state.c
Normal file
@ -0,0 +1,185 @@
|
||||
#include "../vgmstream.h"
|
||||
//#include "../layout/layout.h"
|
||||
//#include "render.h"
|
||||
//#include "decode.h"
|
||||
//#include "mixing.h"
|
||||
//#include "plugins.h"
|
||||
|
||||
|
||||
|
||||
int vgmstream_get_play_forever(VGMSTREAM* vgmstream) {
|
||||
return vgmstream->config.play_forever;
|
||||
}
|
||||
|
||||
void vgmstream_set_play_forever(VGMSTREAM* vgmstream, int enabled) {
|
||||
/* sometimes we need to enable/disable right before playback
|
||||
* (play config is left untouched, should mix ok as this flag is only used during
|
||||
* render, while config is always prepared as if play forever wasn't enabled) */
|
||||
vgmstream->config.play_forever = enabled;
|
||||
setup_vgmstream(vgmstream); /* update config */
|
||||
}
|
||||
|
||||
int32_t vgmstream_get_samples(VGMSTREAM* vgmstream) {
|
||||
if (!vgmstream->config_enabled || !vgmstream->config.config_set)
|
||||
return vgmstream->num_samples;
|
||||
return vgmstream->pstate.play_duration;
|
||||
}
|
||||
|
||||
/* calculate samples based on player's config */
|
||||
int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM* vgmstream) {
|
||||
if (vgmstream->loop_flag) {
|
||||
if (vgmstream->loop_target == (int)looptimes) { /* set externally, as this function is info-only */
|
||||
/* Continue playing the file normally after looping, instead of fading.
|
||||
* Most files cut abruply after the loop, but some do have proper endings.
|
||||
* With looptimes = 1 this option should give the same output vs loop disabled */
|
||||
int loop_count = (int)looptimes; /* no half loops allowed */
|
||||
return vgmstream->loop_start_sample
|
||||
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count
|
||||
+ (vgmstream->num_samples - vgmstream->loop_end_sample);
|
||||
}
|
||||
else {
|
||||
return vgmstream->loop_start_sample
|
||||
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * looptimes
|
||||
+ (fadedelayseconds + fadeseconds) * vgmstream->sample_rate;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return vgmstream->num_samples;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* apply config like forced loops */
|
||||
static void setup_state_modifiers(VGMSTREAM* vgmstream) {
|
||||
play_config_t* pc = &vgmstream->config;
|
||||
|
||||
/* apply final config */
|
||||
if (pc->really_force_loop) {
|
||||
vgmstream_force_loop(vgmstream, true, 0, vgmstream->num_samples);
|
||||
}
|
||||
if (pc->force_loop && !vgmstream->loop_flag) {
|
||||
vgmstream_force_loop(vgmstream, true, 0, vgmstream->num_samples);
|
||||
}
|
||||
if (pc->ignore_loop) {
|
||||
vgmstream_force_loop(vgmstream, false, 0, 0);
|
||||
}
|
||||
|
||||
if (!vgmstream->loop_flag) {
|
||||
pc->play_forever = false;
|
||||
}
|
||||
if (pc->play_forever) {
|
||||
pc->ignore_fade = false;
|
||||
}
|
||||
|
||||
|
||||
/* loop N times, but also play stream end instead of fading out */
|
||||
if (pc->ignore_fade) {
|
||||
vgmstream_set_loop_target(vgmstream, (int)pc->loop_count);
|
||||
pc->fade_time = 0;
|
||||
pc->fade_delay = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* apply config like trims */
|
||||
static void setup_state_processing(VGMSTREAM* vgmstream) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
play_config_t* pc = &vgmstream->config;
|
||||
double sample_rate = vgmstream->sample_rate;
|
||||
|
||||
/* time to samples */
|
||||
if (pc->pad_begin_s)
|
||||
pc->pad_begin = pc->pad_begin_s * sample_rate;
|
||||
if (pc->pad_end_s)
|
||||
pc->pad_end = pc->pad_end_s * sample_rate;
|
||||
if (pc->trim_begin_s)
|
||||
pc->trim_begin = pc->trim_begin_s * sample_rate;
|
||||
if (pc->trim_end_s)
|
||||
pc->trim_end = pc->trim_end_s * sample_rate;
|
||||
if (pc->body_time_s)
|
||||
pc->body_time = pc->body_time_s * sample_rate;
|
||||
//todo fade time also set to samples
|
||||
|
||||
/* samples before all decode */
|
||||
ps->pad_begin_duration = pc->pad_begin;
|
||||
|
||||
/* removed samples from first decode */
|
||||
ps->trim_begin_duration = pc->trim_begin;
|
||||
|
||||
/* main samples part */
|
||||
ps->body_duration = 0;
|
||||
if (pc->body_time) {
|
||||
ps->body_duration += pc->body_time; /* whether it loops or not */
|
||||
}
|
||||
else if (vgmstream->loop_flag) {
|
||||
double loop_count = 1.0;
|
||||
if (pc->loop_count_set) /* may set 0.0 on purpose I guess */
|
||||
loop_count = pc->loop_count;
|
||||
|
||||
ps->body_duration += vgmstream->loop_start_sample;
|
||||
if (pc->ignore_fade) {
|
||||
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * (int)loop_count;
|
||||
ps->body_duration += (vgmstream->num_samples - vgmstream->loop_end_sample);
|
||||
}
|
||||
else {
|
||||
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ps->body_duration += vgmstream->num_samples;
|
||||
}
|
||||
|
||||
/* samples from some modify body */
|
||||
if (pc->trim_begin)
|
||||
ps->body_duration -= pc->trim_begin;
|
||||
if (pc->trim_end)
|
||||
ps->body_duration -= pc->trim_end;
|
||||
if (pc->fade_delay && vgmstream->loop_flag)
|
||||
ps->body_duration += pc->fade_delay * vgmstream->sample_rate;
|
||||
|
||||
/* samples from fade part */
|
||||
if (pc->fade_time && vgmstream->loop_flag)
|
||||
ps->fade_duration = pc->fade_time * vgmstream->sample_rate;
|
||||
|
||||
/* samples from last part (anything beyond this is empty, unless play forever is set) */
|
||||
ps->pad_end_duration = pc->pad_end;
|
||||
|
||||
/* final count */
|
||||
ps->play_duration = ps->pad_begin_duration + ps->body_duration + ps->fade_duration + ps->pad_end_duration;
|
||||
ps->play_position = 0;
|
||||
|
||||
/* values too big can overflow, just ignore */
|
||||
if (ps->pad_begin_duration < 0)
|
||||
ps->pad_begin_duration = 0;
|
||||
if (ps->body_duration < 0)
|
||||
ps->body_duration = 0;
|
||||
if (ps->fade_duration < 0)
|
||||
ps->fade_duration = 0;
|
||||
if (ps->pad_end_duration < 0)
|
||||
ps->pad_end_duration = 0;
|
||||
if (ps->play_duration < 0)
|
||||
ps->play_duration = 0;
|
||||
|
||||
ps->pad_begin_left = ps->pad_begin_duration;
|
||||
ps->trim_begin_left = ps->trim_begin_duration;
|
||||
ps->fade_left = ps->fade_duration;
|
||||
ps->fade_start = ps->pad_begin_duration + ps->body_duration;
|
||||
//ps->pad_end_left = ps->pad_end_duration;
|
||||
ps->pad_end_start = ps->fade_start + ps->fade_duration;
|
||||
|
||||
/* other info (updated once mixing is enabled) */
|
||||
ps->input_channels = vgmstream->channels;
|
||||
ps->output_channels = vgmstream->channels;
|
||||
}
|
||||
|
||||
/* apply play config to internal state */
|
||||
void setup_vgmstream_play_state(VGMSTREAM* vgmstream) {
|
||||
if (!vgmstream->config.config_set)
|
||||
return;
|
||||
|
||||
setup_state_modifiers(vgmstream);
|
||||
setup_state_processing(vgmstream);
|
||||
|
||||
/* save new config for resets */
|
||||
setup_vgmstream(vgmstream);
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
#include "render.h"
|
||||
#include "decode.h"
|
||||
#include "mixing.h"
|
||||
#include "plugins.h"
|
||||
#include "sbuf.h"
|
||||
|
||||
|
||||
/* VGMSTREAM RENDERING
|
||||
@ -47,179 +47,6 @@
|
||||
* This mainly applies to TXTP, segments/layers in metas usually don't need to trigger config mode.
|
||||
*/
|
||||
|
||||
|
||||
int vgmstream_get_play_forever(VGMSTREAM* vgmstream) {
|
||||
return vgmstream->config.play_forever;
|
||||
}
|
||||
|
||||
void vgmstream_set_play_forever(VGMSTREAM* vgmstream, int enabled) {
|
||||
/* sometimes we need to enable/disable right before playback
|
||||
* (play config is left untouched, should mix ok as this flag is only used during
|
||||
* render, while config is always prepared as if play forever wasn't enabled) */
|
||||
vgmstream->config.play_forever = enabled;
|
||||
setup_vgmstream(vgmstream); /* update config */
|
||||
}
|
||||
|
||||
int32_t vgmstream_get_samples(VGMSTREAM* vgmstream) {
|
||||
if (!vgmstream->config_enabled || !vgmstream->config.config_set)
|
||||
return vgmstream->num_samples;
|
||||
return vgmstream->pstate.play_duration;
|
||||
}
|
||||
|
||||
/* calculate samples based on player's config */
|
||||
int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM* vgmstream) {
|
||||
if (vgmstream->loop_flag) {
|
||||
if (vgmstream->loop_target == (int)looptimes) { /* set externally, as this function is info-only */
|
||||
/* Continue playing the file normally after looping, instead of fading.
|
||||
* Most files cut abruply after the loop, but some do have proper endings.
|
||||
* With looptimes = 1 this option should give the same output vs loop disabled */
|
||||
int loop_count = (int)looptimes; /* no half loops allowed */
|
||||
return vgmstream->loop_start_sample
|
||||
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count
|
||||
+ (vgmstream->num_samples - vgmstream->loop_end_sample);
|
||||
}
|
||||
else {
|
||||
return vgmstream->loop_start_sample
|
||||
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * looptimes
|
||||
+ (fadedelayseconds + fadeseconds) * vgmstream->sample_rate;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return vgmstream->num_samples;
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static void setup_state_modifiers(VGMSTREAM* vgmstream) {
|
||||
play_config_t* pc = &vgmstream->config;
|
||||
|
||||
/* apply final config */
|
||||
if (pc->really_force_loop) {
|
||||
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
|
||||
}
|
||||
if (pc->force_loop && !vgmstream->loop_flag) {
|
||||
vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples);
|
||||
}
|
||||
if (pc->ignore_loop) {
|
||||
vgmstream_force_loop(vgmstream, 0, 0,0);
|
||||
}
|
||||
|
||||
if (!vgmstream->loop_flag) {
|
||||
pc->play_forever = 0;
|
||||
}
|
||||
if (pc->play_forever) {
|
||||
pc->ignore_fade = 0;
|
||||
}
|
||||
|
||||
|
||||
/* loop N times, but also play stream end instead of fading out */
|
||||
if (pc->ignore_fade) {
|
||||
vgmstream_set_loop_target(vgmstream, (int)pc->loop_count);
|
||||
pc->fade_time = 0;
|
||||
pc->fade_delay = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_state_processing(VGMSTREAM* vgmstream) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
play_config_t* pc = &vgmstream->config;
|
||||
double sample_rate = vgmstream->sample_rate;
|
||||
|
||||
/* time to samples */
|
||||
if (pc->pad_begin_s)
|
||||
pc->pad_begin = pc->pad_begin_s * sample_rate;
|
||||
if (pc->pad_end_s)
|
||||
pc->pad_end = pc->pad_end_s * sample_rate;
|
||||
if (pc->trim_begin_s)
|
||||
pc->trim_begin = pc->trim_begin_s * sample_rate;
|
||||
if (pc->trim_end_s)
|
||||
pc->trim_end = pc->trim_end_s * sample_rate;
|
||||
if (pc->body_time_s)
|
||||
pc->body_time = pc->body_time_s * sample_rate;
|
||||
//todo fade time also set to samples
|
||||
|
||||
/* samples before all decode */
|
||||
ps->pad_begin_duration = pc->pad_begin;
|
||||
|
||||
/* removed samples from first decode */
|
||||
ps->trim_begin_duration = pc->trim_begin;
|
||||
|
||||
/* main samples part */
|
||||
ps->body_duration = 0;
|
||||
if (pc->body_time) {
|
||||
ps->body_duration += pc->body_time; /* whether it loops or not */
|
||||
}
|
||||
else if (vgmstream->loop_flag) {
|
||||
double loop_count = 1.0;
|
||||
if (pc->loop_count_set) /* may set 0.0 on purpose I guess */
|
||||
loop_count = pc->loop_count;
|
||||
|
||||
ps->body_duration += vgmstream->loop_start_sample;
|
||||
if (pc->ignore_fade) {
|
||||
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * (int)loop_count;
|
||||
ps->body_duration += (vgmstream->num_samples - vgmstream->loop_end_sample);
|
||||
}
|
||||
else {
|
||||
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ps->body_duration += vgmstream->num_samples;
|
||||
}
|
||||
|
||||
/* samples from some modify body */
|
||||
if (pc->trim_begin)
|
||||
ps->body_duration -= pc->trim_begin;
|
||||
if (pc->trim_end)
|
||||
ps->body_duration -= pc->trim_end;
|
||||
if (pc->fade_delay && vgmstream->loop_flag)
|
||||
ps->body_duration += pc->fade_delay * vgmstream->sample_rate;
|
||||
|
||||
/* samples from fade part */
|
||||
if (pc->fade_time && vgmstream->loop_flag)
|
||||
ps->fade_duration = pc->fade_time * vgmstream->sample_rate;
|
||||
|
||||
/* samples from last part (anything beyond this is empty, unless play forever is set) */
|
||||
ps->pad_end_duration = pc->pad_end;
|
||||
|
||||
/* final count */
|
||||
ps->play_duration = ps->pad_begin_duration + ps->body_duration + ps->fade_duration + ps->pad_end_duration;
|
||||
ps->play_position = 0;
|
||||
|
||||
/* values too big can overflow, just ignore */
|
||||
if (ps->pad_begin_duration < 0)
|
||||
ps->pad_begin_duration = 0;
|
||||
if (ps->body_duration < 0)
|
||||
ps->body_duration = 0;
|
||||
if (ps->fade_duration < 0)
|
||||
ps->fade_duration = 0;
|
||||
if (ps->pad_end_duration < 0)
|
||||
ps->pad_end_duration = 0;
|
||||
if (ps->play_duration < 0)
|
||||
ps->play_duration = 0;
|
||||
|
||||
ps->pad_begin_left = ps->pad_begin_duration;
|
||||
ps->trim_begin_left = ps->trim_begin_duration;
|
||||
ps->fade_left = ps->fade_duration;
|
||||
ps->fade_start = ps->pad_begin_duration + ps->body_duration;
|
||||
//ps->pad_end_left = ps->pad_end_duration;
|
||||
ps->pad_end_start = ps->fade_start + ps->fade_duration;
|
||||
|
||||
/* other info (updated once mixing is enabled) */
|
||||
ps->input_channels = vgmstream->channels;
|
||||
ps->output_channels = vgmstream->channels;
|
||||
}
|
||||
|
||||
void setup_state_vgmstream(VGMSTREAM* vgmstream) {
|
||||
if (!vgmstream->config.config_set)
|
||||
return;
|
||||
|
||||
setup_state_modifiers(vgmstream);
|
||||
setup_state_processing(vgmstream);
|
||||
setup_vgmstream(vgmstream); /* save current config for reset */
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
void render_free(VGMSTREAM* vgmstream) {
|
||||
@ -247,11 +74,13 @@ void render_reset(VGMSTREAM* vgmstream) {
|
||||
}
|
||||
|
||||
int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
if (sample_count == 0)
|
||||
return 0;
|
||||
|
||||
/* current_sample goes between loop points (if looped) or up to max samples,
|
||||
* must detect beyond that decoders would encounter garbage data */
|
||||
|
||||
/* not ">=" to allow layouts to loop in some cases when == happens */
|
||||
// nothing to decode: return blank buf (not ">=" to allow layouts to loop in some cases when == happens)
|
||||
if (vgmstream->current_sample > vgmstream->num_samples) {
|
||||
int channels = vgmstream->channels;
|
||||
|
||||
@ -277,7 +106,7 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
case layout_blocked_str_snds:
|
||||
case layout_blocked_ws_aud:
|
||||
case layout_blocked_dec:
|
||||
case layout_blocked_vs:
|
||||
case layout_blocked_vs_mh:
|
||||
case layout_blocked_mul:
|
||||
case layout_blocked_gsb:
|
||||
case layout_blocked_xvas:
|
||||
@ -317,6 +146,7 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
break;
|
||||
}
|
||||
|
||||
// decode past stream samples: blank rest of buf
|
||||
if (vgmstream->current_sample > vgmstream->num_samples) {
|
||||
int channels = vgmstream->channels;
|
||||
int32_t excess, decoded;
|
||||
@ -333,85 +163,123 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
return sample_count;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static void render_trim(VGMSTREAM* vgmstream) {
|
||||
sample_t* tmpbuf = vgmstream->tmpbuf;
|
||||
size_t tmpbuf_size = vgmstream->tmpbuf_size;
|
||||
int32_t buf_samples = tmpbuf_size / vgmstream->channels; /* base channels, no need to apply mixing */
|
||||
typedef struct {
|
||||
//sbuf_t sbuf;
|
||||
int16_t* tmpbuf;
|
||||
int samples_to_do;
|
||||
int samples_done;
|
||||
} render_helper_t;
|
||||
|
||||
while (vgmstream->pstate.trim_begin_left) {
|
||||
int to_do = vgmstream->pstate.trim_begin_left;
|
||||
|
||||
// consumes samples from decoder
|
||||
static void play_op_trim(VGMSTREAM* vgmstream, render_helper_t* renderer) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
if (!ps->trim_begin_left)
|
||||
return;
|
||||
if (!renderer->samples_to_do)
|
||||
return;
|
||||
|
||||
// simpler using external buf?
|
||||
//sample_t* tmpbuf = vgmstream->tmpbuf;
|
||||
//size_t tmpbuf_size = vgmstream->tmpbuf_size;
|
||||
//int32_t buf_samples = tmpbuf_size / vgmstream->channels; /* base channels, no need to apply mixing */
|
||||
sample_t* tmpbuf = renderer->tmpbuf;
|
||||
int buf_samples = renderer->samples_to_do;
|
||||
|
||||
while (ps->trim_begin_left) {
|
||||
int to_do = ps->trim_begin_left;
|
||||
if (to_do > buf_samples)
|
||||
to_do = buf_samples;
|
||||
|
||||
render_layout(tmpbuf, to_do, vgmstream);
|
||||
/* no mixing */
|
||||
vgmstream->pstate.trim_begin_left -= to_do;
|
||||
ps->trim_begin_left -= to_do;
|
||||
}
|
||||
}
|
||||
|
||||
static int render_pad_begin(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_do) {
|
||||
int channels = vgmstream->pstate.output_channels;
|
||||
int to_do = vgmstream->pstate.pad_begin_left;
|
||||
if (to_do > samples_to_do)
|
||||
to_do = samples_to_do;
|
||||
// adds empty samples to buf
|
||||
static void play_op_pad_begin(VGMSTREAM* vgmstream, render_helper_t* renderer) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
if (!ps->pad_begin_left)
|
||||
return;
|
||||
//if (ps->play_position > ps->play_begin_duration) //implicit
|
||||
// return;
|
||||
|
||||
memset(buf, 0, to_do * sizeof(sample_t) * channels);
|
||||
vgmstream->pstate.pad_begin_left -= to_do;
|
||||
int channels = ps->output_channels;
|
||||
int buf_samples = renderer->samples_to_do;
|
||||
|
||||
return to_do;
|
||||
int to_do = ps->pad_begin_left;
|
||||
if (to_do > buf_samples)
|
||||
to_do = buf_samples;
|
||||
|
||||
memset(renderer->tmpbuf, 0, to_do * sizeof(sample_t) * channels);
|
||||
ps->pad_begin_left -= to_do;
|
||||
|
||||
renderer->samples_done += to_do;
|
||||
renderer->samples_to_do -= to_do;
|
||||
renderer->tmpbuf += to_do * channels; /* as if mixed */
|
||||
}
|
||||
|
||||
static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) {
|
||||
// fades (modifies volumes) part of buf
|
||||
static void play_op_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
play_config_t* pc = &vgmstream->config;
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
//play_config_t* pc = &vgmstream->config;
|
||||
|
||||
//if (!ps->fade_left || pc->play_forever)
|
||||
// return;
|
||||
//if (ps->play_position + samples_done < ps->fade_start)
|
||||
// return;
|
||||
if (pc->play_forever || !ps->fade_left)
|
||||
return;
|
||||
if (ps->play_position + samples_done < ps->fade_start)
|
||||
return;
|
||||
|
||||
{
|
||||
int s, ch, start, fade_pos;
|
||||
int channels = ps->output_channels;
|
||||
int32_t to_do = ps->fade_left;
|
||||
int start, fade_pos;
|
||||
int channels = ps->output_channels;
|
||||
int32_t to_do = ps->fade_left;
|
||||
|
||||
if (ps->play_position < ps->fade_start) {
|
||||
start = samples_left - (ps->play_position + samples_left - ps->fade_start);
|
||||
fade_pos = 0;
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
fade_pos = ps->play_position - ps->fade_start;
|
||||
}
|
||||
|
||||
if (to_do > samples_left - start)
|
||||
to_do = samples_left - start;
|
||||
|
||||
//TODO: use delta fadedness to improve performance?
|
||||
for (s = start; s < start + to_do; s++, fade_pos++) {
|
||||
double fadedness = (double)(ps->fade_duration - fade_pos) / ps->fade_duration;
|
||||
for (ch = 0; ch < channels; ch++) {
|
||||
buf[s*channels + ch] = (sample_t)buf[s*channels + ch] * fadedness;
|
||||
}
|
||||
}
|
||||
|
||||
ps->fade_left -= to_do;
|
||||
|
||||
/* next samples after fade end would be pad end/silence, so we can just memset */
|
||||
memset(buf + (start + to_do) * channels, 0, (samples_left - to_do - start) * sizeof(sample_t) * channels);
|
||||
return start + to_do;
|
||||
if (ps->play_position < ps->fade_start) {
|
||||
start = samples_done - (ps->play_position + samples_done - ps->fade_start);
|
||||
fade_pos = 0;
|
||||
}
|
||||
else {
|
||||
start = 0;
|
||||
fade_pos = ps->play_position - ps->fade_start;
|
||||
}
|
||||
|
||||
if (to_do > samples_done - start)
|
||||
to_do = samples_done - start;
|
||||
|
||||
//TODO: use delta fadedness to improve performance?
|
||||
for (int s = start; s < start + to_do; s++, fade_pos++) {
|
||||
double fadedness = (double)(ps->fade_duration - fade_pos) / ps->fade_duration;
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
buf[s * channels + ch] = (sample_t)(buf[s*channels + ch] * fadedness);
|
||||
}
|
||||
}
|
||||
|
||||
ps->fade_left -= to_do;
|
||||
|
||||
/* next samples after fade end would be pad end/silence, so we can just memset */
|
||||
memset(buf + (start + to_do) * channels, 0, (samples_done - to_do - start) * sizeof(sample_t) * channels);
|
||||
}
|
||||
|
||||
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) {
|
||||
// adds null samples after decode
|
||||
// pad-end works like fades, where part of buf is samples and part is padding (blank)
|
||||
// (beyond pad end normally is silence, except with segmented layout)
|
||||
static int play_op_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
play_config_t* pc = &vgmstream->config;
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
|
||||
if (pc->play_forever)
|
||||
return 0;
|
||||
if (samples_done == 0)
|
||||
return 0;
|
||||
if (ps->play_position + samples_done < ps->pad_end_start)
|
||||
return 0;
|
||||
|
||||
int channels = vgmstream->pstate.output_channels;
|
||||
int skip = 0;
|
||||
int32_t to_do;
|
||||
|
||||
/* pad end works like fades, where part of buf samples and part padding (silent),
|
||||
* calc exact totals (beyond pad end normally is silence, except with segmented layout) */
|
||||
if (ps->play_position < ps->pad_end_start) {
|
||||
skip = ps->pad_end_start - ps->play_position;
|
||||
to_do = ps->pad_end_duration;
|
||||
@ -421,92 +289,86 @@ static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_left)
|
||||
to_do = (ps->pad_end_start + ps->pad_end_duration) - ps->play_position;
|
||||
}
|
||||
|
||||
if (to_do > samples_left - skip)
|
||||
to_do = samples_left - skip;
|
||||
if (to_do > samples_done - skip)
|
||||
to_do = samples_done - skip;
|
||||
|
||||
memset(buf + (skip * channels), 0, to_do * sizeof(sample_t) * channels);
|
||||
return skip + to_do;
|
||||
}
|
||||
|
||||
|
||||
/* Decode data into sample buffer. Controls the "external" part of the decoding,
|
||||
* while layout/decode control the "internal" part. */
|
||||
int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
// clamp final play_position + done samples. Probably doesn't matter, but just in case.
|
||||
static void play_adjust_totals(VGMSTREAM* vgmstream, render_helper_t* renderer, int sample_count) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
int samples_to_do = sample_count;
|
||||
int samples_done = 0;
|
||||
int done;
|
||||
sample_t* tmpbuf = buf;
|
||||
|
||||
ps->play_position += renderer->samples_done;
|
||||
|
||||
/* simple mode with no settings (just skip everything below) */
|
||||
if (!vgmstream->config_enabled) {
|
||||
render_layout(buf, samples_to_do, vgmstream);
|
||||
mix_vgmstream(buf, samples_to_do, vgmstream);
|
||||
return samples_to_do;
|
||||
}
|
||||
|
||||
|
||||
/* trim may go first since it doesn't need output nor changes totals */
|
||||
if (ps->trim_begin_left) {
|
||||
render_trim(vgmstream);
|
||||
}
|
||||
|
||||
/* adds empty samples to buf */
|
||||
if (ps->pad_begin_left) {
|
||||
done = render_pad_begin(vgmstream, tmpbuf, samples_to_do);
|
||||
samples_done += done;
|
||||
samples_to_do -= done;
|
||||
tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */
|
||||
}
|
||||
|
||||
/* end padding (before to avoid decoding if possible, but must be inside pad region) */
|
||||
if (!vgmstream->config.play_forever
|
||||
&& ps->play_position /*+ samples_to_do*/ >= ps->pad_end_start
|
||||
&& samples_to_do) {
|
||||
done = render_pad_end(vgmstream, tmpbuf, samples_to_do);
|
||||
samples_done += done;
|
||||
samples_to_do -= done;
|
||||
tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */
|
||||
}
|
||||
|
||||
/* main decode */
|
||||
{ //if (samples_to_do) /* 0 ok, less likely */
|
||||
done = render_layout(tmpbuf, samples_to_do, vgmstream);
|
||||
|
||||
mix_vgmstream(tmpbuf, done, vgmstream);
|
||||
|
||||
samples_done += done;
|
||||
|
||||
if (!vgmstream->config.play_forever) {
|
||||
/* simple fadeout */
|
||||
if (ps->fade_left && ps->play_position + done >= ps->fade_start) {
|
||||
render_fade(vgmstream, tmpbuf, done);
|
||||
}
|
||||
|
||||
/* silence leftover buf samples (rarely used when no fade is set) */
|
||||
if (ps->play_position + done >= ps->pad_end_start) {
|
||||
render_pad_end(vgmstream, tmpbuf, done);
|
||||
}
|
||||
}
|
||||
|
||||
tmpbuf += done * vgmstream->pstate.output_channels;
|
||||
}
|
||||
|
||||
|
||||
vgmstream->pstate.play_position += samples_done;
|
||||
|
||||
/* signal end */
|
||||
if (!vgmstream->config.play_forever
|
||||
&& ps->play_position > ps->play_duration) {
|
||||
/* usually only happens when mixing layers of different lengths (where decoder keeps calling render) */
|
||||
if (!vgmstream->config.play_forever && ps->play_position > ps->play_duration) {
|
||||
int excess = ps->play_position - ps->play_duration;
|
||||
if (excess > sample_count)
|
||||
excess = sample_count;
|
||||
|
||||
samples_done = (sample_count - excess);
|
||||
renderer->samples_done = (sample_count - excess);
|
||||
|
||||
ps->play_position = ps->play_duration;
|
||||
}
|
||||
|
||||
return samples_done;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Decode data into sample buffer. Controls the "external" part of the decoding,
|
||||
* while layout/decode control the "internal" part.
|
||||
*
|
||||
* A stream would be "externally" rendered like this:
|
||||
* [ pad-begin ]( trim )[ decoded data * N loops ][ pad-end ]
|
||||
* \ end-fade |
|
||||
*
|
||||
* Which part we are in depends on play_position. Because vgmstream render's
|
||||
* buf may fall anywhere in the middle of all that. Since some ops add "fake" (non-decoded)
|
||||
* samples to buf, we need to
|
||||
*/
|
||||
|
||||
int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
render_helper_t renderer = {0};
|
||||
renderer.tmpbuf = buf;
|
||||
renderer.samples_done = 0;
|
||||
renderer.samples_to_do = sample_count;
|
||||
|
||||
//sbuf_init16(&renderer.sbuf, buf, sample_count, vgmstream->channels);
|
||||
|
||||
|
||||
/* simple mode with no settings (just skip everything below) */
|
||||
if (!vgmstream->config_enabled) {
|
||||
render_layout(buf, renderer.samples_to_do, vgmstream);
|
||||
mix_vgmstream(buf, renderer.samples_to_do, vgmstream);
|
||||
return renderer.samples_to_do;
|
||||
}
|
||||
|
||||
|
||||
/* adds empty samples to buf and moves it */
|
||||
play_op_pad_begin(vgmstream, &renderer);
|
||||
|
||||
/* trim decoder output (may go anywhere before main render since it doesn't use render output) */
|
||||
play_op_trim(vgmstream, &renderer);
|
||||
|
||||
|
||||
/* main decode */
|
||||
int done = render_layout(renderer.tmpbuf, renderer.samples_to_do, vgmstream);
|
||||
mix_vgmstream(renderer.tmpbuf, done, vgmstream);
|
||||
|
||||
|
||||
/* simple fadeout over decoded data (after mixing since usually results in less samples) */
|
||||
play_op_fade(vgmstream, renderer.tmpbuf, done);
|
||||
|
||||
/* silence leftover buf samples (after fade as rarely may mix decoded buf + trim samples when no fade is set)
|
||||
* (could be done before render to "consume" buf but doesn't matter much) */
|
||||
play_op_pad_end(vgmstream, renderer.tmpbuf, done);
|
||||
|
||||
renderer.samples_done += done;
|
||||
//renderer.samples_to_do -= done; //not useful at this point
|
||||
//renderer.tmpbuf += done * vgmstream->pstate.output_channels;
|
||||
|
||||
|
||||
play_adjust_totals(vgmstream, &renderer, sample_count);
|
||||
return renderer.samples_done;
|
||||
}
|
||||
|
85
src/base/sbuf.c
Normal file
85
src/base/sbuf.c
Normal file
@ -0,0 +1,85 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../util.h"
|
||||
#include "sbuf.h"
|
||||
|
||||
#if 0
|
||||
/* skips N samples from current sbuf */
|
||||
void sbuf_init16(sbuf_t* sbuf, int16_t* buf, int samples, int channels) {
|
||||
memset(sbuf, 0, sizeof(sbuf_t));
|
||||
sbuf->buf = buf;
|
||||
sbuf->samples = samples;
|
||||
sbuf->channels = channels;
|
||||
sbuf->fmt = SFMT_S16;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that)
|
||||
void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels) {
|
||||
for (int s = 0; s < samples * channels; s++) {
|
||||
buf_f32[s] = (float)buf_s16[s]; // / 32767.0f
|
||||
}
|
||||
}
|
||||
|
||||
void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) {
|
||||
/* when casting float to int, value is simply truncated:
|
||||
* - (int)1.7 = 1, (int)-1.7 = -1
|
||||
* alts for more accurate rounding could be:
|
||||
* - (int)floor(f)
|
||||
* - (int)(f < 0 ? f - 0.5f : f + 0.5f)
|
||||
* - (((int) (f1 + 32768.5)) - 32768)
|
||||
* - etc
|
||||
* but since +-1 isn't really audible we'll just cast, as it's the fastest
|
||||
*/
|
||||
for (int s = 0; s < samples * channels; s++) {
|
||||
buf_s16[s] = clamp16( buf_f32[s]); // * 32767.0f
|
||||
}
|
||||
}
|
||||
|
||||
void sbuf_copy_samples(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled) {
|
||||
int pos = samples_filled * dst_channels;
|
||||
|
||||
if (src_channels == dst_channels) { /* most common and probably faster */
|
||||
for (int s = 0; s < samples_to_do * dst_channels; s++) {
|
||||
dst[pos + s] = src[s];
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int s = 0; s < samples_to_do; s++) {
|
||||
for (int ch = 0; ch < src_channels; ch++) {
|
||||
dst[pos + s * dst_channels + ch] = src[s * src_channels + ch];
|
||||
}
|
||||
for (int ch = src_channels; ch < dst_channels; ch++) {
|
||||
dst[pos + s * dst_channels + ch] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* copy interleaving */
|
||||
void sbuf_copy_layers(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled, int dst_ch_start) {
|
||||
// dst_channels == src_channels isn't likely
|
||||
for (int src_ch = 0; src_ch < src_channels; src_ch++) {
|
||||
for (int s = 0; s < samples_to_do; s++) {
|
||||
int src_pos = s * src_channels + src_ch;
|
||||
int dst_pos = (samples_filled + s) * dst_channels + dst_ch_start;
|
||||
|
||||
dst[dst_pos] = src[src_pos];
|
||||
}
|
||||
|
||||
dst_ch_start++;
|
||||
}
|
||||
}
|
||||
|
||||
void sbuf_silence(sample_t* dst, int samples, int channels, int filled) {
|
||||
memset(dst + filled * channels, 0, (samples - filled) * channels * sizeof(sample_t));
|
||||
}
|
||||
|
||||
bool sbuf_realloc(sample_t** dst, int samples, int channels) {
|
||||
sample_t* outbuf_re = realloc(*dst, samples * channels * sizeof(sample_t));
|
||||
if (!outbuf_re) return false;
|
||||
|
||||
*dst = outbuf_re;
|
||||
return true;
|
||||
}
|
@ -1,28 +1,52 @@
|
||||
#ifndef _SBUF_H
|
||||
#define _SBUF_H
|
||||
#ifndef _SBUF_H_
|
||||
#define _SBUF_H_
|
||||
|
||||
#include "../streamtypes.h"
|
||||
|
||||
// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that)
|
||||
static inline void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels) {
|
||||
for (int s = 0; s < samples * channels; s++) {
|
||||
buf_f32[s] = (float)buf_s16[s]; // / 32767.0f
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
/* interleaved: buffer for all channels = [ch*s] = (ch1 ch2 ch1 ch2 ch1 ch2 ch1 ch2 ...) */
|
||||
/* planar: buffer per channel = [ch][s] = (c1 c1 c1 c1 ...) (c2 c2 c2 c2 ...) */
|
||||
typedef enum {
|
||||
SFMT_NONE,
|
||||
SFMT_S16,
|
||||
SFMT_F32,
|
||||
//SFMT_S24,
|
||||
//SFMT_S32,
|
||||
//SFMT_S16P,
|
||||
//SFMT_F32P,
|
||||
} sfmt_t;
|
||||
|
||||
static inline void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) {
|
||||
/* when casting float to int, value is simply truncated:
|
||||
* - (int)1.7 = 1, (int)-1.7 = -1
|
||||
* alts for more accurate rounding could be:
|
||||
* - (int)floor(f)
|
||||
* - (int)(f < 0 ? f - 0.5f : f + 0.5f)
|
||||
* - (((int) (f1 + 32768.5)) - 32768)
|
||||
* - etc
|
||||
* but since +-1 isn't really audible we'll just cast as it's the fastest
|
||||
*/
|
||||
for (int s = 0; s < samples * channels; s++) {
|
||||
buf_s16[s] = clamp16( buf_f32[s]); // * 32767.0f
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
void* buf; /* current sample buffer */
|
||||
int samples; /* max samples */
|
||||
int channels; /* interleaved step or planar buffers */
|
||||
sfmt_t fmt; /* buffer type */
|
||||
//int filled; /* samples in buffer */
|
||||
//int planar;
|
||||
} sbuf_t;
|
||||
|
||||
void sbuf_init16(sbuf_t* sbuf, int16_t* buf, int samples, int channels);
|
||||
|
||||
void sbuf_clamp(sbuf_t* sbuf, int samples);
|
||||
|
||||
/* skips N samples from current sbuf */
|
||||
void sbuf_consume(sbuf_t* sbuf, int samples);
|
||||
#endif
|
||||
|
||||
/* it's probably slightly faster to make those inline'd, but aren't called that often to matter (given big enough total samples) */
|
||||
|
||||
// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that)
|
||||
void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels);
|
||||
|
||||
void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels);
|
||||
|
||||
void sbuf_copy_samples(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled);
|
||||
|
||||
void sbuf_copy_layers(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled, int dst_ch_start);
|
||||
|
||||
void sbuf_silence(sample_t* dst, int samples, int channels, int filled);
|
||||
|
||||
bool sbuf_realloc(sample_t** dst, int samples, int channels);
|
||||
|
||||
#endif
|
||||
|
@ -21,19 +21,19 @@ struct VGMSTREAM_TAGS {
|
||||
char targetpath[VGMSTREAM_TAGS_LINE_MAX];
|
||||
|
||||
/* tag section for filename (see comments below) */
|
||||
int section_found;
|
||||
bool section_found;
|
||||
off_t section_start;
|
||||
off_t section_end;
|
||||
off_t offset;
|
||||
|
||||
/* commands */
|
||||
int autotrack_on;
|
||||
int autotrack_written;
|
||||
bool autotrack_on;
|
||||
bool autotrack_written;
|
||||
int track_count;
|
||||
int exact_match;
|
||||
bool exact_match;
|
||||
|
||||
int autoalbum_on;
|
||||
int autoalbum_written;
|
||||
bool autoalbum_on;
|
||||
bool autoalbum_written;
|
||||
};
|
||||
|
||||
|
||||
@ -50,7 +50,7 @@ static void tags_clean(VGMSTREAM_TAGS* tag) {
|
||||
}
|
||||
|
||||
VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) {
|
||||
VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS));
|
||||
VGMSTREAM_TAGS* tags = calloc(1, sizeof(VGMSTREAM_TAGS));
|
||||
if (!tags) goto fail;
|
||||
|
||||
*tag_key = tags->key;
|
||||
@ -102,7 +102,7 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
if (tags->autotrack_on && !tags->autotrack_written) {
|
||||
sprintf(tags->key, "%s", "TRACK");
|
||||
sprintf(tags->val, "%i", tags->track_count);
|
||||
tags->autotrack_written = 1;
|
||||
tags->autotrack_written = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
|
||||
sprintf(tags->key, "%s", "ALBUM");
|
||||
sprintf(tags->val, "%s", path+1);
|
||||
tags->autoalbum_written = 1;
|
||||
tags->autoalbum_written = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -150,13 +150,13 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
if (ok == 1 || ok == 2) {
|
||||
int key_len = n2 - n1;
|
||||
if (strncasecmp(tags->key, "AUTOTRACK", key_len) == 0) {
|
||||
tags->autotrack_on = 1;
|
||||
tags->autotrack_on = true;
|
||||
}
|
||||
else if (strncasecmp(tags->key, "AUTOALBUM", key_len) == 0) {
|
||||
tags->autoalbum_on = 1;
|
||||
tags->autoalbum_on = true;
|
||||
}
|
||||
else if (strncasecmp(tags->key, "EXACTMATCH", key_len) == 0) {
|
||||
tags->exact_match = 1;
|
||||
tags->exact_match = true;
|
||||
}
|
||||
|
||||
continue; /* not an actual tag */
|
||||
@ -210,7 +210,7 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) {
|
||||
if (filename_found) {
|
||||
/* section ok, start would be set before this (or be 0) */
|
||||
tags->section_end = tags->offset;
|
||||
tags->section_found = 1;
|
||||
tags->section_found = true;
|
||||
tags->offset = tags->section_start;
|
||||
}
|
||||
else {
|
||||
|
@ -489,7 +489,7 @@ static ffmpeg_codec_data* init_ffmpeg_mp4_custom(STREAMFILE* sf, mp4_custom_t* m
|
||||
if (buf_len > 0x100000) /* ??? */
|
||||
goto fail;
|
||||
|
||||
buf = malloc(buf_len);
|
||||
buf = calloc(1, buf_len);
|
||||
if (!buf) goto fail;
|
||||
bytes = make_m4a_header(buf, buf_len, mp4, sf, type); /* before changing stream_offset/size */
|
||||
|
||||
|
@ -320,7 +320,7 @@ circus_handle_t* circus_init(off_t start, uint8_t codec, uint8_t flags) {
|
||||
circus_handle_t* handle = NULL;
|
||||
int scale_index, err;
|
||||
|
||||
handle = malloc(sizeof(circus_handle_t));
|
||||
handle = calloc(1, sizeof(circus_handle_t));
|
||||
if (!handle) goto fail;
|
||||
|
||||
handle->start = start;
|
||||
|
@ -146,7 +146,7 @@ static void THuff_SetPositionData(THuff* self, THuffPositionData* s);
|
||||
//------------------------------------------------------------------------------
|
||||
//create
|
||||
static THuff* THuff_Create(TStream* buf) {
|
||||
THuff* self = malloc(sizeof(THuff));
|
||||
THuff* self = calloc(1, sizeof(THuff));
|
||||
if (!self) return NULL;
|
||||
|
||||
//define stream
|
||||
@ -558,7 +558,7 @@ struct TCompressWaveData {
|
||||
//-----------------------------------------------------------
|
||||
//create
|
||||
TCompressWaveData* TCompressWaveData_Create(void) {
|
||||
TCompressWaveData* self = malloc(sizeof(TCompressWaveData));
|
||||
TCompressWaveData* self = calloc(1, sizeof(TCompressWaveData));
|
||||
if (!self) return NULL;
|
||||
#if 0
|
||||
self->Data = NULL;
|
||||
|
@ -80,7 +80,7 @@ static int is_use_runlength(NWAData* nwa) {
|
||||
NWAData* nwalib_open(STREAMFILE* sf) {
|
||||
uint8_t header[0x2c] = {0};
|
||||
int i;
|
||||
NWAData* const nwa = malloc(sizeof(NWAData));
|
||||
NWAData* const nwa = calloc(1, sizeof(NWAData));
|
||||
if (!nwa) goto fail;
|
||||
|
||||
//NWAData::ReadHeader
|
||||
|
@ -1140,7 +1140,7 @@ tac_handle_t* tac_init(const uint8_t* buf, int buf_size) {
|
||||
if (buf_size < TAC_BLOCK_SIZE)
|
||||
goto fail;
|
||||
|
||||
handle = malloc(sizeof(tac_handle_t));
|
||||
handle = calloc(1, sizeof(tac_handle_t));
|
||||
if (!handle) goto fail;
|
||||
|
||||
{
|
||||
|
@ -392,6 +392,7 @@ static const char* extension_list[] = {
|
||||
"npsf", //fake extension/header id for .nps (in bigfiles)
|
||||
"nsa",
|
||||
"nsopus",
|
||||
"ntx",
|
||||
"nub",
|
||||
"nub2",
|
||||
"nus3audio",
|
||||
@ -956,7 +957,7 @@ static const layout_info layout_info_list[] = {
|
||||
{layout_blocked_str_snds, "blocked (.str SNDS)"},
|
||||
{layout_blocked_ws_aud, "blocked (Westwood Studios .aud)"},
|
||||
{layout_blocked_dec, "blocked (DEC)"},
|
||||
{layout_blocked_vs, "blocked (Melbourne House VS)"},
|
||||
{layout_blocked_vs_mh, "blocked (Melbourne House VS)"},
|
||||
{layout_blocked_mul, "blocked (MUL)"},
|
||||
{layout_blocked_gsb, "blocked (GSB)"},
|
||||
{layout_blocked_thp, "blocked (THP)"},
|
||||
@ -1074,7 +1075,6 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_FSB5, "FMOD FSB5 header"},
|
||||
{meta_RWAX, "Konami RWAX header"},
|
||||
{meta_XWB, "Microsoft XWB header"},
|
||||
{meta_PS2_XA30, "Reflections XA30 PS2 header"},
|
||||
{meta_MUSC, "Krome MUSC header"},
|
||||
{meta_MUSX, "Eurocom MUSX header"},
|
||||
{meta_FILP, "cavia FILp header"},
|
||||
@ -1103,7 +1103,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_SDT, "High Voltage .sdt header"},
|
||||
{meta_WVS, "Swingin' Ape .WVS header"},
|
||||
{meta_DEC, "Falcom .DEC RIFF header"},
|
||||
{meta_VS, "Melbourne House .VS header"},
|
||||
{meta_VS_MH, "Melbourne House .VS header"},
|
||||
{meta_STR_SEGA, "Sega Stream Asset Builder header"},
|
||||
{meta_STR_SEGA_custom, "Sega Stream Asset Builder header (custom)"},
|
||||
{meta_XMU, "Outrage XMU header"},
|
||||
|
@ -1,38 +1,39 @@
|
||||
#include "layout.h"
|
||||
#include "../vgmstream.h"
|
||||
#include "../base/decode.h"
|
||||
#include "../base/sbuf.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
|
||||
/* Decodes samples for blocked streams.
|
||||
* Data is divided into headered blocks with a bunch of data. The layout calls external helper functions
|
||||
* when a block is decoded, and those must parse the new block and move offsets accordingly. */
|
||||
void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
int samples_written = 0;
|
||||
int frame_size, samples_per_frame, samples_this_block;
|
||||
void render_vgmstream_blocked(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
|
||||
frame_size = decode_get_frame_size(vgmstream);
|
||||
samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
||||
samples_this_block = 0;
|
||||
int frame_size = decode_get_frame_size(vgmstream);
|
||||
int samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
||||
int samples_this_block = 0;
|
||||
|
||||
if (vgmstream->current_block_samples) {
|
||||
samples_this_block = vgmstream->current_block_samples;
|
||||
} else if (frame_size == 0) { /* assume 4 bit */ //TODO: decode_get_frame_size() really should return bits... */
|
||||
}
|
||||
else if (frame_size == 0) {
|
||||
//TO-DO: this case doesn't seem possible, codecs that return frame_size 0 (should) set current_block_samples
|
||||
samples_this_block = vgmstream->current_block_size * 2 * samples_per_frame;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
samples_this_block = vgmstream->current_block_size / frame_size * samples_per_frame;
|
||||
}
|
||||
|
||||
|
||||
while (samples_written < sample_count) {
|
||||
int samples_filled = 0;
|
||||
while (samples_filled < sample_count) {
|
||||
int samples_to_do;
|
||||
|
||||
|
||||
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
||||
/* handle looping, readjust back to loop start values */
|
||||
if (vgmstream->current_block_samples) {
|
||||
samples_this_block = vgmstream->current_block_samples;
|
||||
} else if (frame_size == 0) { /* assume 4 bit */ //TODO: decode_get_frame_size() really should return bits... */
|
||||
} else if (frame_size == 0) { /* assume 4 bit */
|
||||
samples_this_block = vgmstream->current_block_size * 2 * samples_per_frame;
|
||||
} else {
|
||||
samples_this_block = vgmstream->current_block_size / frame_size * samples_per_frame;
|
||||
@ -42,28 +43,26 @@ void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM*
|
||||
|
||||
if (samples_this_block < 0) {
|
||||
/* probably block bug or EOF, next calcs would give wrong values/segfaults/infinite loop */
|
||||
VGM_LOG("layout_blocked: wrong block samples at 0x%x\n", (uint32_t)vgmstream->current_block_offset);
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
break;
|
||||
VGM_LOG("BLOCKED: wrong block samples\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
if (vgmstream->current_block_offset < 0 || vgmstream->current_block_offset == 0xFFFFFFFF) {
|
||||
/* probably block bug or EOF, block functions won't be able to read anything useful/infinite loop */
|
||||
VGM_LOG("layout_blocked: wrong block offset found\n");
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
break;
|
||||
VGM_LOG("BLOCKED: wrong block offset found\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
if (samples_to_do > sample_count - samples_filled)
|
||||
samples_to_do = sample_count - samples_filled;
|
||||
|
||||
if (samples_to_do > 0) {
|
||||
/* samples_this_block = 0 is allowed (empty block, do nothing then move to next block) */
|
||||
decode_vgmstream(vgmstream, samples_written, samples_to_do, buffer);
|
||||
decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf);
|
||||
}
|
||||
|
||||
samples_written += samples_to_do;
|
||||
samples_filled += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
|
||||
@ -71,23 +70,29 @@ void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM*
|
||||
/* move to next block when all samples are consumed */
|
||||
if (vgmstream->samples_into_block == samples_this_block
|
||||
/*&& vgmstream->current_sample < vgmstream->num_samples*/) { /* don't go past last block */ //todo
|
||||
block_update(vgmstream->next_block_offset,vgmstream);
|
||||
block_update(vgmstream->next_block_offset, vgmstream);
|
||||
|
||||
/* update since these may change each block */
|
||||
frame_size = decode_get_frame_size(vgmstream);
|
||||
samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
||||
if (vgmstream->current_block_samples) {
|
||||
samples_this_block = vgmstream->current_block_samples;
|
||||
} else if (frame_size == 0) { /* assume 4 bit */ //TODO: decode_get_frame_size() really should return bits... */
|
||||
}
|
||||
else if (frame_size == 0) {
|
||||
//TO-DO: this case doesn't seem possible, codecs that return frame_size 0 (should) set current_block_samples
|
||||
samples_this_block = vgmstream->current_block_size * 2 * samples_per_frame;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
samples_this_block = vgmstream->current_block_size / frame_size * samples_per_frame;
|
||||
}
|
||||
|
||||
vgmstream->samples_into_block = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
}
|
||||
|
||||
/* helper functions to parse new block */
|
||||
@ -132,8 +137,8 @@ void block_update(off_t block_offset, VGMSTREAM* vgmstream) {
|
||||
case layout_blocked_gsb:
|
||||
block_update_gsb(block_offset,vgmstream);
|
||||
break;
|
||||
case layout_blocked_vs:
|
||||
block_update_vs(block_offset,vgmstream);
|
||||
case layout_blocked_vs_mh:
|
||||
block_update_vs_mh(block_offset,vgmstream);
|
||||
break;
|
||||
case layout_blocked_xvas:
|
||||
block_update_xvas(block_offset,vgmstream);
|
||||
@ -214,43 +219,3 @@ void block_update(off_t block_offset, VGMSTREAM* vgmstream) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void blocked_count_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, off_t offset) {
|
||||
if (vgmstream == NULL)
|
||||
return;
|
||||
|
||||
int block_samples;
|
||||
off_t max_offset = get_streamfile_size(sf);
|
||||
|
||||
vgmstream->next_block_offset = offset;
|
||||
do {
|
||||
block_update(vgmstream->next_block_offset, vgmstream);
|
||||
|
||||
if (vgmstream->current_block_samples < 0 || vgmstream->current_block_size == 0xFFFFFFFF)
|
||||
break;
|
||||
|
||||
if (vgmstream->current_block_samples) {
|
||||
block_samples = vgmstream->current_block_samples;
|
||||
}
|
||||
else {
|
||||
switch(vgmstream->coding_type) {
|
||||
case coding_PCM16LE:
|
||||
case coding_PCM16_int: block_samples = pcm16_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_PCM8_int:
|
||||
case coding_PCM8_U_int: block_samples = pcm8_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_XBOX_IMA_mono:
|
||||
case coding_XBOX_IMA: block_samples = xbox_ima_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_NGC_DSP: block_samples = dsp_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_PSX: block_samples = ps_bytes_to_samples(vgmstream->current_block_size,1); break;
|
||||
default:
|
||||
VGM_LOG("BLOCKED: missing codec\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->num_samples += block_samples;
|
||||
}
|
||||
while (vgmstream->next_block_offset < max_offset);
|
||||
|
||||
block_update(offset, vgmstream); /* reset */
|
||||
}
|
||||
|
@ -2,13 +2,12 @@
|
||||
#include "../vgmstream.h"
|
||||
|
||||
/* mini-blocks of size + data */
|
||||
void block_update_vs(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
|
||||
int i;
|
||||
void block_update_vs_mh(off_t block_offset, VGMSTREAM* vgmstream) {
|
||||
STREAMFILE* sf = vgmstream->ch[0].streamfile;
|
||||
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
for (int i = 0; i < vgmstream->channels; i++) {
|
||||
vgmstream->current_block_offset = block_offset;
|
||||
vgmstream->current_block_size = read_32bitLE(vgmstream->current_block_offset,streamFile);
|
||||
vgmstream->current_block_size = read_32bitLE(vgmstream->current_block_offset,sf);
|
||||
vgmstream->next_block_offset = vgmstream->current_block_offset + vgmstream->current_block_size + 0x04;
|
||||
vgmstream->ch[i].offset = vgmstream->current_block_offset + 0x04;
|
||||
if (i == 0) block_offset=vgmstream->next_block_offset;
|
||||
|
@ -1,43 +1,42 @@
|
||||
#include "layout.h"
|
||||
#include "../vgmstream.h"
|
||||
#include "../base/decode.h"
|
||||
#include "../base/sbuf.h"
|
||||
|
||||
|
||||
/* Decodes samples for flat streams.
|
||||
* Data forms a single stream, and the decoder may internally skip chunks and move offsets as needed. */
|
||||
void render_vgmstream_flat(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
int samples_written = 0;
|
||||
int samples_per_frame, samples_this_block;
|
||||
|
||||
samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
||||
samples_this_block = vgmstream->num_samples; /* do all samples if possible */
|
||||
int samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
||||
int samples_this_block = vgmstream->num_samples; /* do all samples if possible */
|
||||
|
||||
|
||||
while (samples_written < sample_count) {
|
||||
int samples_to_do;
|
||||
/* write samples */
|
||||
int samples_filled = 0;
|
||||
while (samples_filled < sample_count) {
|
||||
|
||||
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
||||
/* handle looping */
|
||||
continue;
|
||||
}
|
||||
|
||||
samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_filled)
|
||||
samples_to_do = sample_count - samples_filled;
|
||||
|
||||
if (samples_to_do == 0) { /* when decoding more than num_samples */
|
||||
VGM_LOG_ONCE("FLAT: samples_to_do 0\n");
|
||||
if (samples_to_do <= 0) { /* when decoding more than num_samples */
|
||||
VGM_LOG_ONCE("FLAT: wrong samples_to_do\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
decode_vgmstream(vgmstream, samples_written, samples_to_do, outbuf);
|
||||
decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf);
|
||||
|
||||
samples_written += samples_to_do;
|
||||
samples_filled += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
}
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
memset(outbuf + samples_written * vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
}
|
||||
|
@ -1,157 +1,197 @@
|
||||
#include "layout.h"
|
||||
#include "../vgmstream.h"
|
||||
#include "../base/decode.h"
|
||||
#include "../base/sbuf.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* default */
|
||||
int samples_per_frame_d;
|
||||
int samples_this_block_d;
|
||||
/* first */
|
||||
int samples_per_frame_f;
|
||||
int samples_this_block_f;
|
||||
/* last */
|
||||
int samples_per_frame_l;
|
||||
int samples_this_block_l;
|
||||
|
||||
bool has_interleave_first;
|
||||
bool has_interleave_last;
|
||||
bool has_interleave_internal_updates;
|
||||
} layout_config_t;
|
||||
|
||||
static bool setup_helper(layout_config_t* layout, VGMSTREAM* vgmstream) {
|
||||
//TO-DO: this could be pre-calc'd after main init
|
||||
layout->has_interleave_first = vgmstream->interleave_first_block_size && vgmstream->channels > 1;
|
||||
layout->has_interleave_last = vgmstream->interleave_last_block_size && vgmstream->channels > 1;
|
||||
layout->has_interleave_internal_updates = vgmstream->codec_internal_updates;
|
||||
|
||||
{
|
||||
int frame_size_d = decode_get_frame_size(vgmstream);
|
||||
layout->samples_per_frame_d = decode_get_samples_per_frame(vgmstream);
|
||||
if (frame_size_d == 0 || layout->samples_per_frame_d == 0)
|
||||
goto fail;
|
||||
layout->samples_this_block_d = vgmstream->interleave_block_size / frame_size_d * layout->samples_per_frame_d;
|
||||
}
|
||||
|
||||
if (layout->has_interleave_first) {
|
||||
int frame_size_f = decode_get_frame_size(vgmstream);
|
||||
layout->samples_per_frame_f = decode_get_samples_per_frame(vgmstream); //todo samples per shortframe
|
||||
if (frame_size_f == 0 || layout->samples_per_frame_f == 0)
|
||||
goto fail;
|
||||
layout->samples_this_block_f = vgmstream->interleave_first_block_size / frame_size_f * layout->samples_per_frame_f;
|
||||
}
|
||||
|
||||
if (layout->has_interleave_last) {
|
||||
int frame_size_l = decode_get_shortframe_size(vgmstream);
|
||||
layout->samples_per_frame_l = decode_get_samples_per_shortframe(vgmstream);
|
||||
if (frame_size_l == 0 || layout->samples_per_frame_l == 0) goto fail;
|
||||
layout->samples_this_block_l = vgmstream->interleave_last_block_size / frame_size_l * layout->samples_per_frame_l;
|
||||
}
|
||||
|
||||
return true;
|
||||
fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void update_default_values(layout_config_t* layout, VGMSTREAM* vgmstream, int* p_samples_per_frame, int* p_samples_this_block) {
|
||||
if (layout->has_interleave_first &&
|
||||
vgmstream->current_sample < layout->samples_this_block_f) {
|
||||
*p_samples_per_frame = layout->samples_per_frame_f;
|
||||
*p_samples_this_block = layout->samples_this_block_f;
|
||||
}
|
||||
else if (layout->has_interleave_last &&
|
||||
vgmstream->current_sample - vgmstream->samples_into_block + layout->samples_this_block_d > vgmstream->num_samples) {
|
||||
*p_samples_per_frame = layout->samples_per_frame_l;
|
||||
*p_samples_this_block = layout->samples_this_block_l;
|
||||
}
|
||||
else {
|
||||
*p_samples_per_frame = layout->samples_per_frame_d;
|
||||
*p_samples_this_block = layout->samples_this_block_d;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_loop_values(layout_config_t* layout, VGMSTREAM* vgmstream, int* p_samples_per_frame, int* p_samples_this_block) {
|
||||
if (layout->has_interleave_first &&
|
||||
vgmstream->current_sample < layout->samples_this_block_f) {
|
||||
/* use first interleave*/
|
||||
*p_samples_per_frame = layout->samples_per_frame_f;
|
||||
*p_samples_this_block = layout->samples_this_block_f;
|
||||
if (*p_samples_this_block == 0 && vgmstream->channels == 1)
|
||||
*p_samples_this_block = vgmstream->num_samples;
|
||||
}
|
||||
else if (layout->has_interleave_last) { /* assumes that won't loop back into a interleave_last */
|
||||
*p_samples_per_frame = layout->samples_per_frame_d;
|
||||
*p_samples_this_block = layout->samples_this_block_d;
|
||||
if (*p_samples_this_block == 0 && vgmstream->channels == 1)
|
||||
*p_samples_this_block = vgmstream->num_samples;
|
||||
}
|
||||
}
|
||||
|
||||
static void update_offsets(layout_config_t* layout, VGMSTREAM* vgmstream, int* p_samples_per_frame, int* p_samples_this_block) {
|
||||
int channels = vgmstream->channels;
|
||||
|
||||
if (layout->has_interleave_first &&
|
||||
vgmstream->current_sample == layout->samples_this_block_f) {
|
||||
/* interleave during first interleave: restore standard frame size after going past first interleave */
|
||||
*p_samples_per_frame = layout->samples_per_frame_d;
|
||||
*p_samples_this_block = layout->samples_this_block_d;
|
||||
if (*p_samples_this_block == 0 && channels == 1)
|
||||
*p_samples_this_block = vgmstream->num_samples;
|
||||
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
off_t skip = vgmstream->interleave_first_skip * (channels - 1 - ch) +
|
||||
vgmstream->interleave_first_block_size * (channels - ch) +
|
||||
vgmstream->interleave_block_size * ch;
|
||||
vgmstream->ch[ch].offset += skip;
|
||||
}
|
||||
}
|
||||
else if (layout->has_interleave_last &&
|
||||
vgmstream->current_sample + *p_samples_this_block > vgmstream->num_samples) {
|
||||
/* interleave during last interleave: adjust values again if inside last interleave */
|
||||
*p_samples_per_frame = layout->samples_per_frame_l;
|
||||
*p_samples_this_block = layout->samples_this_block_l;
|
||||
if (*p_samples_this_block == 0 && channels == 1)
|
||||
*p_samples_this_block = vgmstream->num_samples;
|
||||
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
off_t skip = vgmstream->interleave_block_size * (channels - ch) +
|
||||
vgmstream->interleave_last_block_size * ch;
|
||||
vgmstream->ch[ch].offset += skip;
|
||||
}
|
||||
}
|
||||
else if (layout->has_interleave_internal_updates) {
|
||||
/* interleave for some decoders that have already moved offsets over their data, so skip other channels's data */
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
off_t skip = vgmstream->interleave_block_size * (channels - 1);
|
||||
vgmstream->ch[ch].offset += skip;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* regular interleave */
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
off_t skip = vgmstream->interleave_block_size * channels;
|
||||
vgmstream->ch[ch].offset += skip;
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->samples_into_block = 0;
|
||||
}
|
||||
|
||||
|
||||
/* Decodes samples for interleaved streams.
|
||||
* Data has interleaved chunks per channel, and once one is decoded the layout moves offsets,
|
||||
* skipping other chunks (essentially a simplified variety of blocked layout).
|
||||
* Incompatible with decoders that move offsets. */
|
||||
void render_vgmstream_interleave(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) {
|
||||
int samples_written = 0;
|
||||
int samples_per_frame, samples_this_block; /* used */
|
||||
int samples_per_frame_d = 0, samples_this_block_d = 0; /* default */
|
||||
int samples_per_frame_f = 0, samples_this_block_f = 0; /* first */
|
||||
int samples_per_frame_l = 0, samples_this_block_l = 0; /* last */
|
||||
int has_interleave_first = vgmstream->interleave_first_block_size && vgmstream->channels > 1;
|
||||
int has_interleave_last = vgmstream->interleave_last_block_size && vgmstream->channels > 1;
|
||||
int has_interleave_internal_updates = vgmstream->codec_internal_updates;
|
||||
void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
layout_config_t layout = {0};
|
||||
if (!setup_helper(&layout, vgmstream)) {
|
||||
VGM_LOG_ONCE("INTERLEAVE: wrong config found\n");
|
||||
sbuf_silence(outbuf, sample_count, vgmstream->channels, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* setup */
|
||||
{
|
||||
int frame_size_d = decode_get_frame_size(vgmstream);
|
||||
samples_per_frame_d = decode_get_samples_per_frame(vgmstream);
|
||||
if (frame_size_d == 0 || samples_per_frame_d == 0) goto fail;
|
||||
samples_this_block_d = vgmstream->interleave_block_size / frame_size_d * samples_per_frame_d;
|
||||
}
|
||||
if (has_interleave_first) {
|
||||
int frame_size_f = decode_get_frame_size(vgmstream);
|
||||
samples_per_frame_f = decode_get_samples_per_frame(vgmstream); //todo samples per shortframe
|
||||
if (frame_size_f == 0 || samples_per_frame_f == 0) goto fail;
|
||||
samples_this_block_f = vgmstream->interleave_first_block_size / frame_size_f * samples_per_frame_f;
|
||||
}
|
||||
if (has_interleave_last) {
|
||||
int frame_size_l = decode_get_shortframe_size(vgmstream);
|
||||
samples_per_frame_l = decode_get_samples_per_shortframe(vgmstream);
|
||||
if (frame_size_l == 0 || samples_per_frame_l == 0) goto fail;
|
||||
samples_this_block_l = vgmstream->interleave_last_block_size / frame_size_l * samples_per_frame_l;
|
||||
}
|
||||
|
||||
/* set current values */
|
||||
if (has_interleave_first &&
|
||||
vgmstream->current_sample < samples_this_block_f) {
|
||||
samples_per_frame = samples_per_frame_f;
|
||||
samples_this_block = samples_this_block_f;
|
||||
}
|
||||
else if (has_interleave_last &&
|
||||
vgmstream->current_sample - vgmstream->samples_into_block + samples_this_block_d > vgmstream->num_samples) {
|
||||
samples_per_frame = samples_per_frame_l;
|
||||
samples_this_block = samples_this_block_l;
|
||||
}
|
||||
else {
|
||||
samples_per_frame = samples_per_frame_d;
|
||||
samples_this_block = samples_this_block_d;
|
||||
}
|
||||
int samples_per_frame, samples_this_block;
|
||||
update_default_values(&layout, vgmstream, &samples_per_frame, &samples_this_block);
|
||||
|
||||
/* mono interleaved stream with no layout set, just behave like flat layout */
|
||||
if (samples_this_block == 0 && vgmstream->channels == 1)
|
||||
samples_this_block = vgmstream->num_samples;
|
||||
|
||||
|
||||
/* write samples */
|
||||
while (samples_written < sample_count) {
|
||||
int samples_to_do;
|
||||
int samples_filled = 0;
|
||||
while (samples_filled < sample_count) {
|
||||
|
||||
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
||||
/* handle looping, restore standard interleave sizes */
|
||||
|
||||
if (has_interleave_first &&
|
||||
vgmstream->current_sample < samples_this_block_f) {
|
||||
/* use first interleave*/
|
||||
samples_per_frame = samples_per_frame_f;
|
||||
samples_this_block = samples_this_block_f;
|
||||
if (samples_this_block == 0 && vgmstream->channels == 1)
|
||||
samples_this_block = vgmstream->num_samples;
|
||||
}
|
||||
else if (has_interleave_last) { /* assumes that won't loop back into a interleave_last */
|
||||
samples_per_frame = samples_per_frame_d;
|
||||
samples_this_block = samples_this_block_d;
|
||||
if (samples_this_block == 0 && vgmstream->channels == 1)
|
||||
samples_this_block = vgmstream->num_samples;
|
||||
}
|
||||
|
||||
update_loop_values(&layout, vgmstream, &samples_per_frame, &samples_this_block);
|
||||
continue;
|
||||
}
|
||||
|
||||
samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_filled)
|
||||
samples_to_do = sample_count - samples_filled;
|
||||
|
||||
if (samples_to_do == 0) { /* happens when interleave is not set */
|
||||
goto fail;
|
||||
if (samples_to_do <= 0) { /* happens when interleave is not set */
|
||||
VGM_LOG_ONCE("INTERLEAVE: wrong samples_to_do\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
decode_vgmstream(vgmstream, samples_written, samples_to_do, buffer);
|
||||
decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf);
|
||||
|
||||
samples_written += samples_to_do;
|
||||
samples_filled += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
|
||||
|
||||
/* move to next interleaved block when all samples are consumed */
|
||||
if (vgmstream->samples_into_block == samples_this_block) {
|
||||
int ch;
|
||||
|
||||
if (has_interleave_first &&
|
||||
vgmstream->current_sample == samples_this_block_f) {
|
||||
/* restore standard frame size after going past first interleave */
|
||||
samples_per_frame = samples_per_frame_d;
|
||||
samples_this_block = samples_this_block_d;
|
||||
if (samples_this_block == 0 && vgmstream->channels == 1)
|
||||
samples_this_block = vgmstream->num_samples;
|
||||
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
off_t skip =
|
||||
vgmstream->interleave_first_skip*(vgmstream->channels-1-ch) +
|
||||
vgmstream->interleave_first_block_size*(vgmstream->channels-ch) +
|
||||
vgmstream->interleave_block_size*ch;
|
||||
vgmstream->ch[ch].offset += skip;
|
||||
}
|
||||
}
|
||||
else if (has_interleave_last &&
|
||||
vgmstream->current_sample + samples_this_block > vgmstream->num_samples) {
|
||||
/* adjust values again if inside last interleave */
|
||||
samples_per_frame = samples_per_frame_l;
|
||||
samples_this_block = samples_this_block_l;
|
||||
if (samples_this_block == 0 && vgmstream->channels == 1)
|
||||
samples_this_block = vgmstream->num_samples;
|
||||
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
off_t skip =
|
||||
vgmstream->interleave_block_size*(vgmstream->channels-ch) +
|
||||
vgmstream->interleave_last_block_size*ch;
|
||||
vgmstream->ch[ch].offset += skip;
|
||||
}
|
||||
}
|
||||
else if (has_interleave_internal_updates) {
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
off_t skip = vgmstream->interleave_block_size * (vgmstream->channels - 1);
|
||||
vgmstream->ch[ch].offset += skip;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||
off_t skip = vgmstream->interleave_block_size * vgmstream->channels;
|
||||
vgmstream->ch[ch].offset += skip;
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->samples_into_block = 0;
|
||||
update_offsets(&layout, vgmstream, &samples_per_frame, &samples_this_block);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
fail:
|
||||
VGM_LOG_ONCE("layout_interleave: wrong values found\n");
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
decode_fail:
|
||||
sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
}
|
||||
|
@ -3,84 +3,69 @@
|
||||
#include "../base/decode.h"
|
||||
#include "../base/mixing.h"
|
||||
#include "../base/plugins.h"
|
||||
#include "../base/sbuf.h"
|
||||
|
||||
#define VGMSTREAM_MAX_LAYERS 255
|
||||
#define VGMSTREAM_LAYER_SAMPLE_BUFFER 8192
|
||||
|
||||
|
||||
/* Decodes samples for layered streams.
|
||||
* Similar to flat layout, but decoded vgmstream are mixed into a final buffer, each vgmstream
|
||||
* may have different codecs and number of channels, creating a single super-vgmstream.
|
||||
* Usually combined with custom streamfiles to handle data interleaved in weird ways. */
|
||||
* Each decoded vgmstream 'layer' (which may have different codecs and number of channels)
|
||||
* is mixed into a final buffer, creating a single super-vgmstream. */
|
||||
void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
int samples_written = 0;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
int samples_per_frame, samples_this_block;
|
||||
|
||||
samples_per_frame = VGMSTREAM_LAYER_SAMPLE_BUFFER;
|
||||
samples_this_block = vgmstream->num_samples; /* do all samples if possible */
|
||||
|
||||
while (samples_written < sample_count) {
|
||||
int samples_to_do;
|
||||
int layer, ch;
|
||||
int samples_per_frame = VGMSTREAM_LAYER_SAMPLE_BUFFER;
|
||||
int samples_this_block = vgmstream->num_samples; /* do all samples if possible */
|
||||
|
||||
int samples_filled = 0;
|
||||
while (samples_filled < sample_count) {
|
||||
int ch;
|
||||
|
||||
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
||||
/* handle looping (loop_layout has been called below) */
|
||||
/* handle looping (loop_layout has been called inside) */
|
||||
continue;
|
||||
}
|
||||
|
||||
samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_filled)
|
||||
samples_to_do = sample_count - samples_filled;
|
||||
|
||||
if (samples_to_do <= 0) { /* when decoding more than num_samples */
|
||||
VGM_LOG_ONCE("LAYERED: samples_to_do 0\n");
|
||||
VGM_LOG_ONCE("LAYERED: wrong samples_to_do\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
/* decode all layers */
|
||||
ch = 0;
|
||||
for (layer = 0; layer < data->layer_count; layer++) {
|
||||
int s, layer_ch, layer_channels;
|
||||
for (int current_layer = 0; current_layer < data->layer_count; current_layer++) {
|
||||
|
||||
/* layers may have its own number of channels */
|
||||
mixing_info(data->layers[layer], NULL, &layer_channels);
|
||||
/* layers may have their own number of channels */
|
||||
int layer_channels;
|
||||
mixing_info(data->layers[current_layer], NULL, &layer_channels);
|
||||
|
||||
render_vgmstream(
|
||||
data->buffer,
|
||||
samples_to_do,
|
||||
data->layers[layer]);
|
||||
render_vgmstream(data->buffer, samples_to_do, data->layers[current_layer]);
|
||||
|
||||
/* mix layer samples to main samples */
|
||||
for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) {
|
||||
for (s = 0; s < samples_to_do; s++) {
|
||||
size_t layer_sample = s*layer_channels + layer_ch;
|
||||
size_t buffer_sample = (samples_written+s)*data->output_channels + ch;
|
||||
|
||||
outbuf[buffer_sample] = data->buffer[layer_sample];
|
||||
}
|
||||
ch++;
|
||||
}
|
||||
sbuf_copy_layers(outbuf, data->output_channels, data->buffer, layer_channels, samples_to_do, samples_filled, ch);
|
||||
ch += layer_channels;
|
||||
}
|
||||
|
||||
|
||||
samples_written += samples_to_do;
|
||||
samples_filled += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
}
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t));
|
||||
sbuf_silence(outbuf, sample_count, data->output_channels, samples_filled);
|
||||
}
|
||||
|
||||
|
||||
void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
int layer;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
for (layer = 0; layer < data->layer_count; layer++) {
|
||||
for (int layer = 0; layer < data->layer_count; layer++) {
|
||||
seek_vgmstream(data->layers[layer], seek_sample);
|
||||
}
|
||||
|
||||
@ -89,11 +74,9 @@ void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
}
|
||||
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
int layer;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
|
||||
for (layer = 0; layer < data->layer_count; layer++) {
|
||||
for (int layer = 0; layer < data->layer_count; layer++) {
|
||||
if (data->external_looping) {
|
||||
/* looping is applied over resulting decode, as each layer is its own "solid" block with
|
||||
* config and needs 'external' seeking */
|
||||
@ -130,37 +113,35 @@ layered_layout_data* init_layout_layered(int layer_count) {
|
||||
data = calloc(1, sizeof(layered_layout_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->layer_count = layer_count;
|
||||
|
||||
data->layers = calloc(layer_count, sizeof(VGMSTREAM*));
|
||||
if (!data->layers) goto fail;
|
||||
|
||||
data->layer_count = layer_count;
|
||||
|
||||
return data;
|
||||
fail:
|
||||
free_layout_layered(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int setup_layout_layered(layered_layout_data* data) {
|
||||
int i, max_input_channels = 0, max_output_channels = 0;
|
||||
sample_t *outbuf_re = NULL;
|
||||
|
||||
bool setup_layout_layered(layered_layout_data* data) {
|
||||
|
||||
/* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
int layer_input_channels, layer_output_channels;
|
||||
|
||||
int max_input_channels = 0;
|
||||
int max_output_channels = 0;
|
||||
for (int i = 0; i < data->layer_count; i++) {
|
||||
if (data->layers[i] == NULL) {
|
||||
VGM_LOG("LAYERED: no vgmstream in %i\n", i);
|
||||
goto fail;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->layers[i]->num_samples <= 0) {
|
||||
VGM_LOG("LAYERED: no samples in %i\n", i);
|
||||
goto fail;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* different layers may have different input/output channels */
|
||||
int layer_input_channels, layer_output_channels;
|
||||
mixing_info(data->layers[i], &layer_input_channels, &layer_output_channels);
|
||||
|
||||
max_output_channels += layer_output_channels;
|
||||
@ -171,12 +152,15 @@ int setup_layout_layered(layered_layout_data* data) {
|
||||
/* a bit weird, but no matter */
|
||||
if (data->layers[i]->sample_rate != data->layers[i-1]->sample_rate) {
|
||||
VGM_LOG("LAYERED: layer %i has different sample rate\n", i);
|
||||
//TO-DO: setup resampling
|
||||
}
|
||||
|
||||
/* also weird */
|
||||
#if 0
|
||||
/* also weird but less so */
|
||||
if (data->layers[i]->coding_type != data->layers[i-1]->coding_type) {
|
||||
VGM_LOG("LAYERED: layer %i has different coding type\n", i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* loops and other values could be mismatched, but should be handled on allocate */
|
||||
@ -192,44 +176,37 @@ int setup_layout_layered(layered_layout_data* data) {
|
||||
}
|
||||
|
||||
if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS)
|
||||
goto fail;
|
||||
return false;
|
||||
|
||||
/* create internal buffer big enough for mixing */
|
||||
outbuf_re = realloc(data->buffer, VGMSTREAM_LAYER_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t));
|
||||
if (!outbuf_re) goto fail;
|
||||
data->buffer = outbuf_re;
|
||||
/* create internal buffer big enough for mixing all layers */
|
||||
if (!sbuf_realloc(&data->buffer, VGMSTREAM_LAYER_SAMPLE_BUFFER, max_input_channels))
|
||||
goto fail;
|
||||
|
||||
data->input_channels = max_input_channels;
|
||||
data->output_channels = max_output_channels;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
return 0; /* caller is expected to free */
|
||||
return false; /* caller is expected to free */
|
||||
}
|
||||
|
||||
void free_layout_layered(layered_layout_data *data) {
|
||||
int i;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
if (data->layers) {
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
close_vgmstream(data->layers[i]);
|
||||
}
|
||||
free(data->layers);
|
||||
for (int i = 0; i < data->layer_count; i++) {
|
||||
close_vgmstream(data->layers[i]);
|
||||
}
|
||||
free(data->layers);
|
||||
free(data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void reset_layout_layered(layered_layout_data *data) {
|
||||
int i;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
for (int i = 0; i < data->layer_count; i++) {
|
||||
reset_vgmstream(data->layers[i]);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ void render_vgmstream_flat(sample_t* buffer, int32_t sample_count, VGMSTREAM* vg
|
||||
|
||||
void render_vgmstream_interleave(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
|
||||
|
||||
/* segmented layout */
|
||||
/* for files made of "continuous" segments, one per section of a song (using a complete sub-VGMSTREAM) */
|
||||
typedef struct {
|
||||
@ -25,12 +26,13 @@ typedef struct {
|
||||
|
||||
void render_vgmstream_segmented(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
segmented_layout_data* init_layout_segmented(int segment_count);
|
||||
int setup_layout_segmented(segmented_layout_data* data);
|
||||
bool setup_layout_segmented(segmented_layout_data* data);
|
||||
void free_layout_segmented(segmented_layout_data* data);
|
||||
void reset_layout_segmented(segmented_layout_data* data);
|
||||
void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample);
|
||||
|
||||
|
||||
/* layered layout */
|
||||
/* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */
|
||||
typedef struct {
|
||||
@ -45,16 +47,16 @@ typedef struct {
|
||||
|
||||
void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
layered_layout_data* init_layout_layered(int layer_count);
|
||||
int setup_layout_layered(layered_layout_data* data);
|
||||
bool setup_layout_layered(layered_layout_data* data);
|
||||
void free_layout_layered(layered_layout_data* data);
|
||||
void reset_layout_layered(layered_layout_data* data);
|
||||
void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample);
|
||||
|
||||
|
||||
/* blocked layouts */
|
||||
void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
void block_update(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void blocked_count_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, off_t offset);
|
||||
|
||||
void block_update_ast(off_t block_ofset, VGMSTREAM* vgmstream);
|
||||
void block_update_mxch(off_t block_ofset, VGMSTREAM* vgmstream);
|
||||
@ -67,7 +69,7 @@ void block_update_wsi(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void block_update_str_snds(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void block_update_ws_aud(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void block_update_dec(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void block_update_vs(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void block_update_vs_mh(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void block_update_mul(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void block_update_gsb(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
void block_update_xvas(off_t block_offset, VGMSTREAM* vgmstream);
|
||||
|
@ -3,36 +3,38 @@
|
||||
#include "../base/decode.h"
|
||||
#include "../base/mixing.h"
|
||||
#include "../base/plugins.h"
|
||||
#include "../base/sbuf.h"
|
||||
|
||||
#define VGMSTREAM_MAX_SEGMENTS 1024
|
||||
#define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192
|
||||
|
||||
static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written);
|
||||
|
||||
/* Decodes samples for segmented streams.
|
||||
* Chains together sequential vgmstreams, for data divided into separate sections or files
|
||||
* (like one part for intro and other for loop segments, which may even use different codecs). */
|
||||
void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
int samples_written = 0, samples_this_block;
|
||||
segmented_layout_data* data = vgmstream->layout_data;
|
||||
int use_internal_buffer = 0;
|
||||
int current_channels = 0;
|
||||
bool use_internal_buffer = false;
|
||||
|
||||
/* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */
|
||||
if (vgmstream->channels != data->input_channels || data->mixed_channels) {
|
||||
use_internal_buffer = 1;
|
||||
use_internal_buffer = true;
|
||||
}
|
||||
|
||||
if (data->current_segment >= data->segment_count) {
|
||||
VGM_LOG_ONCE("SEGMENT: wrong current segment\n");
|
||||
goto decode_fail;
|
||||
sbuf_silence(outbuf, sample_count, data->output_channels, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
int current_channels = 0;
|
||||
mixing_info(data->segments[data->current_segment], NULL, ¤t_channels);
|
||||
int samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
|
||||
while (samples_written < sample_count) {
|
||||
int samples_filled = 0;
|
||||
while (samples_filled < sample_count) {
|
||||
int samples_to_do;
|
||||
sample_t* buf;
|
||||
|
||||
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
||||
/* handle looping (loop_layout has been called below, changes segments/state) */
|
||||
@ -61,8 +63,8 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
|
||||
|
||||
samples_to_do = decode_get_samples_to_do(samples_this_block, sample_count, vgmstream);
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
if (samples_to_do > sample_count - samples_filled)
|
||||
samples_to_do = sample_count - samples_filled;
|
||||
if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */
|
||||
samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER;
|
||||
|
||||
@ -71,56 +73,30 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
render_vgmstream(
|
||||
use_internal_buffer ?
|
||||
data->buffer : &outbuf[samples_written * data->output_channels],
|
||||
samples_to_do,
|
||||
data->segments[data->current_segment]);
|
||||
buf = use_internal_buffer ? data->buffer : &outbuf[samples_filled * data->output_channels];
|
||||
render_vgmstream(buf, samples_to_do, data->segments[data->current_segment]);
|
||||
|
||||
if (use_internal_buffer) {
|
||||
copy_samples(outbuf, data, current_channels, samples_to_do, samples_written);
|
||||
sbuf_copy_samples(outbuf, data->output_channels, data->buffer, current_channels, samples_to_do, samples_filled);
|
||||
}
|
||||
|
||||
samples_written += samples_to_do;
|
||||
samples_filled += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
}
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t));
|
||||
sbuf_silence(outbuf, sample_count, data->output_channels, samples_filled);
|
||||
}
|
||||
|
||||
static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written) {
|
||||
int ch_out = data->output_channels;
|
||||
int ch_in = current_channels;
|
||||
int pos = samples_written * ch_out;
|
||||
int s;
|
||||
if (ch_in == ch_out) { /* most common and probably faster */
|
||||
for (s = 0; s < samples_to_do * ch_out; s++) {
|
||||
outbuf[pos + s] = data->buffer[s];
|
||||
}
|
||||
}
|
||||
else {
|
||||
int ch;
|
||||
for (s = 0; s < samples_to_do; s++) {
|
||||
for (ch = 0; ch < ch_in; ch++) {
|
||||
outbuf[pos + s*ch_out + ch] = data->buffer[s*ch_in + ch];
|
||||
}
|
||||
for (ch = ch_in; ch < ch_out; ch++) {
|
||||
outbuf[pos + s*ch_out + ch] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
int segment, total_samples;
|
||||
segmented_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
segment = 0;
|
||||
total_samples = 0;
|
||||
int segment = 0;
|
||||
int total_samples = 0;
|
||||
while (total_samples < vgmstream->num_samples) {
|
||||
int32_t segment_samples = vgmstream_get_samples(data->segments[segment]);
|
||||
|
||||
@ -133,13 +109,13 @@ void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||
vgmstream->samples_into_block = seek_relative;
|
||||
break;
|
||||
}
|
||||
|
||||
total_samples += segment_samples;
|
||||
segment++;
|
||||
}
|
||||
|
||||
if (segment == data->segment_count) {
|
||||
VGM_LOG("SEGMENTED: can't find seek segment\n");
|
||||
}
|
||||
// ???
|
||||
VGM_ASSERT(segment == data->segment_count, "SEGMENTED: can't find seek segment\n");
|
||||
}
|
||||
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
@ -156,35 +132,33 @@ segmented_layout_data* init_layout_segmented(int segment_count) {
|
||||
data = calloc(1, sizeof(segmented_layout_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->segment_count = segment_count;
|
||||
data->current_segment = 0;
|
||||
|
||||
data->segments = calloc(segment_count, sizeof(VGMSTREAM*));
|
||||
if (!data->segments) goto fail;
|
||||
|
||||
data->segment_count = segment_count;
|
||||
data->current_segment = 0;
|
||||
|
||||
return data;
|
||||
fail:
|
||||
free_layout_segmented(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int setup_layout_segmented(segmented_layout_data* data) {
|
||||
int i, max_input_channels = 0, max_output_channels = 0, mixed_channels = 0;
|
||||
sample_t *outbuf_re = NULL;
|
||||
bool setup_layout_segmented(segmented_layout_data* data) {
|
||||
int max_input_channels = 0, max_output_channels = 0, mixed_channels = 0;
|
||||
|
||||
|
||||
/* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */
|
||||
for (i = 0; i < data->segment_count; i++) {
|
||||
int segment_input_channels, segment_output_channels;
|
||||
for (int i = 0; i < data->segment_count; i++) {
|
||||
|
||||
if (data->segments[i] == NULL) {
|
||||
VGM_LOG("SEGMENTED: no vgmstream in segment %i\n", i);
|
||||
goto fail;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->segments[i]->num_samples <= 0) {
|
||||
VGM_LOG("SEGMENTED: no samples in segment %i\n", i);
|
||||
goto fail;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* allow config if set for fine-tuned parts (usually TXTP only) */
|
||||
@ -200,8 +174,8 @@ int setup_layout_segmented(segmented_layout_data* data) {
|
||||
}
|
||||
}
|
||||
|
||||
/* different segments may have different input or output channels, we
|
||||
* need to know maxs to properly handle */
|
||||
/* different segments may have different input or output channels (in rare cases of using ex. 2ch + 4ch) */
|
||||
int segment_input_channels, segment_output_channels;
|
||||
mixing_info(data->segments[i], &segment_input_channels, &segment_output_channels);
|
||||
if (max_input_channels < segment_input_channels)
|
||||
max_input_channels = segment_input_channels;
|
||||
@ -236,56 +210,52 @@ int setup_layout_segmented(segmented_layout_data* data) {
|
||||
}
|
||||
|
||||
if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS)
|
||||
goto fail;
|
||||
return false;
|
||||
|
||||
/* create internal buffer big enough for mixing */
|
||||
outbuf_re = realloc(data->buffer, VGMSTREAM_SEGMENT_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t));
|
||||
if (!outbuf_re) goto fail;
|
||||
data->buffer = outbuf_re;
|
||||
if (!sbuf_realloc(&data->buffer, VGMSTREAM_SEGMENT_SAMPLE_BUFFER, max_input_channels))
|
||||
goto fail;
|
||||
|
||||
data->input_channels = max_input_channels;
|
||||
data->output_channels = max_output_channels;
|
||||
data->mixed_channels = mixed_channels;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
return 0; /* caller is expected to free */
|
||||
return false; /* caller is expected to free */
|
||||
}
|
||||
|
||||
void free_layout_segmented(segmented_layout_data* data) {
|
||||
int i, j;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
if (data->segments) {
|
||||
for (i = 0; i < data->segment_count; i++) {
|
||||
int is_repeat = 0;
|
||||
for (int i = 0; i < data->segment_count; i++) {
|
||||
bool is_repeat = false;
|
||||
|
||||
/* segments are allowed to be repeated so don't close the same thing twice */
|
||||
for (j = 0; j < i; j++) {
|
||||
if (data->segments[i] == data->segments[j])
|
||||
is_repeat = 1;
|
||||
/* segments are allowed to be repeated so don't close the same thing twice */
|
||||
for (int j = 0; j < i; j++) {
|
||||
if (data->segments[i] == data->segments[j]) {
|
||||
is_repeat = true;
|
||||
break;
|
||||
}
|
||||
if (is_repeat)
|
||||
continue;
|
||||
|
||||
close_vgmstream(data->segments[i]);
|
||||
}
|
||||
free(data->segments);
|
||||
|
||||
if (is_repeat)
|
||||
continue;
|
||||
close_vgmstream(data->segments[i]);
|
||||
}
|
||||
free(data->segments);
|
||||
free(data->buffer);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void reset_layout_segmented(segmented_layout_data* data) {
|
||||
int i;
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
data->current_segment = 0;
|
||||
for (i = 0; i < data->segment_count; i++) {
|
||||
for (int i = 0; i < data->segment_count; i++) {
|
||||
reset_vgmstream(data->segments[i]);
|
||||
}
|
||||
|
||||
data->current_segment = 0;
|
||||
}
|
||||
|
@ -1,30 +1,29 @@
|
||||
/* libvgmstream: vgmstream's public API */
|
||||
|
||||
#ifndef _LIBVGMSTREAM_H_
|
||||
#define _LIBVGMSTREAM_H_
|
||||
|
||||
//#define LIBVGMSTREAM_ENABLE 1
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
/* By default vgmstream behaves like a decoder (decode samples until stream end), but you can configure
|
||||
/* libvgmstream: vgmstream's public API
|
||||
*
|
||||
* Basic usage (also see api_example.c):
|
||||
* - libvgmstream_init(...) // create context
|
||||
* - libvgmstream_setup(...) // setup config (if needed)
|
||||
* - libvgmstream_open_song(...) // open format
|
||||
* - libvgmstream_render(...) // main decode
|
||||
* - output samples + repeat libvgmstream_render until stream is done
|
||||
* - libvgmstream_free(...) // cleanup
|
||||
*
|
||||
* By default vgmstream behaves like a decoder (returns samples until stream end), but you can configure
|
||||
* it to loop N times or even downmix. In other words, it also behaves a bit like a player.
|
||||
* It exposes multiple convenience stuff mainly for various plugins that mostly repeat the same features.
|
||||
* All this may make the API still WIP and a bit twisted, probably will improve later. Probably.
|
||||
*
|
||||
* It exposes multiple convenience stuff mainly for various plugins with similar features.
|
||||
* This may make the API a bit odd, will probably improve later. Probably.
|
||||
*
|
||||
* Notes:
|
||||
* - now there is an API internals (vgmstream.h) may change in the future
|
||||
* - may dynamically allocate stuff as needed (mainly some buffers, but varies per format)
|
||||
* - now there is an API, internals (vgmstream.h) may change in the future so avoid accesing them
|
||||
* - some details described in the API may not happen at the moment (defined for future changes)
|
||||
* - uses long-winded libvgmstream_* names since internals alredy use the vgmstream_* 'namespace', #define as needed
|
||||
* - c-strings should be in UTF-8
|
||||
*
|
||||
* Basic usage (also see api_example.c):
|
||||
* - libvgmstream_init(...) // base context
|
||||
* - libvgmstream_setup(...) // config if needed
|
||||
* - libvgmstream_open_song(...) // setup format
|
||||
* - libvgmstream_play(...) // main decode
|
||||
* - output samples + repeat libvgmstream_play until stream is done
|
||||
* - libvgmstream_free(...) // cleanup
|
||||
*/
|
||||
|
||||
|
||||
@ -108,7 +107,9 @@ typedef struct {
|
||||
int64_t stream_samples; // file's max samples (not final play duration)
|
||||
int64_t loop_start; // loop start sample
|
||||
int64_t loop_end; // loop end sample
|
||||
bool loop_flag; // if file loops (false + defined loops means looping was forcefully disabled)
|
||||
bool loop_flag; // if file loops
|
||||
// ** false + defined loops means looping was forcefully disabled
|
||||
// ** true + undefined loops means the file loops in a way not representable by loop points
|
||||
|
||||
bool play_forever; // if file loops forever based on current config (meaning _play never stops)
|
||||
int64_t play_samples; // totals after all calculations (after applying loop/fade/etc config)
|
||||
@ -191,8 +192,8 @@ typedef struct {
|
||||
} libvgmstream_config_t;
|
||||
|
||||
/* pass default config, that will be applied to song on open
|
||||
* - invalid config or complex cases (ex. some TXTP) may ignore these settings.
|
||||
* - called without a song loaded (before _open or after _close), otherwise ignored.
|
||||
* - invalid config or complex cases (ex. some TXTP) may ignore these settings
|
||||
* - should be called without a song loaded (before _open or after _close)
|
||||
* - without config vgmstream will decode the current stream once
|
||||
*/
|
||||
LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg);
|
||||
@ -227,7 +228,7 @@ LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib);
|
||||
* - vgmstream supplies its own buffer, updated on lib->decoder->* values (may change between calls)
|
||||
* - returns < 0 on error
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib);
|
||||
LIBVGMSTREAM_API int libvgmstream_render(libvgmstream_t* lib);
|
||||
|
||||
/* Same as _play, but fills some external buffer (also updates lib->decoder->* values)
|
||||
* - returns < 0 on error, or N = number of filled samples.
|
||||
@ -297,7 +298,7 @@ typedef struct {
|
||||
/* Returns if vgmstream can parse a filename by extension, to reject some files earlier
|
||||
* - doesn't check file contents (that's only done on _open)
|
||||
* - config may be NULL
|
||||
* - mainly for plugins that fail early; libvgmstream doesn't use this
|
||||
* - mainly for plugins that want to fail early; libvgmstream doesn't use this
|
||||
*/
|
||||
LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg);
|
||||
|
||||
@ -321,6 +322,9 @@ LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_ti
|
||||
*/
|
||||
LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size);
|
||||
|
||||
/* Return true if vgmstream detects from the filename that file can be used even if doesn't physically exist.
|
||||
*/
|
||||
LIBVGMSTREAM_API bool libvgmstream_is_virtual_filename(const char* filename);
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
|
@ -219,7 +219,6 @@
|
||||
<ClCompile Include="base\api_helpers.c" />
|
||||
<ClCompile Include="base\api_libsf.c" />
|
||||
<ClCompile Include="base\api_tags.c" />
|
||||
<ClCompile Include="base\config.c" />
|
||||
<ClCompile Include="base\decode.c" />
|
||||
<ClCompile Include="base\info.c" />
|
||||
<ClCompile Include="base\mixer.c" />
|
||||
@ -228,8 +227,11 @@
|
||||
<ClCompile Include="base\mixing.c" />
|
||||
<ClCompile Include="base\mixing_commands.c" />
|
||||
<ClCompile Include="base\mixing_macros.c" />
|
||||
<ClCompile Include="base\play_config.c" />
|
||||
<ClCompile Include="base\play_state.c" />
|
||||
<ClCompile Include="base\plugins.c" />
|
||||
<ClCompile Include="base\render.c" />
|
||||
<ClCompile Include="base\sbuf.c" />
|
||||
<ClCompile Include="base\seek.c" />
|
||||
<ClCompile Include="base\streamfile_api.c" />
|
||||
<ClCompile Include="base\streamfile_buffer.c" />
|
||||
@ -630,7 +632,6 @@
|
||||
<ClCompile Include="meta\ps2_vgv.c" />
|
||||
<ClCompile Include="meta\ps2_vms.c" />
|
||||
<ClCompile Include="meta\ps2_wmus.c" />
|
||||
<ClCompile Include="meta\ps2_xa30.c" />
|
||||
<ClCompile Include="meta\psb.c" />
|
||||
<ClCompile Include="meta\psf.c" />
|
||||
<ClCompile Include="meta\psnd.c" />
|
||||
|
@ -487,9 +487,6 @@
|
||||
<ClCompile Include="base\api_tags.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\config.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\decode.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -514,12 +511,21 @@
|
||||
<ClCompile Include="base\mixing_macros.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\play_config.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\play_state.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\plugins.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\render.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\sbuf.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\seek.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -1720,9 +1726,6 @@
|
||||
<ClCompile Include="meta\ps2_wmus.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ps2_xa30.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\psb.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
@ -319,7 +319,7 @@ static int preload_acb_waveform(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload Waveform=%i\n", *p_rows);
|
||||
|
||||
acb->Waveform = malloc(*p_rows * sizeof(Waveform_t));
|
||||
acb->Waveform = calloc(1, *p_rows * sizeof(Waveform_t));
|
||||
if (!acb->Waveform) goto fail;
|
||||
|
||||
c_Id = utf_get_column(Table, "Id");
|
||||
@ -410,7 +410,7 @@ static int preload_acb_synth(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload Synth=%i\n", *p_rows);
|
||||
|
||||
acb->Synth = malloc(*p_rows * sizeof(Synth_t));
|
||||
acb->Synth = calloc(1, *p_rows * sizeof(Synth_t));
|
||||
if (!acb->Synth) goto fail;
|
||||
|
||||
c_Type = utf_get_column(Table, "Type");
|
||||
@ -613,7 +613,7 @@ static int preload_acb_trackcommand(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload TrackEvent/Command=%i\n", *p_rows);
|
||||
|
||||
acb->TrackCommand = malloc(*p_rows * sizeof(TrackCommand_t));
|
||||
acb->TrackCommand = calloc(1, *p_rows * sizeof(TrackCommand_t));
|
||||
if (!acb->TrackCommand) goto fail;
|
||||
|
||||
c_Command = utf_get_column(Table, "Command");
|
||||
@ -665,7 +665,7 @@ static int preload_acb_track(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload Track=%i\n", *p_rows);
|
||||
|
||||
acb->Track = malloc(*p_rows * sizeof(Track_t));
|
||||
acb->Track = calloc(1, *p_rows * sizeof(Track_t));
|
||||
if (!acb->Track) goto fail;
|
||||
|
||||
c_EventIndex = utf_get_column(Table, "EventIndex");
|
||||
@ -724,7 +724,7 @@ static int preload_acb_sequence(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload Sequence=%i\n", *p_rows);
|
||||
|
||||
acb->Sequence = malloc(*p_rows * sizeof(Sequence_t));
|
||||
acb->Sequence = calloc(1, *p_rows * sizeof(Sequence_t));
|
||||
if (!acb->Sequence) goto fail;
|
||||
|
||||
c_NumTracks = utf_get_column(Table, "NumTracks");
|
||||
@ -826,7 +826,7 @@ static int preload_acb_block(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload Block=%i\n", *p_rows);
|
||||
|
||||
acb->Block = malloc(*p_rows * sizeof(Block_t));
|
||||
acb->Block = calloc(1, *p_rows * sizeof(Block_t));
|
||||
if (!acb->Block) goto fail;
|
||||
|
||||
c_NumTracks = utf_get_column(Table, "NumTracks");
|
||||
@ -891,7 +891,7 @@ static int preload_acb_blocksequence(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload BlockSequence=%i\n", *p_rows);
|
||||
|
||||
acb->BlockSequence = malloc(*p_rows * sizeof(BlockSequence_t));
|
||||
acb->BlockSequence = calloc(1, *p_rows * sizeof(BlockSequence_t));
|
||||
if (!acb->BlockSequence) goto fail;
|
||||
|
||||
c_NumTracks = utf_get_column(Table, "NumTracks");
|
||||
@ -972,7 +972,7 @@ static int preload_acb_cue(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload Cue=%i\n", *p_rows);
|
||||
|
||||
acb->Cue = malloc(*p_rows * sizeof(Cue_t));
|
||||
acb->Cue = calloc(1, *p_rows * sizeof(Cue_t));
|
||||
if (!acb->Cue) goto fail;
|
||||
|
||||
c_ReferenceType = utf_get_column(Table, "ReferenceType");
|
||||
@ -1064,7 +1064,7 @@ static int preload_acb_cuename(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload CueName=%i\n", *p_rows);
|
||||
|
||||
acb->CueName = malloc(*p_rows * sizeof(CueName_t));
|
||||
acb->CueName = calloc(1, *p_rows * sizeof(CueName_t));
|
||||
if (!acb->CueName) goto fail;
|
||||
|
||||
c_CueIndex = utf_get_column(Table, "CueIndex");
|
||||
@ -1118,7 +1118,7 @@ static int preload_acb_waveformextensiondata(acb_header* acb) {
|
||||
return 1;
|
||||
//;VGM_LOG("acb: preload WaveformExtensionData=%i\n", *p_rows);
|
||||
|
||||
acb->WaveformExtensionData = malloc(*p_rows * sizeof(WaveformExtensionData_t));
|
||||
acb->WaveformExtensionData = calloc(1, *p_rows * sizeof(WaveformExtensionData_t));
|
||||
if (!acb->WaveformExtensionData) goto fail;
|
||||
|
||||
c_LoopStart = utf_get_column(Table, "LoopStart");
|
||||
|
@ -60,7 +60,7 @@ static void close_bar(BARSTREAMFILE *streamFile) {
|
||||
|
||||
|
||||
/*static*/ STREAMFILE *wrap_bar_STREAMFILE(STREAMFILE *file) {
|
||||
BARSTREAMFILE *streamfile = malloc(sizeof(BARSTREAMFILE));
|
||||
BARSTREAMFILE *streamfile = calloc(1, sizeof(BARSTREAMFILE));
|
||||
|
||||
if (!streamfile)
|
||||
return NULL;
|
||||
|
115
src/meta/bcstm.c
115
src/meta/bcstm.c
@ -3,59 +3,60 @@
|
||||
|
||||
|
||||
/* BCSTM - Nintendo 3DS format */
|
||||
VGMSTREAM * init_vgmstream_bcstm(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
VGMSTREAM* init_vgmstream_bcstm(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
off_t info_offset = 0, seek_offset = 0, data_offset = 0;
|
||||
int channel_count, loop_flag, codec;
|
||||
int is_camelot_ima = 0;
|
||||
int channels, loop_flag, codec;
|
||||
bool is_camelot_ima = false;
|
||||
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"bcstm") )
|
||||
goto fail;
|
||||
if (!is_id32be(0x00, sf, "CSTM"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"bcstm"))
|
||||
return NULL;
|
||||
|
||||
/* CSTM header */
|
||||
if (read_32bitBE(0x00, streamFile) != 0x4353544D) /* "CSTM" */
|
||||
goto fail;
|
||||
/* 0x06(2): header size (0x40), 0x08: version (0x00000400), 0x0c: file size (not accurate for Camelot IMA) */
|
||||
// 04: BOM
|
||||
// 06: header size (0x40)
|
||||
// 08: version (0x00000400)
|
||||
// 0c: file size (not accurate for Camelot IMA)
|
||||
|
||||
/* check BOM */
|
||||
if ((uint16_t)read_16bitLE(0x04, streamFile) != 0xFEFF)
|
||||
goto fail;
|
||||
if (read_u16le(0x04, sf) != 0xFEFF)
|
||||
return NULL;
|
||||
|
||||
/* get sections (should always appear in the same order) */
|
||||
{
|
||||
int i;
|
||||
int section_count = read_16bitLE(0x10, streamFile);
|
||||
for (i = 0; i < section_count; i++) {
|
||||
/* 0x00: id, 0x02(2): padding, 0x04(4): offset, 0x08(4): size */
|
||||
uint16_t section_id = read_16bitLE(0x14 + i*0x0c+0x00, streamFile);
|
||||
switch(section_id) {
|
||||
case 0x4000: info_offset = read_32bitLE(0x14 + i*0x0c+0x04, streamFile); break;
|
||||
case 0x4001: seek_offset = read_32bitLE(0x14 + i*0x0c+0x04, streamFile); break;
|
||||
case 0x4002: data_offset = read_32bitLE(0x14 + i*0x0c+0x04, streamFile); break;
|
||||
//case 0x4003: /* off_t regn_offset = read_32bitLE(0x18 + i * 0xc, streamFile); */ /* ? */
|
||||
//case 0x4004: /* off_t pdat_offset = read_32bitLE(0x18 + i * 0xc, streamFile); */ /* ? */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* get sections (should always appear in the same order though) */
|
||||
int section_count = read_u16le(0x10, sf);
|
||||
for (int i = 0; i < section_count; i++) {
|
||||
// 00: id
|
||||
// 02 padding
|
||||
// 04: offset
|
||||
// 08: size
|
||||
uint16_t section_id = read_u16le(0x14 + i * 0x0c + 0x00, sf);
|
||||
switch(section_id) {
|
||||
case 0x4000: info_offset = read_u32le(0x14 + i * 0x0c + 0x04, sf); break;
|
||||
case 0x4001: seek_offset = read_u32le(0x14 + i * 0x0c + 0x04, sf); break;
|
||||
case 0x4002: data_offset = read_u32le(0x14 + i * 0x0c + 0x04, sf); break;
|
||||
//case 0x4003: regn_offset = read_u32le(0x18 + i * 0x0c + 0x04, sf); break; // ?
|
||||
//case 0x4004: pdat_offset = read_u32le(0x18 + i * 0x0c + 0x04, sf); break; // ?
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* INFO section */
|
||||
if (read_32bitBE(info_offset, streamFile) != 0x494E464F) /* "INFO" */
|
||||
goto fail;
|
||||
codec = read_8bit(info_offset + 0x20, streamFile);
|
||||
loop_flag = read_8bit(info_offset + 0x21, streamFile);
|
||||
channel_count = read_8bit(info_offset + 0x22, streamFile);
|
||||
if (!is_id32be(info_offset, sf, "INFO"))
|
||||
return NULL;
|
||||
codec = read_u8(info_offset + 0x20, sf);
|
||||
loop_flag = read_8bit(info_offset + 0x21, sf);
|
||||
channels = read_8bit(info_offset + 0x22, sf);
|
||||
|
||||
|
||||
/* Camelot games have some weird codec hijack [Mario Tennis Open (3DS), Mario Golf: World Tour (3DS)] */
|
||||
if (codec == 0x02 && read_32bitBE(seek_offset, streamFile) != 0x5345454B) { /* "SEEK" */
|
||||
if (codec == 0x02 && !is_id32be(seek_offset, sf, "SEEK")) {
|
||||
if (seek_offset == 0) goto fail;
|
||||
start_offset = seek_offset;
|
||||
is_camelot_ima = 1;
|
||||
is_camelot_ima = true;
|
||||
}
|
||||
else {
|
||||
if (data_offset == 0) goto fail;
|
||||
@ -64,33 +65,24 @@ VGMSTREAM * init_vgmstream_bcstm(STREAMFILE *streamFile) {
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(info_offset + 0x24, streamFile);
|
||||
vgmstream->num_samples = read_32bitLE(info_offset + 0x2c, streamFile);
|
||||
vgmstream->loop_start_sample = read_32bitLE(info_offset + 0x28, streamFile);
|
||||
vgmstream->sample_rate = read_s32le(info_offset + 0x24, sf);
|
||||
vgmstream->num_samples = read_s32le(info_offset + 0x2c, sf);
|
||||
vgmstream->loop_start_sample = read_s32le(info_offset + 0x28, sf);
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples;
|
||||
|
||||
vgmstream->meta_type = meta_CSTM;
|
||||
vgmstream->layout_type = (channel_count == 1) ? layout_none : layout_interleave;
|
||||
vgmstream->interleave_block_size = read_32bitLE(info_offset + 0x34, streamFile);
|
||||
vgmstream->interleave_last_block_size = read_32bitLE(info_offset + 0x44, streamFile);
|
||||
vgmstream->layout_type = (channels == 1) ? layout_none : layout_interleave;
|
||||
vgmstream->interleave_block_size = read_u32le(info_offset + 0x34, sf);
|
||||
vgmstream->interleave_last_block_size = read_u32le(info_offset + 0x44, sf);
|
||||
|
||||
/* Camelot doesn't follow header values */
|
||||
if (is_camelot_ima) {
|
||||
size_t block_samples, last_samples;
|
||||
size_t data_size = get_streamfile_size(streamFile) - start_offset;
|
||||
size_t data_size = get_streamfile_size(sf) - start_offset;
|
||||
vgmstream->interleave_block_size = 0x200;
|
||||
vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size*channel_count)) / channel_count;
|
||||
|
||||
/* align the loop points back to avoid pops in some IMA streams, seems to help some Mario Golf songs but not all */
|
||||
block_samples = ima_bytes_to_samples(vgmstream->interleave_block_size*channel_count,channel_count); // 5000?
|
||||
last_samples = ima_bytes_to_samples(vgmstream->interleave_last_block_size*channel_count,channel_count);
|
||||
if (vgmstream->loop_start_sample > block_samples) {
|
||||
vgmstream->loop_start_sample -= last_samples;
|
||||
vgmstream->loop_end_sample -= last_samples;
|
||||
}
|
||||
vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size * channels)) / channels;
|
||||
}
|
||||
|
||||
switch(codec) {
|
||||
@ -107,16 +99,15 @@ VGMSTREAM * init_vgmstream_bcstm(STREAMFILE *streamFile) {
|
||||
vgmstream->coding_type = coding_NW_IMA;
|
||||
}
|
||||
else {
|
||||
int i, c;
|
||||
off_t channel_indexes, channel_info_offset, coefs_offset;
|
||||
|
||||
channel_indexes = info_offset+0x08 + read_32bitLE(info_offset + 0x1C, streamFile);
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
channel_info_offset = channel_indexes + read_32bitLE(channel_indexes+0x04+(i*0x08)+0x04, streamFile);
|
||||
coefs_offset = channel_info_offset + read_32bitLE(channel_info_offset+0x04, streamFile);
|
||||
channel_indexes = info_offset+0x08 + read_u32le(info_offset + 0x1C, sf);
|
||||
for (int i = 0; i < vgmstream->channels; i++) {
|
||||
channel_info_offset = channel_indexes + read_u32le(channel_indexes + 0x04 + (i * 0x08) + 0x04, sf);
|
||||
coefs_offset = channel_info_offset + read_u32le(channel_info_offset + 0x04, sf);
|
||||
|
||||
for (c = 0; c < 16; c++) {
|
||||
vgmstream->ch[i].adpcm_coef[c] = read_16bitLE(coefs_offset + c*2, streamFile);
|
||||
for (int c = 0; c < 16; c++) {
|
||||
vgmstream->ch[i].adpcm_coef[c] = read_s16le(coefs_offset + c * 0x02, sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,7 +117,7 @@ VGMSTREAM * init_vgmstream_bcstm(STREAMFILE *streamFile) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
|
@ -2,46 +2,48 @@
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../util/endianness.h"
|
||||
#include "../util/layout_utils.h"
|
||||
|
||||
|
||||
/* EA WVE (Ad10) - from early Electronic Arts movies [Wing Commander 3/4 (PS1), Madden NHL 97 (PC)-w95] */
|
||||
VGMSTREAM* init_vgmstream_ea_wve_ad10(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels;
|
||||
int big_endian, is_ps1;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .wve: common
|
||||
* .mov: Madden NHL 97 (also uses .wve) */
|
||||
if (!check_extensions(sf, "wve,mov"))
|
||||
goto fail;
|
||||
|
||||
start_offset = 0x00;
|
||||
if (!is_id32be(0x00, sf, "AABB") && /* video block */
|
||||
!is_id32be(0x00, sf, "Ad10") && /* audio block */
|
||||
!is_id32be(0x00, sf, "Ad11")) /* last audio block, but could be first */
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
big_endian = guess_endian32(0x04, sf);
|
||||
/* .wve: common
|
||||
* .mov: Madden NHL 97 (also uses .wve) */
|
||||
if (!check_extensions(sf, "wve,mov"))
|
||||
return NULL;
|
||||
|
||||
if (is_id32be(0x00, sf, "AABB"))
|
||||
bool big_endian = guess_endian32(0x04, sf);
|
||||
|
||||
start_offset = 0x00;
|
||||
if (is_id32be(0x00, sf, "AABB")){
|
||||
start_offset += big_endian ? read_u32be(0x04, sf) : read_u32le(0x04, sf);
|
||||
}
|
||||
|
||||
loop_flag = 0;
|
||||
|
||||
bool is_ps1;
|
||||
if (ps_check_format(sf, start_offset + 0x08, 0x40)) {
|
||||
/* no header = no channels, but seems if the first PS-ADPCM header is 00 then it's mono, somehow
|
||||
* (ex. Wing Commander 3 intro / Wing Commander 4 = stereo, rest of Wing Commander 3 = mono) */
|
||||
channels = read_u8(start_offset + 0x08,sf) != 0 ? 2 : 1;
|
||||
is_ps1 = 1;
|
||||
VGM_LOG("ps1");
|
||||
is_ps1 = true;
|
||||
}
|
||||
else {
|
||||
channels = 1;
|
||||
is_ps1 = 0;
|
||||
is_ps1 = false;
|
||||
}
|
||||
|
||||
loop_flag = false;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
@ -59,10 +61,14 @@ VGMSTREAM* init_vgmstream_ea_wve_ad10(STREAMFILE* sf) {
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
|
||||
blocked_count_samples(vgmstream, sf, start_offset);
|
||||
{
|
||||
blocked_counter_t cfg = {0};
|
||||
cfg.offset = start_offset;
|
||||
|
||||
blocked_count_samples(vgmstream, sf, &cfg);
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
|
@ -216,8 +216,6 @@ VGMSTREAM* init_vgmstream_rwax(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_xwb(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps2_xa30(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_musc(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_musx(STREAMFILE * streamFile);
|
||||
@ -269,7 +267,7 @@ VGMSTREAM* init_vgmstream_str_sega_custom(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_dec(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_vs(STREAMFILE *streamFile);
|
||||
VGMSTREAM* init_vgmstream_vs_mh(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_xmu(STREAMFILE *streamFile);
|
||||
|
||||
|
@ -7,17 +7,14 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) {
|
||||
#ifdef VGM_USE_MPEG
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
uint32_t start_offset;
|
||||
int loop_flag = 0;
|
||||
mpeg_frame_info info = {0};
|
||||
uint32_t header_id;
|
||||
|
||||
|
||||
/* checks */
|
||||
header_id = read_u32be(0x00, sf);
|
||||
uint32_t header_id = read_u32be(0x00, sf);
|
||||
if ((header_id & 0xFFF00000) != 0xFFF00000 &&
|
||||
(header_id & 0xFFFFFF00) != get_id32be("ID3\0") &&
|
||||
(header_id & 0xFFFFFF00) != get_id32be("TAG\0"))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
/* detect base offset, since some tags with images are big
|
||||
* (init_mpeg only skips tags in a small-ish buffer) */
|
||||
@ -29,8 +26,9 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) {
|
||||
start_offset += tag_size;
|
||||
}
|
||||
|
||||
mpeg_frame_info info = {0};
|
||||
if (!mpeg_get_frame_info(sf, start_offset, &info))
|
||||
goto fail;
|
||||
return NULL;
|
||||
|
||||
/* .mp3/mp2: standard
|
||||
* .lmp3/lmp2: for plugins
|
||||
@ -38,11 +36,12 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) {
|
||||
* .imf: Colors (Gizmondo)
|
||||
* .aix: Classic Compendium 2 (Gizmondo)
|
||||
* .wav/lwav: The Seventh Seal (PC)
|
||||
* .nfx: Grand Theft Auto III (Android)
|
||||
* (extensionless): Interstellar Flames 2 (Gizmondo) */
|
||||
if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,wav,lwav,"))
|
||||
goto fail;
|
||||
if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,wav,lwav,nfx,"))
|
||||
return NULL;
|
||||
|
||||
loop_flag = 0;
|
||||
bool loop_flag = 0;
|
||||
|
||||
|
||||
/* build VGMSTREAM */
|
||||
|
@ -1,55 +0,0 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* XA30 - found in Driver: Parallel Lines (PS2) */
|
||||
VGMSTREAM * init_vgmstream_ps2_xa30(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count;
|
||||
size_t file_size, data_size;
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* ".xa30" is just the ID, the real filename inside the file uses .XA */
|
||||
if (!check_extensions(streamFile,"xa,xa30"))
|
||||
goto fail;
|
||||
|
||||
/* check header */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x58413330) /* "XA30" */
|
||||
goto fail;
|
||||
if (read_32bitLE(0x04,streamFile) <= 2) goto fail; /* extra check to avoid PS2/PC XA30 mixup */
|
||||
|
||||
loop_flag = 0;
|
||||
channel_count = 1 ; /* 0x08(2): interleave? 0x0a(2): channels? (always 1 in practice) */
|
||||
|
||||
start_offset = read_32bitLE(0x0C,streamFile);
|
||||
|
||||
file_size = get_streamfile_size(streamFile);
|
||||
data_size = read_32bitLE(0x14,streamFile); /* always off by 0x800 */
|
||||
if (data_size-0x0800 != file_size) goto fail;
|
||||
data_size = file_size - start_offset;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(0x04,streamFile);
|
||||
vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count); /* 0x10: some num_samples value (but smaller) */
|
||||
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->meta_type = meta_PS2_XA30;
|
||||
/* the rest of the header has unknown values (and several repeats) and the filename */
|
||||
|
||||
|
||||
/* open the file for reading */
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -131,7 +131,7 @@ static void apply_settings(VGMSTREAM* vgmstream, txtp_entry_t* current) {
|
||||
|
||||
/* default play config (last after sample rate mods/mixing/etc) */
|
||||
txtp_copy_config(&vgmstream->config, ¤t->config);
|
||||
setup_state_vgmstream(vgmstream);
|
||||
setup_vgmstream_play_state(vgmstream);
|
||||
/* config is enabled in layouts or externally (for compatibility, since we don't know yet if this
|
||||
* VGMSTREAM will part of a layout, or is enabled externally to not mess up plugins's calcs) */
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../util/endianness.h"
|
||||
#include "../util/layout_utils.h"
|
||||
|
||||
|
||||
/* VID1 - Factor 5/DivX format GC/Xbox games [Gun (GC), Tony Hawk's American Wasteland (GC), Enter The Matrix (Xbox)]*/
|
||||
@ -10,16 +11,16 @@ VGMSTREAM* init_vgmstream_vid1(STREAMFILE* sf) {
|
||||
uint32_t start_offset, header_offset;
|
||||
int loop_flag = 0, channels, sample_rate;
|
||||
uint32_t codec;
|
||||
int big_endian;
|
||||
bool big_endian;
|
||||
read_u32_t read_u32;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (is_id32be(0x00, sf, "VID1")) { /* BE (GC) */
|
||||
big_endian = 1;
|
||||
big_endian = true;
|
||||
}
|
||||
else if (is_id32le(0x00,sf, "VID1")) { /* LE (Xbox) */
|
||||
big_endian = 0;
|
||||
big_endian = false;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
@ -110,24 +111,10 @@ VGMSTREAM* init_vgmstream_vid1(STREAMFILE* sf) {
|
||||
|
||||
/* calc num_samples as playable data size varies between files/blocks */
|
||||
if (vgmstream->layout_type == layout_blocked_vid1) {
|
||||
int block_samples;
|
||||
blocked_counter_t cfg = {0};
|
||||
cfg.offset = start_offset;
|
||||
|
||||
vgmstream->next_block_offset = start_offset;
|
||||
do {
|
||||
block_update(vgmstream->next_block_offset, vgmstream);
|
||||
if (vgmstream->current_block_samples < 0)
|
||||
break;
|
||||
|
||||
switch(vgmstream->coding_type) {
|
||||
case coding_PCM16_int: block_samples = pcm_bytes_to_samples(vgmstream->current_block_size, 1, 16); break;
|
||||
case coding_XBOX_IMA: block_samples = xbox_ima_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_NGC_DSP: block_samples = dsp_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
default: goto fail;
|
||||
}
|
||||
vgmstream->num_samples += block_samples;
|
||||
}
|
||||
while (vgmstream->next_block_offset < get_streamfile_size(sf));
|
||||
block_update(start_offset, vgmstream);
|
||||
blocked_count_samples(vgmstream, sf, &cfg);
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
@ -1,19 +1,31 @@
|
||||
#include "meta.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../util/layout_utils.h"
|
||||
|
||||
|
||||
/* .VS - from Melbourne House games [Men in Black II (PS2), Grand Prix Challenge (PS2) */
|
||||
VGMSTREAM* init_vgmstream_vs(STREAMFILE* sf) {
|
||||
VGMSTREAM* init_vgmstream_vs_mh(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(sf, "vs"))
|
||||
goto fail;
|
||||
if (read_u32be(0x00,sf) != 0xC8000000)
|
||||
goto fail;
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "vs"))
|
||||
return NULL;
|
||||
|
||||
/* extra checks since format is too simple */
|
||||
int sample_rate = read_s32le(0x04,sf);
|
||||
if (sample_rate != 48000 && sample_rate != 44100)
|
||||
return NULL;
|
||||
if (read_u32le(0x08,sf) != 0x1000)
|
||||
return NULL;
|
||||
if (!ps_check_format(sf, 0x0c, 0x1000))
|
||||
return NULL;
|
||||
|
||||
|
||||
loop_flag = 0;
|
||||
channels = 2;
|
||||
@ -24,24 +36,19 @@ VGMSTREAM* init_vgmstream_vs(STREAMFILE* sf) {
|
||||
vgmstream = allocate_vgmstream(channels,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_VS;
|
||||
vgmstream->sample_rate = read_s32le(0x04,sf);
|
||||
vgmstream->meta_type = meta_VS_MH;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_blocked_vs;
|
||||
vgmstream->layout_type = layout_blocked_vs_mh;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
|
||||
/* calc num_samples */
|
||||
{
|
||||
vgmstream->next_block_offset = start_offset;
|
||||
do {
|
||||
block_update(vgmstream->next_block_offset,vgmstream);
|
||||
vgmstream->num_samples += ps_bytes_to_samples(vgmstream->current_block_size, 1);
|
||||
}
|
||||
while (vgmstream->next_block_offset < get_streamfile_size(sf));
|
||||
block_update(start_offset, vgmstream);
|
||||
blocked_counter_t cfg = {0};
|
||||
cfg.offset = start_offset;
|
||||
|
||||
blocked_count_samples(vgmstream, sf, &cfg);
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
@ -1,76 +1,110 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* XA30 - found in Reflections games [Driver: Parallel Lines (PC), Driver 3 (PC)] */
|
||||
VGMSTREAM * init_vgmstream_xa_xa30(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, codec, interleave_block_size;
|
||||
size_t stream_size;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
/* ".xa30/e4x" is just the ID, the real filename should be .XA */
|
||||
if (!check_extensions(streamFile,"xa,xa30,e4x"))
|
||||
goto fail;
|
||||
|
||||
/* check header */
|
||||
if (read_32bitBE(0x00,streamFile) != 0x58413330 && /* "XA30" [Driver: Parallel Lines (PC)]*/
|
||||
read_32bitBE(0x00,streamFile) != 0x65347892) /* "e4x\92" [Driver 3 (PC)]*/
|
||||
goto fail;
|
||||
if (read_32bitLE(0x04,streamFile) != 2) /* channels?, also extra check to avoid PS2/PC XA30 mixup */
|
||||
goto fail;
|
||||
|
||||
total_subsongs = read_32bitLE(0x14,streamFile) != 0 ? 2 : 1; /* second stream offset (only in Driver 3) */
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
loop_flag = 0;
|
||||
channel_count = 2; /* 0x04: channels? (always 2 in practice) */
|
||||
codec = read_32bitLE(0x0c,streamFile);
|
||||
start_offset = read_32bitLE(0x10 + 0x04*(target_subsong-1),streamFile);
|
||||
stream_size = read_32bitLE(0x18 + 0x04*(target_subsong-1),streamFile);
|
||||
interleave_block_size = read_32bitLE(0x24, streamFile);
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(0x08,streamFile);
|
||||
/* 0x20: always IMA=00016000, PCM=00056000 PCM?, rest of the header is null */
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
vgmstream->meta_type = meta_XA_XA30;
|
||||
|
||||
switch(codec) {
|
||||
case 0x00: /* PCM (rare, seen in Driver 3) */
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave_block_size / 2;
|
||||
vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, 16);
|
||||
break;
|
||||
|
||||
case 0x01: /* MS-IMA variation */
|
||||
vgmstream->coding_type = coding_REF_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->interleave_block_size = interleave_block_size;
|
||||
vgmstream->num_samples = ms_ima_bytes_to_samples(stream_size, vgmstream->interleave_block_size, channel_count);
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* open the file for reading */
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* XA30 - from Reflections games [Driver: Parallel Lines (PC/PS2), Driver 3 (PC)] */
|
||||
VGMSTREAM* init_vgmstream_xa_xa30(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "XA30") && /* [Driver: Parallel Lines (PC/PS2)] */
|
||||
!is_id32be(0x00,sf, "e4x\x92")) /* [Driver 3 (PC)] */
|
||||
return NULL;
|
||||
|
||||
/* .XA: actual extension
|
||||
* .xa30/e4x: header ID */
|
||||
if (!check_extensions(sf,"xa,xa30,e4x"))
|
||||
return NULL;
|
||||
|
||||
|
||||
off_t start_offset;
|
||||
int loop_flag, channels, interleave, sample_rate;
|
||||
uint32_t codec, stream_size, file_size;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
if (read_u32le(0x04,sf) > 2) {
|
||||
/* PS2 */
|
||||
sample_rate = read_s32le(0x04,sf);
|
||||
interleave = read_u16le(0x08, sf); // assumed, always 0x8000
|
||||
channels = read_u16le(0x0a, sf); // assumed, always 1
|
||||
start_offset = read_u32le(0x0C,sf);
|
||||
// 10: some samples value? (smaller than real samples)
|
||||
stream_size = read_u32le(0x14,sf); // always off by 0x800
|
||||
// 18: fixed values
|
||||
// 1c: null
|
||||
// rest of the header: garbage/repeated values or config (includes stream name)
|
||||
|
||||
if (channels != 1)
|
||||
return NULL;
|
||||
|
||||
codec = 0xFF; //fake codec to simplify
|
||||
total_subsongs = 0;
|
||||
|
||||
file_size = get_streamfile_size(sf);
|
||||
if (stream_size - 0x0800 != file_size)
|
||||
return NULL;
|
||||
stream_size = file_size - start_offset;
|
||||
}
|
||||
else {
|
||||
/* PC */
|
||||
total_subsongs = read_u32le(0x14,sf) != 0 ? 2 : 1; /* second stream offset (only in Driver 3) */
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
|
||||
channels = read_s32le(0x04,sf); // assumed, always 2
|
||||
sample_rate = read_s32le(0x08,sf);
|
||||
codec = read_u32le(0x0c,sf);
|
||||
start_offset = read_u32le(0x10 + 0x04 * (target_subsong - 1),sf);
|
||||
stream_size = read_u32le(0x18 + 0x04 * (target_subsong - 1),sf);
|
||||
//20: fixed: IMA=00016000, PCM=00056000
|
||||
interleave = read_u32le(0x24, sf);
|
||||
// rest of the header is null
|
||||
|
||||
if (channels != 2)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
loop_flag = 0;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_XA_XA30;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
|
||||
switch(codec) {
|
||||
case 0x00: /* Driver 3 (PC)-rare */
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = interleave / 2;
|
||||
vgmstream->num_samples = pcm16_bytes_to_samples(stream_size, channels);
|
||||
break;
|
||||
|
||||
case 0x01:
|
||||
vgmstream->coding_type = coding_REF_IMA;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
vgmstream->num_samples = ms_ima_bytes_to_samples(stream_size, interleave, channels);
|
||||
break;
|
||||
|
||||
case 0xFF:
|
||||
vgmstream->coding_type = coding_PSX;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->interleave_block_size = interleave;
|
||||
vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels);
|
||||
break;
|
||||
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream,sf,start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ blowfish_ctx* blowfish_init_ecb(uint8_t* key, int32_t key_len) {
|
||||
uint32_t xl, xr;
|
||||
uint8_t tmpkey[18*4];
|
||||
|
||||
blowfish_ctx* ctx = malloc(sizeof(blowfish_ctx));
|
||||
blowfish_ctx* ctx = calloc(1, sizeof(blowfish_ctx));
|
||||
if (!ctx) return NULL;
|
||||
|
||||
|
||||
|
@ -145,7 +145,7 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const
|
||||
|
||||
utf->table_name = utf->string_table + utf->name_offset;
|
||||
|
||||
utf->schema = malloc(utf->columns * sizeof(struct utf_column_t));
|
||||
utf->schema = calloc(1, utf->columns * sizeof(struct utf_column_t));
|
||||
if (!utf->schema) goto fail;
|
||||
|
||||
for (i = 0; i < utf->columns; i++) {
|
||||
|
@ -293,3 +293,44 @@ fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void blocked_count_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, blocked_counter_t* cfg) {
|
||||
if (vgmstream == NULL)
|
||||
return;
|
||||
|
||||
int block_samples;
|
||||
off_t max_offset = get_streamfile_size(sf);
|
||||
|
||||
vgmstream->next_block_offset = cfg->offset;
|
||||
do {
|
||||
block_update(vgmstream->next_block_offset, vgmstream);
|
||||
|
||||
if (vgmstream->current_block_samples < 0 || vgmstream->current_block_size == 0xFFFFFFFF)
|
||||
break;
|
||||
|
||||
if (vgmstream->current_block_samples) {
|
||||
block_samples = vgmstream->current_block_samples;
|
||||
}
|
||||
else {
|
||||
switch(vgmstream->coding_type) {
|
||||
case coding_PCM16LE:
|
||||
case coding_PCM16_int: block_samples = pcm16_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_PCM8_int:
|
||||
case coding_PCM8_U_int: block_samples = pcm8_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_XBOX_IMA_mono:
|
||||
case coding_XBOX_IMA: block_samples = xbox_ima_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_NGC_DSP: block_samples = dsp_bytes_to_samples(vgmstream->current_block_size, 1); break;
|
||||
case coding_PSX: block_samples = ps_bytes_to_samples(vgmstream->current_block_size,1); break;
|
||||
default:
|
||||
VGM_LOG("BLOCKED: missing codec\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
vgmstream->num_samples += block_samples;
|
||||
}
|
||||
while (vgmstream->next_block_offset < max_offset);
|
||||
|
||||
block_update(cfg->offset, vgmstream); /* reset */
|
||||
}
|
||||
|
@ -22,4 +22,11 @@ bool layered_add_done(VGMSTREAM* vs);
|
||||
|
||||
VGMSTREAM* allocate_layered_vgmstream(layered_layout_data* data);
|
||||
VGMSTREAM* allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment);
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t offset;
|
||||
} blocked_counter_t;
|
||||
|
||||
void blocked_count_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, blocked_counter_t* cfg);
|
||||
#endif
|
||||
|
@ -186,8 +186,15 @@ typedef struct {
|
||||
bool allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
|
||||
int format_id; /* internal format ID */
|
||||
|
||||
|
||||
/* decoder config/state */
|
||||
int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */
|
||||
int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them (may change during decode) */
|
||||
bool codec_internal_updates; /* temp(?) kludge (see vgmstream_open_stream/decode) */
|
||||
int32_t ws_output_size; /* WS ADPCM: output bytes for this block */
|
||||
|
||||
/* layout/block state */
|
||||
int32_t current_sample; /* sample point within the file (for loop detection) */
|
||||
int32_t current_sample; /* sample point within the stream (for loop detection) */
|
||||
int32_t samples_into_block; /* number of samples into the current block/interleave/segment/etc */
|
||||
off_t current_block_offset; /* start of this block (offset of block header) */
|
||||
size_t current_block_size; /* size in usable bytes of the block we're in now (used to calculate num_samples per block) */
|
||||
@ -195,28 +202,24 @@ typedef struct {
|
||||
off_t next_block_offset; /* offset of header of the next block */
|
||||
size_t full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */
|
||||
|
||||
/* loop state (saved when loop is hit to restore later) */
|
||||
/* layout/block state copy for loops (saved on loop_start and restored later on loop_end) */
|
||||
int32_t loop_current_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */
|
||||
int32_t loop_samples_into_block;/* saved from samples_into_block */
|
||||
off_t loop_block_offset; /* saved from current_block_offset */
|
||||
size_t loop_block_size; /* saved from current_block_size */
|
||||
int32_t loop_block_samples; /* saved from current_block_samples */
|
||||
off_t loop_next_block_offset; /* saved from next_block_offset */
|
||||
size_t loop_full_block_size; /* saved from full_block_size (probably unnecessary) */
|
||||
|
||||
bool hit_loop; /* save config when loop is hit, but first time only */
|
||||
|
||||
|
||||
/* decoder config/state */
|
||||
int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */
|
||||
int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */
|
||||
bool codec_internal_updates; /* temp(?) kludge (see vgmstream_open_stream/decode) */
|
||||
int32_t ws_output_size; /* WS ADPCM: output bytes for this block */
|
||||
|
||||
|
||||
/* main state */
|
||||
VGMSTREAMCHANNEL* ch; /* array of channels */
|
||||
VGMSTREAMCHANNEL* start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */
|
||||
VGMSTREAMCHANNEL* ch; /* array of channels with current offset + per-channel codec config */
|
||||
|
||||
VGMSTREAMCHANNEL* loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */
|
||||
|
||||
void* start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */
|
||||
VGMSTREAMCHANNEL* start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */
|
||||
|
||||
void* mixer; /* state for mixing effects */
|
||||
|
||||
@ -242,7 +245,6 @@ typedef struct {
|
||||
} VGMSTREAM;
|
||||
|
||||
|
||||
// VGMStream description in structure format
|
||||
typedef struct {
|
||||
int sample_rate;
|
||||
int channels;
|
||||
@ -326,11 +328,10 @@ void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t ou
|
||||
void get_vgmstream_layout_description(VGMSTREAM* vgmstream, char* out, size_t out_size);
|
||||
void get_vgmstream_meta_description(VGMSTREAM* vgmstream, char* out, size_t out_size);
|
||||
|
||||
//TODO: remove, unused internally
|
||||
/* calculate the number of samples to be played based on looping parameters */
|
||||
int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM* vgmstream);
|
||||
|
||||
void setup_state_vgmstream(VGMSTREAM* vgmstream);
|
||||
|
||||
/* Force enable/disable internal looping. Should be done before playing anything (or after reset),
|
||||
* and not all codecs support arbitrary loop values ATM. */
|
||||
void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sample, int loop_end_sample);
|
||||
@ -338,4 +339,6 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa
|
||||
/* Set number of max loops to do, then play up to stream end (for songs with proper endings) */
|
||||
void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target);
|
||||
|
||||
void setup_vgmstream_play_state(VGMSTREAM* vgmstream);
|
||||
|
||||
#endif
|
||||
|
@ -66,7 +66,6 @@ init_vgmstream_t init_vgmstream_functions[] = {
|
||||
init_vgmstream_fsb5,
|
||||
init_vgmstream_rwax,
|
||||
init_vgmstream_xwb,
|
||||
init_vgmstream_ps2_xa30,
|
||||
init_vgmstream_musc,
|
||||
init_vgmstream_musx,
|
||||
init_vgmstream_filp,
|
||||
@ -90,7 +89,6 @@ init_vgmstream_t init_vgmstream_functions[] = {
|
||||
init_vgmstream_str_sega,
|
||||
init_vgmstream_str_sega_custom,
|
||||
init_vgmstream_dec,
|
||||
init_vgmstream_vs,
|
||||
init_vgmstream_xmu,
|
||||
init_vgmstream_xvas,
|
||||
init_vgmstream_sat_sap,
|
||||
@ -527,6 +525,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
|
||||
init_vgmstream_rage_aud,
|
||||
init_vgmstream_asd_naxat,
|
||||
init_vgmstream_pcm_kceje,
|
||||
init_vgmstream_vs_mh,
|
||||
/* need companion files */
|
||||
init_vgmstream_pos,
|
||||
init_vgmstream_sli_loops,
|
||||
|
@ -208,7 +208,7 @@ typedef enum {
|
||||
layout_blocked_ws_aud,
|
||||
layout_blocked_dec,
|
||||
layout_blocked_xvas,
|
||||
layout_blocked_vs,
|
||||
layout_blocked_vs_mh,
|
||||
layout_blocked_mul,
|
||||
layout_blocked_gsb,
|
||||
layout_blocked_thp,
|
||||
@ -333,7 +333,6 @@ typedef enum {
|
||||
meta_FSB5, /* FMOD Sample Bank, version 5 */
|
||||
meta_RWAX,
|
||||
meta_XWB, /* Microsoft XACT framework (Xbox, X360, Windows) */
|
||||
meta_PS2_XA30, /* Driver - Parallel Lines (PS2) */
|
||||
meta_MUSC, /* Krome PS2 games */
|
||||
meta_MUSX,
|
||||
meta_FILP, /* Resident Evil - Dead Aim */
|
||||
@ -428,7 +427,7 @@ typedef enum {
|
||||
meta_ACM, /* InterPlay ACM header */
|
||||
meta_MUS_ACM, /* MUS playlist of InterPlay ACM files */
|
||||
meta_DEC, /* Falcom PC games (Xanadu Next, Gurumin) */
|
||||
meta_VS, /* Men in Black .vs */
|
||||
meta_VS_MH,
|
||||
meta_BGW,
|
||||
meta_SPW,
|
||||
meta_STS,
|
||||
|
@ -60,29 +60,29 @@ static void ini_get_filename(In_Module* input_module, TCHAR *inifile) {
|
||||
if (IsWindow(input_module->hMainWindow) && SendMessage(input_module->hMainWindow, WM_WA_IPC,0,IPC_GETVERSION) >= 0x5000) {
|
||||
/* newer Winamp with per-user settings */
|
||||
TCHAR *ini_dir = (TCHAR *)SendMessage(input_module->hMainWindow, WM_WA_IPC, 0, IPC_GETINIDIRECTORY);
|
||||
cfg_strncpy(inifile, ini_dir, PATH_LIMIT);
|
||||
cfg_strncpy(inifile, ini_dir, WINAMP_PATH_LIMIT);
|
||||
|
||||
cfg_strncat(inifile, TEXT("\\Plugins\\"), PATH_LIMIT);
|
||||
cfg_strncat(inifile, TEXT("\\Plugins\\"), WINAMP_PATH_LIMIT);
|
||||
|
||||
/* can't be certain that \Plugins already exists in the user dir */
|
||||
CreateDirectory(inifile,NULL);
|
||||
|
||||
cfg_strncat(inifile, CONFIG_INI_NAME, PATH_LIMIT);
|
||||
cfg_strncat(inifile, CONFIG_INI_NAME, WINAMP_PATH_LIMIT);
|
||||
}
|
||||
else {
|
||||
/* older winamp with single settings */
|
||||
TCHAR *lastSlash;
|
||||
|
||||
GetModuleFileName(NULL, inifile, PATH_LIMIT);
|
||||
GetModuleFileName(NULL, inifile, WINAMP_PATH_LIMIT);
|
||||
lastSlash = cfg_strrchr(inifile, TEXT('\\'));
|
||||
|
||||
*(lastSlash + 1) = 0;
|
||||
|
||||
/* XMPlay doesn't have a "plugins" subfolder */
|
||||
if (settings.is_xmplay)
|
||||
cfg_strncat(inifile, CONFIG_INI_NAME,PATH_LIMIT);
|
||||
cfg_strncat(inifile, CONFIG_INI_NAME, WINAMP_PATH_LIMIT);
|
||||
else
|
||||
cfg_strncat(inifile, TEXT("Plugins\\") CONFIG_INI_NAME,PATH_LIMIT);
|
||||
cfg_strncat(inifile, TEXT("Plugins\\") CONFIG_INI_NAME, WINAMP_PATH_LIMIT);
|
||||
/* Maybe should query IPC_GETINIDIRECTORY and use that, not sure what ancient Winamps need.
|
||||
* There must be some proper way to handle dirs since other Winamp plugins save config in
|
||||
* XMPlay correctly (this feels like archaeology, try later) */
|
||||
@ -144,7 +144,7 @@ static void ini_set_b(const char *inifile, const char *entry, int val) {
|
||||
}
|
||||
|
||||
/*static*/ void load_config(In_Module* input_module, winamp_settings_t* settings, winamp_settings_t* defaults) {
|
||||
TCHAR inifile[PATH_LIMIT];
|
||||
TCHAR inifile[WINAMP_PATH_LIMIT];
|
||||
|
||||
ini_get_filename(input_module, inifile);
|
||||
|
||||
@ -176,7 +176,7 @@ static void ini_set_b(const char *inifile, const char *entry, int val) {
|
||||
}
|
||||
|
||||
static void save_config(In_Module* input_module, winamp_settings_t* settings) {
|
||||
TCHAR inifile[PATH_LIMIT];
|
||||
TCHAR inifile[WINAMP_PATH_LIMIT];
|
||||
|
||||
ini_get_filename(input_module, inifile);
|
||||
|
||||
|
@ -61,14 +61,14 @@ static void wasf_get_name(WINAMP_STREAMFILE* sf, char* buffer, size_t length) {
|
||||
}
|
||||
|
||||
static STREAMFILE* wasf_open(WINAMP_STREAMFILE* sf, const char* const filename, size_t buffersize) {
|
||||
in_char wpath[PATH_LIMIT];
|
||||
in_char wpath[WINAMP_PATH_LIMIT];
|
||||
|
||||
if (!filename)
|
||||
return NULL;
|
||||
|
||||
/* no need to wfdopen here, may use standard IO */
|
||||
/* STREAMFILEs carry char/UTF8 names, convert to wchar for Winamp */
|
||||
wa_char_to_ichar(wpath, PATH_LIMIT, filename);
|
||||
wa_char_to_ichar(wpath, WINAMP_PATH_LIMIT, filename);
|
||||
return open_winamp_streamfile_by_ipath(wpath);
|
||||
}
|
||||
|
||||
@ -109,11 +109,11 @@ fail:
|
||||
STREAMFILE* open_winamp_streamfile_by_ipath(const in_char* wpath) {
|
||||
FILE* infile = NULL;
|
||||
STREAMFILE* sf;
|
||||
char path[PATH_LIMIT];
|
||||
char path[WINAMP_PATH_LIMIT];
|
||||
|
||||
|
||||
/* convert to UTF-8 if needed for internal use */
|
||||
wa_ichar_to_char(path,PATH_LIMIT, wpath);
|
||||
wa_ichar_to_char(path, WINAMP_PATH_LIMIT, wpath);
|
||||
|
||||
/* open a FILE from a Winamp (possibly UTF-16) path */
|
||||
infile = wa_fopen(wpath);
|
||||
|
@ -89,9 +89,9 @@ bool split_subsongs(const in_char* filename, int subsong_index, VGMSTREAM* vgmst
|
||||
/* The only way to pass info around in Winamp is encoding it into the filename, so a fake name
|
||||
* is created with the index. Then, winamp_Play (and related) intercepts and reads the index. */
|
||||
for (i = 0; i < vgmstream->num_streams; i++) {
|
||||
in_char stream_fn[PATH_LIMIT];
|
||||
in_char stream_fn[WINAMP_PATH_LIMIT];
|
||||
|
||||
make_fn_subsong(stream_fn,PATH_LIMIT, filename, (i+1)); /* encode index in filename */
|
||||
make_fn_subsong(stream_fn, WINAMP_PATH_LIMIT, filename, (i+1)); /* encode index in filename */
|
||||
|
||||
/* insert at index */
|
||||
{
|
||||
|
@ -51,15 +51,15 @@ const char* tagfile_name = "!tags.m3u";
|
||||
HANDLE decode_thread_handle = INVALID_HANDLE_VALUE;
|
||||
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
in_char lastfn[PATH_LIMIT] = {0}; /* name of the currently playing file */
|
||||
in_char lastfn[WINAMP_PATH_LIMIT] = {0}; /* name of the currently playing file */
|
||||
|
||||
winamp_settings_t defaults;
|
||||
winamp_settings_t settings;
|
||||
winamp_state_t state;
|
||||
short sample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS]; //todo maybe should be dynamic
|
||||
short sample_buffer[SAMPLE_BUFFER_SIZE * 2 * VGMSTREAM_MAX_CHANNELS]; //todo maybe should be dynamic
|
||||
|
||||
/* info cache (optimization) */
|
||||
in_char info_fn[PATH_LIMIT] = {0};
|
||||
in_char info_fn[WINAMP_PATH_LIMIT] = {0};
|
||||
in_char info_title[GETFILEINFO_TITLE_LENGTH];
|
||||
int info_time;
|
||||
int info_valid;
|
||||
@ -116,11 +116,11 @@ static VGMSTREAM* init_vgmstream_winamp(const in_char* fn, int stream_index) {
|
||||
|
||||
/* opens vgmstream with (possibly) an index */
|
||||
static VGMSTREAM* init_vgmstream_winamp_fileinfo(const in_char* fn) {
|
||||
in_char filename[PATH_LIMIT];
|
||||
in_char filename[WINAMP_PATH_LIMIT];
|
||||
int stream_index = 0;
|
||||
|
||||
/* check for info encoded in the filename */
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
parse_fn_string(fn, NULL, filename, WINAMP_PATH_LIMIT);
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
return init_vgmstream_winamp(filename, stream_index);
|
||||
@ -141,14 +141,14 @@ static int is_xmplay() {
|
||||
|
||||
/* unicode utils */
|
||||
static void get_title(in_char* dst, int dst_size, const in_char* fn, VGMSTREAM* infostream) {
|
||||
in_char filename[PATH_LIMIT];
|
||||
char buffer[PATH_LIMIT];
|
||||
char filename_utf8[PATH_LIMIT];
|
||||
in_char filename[WINAMP_PATH_LIMIT];
|
||||
char buffer[WINAMP_PATH_LIMIT];
|
||||
char filename_utf8[WINAMP_PATH_LIMIT];
|
||||
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
parse_fn_string(fn, NULL, filename,WINAMP_PATH_LIMIT);
|
||||
//parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
wa_ichar_to_char(filename_utf8, PATH_LIMIT, filename);
|
||||
wa_ichar_to_char(filename_utf8, WINAMP_PATH_LIMIT, filename);
|
||||
|
||||
/* infostream gets added at first with index 0, then once played it re-adds proper numbers */
|
||||
if (infostream) {
|
||||
@ -187,7 +187,7 @@ static double get_album_gain_volume(const in_char* fn) {
|
||||
if (settings.gain_type == REPLAYGAIN_NONE)
|
||||
return 1.0;
|
||||
|
||||
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,(in_char*)fn); vgm_logi("get_album_gain_volume: file %s\n", f8); }
|
||||
//;{ char f8[WINAMP_PATH_LIMIT]; wa_ichar_to_char(f8,WINAMP_PATH_LIMIT,(in_char*)fn); vgm_logi("get_album_gain_volume: file %s\n", f8); }
|
||||
|
||||
replaygain[0] = '\0'; /* reset each time to make sure we read actual tags */
|
||||
if (settings.gain_type == REPLAYGAIN_ALBUM
|
||||
@ -281,7 +281,7 @@ void winamp_Quit() {
|
||||
int winamp_IsOurFile(const in_char *fn) {
|
||||
VGMSTREAM* infostream;
|
||||
vgmstream_ctx_valid_cfg cfg = {0};
|
||||
char filename_utf8[PATH_LIMIT];
|
||||
char filename_utf8[WINAMP_PATH_LIMIT];
|
||||
int valid;
|
||||
|
||||
/* Winamp file opening 101:
|
||||
@ -315,7 +315,7 @@ int winamp_IsOurFile(const in_char *fn) {
|
||||
cfg.accept_unknown = settings.exts_unknown_on;
|
||||
cfg.accept_common = settings.exts_common_on;
|
||||
|
||||
wa_ichar_to_char(filename_utf8, PATH_LIMIT, fn);
|
||||
wa_ichar_to_char(filename_utf8, WINAMP_PATH_LIMIT, fn);
|
||||
|
||||
//;vgm_logi("winamp_IsOurFile: %s\n", filename_utf8);
|
||||
|
||||
@ -324,7 +324,7 @@ int winamp_IsOurFile(const in_char *fn) {
|
||||
* open/get info from the file (slower so keep some cache) */
|
||||
|
||||
info_valid = 0; /* may not be playable */
|
||||
wa_strncpy(info_fn, fn, PATH_LIMIT); /* copy now for repeat calls */
|
||||
wa_strncpy(info_fn, fn, WINAMP_PATH_LIMIT); /* copy now for repeat calls */
|
||||
|
||||
/* basic extension check */
|
||||
valid = vgmstream_ctx_is_valid(filename_utf8, &cfg);
|
||||
@ -369,17 +369,17 @@ int winamp_IsOurFile(const in_char *fn) {
|
||||
/* request to start playing a file */
|
||||
int winamp_Play(const in_char *fn) {
|
||||
int max_latency;
|
||||
in_char filename[PATH_LIMIT];
|
||||
in_char filename[WINAMP_PATH_LIMIT];
|
||||
int stream_index = 0;
|
||||
|
||||
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("winamp_Play: file %s\n", f8); }
|
||||
//;{ char f8[WINAMP_PATH_LIMIT]; wa_ichar_to_char(f8,WINAMP_PATH_LIMIT,fn); vgm_logi("winamp_Play: file %s\n", f8); }
|
||||
|
||||
/* shouldn't happen */
|
||||
if (vgmstream)
|
||||
return 1;
|
||||
|
||||
/* check for info encoded in the filename */
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
parse_fn_string(fn, NULL, filename,WINAMP_PATH_LIMIT);
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
/* open the stream */
|
||||
@ -412,7 +412,7 @@ int winamp_Play(const in_char *fn) {
|
||||
|
||||
|
||||
/* save original name */
|
||||
wa_strncpy(lastfn,fn,PATH_LIMIT);
|
||||
wa_strncpy(lastfn,fn,WINAMP_PATH_LIMIT);
|
||||
|
||||
/* open the output plugin */
|
||||
max_latency = input_module.outMod->Open(vgmstream->sample_rate, state.output_channels, 16, 0, 0);
|
||||
@ -530,7 +530,7 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
|
||||
if (!vgmstream)
|
||||
return 0;
|
||||
|
||||
describe_vgmstream(vgmstream,description,sizeof(description));
|
||||
describe_vgmstream(vgmstream, description, sizeof(description));
|
||||
}
|
||||
else {
|
||||
/* some other file in playlist given by filename */
|
||||
@ -544,7 +544,7 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
|
||||
vgmstream_mixing_autodownmix(infostream, settings.downmix_channels);
|
||||
vgmstream_mixing_enable(infostream, 0, NULL, NULL);
|
||||
|
||||
describe_vgmstream(infostream,description,sizeof(description));
|
||||
describe_vgmstream(infostream, description, sizeof(description));
|
||||
|
||||
close_vgmstream(infostream);
|
||||
infostream = NULL;
|
||||
@ -592,7 +592,7 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
|
||||
}
|
||||
else {
|
||||
VGMSTREAM* infostream = NULL;
|
||||
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("winamp_GetFileInfo: file %s\n", f8); }
|
||||
//;{ char f8[WINAMP_PATH_LIMIT]; wa_ichar_to_char(f8,WINAMP_PATH_LIMIT,fn); vgm_logi("winamp_GetFileInfo: file %s\n", f8); }
|
||||
|
||||
/* not changed from last IsOurFile (most common) */
|
||||
if (info_valid && wa_strcmp(fn, info_fn) == 0) {
|
||||
@ -789,13 +789,13 @@ __declspec(dllexport) In_Module * winampGetInModule2() {
|
||||
/* IN_TAGS */
|
||||
/* ************************************* */
|
||||
|
||||
/* could malloc and stuff but totals aren't much bigger than PATH_LIMITs anyway */
|
||||
/* could malloc and stuff but totals aren't much bigger than WINAMP_PATH_LIMITs anyway */
|
||||
#define WINAMP_TAGS_ENTRY_MAX 30
|
||||
#define WINAMP_TAGS_ENTRY_SIZE 2048
|
||||
|
||||
typedef struct {
|
||||
int loaded;
|
||||
in_char filename[PATH_LIMIT]; /* tags are loaded for this file */
|
||||
in_char filename[WINAMP_PATH_LIMIT]; /* tags are loaded for this file */
|
||||
int tag_count;
|
||||
|
||||
char keys[WINAMP_TAGS_ENTRY_MAX][WINAMP_TAGS_ENTRY_SIZE+1];
|
||||
@ -809,10 +809,10 @@ winamp_tags last_tags;
|
||||
* Winamp requests one tag at a time and may reask for the same tag several times */
|
||||
static void load_tagfile_info(in_char* filename) {
|
||||
STREAMFILE *tagFile = NULL;
|
||||
in_char filename_clean[PATH_LIMIT];
|
||||
char filename_utf8[PATH_LIMIT];
|
||||
char tagfile_path_utf8[PATH_LIMIT];
|
||||
in_char tagfile_path_i[PATH_LIMIT];
|
||||
in_char filename_clean[WINAMP_PATH_LIMIT];
|
||||
char filename_utf8[WINAMP_PATH_LIMIT];
|
||||
char tagfile_path_utf8[WINAMP_PATH_LIMIT];
|
||||
in_char tagfile_path_i[WINAMP_PATH_LIMIT];
|
||||
char *path;
|
||||
|
||||
|
||||
@ -823,7 +823,7 @@ static void load_tagfile_info(in_char* filename) {
|
||||
}
|
||||
|
||||
/* clean extra part for subsong tags */
|
||||
parse_fn_string(filename, NULL, filename_clean,PATH_LIMIT);
|
||||
parse_fn_string(filename, NULL, filename_clean,WINAMP_PATH_LIMIT);
|
||||
|
||||
if (wa_strcmp(last_tags.filename, filename_clean) == 0) {
|
||||
return; /* not changed, tags still apply */
|
||||
@ -832,18 +832,18 @@ static void load_tagfile_info(in_char* filename) {
|
||||
last_tags.loaded = 0;
|
||||
|
||||
/* tags are now for this filename, find tagfile path */
|
||||
wa_ichar_to_char(filename_utf8, PATH_LIMIT, filename_clean);
|
||||
wa_ichar_to_char(filename_utf8, WINAMP_PATH_LIMIT, filename_clean);
|
||||
strcpy(tagfile_path_utf8,filename_utf8);
|
||||
|
||||
path = strrchr(tagfile_path_utf8,'\\');
|
||||
if (path != NULL) {
|
||||
path[1] = '\0'; /* includes "\", remove after that from tagfile_path */
|
||||
strcat(tagfile_path_utf8,tagfile_name);
|
||||
strcat(tagfile_path_utf8, tagfile_name);
|
||||
}
|
||||
else { /* ??? */
|
||||
strcpy(tagfile_path_utf8,tagfile_name);
|
||||
strcpy(tagfile_path_utf8, tagfile_name);
|
||||
}
|
||||
wa_char_to_ichar(tagfile_path_i, PATH_LIMIT, tagfile_path_utf8);
|
||||
wa_char_to_ichar(tagfile_path_i, WINAMP_PATH_LIMIT, tagfile_path_utf8);
|
||||
|
||||
wa_strcpy(last_tags.filename, filename_clean);
|
||||
last_tags.tag_count = 0;
|
||||
@ -895,7 +895,7 @@ static int winampGetExtendedFileInfo_common(in_char* filename, char *metadata, c
|
||||
int i, tag_found;
|
||||
int max_len;
|
||||
|
||||
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,filename); vgm_logi("winampGetExtendedFileInfo_common: file %s\n", f8); }
|
||||
//;{ char f8[WINAMP_PATH_LIMIT]; wa_ichar_to_char(f8,WINAMP_PATH_LIMIT,filename); vgm_logi("winampGetExtendedFileInfo_common: file %s\n", f8); }
|
||||
|
||||
/* load list current tags, if necessary */
|
||||
load_tagfile_info(filename);
|
||||
@ -952,13 +952,13 @@ fail:
|
||||
|
||||
/* for Winamp 5.24 */
|
||||
__declspec (dllexport) int winampGetExtendedFileInfo(char *filename, char *metadata, char *ret, int retlen) {
|
||||
in_char filename_wchar[PATH_LIMIT];
|
||||
in_char filename_wchar[WINAMP_PATH_LIMIT];
|
||||
int ok;
|
||||
|
||||
if (settings.tagfile_disable)
|
||||
return 0;
|
||||
|
||||
wa_char_to_ichar(filename_wchar,PATH_LIMIT, filename);
|
||||
wa_char_to_ichar(filename_wchar,WINAMP_PATH_LIMIT, filename);
|
||||
|
||||
//;{ vgm_logi("winampGetExtendedFileInfo: file %s\n", filename); }
|
||||
|
||||
@ -971,16 +971,16 @@ __declspec (dllexport) int winampGetExtendedFileInfo(char *filename, char *metad
|
||||
|
||||
/* for Winamp 5.3+ */
|
||||
__declspec (dllexport) int winampGetExtendedFileInfoW(wchar_t *filename, char *metadata, wchar_t *ret, int retlen) {
|
||||
in_char filename_ichar[PATH_LIMIT];
|
||||
in_char filename_ichar[WINAMP_PATH_LIMIT];
|
||||
char ret_utf8[2048];
|
||||
int ok;
|
||||
|
||||
if (settings.tagfile_disable)
|
||||
return 0;
|
||||
|
||||
wa_wchar_to_ichar(filename_ichar,PATH_LIMIT, filename);
|
||||
wa_wchar_to_ichar(filename_ichar,WINAMP_PATH_LIMIT, filename);
|
||||
|
||||
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,filename); vgm_logi("winampGetExtendedFileInfoW: file %s\n", f8); }
|
||||
//;{ char f8[WINAMP_PATH_LIMIT]; wa_ichar_to_char(f8,WINAMP_PATH_LIMIT,filename); vgm_logi("winampGetExtendedFileInfoW: file %s\n", f8); }
|
||||
|
||||
ok = winampGetExtendedFileInfo_common(filename_ichar, metadata, ret_utf8,2048);
|
||||
if (ok == 0)
|
||||
@ -1019,7 +1019,7 @@ short xsample_buffer[SAMPLE_BUFFER_SIZE*2 * VGMSTREAM_MAX_CHANNELS];
|
||||
static void* winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps, int *nch, int *srate) {
|
||||
VGMSTREAM* xvgmstream = NULL;
|
||||
|
||||
//;{ char f8[PATH_LIMIT]; wa_ichar_to_char(f8,PATH_LIMIT,fn); vgm_logi("winampGetExtendedRead_open_common: open common file %s\n", f8); }
|
||||
//;{ char f8[WINAMP_PATH_LIMIT]; wa_ichar_to_char(f8,WINAMP_PATH_LIMIT,fn); vgm_logi("winampGetExtendedRead_open_common: open common file %s\n", f8); }
|
||||
|
||||
/* open the stream */
|
||||
xvgmstream = init_vgmstream_winamp_fileinfo(fn);
|
||||
@ -1056,17 +1056,17 @@ static void* winampGetExtendedRead_open_common(in_char *fn, int *size, int *bps,
|
||||
}
|
||||
|
||||
__declspec(dllexport) void* winampGetExtendedRead_open(const char *fn, int *size, int *bps, int *nch, int *srate) {
|
||||
in_char filename_wchar[PATH_LIMIT];
|
||||
in_char filename_wchar[WINAMP_PATH_LIMIT];
|
||||
|
||||
wa_char_to_ichar(filename_wchar, PATH_LIMIT, fn);
|
||||
wa_char_to_ichar(filename_wchar, WINAMP_PATH_LIMIT, fn);
|
||||
|
||||
return winampGetExtendedRead_open_common(filename_wchar, size, bps, nch, srate);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void* winampGetExtendedRead_openW(const wchar_t *fn, int *size, int *bps, int *nch, int *srate) {
|
||||
in_char filename_ichar[PATH_LIMIT];
|
||||
in_char filename_ichar[WINAMP_PATH_LIMIT];
|
||||
|
||||
wa_wchar_to_ichar(filename_ichar, PATH_LIMIT, fn);
|
||||
wa_wchar_to_ichar(filename_ichar, WINAMP_PATH_LIMIT, fn);
|
||||
|
||||
return winampGetExtendedRead_open_common(filename_ichar, size, bps, nch, srate);
|
||||
}
|
||||
|
@ -33,6 +33,8 @@
|
||||
/* IN_CONFIG */
|
||||
/* ************************************* */
|
||||
|
||||
#define WINAMP_PATH_LIMIT 4096
|
||||
|
||||
extern In_Module input_module;
|
||||
extern const int priority_values[7];
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user