mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-19 00:04:04 +01:00
Add internal simple seeking
This commit is contained in:
parent
079218dca8
commit
4ec6acb4a6
@ -301,18 +301,6 @@ static void apply_config(VGMSTREAM* vgmstream, cli_config* cfg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void apply_seek(sample_t* buf, VGMSTREAM* vgmstream, int len_samples) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
|
||||||
int to_get = SAMPLE_BUFFER_SIZE;
|
|
||||||
if (i + SAMPLE_BUFFER_SIZE > len_samples)
|
|
||||||
to_get = len_samples - i;
|
|
||||||
|
|
||||||
render_vgmstream(buf, to_get, vgmstream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_tags(cli_config* cfg) {
|
static void print_tags(cli_config* cfg) {
|
||||||
VGMSTREAM_TAGS* tags = NULL;
|
VGMSTREAM_TAGS* tags = NULL;
|
||||||
STREAMFILE* sf_tags = NULL;
|
STREAMFILE* sf_tags = NULL;
|
||||||
@ -484,20 +472,6 @@ int main(int argc, char** argv) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* slap on a .wav header */
|
|
||||||
if (!cfg.decode_only) {
|
|
||||||
uint8_t wav_buf[0x100];
|
|
||||||
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
|
|
||||||
size_t bytes_done;
|
|
||||||
|
|
||||||
bytes_done = make_wav_header(wav_buf,0x100,
|
|
||||||
len_samples, vgmstream->sample_rate, channels_write,
|
|
||||||
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end);
|
|
||||||
|
|
||||||
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* decode forever */
|
/* decode forever */
|
||||||
while (cfg.play_forever) {
|
while (cfg.play_forever) {
|
||||||
int to_get = SAMPLE_BUFFER_SIZE;
|
int to_get = SAMPLE_BUFFER_SIZE;
|
||||||
@ -515,7 +489,21 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
apply_seek(buf, vgmstream, cfg.seek_samples);
|
/* slap on a .wav header */
|
||||||
|
if (!cfg.decode_only) {
|
||||||
|
uint8_t wav_buf[0x100];
|
||||||
|
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
|
||||||
|
size_t bytes_done;
|
||||||
|
|
||||||
|
bytes_done = make_wav_header(wav_buf,0x100,
|
||||||
|
len_samples, vgmstream->sample_rate, channels_write,
|
||||||
|
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end);
|
||||||
|
|
||||||
|
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
seek_vgmstream(vgmstream, cfg.seek_samples);
|
||||||
|
|
||||||
/* decode */
|
/* decode */
|
||||||
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
||||||
@ -555,10 +543,6 @@ int main(int argc, char** argv) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_vgmstream(vgmstream);
|
|
||||||
|
|
||||||
apply_seek(buf, vgmstream, cfg.seek_samples);
|
|
||||||
|
|
||||||
/* slap on a .wav header */
|
/* slap on a .wav header */
|
||||||
if (!cfg.decode_only) {
|
if (!cfg.decode_only) {
|
||||||
uint8_t wav_buf[0x100];
|
uint8_t wav_buf[0x100];
|
||||||
@ -572,6 +556,11 @@ int main(int argc, char** argv) {
|
|||||||
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
|
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
reset_vgmstream(vgmstream);
|
||||||
|
|
||||||
|
seek_vgmstream(vgmstream, cfg.seek_samples);
|
||||||
|
|
||||||
/* decode */
|
/* decode */
|
||||||
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
||||||
int to_get = SAMPLE_BUFFER_SIZE;
|
int to_get = SAMPLE_BUFFER_SIZE;
|
||||||
|
147
src/render.c
147
src/render.c
@ -5,6 +5,48 @@
|
|||||||
#include "plugins.h"
|
#include "plugins.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* 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) {
|
int vgmstream_get_play_forever(VGMSTREAM* vgmstream) {
|
||||||
return vgmstream->config.play_forever;
|
return vgmstream->config.play_forever;
|
||||||
}
|
}
|
||||||
@ -89,68 +131,71 @@ static void setup_state_processing(VGMSTREAM* vgmstream) {
|
|||||||
//todo fade time also set to samples
|
//todo fade time also set to samples
|
||||||
|
|
||||||
/* samples before all decode */
|
/* samples before all decode */
|
||||||
ps->pad_begin_left = pc->pad_begin;
|
ps->pad_begin_duration = pc->pad_begin;
|
||||||
|
|
||||||
/* removed samples from first decode */
|
/* removed samples from first decode */
|
||||||
ps->trim_begin_left = pc->trim_begin;
|
ps->trim_begin_duration = pc->trim_begin;
|
||||||
|
|
||||||
/* main samples part */
|
/* main samples part */
|
||||||
ps->body_left = 0;
|
ps->body_duration = 0;
|
||||||
if (pc->body_time) {
|
if (pc->body_time) {
|
||||||
ps->body_left += pc->body_time; /* whether it loops or not */
|
ps->body_duration += pc->body_time; /* whether it loops or not */
|
||||||
}
|
}
|
||||||
else if (vgmstream->loop_flag) {
|
else if (vgmstream->loop_flag) {
|
||||||
double loop_count = 1.0;
|
double loop_count = 1.0;
|
||||||
if (pc->loop_count_set) /* may set 0.0 on purpose I guess */
|
if (pc->loop_count_set) /* may set 0.0 on purpose I guess */
|
||||||
loop_count = pc->loop_count;
|
loop_count = pc->loop_count;
|
||||||
|
|
||||||
ps->body_left += vgmstream->loop_start_sample;
|
ps->body_duration += vgmstream->loop_start_sample;
|
||||||
if (pc->ignore_fade) {
|
if (pc->ignore_fade) {
|
||||||
ps->body_left += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * (int)loop_count;
|
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * (int)loop_count;
|
||||||
ps->body_left += (vgmstream->num_samples - vgmstream->loop_end_sample);
|
ps->body_duration += (vgmstream->num_samples - vgmstream->loop_end_sample);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ps->body_left += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count;
|
ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ps->body_left += vgmstream->num_samples;
|
ps->body_duration += vgmstream->num_samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* samples from some modify body */
|
/* samples from some modify body */
|
||||||
if (pc->trim_begin)
|
if (pc->trim_begin)
|
||||||
ps->body_left -= pc->trim_begin;
|
ps->body_duration -= pc->trim_begin;
|
||||||
if (pc->trim_end)
|
if (pc->trim_end)
|
||||||
ps->body_left -= pc->trim_end;
|
ps->body_duration -= pc->trim_end;
|
||||||
if (pc->fade_delay && vgmstream->loop_flag)
|
if (pc->fade_delay && vgmstream->loop_flag)
|
||||||
ps->body_left += pc->fade_delay * vgmstream->sample_rate;
|
ps->body_duration += pc->fade_delay * vgmstream->sample_rate;
|
||||||
|
|
||||||
/* samples from fade part */
|
/* samples from fade part */
|
||||||
if (pc->fade_time && vgmstream->loop_flag)
|
if (pc->fade_time && vgmstream->loop_flag)
|
||||||
ps->fade_duration = pc->fade_time * vgmstream->sample_rate;
|
ps->fade_duration = pc->fade_time * vgmstream->sample_rate;
|
||||||
ps->fade_start = ps->pad_begin_left + ps->body_left;
|
|
||||||
ps->fade_left = ps->fade_duration;
|
|
||||||
|
|
||||||
/* samples from last part (anything beyond this is empty, unless play forever is set) */
|
/* samples from last part (anything beyond this is empty, unless play forever is set) */
|
||||||
ps->pad_end_start = ps->fade_start + ps->fade_left;
|
ps->pad_end_duration = pc->pad_end;
|
||||||
ps->pad_end_left = pc->pad_end;
|
|
||||||
|
|
||||||
/* final count */
|
/* final count */
|
||||||
ps->play_duration = ps->pad_begin_left + ps->body_left + ps->fade_left + ps->pad_end_left;
|
ps->play_duration = ps->pad_begin_duration + ps->body_duration + ps->fade_duration + ps->pad_end_duration;
|
||||||
ps->play_position = 0;
|
ps->play_position = 0;
|
||||||
|
|
||||||
/* values too big can overflow, just ignore */
|
/* values too big can overflow, just ignore */
|
||||||
if (ps->pad_begin_left < 0)
|
if (ps->pad_begin_duration < 0)
|
||||||
ps->pad_begin_left = 0;
|
ps->pad_begin_duration = 0;
|
||||||
if (ps->body_left < 0)
|
if (ps->body_duration < 0)
|
||||||
ps->body_left = 0;
|
ps->body_duration = 0;
|
||||||
if (ps->fade_left < 0)
|
if (ps->fade_duration < 0)
|
||||||
ps->fade_left = 0;
|
ps->fade_duration = 0;
|
||||||
if (ps->pad_end_left < 0)
|
if (ps->pad_end_duration < 0)
|
||||||
ps->pad_end_left = 0;
|
ps->pad_end_duration = 0;
|
||||||
if (ps->play_duration < 0)
|
if (ps->play_duration < 0)
|
||||||
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) */
|
/* other info (updated once mixing is enabled) */
|
||||||
ps->input_channels = vgmstream->channels;
|
ps->input_channels = vgmstream->channels;
|
||||||
@ -428,3 +473,55 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream)
|
|||||||
|
|
||||||
return samples_done;
|
return samples_done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample) {
|
||||||
|
play_state_t* ps = &vgmstream->pstate;
|
||||||
|
sample_t tmpbuf[0x40000]; /* big-ish buffer as less calls = better */
|
||||||
|
int buf_samples = 0x40000 / vgmstream->channels; /* no need to apply mixing */
|
||||||
|
int i;
|
||||||
|
//int play_duration = ps->play_duration;
|
||||||
|
int play_pos = ps->play_position;
|
||||||
|
int decode_samples = 0;
|
||||||
|
//int decode_pos = 0;
|
||||||
|
|
||||||
|
if (!vgmstream->config_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//todo allow seeking past max for loop forever
|
||||||
|
/* seeking past max for loop forever */
|
||||||
|
//if (seek_sample > play_duration)
|
||||||
|
// seek_sample = play_duration;
|
||||||
|
|
||||||
|
/* find which relative decode sample corresponds to seek sample
|
||||||
|
* (if it falls in a pad or loop it can be simplified rather than decoding the full thing) */
|
||||||
|
//todo calculate
|
||||||
|
|
||||||
|
if (seek_sample < play_pos) {
|
||||||
|
reset_vgmstream(vgmstream);
|
||||||
|
decode_samples = seek_sample;
|
||||||
|
}
|
||||||
|
else if (seek_sample > play_pos) {
|
||||||
|
decode_samples = (seek_sample - play_pos);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (i = 0; i < decode_samples; i += buf_samples) {
|
||||||
|
int to_get = buf_samples;
|
||||||
|
if (i + buf_samples > decode_samples)
|
||||||
|
to_get = decode_samples - i;
|
||||||
|
|
||||||
|
render_vgmstream(tmpbuf, to_get, vgmstream);
|
||||||
|
|
||||||
|
//todo
|
||||||
|
//render_layout(tmpbuf, to_get, vgmstream);
|
||||||
|
///* no mixing needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* adjust positions */
|
||||||
|
//todo
|
||||||
|
}
|
||||||
|
@ -825,14 +825,17 @@ typedef struct {
|
|||||||
int input_channels;
|
int input_channels;
|
||||||
int output_channels;
|
int output_channels;
|
||||||
|
|
||||||
|
int32_t pad_begin_duration;
|
||||||
int32_t pad_begin_left;
|
int32_t pad_begin_left;
|
||||||
|
int32_t trim_begin_duration;
|
||||||
int32_t trim_begin_left;
|
int32_t trim_begin_left;
|
||||||
int32_t body_left;
|
int32_t body_duration;
|
||||||
int32_t fade_duration;
|
int32_t fade_duration;
|
||||||
int32_t fade_start;
|
|
||||||
int32_t fade_left;
|
int32_t fade_left;
|
||||||
int32_t pad_end_start;
|
int32_t fade_start;
|
||||||
|
int32_t pad_end_duration;
|
||||||
int32_t pad_end_left;
|
int32_t pad_end_left;
|
||||||
|
int32_t pad_end_start;
|
||||||
|
|
||||||
int32_t play_duration; /* total samples that the stream lasts (after applying all config) */
|
int32_t play_duration; /* total samples that the stream lasts (after applying all config) */
|
||||||
int32_t play_position; /* absolute sample where stream is */
|
int32_t play_position; /* absolute sample where stream is */
|
||||||
@ -1108,6 +1111,9 @@ int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double
|
|||||||
/* Decode data into sample buffer. Returns < sample_count on stream end */
|
/* Decode data into sample buffer. Returns < sample_count on stream end */
|
||||||
int render_vgmstream(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
int render_vgmstream(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||||
|
|
||||||
|
/* Seek to sample position (next render starts from that point). Use only with internal config set */
|
||||||
|
void seek_vgmstream(VGMSTREAM* vgmstream, int32_t seek_sample);
|
||||||
|
|
||||||
/* Write a description of the stream into array pointed by desc, which must be length bytes long.
|
/* 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 */
|
* Will always be null-terminated if length > 0 */
|
||||||
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length);
|
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user