mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-01 12:27:56 +01:00
Make segmented/layered loop/behave a bit more standard for seeking
This commit is contained in:
parent
ef64005418
commit
9216971505
16
src/decode.c
16
src/decode.c
@ -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 */
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
16
src/render.c
16
src/render.c
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user