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