Make segmented/layered loop/behave a bit more standard for seeking

This commit is contained in:
bnnm 2020-08-01 00:48:15 +02:00
parent ef64005418
commit 9216971505
5 changed files with 119 additions and 110 deletions

View File

@ -1,6 +1,6 @@
#include "vgmstream.h"
#include "decode.h"
//#include "layout/layout.h"
#include "layout/layout.h"
#include "coding/coding.h"
#include "mixing.h"
#include "plugins.h"
@ -1423,7 +1423,7 @@ int vgmstream_do_loop(VGMSTREAM* vgmstream) {
}
}
/* prepare certain codecs' internal state for looping */
/* loop codecs */
seek_codec(vgmstream);
/* restore! */
@ -1436,6 +1436,18 @@ int vgmstream_do_loop(VGMSTREAM* vgmstream) {
vgmstream->next_block_offset = vgmstream->loop_next_block_offset;
//vgmstream->pstate = vgmstream->lstate; /* play state is applied over loops */
/* loop layouts (after restore, in case layout needs state manipulations) */
switch(vgmstream->layout_type) {
case layout_segmented:
loop_layout_segmented(vgmstream, vgmstream->loop_current_sample);
break;
case layout_layered:
loop_layout_layered(vgmstream, vgmstream->loop_current_sample);
break;
default:
break;
}
return 1; /* looped */
}

View File

@ -9,54 +9,34 @@
/* Decodes samples for layered streams.
* Similar to interleave layout, but decodec samples are mixed from complete vgmstreams, each
* with custom codecs and different number of channels, creating a single super-vgmstream.
* 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. */
void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
int samples_written = 0, loop_samples_skip = 0;
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 samples_this_block = VGMSTREAM_LAYER_SAMPLE_BUFFER;
int layer, ch = 0;
int layer, ch;
if (data->external_looping) {
/* normally each layer handles its own looping internally, except when using config
* were each layer is treated as a solid part, so loop is applied externally */
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
for (layer = 0; layer < data->layer_count; layer++) {
reset_vgmstream(data->layers[layer]);
//todo per-layer seeking instead of layout looping
}
loop_samples_skip = vgmstream->loop_start_sample;
vgmstream->current_sample = 0;
vgmstream->samples_into_block = 0;
continue;
}
samples_to_do = get_vgmstream_samples_to_do(vgmstream->num_samples, samples_this_block, vgmstream);
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
/* looping: discard until actual start */
if (loop_samples_skip > 0) {
if (samples_to_do > loop_samples_skip)
samples_to_do = loop_samples_skip;
}
}
else {
samples_to_do = samples_this_block;
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
/* handle looping (loop_layout has been called below) */
continue;
}
samples_to_do = get_vgmstream_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;
/* decode all layers */
ch = 0;
for (layer = 0; layer < data->layer_count; layer++) {
int s, layer_ch, layer_channels;
@ -68,10 +48,6 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
samples_to_do,
data->layers[layer]);
if (loop_samples_skip > 0) {
continue;
}
/* mix layer samples to main samples */
for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) {
for (s = 0; s < samples_to_do; s++) {
@ -84,25 +60,40 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
}
}
if (loop_samples_skip > 0) {
loop_samples_skip -= samples_to_do;
vgmstream->samples_into_block += samples_to_do;
continue;
}
samples_written += samples_to_do;
vgmstream->current_sample += samples_to_do;
vgmstream->samples_into_block += samples_to_do;
}
}
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++) {
if (data->external_looping) {
samples_written += samples_to_do;
vgmstream->current_sample += samples_to_do;
vgmstream->samples_into_block += samples_to_do;
/* looping is applied over resulting decode, as each layer is its own "solid" block with
* config and needs 'external' seeking */
seek_vgmstream(data->layers[layer], loop_sample);
}
else {
samples_written += samples_to_do;
vgmstream->current_sample = data->layers[0]->current_sample;
vgmstream->samples_into_block = 0; /* handled in each layer */
vgmstream->loop_count = data->layers[0]->loop_count;
/* looping is aplied as internal loops. normally each layer does it automatically, but
* just calls do_loop manually to behave a bit more controlled, and so that manual
* calls to do_loop work (used in seek_vgmstream) */
if (data->layers[layer]->loop_flag) { /* mixing looping and non-looping layers is allowed */
data->layers[layer]->current_sample = data->layers[layer]->loop_end_sample; /* forces do loop */
vgmstream_do_loop(data->layers[layer]); /* guaranteed to work should loop_layout be called */
}
}
}
/* could always call seek_vgmstream, but it's not optimized to loop non-config vgmstreams ATM */
vgmstream->current_sample = loop_sample;
vgmstream->samples_into_block = loop_sample;
}

View File

@ -59,6 +59,7 @@ segmented_layout_data* init_layout_segmented(int segment_count);
int setup_layout_segmented(segmented_layout_data* data);
void free_layout_segmented(segmented_layout_data* data);
void reset_layout_segmented(segmented_layout_data* data);
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample);
VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment);
void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
@ -66,6 +67,7 @@ layered_layout_data* init_layout_layered(int layer_count);
int setup_layout_layered(layered_layout_data* data);
void free_layout_layered(layered_layout_data* data);
void reset_layout_layered(layered_layout_data* data);
void loop_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample);
VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data);
#endif

