mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-18 15:54:05 +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) {
|
||||
VGMSTREAM_TAGS* tags = NULL;
|
||||
STREAMFILE* sf_tags = NULL;
|
||||
@ -484,20 +472,6 @@ int main(int argc, char** argv) {
|
||||
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 */
|
||||
while (cfg.play_forever) {
|
||||
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 */
|
||||
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
||||
@ -555,10 +543,6 @@ int main(int argc, char** argv) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
reset_vgmstream(vgmstream);
|
||||
|
||||
apply_seek(buf, vgmstream, cfg.seek_samples);
|
||||
|
||||
/* slap on a .wav header */
|
||||
if (!cfg.decode_only) {
|
||||
uint8_t wav_buf[0x100];
|
||||
@ -572,6 +556,11 @@ int main(int argc, char** argv) {
|
||||
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
|
||||
}
|
||||
|
||||
|
||||
reset_vgmstream(vgmstream);
|
||||
|
||||
seek_vgmstream(vgmstream, cfg.seek_samples);
|
||||
|
||||
/* decode */
|
||||
for (i = 0; i < len_samples; i += SAMPLE_BUFFER_SIZE) {
|
||||
int to_get = SAMPLE_BUFFER_SIZE;
|
||||
|
147
src/render.c
147
src/render.c
@ -5,6 +5,48 @@
|
||||
#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) {
|
||||
return vgmstream->config.play_forever;
|
||||
}
|
||||
@ -89,68 +131,71 @@ static void setup_state_processing(VGMSTREAM* vgmstream) {
|
||||
//todo fade time also set to samples
|
||||
|
||||
/* samples before all decode */
|
||||
ps->pad_begin_left = pc->pad_begin;
|
||||
ps->pad_begin_duration = pc->pad_begin;
|
||||
|
||||
/* removed samples from first decode */
|
||||
ps->trim_begin_left = pc->trim_begin;
|
||||
ps->trim_begin_duration = pc->trim_begin;
|
||||
|
||||
/* main samples part */
|
||||
ps->body_left = 0;
|
||||
ps->body_duration = 0;
|
||||
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) {
|
||||
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_left += vgmstream->loop_start_sample;
|
||||
ps->body_duration += vgmstream->loop_start_sample;
|
||||
if (pc->ignore_fade) {
|
||||
ps->body_left += (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->loop_end_sample - vgmstream->loop_start_sample) * (int)loop_count;
|
||||
ps->body_duration += (vgmstream->num_samples - vgmstream->loop_end_sample);
|
||||
}
|
||||
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 {
|
||||
ps->body_left += vgmstream->num_samples;
|
||||
ps->body_duration += vgmstream->num_samples;
|
||||
}
|
||||
|
||||
/* samples from some modify body */
|
||||
if (pc->trim_begin)
|
||||
ps->body_left -= pc->trim_begin;
|
||||
ps->body_duration -= pc->trim_begin;
|
||||
if (pc->trim_end)
|
||||
ps->body_left -= pc->trim_end;
|
||||
ps->body_duration -= pc->trim_end;
|
||||
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 */
|
||||
if (pc->fade_time && vgmstream->loop_flag)
|
||||
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) */
|
||||
ps->pad_end_start = ps->fade_start + ps->fade_left;
|
||||
ps->pad_end_left = pc->pad_end;
|
||||
ps->pad_end_duration = pc->pad_end;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* values too big can overflow, just ignore */
|
||||
if (ps->pad_begin_left < 0)
|
||||
ps->pad_begin_left = 0;
|
||||
if (ps->body_left < 0)
|
||||
ps->body_left = 0;
|
||||
if (ps->fade_left < 0)
|
||||
ps->fade_left = 0;
|
||||
if (ps->pad_end_left < 0)
|
||||
ps->pad_end_left = 0;
|
||||
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;
|
||||
@ -428,3 +473,55 @@ int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
|
||||
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 output_channels;
|
||||
|
||||
int32_t pad_begin_duration;
|
||||
int32_t pad_begin_left;
|
||||
int32_t trim_begin_duration;
|
||||
int32_t trim_begin_left;
|
||||
int32_t body_left;
|
||||
int32_t body_duration;
|
||||
int32_t fade_duration;
|
||||
int32_t fade_start;
|
||||
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_start;
|
||||
|
||||
int32_t play_duration; /* total samples that the stream lasts (after applying all config) */
|
||||
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 */
|
||||
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.
|
||||
* Will always be null-terminated if length > 0 */
|
||||
void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length);
|
||||
|
Loading…
x
Reference in New Issue
Block a user