vgmstream/src/render.c

710 lines
27 KiB
C
Raw Normal View History

#include "vgmstream.h"
#include "layout/layout.h"
2020-07-29 19:34:55 +02:00
#include "render.h"
#include "decode.h"
#include "mixing.h"
#include "plugins.h"
2020-07-26 20:15:13 +02:00
/* VGMSTREAM RENDERING
* Caller asks for N samples in buf. vgmstream then calls layouts, that call decoders, and some optional pre/post-processing.
* Processing must be enabled externally: padding/trimming (config), mixing (output changes), resampling, etc.
*
* - MIXING
* After decoding sometimes we need to change number of channels, volume, etc. This is applied in order as
* a mixing chain, and modifies the final buffer (see mixing.c).
*
* - CONFIG
* A VGMSTREAM can work in 2 modes, defaults to simple mode:
* - simple mode (lib-like): decodes/loops forever and results are controlled externally (fades/max time/etc).
* - config mode (player-like): everything is internally controlled (pads/trims/time/fades/etc may be applied).
*
* It's done this way mainly for compatibility and to enable complex TXTP for layers/segments in selected cases
* (Wwise emulation). Could apply always some config like begin trim/padding + modify get_vgmstream_samples, but
* external caller may read loops/samples manually or apply its own config/fade, and changed output would mess it up.
*
* To enable config mode it needs 2 steps:
* - add some internal config settings (via TXTP, or passed by plugin).
* - enable flag with function (to signal "really delegate all decoding to vgmstream").
* Once done, plugin should simply decode until max samples (calculated by vgmstream).
*
* For complex layouts, behavior of "internal" (single segment/layer) and "external" (main) VGMSTREAMs is
* a bit complex. Internals' enable flag if play config exists (via TXTP), and this allows each part to be
* padded/trimmed/set time/loop/etc individually.
*
* Config mode in the external VGMSTREAM is mostly straighforward with segments:
* - each internal is always decoded separatedly (in simple or config mode) and results in N samples
* - segments may even loop "internally" before moving to next segment (by default they don't)
* - external's samples is the sum of all segments' N samples
* - looping, fades, etc then can be applied in the external part normally.
*
* With layers it's a bit more complex:
* - external's samples is the max of all layers
* - in simple mode external uses internal's looping to loop (for performance)
* - if layers' config mode is set, external can't rely on internal looping, so it uses it's own
*
* Layouts can contain layouts in cascade, so behavior can be a bit hard to understand at times.
* 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 */
2020-07-26 20:15:13 +02:00
ps->pad_begin_duration = pc->pad_begin;
/* removed samples from first decode */
2020-07-26 20:15:13 +02:00
ps->trim_begin_duration = pc->trim_begin;
/* main samples part */
2020-07-26 20:15:13 +02:00
ps->body_duration = 0;
if (pc->body_time) {
2020-07-26 20:15:13 +02:00
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;
2020-07-26 20:15:13 +02:00
ps->body_duration += vgmstream->loop_start_sample;
if (pc->ignore_fade) {
2020-07-26 20:15:13 +02:00
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 {
2020-07-26 20:15:13 +02:00
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count;
}
}
else {
2020-07-26 20:15:13 +02:00
ps->body_duration += vgmstream->num_samples;
}
/* samples from some modify body */
if (pc->trim_begin)
2020-07-26 20:15:13 +02:00
ps->body_duration -= pc->trim_begin;
if (pc->trim_end)
2020-07-26 20:15:13 +02:00
ps->body_duration -= pc->trim_end;
if (pc->fade_delay && vgmstream->loop_flag)
2020-07-26 20:15:13 +02:00
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) */
2020-07-26 20:15:13 +02:00
ps->pad_end_duration = pc->pad_end;
/* final count */
2020-07-26 20:15:13 +02:00
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 */
2020-07-26 20:15:13 +02:00
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;
2020-07-26 20:15:13 +02:00
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;
2020-07-29 19:34:55 +02:00
//ps->pad_end_left = ps->pad_end_duration;
2020-07-26 20:15:13 +02:00
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 free_layout(VGMSTREAM* vgmstream) {
if (vgmstream->layout_type == layout_segmented) {
free_layout_segmented(vgmstream->layout_data);
}
if (vgmstream->layout_type == layout_layered) {
free_layout_layered(vgmstream->layout_data);
}
}
void reset_layout(VGMSTREAM* vgmstream) {
if (vgmstream->layout_type == layout_segmented) {
reset_layout_segmented(vgmstream->layout_data);
}
if (vgmstream->layout_type == layout_layered) {
reset_layout_layered(vgmstream->layout_data);
}
}
static int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
/* current_sample goes between loop points (if looped) or up to max samples,
* must detect beyond that decoders would encounter garbage data */
2020-08-15 16:13:01 +02:00
/* not ">=" to allow layouts to loop in some cases when == happens */
if (vgmstream->current_sample > vgmstream->num_samples) {
int channels = vgmstream->channels;
memset(buf, 0, sample_count * sizeof(sample_t) * channels);
return sample_count;
}
switch (vgmstream->layout_type) {
case layout_interleave:
render_vgmstream_interleave(buf, sample_count, vgmstream);
break;
case layout_none:
render_vgmstream_flat(buf, sample_count, vgmstream);
break;
case layout_blocked_mxch:
case layout_blocked_ast:
case layout_blocked_halpst:
case layout_blocked_xa:
case layout_blocked_ea_schl:
case layout_blocked_ea_1snh:
case layout_blocked_caf:
case layout_blocked_wsi:
case layout_blocked_str_snds:
case layout_blocked_ws_aud:
case layout_blocked_matx:
case layout_blocked_dec:
case layout_blocked_vs:
case layout_blocked_mul:
case layout_blocked_gsb:
case layout_blocked_xvas:
case layout_blocked_thp:
case layout_blocked_filp:
case layout_blocked_ivaud:
case layout_blocked_ea_swvr:
case layout_blocked_adm:
case layout_blocked_bdsp:
case layout_blocked_tra:
case layout_blocked_ps2_iab:
case layout_blocked_vs_str:
case layout_blocked_rws:
case layout_blocked_hwas:
case layout_blocked_ea_sns:
case layout_blocked_awc:
case layout_blocked_vgs:
case layout_blocked_vawx:
case layout_blocked_xvag_subsong:
case layout_blocked_ea_wve_au00:
case layout_blocked_ea_wve_ad10:
case layout_blocked_sthd:
case layout_blocked_h4m:
case layout_blocked_xa_aiff:
case layout_blocked_vs_square:
case layout_blocked_vid1:
case layout_blocked_ubi_sce:
render_vgmstream_blocked(buf, sample_count, vgmstream);
break;
case layout_segmented:
render_vgmstream_segmented(buf, sample_count,vgmstream);
break;
case layout_layered:
render_vgmstream_layered(buf, sample_count, vgmstream);
break;
default:
break;
}
if (vgmstream->current_sample > vgmstream->num_samples) {
int channels = vgmstream->channels;
int32_t excess, decoded;
excess = (vgmstream->current_sample - vgmstream->num_samples);
if (excess > sample_count)
excess = sample_count;
decoded = sample_count - excess;
memset(buf + decoded * channels, 0, excess * sizeof(sample_t) * channels);
return sample_count;
}
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 */
while (vgmstream->pstate.trim_begin_left) {
int to_do = vgmstream->pstate.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;
}
}
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;
memset(buf, 0, to_do * sizeof(sample_t) * channels);
vgmstream->pstate.pad_begin_left -= to_do;
return to_do;
}
static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
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;
{
int s, ch, start, fade_pos;
int channels = ps->output_channels;
int32_t to_do = ps->fade_left;
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 (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_done - to_do - start) * sizeof(sample_t) * channels);
return samples_done;
}
}
static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_do) {
int channels = vgmstream->pstate.output_channels;
/* since anything beyond pad end is silence no need to check ranges */
memset(buf, 0, samples_to_do * sizeof(sample_t) * channels);
return samples_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) {
play_state_t* ps = &vgmstream->pstate;
int samples_to_do = sample_count;
int samples_done = 0;
int done;
sample_t* tmpbuf = buf;
/* 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 (done before to avoid decoding if possible) */
2020-07-29 19:34:55 +02:00
if (!vgmstream->config.play_forever /* && ps->pad_end_left */
&& ps->play_position + samples_done >= ps->pad_end_start) {
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;
/* simple fadeout */
if (!vgmstream->config.play_forever && ps->fade_left
&& ps->play_position + done >= ps->fade_start) {
render_fade(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) {
int excess = ps->play_position - ps->play_duration;
if (excess > sample_count)
excess = sample_count;
samples_done = (sample_count - excess);
ps->play_position = ps->play_duration;
}
return samples_done;
}
2020-07-26 20:15:13 +02:00
2020-07-29 19:34:55 +02:00
/*****************************************************************************/
2020-07-26 20:15:13 +02:00
2020-08-22 13:40:01 +02:00
static void seek_force_loop(VGMSTREAM* vgmstream, int loop_count) {
2020-07-29 19:34:55 +02:00
/* only called after hit loop */
if (!vgmstream->hit_loop)
return;
/* pretend decoder reached loop end so state is set to loop start */
2020-08-22 13:40:01 +02:00
vgmstream->loop_count = loop_count - 1; /* seeking to first loop musy become ++ > 0 */
2020-07-29 19:34:55 +02:00
vgmstream->current_sample = vgmstream->loop_end_sample;
vgmstream_do_loop(vgmstream);
}
static void seek_force_decode(VGMSTREAM* vgmstream, int samples) {
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 */
2020-07-29 19:34:55 +02:00
while (samples) {
int to_do = samples;
if (to_do > buf_samples)
to_do = buf_samples;
2020-07-29 19:34:55 +02:00
render_layout(tmpbuf, to_do, vgmstream);
2020-07-29 19:34:55 +02:00
/* no mixing */
samples -= to_do;
2020-07-29 19:34:55 +02:00
}
}
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
play_state_t* ps = &vgmstream->pstate;
int play_forever = vgmstream->config.play_forever;
int32_t decode_samples = 0;
int loop_count = -1;
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
2020-07-26 20:15:13 +02:00
/* will decode and loop until seek sample, but slower */
//todo apply same loop logic as below, or pretend we have play_forever + settings?
2020-07-26 20:15:13 +02:00
if (!vgmstream->config_enabled) {
//;VGM_LOG("SEEK: simple seek=%i, cur=%i\n", seek_sample, vgmstream->current_sample);
if (seek_sample < vgmstream->current_sample) {
decode_samples = seek_sample;
reset_vgmstream(vgmstream);
}
else {
decode_samples = seek_sample - vgmstream->current_sample;
}
seek_force_decode(vgmstream, decode_samples);
2020-07-26 20:15:13 +02:00
return;
}
2020-07-29 19:34:55 +02:00
//todo could improve performance bit if hit_loop wasn't lost when calling reset
//todo wrong seek with ignore fade, also for layered layers (pass count to force loop + layers)
2020-07-29 19:34:55 +02:00
/* seeking to requested sample normally means decoding and discarding up to that point (from
* the beginning, or current position), but can be optimized a bit to decode less with some tricks:
* - seek may fall in part of the song that isn't actually decoding (due to config, like padding)
* - if file loops there is no need to decode N full loops, seek can be set relative to loop region
* - can decode to seek sample from current position or loop start depending on lowest
*
* some of the cases below could be simplified but the logic to get this going is kinda mind-bending
*
* (ex. with file = 100, pad=5s, trim=3s, loop=20s..90s)
* | pad-begin | body-begin | body-loop0 | body-loop1 | body-loop2 | fade | pad-end + beyond)
* 0 5s (-3s) 25s 95s 165s 235s 245s Ns
*/
2020-07-26 20:15:13 +02:00
2020-07-29 19:34:55 +02:00
if (seek_sample < 0)
seek_sample = 0;
if (seek_sample > ps->play_duration && !play_forever) /* play forever can seek to any loop */
seek_sample = ps->play_duration;
//;VGM_LOG("SEEK: seek sample=%i, is_looped=%i\n", seek_sample, is_looped);
/* start/pad-begin: consume pad samples */
if (seek_sample < ps->pad_begin_duration) {
/* seek=3: pad=5-3=2 */
decode_samples = 0;
2020-07-26 20:15:13 +02:00
reset_vgmstream(vgmstream);
2020-07-29 19:34:55 +02:00
ps->pad_begin_left = ps->pad_begin_duration - seek_sample;
//;VGM_LOG("SEEK: pad start / dec=%i\n", decode_samples);
2020-07-26 20:15:13 +02:00
}
2020-07-29 19:34:55 +02:00
/* body: find position relative to decoder's current sample */
else if (play_forever || seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
/* seek=10 would be seekr=10-5+3=8 inside decoder */
int32_t seek_relative = seek_sample - ps->pad_begin_duration + ps->trim_begin_duration;
2020-07-26 20:15:13 +02:00
2020-07-29 19:34:55 +02:00
//;VGM_LOG("SEEK: body / seekr=%i, curr=%i\n", seek_relative, vgmstream->current_sample);
2020-07-26 20:15:13 +02:00
2020-07-29 19:34:55 +02:00
/* seek can be in some part of the body, depending on looped/decoder's current/etc */
if (!is_looped && seek_relative < vgmstream->current_sample) {
/* seekr=50s, curr=95 > restart + decode=50s */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: non-loop reset / dec=%i\n", decode_samples);
}
else if (!is_looped && seek_relative < vgmstream->num_samples) {
/* seekr=95s, curr=50 > decode=95-50=45s */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: non-loop forward / dec=%i\n", decode_samples);
}
else if (!is_looped) {
/* seekr=120s (outside decode, can happen when body is set manually) */
decode_samples = 0;
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: non-loop silence / dec=%i\n", decode_samples);
}
else if (seek_relative < vgmstream->loop_start_sample) {
/* seekr=6s > 6-5+3 > seek=4s inside decoder < 20s: decode 4s from start, or 1s if current was at 3s */
if (seek_relative < vgmstream->current_sample) {
/* seekr=9s, current=10s > decode=9s from start */
decode_samples = seek_relative;
reset_vgmstream(vgmstream);
//;VGM_LOG("SEEK: loop start reset / dec=%i\n", decode_samples);
}
else {
/* seekr=9s, current=8s > decode=1s from current */
decode_samples = seek_relative - vgmstream->current_sample;
//;VGM_LOG("SEEK: loop start forward / dec=%i\n", decode_samples);
}
}
else {
/* seek can be clamped between loop parts (relative to decoder's current_sample) to minimize decoding */
int32_t loop_body, loop_seek, loop_curr;
/* current must have reached loop start at some point */
if (!vgmstream->hit_loop) {
int32_t skip_samples;
if (vgmstream->current_sample >= vgmstream->loop_start_sample) {
VGM_LOG("SEEK: bad current sample %i vs %i\n", vgmstream->current_sample, vgmstream->loop_start_sample);
reset_vgmstream(vgmstream);
}
skip_samples = (vgmstream->loop_start_sample - vgmstream->current_sample);
//;VGM_LOG("SEEK: must loop / skip=%i, curr=%i\n", skip_samples, vgmstream->current_sample);
seek_force_decode(vgmstream, skip_samples);
}
/* current must be in loop area (shouldn't happen?) */
if (vgmstream->current_sample < vgmstream->loop_start_sample
|| vgmstream->current_sample < vgmstream->loop_end_sample) {
//;VGM_LOG("SEEK: current outside loop area / curr=%i, ls=%i, le=%i\n", vgmstream->current_sample, vgmstream->current_sample, vgmstream->loop_end_sample);
2020-08-22 13:40:01 +02:00
seek_force_loop(vgmstream, 0);
2020-07-29 19:34:55 +02:00
}
loop_body = (vgmstream->loop_end_sample - vgmstream->loop_start_sample);
loop_seek = seek_relative - vgmstream->loop_start_sample;
loop_count = loop_seek / loop_body;
loop_seek = loop_seek % loop_body;
loop_curr = vgmstream->current_sample - vgmstream->loop_start_sample;
2020-08-22 13:40:01 +02:00
/* when "ignore fade" is used and seek falls into non-fade part, this needs to seek right before it
so when calling seek_force_loop detection kicks in, and non-fade then decodes normally */
if (vgmstream->loop_target && vgmstream->loop_target == loop_count) {
loop_seek = loop_body;
}
2020-07-29 19:34:55 +02:00
//;VGM_LOG("SEEK: in loop / seekl=%i, loops=%i, cur=%i, dec=%i\n", loop_seek, loop_count, loop_curr, decode_samples);
if (loop_seek < loop_curr) {
decode_samples += loop_seek;
2020-08-22 13:40:01 +02:00
seek_force_loop(vgmstream, loop_count);
2020-07-29 19:34:55 +02:00
//;VGM_LOG("SEEK: loop reset / dec=%i, loop=%i\n", decode_samples, loop_count);
}
else {
decode_samples += (loop_seek - loop_curr);
//;VGM_LOG("SEEK: loop forward / dec=%i, loop=%i\n", decode_samples, loop_count);
}
/* adjust fade if seek ends in fade region */
if (!play_forever
&& seek_sample >= ps->pad_begin_duration + ps->body_duration
&& seek_sample < ps->pad_begin_duration + ps->body_duration + ps->fade_duration) {
ps->fade_left = ps->pad_begin_duration + ps->body_duration + ps->fade_duration - seek_sample;
//;VGM_LOG("SEEK: in fade / fade=%i, %i\n", ps->fade_left, ps->fade_duration);
}
}
/* done at the end in case of reset */
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
}
/* pad end and beyond: ignored */
else {
decode_samples = 0;
ps->pad_begin_left = 0;
ps->trim_begin_left = 0;
if (!is_looped)
vgmstream->current_sample = vgmstream->num_samples + 1;
//;VGM_LOG("SEEK: end silence / dec=%i\n", decode_samples);
/* looping decoder state isn't changed (seek backwards could use current sample) */
2020-07-26 20:15:13 +02:00
}
2020-07-29 19:34:55 +02:00
seek_force_decode(vgmstream, decode_samples);
vgmstream->pstate.play_position = seek_sample;
2020-07-26 20:15:13 +02:00
}