View File

@ -12,11 +12,10 @@
* 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, loop_samples_skip = 0;
int samples_written = 0, samples_this_block;
segmented_layout_data* data = vgmstream->layout_data;
int use_internal_buffer = 0;
/* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */
if (vgmstream->channels != data->input_channels) {
use_internal_buffer = 1;
@ -27,81 +26,49 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
return;
}
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
//VGM_LOG("segment decode start: cur=%i, this=%i, into=%i\n", data->current_segment, samples_this_block, vgmstream->samples_into_block);
while (samples_written < sample_count) {
int samples_to_do;
int samples_this_segment = vgmstream_get_samples(data->segments[data->current_segment]);
if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) {
int segment, loop_segment, total_samples;
/* handle looping (loop_layout has been called below, changes segments/state) */
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
continue;
}
/* handle looping by finding loop segment and loop_start inside that segment */
loop_segment = 0;
total_samples = 0;
while (total_samples < vgmstream->num_samples) {
int32_t segment_samples = vgmstream_get_samples(data->segments[loop_segment]);
/* detect segment change and restart (after loop, but before decode, to allow looping to kick in) */
if (vgmstream->samples_into_block == samples_this_block) {
data->current_segment++;
if (vgmstream->loop_current_sample >= total_samples &&
vgmstream->loop_current_sample < total_samples + segment_samples) {
loop_samples_skip = vgmstream->loop_current_sample - total_samples;
break; /* loop_start falls within loop_segment's samples */
}
total_samples += segment_samples;
loop_segment++;
/* could happen on last segment trying to decode more samples */
if (data->current_segment >= data->segment_count) {
VGM_LOG("SEGMENTED: wrong next segment\n");
break;
}
if (loop_segment == data->segment_count) {
VGM_LOG("SEGMENTED: can't find loop segment\n");
loop_segment = 0;
}
data->current_segment = loop_segment;
/* loops can span multiple segments */
for (segment = loop_segment; segment < data->segment_count; segment++) {
reset_vgmstream(data->segments[segment]);
}
/* in case of looping spanning multiple segments */
reset_vgmstream(data->segments[data->current_segment]);
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
vgmstream->samples_into_block = 0;
continue;
}
samples_to_do = get_vgmstream_samples_to_do(samples_this_segment, sample_count, vgmstream);
samples_to_do = get_vgmstream_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 > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */
samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER;
/* looping: discard until actual start */
if (loop_samples_skip > 0) {
if (samples_to_do > loop_samples_skip)
samples_to_do = loop_samples_skip;
}
/* detect segment change and restart */
if (samples_to_do == 0) {
data->current_segment++;
/* could happen on last segment trying to decode more samples */
if (data->current_segment >= data->segment_count) {
break;
}
reset_vgmstream(data->segments[data->current_segment]);
vgmstream->samples_into_block = 0;
continue;
}
render_vgmstream(
use_internal_buffer ?
data->buffer :
&outbuf[samples_written * data->output_channels],
data->buffer : &outbuf[samples_written * data->output_channels],
samples_to_do,
data->segments[data->current_segment]);
if (loop_samples_skip > 0) {
loop_samples_skip -= samples_to_do;
vgmstream->samples_into_block += samples_to_do;
continue;
}
if (use_internal_buffer) {
int s;
for (s = 0; s < samples_to_do * data->output_channels; s++) {
@ -115,6 +82,33 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
}
}
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
int segment, total_samples;
segmented_layout_data* data = vgmstream->layout_data;
segment = 0;
total_samples = 0;
while (total_samples < vgmstream->num_samples) {
int32_t segment_samples = vgmstream_get_samples(data->segments[segment]);
/* find if loop falls within segment's samples */
if (loop_sample >= total_samples && loop_sample < total_samples + segment_samples) {
int32_t loop_relative = loop_sample - total_samples;
seek_vgmstream(data->segments[segment], loop_relative);
data->current_segment = segment;
vgmstream->samples_into_block = loop_relative;
break;
}
total_samples += segment_samples;
segment++;
}
if (segment == data->segment_count) {
VGM_LOG("SEGMENTED: can't find loop segment\n");
}
}
segmented_layout_data* init_layout_segmented(int segment_count) {
segmented_layout_data* data = NULL;

View File

@ -513,14 +513,24 @@ void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
int is_looped = vgmstream->loop_flag || vgmstream->loop_target > 0; /* loop target disabled loop flag during decode */
/* will decode and loop until seek sample, but slower */
//todo apply same loop logic as below, or pretend we have play_forever + settings?
if (!vgmstream->config_enabled) {
//todo same but ignore play duration or play_position
//;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);
return;
}
//todo optimize layout looping with seek_vgmstream
//todo could improve performance bit if hit_loop wasn't lost when calling reset
//todo wrong seek with ignore fade
//todo wrong seek with ignore fade, also for layered layers (pass count to force loop + layers)
/* seeking to requested sample normally means decoding and discarding up to that point (from