diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index 26a689da8..6a3e7da46 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -372,7 +372,7 @@ 834F7EC52C70A786003AC386 /* api_internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 834F7EA12C70A786003AC386 /* api_internal.h */; }; 834F7EC62C70A786003AC386 /* api_libsf.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EA22C70A786003AC386 /* api_libsf.c */; }; 834F7EC72C70A786003AC386 /* api_tags.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EA32C70A786003AC386 /* api_tags.c */; }; - 834F7EC82C70A786003AC386 /* config.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EA42C70A786003AC386 /* config.c */; }; + 834F7EC82C70A786003AC386 /* play_config.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EA42C70A786003AC386 /* play_config.c */; }; 834F7EC92C70A786003AC386 /* decode.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EA52C70A786003AC386 /* decode.c */; }; 834F7ECA2C70A786003AC386 /* decode.h in Headers */ = {isa = PBXBuildFile; fileRef = 834F7EA62C70A786003AC386 /* decode.h */; }; 834F7ECB2C70A786003AC386 /* info.c in Sources */ = {isa = PBXBuildFile; fileRef = 834F7EA72C70A786003AC386 /* info.c */; }; @@ -454,6 +454,8 @@ 835B9B932730BF2D00F87EE3 /* lpcm_shade.c in Sources */ = {isa = PBXBuildFile; fileRef = 835B9B8E2730BF2D00F87EE3 /* lpcm_shade.c */; }; 835C883622CC17BE001B4B3F /* bwav.c in Sources */ = {isa = PBXBuildFile; fileRef = 835C883122CC17BD001B4B3F /* bwav.c */; }; 835C883722CC17BE001B4B3F /* ogg_vorbis_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 835C883522CC17BE001B4B3F /* ogg_vorbis_streamfile.h */; }; + 835DF7032C79ABB50008814A /* sbuf.c in Sources */ = {isa = PBXBuildFile; fileRef = 835DF7022C79ABB50008814A /* sbuf.c */; }; + 835DF7052C79AED70008814A /* play_state.c in Sources */ = {isa = PBXBuildFile; fileRef = 835DF7042C79AED70008814A /* play_state.c */; }; 836C052B23F62F1A00FA07C7 /* libatrac9.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 835FC6C623F62AEF006960FA /* libatrac9.framework */; }; 836C052C23F62F3100FA07C7 /* libatrac9.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 835FC6C623F62AEF006960FA /* libatrac9.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 836DF622298F83F400CD0580 /* cri_keys.h in Headers */ = {isa = PBXBuildFile; fileRef = 836DF61F298F83F400CD0580 /* cri_keys.h */; }; @@ -581,7 +583,6 @@ 836F700B18BDC2190095E648 /* ps2_wad.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ECF18BDC2190095E648 /* ps2_wad.c */; }; 836F700C18BDC2190095E648 /* wb.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED018BDC2190095E648 /* wb.c */; }; 836F700D18BDC2190095E648 /* ps2_wmus.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED118BDC2190095E648 /* ps2_wmus.c */; }; - 836F700F18BDC2190095E648 /* ps2_xa30.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED318BDC2190095E648 /* ps2_xa30.c */; }; 836F701518BDC2190095E648 /* sndp.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED918BDC2190095E648 /* sndp.c */; }; 836F701E18BDC2190095E648 /* redspark.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EE218BDC2190095E648 /* redspark.c */; }; 836F701F18BDC2190095E648 /* riff.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EE318BDC2190095E648 /* riff.c */; }; @@ -1282,7 +1283,7 @@ 834F7EA12C70A786003AC386 /* api_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = api_internal.h; sourceTree = ""; }; 834F7EA22C70A786003AC386 /* api_libsf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = api_libsf.c; sourceTree = ""; }; 834F7EA32C70A786003AC386 /* api_tags.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = api_tags.c; sourceTree = ""; }; - 834F7EA42C70A786003AC386 /* config.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = config.c; sourceTree = ""; }; + 834F7EA42C70A786003AC386 /* play_config.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = play_config.c; sourceTree = ""; }; 834F7EA52C70A786003AC386 /* decode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = decode.c; sourceTree = ""; }; 834F7EA62C70A786003AC386 /* decode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = decode.h; sourceTree = ""; }; 834F7EA72C70A786003AC386 /* info.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = info.c; sourceTree = ""; }; @@ -1363,6 +1364,8 @@ 835B9B8E2730BF2D00F87EE3 /* lpcm_shade.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lpcm_shade.c; sourceTree = ""; }; 835C883122CC17BD001B4B3F /* bwav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bwav.c; sourceTree = ""; }; 835C883522CC17BE001B4B3F /* ogg_vorbis_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ogg_vorbis_streamfile.h; sourceTree = ""; }; + 835DF7022C79ABB50008814A /* sbuf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sbuf.c; sourceTree = ""; }; + 835DF7042C79AED70008814A /* play_state.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = play_state.c; sourceTree = ""; }; 835FC6C123F62AEE006960FA /* libatrac9.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libatrac9.xcodeproj; path = ../libatrac9/libatrac9.xcodeproj; sourceTree = ""; }; 836DF61F298F83F400CD0580 /* cri_keys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cri_keys.h; sourceTree = ""; }; 836DF620298F83F400CD0580 /* bitstream_msb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bitstream_msb.h; sourceTree = ""; }; @@ -1491,7 +1494,6 @@ 836F6ECF18BDC2190095E648 /* ps2_wad.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_wad.c; sourceTree = ""; }; 836F6ED018BDC2190095E648 /* wb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = wb.c; sourceTree = ""; }; 836F6ED118BDC2190095E648 /* ps2_wmus.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_wmus.c; sourceTree = ""; }; - 836F6ED318BDC2190095E648 /* ps2_xa30.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_xa30.c; sourceTree = ""; }; 836F6ED918BDC2190095E648 /* sndp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sndp.c; sourceTree = ""; }; 836F6EE218BDC2190095E648 /* redspark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = redspark.c; sourceTree = ""; }; 836F6EE318BDC2190095E648 /* riff.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = riff.c; sourceTree = ""; }; @@ -1973,7 +1975,6 @@ 834F7EA12C70A786003AC386 /* api_internal.h */, 834F7EA22C70A786003AC386 /* api_libsf.c */, 834F7EA32C70A786003AC386 /* api_tags.c */, - 834F7EA42C70A786003AC386 /* config.c */, 834F7EA52C70A786003AC386 /* decode.c */, 834F7EA62C70A786003AC386 /* decode.h */, 834F7EA72C70A786003AC386 /* info.c */, @@ -1986,10 +1987,13 @@ 834F7EAE2C70A786003AC386 /* mixing_macros.c */, 834F7EAF2C70A786003AC386 /* mixing.c */, 834F7EB02C70A786003AC386 /* mixing.h */, + 834F7EA42C70A786003AC386 /* play_config.c */, + 835DF7042C79AED70008814A /* play_state.c */, 834F7EB12C70A786003AC386 /* plugins.c */, 834F7EB22C70A786003AC386 /* plugins.h */, 834F7EB32C70A786003AC386 /* render.c */, 834F7EB42C70A786003AC386 /* render.h */, + 835DF7022C79ABB50008814A /* sbuf.c */, 834F7EB52C70A786003AC386 /* sbuf.h */, 834F7EB62C70A786003AC386 /* seek.c */, 834F7EB72C70A786003AC386 /* streamfile_api.c */, @@ -2476,7 +2480,6 @@ 836F6ECC18BDC2190095E648 /* ps2_vms.c */, 836F6ECF18BDC2190095E648 /* ps2_wad.c */, 836F6ED118BDC2190095E648 /* ps2_wmus.c */, - 836F6ED318BDC2190095E648 /* ps2_xa30.c */, 8315868326F586E200803A3A /* psb.c */, 837CEAE823487F2B00E62A4A /* psf.c */, 836F6E5518BDC2180095E648 /* psnd.c */, @@ -3105,6 +3108,7 @@ 83A5F75F198DF021009AF94C /* bfwav.c in Sources */, 83FBB1792A4FF71B00CD0580 /* rstm_rockstar.c in Sources */, 8339B326280FDF4B0076F74B /* text_reader.c in Sources */, + 835DF7052C79AED70008814A /* play_state.c in Sources */, 836F702018BDC2190095E648 /* rkv.c in Sources */, 834FE0F4215C79ED000A5D3D /* wsi.c in Sources */, 83D26A7A26E66D98001A9475 /* adp_wildfire.c in Sources */, @@ -3268,6 +3272,7 @@ 836F702418BDC2190095E648 /* rwsd.c in Sources */, 834F7DBE2C7093EA003AC386 /* ea_xas_decoder.c in Sources */, 834F7DBD2C7093EA003AC386 /* ea_xa_decoder.c in Sources */, + 835DF7032C79ABB50008814A /* sbuf.c in Sources */, 830EBE142004656E0023AA10 /* ktss.c in Sources */, 836F6F6618BDC2190095E648 /* aax.c in Sources */, 8306B0BC20984552000302D4 /* blocked_vs.c in Sources */, @@ -3285,7 +3290,6 @@ 834FE101215C79ED000A5D3D /* csmp.c in Sources */, 8306B0A720984552000302D4 /* blocked_xvas.c in Sources */, 8349A9101FE6258200E26435 /* ea_eaac.c in Sources */, - 836F700F18BDC2190095E648 /* ps2_xa30.c in Sources */, 835B9B912730BF2D00F87EE3 /* lopu_fb.c in Sources */, 8319017C28F67EE100B70711 /* miniz.c in Sources */, 8346D97B25BF838C00D1A8B0 /* ktac.c in Sources */, @@ -3525,7 +3529,7 @@ 834F7D312C709231003AC386 /* ea_sbk.c in Sources */, 836F6F9518BDC2190095E648 /* kraw.c in Sources */, 834F7E172C709A1D003AC386 /* ea_schl_map_mpf_mus.c in Sources */, - 834F7EC82C70A786003AC386 /* config.c in Sources */, + 834F7EC82C70A786003AC386 /* play_config.c in Sources */, 836F6FB718BDC2190095E648 /* ngc_ssm.c in Sources */, 8306B0E920984590000302D4 /* opus.c in Sources */, 83709E051ECBC1A4005C03D3 /* ghs.c in Sources */, diff --git a/Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c b/Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c index d7339008f..97b8da858 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c +++ b/Frameworks/vgmstream/vgmstream/src/base/api_decode_play.c @@ -51,7 +51,7 @@ static void update_decoder_info(libvgmstream_priv_t* priv, int samples_done) { priv->dec.done = priv->decode_done; } -LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib) { +LIBVGMSTREAM_API int libvgmstream_render(libvgmstream_t* lib) { if (!lib || !lib->priv) return LIBVGMSTREAM_ERROR_GENERIC; @@ -85,7 +85,7 @@ LIBVGMSTREAM_API int libvgmstream_fill(libvgmstream_t* lib, void* buf, int buf_s return LIBVGMSTREAM_ERROR_GENERIC; if (priv->buf.consumed >= priv->buf.samples) { - int err = libvgmstream_play(lib); + int err = libvgmstream_render(lib); if (err < 0) return err; } diff --git a/Frameworks/vgmstream/vgmstream/src/base/api_helpers.c b/Frameworks/vgmstream/vgmstream/src/base/api_helpers.c index 9bc83ab0e..42b498d21 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/api_helpers.c +++ b/Frameworks/vgmstream/vgmstream/src/base/api_helpers.c @@ -88,4 +88,8 @@ LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_ti return LIBVGMSTREAM_OK; } +LIBVGMSTREAM_API bool libvgmstream_is_virtual_filename(const char* filename) { + return vgmstream_is_virtual_filename(filename); +} + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/decode.c b/Frameworks/vgmstream/vgmstream/src/base/decode.c index 4ea276798..4177741dc 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/decode.c +++ b/Frameworks/vgmstream/vgmstream/src/base/decode.c @@ -4,6 +4,7 @@ #include "decode.h" #include "mixing.h" #include "plugins.h" +#include "sbuf.h" /* custom codec handling, not exactly "decode" stuff but here to simplify adding new codecs */ @@ -824,18 +825,19 @@ bool decode_uses_internal_offset_updates(VGMSTREAM* vgmstream) { return vgmstream->coding_type == coding_MS_IMA || vgmstream->coding_type == coding_MS_IMA_mono; } -/* Decode samples into the buffer. Assume that we have written samples_written into the +/* Decode samples into the buffer. Assume that we have written samples_filled into the * buffer already, and we have samples_to_do consecutive samples ahead of us (won't call * more than one frame if configured above to do so). * Called by layouts since they handle samples written/to_do */ -void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_do, sample_t* buffer) { +void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_do, sample_t* buffer) { int ch; - buffer += samples_written * vgmstream->channels; /* passed externally to simplify I guess */ + buffer += samples_filled * vgmstream->channels; /* passed externally to simplify I guess */ + //samples_to_do -= samples_filled; /* pre-adjusted */ switch (vgmstream->coding_type) { case coding_SILENCE: - memset(buffer, 0, samples_to_do * vgmstream->channels * sizeof(sample_t)); + sbuf_silence(buffer, samples_to_do, vgmstream->channels, 0); break; case coding_CRI_ADX: @@ -1601,10 +1603,10 @@ int decode_get_samples_to_do(int samples_this_block, int samples_per_frame, VGMS return samples_to_do; } -/* Detect loop start and save values, or detect loop end and restore (loop back). - * Returns 1 if loop was done. */ -int decode_do_loop(VGMSTREAM* vgmstream) { - /*if (!vgmstream->loop_flag) return 0;*/ + +/* Detect loop start and save values, or detect loop end and restore (loop back). Returns true if loop was done. */ +bool decode_do_loop(VGMSTREAM* vgmstream) { + //if (!vgmstream->loop_flag) return false; /* is this the loop end? = new loop, continue from loop_start_sample */ if (vgmstream->current_sample == vgmstream->loop_end_sample) { @@ -1613,8 +1615,8 @@ int decode_do_loop(VGMSTREAM* vgmstream) { * (only needed with the "play stream end after looping N times" option enabled) */ vgmstream->loop_count++; if (vgmstream->loop_target && vgmstream->loop_target == vgmstream->loop_count) { - vgmstream->loop_flag = 0; /* could be improved but works ok, will be restored on resets */ - return 0; + vgmstream->loop_flag = false; /* could be improved but works ok, will be restored on resets */ + return false; } /* against everything I hold sacred, preserve adpcm history before looping for certain types */ @@ -1623,8 +1625,7 @@ int decode_do_loop(VGMSTREAM* vgmstream) { vgmstream->meta_type == meta_DSP_CSTR || vgmstream->coding_type == coding_PSX || vgmstream->coding_type == coding_PSX_badflags) { - int ch; - for (ch = 0; ch < vgmstream->channels; ch++) { + for (int ch = 0; ch < vgmstream->channels; ch++) { vgmstream->loop_ch[ch].adpcm_history1_16 = vgmstream->ch[ch].adpcm_history1_16; vgmstream->loop_ch[ch].adpcm_history2_16 = vgmstream->ch[ch].adpcm_history2_16; vgmstream->loop_ch[ch].adpcm_history1_32 = vgmstream->ch[ch].adpcm_history1_32; @@ -1633,12 +1634,13 @@ int decode_do_loop(VGMSTREAM* vgmstream) { } //TODO: improve - /* loop codecs that need special handling, usually: - * - on hit_loop, current offset is copied to loop_ch[].offset - * - some codecs will overwrite loop_ch[].offset with a custom value - * - loop_ch[] is copied to ch[] (with custom value) - * - then codec will use ch[]'s offset - * regular codecs may use copied loop_ch[] offset without issue */ + /* codecs with codec_data that decode_seek need special handling, usually: + * - during decode, codec uses vgmstream->ch[].offset to handle current offset + * - on hit_loop, current offset is auto-copied to vgmstream->loop_ch[].offset + * - decode_seek codecs may overwrite vgmstream->loop_ch[].offset with a custom value (such as start_offset) + * - vgmstream->loop_ch[] is copied below to vgmstream->ch[] (with the newly assigned custom value) + * - then codec will use vgmstream->ch[].offset during decode + * regular codecs will use copied vgmstream->loop_ch[].offset without issue */ decode_seek(vgmstream); /* restore! */ @@ -1649,7 +1651,7 @@ int decode_do_loop(VGMSTREAM* vgmstream) { vgmstream->current_block_samples = vgmstream->loop_block_samples; vgmstream->current_block_offset = vgmstream->loop_block_offset; vgmstream->next_block_offset = vgmstream->loop_next_block_offset; - //vgmstream->pstate = vgmstream->lstate; /* play state is applied over loops */ + vgmstream->full_block_size = vgmstream->loop_full_block_size; /* loop layouts (after restore, in case layout needs state manipulations) */ switch(vgmstream->layout_type) { @@ -1663,24 +1665,30 @@ int decode_do_loop(VGMSTREAM* vgmstream) { break; } - return 1; /* looped */ + /* play state is applied over loops and stream decoding, so it's not restored on loops */ + //vgmstream->pstate = vgmstream->lstate; + + return true; /* has looped */ } /* is this the loop start? save if we haven't saved yet (right when first loop starts) */ if (!vgmstream->hit_loop && vgmstream->current_sample == vgmstream->loop_start_sample) { /* save! */ - memcpy(vgmstream->loop_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); + memcpy(vgmstream->loop_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL) * vgmstream->channels); vgmstream->loop_current_sample = vgmstream->current_sample; vgmstream->loop_samples_into_block = vgmstream->samples_into_block; vgmstream->loop_block_size = vgmstream->current_block_size; vgmstream->loop_block_samples = vgmstream->current_block_samples; vgmstream->loop_block_offset = vgmstream->current_block_offset; vgmstream->loop_next_block_offset = vgmstream->next_block_offset; - //vgmstream->lstate = vgmstream->pstate; /* play state is applied over loops */ + vgmstream->loop_full_block_size = vgmstream->full_block_size; + + /* play state is applied over loops and stream decoding, so it's not saved on loops */ + //vgmstream->lstate = vgmstream->pstate; vgmstream->hit_loop = true; /* info that loop is now ready to use */ } - return 0; /* not looped */ + return false; /* has not looped */ } diff --git a/Frameworks/vgmstream/vgmstream/src/base/decode.h b/Frameworks/vgmstream/vgmstream/src/base/decode.h index e456ce3e2..f959a464e 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/decode.h +++ b/Frameworks/vgmstream/vgmstream/src/base/decode.h @@ -7,12 +7,12 @@ void decode_free(VGMSTREAM* vgmstream); void decode_seek(VGMSTREAM* vgmstream); void decode_reset(VGMSTREAM* vgmstream); -/* Decode samples into the buffer. Assume that we have written samples_written into the +/* Decode samples into the buffer. Assume that we have written samples_filled into the * buffer already, and we have samples_to_do consecutive samples ahead of us. */ -void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_do, sample_t* buffer); +void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_do, sample_t* buffer); -/* Detect loop start and save values, or detect loop end and restore (loop back). Returns 1 if loop was done. */ -int decode_do_loop(VGMSTREAM* vgmstream); +/* Detect loop start and save values, or detect loop end and restore (loop back). Returns true if loop was done. */ +bool decode_do_loop(VGMSTREAM* vgmstream); /* Calculate number of consecutive samples to do (taking into account stopping for loop start and end) */ int decode_get_samples_to_do(int samples_this_block, int samples_per_frame, VGMSTREAM* vgmstream); diff --git a/Frameworks/vgmstream/vgmstream/src/base/info.c b/Frameworks/vgmstream/vgmstream/src/base/info.c index 8929d3fa8..b1b94b17f 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/info.c +++ b/Frameworks/vgmstream/vgmstream/src/base/info.c @@ -6,6 +6,8 @@ #include "../util/channel_mappings.h" #include "../util/sf_utils.h" +#define TEMPSIZE (256+32) + /*******************************************************************************/ /* TEXT */ /*******************************************************************************/ @@ -21,7 +23,6 @@ static void describe_get_time(int32_t samples, int sample_rate, double* p_time_m /* 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) { -#define TEMPSIZE (256+32) char temp[TEMPSIZE]; double time_mm, time_ss; diff --git a/Frameworks/vgmstream/vgmstream/src/base/mixer_ops_common.c b/Frameworks/vgmstream/vgmstream/src/base/mixer_ops_common.c index 3d57cd2b8..cabf27ded 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/mixer_ops_common.c +++ b/Frameworks/vgmstream/vgmstream/src/base/mixer_ops_common.c @@ -115,7 +115,7 @@ void mixer_op_downmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) { sbuf_tmp[ch] = sbuf[ch]; /* copy untouched channels */ } - for (int ch = op->ch_dst; ch < max_channels; ch++) { + for (int ch = op->ch_dst; ch < max_channels - 1; ch++) { sbuf_tmp[ch] = sbuf[ch + 1]; /* 'pull' dropped channels back */ } diff --git a/Frameworks/vgmstream/vgmstream/src/base/config.c b/Frameworks/vgmstream/vgmstream/src/base/play_config.c similarity index 71% rename from Frameworks/vgmstream/vgmstream/src/base/config.c rename to Frameworks/vgmstream/vgmstream/src/base/play_config.c index 86ff2b765..1f577fa07 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/config.c +++ b/Frameworks/vgmstream/vgmstream/src/base/play_config.c @@ -13,50 +13,50 @@ static void copy_time(bool* dst_flag, int32_t* dst_time, double* dst_time_s, boo *dst_time_s = *src_time_s; } -//todo reuse in txtp? +// config that has been set internally via TXTP static void load_default_config(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; + def->play_forever = true; + def->ignore_loop = false; } if (tcfg->loop_count_set) { def->loop_count = tcfg->loop_count; - def->loop_count_set = 1; - def->ignore_loop = 0; + def->loop_count_set = true; + def->ignore_loop = false; if (!tcfg->play_forever) - def->play_forever = 0; + def->play_forever = false; } /* fade priority: #F > #f, #d */ if (tcfg->ignore_fade) { - def->ignore_fade = 1; + def->ignore_fade = true; } if (tcfg->fade_delay_set) { def->fade_delay = tcfg->fade_delay; - def->fade_delay_set = 1; + def->fade_delay_set = true; } if (tcfg->fade_time_set) { def->fade_time = tcfg->fade_time; - def->fade_time_set = 1; + def->fade_time_set = true; } /* loop priority: #i > #e > #E (respect player's ignore too) */ if (tcfg->really_force_loop) { - //def->ignore_loop = 0; - def->force_loop = 0; - def->really_force_loop = 1; + //def->ignore_loop = false; + def->force_loop = false; + def->really_force_loop = true; } if (tcfg->force_loop) { - //def->ignore_loop = 0; - def->force_loop = 1; - def->really_force_loop = 0; + //def->ignore_loop = false; + def->force_loop = true; + def->really_force_loop = false; } if (tcfg->ignore_loop) { - def->ignore_loop = 1; - def->force_loop = 0; - def->really_force_loop = 0; + def->ignore_loop = true; + def->force_loop = false; + def->really_force_loop = false; } copy_time(&def->pad_begin_set, &def->pad_begin, &def->pad_begin_s, &tcfg->pad_begin_set, &tcfg->pad_begin, &tcfg->pad_begin_s); @@ -69,7 +69,8 @@ static void load_default_config(play_config_t* def, play_config_t* tcfg) { def->is_txtp = tcfg->is_txtp; } -static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) { +/* config that has been set externally by plugins */ +static void load_external_config(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; @@ -77,31 +78,32 @@ static void load_player_config(play_config_t* def, vgmstream_cfg_t* vcfg) { def->ignore_fade = vcfg->ignore_fade; def->loop_count = vcfg->loop_count; - def->loop_count_set = 1; + def->loop_count_set = true; def->fade_delay = vcfg->fade_delay; - def->fade_delay_set = 1; + def->fade_delay_set = true; def->fade_time = vcfg->fade_time; - def->fade_time_set = 1; + def->fade_time_set = true; } +/* apply play config to vgmstream */ 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(def, vcfg); - def->config_set = 1; + load_external_config(def, vcfg); + def->config_set = true; if (!vcfg->disable_config_override) load_default_config(def, tcfg); if (!vcfg->allow_play_forever) - def->play_forever = 0; + def->play_forever = false; /* copy final config back */ *tcfg = *def; vgmstream->config_enabled = def->config_set; - setup_state_vgmstream(vgmstream); + setup_vgmstream_play_state(vgmstream); } diff --git a/Frameworks/vgmstream/vgmstream/src/base/play_state.c b/Frameworks/vgmstream/vgmstream/src/base/play_state.c new file mode 100644 index 000000000..91cfb37ce --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/base/play_state.c @@ -0,0 +1,185 @@ +#include "../vgmstream.h" +//#include "../layout/layout.h" +//#include "render.h" +//#include "decode.h" +//#include "mixing.h" +//#include "plugins.h" + + + +int vgmstream_get_play_forever(VGMSTREAM* vgmstream) { + return vgmstream->config.play_forever; +} + +void vgmstream_set_play_forever(VGMSTREAM* vgmstream, int enabled) { + /* sometimes we need to enable/disable right before playback + * (play config is left untouched, should mix ok as this flag is only used during + * render, while config is always prepared as if play forever wasn't enabled) */ + vgmstream->config.play_forever = enabled; + setup_vgmstream(vgmstream); /* update config */ +} + +int32_t vgmstream_get_samples(VGMSTREAM* vgmstream) { + if (!vgmstream->config_enabled || !vgmstream->config.config_set) + return vgmstream->num_samples; + return vgmstream->pstate.play_duration; +} + +/* calculate samples based on player's config */ +int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM* vgmstream) { + if (vgmstream->loop_flag) { + if (vgmstream->loop_target == (int)looptimes) { /* set externally, as this function is info-only */ + /* Continue playing the file normally after looping, instead of fading. + * Most files cut abruply after the loop, but some do have proper endings. + * With looptimes = 1 this option should give the same output vs loop disabled */ + int loop_count = (int)looptimes; /* no half loops allowed */ + return vgmstream->loop_start_sample + + (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count + + (vgmstream->num_samples - vgmstream->loop_end_sample); + } + else { + return vgmstream->loop_start_sample + + (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * looptimes + + (fadedelayseconds + fadeseconds) * vgmstream->sample_rate; + } + } + else { + return vgmstream->num_samples; + } +} + +/*****************************************************************************/ + +/* apply config like forced loops */ +static void setup_state_modifiers(VGMSTREAM* vgmstream) { + play_config_t* pc = &vgmstream->config; + + /* apply final config */ + if (pc->really_force_loop) { + vgmstream_force_loop(vgmstream, true, 0, vgmstream->num_samples); + } + if (pc->force_loop && !vgmstream->loop_flag) { + vgmstream_force_loop(vgmstream, true, 0, vgmstream->num_samples); + } + if (pc->ignore_loop) { + vgmstream_force_loop(vgmstream, false, 0, 0); + } + + if (!vgmstream->loop_flag) { + pc->play_forever = false; + } + if (pc->play_forever) { + pc->ignore_fade = false; + } + + + /* loop N times, but also play stream end instead of fading out */ + if (pc->ignore_fade) { + vgmstream_set_loop_target(vgmstream, (int)pc->loop_count); + pc->fade_time = 0; + pc->fade_delay = 0; + } +} + +/* apply config like trims */ +static void setup_state_processing(VGMSTREAM* vgmstream) { + play_state_t* ps = &vgmstream->pstate; + play_config_t* pc = &vgmstream->config; + double sample_rate = vgmstream->sample_rate; + + /* time to samples */ + if (pc->pad_begin_s) + pc->pad_begin = pc->pad_begin_s * sample_rate; + if (pc->pad_end_s) + pc->pad_end = pc->pad_end_s * sample_rate; + if (pc->trim_begin_s) + pc->trim_begin = pc->trim_begin_s * sample_rate; + if (pc->trim_end_s) + pc->trim_end = pc->trim_end_s * sample_rate; + if (pc->body_time_s) + pc->body_time = pc->body_time_s * sample_rate; + //todo fade time also set to samples + + /* samples before all decode */ + ps->pad_begin_duration = pc->pad_begin; + + /* removed samples from first decode */ + ps->trim_begin_duration = pc->trim_begin; + + /* main samples part */ + ps->body_duration = 0; + if (pc->body_time) { + 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_duration += vgmstream->loop_start_sample; + if (pc->ignore_fade) { + 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_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count; + } + } + else { + ps->body_duration += vgmstream->num_samples; + } + + /* samples from some modify body */ + if (pc->trim_begin) + ps->body_duration -= pc->trim_begin; + 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; + + /* samples from fade part */ + if (pc->fade_time && vgmstream->loop_flag) + ps->fade_duration = pc->fade_time * vgmstream->sample_rate; + + /* samples from last part (anything beyond this is empty, unless play forever is set) */ + ps->pad_end_duration = pc->pad_end; + + /* final count */ + 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_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; + ps->output_channels = vgmstream->channels; +} + +/* apply play config to internal state */ +void setup_vgmstream_play_state(VGMSTREAM* vgmstream) { + if (!vgmstream->config.config_set) + return; + + setup_state_modifiers(vgmstream); + setup_state_processing(vgmstream); + + /* save new config for resets */ + setup_vgmstream(vgmstream); +} diff --git a/Frameworks/vgmstream/vgmstream/src/base/render.c b/Frameworks/vgmstream/vgmstream/src/base/render.c index a881eadb5..f34234901 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/render.c +++ b/Frameworks/vgmstream/vgmstream/src/base/render.c @@ -3,7 +3,7 @@ #include "render.h" #include "decode.h" #include "mixing.h" -#include "plugins.h" +#include "sbuf.h" /* VGMSTREAM RENDERING @@ -47,179 +47,6 @@ * 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; -} - -void vgmstream_set_play_forever(VGMSTREAM* vgmstream, int enabled) { - /* sometimes we need to enable/disable right before playback - * (play config is left untouched, should mix ok as this flag is only used during - * render, while config is always prepared as if play forever wasn't enabled) */ - vgmstream->config.play_forever = enabled; - setup_vgmstream(vgmstream); /* update config */ -} - -int32_t vgmstream_get_samples(VGMSTREAM* vgmstream) { - if (!vgmstream->config_enabled || !vgmstream->config.config_set) - return vgmstream->num_samples; - return vgmstream->pstate.play_duration; -} - -/* calculate samples based on player's config */ -int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM* vgmstream) { - if (vgmstream->loop_flag) { - if (vgmstream->loop_target == (int)looptimes) { /* set externally, as this function is info-only */ - /* Continue playing the file normally after looping, instead of fading. - * Most files cut abruply after the loop, but some do have proper endings. - * With looptimes = 1 this option should give the same output vs loop disabled */ - int loop_count = (int)looptimes; /* no half loops allowed */ - return vgmstream->loop_start_sample - + (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count - + (vgmstream->num_samples - vgmstream->loop_end_sample); - } - else { - return vgmstream->loop_start_sample - + (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * looptimes - + (fadedelayseconds + fadeseconds) * vgmstream->sample_rate; - } - } - else { - return vgmstream->num_samples; - } -} - -/*****************************************************************************/ - -static void setup_state_modifiers(VGMSTREAM* vgmstream) { - play_config_t* pc = &vgmstream->config; - - /* apply final config */ - if (pc->really_force_loop) { - vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples); - } - if (pc->force_loop && !vgmstream->loop_flag) { - vgmstream_force_loop(vgmstream, 1, 0,vgmstream->num_samples); - } - if (pc->ignore_loop) { - vgmstream_force_loop(vgmstream, 0, 0,0); - } - - if (!vgmstream->loop_flag) { - pc->play_forever = 0; - } - if (pc->play_forever) { - pc->ignore_fade = 0; - } - - - /* loop N times, but also play stream end instead of fading out */ - if (pc->ignore_fade) { - vgmstream_set_loop_target(vgmstream, (int)pc->loop_count); - pc->fade_time = 0; - pc->fade_delay = 0; - } -} - -static void setup_state_processing(VGMSTREAM* vgmstream) { - play_state_t* ps = &vgmstream->pstate; - play_config_t* pc = &vgmstream->config; - double sample_rate = vgmstream->sample_rate; - - /* time to samples */ - if (pc->pad_begin_s) - pc->pad_begin = pc->pad_begin_s * sample_rate; - if (pc->pad_end_s) - pc->pad_end = pc->pad_end_s * sample_rate; - if (pc->trim_begin_s) - pc->trim_begin = pc->trim_begin_s * sample_rate; - if (pc->trim_end_s) - pc->trim_end = pc->trim_end_s * sample_rate; - if (pc->body_time_s) - pc->body_time = pc->body_time_s * sample_rate; - //todo fade time also set to samples - - /* samples before all decode */ - ps->pad_begin_duration = pc->pad_begin; - - /* removed samples from first decode */ - ps->trim_begin_duration = pc->trim_begin; - - /* main samples part */ - ps->body_duration = 0; - if (pc->body_time) { - 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_duration += vgmstream->loop_start_sample; - if (pc->ignore_fade) { - 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_duration += (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count; - } - } - else { - ps->body_duration += vgmstream->num_samples; - } - - /* samples from some modify body */ - if (pc->trim_begin) - ps->body_duration -= pc->trim_begin; - 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; - - /* samples from fade part */ - if (pc->fade_time && vgmstream->loop_flag) - ps->fade_duration = pc->fade_time * vgmstream->sample_rate; - - /* samples from last part (anything beyond this is empty, unless play forever is set) */ - ps->pad_end_duration = pc->pad_end; - - /* final count */ - 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_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; - ps->output_channels = vgmstream->channels; -} - -void setup_state_vgmstream(VGMSTREAM* vgmstream) { - if (!vgmstream->config.config_set) - return; - - setup_state_modifiers(vgmstream); - setup_state_processing(vgmstream); - setup_vgmstream(vgmstream); /* save current config for reset */ -} - /*****************************************************************************/ void render_free(VGMSTREAM* vgmstream) { @@ -247,11 +74,13 @@ void render_reset(VGMSTREAM* vgmstream) { } int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { + if (sample_count == 0) + return 0; /* current_sample goes between loop points (if looped) or up to max samples, * must detect beyond that decoders would encounter garbage data */ - /* not ">=" to allow layouts to loop in some cases when == happens */ + // nothing to decode: return blank buf (not ">=" to allow layouts to loop in some cases when == happens) if (vgmstream->current_sample > vgmstream->num_samples) { int channels = vgmstream->channels; @@ -277,7 +106,7 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { case layout_blocked_str_snds: case layout_blocked_ws_aud: case layout_blocked_dec: - case layout_blocked_vs: + case layout_blocked_vs_mh: case layout_blocked_mul: case layout_blocked_gsb: case layout_blocked_xvas: @@ -317,6 +146,7 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { break; } + // decode past stream samples: blank rest of buf if (vgmstream->current_sample > vgmstream->num_samples) { int channels = vgmstream->channels; int32_t excess, decoded; @@ -333,85 +163,123 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { return sample_count; } +/*****************************************************************************/ + +typedef struct { + //sbuf_t sbuf; + int16_t* tmpbuf; + int samples_to_do; + int samples_done; +} render_helper_t; -static void render_trim(VGMSTREAM* vgmstream) { - sample_t* tmpbuf = vgmstream->tmpbuf; - size_t tmpbuf_size = vgmstream->tmpbuf_size; - int32_t buf_samples = tmpbuf_size / vgmstream->channels; /* base channels, no need to apply mixing */ - while (vgmstream->pstate.trim_begin_left) { - int to_do = vgmstream->pstate.trim_begin_left; +// consumes samples from decoder +static void play_op_trim(VGMSTREAM* vgmstream, render_helper_t* renderer) { + play_state_t* ps = &vgmstream->pstate; + if (!ps->trim_begin_left) + return; + if (!renderer->samples_to_do) + return; + + // simpler using external buf? + //sample_t* tmpbuf = vgmstream->tmpbuf; + //size_t tmpbuf_size = vgmstream->tmpbuf_size; + //int32_t buf_samples = tmpbuf_size / vgmstream->channels; /* base channels, no need to apply mixing */ + sample_t* tmpbuf = renderer->tmpbuf; + int buf_samples = renderer->samples_to_do; + + while (ps->trim_begin_left) { + int to_do = ps->trim_begin_left; if (to_do > buf_samples) to_do = buf_samples; render_layout(tmpbuf, to_do, vgmstream); /* no mixing */ - vgmstream->pstate.trim_begin_left -= to_do; + ps->trim_begin_left -= to_do; } } -static int render_pad_begin(VGMSTREAM* vgmstream, sample_t* buf, int samples_to_do) { - int channels = vgmstream->pstate.output_channels; - int to_do = vgmstream->pstate.pad_begin_left; - if (to_do > samples_to_do) - to_do = samples_to_do; +// adds empty samples to buf +static void play_op_pad_begin(VGMSTREAM* vgmstream, render_helper_t* renderer) { + play_state_t* ps = &vgmstream->pstate; + if (!ps->pad_begin_left) + return; + //if (ps->play_position > ps->play_begin_duration) //implicit + // return; + + int channels = ps->output_channels; + int buf_samples = renderer->samples_to_do; + + int to_do = ps->pad_begin_left; + if (to_do > buf_samples) + to_do = buf_samples; - memset(buf, 0, to_do * sizeof(sample_t) * channels); - vgmstream->pstate.pad_begin_left -= to_do; + memset(renderer->tmpbuf, 0, to_do * sizeof(sample_t) * channels); + ps->pad_begin_left -= to_do; - return to_do; + renderer->samples_done += to_do; + renderer->samples_to_do -= to_do; + renderer->tmpbuf += to_do * channels; /* as if mixed */ } -static int render_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) { +// fades (modifies volumes) part of buf +static void play_op_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { + play_config_t* pc = &vgmstream->config; play_state_t* ps = &vgmstream->pstate; - //play_config_t* pc = &vgmstream->config; - //if (!ps->fade_left || pc->play_forever) - // return; - //if (ps->play_position + samples_done < ps->fade_start) - // return; + if (pc->play_forever || !ps->fade_left) + return; + if (ps->play_position + samples_done < ps->fade_start) + return; - { - int s, ch, start, fade_pos; - int channels = ps->output_channels; - int32_t to_do = ps->fade_left; + int start, fade_pos; + int channels = ps->output_channels; + int32_t to_do = ps->fade_left; - if (ps->play_position < ps->fade_start) { - start = samples_left - (ps->play_position + samples_left - ps->fade_start); - fade_pos = 0; - } - else { - start = 0; - fade_pos = ps->play_position - ps->fade_start; - } + if (ps->play_position < ps->fade_start) { + start = samples_done - (ps->play_position + samples_done - ps->fade_start); + fade_pos = 0; + } + else { + start = 0; + fade_pos = ps->play_position - ps->fade_start; + } - if (to_do > samples_left - start) - to_do = samples_left - start; + if (to_do > samples_done - start) + to_do = samples_done - start; - //TODO: use delta fadedness to improve performance? - for (s = start; s < start + to_do; 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; - } + //TODO: use delta fadedness to improve performance? + for (int s = start; s < start + to_do; s++, fade_pos++) { + double fadedness = (double)(ps->fade_duration - fade_pos) / ps->fade_duration; + for (int ch = 0; ch < channels; ch++) { + buf[s * channels + ch] = (sample_t)(buf[s*channels + ch] * fadedness); } + } - ps->fade_left -= to_do; + ps->fade_left -= to_do; - /* next samples after fade end would be pad end/silence, so we can just memset */ - memset(buf + (start + to_do) * channels, 0, (samples_left - to_do - start) * sizeof(sample_t) * channels); - return start + to_do; - } + /* next samples after fade end would be pad end/silence, so we can just memset */ + memset(buf + (start + to_do) * channels, 0, (samples_done - to_do - start) * sizeof(sample_t) * channels); } -static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) { +// adds null samples after decode +// pad-end works like fades, where part of buf is samples and part is padding (blank) +// (beyond pad end normally is silence, except with segmented layout) +static int play_op_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) { + play_config_t* pc = &vgmstream->config; play_state_t* ps = &vgmstream->pstate; + + if (pc->play_forever) + return 0; + if (samples_done == 0) + return 0; + if (ps->play_position + samples_done < ps->pad_end_start) + return 0; + int channels = vgmstream->pstate.output_channels; int skip = 0; int32_t to_do; - /* pad end works like fades, where part of buf samples and part padding (silent), - * calc exact totals (beyond pad end normally is silence, except with segmented layout) */ if (ps->play_position < ps->pad_end_start) { skip = ps->pad_end_start - ps->play_position; to_do = ps->pad_end_duration; @@ -421,92 +289,86 @@ static int render_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_left) to_do = (ps->pad_end_start + ps->pad_end_duration) - ps->play_position; } - if (to_do > samples_left - skip) - to_do = samples_left - skip; + if (to_do > samples_done - skip) + to_do = samples_done - skip; memset(buf + (skip * channels), 0, to_do * sizeof(sample_t) * channels); return skip + to_do; } - -/* Decode data into sample buffer. Controls the "external" part of the decoding, - * while layout/decode control the "internal" part. */ -int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { +// clamp final play_position + done samples. Probably doesn't matter, but just in case. +static void play_adjust_totals(VGMSTREAM* vgmstream, render_helper_t* renderer, int sample_count) { play_state_t* ps = &vgmstream->pstate; - int samples_to_do = sample_count; - int samples_done = 0; - int done; - sample_t* tmpbuf = buf; + ps->play_position += renderer->samples_done; - /* simple mode with no settings (just skip everything below) */ - if (!vgmstream->config_enabled) { - render_layout(buf, samples_to_do, vgmstream); - mix_vgmstream(buf, samples_to_do, vgmstream); - return samples_to_do; - } + /* usually only happens when mixing layers of different lengths (where decoder keeps calling render) */ + if (!vgmstream->config.play_forever && ps->play_position > ps->play_duration) { + int excess = ps->play_position - ps->play_duration; + if (excess > sample_count) + excess = sample_count; + renderer->samples_done = (sample_count - excess); - /* trim may go first since it doesn't need output nor changes totals */ - if (ps->trim_begin_left) { - render_trim(vgmstream); + ps->play_position = ps->play_duration; } +} - /* adds empty samples to buf */ - if (ps->pad_begin_left) { - done = render_pad_begin(vgmstream, tmpbuf, samples_to_do); - samples_done += done; - samples_to_do -= done; - tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */ - } +/*****************************************************************************/ - /* end padding (before to avoid decoding if possible, but must be inside pad region) */ - if (!vgmstream->config.play_forever - && ps->play_position /*+ samples_to_do*/ >= ps->pad_end_start - && samples_to_do) { - done = render_pad_end(vgmstream, tmpbuf, samples_to_do); - samples_done += done; - samples_to_do -= done; - tmpbuf += done * vgmstream->pstate.output_channels; /* as if mixed */ - } +/* Decode data into sample buffer. Controls the "external" part of the decoding, + * while layout/decode control the "internal" part. + * + * A stream would be "externally" rendered like this: + * [ pad-begin ]( trim )[ decoded data * N loops ][ pad-end ] + * \ end-fade | + * + * Which part we are in depends on play_position. Because vgmstream render's + * buf may fall anywhere in the middle of all that. Since some ops add "fake" (non-decoded) + * samples to buf, we need to + */ - /* main decode */ - { //if (samples_to_do) /* 0 ok, less likely */ - done = render_layout(tmpbuf, samples_to_do, vgmstream); +int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) { + render_helper_t renderer = {0}; + renderer.tmpbuf = buf; + renderer.samples_done = 0; + renderer.samples_to_do = sample_count; + + //sbuf_init16(&renderer.sbuf, buf, sample_count, vgmstream->channels); - mix_vgmstream(tmpbuf, done, vgmstream); - samples_done += done; + /* simple mode with no settings (just skip everything below) */ + if (!vgmstream->config_enabled) { + render_layout(buf, renderer.samples_to_do, vgmstream); + mix_vgmstream(buf, renderer.samples_to_do, vgmstream); + return renderer.samples_to_do; + } - if (!vgmstream->config.play_forever) { - /* simple fadeout */ - if (ps->fade_left && ps->play_position + done >= ps->fade_start) { - render_fade(vgmstream, tmpbuf, done); - } - /* silence leftover buf samples (rarely used when no fade is set) */ - if (ps->play_position + done >= ps->pad_end_start) { - render_pad_end(vgmstream, tmpbuf, done); - } - } + /* adds empty samples to buf and moves it */ + play_op_pad_begin(vgmstream, &renderer); - tmpbuf += done * vgmstream->pstate.output_channels; - } + /* trim decoder output (may go anywhere before main render since it doesn't use render output) */ + play_op_trim(vgmstream, &renderer); - vgmstream->pstate.play_position += samples_done; + /* main decode */ + int done = render_layout(renderer.tmpbuf, renderer.samples_to_do, vgmstream); + mix_vgmstream(renderer.tmpbuf, done, vgmstream); - /* signal end */ - if (!vgmstream->config.play_forever - && ps->play_position > ps->play_duration) { - int excess = ps->play_position - ps->play_duration; - if (excess > sample_count) - excess = sample_count; - samples_done = (sample_count - excess); + /* simple fadeout over decoded data (after mixing since usually results in less samples) */ + play_op_fade(vgmstream, renderer.tmpbuf, done); + + /* silence leftover buf samples (after fade as rarely may mix decoded buf + trim samples when no fade is set) + * (could be done before render to "consume" buf but doesn't matter much) */ + play_op_pad_end(vgmstream, renderer.tmpbuf, done); + + renderer.samples_done += done; + //renderer.samples_to_do -= done; //not useful at this point + //renderer.tmpbuf += done * vgmstream->pstate.output_channels; - ps->play_position = ps->play_duration; - } - return samples_done; + play_adjust_totals(vgmstream, &renderer, sample_count); + return renderer.samples_done; } diff --git a/Frameworks/vgmstream/vgmstream/src/base/sbuf.c b/Frameworks/vgmstream/vgmstream/src/base/sbuf.c new file mode 100644 index 000000000..c0c07945e --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/base/sbuf.c @@ -0,0 +1,85 @@ +#include +#include +#include "../util.h" +#include "sbuf.h" + +#if 0 +/* skips N samples from current sbuf */ +void sbuf_init16(sbuf_t* sbuf, int16_t* buf, int samples, int channels) { + memset(sbuf, 0, sizeof(sbuf_t)); + sbuf->buf = buf; + sbuf->samples = samples; + sbuf->channels = channels; + sbuf->fmt = SFMT_S16; +} +#endif + + +// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that) +void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels) { + for (int s = 0; s < samples * channels; s++) { + buf_f32[s] = (float)buf_s16[s]; // / 32767.0f + } +} + +void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) { + /* when casting float to int, value is simply truncated: + * - (int)1.7 = 1, (int)-1.7 = -1 + * alts for more accurate rounding could be: + * - (int)floor(f) + * - (int)(f < 0 ? f - 0.5f : f + 0.5f) + * - (((int) (f1 + 32768.5)) - 32768) + * - etc + * but since +-1 isn't really audible we'll just cast, as it's the fastest + */ + for (int s = 0; s < samples * channels; s++) { + buf_s16[s] = clamp16( buf_f32[s]); // * 32767.0f + } +} + +void sbuf_copy_samples(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled) { + int pos = samples_filled * dst_channels; + + if (src_channels == dst_channels) { /* most common and probably faster */ + for (int s = 0; s < samples_to_do * dst_channels; s++) { + dst[pos + s] = src[s]; + } + } + else { + for (int s = 0; s < samples_to_do; s++) { + for (int ch = 0; ch < src_channels; ch++) { + dst[pos + s * dst_channels + ch] = src[s * src_channels + ch]; + } + for (int ch = src_channels; ch < dst_channels; ch++) { + dst[pos + s * dst_channels + ch] = 0; + } + } + } +} + +/* copy interleaving */ +void sbuf_copy_layers(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled, int dst_ch_start) { + // dst_channels == src_channels isn't likely + for (int src_ch = 0; src_ch < src_channels; src_ch++) { + for (int s = 0; s < samples_to_do; s++) { + int src_pos = s * src_channels + src_ch; + int dst_pos = (samples_filled + s) * dst_channels + dst_ch_start; + + dst[dst_pos] = src[src_pos]; + } + + dst_ch_start++; + } +} + +void sbuf_silence(sample_t* dst, int samples, int channels, int filled) { + memset(dst + filled * channels, 0, (samples - filled) * channels * sizeof(sample_t)); +} + +bool sbuf_realloc(sample_t** dst, int samples, int channels) { + sample_t* outbuf_re = realloc(*dst, samples * channels * sizeof(sample_t)); + if (!outbuf_re) return false; + + *dst = outbuf_re; + return true; +} diff --git a/Frameworks/vgmstream/vgmstream/src/base/sbuf.h b/Frameworks/vgmstream/vgmstream/src/base/sbuf.h index 329b08f43..3d11b4df4 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/sbuf.h +++ b/Frameworks/vgmstream/vgmstream/src/base/sbuf.h @@ -1,28 +1,52 @@ -#ifndef _SBUF_H -#define _SBUF_H +#ifndef _SBUF_H_ +#define _SBUF_H_ #include "../streamtypes.h" +#if 0 +/* interleaved: buffer for all channels = [ch*s] = (ch1 ch2 ch1 ch2 ch1 ch2 ch1 ch2 ...) */ +/* planar: buffer per channel = [ch][s] = (c1 c1 c1 c1 ...) (c2 c2 c2 c2 ...) */ +typedef enum { + SFMT_NONE, + SFMT_S16, + SFMT_F32, + //SFMT_S24, + //SFMT_S32, + //SFMT_S16P, + //SFMT_F32P, +} sfmt_t; + + +typedef struct { + void* buf; /* current sample buffer */ + int samples; /* max samples */ + int channels; /* interleaved step or planar buffers */ + sfmt_t fmt; /* buffer type */ + //int filled; /* samples in buffer */ + //int planar; +} sbuf_t; + +void sbuf_init16(sbuf_t* sbuf, int16_t* buf, int samples, int channels); + +void sbuf_clamp(sbuf_t* sbuf, int samples); + +/* skips N samples from current sbuf */ +void sbuf_consume(sbuf_t* sbuf, int samples); +#endif + +/* it's probably slightly faster to make those inline'd, but aren't called that often to matter (given big enough total samples) */ + // TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that) -static inline void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels) { - for (int s = 0; s < samples * channels; s++) { - buf_f32[s] = (float)buf_s16[s]; // / 32767.0f - } -} - -static inline void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) { - /* when casting float to int, value is simply truncated: - * - (int)1.7 = 1, (int)-1.7 = -1 - * alts for more accurate rounding could be: - * - (int)floor(f) - * - (int)(f < 0 ? f - 0.5f : f + 0.5f) - * - (((int) (f1 + 32768.5)) - 32768) - * - etc - * but since +-1 isn't really audible we'll just cast as it's the fastest - */ - for (int s = 0; s < samples * channels; s++) { - buf_s16[s] = clamp16( buf_f32[s]); // * 32767.0f - } -} +void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels); + +void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels); + +void sbuf_copy_samples(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled); + +void sbuf_copy_layers(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled, int dst_ch_start); + +void sbuf_silence(sample_t* dst, int samples, int channels, int filled); + +bool sbuf_realloc(sample_t** dst, int samples, int channels); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/base/tags.c b/Frameworks/vgmstream/vgmstream/src/base/tags.c index aacb2c043..21285625c 100644 --- a/Frameworks/vgmstream/vgmstream/src/base/tags.c +++ b/Frameworks/vgmstream/vgmstream/src/base/tags.c @@ -21,19 +21,19 @@ struct VGMSTREAM_TAGS { char targetpath[VGMSTREAM_TAGS_LINE_MAX]; /* tag section for filename (see comments below) */ - int section_found; + bool section_found; off_t section_start; off_t section_end; off_t offset; /* commands */ - int autotrack_on; - int autotrack_written; + bool autotrack_on; + bool autotrack_written; int track_count; - int exact_match; + bool exact_match; - int autoalbum_on; - int autoalbum_written; + bool autoalbum_on; + bool autoalbum_written; }; @@ -50,7 +50,7 @@ static void tags_clean(VGMSTREAM_TAGS* tag) { } VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) { - VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS)); + VGMSTREAM_TAGS* tags = calloc(1, sizeof(VGMSTREAM_TAGS)); if (!tags) goto fail; *tag_key = tags->key; @@ -102,7 +102,7 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) { if (tags->autotrack_on && !tags->autotrack_written) { sprintf(tags->key, "%s", "TRACK"); sprintf(tags->val, "%i", tags->track_count); - tags->autotrack_written = 1; + tags->autotrack_written = true; return 1; } @@ -119,7 +119,7 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) { sprintf(tags->key, "%s", "ALBUM"); sprintf(tags->val, "%s", path+1); - tags->autoalbum_written = 1; + tags->autoalbum_written = true; return 1; } @@ -150,13 +150,13 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) { if (ok == 1 || ok == 2) { int key_len = n2 - n1; if (strncasecmp(tags->key, "AUTOTRACK", key_len) == 0) { - tags->autotrack_on = 1; + tags->autotrack_on = true; } else if (strncasecmp(tags->key, "AUTOALBUM", key_len) == 0) { - tags->autoalbum_on = 1; + tags->autoalbum_on = true; } else if (strncasecmp(tags->key, "EXACTMATCH", key_len) == 0) { - tags->exact_match = 1; + tags->exact_match = true; } continue; /* not an actual tag */ @@ -210,7 +210,7 @@ int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) { if (filename_found) { /* section ok, start would be set before this (or be 0) */ tags->section_end = tags->offset; - tags->section_found = 1; + tags->section_found = true; tags->offset = tags->section_start; } else { diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_mp4.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_mp4.c index fb8379866..cfe6245f6 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_mp4.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_mp4.c @@ -489,7 +489,7 @@ static ffmpeg_codec_data* init_ffmpeg_mp4_custom(STREAMFILE* sf, mp4_custom_t* m if (buf_len > 0x100000) /* ??? */ goto fail; - buf = malloc(buf_len); + buf = calloc(1, buf_len); if (!buf) goto fail; bytes = make_m4a_header(buf, buf_len, mp4, sf, type); /* before changing stream_offset/size */ diff --git a/Frameworks/vgmstream/vgmstream/src/coding/libs/circus_vq_lib.c b/Frameworks/vgmstream/vgmstream/src/coding/libs/circus_vq_lib.c index 0f9fccdce..14f64516d 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/libs/circus_vq_lib.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/libs/circus_vq_lib.c @@ -320,7 +320,7 @@ circus_handle_t* circus_init(off_t start, uint8_t codec, uint8_t flags) { circus_handle_t* handle = NULL; int scale_index, err; - handle = malloc(sizeof(circus_handle_t)); + handle = calloc(1, sizeof(circus_handle_t)); if (!handle) goto fail; handle->start = start; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/libs/compresswave_lib.c b/Frameworks/vgmstream/vgmstream/src/coding/libs/compresswave_lib.c index f90c8d10c..8c4b17786 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/libs/compresswave_lib.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/libs/compresswave_lib.c @@ -146,7 +146,7 @@ static void THuff_SetPositionData(THuff* self, THuffPositionData* s); //------------------------------------------------------------------------------ //create static THuff* THuff_Create(TStream* buf) { - THuff* self = malloc(sizeof(THuff)); + THuff* self = calloc(1, sizeof(THuff)); if (!self) return NULL; //define stream @@ -558,7 +558,7 @@ struct TCompressWaveData { //----------------------------------------------------------- //create TCompressWaveData* TCompressWaveData_Create(void) { - TCompressWaveData* self = malloc(sizeof(TCompressWaveData)); + TCompressWaveData* self = calloc(1, sizeof(TCompressWaveData)); if (!self) return NULL; #if 0 self->Data = NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/libs/nwa_lib.c b/Frameworks/vgmstream/vgmstream/src/coding/libs/nwa_lib.c index 442ed2301..7cd70ceb8 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/libs/nwa_lib.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/libs/nwa_lib.c @@ -80,7 +80,7 @@ static int is_use_runlength(NWAData* nwa) { NWAData* nwalib_open(STREAMFILE* sf) { uint8_t header[0x2c] = {0}; int i; - NWAData* const nwa = malloc(sizeof(NWAData)); + NWAData* const nwa = calloc(1, sizeof(NWAData)); if (!nwa) goto fail; //NWAData::ReadHeader diff --git a/Frameworks/vgmstream/vgmstream/src/coding/libs/tac_lib.c b/Frameworks/vgmstream/vgmstream/src/coding/libs/tac_lib.c index 1f1201640..3b77b7756 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/libs/tac_lib.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/libs/tac_lib.c @@ -1140,7 +1140,7 @@ tac_handle_t* tac_init(const uint8_t* buf, int buf_size) { if (buf_size < TAC_BLOCK_SIZE) goto fail; - handle = malloc(sizeof(tac_handle_t)); + handle = calloc(1, sizeof(tac_handle_t)); if (!handle) goto fail; { diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index 167706069..9bf6e1238 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -392,6 +392,7 @@ static const char* extension_list[] = { "npsf", //fake extension/header id for .nps (in bigfiles) "nsa", "nsopus", + "ntx", "nub", "nub2", "nus3audio", @@ -956,7 +957,7 @@ static const layout_info layout_info_list[] = { {layout_blocked_str_snds, "blocked (.str SNDS)"}, {layout_blocked_ws_aud, "blocked (Westwood Studios .aud)"}, {layout_blocked_dec, "blocked (DEC)"}, - {layout_blocked_vs, "blocked (Melbourne House VS)"}, + {layout_blocked_vs_mh, "blocked (Melbourne House VS)"}, {layout_blocked_mul, "blocked (MUL)"}, {layout_blocked_gsb, "blocked (GSB)"}, {layout_blocked_thp, "blocked (THP)"}, @@ -1074,7 +1075,6 @@ static const meta_info meta_info_list[] = { {meta_FSB5, "FMOD FSB5 header"}, {meta_RWAX, "Konami RWAX header"}, {meta_XWB, "Microsoft XWB header"}, - {meta_PS2_XA30, "Reflections XA30 PS2 header"}, {meta_MUSC, "Krome MUSC header"}, {meta_MUSX, "Eurocom MUSX header"}, {meta_FILP, "cavia FILp header"}, @@ -1103,7 +1103,7 @@ static const meta_info meta_info_list[] = { {meta_SDT, "High Voltage .sdt header"}, {meta_WVS, "Swingin' Ape .WVS header"}, {meta_DEC, "Falcom .DEC RIFF header"}, - {meta_VS, "Melbourne House .VS header"}, + {meta_VS_MH, "Melbourne House .VS header"}, {meta_STR_SEGA, "Sega Stream Asset Builder header"}, {meta_STR_SEGA_custom, "Sega Stream Asset Builder header (custom)"}, {meta_XMU, "Outrage XMU header"}, diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked.c index cc0fca1de..1684604d3 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked.c @@ -1,38 +1,39 @@ #include "layout.h" #include "../vgmstream.h" #include "../base/decode.h" +#include "../base/sbuf.h" #include "../coding/coding.h" /* Decodes samples for blocked streams. * Data is divided into headered blocks with a bunch of data. The layout calls external helper functions * when a block is decoded, and those must parse the new block and move offsets accordingly. */ -void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream) { - int samples_written = 0; - int frame_size, samples_per_frame, samples_this_block; +void render_vgmstream_blocked(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { - frame_size = decode_get_frame_size(vgmstream); - samples_per_frame = decode_get_samples_per_frame(vgmstream); - samples_this_block = 0; + int frame_size = decode_get_frame_size(vgmstream); + int samples_per_frame = decode_get_samples_per_frame(vgmstream); + int samples_this_block = 0; if (vgmstream->current_block_samples) { samples_this_block = vgmstream->current_block_samples; - } else if (frame_size == 0) { /* assume 4 bit */ //TODO: decode_get_frame_size() really should return bits... */ + } + else if (frame_size == 0) { + //TO-DO: this case doesn't seem possible, codecs that return frame_size 0 (should) set current_block_samples samples_this_block = vgmstream->current_block_size * 2 * samples_per_frame; - } else { + } + else { samples_this_block = vgmstream->current_block_size / frame_size * samples_per_frame; } - - while (samples_written < sample_count) { + int samples_filled = 0; + while (samples_filled < sample_count) { int samples_to_do; - if (vgmstream->loop_flag && decode_do_loop(vgmstream)) { /* handle looping, readjust back to loop start values */ if (vgmstream->current_block_samples) { samples_this_block = vgmstream->current_block_samples; - } else if (frame_size == 0) { /* assume 4 bit */ //TODO: decode_get_frame_size() really should return bits... */ + } else if (frame_size == 0) { /* assume 4 bit */ samples_this_block = vgmstream->current_block_size * 2 * samples_per_frame; } else { samples_this_block = vgmstream->current_block_size / frame_size * samples_per_frame; @@ -42,28 +43,26 @@ void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* if (samples_this_block < 0) { /* probably block bug or EOF, next calcs would give wrong values/segfaults/infinite loop */ - VGM_LOG("layout_blocked: wrong block samples at 0x%x\n", (uint32_t)vgmstream->current_block_offset); - memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t)); - break; + VGM_LOG("BLOCKED: wrong block samples\n"); + goto decode_fail; } if (vgmstream->current_block_offset < 0 || vgmstream->current_block_offset == 0xFFFFFFFF) { /* probably block bug or EOF, block functions won't be able to read anything useful/infinite loop */ - VGM_LOG("layout_blocked: wrong block offset found\n"); - memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t)); - break; + VGM_LOG("BLOCKED: wrong block offset found\n"); + goto decode_fail; } samples_to_do = decode_get_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; + if (samples_to_do > sample_count - samples_filled) + samples_to_do = sample_count - samples_filled; if (samples_to_do > 0) { /* samples_this_block = 0 is allowed (empty block, do nothing then move to next block) */ - decode_vgmstream(vgmstream, samples_written, samples_to_do, buffer); + decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf); } - samples_written += samples_to_do; + samples_filled += samples_to_do; vgmstream->current_sample += samples_to_do; vgmstream->samples_into_block += samples_to_do; @@ -71,23 +70,29 @@ void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* /* move to next block when all samples are consumed */ if (vgmstream->samples_into_block == samples_this_block /*&& vgmstream->current_sample < vgmstream->num_samples*/) { /* don't go past last block */ //todo - block_update(vgmstream->next_block_offset,vgmstream); + block_update(vgmstream->next_block_offset, vgmstream); /* update since these may change each block */ frame_size = decode_get_frame_size(vgmstream); samples_per_frame = decode_get_samples_per_frame(vgmstream); if (vgmstream->current_block_samples) { samples_this_block = vgmstream->current_block_samples; - } else if (frame_size == 0) { /* assume 4 bit */ //TODO: decode_get_frame_size() really should return bits... */ + } + else if (frame_size == 0) { + //TO-DO: this case doesn't seem possible, codecs that return frame_size 0 (should) set current_block_samples samples_this_block = vgmstream->current_block_size * 2 * samples_per_frame; - } else { + } + else { samples_this_block = vgmstream->current_block_size / frame_size * samples_per_frame; } vgmstream->samples_into_block = 0; } - } + + return; +decode_fail: + sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled); } /* helper functions to parse new block */ @@ -132,8 +137,8 @@ void block_update(off_t block_offset, VGMSTREAM* vgmstream) { case layout_blocked_gsb: block_update_gsb(block_offset,vgmstream); break; - case layout_blocked_vs: - block_update_vs(block_offset,vgmstream); + case layout_blocked_vs_mh: + block_update_vs_mh(block_offset,vgmstream); break; case layout_blocked_xvas: block_update_xvas(block_offset,vgmstream); @@ -214,43 +219,3 @@ void block_update(off_t block_offset, VGMSTREAM* vgmstream) { break; } } - -void blocked_count_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, off_t offset) { - if (vgmstream == NULL) - return; - - int block_samples; - off_t max_offset = get_streamfile_size(sf); - - vgmstream->next_block_offset = offset; - do { - block_update(vgmstream->next_block_offset, vgmstream); - - if (vgmstream->current_block_samples < 0 || vgmstream->current_block_size == 0xFFFFFFFF) - break; - - if (vgmstream->current_block_samples) { - block_samples = vgmstream->current_block_samples; - } - else { - switch(vgmstream->coding_type) { - case coding_PCM16LE: - case coding_PCM16_int: block_samples = pcm16_bytes_to_samples(vgmstream->current_block_size, 1); break; - case coding_PCM8_int: - case coding_PCM8_U_int: block_samples = pcm8_bytes_to_samples(vgmstream->current_block_size, 1); break; - case coding_XBOX_IMA_mono: - case coding_XBOX_IMA: block_samples = xbox_ima_bytes_to_samples(vgmstream->current_block_size, 1); break; - case coding_NGC_DSP: block_samples = dsp_bytes_to_samples(vgmstream->current_block_size, 1); break; - case coding_PSX: block_samples = ps_bytes_to_samples(vgmstream->current_block_size,1); break; - default: - VGM_LOG("BLOCKED: missing codec\n"); - return; - } - } - - vgmstream->num_samples += block_samples; - } - while (vgmstream->next_block_offset < max_offset); - - block_update(offset, vgmstream); /* reset */ -} diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_vs.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_vs.c index d53732d6b..b2e18ee2a 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_vs.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_vs.c @@ -2,13 +2,12 @@ #include "../vgmstream.h" /* mini-blocks of size + data */ -void block_update_vs(off_t block_offset, VGMSTREAM * vgmstream) { - STREAMFILE* streamFile = vgmstream->ch[0].streamfile; - int i; +void block_update_vs_mh(off_t block_offset, VGMSTREAM* vgmstream) { + STREAMFILE* sf = vgmstream->ch[0].streamfile; - for (i = 0; i < vgmstream->channels; i++) { + for (int i = 0; i < vgmstream->channels; i++) { vgmstream->current_block_offset = block_offset; - vgmstream->current_block_size = read_32bitLE(vgmstream->current_block_offset,streamFile); + vgmstream->current_block_size = read_32bitLE(vgmstream->current_block_offset,sf); vgmstream->next_block_offset = vgmstream->current_block_offset + vgmstream->current_block_size + 0x04; vgmstream->ch[i].offset = vgmstream->current_block_offset + 0x04; if (i == 0) block_offset=vgmstream->next_block_offset; diff --git a/Frameworks/vgmstream/vgmstream/src/layout/flat.c b/Frameworks/vgmstream/vgmstream/src/layout/flat.c index 05ef82627..6cf979ff5 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/flat.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/flat.c @@ -1,43 +1,42 @@ #include "layout.h" #include "../vgmstream.h" #include "../base/decode.h" +#include "../base/sbuf.h" /* Decodes samples for flat streams. * Data forms a single stream, and the decoder may internally skip chunks and move offsets as needed. */ void render_vgmstream_flat(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { - int samples_written = 0; - int samples_per_frame, samples_this_block; - samples_per_frame = decode_get_samples_per_frame(vgmstream); - samples_this_block = vgmstream->num_samples; /* do all samples if possible */ + int samples_per_frame = decode_get_samples_per_frame(vgmstream); + int samples_this_block = vgmstream->num_samples; /* do all samples if possible */ - - while (samples_written < sample_count) { - int samples_to_do; + /* write samples */ + int samples_filled = 0; + while (samples_filled < sample_count) { if (vgmstream->loop_flag && decode_do_loop(vgmstream)) { /* handle looping */ continue; } - samples_to_do = decode_get_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; + int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream); + if (samples_to_do > sample_count - samples_filled) + samples_to_do = sample_count - samples_filled; - if (samples_to_do == 0) { /* when decoding more than num_samples */ - VGM_LOG_ONCE("FLAT: samples_to_do 0\n"); + if (samples_to_do <= 0) { /* when decoding more than num_samples */ + VGM_LOG_ONCE("FLAT: wrong samples_to_do\n"); goto decode_fail; } - decode_vgmstream(vgmstream, samples_written, samples_to_do, outbuf); + decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf); - samples_written += samples_to_do; + samples_filled += samples_to_do; vgmstream->current_sample += samples_to_do; vgmstream->samples_into_block += samples_to_do; } return; decode_fail: - memset(outbuf + samples_written * vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t)); + sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled); } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/interleave.c b/Frameworks/vgmstream/vgmstream/src/layout/interleave.c index 93fe67008..5d9195385 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/interleave.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/interleave.c @@ -1,157 +1,197 @@ #include "layout.h" #include "../vgmstream.h" #include "../base/decode.h" +#include "../base/sbuf.h" + + +typedef struct { + /* default */ + int samples_per_frame_d; + int samples_this_block_d; + /* first */ + int samples_per_frame_f; + int samples_this_block_f; + /* last */ + int samples_per_frame_l; + int samples_this_block_l; + + bool has_interleave_first; + bool has_interleave_last; + bool has_interleave_internal_updates; +} layout_config_t; + +static bool setup_helper(layout_config_t* layout, VGMSTREAM* vgmstream) { + //TO-DO: this could be pre-calc'd after main init + layout->has_interleave_first = vgmstream->interleave_first_block_size && vgmstream->channels > 1; + layout->has_interleave_last = vgmstream->interleave_last_block_size && vgmstream->channels > 1; + layout->has_interleave_internal_updates = vgmstream->codec_internal_updates; - -/* Decodes samples for interleaved streams. - * Data has interleaved chunks per channel, and once one is decoded the layout moves offsets, - * skipping other chunks (essentially a simplified variety of blocked layout). - * Incompatible with decoders that move offsets. */ -void render_vgmstream_interleave(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { - int samples_written = 0; - int samples_per_frame, samples_this_block; /* used */ - int samples_per_frame_d = 0, samples_this_block_d = 0; /* default */ - int samples_per_frame_f = 0, samples_this_block_f = 0; /* first */ - int samples_per_frame_l = 0, samples_this_block_l = 0; /* last */ - int has_interleave_first = vgmstream->interleave_first_block_size && vgmstream->channels > 1; - int has_interleave_last = vgmstream->interleave_last_block_size && vgmstream->channels > 1; - int has_interleave_internal_updates = vgmstream->codec_internal_updates; - - - /* setup */ { int frame_size_d = decode_get_frame_size(vgmstream); - samples_per_frame_d = decode_get_samples_per_frame(vgmstream); - if (frame_size_d == 0 || samples_per_frame_d == 0) goto fail; - samples_this_block_d = vgmstream->interleave_block_size / frame_size_d * samples_per_frame_d; + layout->samples_per_frame_d = decode_get_samples_per_frame(vgmstream); + if (frame_size_d == 0 || layout->samples_per_frame_d == 0) + goto fail; + layout->samples_this_block_d = vgmstream->interleave_block_size / frame_size_d * layout->samples_per_frame_d; } - if (has_interleave_first) { + + if (layout->has_interleave_first) { int frame_size_f = decode_get_frame_size(vgmstream); - samples_per_frame_f = decode_get_samples_per_frame(vgmstream); //todo samples per shortframe - if (frame_size_f == 0 || samples_per_frame_f == 0) goto fail; - samples_this_block_f = vgmstream->interleave_first_block_size / frame_size_f * samples_per_frame_f; + layout->samples_per_frame_f = decode_get_samples_per_frame(vgmstream); //todo samples per shortframe + if (frame_size_f == 0 || layout->samples_per_frame_f == 0) + goto fail; + layout->samples_this_block_f = vgmstream->interleave_first_block_size / frame_size_f * layout->samples_per_frame_f; } - if (has_interleave_last) { + + if (layout->has_interleave_last) { int frame_size_l = decode_get_shortframe_size(vgmstream); - samples_per_frame_l = decode_get_samples_per_shortframe(vgmstream); - if (frame_size_l == 0 || samples_per_frame_l == 0) goto fail; - samples_this_block_l = vgmstream->interleave_last_block_size / frame_size_l * samples_per_frame_l; + layout->samples_per_frame_l = decode_get_samples_per_shortframe(vgmstream); + if (frame_size_l == 0 || layout->samples_per_frame_l == 0) goto fail; + layout->samples_this_block_l = vgmstream->interleave_last_block_size / frame_size_l * layout->samples_per_frame_l; } - /* set current values */ - if (has_interleave_first && - vgmstream->current_sample < samples_this_block_f) { - samples_per_frame = samples_per_frame_f; - samples_this_block = samples_this_block_f; + return true; +fail: + return false; +} + +static void update_default_values(layout_config_t* layout, VGMSTREAM* vgmstream, int* p_samples_per_frame, int* p_samples_this_block) { + if (layout->has_interleave_first && + vgmstream->current_sample < layout->samples_this_block_f) { + *p_samples_per_frame = layout->samples_per_frame_f; + *p_samples_this_block = layout->samples_this_block_f; + } + else if (layout->has_interleave_last && + vgmstream->current_sample - vgmstream->samples_into_block + layout->samples_this_block_d > vgmstream->num_samples) { + *p_samples_per_frame = layout->samples_per_frame_l; + *p_samples_this_block = layout->samples_this_block_l; + } + else { + *p_samples_per_frame = layout->samples_per_frame_d; + *p_samples_this_block = layout->samples_this_block_d; + } +} + +static void update_loop_values(layout_config_t* layout, VGMSTREAM* vgmstream, int* p_samples_per_frame, int* p_samples_this_block) { + if (layout->has_interleave_first && + vgmstream->current_sample < layout->samples_this_block_f) { + /* use first interleave*/ + *p_samples_per_frame = layout->samples_per_frame_f; + *p_samples_this_block = layout->samples_this_block_f; + if (*p_samples_this_block == 0 && vgmstream->channels == 1) + *p_samples_this_block = vgmstream->num_samples; + } + else if (layout->has_interleave_last) { /* assumes that won't loop back into a interleave_last */ + *p_samples_per_frame = layout->samples_per_frame_d; + *p_samples_this_block = layout->samples_this_block_d; + if (*p_samples_this_block == 0 && vgmstream->channels == 1) + *p_samples_this_block = vgmstream->num_samples; + } +} + +static void update_offsets(layout_config_t* layout, VGMSTREAM* vgmstream, int* p_samples_per_frame, int* p_samples_this_block) { + int channels = vgmstream->channels; + + if (layout->has_interleave_first && + vgmstream->current_sample == layout->samples_this_block_f) { + /* interleave during first interleave: restore standard frame size after going past first interleave */ + *p_samples_per_frame = layout->samples_per_frame_d; + *p_samples_this_block = layout->samples_this_block_d; + if (*p_samples_this_block == 0 && channels == 1) + *p_samples_this_block = vgmstream->num_samples; + + for (int ch = 0; ch < channels; ch++) { + off_t skip = vgmstream->interleave_first_skip * (channels - 1 - ch) + + vgmstream->interleave_first_block_size * (channels - ch) + + vgmstream->interleave_block_size * ch; + vgmstream->ch[ch].offset += skip; + } + } + else if (layout->has_interleave_last && + vgmstream->current_sample + *p_samples_this_block > vgmstream->num_samples) { + /* interleave during last interleave: adjust values again if inside last interleave */ + *p_samples_per_frame = layout->samples_per_frame_l; + *p_samples_this_block = layout->samples_this_block_l; + if (*p_samples_this_block == 0 && channels == 1) + *p_samples_this_block = vgmstream->num_samples; + + for (int ch = 0; ch < channels; ch++) { + off_t skip = vgmstream->interleave_block_size * (channels - ch) + + vgmstream->interleave_last_block_size * ch; + vgmstream->ch[ch].offset += skip; + } } - else if (has_interleave_last && - vgmstream->current_sample - vgmstream->samples_into_block + samples_this_block_d > vgmstream->num_samples) { - samples_per_frame = samples_per_frame_l; - samples_this_block = samples_this_block_l; + else if (layout->has_interleave_internal_updates) { + /* interleave for some decoders that have already moved offsets over their data, so skip other channels's data */ + for (int ch = 0; ch < channels; ch++) { + off_t skip = vgmstream->interleave_block_size * (channels - 1); + vgmstream->ch[ch].offset += skip; + } } else { - samples_per_frame = samples_per_frame_d; - samples_this_block = samples_this_block_d; + /* regular interleave */ + for (int ch = 0; ch < channels; ch++) { + off_t skip = vgmstream->interleave_block_size * channels; + vgmstream->ch[ch].offset += skip; + } } + vgmstream->samples_into_block = 0; +} + + +/* Decodes samples for interleaved streams. + * Data has interleaved chunks per channel, and once one is decoded the layout moves offsets, + * skipping other chunks (essentially a simplified variety of blocked layout). + * Incompatible with decoders that move offsets. */ +void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { + layout_config_t layout = {0}; + if (!setup_helper(&layout, vgmstream)) { + VGM_LOG_ONCE("INTERLEAVE: wrong config found\n"); + sbuf_silence(outbuf, sample_count, vgmstream->channels, 0); + return; + } + + + /* set current values */ + int samples_per_frame, samples_this_block; + update_default_values(&layout, vgmstream, &samples_per_frame, &samples_this_block); + /* mono interleaved stream with no layout set, just behave like flat layout */ if (samples_this_block == 0 && vgmstream->channels == 1) samples_this_block = vgmstream->num_samples; - - /* write samples */ - while (samples_written < sample_count) { - int samples_to_do; + int samples_filled = 0; + while (samples_filled < sample_count) { if (vgmstream->loop_flag && decode_do_loop(vgmstream)) { /* handle looping, restore standard interleave sizes */ - - if (has_interleave_first && - vgmstream->current_sample < samples_this_block_f) { - /* use first interleave*/ - samples_per_frame = samples_per_frame_f; - samples_this_block = samples_this_block_f; - if (samples_this_block == 0 && vgmstream->channels == 1) - samples_this_block = vgmstream->num_samples; - } - else if (has_interleave_last) { /* assumes that won't loop back into a interleave_last */ - samples_per_frame = samples_per_frame_d; - samples_this_block = samples_this_block_d; - if (samples_this_block == 0 && vgmstream->channels == 1) - samples_this_block = vgmstream->num_samples; - } - + update_loop_values(&layout, vgmstream, &samples_per_frame, &samples_this_block); continue; } - samples_to_do = decode_get_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; + int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream); + if (samples_to_do > sample_count - samples_filled) + samples_to_do = sample_count - samples_filled; - if (samples_to_do == 0) { /* happens when interleave is not set */ - goto fail; + if (samples_to_do <= 0) { /* happens when interleave is not set */ + VGM_LOG_ONCE("INTERLEAVE: wrong samples_to_do\n"); + goto decode_fail; } - decode_vgmstream(vgmstream, samples_written, samples_to_do, buffer); + decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf); - samples_written += samples_to_do; + samples_filled += samples_to_do; vgmstream->current_sample += samples_to_do; vgmstream->samples_into_block += samples_to_do; /* move to next interleaved block when all samples are consumed */ if (vgmstream->samples_into_block == samples_this_block) { - int ch; - - if (has_interleave_first && - vgmstream->current_sample == samples_this_block_f) { - /* restore standard frame size after going past first interleave */ - samples_per_frame = samples_per_frame_d; - samples_this_block = samples_this_block_d; - if (samples_this_block == 0 && vgmstream->channels == 1) - samples_this_block = vgmstream->num_samples; - - for (ch = 0; ch < vgmstream->channels; ch++) { - off_t skip = - vgmstream->interleave_first_skip*(vgmstream->channels-1-ch) + - vgmstream->interleave_first_block_size*(vgmstream->channels-ch) + - vgmstream->interleave_block_size*ch; - vgmstream->ch[ch].offset += skip; - } - } - else if (has_interleave_last && - vgmstream->current_sample + samples_this_block > vgmstream->num_samples) { - /* adjust values again if inside last interleave */ - samples_per_frame = samples_per_frame_l; - samples_this_block = samples_this_block_l; - if (samples_this_block == 0 && vgmstream->channels == 1) - samples_this_block = vgmstream->num_samples; - - for (ch = 0; ch < vgmstream->channels; ch++) { - off_t skip = - vgmstream->interleave_block_size*(vgmstream->channels-ch) + - vgmstream->interleave_last_block_size*ch; - vgmstream->ch[ch].offset += skip; - } - } - else if (has_interleave_internal_updates) { - for (ch = 0; ch < vgmstream->channels; ch++) { - off_t skip = vgmstream->interleave_block_size * (vgmstream->channels - 1); - vgmstream->ch[ch].offset += skip; - } - } - else { - for (ch = 0; ch < vgmstream->channels; ch++) { - off_t skip = vgmstream->interleave_block_size * vgmstream->channels; - vgmstream->ch[ch].offset += skip; - } - } - - vgmstream->samples_into_block = 0; + update_offsets(&layout, vgmstream, &samples_per_frame, &samples_this_block); } } + return; -fail: - VGM_LOG_ONCE("layout_interleave: wrong values found\n"); - memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t)); +decode_fail: + sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled); } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/layered.c b/Frameworks/vgmstream/vgmstream/src/layout/layered.c index d27fef6f0..746b959c2 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/layered.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/layered.c @@ -3,84 +3,69 @@ #include "../base/decode.h" #include "../base/mixing.h" #include "../base/plugins.h" +#include "../base/sbuf.h" #define VGMSTREAM_MAX_LAYERS 255 #define VGMSTREAM_LAYER_SAMPLE_BUFFER 8192 /* Decodes samples for layered streams. - * 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. */ + * Each decoded vgmstream 'layer' (which may have different codecs and number of channels) + * is mixed into a final buffer, creating a single super-vgmstream. */ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { - 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 layer, ch; + int samples_per_frame = VGMSTREAM_LAYER_SAMPLE_BUFFER; + int samples_this_block = vgmstream->num_samples; /* do all samples if possible */ + int samples_filled = 0; + while (samples_filled < sample_count) { + int ch; if (vgmstream->loop_flag && decode_do_loop(vgmstream)) { - /* handle looping (loop_layout has been called below) */ + /* handle looping (loop_layout has been called inside) */ continue; } - samples_to_do = decode_get_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; + int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream); + if (samples_to_do > sample_count - samples_filled) + samples_to_do = sample_count - samples_filled; if (samples_to_do <= 0) { /* when decoding more than num_samples */ - VGM_LOG_ONCE("LAYERED: samples_to_do 0\n"); + VGM_LOG_ONCE("LAYERED: wrong samples_to_do\n"); goto decode_fail; } /* decode all layers */ ch = 0; - for (layer = 0; layer < data->layer_count; layer++) { - int s, layer_ch, layer_channels; + for (int current_layer = 0; current_layer < data->layer_count; current_layer++) { - /* layers may have its own number of channels */ - mixing_info(data->layers[layer], NULL, &layer_channels); + /* layers may have their own number of channels */ + int layer_channels; + mixing_info(data->layers[current_layer], NULL, &layer_channels); - render_vgmstream( - data->buffer, - samples_to_do, - data->layers[layer]); + render_vgmstream(data->buffer, samples_to_do, data->layers[current_layer]); /* mix layer samples to main samples */ - for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) { - for (s = 0; s < samples_to_do; s++) { - size_t layer_sample = s*layer_channels + layer_ch; - size_t buffer_sample = (samples_written+s)*data->output_channels + ch; - - outbuf[buffer_sample] = data->buffer[layer_sample]; - } - ch++; - } + sbuf_copy_layers(outbuf, data->output_channels, data->buffer, layer_channels, samples_to_do, samples_filled, ch); + ch += layer_channels; } - - samples_written += samples_to_do; + samples_filled += samples_to_do; vgmstream->current_sample += samples_to_do; vgmstream->samples_into_block += samples_to_do; } return; decode_fail: - memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t)); + sbuf_silence(outbuf, sample_count, data->output_channels, samples_filled); } void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample) { - int layer; layered_layout_data* data = vgmstream->layout_data; - for (layer = 0; layer < data->layer_count; layer++) { + for (int layer = 0; layer < data->layer_count; layer++) { seek_vgmstream(data->layers[layer], seek_sample); } @@ -89,11 +74,9 @@ void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample) { } 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++) { + for (int layer = 0; layer < data->layer_count; layer++) { if (data->external_looping) { /* looping is applied over resulting decode, as each layer is its own "solid" block with * config and needs 'external' seeking */ @@ -130,37 +113,35 @@ layered_layout_data* init_layout_layered(int layer_count) { data = calloc(1, sizeof(layered_layout_data)); if (!data) goto fail; - data->layer_count = layer_count; - data->layers = calloc(layer_count, sizeof(VGMSTREAM*)); if (!data->layers) goto fail; + data->layer_count = layer_count; + return data; fail: free_layout_layered(data); return NULL; } -int setup_layout_layered(layered_layout_data* data) { - int i, max_input_channels = 0, max_output_channels = 0; - sample_t *outbuf_re = NULL; - +bool setup_layout_layered(layered_layout_data* data) { /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ - for (i = 0; i < data->layer_count; i++) { - int layer_input_channels, layer_output_channels; - + int max_input_channels = 0; + int max_output_channels = 0; + for (int i = 0; i < data->layer_count; i++) { if (data->layers[i] == NULL) { VGM_LOG("LAYERED: no vgmstream in %i\n", i); - goto fail; + return false; } if (data->layers[i]->num_samples <= 0) { VGM_LOG("LAYERED: no samples in %i\n", i); - goto fail; + return false; } /* different layers may have different input/output channels */ + int layer_input_channels, layer_output_channels; mixing_info(data->layers[i], &layer_input_channels, &layer_output_channels); max_output_channels += layer_output_channels; @@ -171,12 +152,15 @@ int setup_layout_layered(layered_layout_data* data) { /* a bit weird, but no matter */ if (data->layers[i]->sample_rate != data->layers[i-1]->sample_rate) { VGM_LOG("LAYERED: layer %i has different sample rate\n", i); + //TO-DO: setup resampling } - /* also weird */ +#if 0 + /* also weird but less so */ if (data->layers[i]->coding_type != data->layers[i-1]->coding_type) { VGM_LOG("LAYERED: layer %i has different coding type\n", i); } +#endif } /* loops and other values could be mismatched, but should be handled on allocate */ @@ -192,44 +176,37 @@ int setup_layout_layered(layered_layout_data* data) { } if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS) - goto fail; + return false; - /* create internal buffer big enough for mixing */ - outbuf_re = realloc(data->buffer, VGMSTREAM_LAYER_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t)); - if (!outbuf_re) goto fail; - data->buffer = outbuf_re; + /* create internal buffer big enough for mixing all layers */ + if (!sbuf_realloc(&data->buffer, VGMSTREAM_LAYER_SAMPLE_BUFFER, max_input_channels)) + goto fail; data->input_channels = max_input_channels; data->output_channels = max_output_channels; - return 1; + return true; fail: - return 0; /* caller is expected to free */ + return false; /* caller is expected to free */ } void free_layout_layered(layered_layout_data *data) { - int i; - if (!data) return; - if (data->layers) { - for (i = 0; i < data->layer_count; i++) { - close_vgmstream(data->layers[i]); - } - free(data->layers); + for (int i = 0; i < data->layer_count; i++) { + close_vgmstream(data->layers[i]); } + free(data->layers); free(data->buffer); free(data); } void reset_layout_layered(layered_layout_data *data) { - int i; - if (!data) return; - for (i = 0; i < data->layer_count; i++) { + for (int i = 0; i < data->layer_count; i++) { reset_vgmstream(data->layers[i]); } } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/layout.h b/Frameworks/vgmstream/vgmstream/src/layout/layout.h index 97fd1cc57..ddbc8da9c 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/layout.h +++ b/Frameworks/vgmstream/vgmstream/src/layout/layout.h @@ -11,6 +11,7 @@ void render_vgmstream_flat(sample_t* buffer, int32_t sample_count, VGMSTREAM* vg void render_vgmstream_interleave(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); + /* segmented layout */ /* for files made of "continuous" segments, one per section of a song (using a complete sub-VGMSTREAM) */ typedef struct { @@ -25,12 +26,13 @@ typedef struct { void render_vgmstream_segmented(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); segmented_layout_data* init_layout_segmented(int segment_count); -int setup_layout_segmented(segmented_layout_data* data); +bool setup_layout_segmented(segmented_layout_data* data); void free_layout_segmented(segmented_layout_data* data); void reset_layout_segmented(segmented_layout_data* data); void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample); void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample); + /* layered layout */ /* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */ typedef struct { @@ -45,16 +47,16 @@ typedef struct { void render_vgmstream_layered(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); layered_layout_data* init_layout_layered(int layer_count); -int setup_layout_layered(layered_layout_data* data); +bool setup_layout_layered(layered_layout_data* data); void free_layout_layered(layered_layout_data* data); void reset_layout_layered(layered_layout_data* data); void seek_layout_layered(VGMSTREAM* vgmstream, int32_t seek_sample); void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample); + /* blocked layouts */ void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream); void block_update(off_t block_offset, VGMSTREAM* vgmstream); -void blocked_count_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, off_t offset); void block_update_ast(off_t block_ofset, VGMSTREAM* vgmstream); void block_update_mxch(off_t block_ofset, VGMSTREAM* vgmstream); @@ -67,7 +69,7 @@ void block_update_wsi(off_t block_offset, VGMSTREAM* vgmstream); void block_update_str_snds(off_t block_offset, VGMSTREAM* vgmstream); void block_update_ws_aud(off_t block_offset, VGMSTREAM* vgmstream); void block_update_dec(off_t block_offset, VGMSTREAM* vgmstream); -void block_update_vs(off_t block_offset, VGMSTREAM* vgmstream); +void block_update_vs_mh(off_t block_offset, VGMSTREAM* vgmstream); void block_update_mul(off_t block_offset, VGMSTREAM* vgmstream); void block_update_gsb(off_t block_offset, VGMSTREAM* vgmstream); void block_update_xvas(off_t block_offset, VGMSTREAM* vgmstream); diff --git a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c index 1b549dd8e..c411fa338 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c @@ -3,36 +3,38 @@ #include "../base/decode.h" #include "../base/mixing.h" #include "../base/plugins.h" +#include "../base/sbuf.h" #define VGMSTREAM_MAX_SEGMENTS 1024 #define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192 -static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written); /* Decodes samples for segmented streams. * 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, samples_this_block; segmented_layout_data* data = vgmstream->layout_data; - int use_internal_buffer = 0; - int current_channels = 0; + bool use_internal_buffer = false; /* normally uses outbuf directly (faster?) but could need internal buffer if downmixing */ if (vgmstream->channels != data->input_channels || data->mixed_channels) { - use_internal_buffer = 1; + use_internal_buffer = true; } if (data->current_segment >= data->segment_count) { VGM_LOG_ONCE("SEGMENT: wrong current segment\n"); - goto decode_fail; + sbuf_silence(outbuf, sample_count, data->output_channels, 0); + return; } - samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); + int current_channels = 0; mixing_info(data->segments[data->current_segment], NULL, ¤t_channels); + int samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]); - while (samples_written < sample_count) { + int samples_filled = 0; + while (samples_filled < sample_count) { int samples_to_do; + sample_t* buf; if (vgmstream->loop_flag && decode_do_loop(vgmstream)) { /* handle looping (loop_layout has been called below, changes segments/state) */ @@ -61,8 +63,8 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA samples_to_do = decode_get_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 > sample_count - samples_filled) + samples_to_do = sample_count - samples_filled; if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */ samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER; @@ -71,56 +73,30 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA goto decode_fail; } - render_vgmstream( - use_internal_buffer ? - data->buffer : &outbuf[samples_written * data->output_channels], - samples_to_do, - data->segments[data->current_segment]); + buf = use_internal_buffer ? data->buffer : &outbuf[samples_filled * data->output_channels]; + render_vgmstream(buf, samples_to_do, data->segments[data->current_segment]); if (use_internal_buffer) { - copy_samples(outbuf, data, current_channels, samples_to_do, samples_written); + sbuf_copy_samples(outbuf, data->output_channels, data->buffer, current_channels, samples_to_do, samples_filled); } - samples_written += samples_to_do; + samples_filled += samples_to_do; vgmstream->current_sample += samples_to_do; vgmstream->samples_into_block += samples_to_do; } return; decode_fail: - memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t)); + sbuf_silence(outbuf, sample_count, data->output_channels, samples_filled); } -static inline void copy_samples(sample_t* outbuf, segmented_layout_data* data, int current_channels, int32_t samples_to_do, int32_t samples_written) { - int ch_out = data->output_channels; - int ch_in = current_channels; - int pos = samples_written * ch_out; - int s; - if (ch_in == ch_out) { /* most common and probably faster */ - for (s = 0; s < samples_to_do * ch_out; s++) { - outbuf[pos + s] = data->buffer[s]; - } - } - else { - int ch; - for (s = 0; s < samples_to_do; s++) { - for (ch = 0; ch < ch_in; ch++) { - outbuf[pos + s*ch_out + ch] = data->buffer[s*ch_in + ch]; - } - for (ch = ch_in; ch < ch_out; ch++) { - outbuf[pos + s*ch_out + ch] = 0; - } - } - } -} void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample) { - int segment, total_samples; segmented_layout_data* data = vgmstream->layout_data; - segment = 0; - total_samples = 0; + int segment = 0; + int total_samples = 0; while (total_samples < vgmstream->num_samples) { int32_t segment_samples = vgmstream_get_samples(data->segments[segment]); @@ -133,13 +109,13 @@ void seek_layout_segmented(VGMSTREAM* vgmstream, int32_t seek_sample) { vgmstream->samples_into_block = seek_relative; break; } + total_samples += segment_samples; segment++; } - if (segment == data->segment_count) { - VGM_LOG("SEGMENTED: can't find seek segment\n"); - } + // ??? + VGM_ASSERT(segment == data->segment_count, "SEGMENTED: can't find seek segment\n"); } void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) { @@ -156,35 +132,33 @@ segmented_layout_data* init_layout_segmented(int segment_count) { data = calloc(1, sizeof(segmented_layout_data)); if (!data) goto fail; - data->segment_count = segment_count; - data->current_segment = 0; - data->segments = calloc(segment_count, sizeof(VGMSTREAM*)); if (!data->segments) goto fail; + data->segment_count = segment_count; + data->current_segment = 0; + return data; fail: free_layout_segmented(data); return NULL; } -int setup_layout_segmented(segmented_layout_data* data) { - int i, max_input_channels = 0, max_output_channels = 0, mixed_channels = 0; - sample_t *outbuf_re = NULL; +bool setup_layout_segmented(segmented_layout_data* data) { + int max_input_channels = 0, max_output_channels = 0, mixed_channels = 0; /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ - for (i = 0; i < data->segment_count; i++) { - int segment_input_channels, segment_output_channels; + for (int i = 0; i < data->segment_count; i++) { if (data->segments[i] == NULL) { VGM_LOG("SEGMENTED: no vgmstream in segment %i\n", i); - goto fail; + return false; } if (data->segments[i]->num_samples <= 0) { VGM_LOG("SEGMENTED: no samples in segment %i\n", i); - goto fail; + return false; } /* allow config if set for fine-tuned parts (usually TXTP only) */ @@ -200,8 +174,8 @@ int setup_layout_segmented(segmented_layout_data* data) { } } - /* different segments may have different input or output channels, we - * need to know maxs to properly handle */ + /* different segments may have different input or output channels (in rare cases of using ex. 2ch + 4ch) */ + int segment_input_channels, segment_output_channels; mixing_info(data->segments[i], &segment_input_channels, &segment_output_channels); if (max_input_channels < segment_input_channels) max_input_channels = segment_input_channels; @@ -236,56 +210,52 @@ int setup_layout_segmented(segmented_layout_data* data) { } if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS) - goto fail; + return false; /* create internal buffer big enough for mixing */ - outbuf_re = realloc(data->buffer, VGMSTREAM_SEGMENT_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t)); - if (!outbuf_re) goto fail; - data->buffer = outbuf_re; + if (!sbuf_realloc(&data->buffer, VGMSTREAM_SEGMENT_SAMPLE_BUFFER, max_input_channels)) + goto fail; data->input_channels = max_input_channels; data->output_channels = max_output_channels; data->mixed_channels = mixed_channels; - return 1; + return true; fail: - return 0; /* caller is expected to free */ + return false; /* caller is expected to free */ } void free_layout_segmented(segmented_layout_data* data) { - int i, j; - if (!data) return; - if (data->segments) { - for (i = 0; i < data->segment_count; i++) { - int is_repeat = 0; + for (int i = 0; i < data->segment_count; i++) { + bool is_repeat = false; - /* segments are allowed to be repeated so don't close the same thing twice */ - for (j = 0; j < i; j++) { - if (data->segments[i] == data->segments[j]) - is_repeat = 1; + /* segments are allowed to be repeated so don't close the same thing twice */ + for (int j = 0; j < i; j++) { + if (data->segments[i] == data->segments[j]) { + is_repeat = true; + break; } - if (is_repeat) - continue; - - close_vgmstream(data->segments[i]); } - free(data->segments); + + if (is_repeat) + continue; + close_vgmstream(data->segments[i]); } + free(data->segments); free(data->buffer); free(data); } void reset_layout_segmented(segmented_layout_data* data) { - int i; - if (!data) return; - data->current_segment = 0; - for (i = 0; i < data->segment_count; i++) { + for (int i = 0; i < data->segment_count; i++) { reset_vgmstream(data->segments[i]); } + + data->current_segment = 0; } diff --git a/Frameworks/vgmstream/vgmstream/src/libvgmstream.h b/Frameworks/vgmstream/vgmstream/src/libvgmstream.h index 177685206..3ac947eb1 100644 --- a/Frameworks/vgmstream/vgmstream/src/libvgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/libvgmstream.h @@ -1,30 +1,29 @@ -/* libvgmstream: vgmstream's public API */ - #ifndef _LIBVGMSTREAM_H_ #define _LIBVGMSTREAM_H_ //#define LIBVGMSTREAM_ENABLE 1 #if LIBVGMSTREAM_ENABLE -/* By default vgmstream behaves like a decoder (decode samples until stream end), but you can configure +/* libvgmstream: vgmstream's public API + * + * Basic usage (also see api_example.c): + * - libvgmstream_init(...) // create context + * - libvgmstream_setup(...) // setup config (if needed) + * - libvgmstream_open_song(...) // open format + * - libvgmstream_render(...) // main decode + * - output samples + repeat libvgmstream_render until stream is done + * - libvgmstream_free(...) // cleanup + * + * By default vgmstream behaves like a decoder (returns samples until stream end), but you can configure * it to loop N times or even downmix. In other words, it also behaves a bit like a player. - * It exposes multiple convenience stuff mainly for various plugins that mostly repeat the same features. - * All this may make the API still WIP and a bit twisted, probably will improve later. Probably. - * + * It exposes multiple convenience stuff mainly for various plugins with similar features. + * This may make the API a bit odd, will probably improve later. Probably. + * * Notes: - * - now there is an API internals (vgmstream.h) may change in the future - * - may dynamically allocate stuff as needed (mainly some buffers, but varies per format) + * - now there is an API, internals (vgmstream.h) may change in the future so avoid accesing them * - some details described in the API may not happen at the moment (defined for future changes) * - uses long-winded libvgmstream_* names since internals alredy use the vgmstream_* 'namespace', #define as needed * - c-strings should be in UTF-8 - * - * Basic usage (also see api_example.c): - * - libvgmstream_init(...) // base context - * - libvgmstream_setup(...) // config if needed - * - libvgmstream_open_song(...) // setup format - * - libvgmstream_play(...) // main decode - * - output samples + repeat libvgmstream_play until stream is done - * - libvgmstream_free(...) // cleanup */ @@ -108,7 +107,9 @@ typedef struct { int64_t stream_samples; // file's max samples (not final play duration) int64_t loop_start; // loop start sample int64_t loop_end; // loop end sample - bool loop_flag; // if file loops (false + defined loops means looping was forcefully disabled) + bool loop_flag; // if file loops + // ** false + defined loops means looping was forcefully disabled + // ** true + undefined loops means the file loops in a way not representable by loop points bool play_forever; // if file loops forever based on current config (meaning _play never stops) int64_t play_samples; // totals after all calculations (after applying loop/fade/etc config) @@ -191,8 +192,8 @@ typedef struct { } libvgmstream_config_t; /* pass default config, that will be applied to song on open - * - invalid config or complex cases (ex. some TXTP) may ignore these settings. - * - called without a song loaded (before _open or after _close), otherwise ignored. + * - invalid config or complex cases (ex. some TXTP) may ignore these settings + * - should be called without a song loaded (before _open or after _close) * - without config vgmstream will decode the current stream once */ LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_config_t* cfg); @@ -227,7 +228,7 @@ LIBVGMSTREAM_API void libvgmstream_close_song(libvgmstream_t* lib); * - vgmstream supplies its own buffer, updated on lib->decoder->* values (may change between calls) * - returns < 0 on error */ -LIBVGMSTREAM_API int libvgmstream_play(libvgmstream_t* lib); +LIBVGMSTREAM_API int libvgmstream_render(libvgmstream_t* lib); /* Same as _play, but fills some external buffer (also updates lib->decoder->* values) * - returns < 0 on error, or N = number of filled samples. @@ -297,7 +298,7 @@ typedef struct { /* Returns if vgmstream can parse a filename by extension, to reject some files earlier * - doesn't check file contents (that's only done on _open) * - config may be NULL - * - mainly for plugins that fail early; libvgmstream doesn't use this + * - mainly for plugins that want to fail early; libvgmstream doesn't use this */ LIBVGMSTREAM_API bool libvgmstream_is_valid(const char* filename, libvgmstream_valid_t* cfg); @@ -321,6 +322,9 @@ LIBVGMSTREAM_API int libvgmstream_get_title(libvgmstream_t* lib, libvgmstream_ti */ LIBVGMSTREAM_API int libvgmstream_format_describe(libvgmstream_t* lib, char* dst, int dst_size); +/* Return true if vgmstream detects from the filename that file can be used even if doesn't physically exist. + */ +LIBVGMSTREAM_API bool libvgmstream_is_virtual_filename(const char* filename); /*****************************************************************************/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/acb.c b/Frameworks/vgmstream/vgmstream/src/meta/acb.c index ef7beed94..bc2c52767 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/acb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/acb.c @@ -319,7 +319,7 @@ static int preload_acb_waveform(acb_header* acb) { return 1; //;VGM_LOG("acb: preload Waveform=%i\n", *p_rows); - acb->Waveform = malloc(*p_rows * sizeof(Waveform_t)); + acb->Waveform = calloc(1, *p_rows * sizeof(Waveform_t)); if (!acb->Waveform) goto fail; c_Id = utf_get_column(Table, "Id"); @@ -410,7 +410,7 @@ static int preload_acb_synth(acb_header* acb) { return 1; //;VGM_LOG("acb: preload Synth=%i\n", *p_rows); - acb->Synth = malloc(*p_rows * sizeof(Synth_t)); + acb->Synth = calloc(1, *p_rows * sizeof(Synth_t)); if (!acb->Synth) goto fail; c_Type = utf_get_column(Table, "Type"); @@ -613,7 +613,7 @@ static int preload_acb_trackcommand(acb_header* acb) { return 1; //;VGM_LOG("acb: preload TrackEvent/Command=%i\n", *p_rows); - acb->TrackCommand = malloc(*p_rows * sizeof(TrackCommand_t)); + acb->TrackCommand = calloc(1, *p_rows * sizeof(TrackCommand_t)); if (!acb->TrackCommand) goto fail; c_Command = utf_get_column(Table, "Command"); @@ -665,7 +665,7 @@ static int preload_acb_track(acb_header* acb) { return 1; //;VGM_LOG("acb: preload Track=%i\n", *p_rows); - acb->Track = malloc(*p_rows * sizeof(Track_t)); + acb->Track = calloc(1, *p_rows * sizeof(Track_t)); if (!acb->Track) goto fail; c_EventIndex = utf_get_column(Table, "EventIndex"); @@ -724,7 +724,7 @@ static int preload_acb_sequence(acb_header* acb) { return 1; //;VGM_LOG("acb: preload Sequence=%i\n", *p_rows); - acb->Sequence = malloc(*p_rows * sizeof(Sequence_t)); + acb->Sequence = calloc(1, *p_rows * sizeof(Sequence_t)); if (!acb->Sequence) goto fail; c_NumTracks = utf_get_column(Table, "NumTracks"); @@ -826,7 +826,7 @@ static int preload_acb_block(acb_header* acb) { return 1; //;VGM_LOG("acb: preload Block=%i\n", *p_rows); - acb->Block = malloc(*p_rows * sizeof(Block_t)); + acb->Block = calloc(1, *p_rows * sizeof(Block_t)); if (!acb->Block) goto fail; c_NumTracks = utf_get_column(Table, "NumTracks"); @@ -891,7 +891,7 @@ static int preload_acb_blocksequence(acb_header* acb) { return 1; //;VGM_LOG("acb: preload BlockSequence=%i\n", *p_rows); - acb->BlockSequence = malloc(*p_rows * sizeof(BlockSequence_t)); + acb->BlockSequence = calloc(1, *p_rows * sizeof(BlockSequence_t)); if (!acb->BlockSequence) goto fail; c_NumTracks = utf_get_column(Table, "NumTracks"); @@ -972,7 +972,7 @@ static int preload_acb_cue(acb_header* acb) { return 1; //;VGM_LOG("acb: preload Cue=%i\n", *p_rows); - acb->Cue = malloc(*p_rows * sizeof(Cue_t)); + acb->Cue = calloc(1, *p_rows * sizeof(Cue_t)); if (!acb->Cue) goto fail; c_ReferenceType = utf_get_column(Table, "ReferenceType"); @@ -1064,7 +1064,7 @@ static int preload_acb_cuename(acb_header* acb) { return 1; //;VGM_LOG("acb: preload CueName=%i\n", *p_rows); - acb->CueName = malloc(*p_rows * sizeof(CueName_t)); + acb->CueName = calloc(1, *p_rows * sizeof(CueName_t)); if (!acb->CueName) goto fail; c_CueIndex = utf_get_column(Table, "CueIndex"); @@ -1118,7 +1118,7 @@ static int preload_acb_waveformextensiondata(acb_header* acb) { return 1; //;VGM_LOG("acb: preload WaveformExtensionData=%i\n", *p_rows); - acb->WaveformExtensionData = malloc(*p_rows * sizeof(WaveformExtensionData_t)); + acb->WaveformExtensionData = calloc(1, *p_rows * sizeof(WaveformExtensionData_t)); if (!acb->WaveformExtensionData) goto fail; c_LoopStart = utf_get_column(Table, "LoopStart"); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bar_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/bar_streamfile.h index 308def89b..8d4c31e89 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bar_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/bar_streamfile.h @@ -60,7 +60,7 @@ static void close_bar(BARSTREAMFILE *streamFile) { /*static*/ STREAMFILE *wrap_bar_STREAMFILE(STREAMFILE *file) { - BARSTREAMFILE *streamfile = malloc(sizeof(BARSTREAMFILE)); + BARSTREAMFILE *streamfile = calloc(1, sizeof(BARSTREAMFILE)); if (!streamfile) return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bcstm.c b/Frameworks/vgmstream/vgmstream/src/meta/bcstm.c index 2ad97a8ad..d6eca09da 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bcstm.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/bcstm.c @@ -3,59 +3,60 @@ /* BCSTM - Nintendo 3DS format */ -VGMSTREAM * init_vgmstream_bcstm(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; +VGMSTREAM* init_vgmstream_bcstm(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; off_t start_offset; off_t info_offset = 0, seek_offset = 0, data_offset = 0; - int channel_count, loop_flag, codec; - int is_camelot_ima = 0; + int channels, loop_flag, codec; + bool is_camelot_ima = false; /* checks */ - if ( !check_extensions(streamFile,"bcstm") ) - goto fail; - - /* CSTM header */ - if (read_32bitBE(0x00, streamFile) != 0x4353544D) /* "CSTM" */ - goto fail; - /* 0x06(2): header size (0x40), 0x08: version (0x00000400), 0x0c: file size (not accurate for Camelot IMA) */ - - /* check BOM */ - if ((uint16_t)read_16bitLE(0x04, streamFile) != 0xFEFF) - goto fail; - - /* get sections (should always appear in the same order) */ - { - int i; - int section_count = read_16bitLE(0x10, streamFile); - for (i = 0; i < section_count; i++) { - /* 0x00: id, 0x02(2): padding, 0x04(4): offset, 0x08(4): size */ - uint16_t section_id = read_16bitLE(0x14 + i*0x0c+0x00, streamFile); - switch(section_id) { - case 0x4000: info_offset = read_32bitLE(0x14 + i*0x0c+0x04, streamFile); break; - case 0x4001: seek_offset = read_32bitLE(0x14 + i*0x0c+0x04, streamFile); break; - case 0x4002: data_offset = read_32bitLE(0x14 + i*0x0c+0x04, streamFile); break; - //case 0x4003: /* off_t regn_offset = read_32bitLE(0x18 + i * 0xc, streamFile); */ /* ? */ - //case 0x4004: /* off_t pdat_offset = read_32bitLE(0x18 + i * 0xc, streamFile); */ /* ? */ - default: - break; - } + if (!is_id32be(0x00, sf, "CSTM")) + return NULL; + if (!check_extensions(sf,"bcstm")) + return NULL; + + // 04: BOM + // 06: header size (0x40) + // 08: version (0x00000400) + // 0c: file size (not accurate for Camelot IMA) + + if (read_u16le(0x04, sf) != 0xFEFF) + return NULL; + + /* get sections (should always appear in the same order though) */ + int section_count = read_u16le(0x10, sf); + for (int i = 0; i < section_count; i++) { + // 00: id + // 02 padding + // 04: offset + // 08: size + uint16_t section_id = read_u16le(0x14 + i * 0x0c + 0x00, sf); + switch(section_id) { + case 0x4000: info_offset = read_u32le(0x14 + i * 0x0c + 0x04, sf); break; + case 0x4001: seek_offset = read_u32le(0x14 + i * 0x0c + 0x04, sf); break; + case 0x4002: data_offset = read_u32le(0x14 + i * 0x0c + 0x04, sf); break; + //case 0x4003: regn_offset = read_u32le(0x18 + i * 0x0c + 0x04, sf); break; // ? + //case 0x4004: pdat_offset = read_u32le(0x18 + i * 0x0c + 0x04, sf); break; // ? + default: + break; } } /* INFO section */ - if (read_32bitBE(info_offset, streamFile) != 0x494E464F) /* "INFO" */ - goto fail; - codec = read_8bit(info_offset + 0x20, streamFile); - loop_flag = read_8bit(info_offset + 0x21, streamFile); - channel_count = read_8bit(info_offset + 0x22, streamFile); + if (!is_id32be(info_offset, sf, "INFO")) + return NULL; + codec = read_u8(info_offset + 0x20, sf); + loop_flag = read_8bit(info_offset + 0x21, sf); + channels = read_8bit(info_offset + 0x22, sf); /* Camelot games have some weird codec hijack [Mario Tennis Open (3DS), Mario Golf: World Tour (3DS)] */ - if (codec == 0x02 && read_32bitBE(seek_offset, streamFile) != 0x5345454B) { /* "SEEK" */ + if (codec == 0x02 && !is_id32be(seek_offset, sf, "SEEK")) { if (seek_offset == 0) goto fail; start_offset = seek_offset; - is_camelot_ima = 1; + is_camelot_ima = true; } else { if (data_offset == 0) goto fail; @@ -64,33 +65,24 @@ VGMSTREAM * init_vgmstream_bcstm(STREAMFILE *streamFile) { /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count, loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = read_32bitLE(info_offset + 0x24, streamFile); - vgmstream->num_samples = read_32bitLE(info_offset + 0x2c, streamFile); - vgmstream->loop_start_sample = read_32bitLE(info_offset + 0x28, streamFile); + vgmstream->sample_rate = read_s32le(info_offset + 0x24, sf); + vgmstream->num_samples = read_s32le(info_offset + 0x2c, sf); + vgmstream->loop_start_sample = read_s32le(info_offset + 0x28, sf); vgmstream->loop_end_sample = vgmstream->num_samples; vgmstream->meta_type = meta_CSTM; - vgmstream->layout_type = (channel_count == 1) ? layout_none : layout_interleave; - vgmstream->interleave_block_size = read_32bitLE(info_offset + 0x34, streamFile); - vgmstream->interleave_last_block_size = read_32bitLE(info_offset + 0x44, streamFile); + vgmstream->layout_type = (channels == 1) ? layout_none : layout_interleave; + vgmstream->interleave_block_size = read_u32le(info_offset + 0x34, sf); + vgmstream->interleave_last_block_size = read_u32le(info_offset + 0x44, sf); /* Camelot doesn't follow header values */ if (is_camelot_ima) { - size_t block_samples, last_samples; - size_t data_size = get_streamfile_size(streamFile) - start_offset; + size_t data_size = get_streamfile_size(sf) - start_offset; vgmstream->interleave_block_size = 0x200; - vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size*channel_count)) / channel_count; - - /* align the loop points back to avoid pops in some IMA streams, seems to help some Mario Golf songs but not all */ - block_samples = ima_bytes_to_samples(vgmstream->interleave_block_size*channel_count,channel_count); // 5000? - last_samples = ima_bytes_to_samples(vgmstream->interleave_last_block_size*channel_count,channel_count); - if (vgmstream->loop_start_sample > block_samples) { - vgmstream->loop_start_sample -= last_samples; - vgmstream->loop_end_sample -= last_samples; - } + vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size * channels)) / channels; } switch(codec) { @@ -107,16 +99,15 @@ VGMSTREAM * init_vgmstream_bcstm(STREAMFILE *streamFile) { vgmstream->coding_type = coding_NW_IMA; } else { - int i, c; off_t channel_indexes, channel_info_offset, coefs_offset; - channel_indexes = info_offset+0x08 + read_32bitLE(info_offset + 0x1C, streamFile); - for (i = 0; i < vgmstream->channels; i++) { - channel_info_offset = channel_indexes + read_32bitLE(channel_indexes+0x04+(i*0x08)+0x04, streamFile); - coefs_offset = channel_info_offset + read_32bitLE(channel_info_offset+0x04, streamFile); + channel_indexes = info_offset+0x08 + read_u32le(info_offset + 0x1C, sf); + for (int i = 0; i < vgmstream->channels; i++) { + channel_info_offset = channel_indexes + read_u32le(channel_indexes + 0x04 + (i * 0x08) + 0x04, sf); + coefs_offset = channel_info_offset + read_u32le(channel_info_offset + 0x04, sf); - for (c = 0; c < 16; c++) { - vgmstream->ch[i].adpcm_coef[c] = read_16bitLE(coefs_offset + c*2, streamFile); + for (int c = 0; c < 16; c++) { + vgmstream->ch[i].adpcm_coef[c] = read_s16le(coefs_offset + c * 0x02, sf); } } } @@ -126,7 +117,7 @@ VGMSTREAM * init_vgmstream_bcstm(STREAMFILE *streamFile) { goto fail; } - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_wve_ad10.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_wve_ad10.c index a720265e2..28e7eb9e3 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_wve_ad10.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_wve_ad10.c @@ -2,46 +2,48 @@ #include "../coding/coding.h" #include "../layout/layout.h" #include "../util/endianness.h" +#include "../util/layout_utils.h" + /* EA WVE (Ad10) - from early Electronic Arts movies [Wing Commander 3/4 (PS1), Madden NHL 97 (PC)-w95] */ VGMSTREAM* init_vgmstream_ea_wve_ad10(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; off_t start_offset; int loop_flag, channels; - int big_endian, is_ps1; /* checks */ - /* .wve: common - * .mov: Madden NHL 97 (also uses .wve) */ - if (!check_extensions(sf, "wve,mov")) - goto fail; - - start_offset = 0x00; if (!is_id32be(0x00, sf, "AABB") && /* video block */ !is_id32be(0x00, sf, "Ad10") && /* audio block */ !is_id32be(0x00, sf, "Ad11")) /* last audio block, but could be first */ - goto fail; + return NULL; - big_endian = guess_endian32(0x04, sf); + /* .wve: common + * .mov: Madden NHL 97 (also uses .wve) */ + if (!check_extensions(sf, "wve,mov")) + return NULL; - if (is_id32be(0x00, sf, "AABB")) - start_offset += big_endian ? read_u32be(0x04, sf) : read_u32le(0x04, sf); + bool big_endian = guess_endian32(0x04, sf); - loop_flag = 0; + start_offset = 0x00; + if (is_id32be(0x00, sf, "AABB")){ + start_offset += big_endian ? read_u32be(0x04, sf) : read_u32le(0x04, sf); + } + bool is_ps1; if (ps_check_format(sf, start_offset + 0x08, 0x40)) { /* no header = no channels, but seems if the first PS-ADPCM header is 00 then it's mono, somehow * (ex. Wing Commander 3 intro / Wing Commander 4 = stereo, rest of Wing Commander 3 = mono) */ channels = read_u8(start_offset + 0x08,sf) != 0 ? 2 : 1; - is_ps1 = 1; - VGM_LOG("ps1"); + is_ps1 = true; } else { channels = 1; - is_ps1 = 0; + is_ps1 = false; } + loop_flag = false; + /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channels, loop_flag); @@ -59,10 +61,14 @@ VGMSTREAM* init_vgmstream_ea_wve_ad10(STREAMFILE* sf) { if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; - blocked_count_samples(vgmstream, sf, start_offset); + { + blocked_counter_t cfg = {0}; + cfg.offset = start_offset; - return vgmstream; + blocked_count_samples(vgmstream, sf, &cfg); + } + return vgmstream; fail: close_vgmstream(vgmstream); return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index e606344bf..754eca880 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -216,8 +216,6 @@ VGMSTREAM* init_vgmstream_rwax(STREAMFILE* sf); VGMSTREAM * init_vgmstream_xwb(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_ps2_xa30(STREAMFILE * streamFile); - VGMSTREAM * init_vgmstream_musc(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_musx(STREAMFILE * streamFile); @@ -269,7 +267,7 @@ VGMSTREAM* init_vgmstream_str_sega_custom(STREAMFILE* sf); VGMSTREAM * init_vgmstream_dec(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_vs(STREAMFILE *streamFile); +VGMSTREAM* init_vgmstream_vs_mh(STREAMFILE* sf); VGMSTREAM * init_vgmstream_xmu(STREAMFILE *streamFile); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mpeg.c b/Frameworks/vgmstream/vgmstream/src/meta/mpeg.c index 89bf55328..6e76bc4ef 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/mpeg.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/mpeg.c @@ -7,17 +7,14 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) { #ifdef VGM_USE_MPEG VGMSTREAM* vgmstream = NULL; uint32_t start_offset; - int loop_flag = 0; - mpeg_frame_info info = {0}; - uint32_t header_id; /* checks */ - header_id = read_u32be(0x00, sf); + uint32_t header_id = read_u32be(0x00, sf); if ((header_id & 0xFFF00000) != 0xFFF00000 && (header_id & 0xFFFFFF00) != get_id32be("ID3\0") && (header_id & 0xFFFFFF00) != get_id32be("TAG\0")) - goto fail; + return NULL; /* detect base offset, since some tags with images are big * (init_mpeg only skips tags in a small-ish buffer) */ @@ -29,8 +26,9 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) { start_offset += tag_size; } + mpeg_frame_info info = {0}; if (!mpeg_get_frame_info(sf, start_offset, &info)) - goto fail; + return NULL; /* .mp3/mp2: standard * .lmp3/lmp2: for plugins @@ -38,11 +36,12 @@ VGMSTREAM* init_vgmstream_mpeg(STREAMFILE* sf) { * .imf: Colors (Gizmondo) * .aix: Classic Compendium 2 (Gizmondo) * .wav/lwav: The Seventh Seal (PC) + * .nfx: Grand Theft Auto III (Android) * (extensionless): Interstellar Flames 2 (Gizmondo) */ - if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,wav,lwav,")) - goto fail; + if (!check_extensions(sf, "mp3,mp2,lmp3,lmp2,mus,imf,aix,wav,lwav,nfx,")) + return NULL; - loop_flag = 0; + bool loop_flag = 0; /* build VGMSTREAM */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_xa30.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_xa30.c deleted file mode 100644 index 0f07f7f55..000000000 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_xa30.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "meta.h" -#include "../coding/coding.h" - -/* XA30 - found in Driver: Parallel Lines (PS2) */ -VGMSTREAM * init_vgmstream_ps2_xa30(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count; - size_t file_size, data_size; - - - /* check extension, case insensitive */ - /* ".xa30" is just the ID, the real filename inside the file uses .XA */ - if (!check_extensions(streamFile,"xa,xa30")) - goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x58413330) /* "XA30" */ - goto fail; - if (read_32bitLE(0x04,streamFile) <= 2) goto fail; /* extra check to avoid PS2/PC XA30 mixup */ - - loop_flag = 0; - channel_count = 1 ; /* 0x08(2): interleave? 0x0a(2): channels? (always 1 in practice) */ - - start_offset = read_32bitLE(0x0C,streamFile); - - file_size = get_streamfile_size(streamFile); - data_size = read_32bitLE(0x14,streamFile); /* always off by 0x800 */ - if (data_size-0x0800 != file_size) goto fail; - data_size = file_size - start_offset; - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = read_32bitLE(0x04,streamFile); - vgmstream->num_samples = ps_bytes_to_samples(data_size,channel_count); /* 0x10: some num_samples value (but smaller) */ - - vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_none; - vgmstream->meta_type = meta_PS2_XA30; - /* the rest of the header has unknown values (and several repeats) and the filename */ - - - /* open the file for reading */ - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) - goto fail; - - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txtp_process.c b/Frameworks/vgmstream/vgmstream/src/meta/txtp_process.c index 69b2445e6..83fbcb65b 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txtp_process.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txtp_process.c @@ -131,7 +131,7 @@ static void apply_settings(VGMSTREAM* vgmstream, txtp_entry_t* current) { /* default play config (last after sample rate mods/mixing/etc) */ txtp_copy_config(&vgmstream->config, ¤t->config); - setup_state_vgmstream(vgmstream); + setup_vgmstream_play_state(vgmstream); /* config is enabled in layouts or externally (for compatibility, since we don't know yet if this * VGMSTREAM will part of a layout, or is enabled externally to not mess up plugins's calcs) */ } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/vid1.c b/Frameworks/vgmstream/vgmstream/src/meta/vid1.c index 3bc12b152..d99f7b6c3 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/vid1.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/vid1.c @@ -2,6 +2,7 @@ #include "../coding/coding.h" #include "../layout/layout.h" #include "../util/endianness.h" +#include "../util/layout_utils.h" /* VID1 - Factor 5/DivX format GC/Xbox games [Gun (GC), Tony Hawk's American Wasteland (GC), Enter The Matrix (Xbox)]*/ @@ -10,16 +11,16 @@ VGMSTREAM* init_vgmstream_vid1(STREAMFILE* sf) { uint32_t start_offset, header_offset; int loop_flag = 0, channels, sample_rate; uint32_t codec; - int big_endian; + bool big_endian; read_u32_t read_u32; /* checks */ if (is_id32be(0x00, sf, "VID1")) { /* BE (GC) */ - big_endian = 1; + big_endian = true; } else if (is_id32le(0x00,sf, "VID1")) { /* LE (Xbox) */ - big_endian = 0; + big_endian = false; } else { return NULL; @@ -110,24 +111,10 @@ VGMSTREAM* init_vgmstream_vid1(STREAMFILE* sf) { /* calc num_samples as playable data size varies between files/blocks */ if (vgmstream->layout_type == layout_blocked_vid1) { - int block_samples; - - vgmstream->next_block_offset = start_offset; - do { - block_update(vgmstream->next_block_offset, vgmstream); - if (vgmstream->current_block_samples < 0) - break; - - switch(vgmstream->coding_type) { - case coding_PCM16_int: block_samples = pcm_bytes_to_samples(vgmstream->current_block_size, 1, 16); break; - case coding_XBOX_IMA: block_samples = xbox_ima_bytes_to_samples(vgmstream->current_block_size, 1); break; - case coding_NGC_DSP: block_samples = dsp_bytes_to_samples(vgmstream->current_block_size, 1); break; - default: goto fail; - } - vgmstream->num_samples += block_samples; - } - while (vgmstream->next_block_offset < get_streamfile_size(sf)); - block_update(start_offset, vgmstream); + blocked_counter_t cfg = {0}; + cfg.offset = start_offset; + + blocked_count_samples(vgmstream, sf, &cfg); } return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/vs.c b/Frameworks/vgmstream/vgmstream/src/meta/vs.c index 4e8b94c06..a56cb0cd2 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/vs.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/vs.c @@ -1,19 +1,31 @@ #include "meta.h" #include "../layout/layout.h" #include "../coding/coding.h" +#include "../util/layout_utils.h" + /* .VS - from Melbourne House games [Men in Black II (PS2), Grand Prix Challenge (PS2) */ -VGMSTREAM* init_vgmstream_vs(STREAMFILE* sf) { +VGMSTREAM* init_vgmstream_vs_mh(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; off_t start_offset; int loop_flag, channels; /* checks */ - if (!check_extensions(sf, "vs")) - goto fail; if (read_u32be(0x00,sf) != 0xC8000000) - goto fail; + return NULL; + if (!check_extensions(sf, "vs")) + return NULL; + + /* extra checks since format is too simple */ + int sample_rate = read_s32le(0x04,sf); + if (sample_rate != 48000 && sample_rate != 44100) + return NULL; + if (read_u32le(0x08,sf) != 0x1000) + return NULL; + if (!ps_check_format(sf, 0x0c, 0x1000)) + return NULL; + loop_flag = 0; channels = 2; @@ -24,24 +36,19 @@ VGMSTREAM* init_vgmstream_vs(STREAMFILE* sf) { vgmstream = allocate_vgmstream(channels,loop_flag); if (!vgmstream) goto fail; - vgmstream->meta_type = meta_VS; - vgmstream->sample_rate = read_s32le(0x04,sf); + vgmstream->meta_type = meta_VS_MH; + vgmstream->sample_rate = sample_rate; vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = layout_blocked_vs; + vgmstream->layout_type = layout_blocked_vs_mh; if (!vgmstream_open_stream(vgmstream, sf, start_offset)) goto fail; - /* calc num_samples */ { - vgmstream->next_block_offset = start_offset; - do { - block_update(vgmstream->next_block_offset,vgmstream); - vgmstream->num_samples += ps_bytes_to_samples(vgmstream->current_block_size, 1); - } - while (vgmstream->next_block_offset < get_streamfile_size(sf)); - block_update(start_offset, vgmstream); + blocked_counter_t cfg = {0}; + cfg.offset = start_offset; + blocked_count_samples(vgmstream, sf, &cfg); } return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xa_xa30.c b/Frameworks/vgmstream/vgmstream/src/meta/xa_xa30.c index 9b49c0460..78d9375e6 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xa_xa30.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xa_xa30.c @@ -1,62 +1,99 @@ #include "meta.h" #include "../coding/coding.h" -/* XA30 - found in Reflections games [Driver: Parallel Lines (PC), Driver 3 (PC)] */ -VGMSTREAM * init_vgmstream_xa_xa30(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count, codec, interleave_block_size; - size_t stream_size; - int total_subsongs, target_subsong = streamFile->stream_index; +/* XA30 - from Reflections games [Driver: Parallel Lines (PC/PS2), Driver 3 (PC)] */ +VGMSTREAM* init_vgmstream_xa_xa30(STREAMFILE* sf) { + VGMSTREAM* vgmstream = NULL; + /* checks */ + if (!is_id32be(0x00,sf, "XA30") && /* [Driver: Parallel Lines (PC/PS2)] */ + !is_id32be(0x00,sf, "e4x\x92")) /* [Driver 3 (PC)] */ + return NULL; - /* check extension, case insensitive */ - /* ".xa30/e4x" is just the ID, the real filename should be .XA */ - if (!check_extensions(streamFile,"xa,xa30,e4x")) - goto fail; + /* .XA: actual extension + * .xa30/e4x: header ID */ + if (!check_extensions(sf,"xa,xa30,e4x")) + return NULL; - /* check header */ - if (read_32bitBE(0x00,streamFile) != 0x58413330 && /* "XA30" [Driver: Parallel Lines (PC)]*/ - read_32bitBE(0x00,streamFile) != 0x65347892) /* "e4x\92" [Driver 3 (PC)]*/ - goto fail; - if (read_32bitLE(0x04,streamFile) != 2) /* channels?, also extra check to avoid PS2/PC XA30 mixup */ - goto fail; - total_subsongs = read_32bitLE(0x14,streamFile) != 0 ? 2 : 1; /* second stream offset (only in Driver 3) */ - if (target_subsong == 0) target_subsong = 1; - if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + off_t start_offset; + int loop_flag, channels, interleave, sample_rate; + uint32_t codec, stream_size, file_size; + int total_subsongs, target_subsong = sf->stream_index; + + if (read_u32le(0x04,sf) > 2) { + /* PS2 */ + sample_rate = read_s32le(0x04,sf); + interleave = read_u16le(0x08, sf); // assumed, always 0x8000 + channels = read_u16le(0x0a, sf); // assumed, always 1 + start_offset = read_u32le(0x0C,sf); + // 10: some samples value? (smaller than real samples) + stream_size = read_u32le(0x14,sf); // always off by 0x800 + // 18: fixed values + // 1c: null + // rest of the header: garbage/repeated values or config (includes stream name) + + if (channels != 1) + return NULL; + + codec = 0xFF; //fake codec to simplify + total_subsongs = 0; + + file_size = get_streamfile_size(sf); + if (stream_size - 0x0800 != file_size) + return NULL; + stream_size = file_size - start_offset; + } + else { + /* PC */ + total_subsongs = read_u32le(0x14,sf) != 0 ? 2 : 1; /* second stream offset (only in Driver 3) */ + if (target_subsong == 0) target_subsong = 1; + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + channels = read_s32le(0x04,sf); // assumed, always 2 + sample_rate = read_s32le(0x08,sf); + codec = read_u32le(0x0c,sf); + start_offset = read_u32le(0x10 + 0x04 * (target_subsong - 1),sf); + stream_size = read_u32le(0x18 + 0x04 * (target_subsong - 1),sf); + //20: fixed: IMA=00016000, PCM=00056000 + interleave = read_u32le(0x24, sf); + // rest of the header is null + + if (channels != 2) + return NULL; + } loop_flag = 0; - channel_count = 2; /* 0x04: channels? (always 2 in practice) */ - codec = read_32bitLE(0x0c,streamFile); - start_offset = read_32bitLE(0x10 + 0x04*(target_subsong-1),streamFile); - stream_size = read_32bitLE(0x18 + 0x04*(target_subsong-1),streamFile); - interleave_block_size = read_32bitLE(0x24, streamFile); - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + vgmstream = allocate_vgmstream(channels, loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = read_32bitLE(0x08,streamFile); - /* 0x20: always IMA=00016000, PCM=00056000 PCM?, rest of the header is null */ + vgmstream->meta_type = meta_XA_XA30; + vgmstream->sample_rate = sample_rate; vgmstream->num_streams = total_subsongs; vgmstream->stream_size = stream_size; - vgmstream->meta_type = meta_XA_XA30; switch(codec) { - case 0x00: /* PCM (rare, seen in Driver 3) */ + case 0x00: /* Driver 3 (PC)-rare */ vgmstream->coding_type = coding_PCM16LE; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = interleave_block_size / 2; - vgmstream->num_samples = pcm_bytes_to_samples(stream_size, channel_count, 16); + vgmstream->interleave_block_size = interleave / 2; + vgmstream->num_samples = pcm16_bytes_to_samples(stream_size, channels); break; - case 0x01: /* MS-IMA variation */ + case 0x01: vgmstream->coding_type = coding_REF_IMA; vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size = interleave_block_size; - vgmstream->num_samples = ms_ima_bytes_to_samples(stream_size, vgmstream->interleave_block_size, channel_count); + vgmstream->interleave_block_size = interleave; + vgmstream->num_samples = ms_ima_bytes_to_samples(stream_size, interleave, channels); + break; + + case 0xFF: + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_none; + vgmstream->interleave_block_size = interleave; + vgmstream->num_samples = ps_bytes_to_samples(stream_size, channels); break; default: @@ -64,12 +101,9 @@ VGMSTREAM * init_vgmstream_xa_xa30(STREAMFILE *streamFile) { } - /* open the file for reading */ - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + if (!vgmstream_open_stream(vgmstream,sf,start_offset)) goto fail; - return vgmstream; - fail: close_vgmstream(vgmstream); return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/util/cipher_blowfish.c b/Frameworks/vgmstream/vgmstream/src/util/cipher_blowfish.c index bbbef2548..8bd3181c5 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/cipher_blowfish.c +++ b/Frameworks/vgmstream/vgmstream/src/util/cipher_blowfish.c @@ -244,7 +244,7 @@ blowfish_ctx* blowfish_init_ecb(uint8_t* key, int32_t key_len) { uint32_t xl, xr; uint8_t tmpkey[18*4]; - blowfish_ctx* ctx = malloc(sizeof(blowfish_ctx)); + blowfish_ctx* ctx = calloc(1, sizeof(blowfish_ctx)); if (!ctx) return NULL; diff --git a/Frameworks/vgmstream/vgmstream/src/util/cri_utf.c b/Frameworks/vgmstream/vgmstream/src/util/cri_utf.c index f2c5672d7..4f350a1ed 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/cri_utf.c +++ b/Frameworks/vgmstream/vgmstream/src/util/cri_utf.c @@ -145,7 +145,7 @@ utf_context* utf_open(STREAMFILE* sf, uint32_t table_offset, int* p_rows, const utf->table_name = utf->string_table + utf->name_offset; - utf->schema = malloc(utf->columns * sizeof(struct utf_column_t)); + utf->schema = calloc(1, utf->columns * sizeof(struct utf_column_t)); if (!utf->schema) goto fail; for (i = 0; i < utf->columns; i++) { diff --git a/Frameworks/vgmstream/vgmstream/src/util/layout_utils.c b/Frameworks/vgmstream/vgmstream/src/util/layout_utils.c index 8432cbb22..e40b92d3c 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/layout_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/util/layout_utils.c @@ -293,3 +293,44 @@ VGMSTREAM* allocate_segmented_vgmstream(segmented_layout_data* data, int loop_fl close_vgmstream(vgmstream); return NULL; } + + +void blocked_count_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, blocked_counter_t* cfg) { + if (vgmstream == NULL) + return; + + int block_samples; + off_t max_offset = get_streamfile_size(sf); + + vgmstream->next_block_offset = cfg->offset; + do { + block_update(vgmstream->next_block_offset, vgmstream); + + if (vgmstream->current_block_samples < 0 || vgmstream->current_block_size == 0xFFFFFFFF) + break; + + if (vgmstream->current_block_samples) { + block_samples = vgmstream->current_block_samples; + } + else { + switch(vgmstream->coding_type) { + case coding_PCM16LE: + case coding_PCM16_int: block_samples = pcm16_bytes_to_samples(vgmstream->current_block_size, 1); break; + case coding_PCM8_int: + case coding_PCM8_U_int: block_samples = pcm8_bytes_to_samples(vgmstream->current_block_size, 1); break; + case coding_XBOX_IMA_mono: + case coding_XBOX_IMA: block_samples = xbox_ima_bytes_to_samples(vgmstream->current_block_size, 1); break; + case coding_NGC_DSP: block_samples = dsp_bytes_to_samples(vgmstream->current_block_size, 1); break; + case coding_PSX: block_samples = ps_bytes_to_samples(vgmstream->current_block_size,1); break; + default: + VGM_LOG("BLOCKED: missing codec\n"); + return; + } + } + + vgmstream->num_samples += block_samples; + } + while (vgmstream->next_block_offset < max_offset); + + block_update(cfg->offset, vgmstream); /* reset */ +} diff --git a/Frameworks/vgmstream/vgmstream/src/util/layout_utils.h b/Frameworks/vgmstream/vgmstream/src/util/layout_utils.h index cdf850df1..c63f0e73b 100644 --- a/Frameworks/vgmstream/vgmstream/src/util/layout_utils.h +++ b/Frameworks/vgmstream/vgmstream/src/util/layout_utils.h @@ -22,4 +22,11 @@ bool layered_add_done(VGMSTREAM* vs); VGMSTREAM* allocate_layered_vgmstream(layered_layout_data* data); VGMSTREAM* allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment); + + +typedef struct { + off_t offset; +} blocked_counter_t; + +void blocked_count_samples(VGMSTREAM* vgmstream, STREAMFILE* sf, blocked_counter_t* cfg); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 631617d80..0b5372f34 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -194,8 +194,15 @@ typedef struct { bool allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ int format_id; /* internal format ID */ + + /* decoder config/state */ + int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ + int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them (may change during decode) */ + bool codec_internal_updates; /* temp(?) kludge (see vgmstream_open_stream/decode) */ + int32_t ws_output_size; /* WS ADPCM: output bytes for this block */ + /* layout/block state */ - int32_t current_sample; /* sample point within the file (for loop detection) */ + int32_t current_sample; /* sample point within the stream (for loop detection) */ int32_t samples_into_block; /* number of samples into the current block/interleave/segment/etc */ off_t current_block_offset; /* start of this block (offset of block header) */ size_t current_block_size; /* size in usable bytes of the block we're in now (used to calculate num_samples per block) */ @@ -203,28 +210,24 @@ typedef struct { off_t next_block_offset; /* offset of header of the next block */ size_t full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ - /* loop state (saved when loop is hit to restore later) */ + /* layout/block state copy for loops (saved on loop_start and restored later on loop_end) */ int32_t loop_current_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */ int32_t loop_samples_into_block;/* saved from samples_into_block */ off_t loop_block_offset; /* saved from current_block_offset */ size_t loop_block_size; /* saved from current_block_size */ int32_t loop_block_samples; /* saved from current_block_samples */ off_t loop_next_block_offset; /* saved from next_block_offset */ - bool hit_loop; /* save config when loop is hit, but first time only */ - - - /* decoder config/state */ - int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ - int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */ - bool codec_internal_updates; /* temp(?) kludge (see vgmstream_open_stream/decode) */ - int32_t ws_output_size; /* WS ADPCM: output bytes for this block */ + size_t loop_full_block_size; /* saved from full_block_size (probably unnecessary) */ + bool hit_loop; /* save config when loop is hit, but first time only */ /* main state */ - VGMSTREAMCHANNEL* ch; /* array of channels */ - VGMSTREAMCHANNEL* start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ + VGMSTREAMCHANNEL* ch; /* array of channels with current offset + per-channel codec config */ + VGMSTREAMCHANNEL* loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ + void* start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ + VGMSTREAMCHANNEL* start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ void* mixer; /* state for mixing effects */ @@ -250,7 +253,6 @@ typedef struct { } VGMSTREAM; -// VGMStream description in structure format typedef struct { int sample_rate; int channels; @@ -334,11 +336,10 @@ void get_vgmstream_coding_description(VGMSTREAM* vgmstream, char* out, size_t ou void get_vgmstream_layout_description(VGMSTREAM* vgmstream, char* out, size_t out_size); void get_vgmstream_meta_description(VGMSTREAM* vgmstream, char* out, size_t out_size); +//TODO: remove, unused internally /* calculate the number of samples to be played based on looping parameters */ int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double fadedelayseconds, VGMSTREAM* vgmstream); -void setup_state_vgmstream(VGMSTREAM* vgmstream); - /* Force enable/disable internal looping. Should be done before playing anything (or after reset), * and not all codecs support arbitrary loop values ATM. */ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sample, int loop_end_sample); @@ -346,4 +347,6 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa /* Set number of max loops to do, then play up to stream end (for songs with proper endings) */ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target); +void setup_vgmstream_play_state(VGMSTREAM* vgmstream); + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream_init.c b/Frameworks/vgmstream/vgmstream/src/vgmstream_init.c index 0e96c86f2..c2ad5cca7 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream_init.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream_init.c @@ -66,7 +66,6 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_fsb5, init_vgmstream_rwax, init_vgmstream_xwb, - init_vgmstream_ps2_xa30, init_vgmstream_musc, init_vgmstream_musx, init_vgmstream_filp, @@ -90,7 +89,6 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_str_sega, init_vgmstream_str_sega_custom, init_vgmstream_dec, - init_vgmstream_vs, init_vgmstream_xmu, init_vgmstream_xvas, init_vgmstream_sat_sap, @@ -527,6 +525,7 @@ init_vgmstream_t init_vgmstream_functions[] = { init_vgmstream_rage_aud, init_vgmstream_asd_naxat, init_vgmstream_pcm_kceje, + init_vgmstream_vs_mh, /* need companion files */ init_vgmstream_pos, init_vgmstream_sli_loops, diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream_types.h b/Frameworks/vgmstream/vgmstream/src/vgmstream_types.h index 1d133cf89..1a7674aa6 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream_types.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream_types.h @@ -208,7 +208,7 @@ typedef enum { layout_blocked_ws_aud, layout_blocked_dec, layout_blocked_xvas, - layout_blocked_vs, + layout_blocked_vs_mh, layout_blocked_mul, layout_blocked_gsb, layout_blocked_thp, @@ -333,7 +333,6 @@ typedef enum { meta_FSB5, /* FMOD Sample Bank, version 5 */ meta_RWAX, meta_XWB, /* Microsoft XACT framework (Xbox, X360, Windows) */ - meta_PS2_XA30, /* Driver - Parallel Lines (PS2) */ meta_MUSC, /* Krome PS2 games */ meta_MUSX, meta_FILP, /* Resident Evil - Dead Aim */ @@ -428,7 +427,7 @@ typedef enum { meta_ACM, /* InterPlay ACM header */ meta_MUS_ACM, /* MUS playlist of InterPlay ACM files */ meta_DEC, /* Falcom PC games (Xanadu Next, Gurumin) */ - meta_VS, /* Men in Black .vs */ + meta_VS_MH, meta_BGW, meta_SPW, meta_STS, diff --git a/Info.plist.template b/Info.plist.template index af5a23c03..1e721391a 100644 --- a/Info.plist.template +++ b/Info.plist.template @@ -694,6 +694,7 @@ npsf nsa nsopus + ntx nub nub2 nus3audio