mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-19 00:04:04 +01:00
commit
808a3233a8
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -6,6 +6,7 @@
|
||||
|
||||
*.cpp text eol=lf
|
||||
*.cc text eol=lf
|
||||
*.h text eol=lf
|
||||
|
||||
*.sh text eol=lf
|
||||
*.bat text eol=crlf
|
||||
|
2
.github/formats-info.py
vendored
2
.github/formats-info.py
vendored
@ -13,7 +13,7 @@ import glob, os, re
|
||||
|
||||
META_SRC = '../**/meta/*.c'
|
||||
FORMAT_SRC = '../**/formats.c'
|
||||
SORT_SRC = '../**/vgmstream.c'
|
||||
SORT_SRC = '../**/vgmstream_init.c'
|
||||
FORMAT_DOC = '../**/FORMATS.md'
|
||||
DUMP_DOC = 'formats-info.md'
|
||||
IS_SORT = True #sorts metas lines based on LIST_SRC
|
||||
|
22
.github/workflows/cmake-lx.yml
vendored
22
.github/workflows/cmake-lx.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch Git tags
|
||||
run: |
|
||||
@ -33,7 +33,7 @@ jobs:
|
||||
echo "RELEASE=$(lsb_release -sr)" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache celt
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/dependencies/celt-0061
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
key: linux-${{ env.RELEASE }}-celt-${{ hashFiles('cmake/dependencies/celt.cmake') }}
|
||||
|
||||
- name: Cache ffmpeg
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/build/dependencies/ffmpeg/bin/usr/local/include
|
||||
@ -51,21 +51,21 @@ jobs:
|
||||
key: linux-${{ env.RELEASE }}-ffmpeg-${{ hashFiles('cmake/dependencies/ffmpeg.cmake') }}
|
||||
|
||||
- name: Cache atrac9
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/build/dependencies/LibAtrac9/bin
|
||||
key: linux-${{ env.RELEASE }}-atrac9-${{ hashFiles('cmake/dependencies/atrac9.cmake') }}
|
||||
|
||||
- name: Cache g719
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/build/dependencies/libg719_decode/libg719_decode.a
|
||||
key: linux-${{ env.RELEASE }}-g719-${{ hashFiles('cmake/dependencies/g719.cmake') }}
|
||||
|
||||
- name: Cache mpg123
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/dependencies/mpg123
|
||||
@ -73,14 +73,14 @@ jobs:
|
||||
key: linux-${{ env.RELEASE }}-mpg123-${{ hashFiles('cmake/dependencies/mpg123.cmake') }}
|
||||
|
||||
- name: Cache speex
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/build/dependencies/speex/libspeex/.libs
|
||||
key: linux-${{ env.RELEASE }}-speex-${{ hashFiles('cmake/dependencies/speex.cmake') }}
|
||||
|
||||
- name: Cache ogg
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/build/dependencies/ogg/libogg.a
|
||||
@ -88,7 +88,7 @@ jobs:
|
||||
key: linux-${{ env.RELEASE }}-ogg-${{ hashFiles('cmake/dependencies/ogg.cmake') }}
|
||||
|
||||
- name: Cache vorbis
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/build/dependencies/vorbis/lib/*.a
|
||||
@ -111,7 +111,7 @@ jobs:
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Upload CLI tools artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/build/cli/vgmstream-cli
|
||||
@ -119,7 +119,7 @@ jobs:
|
||||
name: vgmstream-cli
|
||||
|
||||
#- name: Upload Audacious plugin artifact
|
||||
# uses: actions/upload-artifact@v3
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# path: ${{runner.workspace}}/build/audacious/vgmstream.so
|
||||
# name: vgmstream-audacious
|
||||
|
10
.github/workflows/cmake-mac.yml
vendored
10
.github/workflows/cmake-mac.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -22,7 +22,7 @@ jobs:
|
||||
run: brew install autoconf automake cmake pkgconfig ffmpeg jansson libao libvorbis mpg123
|
||||
|
||||
- name: Cache celt
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ github.workspace }}/dependencies/celt-0061
|
||||
@ -32,13 +32,13 @@ jobs:
|
||||
key: mac-celt-${{ hashFiles('cmake/dependencies/celt.cmake') }}
|
||||
|
||||
- name: Cache atrac9
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/build/dependencies/LibAtrac9/bin
|
||||
key: mac-atrac9-${{ hashFiles('cmake/dependencies/atrac9.cmake') }}
|
||||
|
||||
- name: Cache g719
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/build/dependencies/libg719_decode/libg719_decode.a
|
||||
key: mac-g719-${{ hashFiles('cmake/dependencies/g719.cmake') }}
|
||||
@ -50,7 +50,7 @@ jobs:
|
||||
cmake --build build
|
||||
|
||||
- name: Upload CLI tools artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/build/cli/vgmstream-cli
|
||||
name: vgmstream-mac
|
||||
|
22
.github/workflows/cmake-wasm.yml
vendored
22
.github/workflows/cmake-wasm.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch Git tags
|
||||
run: |
|
||||
@ -32,7 +32,7 @@ jobs:
|
||||
run: cmake -E make_directory ${{runner.workspace}}/embuild
|
||||
|
||||
- name: Cache celt
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/dependencies/celt-0061
|
||||
@ -42,7 +42,7 @@ jobs:
|
||||
key: wasm-celt-${{ hashFiles('cmake/dependencies/celt.cmake') }}
|
||||
|
||||
- name: Cache ffmpeg
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/embuild/dependencies/ffmpeg/bin/usr/local/include
|
||||
@ -50,28 +50,28 @@ jobs:
|
||||
key: wasm-ffmpeg-${{ hashFiles('cmake/dependencies/ffmpeg.cmake') }}
|
||||
|
||||
- name: Cache jansson
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/embuild/dependencies/jansson/src/.libs
|
||||
key: wasm-jansson-${{ hashFiles('cmake/dependencies/jansson.cmake') }}
|
||||
|
||||
- name: Cache atrac9
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/embuild/dependencies/LibAtrac9/bin
|
||||
key: wasm-atrac9-${{ hashFiles('cmake/dependencies/atrac9.cmake') }}
|
||||
|
||||
- name: Cache g719
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/embuild/dependencies/libg719_decode/libg719_decode.a
|
||||
key: wasm-g719-${{ hashFiles('cmake/dependencies/g719.cmake') }}
|
||||
|
||||
- name: Cache mpg123
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/dependencies/mpg123
|
||||
@ -79,14 +79,14 @@ jobs:
|
||||
key: wasm-mpg123-${{ hashFiles('cmake/dependencies/mpg123.cmake') }}
|
||||
|
||||
- name: Cache speex
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/embuild/dependencies/speex/libspeex/.libs
|
||||
key: wasm-speex-${{ hashFiles('cmake/dependencies/speex.cmake') }}
|
||||
|
||||
- name: Cache ogg
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/embuild/dependencies/ogg/libogg.a
|
||||
@ -94,7 +94,7 @@ jobs:
|
||||
key: wasm-ogg-${{ hashFiles('cmake/dependencies/ogg.cmake') }}
|
||||
|
||||
- name: Cache vorbis
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/embuild/dependencies/vorbis/lib/*.a
|
||||
@ -111,7 +111,7 @@ jobs:
|
||||
run: emmake make
|
||||
|
||||
- name: Upload WebAssembly artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: |
|
||||
${{runner.workspace}}/embuild/cli/vgmstream-cli.js
|
||||
|
10
.github/workflows/vs-win.yml
vendored
10
.github/workflows/vs-win.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch Git tags
|
||||
shell: cmd
|
||||
@ -39,25 +39,25 @@ jobs:
|
||||
|
||||
# upload to "actions" tab artifacts (can only make a single .zip from a dir's files)
|
||||
- name: Upload foobar2000 component artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: foo_input_vgmstream.fb2k-component
|
||||
path: ${{github.workspace}}\bin\artifacts\foobar2000
|
||||
|
||||
- name: Upload CLI tools 32-bit artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vgmstream-win
|
||||
path: ${{github.workspace}}\bin\artifacts\cli-x32
|
||||
|
||||
- name: Upload CLI tools 64-bit artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vgmstream-win64
|
||||
path: ${{github.workspace}}\bin\artifacts\cli-x64
|
||||
|
||||
- name: debug symbols artifact (all)
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debug-symbols-pdb
|
||||
path: ${{github.workspace}}\bin\artifacts\pdb
|
||||
|
@ -1,7 +1,7 @@
|
||||
# CLI
|
||||
|
||||
add_executable(vgmstream_cli
|
||||
vgmstream_cli.c wav_utils.c)
|
||||
vgmstream_cli.c vgmstream_cli_utils.c wav_utils.c)
|
||||
|
||||
set_target_properties(vgmstream_cli PROPERTIES
|
||||
PREFIX ""
|
||||
|
10
cli/Makefile
10
cli/Makefile
@ -22,6 +22,12 @@ ifeq ($(TARGET_OS),Windows_NT)
|
||||
|
||||
LIBAO_INC = -I$(LIBAO_IPATH)
|
||||
LIBAO_LIB = -L$(LIBAO_LPATH) -lao
|
||||
|
||||
# strip needs full name
|
||||
OUTPUT_CLI = vgmstream-cli.exe
|
||||
OUTPUT_123 = vgmstream123.exe
|
||||
OUTPUT_API = api_example.exe
|
||||
|
||||
else
|
||||
#todo move to subfolders and remove
|
||||
CFLAGS += -I../ext_includes
|
||||
@ -41,7 +47,7 @@ export CFLAGS LDFLAGS
|
||||
### targets
|
||||
|
||||
vgmstream_cli: libvgmstream.a $(TARGET_EXT_LIBS)
|
||||
$(CC) $(CFLAGS) vgmstream_cli.c wav_utils.c $(LDFLAGS) -o $(OUTPUT_CLI)
|
||||
$(CC) $(CFLAGS) vgmstream_cli.c vgmstream_cli_utils.c wav_utils.c $(LDFLAGS) -o $(OUTPUT_CLI)
|
||||
$(STRIP) $(OUTPUT_CLI)
|
||||
|
||||
vgmstream123: libvgmstream.a $(TARGET_EXT_LIBS)
|
||||
@ -50,7 +56,7 @@ vgmstream123: libvgmstream.a $(TARGET_EXT_LIBS)
|
||||
|
||||
api_example: libvgmstream.a $(TARGET_EXT_LIBS)
|
||||
$(CC) $(CFLAGS) api_example.c $(LDFLAGS) -o $(OUTPUT_API)
|
||||
$(STRIP) api_example
|
||||
$(STRIP) $(OUTPUT_API)
|
||||
|
||||
libvgmstream.a:
|
||||
$(MAKE) -C ../src $@
|
||||
|
@ -9,7 +9,7 @@ endif
|
||||
AM_CFLAGS = -DVGMSTREAM_VERSION_AUTO -DVGM_LOG_OUTPUT -I$(top_builddir) -I$(top_srcdir) -I$(top_srcdir)/ext_includes/ $(AO_CFLAGS)
|
||||
AM_MAKEFLAGS = -f Makefile.autotools
|
||||
|
||||
vgmstream_cli_SOURCES = vgmstream_cli.c wav_utils.c
|
||||
vgmstream_cli_SOURCES = vgmstream_cli.c vgmstream_cli_utils.c wav_utils.c
|
||||
vgmstream_cli_LDADD = ../src/libvgmstream.la
|
||||
|
||||
vgmstream123_SOURCES = vgmstream123.c wav_utils.c
|
||||
|
@ -22,7 +22,9 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <strings.h>
|
||||
#include <getopt.h>
|
||||
#include <ao/ao.h>
|
||||
@ -37,6 +39,8 @@
|
||||
# include <termios.h>
|
||||
#endif
|
||||
|
||||
|
||||
#include "wav_utils.h"
|
||||
#include "../src/vgmstream.h"
|
||||
#include "../src/api.h"
|
||||
|
||||
@ -396,7 +400,7 @@ static int play_vgmstream(const char* filename, song_settings_t* cfg) {
|
||||
render_vgmstream(buffer, to_do, vgmstream);
|
||||
|
||||
#if LITTLE_ENDIAN_OUTPUT
|
||||
swap_samples_le(buffer, output_channels * to_do, 0);
|
||||
wav_swap_samples_le(buffer, output_channels * to_do, 0);
|
||||
#endif
|
||||
|
||||
if (verbose && !out_filename) {
|
||||
@ -464,6 +468,7 @@ fail:
|
||||
}
|
||||
|
||||
static int play_playlist(const char *filename, song_settings_t *default_par) {
|
||||
#ifndef WIN32
|
||||
int ret = 0;
|
||||
FILE *f;
|
||||
char *line = NULL;
|
||||
@ -537,6 +542,9 @@ static int play_playlist(const char *filename, song_settings_t *default_par) {
|
||||
fclose(f);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int play_compressed_file(const char *filename, song_settings_t *par, const char *expand_cmd) {
|
||||
@ -684,7 +692,7 @@ static void add_driver_option(const char *key_value) {
|
||||
}
|
||||
|
||||
|
||||
static void usage(const char* progname, int is_help) {
|
||||
static void print_usage(const char* progname, int is_help) {
|
||||
song_settings_t default_par = DEFAULT_PARAMS;
|
||||
const char* default_driver = "???";
|
||||
|
||||
@ -765,7 +773,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (argc == 1) {
|
||||
/* We were invoked with no arguments */
|
||||
usage(argv[0], 0);
|
||||
print_usage(argv[0], 0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
@ -848,7 +856,7 @@ again_opts:
|
||||
out_filename = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
usage(argv[0], 1);
|
||||
print_usage(argv[0], 1);
|
||||
goto done;
|
||||
case 'P':
|
||||
add_driver_option(optarg);
|
||||
|
File diff suppressed because it is too large
Load Diff
77
cli/vgmstream_cli.h
Normal file
77
cli/vgmstream_cli.h
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef _VGMSTREAM_CLI_H
|
||||
#define _VGMSTREAM_CLI_H
|
||||
|
||||
#include "../src/api.h"
|
||||
#include "../src/vgmstream.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
char** infilenames;
|
||||
int infilenames_count;
|
||||
const char* infilename;
|
||||
|
||||
const char* outfilename_config;
|
||||
const char* outfilename;
|
||||
|
||||
int sample_buffer_size;
|
||||
|
||||
// playback config
|
||||
double loop_count;
|
||||
double fade_time;
|
||||
double fade_delay;
|
||||
bool ignore_fade;
|
||||
bool ignore_loop;
|
||||
bool force_loop;
|
||||
bool really_force_loop;
|
||||
|
||||
bool play_forever;
|
||||
bool play_sdtout;
|
||||
bool play_wreckless;
|
||||
|
||||
// subsongs
|
||||
int subsong_index;
|
||||
int subsong_end;
|
||||
|
||||
// wav config
|
||||
bool write_lwav;
|
||||
bool write_original_wav;
|
||||
|
||||
// print flags
|
||||
bool print_metaonly;
|
||||
bool print_adxencd;
|
||||
bool print_oggenc;
|
||||
bool print_batchvar;
|
||||
bool print_title;
|
||||
#ifdef HAVE_JSON
|
||||
bool print_metajson;
|
||||
#endif
|
||||
const char* tag_filename;
|
||||
|
||||
// debug stuff
|
||||
bool decode_only;
|
||||
bool test_reset;
|
||||
bool validate_extensions;
|
||||
int seek_samples1;
|
||||
int seek_samples2;
|
||||
int downmix_channels;
|
||||
int stereo_track;
|
||||
|
||||
|
||||
/* not quite config but eh */
|
||||
int lwav_loop_start;
|
||||
int lwav_loop_end;
|
||||
} cli_config_t;
|
||||
|
||||
|
||||
void replace_filename(char* dst, size_t dstsize, cli_config_t* cfg, VGMSTREAM* vgmstream);
|
||||
void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg);
|
||||
void print_tags(cli_config_t* cfg);
|
||||
void print_title(VGMSTREAM* vgmstream, cli_config_t* cfg);
|
||||
|
||||
#ifdef HAVE_JSON
|
||||
void print_json_version(const char* vgmstream_version);
|
||||
void print_json_info(VGMSTREAM* vgm, cli_config_t* cfg, const char* vgmstream_version);
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
@ -104,10 +104,12 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="vgmstream_cli.h" />
|
||||
<ClInclude Include="wav_utils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="vgmstream_cli.c" />
|
||||
<ClCompile Include="vgmstream_cli_utils.c" />
|
||||
<ClCompile Include="wav_utils.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -19,6 +19,9 @@
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="vgmstream_cli.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="wav_utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClCompile>
|
||||
@ -27,6 +30,9 @@
|
||||
<ClCompile Include="vgmstream_cli.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vgmstream_cli_utils.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="wav_utils.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
296
cli/vgmstream_cli_utils.c
Normal file
296
cli/vgmstream_cli_utils.c
Normal file
@ -0,0 +1,296 @@
|
||||
#include "vgmstream_cli.h"
|
||||
|
||||
#include "../src/api.h"
|
||||
#include "../src/vgmstream.h"
|
||||
|
||||
//todo use <>?
|
||||
#ifdef HAVE_JSON
|
||||
#include "jansson/jansson.h"
|
||||
#endif
|
||||
|
||||
|
||||
static void clean_filename(char* dst, int clean_paths) {
|
||||
for (int i = 0; i < strlen(dst); i++) {
|
||||
char c = dst[i];
|
||||
int is_badchar = (clean_paths && (c == '\\' || c == '/'))
|
||||
|| c == '*' || c == '?' || c == ':' /*|| c == '|'*/ || c == '<' || c == '>';
|
||||
if (is_badchar)
|
||||
dst[i] = '_';
|
||||
}
|
||||
}
|
||||
|
||||
/* replaces a filename with "?n" (stream name), "?f" (infilename) or "?s" (subsong) wildcards
|
||||
* ("?" was chosen since it's not a valid Windows filename char and hopefully nobody uses it on Linux) */
|
||||
void replace_filename(char* dst, size_t dstsize, cli_config_t* cfg, VGMSTREAM* vgmstream) {
|
||||
int subsong;
|
||||
char stream_name[PATH_LIMIT];
|
||||
char buf[PATH_LIMIT];
|
||||
char tmp[PATH_LIMIT];
|
||||
|
||||
|
||||
/* file has a "%" > temp replace for sprintf */
|
||||
strcpy(buf, cfg->outfilename_config);
|
||||
for (int i = 0; i < strlen(buf); i++) {
|
||||
if (buf[i] == '%')
|
||||
buf[i] = '|'; /* non-valid filename, not used in format */
|
||||
}
|
||||
|
||||
/* init config */
|
||||
subsong = vgmstream->stream_index;
|
||||
if (subsong > vgmstream->num_streams || subsong != cfg->subsong_index) {
|
||||
subsong = 0; /* for games without subsongs / bad config */
|
||||
}
|
||||
|
||||
if (vgmstream->stream_name[0] != '\0') {
|
||||
snprintf(stream_name, sizeof(stream_name), "%s", vgmstream->stream_name);
|
||||
clean_filename(stream_name, 1); /* clean subsong name's subdirs */
|
||||
}
|
||||
else {
|
||||
snprintf(stream_name, sizeof(stream_name), "%s", cfg->infilename);
|
||||
clean_filename(stream_name, 0); /* don't clean user's subdirs */
|
||||
}
|
||||
|
||||
/* do controlled replaces of each wildcard (in theory could appear N times) */
|
||||
do {
|
||||
char* pos = strchr(buf, '?');
|
||||
if (!pos)
|
||||
break;
|
||||
|
||||
/* use buf as format and copy formatted result to tmp (assuming sprintf's format must not overlap with dst) */
|
||||
if (pos[1] == 'n') {
|
||||
pos[0] = '%';
|
||||
pos[1] = 's'; /* use %s */
|
||||
snprintf(tmp, sizeof(tmp), buf, stream_name);
|
||||
}
|
||||
else if (pos[1] == 'f') {
|
||||
pos[0] = '%';
|
||||
pos[1] = 's'; /* use %s */
|
||||
snprintf(tmp, sizeof(tmp), buf, cfg->infilename);
|
||||
}
|
||||
else if (pos[1] == 's') {
|
||||
pos[0] = '%';
|
||||
pos[1] = 'i'; /* use %i */
|
||||
snprintf(tmp, sizeof(tmp), buf, subsong);
|
||||
}
|
||||
else if ((pos[1] == '0' && pos[2] >= '1' && pos[2] <= '9' && pos[3] == 's')) {
|
||||
pos[0] = '%';
|
||||
pos[3] = 'i'; /* use %0Ni */
|
||||
snprintf(tmp, sizeof(tmp), buf, subsong);
|
||||
}
|
||||
else {
|
||||
/* not recognized */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* copy result to buf again, so it can be used as format in next replace
|
||||
* (can be optimized with some pointer swapping but who cares about a few extra nanoseconds) */
|
||||
strcpy(buf, tmp);
|
||||
}
|
||||
while (1);
|
||||
|
||||
/* replace % back */
|
||||
for (int i = 0; i < strlen(buf); i++) {
|
||||
if (buf[i] == '|')
|
||||
buf[i] = '%';
|
||||
}
|
||||
|
||||
snprintf(dst, dstsize, "%s", buf);
|
||||
}
|
||||
|
||||
|
||||
void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
||||
int channels = vgmstream->channels;
|
||||
if (!cfg->play_sdtout) {
|
||||
if (cfg->print_adxencd) {
|
||||
printf("adxencd");
|
||||
if (!cfg->print_metaonly)
|
||||
printf(" \"%s\"",cfg->outfilename);
|
||||
if (vgmstream->loop_flag)
|
||||
printf(" -lps%d -lpe%d", vgmstream->loop_start_sample, vgmstream->loop_end_sample);
|
||||
printf("\n");
|
||||
}
|
||||
else if (cfg->print_oggenc) {
|
||||
printf("oggenc");
|
||||
if (!cfg->print_metaonly)
|
||||
printf(" \"%s\"", cfg->outfilename);
|
||||
if (vgmstream->loop_flag)
|
||||
printf(" -c LOOPSTART=%d -c LOOPLENGTH=%d", vgmstream->loop_start_sample, vgmstream->loop_end_sample-vgmstream->loop_start_sample);
|
||||
printf("\n");
|
||||
}
|
||||
else if (cfg->print_batchvar) {
|
||||
if (!cfg->print_metaonly)
|
||||
printf("set fname=\"%s\"\n", cfg->outfilename);
|
||||
printf("set tsamp=%d\nset chan=%d\n", vgmstream->num_samples, channels);
|
||||
if (vgmstream->loop_flag)
|
||||
printf("set lstart=%d\nset lend=%d\nset loop=1\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample);
|
||||
else
|
||||
printf("set loop=0\n");
|
||||
}
|
||||
else if (cfg->print_metaonly) {
|
||||
printf("metadata for %s\n", cfg->infilename);
|
||||
}
|
||||
else {
|
||||
printf("decoding %s\n", cfg->infilename);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cfg->play_sdtout && !cfg->print_adxencd && !cfg->print_oggenc && !cfg->print_batchvar) {
|
||||
char description[1024];
|
||||
describe_vgmstream(vgmstream, description, 1024);
|
||||
printf("%s", description);
|
||||
}
|
||||
}
|
||||
|
||||
void print_tags(cli_config_t* cfg) {
|
||||
VGMSTREAM_TAGS* tags = NULL;
|
||||
STREAMFILE* sf_tags = NULL;
|
||||
const char *tag_key, *tag_val;
|
||||
|
||||
if (!cfg->tag_filename)
|
||||
return;
|
||||
|
||||
sf_tags = open_stdio_streamfile(cfg->tag_filename);
|
||||
if (!sf_tags) {
|
||||
printf("tag file %s not found\n", cfg->tag_filename);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("tags:\n");
|
||||
|
||||
tags = vgmstream_tags_init(&tag_key, &tag_val);
|
||||
vgmstream_tags_reset(tags, cfg->infilename);
|
||||
while (vgmstream_tags_next_tag(tags, sf_tags)) {
|
||||
printf("- '%s'='%s'\n", tag_key, tag_val);
|
||||
}
|
||||
|
||||
vgmstream_tags_close(tags);
|
||||
close_streamfile(sf_tags);
|
||||
}
|
||||
|
||||
void print_title(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
||||
char title[1024];
|
||||
vgmstream_title_t tcfg = {0};
|
||||
|
||||
if (!cfg->print_title)
|
||||
return;
|
||||
|
||||
tcfg.force_title = 0;
|
||||
tcfg.subsong_range = 0;
|
||||
tcfg.remove_extension = 0;
|
||||
|
||||
vgmstream_get_title(title, sizeof(title), cfg->infilename, vgmstream, &tcfg);
|
||||
|
||||
printf("title: %s\n", title);
|
||||
}
|
||||
|
||||
#ifdef HAVE_JSON
|
||||
void print_json_version(const char* vgmstream_version) {
|
||||
size_t extension_list_len;
|
||||
size_t common_extension_list_len;
|
||||
const char** extension_list;
|
||||
const char** common_extension_list;
|
||||
extension_list = vgmstream_get_formats(&extension_list_len);
|
||||
common_extension_list = vgmstream_get_common_formats(&common_extension_list_len);
|
||||
|
||||
json_t* ext_list = json_array();
|
||||
json_t* cext_list = json_array();
|
||||
|
||||
for (size_t i = 0; i < extension_list_len; ++i) {
|
||||
json_t* ext = json_string(extension_list[i]);
|
||||
json_array_append(ext_list, ext);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < common_extension_list_len; ++i) {
|
||||
json_t* cext = json_string(common_extension_list[i]);
|
||||
json_array_append(cext_list, cext);
|
||||
}
|
||||
|
||||
json_t* version_string = json_string(vgmstream_version);
|
||||
|
||||
json_t* final_object = json_object();
|
||||
json_object_set(final_object, "version", version_string);
|
||||
json_decref(version_string);
|
||||
|
||||
json_object_set(final_object, "extensions",
|
||||
json_pack("{soso}",
|
||||
"vgm", ext_list,
|
||||
"common", cext_list));
|
||||
|
||||
json_dumpf(final_object, stdout, JSON_COMPACT);
|
||||
}
|
||||
|
||||
void print_json_info(VGMSTREAM* vgm, cli_config_t* cfg, const char* vgmstream_version) {
|
||||
json_t* version_string = json_string(vgmstream_version);
|
||||
vgmstream_info info;
|
||||
describe_vgmstream_info(vgm, &info);
|
||||
|
||||
json_t* mixing_info = NULL;
|
||||
|
||||
// The JSON pack format string is defined here: https://jansson.readthedocs.io/en/latest/apiref.html#building-values
|
||||
|
||||
if (info.mixing_info.input_channels > 0) {
|
||||
mixing_info = json_pack("{sisi}",
|
||||
"inputChannels", info.mixing_info.input_channels,
|
||||
"outputChannels", info.mixing_info.output_channels);
|
||||
}
|
||||
|
||||
json_t* loop_info = NULL;
|
||||
|
||||
if (info.loop_info.end > info.loop_info.start) {
|
||||
loop_info = json_pack("{sisi}",
|
||||
"start", info.loop_info.start,
|
||||
"end", info.loop_info.end);
|
||||
}
|
||||
|
||||
json_t* interleave_info = NULL;
|
||||
|
||||
if (info.interleave_info.last_block > info.interleave_info.first_block) {
|
||||
interleave_info = json_pack("{sisi}",
|
||||
"firstBlock", info.interleave_info.first_block,
|
||||
"lastBlock", info.interleave_info.last_block
|
||||
);
|
||||
}
|
||||
|
||||
json_t* stream_info = json_pack("{sisssi}",
|
||||
"index", info.stream_info.current,
|
||||
"name", info.stream_info.name,
|
||||
"total", info.stream_info.total
|
||||
);
|
||||
|
||||
if (info.stream_info.name[0] == '\0') {
|
||||
json_object_set(stream_info, "name", json_null());
|
||||
}
|
||||
|
||||
json_t* final_object = json_pack(
|
||||
"{sssisiso?siso?so?sisssssisssiso?}",
|
||||
"version", version_string,
|
||||
"sampleRate", info.sample_rate,
|
||||
"channels", info.channels,
|
||||
"mixingInfo", mixing_info,
|
||||
"channelLayout", info.channel_layout,
|
||||
"loopingInfo", loop_info,
|
||||
"interleaveInfo", interleave_info,
|
||||
"numberOfSamples", info.num_samples,
|
||||
"encoding", info.encoding,
|
||||
"layout", info.layout,
|
||||
"frameSize", info.frame_size,
|
||||
"metadataSource", info.metadata,
|
||||
"bitrate", info.bitrate,
|
||||
"streamInfo", stream_info
|
||||
);
|
||||
|
||||
if (info.frame_size == 0) {
|
||||
json_object_set(final_object, "frameSize", json_null());
|
||||
}
|
||||
|
||||
if (info.channel_layout == 0) {
|
||||
json_object_set(final_object, "channelLayout", json_null());
|
||||
}
|
||||
|
||||
json_dumpf(final_object, stdout, JSON_COMPACT);
|
||||
|
||||
json_decref(final_object);
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
#endif
|
@ -104,27 +104,6 @@ size_t wav_make_header(uint8_t* buf, size_t buf_size, wav_header_t* wav) {
|
||||
return header_size;
|
||||
}
|
||||
|
||||
#if 0
|
||||
void swap_samples_le(sample_t *buf, int count) {
|
||||
/* Windows can't be BE... I think */
|
||||
#if !defined(_WIN32)
|
||||
#if !defined(__BYTE_ORDER__) || __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
for (int i = 0; i < count; i++) {
|
||||
/* 16b sample in memory: aabb where aa=MSB, bb=LSB */
|
||||
uint8_t b0 = buf[i] & 0xff;
|
||||
uint8_t b1 = buf[i] >> 8;
|
||||
uint8_t *p = (uint8_t*)&(buf[i]);
|
||||
/* 16b sample in buffer: bbaa where bb=LSB, aa=MSB */
|
||||
p[0] = b0;
|
||||
p[1] = b1;
|
||||
/* when endianness is LE, buffer has bbaa already so this function can be skipped */
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static inline void swap_value(uint8_t* buf, int sample_size) {
|
||||
for (int i = 0; i < sample_size / 2; i++) {
|
||||
char temp = buf[i];
|
||||
@ -134,7 +113,7 @@ static inline void swap_value(uint8_t* buf, int sample_size) {
|
||||
}
|
||||
|
||||
/* when endianness is LE buffer is correct already and this function can be skipped */
|
||||
void swap_samples_le(void* samples, int samples_len, int sample_size) {
|
||||
void wav_swap_samples_le(void* samples, int samples_len, int sample_size) {
|
||||
/* Windows can't be BE... I think */
|
||||
#if !defined(_WIN32)
|
||||
#if !defined(__BYTE_ORDER__) || __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
|
@ -24,6 +24,6 @@ size_t wav_make_header(uint8_t* buf, size_t buf_size, wav_header_t* wav);
|
||||
/* swap big endian samples to little endian. Does nothing if machine is already LE.
|
||||
* Used when writting .WAV files, where samples in memory/buf may be BE while RIFF
|
||||
* is expected to have LE samples. */
|
||||
void swap_samples_le(void* samples, int samples_len, int sample_size);
|
||||
void wav_swap_samples_le(void* samples, int samples_len, int sample_size);
|
||||
|
||||
#endif
|
||||
|
@ -10,16 +10,17 @@ There are no hard coding rules but for consistency one should follow the style u
|
||||
- 4 spaces instead of tabs
|
||||
- `\n` breaks (LF, Linux style), instead of `\r\n` (CRLF, Windows style)
|
||||
- `underscore_and_lowercase_names` instead of `CamelCase`
|
||||
- `/* C89 comments */` for general comments, `//C99 comments` for special comments (like disabling code but leaving it there for visibility)
|
||||
- `/* C89 comments */` for general comments, `//C99 comments` for other comments
|
||||
- brackets starting in the same line
|
||||
- ex. `if (..) { LF ... LF }`
|
||||
- line length ~100, more is ok for 'noise code' (uninteresting calcs or function defs)
|
||||
- offsets/sizes in hex, counts/numbers in decimal
|
||||
- test functions may return 1=ok, 0=ko for simplicity
|
||||
- `free(ptr)` no need to NULL-check per standard, `close_stuff(ptr)` should follow when possible
|
||||
- `lowercase_helper_structs_t`, `UPPERCASE_MAIN_STRUCTS`
|
||||
- `lowercase_helper_structs_t`, `UPPERCASE_INTERNAL_STRUCTS`
|
||||
- spaces in calcs/ifs/etc may be added as desired for clarity
|
||||
- ex. `if (simple_check)` or `if ( complex_and_important_stuff(weird + weird) )`
|
||||
- though generally you should split steps if readibility is impaired
|
||||
- `goto` are used to abort and reach "fail" sections (typical C cleanup style), beware vars should be defined first
|
||||
- pointer definitions should keep the `*` together for consistency
|
||||
- ex. `VGMSTREAM* init_x() { ... }` `STREAMFILE* sf = ...`
|
||||
@ -79,7 +80,8 @@ Some of the code can be inefficient or duplicated at places, but it isn't that m
|
||||
./ext_includes/ external includes for compiling
|
||||
./ext_libs/ external libs/DLLs for linking
|
||||
./fb2k/ foobar2000 plugin
|
||||
./src/ main vgmstream code
|
||||
./src/ initial vgmstream code
|
||||
./src/base/ core vgmstream features
|
||||
./src/coding/ format data decoders
|
||||
./src/layout/ format data demuxers
|
||||
./src/meta/ format header parsers
|
||||
|
@ -830,9 +830,9 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Sony MSF header [*MSF*]
|
||||
- *msf*: `.msf .msa .at3 .mp3 .str .snd`
|
||||
- Codecs: PCM16BE PCM16LE PSX ATRAC3 FFmpeg(various)
|
||||
- **ps3_past.c**
|
||||
- SNDP header [*PS3_PAST*]
|
||||
- *ps3_past*: `.past`
|
||||
- **sndp.c**
|
||||
- Premium Agency SNDP header [*SNDP*]
|
||||
- *sndp*: `.past`
|
||||
- Codecs: PCM16LE
|
||||
- **sgxd.c**
|
||||
- Sony SGXD header [*SGXD*]
|
||||
@ -1262,7 +1262,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- *smc_smh*: `.smc + .smh`
|
||||
- Codecs: PSX
|
||||
- **ppst.c**
|
||||
- Parappa PPST header [*PPST*]
|
||||
- epics PPST header [*PPST*]
|
||||
- *ppst*: `.sng`
|
||||
- Subfiles: *riff*
|
||||
- **ubi_bao.c**
|
||||
@ -2033,6 +2033,7 @@ are used in few games.
|
||||
- Paradigm MC3 ADPCM
|
||||
- Ocean DSA ADPCM
|
||||
- lsf ADPCM
|
||||
- Ongakukan ADPCM
|
||||
- ITU-T G.721
|
||||
- CompressWave (CWav) Huffman ADPCM
|
||||
- Perceptual/transform-based
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
|
||||
void decode_free(VGMSTREAM* vgmstream) {
|
||||
if (!vgmstream->codec_data)
|
||||
return;
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
if (vgmstream->coding_type == coding_OGG_VORBIS) {
|
||||
@ -124,6 +126,9 @@ void decode_free(VGMSTREAM* vgmstream) {
|
||||
|
||||
|
||||
void decode_seek(VGMSTREAM* vgmstream) {
|
||||
if (!vgmstream->codec_data)
|
||||
return;
|
||||
|
||||
if (vgmstream->coding_type == coding_CIRCUS_VQ) {
|
||||
seek_circus_vq(vgmstream->codec_data, vgmstream->loop_current_sample);
|
||||
}
|
||||
@ -222,6 +227,8 @@ void decode_seek(VGMSTREAM* vgmstream) {
|
||||
|
||||
|
||||
void decode_reset(VGMSTREAM* vgmstream) {
|
||||
if (!vgmstream->codec_data)
|
||||
return;
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
if (vgmstream->coding_type == coding_OGG_VORBIS) {
|
||||
@ -1672,7 +1679,7 @@ int decode_do_loop(VGMSTREAM* vgmstream) {
|
||||
vgmstream->loop_next_block_offset = vgmstream->next_block_offset;
|
||||
//vgmstream->lstate = vgmstream->pstate; /* play state is applied over loops */
|
||||
|
||||
vgmstream->hit_loop = 1; /* info that loop is now ready to use */
|
||||
vgmstream->hit_loop = true; /* info that loop is now ready to use */
|
||||
}
|
||||
|
||||
return 0; /* not looped */
|
||||
|
118
src/base/mixer.c
Normal file
118
src/base/mixer.c
Normal file
@ -0,0 +1,118 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "mixing.h"
|
||||
#include "mixer_priv.h"
|
||||
#include "mixer.h"
|
||||
#include "sbuf.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
//TODO simplify
|
||||
/**
|
||||
* Mixer modifies decoded sample buffer before final output. This is implemented
|
||||
* mostly with simplicity in mind rather than performance. Process:
|
||||
* - detect if mixing applies at current moment or exit (mini performance optimization)
|
||||
* - copy/upgrade buf to float mixbuf if needed
|
||||
* - do mixing ops
|
||||
* - copy/downgrade mixbuf to original buf if needed
|
||||
*
|
||||
* Mixing may add or remove channels. input_channels is the buf's original channels,
|
||||
* and output_channels the resulting buf's channels. buf and mixbuf must be
|
||||
* as big as max channels (mixing_channels).
|
||||
*
|
||||
* Mixing ops are added by a meta (ex. TXTP) or plugin through the API. Non-sensical
|
||||
* mixes are ignored (to avoid rechecking every time).
|
||||
*
|
||||
* Currently, mixing must be manually enabled before starting to decode, because plugins
|
||||
* need to setup bigger bufs when upmixing. (to be changed)
|
||||
*
|
||||
* segmented/layered layouts handle mixing on their own.
|
||||
*/
|
||||
|
||||
mixer_t* mixer_init(int channels) {
|
||||
mixer_t* mixer = calloc(1, sizeof(mixer_t));
|
||||
if (!mixer) goto fail;
|
||||
|
||||
mixer->chain_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */
|
||||
mixer->mixing_channels = channels;
|
||||
mixer->output_channels = channels;
|
||||
mixer->input_channels = channels;
|
||||
|
||||
return mixer;
|
||||
|
||||
fail:
|
||||
mixer_free(mixer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void mixer_free(mixer_t* mixer) {
|
||||
if (!mixer) return;
|
||||
|
||||
free(mixer->mixbuf);
|
||||
free(mixer);
|
||||
}
|
||||
|
||||
void mixer_update_channel(mixer_t* mixer) {
|
||||
if (!mixer) return;
|
||||
|
||||
/* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */
|
||||
mixer->mixing_channels++;
|
||||
mixer->output_channels++;
|
||||
}
|
||||
|
||||
bool mixer_is_active(mixer_t* mixer) {
|
||||
/* no support or not need to apply */
|
||||
if (!mixer || !mixer->active || mixer->chain_count == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void mixer_process(mixer_t* mixer, sample_t* outbuf, int32_t sample_count, int32_t current_pos) {
|
||||
|
||||
/* no support or not need to apply */
|
||||
if (!mixer || !mixer->active || mixer->chain_count == 0)
|
||||
return;
|
||||
|
||||
/* try to skip if no fades apply (set but does nothing yet) + only has fades
|
||||
* (could be done in mix op but avoids upgrading bufs in some cases) */
|
||||
mixer->current_subpos = 0;
|
||||
if (mixer->has_fade) {
|
||||
//;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, mixer_op_fade_is_active(data, current_pos, current_pos + sample_count));
|
||||
if (!mixer->has_non_fade && !mixer_op_fade_is_active(mixer, current_pos, current_pos + sample_count))
|
||||
return;
|
||||
|
||||
//;VGM_LOG("MIX: fade pos=%i\n", current_pos);
|
||||
mixer->current_subpos = current_pos;
|
||||
}
|
||||
|
||||
// upgrade buf for mixing (somehow using float buf rather than int32 is faster?)
|
||||
sbuf_copy_s16_to_f32(mixer->mixbuf, outbuf, sample_count, mixer->input_channels);
|
||||
|
||||
/* apply mixing ops in order. Some ops change total channels they may change around:
|
||||
* - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2
|
||||
* - 2ch w/ "1u" = downmix to 1ch (current_channels decreases once)
|
||||
*/
|
||||
mixer->current_channels = mixer->input_channels;
|
||||
for (int m = 0; m < mixer->chain_count; m++) {
|
||||
mix_op_t* mix = &mixer->chain[m];
|
||||
|
||||
//TODO: set callback
|
||||
switch(mix->type) {
|
||||
case MIX_SWAP: mixer_op_swap(mixer, sample_count, mix); break;
|
||||
case MIX_ADD: mixer_op_add(mixer, sample_count, mix); break;
|
||||
case MIX_VOLUME: mixer_op_volume(mixer, sample_count, mix); break;
|
||||
case MIX_LIMIT: mixer_op_limit(mixer, sample_count, mix); break;
|
||||
case MIX_UPMIX: mixer_op_upmix(mixer, sample_count, mix); break;
|
||||
case MIX_DOWNMIX: mixer_op_downmix(mixer, sample_count, mix); break;
|
||||
case MIX_KILLMIX: mixer_op_killmix(mixer, sample_count, mix); break;
|
||||
case MIX_FADE: mixer_op_fade(mixer, sample_count, mix);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* downgrade mix to original output */
|
||||
sbuf_copy_f32_to_s16(outbuf, mixer->mixbuf, sample_count, mixer->output_channels);
|
||||
}
|
16
src/base/mixer.h
Normal file
16
src/base/mixer.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef _MIXER_H_
|
||||
#define _MIXER_H_
|
||||
|
||||
#include "../streamtypes.h"
|
||||
|
||||
typedef struct mixer_t mixer_t;
|
||||
|
||||
/* internal mixing pre-setup for vgmstream (doesn't imply usage).
|
||||
* If init somehow fails next calls are ignored. */
|
||||
mixer_t* mixer_init(int channels);
|
||||
void mixer_free(mixer_t* mixer);
|
||||
void mixer_update_channel(mixer_t* mixer);
|
||||
void mixer_process(mixer_t* mixer, sample_t *outbuf, int32_t sample_count, int32_t current_pos);
|
||||
bool mixer_is_active(mixer_t* mixer);
|
||||
|
||||
#endif
|
142
src/base/mixer_ops_common.c
Normal file
142
src/base/mixer_ops_common.c
Normal file
@ -0,0 +1,142 @@
|
||||
#include "mixer_priv.h"
|
||||
|
||||
|
||||
// TO-DO: some ops can be done with original PCM sbuf to avoid copying to the float sbuf
|
||||
// when there are no actual float ops (ex. 'swap', if no ' volume' )
|
||||
// Performance gain is probably fairly small, though.
|
||||
|
||||
void mixer_op_swap(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
||||
float* sbuf = mixer->mixbuf;
|
||||
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
float temp_f = sbuf[op->ch_dst];
|
||||
sbuf[op->ch_dst] = sbuf[op->ch_src];
|
||||
sbuf[op->ch_src] = temp_f;
|
||||
|
||||
sbuf += mixer->current_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_add(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
||||
float* sbuf = mixer->mixbuf;
|
||||
|
||||
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
sbuf[op->ch_dst] = sbuf[op->ch_dst] + sbuf[op->ch_src] * op->vol;
|
||||
|
||||
sbuf += mixer->current_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_volume(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
||||
float* sbuf = mixer->mixbuf;
|
||||
|
||||
if (op->ch_dst < 0) {
|
||||
/* "all channels", most common case */
|
||||
for (int s = 0; s < sample_count * mixer->current_channels; s++) {
|
||||
sbuf[s] = sbuf[s] * op->vol;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
sbuf[op->ch_dst] = sbuf[op->ch_dst] * op->vol;
|
||||
|
||||
sbuf += mixer->current_channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_limit(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
||||
float* sbuf = mixer->mixbuf;
|
||||
|
||||
const float limiter_max = 32767.0f;
|
||||
const float limiter_min = -32768.0f;
|
||||
|
||||
const float temp_max = limiter_max * op->vol;
|
||||
const float temp_min = limiter_min * op->vol;
|
||||
|
||||
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
|
||||
if (op->ch_dst < 0) {
|
||||
for (int ch = 0; ch < mixer->current_channels; ch++) {
|
||||
if (sbuf[ch] > temp_max)
|
||||
sbuf[ch] = temp_max;
|
||||
else if (sbuf[ch] < temp_min)
|
||||
sbuf[ch] = temp_min;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (sbuf[op->ch_dst] > temp_max)
|
||||
sbuf[op->ch_dst] = temp_max;
|
||||
else if (sbuf[op->ch_dst] < temp_min)
|
||||
sbuf[op->ch_dst] = temp_min;
|
||||
}
|
||||
|
||||
sbuf += mixer->current_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_upmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
||||
int max_channels = mixer->current_channels;
|
||||
mixer->current_channels += 1;
|
||||
|
||||
float* sbuf_tmp = mixer->mixbuf + sample_count * mixer->current_channels;
|
||||
float* sbuf = mixer->mixbuf + sample_count * max_channels;
|
||||
|
||||
/* copy 'backwards' as otherwise would overwrite samples before moving them forward */
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
sbuf_tmp -= mixer->current_channels;
|
||||
sbuf -= max_channels;
|
||||
|
||||
int sbuf_ch = max_channels - 1;
|
||||
for (int ch = mixer->current_channels - 1; ch >= 0; ch--) {
|
||||
if (ch == op->ch_dst) {
|
||||
sbuf_tmp[ch] = 0; /* inserted as silent */
|
||||
}
|
||||
else {
|
||||
sbuf_tmp[ch] = sbuf[sbuf_ch]; /* 'pull' channels backward */
|
||||
sbuf_ch--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_downmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
||||
int max_channels = mixer->current_channels;
|
||||
mixer->current_channels -= 1;
|
||||
|
||||
float* sbuf = mixer->mixbuf;
|
||||
float* sbuf_tmp = sbuf;
|
||||
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
|
||||
for (int ch = 0; ch < op->ch_dst; ch++) {
|
||||
sbuf_tmp[ch] = sbuf[ch]; /* copy untouched channels */
|
||||
}
|
||||
|
||||
for (int ch = op->ch_dst; ch < max_channels; ch++) {
|
||||
sbuf_tmp[ch] = sbuf[ch + 1]; /* 'pull' dropped channels back */
|
||||
}
|
||||
|
||||
sbuf_tmp += mixer->current_channels;
|
||||
sbuf += max_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_op_killmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
||||
int max_channels = mixer->current_channels;
|
||||
mixer->current_channels = op->ch_dst; /* clamp channels */
|
||||
|
||||
float* sbuf = mixer->mixbuf;
|
||||
float* sbuf_tmp = sbuf;
|
||||
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
for (int ch = 0; ch < mixer->current_channels; ch++) {
|
||||
sbuf_tmp[ch] = sbuf[ch];
|
||||
}
|
||||
|
||||
sbuf_tmp += mixer->current_channels;
|
||||
sbuf += max_channels;
|
||||
}
|
||||
}
|
@ -1,65 +1,9 @@
|
||||
#ifndef _MIXING_FADE_H_
|
||||
#define _MIXING_FADE_H_
|
||||
|
||||
#include "mixing_priv.h"
|
||||
#include <math.h>
|
||||
#include "mixer_priv.h"
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#define MIXING_PI 3.14159265358979323846f
|
||||
|
||||
|
||||
static inline int is_fade_active(mixing_data *data, int32_t current_start, int32_t current_end) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < data->mixing_count; i++) {
|
||||
mix_command_data *mix = &data->mixing_chain[i];
|
||||
int32_t fade_start, fade_end;
|
||||
float vol_start = mix->vol_start;
|
||||
|
||||
if (mix->command != MIX_FADE)
|
||||
continue;
|
||||
|
||||
/* check is current range falls within a fade
|
||||
* (assuming fades were already optimized on add) */
|
||||
if (mix->time_pre < 0 && vol_start == 1.0) {
|
||||
fade_start = mix->time_start; /* ignore unused */
|
||||
}
|
||||
else {
|
||||
fade_start = mix->time_pre < 0 ? 0 : mix->time_pre;
|
||||
}
|
||||
fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post;
|
||||
|
||||
//;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end);
|
||||
if (current_start < fade_end && current_end > fade_start) {
|
||||
//;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) {
|
||||
int32_t current_pos;
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
return vgmstream->pstate.play_position;
|
||||
}
|
||||
|
||||
if (vgmstream->loop_flag && vgmstream->loop_count > 0) {
|
||||
int loop_pre = vgmstream->loop_start_sample; /* samples before looping */
|
||||
int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */
|
||||
int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */
|
||||
|
||||
current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count;
|
||||
}
|
||||
else {
|
||||
current_pos = (vgmstream->current_sample - sample_count);
|
||||
}
|
||||
|
||||
return current_pos;
|
||||
}
|
||||
|
||||
static inline float get_fade_gain_curve(char shape, float index) {
|
||||
float gain;
|
||||
|
||||
@ -68,7 +12,7 @@ static inline float get_fade_gain_curve(char shape, float index) {
|
||||
return index;
|
||||
}
|
||||
|
||||
//todo optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
|
||||
//TODO optimizations: interleave calcs, maybe use cosf, powf, etc? (with extra defines)
|
||||
|
||||
/* (curve math mostly from SoX/FFmpeg) */
|
||||
switch(shape) {
|
||||
@ -79,6 +23,7 @@ static inline float get_fade_gain_curve(char shape, float index) {
|
||||
//gain = pow(0.1f, (1.0f - index) * 2.5f);
|
||||
gain = exp(-5.75646273248511f * (1.0f - index));
|
||||
break;
|
||||
|
||||
case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */
|
||||
//gain = 1 - pow(0.1f, (index) * 2.5f);
|
||||
gain = 1 - exp(-5.75646273248511f * (index));
|
||||
@ -95,6 +40,7 @@ static inline float get_fade_gain_curve(char shape, float index) {
|
||||
case 'p': /* parabola (maybe for crossfades) */
|
||||
gain = 1.0f - sqrt(1.0f - index);
|
||||
break;
|
||||
|
||||
case 'P': /* inverted parabola (maybe for fades) */
|
||||
gain = (1.0f - (1.0f - index) * (1.0f - index));
|
||||
break;
|
||||
@ -108,28 +54,28 @@ static inline float get_fade_gain_curve(char shape, float index) {
|
||||
return gain;
|
||||
}
|
||||
|
||||
static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) {
|
||||
static bool get_fade_gain(mix_op_t* op, float* out_cur_vol, int32_t current_subpos) {
|
||||
float cur_vol = 0.0f;
|
||||
|
||||
if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) {
|
||||
cur_vol = mix->vol_start; /* before */
|
||||
if ((current_subpos >= op->time_pre || op->time_pre < 0) && current_subpos < op->time_start) {
|
||||
cur_vol = op->vol_start; /* before */
|
||||
}
|
||||
else if (current_subpos >= mix->time_end && (current_subpos < mix->time_post || mix->time_post < 0)) {
|
||||
cur_vol = mix->vol_end; /* after */
|
||||
else if (current_subpos >= op->time_end && (current_subpos < op->time_post || op->time_post < 0)) {
|
||||
cur_vol = op->vol_end; /* after */
|
||||
}
|
||||
else if (current_subpos >= mix->time_start && current_subpos < mix->time_end) {
|
||||
else if (current_subpos >= op->time_start && current_subpos < op->time_end) {
|
||||
/* in between */
|
||||
float range_vol, range_dur, range_idx, index, gain;
|
||||
|
||||
if (mix->vol_start < mix->vol_end) { /* fade in */
|
||||
range_vol = mix->vol_end - mix->vol_start;
|
||||
range_dur = mix->time_end - mix->time_start;
|
||||
range_idx = current_subpos - mix->time_start;
|
||||
if (op->vol_start < op->vol_end) { /* fade in */
|
||||
range_vol = op->vol_end - op->vol_start;
|
||||
range_dur = op->time_end - op->time_start;
|
||||
range_idx = current_subpos - op->time_start;
|
||||
index = range_idx / range_dur;
|
||||
} else { /* fade out */
|
||||
range_vol = mix->vol_end - mix->vol_start;
|
||||
range_dur = mix->time_end - mix->time_start;
|
||||
range_idx = mix->time_end - current_subpos;
|
||||
range_vol = op->vol_end - op->vol_start;
|
||||
range_dur = op->time_end - op->time_start;
|
||||
range_idx = op->time_end - current_subpos;
|
||||
index = range_idx / range_dur;
|
||||
}
|
||||
|
||||
@ -149,23 +95,79 @@ static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t curr
|
||||
* curves are complementary (exponential fade-in ~= logarithmic fade-out); the following
|
||||
* are described taking fade-in = normal.
|
||||
*/
|
||||
gain = get_fade_gain_curve(mix->shape, index);
|
||||
gain = get_fade_gain_curve(op->shape, index);
|
||||
|
||||
if (mix->vol_start < mix->vol_end) { /* fade in */
|
||||
cur_vol = mix->vol_start + range_vol * gain;
|
||||
if (op->vol_start < op->vol_end) { /* fade in */
|
||||
cur_vol = op->vol_start + range_vol * gain;
|
||||
} else { /* fade out */
|
||||
cur_vol = mix->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain);
|
||||
cur_vol = op->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* fade is outside reach */
|
||||
goto fail;
|
||||
return false;
|
||||
}
|
||||
|
||||
*out_cur_vol = cur_vol;
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
void mixer_op_fade(mixer_t* mixer, int32_t sample_count, mix_op_t* mix) {
|
||||
float* sbuf = mixer->mixbuf;
|
||||
float new_gain = 0.0f;
|
||||
|
||||
int channels = mixer->current_channels;
|
||||
int32_t current_subpos = mixer->current_subpos;
|
||||
|
||||
//TODO optimize for case 0?
|
||||
for (int s = 0; s < sample_count; s++) {
|
||||
bool fade_applies = get_fade_gain(mix, &new_gain, current_subpos);
|
||||
if (!fade_applies) //TODO optimize?
|
||||
continue;
|
||||
|
||||
if (mix->ch_dst < 0) {
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
sbuf[ch] = sbuf[ch] * new_gain;
|
||||
}
|
||||
}
|
||||
else {
|
||||
sbuf[mix->ch_dst] = sbuf[mix->ch_dst] * new_gain;
|
||||
}
|
||||
|
||||
sbuf += channels;
|
||||
current_subpos++;
|
||||
}
|
||||
|
||||
mixer->current_subpos = current_subpos;
|
||||
}
|
||||
|
||||
|
||||
bool mixer_op_fade_is_active(mixer_t* mixer, int32_t current_start, int32_t current_end) {
|
||||
|
||||
for (int i = 0; i < mixer->chain_count; i++) {
|
||||
mix_op_t* mix = &mixer->chain[i];
|
||||
int32_t fade_start, fade_end;
|
||||
float vol_start = mix->vol_start;
|
||||
|
||||
if (mix->type != MIX_FADE)
|
||||
continue;
|
||||
|
||||
/* check is current range falls within a fade
|
||||
* (assuming fades were already optimized on add) */
|
||||
if (mix->time_pre < 0 && vol_start == 1.0) {
|
||||
fade_start = mix->time_start; /* ignore unused */
|
||||
}
|
||||
else {
|
||||
fade_start = mix->time_pre < 0 ? 0 : mix->time_pre;
|
||||
}
|
||||
fade_end = mix->time_post < 0 ? INT_MAX : mix->time_post;
|
||||
|
||||
//;VGM_LOG("MIX: fade test, tp=%i, te=%i, cs=%i, ce=%i\n", mix->time_pre, mix->time_post, current_start, current_end);
|
||||
if (current_start < fade_end && current_end > fade_start) {
|
||||
//;VGM_LOG("MIX: fade active, cs=%i < fe=%i and ce=%i > fs=%i\n", current_start, fade_end, current_end, fade_start);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
66
src/base/mixer_priv.h
Normal file
66
src/base/mixer_priv.h
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef _MIXER_PRIV_H_
|
||||
#define _MIXER_PRIV_H_
|
||||
#include "../streamtypes.h"
|
||||
#include "mixer.h"
|
||||
|
||||
#define VGMSTREAM_MAX_MIXING 512
|
||||
|
||||
typedef enum {
|
||||
MIX_SWAP,
|
||||
MIX_ADD,
|
||||
MIX_VOLUME,
|
||||
MIX_LIMIT,
|
||||
MIX_UPMIX,
|
||||
MIX_DOWNMIX,
|
||||
MIX_KILLMIX,
|
||||
MIX_FADE
|
||||
} mix_type_t;
|
||||
|
||||
typedef struct {
|
||||
mix_type_t type;
|
||||
/* common */
|
||||
int ch_dst;
|
||||
int ch_src;
|
||||
float vol;
|
||||
|
||||
/* fade envelope */
|
||||
float vol_start; /* volume from pre to start */
|
||||
float vol_end; /* volume from end to post */
|
||||
char shape; /* curve type */
|
||||
int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */
|
||||
int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */
|
||||
int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */
|
||||
int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */
|
||||
} mix_op_t;
|
||||
|
||||
struct mixer_t {
|
||||
int input_channels; /* starting channels before mixing */
|
||||
int output_channels; /* resulting channels after mixing */
|
||||
int mixing_channels; /* max channels needed to mix */
|
||||
|
||||
bool active; /* mixing working */
|
||||
|
||||
int chain_count; /* op number */
|
||||
size_t chain_size; /* max ops */
|
||||
mix_op_t chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */
|
||||
|
||||
/* fades only apply at some points, other mixes are active */
|
||||
bool has_non_fade;
|
||||
bool has_fade;
|
||||
|
||||
float* mixbuf; /* internal mixing buffer */
|
||||
int current_channels; /* state: channels may increase/decrease during ops */
|
||||
int32_t current_subpos; /* state: current sample pos in the stream */
|
||||
|
||||
};
|
||||
|
||||
void mixer_op_swap(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
void mixer_op_add(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
void mixer_op_volume(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
void mixer_op_limit(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
void mixer_op_upmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
void mixer_op_downmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
void mixer_op_killmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
void mixer_op_fade(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
bool mixer_op_fade_is_active(mixer_t* mixer, int32_t current_start, int32_t current_end);
|
||||
#endif
|
@ -1,268 +1,71 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "mixing.h"
|
||||
#include "mixing_priv.h"
|
||||
#include "mixing_fades.h"
|
||||
#include "plugins.h"
|
||||
#include "mixer.h"
|
||||
#include "mixer_priv.h"
|
||||
#include "sbuf.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
//TODO simplify
|
||||
/**
|
||||
* Mixing lets vgmstream modify the resulting sample buffer before final output.
|
||||
* This can be implemented in a number of ways but it's done like it is considering
|
||||
* overall simplicity in coding, usage and performance (main complexity is allowing
|
||||
* down/upmixing). Code is mostly independent with some hooks in the main vgmstream
|
||||
* code.
|
||||
* Mixer modifies decoded sample buffer before final output. This is implemented
|
||||
* mostly with simplicity in mind rather than performance. Process:
|
||||
* - detect if mixing applies at current moment or exit (mini performance optimization)
|
||||
* - copy/upgrade buf to float mixbuf if needed
|
||||
* - do mixing ops
|
||||
* - copy/downgrade mixbuf to original buf if needed
|
||||
*
|
||||
* Mixing may add or remove channels. input_channels is the buf's original channels,
|
||||
* and output_channels the resulting buf's channels. buf and mixbuf must be
|
||||
* as big as max channels (mixing_channels).
|
||||
*
|
||||
* Mixing ops are added by a meta (ex. TXTP) or plugin through the API. Non-sensical
|
||||
* mixes are ignored (to avoid rechecking every time).
|
||||
*
|
||||
* Currently, mixing must be manually enabled before starting to decode, because plugins
|
||||
* need to setup bigger bufs when upmixing. (to be changed)
|
||||
*
|
||||
* It works using two buffers:
|
||||
* - outbuf: plugin's pcm16 buffer, at least input_channels*sample_count
|
||||
* - mixbuf: internal's pcmfloat buffer, at least mixing_channels*sample_count
|
||||
* outbuf starts with decoded samples of vgmstream->channel size. This unsures that
|
||||
* if no mixing is done (most common case) we can skip copying samples between buffers.
|
||||
* Resulting outbuf after mixing has samples for ->output_channels (plus garbage).
|
||||
* - output_channels is the resulting total channels (that may be less/more/equal)
|
||||
* - input_channels is normally ->channels or ->output_channels when it's higher
|
||||
*
|
||||
* First, a meta (ex. TXTP) or plugin may add mixing commands through the API,
|
||||
* validated so non-sensical mixes are ignored (to ensure mixing code doesn't
|
||||
* have to recheck every time). Then, before starting to decode mixing must be
|
||||
* manually activated, because plugins need to be ready for possibly different
|
||||
* input/output channels. API could be improved but this way we can avoid having
|
||||
* to update all plugins, while allowing internal setup and layer/segment mixing
|
||||
* (may change in the future for simpler usage).
|
||||
*
|
||||
* Then after decoding normally, vgmstream applies mixing internally:
|
||||
* - detect if mixing is active and needs to be done at this point (some effects
|
||||
* like fades only apply after certain time) and skip otherwise.
|
||||
* - copy outbuf to mixbuf, as using a float buffer to increase accuracy (most ops
|
||||
* apply float volumes) and slightly improve performance (avoids doing
|
||||
* int16-to-float casts per mix, as it's not free)
|
||||
* - apply all mixes on mixbuf
|
||||
* - copy mixbuf to outbuf
|
||||
* segmented/layered layouts handle mixing on their own.
|
||||
*
|
||||
* Mixing is tuned for most common case (no mix except fade-out at the end) and is
|
||||
* fast enough but not super-optimized yet, there is some penalty the more effects
|
||||
* are applied. Maybe could add extra sub-ops to avoid ifs and dumb values (volume=0.0
|
||||
* could simply use a clear op), only use mixbuf if necessary (swap can be done without
|
||||
* mixbuf if it goes first) or add function pointer indexes but isn't too important.
|
||||
* Operations are applied once per "step" with 1 sample from all channels to simplify code
|
||||
* (and maybe improve memory cache?), though maybe it should call one function per operation.
|
||||
*/
|
||||
|
||||
/* ******************************************************************* */
|
||||
|
||||
static void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) {
|
||||
for (int s = 0; s < samples * channels; s++) {
|
||||
/* 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
|
||||
*/
|
||||
buf_s16[s] = clamp16( (int32_t)buf_f32[s] );
|
||||
static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) {
|
||||
int32_t current_pos;
|
||||
|
||||
if (vgmstream->config_enabled) {
|
||||
return vgmstream->pstate.play_position;
|
||||
}
|
||||
|
||||
if (vgmstream->loop_flag && vgmstream->loop_count > 0) {
|
||||
int loop_pre = vgmstream->loop_start_sample; /* samples before looping */
|
||||
int loop_into = (vgmstream->current_sample - vgmstream->loop_start_sample); /* samples after loop */
|
||||
int loop_samples = (vgmstream->loop_end_sample - vgmstream->loop_start_sample); /* looped section */
|
||||
|
||||
current_pos = loop_pre + (loop_samples * vgmstream->loop_count) + loop_into - sample_count;
|
||||
}
|
||||
else {
|
||||
current_pos = (vgmstream->current_sample - sample_count);
|
||||
}
|
||||
|
||||
return current_pos;
|
||||
}
|
||||
|
||||
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch, s, m, ok;
|
||||
|
||||
int32_t current_subpos = 0;
|
||||
float temp_f, temp_min, temp_max, cur_vol = 0.0f;
|
||||
float *temp_mixbuf;
|
||||
sample_t *temp_outbuf;
|
||||
|
||||
const float limiter_max = 32767.0f;
|
||||
const float limiter_min = -32768.0f;
|
||||
|
||||
/* no support or not need to apply */
|
||||
if (!data || !data->mixing_on || data->mixing_count == 0)
|
||||
if (!mixer_is_active(vgmstream->mixer))
|
||||
return;
|
||||
|
||||
/* try to skip if no fades apply (set but does nothing yet) + only has fades */
|
||||
if (data->has_fade) {
|
||||
int32_t current_pos = get_current_pos(vgmstream, sample_count);
|
||||
//;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, is_fade_active(data, current_pos, current_pos + sample_count));
|
||||
if (!data->has_non_fade && !is_fade_active(data, current_pos, current_pos + sample_count))
|
||||
return;
|
||||
//;VGM_LOG("MIX: fade pos=%i\n", current_pos);
|
||||
current_subpos = current_pos;
|
||||
}
|
||||
int32_t current_pos = get_current_pos(vgmstream, sample_count);
|
||||
|
||||
|
||||
/* use advancing buffer pointers to simplify logic */
|
||||
temp_mixbuf = data->mixbuf; /* you'd think using a int32 temp buf would be faster but somehow it's slower? */
|
||||
temp_outbuf = outbuf;
|
||||
|
||||
/* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change
|
||||
* total channels, channel number meaning varies as ops move them around, ex:
|
||||
* - 4ch w/ "1-2,2+3" = ch1<>ch3, ch2(old ch1)+ch3 = 4ch: ch2 ch1+ch3 ch3 ch4
|
||||
* - 4ch w/ "2+3,1-2" = ch2+ch3, ch1<>ch2(modified) = 4ch: ch2+ch3 ch1 ch3 ch4
|
||||
* - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2
|
||||
* - 2ch w/ "1u,1+2" = ch1(add and push rest) = 3ch: ch1'+ch1 ch1 ch2
|
||||
* - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1
|
||||
* - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2
|
||||
*/
|
||||
|
||||
/* apply mixes in order per channel */
|
||||
for (s = 0; s < sample_count; s++) {
|
||||
/* reset after new sample 'step'*/
|
||||
float *stpbuf = temp_mixbuf;
|
||||
int step_channels = vgmstream->channels;
|
||||
|
||||
for (ch = 0; ch < step_channels; ch++) {
|
||||
stpbuf[ch] = temp_outbuf[ch]; /* copy current 'lane' */
|
||||
}
|
||||
|
||||
for (m = 0; m < data->mixing_count; m++) {
|
||||
mix_command_data *mix = &data->mixing_chain[m];
|
||||
|
||||
switch(mix->command) {
|
||||
|
||||
case MIX_SWAP:
|
||||
temp_f = stpbuf[mix->ch_dst];
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_src];
|
||||
stpbuf[mix->ch_src] = temp_f;
|
||||
break;
|
||||
|
||||
case MIX_ADD:
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src] * mix->vol;
|
||||
break;
|
||||
|
||||
case MIX_ADD_COPY:
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] + stpbuf[mix->ch_src];
|
||||
break;
|
||||
|
||||
case MIX_VOLUME:
|
||||
if (mix->ch_dst < 0) {
|
||||
for (ch = 0; ch < step_channels; ch++) {
|
||||
stpbuf[ch] = stpbuf[ch] * mix->vol;
|
||||
}
|
||||
}
|
||||
else {
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * mix->vol;
|
||||
}
|
||||
break;
|
||||
|
||||
case MIX_LIMIT:
|
||||
temp_max = limiter_max * mix->vol;
|
||||
temp_min = limiter_min * mix->vol;
|
||||
|
||||
if (mix->ch_dst < 0) {
|
||||
for (ch = 0; ch < step_channels; ch++) {
|
||||
if (stpbuf[ch] > temp_max)
|
||||
stpbuf[ch] = temp_max;
|
||||
else if (stpbuf[ch] < temp_min)
|
||||
stpbuf[ch] = temp_min;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (stpbuf[mix->ch_dst] > temp_max)
|
||||
stpbuf[mix->ch_dst] = temp_max;
|
||||
else if (stpbuf[mix->ch_dst] < temp_min)
|
||||
stpbuf[mix->ch_dst] = temp_min;
|
||||
}
|
||||
break;
|
||||
|
||||
case MIX_UPMIX:
|
||||
step_channels += 1;
|
||||
for (ch = step_channels - 1; ch > mix->ch_dst; ch--) {
|
||||
stpbuf[ch] = stpbuf[ch-1]; /* 'push' channels forward (or pull backwards) */
|
||||
}
|
||||
stpbuf[mix->ch_dst] = 0; /* inserted as silent */
|
||||
break;
|
||||
|
||||
case MIX_DOWNMIX:
|
||||
step_channels -= 1;
|
||||
for (ch = mix->ch_dst; ch < step_channels; ch++) {
|
||||
stpbuf[ch] = stpbuf[ch+1]; /* 'pull' channels back */
|
||||
}
|
||||
break;
|
||||
|
||||
case MIX_KILLMIX:
|
||||
step_channels = mix->ch_dst; /* clamp channels */
|
||||
break;
|
||||
|
||||
case MIX_FADE:
|
||||
ok = get_fade_gain(mix, &cur_vol, current_subpos);
|
||||
if (!ok) {
|
||||
break; /* fade doesn't apply right now */
|
||||
}
|
||||
|
||||
if (mix->ch_dst < 0) {
|
||||
for (ch = 0; ch < step_channels; ch++) {
|
||||
stpbuf[ch] = stpbuf[ch] * cur_vol;
|
||||
}
|
||||
}
|
||||
else {
|
||||
stpbuf[mix->ch_dst] = stpbuf[mix->ch_dst] * cur_vol;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
current_subpos++;
|
||||
|
||||
temp_mixbuf += step_channels;
|
||||
temp_outbuf += vgmstream->channels;
|
||||
}
|
||||
|
||||
/* copy resulting temp mix to output */
|
||||
sbuf_copy_f32_to_s16(outbuf, data->mixbuf, sample_count, data->output_channels);
|
||||
mixer_process(vgmstream->mixer, outbuf, sample_count, current_pos);
|
||||
}
|
||||
|
||||
/* ******************************************************************* */
|
||||
|
||||
void mixing_init(VGMSTREAM* vgmstream) {
|
||||
mixing_data *data = calloc(1, sizeof(mixing_data));
|
||||
if (!data) goto fail;
|
||||
|
||||
data->mixing_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */
|
||||
data->mixing_channels = vgmstream->channels;
|
||||
data->output_channels = vgmstream->channels;
|
||||
|
||||
vgmstream->mixing_data = data;
|
||||
return;
|
||||
|
||||
fail:
|
||||
free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
void mixing_close(VGMSTREAM* vgmstream) {
|
||||
mixing_data *data = NULL;
|
||||
if (!vgmstream) return;
|
||||
|
||||
data = vgmstream->mixing_data;
|
||||
if (!data) return;
|
||||
|
||||
free(data->mixbuf);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void mixing_update_channel(VGMSTREAM* vgmstream) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
if (!data) return;
|
||||
|
||||
/* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */
|
||||
data->mixing_channels++;
|
||||
data->output_channels++;
|
||||
}
|
||||
|
||||
|
||||
/* ******************************************************************* */
|
||||
|
||||
static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
|
||||
int i;
|
||||
mixing_data* data = vgmstream->mixing_data;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
layered_layout_data* layout_data;
|
||||
uint32_t prev_cl;
|
||||
|
||||
@ -272,7 +75,7 @@ static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
|
||||
layout_data = vgmstream->layout_data;
|
||||
|
||||
/* mainly layer-v (in cases of layers-within-layers should cascade) */
|
||||
if (data->output_channels != layout_data->layers[0]->channels)
|
||||
if (mixer->output_channels != layout_data->layers[0]->channels)
|
||||
return 0;
|
||||
|
||||
/* check all layers share layout (implicitly works as a channel check, if not 0) */
|
||||
@ -280,7 +83,7 @@ static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
|
||||
if (prev_cl == 0)
|
||||
return 0;
|
||||
|
||||
for (i = 1; i < layout_data->layer_count; i++) {
|
||||
for (int i = 1; i < layout_data->layer_count; i++) {
|
||||
uint32_t layer_cl = layout_data->layers[i]->channel_layout;
|
||||
if (prev_cl != layer_cl)
|
||||
return 0;
|
||||
@ -294,7 +97,7 @@ static int fix_layered_channel_layout(VGMSTREAM* vgmstream) {
|
||||
|
||||
/* channel layout + down/upmixing = ?, salvage what we can */
|
||||
static void fix_channel_layout(VGMSTREAM* vgmstream) {
|
||||
mixing_data* data = vgmstream->mixing_data;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
|
||||
if (fix_layered_channel_layout(vgmstream))
|
||||
goto done;
|
||||
@ -302,7 +105,7 @@ static void fix_channel_layout(VGMSTREAM* vgmstream) {
|
||||
/* segments should share channel layout automatically */
|
||||
|
||||
/* a bit wonky but eh... */
|
||||
if (vgmstream->channel_layout && vgmstream->channels != data->output_channels) {
|
||||
if (vgmstream->channel_layout && vgmstream->channels != mixer->output_channels) {
|
||||
vgmstream->channel_layout = 0;
|
||||
}
|
||||
|
||||
@ -310,22 +113,23 @@ done:
|
||||
((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = vgmstream->channel_layout;
|
||||
}
|
||||
|
||||
void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
float *mixbuf_re = NULL;
|
||||
|
||||
if (!data) goto fail;
|
||||
void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count) {
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
|
||||
if (!mixer)
|
||||
return;
|
||||
|
||||
/* special value to not actually enable anything (used to query values) */
|
||||
if (max_sample_count <= 0)
|
||||
goto fail;
|
||||
return;
|
||||
|
||||
/* create or alter internal buffer */
|
||||
mixbuf_re = realloc(data->mixbuf, max_sample_count*data->mixing_channels*sizeof(float));
|
||||
float* mixbuf_re = realloc(mixer->mixbuf, max_sample_count * mixer->mixing_channels * sizeof(float));
|
||||
if (!mixbuf_re) goto fail;
|
||||
|
||||
data->mixbuf = mixbuf_re;
|
||||
data->mixing_on = 1;
|
||||
mixer->mixbuf = mixbuf_re;
|
||||
mixer->active = true;
|
||||
|
||||
fix_channel_layout(vgmstream);
|
||||
|
||||
@ -340,14 +144,15 @@ fail:
|
||||
}
|
||||
|
||||
void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_channels) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
int input_channels, output_channels;
|
||||
|
||||
if (!data) goto fail;
|
||||
if (!mixer)
|
||||
goto fail;
|
||||
|
||||
output_channels = data->output_channels;
|
||||
if (data->output_channels > vgmstream->channels)
|
||||
input_channels = data->output_channels;
|
||||
output_channels = mixer->output_channels;
|
||||
if (mixer->output_channels > vgmstream->channels)
|
||||
input_channels = mixer->output_channels;
|
||||
else
|
||||
input_channels = vgmstream->channels;
|
||||
|
||||
|
@ -7,19 +7,13 @@
|
||||
* outbuf must big enough to hold output_channels*samples_to_do */
|
||||
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
|
||||
/* internal mixing pre-setup for vgmstream (doesn't imply usage).
|
||||
* If init somehow fails next calls are ignored. */
|
||||
void mixing_init(VGMSTREAM* vgmstream);
|
||||
void mixing_close(VGMSTREAM* vgmstream);
|
||||
void mixing_update_channel(VGMSTREAM* vgmstream);
|
||||
|
||||
/* Call to let vgmstream apply mixing, which must handle input/output_channels.
|
||||
* Once mixing is active any new mixes are ignored (to avoid the possibility
|
||||
* of down/upmixing without querying input/output_channels). */
|
||||
void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count);
|
||||
void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count);
|
||||
|
||||
/* gets current mixing info */
|
||||
void mixing_info(VGMSTREAM * vgmstream, int *input_channels, int *output_channels);
|
||||
void mixing_info(VGMSTREAM* vgmstream, int *input_channels, int *output_channels);
|
||||
|
||||
/* adds mixes filtering and optimizing if needed */
|
||||
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src);
|
||||
@ -39,4 +33,4 @@ void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode);
|
||||
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/);
|
||||
|
||||
|
||||
#endif /* _MIXING_H_ */
|
||||
#endif
|
||||
|
@ -1,172 +1,170 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "mixing.h"
|
||||
#include "mixing_priv.h"
|
||||
#include "mixer_priv.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
if (!data) return 0;
|
||||
static bool add_mixing(VGMSTREAM* vgmstream, mix_op_t* op) {
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
if (!mixer)
|
||||
return false;
|
||||
|
||||
|
||||
if (data->mixing_on) {
|
||||
VGM_LOG("MIX: ignoring new mixes when mixing active\n");
|
||||
return 0; /* to avoid down/upmixing after activation */
|
||||
if (mixer->active) {
|
||||
VGM_LOG("MIX: ignoring new ops when mixer is active\n");
|
||||
return false; /* to avoid down/upmixing after activation */
|
||||
}
|
||||
|
||||
if (data->mixing_count + 1 > data->mixing_size) {
|
||||
if (mixer->chain_count + 1 > mixer->chain_size) {
|
||||
VGM_LOG("MIX: too many mixes\n");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
data->mixing_chain[data->mixing_count] = *mix; /* memcpy */
|
||||
data->mixing_count++;
|
||||
mixer->chain[mixer->chain_count] = *op; /* memcpy */
|
||||
mixer->chain_count++;
|
||||
|
||||
|
||||
if (mix->command == MIX_FADE) {
|
||||
data->has_fade = 1;
|
||||
if (op->type == MIX_FADE) {
|
||||
mixer->has_fade = true;
|
||||
}
|
||||
else {
|
||||
data->has_non_fade = 1;
|
||||
mixer->has_non_fade = true;
|
||||
}
|
||||
|
||||
//;VGM_LOG("MIX: total %i\n", data->mixing_count);
|
||||
return 1;
|
||||
//;VGM_LOG("MIX: total %i\n", data->chain_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
mix_op_t op = {0};
|
||||
|
||||
if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return;
|
||||
if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return;
|
||||
mix.command = MIX_SWAP;
|
||||
mix.ch_dst = ch_dst;
|
||||
mix.ch_src = ch_src;
|
||||
if (!mixer || ch_dst >= mixer->output_channels || ch_src >= mixer->output_channels) return;
|
||||
op.type = MIX_SWAP;
|
||||
op.ch_dst = ch_dst;
|
||||
op.ch_src = ch_src;
|
||||
|
||||
add_mixing(vgmstream, &mix);
|
||||
add_mixing(vgmstream, &op);
|
||||
}
|
||||
|
||||
void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
if (!data) return;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
mix_op_t op = {0};
|
||||
if (!mixer) return;
|
||||
|
||||
//if (volume < 0.0) return; /* negative volume inverts the waveform */
|
||||
if (volume == 0.0) return; /* ch_src becomes silent and nothing is added */
|
||||
if (ch_dst < 0 || ch_src < 0) return;
|
||||
if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return;
|
||||
if (!mixer || ch_dst >= mixer->output_channels || ch_src >= mixer->output_channels) return;
|
||||
|
||||
mix.command = (volume == 1.0) ? MIX_ADD_COPY : MIX_ADD;
|
||||
mix.ch_dst = ch_dst;
|
||||
mix.ch_src = ch_src;
|
||||
mix.vol = volume;
|
||||
op.type = MIX_ADD;
|
||||
op.ch_dst = ch_dst;
|
||||
op.ch_src = ch_src;
|
||||
op.vol = volume;
|
||||
|
||||
//;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,volume);
|
||||
add_mixing(vgmstream, &mix);
|
||||
add_mixing(vgmstream, &op);
|
||||
}
|
||||
|
||||
void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
mix_op_t op = {0};
|
||||
|
||||
//if (ch_dst < 0) return; /* means all channels */
|
||||
//if (volume < 0.0) return; /* negative volume inverts the waveform */
|
||||
if (volume == 1.0) return; /* no change */
|
||||
if (!data || ch_dst >= data->output_channels) return;
|
||||
if (!mixer || ch_dst >= mixer->output_channels) return;
|
||||
|
||||
mix.command = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */
|
||||
mix.ch_dst = ch_dst;
|
||||
mix.vol = volume;
|
||||
op.type = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */
|
||||
op.ch_dst = ch_dst;
|
||||
op.vol = volume;
|
||||
|
||||
//;VGM_LOG("MIX: volume %i*%f\n", ch_dst,volume);
|
||||
add_mixing(vgmstream, &mix);
|
||||
add_mixing(vgmstream, &op);
|
||||
}
|
||||
|
||||
void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
mix_op_t op = {0};
|
||||
|
||||
//if (ch_dst < 0) return; /* means all channels */
|
||||
if (volume < 0.0) return;
|
||||
if (volume == 1.0) return; /* no actual difference */
|
||||
if (!data || ch_dst >= data->output_channels) return;
|
||||
if (!mixer || ch_dst >= mixer->output_channels) return;
|
||||
//if (volume == 0.0) return; /* dumb but whatevs */
|
||||
|
||||
mix.command = MIX_LIMIT;
|
||||
mix.ch_dst = ch_dst;
|
||||
mix.vol = volume;
|
||||
op.type = MIX_LIMIT;
|
||||
op.ch_dst = ch_dst;
|
||||
op.vol = volume;
|
||||
|
||||
add_mixing(vgmstream, &mix);
|
||||
add_mixing(vgmstream, &op);
|
||||
}
|
||||
|
||||
void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
mix_op_t op = {0};
|
||||
int ok;
|
||||
|
||||
if (ch_dst < 0) return;
|
||||
if (!data || ch_dst > data->output_channels || data->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return;
|
||||
if (!mixer || ch_dst > mixer->output_channels || mixer->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return;
|
||||
/* dst can be == output_channels here, since we are inserting */
|
||||
|
||||
mix.command = MIX_UPMIX;
|
||||
mix.ch_dst = ch_dst;
|
||||
op.type = MIX_UPMIX;
|
||||
op.ch_dst = ch_dst;
|
||||
|
||||
ok = add_mixing(vgmstream, &mix);
|
||||
ok = add_mixing(vgmstream, &op);
|
||||
if (ok) {
|
||||
data->output_channels += 1;
|
||||
if (data->mixing_channels < data->output_channels)
|
||||
data->mixing_channels = data->output_channels;
|
||||
mixer->output_channels += 1;
|
||||
if (mixer->mixing_channels < mixer->output_channels)
|
||||
mixer->mixing_channels = mixer->output_channels;
|
||||
}
|
||||
}
|
||||
|
||||
void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
mix_op_t op = {0};
|
||||
int ok;
|
||||
|
||||
if (ch_dst < 0) return;
|
||||
if (!data || ch_dst >= data->output_channels || data->output_channels - 1 < 1) return;
|
||||
if (!mixer || ch_dst >= mixer->output_channels || mixer->output_channels - 1 < 1) return;
|
||||
|
||||
mix.command = MIX_DOWNMIX;
|
||||
mix.ch_dst = ch_dst;
|
||||
op.type = MIX_DOWNMIX;
|
||||
op.ch_dst = ch_dst;
|
||||
|
||||
ok = add_mixing(vgmstream, &mix);
|
||||
ok = add_mixing(vgmstream, &op);
|
||||
if (ok) {
|
||||
data->output_channels -= 1;
|
||||
mixer->output_channels -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
int ok;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
mix_op_t op = {0};
|
||||
|
||||
if (ch_dst <= 0) return; /* can't kill from first channel */
|
||||
if (!data || ch_dst >= data->output_channels) return;
|
||||
if (!mixer || ch_dst >= mixer->output_channels) return;
|
||||
|
||||
mix.command = MIX_KILLMIX;
|
||||
mix.ch_dst = ch_dst;
|
||||
op.type = MIX_KILLMIX;
|
||||
op.ch_dst = ch_dst;
|
||||
|
||||
//;VGM_LOG("MIX: killmix %i\n", ch_dst);
|
||||
ok = add_mixing(vgmstream, &mix);
|
||||
bool ok = add_mixing(vgmstream, &op);
|
||||
if (ok) {
|
||||
data->output_channels = ch_dst; /* clamp channels */
|
||||
mixer->output_channels = ch_dst; /* clamp channels */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static mix_command_data* get_last_fade(mixing_data *data, int target_channel) {
|
||||
int i;
|
||||
for (i = data->mixing_count; i > 0; i--) {
|
||||
mix_command_data *mix = &data->mixing_chain[i-1];
|
||||
if (mix->command != MIX_FADE)
|
||||
static mix_op_t* get_last_fade(mixer_t* mixer, int target_channel) {
|
||||
for (int i = mixer->chain_count; i > 0; i--) {
|
||||
mix_op_t* op = &mixer->chain[i-1];
|
||||
if (op->type != MIX_FADE)
|
||||
continue;
|
||||
if (mix->ch_dst == target_channel)
|
||||
return mix;
|
||||
if (op->ch_dst == target_channel)
|
||||
return op;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -175,13 +173,13 @@ static mix_command_data* get_last_fade(mixing_data *data, int target_channel) {
|
||||
|
||||
void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape,
|
||||
int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mix_command_data mix = {0};
|
||||
mix_command_data *mix_prev;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
mix_op_t op = {0};
|
||||
mix_op_t* op_prev;
|
||||
|
||||
|
||||
//if (ch_dst < 0) return; /* means all channels */
|
||||
if (!data || ch_dst >= data->output_channels) return;
|
||||
if (!mixer || ch_dst >= mixer->output_channels) return;
|
||||
if (time_pre > time_start || time_start > time_end || (time_post >= 0 && time_end > time_post)) return;
|
||||
if (time_start < 0 || time_end < 0) return;
|
||||
//if (time_pre < 0 || time_post < 0) return; /* special meaning of file start/end */
|
||||
@ -192,15 +190,15 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
|
||||
if (shape == '(' || shape == ')')
|
||||
shape = 'H';
|
||||
|
||||
mix.command = MIX_FADE;
|
||||
mix.ch_dst = ch_dst;
|
||||
mix.vol_start = vol_start;
|
||||
mix.vol_end = vol_end;
|
||||
mix.shape = shape;
|
||||
mix.time_pre = time_pre;
|
||||
mix.time_start = time_start;
|
||||
mix.time_end = time_end;
|
||||
mix.time_post = time_post;
|
||||
op.type = MIX_FADE;
|
||||
op.ch_dst = ch_dst;
|
||||
op.vol_start = vol_start;
|
||||
op.vol_end = vol_end;
|
||||
op.shape = shape;
|
||||
op.time_pre = time_pre;
|
||||
op.time_start = time_start;
|
||||
op.time_end = time_end;
|
||||
op.time_post = time_post;
|
||||
|
||||
|
||||
/* cancel fades and optimize a bit when using negative pre/post:
|
||||
@ -216,33 +214,33 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
|
||||
* as they're uncommon and hard to optimize
|
||||
* fades cancel fades of the same channel, and 'all channel' (-1) fades also cancel 'all channels'
|
||||
*/
|
||||
mix_prev = get_last_fade(data, mix.ch_dst);
|
||||
if (mix_prev == NULL) {
|
||||
op_prev = get_last_fade(mixer, op.ch_dst);
|
||||
if (op_prev == NULL) {
|
||||
if (vol_start == 1.0 && time_pre < 0)
|
||||
time_pre = time_start; /* fade-out helds default volume before fade start can be clamped */
|
||||
if (vol_end == 1.0 && time_post < 0)
|
||||
time_post = time_end; /* fade-in helds default volume after fade end can be clamped */
|
||||
}
|
||||
else if (mix_prev->time_post < 0 || mix.time_pre < 0) {
|
||||
else if (op_prev->time_post < 0 || op.time_pre < 0) {
|
||||
int is_prev = 1;
|
||||
/* test if prev is really cancelled by this */
|
||||
if ((mix_prev->time_end > mix.time_start) ||
|
||||
(mix_prev->time_post >= 0 && mix_prev->time_post > mix.time_start) ||
|
||||
(mix.time_pre >= 0 && mix.time_pre < mix_prev->time_end))
|
||||
if ((op_prev->time_end > op.time_start) ||
|
||||
(op_prev->time_post >= 0 && op_prev->time_post > op.time_start) ||
|
||||
(op.time_pre >= 0 && op.time_pre < op_prev->time_end))
|
||||
is_prev = 0;
|
||||
|
||||
if (is_prev) {
|
||||
/* change negative values to actual points */
|
||||
if (mix_prev->time_post < 0 && mix.time_pre < 0) {
|
||||
mix_prev->time_post = mix_prev->time_end;
|
||||
mix.time_pre = mix_prev->time_post;
|
||||
if (op_prev->time_post < 0 && op.time_pre < 0) {
|
||||
op_prev->time_post = op_prev->time_end;
|
||||
op.time_pre = op_prev->time_post;
|
||||
}
|
||||
|
||||
if (mix_prev->time_post >= 0 && mix.time_pre < 0) {
|
||||
mix.time_pre = mix_prev->time_post;
|
||||
if (op_prev->time_post >= 0 && op.time_pre < 0) {
|
||||
op.time_pre = op_prev->time_post;
|
||||
}
|
||||
else if (mix_prev->time_post < 0 && mix.time_pre >= 0) {
|
||||
mix_prev->time_post = mix.time_pre;
|
||||
else if (op_prev->time_post < 0 && op.time_pre >= 0) {
|
||||
op_prev->time_post = op.time_pre;
|
||||
}
|
||||
/* else: both define start/ends, do nothing */
|
||||
}
|
||||
@ -250,5 +248,5 @@ void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double
|
||||
}
|
||||
|
||||
//;VGM_LOG("MIX: fade %i^%f~%f=%c@%i~%i~%i~%i\n", ch_dst, vol_start, vol_end, shape, time_pre, time_start, time_end, time_post);
|
||||
add_mixing(vgmstream, &mix);
|
||||
add_mixing(vgmstream, &op);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "mixing.h"
|
||||
#include "mixing_priv.h"
|
||||
#include "mixer_priv.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
@ -11,10 +11,8 @@
|
||||
#define MIX_MACRO_BGM 'b'
|
||||
|
||||
void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch;
|
||||
|
||||
if (!data)
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
if (!mixer)
|
||||
return;
|
||||
|
||||
if (mask == 0) {
|
||||
@ -22,7 +20,7 @@ void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ch = 0; ch < data->output_channels; ch++) {
|
||||
for (int ch = 0; ch < mixer->output_channels; ch++) {
|
||||
if (!((mask >> ch) & 1))
|
||||
continue;
|
||||
mixing_push_volume(vgmstream, ch, volume);
|
||||
@ -30,10 +28,8 @@ void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) {
|
||||
}
|
||||
|
||||
void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
int ch;
|
||||
|
||||
if (!data)
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
if (!mixer)
|
||||
return;
|
||||
|
||||
if (mask == 0) {
|
||||
@ -41,7 +37,7 @@ void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
|
||||
}
|
||||
|
||||
/* reverse remove all channels (easier this way as when removing channels numbers change) */
|
||||
for (ch = data->output_channels - 1; ch >= 0; ch--) {
|
||||
for (int ch = mixer->output_channels - 1; ch >= 0; ch--) {
|
||||
if ((mask >> ch) & 1)
|
||||
continue;
|
||||
mixing_push_downmix(vgmstream, ch);
|
||||
@ -51,16 +47,13 @@ void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) {
|
||||
|
||||
/* get highest channel count */
|
||||
static int get_layered_max_channels(VGMSTREAM* vgmstream) {
|
||||
int i, max;
|
||||
layered_layout_data* data;
|
||||
|
||||
if (vgmstream->layout_type != layout_layered)
|
||||
return 0;
|
||||
|
||||
data = vgmstream->layout_data;
|
||||
layered_layout_data* data = vgmstream->layout_data;
|
||||
|
||||
max = 0;
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
int max = 0;
|
||||
for (int i = 0; i < data->layer_count; i++) {
|
||||
int output_channels = 0;
|
||||
|
||||
mixing_info(data->layers[i], NULL, &output_channels);
|
||||
@ -73,11 +66,6 @@ static int get_layered_max_channels(VGMSTREAM* vgmstream) {
|
||||
}
|
||||
|
||||
static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
int i;
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
layered_layout_data* l_data;
|
||||
|
||||
|
||||
if (vgmstream->layout_type != layout_layered)
|
||||
return 0;
|
||||
|
||||
@ -86,15 +74,16 @@ static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
return 0;
|
||||
|
||||
/* no channel down/upmixing (cannot guess output) */
|
||||
for (i = 0; i < data->mixing_count; i++) {
|
||||
mix_command_t mix = data->mixing_chain[i].command;
|
||||
if (mix == MIX_UPMIX || mix == MIX_DOWNMIX || mix == MIX_KILLMIX) /*mix == MIX_SWAP || ??? */
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
for (int i = 0; i < mixer->chain_count; i++) {
|
||||
mix_type_t type = mixer->chain[i].type;
|
||||
if (type == MIX_UPMIX || type == MIX_DOWNMIX || type == MIX_KILLMIX) /*type == MIX_SWAP || ??? */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* only previsible cases */
|
||||
l_data = vgmstream->layout_data;
|
||||
for (i = 0; i < l_data->layer_count; i++) {
|
||||
layered_layout_data* l_data = vgmstream->layout_data;
|
||||
for (int i = 0; i < l_data->layer_count; i++) {
|
||||
int output_channels = 0;
|
||||
|
||||
mixing_info(l_data->layers[i], NULL, &output_channels);
|
||||
@ -110,9 +99,8 @@ static int is_layered_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
/* special layering, where channels are respected (so Ls only go to Ls), also more optimized */
|
||||
static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
layered_layout_data* ldata = vgmstream->layout_data;
|
||||
int i, ch;
|
||||
int target_layer = 0, target_chs = 0, ch_max, target_ch = 0, target_silence = 0;
|
||||
int ch_num;
|
||||
int target_layer = 0, target_chs = 0, target_ch = 0, target_silence = 0;
|
||||
int ch_num, ch_max;
|
||||
|
||||
/* With N layers like: (ch1 ch2) (ch1 ch2 ch3 ch4) (ch1 ch2), output is normally 2+4+2=8ch.
|
||||
* We want to find highest layer (ch1..4) = 4ch, add other channels to it and drop them */
|
||||
@ -120,7 +108,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
/* find target "main" channels (will be first most of the time) */
|
||||
ch_num = 0;
|
||||
ch_max = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
for (int i = 0; i < ldata->layer_count; i++) {
|
||||
int layer_chs = 0;
|
||||
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
@ -148,7 +136,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
/* add other channels to target (assumes standard channel mapping to simplify)
|
||||
* most of the time all layers will have same number of channels though */
|
||||
ch_num = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
for (int i = 0; i < ldata->layer_count; i++) {
|
||||
int layer_chs = 0;
|
||||
|
||||
if (target_layer == i) {
|
||||
@ -165,7 +153,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
|
||||
if (layer_chs == target_chs) {
|
||||
/* 1:1 mapping */
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
for (int ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
|
||||
}
|
||||
}
|
||||
@ -184,7 +172,7 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
break;
|
||||
default: /* less common */
|
||||
//TODO add other mixes, depends on target_chs + mapping (ex. 4.0 to 5.0 != 5.1, 2.1 xiph to 5.1 != 5.1 xiph)
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
for (int ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_add(vgmstream, target_ch + ch, ch_num + ch, 1.0);
|
||||
}
|
||||
break;
|
||||
@ -196,13 +184,13 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
|
||||
/* drop non-target channels */
|
||||
ch_num = 0;
|
||||
for (i = 0; i < ldata->layer_count; i++) {
|
||||
for (int i = 0; i < ldata->layer_count; i++) {
|
||||
|
||||
if (i < target_layer) { /* least common, hopefully (slower to drop chs 1 by 1) */
|
||||
int layer_chs = 0;
|
||||
mixing_info(ldata->layers[i], NULL, &layer_chs);
|
||||
|
||||
for (ch = 0; ch < layer_chs; ch++) {
|
||||
for (int ch = 0; ch < layer_chs; ch++) {
|
||||
mixing_push_downmix(vgmstream, ch_num); //+ ch
|
||||
}
|
||||
|
||||
@ -220,10 +208,10 @@ static void mixing_macro_layer_auto(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
|
||||
|
||||
void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
int current, ch, output_channels, selected_channels;
|
||||
|
||||
if (!data)
|
||||
if (!mixer)
|
||||
return;
|
||||
|
||||
if (is_layered_auto(vgmstream, max, mode)) {
|
||||
@ -236,7 +224,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
||||
if (max == 0) /* auto calculate */
|
||||
max = get_layered_max_channels(vgmstream);
|
||||
|
||||
if (max <= 0 || data->output_channels <= max)
|
||||
if (max <= 0 || mixer->output_channels <= max)
|
||||
return;
|
||||
|
||||
/* set all channels (non-existant channels will be ignored) */
|
||||
@ -245,7 +233,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
||||
}
|
||||
|
||||
/* save before adding fake channels */
|
||||
output_channels = data->output_channels;
|
||||
output_channels = mixer->output_channels;
|
||||
|
||||
/* count possibly set channels */
|
||||
selected_channels = 0;
|
||||
@ -304,19 +292,19 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
||||
}
|
||||
|
||||
void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
int current, ch, track, track_ch, track_num, output_channels;
|
||||
int32_t change_pos, change_next, change_time;
|
||||
|
||||
if (!data)
|
||||
if (!mixer)
|
||||
return;
|
||||
if (max <= 0 || data->output_channels <= max)
|
||||
if (max <= 0 || mixer->output_channels <= max)
|
||||
return;
|
||||
if (!vgmstream->loop_flag) /* maybe force loop? */
|
||||
return;
|
||||
|
||||
/* this probably only makes sense for even channels so upmix before if needed) */
|
||||
output_channels = data->output_channels;
|
||||
output_channels = mixer->output_channels;
|
||||
if (output_channels % 2) {
|
||||
mixing_push_upmix(vgmstream, output_channels);
|
||||
output_channels += 1;
|
||||
@ -368,19 +356,19 @@ void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) {
|
||||
}
|
||||
|
||||
void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
int current, ch, layer, layer_ch, layer_num, loop, output_channels;
|
||||
int32_t change_pos, change_time;
|
||||
|
||||
if (!data)
|
||||
if (!mixer)
|
||||
return;
|
||||
if (max <= 0 || data->output_channels <= max)
|
||||
if (max <= 0 || mixer->output_channels <= max)
|
||||
return;
|
||||
if (!vgmstream->loop_flag) /* maybe force loop? */
|
||||
return;
|
||||
|
||||
/* this probably only makes sense for even channels so upmix before if needed) */
|
||||
output_channels = data->output_channels;
|
||||
output_channels = mixer->output_channels;
|
||||
if (output_channels % 2) {
|
||||
mixing_push_upmix(vgmstream, output_channels);
|
||||
output_channels += 1;
|
||||
@ -481,7 +469,7 @@ typedef enum {
|
||||
} mixing_position_t;
|
||||
|
||||
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) {
|
||||
mixing_data *data = vgmstream->mixing_data;
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
int ch, output_channels, mp_in, mp_out, ch_in, ch_out;
|
||||
channel_mapping_t input_mapping, output_mapping;
|
||||
const double vol_max = 1.0;
|
||||
@ -490,15 +478,15 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map
|
||||
double matrix[16][16] = {{0}};
|
||||
|
||||
|
||||
if (!data)
|
||||
if (!mixer)
|
||||
return;
|
||||
if (max <= 1 || data->output_channels <= max || max >= 8)
|
||||
if (max <= 1 || mixer->output_channels <= max || max >= 8)
|
||||
return;
|
||||
|
||||
/* assume WAV defaults if not set */
|
||||
input_mapping = vgmstream->channel_layout;
|
||||
if (input_mapping == 0) {
|
||||
switch(data->output_channels) {
|
||||
switch(mixer->output_channels) {
|
||||
case 1: input_mapping = mapping_MONO; break;
|
||||
case 2: input_mapping = mapping_STEREO; break;
|
||||
case 3: input_mapping = mapping_2POINT1; break;
|
||||
@ -544,7 +532,7 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map
|
||||
}
|
||||
|
||||
/* save and make N fake channels at the beginning for easier calcs */
|
||||
output_channels = data->output_channels;
|
||||
output_channels = mixer->output_channels;
|
||||
for (ch = 0; ch < max; ch++) {
|
||||
mixing_push_upmix(vgmstream, 0);
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
#ifndef _MIXING_PRIV_H_
|
||||
#define _MIXING_PRIV_H_
|
||||
|
||||
#include "../vgmstream.h"
|
||||
#define VGMSTREAM_MAX_MIXING 512
|
||||
|
||||
/* mixing info */
|
||||
typedef enum {
|
||||
MIX_SWAP,
|
||||
MIX_ADD,
|
||||
MIX_ADD_COPY,
|
||||
MIX_VOLUME,
|
||||
MIX_LIMIT,
|
||||
MIX_UPMIX,
|
||||
MIX_DOWNMIX,
|
||||
MIX_KILLMIX,
|
||||
MIX_FADE
|
||||
} mix_command_t;
|
||||
|
||||
typedef struct {
|
||||
mix_command_t command;
|
||||
/* common */
|
||||
int ch_dst;
|
||||
int ch_src;
|
||||
float vol;
|
||||
|
||||
/* fade envelope */
|
||||
float vol_start; /* volume from pre to start */
|
||||
float vol_end; /* volume from end to post */
|
||||
char shape; /* curve type */
|
||||
int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */
|
||||
int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */
|
||||
int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */
|
||||
int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */
|
||||
} mix_command_data;
|
||||
|
||||
typedef struct {
|
||||
int mixing_channels; /* max channels needed to mix */
|
||||
int output_channels; /* resulting channels after mixing */
|
||||
int mixing_on; /* mixing allowed */
|
||||
int mixing_count; /* mixing number */
|
||||
size_t mixing_size; /* mixing max */
|
||||
mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */
|
||||
float* mixbuf; /* internal mixing buffer */
|
||||
|
||||
/* fades only apply at some points, other mixes are active */
|
||||
int has_non_fade;
|
||||
int has_fade;
|
||||
} mixing_data;
|
||||
|
||||
|
||||
#endif
|
@ -156,7 +156,7 @@ void vgmstream_get_title(char* buf, int buf_len, const char* filename, VGMSTREAM
|
||||
/* MIXING: modifies vgmstream output */
|
||||
/* ****************************************** */
|
||||
|
||||
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) {
|
||||
void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int* input_channels, int* output_channels) {
|
||||
mixing_setup(vgmstream, max_sample_count);
|
||||
mixing_info(vgmstream, input_channels, output_channels);
|
||||
|
||||
@ -165,7 +165,7 @@ void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int
|
||||
setup_vgmstream(vgmstream);
|
||||
}
|
||||
|
||||
void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) {
|
||||
void vgmstream_mixing_autodownmix(VGMSTREAM* vgmstream, int max_channels) {
|
||||
if (max_channels <= 0)
|
||||
return;
|
||||
|
||||
@ -181,7 +181,7 @@ void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) {
|
||||
return;
|
||||
}
|
||||
|
||||
void vgmstream_mixing_stereo_only(VGMSTREAM *vgmstream, int start) {
|
||||
void vgmstream_mixing_stereo_only(VGMSTREAM* vgmstream, int start) {
|
||||
if (start < 0)
|
||||
return;
|
||||
/* could check to avoid making mono files in edge cases but meh */
|
||||
|
@ -223,6 +223,8 @@ void setup_state_vgmstream(VGMSTREAM* vgmstream) {
|
||||
/*****************************************************************************/
|
||||
|
||||
void render_free(VGMSTREAM* vgmstream) {
|
||||
if (!vgmstream->layout_data)
|
||||
return;
|
||||
|
||||
if (vgmstream->layout_type == layout_segmented) {
|
||||
free_layout_segmented(vgmstream->layout_data);
|
||||
|
28
src/base/sbuf.h
Normal file
28
src/base/sbuf.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _SBUF_H
|
||||
#define _SBUF_H
|
||||
|
||||
#include "../streamtypes.h"
|
||||
|
||||
// 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] = 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
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -13,7 +13,7 @@ void adx_next_key(VGMSTREAMCHANNEL* stream);
|
||||
|
||||
|
||||
/* g721_decoder */
|
||||
void decode_g721(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_g721(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void g72x_init_state(struct g72x_state* state_ptr);
|
||||
|
||||
|
||||
@ -126,14 +126,14 @@ size_t xa_bytes_to_samples(size_t bytes, int channels, int is_blocked, int is_fo
|
||||
|
||||
|
||||
/* ea_xa_decoder */
|
||||
void decode_ea_xa(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo);
|
||||
void decode_ea_xa_v2(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_maxis_xa(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_ea_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo);
|
||||
void decode_ea_xa_v2(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_maxis_xa(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
int32_t ea_xa_bytes_to_samples(size_t bytes, int channels);
|
||||
|
||||
|
||||
/* ea_xas_decoder */
|
||||
void decode_ea_xas_v0(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_ea_xas_v0(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_ea_xas_v1(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
|
||||
|
||||
@ -145,7 +145,7 @@ void decode_cbd2_int(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspac
|
||||
|
||||
|
||||
/* ws_decoder */
|
||||
void decode_ws(VGMSTREAM* vgmstream, int channel, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_ws(VGMSTREAM* vgmstream, int channel, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
|
||||
|
||||
/* acm_decoder */
|
||||
@ -197,11 +197,11 @@ void decode_nds_procyon(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channels
|
||||
|
||||
|
||||
/* l5_555_decoder */
|
||||
void decode_l5_555(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_l5_555(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
|
||||
|
||||
/* sassc_decoder */
|
||||
void decode_sassc(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_sassc(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
|
||||
|
||||
/* lsf_decode */
|
||||
@ -217,7 +217,7 @@ void decode_mta2(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing,
|
||||
|
||||
|
||||
/* mc3_decoder */
|
||||
void decode_mc3(VGMSTREAM* vgmstream, VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_mc3(VGMSTREAM* vgmstream, VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
|
||||
|
||||
/* fadpcm_decoder */
|
||||
@ -243,7 +243,7 @@ int32_t tantalus_bytes_to_samples(size_t bytes, int channels);
|
||||
|
||||
|
||||
/* derf_decoder */
|
||||
void decode_derf(VGMSTREAMCHANNEL* stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_derf(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
|
||||
/* wady_decoder */
|
||||
void decode_wady(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
@ -322,7 +322,7 @@ typedef struct ea_mt_codec_data ea_mt_codec_data;
|
||||
ea_mt_codec_data* init_ea_mt(int channels, int pcm_blocks);
|
||||
ea_mt_codec_data* init_ea_mt_loops(int channels, int pcm_blocks, int loop_sample, off_t* loop_offsets);
|
||||
ea_mt_codec_data* init_ea_mt_cbx(int channels);
|
||||
void decode_ea_mt(VGMSTREAM* vgmstream, sample * outbuf, int channelspacing, int32_t samples_to_do, int channel);
|
||||
void decode_ea_mt(VGMSTREAM* vgmstream, sample_t* outbuf, int channelspacing, int32_t samples_to_do, int channel);
|
||||
void reset_ea_mt(VGMSTREAM* vgmstream);
|
||||
void flush_ea_mt(VGMSTREAM* vgmstream);
|
||||
void seek_ea_mt(VGMSTREAM* vgmstream, int32_t num_sample);
|
||||
@ -560,7 +560,7 @@ void free_g719(g719_codec_data* data, int channels);
|
||||
|
||||
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
|
||||
/* mp4_aac_decoder */
|
||||
void decode_mp4_aac(mp4_aac_codec_data* data, sample * outbuf, int32_t samples_to_do, int channels);
|
||||
void decode_mp4_aac(mp4_aac_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels);
|
||||
void reset_mp4_aac(VGMSTREAM* vgmstream);
|
||||
void seek_mp4_aac(VGMSTREAM* vgmstream, int32_t num_sample);
|
||||
void free_mp4_aac(mp4_aac_codec_data* data);
|
||||
|
@ -1218,7 +1218,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_NGC_NST_DSP, "Animaniacs NST header"},
|
||||
{meta_BAF, "Bizarre Creations .baf header"},
|
||||
{meta_MSF, "Sony MSF header"},
|
||||
{meta_PS3_PAST, "SNDP header"},
|
||||
{meta_SNDP, "Premium Agency SNDP header"},
|
||||
{meta_SGXD, "Sony SGXD header"},
|
||||
{meta_WII_RAS, "RAS header"},
|
||||
{meta_SPM, "Square SPM header"},
|
||||
@ -1316,7 +1316,7 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_MSB_MSH, "Sony MultiStream MSH+MSB header"},
|
||||
{meta_TXTP, "TXTP generic header"},
|
||||
{meta_SMC_SMH, "Genki SMC+SMH header"},
|
||||
{meta_PPST, "Parappa PPST header"},
|
||||
{meta_PPST, "epics PPST header"},
|
||||
{meta_SPS_N1, "Nippon Ichi .SPS header"},
|
||||
{meta_UBI_BAO, "Ubisoft BAO header"},
|
||||
{meta_DSP_SWITCH_AUDIO, "UE4 Switch Audio header"},
|
||||
|
@ -90,14 +90,16 @@
|
||||
<ClInclude Include="streamtypes.h" />
|
||||
<ClInclude Include="util.h" />
|
||||
<ClInclude Include="vgmstream.h" />
|
||||
<ClInclude Include="vgmstream_init.h" />
|
||||
<ClInclude Include="vgmstream_types.h" />
|
||||
<ClInclude Include="base\api_internal.h" />
|
||||
<ClInclude Include="base\decode.h" />
|
||||
<ClInclude Include="base\mixer.h" />
|
||||
<ClInclude Include="base\mixer_priv.h" />
|
||||
<ClInclude Include="base\mixing.h" />
|
||||
<ClInclude Include="base\mixing_fades.h" />
|
||||
<ClInclude Include="base\mixing_priv.h" />
|
||||
<ClInclude Include="base\plugins.h" />
|
||||
<ClInclude Include="base\render.h" />
|
||||
<ClInclude Include="base\sbuf.h" />
|
||||
<ClInclude Include="coding\coding.h" />
|
||||
<ClInclude Include="coding\coding_utils_samples.h" />
|
||||
<ClInclude Include="coding\g72x_state.h" />
|
||||
@ -212,6 +214,7 @@
|
||||
<ClCompile Include="streamfile.c" />
|
||||
<ClCompile Include="util.c" />
|
||||
<ClCompile Include="vgmstream.c" />
|
||||
<ClCompile Include="vgmstream_init.c" />
|
||||
<ClCompile Include="base\api_decode_base.c" />
|
||||
<ClCompile Include="base\api_decode_open.c" />
|
||||
<ClCompile Include="base\api_decode_play.c" />
|
||||
@ -221,6 +224,9 @@
|
||||
<ClCompile Include="base\config.c" />
|
||||
<ClCompile Include="base\decode.c" />
|
||||
<ClCompile Include="base\info.c" />
|
||||
<ClCompile Include="base\mixer.c" />
|
||||
<ClCompile Include="base\mixer_ops_common.c" />
|
||||
<ClCompile Include="base\mixer_ops_fade.c" />
|
||||
<ClCompile Include="base\mixing.c" />
|
||||
<ClCompile Include="base\mixing_commands.c" />
|
||||
<ClCompile Include="base\mixing_macros.c" />
|
||||
@ -627,7 +633,6 @@
|
||||
<ClCompile Include="meta\ps2_vms.c" />
|
||||
<ClCompile Include="meta\ps2_wmus.c" />
|
||||
<ClCompile Include="meta\ps2_xa30.c" />
|
||||
<ClCompile Include="meta\ps3_past.c" />
|
||||
<ClCompile Include="meta\psb.c" />
|
||||
<ClCompile Include="meta\psf.c" />
|
||||
<ClCompile Include="meta\psnd.c" />
|
||||
@ -680,6 +685,7 @@
|
||||
<ClCompile Include="meta\smp.c" />
|
||||
<ClCompile Include="meta\smpl.c" />
|
||||
<ClCompile Include="meta\smv.c" />
|
||||
<ClCompile Include="meta\sndp.c" />
|
||||
<ClCompile Include="meta\snds.c" />
|
||||
<ClCompile Include="meta\sndx.c" />
|
||||
<ClCompile Include="meta\sndz.c" />
|
||||
|
@ -104,6 +104,9 @@
|
||||
<ClInclude Include="vgmstream.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vgmstream_init.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vgmstream_types.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -113,21 +116,24 @@
|
||||
<ClInclude Include="base\decode.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\mixer.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\mixer_priv.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\mixing.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\mixing_fades.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\mixing_priv.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\plugins.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\render.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="base\sbuf.h">
|
||||
<Filter>base\Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="coding\coding.h">
|
||||
<Filter>coding\Header Files</Filter>
|
||||
</ClInclude>
|
||||
@ -466,6 +472,9 @@
|
||||
<ClCompile Include="vgmstream.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="vgmstream_init.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\api_decode_base.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -493,6 +502,15 @@
|
||||
<ClCompile Include="base\info.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\mixer.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\mixer_ops_common.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\mixer_ops_fade.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="base\mixing.c">
|
||||
<Filter>base\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -1711,9 +1729,6 @@
|
||||
<ClCompile Include="meta\ps2_xa30.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ps3_past.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\psb.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -1870,6 +1885,9 @@
|
||||
<ClCompile Include="meta\smv.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\sndp.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\snds.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
235
src/meta/9tav.c
235
src/meta/9tav.c
@ -1,118 +1,117 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "9tav_streamfile.h"
|
||||
|
||||
/* 9TAV - from Metal Gear Solid 2/3 HD (Vita) */
|
||||
VGMSTREAM * init_vgmstream_9tav(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, sample_rate, track_count;
|
||||
int32_t num_samples, loop_start, loop_end;
|
||||
size_t track_size;
|
||||
uint32_t config_data;
|
||||
int i, is_padded;
|
||||
layered_layout_data * data = NULL;
|
||||
STREAMFILE* temp_streamFile = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
/* .9tav: header id */
|
||||
if (!check_extensions(streamFile, "9tav"))
|
||||
goto fail;
|
||||
|
||||
if (read_32bitBE(0x00,streamFile) != 0x39544156) /* "9TAV" */
|
||||
goto fail;
|
||||
|
||||
/* 0x04: always 0x09 */
|
||||
channel_count = read_16bitLE(0x08,streamFile);
|
||||
track_count = read_16bitLE(0x0a,streamFile); /* MGS3 uses multitracks */
|
||||
sample_rate = read_32bitLE(0x0c,streamFile);
|
||||
track_size = read_32bitLE(0x10,streamFile); /* without padding */
|
||||
//data_size = read_32bitLE(0x14,streamFile); /* without padding */
|
||||
num_samples = read_32bitLE(0x18,streamFile);
|
||||
config_data = read_32bitBE(0x1c,streamFile);
|
||||
|
||||
|
||||
if (read_32bitBE(0x20,streamFile) == 0x4D544146) { /* "MTAF" */
|
||||
/* MGS3 has a MTAF header (data size and stuff don't match, probably for track info) */
|
||||
loop_start = read_32bitLE(0x78, streamFile);
|
||||
loop_end = read_32bitLE(0x7c, streamFile);
|
||||
loop_flag = read_32bitLE(0x90, streamFile) & 1;
|
||||
|
||||
is_padded = 1; /* data also has padding and other oddities */
|
||||
start_offset = 0x00;
|
||||
}
|
||||
else {
|
||||
/* MGS2 doesn't */
|
||||
loop_start = 0;
|
||||
loop_end = 0;
|
||||
loop_flag = 0;
|
||||
|
||||
is_padded = 0;
|
||||
start_offset = 0x20;
|
||||
}
|
||||
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(track_count);
|
||||
if (!data) goto fail;
|
||||
|
||||
/* open each layer subfile */
|
||||
for (i = 0; i < data->layer_count; i++) {
|
||||
data->layers[i] = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!data->layers[i]) goto fail;
|
||||
|
||||
data->layers[i]->meta_type = meta_9TAV;
|
||||
data->layers[i]->sample_rate = sample_rate;
|
||||
data->layers[i]->num_samples = num_samples;
|
||||
data->layers[i]->loop_start_sample = loop_start;
|
||||
data->layers[i]->loop_end_sample = loop_end;
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
{
|
||||
atrac9_config cfg = {0};
|
||||
cfg.channels = channel_count;
|
||||
cfg.config_data = config_data;
|
||||
cfg.encoder_delay = atrac9_bytes_to_samples_cfg(track_size, cfg.config_data) - num_samples; /* seems ok */
|
||||
if (cfg.encoder_delay > 4096) /* doesn't seem too normal */
|
||||
cfg.encoder_delay = 0;
|
||||
|
||||
data->layers[i]->codec_data = init_atrac9(&cfg);
|
||||
if (!data->layers[i]->codec_data) goto fail;
|
||||
data->layers[i]->coding_type = coding_ATRAC9;
|
||||
data->layers[i]->layout_type = layout_none;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if (is_padded) {
|
||||
temp_streamFile = setup_9tav_streamfile(streamFile, 0xFE4, track_size, i, track_count);
|
||||
if (!temp_streamFile) goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(data->layers[i],temp_streamFile == NULL ? streamFile : temp_streamFile,start_offset))
|
||||
goto fail;
|
||||
|
||||
close_streamfile(temp_streamFile);
|
||||
temp_streamFile = NULL;
|
||||
}
|
||||
|
||||
/* setup layered VGMSTREAMs */
|
||||
if (!setup_layout_layered(data))
|
||||
goto fail;
|
||||
|
||||
/* build the layered VGMSTREAM */
|
||||
vgmstream = allocate_layered_vgmstream(data);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
if (!vgmstream)
|
||||
free_layout_layered(data);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "9tav_streamfile.h"
|
||||
|
||||
/* 9TAV - from Metal Gear Solid 2/3 HD (Vita) */
|
||||
VGMSTREAM* init_vgmstream_9tav(STREAMFILE* sf) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channels, sample_rate, track_count;
|
||||
int32_t num_samples, loop_start, loop_end;
|
||||
size_t track_size;
|
||||
uint32_t config_data;
|
||||
bool is_padded;
|
||||
layered_layout_data* data = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "9TAV"))
|
||||
return NULL;
|
||||
/* .9tav: header id */
|
||||
if (!check_extensions(sf, "9tav"))
|
||||
return NULL;
|
||||
|
||||
/* 0x04: always 0x09 (codec?) */
|
||||
channels = read_u16le(0x08,sf);
|
||||
track_count = read_u16le(0x0a,sf); /* MGS3 uses multitracks */
|
||||
sample_rate = read_s32le(0x0c,sf);
|
||||
track_size = read_u32le(0x10,sf); /* without padding */
|
||||
//data_size = read_u32le(0x14,sf); /* without padding */
|
||||
num_samples = read_s32le(0x18,sf);
|
||||
config_data = read_u32be(0x1c,sf);
|
||||
|
||||
|
||||
if (is_id32be(0x20,sf, "MTAF")) {
|
||||
/* MGS3 has a MTAF header (data size and stuff don't match, probably for track info) */
|
||||
loop_start = read_s32le(0x78, sf);
|
||||
loop_end = read_s32le(0x7c, sf);
|
||||
loop_flag = read_u32le(0x90, sf) & 1;
|
||||
|
||||
is_padded = true; /* data also has padding and other oddities */
|
||||
start_offset = 0x00;
|
||||
}
|
||||
else {
|
||||
/* MGS2 doesn't */
|
||||
loop_start = 0;
|
||||
loop_end = 0;
|
||||
loop_flag = false;
|
||||
|
||||
is_padded = false;
|
||||
start_offset = 0x20;
|
||||
}
|
||||
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(track_count);
|
||||
if (!data) goto fail;
|
||||
|
||||
/* open each layer subfile */
|
||||
for (int i = 0; i < data->layer_count; i++) {
|
||||
data->layers[i] = allocate_vgmstream(channels, loop_flag);
|
||||
if (!data->layers[i]) goto fail;
|
||||
|
||||
data->layers[i]->meta_type = meta_9TAV;
|
||||
data->layers[i]->sample_rate = sample_rate;
|
||||
data->layers[i]->num_samples = num_samples;
|
||||
data->layers[i]->loop_start_sample = loop_start;
|
||||
data->layers[i]->loop_end_sample = loop_end;
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
{
|
||||
atrac9_config cfg = {0};
|
||||
cfg.channels = channels;
|
||||
cfg.config_data = config_data;
|
||||
cfg.encoder_delay = atrac9_bytes_to_samples_cfg(track_size, cfg.config_data) - num_samples; /* seems ok */
|
||||
if (cfg.encoder_delay > 4096) /* doesn't seem too normal */
|
||||
cfg.encoder_delay = 0;
|
||||
|
||||
data->layers[i]->codec_data = init_atrac9(&cfg);
|
||||
if (!data->layers[i]->codec_data) goto fail;
|
||||
data->layers[i]->coding_type = coding_ATRAC9;
|
||||
data->layers[i]->layout_type = layout_none;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if (is_padded) {
|
||||
temp_sf = setup_9tav_streamfile(sf, 0xFE4, track_size, i, track_count);
|
||||
if (!temp_sf) goto fail;
|
||||
}
|
||||
|
||||
if (!vgmstream_open_stream(data->layers[i],temp_sf == NULL ? sf : temp_sf,start_offset))
|
||||
goto fail;
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
temp_sf = NULL;
|
||||
}
|
||||
|
||||
/* setup layered VGMSTREAMs */
|
||||
if (!setup_layout_layered(data))
|
||||
goto fail;
|
||||
|
||||
/* build the layered VGMSTREAM */
|
||||
vgmstream = allocate_layered_vgmstream(data);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
if (!vgmstream)
|
||||
free_layout_layered(data);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1,182 +1,182 @@
|
||||
#ifndef _9TAV_STREAMFILE_H_
|
||||
#define _9TAV_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
size_t track_size;
|
||||
int track_number;
|
||||
int track_count;
|
||||
int skip_count;
|
||||
int read_count;
|
||||
size_t frame_size;
|
||||
size_t interleave_count;
|
||||
size_t interleave_last_count;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} ntav_io_data;
|
||||
|
||||
|
||||
static size_t ntav_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ntav_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
data->skip_size = 0;
|
||||
data->read_count = 0;
|
||||
data->skip_count = data->interleave_count * data->track_number;
|
||||
//VGM_LOG("0 o=%lx, sc=%i\n", data->physical_offset, data->skip_count);
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
//VGM_LOG("1 of=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset);
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
//VGM_LOG("9 o=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset);
|
||||
//VGM_LOG("eof\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
/* not very exact compared to real blocks but ok enough */
|
||||
if (read_32bitLE(data->physical_offset, streamfile) == 0x00) {
|
||||
data->block_size = 0x10;
|
||||
//VGM_LOG("1 o=%lx, lo=%lx skip\n", data->physical_offset, data->logical_offset);
|
||||
}
|
||||
else {
|
||||
data->block_size = data->frame_size;
|
||||
|
||||
//VGM_LOG("2 o=%lx, lo=%lx, skip=%i, read=%i\n", data->physical_offset, data->logical_offset, data->skip_count, data->read_count);
|
||||
|
||||
/* each track interleaves NTAV_INTERLEAVE frames, but can contain padding in between,
|
||||
* so must read one by one up to max */
|
||||
|
||||
if (data->skip_count == 0 && data->read_count == 0) {
|
||||
data->read_count = data->interleave_count;
|
||||
}
|
||||
|
||||
if (data->skip_count) {
|
||||
data->skip_count--;
|
||||
}
|
||||
|
||||
if (data->read_count) {
|
||||
data->data_size = data->block_size;
|
||||
data->read_count--;
|
||||
|
||||
if (data->read_count == 0) {
|
||||
if (data->logical_offset + data->interleave_count * data->frame_size > data->track_size)
|
||||
data->skip_count = data->interleave_last_count * (data->track_count - 1);
|
||||
else
|
||||
data->skip_count = data->interleave_count * (data->track_count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t ntav_io_size(STREAMFILE *streamfile, ntav_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
ntav_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles deinterleaving of 9TAV blocked streams. Unlike other games using .sdt,
|
||||
* KCEJ blocks have a data_size field and rest is padding. Even after that all blocks start
|
||||
* with 0 (skipped) and there are padding blocks that start with LE 0xDEADBEEF.
|
||||
* This streamfile handles 9tav extracted like regular sdt and remove padding manually. */
|
||||
static STREAMFILE* setup_9tav_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t track_size, int track_number, int track_count) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
ntav_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(ntav_io_data);
|
||||
size_t last_size;
|
||||
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = get_streamfile_size(streamFile) - stream_offset;
|
||||
io_data.track_size = track_size;
|
||||
io_data.track_number = track_number;
|
||||
io_data.track_count = track_count;
|
||||
io_data.frame_size = 0x40;
|
||||
io_data.interleave_count = 256;
|
||||
last_size = track_size % (io_data.interleave_count * io_data.frame_size);
|
||||
if (last_size)
|
||||
io_data.interleave_last_count = last_size / io_data.frame_size;
|
||||
io_data.logical_offset = -1; /* force state reset */
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, ntav_io_read,ntav_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _9TAV_STREAMFILE_H_ */
|
||||
#ifndef _9TAV_STREAMFILE_H_
|
||||
#define _9TAV_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
off_t stream_offset;
|
||||
size_t stream_size;
|
||||
size_t track_size;
|
||||
int track_number;
|
||||
int track_count;
|
||||
int skip_count;
|
||||
int read_count;
|
||||
size_t frame_size;
|
||||
size_t interleave_count;
|
||||
size_t interleave_last_count;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* fake offset */
|
||||
off_t physical_offset; /* actual offset */
|
||||
size_t block_size; /* current size */
|
||||
size_t skip_size; /* size from block start to reach data */
|
||||
size_t data_size; /* usable size in a block */
|
||||
|
||||
size_t logical_size;
|
||||
} ntav_io_data;
|
||||
|
||||
|
||||
static size_t ntav_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ntav_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
/* re-start when previous offset (can't map logical<>physical offsets) */
|
||||
if (data->logical_offset < 0 || offset < data->logical_offset) {
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->data_size = 0;
|
||||
data->skip_size = 0;
|
||||
data->read_count = 0;
|
||||
data->skip_count = data->interleave_count * data->track_number;
|
||||
//VGM_LOG("0 o=%lx, sc=%i\n", data->physical_offset, data->skip_count);
|
||||
}
|
||||
|
||||
/* read blocks */
|
||||
while (length > 0) {
|
||||
//VGM_LOG("1 of=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset);
|
||||
|
||||
/* ignore EOF */
|
||||
if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) {
|
||||
//VGM_LOG("9 o=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset);
|
||||
//VGM_LOG("eof\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
/* not very exact compared to real blocks but ok enough */
|
||||
if (read_32bitLE(data->physical_offset, streamfile) == 0x00) {
|
||||
data->block_size = 0x10;
|
||||
//VGM_LOG("1 o=%lx, lo=%lx skip\n", data->physical_offset, data->logical_offset);
|
||||
}
|
||||
else {
|
||||
data->block_size = data->frame_size;
|
||||
|
||||
//VGM_LOG("2 o=%lx, lo=%lx, skip=%i, read=%i\n", data->physical_offset, data->logical_offset, data->skip_count, data->read_count);
|
||||
|
||||
/* each track interleaves NTAV_INTERLEAVE frames, but can contain padding in between,
|
||||
* so must read one by one up to max */
|
||||
|
||||
if (data->skip_count == 0 && data->read_count == 0) {
|
||||
data->read_count = data->interleave_count;
|
||||
}
|
||||
|
||||
if (data->skip_count) {
|
||||
data->skip_count--;
|
||||
}
|
||||
|
||||
if (data->read_count) {
|
||||
data->data_size = data->block_size;
|
||||
data->read_count--;
|
||||
|
||||
if (data->read_count == 0) {
|
||||
if (data->logical_offset + data->interleave_count * data->frame_size > data->track_size)
|
||||
data->skip_count = data->interleave_last_count * (data->track_count - 1);
|
||||
else
|
||||
data->skip_count = data->interleave_count * (data->track_count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->block_size;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
total_read += bytes_done;
|
||||
dest += bytes_done;
|
||||
offset += bytes_done;
|
||||
length -= bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t ntav_io_size(STREAMFILE *streamfile, ntav_io_data* data) {
|
||||
uint8_t buf[1];
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* force a fake read at max offset, to get max logical_offset (will be reset next read) */
|
||||
ntav_io_read(streamfile, buf, 0x7FFFFFFF, 1, data);
|
||||
data->logical_size = data->logical_offset;
|
||||
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Handles deinterleaving of 9TAV blocked streams. Unlike other games using .sdt,
|
||||
* KCEJ blocks have a data_size field and rest is padding. Even after that all blocks start
|
||||
* with 0 (skipped) and there are padding blocks that start with LE 0xDEADBEEF.
|
||||
* This streamfile handles 9tav extracted like regular sdt and remove padding manually. */
|
||||
static STREAMFILE* setup_9tav_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t track_size, int track_number, int track_count) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
ntav_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(ntav_io_data);
|
||||
size_t last_size;
|
||||
|
||||
io_data.stream_offset = stream_offset;
|
||||
io_data.stream_size = get_streamfile_size(streamFile) - stream_offset;
|
||||
io_data.track_size = track_size;
|
||||
io_data.track_number = track_number;
|
||||
io_data.track_count = track_count;
|
||||
io_data.frame_size = 0x40;
|
||||
io_data.interleave_count = 256;
|
||||
last_size = track_size % (io_data.interleave_count * io_data.frame_size);
|
||||
if (last_size)
|
||||
io_data.interleave_last_count = last_size / io_data.frame_size;
|
||||
io_data.logical_offset = -1; /* force state reset */
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, ntav_io_read,ntav_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _9TAV_STREAMFILE_H_ */
|
||||
|
@ -51,7 +51,7 @@ VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf) {
|
||||
}
|
||||
}
|
||||
|
||||
//;VGM_LOG("acb: subfile offset=%lx + %x\n", subfile_offset, subfile_size);
|
||||
//;VGM_LOG("acb: subfile offset=%x + %x\n", subfile_offset, subfile_size);
|
||||
|
||||
temp_sf = setup_subfile_streamfile(sf, subfile_offset,subfile_size, "awb");
|
||||
if (!temp_sf) goto fail;
|
||||
@ -538,7 +538,7 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma
|
||||
|
||||
tlv_type = read_u16be(Command_offset + pos + 0x00, sf); /* ReferenceItem */
|
||||
tlv_index = read_u16be(Command_offset + pos + 0x02, sf);
|
||||
//;VGM_LOG("acb: TLV at %x: type %x, index=%x\n", offset, tlv_type, tlv_index);
|
||||
//;VGM_LOG("acb: TLV at %x: type %x, index=%x\n", Command_offset, tlv_type, tlv_index);
|
||||
|
||||
/* same as Synth's ReferenceItem type? */
|
||||
switch(tlv_type) {
|
||||
@ -561,7 +561,7 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma
|
||||
|
||||
case 2004: /* noteOnWithDuration */
|
||||
/* same as the above plus extra field */
|
||||
//;VGM_LOG("acb: TLV at %x: usable code %i?\n", offset-0x03, tlv_code);
|
||||
//;VGM_LOG("acb: TLV at %x: usable code %i?\n", Command_offset-0x03, tlv_code);
|
||||
break;
|
||||
|
||||
case 33: /* mute */
|
||||
@ -576,7 +576,7 @@ static int load_acb_command_tlvs(acb_header* acb, STREAMFILE* sf, uint32_t Comma
|
||||
case 7100: /* startAction */
|
||||
case 7101: /* stopAction */
|
||||
/* may be needed? */
|
||||
//;VGM_LOG("acb: TLV at %x: check code %i?\n", offset-0x03, tlv_code);
|
||||
//;VGM_LOG("acb: TLV at %x: check code %i?\n", Command_offset-0x03, tlv_code);
|
||||
break;
|
||||
|
||||
case 0: /* no-op */
|
||||
@ -1167,7 +1167,7 @@ static int load_acb_loops(acb_header* acb, VGMSTREAM* vgmstream) {
|
||||
|
||||
r = &acb->WaveformExtensionData[ExtensionIndex];
|
||||
|
||||
//;VGM_LOG("acb: WaveformExtensionData[%i]: LoopStart=%i, LoopEnd=%i\n", Index, r->LoopStart, r->LoopEnd);
|
||||
//;VGM_LOG("acb: WaveformExtensionData[%i]: LoopStart=%i, LoopEnd=%i\n", ExtensionIndex, r->LoopStart, r->LoopEnd);
|
||||
|
||||
vgmstream_force_loop(vgmstream, 1, r->LoopStart, r->LoopEnd);
|
||||
|
||||
|
@ -120,7 +120,7 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
||||
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
|
||||
if (!vgmstream->layout_data) goto fail;
|
||||
vgmstream->layout_type = layout_layered;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->coding_type = coding_MPEG_custom;
|
||||
}
|
||||
else {
|
||||
vgmstream->codec_data = init_mpeg_custom(sf_body, awc.stream_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_STANDARD, NULL);
|
||||
|
@ -1,21 +1,21 @@
|
||||
#ifndef _FSB5_STREAMFILE_H_
|
||||
#define _FSB5_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
static STREAMFILE* setup_fsb5_streamfile(STREAMFILE* sf, off_t stream_start, size_t stream_size, int stream_count, int stream_number, size_t interleave) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_start;
|
||||
cfg.stream_size = stream_size;
|
||||
cfg.chunk_size = interleave;
|
||||
cfg.step_start = stream_number;
|
||||
cfg.step_count = stream_count;
|
||||
|
||||
/* setup sf */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _FSB5_STREAMFILE_H_ */
|
||||
#ifndef _FSB5_STREAMFILE_H_
|
||||
#define _FSB5_STREAMFILE_H_
|
||||
#include "deblock_streamfile.h"
|
||||
|
||||
static STREAMFILE* setup_fsb5_streamfile(STREAMFILE* sf, off_t stream_start, size_t stream_size, int stream_count, int stream_number, size_t interleave) {
|
||||
STREAMFILE* new_sf = NULL;
|
||||
deblock_config_t cfg = {0};
|
||||
|
||||
cfg.stream_start = stream_start;
|
||||
cfg.stream_size = stream_size;
|
||||
cfg.chunk_size = interleave;
|
||||
cfg.step_start = stream_number;
|
||||
cfg.step_count = stream_count;
|
||||
|
||||
/* setup sf */
|
||||
new_sf = open_wrap_streamfile(sf);
|
||||
new_sf = open_io_deblock_streamfile_f(new_sf, &cfg);
|
||||
return new_sf;
|
||||
}
|
||||
|
||||
#endif /* _FSB5_STREAMFILE_H_ */
|
||||
|
@ -1,198 +1,198 @@
|
||||
#ifndef _FSB_INTERLEAVE_STREAMFILE_H_
|
||||
#define _FSB_INTERLEAVE_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef enum { FSB_INT_CELT } fsb_interleave_codec_t;
|
||||
typedef struct {
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
int skip_frames; /* frames to skip from other streams at points */
|
||||
|
||||
/* config */
|
||||
fsb_interleave_codec_t codec;
|
||||
int stream_count;
|
||||
int stream_number;
|
||||
size_t stream_size;
|
||||
off_t start_offset; /* pointing to the stream's beginning */
|
||||
size_t total_size; /* size of the resulting substream */
|
||||
} fsb_interleave_io_data;
|
||||
|
||||
|
||||
|
||||
/* Reads skipping other streams */
|
||||
static size_t fsb_interleave_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, fsb_interleave_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* ignore bad reads */
|
||||
if (offset < 0 || offset > data->total_size) {
|
||||
return total_read;
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
data->physical_offset = data->start_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->skip_frames = data->stream_number;
|
||||
}
|
||||
|
||||
/* read doing one frame at a time */
|
||||
while (length > 0) {
|
||||
size_t to_read, bytes_read;
|
||||
off_t intrablock_offset, intradata_offset;
|
||||
uint32_t data_size;
|
||||
|
||||
if (offset >= data->total_size)
|
||||
break;
|
||||
|
||||
/* get current data */
|
||||
switch (data->codec) {
|
||||
case FSB_INT_CELT:
|
||||
data_size = 0x04+0x04+read_32bitLE(data->physical_offset+0x04,streamfile);
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* skip frames from other streams */
|
||||
if (data->skip_frames) {
|
||||
data->physical_offset += data_size;
|
||||
data->skip_frames--;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* requested offset is outside current block, try next */
|
||||
if (offset >= data->logical_offset + data_size) {
|
||||
data->physical_offset += data_size;
|
||||
data->logical_offset += data_size;
|
||||
data->skip_frames = data->stream_count - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* reads could fall in the middle of the block */
|
||||
intradata_offset = offset - data->logical_offset;
|
||||
intrablock_offset = intradata_offset;
|
||||
|
||||
/* clamp reads up to this block's end */
|
||||
to_read = (data_size - intradata_offset);
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
if (to_read == 0)
|
||||
break; /* should never happen... */
|
||||
|
||||
/* finally read and move buffer/offsets */
|
||||
bytes_read = read_streamfile(dest, data->physical_offset + intrablock_offset, to_read, streamfile);
|
||||
total_read += bytes_read;
|
||||
if (bytes_read != to_read)
|
||||
break; /* couldn't read fully */
|
||||
|
||||
dest += bytes_read;
|
||||
offset += bytes_read;
|
||||
length -= bytes_read;
|
||||
|
||||
/* block fully read, go next */
|
||||
if (intradata_offset + bytes_read == data_size) {
|
||||
data->physical_offset += data_size;
|
||||
data->logical_offset += data_size;
|
||||
data->skip_frames = data->stream_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t fsb_interleave_io_size(STREAMFILE *streamfile, fsb_interleave_io_data* data) {
|
||||
off_t physical_offset, max_physical_offset;
|
||||
size_t total_size = 0;
|
||||
int skip_frames = 0;
|
||||
|
||||
if (data->total_size)
|
||||
return data->total_size;
|
||||
|
||||
physical_offset = data->start_offset;
|
||||
max_physical_offset = physical_offset + data->stream_size;
|
||||
skip_frames = data->stream_number;
|
||||
|
||||
/* get size of the underlying stream, skipping other streams
|
||||
* (all streams should have the same frame count) */
|
||||
while (physical_offset < max_physical_offset) {
|
||||
size_t data_size;
|
||||
uint32_t id;
|
||||
|
||||
switch(data->codec) {
|
||||
case FSB_INT_CELT:
|
||||
id = read_32bitBE(physical_offset+0x00,streamfile);
|
||||
if (id != 0x17C30DF3) /* incorrect FSB CELT frame sync */
|
||||
data_size = 0;
|
||||
else
|
||||
data_size = 0x04+0x04+read_32bitLE(physical_offset+0x04,streamfile);
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* there may be padding at the end, so this doubles as EOF marker */
|
||||
if (data_size == 0)
|
||||
break;
|
||||
|
||||
/* skip frames from other streams */
|
||||
if (skip_frames) {
|
||||
physical_offset += data_size;
|
||||
skip_frames--;
|
||||
continue;
|
||||
}
|
||||
|
||||
physical_offset += data_size;
|
||||
total_size += data_size;
|
||||
skip_frames = data->stream_count - 1;
|
||||
}
|
||||
|
||||
data->total_size = total_size;
|
||||
return data->total_size;
|
||||
}
|
||||
|
||||
|
||||
/* Prepares custom IO for multistreams, interleaves 1 packet per stream */
|
||||
static STREAMFILE* setup_fsb_interleave_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t stream_size, int stream_count, int stream_number, fsb_interleave_codec_t codec) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
fsb_interleave_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(fsb_interleave_io_data);
|
||||
|
||||
io_data.start_offset = start_offset;
|
||||
io_data.physical_offset = start_offset;
|
||||
io_data.skip_frames = stream_number; /* adjust since start_offset points to the first */
|
||||
io_data.codec = codec;
|
||||
io_data.stream_count = stream_count;
|
||||
io_data.stream_number = stream_number;
|
||||
io_data.stream_size = stream_size;
|
||||
io_data.total_size = fsb_interleave_io_size(streamFile, &io_data); /* force init */
|
||||
|
||||
if (io_data.total_size == 0 || io_data.total_size > io_data.stream_size) {
|
||||
VGM_LOG("FSB INTERLEAVE: wrong total_size %x vs %x\n", io_data.total_size,io_data.stream_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, fsb_interleave_io_read,fsb_interleave_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _FSB_INTERLEAVE_STREAMFILE_H_ */
|
||||
#ifndef _FSB_INTERLEAVE_STREAMFILE_H_
|
||||
#define _FSB_INTERLEAVE_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef enum { FSB_INT_CELT } fsb_interleave_codec_t;
|
||||
typedef struct {
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
int skip_frames; /* frames to skip from other streams at points */
|
||||
|
||||
/* config */
|
||||
fsb_interleave_codec_t codec;
|
||||
int stream_count;
|
||||
int stream_number;
|
||||
size_t stream_size;
|
||||
off_t start_offset; /* pointing to the stream's beginning */
|
||||
size_t total_size; /* size of the resulting substream */
|
||||
} fsb_interleave_io_data;
|
||||
|
||||
|
||||
|
||||
/* Reads skipping other streams */
|
||||
static size_t fsb_interleave_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, fsb_interleave_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* ignore bad reads */
|
||||
if (offset < 0 || offset > data->total_size) {
|
||||
return total_read;
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
data->physical_offset = data->start_offset;
|
||||
data->logical_offset = 0x00;
|
||||
data->skip_frames = data->stream_number;
|
||||
}
|
||||
|
||||
/* read doing one frame at a time */
|
||||
while (length > 0) {
|
||||
size_t to_read, bytes_read;
|
||||
off_t intrablock_offset, intradata_offset;
|
||||
uint32_t data_size;
|
||||
|
||||
if (offset >= data->total_size)
|
||||
break;
|
||||
|
||||
/* get current data */
|
||||
switch (data->codec) {
|
||||
case FSB_INT_CELT:
|
||||
data_size = 0x04+0x04+read_32bitLE(data->physical_offset+0x04,streamfile);
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* skip frames from other streams */
|
||||
if (data->skip_frames) {
|
||||
data->physical_offset += data_size;
|
||||
data->skip_frames--;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* requested offset is outside current block, try next */
|
||||
if (offset >= data->logical_offset + data_size) {
|
||||
data->physical_offset += data_size;
|
||||
data->logical_offset += data_size;
|
||||
data->skip_frames = data->stream_count - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* reads could fall in the middle of the block */
|
||||
intradata_offset = offset - data->logical_offset;
|
||||
intrablock_offset = intradata_offset;
|
||||
|
||||
/* clamp reads up to this block's end */
|
||||
to_read = (data_size - intradata_offset);
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
if (to_read == 0)
|
||||
break; /* should never happen... */
|
||||
|
||||
/* finally read and move buffer/offsets */
|
||||
bytes_read = read_streamfile(dest, data->physical_offset + intrablock_offset, to_read, streamfile);
|
||||
total_read += bytes_read;
|
||||
if (bytes_read != to_read)
|
||||
break; /* couldn't read fully */
|
||||
|
||||
dest += bytes_read;
|
||||
offset += bytes_read;
|
||||
length -= bytes_read;
|
||||
|
||||
/* block fully read, go next */
|
||||
if (intradata_offset + bytes_read == data_size) {
|
||||
data->physical_offset += data_size;
|
||||
data->logical_offset += data_size;
|
||||
data->skip_frames = data->stream_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t fsb_interleave_io_size(STREAMFILE *streamfile, fsb_interleave_io_data* data) {
|
||||
off_t physical_offset, max_physical_offset;
|
||||
size_t total_size = 0;
|
||||
int skip_frames = 0;
|
||||
|
||||
if (data->total_size)
|
||||
return data->total_size;
|
||||
|
||||
physical_offset = data->start_offset;
|
||||
max_physical_offset = physical_offset + data->stream_size;
|
||||
skip_frames = data->stream_number;
|
||||
|
||||
/* get size of the underlying stream, skipping other streams
|
||||
* (all streams should have the same frame count) */
|
||||
while (physical_offset < max_physical_offset) {
|
||||
size_t data_size;
|
||||
uint32_t id;
|
||||
|
||||
switch(data->codec) {
|
||||
case FSB_INT_CELT:
|
||||
id = read_32bitBE(physical_offset+0x00,streamfile);
|
||||
if (id != 0x17C30DF3) /* incorrect FSB CELT frame sync */
|
||||
data_size = 0;
|
||||
else
|
||||
data_size = 0x04+0x04+read_32bitLE(physical_offset+0x04,streamfile);
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* there may be padding at the end, so this doubles as EOF marker */
|
||||
if (data_size == 0)
|
||||
break;
|
||||
|
||||
/* skip frames from other streams */
|
||||
if (skip_frames) {
|
||||
physical_offset += data_size;
|
||||
skip_frames--;
|
||||
continue;
|
||||
}
|
||||
|
||||
physical_offset += data_size;
|
||||
total_size += data_size;
|
||||
skip_frames = data->stream_count - 1;
|
||||
}
|
||||
|
||||
data->total_size = total_size;
|
||||
return data->total_size;
|
||||
}
|
||||
|
||||
|
||||
/* Prepares custom IO for multistreams, interleaves 1 packet per stream */
|
||||
static STREAMFILE* setup_fsb_interleave_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t stream_size, int stream_count, int stream_number, fsb_interleave_codec_t codec) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
fsb_interleave_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(fsb_interleave_io_data);
|
||||
|
||||
io_data.start_offset = start_offset;
|
||||
io_data.physical_offset = start_offset;
|
||||
io_data.skip_frames = stream_number; /* adjust since start_offset points to the first */
|
||||
io_data.codec = codec;
|
||||
io_data.stream_count = stream_count;
|
||||
io_data.stream_number = stream_number;
|
||||
io_data.stream_size = stream_size;
|
||||
io_data.total_size = fsb_interleave_io_size(streamFile, &io_data); /* force init */
|
||||
|
||||
if (io_data.total_size == 0 || io_data.total_size > io_data.stream_size) {
|
||||
VGM_LOG("FSB INTERLEAVE: wrong total_size %x vs %x\n", io_data.total_size,io_data.stream_size);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, fsb_interleave_io_read,fsb_interleave_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _FSB_INTERLEAVE_STREAMFILE_H_ */
|
||||
|
109
src/meta/gin.c
109
src/meta/gin.c
@ -1,55 +1,54 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* .gin - EA engine sounds [Need for Speed: Most Wanted (multi)] */
|
||||
VGMSTREAM * init_vgmstream_gin(STREAMFILE *sf) {
|
||||
VGMSTREAM *vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, sample_rate, num_samples;
|
||||
|
||||
if (!check_extensions(sf, "gin"))
|
||||
goto fail;
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "Gnsu") && /* original */
|
||||
!is_id32be(0x00, sf, "Octn")) /* later (2013+) games, looks same as "Gnsu" */
|
||||
goto fail;
|
||||
|
||||
/* contains mapped values for engine RPM sounds but we'll just play the whole thing */
|
||||
/* 0x04: size? "20\00\00"? */
|
||||
/* 0x08/0c: min/max float RPM? */
|
||||
/* 0x10: RPM up? (pitch/frequency) table size */
|
||||
/* 0x14: RPM ??? table size */
|
||||
/* always LE even on X360/PS3 */
|
||||
|
||||
num_samples = read_u32le(0x18, sf);
|
||||
sample_rate = read_u32le(0x1c, sf);
|
||||
start_offset = 0x20 +
|
||||
(read_u32le(0x10, sf) + 1) * 0x04 +
|
||||
(read_u32le(0x14, sf) + 1) * 0x04;
|
||||
channel_count = 1;
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_GIN;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
|
||||
vgmstream->coding_type = coding_EA_XAS_V0;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
/* calculate size for TMX */
|
||||
vgmstream->stream_size = (align_size_to_block(num_samples, 32) / 32) * 0x13;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* .gin - EA engine sounds [Need for Speed: Most Wanted (multi)] */
|
||||
VGMSTREAM* init_vgmstream_gin(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
off_t start_offset;
|
||||
int loop_flag, channel_count, sample_rate, num_samples;
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00, sf, "Gnsu") && /* original */
|
||||
!is_id32be(0x00, sf, "Octn")) /* later (2013+) games */
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "gin"))
|
||||
return NULL;
|
||||
|
||||
/* contains mapped values for engine RPM sounds but we'll just play the whole thing */
|
||||
/* 0x04: size? "20\00\00"? */
|
||||
/* 0x08/0c: min/max float RPM? */
|
||||
/* 0x10: RPM up? (pitch/frequency) table size */
|
||||
/* 0x14: RPM ??? table size */
|
||||
/* always LE even on X360/PS3 */
|
||||
|
||||
num_samples = read_u32le(0x18, sf);
|
||||
sample_rate = read_u32le(0x1c, sf);
|
||||
start_offset = 0x20 +
|
||||
(read_u32le(0x10, sf) + 1) * 0x04 +
|
||||
(read_u32le(0x14, sf) + 1) * 0x04;
|
||||
channel_count = 1;
|
||||
loop_flag = 0;
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_GIN;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
|
||||
vgmstream->coding_type = coding_EA_XAS_V0;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
/* calculate size for TMX */
|
||||
vgmstream->stream_size = (align_size_to_block(num_samples, 32) / 32) * 0x13;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
165
src/meta/kma9.c
165
src/meta/kma9.c
@ -1,83 +1,82 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "kma9_streamfile.h"
|
||||
|
||||
|
||||
/* KMA9 - Koei Tecmo's interleaved ATRAC9 [Nobunaga no Yabou - Souzou (Vita)] */
|
||||
VGMSTREAM * init_vgmstream_kma9(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
STREAMFILE* temp_streamFile = NULL;
|
||||
off_t start_offset;
|
||||
size_t stream_size, interleave;
|
||||
int loop_flag, channel_count;
|
||||
int total_subsongs = 0, target_subsong = streamFile->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if ( !check_extensions(streamFile,"km9") )
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x4B4D4139) /* "KMA9" */
|
||||
goto fail;
|
||||
|
||||
start_offset = read_32bitLE(0x04,streamFile);
|
||||
channel_count = read_16bitLE(0x32,streamFile);
|
||||
loop_flag = (read_32bitLE(0x28,streamFile) != 0);
|
||||
|
||||
total_subsongs = read_32bitLE(0x08,streamFile);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
/* 0x0c: unknown */
|
||||
interleave = read_32bitLE(0x10,streamFile); /* 1 superframe */
|
||||
stream_size = read_32bitLE(0x14,streamFile); /* per subsong */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_32bitLE(0x34,streamFile);
|
||||
vgmstream->num_samples = read_32bitLE(0x18,streamFile); /* without skip_samples? */
|
||||
vgmstream->loop_start_sample = read_32bitLE(0x24,streamFile); /* with skip_samples? */
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples; /* 0x28 looks like end samples but isn't, no idea */
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
vgmstream->meta_type = meta_KMA9;
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
{
|
||||
atrac9_config cfg = {0};
|
||||
|
||||
cfg.channels = vgmstream->channels;
|
||||
cfg.encoder_delay = read_32bitLE(0x20,streamFile);
|
||||
cfg.config_data = read_32bitBE(0x5c,streamFile);
|
||||
|
||||
vgmstream->codec_data = init_atrac9(&cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if (loop_flag) { /* seems correct but must double check */
|
||||
vgmstream->loop_start_sample -= cfg.encoder_delay;
|
||||
//vgmstream->loop_end_sample -= cfg.encoder_delay;
|
||||
}
|
||||
|
||||
/* KMA9 interleaves one ATRAC9 frame per subsong */
|
||||
temp_streamFile = setup_kma9_streamfile(streamFile, start_offset, stream_size, interleave, (target_subsong-1), total_subsongs);
|
||||
if (!temp_streamFile) goto fail;
|
||||
start_offset = 0;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
/* open the file for reading */
|
||||
if ( !vgmstream_open_stream(vgmstream, temp_streamFile, start_offset) )
|
||||
goto fail;
|
||||
close_streamfile(temp_streamFile);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "kma9_streamfile.h"
|
||||
|
||||
|
||||
/* KMA9 - Koei Tecmo games [Nobunaga no Yabou: Souzou (Vita)] */
|
||||
VGMSTREAM* init_vgmstream_kma9(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
off_t start_offset;
|
||||
size_t stream_size, interleave;
|
||||
int loop_flag, channels;
|
||||
int total_subsongs = 0, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "KMA9"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"km9"))
|
||||
return NULL;
|
||||
|
||||
start_offset = read_u32le(0x04,sf);
|
||||
channels = read_u16le(0x32,sf);
|
||||
loop_flag = (read_s32le(0x28,sf) != 0);
|
||||
|
||||
total_subsongs = read_s32le(0x08,sf);
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
/* 0x0c: unknown */
|
||||
interleave = read_u32le(0x10,sf); /* 1 superframe */
|
||||
stream_size = read_u32le(0x14,sf); /* per subsong */
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_s32le(0x34,sf);
|
||||
vgmstream->num_samples = read_s32le(0x18,sf); /* without skip_samples? */
|
||||
vgmstream->loop_start_sample = read_s32le(0x24,sf); /* with skip_samples? */
|
||||
vgmstream->loop_end_sample = vgmstream->num_samples; /* 0x28 looks like end samples but isn't, no idea */
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
vgmstream->meta_type = meta_KMA9;
|
||||
|
||||
#ifdef VGM_USE_ATRAC9
|
||||
{
|
||||
atrac9_config cfg = {0};
|
||||
|
||||
cfg.channels = channels;
|
||||
cfg.encoder_delay = read_s32le(0x20,sf);
|
||||
cfg.config_data = read_u32be(0x5c,sf);
|
||||
|
||||
vgmstream->codec_data = init_atrac9(&cfg);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_ATRAC9;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
if (loop_flag) { /* seems correct but must double check */
|
||||
vgmstream->loop_start_sample -= cfg.encoder_delay;
|
||||
//vgmstream->loop_end_sample -= cfg.encoder_delay;
|
||||
}
|
||||
|
||||
/* KMA9 interleaves one ATRAC9 frame per subsong */
|
||||
temp_sf = setup_kma9_streamfile(sf, start_offset, stream_size, interleave, (target_subsong-1), total_subsongs);
|
||||
if (!temp_sf) goto fail;
|
||||
start_offset = 0;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, temp_sf, start_offset))
|
||||
goto fail;
|
||||
close_streamfile(temp_sf);
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_sf);
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -477,7 +477,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE* streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_msf(STREAMFILE* streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ps3_past(STREAMFILE* streamFile);
|
||||
VGMSTREAM* init_vgmstream_sndp(STREAMFILE* sf);
|
||||
|
||||
VGMSTREAM * init_vgmstream_sgxd(STREAMFILE* streamFile);
|
||||
|
||||
|
120
src/meta/ppst.c
120
src/meta/ppst.c
@ -1,60 +1,60 @@
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "ppst_streamfile.h"
|
||||
|
||||
|
||||
/* PPST - ParaPpa STream (maybe), extracted from .img bigfile [Parappa the Rapper (PSP)] */
|
||||
VGMSTREAM * init_vgmstream_ppst(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
int total_subsongs, target_subsong = streamFile->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!check_extensions(streamFile, "sng"))
|
||||
goto fail;
|
||||
if (read_32bitBE(0x00,streamFile) != 0x50505354) /* "PPST" */
|
||||
goto fail;
|
||||
|
||||
/* header has some control and comment fields then interleaved RIFF .at3 */
|
||||
|
||||
/* count subsongs (mainly 4, rarely 1) */
|
||||
{
|
||||
off_t offset = 0xa0;
|
||||
|
||||
total_subsongs = 0;
|
||||
while (offset < 0x800) {
|
||||
if (read_32bitLE(offset + 0x04, streamFile) == 0) /* subsong size */
|
||||
break;
|
||||
total_subsongs++;
|
||||
offset += 0x08;
|
||||
}
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
}
|
||||
|
||||
{
|
||||
off_t start_offset = 0x800;
|
||||
size_t interleave_size = 0x4000;
|
||||
size_t stride_size = 0x4000*total_subsongs;
|
||||
/* subsong header at 0xa0, 0x00(1): id, 0x01(3): blocks of interleave */
|
||||
size_t stream_size = read_32bitLE(0xA0+0x08*(target_subsong-1)+0x04, streamFile);
|
||||
|
||||
STREAMFILE* temp_streamFile = setup_ppst_streamfile(streamFile, start_offset+interleave_size*(target_subsong-1), interleave_size, stride_size, stream_size);
|
||||
if (!temp_streamFile) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_riff(temp_streamFile);
|
||||
close_streamfile(temp_streamFile);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
vgmstream->meta_type = meta_PPST;
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
#include "meta.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "ppst_streamfile.h"
|
||||
|
||||
|
||||
/* PPST - ParaPpa STream (maybe) [Parappa the Rapper (PSP)] */
|
||||
VGMSTREAM* init_vgmstream_ppst(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
int total_subsongs, target_subsong = sf->stream_index;
|
||||
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "PPST"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf, "sng"))
|
||||
return NULL;
|
||||
|
||||
/* header has some control and comment fields then interleaved RIFF .at3 */
|
||||
|
||||
/* count subsongs (mainly 4, rarely 1) */
|
||||
{
|
||||
off_t offset = 0xa0;
|
||||
|
||||
total_subsongs = 0;
|
||||
while (offset < 0x800) {
|
||||
if (read_u32le(offset + 0x04, sf) == 0) /* subsong size */
|
||||
break;
|
||||
total_subsongs++;
|
||||
offset += 0x08;
|
||||
}
|
||||
|
||||
if (target_subsong == 0) target_subsong = 1;
|
||||
if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;
|
||||
}
|
||||
|
||||
{
|
||||
off_t start_offset = 0x800;
|
||||
size_t interleave_size = 0x4000;
|
||||
size_t stride_size = 0x4000 * total_subsongs;
|
||||
/* subsong header at 0xa0, 0x00(1): id, 0x01(3): blocks of interleave */
|
||||
size_t stream_size = read_u32le(0xA0 + 0x08 * (target_subsong - 1) + 0x04, sf);
|
||||
|
||||
STREAMFILE* temp_sf = setup_ppst_streamfile(sf, start_offset + interleave_size * (target_subsong - 1), interleave_size, stride_size, stream_size);
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
vgmstream = init_vgmstream_riff(temp_sf);
|
||||
close_streamfile(temp_sf);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->num_streams = total_subsongs;
|
||||
vgmstream->stream_size = stream_size;
|
||||
vgmstream->meta_type = meta_PPST;
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1,101 +1,101 @@
|
||||
#ifndef _PPST_STREAMFILE_H_
|
||||
#define _PPST_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t start_physical_offset; /* interleaved data start, for this substream */
|
||||
size_t interleave_block_size; /* max size that can be read before encountering other substreams */
|
||||
size_t stride_size; /* step size between interleave blocks (interleave*channels) */
|
||||
size_t stream_size; /* final size of the deinterleaved substream */
|
||||
} ppst_io_data;
|
||||
|
||||
|
||||
/* Handles deinterleaving of complete files, skipping portions or other substreams. */
|
||||
static size_t ppst_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ppst_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
while (length > 0) {
|
||||
size_t to_read;
|
||||
size_t length_available;
|
||||
off_t block_num;
|
||||
off_t intrablock_offset;
|
||||
off_t physical_offset;
|
||||
|
||||
if (offset >= data->stream_size)
|
||||
return total_read;
|
||||
|
||||
block_num = offset / data->interleave_block_size;
|
||||
intrablock_offset = offset % data->interleave_block_size;
|
||||
physical_offset = data->start_physical_offset + block_num*data->stride_size + intrablock_offset;
|
||||
length_available = data->interleave_block_size - intrablock_offset;
|
||||
if (length_available > data->stream_size - offset)
|
||||
length_available = data->stream_size - offset;
|
||||
|
||||
if (length < length_available) {
|
||||
to_read = length;
|
||||
}
|
||||
else {
|
||||
to_read = length_available;
|
||||
}
|
||||
|
||||
if (to_read > 0) {
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(dest, physical_offset, to_read, streamfile);
|
||||
total_read += bytes_read;
|
||||
|
||||
if (bytes_read != to_read) {
|
||||
return total_read;
|
||||
}
|
||||
|
||||
dest += bytes_read;
|
||||
offset += bytes_read;
|
||||
length -= bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t ppst_io_size(STREAMFILE *streamfile, ppst_io_data* data) {
|
||||
return data->stream_size;
|
||||
}
|
||||
|
||||
|
||||
static STREAMFILE* setup_ppst_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t interleave_block_size, size_t stride_size, size_t stream_size) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
ppst_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(ppst_io_data);
|
||||
|
||||
io_data.start_physical_offset = start_offset;
|
||||
io_data.interleave_block_size = interleave_block_size;
|
||||
io_data.stride_size = stride_size;
|
||||
io_data.stream_size = stream_size;
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ppst_io_read,ppst_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"at3");
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _SCD_STREAMFILE_H_ */
|
||||
#ifndef _PPST_STREAMFILE_H_
|
||||
#define _PPST_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
off_t start_physical_offset; /* interleaved data start, for this substream */
|
||||
size_t interleave_block_size; /* max size that can be read before encountering other substreams */
|
||||
size_t stride_size; /* step size between interleave blocks (interleave*channels) */
|
||||
size_t stream_size; /* final size of the deinterleaved substream */
|
||||
} ppst_io_data;
|
||||
|
||||
|
||||
/* Handles deinterleaving of complete files, skipping portions or other substreams. */
|
||||
static size_t ppst_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ppst_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
|
||||
while (length > 0) {
|
||||
size_t to_read;
|
||||
size_t length_available;
|
||||
off_t block_num;
|
||||
off_t intrablock_offset;
|
||||
off_t physical_offset;
|
||||
|
||||
if (offset >= data->stream_size)
|
||||
return total_read;
|
||||
|
||||
block_num = offset / data->interleave_block_size;
|
||||
intrablock_offset = offset % data->interleave_block_size;
|
||||
physical_offset = data->start_physical_offset + block_num*data->stride_size + intrablock_offset;
|
||||
length_available = data->interleave_block_size - intrablock_offset;
|
||||
if (length_available > data->stream_size - offset)
|
||||
length_available = data->stream_size - offset;
|
||||
|
||||
if (length < length_available) {
|
||||
to_read = length;
|
||||
}
|
||||
else {
|
||||
to_read = length_available;
|
||||
}
|
||||
|
||||
if (to_read > 0) {
|
||||
size_t bytes_read;
|
||||
|
||||
bytes_read = read_streamfile(dest, physical_offset, to_read, streamfile);
|
||||
total_read += bytes_read;
|
||||
|
||||
if (bytes_read != to_read) {
|
||||
return total_read;
|
||||
}
|
||||
|
||||
dest += bytes_read;
|
||||
offset += bytes_read;
|
||||
length -= bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t ppst_io_size(STREAMFILE *streamfile, ppst_io_data* data) {
|
||||
return data->stream_size;
|
||||
}
|
||||
|
||||
|
||||
static STREAMFILE* setup_ppst_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t interleave_block_size, size_t stride_size, size_t stream_size) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
ppst_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(ppst_io_data);
|
||||
|
||||
io_data.start_physical_offset = start_offset;
|
||||
io_data.interleave_block_size = interleave_block_size;
|
||||
io_data.stride_size = stride_size;
|
||||
io_data.stream_size = stream_size;
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, ppst_io_read,ppst_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_buffer_streamfile(new_streamFile,0);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"at3");
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _SCD_STREAMFILE_H_ */
|
||||
|
@ -1,73 +0,0 @@
|
||||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
|
||||
/* .PAST (Bakugan Battle Brawlers */
|
||||
VGMSTREAM * init_vgmstream_ps3_past(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
char filename[PATH_LIMIT];
|
||||
off_t start_offset;
|
||||
|
||||
int loop_flag;
|
||||
int channel_count;
|
||||
|
||||
/* check extension, case insensitive */
|
||||
streamFile->get_name(streamFile,filename,sizeof(filename));
|
||||
if (strcasecmp("past",filename_extension(filename))) goto fail;
|
||||
|
||||
/* check header */
|
||||
if (read_32bitBE(0x0,streamFile) != 0x534E4450) /* SNDP */
|
||||
goto fail;
|
||||
|
||||
loop_flag = (read_32bitBE(0x1C,streamFile)!=0);
|
||||
channel_count = (uint16_t)read_16bitBE(0xC,streamFile);
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
/* fill in the vital statistics */
|
||||
start_offset = 0x30;
|
||||
vgmstream->channels = channel_count;
|
||||
vgmstream->sample_rate = read_32bitBE(0x10,streamFile);
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->num_samples = (read_32bitBE(0x14,streamFile))/2/channel_count;
|
||||
if (loop_flag) {
|
||||
vgmstream->loop_start_sample = read_32bitBE(0x18,streamFile)/2/channel_count;
|
||||
vgmstream->loop_end_sample = read_32bitBE(0x1C,streamFile)/2/channel_count;
|
||||
}
|
||||
|
||||
if (channel_count == 1)
|
||||
{
|
||||
vgmstream->layout_type = layout_none;
|
||||
}
|
||||
else
|
||||
{
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x2;
|
||||
}
|
||||
|
||||
vgmstream->meta_type = meta_PS3_PAST;
|
||||
|
||||
/* open the file for reading */
|
||||
{
|
||||
int i;
|
||||
STREAMFILE * file;
|
||||
file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
|
||||
if (!file) goto fail;
|
||||
for (i=0;i<channel_count;i++) {
|
||||
vgmstream->ch[i].streamfile = file;
|
||||
|
||||
vgmstream->ch[i].channel_start_offset=
|
||||
vgmstream->ch[i].offset=start_offset+
|
||||
vgmstream->interleave_block_size*i;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
/* clean up anything we may have opened */
|
||||
if (vgmstream) close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -30,7 +30,7 @@ VGMSTREAM* init_vgmstream_rstm_rockstar(STREAMFILE* sf) {
|
||||
//loop_flag = (read_s32le(0x24,sf) > 0);
|
||||
loop_flag = loop_end != stream_size;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
|
40
src/meta/sndp.c
Normal file
40
src/meta/sndp.c
Normal file
@ -0,0 +1,40 @@
|
||||
#include "meta.h"
|
||||
#include "../util.h"
|
||||
#include "../coding/coding.h"
|
||||
|
||||
/* SNDP - from from Premium Agency games [Bakugan Battle Brawlers (PS3)] */
|
||||
VGMSTREAM* init_vgmstream_sndp(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
|
||||
/* checks */
|
||||
if (!is_id32be(0x00,sf, "SNDP"))
|
||||
return NULL;
|
||||
if (!check_extensions(sf,"past"))
|
||||
return NULL;
|
||||
|
||||
bool loop_flag = (read_u32be(0x1c,sf) !=0);
|
||||
int channels = read_u16be(0x0c,sf);
|
||||
off_t start_offset = 0x30;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = read_u32be(0x10,sf);
|
||||
vgmstream->num_samples = pcm16_bytes_to_samples(read_u32be(0x14,sf), channels);
|
||||
vgmstream->loop_start_sample = pcm16_bytes_to_samples(read_u32be(0x18,sf), channels);
|
||||
vgmstream->loop_end_sample = pcm16_bytes_to_samples(read_u32be(0x1C,sf), channels);
|
||||
|
||||
vgmstream->coding_type = coding_PCM16LE;
|
||||
vgmstream->layout_type = layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x02;
|
||||
|
||||
vgmstream->meta_type = meta_SNDP;
|
||||
|
||||
if (!vgmstream_open_stream(vgmstream, sf, start_offset))
|
||||
goto fail;
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -1,163 +1,163 @@
|
||||
#ifndef _XVAG_STREAMFILE_H_
|
||||
#define _XVAG_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
int stream_number;
|
||||
int stream_count;
|
||||
size_t interleave_size;
|
||||
size_t frame_size;
|
||||
off_t stream_offset;
|
||||
//size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
|
||||
size_t skip_size; /* size to skip from a block start to reach data start */
|
||||
size_t data_size; /* logical size of the block */
|
||||
|
||||
size_t logical_size;
|
||||
} xvag_io_data;
|
||||
|
||||
|
||||
static size_t xvag_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, xvag_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* ignore bad reads */
|
||||
if (offset < 0 || offset > data->logical_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
data->logical_offset = 0x00;
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks, one at a time */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (data->logical_offset >= data->logical_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
data->skip_size = data->interleave_size * data->stream_number;
|
||||
data->data_size = data->interleave_size;
|
||||
|
||||
/* some ATRAC9 XVAG have padding+RIFF at start [The Last of Us (PS4), Farpoint (PS4)] */
|
||||
if (data->logical_offset == 0 && read_32bitBE(data->physical_offset+data->skip_size,streamfile) == 0) {
|
||||
data->skip_size += data->frame_size;
|
||||
data->data_size -= data->frame_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->interleave_size*data->stream_count;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
offset += bytes_done;
|
||||
total_read += bytes_done;
|
||||
length -= bytes_done;
|
||||
dest += bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t xvag_io_size(STREAMFILE *streamfile, xvag_io_data* data) {
|
||||
off_t physical_offset = data->stream_offset;
|
||||
off_t max_physical_offset = get_streamfile_size(streamfile);
|
||||
size_t logical_size = 0;
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* get size of the logical stream */
|
||||
while (physical_offset < max_physical_offset) {
|
||||
size_t skip_size = data->interleave_size * data->stream_number;
|
||||
size_t data_size = data->interleave_size;
|
||||
|
||||
/* some ATRAC9 XVAG have padding+RIFF at start [The Last of Us (PS4), Farpoint (PS4)] */
|
||||
if (logical_size == 0 && read_32bitBE(physical_offset+skip_size,streamfile) == 0) {
|
||||
skip_size += data->frame_size;
|
||||
data_size -= data->frame_size;
|
||||
}
|
||||
|
||||
logical_size += data_size;
|
||||
physical_offset += data->interleave_size*data->stream_count;
|
||||
}
|
||||
|
||||
if (logical_size > max_physical_offset)
|
||||
return 0;
|
||||
data->logical_size = logical_size;
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Prepares custom IO for XVAG, which interleaves many superframes per subsong/layer.
|
||||
* May have start padding, even with only one subsong. All layers share config_data too. */
|
||||
static STREAMFILE* setup_xvag_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t interleave_size, size_t frame_size, int stream_number, int stream_count) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
xvag_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(xvag_io_data);
|
||||
|
||||
io_data.stream_number = stream_number;
|
||||
io_data.stream_count = stream_count;
|
||||
io_data.stream_offset = stream_offset;
|
||||
//io_data.stream_size = stream_size;
|
||||
io_data.interleave_size = interleave_size;
|
||||
io_data.frame_size = frame_size;
|
||||
io_data.physical_offset = stream_offset;
|
||||
io_data.logical_size = xvag_io_size(streamFile, &io_data); /* force init */
|
||||
|
||||
if (io_data.logical_size == 0) {
|
||||
VGM_LOG("XVAG: wrong logical size\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, xvag_io_read,xvag_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _XVAG_STREAMFILE_H_ */
|
||||
#ifndef _XVAG_STREAMFILE_H_
|
||||
#define _XVAG_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* config */
|
||||
int stream_number;
|
||||
int stream_count;
|
||||
size_t interleave_size;
|
||||
size_t frame_size;
|
||||
off_t stream_offset;
|
||||
//size_t stream_size;
|
||||
|
||||
/* state */
|
||||
off_t logical_offset; /* offset that corresponds to physical_offset */
|
||||
off_t physical_offset; /* actual file offset */
|
||||
|
||||
size_t skip_size; /* size to skip from a block start to reach data start */
|
||||
size_t data_size; /* logical size of the block */
|
||||
|
||||
size_t logical_size;
|
||||
} xvag_io_data;
|
||||
|
||||
|
||||
static size_t xvag_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, xvag_io_data* data) {
|
||||
size_t total_read = 0;
|
||||
|
||||
/* ignore bad reads */
|
||||
if (offset < 0 || offset > data->logical_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* previous offset: re-start as we can't map logical<>physical offsets
|
||||
* (kinda slow as it trashes buffers, but shouldn't happen often) */
|
||||
if (offset < data->logical_offset) {
|
||||
data->logical_offset = 0x00;
|
||||
data->physical_offset = data->stream_offset;
|
||||
data->data_size = 0;
|
||||
}
|
||||
|
||||
/* read blocks, one at a time */
|
||||
while (length > 0) {
|
||||
|
||||
/* ignore EOF */
|
||||
if (data->logical_offset >= data->logical_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* process new block */
|
||||
if (data->data_size == 0) {
|
||||
data->skip_size = data->interleave_size * data->stream_number;
|
||||
data->data_size = data->interleave_size;
|
||||
|
||||
/* some ATRAC9 XVAG have padding+RIFF at start [The Last of Us (PS4), Farpoint (PS4)] */
|
||||
if (data->logical_offset == 0 && read_32bitBE(data->physical_offset+data->skip_size,streamfile) == 0) {
|
||||
data->skip_size += data->frame_size;
|
||||
data->data_size -= data->frame_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
if (offset >= data->logical_offset + data->data_size) {
|
||||
data->physical_offset += data->interleave_size*data->stream_count;
|
||||
data->logical_offset += data->data_size;
|
||||
data->data_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
{
|
||||
size_t bytes_consumed, bytes_done, to_read;
|
||||
|
||||
bytes_consumed = offset - data->logical_offset;
|
||||
|
||||
to_read = data->data_size - bytes_consumed;
|
||||
if (to_read > length)
|
||||
to_read = length;
|
||||
bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile);
|
||||
|
||||
offset += bytes_done;
|
||||
total_read += bytes_done;
|
||||
length -= bytes_done;
|
||||
dest += bytes_done;
|
||||
|
||||
if (bytes_done != to_read || bytes_done == 0) {
|
||||
break; /* error/EOF */
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static size_t xvag_io_size(STREAMFILE *streamfile, xvag_io_data* data) {
|
||||
off_t physical_offset = data->stream_offset;
|
||||
off_t max_physical_offset = get_streamfile_size(streamfile);
|
||||
size_t logical_size = 0;
|
||||
|
||||
if (data->logical_size)
|
||||
return data->logical_size;
|
||||
|
||||
/* get size of the logical stream */
|
||||
while (physical_offset < max_physical_offset) {
|
||||
size_t skip_size = data->interleave_size * data->stream_number;
|
||||
size_t data_size = data->interleave_size;
|
||||
|
||||
/* some ATRAC9 XVAG have padding+RIFF at start [The Last of Us (PS4), Farpoint (PS4)] */
|
||||
if (logical_size == 0 && read_32bitBE(physical_offset+skip_size,streamfile) == 0) {
|
||||
skip_size += data->frame_size;
|
||||
data_size -= data->frame_size;
|
||||
}
|
||||
|
||||
logical_size += data_size;
|
||||
physical_offset += data->interleave_size*data->stream_count;
|
||||
}
|
||||
|
||||
if (logical_size > max_physical_offset)
|
||||
return 0;
|
||||
data->logical_size = logical_size;
|
||||
return data->logical_size;
|
||||
}
|
||||
|
||||
/* Prepares custom IO for XVAG, which interleaves many superframes per subsong/layer.
|
||||
* May have start padding, even with only one subsong. All layers share config_data too. */
|
||||
static STREAMFILE* setup_xvag_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t interleave_size, size_t frame_size, int stream_number, int stream_count) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
xvag_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(xvag_io_data);
|
||||
|
||||
io_data.stream_number = stream_number;
|
||||
io_data.stream_count = stream_count;
|
||||
io_data.stream_offset = stream_offset;
|
||||
//io_data.stream_size = stream_size;
|
||||
io_data.interleave_size = interleave_size;
|
||||
io_data.frame_size = frame_size;
|
||||
io_data.physical_offset = stream_offset;
|
||||
io_data.logical_size = xvag_io_size(streamFile, &io_data); /* force init */
|
||||
|
||||
if (io_data.logical_size == 0) {
|
||||
VGM_LOG("XVAG: wrong logical size\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* setup subfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, xvag_io_read,xvag_io_size);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _XVAG_STREAMFILE_H_ */
|
||||
|
@ -1,58 +1,58 @@
|
||||
#ifndef _ZSND_STREAMFILE_H_
|
||||
#define _ZSND_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
off_t max_offset;
|
||||
} zsnd_io_data;
|
||||
|
||||
static size_t zsnd_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, zsnd_io_data* data) {
|
||||
size_t bytes_read, bytes_to_do;
|
||||
int i;
|
||||
|
||||
/* clamp reads */
|
||||
bytes_to_do = length;
|
||||
if (offset > data->max_offset)
|
||||
offset = data->max_offset;
|
||||
if (offset + length > data->max_offset)
|
||||
bytes_to_do = data->max_offset - offset;
|
||||
|
||||
bytes_read = streamfile->read(streamfile, dest, offset, bytes_to_do);
|
||||
|
||||
/* pretend we got data after max_offset */
|
||||
if (bytes_read < length) {
|
||||
for (i = bytes_read; i < length; i++) {
|
||||
dest[i] = 0;
|
||||
}
|
||||
bytes_read = length;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/* ZSND removes last interleave data from the file if blank, but codecs still need to read after it.
|
||||
* This data could be from next stream or from EOF, so return blank data instead. */
|
||||
static STREAMFILE* setup_zsnd_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t stream_size) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
zsnd_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(zsnd_io_data);
|
||||
|
||||
io_data.max_offset = start_offset + stream_size;
|
||||
|
||||
/* setup custom streamfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, zsnd_io_read,NULL);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _ZSND_STREAMFILE_H_ */
|
||||
#ifndef _ZSND_STREAMFILE_H_
|
||||
#define _ZSND_STREAMFILE_H_
|
||||
#include "../streamfile.h"
|
||||
|
||||
typedef struct {
|
||||
off_t max_offset;
|
||||
} zsnd_io_data;
|
||||
|
||||
static size_t zsnd_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, zsnd_io_data* data) {
|
||||
size_t bytes_read, bytes_to_do;
|
||||
int i;
|
||||
|
||||
/* clamp reads */
|
||||
bytes_to_do = length;
|
||||
if (offset > data->max_offset)
|
||||
offset = data->max_offset;
|
||||
if (offset + length > data->max_offset)
|
||||
bytes_to_do = data->max_offset - offset;
|
||||
|
||||
bytes_read = streamfile->read(streamfile, dest, offset, bytes_to_do);
|
||||
|
||||
/* pretend we got data after max_offset */
|
||||
if (bytes_read < length) {
|
||||
for (i = bytes_read; i < length; i++) {
|
||||
dest[i] = 0;
|
||||
}
|
||||
bytes_read = length;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/* ZSND removes last interleave data from the file if blank, but codecs still need to read after it.
|
||||
* This data could be from next stream or from EOF, so return blank data instead. */
|
||||
static STREAMFILE* setup_zsnd_streamfile(STREAMFILE *streamFile, off_t start_offset, size_t stream_size) {
|
||||
STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL;
|
||||
zsnd_io_data io_data = {0};
|
||||
size_t io_data_size = sizeof(zsnd_io_data);
|
||||
|
||||
io_data.max_offset = start_offset + stream_size;
|
||||
|
||||
/* setup custom streamfile */
|
||||
new_streamFile = open_wrap_streamfile(streamFile);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
new_streamFile = open_io_streamfile(temp_streamFile, &io_data,io_data_size, zsnd_io_read,NULL);
|
||||
if (!new_streamFile) goto fail;
|
||||
temp_streamFile = new_streamFile;
|
||||
|
||||
return temp_streamFile;
|
||||
|
||||
fail:
|
||||
close_streamfile(temp_streamFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* _ZSND_STREAMFILE_H_ */
|
||||
|
960
src/vgmstream.c
960
src/vgmstream.c
File diff suppressed because it is too large
Load Diff
@ -164,7 +164,7 @@ typedef struct {
|
||||
layout_t layout_type; /* type of layout */
|
||||
meta_t meta_type; /* type of metadata */
|
||||
|
||||
/* loopin config */
|
||||
/* loop config */
|
||||
int loop_flag; /* is this stream looped? */
|
||||
int32_t loop_start_sample; /* first sample of the loop (included in the loop) */
|
||||
int32_t loop_end_sample; /* last sample of the loop (not included in the loop) */
|
||||
@ -186,7 +186,7 @@ typedef struct {
|
||||
uint32_t channel_layout; /* order: FL FR FC LFE BL BR FLC FRC BC SL SR etc (WAVEFORMATEX flags where FL=lowest bit set) */
|
||||
|
||||
/* other config */
|
||||
int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
|
||||
bool allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */
|
||||
|
||||
|
||||
/* layout/block state */
|
||||
@ -205,7 +205,7 @@ typedef struct {
|
||||
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 */
|
||||
int hit_loop; /* save config when loop is hit, but first time only */
|
||||
bool hit_loop; /* save config when loop is hit, but first time only */
|
||||
|
||||
|
||||
/* decoder config/state */
|
||||
@ -221,7 +221,7 @@ typedef struct {
|
||||
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) */
|
||||
|
||||
void* mixing_data; /* state for mixing effects */
|
||||
void* mixer; /* state for mixing effects */
|
||||
|
||||
/* Optional data the codec needs for the whole stream. This is for codecs too
|
||||
* different from vgmstream's structure to be reasonably shoehorned.
|
||||
@ -233,7 +233,7 @@ typedef struct {
|
||||
|
||||
|
||||
/* play config/state */
|
||||
int config_enabled; /* config can be used */
|
||||
bool config_enabled; /* config can be used */
|
||||
play_config_t config; /* player config (applied over decoding) */
|
||||
play_state_t pstate; /* player state (applied over decoding) */
|
||||
int loop_count; /* counter of complete loops (1=looped once) */
|
||||
|
600
src/vgmstream_init.c
Normal file
600
src/vgmstream_init.c
Normal file
@ -0,0 +1,600 @@
|
||||
#include "vgmstream_init.h"
|
||||
|
||||
//typedef VGMSTREAM* (*init_vgmstream_t)(STREAMFILE*);
|
||||
|
||||
/* list of metadata parser functions that will recognize files, used on init */
|
||||
init_vgmstream_t init_vgmstream_functions[] = {
|
||||
init_vgmstream_adx,
|
||||
init_vgmstream_brstm,
|
||||
init_vgmstream_brwav,
|
||||
init_vgmstream_bfwav,
|
||||
init_vgmstream_bcwav,
|
||||
init_vgmstream_brwar,
|
||||
init_vgmstream_nds_strm,
|
||||
init_vgmstream_afc,
|
||||
init_vgmstream_ast,
|
||||
init_vgmstream_halpst,
|
||||
init_vgmstream_rs03,
|
||||
init_vgmstream_ngc_dsp_std,
|
||||
init_vgmstream_ngc_dsp_std_le,
|
||||
init_vgmstream_ngc_mdsp_std,
|
||||
init_vgmstream_csmp,
|
||||
init_vgmstream_rfrm,
|
||||
init_vgmstream_cstr,
|
||||
init_vgmstream_gcsw,
|
||||
init_vgmstream_ads,
|
||||
init_vgmstream_npsf,
|
||||
init_vgmstream_xa,
|
||||
init_vgmstream_rxws,
|
||||
init_vgmstream_ngc_dsp_stm,
|
||||
init_vgmstream_exst,
|
||||
init_vgmstream_svag_kcet,
|
||||
init_vgmstream_ngc_mpdsp,
|
||||
init_vgmstream_ngc_dsp_std_int,
|
||||
init_vgmstream_vag,
|
||||
init_vgmstream_vag_aaap,
|
||||
init_vgmstream_vag_footer,
|
||||
init_vgmstream_vag_evolution_games,
|
||||
init_vgmstream_ild,
|
||||
init_vgmstream_ngc_str,
|
||||
init_vgmstream_ea_schl,
|
||||
init_vgmstream_caf,
|
||||
init_vgmstream_vpk,
|
||||
init_vgmstream_genh,
|
||||
init_vgmstream_ogg_vorbis,
|
||||
init_vgmstream_sfl_ogg,
|
||||
init_vgmstream_sadb,
|
||||
init_vgmstream_ps2_bmdx,
|
||||
init_vgmstream_wsi,
|
||||
init_vgmstream_aifc,
|
||||
init_vgmstream_str_snds,
|
||||
init_vgmstream_ws_aud,
|
||||
init_vgmstream_ahx,
|
||||
init_vgmstream_iivb,
|
||||
init_vgmstream_svs,
|
||||
init_vgmstream_riff,
|
||||
init_vgmstream_rifx,
|
||||
init_vgmstream_nwa,
|
||||
init_vgmstream_ea_1snh,
|
||||
init_vgmstream_ea_eacs,
|
||||
init_vgmstream_xss,
|
||||
init_vgmstream_sl3,
|
||||
init_vgmstream_hgc1,
|
||||
init_vgmstream_aus,
|
||||
init_vgmstream_rws,
|
||||
init_vgmstream_fsb,
|
||||
init_vgmstream_fsb5,
|
||||
init_vgmstream_rwax,
|
||||
init_vgmstream_xwb,
|
||||
init_vgmstream_ps2_xa30,
|
||||
init_vgmstream_musc,
|
||||
init_vgmstream_musx,
|
||||
init_vgmstream_filp,
|
||||
init_vgmstream_ikm,
|
||||
init_vgmstream_ster,
|
||||
init_vgmstream_bg00,
|
||||
init_vgmstream_sat_dvi,
|
||||
init_vgmstream_dc_kcey,
|
||||
init_vgmstream_rstm_rockstar,
|
||||
init_vgmstream_acm,
|
||||
init_vgmstream_mus_acm,
|
||||
init_vgmstream_vig_kces,
|
||||
init_vgmstream_hxd,
|
||||
init_vgmstream_vsv,
|
||||
init_vgmstream_ps2_pcm,
|
||||
init_vgmstream_ps2_rkv,
|
||||
init_vgmstream_lp_ap_lep,
|
||||
init_vgmstream_sdt,
|
||||
init_vgmstream_aix,
|
||||
init_vgmstream_wvs_xbox,
|
||||
init_vgmstream_wvs_ngc,
|
||||
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,
|
||||
init_vgmstream_dc_idvi,
|
||||
init_vgmstream_ps2_rnd,
|
||||
init_vgmstream_idsp_tt,
|
||||
init_vgmstream_kraw,
|
||||
init_vgmstream_omu,
|
||||
init_vgmstream_xa2_acclaim,
|
||||
init_vgmstream_idsp_nl,
|
||||
init_vgmstream_idsp_ie,
|
||||
init_vgmstream_ymf,
|
||||
init_vgmstream_sadl,
|
||||
init_vgmstream_fag,
|
||||
init_vgmstream_mic,
|
||||
init_vgmstream_ngc_pdt_split,
|
||||
init_vgmstream_ngc_pdt,
|
||||
init_vgmstream_mus_krome,
|
||||
init_vgmstream_spsd,
|
||||
init_vgmstream_rsd,
|
||||
init_vgmstream_bgw,
|
||||
init_vgmstream_spw,
|
||||
init_vgmstream_ps2_ass,
|
||||
init_vgmstream_ubi_jade,
|
||||
init_vgmstream_ubi_jade_container,
|
||||
init_vgmstream_seg,
|
||||
init_vgmstream_nds_strm_ffta2,
|
||||
init_vgmstream_knon,
|
||||
init_vgmstream_gca,
|
||||
init_vgmstream_spt_spd,
|
||||
init_vgmstream_ish_isd,
|
||||
init_vgmstream_gsnd,
|
||||
init_vgmstream_ydsp,
|
||||
init_vgmstream_ssm,
|
||||
init_vgmstream_ps2_joe,
|
||||
init_vgmstream_vgs,
|
||||
init_vgmstream_dcs_wav,
|
||||
init_vgmstream_mul,
|
||||
init_vgmstream_thp,
|
||||
init_vgmstream_sts,
|
||||
init_vgmstream_p2bt_move_visa,
|
||||
init_vgmstream_gbts,
|
||||
init_vgmstream_wii_sng,
|
||||
init_vgmstream_ngc_dsp_iadp,
|
||||
init_vgmstream_aax,
|
||||
init_vgmstream_utf_dsp,
|
||||
init_vgmstream_ngc_ffcc_str,
|
||||
init_vgmstream_sat_baka,
|
||||
init_vgmstream_swav,
|
||||
init_vgmstream_vsf,
|
||||
init_vgmstream_nds_rrds,
|
||||
init_vgmstream_ps2_vsf_tta,
|
||||
init_vgmstream_ads_midway,
|
||||
init_vgmstream_ps2_mcg,
|
||||
init_vgmstream_zsd,
|
||||
init_vgmstream_vgs_ps,
|
||||
init_vgmstream_redspark,
|
||||
init_vgmstream_wii_wsd,
|
||||
init_vgmstream_dsp_ndp,
|
||||
init_vgmstream_ps2_sps,
|
||||
init_vgmstream_nds_hwas,
|
||||
init_vgmstream_ngc_lps,
|
||||
init_vgmstream_ps2_snd,
|
||||
init_vgmstream_naomi_adpcm,
|
||||
init_vgmstream_sd9,
|
||||
init_vgmstream_2dx9,
|
||||
init_vgmstream_dsp_ygo,
|
||||
init_vgmstream_ps2_vgv,
|
||||
init_vgmstream_gcub,
|
||||
init_vgmstream_maxis_xa,
|
||||
init_vgmstream_ngc_sck_dsp,
|
||||
init_vgmstream_apple_caff,
|
||||
init_vgmstream_pc_mxst,
|
||||
init_vgmstream_sab,
|
||||
init_vgmstream_bns,
|
||||
init_vgmstream_wii_was,
|
||||
init_vgmstream_pona_3do,
|
||||
init_vgmstream_pona_psx,
|
||||
init_vgmstream_xbox_hlwav,
|
||||
init_vgmstream_myspd,
|
||||
init_vgmstream_his,
|
||||
init_vgmstream_ast_mmv,
|
||||
init_vgmstream_ast_mv,
|
||||
init_vgmstream_dmsg,
|
||||
init_vgmstream_ngc_dsp_aaap,
|
||||
init_vgmstream_ngc_dsp_konami,
|
||||
init_vgmstream_wb,
|
||||
init_vgmstream_bnsf,
|
||||
init_vgmstream_ps2_gcm,
|
||||
init_vgmstream_smpl,
|
||||
init_vgmstream_msa,
|
||||
init_vgmstream_voi,
|
||||
init_vgmstream_ngc_rkv,
|
||||
init_vgmstream_dsp_ddsp,
|
||||
init_vgmstream_p3d,
|
||||
init_vgmstream_ngc_dsp_mpds,
|
||||
init_vgmstream_dsp_str_ig,
|
||||
init_vgmstream_ea_swvr,
|
||||
init_vgmstream_dsp_xiii,
|
||||
init_vgmstream_dsp_cabelas,
|
||||
init_vgmstream_lpcm_shade,
|
||||
init_vgmstream_ps2_vms,
|
||||
init_vgmstream_xau,
|
||||
init_vgmstream_bar,
|
||||
init_vgmstream_dsp_dspw,
|
||||
init_vgmstream_jstm,
|
||||
init_vgmstream_xvag,
|
||||
init_vgmstream_cps,
|
||||
init_vgmstream_sqex_scd,
|
||||
init_vgmstream_ngc_nst_dsp,
|
||||
init_vgmstream_baf,
|
||||
init_vgmstream_msf,
|
||||
init_vgmstream_sndp,
|
||||
init_vgmstream_sgxd,
|
||||
init_vgmstream_wii_ras,
|
||||
init_vgmstream_spm,
|
||||
init_vgmstream_ps2_iab,
|
||||
init_vgmstream_vs_str,
|
||||
init_vgmstream_lsf_n1nj4n,
|
||||
init_vgmstream_xwav_new,
|
||||
init_vgmstream_xwav_old,
|
||||
init_vgmstream_hyperscan_kvag,
|
||||
init_vgmstream_psnd,
|
||||
init_vgmstream_adp_wildfire,
|
||||
init_vgmstream_adp_qd,
|
||||
init_vgmstream_eb_sfx,
|
||||
init_vgmstream_eb_sf0,
|
||||
init_vgmstream_mtaf,
|
||||
init_vgmstream_alp,
|
||||
init_vgmstream_wpd,
|
||||
init_vgmstream_mn_str,
|
||||
init_vgmstream_mss,
|
||||
init_vgmstream_ps2_hsf,
|
||||
init_vgmstream_ivag,
|
||||
init_vgmstream_2pfs,
|
||||
init_vgmstream_xnb,
|
||||
init_vgmstream_ubi_ckd,
|
||||
init_vgmstream_ps2_vbk,
|
||||
init_vgmstream_otm,
|
||||
init_vgmstream_bcstm,
|
||||
init_vgmstream_idsp_namco,
|
||||
init_vgmstream_kt_g1l,
|
||||
init_vgmstream_kt_wiibgm,
|
||||
init_vgmstream_bfstm,
|
||||
init_vgmstream_mca,
|
||||
#if 0
|
||||
init_vgmstream_mp4_aac,
|
||||
#endif
|
||||
#if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC)
|
||||
init_vgmstream_akb_mp4,
|
||||
#endif
|
||||
init_vgmstream_ktss,
|
||||
init_vgmstream_hca,
|
||||
init_vgmstream_svag_snk,
|
||||
init_vgmstream_ps2_vds_vdm,
|
||||
init_vgmstream_cxs,
|
||||
init_vgmstream_adx_monster,
|
||||
init_vgmstream_akb,
|
||||
init_vgmstream_akb2,
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
init_vgmstream_mp4_aac_ffmpeg,
|
||||
#endif
|
||||
init_vgmstream_bik,
|
||||
init_vgmstream_astb,
|
||||
init_vgmstream_wwise,
|
||||
init_vgmstream_ubi_raki,
|
||||
init_vgmstream_pasx,
|
||||
init_vgmstream_xma,
|
||||
init_vgmstream_sndx,
|
||||
init_vgmstream_ogl,
|
||||
init_vgmstream_mc3,
|
||||
init_vgmstream_ghs,
|
||||
init_vgmstream_aac_triace,
|
||||
init_vgmstream_va3,
|
||||
init_vgmstream_mta2,
|
||||
init_vgmstream_mta2_container,
|
||||
init_vgmstream_xa_xa30,
|
||||
init_vgmstream_xa_04sw,
|
||||
init_vgmstream_ea_bnk,
|
||||
init_vgmstream_ea_abk_schl,
|
||||
init_vgmstream_ea_amb_schl,
|
||||
init_vgmstream_ea_hdr_dat,
|
||||
init_vgmstream_ea_hdr_dat_v2,
|
||||
init_vgmstream_ea_map_mus,
|
||||
init_vgmstream_ea_mpf_mus_schl,
|
||||
init_vgmstream_ea_msb_mus_schl,
|
||||
init_vgmstream_ea_schl_fixed,
|
||||
init_vgmstream_sk_aud,
|
||||
init_vgmstream_stma,
|
||||
init_vgmstream_ea_snu,
|
||||
init_vgmstream_awc,
|
||||
init_vgmstream_opus_std,
|
||||
init_vgmstream_opus_n1,
|
||||
init_vgmstream_opus_capcom,
|
||||
init_vgmstream_opus_nop,
|
||||
init_vgmstream_opus_shinen,
|
||||
init_vgmstream_opus_nus3,
|
||||
init_vgmstream_opus_sps_n1,
|
||||
init_vgmstream_pc_ast,
|
||||
init_vgmstream_naac,
|
||||
init_vgmstream_ubi_sb,
|
||||
init_vgmstream_ubi_sm,
|
||||
init_vgmstream_ubi_bnm,
|
||||
init_vgmstream_ubi_bnm_ps2,
|
||||
init_vgmstream_ubi_dat,
|
||||
init_vgmstream_ubi_blk,
|
||||
init_vgmstream_ezw,
|
||||
init_vgmstream_vxn,
|
||||
init_vgmstream_ea_snr_sns,
|
||||
init_vgmstream_ea_sps,
|
||||
init_vgmstream_ea_abk_eaac,
|
||||
init_vgmstream_ea_amb_eaac,
|
||||
init_vgmstream_ea_hdr_sth_dat,
|
||||
init_vgmstream_ea_mpf_mus_eaac,
|
||||
init_vgmstream_ea_msb_mus_eaac,
|
||||
init_vgmstream_ea_tmx,
|
||||
init_vgmstream_ea_sbr,
|
||||
init_vgmstream_ea_sbr_harmony,
|
||||
init_vgmstream_vid1,
|
||||
init_vgmstream_flx,
|
||||
init_vgmstream_mogg,
|
||||
init_vgmstream_kma9,
|
||||
init_vgmstream_xwc,
|
||||
init_vgmstream_atsl,
|
||||
init_vgmstream_sps_n1,
|
||||
init_vgmstream_apa3,
|
||||
init_vgmstream_sqex_sead,
|
||||
init_vgmstream_waf,
|
||||
init_vgmstream_wave,
|
||||
init_vgmstream_wave_segmented,
|
||||
init_vgmstream_smv,
|
||||
init_vgmstream_nxap,
|
||||
init_vgmstream_ea_wve_au00,
|
||||
init_vgmstream_ea_wve_ad10,
|
||||
init_vgmstream_sthd,
|
||||
init_vgmstream_pcm_sre,
|
||||
init_vgmstream_dsp_mcadpcm,
|
||||
init_vgmstream_ubi_lyn,
|
||||
init_vgmstream_ubi_lyn_container,
|
||||
init_vgmstream_msb_msh,
|
||||
init_vgmstream_txtp,
|
||||
init_vgmstream_smc_smh,
|
||||
init_vgmstream_ppst,
|
||||
init_vgmstream_sps_n1_segmented,
|
||||
init_vgmstream_ubi_bao_pk,
|
||||
init_vgmstream_ubi_bao_atomic,
|
||||
init_vgmstream_dsp_switch_audio,
|
||||
init_vgmstream_sadf,
|
||||
init_vgmstream_h4m,
|
||||
init_vgmstream_ads_container,
|
||||
init_vgmstream_asf,
|
||||
init_vgmstream_xmd,
|
||||
init_vgmstream_cks,
|
||||
init_vgmstream_ckb,
|
||||
init_vgmstream_wv6,
|
||||
init_vgmstream_str_wav,
|
||||
init_vgmstream_wavebatch,
|
||||
init_vgmstream_hd3_bd3,
|
||||
init_vgmstream_bnk_sony,
|
||||
init_vgmstream_nus3bank,
|
||||
init_vgmstream_sscf,
|
||||
init_vgmstream_dsp_sps_n1,
|
||||
init_vgmstream_dsp_itl_ch,
|
||||
init_vgmstream_a2m,
|
||||
init_vgmstream_ahv,
|
||||
init_vgmstream_msv,
|
||||
init_vgmstream_sdf,
|
||||
init_vgmstream_svg,
|
||||
init_vgmstream_vai,
|
||||
init_vgmstream_aif_asobo,
|
||||
init_vgmstream_ao,
|
||||
init_vgmstream_apc,
|
||||
init_vgmstream_wv2,
|
||||
init_vgmstream_xau_konami,
|
||||
init_vgmstream_derf,
|
||||
init_vgmstream_utk,
|
||||
init_vgmstream_nxa1,
|
||||
init_vgmstream_adpcm_capcom,
|
||||
init_vgmstream_ue4opus,
|
||||
init_vgmstream_xwma,
|
||||
init_vgmstream_xopus,
|
||||
init_vgmstream_vs_square,
|
||||
init_vgmstream_msf_banpresto_wmsf,
|
||||
init_vgmstream_msf_banpresto_2msf,
|
||||
init_vgmstream_nwav,
|
||||
init_vgmstream_xpcm,
|
||||
init_vgmstream_msf_tamasoft,
|
||||
init_vgmstream_xps_dat,
|
||||
init_vgmstream_xps,
|
||||
init_vgmstream_zsnd,
|
||||
init_vgmstream_opus_opusx,
|
||||
init_vgmstream_dsp_adpy,
|
||||
init_vgmstream_dsp_adpx,
|
||||
init_vgmstream_ogg_opus,
|
||||
init_vgmstream_nus3audio,
|
||||
init_vgmstream_imc,
|
||||
init_vgmstream_imc_container,
|
||||
init_vgmstream_smp,
|
||||
init_vgmstream_gin,
|
||||
init_vgmstream_dsf,
|
||||
init_vgmstream_208,
|
||||
init_vgmstream_dsp_lucasarts_ds2,
|
||||
init_vgmstream_ffdl,
|
||||
init_vgmstream_mus_vc,
|
||||
init_vgmstream_strm_abylight,
|
||||
init_vgmstream_sfh,
|
||||
init_vgmstream_ea_schl_video,
|
||||
init_vgmstream_msf_konami,
|
||||
init_vgmstream_xwma_konami,
|
||||
init_vgmstream_9tav,
|
||||
init_vgmstream_fsb5_fev_bank,
|
||||
init_vgmstream_bwav,
|
||||
init_vgmstream_opus_prototype,
|
||||
init_vgmstream_awb,
|
||||
init_vgmstream_acb,
|
||||
init_vgmstream_rad,
|
||||
init_vgmstream_smk,
|
||||
init_vgmstream_mzrt_v0,
|
||||
init_vgmstream_xavs,
|
||||
init_vgmstream_psf_single,
|
||||
init_vgmstream_psf_segmented,
|
||||
init_vgmstream_dsp_itl,
|
||||
init_vgmstream_sch,
|
||||
init_vgmstream_ima,
|
||||
init_vgmstream_nub,
|
||||
init_vgmstream_nub_wav,
|
||||
init_vgmstream_nub_vag,
|
||||
init_vgmstream_nub_at3,
|
||||
init_vgmstream_nub_xma,
|
||||
init_vgmstream_nub_idsp,
|
||||
init_vgmstream_nub_is14,
|
||||
init_vgmstream_xwv_valve,
|
||||
init_vgmstream_ubi_hx,
|
||||
init_vgmstream_bmp_konami,
|
||||
init_vgmstream_opus_opusnx,
|
||||
init_vgmstream_opus_sqex,
|
||||
init_vgmstream_isb,
|
||||
init_vgmstream_xssb,
|
||||
init_vgmstream_xma_ue3,
|
||||
init_vgmstream_csb,
|
||||
init_vgmstream_fwse,
|
||||
init_vgmstream_fda,
|
||||
init_vgmstream_kwb,
|
||||
init_vgmstream_lrmd,
|
||||
init_vgmstream_bkhd,
|
||||
init_vgmstream_bkhd_fx,
|
||||
init_vgmstream_diva,
|
||||
init_vgmstream_imuse,
|
||||
init_vgmstream_ktsr,
|
||||
init_vgmstream_asrs,
|
||||
init_vgmstream_mups,
|
||||
init_vgmstream_kat,
|
||||
init_vgmstream_pcm_success,
|
||||
init_vgmstream_ktsc,
|
||||
init_vgmstream_adp_konami,
|
||||
init_vgmstream_zwv,
|
||||
init_vgmstream_dsb,
|
||||
init_vgmstream_bsf,
|
||||
init_vgmstream_sdrh_new,
|
||||
init_vgmstream_sdrh_old,
|
||||
init_vgmstream_wady,
|
||||
init_vgmstream_dsp_sqex,
|
||||
init_vgmstream_dsp_wiivoice,
|
||||
init_vgmstream_xws,
|
||||
init_vgmstream_cpk,
|
||||
init_vgmstream_opus_nsopus,
|
||||
init_vgmstream_sbk,
|
||||
init_vgmstream_dsp_wiiadpcm,
|
||||
init_vgmstream_dsp_cwac,
|
||||
init_vgmstream_ifs,
|
||||
init_vgmstream_acx,
|
||||
init_vgmstream_compresswave,
|
||||
init_vgmstream_ktac,
|
||||
init_vgmstream_mzrt_v1,
|
||||
init_vgmstream_bsnf,
|
||||
init_vgmstream_tac,
|
||||
init_vgmstream_idsp_tose,
|
||||
init_vgmstream_dsp_kwa,
|
||||
init_vgmstream_ogv_3rdeye,
|
||||
init_vgmstream_sspr,
|
||||
init_vgmstream_piff_tpcm,
|
||||
init_vgmstream_wxd_wxh,
|
||||
init_vgmstream_bnk_relic,
|
||||
init_vgmstream_xsh_xsd_xss,
|
||||
init_vgmstream_psb,
|
||||
init_vgmstream_lopu_fb,
|
||||
init_vgmstream_lpcm_fb,
|
||||
init_vgmstream_wbk,
|
||||
init_vgmstream_wbk_nslb,
|
||||
init_vgmstream_dsp_apex,
|
||||
init_vgmstream_ubi_ckd_cwav,
|
||||
init_vgmstream_sspf,
|
||||
init_vgmstream_opus_rsnd,
|
||||
init_vgmstream_s3v,
|
||||
init_vgmstream_esf,
|
||||
init_vgmstream_adm3,
|
||||
init_vgmstream_tt_ad,
|
||||
init_vgmstream_bw_mp3_riff,
|
||||
init_vgmstream_bw_riff_mp3,
|
||||
init_vgmstream_sndz,
|
||||
init_vgmstream_vab,
|
||||
init_vgmstream_bigrp,
|
||||
init_vgmstream_sscf_encrypted,
|
||||
init_vgmstream_s_p_sth,
|
||||
init_vgmstream_utf_ahx,
|
||||
init_vgmstream_ego_dic,
|
||||
init_vgmstream_awd,
|
||||
init_vgmstream_rws_809,
|
||||
init_vgmstream_pwb,
|
||||
init_vgmstream_squeakstream,
|
||||
init_vgmstream_squeaksample,
|
||||
init_vgmstream_snds,
|
||||
init_vgmstream_adm2,
|
||||
init_vgmstream_nxof,
|
||||
init_vgmstream_gwb_gwd,
|
||||
init_vgmstream_s_pack,
|
||||
init_vgmstream_cbx,
|
||||
init_vgmstream_vas_rockstar,
|
||||
init_vgmstream_ea_sbk,
|
||||
init_vgmstream_dsp_asura,
|
||||
init_vgmstream_dsp_asura_ds2,
|
||||
init_vgmstream_dsp_asura_ttss,
|
||||
init_vgmstream_dsp_asura_sfx,
|
||||
init_vgmstream_adp_ongakukan,
|
||||
|
||||
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
|
||||
init_vgmstream_agsc,
|
||||
init_vgmstream_scd_pcm,
|
||||
init_vgmstream_vas_kceo,
|
||||
init_vgmstream_vas_kceo_container,
|
||||
init_vgmstream_ps2_wmus,
|
||||
init_vgmstream_mib_mih,
|
||||
init_vgmstream_mjb_mjh,
|
||||
init_vgmstream_mic_koei,
|
||||
init_vgmstream_seb,
|
||||
init_vgmstream_tgc,
|
||||
init_vgmstream_rage_aud,
|
||||
init_vgmstream_asd_naxat,
|
||||
/* need companion files */
|
||||
init_vgmstream_pos,
|
||||
init_vgmstream_sli_loops,
|
||||
|
||||
/* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */
|
||||
init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */
|
||||
init_vgmstream_dtk, /* semi-raw GC streamed files */
|
||||
init_vgmstream_mpeg, /* semi-raw MP3 */
|
||||
init_vgmstream_btsnd, /* semi-headerless */
|
||||
init_vgmstream_fsb_encrypted,
|
||||
init_vgmstream_nus3bank_encrypted,
|
||||
init_vgmstream_encrypted, /* encrypted stuff */
|
||||
init_vgmstream_raw_rsf, /* raw GC streamed files */
|
||||
init_vgmstream_raw_int, /* .int raw PCM */
|
||||
init_vgmstream_ps_headerless, /* tries to detect a bunch of PS-ADPCM formats */
|
||||
init_vgmstream_raw_snds, /* .snds raw SNDS IMA */
|
||||
init_vgmstream_raw_wavm, /* .wavm raw xbox */
|
||||
init_vgmstream_raw_pcm, /* .raw raw PCM */
|
||||
init_vgmstream_raw_s14_sss, /* .s14/sss raw siren14 */
|
||||
init_vgmstream_exakt_sc, /* .sc raw PCM */
|
||||
init_vgmstream_zwdsp, /* fake format */
|
||||
init_vgmstream_ps2_adm, /* weird non-constant PSX blocks */
|
||||
init_vgmstream_rwsd, /* crap, to be removed */
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
init_vgmstream_ffmpeg, /* may play anything incorrectly, since FFmpeg doesn't check extensions */
|
||||
#endif
|
||||
};
|
||||
|
||||
#define LOCAL_ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0]))
|
||||
static const int init_vgmstream_count = LOCAL_ARRAY_LENGTH(init_vgmstream_functions);
|
||||
|
||||
|
||||
VGMSTREAM* detect_vgmstream_format(STREAMFILE* sf) {
|
||||
if (!sf)
|
||||
return NULL;
|
||||
|
||||
/* try a series of formats, see which works */
|
||||
for (int i = 0; i < init_vgmstream_count; i++) {
|
||||
init_vgmstream_t init_vgmstream_function = init_vgmstream_functions[i];
|
||||
|
||||
/* call init function and see if valid VGMSTREAM was returned */
|
||||
VGMSTREAM* vgmstream = init_vgmstream_function(sf);
|
||||
if (!vgmstream)
|
||||
continue;
|
||||
|
||||
int format_id = i + 1;
|
||||
|
||||
/* validate + setup vgmstream */
|
||||
if (!prepare_vgmstream(vgmstream, sf, format_id)) {
|
||||
/* keep trying if wasn't valid, as simpler formats may return a vgmstream by mistake */
|
||||
close_vgmstream(vgmstream);
|
||||
continue;
|
||||
}
|
||||
|
||||
return vgmstream;
|
||||
}
|
||||
|
||||
/* not supported */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
init_vgmstream_t get_vgmstream_format_init(int format_id) {
|
||||
// ID is expected to be from 1...N, to distinguish from 0 = not set
|
||||
if (format_id <= 0 || format_id > init_vgmstream_count)
|
||||
return NULL;
|
||||
|
||||
return init_vgmstream_functions[format_id - 1];
|
||||
}
|
11
src/vgmstream_init.h
Normal file
11
src/vgmstream_init.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef _DETECTION_H_
|
||||
#define _DETECTION_H_
|
||||
|
||||
#include "meta/meta.h"
|
||||
#include "vgmstream.h"
|
||||
|
||||
bool prepare_vgmstream(VGMSTREAM* vgmstream, STREAMFILE* sf, int format_id);
|
||||
VGMSTREAM* detect_vgmstream_format(STREAMFILE* sf);
|
||||
init_vgmstream_t get_vgmstream_format_init(int format_id);
|
||||
|
||||
#endif
|
@ -486,7 +486,7 @@ typedef enum {
|
||||
meta_XVAG, /* Ratchet & Clank Future: Quest for Booty (PS3) */
|
||||
meta_CPS,
|
||||
meta_MSF,
|
||||
meta_PS3_PAST, /* Bakugan Battle Brawlers (PS3) */
|
||||
meta_SNDP,
|
||||
meta_SGXD, /* Sony: Folklore, Genji, Tokyo Jungle (PS3), Brave Story, Kurohyo (PSP) */
|
||||
meta_WII_RAS, /* Donkey Kong Country Returns (Wii) */
|
||||
meta_SPM,
|
||||
@ -583,7 +583,7 @@ typedef enum {
|
||||
meta_MSB_MSH, /* sfx companion of MIH+MIB */
|
||||
meta_TXTP,
|
||||
meta_SMC_SMH, /* Wangan Midnight (System 246) */
|
||||
meta_PPST, /* PPST [Parappa the Rapper (PSP)] */
|
||||
meta_PPST,
|
||||
meta_SPS_N1,
|
||||
meta_UBI_BAO,
|
||||
meta_DSP_SWITCH_AUDIO, /* Gal Gun 2 (Switch) */
|
||||
|
Loading…
x
Reference in New Issue
Block a user