mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-31 04:13:47 +01:00
Merge pull request #1670 from bnnm/float-etc
- Add HCA key - Fix some .nub again [We Ski (Wii)] - Fix some .srsa+srst KTAC [Shin Hokuto Musou (Android)] - Fix some .msf loops being slightly off - Fix .bcwav IMA waveforms [3DS eShop (3DS)] - Fix some stereo .vag [NBA 06 (PS2)] - Add .skx+.tbl [Syphon Filter: Dark Mirror (PS2/PSP), MLB 14 (Vita)] - cleanup: rename nw_ima to camelot_ima - api: allow float decoders internally
This commit is contained in:
commit
fcbdf1c048
@ -65,7 +65,7 @@ static void print_usage(const char* progname, bool is_help) {
|
|||||||
" -P: output to stdout even if stdout is a terminal\n"
|
" -P: output to stdout even if stdout is a terminal\n"
|
||||||
" -c: loop forever (continuously) to stdout\n"
|
" -c: loop forever (continuously) to stdout\n"
|
||||||
" -L: append a smpl chunk and create a looping wav\n"
|
" -L: append a smpl chunk and create a looping wav\n"
|
||||||
//" -w: allow .wav in original sample format rather than downmixing to PCM16\n"
|
//" -w: allow .wav in original sample format rather than mixing to PCM16\n"
|
||||||
" -V: print version info and supported extensions as JSON\n"
|
" -V: print version info and supported extensions as JSON\n"
|
||||||
" -I: print requested file info as JSON\n"
|
" -I: print requested file info as JSON\n"
|
||||||
" -h: print all commands\n"
|
" -h: print all commands\n"
|
||||||
@ -89,6 +89,7 @@ static void print_usage(const char* progname, bool is_help) {
|
|||||||
" -T: print title (for title testing)\n"
|
" -T: print title (for title testing)\n"
|
||||||
" -D <max channels>: downmix to <max channels> (for plugin downmix testing)\n"
|
" -D <max channels>: downmix to <max channels> (for plugin downmix testing)\n"
|
||||||
" -B <samples> force a sample buffer size (for api testing)\n"
|
" -B <samples> force a sample buffer size (for api testing)\n"
|
||||||
|
//" -W: force .wav to output in float sample format\n"
|
||||||
" -O: decode but don't write to file (for performance testing)\n"
|
" -O: decode but don't write to file (for performance testing)\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ static bool parse_config(cli_config_t* cfg, int argc, char** argv) {
|
|||||||
optind = 1; /* reset getopt's ugly globals (needed in wasm that may call same main() multiple times) */
|
optind = 1; /* reset getopt's ugly globals (needed in wasm that may call same main() multiple times) */
|
||||||
|
|
||||||
/* read config */
|
/* read config */
|
||||||
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:tTk:K:hOvD:S:B:VI")) != -1) {
|
while ((opt = getopt(argc, argv, "o:l:f:d:ipPcmxeLEFrgb2:s:tTk:K:hOvD:S:B:VIwW")) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'o':
|
case 'o':
|
||||||
cfg->outfilename = optarg;
|
cfg->outfilename = optarg;
|
||||||
@ -216,6 +217,9 @@ static bool parse_config(cli_config_t* cfg, int argc, char** argv) {
|
|||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'W':
|
||||||
|
cfg->write_float_wav = true;
|
||||||
|
break;
|
||||||
case '2':
|
case '2':
|
||||||
cfg->stereo_track = atoi(optarg) + 1;
|
cfg->stereo_track = atoi(optarg) + 1;
|
||||||
break;
|
break;
|
||||||
@ -330,29 +334,23 @@ static void apply_config(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
|||||||
|
|
||||||
static bool write_file(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
static bool write_file(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
||||||
FILE* outfile = NULL;
|
FILE* outfile = NULL;
|
||||||
int32_t len_samples;
|
|
||||||
sample_t* buf = NULL;
|
|
||||||
int channels, input_channels;
|
|
||||||
|
|
||||||
|
int channels = vgmstream->channels;
|
||||||
|
|
||||||
channels = vgmstream->channels;
|
int input_channels = vgmstream->channels;
|
||||||
input_channels = vgmstream->channels;
|
|
||||||
|
|
||||||
vgmstream_mixing_enable(vgmstream, 0, &input_channels, &channels);
|
vgmstream_mixing_enable(vgmstream, 0, &input_channels, &channels);
|
||||||
|
sample_t* buf = malloc(cfg->sample_buffer_size * sizeof(sample_t) * input_channels);
|
||||||
/* last init */
|
|
||||||
buf = malloc(cfg->sample_buffer_size * sizeof(sample_t) * input_channels);
|
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
fprintf(stderr, "failed allocating output buffer\n");
|
fprintf(stderr, "failed allocating output buffer\n");
|
||||||
goto fail;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* simulate seek */
|
/* simulate seek */
|
||||||
len_samples = vgmstream_get_samples(vgmstream);
|
int32_t play_samples = vgmstream_get_samples(vgmstream);
|
||||||
if (cfg->seek_samples2 >= 0)
|
if (cfg->seek_samples2 >= 0)
|
||||||
len_samples -= cfg->seek_samples2;
|
play_samples -= cfg->seek_samples2;
|
||||||
else if (cfg->seek_samples1 >= 0)
|
else if (cfg->seek_samples1 >= 0)
|
||||||
len_samples -= cfg->seek_samples1;
|
play_samples -= cfg->seek_samples1;
|
||||||
|
|
||||||
if (cfg->seek_samples1 >= 0)
|
if (cfg->seek_samples1 >= 0)
|
||||||
seek_vgmstream(vgmstream, cfg->seek_samples1);
|
seek_vgmstream(vgmstream, cfg->seek_samples1);
|
||||||
@ -385,7 +383,7 @@ static bool write_file(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
|||||||
size_t bytes_done;
|
size_t bytes_done;
|
||||||
|
|
||||||
wav_header_t wav = {
|
wav_header_t wav = {
|
||||||
.sample_count = len_samples,
|
.sample_count = play_samples,
|
||||||
.sample_rate = vgmstream->sample_rate,
|
.sample_rate = vgmstream->sample_rate,
|
||||||
.channels = channels,
|
.channels = channels,
|
||||||
.write_smpl_chunk = cfg->write_lwav,
|
.write_smpl_chunk = cfg->write_lwav,
|
||||||
@ -394,31 +392,39 @@ static bool write_file(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bytes_done = wav_make_header(wav_buf, 0x100, &wav);
|
bytes_done = wav_make_header(wav_buf, 0x100, &wav);
|
||||||
|
if (bytes_done == 0) goto fail;
|
||||||
fwrite(wav_buf, sizeof(uint8_t), bytes_done, outfile);
|
fwrite(wav_buf, sizeof(uint8_t), bytes_done, outfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* decode forever */
|
/* decode forever */// TODO improve logic of play forever + normal play
|
||||||
while (cfg->play_forever && !cfg->decode_only) {
|
while (cfg->play_forever && !cfg->decode_only) {
|
||||||
int to_get = cfg->sample_buffer_size;
|
int to_get = cfg->sample_buffer_size;
|
||||||
|
|
||||||
render_vgmstream(buf, to_get, vgmstream);
|
render_vgmstream(buf, to_get, vgmstream);
|
||||||
|
|
||||||
wav_swap_samples_le(buf, channels * to_get, 0);
|
int buf_bytes = to_get * channels * sizeof(sample_t);
|
||||||
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
|
int buf_samples = to_get;
|
||||||
|
int sample_size = 0;
|
||||||
|
|
||||||
|
wav_swap_samples_le(buf, channels * buf_samples, sample_size);
|
||||||
|
fwrite(buf, sizeof(uint8_t), buf_bytes, outfile);
|
||||||
/* should write infinitely until program kill */
|
/* should write infinitely until program kill */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* decode */
|
/* decode */
|
||||||
for (int i = 0; i < len_samples; i += cfg->sample_buffer_size) {
|
for (int i = 0; i < play_samples; i += cfg->sample_buffer_size) {
|
||||||
int to_get = cfg->sample_buffer_size;
|
int to_get = cfg->sample_buffer_size;
|
||||||
if (i + cfg->sample_buffer_size > len_samples)
|
if (i + cfg->sample_buffer_size > play_samples)
|
||||||
to_get = len_samples - i;
|
to_get = play_samples - i;
|
||||||
|
|
||||||
render_vgmstream(buf, to_get, vgmstream);
|
render_vgmstream(buf, to_get, vgmstream);
|
||||||
|
|
||||||
|
int buf_bytes = to_get * channels * sizeof(sample_t);
|
||||||
|
int buf_samples = to_get;
|
||||||
|
int sample_size = 0;
|
||||||
|
|
||||||
if (!cfg->decode_only) {
|
if (!cfg->decode_only) {
|
||||||
wav_swap_samples_le(buf, channels * to_get, 0);
|
wav_swap_samples_le(buf, channels * buf_samples, sample_size);
|
||||||
fwrite(buf, sizeof(sample_t), to_get * channels, outfile);
|
fwrite(buf, sizeof(uint8_t), buf_bytes, outfile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,7 +496,7 @@ fail:
|
|||||||
static bool convert_file(cli_config_t* cfg) {
|
static bool convert_file(cli_config_t* cfg) {
|
||||||
VGMSTREAM* vgmstream = NULL;
|
VGMSTREAM* vgmstream = NULL;
|
||||||
char outfilename_temp[CLI_PATH_LIMIT];
|
char outfilename_temp[CLI_PATH_LIMIT];
|
||||||
int32_t len_samples;
|
int32_t play_samples;
|
||||||
|
|
||||||
|
|
||||||
/* for plugin testing */
|
/* for plugin testing */
|
||||||
@ -510,8 +516,8 @@ static bool convert_file(cli_config_t* cfg) {
|
|||||||
|
|
||||||
|
|
||||||
/* get final play config */
|
/* get final play config */
|
||||||
len_samples = vgmstream_get_samples(vgmstream);
|
play_samples = vgmstream_get_samples(vgmstream);
|
||||||
if (len_samples <= 0) {
|
if (play_samples <= 0) {
|
||||||
fprintf(stderr, "wrong time config\n");
|
fprintf(stderr, "wrong time config\n");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
@ -525,7 +531,7 @@ static bool convert_file(cli_config_t* cfg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* would be ignored by seek code though (allowed for seek_samples2 to test this) */
|
/* would be ignored by seek code though (allowed for seek_samples2 to test this) */
|
||||||
if (cfg->seek_samples1 < -1 || cfg->seek_samples1 >= len_samples) {
|
if (cfg->seek_samples1 < -1 || cfg->seek_samples1 >= play_samples) {
|
||||||
fprintf(stderr, "wrong seek config\n");
|
fprintf(stderr, "wrong seek config\n");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ typedef struct {
|
|||||||
// wav config
|
// wav config
|
||||||
bool write_lwav;
|
bool write_lwav;
|
||||||
bool write_original_wav;
|
bool write_original_wav;
|
||||||
|
bool write_float_wav;
|
||||||
|
|
||||||
// print flags
|
// print flags
|
||||||
bool print_metaonly;
|
bool print_metaonly;
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "vgmstream_cli.h"
|
#include "vgmstream_cli.h"
|
||||||
|
#include "vjson.h"
|
||||||
#include "../src/api.h"
|
#include "../src/api.h"
|
||||||
#include "../src/vgmstream.h"
|
#include "../src/vgmstream.h"
|
||||||
|
|
||||||
#include "vjson.h"
|
|
||||||
|
|
||||||
|
|
||||||
static void clean_filename(char* dst, int clean_paths) {
|
static void clean_filename(char* dst, int clean_paths) {
|
||||||
for (int i = 0; i < strlen(dst); i++) {
|
for (int i = 0; i < strlen(dst); i++) {
|
||||||
char c = dst[i];
|
char c = dst[i];
|
||||||
@ -79,7 +76,7 @@ void replace_filename(char* dst, size_t dstsize, cli_config_t* cfg, VGMSTREAM* v
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* not recognized */
|
/* not recognized */
|
||||||
// TO-DO should move buf or swap "?" with "_"? may happen with non-ascii on Windows; for now break to avoid infinite loops
|
// TO-DO: should move buf or swap "?" with "_"? may happen with non-ascii on Windows; for now break to avoid infinite loops
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,8 +98,8 @@ void replace_filename(char* dst, size_t dstsize, cli_config_t* cfg, VGMSTREAM* v
|
|||||||
|
|
||||||
void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
void print_info(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
||||||
int channels = vgmstream->channels;
|
int channels = vgmstream->channels;
|
||||||
int64_t num_samples = vgmstream->num_samples;
|
|
||||||
bool loop_flag = vgmstream->loop_flag;
|
bool loop_flag = vgmstream->loop_flag;
|
||||||
|
int64_t num_samples = vgmstream->num_samples;
|
||||||
int64_t loop_start = vgmstream->loop_start_sample;
|
int64_t loop_start = vgmstream->loop_start_sample;
|
||||||
int64_t loop_end = vgmstream->loop_start_sample;
|
int64_t loop_end = vgmstream->loop_start_sample;
|
||||||
|
|
||||||
@ -180,9 +177,9 @@ void print_title(VGMSTREAM* vgmstream, cli_config_t* cfg) {
|
|||||||
if (!cfg->print_title)
|
if (!cfg->print_title)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
tcfg.force_title = 0;
|
tcfg.force_title = false;
|
||||||
tcfg.subsong_range = 0;
|
tcfg.subsong_range = false;
|
||||||
tcfg.remove_extension = 0;
|
tcfg.remove_extension = true;
|
||||||
|
|
||||||
vgmstream_get_title(title, sizeof(title), cfg->infilename, vgmstream, &tcfg);
|
vgmstream_get_title(title, sizeof(title), cfg->infilename, vgmstream, &tcfg);
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
|||||||
- *bcwav*: `.bcwav .adpcm .bms .sfx .str .zic`
|
- *bcwav*: `.bcwav .adpcm .bms .sfx .str .zic`
|
||||||
- *brwar*: `.rwar`
|
- *brwar*: `.rwar`
|
||||||
- Subfiles: *brwav*
|
- Subfiles: *brwav*
|
||||||
- Codecs: PCM8 PCM16BE PCM16LE NGC_DSP NW_IMA
|
- Codecs: PCM8 PCM16BE PCM16LE NGC_DSP IMA
|
||||||
- **nds_strm.c**
|
- **nds_strm.c**
|
||||||
- Nintendo STRM header [*STRM*]
|
- Nintendo STRM header [*STRM*]
|
||||||
- *nds_strm*: `.strm`
|
- *nds_strm*: `.strm`
|
||||||
@ -918,7 +918,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
|||||||
- **bcstm.c**
|
- **bcstm.c**
|
||||||
- Nintendo CSTM Header [*CSTM*]
|
- Nintendo CSTM Header [*CSTM*]
|
||||||
- *bcstm*: `.bcstm`
|
- *bcstm*: `.bcstm`
|
||||||
- Codecs: PCM8 PCM16LE NGC_DSP NW_IMA
|
- Codecs: PCM8 PCM16LE NGC_DSP CAMELOT_IMA
|
||||||
- **g1l.c**
|
- **g1l.c**
|
||||||
- Koei Tecmo WiiBGM Header [*KT_WIIBGM*]
|
- Koei Tecmo WiiBGM Header [*KT_WIIBGM*]
|
||||||
- *kt_g1l*: `.g1l`
|
- *kt_g1l*: `.g1l`
|
||||||
@ -1846,6 +1846,10 @@ different internally (encrypted, different versions, etc) and not always can be
|
|||||||
- Codemasters i3DS header [*I3DS*]
|
- Codemasters i3DS header [*I3DS*]
|
||||||
- *i3ds*: `.3ds`
|
- *i3ds*: `.3ds`
|
||||||
- Codecs: NGC_DSP
|
- Codecs: NGC_DSP
|
||||||
|
- **skex.c**
|
||||||
|
- (container)
|
||||||
|
- *skex*: `.skx + .tbl`
|
||||||
|
- Subfiles: *vag riff vpk*
|
||||||
- **agsc.c**
|
- **agsc.c**
|
||||||
- Retro Studios AGSC header [*AGSC*]
|
- Retro Studios AGSC header [*AGSC*]
|
||||||
- *agsc*: `.agsc`
|
- *agsc*: `.agsc`
|
||||||
|
@ -14,6 +14,9 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
#include "foo_vgmstream.h"
|
#include "foo_vgmstream.h"
|
||||||
|
|
||||||
|
/* Value can be adjusted freely but 8k is a good enough compromise. */
|
||||||
|
#define FOO_STREAMFILE_DEFAULT_BUFFER_SIZE 0x8000
|
||||||
|
|
||||||
|
|
||||||
/* a STREAMFILE that operates via foobar's file service using a buffer */
|
/* a STREAMFILE that operates via foobar's file service using a buffer */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -30,8 +33,8 @@ typedef struct {
|
|||||||
int archpath_end; /* where the last \ ends before archive name */
|
int archpath_end; /* where the last \ ends before archive name */
|
||||||
int archfile_end; /* where the last | ends before file name */
|
int archfile_end; /* where the last | ends before file name */
|
||||||
|
|
||||||
offv_t offset; /* last read offset (info) */
|
int64_t offset; /* last read offset (info) */
|
||||||
offv_t buf_offset; /* current buffer data start */
|
int64_t buf_offset; /* current buffer data start */
|
||||||
uint8_t* buf; /* data buffer */
|
uint8_t* buf; /* data buffer */
|
||||||
size_t buf_size; /* max buffer size */
|
size_t buf_size; /* max buffer size */
|
||||||
size_t valid_size; /* current buffer size */
|
size_t valid_size; /* current buffer size */
|
||||||
@ -41,35 +44,36 @@ typedef struct {
|
|||||||
static STREAMFILE* open_foo_streamfile_buffer(const char* const filename, size_t buf_size, abort_callback* p_abort, t_filestats* stats);
|
static STREAMFILE* open_foo_streamfile_buffer(const char* const filename, size_t buf_size, abort_callback* p_abort, t_filestats* stats);
|
||||||
static STREAMFILE* open_foo_streamfile_buffer_by_file(service_ptr_t<file> m_file, bool m_file_opened, const char* const filename, size_t buf_size, abort_callback* p_abort);
|
static STREAMFILE* open_foo_streamfile_buffer_by_file(service_ptr_t<file> m_file, bool m_file_opened, const char* const filename, size_t buf_size, abort_callback* p_abort);
|
||||||
|
|
||||||
static size_t foo_read(FOO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
static size_t foo_read(FOO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t dst_size) {
|
||||||
size_t read_total = 0;
|
size_t read_total = 0;
|
||||||
|
if (!sf || !sf->m_file_opened || !dst || dst_size <= 0 || offset < 0)
|
||||||
if (!sf || !sf->m_file_opened || !dst || length <= 0 || offset < 0)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
sf->offset = offset; /* current offset */
|
||||||
|
|
||||||
/* is the part of the requested length in the buffer? */
|
/* is the part of the requested length in the buffer? */
|
||||||
if (offset >= sf->buf_offset && offset < sf->buf_offset + sf->valid_size) {
|
if (sf->offset >= sf->buf_offset && sf->offset < sf->buf_offset + sf->valid_size) {
|
||||||
size_t buf_limit;
|
size_t buf_limit;
|
||||||
int buf_into = (int)(offset - sf->buf_offset);
|
int buf_into = (int)(sf->offset - sf->buf_offset);
|
||||||
|
|
||||||
buf_limit = sf->valid_size - buf_into;
|
buf_limit = sf->valid_size - buf_into;
|
||||||
if (buf_limit > length)
|
if (buf_limit > dst_size)
|
||||||
buf_limit = length;
|
buf_limit = dst_size;
|
||||||
|
|
||||||
memcpy(dst, sf->buf + buf_into, buf_limit);
|
memcpy(dst, sf->buf + buf_into, buf_limit);
|
||||||
read_total += buf_limit;
|
read_total += buf_limit;
|
||||||
length -= buf_limit;
|
dst_size -= buf_limit;
|
||||||
offset += buf_limit;
|
sf->offset += buf_limit;
|
||||||
dst += buf_limit;
|
dst += buf_limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* read the rest of the requested length */
|
/* read the rest of the requested length */
|
||||||
while (length > 0) {
|
while (dst_size > 0) {
|
||||||
size_t buf_limit;
|
size_t buf_limit;
|
||||||
|
|
||||||
/* ignore requests at EOF */
|
/* ignore requests at EOF */
|
||||||
if (offset >= sf->file_size) {
|
if (sf->offset >= sf->file_size) {
|
||||||
//offset = sf->file_size; /* seems fseek doesn't clamp offset */
|
//offset = sf->file_size; /* seems fseek doesn't clamp offset */
|
||||||
//VGM_ASSERT_ONCE(offset > sf->file_size, "STDIO: reading over file_size 0x%x @ 0x%lx + 0x%x\n", sf->file_size, offset, length);
|
//VGM_ASSERT_ONCE(offset > sf->file_size, "STDIO: reading over file_size 0x%x @ 0x%lx + 0x%x\n", sf->file_size, offset, length);
|
||||||
break;
|
break;
|
||||||
@ -77,42 +81,41 @@ static size_t foo_read(FOO_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t l
|
|||||||
|
|
||||||
/* position to new offset */
|
/* position to new offset */
|
||||||
try {
|
try {
|
||||||
sf->m_file->seek(offset, *sf->p_abort);
|
sf->m_file->seek(sf->offset, *sf->p_abort);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
break; /* this shouldn't happen in our code */
|
break; /* this shouldn't happen in our code */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fill the buffer (offset now is beyond buf_offset) */
|
/* fill the buffer (offset now is beyond buf_offset) */
|
||||||
try {
|
try {
|
||||||
sf->buf_offset = offset;
|
sf->buf_offset = sf->offset;
|
||||||
sf->valid_size = sf->m_file->read(sf->buf, sf->buf_size, *sf->p_abort);
|
sf->valid_size = sf->m_file->read(sf->buf, sf->buf_size, *sf->p_abort);
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
break; /* improbable? */
|
break; /* improbable? */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* decide how much must be read this time */
|
/* decide how much must be read this time */
|
||||||
if (length > sf->buf_size)
|
if (dst_size > sf->buf_size)
|
||||||
buf_limit = sf->buf_size;
|
buf_limit = sf->buf_size;
|
||||||
else
|
else
|
||||||
buf_limit = length;
|
buf_limit = dst_size;
|
||||||
|
|
||||||
/* give up on partial reads (EOF) */
|
/* give up on partial reads (EOF) */
|
||||||
if (sf->valid_size < buf_limit) {
|
if (sf->valid_size < buf_limit) {
|
||||||
memcpy(dst, sf->buf, sf->valid_size);
|
memcpy(dst, sf->buf, sf->valid_size);
|
||||||
offset += sf->valid_size;
|
sf->offset += sf->valid_size;
|
||||||
read_total += sf->valid_size;
|
read_total += sf->valid_size;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* use the new buffer */
|
/* use the new buffer */
|
||||||
memcpy(dst, sf->buf, buf_limit);
|
memcpy(dst, sf->buf, buf_limit);
|
||||||
offset += buf_limit;
|
sf->offset += buf_limit;
|
||||||
read_total += buf_limit;
|
read_total += buf_limit;
|
||||||
length -= buf_limit;
|
dst_size -= buf_limit;
|
||||||
dst += buf_limit;
|
dst += buf_limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
sf->offset = offset; /* last fread offset */
|
|
||||||
return read_total;
|
return read_total;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,9 +154,8 @@ static void foo_close(FOO_STREAMFILE* sf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static STREAMFILE* foo_open(FOO_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
static STREAMFILE* foo_open(FOO_STREAMFILE* sf, const char* const filename, size_t buf_size) {
|
||||||
service_ptr_t<file> m_file;
|
|
||||||
|
|
||||||
if (!filename)
|
if (!sf || !filename)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
// vgmstream may need to open "files based on another" (like a changing extension) and "files in the same subdir" (like .txth)
|
// vgmstream may need to open "files based on another" (like a changing extension) and "files in the same subdir" (like .txth)
|
||||||
@ -166,29 +168,32 @@ static STREAMFILE* foo_open(FOO_STREAMFILE* sf, const char* const filename, size
|
|||||||
// > opens: "unpack://zip|23|file://C:\file.zip|.txth
|
// > opens: "unpack://zip|23|file://C:\file.zip|.txth
|
||||||
// (assumes archives won't need to open files outside archives, and goes before filedup trick)
|
// (assumes archives won't need to open files outside archives, and goes before filedup trick)
|
||||||
if (sf->archname) {
|
if (sf->archname) {
|
||||||
char finalname[PATH_LIMIT];
|
char finalname[FOO_PATH_LIMIT];
|
||||||
const char* dirsep = NULL;
|
const char* filepart = NULL;
|
||||||
|
|
||||||
// newly open files should be "(current-path)\newfile" or "(current-path)\folder\newfile", so we need to make
|
// newly open files should be "(current-path)\newfile" or "(current-path)\folder\newfile", so we need to make
|
||||||
// (archive-path = current-path)\(rest = newfile plus new folders)
|
// (archive-path = current-path)\(rest = newfile plus new folders)
|
||||||
int filename_len = strlen(filename);
|
|
||||||
|
|
||||||
|
int filename_len = strlen(filename);
|
||||||
if (filename_len > sf->archpath_end) {
|
if (filename_len > sf->archpath_end) {
|
||||||
dirsep = &filename[sf->archpath_end];
|
filepart = &filename[sf->archpath_end];
|
||||||
} else {
|
} else {
|
||||||
dirsep = strrchr(filename, '\\'); // vgmstream shouldn't remove paths though
|
filepart = strrchr(filename, '\\'); // vgmstream shouldn't remove paths though
|
||||||
if (!dirsep)
|
if (!filepart)
|
||||||
dirsep = filename;
|
filepart = filename;
|
||||||
else
|
else
|
||||||
dirsep += 1;
|
filepart += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO improve str ops
|
//TODO improve str ops
|
||||||
memcpy(finalname, sf->archname, sf->archfile_end); //copy current path+archive
|
|
||||||
finalname[sf->archfile_end] = '\0';
|
|
||||||
concatn(sizeof(finalname), finalname, dirsep); //paste possible extra dirs and filename
|
|
||||||
|
|
||||||
// subfolders inside archives use "/" (path\archive.ext|subfolder/file.ext)
|
// copy current path+archive ("unpack://zip|23|file://C:\file.zip|")
|
||||||
|
memcpy(finalname, sf->archname, sf->archfile_end);
|
||||||
|
finalname[sf->archfile_end] = '\0';
|
||||||
|
// concat possible extra dirs and filename ("unpack://zip|23|file://C:\file.zip|" + "folder/bgm01.vag")
|
||||||
|
concatn(sizeof(finalname), finalname, filepart);
|
||||||
|
|
||||||
|
// normalize subfolders inside archives to use "/" (path\archive.ext|subfolder/file.ext)
|
||||||
for (int i = sf->archfile_end; i < sizeof(finalname); i++) {
|
for (int i = sf->archfile_end; i < sizeof(finalname); i++) {
|
||||||
if (finalname[i] == '\0')
|
if (finalname[i] == '\0')
|
||||||
break;
|
break;
|
||||||
@ -202,15 +207,13 @@ static STREAMFILE* foo_open(FOO_STREAMFILE* sf, const char* const filename, size
|
|||||||
|
|
||||||
// if same name, duplicate the file pointer we already have open
|
// if same name, duplicate the file pointer we already have open
|
||||||
if (sf->m_file_opened && !strcmp(sf->name, filename)) {
|
if (sf->m_file_opened && !strcmp(sf->name, filename)) {
|
||||||
m_file = sf->m_file; //copy?
|
service_ptr_t<file> m_file = sf->m_file; //copy?
|
||||||
{
|
|
||||||
STREAMFILE* new_sf = open_foo_streamfile_buffer_by_file(m_file, sf->m_file_opened, filename, buf_size, sf->p_abort);
|
STREAMFILE* new_sf = open_foo_streamfile_buffer_by_file(m_file, sf->m_file_opened, filename, buf_size, sf->p_abort);
|
||||||
if (new_sf) {
|
if (new_sf) {
|
||||||
return new_sf;
|
return new_sf;
|
||||||
}
|
}
|
||||||
// failure, close it and try the default path (which will probably fail a second time)
|
// failure, close it and try the default path (which will probably fail a second time)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// a normal open, open a new file
|
// a normal open, open a new file
|
||||||
return open_foo_streamfile_buffer(filename, buf_size, sf->p_abort, NULL);
|
return open_foo_streamfile_buffer(filename, buf_size, sf->p_abort, NULL);
|
||||||
@ -324,5 +327,5 @@ static STREAMFILE* open_foo_streamfile_buffer(const char* const filename, size_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
STREAMFILE* open_foo_streamfile(const char* const filename, abort_callback* p_abort, t_filestats* stats) {
|
STREAMFILE* open_foo_streamfile(const char* const filename, abort_callback* p_abort, t_filestats* stats) {
|
||||||
return open_foo_streamfile_buffer(filename, STREAMFILE_DEFAULT_BUFFER_SIZE, p_abort, stats);
|
return open_foo_streamfile_buffer(filename, FOO_STREAMFILE_DEFAULT_BUFFER_SIZE, p_abort, stats);
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,6 @@
|
|||||||
|
|
||||||
#include <foobar2000/SDK/foobar2000.h>
|
#include <foobar2000/SDK/foobar2000.h>
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include "../src/vgmstream.h"
|
|
||||||
#include "../src/api.h"
|
|
||||||
}
|
|
||||||
#include "foo_vgmstream.h"
|
#include "foo_vgmstream.h"
|
||||||
#include "foo_filetypes.h"
|
#include "foo_filetypes.h"
|
||||||
|
|
||||||
@ -50,7 +46,7 @@ input_vgmstream::input_vgmstream() {
|
|||||||
output_channels = 0;
|
output_channels = 0;
|
||||||
|
|
||||||
decoding = false;
|
decoding = false;
|
||||||
paused = 0;
|
|
||||||
decode_pos_ms = 0;
|
decode_pos_ms = 0;
|
||||||
decode_pos_samples = 0;
|
decode_pos_samples = 0;
|
||||||
length_samples = 0;
|
length_samples = 0;
|
||||||
@ -199,6 +195,7 @@ void input_vgmstream::put_into_tagfile(file_info& p_info, abort_callback& p_abor
|
|||||||
strcpy(tagfile_path, tagfile_name);
|
strcpy(tagfile_path, tagfile_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
STREAMFILE* sf_tags = open_foo_streamfile(tagfile_path, &p_abort, NULL);
|
STREAMFILE* sf_tags = open_foo_streamfile(tagfile_path, &p_abort, NULL);
|
||||||
if (sf_tags == NULL)
|
if (sf_tags == NULL)
|
||||||
return;
|
return;
|
||||||
@ -445,7 +442,6 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
|
|||||||
|
|
||||||
decode_pos_ms = 0;
|
decode_pos_ms = 0;
|
||||||
decode_pos_samples = 0;
|
decode_pos_samples = 0;
|
||||||
paused = 0;
|
|
||||||
length_samples = vgmstream_get_samples(vgmstream);
|
length_samples = vgmstream_get_samples(vgmstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "../src/vgmstream.h"
|
#include "../src/vgmstream.h"
|
||||||
|
#include "../src/api.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -83,7 +84,7 @@ class input_vgmstream : public input_stubs {
|
|||||||
int output_channels;
|
int output_channels;
|
||||||
|
|
||||||
bool decoding;
|
bool decoding;
|
||||||
int paused;
|
|
||||||
int decode_pos_ms;
|
int decode_pos_ms;
|
||||||
int decode_pos_samples;
|
int decode_pos_samples;
|
||||||
int length_samples;
|
int length_samples;
|
||||||
@ -124,5 +125,4 @@ class input_vgmstream : public input_stubs {
|
|||||||
/* foo_streamfile.cpp */
|
/* foo_streamfile.cpp */
|
||||||
STREAMFILE* open_foo_streamfile(const char* const filename, abort_callback* p_abort, t_filestats* stats);
|
STREAMFILE* open_foo_streamfile(const char* const filename, abort_callback* p_abort, t_filestats* stats);
|
||||||
|
|
||||||
|
#endif
|
||||||
#endif /*_FOO_VGMSTREAM_*/
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "api_internal.h"
|
#include "api_internal.h"
|
||||||
#include "mixing.h"
|
#include "mixing.h"
|
||||||
|
#include "render.h"
|
||||||
|
|
||||||
#if LIBVGMSTREAM_ENABLE
|
#if LIBVGMSTREAM_ENABLE
|
||||||
|
|
||||||
@ -17,24 +18,24 @@ static bool reset_buf(libvgmstream_priv_t* priv) {
|
|||||||
int input_channels = 0, output_channels = 0;
|
int input_channels = 0, output_channels = 0;
|
||||||
vgmstream_mixing_enable(priv->vgmstream, 0, &input_channels, &output_channels); //query
|
vgmstream_mixing_enable(priv->vgmstream, 0, &input_channels, &output_channels); //query
|
||||||
|
|
||||||
int min_channels = input_channels;
|
int max_channels = input_channels;
|
||||||
if (min_channels < output_channels)
|
if (max_channels < output_channels)
|
||||||
min_channels = output_channels;
|
max_channels = output_channels;
|
||||||
|
|
||||||
sfmt_t input_sfmt = mixing_get_input_sample_type(priv->vgmstream);
|
sfmt_t input_sfmt = mixing_get_input_sample_type(priv->vgmstream);
|
||||||
sfmt_t output_sfmt = mixing_get_output_sample_type(priv->vgmstream);
|
sfmt_t output_sfmt = mixing_get_output_sample_type(priv->vgmstream);
|
||||||
int input_sample_size = sfmt_get_sample_size(input_sfmt);
|
int input_sample_size = sfmt_get_sample_size(input_sfmt);
|
||||||
int output_sample_size = sfmt_get_sample_size(output_sfmt);
|
int output_sample_size = sfmt_get_sample_size(output_sfmt);
|
||||||
|
|
||||||
int min_sample_size = input_sample_size;
|
int max_sample_size = input_sample_size;
|
||||||
if (min_sample_size < output_sample_size)
|
if (max_sample_size < output_sample_size)
|
||||||
min_sample_size = output_sample_size;
|
max_sample_size = output_sample_size;
|
||||||
|
|
||||||
priv->buf.max_samples = INTERNAL_BUF_SAMPLES;
|
priv->buf.max_samples = INTERNAL_BUF_SAMPLES;
|
||||||
priv->buf.sample_size = output_sample_size;
|
priv->buf.sample_size = output_sample_size;
|
||||||
priv->buf.channels = output_channels;
|
priv->buf.channels = output_channels;
|
||||||
|
|
||||||
int max_bytes = priv->buf.max_samples * min_sample_size * min_channels;
|
int max_bytes = priv->buf.max_samples * max_sample_size * max_channels;
|
||||||
priv->buf.data = malloc(max_bytes);
|
priv->buf.data = malloc(max_bytes);
|
||||||
if (!priv->buf.data) return false;
|
if (!priv->buf.data) return false;
|
||||||
|
|
||||||
@ -79,7 +80,11 @@ LIBVGMSTREAM_API int libvgmstream_render(libvgmstream_t* lib) {
|
|||||||
if (!priv->pos.play_forever && to_get + priv->pos.current > priv->pos.play_samples)
|
if (!priv->pos.play_forever && to_get + priv->pos.current > priv->pos.play_samples)
|
||||||
to_get = priv->pos.play_samples - priv->pos.current;
|
to_get = priv->pos.play_samples - priv->pos.current;
|
||||||
|
|
||||||
int decoded = render_vgmstream(priv->buf.data, to_get, priv->vgmstream);
|
sbuf_t ssrc;
|
||||||
|
sfmt_t sfmt = mixing_get_input_sample_type(priv->vgmstream);
|
||||||
|
sbuf_init(&ssrc, sfmt, priv->buf.data, to_get, priv->vgmstream->channels);
|
||||||
|
|
||||||
|
int decoded = render_main(&ssrc, priv->vgmstream);
|
||||||
update_buf(priv, decoded);
|
update_buf(priv, decoded);
|
||||||
update_decoder_info(priv, decoded);
|
update_decoder_info(priv, decoded);
|
||||||
|
|
||||||
|
@ -469,7 +469,7 @@ int decode_get_samples_per_frame(VGMSTREAM* vgmstream) {
|
|||||||
case coding_PCM4_U:
|
case coding_PCM4_U:
|
||||||
case coding_IMA_int:
|
case coding_IMA_int:
|
||||||
case coding_DVI_IMA_int:
|
case coding_DVI_IMA_int:
|
||||||
case coding_NW_IMA:
|
case coding_CAMELOT_IMA:
|
||||||
case coding_WV6_IMA:
|
case coding_WV6_IMA:
|
||||||
case coding_HV_IMA:
|
case coding_HV_IMA:
|
||||||
case coding_FFTA2_IMA:
|
case coding_FFTA2_IMA:
|
||||||
@ -685,7 +685,7 @@ int decode_get_frame_size(VGMSTREAM* vgmstream) {
|
|||||||
case coding_IMA_int:
|
case coding_IMA_int:
|
||||||
case coding_DVI_IMA:
|
case coding_DVI_IMA:
|
||||||
case coding_DVI_IMA_int:
|
case coding_DVI_IMA_int:
|
||||||
case coding_NW_IMA:
|
case coding_CAMELOT_IMA:
|
||||||
case coding_WV6_IMA:
|
case coding_WV6_IMA:
|
||||||
case coding_HV_IMA:
|
case coding_HV_IMA:
|
||||||
case coding_FFTA2_IMA:
|
case coding_FFTA2_IMA:
|
||||||
@ -935,10 +935,12 @@ decode_fail:
|
|||||||
* buffer already, and we have samples_to_do consecutive samples ahead of us (won't call
|
* buffer already, and we have samples_to_do consecutive samples ahead of us (won't call
|
||||||
* more than one frame if configured above to do so).
|
* more than one frame if configured above to do so).
|
||||||
* Called by layouts since they handle samples written/to_do */
|
* Called by layouts since they handle samples written/to_do */
|
||||||
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_do, sample_t* buffer) {
|
void decode_vgmstream(sbuf_t* sdst, VGMSTREAM* vgmstream, int samples_to_do) {
|
||||||
int ch;
|
int ch;
|
||||||
|
|
||||||
buffer += samples_filled * vgmstream->channels; /* passed externally to simplify I guess */
|
//TODO: this cast isn't correct for float sbuf-decoders but shouldn't be used/matter (for buffer+ch below)
|
||||||
|
int16_t* buffer = sdst->buf;
|
||||||
|
buffer += sdst->filled * vgmstream->channels; // passed externally to decoders to simplify I guess
|
||||||
//samples_to_do -= samples_filled; /* pre-adjusted */
|
//samples_to_do -= samples_filled; /* pre-adjusted */
|
||||||
|
|
||||||
switch (vgmstream->coding_type) {
|
switch (vgmstream->coding_type) {
|
||||||
@ -1334,9 +1336,9 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_d
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case coding_NW_IMA:
|
case coding_CAMELOT_IMA:
|
||||||
for (ch = 0; ch < vgmstream->channels; ch++) {
|
for (ch = 0; ch < vgmstream->channels; ch++) {
|
||||||
decode_nw_ima(&vgmstream->ch[ch], buffer+ch,
|
decode_camelot_ima(&vgmstream->ch[ch], buffer+ch,
|
||||||
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
|
vgmstream->channels, vgmstream->samples_into_block, samples_to_do);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -1673,14 +1675,10 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_d
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
sbuf_t sbuf_tmp = {0};
|
sbuf_t stmp = *sdst;
|
||||||
sbuf_t* sbuf = &sbuf_tmp;
|
stmp.samples = stmp.filled + samples_to_do; //TODO improve
|
||||||
|
|
||||||
// buffers already adjusted
|
decode_frames(&stmp, vgmstream);
|
||||||
sbuf_init_s16(sbuf, buffer, /*samples_filled +*/ samples_to_do, vgmstream->channels);
|
|
||||||
sbuf->filled = 0; // samples_filled;
|
|
||||||
|
|
||||||
decode_frames(sbuf, vgmstream);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ void decode_reset(VGMSTREAM* vgmstream);
|
|||||||
|
|
||||||
/* Decode samples into the buffer. Assume that we have written samples_filled into the
|
/* Decode samples into the buffer. Assume that we have written samples_filled into the
|
||||||
* buffer already, and we have samples_to_do consecutive samples ahead of us. */
|
* buffer already, and we have samples_to_do consecutive samples ahead of us. */
|
||||||
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_do, sample_t* buffer);
|
void decode_vgmstream(sbuf_t* sdst, VGMSTREAM* vgmstream, int samples_to_do);
|
||||||
|
|
||||||
/* Detect loop start and save values, or detect loop end and restore (loop back). Returns true if loop was done. */
|
/* Detect loop start and save values, or detect loop end and restore (loop back). Returns true if loop was done. */
|
||||||
bool decode_do_loop(VGMSTREAM* vgmstream);
|
bool decode_do_loop(VGMSTREAM* vgmstream);
|
||||||
|
@ -171,6 +171,21 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
|||||||
concatn(length,desc,temp);
|
concatn(length,desc,temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sfmt_t sfmt = mixing_get_input_sample_type(vgmstream);
|
||||||
|
if (sfmt != SFMT_S16) {
|
||||||
|
const char* sfmt_desc;
|
||||||
|
switch(sfmt) {
|
||||||
|
case SFMT_FLT: sfmt_desc = "float"; break;
|
||||||
|
case SFMT_F32: sfmt_desc = "float32"; break;
|
||||||
|
case SFMT_S16: sfmt_desc = "pcm16"; break;
|
||||||
|
default: sfmt_desc = "???";
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(temp,TEMPSIZE, "sample type: %s\n", sfmt_desc);
|
||||||
|
concatn(length,desc,temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (vgmstream->config_enabled) {
|
if (vgmstream->config_enabled) {
|
||||||
int32_t samples = vgmstream->pstate.play_duration;
|
int32_t samples = vgmstream->pstate.play_duration;
|
||||||
|
|
||||||
@ -178,6 +193,7 @@ void describe_vgmstream(VGMSTREAM* vgmstream, char* desc, int length) {
|
|||||||
snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss);
|
snprintf(temp,TEMPSIZE, "play duration: %d samples (%1.0f:%06.3f seconds)\n", samples, time_mm, time_ss);
|
||||||
concatn(length,desc,temp);
|
concatn(length,desc,temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void describe_vgmstream_info(VGMSTREAM* vgmstream, vgmstream_info* info) {
|
void describe_vgmstream_info(VGMSTREAM* vgmstream, vgmstream_info* info) {
|
||||||
|
@ -70,6 +70,34 @@ bool mixer_is_active(mixer_t* mixer) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: probably could be pre-initialized
|
||||||
|
static void setup_mixbuf(mixer_t* mixer, sbuf_t* sbuf) {
|
||||||
|
sbuf_t* smix = &mixer->smix;
|
||||||
|
|
||||||
|
// mixbuf can be interpreted as FLT or F32; try to use src's to keep buf as-is (less rounding errors)
|
||||||
|
if (sbuf->fmt == SFMT_F32 || sbuf->fmt == SFMT_FLT)
|
||||||
|
sbuf_init(smix, sbuf->fmt, mixer->mixbuf, sbuf->filled, sbuf->channels); //mixer->input_channels
|
||||||
|
else
|
||||||
|
sbuf_init(smix, SFMT_F32, mixer->mixbuf, sbuf->filled, sbuf->channels);
|
||||||
|
|
||||||
|
// remix to temp buf (somehow using float buf rather than int32 is faster?)
|
||||||
|
sbuf_copy_segments(smix, sbuf, sbuf->filled);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setup_outbuf(mixer_t* mixer, sbuf_t* sbuf) {
|
||||||
|
sbuf_t* smix = &mixer->smix; //TODO: probably could be pre-initialized
|
||||||
|
|
||||||
|
// setup + remix to output buf (buf is expected to be big enough to handle config)
|
||||||
|
sbuf->channels = mixer->output_channels;
|
||||||
|
sbuf->filled = 0;
|
||||||
|
smix->channels = mixer->output_channels;
|
||||||
|
if (mixer->force_type) {
|
||||||
|
sbuf->fmt = mixer->force_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
sbuf_copy_segments(sbuf, smix, smix->filled);
|
||||||
|
}
|
||||||
|
|
||||||
void mixer_process(mixer_t* mixer, sbuf_t* sbuf, int32_t current_pos) {
|
void mixer_process(mixer_t* mixer, sbuf_t* sbuf, int32_t current_pos) {
|
||||||
|
|
||||||
/* external */
|
/* external */
|
||||||
@ -78,46 +106,36 @@ void mixer_process(mixer_t* mixer, sbuf_t* sbuf, int32_t current_pos) {
|
|||||||
|
|
||||||
/* try to skip if no fades apply (set but does nothing yet) + only has fades
|
/* 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) */
|
* (could be done in mix op but avoids upgrading bufs in some cases) */
|
||||||
mixer->current_subpos = 0;
|
|
||||||
if (mixer->has_fade) {
|
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));
|
//;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 + sbuf->filled))
|
if (!mixer->has_non_fade && !mixer_op_fade_is_active(mixer, current_pos, current_pos + sbuf->filled))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//;VGM_LOG("MIX: fade pos=%i\n", current_pos);
|
|
||||||
mixer->current_subpos = current_pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remix to temp buf for mixing (somehow using float buf rather than int32 is faster?)
|
mixer->current_subpos = current_pos;
|
||||||
sbuf_copy_to_f32(mixer->mixbuf, sbuf);
|
|
||||||
|
|
||||||
// apply mixing ops in order. current_channels may increase or decrease per op
|
setup_mixbuf(mixer, sbuf);
|
||||||
|
|
||||||
|
// apply mixing ops in order. channesl in mixersmix may increase or decrease per op
|
||||||
// - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2
|
// - 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)
|
// - 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++) {
|
for (int m = 0; m < mixer->chain_count; m++) {
|
||||||
mix_op_t* mix = &mixer->chain[m];
|
mix_op_t* mix = &mixer->chain[m];
|
||||||
|
|
||||||
//TO-DO: set callback
|
//TO-DO: set callback
|
||||||
switch(mix->type) {
|
switch(mix->type) {
|
||||||
case MIX_SWAP: mixer_op_swap(mixer, sbuf->filled, mix); break;
|
case MIX_SWAP: mixer_op_swap(mixer, mix); break;
|
||||||
case MIX_ADD: mixer_op_add(mixer, sbuf->filled, mix); break;
|
case MIX_ADD: mixer_op_add(mixer, mix); break;
|
||||||
case MIX_VOLUME: mixer_op_volume(mixer, sbuf->filled, mix); break;
|
case MIX_VOLUME: mixer_op_volume(mixer, mix); break;
|
||||||
case MIX_LIMIT: mixer_op_limit(mixer, sbuf->filled, mix); break;
|
case MIX_LIMIT: mixer_op_limit(mixer, mix); break;
|
||||||
case MIX_UPMIX: mixer_op_upmix(mixer, sbuf->filled, mix); break;
|
case MIX_UPMIX: mixer_op_upmix(mixer, mix); break;
|
||||||
case MIX_DOWNMIX: mixer_op_downmix(mixer, sbuf->filled, mix); break;
|
case MIX_DOWNMIX: mixer_op_downmix(mixer, mix); break;
|
||||||
case MIX_KILLMIX: mixer_op_killmix(mixer, sbuf->filled, mix); break;
|
case MIX_KILLMIX: mixer_op_killmix(mixer, mix); break;
|
||||||
case MIX_FADE: mixer_op_fade(mixer, sbuf->filled, mix);
|
case MIX_FADE: mixer_op_fade(mixer, mix);
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup + remix to output buf (buf is expected to be big enough to handle config)
|
setup_outbuf(mixer, sbuf);
|
||||||
sbuf->channels = mixer->output_channels;
|
|
||||||
if (mixer->force_type) {
|
|
||||||
sbuf->fmt = mixer->force_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
sbuf_copy_from_f32(sbuf, mixer->mixbuf);
|
|
||||||
}
|
}
|
||||||
|
@ -5,138 +5,147 @@
|
|||||||
// when there are no actual float ops (ex. 'swap', if no ' volume' )
|
// when there are no actual float ops (ex. 'swap', if no ' volume' )
|
||||||
// Performance gain is probably fairly small, though.
|
// Performance gain is probably fairly small, though.
|
||||||
|
|
||||||
void mixer_op_swap(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
void mixer_op_swap(mixer_t* mixer, mix_op_t* op) {
|
||||||
float* sbuf = mixer->mixbuf;
|
sbuf_t* smix = &mixer->smix;
|
||||||
|
float* dst = smix->buf;
|
||||||
|
|
||||||
for (int s = 0; s < sample_count; s++) {
|
for (int s = 0; s < smix->filled; s++) {
|
||||||
float temp_f = sbuf[op->ch_dst];
|
float temp_f = dst[op->ch_dst];
|
||||||
sbuf[op->ch_dst] = sbuf[op->ch_src];
|
dst[op->ch_dst] = dst[op->ch_src];
|
||||||
sbuf[op->ch_src] = temp_f;
|
dst[op->ch_src] = temp_f;
|
||||||
|
|
||||||
sbuf += mixer->current_channels;
|
dst += smix->channels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixer_op_add(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
void mixer_op_add(mixer_t* mixer, mix_op_t* op) {
|
||||||
float* sbuf = mixer->mixbuf;
|
sbuf_t* smix = &mixer->smix;
|
||||||
|
float* dst = smix->buf;
|
||||||
|
|
||||||
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
|
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
|
||||||
for (int s = 0; s < sample_count; s++) {
|
for (int s = 0; s < smix->filled; s++) {
|
||||||
sbuf[op->ch_dst] = sbuf[op->ch_dst] + sbuf[op->ch_src] * op->vol;
|
dst[op->ch_dst] = dst[op->ch_dst] + dst[op->ch_src] * op->vol;
|
||||||
|
|
||||||
sbuf += mixer->current_channels;
|
dst += smix->channels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixer_op_volume(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
void mixer_op_volume(mixer_t* mixer, mix_op_t* op) {
|
||||||
float* sbuf = mixer->mixbuf;
|
sbuf_t* smix = &mixer->smix;
|
||||||
|
float* dst = smix->buf;
|
||||||
|
|
||||||
if (op->ch_dst < 0) {
|
if (op->ch_dst < 0) {
|
||||||
/* "all channels", most common case */
|
/* "all channels", most common case */
|
||||||
for (int s = 0; s < sample_count * mixer->current_channels; s++) {
|
for (int s = 0; s < smix->filled * smix->channels; s++) {
|
||||||
sbuf[s] = sbuf[s] * op->vol;
|
dst[s] = dst[s] * op->vol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int s = 0; s < sample_count; s++) {
|
for (int s = 0; s < smix->filled; s++) {
|
||||||
sbuf[op->ch_dst] = sbuf[op->ch_dst] * op->vol;
|
dst[op->ch_dst] = dst[op->ch_dst] * op->vol;
|
||||||
|
|
||||||
sbuf += mixer->current_channels;
|
dst += smix->channels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixer_op_limit(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
void mixer_op_limit(mixer_t* mixer, mix_op_t* op) {
|
||||||
float* sbuf = mixer->mixbuf;
|
sbuf_t* smix = &mixer->smix;
|
||||||
|
float* dst = smix->buf;
|
||||||
|
|
||||||
const float limiter_max = 32767.0f;
|
const float limiter_max = smix->fmt == SFMT_FLT ? 1.0f : 32767.0f;
|
||||||
const float limiter_min = -32768.0f;
|
const float limiter_min = smix->fmt == SFMT_FLT ? -1.0f : -32768.0f;
|
||||||
|
|
||||||
const float temp_max = limiter_max * op->vol;
|
const float temp_max = limiter_max * op->vol;
|
||||||
const float temp_min = limiter_min * op->vol;
|
const float temp_min = limiter_min * op->vol;
|
||||||
|
|
||||||
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
|
/* could optimize when vol == 1 to avoid one multiplication but whatevs (not common) */
|
||||||
for (int s = 0; s < sample_count; s++) {
|
for (int s = 0; s < smix->filled; s++) {
|
||||||
|
|
||||||
if (op->ch_dst < 0) {
|
if (op->ch_dst < 0) {
|
||||||
for (int ch = 0; ch < mixer->current_channels; ch++) {
|
for (int ch = 0; ch < smix->channels; ch++) {
|
||||||
if (sbuf[ch] > temp_max)
|
if (dst[ch] > temp_max)
|
||||||
sbuf[ch] = temp_max;
|
dst[ch] = temp_max;
|
||||||
else if (sbuf[ch] < temp_min)
|
else if (dst[ch] < temp_min)
|
||||||
sbuf[ch] = temp_min;
|
dst[ch] = temp_min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (sbuf[op->ch_dst] > temp_max)
|
if (dst[op->ch_dst] > temp_max)
|
||||||
sbuf[op->ch_dst] = temp_max;
|
dst[op->ch_dst] = temp_max;
|
||||||
else if (sbuf[op->ch_dst] < temp_min)
|
else if (dst[op->ch_dst] < temp_min)
|
||||||
sbuf[op->ch_dst] = temp_min;
|
dst[op->ch_dst] = temp_min;
|
||||||
}
|
}
|
||||||
|
|
||||||
sbuf += mixer->current_channels;
|
dst += smix->channels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixer_op_upmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
void mixer_op_upmix(mixer_t* mixer, mix_op_t* op) {
|
||||||
int max_channels = mixer->current_channels;
|
sbuf_t* smix = &mixer->smix;
|
||||||
mixer->current_channels += 1;
|
float* sbuf = smix->buf;
|
||||||
|
|
||||||
float* sbuf_tmp = mixer->mixbuf + sample_count * mixer->current_channels;
|
int max_channels = smix->channels;
|
||||||
float* sbuf = mixer->mixbuf + sample_count * max_channels;
|
smix->channels += 1;
|
||||||
|
|
||||||
|
float* dst = sbuf + smix->filled * smix->channels;
|
||||||
|
float* src = sbuf + smix->filled * max_channels;
|
||||||
|
|
||||||
/* copy 'backwards' as otherwise would overwrite samples before moving them forward */
|
/* copy 'backwards' as otherwise would overwrite samples before moving them forward */
|
||||||
for (int s = 0; s < sample_count; s++) {
|
for (int s = 0; s < smix->filled; s++) {
|
||||||
sbuf_tmp -= mixer->current_channels;
|
dst -= smix->channels;
|
||||||
sbuf -= max_channels;
|
src -= max_channels;
|
||||||
|
|
||||||
int sbuf_ch = max_channels - 1;
|
int sbuf_ch = max_channels - 1;
|
||||||
for (int ch = mixer->current_channels - 1; ch >= 0; ch--) {
|
for (int ch = smix->channels - 1; ch >= 0; ch--) {
|
||||||
if (ch == op->ch_dst) {
|
if (ch == op->ch_dst) {
|
||||||
sbuf_tmp[ch] = 0; /* inserted as silent */
|
dst[ch] = 0; // inserted as silent
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sbuf_tmp[ch] = sbuf[sbuf_ch]; /* 'pull' channels backward */
|
dst[ch] = src[sbuf_ch]; // 'pull' channels backward
|
||||||
sbuf_ch--;
|
sbuf_ch--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixer_op_downmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
void mixer_op_downmix(mixer_t* mixer, mix_op_t* op) {
|
||||||
int max_channels = mixer->current_channels;
|
sbuf_t* smix = &mixer->smix;
|
||||||
mixer->current_channels -= 1;
|
float* src = smix->buf;
|
||||||
|
float* dst = smix->buf;
|
||||||
|
|
||||||
float* sbuf = mixer->mixbuf;
|
int max_channels = smix->channels;
|
||||||
float* sbuf_tmp = sbuf;
|
smix->channels -= 1;
|
||||||
|
|
||||||
for (int s = 0; s < sample_count; s++) {
|
for (int s = 0; s < smix->filled; s++) {
|
||||||
|
|
||||||
for (int ch = 0; ch < op->ch_dst; ch++) {
|
for (int ch = 0; ch < op->ch_dst; ch++) {
|
||||||
sbuf_tmp[ch] = sbuf[ch]; /* copy untouched channels */
|
dst[ch] = src[ch]; // copy untouched channels
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int ch = op->ch_dst; ch < max_channels - 1; ch++) {
|
for (int ch = op->ch_dst; ch < max_channels - 1; ch++) {
|
||||||
sbuf_tmp[ch] = sbuf[ch + 1]; /* 'pull' dropped channels back */
|
dst[ch] = src[ch + 1]; // 'pull' dropped channels back
|
||||||
}
|
}
|
||||||
|
|
||||||
sbuf_tmp += mixer->current_channels;
|
dst += smix->channels;
|
||||||
sbuf += max_channels;
|
src += max_channels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixer_op_killmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op) {
|
void mixer_op_killmix(mixer_t* mixer, mix_op_t* op) {
|
||||||
int max_channels = mixer->current_channels;
|
sbuf_t* smix = &mixer->smix;
|
||||||
mixer->current_channels = op->ch_dst; /* clamp channels */
|
float* src = smix->buf;
|
||||||
|
float* dst = smix->buf;
|
||||||
|
|
||||||
float* sbuf = mixer->mixbuf;
|
int max_channels = smix->channels;
|
||||||
float* sbuf_tmp = sbuf;
|
smix->channels = op->ch_dst; // clamp channels
|
||||||
|
|
||||||
for (int s = 0; s < sample_count; s++) {
|
for (int s = 0; s < smix->filled; s++) {
|
||||||
for (int ch = 0; ch < mixer->current_channels; ch++) {
|
for (int ch = 0; ch < smix->channels; ch++) {
|
||||||
sbuf_tmp[ch] = sbuf[ch];
|
dst[ch] = src[ch];
|
||||||
}
|
}
|
||||||
|
|
||||||
sbuf_tmp += mixer->current_channels;
|
dst += smix->channels;
|
||||||
sbuf += max_channels;
|
src += max_channels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
//TODO: could precalculate tables + interpolate for some performance gain
|
||||||
|
|
||||||
#define MIXING_PI 3.14159265358979323846f
|
#define MIXING_PI 3.14159265358979323846f
|
||||||
|
|
||||||
static inline float get_fade_gain_curve(char shape, float index) {
|
static inline float get_fade_gain_curve(char shape, float index) {
|
||||||
@ -112,36 +114,34 @@ static bool get_fade_gain(mix_op_t* op, float* out_cur_vol, int32_t current_subp
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mixer_op_fade(mixer_t* mixer, int32_t sample_count, mix_op_t* mix) {
|
void mixer_op_fade(mixer_t* mixer, mix_op_t* mix) {
|
||||||
float* sbuf = mixer->mixbuf;
|
sbuf_t* smix = &mixer->smix;
|
||||||
|
float* dst = smix->buf;
|
||||||
float new_gain = 0.0f;
|
float new_gain = 0.0f;
|
||||||
|
|
||||||
int channels = mixer->current_channels;
|
int channels = smix->channels;
|
||||||
int32_t current_subpos = mixer->current_subpos;
|
int32_t current_subpos = mixer->current_subpos;
|
||||||
|
|
||||||
//TODO optimize for case 0?
|
//TODO optimize for case 0?
|
||||||
for (int s = 0; s < sample_count; s++) {
|
for (int s = 0; s < smix->filled; s++) {
|
||||||
bool fade_applies = get_fade_gain(mix, &new_gain, current_subpos);
|
bool fade_applies = get_fade_gain(mix, &new_gain, current_subpos);
|
||||||
if (!fade_applies) //TODO optimize?
|
if (!fade_applies) //TODO optimize?
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (mix->ch_dst < 0) {
|
if (mix->ch_dst < 0) {
|
||||||
for (int ch = 0; ch < channels; ch++) {
|
for (int ch = 0; ch < channels; ch++) {
|
||||||
sbuf[ch] = sbuf[ch] * new_gain;
|
dst[ch] = dst[ch] * new_gain;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sbuf[mix->ch_dst] = sbuf[mix->ch_dst] * new_gain;
|
dst[mix->ch_dst] = dst[mix->ch_dst] * new_gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
sbuf += channels;
|
dst += channels;
|
||||||
current_subpos++;
|
current_subpos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
mixer->current_subpos = current_subpos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool mixer_op_fade_is_active(mixer_t* mixer, int32_t current_start, int32_t current_end) {
|
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++) {
|
for (int i = 0; i < mixer->chain_count; i++) {
|
||||||
|
@ -49,20 +49,20 @@ struct mixer_t {
|
|||||||
bool has_non_fade;
|
bool has_non_fade;
|
||||||
bool has_fade;
|
bool has_fade;
|
||||||
|
|
||||||
float* mixbuf; /* internal mixing buffer */
|
float* mixbuf; // internal mixing buffer
|
||||||
int current_channels; /* state: channels may increase/decrease during ops */
|
sbuf_t smix; // temp sbuf
|
||||||
int32_t current_subpos; /* state: current sample pos in the stream */
|
int32_t current_subpos; // state: current sample pos in the stream
|
||||||
|
|
||||||
sfmt_t force_type;
|
sfmt_t force_type; // mixer output is original buffer's by default, unless forced
|
||||||
};
|
};
|
||||||
|
|
||||||
void mixer_op_swap(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
void mixer_op_swap(mixer_t* mixer, mix_op_t* op);
|
||||||
void mixer_op_add(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
void mixer_op_add(mixer_t* mixer, mix_op_t* op);
|
||||||
void mixer_op_volume(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
void mixer_op_volume(mixer_t* mixer, mix_op_t* op);
|
||||||
void mixer_op_limit(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
void mixer_op_limit(mixer_t* mixer, mix_op_t* op);
|
||||||
void mixer_op_upmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
void mixer_op_upmix(mixer_t* mixer, mix_op_t* op);
|
||||||
void mixer_op_downmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
void mixer_op_downmix(mixer_t* mixer, mix_op_t* op);
|
||||||
void mixer_op_killmix(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
void mixer_op_killmix(mixer_t* mixer, mix_op_t* op);
|
||||||
void mixer_op_fade(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
void mixer_op_fade(mixer_t* mixer, mix_op_t* op);
|
||||||
bool mixer_op_fade_is_active(mixer_t* mixer, int32_t current_start, int32_t current_end);
|
bool mixer_op_fade_is_active(mixer_t* mixer, int32_t current_start, int32_t current_end);
|
||||||
#endif
|
#endif
|
||||||
|
@ -143,10 +143,14 @@ void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_chan
|
|||||||
}
|
}
|
||||||
|
|
||||||
sfmt_t mixing_get_input_sample_type(VGMSTREAM* vgmstream) {
|
sfmt_t mixing_get_input_sample_type(VGMSTREAM* vgmstream) {
|
||||||
// TODO: check vgmstream
|
|
||||||
// TODO: on layered/segments, detect biggest value and use that (ex. if one of the layers uses flt > flt)
|
// TODO: on layered/segments, detect biggest value and use that (ex. if one of the layers uses flt > flt)
|
||||||
|
switch(vgmstream->coding_type) {
|
||||||
|
case coding_KA1A:
|
||||||
|
return SFMT_FLT;
|
||||||
|
default:
|
||||||
return SFMT_S16;
|
return SFMT_S16;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sfmt_t mixing_get_output_sample_type(VGMSTREAM* vgmstream) {
|
sfmt_t mixing_get_output_sample_type(VGMSTREAM* vgmstream) {
|
||||||
sfmt_t input_fmt = mixing_get_input_sample_type(vgmstream);
|
sfmt_t input_fmt = mixing_get_input_sample_type(vgmstream);
|
||||||
|
@ -73,7 +73,6 @@ void render_reset(VGMSTREAM* vgmstream) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int render_layout(sbuf_t* sbuf, VGMSTREAM* vgmstream) {
|
int render_layout(sbuf_t* sbuf, VGMSTREAM* vgmstream) {
|
||||||
void* buf = sbuf->buf;
|
|
||||||
int sample_count = sbuf->samples;
|
int sample_count = sbuf->samples;
|
||||||
|
|
||||||
if (sample_count == 0)
|
if (sample_count == 0)
|
||||||
@ -90,10 +89,10 @@ int render_layout(sbuf_t* sbuf, VGMSTREAM* vgmstream) {
|
|||||||
|
|
||||||
switch (vgmstream->layout_type) {
|
switch (vgmstream->layout_type) {
|
||||||
case layout_interleave:
|
case layout_interleave:
|
||||||
render_vgmstream_interleave(buf, sample_count, vgmstream);
|
render_vgmstream_interleave(sbuf, vgmstream);
|
||||||
break;
|
break;
|
||||||
case layout_none:
|
case layout_none:
|
||||||
render_vgmstream_flat(buf, sample_count, vgmstream);
|
render_vgmstream_flat(sbuf, vgmstream);
|
||||||
break;
|
break;
|
||||||
case layout_blocked_mxch:
|
case layout_blocked_mxch:
|
||||||
case layout_blocked_ast:
|
case layout_blocked_ast:
|
||||||
@ -134,7 +133,7 @@ int render_layout(sbuf_t* sbuf, VGMSTREAM* vgmstream) {
|
|||||||
case layout_blocked_ubi_sce:
|
case layout_blocked_ubi_sce:
|
||||||
case layout_blocked_tt_ad:
|
case layout_blocked_tt_ad:
|
||||||
case layout_blocked_vas:
|
case layout_blocked_vas:
|
||||||
render_vgmstream_blocked(buf, sample_count, vgmstream);
|
render_vgmstream_blocked(sbuf, vgmstream);
|
||||||
break;
|
break;
|
||||||
case layout_segmented:
|
case layout_segmented:
|
||||||
render_vgmstream_segmented(sbuf, vgmstream);
|
render_vgmstream_segmented(sbuf, vgmstream);
|
||||||
|
@ -112,8 +112,6 @@ void sbuf_copy_to_f32(float* dst, sbuf_t* sbuf) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SFMT_FLT:
|
|
||||||
case SFMT_F32: {
|
case SFMT_F32: {
|
||||||
float* src = sbuf->buf;
|
float* src = sbuf->buf;
|
||||||
for (int s = 0; s < sbuf->filled * sbuf->channels; s++) {
|
for (int s = 0; s < sbuf->filled * sbuf->channels; s++) {
|
||||||
@ -121,6 +119,13 @@ void sbuf_copy_to_f32(float* dst, sbuf_t* sbuf) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SFMT_FLT: {
|
||||||
|
float* src = sbuf->buf;
|
||||||
|
for (int s = 0; s < sbuf->filled * sbuf->channels; s++) {
|
||||||
|
dst[s] = src[s] * 32768.0f;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ void g72x_init_state(struct g72x_state* state_ptr);
|
|||||||
|
|
||||||
/* ima_decoder */
|
/* ima_decoder */
|
||||||
void decode_standard_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo, int is_high_first);
|
void decode_standard_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo, int is_high_first);
|
||||||
void decode_nw_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
void decode_camelot_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||||
void decode_snds_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
void decode_snds_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||||
void decode_otns_ima(VGMSTREAM* vgmstream, VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
void decode_otns_ima(VGMSTREAM* vgmstream, VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||||
void decode_wv6_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
void decode_wv6_ima(VGMSTREAMCHANNEL* stream, sample_t* outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||||
|
@ -124,8 +124,8 @@ static void std_ima_expand_nibble_mul(VGMSTREAMCHANNEL * stream, off_t byte_offs
|
|||||||
if (*step_index > 88) *step_index=88;
|
if (*step_index > 88) *step_index=88;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NintendoWare IMA (Mario Golf, Mario Tennis; maybe other Camelot games) */
|
/* Camelot IMA (Mario Golf, Mario Tennis; maybe other Camelot games) */
|
||||||
static void nw_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
|
static void camelot_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) {
|
||||||
int sample_nibble, sample_decoded, step, delta;
|
int sample_nibble, sample_decoded, step, delta;
|
||||||
|
|
||||||
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
|
sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf;
|
||||||
@ -418,7 +418,7 @@ void decode_mtf_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspa
|
|||||||
stream->adpcm_step_index = step_index;
|
stream->adpcm_step_index = step_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void decode_nw_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
void decode_camelot_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||||
int i, sample_count;
|
int i, sample_count;
|
||||||
int32_t hist1 = stream->adpcm_history1_32;
|
int32_t hist1 = stream->adpcm_history1_32;
|
||||||
int step_index = stream->adpcm_step_index;
|
int step_index = stream->adpcm_step_index;
|
||||||
@ -431,7 +431,7 @@ void decode_nw_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspac
|
|||||||
off_t byte_offset = stream->offset + i/2;
|
off_t byte_offset = stream->offset + i/2;
|
||||||
int nibble_shift = (i&1?4:0); //low nibble order
|
int nibble_shift = (i&1?4:0); //low nibble order
|
||||||
|
|
||||||
nw_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index);
|
camelot_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index);
|
||||||
outbuf[sample_count] = (short)(hist1);
|
outbuf[sample_count] = (short)(hist1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
//TODO change to streaming decoder
|
//TODO change to streaming decoder
|
||||||
// Currently lib expects most data in memory. Due to how format is designed it's not the
|
// Currently lib expects most data in memory. Due to how format is designed it's not the
|
||||||
// easiest thing to change, to be fixed it later:
|
// easiest thing to change, to be fixed later:
|
||||||
// - data is divided into 2 blocks (intro+body) that are decoded separatedly
|
// - data is divided into 2 blocks (intro+body) that are decoded separatedly
|
||||||
// (streaming should read up to block max)
|
// (streaming should read up to block max)
|
||||||
// - code data isn't divided into frames, just keeps reading from the file buf
|
// - code data isn't divided into frames, just keeps reading from the file buf
|
||||||
@ -30,36 +30,11 @@
|
|||||||
|
|
||||||
//#include "zlib.h"
|
//#include "zlib.h"
|
||||||
#include "../../util/zlib_vgmstream.h"
|
#include "../../util/zlib_vgmstream.h"
|
||||||
|
#include "../../util/reader_get.h"
|
||||||
|
|
||||||
#define ICESND_MAX_CHANNELS 2
|
#define ICESND_MAX_CHANNELS 2
|
||||||
|
|
||||||
|
|
||||||
/* ************************************************************ */
|
|
||||||
/* COMMON */
|
|
||||||
/* ************************************************************ */
|
|
||||||
|
|
||||||
static inline uint8_t get_u8(const uint8_t* p) {
|
|
||||||
uint8_t ret;
|
|
||||||
ret = ((uint16_t)(const uint8_t)p[0]) << 0;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint16_t get_u16le(const uint8_t* p) {
|
|
||||||
uint16_t ret;
|
|
||||||
ret = ((uint16_t)(const uint8_t)p[0]) << 0;
|
|
||||||
ret |= ((uint16_t)(const uint8_t)p[1]) << 8;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t get_u32le(const uint8_t* p) {
|
|
||||||
uint32_t ret;
|
|
||||||
ret = ((uint32_t)(const uint8_t)p[0]) << 0;
|
|
||||||
ret |= ((uint32_t)(const uint8_t)p[1]) << 8;
|
|
||||||
ret |= ((uint32_t)(const uint8_t)p[2]) << 16;
|
|
||||||
ret |= ((uint32_t)(const uint8_t)p[3]) << 24;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* bigrp entry info as read from header */
|
/* bigrp entry info as read from header */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t hash1; /* usually matches filename, different files vary on bytes, seems internally used to identify files */
|
uint32_t hash1; /* usually matches filename, different files vary on bytes, seems internally used to identify files */
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* OG code isn't too optimized though.
|
* OG code isn't too optimized though.
|
||||||
*
|
*
|
||||||
* Reverse engineered from exes, thanks to Kelebek1 and AceKombat for help and debugging.
|
* Reverse engineered from exes, thanks to Kelebek1 and AceKombat for help and debugging.
|
||||||
* Output has been compared to memdumps and should be accurate with minor +-diffs.
|
* Output has been compared to memdumps and should be accurate with minor +-diffs (vs MSVC 22 /O2).
|
||||||
*
|
*
|
||||||
* Even though some parts can be simplified/optimized code tries to emulate what source code
|
* Even though some parts can be simplified/optimized code tries to emulate what source code
|
||||||
* may look like, undoing unrolled/vectorized parts. Functions marked as 'inline' don't exist in
|
* may look like, undoing unrolled/vectorized parts. Functions marked as 'inline' don't exist in
|
||||||
@ -147,7 +147,7 @@ static inline int unpack_get_bits(uint8_t* src, int* p_byte_pos, int* p_bit_pos,
|
|||||||
//
|
//
|
||||||
// Bands encode less coefs than dst may hold, so 'positions' are used to put coefs
|
// Bands encode less coefs than dst may hold, so 'positions' are used to put coefs
|
||||||
// non-linearly, where unset indexes are 0 (dst must be memset before calling unpack frame).
|
// non-linearly, where unset indexes are 0 (dst must be memset before calling unpack frame).
|
||||||
// dst should be 1024, though usually only lower 512 (max step is 390 + ((1<<7) - 1)).
|
// dst should be 1024, though usually only lower 512 are used (max step is 390 + ((1<<7) - 1)).
|
||||||
static void unpack_frame(uint8_t* src, float* dst, int steps_size, void* unused, int bitrate_index) {
|
static void unpack_frame(uint8_t* src, float* dst, int steps_size, void* unused, int bitrate_index) {
|
||||||
|
|
||||||
// copy coefs counts as they may be modified below
|
// copy coefs counts as they may be modified below
|
||||||
@ -461,7 +461,7 @@ void transform_frame(void* unused1, float* src, float* dst, void* unused2, float
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reorder output (input buf may be reused as output here as there is no overlap).
|
// Reorder output (input buf may be reused as output here as there is no overlap).
|
||||||
// Note that input is 512 coefs but output is 1024 samples (externally combined with samples)
|
// Note that input is 512 coefs but output is 1024 samples (externally combined with prev samples)
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
for (int i = 0; i < 128; i++) {
|
for (int i = 0; i < 128; i++) {
|
||||||
dst[pos++] = real[128 + i];
|
dst[pos++] = real[128 + i];
|
||||||
@ -522,13 +522,13 @@ static void decode_frame(unsigned char* src, int tracks, int channels, float* ds
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setup_flag) // MOD: expect only 1 block per call
|
if (setup_flag) // OG MOD: changed to expect only 1 block per call
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// decode 'current block of frames' (writes 512 samples, plus setups 'prev' buf)
|
// decode 'current block of frames' (writes 512 samples, plus setups 'prev' buf)
|
||||||
{
|
{
|
||||||
//uint8_t* src_block = &src[channels * tracks * frame_size]; // 2nd block in src in OG code
|
//uint8_t* src_block = &src[channels * tracks * frame_size]; // 2nd block in src in OG code
|
||||||
uint8_t* src_block = &src[0]; // MOD: expect only 1 block per call
|
uint8_t* src_block = &src[0]; // OG MOD: changed to expect only 1 block per call
|
||||||
|
|
||||||
for (int track = 0; track < tracks; track++) {
|
for (int track = 0; track < tracks; track++) {
|
||||||
int frame_num = channels * track;
|
int frame_num = channels * track;
|
||||||
@ -568,7 +568,7 @@ struct ka1a_handle_t {
|
|||||||
|
|
||||||
// state
|
// state
|
||||||
bool setup_flag; // next frame will be used as setup and won't output samples
|
bool setup_flag; // next frame will be used as setup and won't output samples
|
||||||
float temp[1024 * 2]; // fft + coef buf
|
float temp[1024 * 2]; // fft + spectrum coefs buf
|
||||||
float* prev; // at least samples * channels * tracks
|
float* prev; // at least samples * channels * tracks
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -522,6 +522,7 @@ static const char* extension_list[] = {
|
|||||||
"sgb",
|
"sgb",
|
||||||
"sgd",
|
"sgd",
|
||||||
"sgt",
|
"sgt",
|
||||||
|
"skx",
|
||||||
"slb", //txth/reserved [THE Nekomura no Hitobito (PS2)]
|
"slb", //txth/reserved [THE Nekomura no Hitobito (PS2)]
|
||||||
"sli",
|
"sli",
|
||||||
"smc",
|
"smc",
|
||||||
@ -835,7 +836,7 @@ static const coding_info coding_info_list[] = {
|
|||||||
{coding_IMA_int, "IMA 4-bit ADPCM (mono/interleave)"},
|
{coding_IMA_int, "IMA 4-bit ADPCM (mono/interleave)"},
|
||||||
{coding_DVI_IMA, "Intel DVI 4-bit IMA ADPCM"},
|
{coding_DVI_IMA, "Intel DVI 4-bit IMA ADPCM"},
|
||||||
{coding_DVI_IMA_int, "Intel DVI 4-bit IMA ADPCM (mono/interleave)"},
|
{coding_DVI_IMA_int, "Intel DVI 4-bit IMA ADPCM (mono/interleave)"},
|
||||||
{coding_NW_IMA, "NintendoWare IMA 4-bit ADPCM"},
|
{coding_CAMELOT_IMA, "Camelot IMA 4-bit ADPCM"},
|
||||||
{coding_SNDS_IMA, "Heavy Iron .snds 4-bit IMA ADPCM"},
|
{coding_SNDS_IMA, "Heavy Iron .snds 4-bit IMA ADPCM"},
|
||||||
{coding_QD_IMA, "Quantic Dream 4-bit IMA ADPCM"},
|
{coding_QD_IMA, "Quantic Dream 4-bit IMA ADPCM"},
|
||||||
{coding_WV6_IMA, "Gorilla Systems WV6 4-bit IMA ADPCM"},
|
{coding_WV6_IMA, "Gorilla Systems WV6 4-bit IMA ADPCM"},
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
/* Decodes samples for blocked streams.
|
/* Decodes samples for blocked streams.
|
||||||
* Data is divided into headered blocks with a bunch of data. The layout calls external helper functions
|
* Data is divided into headered blocks with a bunch of data. The layout calls external helper functions
|
||||||
* when a block is decoded, and those must parse the new block and move offsets accordingly. */
|
* when a block is decoded, and those must parse the new block and move offsets accordingly. */
|
||||||
void render_vgmstream_blocked(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
void render_vgmstream_blocked(sbuf_t* sdst, VGMSTREAM* vgmstream) {
|
||||||
|
|
||||||
int frame_size = decode_get_frame_size(vgmstream);
|
int frame_size = decode_get_frame_size(vgmstream);
|
||||||
int samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
int samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
||||||
@ -25,8 +25,7 @@ void render_vgmstream_blocked(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
|||||||
samples_this_block = vgmstream->current_block_size / frame_size * samples_per_frame;
|
samples_this_block = vgmstream->current_block_size / frame_size * samples_per_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
int samples_filled = 0;
|
while (sdst->filled < sdst->samples) {
|
||||||
while (samples_filled < sample_count) {
|
|
||||||
int samples_to_do;
|
int samples_to_do;
|
||||||
|
|
||||||
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
||||||
@ -54,15 +53,15 @@ void render_vgmstream_blocked(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
|||||||
}
|
}
|
||||||
|
|
||||||
samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||||
if (samples_to_do > sample_count - samples_filled)
|
if (samples_to_do > sdst->samples - sdst->filled)
|
||||||
samples_to_do = sample_count - samples_filled;
|
samples_to_do = sdst->samples - sdst->filled;
|
||||||
|
|
||||||
if (samples_to_do > 0) {
|
if (samples_to_do > 0) {
|
||||||
/* samples_this_block = 0 is allowed (empty block, do nothing then move to next block) */
|
/* samples_this_block = 0 is allowed (empty block, do nothing then move to next block) */
|
||||||
decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf);
|
decode_vgmstream(sdst, vgmstream, samples_to_do);
|
||||||
}
|
}
|
||||||
|
|
||||||
samples_filled += samples_to_do;
|
sdst->filled += samples_to_do;
|
||||||
vgmstream->current_sample += samples_to_do;
|
vgmstream->current_sample += samples_to_do;
|
||||||
vgmstream->samples_into_block += samples_to_do;
|
vgmstream->samples_into_block += samples_to_do;
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ void render_vgmstream_blocked(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
decode_fail:
|
decode_fail:
|
||||||
sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled);
|
sbuf_silence_rest(sdst);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* helper functions to parse new block */
|
/* helper functions to parse new block */
|
||||||
|
@ -6,14 +6,13 @@
|
|||||||
|
|
||||||
/* Decodes samples for flat streams.
|
/* Decodes samples for flat streams.
|
||||||
* Data forms a single stream, and the decoder may internally skip chunks and move offsets as needed. */
|
* Data forms a single stream, and the decoder may internally skip chunks and move offsets as needed. */
|
||||||
void render_vgmstream_flat(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
void render_vgmstream_flat(sbuf_t* sdst, VGMSTREAM* vgmstream) {
|
||||||
|
|
||||||
int samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
int samples_per_frame = decode_get_samples_per_frame(vgmstream);
|
||||||
int samples_this_block = vgmstream->num_samples; /* do all samples if possible */
|
int samples_this_block = vgmstream->num_samples; /* do all samples if possible */
|
||||||
|
|
||||||
/* write samples */
|
/* write samples */
|
||||||
int samples_filled = 0;
|
while (sdst->filled < sdst->samples) {
|
||||||
while (samples_filled < sample_count) {
|
|
||||||
|
|
||||||
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
||||||
/* handle looping */
|
/* handle looping */
|
||||||
@ -21,22 +20,22 @@ void render_vgmstream_flat(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vg
|
|||||||
}
|
}
|
||||||
|
|
||||||
int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||||
if (samples_to_do > sample_count - samples_filled)
|
if (samples_to_do > sdst->samples - sdst->filled)
|
||||||
samples_to_do = sample_count - samples_filled;
|
samples_to_do = sdst->samples - sdst->filled;
|
||||||
|
|
||||||
if (samples_to_do <= 0) { /* when decoding more than num_samples */
|
if (samples_to_do <= 0) { /* when decoding more than num_samples */
|
||||||
VGM_LOG_ONCE("FLAT: wrong samples_to_do\n");
|
VGM_LOG_ONCE("FLAT: wrong samples_to_do\n");
|
||||||
goto decode_fail;
|
goto decode_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf);
|
decode_vgmstream(sdst, vgmstream, samples_to_do);
|
||||||
|
|
||||||
samples_filled += samples_to_do;
|
sdst->filled += samples_to_do;
|
||||||
vgmstream->current_sample += samples_to_do;
|
vgmstream->current_sample += samples_to_do;
|
||||||
vgmstream->samples_into_block += samples_to_do;
|
vgmstream->samples_into_block += samples_to_do;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
decode_fail:
|
decode_fail:
|
||||||
sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled);
|
sbuf_silence_rest(sdst);
|
||||||
}
|
}
|
||||||
|
@ -143,11 +143,11 @@ static void update_offsets(layout_config_t* layout, VGMSTREAM* vgmstream, int* p
|
|||||||
* Data has interleaved chunks per channel, and once one is decoded the layout moves offsets,
|
* Data has interleaved chunks per channel, and once one is decoded the layout moves offsets,
|
||||||
* skipping other chunks (essentially a simplified variety of blocked layout).
|
* skipping other chunks (essentially a simplified variety of blocked layout).
|
||||||
* Incompatible with decoders that move offsets. */
|
* Incompatible with decoders that move offsets. */
|
||||||
void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
void render_vgmstream_interleave(sbuf_t* sdst, VGMSTREAM* vgmstream) {
|
||||||
layout_config_t layout = {0};
|
layout_config_t layout = {0};
|
||||||
if (!setup_helper(&layout, vgmstream)) {
|
if (!setup_helper(&layout, vgmstream)) {
|
||||||
VGM_LOG_ONCE("INTERLEAVE: wrong config found\n");
|
VGM_LOG_ONCE("INTERLEAVE: wrong config found\n");
|
||||||
sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, 0);
|
sbuf_silence_rest(sdst);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,8 +160,7 @@ void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTRE
|
|||||||
if (samples_this_block == 0 && vgmstream->channels == 1)
|
if (samples_this_block == 0 && vgmstream->channels == 1)
|
||||||
samples_this_block = vgmstream->num_samples;
|
samples_this_block = vgmstream->num_samples;
|
||||||
|
|
||||||
int samples_filled = 0;
|
while (sdst->filled < sdst->samples) {
|
||||||
while (samples_filled < sample_count) {
|
|
||||||
|
|
||||||
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
if (vgmstream->loop_flag && decode_do_loop(vgmstream)) {
|
||||||
/* handle looping, restore standard interleave sizes */
|
/* handle looping, restore standard interleave sizes */
|
||||||
@ -170,17 +169,17 @@ void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTRE
|
|||||||
}
|
}
|
||||||
|
|
||||||
int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
int samples_to_do = decode_get_samples_to_do(samples_this_block, samples_per_frame, vgmstream);
|
||||||
if (samples_to_do > sample_count - samples_filled)
|
if (samples_to_do > sdst->samples - sdst->filled)
|
||||||
samples_to_do = sample_count - samples_filled;
|
samples_to_do = sdst->samples - sdst->filled;
|
||||||
|
|
||||||
if (samples_to_do <= 0) { /* happens when interleave is not set */
|
if (samples_to_do <= 0) { /* happens when interleave is not set */
|
||||||
VGM_LOG_ONCE("INTERLEAVE: wrong samples_to_do\n");
|
VGM_LOG_ONCE("INTERLEAVE: wrong samples_to_do\n");
|
||||||
goto decode_fail;
|
goto decode_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
decode_vgmstream(vgmstream, samples_filled, samples_to_do, outbuf);
|
decode_vgmstream(sdst, vgmstream, samples_to_do);
|
||||||
|
|
||||||
samples_filled += samples_to_do;
|
sdst->filled += samples_to_do;
|
||||||
vgmstream->current_sample += samples_to_do;
|
vgmstream->current_sample += samples_to_do;
|
||||||
vgmstream->samples_into_block += samples_to_do;
|
vgmstream->samples_into_block += samples_to_do;
|
||||||
|
|
||||||
@ -193,5 +192,5 @@ void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTRE
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
decode_fail:
|
decode_fail:
|
||||||
sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled);
|
sbuf_silence_rest(sdst);
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
#include "../base/sbuf.h"
|
#include "../base/sbuf.h"
|
||||||
|
|
||||||
/* basic layouts */
|
/* basic layouts */
|
||||||
void render_vgmstream_flat(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
void render_vgmstream_flat(sbuf_t* sbuf, VGMSTREAM* vgmstream);
|
||||||
|
|
||||||
void render_vgmstream_interleave(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
void render_vgmstream_interleave(sbuf_t* sbuf, VGMSTREAM* vgmstream);
|
||||||
|
|
||||||
|
|
||||||
/* segmented layout */
|
/* segmented layout */
|
||||||
@ -56,7 +56,7 @@ void loop_layout_layered(VGMSTREAM* vgmstream, int32_t loop_sample);
|
|||||||
|
|
||||||
|
|
||||||
/* blocked layouts */
|
/* blocked layouts */
|
||||||
void render_vgmstream_blocked(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream);
|
void render_vgmstream_blocked(sbuf_t* sbuf, VGMSTREAM* vgmstream);
|
||||||
void block_update(off_t block_offset, VGMSTREAM* vgmstream);
|
void block_update(off_t block_offset, VGMSTREAM* vgmstream);
|
||||||
|
|
||||||
void block_update_ast(off_t block_ofset, VGMSTREAM* vgmstream);
|
void block_update_ast(off_t block_ofset, VGMSTREAM* vgmstream);
|
||||||
|
@ -685,6 +685,7 @@
|
|||||||
<ClCompile Include="meta\sfl.c" />
|
<ClCompile Include="meta\sfl.c" />
|
||||||
<ClCompile Include="meta\sgxd.c" />
|
<ClCompile Include="meta\sgxd.c" />
|
||||||
<ClCompile Include="meta\silence.c" />
|
<ClCompile Include="meta\silence.c" />
|
||||||
|
<ClCompile Include="meta\skex.c" />
|
||||||
<ClCompile Include="meta\sk_aud.c" />
|
<ClCompile Include="meta\sk_aud.c" />
|
||||||
<ClCompile Include="meta\sl3.c" />
|
<ClCompile Include="meta\sl3.c" />
|
||||||
<ClCompile Include="meta\sli.c" />
|
<ClCompile Include="meta\sli.c" />
|
||||||
|
@ -1885,6 +1885,9 @@
|
|||||||
<ClCompile Include="meta\silence.c">
|
<ClCompile Include="meta\silence.c">
|
||||||
<Filter>meta\Source Files</Filter>
|
<Filter>meta\Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="meta\skex.c">
|
||||||
|
<Filter>meta\Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="meta\sk_aud.c">
|
<ClCompile Include="meta\sk_aud.c">
|
||||||
<Filter>meta\Source Files</Filter>
|
<Filter>meta\Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -205,7 +205,7 @@ VGMSTREAM* init_vgmstream_awc(STREAMFILE* sf) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#ifdef VGM_USE_ATRAC9
|
#ifdef VGM_USE_FFMPEG
|
||||||
case 0x0D: { /* OPUS (PC) [Red Dead Redemption (PC)] */
|
case 0x0D: { /* OPUS (PC) [Red Dead Redemption (PC)] */
|
||||||
if (awc.is_streamed) {
|
if (awc.is_streamed) {
|
||||||
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
|
vgmstream->layout_data = build_layered_awc(sf_body, &awc);
|
||||||
|
@ -96,7 +96,7 @@ VGMSTREAM* init_vgmstream_bcstm(STREAMFILE* sf) {
|
|||||||
vgmstream->coding_type = coding_NGC_DSP;
|
vgmstream->coding_type = coding_NGC_DSP;
|
||||||
|
|
||||||
if (is_camelot_ima) {
|
if (is_camelot_ima) {
|
||||||
vgmstream->coding_type = coding_NW_IMA;
|
vgmstream->coding_type = coding_CAMELOT_IMA;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
off_t channel_indexes, channel_info_offset, coefs_offset;
|
off_t channel_indexes, channel_info_offset, coefs_offset;
|
||||||
@ -113,7 +113,7 @@ VGMSTREAM* init_vgmstream_bcstm(STREAMFILE* sf) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: /* 0x03: IMA? */
|
default: /* 0x03: regular IMA? (like .bcwav) */
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ static VGMSTREAM* init_vgmstream_bxwav(STREAMFILE* sf, bxwav_type_t type) {
|
|||||||
|
|
||||||
vgmstream->layout_type = layout_none;
|
vgmstream->layout_type = layout_none;
|
||||||
|
|
||||||
/* only 0x02 is known, others can be made with SDK tools */
|
/* only 0x02/03 are known, others can be made with SDK tools */
|
||||||
switch (codec) {
|
switch (codec) {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
vgmstream->coding_type = coding_PCM8;
|
vgmstream->coding_type = coding_PCM8;
|
||||||
@ -227,7 +227,7 @@ static VGMSTREAM* init_vgmstream_bxwav(STREAMFILE* sf, bxwav_type_t type) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x03:
|
case 0x03:
|
||||||
vgmstream->coding_type = coding_NW_IMA;
|
vgmstream->coding_type = coding_IMA; // 3DS eShop applet (3DS)
|
||||||
/* hist is read below */
|
/* hist is read below */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1515,6 +1515,9 @@ static const hcakey_info hcakey_list[] = {
|
|||||||
|
|
||||||
// Muv-Luv Dimensions (Android)
|
// Muv-Luv Dimensions (Android)
|
||||||
{8848}, // 0000000000002290
|
{8848}, // 0000000000002290
|
||||||
|
|
||||||
|
// Tales of Graces f Remastered (PC)
|
||||||
|
{51485416730473395}, // 00B6E9B6B75533B3
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -31,7 +31,7 @@ VGMSTREAM* init_vgmstream_hd_bd(STREAMFILE* sf) {
|
|||||||
// 0x1c: Smpl offset
|
// 0x1c: Smpl offset
|
||||||
uint32_t vagi_offset = read_u32le(head_offset + 0x20, sf);
|
uint32_t vagi_offset = read_u32le(head_offset + 0x20, sf);
|
||||||
// 0x24: Setb offset
|
// 0x24: Setb offset
|
||||||
// rest: reserved (-1)
|
// rest: reserved (-1, or rarely 0 [Midnight Club 2 (PS2)])
|
||||||
|
|
||||||
meta_header_t h = {
|
meta_header_t h = {
|
||||||
.meta = meta_HD_BD,
|
.meta = meta_HD_BD,
|
||||||
|
@ -20,16 +20,17 @@ VGMSTREAM* init_vgmstream_ktac(STREAMFILE* sf) {
|
|||||||
ktac_header_t ktac = {0};
|
ktac_header_t ktac = {0};
|
||||||
|
|
||||||
/* checks */
|
/* checks */
|
||||||
/* .ktac: header id */
|
|
||||||
if (!check_extensions(sf,"ktac"))
|
|
||||||
goto fail;
|
|
||||||
if (!is_id32be(0x00,sf, "KTAC"))
|
if (!is_id32be(0x00,sf, "KTAC"))
|
||||||
goto fail;
|
return NULL;
|
||||||
|
|
||||||
|
/* .ktac: header id (probable extension from debug strings is "kac" */
|
||||||
|
if (!check_extensions(sf,"ktac"))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
/* 0x04: version? (always 1) */
|
/* 0x04: version? (always 1) */
|
||||||
ktac.file_size = read_u32le(0x08,sf);
|
ktac.file_size = read_u32le(0x08,sf);
|
||||||
if (ktac.file_size != get_streamfile_size(sf))
|
if (ktac.file_size != get_streamfile_size(sf))
|
||||||
goto fail;
|
return NULL;
|
||||||
ktac.mp4.stream_offset = read_u32le(0x0c,sf);
|
ktac.mp4.stream_offset = read_u32le(0x0c,sf);
|
||||||
ktac.mp4.stream_size = read_u32le(0x10,sf);
|
ktac.mp4.stream_size = read_u32le(0x10,sf);
|
||||||
ktac.type = read_u32le(0x14,sf);
|
ktac.type = read_u32le(0x14,sf);
|
||||||
|
@ -28,6 +28,7 @@ typedef struct {
|
|||||||
uint32_t audio_id;
|
uint32_t audio_id;
|
||||||
int platform;
|
int platform;
|
||||||
int format;
|
int format;
|
||||||
|
uint32_t codec_value;
|
||||||
uint32_t sound_id;
|
uint32_t sound_id;
|
||||||
uint32_t sound_flags;
|
uint32_t sound_flags;
|
||||||
uint32_t config_flags;
|
uint32_t config_flags;
|
||||||
@ -52,9 +53,9 @@ typedef struct {
|
|||||||
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, ktsr_meta_t* info);
|
static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, ktsr_meta_t* info);
|
||||||
static bool parse_ktsr(ktsr_header_t* ktsr, STREAMFILE* sf);
|
static bool parse_ktsr(ktsr_header_t* ktsr, STREAMFILE* sf);
|
||||||
static layered_layout_data* build_layered_atrac9(ktsr_header_t* ktsr, STREAMFILE *sf, uint32_t config_data);
|
static layered_layout_data* build_layered_atrac9(ktsr_header_t* ktsr, STREAMFILE *sf, uint32_t config_data);
|
||||||
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, uint32_t st_offset, ktsr_header_t* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext);
|
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, uint32_t st_offset, ktsr_header_t* ktsr, init_vgmstream_t init_vgmstream, const char* ext);
|
||||||
|
|
||||||
/* KTSR - Koei Tecmo sound resource container */
|
/* KTSR - Koei Tecmo sound resource container (KTSL2 sound lib) */
|
||||||
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
|
VGMSTREAM* init_vgmstream_ktsr(STREAMFILE* sf) {
|
||||||
|
|
||||||
/* checks */
|
/* checks */
|
||||||
@ -204,6 +205,7 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, ktsr_meta_t* info
|
|||||||
|
|
||||||
|
|
||||||
sf_b = setup_sf_body(sf, &ktsr, info);
|
sf_b = setup_sf_body(sf, &ktsr, info);
|
||||||
|
if (!sf_b) goto fail;
|
||||||
|
|
||||||
/* subfiles */
|
/* subfiles */
|
||||||
{
|
{
|
||||||
@ -215,7 +217,7 @@ static VGMSTREAM* init_vgmstream_ktsr_internal(STREAMFILE* sf, ktsr_meta_t* info
|
|||||||
ktsr.codec = RIFF_ATRAC9;
|
ktsr.codec = RIFF_ATRAC9;
|
||||||
}
|
}
|
||||||
|
|
||||||
VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf) = NULL;
|
init_vgmstream_t init_vgmstream = NULL;
|
||||||
const char* ext;
|
const char* ext;
|
||||||
switch(ktsr.codec) {
|
switch(ktsr.codec) {
|
||||||
case RIFF_ATRAC9: init_vgmstream = init_vgmstream_riff; ext = "at9"; break; // Nioh (PS4)
|
case RIFF_ATRAC9: init_vgmstream = init_vgmstream_riff; ext = "at9"; break; // Nioh (PS4)
|
||||||
@ -330,7 +332,7 @@ fail:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO improve, unify with other metas that do similar stuff
|
// TODO improve, unify with other metas that do similar stuff
|
||||||
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, uint32_t st_offset, ktsr_header_t* ktsr, VGMSTREAM* (*init_vgmstream)(STREAMFILE* sf), const char* ext) {
|
static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, uint32_t st_offset, ktsr_header_t* ktsr, init_vgmstream_t init_vgmstream, const char* ext) {
|
||||||
VGMSTREAM* sub_vgmstream = NULL;
|
VGMSTREAM* sub_vgmstream = NULL;
|
||||||
STREAMFILE* temp_sf = NULL;
|
STREAMFILE* temp_sf = NULL;
|
||||||
|
|
||||||
@ -340,7 +342,7 @@ static VGMSTREAM* init_vgmstream_ktsr_sub(STREAMFILE* sf_b, uint32_t st_offset,
|
|||||||
sub_vgmstream = init_vgmstream(temp_sf);
|
sub_vgmstream = init_vgmstream(temp_sf);
|
||||||
close_streamfile(temp_sf);
|
close_streamfile(temp_sf);
|
||||||
if (!sub_vgmstream) {
|
if (!sub_vgmstream) {
|
||||||
VGM_LOG("ktsr: can't open subfile at %x (size %x)\n", ktsr->stream_offsets[0], ktsr->stream_sizes[0]);
|
VGM_LOG("ktsr: can't open subfile %s at %x (size %x)\n", ext, ktsr->stream_offsets[0], ktsr->stream_sizes[0]);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,11 +416,13 @@ static int parse_codec(ktsr_header_t* ktsr) {
|
|||||||
/* platform + format to codec, simplified until more codec combos are found */
|
/* platform + format to codec, simplified until more codec combos are found */
|
||||||
switch(ktsr->platform) {
|
switch(ktsr->platform) {
|
||||||
case 0x01: /* PC */
|
case 0x01: /* PC */
|
||||||
case 0x05: /* PC/Steam [Fate/Samurai Remnant (PC)] */
|
case 0x05: /* PC/Steam, Android [Fate/Samurai Remnant (PC)] */
|
||||||
if (ktsr->format == 0x0000 && !ktsr->is_external)
|
if (ktsr->format == 0x0000 && !ktsr->is_external)
|
||||||
ktsr->codec = MSADPCM; // Warrior Orochi 4 (PC)
|
ktsr->codec = MSADPCM; // Warrior Orochi 4 (PC)
|
||||||
else if (ktsr->format == 0x0001)
|
else if (ktsr->format == 0x0001)
|
||||||
ktsr->codec = KA1A_INTERNAL; // Dynasty Warriors Origins (PC)
|
ktsr->codec = KA1A_INTERNAL; // Dynasty Warriors Origins (PC)
|
||||||
|
else if (ktsr->format == 0x0005 && ktsr->is_external && ktsr->codec_value == 0x0840)
|
||||||
|
ktsr->codec = KTAC; // Shin Hokuto Musou (Android
|
||||||
else if (ktsr->format == 0x0005 && ktsr->is_external)
|
else if (ktsr->format == 0x0005 && ktsr->is_external)
|
||||||
ktsr->codec = KOVS; // Atelier Ryza (PC)
|
ktsr->codec = KOVS; // Atelier Ryza (PC)
|
||||||
else if (ktsr->format == 0x1001 && ktsr->is_external)
|
else if (ktsr->format == 0x1001 && ktsr->is_external)
|
||||||
@ -481,7 +485,7 @@ static bool parse_ktsr_subfile(ktsr_header_t* ktsr, STREAMFILE* sf, uint32_t off
|
|||||||
* 14 external codec
|
* 14 external codec
|
||||||
* 18 sample rate
|
* 18 sample rate
|
||||||
* 1c num samples
|
* 1c num samples
|
||||||
* 20 null / 0x1000?
|
* 20 null or codec-related value (RIFF_AT9/KM9=0x100, KTAC=0x840)
|
||||||
* 24 loop start or -1 (loop end is num samples)
|
* 24 loop start or -1 (loop end is num samples)
|
||||||
* 28 channel layout (or null?)
|
* 28 channel layout (or null?)
|
||||||
* 2c null
|
* 2c null
|
||||||
@ -494,6 +498,7 @@ static bool parse_ktsr_subfile(ktsr_header_t* ktsr, STREAMFILE* sf, uint32_t off
|
|||||||
|
|
||||||
ktsr->channels = read_u32le(offset + 0x0c, sf);
|
ktsr->channels = read_u32le(offset + 0x0c, sf);
|
||||||
ktsr->format = read_u32le(offset + 0x14, sf);
|
ktsr->format = read_u32le(offset + 0x14, sf);
|
||||||
|
ktsr->codec_value = read_u32le(offset + 0x20, sf);
|
||||||
/* other fields will be read in the external stream */
|
/* other fields will be read in the external stream */
|
||||||
|
|
||||||
ktsr->channel_layout = read_u32le(offset + 0x28, sf);
|
ktsr->channel_layout = read_u32le(offset + 0x28, sf);
|
||||||
@ -507,7 +512,7 @@ static bool parse_ktsr_subfile(ktsr_header_t* ktsr, STREAMFILE* sf, uint32_t off
|
|||||||
ktsr->stream_sizes[0] = read_u32le(offset + 0x38, sf);
|
ktsr->stream_sizes[0] = read_u32le(offset + 0x38, sf);
|
||||||
}
|
}
|
||||||
ktsr->is_external = true;
|
ktsr->is_external = true;
|
||||||
|
VGM_LOG("k=%x\n", ktsr->codec_value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x41FDBD4E: /* internal [Attack on Titan: Wings of Freedom (Vita)] */
|
case 0x41FDBD4E: /* internal [Attack on Titan: Wings of Freedom (Vita)] */
|
||||||
@ -517,14 +522,14 @@ static bool parse_ktsr_subfile(ktsr_header_t* ktsr, STREAMFILE* sf, uint32_t off
|
|||||||
case 0x10250527: /* internal [Fire Emblem: Three Houses DLC (Switch)] */
|
case 0x10250527: /* internal [Fire Emblem: Three Houses DLC (Switch)] */
|
||||||
/* 08 subtype? (0x6029DBD2, 0xD20A92F90, 0xDC6FF709)
|
/* 08 subtype? (0x6029DBD2, 0xD20A92F90, 0xDC6FF709)
|
||||||
* 0c channels
|
* 0c channels
|
||||||
* 10 format? (00=platform's ADPCM? 01=ATRAC9?)
|
* 10 format
|
||||||
* 11 bps? (always 16)
|
* 11 null or sometimes 16
|
||||||
* 12 null
|
* 12 null
|
||||||
* 14 sample rate
|
* 14 sample rate
|
||||||
* 18 num samples
|
* 18 num samples
|
||||||
* 1c null or 0x100?
|
* 1c null or codec-related value?
|
||||||
* 20 loop start or -1 (loop end is num samples)
|
* 20 loop start or -1 (loop end is num samples)
|
||||||
* 24 null or channel layout (for 1 track in case of multi-track streams))
|
* 24 null or channel layout (for 1 track in case of multi-track streams)
|
||||||
* 28 header offset (within subfile)
|
* 28 header offset (within subfile)
|
||||||
* 2c header size [B, C]
|
* 2c header size [B, C]
|
||||||
* 30 offset to data start offset [A, C] or to data start+size [B]
|
* 30 offset to data start offset [A, C] or to data start+size [B]
|
||||||
@ -679,7 +684,7 @@ static bool parse_ktsr(ktsr_header_t* ktsr, STREAMFILE* sf) {
|
|||||||
* 04: type
|
* 04: type
|
||||||
* 08: version?
|
* 08: version?
|
||||||
* 0a: unknown (usually 00, 02/03 seen in Vita)
|
* 0a: unknown (usually 00, 02/03 seen in Vita)
|
||||||
* 0b: platform (01=PC, 03=Vita, 04=Switch)
|
* 0b: platform
|
||||||
* 0c: audio id? (seen in multiple files/games and used as Ogg stream IDs)
|
* 0c: audio id? (seen in multiple files/games and used as Ogg stream IDs)
|
||||||
* 10: null
|
* 10: null
|
||||||
* 14: null
|
* 14: null
|
||||||
|
@ -1024,4 +1024,6 @@ VGMSTREAM* init_vgmstream_xabp(STREAMFILE* sf);
|
|||||||
|
|
||||||
VGMSTREAM* init_vgmstream_i3ds(STREAMFILE* sf);
|
VGMSTREAM* init_vgmstream_i3ds(STREAMFILE* sf);
|
||||||
|
|
||||||
|
VGMSTREAM* init_vgmstream_skex(STREAMFILE* sf);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -11,6 +11,15 @@ VGMSTREAM* init_vgmstream_msf(STREAMFILE* sf) {
|
|||||||
|
|
||||||
|
|
||||||
/* checks */
|
/* checks */
|
||||||
|
if ((read_u32be(0x00,sf) & 0xffffff00) != get_id32be("MSF\0"))
|
||||||
|
return NULL;
|
||||||
|
// "MSF" + n.n version:
|
||||||
|
// - 0x01: Megazone 23: Aoi Garland (PS3)
|
||||||
|
// - 0x02: Switchball (PS3)
|
||||||
|
// - 0x30 ('0'): ?
|
||||||
|
// - 0x35 ('5'): SDKs
|
||||||
|
// - 0x43 ('C'): latest/most common
|
||||||
|
|
||||||
/* .msf: standard
|
/* .msf: standard
|
||||||
* .msa: Sonic & Sega All-Stars Racing (PS3)
|
* .msa: Sonic & Sega All-Stars Racing (PS3)
|
||||||
* .at3: Silent Hill HD Collection (PS3), Z/X Zekkai no Crusade (PS3)
|
* .at3: Silent Hill HD Collection (PS3), Z/X Zekkai no Crusade (PS3)
|
||||||
@ -18,12 +27,7 @@ VGMSTREAM* init_vgmstream_msf(STREAMFILE* sf) {
|
|||||||
* .str: Pac-Man and the Ghostly Adventures (PS3)
|
* .str: Pac-Man and the Ghostly Adventures (PS3)
|
||||||
* .snd: HamsterBall (PS3) */
|
* .snd: HamsterBall (PS3) */
|
||||||
if (!check_extensions(sf,"msf,msa,at3,mp3,str,snd"))
|
if (!check_extensions(sf,"msf,msa,at3,mp3,str,snd"))
|
||||||
goto fail;
|
return NULL;
|
||||||
|
|
||||||
/* check header "MSF" + version-char, usually:
|
|
||||||
* 0x01, 0x02, 0x30="0", 0x35="5", 0x43="C" (last/most common version) */
|
|
||||||
if ((read_u32be(0x00,sf) & 0xffffff00) != 0x4D534600) /* "MSF\0" */
|
|
||||||
goto fail;
|
|
||||||
|
|
||||||
start_offset = 0x40;
|
start_offset = 0x40;
|
||||||
|
|
||||||
@ -45,13 +49,11 @@ VGMSTREAM* init_vgmstream_msf(STREAMFILE* sf) {
|
|||||||
* 0x10 often goes with 0x01 but not always (Castlevania HoD); Malicious PS3 uses flag 0x2 instead */
|
* 0x10 often goes with 0x01 but not always (Castlevania HoD); Malicious PS3 uses flag 0x2 instead */
|
||||||
loop_flag = (flags != 0xffffffff) && ((flags & 0x01) || (flags & 0x02));
|
loop_flag = (flags != 0xffffffff) && ((flags & 0x01) || (flags & 0x02));
|
||||||
|
|
||||||
/* loop markers (marker N @ 0x18 + N*(4+4), but in practice only marker 0 is used) */
|
/* loop offset markers (marker N @ 0x18 + N*(4+4), but in practice only marker 0 is used) */
|
||||||
if (loop_flag) {
|
if (loop_flag) {
|
||||||
loop_start = read_u32be(0x18,sf);
|
loop_start = read_u32be(0x18,sf);
|
||||||
loop_end = read_u32be(0x1C,sf); /* loop duration */
|
loop_end = read_u32be(0x1C,sf); /* loop duration */
|
||||||
loop_end = loop_start + loop_end; /* usually equals data_size but not always */
|
loop_end = loop_start + loop_end; /* usually equals data_size but not always */
|
||||||
if (loop_end > data_size) /* not seen */
|
|
||||||
loop_end = data_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -71,12 +73,11 @@ VGMSTREAM* init_vgmstream_msf(STREAMFILE* sf) {
|
|||||||
vgmstream->layout_type = layout_interleave;
|
vgmstream->layout_type = layout_interleave;
|
||||||
vgmstream->interleave_block_size = 0x02;
|
vgmstream->interleave_block_size = 0x02;
|
||||||
|
|
||||||
vgmstream->num_samples = pcm_bytes_to_samples(data_size, channels, 16);
|
vgmstream->num_samples = pcm16_bytes_to_samples(data_size, channels);
|
||||||
if (loop_flag){
|
if (loop_flag){
|
||||||
vgmstream->loop_start_sample = pcm_bytes_to_samples(loop_start, channels, 16);
|
vgmstream->loop_start_sample = pcm16_bytes_to_samples(loop_start, channels);
|
||||||
vgmstream->loop_end_sample = pcm_bytes_to_samples(loop_end, channels, 16);
|
vgmstream->loop_end_sample = pcm16_bytes_to_samples(loop_end, channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,29 +103,35 @@ VGMSTREAM* init_vgmstream_msf(STREAMFILE* sf) {
|
|||||||
case 0x04: /* ATRAC3 low (66 kbps, frame size 96, Joint Stereo) [Silent Hill HD (PS3)] */
|
case 0x04: /* ATRAC3 low (66 kbps, frame size 96, Joint Stereo) [Silent Hill HD (PS3)] */
|
||||||
case 0x05: /* ATRAC3 mid (105 kbps, frame size 152) [Atelier Rorona (PS3)] */
|
case 0x05: /* ATRAC3 mid (105 kbps, frame size 152) [Atelier Rorona (PS3)] */
|
||||||
case 0x06: { /* ATRAC3 high (132 kbps, frame size 192) [Tekken Tag Tournament HD (PS3)] */
|
case 0x06: { /* ATRAC3 high (132 kbps, frame size 192) [Tekken Tag Tournament HD (PS3)] */
|
||||||
int block_align, encoder_delay;
|
|
||||||
|
|
||||||
/* MSF skip samples: from tests with MSEnc and real files (ex. TTT2 eddy.msf v43, v01 demos) seems like 1162 is consistent.
|
/* some MSFv1 voices [Digi World SP (PS3)] */
|
||||||
* Atelier Rorona bt_normal01 needs it to properly skip the beginning garbage but usually doesn't matter.
|
if (vgmstream->sample_rate == -1)
|
||||||
* (note that encoder may add a fade-in with looping/resampling enabled but should be skipped) */
|
vgmstream->sample_rate = 44100;
|
||||||
encoder_delay = 1024 + 69*2;
|
|
||||||
block_align = (codec==4 ? 0x60 : (codec==5 ? 0x98 : 0xC0)) * vgmstream->channels;
|
int block_align = (codec==4 ? 0x60 : (codec==5 ? 0x98 : 0xC0)) * vgmstream->channels;
|
||||||
vgmstream->num_samples = atrac3_bytes_to_samples(data_size, block_align) - encoder_delay;
|
vgmstream->num_samples = atrac3_bytes_to_samples(data_size, block_align);
|
||||||
if (vgmstream->sample_rate == -1) /* some MSFv1 (Digi World SP) */
|
vgmstream->loop_start_sample = atrac3_bytes_to_samples(loop_start, block_align);
|
||||||
vgmstream->sample_rate = 44100; /* voice tracks seems to use 44khz, not sure about other tracks */
|
vgmstream->loop_end_sample = atrac3_bytes_to_samples(loop_end, block_align);
|
||||||
|
|
||||||
|
/* MSF skip samples: from MSEnc tests and real files (ex. TTT2 eddy.msf v43, v01 demos) seems like 1162 is consistent.
|
||||||
|
* Often doesn't matter but sometimes there is audible garbage [Atelier Rorona (PS3)-bt_normal01]
|
||||||
|
* However full loops use offset 0 to file end, so maybe decoder doesn't actually skip samples (like in MPEG).
|
||||||
|
* MSEnc accepts samples and will adjust loops somewhat to closest frame but is not accurate enough.
|
||||||
|
* Comparing vs other platforms loop start+end need to be in sync [Mamoru-kun wa Norowarette Shimatta! (PS3)]
|
||||||
|
* For now only remove samples if wouldn't mess up loops. */
|
||||||
|
int encoder_delay = 1024 + 69*2;
|
||||||
|
if (vgmstream->loop_flag && encoder_delay > vgmstream->loop_start_sample) {
|
||||||
|
encoder_delay = 0;
|
||||||
|
}
|
||||||
|
vgmstream->num_samples -= encoder_delay;
|
||||||
|
vgmstream->loop_start_sample -= encoder_delay;
|
||||||
|
vgmstream->loop_end_sample -= encoder_delay;
|
||||||
|
|
||||||
vgmstream->codec_data = init_ffmpeg_atrac3_raw(sf, start_offset,data_size, vgmstream->num_samples,vgmstream->channels, vgmstream->sample_rate, block_align, encoder_delay);
|
vgmstream->codec_data = init_ffmpeg_atrac3_raw(sf, start_offset,data_size, vgmstream->num_samples,vgmstream->channels, vgmstream->sample_rate, block_align, encoder_delay);
|
||||||
if (!vgmstream->codec_data) goto fail;
|
if (!vgmstream->codec_data) goto fail;
|
||||||
vgmstream->coding_type = coding_FFmpeg;
|
vgmstream->coding_type = coding_FFmpeg;
|
||||||
vgmstream->layout_type = layout_none;
|
vgmstream->layout_type = layout_none;
|
||||||
|
|
||||||
/* MSF loop/sample values are offsets so trickier to adjust but this seems correct */
|
|
||||||
if (loop_flag) {
|
|
||||||
/* set offset samples (offset 0 jumps to sample 0 > pre-applied delay, and offset end loops after sample end > adjusted delay) */
|
|
||||||
vgmstream->loop_start_sample = atrac3_bytes_to_samples(loop_start, block_align); //- encoder_delay
|
|
||||||
vgmstream->loop_end_sample = atrac3_bytes_to_samples(loop_end, block_align) - encoder_delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -139,7 +146,7 @@ VGMSTREAM* init_vgmstream_msf(STREAMFILE* sf) {
|
|||||||
vgmstream->num_samples = mpeg_get_samples_clean(sf, start_offset, data_size, &loop_start, &loop_end, is_vbr);
|
vgmstream->num_samples = mpeg_get_samples_clean(sf, start_offset, data_size, &loop_start, &loop_end, is_vbr);
|
||||||
vgmstream->loop_start_sample = loop_start;
|
vgmstream->loop_start_sample = loop_start;
|
||||||
vgmstream->loop_end_sample = loop_end;
|
vgmstream->loop_end_sample = loop_end;
|
||||||
/* MPEG here seems stripped from ID3/Xing headers, loops are frame offsets */
|
/* MSEnc seems to strip ID3/Xing headers, loops are frame offsets */
|
||||||
|
|
||||||
/* encoder delay varies between 1152 (1f), 528, 576, etc; probably not actually skipped */
|
/* encoder delay varies between 1152 (1f), 528, 576, etc; probably not actually skipped */
|
||||||
break;
|
break;
|
||||||
|
@ -717,13 +717,14 @@ VGMSTREAM* init_vgmstream_idsp_namco(STREAMFILE* sf) {
|
|||||||
dspm.header_spacing = read_u32be(0x24,sf);
|
dspm.header_spacing = read_u32be(0x24,sf);
|
||||||
dspm.start_offset = read_u32be(0x28,sf);
|
dspm.start_offset = read_u32be(0x28,sf);
|
||||||
|
|
||||||
/* Soul Calibur: Broken destiny (PSP), Taiko no Tatsujin: Atsumete Tomodachi Daisakusen (WiiU) */
|
/* SoulCalibur Legends (Wii), Taiko no Tatsujin: Atsumete Tomodachi Daisakusen (WiiU) */
|
||||||
if (dspm.interleave == 0) {
|
if (dspm.interleave == 0) {
|
||||||
/* half interleave (happens sometimes), use channel size */
|
/* half interleave (uncommon), use channel size */
|
||||||
dspm.interleave = read_u32be(0x2c,sf);
|
dspm.interleave = read_u32be(0x2c,sf);
|
||||||
/* Rarely 2nd channel stars with a padding frame then real 2nd channel with initial_ps. Must be some
|
/* Rarely 2nd channel stars with a padding frame then real 2nd channel with initial_ps. Must be some NUS2 bug
|
||||||
* NUS2 bug when importing DSP data as only happens for one subsong and offsets/sizes are fine [We Ski (Wii)] */
|
* when importing DSP data as only happens for some subsongs and offsets/sizes are fine [We Ski (Wii), Go Vacation (Wii)] */
|
||||||
dspm.ignore_initial_ps = true;
|
dspm.ignore_initial_ps = true;
|
||||||
|
dspm.ignore_loop_ps = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// rare but valid IDSP [Super Smash Bros. Ultimate (Switch)-vc_kirby.nus3audio]
|
// rare but valid IDSP [Super Smash Bros. Ultimate (Switch)-vc_kirby.nus3audio]
|
||||||
|
@ -186,7 +186,7 @@ static int _init_vgmstream_ogg_vorbis_tests(STREAMFILE* sf, ogg_vorbis_io_config
|
|||||||
|
|
||||||
cfg->start = 0x20;
|
cfg->start = 0x20;
|
||||||
|
|
||||||
/* .kvs: Atelier Sophie (PC)
|
/* .kvs: Atelier Sophie (PC), debug strings
|
||||||
* .kovs: header id only? */
|
* .kovs: header id only? */
|
||||||
if (!check_extensions(sf,"kvs,kovs"))
|
if (!check_extensions(sf,"kvs,kovs"))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
272
src/meta/skex.c
Normal file
272
src/meta/skex.c
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
#include "meta.h"
|
||||||
|
#include "../coding/coding.h"
|
||||||
|
#include "../util.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* SKEX - from SCE America second party devs [Syphon Filter: Dark Mirror (PS2/PSP), MLB 2004 (PS2), MLB 15 (Vita)] */
|
||||||
|
VGMSTREAM* init_vgmstream_skex(STREAMFILE* sf) {
|
||||||
|
VGMSTREAM* vgmstream = NULL;
|
||||||
|
STREAMFILE* temp_sf = NULL;
|
||||||
|
STREAMFILE* sf_h = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
/* checks */
|
||||||
|
if (!is_id32be(0x00,sf, "SKEX"))
|
||||||
|
return NULL;
|
||||||
|
if (!check_extensions(sf,"skx"))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// bank-like format with helper files typically found inside memory/bigfiles (.SWD, .DAT, etc)
|
||||||
|
// - .skx: external streams (pack of full formats)
|
||||||
|
// - .tbl: main stream info
|
||||||
|
// - .ctl: cues?
|
||||||
|
// - .mrk: text script related to .tbl
|
||||||
|
// usually .tbl is the header and .skx its body, but rarely may be combined so use .skx as a base
|
||||||
|
|
||||||
|
uint16_t version = read_u16le(0x04, sf); // in hex NN.NN form
|
||||||
|
// 06: low number, seems related to file (id?)
|
||||||
|
// 08: null
|
||||||
|
// 0c: null
|
||||||
|
uint32_t head_offset = read_u32le(0x10, sf);
|
||||||
|
uint32_t head_size = read_u32le(0x14, sf);
|
||||||
|
int entries = read_u16le(0x18, sf); // even with no head_offset/size
|
||||||
|
|
||||||
|
// micro optimization (empty banks do exist)
|
||||||
|
if (get_streamfile_size(sf) <= 0x100) {
|
||||||
|
vgm_logi("SKEX: bank has no subsongs\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup header
|
||||||
|
if (head_offset && head_size) {
|
||||||
|
// rare [MLB 2004 (PS2), NBA 06 (PS2)]
|
||||||
|
sf_h = sf;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// note that may .skx may be uppercase and companion file lowercase (meaning Linux won't open this)
|
||||||
|
sf_h = open_streamfile_by_ext(sf, "tbl");
|
||||||
|
if (!sf_h) {
|
||||||
|
vgm_logi("SKEX: companion file .tbl not found (put together)\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t subfile_offset = 0, subfile_size = 0, prev_offset = 0, subfile_type = 0;
|
||||||
|
|
||||||
|
int total_subsongs = 0;
|
||||||
|
int target_subsong = sf->stream_index;
|
||||||
|
if (target_subsong == 0) target_subsong = 1;
|
||||||
|
|
||||||
|
// Entries have many repeats so calculate totals.
|
||||||
|
// After last entry there is a fake entry with .skx size (meaning next_offset is always valid).
|
||||||
|
// With flags = 0x1000, after all is another table with increasing low number per entry
|
||||||
|
switch(version) {
|
||||||
|
case 0x1070: { // MLB 2003 (PS2), MLB 2004 (PS2)
|
||||||
|
uint32_t offset = head_offset;
|
||||||
|
|
||||||
|
// entries go after files
|
||||||
|
for (int i = 0; i < entries; i++) {
|
||||||
|
uint32_t curr_offset = read_u32le(offset + 0x00, sf_h);
|
||||||
|
uint32_t curr_type = read_u32le(offset + 0x04, sf_h);
|
||||||
|
// 08: null?
|
||||||
|
|
||||||
|
offset += 0x0c;
|
||||||
|
|
||||||
|
switch(curr_type) {
|
||||||
|
case 0x05: // .vag (mono)
|
||||||
|
case 0x0c: // .vag (stereo)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
vgm_logi("SKEX: unknown format %x (report)\n", curr_type);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev_offset == curr_offset)
|
||||||
|
continue;
|
||||||
|
prev_offset = curr_offset;
|
||||||
|
|
||||||
|
total_subsongs++;
|
||||||
|
|
||||||
|
if (target_subsong == total_subsongs && !subfile_offset) {
|
||||||
|
uint32_t next_offset = read_u32le(offset, sf_h);
|
||||||
|
subfile_offset = curr_offset;
|
||||||
|
subfile_size = next_offset - curr_offset;
|
||||||
|
subfile_type = curr_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x2040: // MLB 2005 (PS2)
|
||||||
|
case 0x2070: // MLB 2006 (PS2), NBA 06 (PS2), MLB (PSP)
|
||||||
|
case 0x3000: { // Syphon Filter: Dark Mirror (PS2/PSP), Syphon Filter: Logan's Shadow (PSP)
|
||||||
|
uint32_t offset = head_offset;
|
||||||
|
|
||||||
|
// 00: header id
|
||||||
|
// 04: version
|
||||||
|
// 06: low number, seems related to file
|
||||||
|
// 08: entries (same as .skx)
|
||||||
|
// 0a: flags
|
||||||
|
// 0c: null?
|
||||||
|
// 10: entries again?
|
||||||
|
if (!is_id32be(offset + 0x00,sf_h, "STBL")) {
|
||||||
|
VGM_LOG("SKEX: incorrect .tbl\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
offset += 0x50;
|
||||||
|
|
||||||
|
for (int i = 0; i < entries; i++) {
|
||||||
|
uint32_t curr_offset = read_u32le(offset + 0x00, sf_h);
|
||||||
|
// 04: 0 or 1 (doesn't seem to be related to loops, companion files or such)
|
||||||
|
// 05: null?
|
||||||
|
// 06: null?
|
||||||
|
uint8_t curr_type = read_u8 (offset + 0x07, sf_h);
|
||||||
|
|
||||||
|
offset += 0x08;
|
||||||
|
|
||||||
|
switch(curr_type) {
|
||||||
|
case 0x00: // dummy/config?
|
||||||
|
case 0x01: // dummy/config?
|
||||||
|
case 0x0e: // "Names" in .skx (empty?)
|
||||||
|
continue;
|
||||||
|
case 0x05: // .vag (mono)
|
||||||
|
case 0x09: // .at3
|
||||||
|
case 0x0b: // .vpk
|
||||||
|
case 0x0c: // .vag (stereo)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
vgm_logi("SKEX: unknown format %x (report)\n", curr_type);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev_offset == curr_offset)
|
||||||
|
continue;
|
||||||
|
prev_offset = curr_offset;
|
||||||
|
|
||||||
|
total_subsongs++;
|
||||||
|
|
||||||
|
if (target_subsong == total_subsongs && !subfile_offset) {
|
||||||
|
uint32_t next_offset = read_u32le(offset, sf_h);
|
||||||
|
subfile_offset = curr_offset;
|
||||||
|
subfile_size = next_offset - curr_offset;
|
||||||
|
subfile_type = curr_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x5100: { // MLB 14 (Vita), MLB 15 (Vita)
|
||||||
|
uint32_t offset = head_offset;
|
||||||
|
uint16_t multiplier, align = 0;
|
||||||
|
|
||||||
|
// 00: header id
|
||||||
|
// 04: version
|
||||||
|
// 06: low number, seems related to file
|
||||||
|
// 08: entries (same as .skx)
|
||||||
|
// 0a: null?
|
||||||
|
// 0c: file size (without padding)
|
||||||
|
// 10: offset to 2nd table
|
||||||
|
// 14: null?
|
||||||
|
// 18: offset multiplier (0x800/0x400/0x01)
|
||||||
|
// 1a: flags? (rarely 0x08)
|
||||||
|
// 1c: some entries?
|
||||||
|
// 20: null
|
||||||
|
// 24: entries again?
|
||||||
|
if (!is_id32be(offset + 0x00,sf_h, "STBL")) {
|
||||||
|
VGM_LOG("SKEX: incorrect .tbl\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
multiplier = read_u16le(offset + 0x18, sf_h);
|
||||||
|
|
||||||
|
offset += 0x64;
|
||||||
|
for (int i = 0; i < entries; i++) {
|
||||||
|
uint32_t curr_offset = read_u32le(offset + 0x00, sf_h) * multiplier;
|
||||||
|
// 04: null?
|
||||||
|
uint8_t curr_type = read_u8 (offset + 0x05, sf_h);
|
||||||
|
|
||||||
|
offset += 0x06;
|
||||||
|
|
||||||
|
switch(curr_type) {
|
||||||
|
case 0x00: // dummy?
|
||||||
|
case 0x01: // some config?
|
||||||
|
case 0x0e: // "<HR_EMITTER>"
|
||||||
|
case 0x0f: // MIDX (maybe some instrument/midi definition, but data doesn't look midi-like)
|
||||||
|
continue;
|
||||||
|
case 0x02: // .at9
|
||||||
|
case 0x42: // .at9 (no diffs?)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
vgm_logi("SKEX: unknown format %x (report)\n", curr_type);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// oddly misaligned by 1, no apparent flags [MLB 15 (Vita)-FEPXP.SKX]
|
||||||
|
if (curr_offset == 0x00 && multiplier == 0x800 && !align) {
|
||||||
|
align = multiplier;
|
||||||
|
}
|
||||||
|
curr_offset += align;
|
||||||
|
|
||||||
|
if (prev_offset == curr_offset)
|
||||||
|
continue;
|
||||||
|
prev_offset = curr_offset;
|
||||||
|
|
||||||
|
total_subsongs++;
|
||||||
|
|
||||||
|
if (target_subsong == total_subsongs && !subfile_offset) {
|
||||||
|
uint32_t next_offset = read_u32le(offset, sf_h) * multiplier + align;
|
||||||
|
subfile_offset = curr_offset;
|
||||||
|
subfile_size = next_offset - curr_offset;
|
||||||
|
subfile_type = curr_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_subsongs == 0) {
|
||||||
|
vgm_logi("SKEX: bank has no subsongs\n"); //sometimes
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!check_subsongs(&target_subsong, total_subsongs))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
;VGM_LOG("subfile=%x, %x, %x, %i\n", subfile_offset, subfile_size, subfile_type, total_subsongs);
|
||||||
|
{
|
||||||
|
init_vgmstream_t init_vgmstream = NULL;
|
||||||
|
const char* ext;
|
||||||
|
switch(subfile_type) {
|
||||||
|
case 0x05:
|
||||||
|
case 0x0c: init_vgmstream = init_vgmstream_vag; ext = "vag"; break;
|
||||||
|
case 0x09: init_vgmstream = init_vgmstream_riff; ext = "at3"; break;
|
||||||
|
case 0x0b: init_vgmstream = init_vgmstream_vpk; ext = "vpk"; break;
|
||||||
|
case 0x02:
|
||||||
|
case 0x42: init_vgmstream = init_vgmstream_riff; ext = "at9"; break;
|
||||||
|
default: goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subfile_type == 0x09 || subfile_type == 0x02) { // use RIFF's
|
||||||
|
subfile_size = read_u32le(subfile_offset + 0x04, sf) + 0x08;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, ext);
|
||||||
|
if (!temp_sf) goto fail;
|
||||||
|
|
||||||
|
vgmstream = init_vgmstream(temp_sf);
|
||||||
|
if (!vgmstream) goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
vgmstream->num_streams = total_subsongs;
|
||||||
|
|
||||||
|
if (sf_h != sf) close_streamfile(sf_h);
|
||||||
|
close_streamfile(temp_sf);
|
||||||
|
return vgmstream;
|
||||||
|
fail:
|
||||||
|
if (sf_h != sf) close_streamfile(sf_h);
|
||||||
|
close_streamfile(temp_sf);
|
||||||
|
close_vgmstream(vgmstream);
|
||||||
|
return NULL;
|
||||||
|
}
|
@ -307,6 +307,15 @@ VGMSTREAM* init_vgmstream_vag(STREAMFILE* sf) {
|
|||||||
channel_size -= 0x40;
|
channel_size -= 0x40;
|
||||||
loop_flag = ps_find_loop_offsets(sf, start_offset, channel_size, channels, interleave, &loop_start_sample, &loop_end_sample);
|
loop_flag = ps_find_loop_offsets(sf, start_offset, channel_size, channels, interleave, &loop_start_sample, &loop_end_sample);
|
||||||
}
|
}
|
||||||
|
else if (version == 0x00000020 && is_id64be(0x20, sf, "KAudioDL") && ( (channel_size + 0x30) * 2 == file_size
|
||||||
|
|| align_size(channel_size + 0x30, 0x800) * 2 == file_size || align_size(channel_size + 0x30, 0x400) * 2 == file_size) ) {
|
||||||
|
/* .SKX stereo vag (name is always KAudioDLL and streams are padded unlike memory audio) [NBA 06 (PS2)] */
|
||||||
|
start_offset = 0x30;
|
||||||
|
interleave = file_size / 2;
|
||||||
|
channels = 2; // mono KAudioDLL streams also exist
|
||||||
|
|
||||||
|
loop_flag = ps_find_loop_offsets(sf, start_offset, channel_size, channels, interleave, &loop_start_sample, &loop_end_sample);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
/* standard PS1/PS2/PS3 .vag [Ecco the Dolphin (PS2), Legasista (PS3)] */
|
/* standard PS1/PS2/PS3 .vag [Ecco the Dolphin (PS2), Legasista (PS3)] */
|
||||||
start_offset = 0x30;
|
start_offset = 0x30;
|
||||||
|
@ -47,6 +47,9 @@ uint32_t clamp_u32(uint32_t v, uint32_t min, uint32_t max);
|
|||||||
|
|
||||||
int round10(int val);
|
int round10(int val);
|
||||||
|
|
||||||
|
#define align_size align_size_to_block
|
||||||
|
|
||||||
|
// returns size with padding, ex. value=0x560, block=0x100 > 0x600
|
||||||
size_t align_size_to_block(size_t value, size_t block_align);
|
size_t align_size_to_block(size_t value, size_t block_align);
|
||||||
|
|
||||||
/* return a file's extension (a pointer to the first character of the
|
/* return a file's extension (a pointer to the first character of the
|
||||||
|
@ -516,6 +516,7 @@ init_vgmstream_t init_vgmstream_functions[] = {
|
|||||||
init_vgmstream_xabp,
|
init_vgmstream_xabp,
|
||||||
init_vgmstream_i3ds,
|
init_vgmstream_i3ds,
|
||||||
init_vgmstream_sdbs,
|
init_vgmstream_sdbs,
|
||||||
|
init_vgmstream_skex,
|
||||||
|
|
||||||
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
|
/* lower priority metas (no clean header identity, somewhat ambiguous, or need extension/companion file to identify) */
|
||||||
init_vgmstream_agsc,
|
init_vgmstream_agsc,
|
||||||
|
@ -63,7 +63,7 @@ typedef enum {
|
|||||||
coding_IMA_int, /* IMA ADPCM (mono/interleave, low nibble first) */
|
coding_IMA_int, /* IMA ADPCM (mono/interleave, low nibble first) */
|
||||||
coding_DVI_IMA, /* DVI IMA ADPCM (stereo or mono, high nibble first) */
|
coding_DVI_IMA, /* DVI IMA ADPCM (stereo or mono, high nibble first) */
|
||||||
coding_DVI_IMA_int, /* DVI IMA ADPCM (mono/interleave, high nibble first) */
|
coding_DVI_IMA_int, /* DVI IMA ADPCM (mono/interleave, high nibble first) */
|
||||||
coding_NW_IMA,
|
coding_CAMELOT_IMA,
|
||||||
coding_SNDS_IMA, /* Heavy Iron Studios .snds IMA ADPCM */
|
coding_SNDS_IMA, /* Heavy Iron Studios .snds IMA ADPCM */
|
||||||
coding_QD_IMA,
|
coding_QD_IMA,
|
||||||
coding_WV6_IMA, /* Gorilla Systems WV6 4-bit IMA ADPCM */
|
coding_WV6_IMA, /* Gorilla Systems WV6 4-bit IMA ADPCM */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user