diff --git a/src/libvgmstream.vcproj b/src/libvgmstream.vcproj index 39c1470f..91e65c47 100644 --- a/src/libvgmstream.vcproj +++ b/src/libvgmstream.vcproj @@ -185,6 +185,10 @@ RelativePath=".\mixing.c" > + + diff --git a/src/libvgmstream.vcxproj b/src/libvgmstream.vcxproj index 6f0bfc37..d3eafd9e 100644 --- a/src/libvgmstream.vcxproj +++ b/src/libvgmstream.vcxproj @@ -247,6 +247,7 @@ + diff --git a/src/libvgmstream.vcxproj.filters b/src/libvgmstream.vcxproj.filters index d7bd2c54..a1dff74a 100644 --- a/src/libvgmstream.vcxproj.filters +++ b/src/libvgmstream.vcxproj.filters @@ -271,6 +271,9 @@ Source Files + + Source Files + Source Files diff --git a/src/player.c b/src/player.c new file mode 100644 index 00000000..c4af77e6 --- /dev/null +++ b/src/player.c @@ -0,0 +1,216 @@ +#include "vgmstream.h" +#include "plugins.h" + + +int vgmstream_get_play_forever(VGMSTREAM* vgmstream) { + return vgmstream->config.play_forever; +} +int32_t vgmstream_get_samples(VGMSTREAM* vgmstream) { + if (!vgmstream->config_set) + return vgmstream->num_samples; + return vgmstream->pstate.play_duration; +} + +/*****************************************************************************/ + +static void setup_state_vgmstream(VGMSTREAM* vgmstream) { + play_state_t* ps = &vgmstream->pstate; + play_config_t* pc = &vgmstream->config; + + + ps->pad_begin_duration = pc->pad_begin; + ps->pad_begin_start = 0; + ps->pad_begin_end = ps->pad_begin_start + ps->pad_begin_duration; + + ps->trim_begin_duration = pc->trim_begin; + ps->trim_begin_start = ps->pad_begin_end; + ps->trim_begin_end = ps->trim_begin_start + ps->trim_begin_duration; + + /* main samples part */ + ps->body_duration = 0; + if (pc->target_time) { + ps->body_duration += pc->target_time; /* wheter it loops or not */ + } + else if (vgmstream->loop_flag) { + ps->body_duration += vgmstream->loop_start_sample; + if (pc->ignore_fade) { + ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * (int)pc->loop_count; + ps->body_duration += (vgmstream->num_samples - vgmstream->loop_end_sample); + } + else { + ps->body_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * pc->loop_count; + } + } + else { + ps->body_duration += vgmstream->num_samples; + } + + /* no need to apply in real time */ + if (pc->trim_end) + ps->body_duration -= pc->trim_end; + if (pc->fade_delay && vgmstream->loop_flag) + ps->body_duration += pc->fade_delay * vgmstream->sample_rate; + if (ps->body_duration < 0) /* ? */ + ps->body_duration = 0; + ps->body_start = ps->trim_begin_end; + ps->body_end = ps->body_start + ps->body_duration; + + if (pc->fade_time && vgmstream->loop_flag) + ps->fade_duration = pc->fade_time * vgmstream->sample_rate; + ps->fade_start = ps->body_end; + ps->fade_end = ps->fade_start + ps->fade_duration; + + ps->pad_end_duration = pc->pad_end; + ps->pad_end_start = ps->fade_end; + ps->pad_end_end = ps->pad_end_start + ps->pad_end_duration; + + /* final count */ + ps->play_duration = ps->pad_end_end; + ps->play_position = 0; + + /* other info */ + vgmstream_mixing_enable(vgmstream, 0, &ps->input_channels, &ps->output_channels); + + VGM_LOG("**%i, %i, %i\n", ps->body_duration, ps->fade_duration, ps->play_duration);//todo +} + +static void load_player_config(VGMSTREAM* vgmstream, play_config_t* def, vgmstream_cfg_t* vcfg) { + def->play_forever = vcfg->play_forever; + def->ignore_loop = vcfg->ignore_loop; + def->force_loop = vcfg->force_loop; + def->really_force_loop = vcfg->really_force_loop; + def->ignore_fade = vcfg->ignore_fade; + def->loop_count = vcfg->loop_times; //todo loop times + def->fade_delay = vcfg->fade_delay; + def->fade_time = vcfg->fade_period; //todo loop period +} + +static void load_internal_config(VGMSTREAM* vgmstream, play_config_t* def, play_config_t* tcfg) { + /* loop limit: txtp #L > txtp #l > player #L > player #l */ + if (tcfg->play_forever) { + def->play_forever = 1; + def->ignore_loop = 0; + } + if (tcfg->loop_count_set) { + def->ignore_loop = 0; + def->loop_count = tcfg->loop_count; + if (!tcfg->play_forever) + def->play_forever = 0; + } + + /* fade priority: #F > #f, #d */ + if (tcfg->ignore_fade) { + def->ignore_fade = 1; + } + if (tcfg->fade_delay_set) { + def->fade_delay = tcfg->fade_delay; + } + if (tcfg->fade_time_set) { + def->fade_time = tcfg->fade_time; + } + + /* loop priority: #i > #e > #E */ + if (tcfg->really_force_loop) { + def->ignore_loop = 0; + def->force_loop = 0; + def->really_force_loop = 1; + } + if (tcfg->force_loop) { + def->ignore_loop = 0; + def->force_loop = 1; + def->really_force_loop = 0; + } + if (tcfg->ignore_loop) { + def->ignore_loop = 1; + def->force_loop = 0; + def->really_force_loop = 0; + } +} + + +void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* vcfg) { + play_config_t defs = {0}; + play_config_t* def = &defs; /* for convenience... */ + play_config_t* tcfg = &vgmstream->config; + + + load_player_config(vgmstream, def, vcfg); + + if (!vcfg->disable_config_override) + load_internal_config(vgmstream, def, tcfg); + + + /* apply final config */ + if (def->really_force_loop) { + vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples); + } + if (def->force_loop && !vgmstream->loop_flag) { + vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples); + } + if (def->ignore_loop) { + vgmstream_force_loop(vgmstream, 0, 0,0); + } + + /* remove non-compatible options */ + if (!vcfg->allow_play_forever) + def->play_forever = 0; + + if (!vgmstream->loop_flag) { + def->play_forever = 0; + } + if (def->play_forever) { + def->ignore_fade = 0; + } + + /* loop N times, but also play stream end instead of fading out */ + if (def->ignore_fade) { + vgmstream_set_loop_target(vgmstream, (int)def->loop_count); + def->fade_time = 0; + def->fade_delay = 0; + } + + + /* copy final config back */ + *tcfg = *def; + vgmstream->config_set = 1; + + setup_state_vgmstream(vgmstream); + setup_vgmstream(vgmstream); /* save current config for reset */ +} + +/*****************************************************************************/ + +void fade_vgmstream(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { + play_state_t* ps = &vgmstream->pstate; + play_config_t* pc = &vgmstream->config; + + if (!ps->fade_duration || pc->play_forever) + return; + if (ps->play_position + samples_done < ps->fade_start) + return; + if (ps->play_position > ps->fade_end) + return; + + { + int s, ch; + int channels = ps->output_channels; + int sample_start, fade_pos; + + if (ps->play_position < ps->fade_start) { + sample_start = samples_done - (ps->play_position + samples_done - ps->fade_start); + fade_pos = 0; + } + else { + sample_start = 0; + fade_pos = ps->play_position - ps->fade_start; + } + + //TODO: use delta fadedness to improve performance? + for (s = sample_start; s < samples_done; 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; + } + } + } +} diff --git a/src/plugins.h b/src/plugins.h index 9133eae8..db1ab55c 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -5,6 +5,25 @@ #define _PLUGINS_H_ #include "streamfile.h" +//todo rename to api.h once public enough + + +#if 0 +/* define standard C param call and name mangling (to avoid __stdcall / .defs) */ +//#define VGMSTREAM_CALL __cdecl //needed? + +/* define external function types (during compilation) */ +#if defined(VGMSTREAM_EXPORT) + #define VGMSTREAM_API __declspec(dllexport) /* when exporting/creating vgmstream DLL */ +#elif defined(VGMSTREAM_IMPORT) + #define VGMSTREAM_API __declspec(dllimport) /* when importing/linking vgmstream DLL */ +#else + #define VGMSTREAM_API /* nothing, internal/default */ +#endif + +//VGMSTREAM_API void VGMSTREAM_CALL vgmstream_function(void); +#endif + /* ****************************************** */ /* CONTEXT: simplifies plugin code */ @@ -21,42 +40,129 @@ typedef struct { /* returns if vgmstream can parse file by extension */ int vgmstream_ctx_is_valid(const char* filename, vgmstream_ctx_valid_cfg *cfg); -#if 0 - -/* opaque player state */ -typedef struct VGMSTREAM_CTX VGMSTREAM_CTX; typedef struct { - //... -} VGMSTREAM_CTX_INFO; + int allow_play_forever; + int disable_config_override; -VGMSTREAM_CTX* vgmstream_ctx_init(...); + /* song mofidiers */ + int play_forever; /* keeps looping forever (needs loop points) */ + int ignore_loop; /* ignores loops points */ + int force_loop; /* enables full loops (0..samples) if file doesn't have loop points */ + int really_force_loop; /* forces full loops even if file has loop points */ + int ignore_fade; /* don't fade after N loops */ -VGMSTREAM_CTX* vgmstream_ctx_format_check(...); -VGMSTREAM_CTX* vgmstream_ctx_set_format_whilelist(...); -VGMSTREAM_CTX* vgmstream_ctx_set_format_blacklist(...); + /* song processing */ + double loop_times; /* target loops */ + double fade_delay; /* fade delay after target loops */ + double fade_period; /* fade time after target loops */ -VGMSTREAM_CTX* vgmstream_ctx_set_file(...); + //int downmix; /* max number of channels allowed (0=disable downmix) */ -VGMSTREAM_CTX* vgmstream_ctx_get_config(...); +} vgmstream_cfg_t; -VGMSTREAM_CTX* vgmstream_ctx_set_config(...); +// WARNING: these are not stable and may change anytime without notice +void vgmstream_apply_config(VGMSTREAM* vgmstream, vgmstream_cfg_t* pcfg); +int32_t vgmstream_get_samples(VGMSTREAM* vgmstream); +int vgmstream_get_play_forever(VGMSTREAM* vgmstream); -VGMSTREAM_CTX* vgmstream_ctx_get_buffer(...); -VGMSTREAM_CTX* vgmstream_ctx_get_info(...); -VGMSTREAM_CTX* vgmstream_ctx_describe(...); +#if 0 +//possible future public/opaque API -VGMSTREAM_CTX* vgmstream_ctx_get_title(...); +/* opaque player state */ +//#define VGMSTREAM_CTX_VERSION 1 +typedef struct VGMSTREAM_CTX VGMSTREAM_CTX; -VGMSTREAM_CTX* vgmstream_ctx_get_tagfile(...); -VGMSTREAM_CTX* vgmstream_ctx_play(...); +/* Setups base vgmstream player context. */ +VGMSTREAM_CTX* vgmstream_init_ctx(void); -VGMSTREAM_CTX* vgmstream_ctx_seek(...); -VGMSTREAM_CTX* vgmstream_ctx_close(...); +/* Sets default config, that will be applied to song on open (some formats like TXTP may override + * these settings). + * May only be called without song loaded (before _open or after _close), otherwise ignored. */ +void vgmstream_set_config(VGMSTREAM_CTX* vctx, VGMSTREAM_CFG* vcfg); + +void vgmstream_set_buffer(VGMSTREAM_CTX* vctx, int samples, int max_samples); + +/* Opens a new STREAMFILE to play. Returns < 0 on error when the file isn't recogniced. + * If file has subsongs, first open usually loads first subsong. get_info then can be used to check + * whether file has more subsongs (total_subsongs > 1), and call others. + * */ +int vgmstream_open(STREAMFILE* sf); +int vgmstream_open_subsong(STREAMFILE* sf, int subsong); + +typedef struct { + const int channels; + const int sample_rate; + + const int sample_count; /* file's samples (not final duration) */ + const int loop_start_sample; + const int loop_end_sample; + const int loop_flag; + + const int current_subsong; /* 0=not set, N=loaded subsong N */ + const int total_subsongs; /* 0=format has no subsongs, N=has N subsongs */ + const int file_bitrate; /* file's average bitrate */ + //const int codec_bitrate; /* codec's average bitrate */ + + /* descriptions */ + //const char* codec; + //const char* layout; + //const char* metadata; + + //int type; /* 0=pcm16, 1=float32, always interleaved: [0]=ch0, [1]=ch1 ... */ +} VGMSTREAM_INFO; + +/* Get info from current song. */ +void vgmstream_ctx_get_info(VGMSTREAM_CTX* vctx, VGMSTREAM_INFO* vinfo); + + +/* Gets final time based on config and current song. If config is set to "play forever" + * this still returns final time based on config as a reference. Returns > 0 on success. */ +int32_t vgmstream_get_total_time(VGMSTREAM_CTX* vctx); +double vgmstream_get_total_samples(VGMSTREAM_CTX* vctx); + + +/* Gets current position within song. When "play forever" is set, it'll clamp results to total_time. */ +int32_t vgmstream_get_current_time(VGMSTREAM_CTX* vctx); +double vgmstream_get_current_samples(VGMSTREAM_CTX* vctx); + + +/* Seeks to position */ +VGMSTREAM_CTX* vgmstream_seek_absolute_sample(VGMSTREAM_CTX* vctx, int32_t sample); +VGMSTREAM_CTX* vgmstream_seek_absolute_time(VGMSTREAM_CTX* vctx, double time); +VGMSTREAM_CTX* vgmstream_seek_current_sample(VGMSTREAM_CTX* vctx, int32_t sample); +VGMSTREAM_CTX* vgmstream_seek_current_time(VGMSTREAM_CTX* vctx, double time); + + +/* Closes current song. */ +void vgmstream_close(VGMSTREAM_CTX* vctx); + +/* Frees vgmstream context. */ +void vgmstream_free_ctx(VGMSTREAM_CTX* vctx); + + +/* Converts samples. returns number of rendered samples, or <=0 if no more + * samples left (will fill buffer with silence) */ +int vgmstream_play(VGMSTREAM_CTX* vctx); + + +#if 0 +void vgmstream_get_buffer(...); + +void vgmstream_format_check(...); +void vgmstream_set_format_whilelist(...); +void vgmstream_set_format_blacklist(...); + +const char* vgmstream_describe(...); + +const char* vgmstream_get_title(...); + +VGMSTREAM_TAGS* vgmstream_get_tagfile(...); +#endif #endif